diff --git a/app/Http/Controllers/Repuve/CancellationController.php b/app/Http/Controllers/Repuve/CancellationController.php index 67e70a7..d04bc87 100644 --- a/app/Http/Controllers/Repuve/CancellationController.php +++ b/app/Http/Controllers/Repuve/CancellationController.php @@ -5,6 +5,7 @@ use App\Http\Controllers\Controller; use App\Http\Requests\Repuve\CancelConstanciaRequest; use App\Models\Record; +use App\Models\Tag; use App\Models\VehicleTagLog; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\DB; @@ -49,23 +50,73 @@ public function cancelarConstancia(CancelConstanciaRequest $request) ]); } - // Crear registro en el log de cancelaciones + // Guardar información del tag anterior ANTES de cancelarlo + $oldTagNumber = $tag->tag_number; + $oldFolio = $tag->folio; + + // Crear registro en el log de vehiculos $cancellationLog = VehicleTagLog::create([ 'vehicle_id' => $vehicle->id, 'tag_id' => $tag->id, + 'action_type' => 'cancelacion', 'cancellation_reason' => $request->cancellation_reason, 'cancellation_observations' => $request->cancellation_observations, 'cancellation_at' => now(), 'cancelled_by' => Auth::id(), + 'performed_by' => Auth::id(), ]); // Actualizar estado del tag a 'cancelled' y desasignar vehículo $tag->markAsCancelled(); + $newTag = null; + $substitutionLog = null; + $isSubstitution = $request->filled('new_tag_number'); + + if ($isSubstitution) { + $newTag = Tag::where('tag_number', $request->new_tag_number)->first(); + + if(!$newTag){ + DB::rollBack(); + return ApiResponse::NOT_FOUND->response([ + 'message' => 'El nuevo tag proporcionado no existe.', + 'new_tag_number' => $request->new_tag_number, + ]); + } + + if(!$newTag->isAvailable()) { + DB::rollBack(); + return ApiResponse::BAD_REQUEST->response([ + 'message' => 'El nuevo tag no está disponible para asignación', + 'new_tag_number' => $request->new_tag_number, + 'current_status' => $newTag->status->name, + ]); + } + + // Usar el folio del NUEVO TAG + $newTag->markAsAssigned($vehicle->id, $newTag->folio); + + $substitutionLog = VehicleTagLog::create([ + 'vehicle_id' => $vehicle->id, + 'tag_id' => $newTag->id, + 'action_type' => 'sustitucion', + 'cancellation_reason' => $request->cancellation_reason, + 'cancellation_observations' => 'Tag sustituido. Tag anterior: ' . $oldTagNumber . 'Motivo: ' . ($request->cancellation_observations ?? ''), + 'performed_by' => Auth::id(), + ]); + + $record->update(['folio' => $newTag->folio]); + } + DB::commit(); + $message = $isSubstitution + ? 'Tag cancelado y sustituido exitosamente' + : 'Constancia cancelada exitosamente'; + return ApiResponse::OK->response([ - 'message' => 'Constancia cancelada exitosamente', + 'message' => $message, + 'is_substitution' => $isSubstitution, 'cancellation' => [ 'id' => $cancellationLog->id, 'vehicle' => [ @@ -73,12 +124,18 @@ public function cancelarConstancia(CancelConstanciaRequest $request) 'placa' => $vehicle->placa, 'niv' => $vehicle->niv, ], - 'tag' => [ + 'old_tag' => [ 'id' => $tag->id, 'folio' => $tag->folio, - 'old_status' => $tag->status->name, + 'tag_number' => $tag->tag_number, 'new_status' => 'Cancelado', ], + 'new_tag' => $newTag ? [ + 'id' => $newTag->id, + 'folio' => $newTag->folio, + 'tag_number' => $newTag->tag_number, + 'status' => $newTag->status->name, + ] : null, 'cancellation_reason' => $request->cancellation_reason, 'cancellation_observations' => $request->cancellation_observations, 'cancelled_at' => $cancellationLog->cancellation_at->toDateTimeString(), diff --git a/app/Http/Controllers/Repuve/ExcelController.php b/app/Http/Controllers/Repuve/ExcelController.php new file mode 100644 index 0000000..da25b10 --- /dev/null +++ b/app/Http/Controllers/Repuve/ExcelController.php @@ -0,0 +1,262 @@ +validate([ + 'fecha_inicio' => 'required|date', + 'fecha_fin' => 'required|date|after_or_equal:fecha_inicio', + 'module_id' => 'required|exists:modules,id', + ]); + + $fechaInicio = Carbon::parse($request->fecha_inicio)->startOfDay(); + $fechaFin = Carbon::parse($request->fecha_fin)->endOfDay(); + $moduleId = $request->module_id; + + // Obtener información del módulo + $module = Module::findOrFail($moduleId); + + // Obtener logs de sustitución en el rango de fechas + $logs = VehicleTagLog::with([ + 'vehicle', + 'tag', + ]) + ->where('action_type', 'sustitucion') + ->whereHas('vehicle.records', function ($query) use ($moduleId) { + $query->where('module_id', $moduleId); + }) + ->whereBetween('created_at', [$fechaInicio, $fechaFin]) + ->orderBy('created_at', 'asc') + ->get(); + + if ($logs->isEmpty()) { + return ApiResponse::NOT_FOUND->response([ + 'message' => 'No se encontraron constancias sustituidas en el periodo especificado', + 'fecha_inicio' => $fechaInicio->format('Y-m-d'), + 'fecha_fin' => $fechaFin->format('Y-m-d'), + 'module_id' => $moduleId, + ]); + } + + // Preparar datos para el Excel + $data = $this->prepareExcelData($logs); + + // Generar archivo Excel + $fileName = 'Constancias_Sustituidas_' . $fechaInicio->format('Ymd') . '_' . $fechaFin->format('Ymd') . '.xlsx'; + $filePath = storage_path('app/temp/' . $fileName); + + // Crear directorio temporal si no existe + if (!file_exists(storage_path('app/temp'))) { + mkdir(storage_path('app/temp'), 0755, true); + } + + // Crear Excel + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + + // Agregar logo + $logoPath = storage_path('app/images/logo-seguridad.png'); + if (file_exists($logoPath)) { + $drawing = new Drawing(); + $drawing->setName('Logo Seguridad'); + $drawing->setDescription('Logo Seguridad Pública'); + $drawing->setPath($logoPath); + $drawing->setHeight(100); // Altura del logo en pixeles + $drawing->setCoordinates('B1'); // Posición del logo + $drawing->setWorksheet($sheet); + + // Ajustar altura de las filas del logo + $sheet->getRowDimension(1)->setRowHeight(45); + } + + // Definir estilo de bordes + $borderStyle = [ + 'borders' => [ + 'allBorders' => [ + 'borderStyle' => PhpSpreadsheetBorder::BORDER_THIN, + 'color' => ['rgb' => '000000'], + ], + ], + ]; + + // Empezar después del logo + $row = 5; + + // Fila 1: ENTIDAD (empieza en B) + $sheet->setCellValue('B' . $row, 'ENTIDAD:'); + $sheet->mergeCells('C' . $row . ':K' . $row); + $sheet->setCellValue('C' . $row, 'TABASCO'); + $sheet->getStyle('B' . $row)->getFont()->setBold(true)->setSize(11); + $sheet->getStyle('B' . $row . ':K' . $row)->applyFromArray($borderStyle); + $sheet->getStyle('B' . $row . ':K' . $row)->getAlignment()->setWrapText(true); + $row++; + + // Fila 2: MÓDULO (empieza en B) + $sheet->setCellValue('B' . $row, 'MÓDULO:'); + $sheet->mergeCells('C' . $row . ':K' . $row); + $sheet->setCellValue('C' . $row, $module->name ?? 'MÓDULO 1. BASE 4'); + $sheet->getStyle('B' . $row)->getFont()->setBold(true)->setSize(11); + $sheet->getStyle('B' . $row . ':K' . $row)->applyFromArray($borderStyle); + $sheet->getStyle('B' . $row . ':K' . $row)->getAlignment()->setWrapText(true); + $row++; + + // Fila 3: PERIODO A INFORMAR (empieza en B, fecha completa con año) + $sheet->setCellValue('B' . $row, 'PERIODO A INFORMAR:'); + $sheet->mergeCells('C' . $row . ':K' . $row); + $sheet->setCellValue('C' . $row, 'del ' . $fechaInicio->format('d') . ' al ' . $fechaFin->format('d') . ' de ' . $fechaInicio->translatedFormat('F') . ' de ' . $fechaInicio->year); + $sheet->getStyle('B' . $row)->getFont()->setBold(true)->setSize(11); + $sheet->getStyle('B' . $row . ':K' . $row)->applyFromArray($borderStyle); + $sheet->getStyle('B' . $row . ':K' . $row)->getAlignment()->setWrapText(true); + $row++; + + // Fila vacía + $row++; + + // Título: CONSTANCIAS SUSTITUIDAS (combinada B:K) + $sheet->mergeCells('B' . $row . ':K' . $row); + $sheet->setCellValue('B' . $row, 'CONSTANCIAS SUSTITUIDAS'); + $sheet->getStyle('B' . $row)->getFont()->setBold(true)->setSize(12); + $sheet->getStyle('B' . $row)->getAlignment()->setHorizontal(Alignment::HORIZONTAL_CENTER)->setWrapText(true); + $sheet->getStyle('B' . $row . ':K' . $row)->applyFromArray($borderStyle); + $row++; + + // Fila vacía + $row++; + + // Encabezados de la tabla + $headers = [ + 'No.', + 'NIV DEL VEHÍCULO', + 'NRPV/NCI', + 'MARCA DEL VEHÍCULO', + 'PLACA', + 'AÑO MODELO', + 'FOLIO ANTERIOR', + 'FOLIO ACTUAL', + 'ID DE LA CONSTANCIA (CHIP)', + 'FECHA DE REEMPLAZO', + 'OBSERVACIONES (MOTIVO DEL REEMPLAZO)', + ]; + + $col = 'A'; + foreach ($headers as $header) { + $sheet->setCellValue($col . $row, $header); + $col++; + } + + $sheet->getStyle('A' . $row . ':K' . $row)->applyFromArray([ + 'font' => ['bold' => true, 'color' => ['rgb' => 'FFFFFF'], 'size' => 10], + 'fill' => ['fillType' => Fill::FILL_SOLID, 'startColor' => ['rgb' => '8B0000']], + 'alignment' => ['horizontal' => Alignment::HORIZONTAL_CENTER, 'wrapText' => true], + 'borders' => [ + 'allBorders' => [ + 'borderStyle' => PhpSpreadsheetBorder::BORDER_THIN, + 'color' => ['rgb' => '000000'], + ], + ], + ]); + $row++; + + // Agregar datos + foreach ($data as $rowData) { + $col = 'A'; + foreach ($rowData as $value) { + $sheet->setCellValue($col . $row, $value); + $col++; + } + $sheet->getStyle('A' . $row . ':K' . $row)->applyFromArray([ + 'font' => ['size' => 10], + 'alignment' => ['wrapText' => true], + 'borders' => [ + 'allBorders' => [ + 'borderStyle' => PhpSpreadsheetBorder::BORDER_THIN, + 'color' => ['rgb' => '000000'], + ], + ], + ]); + $row++; + } + + // Ajustar anchos de columnas + $sheet->getColumnDimension('A')->setWidth(6); + $sheet->getColumnDimension('B')->setWidth(25); + $sheet->getColumnDimension('C')->setWidth(15); + $sheet->getColumnDimension('D')->setWidth(20); + $sheet->getColumnDimension('E')->setWidth(12); + $sheet->getColumnDimension('F')->setWidth(15); + $sheet->getColumnDimension('G')->setWidth(18); + $sheet->getColumnDimension('H')->setWidth(18); + $sheet->getColumnDimension('I')->setWidth(30); + $sheet->getColumnDimension('J')->setWidth(20); + $sheet->getColumnDimension('K')->setWidth(50); + + // Guardar archivo + $writer = new Xlsx($spreadsheet); + $writer->save($filePath); + + // Descargar archivo y eliminarlo después + return response()->download($filePath, $fileName)->deleteFileAfterSend(true); + } + + private function prepareExcelData($logs) + { + $data = []; + $no = 1; + + foreach ($logs as $log) { + $vehicle = $log->vehicle; + $newTag = $log->tag; + + // Extraer el folio anterior de las observaciones + $folioAnterior = 'N/A'; + if ($log->cancellation_observations) { + if (preg_match('/Folio:\s*([^)]+)/', $log->cancellation_observations, $matches)) { + $folioAnterior = trim($matches[1]); + } + } + + $data[] = [ + $no, + $vehicle->niv ?? 'N/A', + $vehicle->nrpv ?? 'N/A', + strtoupper($vehicle->marca ?? 'N/A'), + strtoupper($vehicle->placa ?? 'N/A'), + $vehicle->modelo ?? 'N/A', + $folioAnterior, + $newTag->folio ?? 'N/A', + $newTag->tag_number ?? 'N/A', + $log->created_at->format('d/m/Y'), + $log->cancellation_observations ?? 'CONSTANCIA DAÑADA', + ]; + + $no++; + } + + return $data; + } +} diff --git a/app/Http/Controllers/Repuve/InscriptionController.php b/app/Http/Controllers/Repuve/InscriptionController.php index f4710e9..f8802be 100644 --- a/app/Http/Controllers/Repuve/InscriptionController.php +++ b/app/Http/Controllers/Repuve/InscriptionController.php @@ -13,6 +13,7 @@ use App\Models\File; use App\Models\Tag; use App\Models\Error; +use App\Models\VehicleTagLog; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Auth; use App\Services\RepuveService; @@ -129,6 +130,13 @@ public function vehicleInscription(VehicleStoreRequest $request) // Asignar Tag al vehículo $tag->markAsAssigned($vehicle->id, $folio); + VehicleTagLog::create([ + 'vehicle_id' => $vehicle->id, + 'tag_id' => $tag->id, + 'action_type' => 'inscripcion', + 'performed_by' => Auth::id(), + ]); + // Crear registro $record = Record::create([ 'folio' => $folio, diff --git a/app/Http/Controllers/Repuve/UpdateController.php b/app/Http/Controllers/Repuve/UpdateController.php index 96e4b78..ebd1367 100644 --- a/app/Http/Controllers/Repuve/UpdateController.php +++ b/app/Http/Controllers/Repuve/UpdateController.php @@ -14,7 +14,6 @@ use App\Models\Owner; use App\Models\Error; use App\Models\CatalogNameImg; -use App\Models\Tag; use App\Models\VehicleTagLog; use App\Services\RepuveService; use App\Services\PadronEstatalService; @@ -64,16 +63,16 @@ public function vehicleData(Request $request) 'user:id,name,email', 'error:id,code,description' ]) - ->select([ - 'id', - 'folio', - 'vehicle_id', - 'user_id', - 'error_id', - 'created_at', - 'updated_at' - ])->where('folio', $folio) - ->first(); + ->select([ + 'id', + 'folio', + 'vehicle_id', + 'user_id', + 'error_id', + 'created_at', + 'updated_at' + ])->where('folio', $folio) + ->first(); if (!$record) { return ApiResponse::NOT_FOUND->response([ @@ -158,28 +157,15 @@ public function vehicleUpdate(VehicleUpdateRequest $request) } $vehicle = $record->vehicle; - $currentTag = $vehicle->tag; - $newTag = Tag::where('tag_number', $tagNumber)->first(); + $tag = $vehicle->tag; - if (!$newTag) { + if (!$tag) { return ApiResponse::NOT_FOUND->response([ 'message' => 'No se encontró el tag con el tag_number proporcionado', 'tag_number' => $tagNumber, ]); } - $isTagReplacement = false; - if($currentTag && $currentTag->id !== $newTag->id) { - if(!$newTag->isAvailable()) { - return ApiResponse::BAD_REQUEST->response([ - 'message' => 'El tag proporcionado no está disponible para asignación', - 'tag_number' => $tagNumber, - 'current_status' => $newTag->status->name, - ]); - } - $isTagReplacement = true; - } - if ($vehicle->niv !== $niv) { return ApiResponse::BAD_REQUEST->response([ 'message' => 'El NIV no coincide con el registrado en el expediente', @@ -257,26 +243,6 @@ public function vehicleUpdate(VehicleUpdateRequest $request) ]); } - $tagReplacementLog = null; - if($isTagReplacement) { - $tagReplacementLog = VehicleTagLog::create([ - 'vehicle_id' => $vehicle->id, - 'tag_id' => $currentTag->id, - 'cancellation_reason' => 'Otro', - 'cancellation_observations' => 'Reemplazo automático al actualizar datos del vehículo: '. $newTag->tag_number, - 'cancellation_at' => now(), - 'cancelled_by' => Auth::id(), - ]); - - $currentTag->markAsCancelled(); - - $newTag->markAsAssigned($vehicle->id, $folio); - - $tag = $newTag; - }else{ - $tag = $currentTag; - } - $uploadedFiles = []; $replacedFiles = []; @@ -346,8 +312,17 @@ public function vehicleUpdate(VehicleUpdateRequest $request) } } + if ($hasVehicleChanges || $hasOwnerChanges || count($uploadedFiles) > 0) { + VehicleTagLog::create([ + 'vehicle_id' => $vehicle->id, + 'tag_id' => $tag->id, + 'action_type' => 'actualizacion', + 'performed_by' => Auth::id(), + ]); + } + // Solo enviar a REPUVE Nacional si hay cambios - if ($hasVehicleChanges || $hasOwnerChanges || count($uploadedFiles) > 0|| $isTagReplacement) { + if ($hasVehicleChanges || $hasOwnerChanges || count($uploadedFiles) > 0) { //Envio de datos $apiResponse = $this->sendToRepuveNacional($niv); $apiResponse["repuve_response"]["folio_ci"] = $record->folio; @@ -411,15 +386,17 @@ public function vehicleUpdate(VehicleUpdateRequest $request) DB::commit(); - $sentToRepuve = $hasVehicleChanges || $hasOwnerChanges || count($uploadedFiles) > 0 || $isTagReplacement; + $sentToRepuve = $hasVehicleChanges || $hasOwnerChanges || count($uploadedFiles) > 0; $message = 'Vehículo actualizado exitosamente'; if (!$hasVehicleChanges && !$hasOwnerChanges && empty($uploadedFiles)) { $message = 'No se detectaron cambios. Los datos ya estaban actualizados.'; - } elseif (!$hasVehicleChanges && !$hasOwnerChanges && !$isTagReplacement) { + } elseif (!$hasVehicleChanges && !$hasOwnerChanges && empty($uploadedFiles)) { $message = 'Solo se actualizaron archivos. Los datos del vehículo/propietario no cambiaron.'; - } elseif ($isTagReplacement && !$hasVehicleChanges && !$hasOwnerChanges && empty($uploadedFiles)) { - $message = 'Tag reemplazado exitosamente. Los datos del vehículo/propietario no cambiaron.'; + } elseif (($hasVehicleChanges || $hasOwnerChanges) && empty($uploadedFiles)) { + $message = 'Datos del vehículo/propietario actualizados exitosamente. No se subieron archivos.'; + } elseif ((!$hasVehicleChanges && !$hasOwnerChanges) && !empty($uploadedFiles)) { + $message = 'Archivos subidos exitosamente. No hubo cambios en los datos del vehículo/propietario.'; } return ApiResponse::OK->response([ @@ -430,20 +407,7 @@ public function vehicleUpdate(VehicleUpdateRequest $request) 'vehicle_updated' => $hasVehicleChanges, 'owner_updated' => $hasOwnerChanges, 'files_uploaded' => count($uploadedFiles) > 0, - 'tag_replaced' => $isTagReplacement, ], - 'tag_replacement' => $isTagReplacement ? [ - 'old_tag' => [ - 'id' => $currentTag->id, - 'tag_number' => $currentTag->tag_number, - 'status' => $currentTag->status->name, - ], - 'new_tag' => [ - 'id' => $newTag->id, - 'tag_number' => $newTag->tag_number, - 'status' => $newTag->status->name, - ], - ] : null, 'record' => [ 'id' => $record->id, 'folio' => $record->folio, diff --git a/app/Http/Requests/Repuve/CancelConstanciaRequest.php b/app/Http/Requests/Repuve/CancelConstanciaRequest.php index f6c30db..9f3a30c 100644 --- a/app/Http/Requests/Repuve/CancelConstanciaRequest.php +++ b/app/Http/Requests/Repuve/CancelConstanciaRequest.php @@ -20,9 +20,10 @@ public function authorize(): bool public function rules(): array { return [ - 'record_id' => 'required|integer|exists:records,id', + 'record_id' => 'required|exists:records,id', 'cancellation_reason' => 'required|in:fallo_lectura_handheld,cambio_parabrisas,roto_al_pegarlo,extravio,otro', - 'cancellation_observations' => 'nullable|string|max:1000', + 'cancellation_observations' => 'nullable|string', + 'new_tag_number' => 'nullable|exists:tags,tag_number', ]; } diff --git a/app/Http/Requests/Repuve/VehicleUpdateRequest.php b/app/Http/Requests/Repuve/VehicleUpdateRequest.php index d616543..41b1346 100644 --- a/app/Http/Requests/Repuve/VehicleUpdateRequest.php +++ b/app/Http/Requests/Repuve/VehicleUpdateRequest.php @@ -13,7 +13,6 @@ public function authorize(): bool public function rules(): array { return [ - 'record_id' => ['required', 'integer', 'exists:records,id'], 'files' => ['nullable', 'array', 'min:1'], 'files.*' => ['file', 'mimes:jpeg,png,jpg,pdf', 'max:10240'], 'names' => ['nullable', 'array'], @@ -24,8 +23,6 @@ public function rules(): array 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', ]; diff --git a/app/Models/Tag.php b/app/Models/Tag.php index bea7411..0f01dc5 100644 --- a/app/Models/Tag.php +++ b/app/Models/Tag.php @@ -72,6 +72,7 @@ public function markAsCancelled(): void $this->update([ 'status_id' => $statusCancelled->id, 'vehicle_id' => null, + 'folio' => null, ]); } diff --git a/app/Models/VehicleTagLog.php b/app/Models/VehicleTagLog.php index bd8c775..ca6f073 100644 --- a/app/Models/VehicleTagLog.php +++ b/app/Models/VehicleTagLog.php @@ -14,10 +14,12 @@ class VehicleTagLog extends Model protected $fillable = [ 'vehicle_id', 'tag_id', + 'action_type', 'cancellation_reason', 'cancellation_observations', 'cancellation_at', 'cancelled_by', + 'performed_by', ]; protected function casts(): array @@ -39,4 +41,24 @@ public function cancelledBy() { return $this->belongsTo(User::class, 'cancelled_by'); } + public function isInscription() + { + return $this->action_type === 'inscripcion'; + } + + public function isUpdate() + { + return $this->action_type === 'actualizacion'; + } + + public function isSubstitution() + { + return $this->action_type === 'sustitucion'; + } + + public function isCancellation() + { + return $this->action_type === 'cancelacion'; + } + } diff --git a/app/Services/PadronEstatalService.php b/app/Services/PadronEstatalService.php index 8d7fb21..6cc9eb0 100644 --- a/app/Services/PadronEstatalService.php +++ b/app/Services/PadronEstatalService.php @@ -66,7 +66,7 @@ private function consultarPadron(string $tipo, string $valor): array curl_close($ch); if ($error) { - throw new Exception("Error en la petición SOAP al padrón estatal: {$error}"); + throw new Exception("Error en la petición al padrón estatal: {$error}"); } if ($httpCode !== 200) { @@ -86,7 +86,7 @@ private function parsearRespuesta(string $soapResponse): array preg_match('/(.*?)<\/result>/s', $soapResponse, $matches); if (!isset($matches[1])) { - throw new Exception("No se pudo extraer el resultado del SOAP del padrón estatal"); + throw new Exception("No se pudo extraer el resultado del padrón estatal"); } $jsonContent = trim($matches[1]); diff --git a/composer.json b/composer.json index 9fb4620..bb6f7a3 100644 --- a/composer.json +++ b/composer.json @@ -16,8 +16,10 @@ "laravel/tinker": "^2.10", "milon/barcode": "^12.0", "notsoweb/laravel-core": "dev-main", + "phpoffice/phpspreadsheet": "*", "setasign/fpdf": "^1.8", "spatie/laravel-permission": "^6.16", + "spatie/simple-excel": "^3.8", "tightenco/ziggy": "^2.5" }, "require-dev": { diff --git a/composer.lock b/composer.lock index f7cac14..ffdc41f 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "ddec2d00c4e2c1ad362c8e28c77eb079", + "content-hash": "7a79a1a1e064bf49d5a8cfcf7ee4c69c", "packages": [ { "name": "barryvdh/laravel-dompdf", @@ -397,6 +397,85 @@ }, "time": "2025-03-14T15:54:58+00:00" }, + { + "name": "composer/pcre", + "version": "3.3.2", + "source": { + "type": "git", + "url": "https://github.com/composer/pcre.git", + "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/pcre/zipball/b2bed4734f0cc156ee1fe9c0da2550420d99a21e", + "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0" + }, + "conflict": { + "phpstan/phpstan": "<1.11.10" + }, + "require-dev": { + "phpstan/phpstan": "^1.12 || ^2", + "phpstan/phpstan-strict-rules": "^1 || ^2", + "phpunit/phpunit": "^8 || ^9" + }, + "type": "library", + "extra": { + "phpstan": { + "includes": [ + "extension.neon" + ] + }, + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Pcre\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "PCRE wrapping library that offers type-safe preg_* replacements.", + "keywords": [ + "PCRE", + "preg", + "regex", + "regular expression" + ], + "support": { + "issues": "https://github.com/composer/pcre/issues", + "source": "https://github.com/composer/pcre/tree/3.3.2" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2024-11-12T16:29:46+00:00" + }, { "name": "defuse/php-encryption", "version": "v2.4.0", @@ -3258,6 +3337,191 @@ ], "time": "2025-07-17T05:12:15+00:00" }, + { + "name": "maennchen/zipstream-php", + "version": "3.2.0", + "source": { + "type": "git", + "url": "https://github.com/maennchen/ZipStream-PHP.git", + "reference": "9712d8fa4cdf9240380b01eb4be55ad8dcf71416" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/maennchen/ZipStream-PHP/zipball/9712d8fa4cdf9240380b01eb4be55ad8dcf71416", + "reference": "9712d8fa4cdf9240380b01eb4be55ad8dcf71416", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "ext-zlib": "*", + "php-64bit": "^8.3" + }, + "require-dev": { + "brianium/paratest": "^7.7", + "ext-zip": "*", + "friendsofphp/php-cs-fixer": "^3.16", + "guzzlehttp/guzzle": "^7.5", + "mikey179/vfsstream": "^1.6", + "php-coveralls/php-coveralls": "^2.5", + "phpunit/phpunit": "^12.0", + "vimeo/psalm": "^6.0" + }, + "suggest": { + "guzzlehttp/psr7": "^2.4", + "psr/http-message": "^2.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "ZipStream\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paul Duncan", + "email": "pabs@pablotron.org" + }, + { + "name": "Jonatan Männchen", + "email": "jonatan@maennchen.ch" + }, + { + "name": "Jesse Donat", + "email": "donatj@gmail.com" + }, + { + "name": "András Kolesár", + "email": "kolesar@kolesar.hu" + } + ], + "description": "ZipStream is a library for dynamically streaming dynamic zip files from PHP without writing to the disk at all on the server.", + "keywords": [ + "stream", + "zip" + ], + "support": { + "issues": "https://github.com/maennchen/ZipStream-PHP/issues", + "source": "https://github.com/maennchen/ZipStream-PHP/tree/3.2.0" + }, + "funding": [ + { + "url": "https://github.com/maennchen", + "type": "github" + } + ], + "time": "2025-07-17T11:15:13+00:00" + }, + { + "name": "markbaker/complex", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/MarkBaker/PHPComplex.git", + "reference": "95c56caa1cf5c766ad6d65b6344b807c1e8405b9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/MarkBaker/PHPComplex/zipball/95c56caa1cf5c766ad6d65b6344b807c1e8405b9", + "reference": "95c56caa1cf5c766ad6d65b6344b807c1e8405b9", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "dev-master", + "phpcompatibility/php-compatibility": "^9.3", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0", + "squizlabs/php_codesniffer": "^3.7" + }, + "type": "library", + "autoload": { + "psr-4": { + "Complex\\": "classes/src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mark Baker", + "email": "mark@lange.demon.co.uk" + } + ], + "description": "PHP Class for working with complex numbers", + "homepage": "https://github.com/MarkBaker/PHPComplex", + "keywords": [ + "complex", + "mathematics" + ], + "support": { + "issues": "https://github.com/MarkBaker/PHPComplex/issues", + "source": "https://github.com/MarkBaker/PHPComplex/tree/3.0.2" + }, + "time": "2022-12-06T16:21:08+00:00" + }, + { + "name": "markbaker/matrix", + "version": "3.0.1", + "source": { + "type": "git", + "url": "https://github.com/MarkBaker/PHPMatrix.git", + "reference": "728434227fe21be27ff6d86621a1b13107a2562c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/MarkBaker/PHPMatrix/zipball/728434227fe21be27ff6d86621a1b13107a2562c", + "reference": "728434227fe21be27ff6d86621a1b13107a2562c", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "dev-master", + "phpcompatibility/php-compatibility": "^9.3", + "phpdocumentor/phpdocumentor": "2.*", + "phploc/phploc": "^4.0", + "phpmd/phpmd": "2.*", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0", + "sebastian/phpcpd": "^4.0", + "squizlabs/php_codesniffer": "^3.7" + }, + "type": "library", + "autoload": { + "psr-4": { + "Matrix\\": "classes/src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mark Baker", + "email": "mark@demon-angel.eu" + } + ], + "description": "PHP Class for working with matrices", + "homepage": "https://github.com/MarkBaker/PHPMatrix", + "keywords": [ + "mathematics", + "matrix", + "vector" + ], + "support": { + "issues": "https://github.com/MarkBaker/PHPMatrix/issues", + "source": "https://github.com/MarkBaker/PHPMatrix/tree/3.0.1" + }, + "time": "2022-12-02T22:17:43+00:00" + }, { "name": "masterminds/html5", "version": "2.10.0", @@ -4067,6 +4331,99 @@ ], "time": "2024-09-09T07:06:30+00:00" }, + { + "name": "openspout/openspout", + "version": "v4.32.0", + "source": { + "type": "git", + "url": "https://github.com/openspout/openspout.git", + "reference": "41f045c1f632e1474e15d4c7bc3abcb4a153563d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/openspout/openspout/zipball/41f045c1f632e1474e15d4c7bc3abcb4a153563d", + "reference": "41f045c1f632e1474e15d4c7bc3abcb4a153563d", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-fileinfo": "*", + "ext-filter": "*", + "ext-libxml": "*", + "ext-xmlreader": "*", + "ext-zip": "*", + "php": "~8.3.0 || ~8.4.0 || ~8.5.0" + }, + "require-dev": { + "ext-zlib": "*", + "friendsofphp/php-cs-fixer": "^3.86.0", + "infection/infection": "^0.31.2", + "phpbench/phpbench": "^1.4.1", + "phpstan/phpstan": "^2.1.22", + "phpstan/phpstan-phpunit": "^2.0.7", + "phpstan/phpstan-strict-rules": "^2.0.6", + "phpunit/phpunit": "^12.3.7" + }, + "suggest": { + "ext-iconv": "To handle non UTF-8 CSV files (if \"php-mbstring\" is not already installed or is too limited)", + "ext-mbstring": "To handle non UTF-8 CSV files (if \"iconv\" is not already installed)" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3.x-dev" + } + }, + "autoload": { + "psr-4": { + "OpenSpout\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Adrien Loison", + "email": "adrien@box.com" + } + ], + "description": "PHP Library to read and write spreadsheet files (CSV, XLSX and ODS), in a fast and scalable way", + "homepage": "https://github.com/openspout/openspout", + "keywords": [ + "OOXML", + "csv", + "excel", + "memory", + "odf", + "ods", + "office", + "open", + "php", + "read", + "scale", + "spreadsheet", + "stream", + "write", + "xlsx" + ], + "support": { + "issues": "https://github.com/openspout/openspout/issues", + "source": "https://github.com/openspout/openspout/tree/v4.32.0" + }, + "funding": [ + { + "url": "https://paypal.me/filippotessarotto", + "type": "custom" + }, + { + "url": "https://github.com/Slamdunk", + "type": "github" + } + ], + "time": "2025-09-03T16:03:54+00:00" + }, { "name": "paragonie/constant_time_encoding", "version": "v3.1.3", @@ -4282,6 +4639,112 @@ }, "time": "2025-10-06T08:47:40+00:00" }, + { + "name": "phpoffice/phpspreadsheet", + "version": "5.3.0", + "source": { + "type": "git", + "url": "https://github.com/PHPOffice/PhpSpreadsheet.git", + "reference": "4d597c1aacdde1805a33c525b9758113ea0d90df" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPOffice/PhpSpreadsheet/zipball/4d597c1aacdde1805a33c525b9758113ea0d90df", + "reference": "4d597c1aacdde1805a33c525b9758113ea0d90df", + "shasum": "" + }, + "require": { + "composer/pcre": "^1||^2||^3", + "ext-ctype": "*", + "ext-dom": "*", + "ext-fileinfo": "*", + "ext-gd": "*", + "ext-iconv": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-simplexml": "*", + "ext-xml": "*", + "ext-xmlreader": "*", + "ext-xmlwriter": "*", + "ext-zip": "*", + "ext-zlib": "*", + "maennchen/zipstream-php": "^2.1 || ^3.0", + "markbaker/complex": "^3.0", + "markbaker/matrix": "^3.0", + "php": "^8.1", + "psr/http-client": "^1.0", + "psr/http-factory": "^1.0", + "psr/simple-cache": "^1.0 || ^2.0 || ^3.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "dev-main", + "dompdf/dompdf": "^2.0 || ^3.0", + "friendsofphp/php-cs-fixer": "^3.2", + "mitoteam/jpgraph": "^10.5", + "mpdf/mpdf": "^8.1.1", + "phpcompatibility/php-compatibility": "^9.3", + "phpstan/phpstan": "^1.1 || ^2.0", + "phpstan/phpstan-deprecation-rules": "^1.0 || ^2.0", + "phpstan/phpstan-phpunit": "^1.0 || ^2.0", + "phpunit/phpunit": "^10.5", + "squizlabs/php_codesniffer": "^3.7", + "tecnickcom/tcpdf": "^6.5" + }, + "suggest": { + "dompdf/dompdf": "Option for rendering PDF with PDF Writer", + "ext-intl": "PHP Internationalization Functions, required for NumberFormat Wizard", + "mitoteam/jpgraph": "Option for rendering charts, or including charts with PDF or HTML Writers", + "mpdf/mpdf": "Option for rendering PDF with PDF Writer", + "tecnickcom/tcpdf": "Option for rendering PDF with PDF Writer" + }, + "type": "library", + "autoload": { + "psr-4": { + "PhpOffice\\PhpSpreadsheet\\": "src/PhpSpreadsheet" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Maarten Balliauw", + "homepage": "https://blog.maartenballiauw.be" + }, + { + "name": "Mark Baker", + "homepage": "https://markbakeruk.net" + }, + { + "name": "Franck Lefevre", + "homepage": "https://rootslabs.net" + }, + { + "name": "Erik Tilt" + }, + { + "name": "Adrien Crivelli" + } + ], + "description": "PHPSpreadsheet - Read, Create and Write Spreadsheet documents in PHP - Spreadsheet engine", + "homepage": "https://github.com/PHPOffice/PhpSpreadsheet", + "keywords": [ + "OpenXML", + "excel", + "gnumeric", + "ods", + "php", + "spreadsheet", + "xls", + "xlsx" + ], + "support": { + "issues": "https://github.com/PHPOffice/PhpSpreadsheet/issues", + "source": "https://github.com/PHPOffice/PhpSpreadsheet/tree/5.3.0" + }, + "time": "2025-11-24T15:47:10+00:00" + }, { "name": "phpoption/phpoption", "version": "1.9.4", @@ -6000,6 +6463,67 @@ ], "time": "2025-07-23T16:08:05+00:00" }, + { + "name": "spatie/simple-excel", + "version": "3.8.1", + "source": { + "type": "git", + "url": "https://github.com/spatie/simple-excel.git", + "reference": "80c2fd16090d28e1d0036bfac1afc6bfd8452ea3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/simple-excel/zipball/80c2fd16090d28e1d0036bfac1afc6bfd8452ea3", + "reference": "80c2fd16090d28e1d0036bfac1afc6bfd8452ea3", + "shasum": "" + }, + "require": { + "illuminate/support": "^9.0|^10.0|^11.0|^12.0", + "openspout/openspout": "^4.30", + "php": "^8.3" + }, + "require-dev": { + "pestphp/pest-plugin-laravel": "^1.3|^2.3|^3.0", + "phpunit/phpunit": "^9.4|^10.5|^11.0|^12.0", + "spatie/pest-plugin-snapshots": "^1.1|^2.1", + "spatie/phpunit-snapshot-assertions": "^4.0|^5.1", + "spatie/temporary-directory": "^1.2|^2.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "Spatie\\SimpleExcel\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Freek Van der Herten", + "email": "freek@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" + } + ], + "description": "Read and write simple Excel and CSV files", + "homepage": "https://github.com/spatie/simple-excel", + "keywords": [ + "simple-excel", + "spatie" + ], + "support": { + "source": "https://github.com/spatie/simple-excel/tree/3.8.1" + }, + "funding": [ + { + "url": "https://github.com/spatie", + "type": "github" + } + ], + "time": "2025-09-24T06:40:28+00:00" + }, { "name": "symfony/clock", "version": "v7.3.0", diff --git a/database/migrations/2025_11_26_121848_add_action_type_to_vehicle_tags_logs_table.php b/database/migrations/2025_11_26_121848_add_action_type_to_vehicle_tags_logs_table.php new file mode 100644 index 0000000..8df0efe --- /dev/null +++ b/database/migrations/2025_11_26_121848_add_action_type_to_vehicle_tags_logs_table.php @@ -0,0 +1,43 @@ +enum('action_type', [ + 'inscripcion', + 'actualizacion', + 'sustitucion', + 'cancelacion' + ])->after('tag_id')->default('cancelacion'); + + $table->foreignId('performed_by')->nullable()->after('action_type')->constrained('users')->nullOnDelete(); + $table->index('action_type'); + $table->index('vehicle_id', 'action_type'); + $table->index('performed_by'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('vehicle_tags_logs', function (Blueprint $table) { + $table->dropIndex(['action_type']); + $table->dropIndex(['vehicle_id', 'action_type']); + $table->dropIndex(['performed_by']); + + $table->dropForeign(['performed_by']); + $table->dropColumn(['action_type', 'performed_by']); + }); + } +}; diff --git a/routes/api.php b/routes/api.php index 253a3cb..5e70e80 100644 --- a/routes/api.php +++ b/routes/api.php @@ -9,6 +9,7 @@ use App\Http\Controllers\Repuve\UpdateController; use App\Http\Controllers\Repuve\CatalogNameImgController; use App\Http\Controllers\Repuve\DeviceController; +use App\Http\Controllers\Repuve\ExcelController; use App\Http\Controllers\Repuve\PackageController; use App\Http\Controllers\Repuve\TagsController; @@ -72,5 +73,6 @@ /** Rutas públicas */ // Tus rutas públicas +Route::get('excel/constancias-sustituidas', [ExcelController::class, 'constanciasSustituidas']);