From 6eeba6f9fe2fb47aa35f09de61201b6d9154ec8e Mon Sep 17 00:00:00 2001 From: Juan Felipe Zapata Moreno Date: Wed, 21 Jan 2026 12:03:45 -0600 Subject: [PATCH] =?UTF-8?q?feat:=20agregar=20generaci=C3=B3n=20de=20PDF=20?= =?UTF-8?q?para=20tags=20da=C3=B1ados=20y=20actualizar=20rutas=20de=20Exce?= =?UTF-8?q?l?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controllers/Repuve/ExcelController.php | 270 +++++++++++++++++- .../Controllers/Repuve/RecordController.php | 60 +++- resources/views/pdfs/tag_cancelada.blade.php | 146 ++++++++++ routes/api.php | 2 + 4 files changed, 468 insertions(+), 10 deletions(-) create mode 100644 resources/views/pdfs/tag_cancelada.blade.php diff --git a/app/Http/Controllers/Repuve/ExcelController.php b/app/Http/Controllers/Repuve/ExcelController.php index 20d8092..16d0061 100644 --- a/app/Http/Controllers/Repuve/ExcelController.php +++ b/app/Http/Controllers/Repuve/ExcelController.php @@ -28,27 +28,285 @@ */ class ExcelController extends Controller { - public function constanciasSustituidas(Request $request) + + public function vehicleActualizaciones(Request $request) { // VALIDACIÓN Y OBTENCIÓN DE DATOS $request->validate([ 'fecha_inicio' => 'required|date', 'fecha_fin' => 'required|date|after_or_equal:fecha_inicio', - 'module_id' => 'required|exists:modules,id', + 'module_id' => 'nullable|exists:modules,id', ]); $fechaInicio = Carbon::parse($request->fecha_inicio)->startOfDay(); $fechaFin = Carbon::parse($request->fecha_fin)->endOfDay(); $moduleId = $request->module_id; - $module = Module::findOrFail($moduleId); + $module = $moduleId ? Module::find($moduleId) : null; + + // Consulta de Logs de actualizaciones + $logs = VehicleTagLog::with(['vehicle', 'tag']) + ->where('action_type', 'actualizacion') + ->when($moduleId, function ($query) use ($moduleId) { + $query->whereHas('vehicle.records', function ($q) use ($moduleId) { + $q->where('module_id', $moduleId); + }); + }) + ->whereBetween('created_at', [$fechaInicio, $fechaFin]) + ->orderBy('created_at', 'asc') + ->get(); + + if ($logs->isEmpty()) { + return response()->json(['message' => 'No se encontraron registros de actualizaciones.'], 404); + } + + // PREPARACIÓN DE DATOS + $data = $logs->map(function ($log) { + return [ + 'niv' => $log->vehicle->niv ?? '', + 'marca' => $log->vehicle->marca ?? '', + 'placa' => $log->vehicle->placa ?? '', + 'modelo' => $log->vehicle->modelo ?? '', + 'folio' => $log->tag->folio ?? '', + 'chip' => $log->tag->tag_number ?? '', + 'fecha' => $log->created_at->format('d/m/Y'), + ]; + }); + + // CONFIGURACIÓN INICIAL DEL EXCEL + $fileName = 'Constancias_Actualizadas_' . $fechaInicio->format('Ymd') . '.xlsx'; + $tempPath = storage_path('app/temp/'); + $filePath = $tempPath . $fileName; + + if (!file_exists($tempPath)) { + mkdir($tempPath, 0755, true); + } + + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + + // Fuente Global + $sheet->getParent()->getDefaultStyle()->getFont()->setName('Montserrat'); + $sheet->getParent()->getDefaultStyle()->getFont()->setSize(10); + + // Estilo para cajas de texto + $styleBox = [ + 'borders' => [ + 'allBorders' => ['borderStyle' => PhpSpreadsheetBorder::BORDER_THIN, 'color' => ['rgb' => '000000']] + ], + 'alignment' => ['horizontal' => Alignment::HORIZONTAL_CENTER, 'vertical' => Alignment::VERTICAL_CENTER] + ]; + + // Estilo para etiquetas + $styleLabel = [ + 'font' => ['bold' => true, 'size' => 14], + 'alignment' => ['horizontal' => Alignment::HORIZONTAL_RIGHT, 'vertical' => Alignment::VERTICAL_CENTER] + ]; + + // Estilo Encabezado Tabla + $styleTableHeader = [ + 'font' => ['bold' => true, 'size' => 9, 'color' => ['rgb' => '000000']], + 'fill' => ['fillType' => Fill::FILL_SOLID, 'startColor' => ['rgb' => 'F2DCDB']], + 'alignment' => ['horizontal' => Alignment::HORIZONTAL_CENTER, 'vertical' => Alignment::VERTICAL_CENTER, 'wrapText' => true], + 'borders' => ['allBorders' => ['borderStyle' => PhpSpreadsheetBorder::BORDER_THIN]] + ]; + + // ESTRUCTURA DEL DOCUMENTO + $sheet->getRowDimension(1)->setRowHeight(10); + $sheet->getRowDimension(2)->setRowHeight(55); + $sheet->getRowDimension(5)->setRowHeight(30); + $sheet->getRowDimension(7)->setRowHeight(38); + $sheet->getRowDimension(9)->setRowHeight(30); + + // LOGO izquierdo + $logoPath = storage_path('app/images/logo_excel.png'); + if (file_exists($logoPath)) { + $drawing = new Drawing(); + $drawing->setName('Logo'); + $drawing->setPath($logoPath); + $drawing->setHeight(55); + $drawing->setCoordinates('B2'); + $drawing->setWorksheet($sheet); + } + + // LOGO derecho + $logoPath = storage_path('app/images/repuve_excel.png'); + if (file_exists($logoPath)) { + $drawing = new Drawing(); + $drawing->setName('Logo'); + $drawing->setPath($logoPath); + $drawing->setHeight(55); + $drawing->setCoordinates('J2'); + $drawing->setWorksheet($sheet); + } + + // --- BLOQUE DE INFORMACIÓN --- + + // Fila 5: ENTIDAD + $sheet->setCellValue('B5', 'ENTIDAD:'); + $sheet->getStyle('B5')->applyFromArray($styleLabel); + $sheet->getStyle('B5')->getFont()->setBold(false)->setSize(14); + + $sheet->mergeCells('C5:I5'); + $sheet->setCellValue('C5', 'TABASCO'); + $sheet->getStyle('C5:I5')->applyFromArray($styleBox); + $sheet->getStyle('C5')->getFont()->setBold(true)->setSize(14); + + // Fila 7: MÓDULO + $sheet->setCellValue('B7', 'MÓDULO:'); + $sheet->getStyle('B7')->applyFromArray($styleLabel); + $sheet->getStyle('B7')->getFont()->setBold(false)->setSize(14); + + $sheet->mergeCells('C7:I7'); + $sheet->setCellValue('C7', mb_strtoupper($module->name ?? 'TODOS LOS MÓDULOS', 'UTF-8')); + $sheet->getStyle('C7:I7')->applyFromArray($styleBox); + $sheet->getStyle('C7')->getFont()->setBold(true)->setSize(20); + + // Fila 9: PERIODO A INFORMAR + $sheet->setCellValue('B9', 'PERIODO A INFORMAR:'); + $sheet->getStyle('B9')->applyFromArray($styleLabel); + $sheet->getStyle('B9')->getFont()->setBold(false)->setSize(14); + + Carbon::setLocale('es'); + if ($fechaInicio->format('m/Y') === $fechaFin->format('m/Y')) { + $periodoTexto = 'del ' . $fechaInicio->format('d') . ' al ' . $fechaFin->format('d') . ' de ' . $fechaFin->translatedFormat('F'); + $anioTexto = $fechaFin->format('Y'); + } elseif ($fechaInicio->format('Y') === $fechaFin->format('Y')) { + $periodoTexto = 'del ' . $fechaInicio->format('d') . ' de ' . $fechaInicio->translatedFormat('F') . ' al ' . $fechaFin->format('d') . ' de ' . $fechaFin->translatedFormat('F'); + $anioTexto = $fechaFin->format('Y'); + } else { + $periodoTexto = 'del ' . $fechaInicio->format('d') . ' de ' . $fechaInicio->translatedFormat('F') . ' ' . $fechaInicio->format('Y') . ' al ' . $fechaFin->format('d') . ' de ' . $fechaFin->translatedFormat('F'); + $anioTexto = $fechaFin->format('Y'); + } + + // Fechas + $sheet->mergeCells('C9:F9'); + $sheet->setCellValue('C9', $periodoTexto); + $sheet->getStyle('C9:F9')->applyFromArray($styleBox); + $sheet->getStyle('C9')->getFont()->setSize(14); + + // Conector "de" + $sheet->setCellValue('G9', 'de'); + $sheet->getStyle('G9')->getAlignment()->setHorizontal(Alignment::HORIZONTAL_CENTER)->setVertical(Alignment::VERTICAL_CENTER); + $sheet->getStyle('G9')->getFont()->setSize(14); + + // Año + $sheet->mergeCells('H9:I9'); + $sheet->setCellValue('H9', $anioTexto); + $sheet->getStyle('H9:I9')->applyFromArray($styleBox); + $sheet->getStyle('H9')->getFont()->setSize(14); + + // TÍTULO + $rowTitle = 11; + $sheet->mergeCells("A{$rowTitle}:I{$rowTitle}"); + $sheet->setCellValue("A{$rowTitle}", 'CONSTANCIAS ACTUALIZADAS'); + $sheet->getStyle("A{$rowTitle}")->applyFromArray([ + 'font' => ['bold' => true, 'color' => ['rgb' => '000000'], 'size' => 14], + 'alignment' => ['horizontal' => Alignment::HORIZONTAL_CENTER, 'vertical' => Alignment::VERTICAL_CENTER], + ]); + $sheet->getRowDimension($rowTitle)->setRowHeight(25); + + // BARRA ROJA + $rowRedBar = 12; + $sheet->mergeCells("A{$rowRedBar}:I{$rowRedBar}"); + $sheet->getStyle("A{$rowRedBar}")->applyFromArray([ + 'fill' => ['fillType' => Fill::FILL_SOLID, 'startColor' => ['rgb' => '900000']], + ]); + $sheet->getRowDimension($rowRedBar)->setRowHeight(6); + + // --- ENCABEZADOS DE TABLA --- + $h1 = 13; + $h2 = 14; + + $headers = [ + 'A' => 'No.', + 'B' => 'NIV DEL VEHÍCULO', + 'C' => 'MARCA DEL VEHÍCULO', + 'D' => 'PLACA', + 'E' => "AÑO\nMODELO", + 'F' => "FOLIO\nCONSTANCIA", + 'G' => 'ID DE LA CONSTANCIA (CHIP)', + 'H' => "FECHA\nACTUALIZACIÓN", + ]; + + foreach ($headers as $col => $text) { + $sheet->mergeCells("{$col}{$h1}:{$col}{$h2}"); + $sheet->setCellValue("{$col}{$h1}", $text); + } + + $sheet->getStyle("A{$h1}:H{$h2}")->applyFromArray($styleTableHeader); + $sheet->getRowDimension($h1)->setRowHeight(20); + $sheet->getRowDimension($h2)->setRowHeight(25); + + // --- LLENADO DE DATOS --- + $row = 15; + $i = 1; + + foreach ($data as $item) { + $sheet->setCellValue('A' . $row, $i); + $sheet->setCellValue('B' . $row, $item['niv']); + $sheet->setCellValue('C' . $row, $item['marca']); + $sheet->setCellValue('D' . $row, $item['placa']); + $sheet->setCellValue('E' . $row, $item['modelo']); + $sheet->setCellValue('F' . $row, $item['folio']); + $sheet->setCellValue('G' . $row, $item['chip']); + $sheet->setCellValue('H' . $row, $item['fecha']); + + $sheet->getStyle("A{$row}:H{$row}")->applyFromArray([ + 'borders' => ['allBorders' => ['borderStyle' => PhpSpreadsheetBorder::BORDER_THIN]], + 'alignment' => ['vertical' => Alignment::VERTICAL_CENTER, 'wrapText' => true], + 'font' => ['size' => 9] + ]); + + // Centrados específicos + $sheet->getStyle("A{$row}")->getAlignment()->setHorizontal(Alignment::HORIZONTAL_CENTER); + $sheet->getStyle("E{$row}")->getAlignment()->setHorizontal(Alignment::HORIZONTAL_CENTER); + $sheet->getStyle("F{$row}")->getAlignment()->setHorizontal(Alignment::HORIZONTAL_CENTER); + $sheet->getStyle("H{$row}")->getAlignment()->setHorizontal(Alignment::HORIZONTAL_CENTER); + + $row++; + $i++; + } + + // Anchos de columna + $sheet->getColumnDimension('A')->setWidth(5); + $sheet->getColumnDimension('B')->setWidth(23); + $sheet->getColumnDimension('C')->setWidth(20); + $sheet->getColumnDimension('D')->setWidth(13); + $sheet->getColumnDimension('E')->setWidth(10); + $sheet->getColumnDimension('F')->setWidth(15); + $sheet->getColumnDimension('G')->setWidth(30); + $sheet->getColumnDimension('H')->setWidth(16); + + $writer = new Xlsx($spreadsheet); + $writer->save($filePath); + + return response()->download($filePath, $fileName)->deleteFileAfterSend(true); + } + + public function constanciasSustituidas(Request $request) + { + // VALIDACIÓN Y OBTENCIÓN DE DATOS + $request->validate([ + 'fecha_inicio' => 'required|date', + 'fecha_fin' => 'required|date|after_or_equal:fecha_inicio', + 'module_id' => 'nullable|exists:modules,id', + ]); + + $fechaInicio = Carbon::parse($request->fecha_inicio)->startOfDay(); + $fechaFin = Carbon::parse($request->fecha_fin)->endOfDay(); + $moduleId = $request->module_id; + + $module = $moduleId ? Module::find($moduleId) : null; // Consulta de Logs $logs = VehicleTagLog::with(['vehicle', 'tag', 'cancellationReason']) ->where('action_type', 'sustitucion') ->whereNotNull('cancellation_at') - ->whereHas('vehicle.records', function ($query) use ($moduleId) { - $query->where('module_id', $moduleId); + ->when($moduleId, function ($query) use ($moduleId) { + $query->whereHas('vehicle.records', function ($q) use ($moduleId) { + $q->where('module_id', $moduleId); + }); }) ->whereBetween('created_at', [$fechaInicio, $fechaFin]) ->orderBy('created_at', 'asc') @@ -171,7 +429,7 @@ public function constanciasSustituidas(Request $request) $sheet->getStyle('B7')->getFont()->setBold(false)->setSize(14); $sheet->mergeCells('C7:I7'); - $sheet->setCellValue('C7', mb_strtoupper($module->name, 'UTF-8')); + $sheet->setCellValue('C7', mb_strtoupper($module->name ?? 'TODOS LOS MÓDULOS', 'UTF-8')); $sheet->getStyle('C7:I7')->applyFromArray($styleBox); $sheet->getStyle('C7')->getFont()->setBold(true)->setSize(20); diff --git a/app/Http/Controllers/Repuve/RecordController.php b/app/Http/Controllers/Repuve/RecordController.php index 0156807..bc602a9 100644 --- a/app/Http/Controllers/Repuve/RecordController.php +++ b/app/Http/Controllers/Repuve/RecordController.php @@ -247,6 +247,49 @@ public function generatePdfForm($id) } } + public function pdfDamagedTag(Tag $tag) + { + try{ + $tag->load('status'); + + if(!$tag->status){ + return ApiResponse::NOT_FOUND->response([ + 'message' => 'El tag no tiene un estado asociado.', + 'tag_id' => $tag->id, + ]); + } + + // Validar que el tag esté cancelado + if (!$tag->isDamaged()) { + return ApiResponse::BAD_REQUEST->response([ + 'message' => 'Solo se puede generar PDF para tags dañados.', + 'current_status' => $tag->status->name, + ]); + } + + // Obtener datos de cancelación + $cancellationData = $this->cancellationData($tag); + + $pdf = Pdf::loadView('pdfs.tag', [ + 'cancellation' => $cancellationData, + ]) + ->setPaper('a4', 'portrait') + ->setOptions([ + 'defaultFont' => 'sans-serif', + 'isHtml5ParserEnabled' => true, + 'isRemoteEnabled' => true, + ]); + + return $pdf->stream('constancia_dañada_' . $tag->tag_number . '.pdf'); + + } catch (\Exception $e) { + return ApiResponse::INTERNAL_ERROR->response([ + 'message' => 'Error al generar el PDF.', + 'error' => $e->getMessage(), + ]); + } + } + public function pdfCancelledTag(Tag $tag) { try { @@ -270,7 +313,7 @@ public function pdfCancelledTag(Tag $tag) // Obtener datos de cancelación $cancellationData = $this->cancellationData($tag); - $pdf = Pdf::loadView('pdfs.tag', [ + $pdf = Pdf::loadView('pdfs.tag_cancelada', [ 'cancellation' => $cancellationData, ]) ->setPaper('a4', 'portrait') @@ -359,15 +402,24 @@ private function cancellationData(Tag $tag) $data = [ 'fecha' => now()->format('d/m/Y'), 'folio' => $tag->folio ?? '', - 'id_chip' => '', + 'tag_number' => $tag->tag_number ?? '', 'placa' => '', 'niv' => '', 'motivo' => 'N/A', 'operador' => 'N/A', - 'modulo' => '', - 'ubicacion' => '', + 'modulo' => 'No especificado', + 'ubicacion' => 'No especificado', ]; + // Cargar módulo del tag si existe + if ($tag->module_id) { + $tag->load('module'); + if ($tag->module) { + $data['modulo'] = $tag->module->name ?? ''; + $data['ubicacion'] = $tag->module->address ?? ''; + } + } + // Intentar obtener datos del vehículo si existe if ($tag->vehicle_id && $tag->vehicle) { $data['id_chip'] = $tag->vehicle->id_chip ?? ''; diff --git a/resources/views/pdfs/tag_cancelada.blade.php b/resources/views/pdfs/tag_cancelada.blade.php new file mode 100644 index 0000000..1d107b7 --- /dev/null +++ b/resources/views/pdfs/tag_cancelada.blade.php @@ -0,0 +1,146 @@ + + + + + Constancia Cancelada - REPUVE Tabasco + + + +
+ +
+ REPUVE TABASCO +
+ + +
+ CONSTANCIA CANCELADA +
+ + +
+
+ PEGAR CONSTANCIA +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FECHA:{{ $cancellation['fecha'] }}
FOLIO:{{ $cancellation['folio'] ?? '' }}
TAG NUMBER:{{ $cancellation['tag_number'] ?? 'N/A' }}
MOTIVO:{{ mb_strtoupper($cancellation['motivo'] ?? '') }}
OPERADOR:{{ mb_strtoupper($cancellation['operador'] ?? '') }}
MÓDULO:{{ $cancellation['modulo']}}
UBICACIÓN:{{ $cancellation['ubicacion'] }}
+
+ + diff --git a/routes/api.php b/routes/api.php index 440cd8e..ad5a352 100644 --- a/routes/api.php +++ b/routes/api.php @@ -44,6 +44,7 @@ Route::get('expediente/{id}/pdfConstancia', [RecordController::class, 'generatePdfConstancia']); Route::get('expediente/{id}/pdfImagenes', [RecordController::class, 'generatePdfImages']); Route::get('tags/{id}/pdfTag-sustituido', [RecordController::class, 'pdfSubstitutedTag']); + Route::get('tags/{tag}/pdfTag-damaged', [RecordController::class, 'pdfDamagedTag']); Route::get('tags/{tag}/pdfTag-cancelado', [RecordController::class, 'pdfCancelledTag']); Route::get('expediente/{id}/pdfFormulario', [RecordController::class, 'generatePdfForm']); Route::get('RecordErrors', [RecordController::class, 'errors']); @@ -60,6 +61,7 @@ Route::post('tags/cancelar', [CancellationController::class, 'cancelarTagNoAsignado']); Route::get('excel/constancias-sustituidas', [ExcelController::class, 'constanciasSustituidas']); Route::get('excel/constancias-canceladas', [ExcelController::class, 'constanciasCanceladas']); + Route::get('excel/constancias-actualizadas', [ExcelController::class, 'vehicleActualizaciones']); Route::get('excel/constancias-general', [ExcelController::class, 'excelGeneral']); //Rutas de Modulos