This commit is contained in:
Juan Felipe Zapata Moreno 2025-10-28 16:50:40 -06:00
parent 1d69c5ee7c
commit 3a25257dc5
7 changed files with 204 additions and 108 deletions

View File

@ -113,10 +113,7 @@ public function vehicleInscription(VehicleStoreRequest $request)
]); ]);
// Asignar Tag al vehículo // Asignar Tag al vehículo
$tag->update([ $tag->markAsAssigned($vehicle->id, $folio);
'vehicle_id' => $vehicle->id,
'folio' => $folio,
]);
// Crear registro // Crear registro
$record = Record::create([ $record = Record::create([
@ -132,12 +129,15 @@ public function vehicleInscription(VehicleStoreRequest $request)
$fileNames = $request->input('names', []); $fileNames = $request->input('names', []);
foreach ($files as $index => $file) { foreach ($files as $index => $file) {
$fileName = uniqid() . '_' . time() . '_' . $file->getClientOriginalName(); $customName = $fileNames[$index] ?? "archivo_" . ($index + 1);
$customName = str_replace(' ', '_', $customName);
$extension = $file->getClientOriginalExtension();
$fileName = $customName . '_' . time() . '.' . $extension;
$path = $file->storeAs('/', $fileName, 'records'); $path = $file->storeAs('/', $fileName, 'records');
$md5 = md5_file($file->getRealPath()); $md5 = md5_file($file->getRealPath());
$fileRecord = File::create([ $fileRecord = File::create([
'name' => $fileNames[$index] ?? "Archivo " . ($index + 1), 'name' => $customName,
'path' => $path, 'path' => $path,
'md5' => $md5, 'md5' => $md5,
'record_id' => $record->id, 'record_id' => $record->id,
@ -240,6 +240,7 @@ public function vehicleInscription(VehicleStoreRequest $request)
'tag' => [ 'tag' => [
'id' => $tag->id, 'id' => $tag->id,
'folio' => $tag->folio, 'folio' => $tag->folio,
'status' => $tag->status,
], ],
'files' => $uploadedFiles, 'files' => $uploadedFiles,
'total_files' => count($uploadedFiles), 'total_files' => count($uploadedFiles),

View File

@ -78,7 +78,6 @@ public function generatePdfImages($id)
$pdf->SetAutoPageBreak(false); $pdf->SetAutoPageBreak(false);
$pdf->SetMargins(10, 10, 10); $pdf->SetMargins(10, 10, 10);
$totalImages = $record->files->count();
$currentImage = 0; $currentImage = 0;
foreach ($record->files as $file) { foreach ($record->files as $file) {
@ -137,6 +136,9 @@ public function generatePdfImages($id)
case IMAGETYPE_JPEG: case IMAGETYPE_JPEG:
$imageExtension = 'JPEG'; $imageExtension = 'JPEG';
break; break;
case IMAGETYPE_JPEG:
$imageExtension = 'JPG';
break;
case IMAGETYPE_PNG: case IMAGETYPE_PNG:
$imageExtension = 'PNG'; $imageExtension = 'PNG';
break; break;

View File

@ -56,4 +56,21 @@ public function show(Tag $tag)
'tag' => $tag, 'tag' => $tag,
]); ]);
} }
public function destroy(Tag $tag)
{
try{
$tag->delete();
return ApiResponse::OK->response([
'message' => 'Tag eliminado correctamente.',
]);
}catch(\Exception $e){
return ApiResponse::INTERNAL_ERROR->response([
'message' => 'Error al eliminar el tag.',
'error' => $e->getMessage(),
]);
}
}
} }

View File

@ -20,36 +20,36 @@ class UpdateController extends Controller
public function vehicleUpdate(VehicleUpdateRequest $request) public function vehicleUpdate(VehicleUpdateRequest $request)
{ {
try { try {
$folio = $request->input('folio'); $recordId = $request->input('record_id');
$tagId = $request->input('tag_id');
// Buscar vehículo por folio y tag // Buscar vehículo por folio y tag
$tag = Tag::with('vehicle.owner')->findOrFail($tagId); $record = Record::with(['vehicle.owner', 'vehicle.tag', 'files', 'error'])->find($recordId);
// Validar que el tag corresponda al folio // Validar que el tag
if ($tag->folio !== $folio) { if (!$record) {
return ApiResponse::BAD_REQUEST->response([ return ApiResponse::BAD_REQUEST->response([
'message' => 'El folio no coincide con el tag RFID proporcionado', 'message' => 'No se encontró el expediente',
'folio_request' => $folio, 'record_id' => $recordId,
'folio_tag' => $tag->folio,
]); ]);
} }
$vehicle = $tag->vehicle; $vehicle = $record->vehicle;
$tag = $vehicle->tag;
if (!$vehicle) { if (!$tag) {
return ApiResponse::NOT_FOUND->response([ return ApiResponse::NOT_FOUND->response([
'message' => 'No se encontró un vehículo asociado al tag', 'message' => 'El vehículo no tiene tag asociado',
'vehicle_id' => $vehicle->placa,
]); ]);
} }
// Consultar API Repuve Nacional (verificar robo) // Consultar API Repuve Nacional (verificar robo)
$isStolen = $this->checkIfStolen($folio); $isStolen = $this->checkIfStolen($record->folio);
if ($isStolen) { if ($isStolen) {
return ApiResponse::FORBIDDEN->response([ return ApiResponse::FORBIDDEN->response([
'folio' => $folio, 'record_id' => $recordId,
'tag_id' => $tagId, 'tag_number' => $tag->tag_number,
'stolen' => true, 'stolen' => true,
'message' => 'El vehículo reporta robo. No se puede continuar con la actualización.', 'message' => 'El vehículo reporta robo. No se puede continuar con la actualización.',
]); ]);
@ -80,7 +80,7 @@ public function vehicleUpdate(VehicleUpdateRequest $request)
// NO actualizar 'placa' - es UNIQUE // NO actualizar 'placa' - es UNIQUE
// NO actualizar 'numero_serie' - es UNIQUE (NIV/VIN) // NO actualizar 'numero_serie' - es UNIQUE (NIV/VIN)
'rfc' => $vehicleData['RFC'], 'rfc' => $vehicleData['RFC'],
'folio' => $folio, // NO actualizar 'folio' => $folio,
'vigencia' => $vehicleData['VIGENCIA'], 'vigencia' => $vehicleData['VIGENCIA'],
'fecha_impresion' => $vehicleData['FECHA_IMPRESION'], 'fecha_impresion' => $vehicleData['FECHA_IMPRESION'],
'qr_hash' => $vehicleData['QR_HASH'], 'qr_hash' => $vehicleData['QR_HASH'],
@ -107,16 +107,40 @@ public function vehicleUpdate(VehicleUpdateRequest $request)
'owner_id' => $owner->id, 'owner_id' => $owner->id,
]); ]);
$record = Record::firstOrCreate( // Procesar archivos si existen
['vehicle_id' => $vehicle->id], $uploadedFiles = [];
[ if ($request->hasFile('files')) {
'folio' => $folio, $files = $request->file('files');
'user_id' => Auth::id(), $fileNames = $request->input('names', []);
]
); foreach ($files as $index => $file) {
$customName = $fileNames[$index] ?? "archivo_" . ($index + 1);
$customName = str_replace(' ', '_', $customName);
$extension = $file->getClientOriginalExtension();
$fileName = $customName . '_' . time() . '.' . $extension;
$path = $file->storeAs('/', $fileName, 'records');
$md5 = md5_file($file->getRealPath());
$fileRecord = File::create([
'name' => $customName,
'path' => $path,
'md5' => $md5,
'record_id' => $record->id,
]);
$uploadedFiles[] = [
'id' => $fileRecord->id,
'name' => $fileRecord->name,
'path' => $fileRecord->path,
'url' => $fileRecord->url,
];
}
}
// Enviar datos a API Repuve Nacional // Enviar datos a API Repuve Nacional
$apiResponse = $this->sendToRepuveNacional($folio, $tagId, $vehicleData); $apiResponse = $this->sendToRepuveNacional($vehicle->numero_serie);
$apiResponse["repuve_response"]["folio_ci"] = $record->folio;
$apiResponse["repuve_response"]["identificador_ci"] = $tag->tag_number;
// Procesar respuesta de la API // Procesar respuesta de la API
if (isset($apiResponse['has_error']) && $apiResponse['has_error']) { if (isset($apiResponse['has_error']) && $apiResponse['has_error']) {
@ -135,7 +159,7 @@ public function vehicleUpdate(VehicleUpdateRequest $request)
// guarda error // guarda error
$record->update([ $record->update([
'error_id' => $error->id, 'error_id' => $error->id,
'api_response' => $apiResponse, // Guarda respuesta con error 'api_response' => $apiResponse,
'error_occurred_at' => now(), 'error_occurred_at' => now(),
]); ]);
@ -146,7 +170,7 @@ public function vehicleUpdate(VehicleUpdateRequest $request)
return ApiResponse::OK->response([ return ApiResponse::OK->response([
'message' => 'Datos guardados con error. Corrija los datos y vuelva a enviar.', 'message' => 'Datos guardados con error. Corrija los datos y vuelva a enviar.',
'has_error' => true, 'has_error' => true,
'can_retry' => true, 'can_update' => true,
'error' => [ 'error' => [
'code' => $error->code, 'code' => $error->code,
'description' => $error->description, 'description' => $error->description,
@ -158,6 +182,16 @@ public function vehicleUpdate(VehicleUpdateRequest $request)
], ],
'vehicle' => $vehicleData, 'vehicle' => $vehicleData,
'owner' => $ownerData, 'owner' => $ownerData,
'new_files' => $uploadedFiles,
'total_new_files' => count($uploadedFiles),
'existing_files' => $record->files->map(function ($file) {
return [
'id' => $file->id,
'name' => $file->name,
'path' => $file->path,
'url' => $file->url,
];
}),
]); ]);
} }
@ -168,33 +202,6 @@ public function vehicleUpdate(VehicleUpdateRequest $request)
'error_occurred_at' => null, 'error_occurred_at' => null,
]); ]);
// Procesar archivos si existen
$uploadedFiles = [];
if ($request->hasFile('files')) {
$files = $request->file('files');
$fileNames = $request->input('names', []);
foreach ($files as $index => $file) {
$fileName = uniqid() . '_' . time() . '_' . $file->getClientOriginalName();
$path = $file->storeAs('records', $fileName, 'public');
$md5 = md5_file($file->getRealPath());
$fileRecord = File::create([
'name' => $fileNames[$index] ?? "Archivo " . ($index + 1),
'path' => $path,
'md5' => $md5,
'record_id' => $record->id,
]);
$uploadedFiles[] = [
'id' => $fileRecord->id,
'name' => $fileRecord->name,
'path' => $fileRecord->path,
'url' => $fileRecord->url,
];
}
}
DB::commit(); DB::commit();
return ApiResponse::OK->response([ return ApiResponse::OK->response([
@ -203,6 +210,8 @@ public function vehicleUpdate(VehicleUpdateRequest $request)
'record' => [ 'record' => [
'id' => $record->id, 'id' => $record->id,
'folio' => $record->folio, 'folio' => $record->folio,
'vehicle_id' => $vehicle->id,
'user_id' => $record->user_id,
'updated_at' => $record->updated_at->toDateTimeString(), 'updated_at' => $record->updated_at->toDateTimeString(),
], ],
'vehicle' => [ 'vehicle' => [
@ -211,6 +220,7 @@ public function vehicleUpdate(VehicleUpdateRequest $request)
'numero_serie' => $vehicle->numero_serie, 'numero_serie' => $vehicle->numero_serie,
'marca' => $vehicle->marca, 'marca' => $vehicle->marca,
'modelo' => $vehicle->modelo, 'modelo' => $vehicle->modelo,
'color' => $vehicle->color,
], ],
'owner' => [ 'owner' => [
'id' => $owner->id, 'id' => $owner->id,
@ -220,11 +230,20 @@ public function vehicleUpdate(VehicleUpdateRequest $request)
'tag' => [ 'tag' => [
'id' => $tag->id, 'id' => $tag->id,
'folio' => $tag->folio, 'folio' => $tag->folio,
'tag_number' => $tag->tag_number,
'status' => $tag->status,
], ],
'new_files' => $uploadedFiles, 'new_files' => $uploadedFiles,
'total_new_files' => count($uploadedFiles), 'total_new_files' => count($uploadedFiles),
'existing_files' => $record->files->map(function ($file) {
return [
'id' => $file->id,
'name' => $file->name,
'url' => $file->url,
];
}),
'total_files' => $record->files->count() + count($uploadedFiles),
]); ]);
} catch (\Exception $e) { } catch (\Exception $e) {
DB::rollBack(); DB::rollBack();
return ApiResponse::BAD_REQUEST->response([ return ApiResponse::BAD_REQUEST->response([
@ -241,55 +260,57 @@ private function checkIfStolen(string $folio): bool
return (bool) rand(0, 1); return (bool) rand(0, 1);
} }
private function sendToRepuveNacional(string $folio, int $tagId, array $vehicleData): array private function sendToRepuveNacional(string $vin): array
{ {
// Enviar datos a API Repuve Nacional // Enviar datos a API Repuve Nacional
// Aquí se haría la llamada real a la API de Repuve Nacional // Aquí se haría la llamada real a la API de Repuve Nacional
// Por ahora simulamos respuestas aleatorias usando la tabla errors // Por ahora simulamos con mock igual que InscriptionController
$hasError = (bool) rand(0, 1); // Respuesta exitosa mockup REPUVE
$mockResponse = "OK:SIN INFORMACION|OTROS|SIN INFORMACION|10/07/2023|CENTRO|$vin|WSA548B|HR16777934V|2023|BLANCO|ADVANCE|TABASCO|NISSAN|MARCH|PARTICULAR|AUTOMOVIL|ACTIVO|null||null|0|null|0|10337954|E2003412012BB0C130FAD04D|Sin observaciones
";
if ($hasError) { // Parsear la cadena a JSON usando los campos oficiales
// Obtener un error aleatorio de la tabla errors $fields = [
$error = Error::inRandomOrder()->first(); 'marca',
'submarca',
if (!$error) { 'tipo_vehiculo',
// Si no hay errores en la tabla, retornar error genérico 'fecha_expedicion',
return [ 'oficina',
'has_error' => true, 'niv',
'error_code' => 'ERR_UNKNOWN', 'placa',
'error_message' => 'No hay errores registrados en el catálogo', 'motor',
'timestamp' => now()->toDateTimeString(), 'modelo',
'folio' => $folio, 'color',
'tag_id' => $tagId, 'version',
'response_data' => null, 'entidad',
'marca_padron',
'submarca_padron',
'tipo_uso_padron',
'tipo_vehiculo_padron',
'estatus_registro',
'aduana',
'nombre_aduana',
'patente',
'pedimento',
'fecha_pedimento',
'clave_importador',
'folio_ci',
'identificador_ci',
'observaciones'
]; ];
$values = explode('|', str_replace('OK:', '', $mockResponse));
$jsonResponse = [];
foreach ($fields as $i => $field) {
$jsonResponse[$field] = $values[$i] ?? null;
} }
return [
'has_error' => true,
'error_code' => $error->code,
'error_message' => $error->description,
'timestamp' => now()->toDateTimeString(),
'folio' => $folio,
'tag_id' => $tagId,
'response_data' => null,
];
}
// Respuesta exitosa
return [ return [
'has_error' => false, 'has_error' => false,
'error_code' => null, 'error_code' => null,
'error_message' => null, 'error_message' => null,
'timestamp' => now()->toDateTimeString(), 'timestamp' => now()->toDateTimeString(),
'folio' => $folio, 'vin' => $vin,
'tag_id' => $tagId, 'repuve_response' => $jsonResponse,
'response_data' => [
'status' => 'success',
'repuve_id' => 'REPUVE-' . strtoupper(uniqid()),
'validated' => true,
],
]; ];
} }

View File

@ -13,13 +13,21 @@ public function authorize(): bool
public function rules(): array public function rules(): array
{ {
return [ return [
'folio' => ['required', 'string', 'max:50'], 'record_id' => ['required', 'integer', 'exists:records,id'],
'tag_id' => ['required', 'exists:tags,id'],
'files' => ['nullable', 'array', 'min:1'], 'files' => ['nullable', 'array', 'min:1'],
'files.*' => ['file', 'mimes:jpeg,png,jpg,pdf', 'max:10240'], 'files.*' => ['file', 'mimes:jpeg,png,jpg,pdf', 'max:10240'],
'names' => ['nullable', 'array'], 'names' => ['nullable', 'array'],
'names.*' => ['string', 'max:255'], 'names.*' => ['string', 'max:255'],
'replace_files' => ['nullable', 'boolean'], ];
}
public function messages(): array
{
return [
'record_id.required' => 'El id del expediente es requerido',
'record_id.exists' => 'El expediente no existe en el sistema',
'files.*.mimes' => 'Solo se permiten archivos JPG, PNG o JPEG',
'files.*.max' => 'El archivo no debe superar 3MB',
]; ];
} }
} }

View File

@ -9,11 +9,18 @@ class Tag extends Model
{ {
use HasFactory; use HasFactory;
// Constantes de status
const STATUS_AVAILABLE = 'available';
const STATUS_ASSIGNED = 'assigned';
const STATUS_CANCELLED = 'cancelled';
const STATUS_LOST = 'lost';
protected $fillable = [ protected $fillable = [
'folio', 'folio',
'tag_number', 'tag_number',
'vehicle_id', 'vehicle_id',
'package_id', 'package_id',
'status',
]; ];
public function vehicle() public function vehicle()
@ -35,4 +42,48 @@ public function scanHistories()
{ {
return $this->hasMany(ScanHistory::class); return $this->hasMany(ScanHistory::class);
} }
/**
* Marcar tag como asignado a un vehículo
*/
public function markAsAssigned(int $vehicleId, string $folio): void
{
$this->update([
'vehicle_id' => $vehicleId,
'folio' => $folio,
'status' => self::STATUS_ASSIGNED,
]);
}
/**
* Verificar si el tag está disponible
*/
public function isAvailable(): bool
{
return $this->status === self::STATUS_AVAILABLE;
}
/**
* Verificar si el tag está asignado
*/
public function isAssigned(): bool
{
return $this->status === self::STATUS_ASSIGNED;
}
/**
* Verificar si el tag está cancelado
*/
public function isCancelled(): bool
{
return $this->status === self::STATUS_CANCELLED;
}
/**
* Verificar si el tag está perdido
*/
public function isLost(): bool
{
return $this->status === self::STATUS_LOST;
}
} }

View File

@ -37,7 +37,7 @@
Route::get('expediente/{id}/pdfImagenes', [RecordController::class, 'generatePdfImages']); Route::get('expediente/{id}/pdfImagenes', [RecordController::class, 'generatePdfImages']);
//Rutas de Actualización //Rutas de Actualización
Route::put('actualizar-vehiculo', [UpdateController::class, 'vehicleUpdate']); Route::post('actualizar', [UpdateController::class, 'vehicleUpdate']);
// Rutas de cancelación de constancias // Rutas de cancelación de constancias
Route::post('cancelacion/cancelar', [CancellationController::class, 'cancelarConstancia']); Route::post('cancelacion/cancelar', [CancellationController::class, 'cancelarConstancia']);
@ -58,12 +58,8 @@
Route::put('/devices/{id}', [DeviceController::class, 'update']); Route::put('/devices/{id}', [DeviceController::class, 'update']);
Route::delete('/devices/{id}', [DeviceController::class, 'destroy']); Route::delete('/devices/{id}', [DeviceController::class, 'destroy']);
//Ruta de paquetes //Ruta de paquetes CRUD
Route::get('/packages', [PackageController::class, 'index']); Route::resource('packages', PackageController::class);
Route::post('/packages-create', [PackageController::class, 'store']);
Route::put('/packages-update/{id}', [PackageController::class, 'update']);
Route::delete('/packages/{id}', [PackageController::class, 'destroy']);
//Ruta CRUD Tags //Ruta CRUD Tags
Route::resource('tags', TagsController::class); Route::resource('tags', TagsController::class);