Document Upload System
Viax requires drivers to upload photos of official documents for verification. All uploads are secure, organized by driver, and reviewed by our admin team.
Documents are stored securely on the server with automatic cleanup of outdated files. Only the latest version is kept active.
Required Documents
Driver's License Front and back photos of your valid driver’s license
SOAT Insurance Mandatory vehicle insurance certificate photo
Tecnomecánica Technical inspection certificate photo
Vehicle Ownership Tarjeta de Propiedad (ownership card) photo
Upload Process
Step 1: Document Selection
The app provides an easy interface to select photos:
Widget _buildPhotoUpload ({
required String label,
required String ? photoPath,
required VoidCallback onTap,
}) {
final hasPhoto = photoPath != null ;
return GestureDetector (
onTap : onTap,
child : Container (
padding : EdgeInsets . all ( 16 ),
decoration : BoxDecoration (
color : hasPhoto ? Colors .green. withOpacity ( 0.1 ) : Colors .grey[ 100 ],
borderRadius : BorderRadius . circular ( 12 ),
border : Border . all (
color : hasPhoto ? Colors .green : Colors .grey[ 300 ],
width : 2 ,
),
),
child : Row (
children : [
Icon (
hasPhoto ? Icons .check_circle : Icons .camera_alt,
color : hasPhoto ? Colors .green : Colors .grey[ 600 ],
),
SizedBox (width : 12 ),
Expanded (
child : Text (
label,
style : TextStyle (
fontSize : 16 ,
fontWeight : hasPhoto ? FontWeight .bold : FontWeight .normal,
),
),
),
if (hasPhoto)
Text ( 'Selected ✓' , style : TextStyle (color : Colors .green)),
],
),
),
);
}
Step 2: Image Selection Method
Choose between camera or gallery:
Take a photo directly with your phone’s camera Future < void > _pickImageFromCamera ( String documentType) async {
final ImagePicker picker = ImagePicker ();
final XFile ? image = await picker. pickImage (
source : ImageSource .camera,
maxWidth : 1920 ,
maxHeight : 1920 ,
imageQuality : 85 ,
);
if (image != null ) {
setState (() {
switch (documentType) {
case 'soat' :
_soatFotoPath = image.path;
break ;
case 'tecnomecanica' :
_tecnomecanicaFotoPath = image.path;
break ;
case 'tarjeta_propiedad' :
_tarjetaPropiedadFotoPath = image.path;
break ;
}
});
}
}
Select an existing photo from your gallery Future < void > _pickImageFromGallery ( String documentType) async {
final ImagePicker picker = ImagePicker ();
final XFile ? image = await picker. pickImage (
source : ImageSource .gallery,
maxWidth : 1920 ,
maxHeight : 1920 ,
imageQuality : 85 ,
);
if (image != null ) {
// Update state with selected image path
_updateDocumentPath (documentType, image.path);
}
}
Image Quality Requirements:
Maximum size: 5MB per photo
Recommended resolution: 1920x1920 pixels
Automatic compression to 85% quality
Supported formats: JPG, PNG, WEBP, PDF
Step 3: Document Upload Service
The DocumentUploadService handles secure uploads:
class DocumentUploadService {
static const String baseUrl = 'http://76.13.114.194' ;
static const String uploadEndpoint = ' $ baseUrl /conductor/upload_documents.php' ;
/// Upload a single document
static Future < String ?> uploadDocument ({
required int conductorId,
required String tipoDocumento,
required String imagePath,
}) async {
try {
var request = http. MultipartRequest ( 'POST' , Uri . parse (uploadEndpoint));
// Add form fields
request.fields[ 'conductor_id' ] = conductorId. toString ();
request.fields[ 'tipo_documento' ] = tipoDocumento;
// Add file
var file = await http. MultipartFile . fromPath (
'documento' ,
imagePath,
contentType : MediaType ( 'image' , 'jpeg' ),
);
request.files. add (file);
// Send request
var response = await request. send ();
var responseData = await response.stream. bytesToString ();
var jsonResponse = json. decode (responseData);
if (jsonResponse[ 'success' ] == true ) {
return jsonResponse[ 'data' ][ 'url' ];
} else {
throw Exception (jsonResponse[ 'message' ] ?? 'Upload failed' );
}
} catch (e) {
print ( 'Upload error: $ e ' );
return null ;
}
}
/// Upload multiple documents at once
static Future < Map < String , String ?>> uploadMultipleDocuments ({
required int conductorId,
required Map < String , String > documents,
}) async {
Map < String , String ?> results = {};
for ( var entry in documents.entries) {
final url = await uploadDocument (
conductorId : conductorId,
tipoDocumento : entry.key,
imagePath : entry.value,
);
results[entry.key] = url;
}
return results;
}
}
Document Types & Validation
Driver’s License
Driver’s license with front and back photos
// Upload license photo
final licenseUrl = await DocumentUploadService . uploadDocument (
conductorId : conductorId,
tipoDocumento : 'licencia' ,
imagePath : licenseFotoPath,
);
// Update license in profile
final license = DriverLicense (
numero : licenseNumber,
categoria : LicenseCategory .c1,
fechaExpedicion : issueDate,
fechaVencimiento : expiryDate,
foto : licenseUrl,
isVerified : false , // Will be verified by admin
);
Validation checks:
✅ License number is not empty
✅ Category is C1, C2, or C3 (public service)
✅ Expiry date is in the future
✅ Not expiring within 30 days
SOAT (Vehicle Insurance)
Mandatory vehicle insurance certificate
class VehicleModel {
final String ? soatNumero;
final DateTime ? soatVencimiento;
final String ? fotoSoat;
bool get isSoatValid {
if (soatVencimiento == null ) return false ;
return soatVencimiento ! . isAfter ( DateTime . now ());
}
bool get isSoatExpiringSoon {
if (soatVencimiento == null ) return false ;
final daysUntilExpiry = soatVencimiento ! . difference ( DateTime . now ()).inDays;
return daysUntilExpiry <= 30 && daysUntilExpiry > 0 ;
}
}
Validation checks:
✅ SOAT number provided
✅ Expiry date is valid and in the future
✅ Photo uploaded and visible
Tecnomecánica (Technical Inspection)
Vehicle technical inspection certificate
final String ? tecnomecanicaNumero;
final DateTime ? tecnomecanicaVencimiento;
final String ? fotoTecnomecanica;
bool get isTecnomecanicaValid {
return tecnomecanicaNumero != null &&
tecnomecanicaNumero ! .isNotEmpty &&
tecnomecanicaVencimiento != null &&
tecnomecanicaVencimiento ! . isAfter ( DateTime . now ());
}
Tarjeta de Propiedad (Ownership Card)
Vehicle ownership registration card
final String ? tarjetaPropiedadNumero;
final String ? fotoTarjetaPropiedad;
bool get isOwnershipComplete {
return tarjetaPropiedadNumero != null &&
tarjetaPropiedadNumero ! .isNotEmpty &&
fotoTarjetaPropiedad != null &&
fotoTarjetaPropiedad ! .isNotEmpty;
}
Upload Workflow Example
Complete example of uploading vehicle documents:
Collect Document Paths
User selects all required photos String ? _soatFotoPath;
String ? _tecnomecanicaFotoPath;
String ? _tarjetaPropiedadFotoPath;
Upload Documents
Upload all documents before saving vehicle final uploadResults = await provider. uploadVehicleDocuments (
conductorId : conductorId,
soatFotoPath : _soatFotoPath,
tecnomecanicaFotoPath : _tecnomecanicaFotoPath,
tarjetaPropiedadFotoPath : _tarjetaPropiedadFotoPath,
);
if (uploadResults. containsValue ( null )) {
// Some uploads failed
ScaffoldMessenger . of (context). showSnackBar (
SnackBar (content : Text ( 'Some documents failed to upload' ))
);
return ;
}
Save Vehicle with URLs
Create vehicle with uploaded document URLs final vehicle = VehicleModel (
placa : plateNumber,
tipo : VehicleType .auto,
marca : brand,
modelo : model,
anio : year,
color : color,
soatNumero : soatNumber,
soatVencimiento : soatExpiry,
fotoSoat : uploadResults[ 'soat' ],
tecnomecanicaNumero : techNumber,
tecnomecanicaVencimiento : techExpiry,
fotoTecnomecanica : uploadResults[ 'tecnomecanica' ],
tarjetaPropiedadNumero : ownershipNumber,
fotoTarjetaPropiedad : uploadResults[ 'tarjeta_propiedad' ],
);
await provider. updateVehicle (
conductorId : conductorId,
vehicle : vehicle,
);
Show Success
Display confirmation message if (success) {
showDialog (
context : context,
builder : (context) => ApprovalSuccessDialog (
message : 'Vehicle registered successfully!'
),
);
}
Server-Side Processing
File Storage Structure
Documents are organized on the server:
viax/backend/uploads/documentos/
├── conductor_1/
│ ├── soat_1730000000_a1b2c3d4.jpg
│ ├── tecnomecanica_1730000000_e5f6g7h8.jpg
│ └── tarjeta_propiedad_1730000000_i9j0k1l2.jpg
├── conductor_7/
│ ├── licencia_1730000000_m3n4o5p6.jpg
│ └── soat_1730000000_q7r8s9t0.jpg
└── conductor_15/
└── ...
Upload Endpoint Response
{
"success" : true ,
"message" : "Document uploaded successfully" ,
"data" : {
"tipo_documento" : "soat" ,
"url" : "uploads/documentos/conductor_7/soat_1730000000_a1b2c3d4.jpg" ,
"conductor_id" : 7 ,
"fecha_subida" : "2025-10-25 15:30:00"
}
}
Security & Privacy
Server performs strict validation:
File type verification (MIME type check)
Size limit enforcement (max 5MB)
Only allowed formats: JPG, PNG, WEBP, PDF
Conductor existence verification
Files stored outside web root when possible
Unique filenames prevent overwrites
.htaccess protection against PHP execution
Automatic cleanup of old documents
Only authenticated conductors can upload
Conductors can only access their own documents
Admin approval required before activation
Document History
The system maintains a history of all uploaded documents:
CREATE TABLE documentos_conductor_historial (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY ,
conductor_id BIGINT UNSIGNED NOT NULL ,
tipo_documento VARCHAR ( 50 ) NOT NULL ,
url_documento VARCHAR ( 500 ) NOT NULL ,
activo TINYINT ( 1 ) DEFAULT 1 ,
fecha_subida TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
reemplazado_en TIMESTAMP NULL ,
FOREIGN KEY (conductor_id) REFERENCES usuarios(id)
);
When a new document is uploaded, the old one is marked as activo = 0
The reemplazado_en timestamp is set
Old files are deleted from the server
Troubleshooting
Possible causes:
File too large (over 5MB)
Invalid file format
Network connection issue
Server permissions problem
Solution:
Reduce image quality/size
Use JPG format
Check internet connection
Try again later
Common reasons:
Photo is blurry or unreadable
Document is expired
Information doesn’t match profile
Wrong document uploaded
Solution:
Take a clear, well-lit photo
Ensure all text is readable
Verify dates are valid
Upload the correct document
Best Practices:
Take photos in good lighting
Ensure all corners of the document are visible
Avoid shadows and glare
Use a flat surface as background
Verify all text is readable before uploading
Next Steps
Vehicle Management Complete vehicle registration and details
Profile Management Update your driver profile settings