repuveService = $repuveService; } /** * Middleware */ public static function middleware(): array { return [ self::can('reports.vehicle_updates.index', ['vehicleActualizaciones']), self::can('reports.substitutions.index', ['constanciasSustituidas']), self::can('reports.cancellations.index', ['constanciasCanceladas']), self::can('reports.general.index', ['excelGeneral']), self::can('reports.search_records.index', ['exportSearchRecords']), ]; } 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' => '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 de actualizaciones (último por vehículo) $logs = VehicleTagLog::with(['vehicle', 'tag', 'performedBy.module']) ->where('action_type', 'actualizacion') ->when($moduleId, function ($query) use ($moduleId) { $query->whereHas('performedBy', function ($q) use ($moduleId) { $q->where('module_id', $moduleId); }); }) ->whereBetween('created_at', [$fechaInicio, $fechaFin]) ->orderBy('created_at', 'desc') ->get() ->unique('vehicle_id') ->sortBy('created_at') ->values(); 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' => substr($log->tag->rfid ?? $log->tag->tag_number ?? '', 0, 24), '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}:H{$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(function ($query) use ($fechaInicio, $fechaFin) { // sustitucion normal: el log del TAG viejo tiene cancellation_at $query->where(function ($q) use ($fechaInicio, $fechaFin) { $q->where('action_type', 'sustitucion') ->whereNotNull('cancellation_at') ->whereBetween('cancellation_at', [$fechaInicio, $fechaFin]); }) // sustitucion_primera_vez: un solo log sin cancellation_at, filtrar por created_at ->orWhere(function ($q) use ($fechaInicio, $fechaFin) { $q->where('action_type', 'sustitucion_primera_vez') ->whereNull('cancellation_at') ->whereBetween('created_at', [$fechaInicio, $fechaFin]); }); }) ->when($moduleId, function ($query) use ($moduleId) { $query->whereHas('performedBy', function ($q) use ($moduleId) { $q->where('module_id', $moduleId); }); }) ->orderByRaw('COALESCE(cancellation_at, created_at) ASC') ->get(); if ($logs->isEmpty()) { return response()->json(['message' => 'No se encontraron registros.'], 404); } // PREPARACIÓN DE DATOS $data = $logs->map(function ($log) { // sustitucion_primera_vez: un solo log, el log mismo ES el nuevo TAG asignado if ($log->action_type === 'sustitucion_primera_vez') { return [ 'niv' => $log->vehicle->niv ?? '', 'nrpv' => $log->vehicle->nrpv ?? '', 'marca' => $log->vehicle->marca ?? '', 'placa' => $log->vehicle->placa ?? '', 'modelo' => $log->vehicle->modelo ?? '', 'folio_ant' => '', 'folio_act' => $log->tag->folio ?? '', 'chip' => substr($log->tag->rfid ?? $log->tag->tag_number ?? '', 0, 24), 'fecha' => $log->created_at->format('d/m/Y'), 'observaciones' => $log->cancellationReason?->name ?? $log->cancellation_observations ?? '', ]; } // sustitucion normal: este log es el TAG viejo cancelado, buscar el nuevo TAG $newTagLog = VehicleTagLog::with('tag') ->where('vehicle_id', $log->vehicle_id) ->whereIn('action_type', ['sustitucion_primera_vez', 'sustitucion']) ->whereNull('cancellation_at') ->where('id', '!=', $log->id) ->where('created_at', '>=', $log->created_at) ->orderBy('created_at', 'asc') ->first(); // Consultar REPUVE Nacional para obtener el folio_CI que se sustituyó $repuveData = $this->repuveService->consultarVehiculo($log->vehicle->niv, $log->vehicle->placa); $folioCi = $repuveData['folio_CI'] ?? ''; return [ 'niv' => $log->vehicle->niv ?? '', 'nrpv' => $log->vehicle->nrpv ?? '', 'marca' => $log->vehicle->marca ?? '', 'placa' => $log->vehicle->placa ?? '', 'modelo' => $log->vehicle->modelo ?? '', 'folio_ant' => $folioCi, 'folio_act' => $newTagLog?->tag?->folio ?? '', 'chip' => substr($newTagLog?->tag?->rfid ?? $newTagLog?->tag?->tag_number ?? '', 0, 24), 'fecha' => $log->cancellation_at?->format('d/m/Y') ?? $log->created_at->format('d/m/Y'), 'observaciones' => $log->cancellationReason?->name ?? $log->cancellation_observations ?? '', ]; }); // 3. CONFIGURACIÓN INICIAL DEL EXCEL $fileName = 'Constancias_Sustituidas_' . $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 // Ajuste de altura para filas superiores $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 $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); } $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('M2'); $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'); // Formato completo para manejar diferentes meses/años if ($fechaInicio->format('m/Y') === $fechaFin->format('m/Y')) { // Mismo mes y año $periodoTexto = 'del ' . $fechaInicio->format('d') . ' al ' . $fechaFin->format('d') . ' de ' . $fechaFin->translatedFormat('F'); $anioTexto = $fechaFin->format('Y'); } elseif ($fechaInicio->format('Y') === $fechaFin->format('Y')) { // Diferente mes, mismo año $periodoTexto = 'del ' . $fechaInicio->format('d') . ' de ' . $fechaInicio->translatedFormat('F') . ' al ' . $fechaFin->format('d') . ' de ' . $fechaFin->translatedFormat('F'); $anioTexto = $fechaFin->format('Y'); } else { // Diferente año $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}:K{$rowTitle}"); $sheet->setCellValue("A{$rowTitle}", 'CONSTANCIAS SUSTITUIDAS'); $sheet->getStyle("A{$rowTitle}")->applyFromArray([ 'font' => ['bold' => true, 'color' => ['rgb' => '000000'], 'size' => 14], // Texto Negro 'alignment' => ['horizontal' => Alignment::HORIZONTAL_CENTER, 'vertical' => Alignment::VERTICAL_CENTER], ]); $sheet->getRowDimension($rowTitle)->setRowHeight(25); // BARRA ROJA $rowRedBar = 13; $sheet->mergeCells("A{$rowRedBar}:K{$rowRedBar}"); $sheet->getStyle("A{$rowRedBar}")->applyFromArray([ 'fill' => ['fillType' => Fill::FILL_SOLID, 'startColor' => ['rgb' => '900000']], // Rojo Oscuro ]); $sheet->getRowDimension($rowRedBar)->setRowHeight(20); // --- ENCABEZADOS DE TABLA --- $h1 = 14; $h2 = 15; $headers = [ 'A' => 'No.', 'B' => 'NIV DEL VEHÍCULO', 'C' => 'NRPV/NCI', 'D' => 'MARCA DEL VEHÍCULO', 'E' => 'PLACA', 'F' => "AÑO\nMODELO", 'I' => 'ID DE LA CONSTANCIA (CHIP)', 'J' => "FECHA\nREEMPLAZO", 'K' => "OBSERVACIONES (MOTIVO\nDEL REEMPLAZO)" ]; foreach ($headers as $col => $text) { $sheet->mergeCells("{$col}{$h1}:{$col}{$h2}"); $sheet->setCellValue("{$col}{$h1}", $text); } $sheet->mergeCells("G{$h1}:H{$h1}"); $sheet->setCellValue("G{$h1}", 'FOLIO'); $sheet->setCellValue("G{$h2}", 'ANTERIOR'); $sheet->setCellValue("H{$h2}", 'ACTUAL'); $sheet->getStyle("A{$h1}:K{$h2}")->applyFromArray($styleTableHeader); $sheet->getRowDimension($h1)->setRowHeight(20); $sheet->getRowDimension($h2)->setRowHeight(25); // --- LLENADO DE DATOS --- $row = 16; $i = 1; foreach ($data as $item) { $sheet->setCellValue('A' . $row, $i); $sheet->setCellValue('B' . $row, $item['niv']); $sheet->setCellValue('C' . $row, $item['nrpv']); $sheet->setCellValue('D' . $row, $item['marca']); $sheet->setCellValue('E' . $row, $item['placa']); $sheet->setCellValue('F' . $row, $item['modelo']); $sheet->setCellValue('G' . $row, $item['folio_ant']); $sheet->setCellValue('H' . $row, $item['folio_act']); $sheet->setCellValue('I' . $row, $item['chip']); $sheet->setCellValue('J' . $row, $item['fecha']); $sheet->setCellValue('K' . $row, $item['observaciones']); $sheet->getStyle("A{$row}:K{$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("F{$row}")->getAlignment()->setHorizontal(Alignment::HORIZONTAL_CENTER); $sheet->getStyle("G{$row}:H{$row}")->getAlignment()->setHorizontal(Alignment::HORIZONTAL_CENTER); $sheet->getStyle("J{$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(14); $sheet->getColumnDimension('D')->setWidth(20); $sheet->getColumnDimension('E')->setWidth(13); $sheet->getColumnDimension('F')->setWidth(9); $sheet->getColumnDimension('G')->setWidth(13); $sheet->getColumnDimension('H')->setWidth(13); $sheet->getColumnDimension('I')->setWidth(30); $sheet->getColumnDimension('J')->setWidth(14); $sheet->getColumnDimension('K')->setWidth(35); $writer = new Xlsx($spreadsheet); $writer->save($filePath); return response()->download($filePath, $fileName)->deleteFileAfterSend(true); } public function constanciasCanceladas(Request $request) { // 1. 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; // Información del módulo $module = $moduleId ? Module::find($moduleId) : null; $logs = VehicleTagLog::with(['vehicle', 'tag', 'cancellationReason']) ->where('action_type', 'cancelacion') ->when($moduleId, function ($query) use ($moduleId) { $query->whereHas('vehicle.records', function ($q) use ($moduleId) { $q->where('module_id', $moduleId); }); }) ->whereBetween('cancellation_at', [$fechaInicio, $fechaFin]) ->get(); $logsT = TagCancellationLog::with(['tag.vehicle', 'cancellationReason']) ->when($moduleId, function ($query) use ($moduleId) { $query->whereHas('tag', function ($q) use ($moduleId) { $q->where('module_id', $moduleId); }); }) ->whereBetween('cancellation_at', [$fechaInicio, $fechaFin]) ->get(); if ($logs->isEmpty() && $logsT->isEmpty()) { return response()->json(['message' => 'No se encontraron constancias canceladas.'], 404); } // Unir y ordenar $allLogs = $logs->concat($logsT)->sortBy('cancellation_at'); // MAPEO DE DATOS $data = $allLogs->map(function ($log) { return [ 'folio' => $log->tag->folio ?? 'S/F', 'tag_number' => substr( $log->tag->tag_number ?? '', 0, 24) ?? 'N/A', 'fecha' => $log->cancellation_at ? Carbon::parse($log->cancellation_at)->format('d/m/Y') : '', 'motivo' => $log->cancellationReason->name ?? 'DAÑADA', ]; }); // CONFIGURACIÓN EXCEL Y ESTILOS $fileName = 'Constancias_dañadas_' . $fechaInicio->format('Ymd') . '.xlsx'; $filePath = storage_path('app/temp/' . $fileName); if (!file_exists(dirname($filePath))) mkdir(dirname($filePath), 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 // Ajuste de altura para filas superiores $sheet->getRowDimension(2)->setRowHeight(10); $sheet->getRowDimension(3)->setRowHeight(55); $sheet->getRowDimension(6)->setRowHeight(30); $sheet->getRowDimension(8)->setRowHeight(38); $sheet->getRowDimension(10)->setRowHeight(30); // LOGO $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); } $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('M2'); $drawing->setWorksheet($sheet); } // --- BLOQUE DE INFORMACIÓN --- // Fila 5: ENTIDAD $sheet->mergeCells('A5:C5'); // Combinar A-C para la etiqueta $sheet->setCellValue('A5', 'ENTIDAD:'); $sheet->getStyle('A5')->applyFromArray($styleLabel); $sheet->getStyle('A5')->getFont()->setBold(false)->setSize(14); $sheet->mergeCells('D5:M5'); // El valor ocupa el resto $sheet->setCellValue('D5', 'TABASCO'); $sheet->getStyle('D5:M5')->applyFromArray($styleBox); $sheet->getStyle('D5')->getFont()->setBold(true)->setSize(14); // Fila 7: MÓDULO $sheet->mergeCells('A7:C7'); $sheet->setCellValue('A7', 'MÓDULO:'); $sheet->getStyle('A7')->applyFromArray($styleLabel); $sheet->getStyle('A7')->getFont()->setBold(false)->setSize(14); $sheet->mergeCells('D7:M7'); $sheet->setCellValue('D7', mb_strtoupper($module->name ?? 'TODOS LOS MÓDULOS', 'UTF-8')); $sheet->getStyle('D7:M7')->applyFromArray($styleBox); $sheet->getStyle('D7')->getFont()->setBold(true)->setSize(20); // Fila 9: PERIODO A INFORMAR $sheet->mergeCells('A9:C9'); $sheet->setCellValue('A9', 'PERIODO A INFORMAR:'); $sheet->getStyle('A9')->applyFromArray($styleLabel); $sheet->getStyle('A9')->getFont()->setBold(false)->setSize(14); Carbon::setLocale('es'); // Formato completo para manejar diferentes meses/años if ($fechaInicio->format('m/Y') === $fechaFin->format('m/Y')) { // Mismo mes y año $periodoTexto = 'del ' . $fechaInicio->format('d') . ' al ' . $fechaFin->format('d') . ' de ' . $fechaFin->translatedFormat('F'); $anioTexto = $fechaFin->format('Y'); } elseif ($fechaInicio->format('Y') === $fechaFin->format('Y')) { // Diferente mes, mismo año $periodoTexto = 'del ' . $fechaInicio->format('d') . ' de ' . $fechaInicio->translatedFormat('F') . ' al ' . $fechaFin->format('d') . ' de ' . $fechaFin->translatedFormat('F'); $anioTexto = $fechaFin->format('Y'); } else { // Diferente año $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 (Movemos a D-G) $sheet->mergeCells('D9:G9'); $sheet->setCellValue('D9', $periodoTexto); $sheet->getStyle('D9:G9')->applyFromArray($styleBox); $sheet->getStyle('D9')->getFont()->setSize(14); // "de" (Movemos a H) $sheet->setCellValue('H9', 'de'); $sheet->getStyle('H9')->getAlignment()->setHorizontal(Alignment::HORIZONTAL_CENTER)->setVertical(Alignment::VERTICAL_CENTER); $sheet->getStyle('H9')->getFont()->setSize(14); // Año (Movemos a I-J) $sheet->mergeCells('I9:J9'); $sheet->setCellValue('I9', $anioTexto); $sheet->getStyle('I9:J9')->applyFromArray($styleBox); $sheet->getStyle('I9')->getFont()->setSize(14); // --- TÍTULO Y BARRA DECORATIVA --- $rowTitle = 11; $sheet->mergeCells("A{$rowTitle}:M{$rowTitle}"); $sheet->setCellValue("A{$rowTitle}", 'CONSTANCIAS DAÑADAS (NO FUERON SUSTITUIDAS)'); $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 = 13; $sheet->mergeCells("A{$rowRedBar}:M{$rowRedBar}"); $sheet->getStyle("A{$rowRedBar}")->applyFromArray([ 'fill' => ['fillType' => Fill::FILL_SOLID, 'startColor' => ['rgb' => '900000']], ]); $sheet->getRowDimension($rowRedBar)->setRowHeight(6); // --- ENCABEZADOS DE TABLA --- $h1 = 14; $h2 = 15; // Definir columnas expandidas $sheet->mergeCells("A{$h1}:B{$h2}"); $sheet->setCellValue("A{$h1}", 'No.'); $sheet->mergeCells("C{$h1}:F{$h2}"); $sheet->setCellValue("C{$h1}", "FOLIO DE LA\nCONSTANCIA"); $sheet->mergeCells("G{$h1}:I{$h2}"); $sheet->setCellValue("G{$h1}", "ID DE LA\nCONSTANCIA (CHIP)"); $sheet->mergeCells("J{$h1}:K{$h2}"); $sheet->setCellValue("J{$h1}", "FECHA DE\nVINCULACIÓN"); $sheet->mergeCells("L{$h1}:N{$h2}"); $sheet->setCellValue("L{$h1}", "OBSERVACIONES \n(MOTIVO DEL DAÑO, ERRORES, ETC)"); // Aplicar estilo beige $sheet->getStyle("A{$h1}:N{$h2}")->applyFromArray($styleTableHeader); $sheet->getRowDimension($h1)->setRowHeight(20); $sheet->getRowDimension($h2)->setRowHeight(25); // --- LLENADO DE DATOS --- $row = 16; $i = 1; foreach ($data as $item) { $sheet->mergeCells("A{$row}:B{$row}"); $sheet->setCellValue("A{$row}", $i); $sheet->mergeCells("C{$row}:F{$row}"); $sheet->setCellValue("C{$row}", $item['folio']); $sheet->mergeCells("G{$row}:I{$row}"); $sheet->setCellValue("G{$row}", $item['tag_number']); $sheet->mergeCells("J{$row}:K{$row}"); $sheet->setCellValue("J{$row}", $item['fecha']); $sheet->mergeCells("L{$row}:N{$row}"); $sheet->setCellValue("L{$row}", $item['motivo']); // Estilos de celda $sheet->getStyle("A{$row}:N{$row}")->applyFromArray([ 'borders' => ['allBorders' => ['borderStyle' => PhpSpreadsheetBorder::BORDER_THIN]], 'alignment' => ['horizontal' => Alignment::HORIZONTAL_CENTER, 'vertical' => Alignment::VERTICAL_CENTER, 'wrapText' => true], 'font' => ['size' => 9] ]); $row++; $i++; } // --- ANCHOS DE COLUMNA --- $sheet->getColumnDimension('A')->setWidth(5); $sheet->getColumnDimension('B')->setWidth(5); $sheet->getColumnDimension('C')->setWidth(22); $sheet->getColumnDimension('D')->setWidth(10); $sheet->getColumnDimension('E')->setWidth(10); $sheet->getColumnDimension('F')->setWidth(10); $sheet->getColumnDimension('G')->setWidth(10); $sheet->getColumnDimension('H')->setWidth(10); $sheet->getColumnDimension('I')->setWidth(10); $sheet->getColumnDimension('J')->setWidth(15); $sheet->getColumnDimension('K')->setWidth(15); $sheet->getColumnDimension('L')->setWidth(15); $sheet->getColumnDimension('M')->setWidth(15); // Guardar $writer = new Xlsx($spreadsheet); $writer->save($filePath); return response()->download($filePath, $fileName)->deleteFileAfterSend(true); } public function excelGeneral(Request $request) { // 1. 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; // Obtener información del módulo $module = $moduleId ? Module::find($moduleId) : null; // QUERY 1: VehicleTagLogs (Inscripciones, Sustituciones, Actualizaciones, Cancelaciones de Vehículo) $logs = VehicleTagLog::with([ 'vehicle.records.module', 'tag', 'cancellationReason' ]) ->whereIn('action_type', ['sustitucion_primera_vez', 'sustitucion', 'actualizacion', 'cancelacion']) ->where(function ($query) use ($fechaInicio, $fechaFin) { $query->where(function ($q) use ($fechaInicio, $fechaFin) { $q->whereIn('action_type', ['sustitucion_primera_vez', 'sustitucion', 'actualizacion']) ->whereBetween('created_at', [$fechaInicio, $fechaFin]); }) ->orWhere(function ($q) use ($fechaInicio, $fechaFin) { $q->where('action_type', 'cancelacion') ->whereBetween('cancellation_at', [$fechaInicio, $fechaFin]); }); }) ->when($moduleId, function ($query) use ($moduleId) { $query->whereHas('vehicle.records', function ($q) use ($moduleId) { $q->where('module_id', $moduleId); }); }) ->get(); //TagCancellationLogs (Cancelaciones de Tags sueltos) $logsT = TagCancellationLog::with([ 'tag.module', 'tag.vehicle', 'cancellationReason' ]) ->whereBetween('cancellation_at', [$fechaInicio, $fechaFin]) ->when($moduleId, function ($query) use ($moduleId) { $query->whereHas('tag', function ($q) use ($moduleId) { $q->where('module_id', $moduleId); }); }) ->get(); if ($logs->isEmpty() && $logsT->isEmpty()) { return response()->json(['message' => 'No se encontraron registros en el periodo especificado'], 404); } // Unificación y Ordenamiento $allLogs = collect(); foreach ($logs as $log) { $log->sort_date = $log->action_type == 'cancelacion' ? $log->cancellation_at : $log->created_at; $log->source_type = 'vehicle_log'; // Marcador auxiliar $allLogs->push($log); } foreach ($logsT as $log) { $log->sort_date = $log->cancellation_at; $log->action_type = 'cancelacion'; // Normalizamos el tipo $log->source_type = 'tag_log'; // Marcador auxiliar $allLogs->push($log); } $allLogs = $allLogs->sortBy('sort_date'); // 2. MAPEO DE DATOS (Normalización A-M) $data = $allLogs->map(function ($log) { // Determinar Vehículo $vehicle = $log->vehicle ?? $log->tag->vehicle ?? null; // Determinar Nombre Módulo // Intentamos sacar el módulo del registro del vehículo o del tag $nombreModulo = 'S/N'; if ($log->source_type == 'vehicle_log') { // Lógica aproximada: tomar el último módulo registrado o el actual $nombreModulo = $vehicle->records->last()->module->name ?? 'GENERAL'; } else { $nombreModulo = $log->tag->module->name ?? 'GENERAL'; } // Formatear Acción $accion = ucfirst($log->action_type ?? 'Registro'); // Fechas $fecha = $log->sort_date ? Carbon::parse($log->sort_date)->format('d/m/Y') : ''; // Motivo $motivo = $log->cancellationReason->reason ?? $log->cancellationReason->name ?? ''; if ($log->action_type == 'sustitucion') $motivo = 'SUSTITUCIÓN'; if ($log->action_type == 'sustitucion_primera_vez') $motivo = 'SUSTITUCIÓN POR PRIMERA VEZ'; if ($log->action_type == 'actualizacion') $motivo = 'ACTUALIZACIÓN'; if ($log->action_type == 'cancelacion' && empty($motivo)) $motivo = 'DAÑADA'; return [ 'modulo' => mb_strtoupper($nombreModulo, 'UTF-8'), 'accion' => mb_strtoupper($accion, 'UTF-8'), 'niv' => $vehicle->niv ?? '', 'nrpv' => $vehicle->nrpv ?? '', 'marca' => $vehicle->marca ?? '', 'placa' => $vehicle->placa ?? '', 'modelo' => $vehicle->modelo ?? '', 'folio' => $log->tag->folio ?? $log->new_tag_folio ?? '', 'chip' => $log->tag->tag_number ?? '', 'fecha' => $fecha, 'motivo' => mb_strtoupper($motivo, 'UTF-8'), 'observaciones' => $log->observations ?? '' ]; }); // 3. CONFIGURACIÓN EXCEL Y ESTILOS $fileName = 'Reporte_General_' . $fechaInicio->format('Ymd') . '.xlsx'; $filePath = storage_path('app/temp/' . $fileName); if (!file_exists(dirname($filePath))) mkdir(dirname($filePath), 0755, true); $spreadsheet = new Spreadsheet(); $sheet = $spreadsheet->getActiveSheet(); // Fuente Global: Montserrat $sheet->getParent()->getDefaultStyle()->getFont()->setName('Montserrat'); $sheet->getParent()->getDefaultStyle()->getFont()->setSize(10); // Estilos Comunes $styleBox = [ 'borders' => ['allBorders' => ['borderStyle' => PhpSpreadsheetBorder::BORDER_THIN, 'color' => ['rgb' => '000000']]], 'alignment' => ['horizontal' => Alignment::HORIZONTAL_CENTER, 'vertical' => Alignment::VERTICAL_CENTER] ]; $styleLabel = [ 'font' => ['size' => 14], 'alignment' => ['horizontal' => Alignment::HORIZONTAL_RIGHT, 'vertical' => Alignment::VERTICAL_CENTER] ]; $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(2)->setRowHeight(10); $sheet->getRowDimension(3)->setRowHeight(55); // Logo space $sheet->getRowDimension(5)->setRowHeight(30); $sheet->getRowDimension(7)->setRowHeight(38); $sheet->getRowDimension(9)->setRowHeight(30); // LOGO $logoPath = storage_path('app/images/logo-seguridad.png'); if (file_exists($logoPath)) { $drawing = new Drawing(); $drawing->setName('Logo'); $drawing->setPath($logoPath); $drawing->setHeight(55); $drawing->setCoordinates('B3'); $drawing->setWorksheet($sheet); } // --- BLOQUE DE INFORMACIÓN (Encabezado con Merges A-C) --- // Fila 5: ENTIDAD $sheet->mergeCells('A5:C5'); $sheet->setCellValue('A5', 'ENTIDAD:'); $sheet->getStyle('A5')->applyFromArray($styleLabel); $sheet->mergeCells('D5:M5'); $sheet->setCellValue('D5', 'TABASCO'); $sheet->getStyle('D5:M5')->applyFromArray($styleBox); $sheet->getStyle('D5')->getFont()->setBold(true)->setSize(14); // Fila 7: MÓDULO $sheet->mergeCells('A7:C7'); $sheet->setCellValue('A7', 'MÓDULO:'); $sheet->getStyle('A7')->applyFromArray($styleLabel); $sheet->mergeCells('D7:M7'); $sheet->setCellValue('D7', mb_strtoupper($module->name ?? 'TODOS LOS MÓDULOS', 'UTF-8')); $sheet->getStyle('D7:M7')->applyFromArray($styleBox); $sheet->getStyle('D7')->getFont()->setBold(true)->setSize(20); // Fila 9: PERIODO $sheet->mergeCells('A9:C9'); $sheet->setCellValue('A9', 'PERIODO A INFORMAR:'); $sheet->getStyle('A9')->applyFromArray($styleLabel); Carbon::setLocale('es'); // Formato completo para manejar diferentes meses/años if ($fechaInicio->format('m/Y') === $fechaFin->format('m/Y')) { // Mismo mes y año $periodoTexto = 'del ' . $fechaInicio->format('d') . ' al ' . $fechaFin->format('d') . ' de ' . $fechaFin->translatedFormat('F'); $anioTexto = $fechaFin->format('Y'); } elseif ($fechaInicio->format('Y') === $fechaFin->format('Y')) { // Diferente mes, mismo año $periodoTexto = 'del ' . $fechaInicio->format('d') . ' de ' . $fechaInicio->translatedFormat('F') . ' al ' . $fechaFin->format('d') . ' de ' . $fechaFin->translatedFormat('F'); $anioTexto = $fechaFin->format('Y'); } else { // Diferente año $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 (D-G) $sheet->mergeCells('D9:G9'); $sheet->setCellValue('D9', $periodoTexto); $sheet->getStyle('D9:G9')->applyFromArray($styleBox); $sheet->getStyle('D9')->getFont()->setSize(14); // "de" (H) $sheet->setCellValue('H9', 'de'); $sheet->getStyle('H9')->getAlignment()->setHorizontal(Alignment::HORIZONTAL_CENTER)->setVertical(Alignment::VERTICAL_CENTER); $sheet->getStyle('H9')->getFont()->setSize(14); // Año (I-J) $sheet->mergeCells('I9:J9'); $sheet->setCellValue('I9', $anioTexto); $sheet->getStyle('I9:J9')->applyFromArray($styleBox); $sheet->getStyle('I9')->getFont()->setSize(14); // --- TÍTULO Y BARRA ROJA --- $rowTitle = 11; $sheet->mergeCells("A{$rowTitle}:M{$rowTitle}"); $sheet->setCellValue("A{$rowTitle}", 'REPORTE GENERAL DE CONSTANCIAS'); $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); $rowRedBar = 13; $sheet->mergeCells("A{$rowRedBar}:M{$rowRedBar}"); $sheet->getStyle("A{$rowRedBar}")->applyFromArray([ 'fill' => ['fillType' => Fill::FILL_SOLID, 'startColor' => ['rgb' => '900000']], ]); $sheet->getRowDimension($rowRedBar)->setRowHeight(6); // --- ENCABEZADOS DE TABLA (A-M) --- $h = 14; $headers = [ 'A' => 'No.', 'B' => 'MÓDULO', 'C' => "TIPO DE\nACCIÓN", 'D' => "NIV DEL\nVEHÍCULO", 'E' => 'NRPV/NCI', 'F' => "MARCA DEL\nVEHÍCULO", 'G' => 'PLACA', 'H' => "AÑO\nMODELO", 'I' => 'FOLIO', 'J' => "ID DE LA CONSTANCIA\n(CHIP)", 'K' => 'FECHA', 'L' => "MOTIVO/\nRAZÓN", 'M' => 'OBSERVACIONES' ]; foreach ($headers as $col => $text) { $sheet->setCellValue("{$col}{$h}", $text); } $sheet->getStyle("A{$h}:M{$h}")->applyFromArray($styleTableHeader); $sheet->getRowDimension($h)->setRowHeight(35); // --- LLENADO DE DATOS --- $row = 15; $i = 1; foreach ($data as $item) { $sheet->setCellValue('A' . $row, $i); $sheet->setCellValue('B' . $row, $item['modulo']); $sheet->setCellValue('C' . $row, $item['accion']); $sheet->setCellValue('D' . $row, $item['niv']); $sheet->setCellValue('E' . $row, $item['nrpv']); $sheet->setCellValue('F' . $row, $item['marca']); $sheet->setCellValue('G' . $row, $item['placa']); $sheet->setCellValue('H' . $row, $item['modelo']); $sheet->setCellValue('I' . $row, $item['folio']); $sheet->setCellValue('J' . $row, $item['chip']); $sheet->setCellValue('K' . $row, $item['fecha']); $sheet->setCellValue('L' . $row, $item['motivo']); $sheet->setCellValue('M' . $row, $item['observaciones']); // Estilos de fila $sheet->getStyle("A{$row}:M{$row}")->applyFromArray([ 'borders' => ['allBorders' => ['borderStyle' => PhpSpreadsheetBorder::BORDER_THIN]], 'alignment' => ['vertical' => Alignment::VERTICAL_CENTER, 'wrapText' => true], 'font' => ['size' => 9] ]); // Centrados $sheet->getStyle("A{$row}")->getAlignment()->setHorizontal(Alignment::HORIZONTAL_CENTER); // No $sheet->getStyle("H{$row}")->getAlignment()->setHorizontal(Alignment::HORIZONTAL_CENTER); // Año $sheet->getStyle("I{$row}")->getAlignment()->setHorizontal(Alignment::HORIZONTAL_CENTER); // Folio $sheet->getStyle("K{$row}")->getAlignment()->setHorizontal(Alignment::HORIZONTAL_CENTER); // Fecha $row++; $i++; } // --- ANCHOS DE COLUMNA --- $sheet->getColumnDimension('A')->setWidth(5); $sheet->getColumnDimension('B')->setWidth(25); $sheet->getColumnDimension('C')->setWidth(18); $sheet->getColumnDimension('D')->setWidth(22); // NIV $sheet->getColumnDimension('E')->setWidth(15); $sheet->getColumnDimension('F')->setWidth(18); $sheet->getColumnDimension('G')->setWidth(12); $sheet->getColumnDimension('H')->setWidth(10); $sheet->getColumnDimension('I')->setWidth(15); $sheet->getColumnDimension('J')->setWidth(25); // Chip $sheet->getColumnDimension('K')->setWidth(14); // Fecha $sheet->getColumnDimension('L')->setWidth(20); // Motivo $sheet->getColumnDimension('M')->setWidth(30); // Obs $writer = new Xlsx($spreadsheet); $writer->save($filePath); return response()->download($filePath, $fileName)->deleteFileAfterSend(true); } /** * Exportar resultados de búsqueda a Excel */ public function exportSearchRecords(Request $request) { // Reutilizar la misma validación y lógica de búsqueda $request->validate([ 'folio' => 'nullable|string', 'placa' => 'nullable|string', 'vin' => 'nullable|string', 'tag_number' => 'nullable|string', 'module_id' => 'nullable|integer|exists:modules,id', 'action_type' => 'nullable|string|in:sustitucion_primera_vez,sustitucion,cancelacion', 'status' => 'nullable|string', 'start_date' => 'nullable|date', 'end_date' => 'nullable|date|after_or_equal:start_date', ]); // Reutilizar la misma query de búsqueda (sin paginación) $records = Record::forUserModule(Auth::user()) ->with([ 'vehicle', 'vehicle.owner', 'vehicle.tag:id,vehicle_id,folio,tag_number,status_id,package_id,module_id', 'vehicle.tag.status:id,code,name', 'vehicle.tag.package:id,lot,box_number', 'vehicle.tag.module:id,name', 'files:id,record_id,name_id,path,md5', 'files.catalogName:id,name', 'user:id,name,username,module_id', 'module:id,name', 'error:id,code,description', 'vehicle.vehicleTagLogs' => function ($q) { $q->with([ 'tag:id,folio,tag_number,status_id,module_id,package_id', 'tag.status:id,code,name', 'tag.module:id,name', 'tag.package:id,lot,box_number' ])->orderBy('created_at', 'DESC'); }, ])->orderBy('id', 'ASC'); // Aplicar los mismos filtros if ($request->filled('folio')) { $records->whereHas('vehicle.tag', function ($q) use ($request) { $q->where('folio', 'LIKE', '%' . $request->input('folio') . '%'); }); } if ($request->filled('placa')) { $records->whereHas('vehicle', function ($q) use ($request) { $q->where('placa', 'LIKE', '%' . $request->input('placa') . '%'); }); } if ($request->filled('vin')) { $records->whereHas('vehicle', function ($q) use ($request) { $q->where('niv', 'LIKE', '%' . $request->input('vin') . '%'); }); } if ($request->filled('tag_number')) { $records->whereHas('vehicle.tag', function ($q) use ($request) { $q->where('tag_number', 'LIKE', '%' . $request->input('tag_number') . '%'); }); } if ($request->filled('module_id')) { $records->where('module_id', $request->input('module_id')); } if ($request->filled('action_type')) { $records->whereHas('vehicle.vehicleTagLogs', function ($q) use ($request) { $q->where('action_type', $request->input('action_type')) ->whereRaw('id = ( SELECT MAX(id) FROM vehicle_tags_logs WHERE vehicle_id = vehicle.id )'); }); } if ($request->filled('status')) { $records->whereHas('vehicle.tag.status', function ($q) use ($request) { $q->where('code', $request->input('status')); }); } if ($request->filled('start_date')) { $records->whereDate('created_at', '>=', $request->input('start_date')); } if ($request->filled('end_date')) { $records->whereDate('created_at', '<=', $request->input('end_date')); } $allRecords = $records->get(); if ($allRecords->isEmpty()) { return ApiResponse::NOT_FOUND->response([ 'message' => 'No se encontraron registros con los criterios de búsqueda proporcionados.', ]); } // Preparar datos para Excel: una fila por cada entrada del historial de tags $excelRows = []; foreach ($allRecords as $record) { $vehicle = $record->vehicle; $niv = $vehicle->niv ?? ''; $nrpv = $vehicle->nrpv ?? ''; $marca = $vehicle->marca ?? ''; $placa = $vehicle->placa ?? ''; $modelo = $vehicle->modelo ?? ''; $moduleName = $record->module->name ?? ''; $operador = $record->user->name ?? $record->user->username ?? ''; // Obtener logs ordenados por fecha, excluyendo actualizaciones $vehicleLogs = $vehicle->vehicleTagLogs ->whereIn('action_type', ['sustitucion_primera_vez', 'sustitucion', 'cancelacion']) ->sortBy('created_at'); // Generar una fila por cada log (cada acción en el historial) foreach ($vehicleLogs as $log) { $tag = $log->tag; // Determinar status basado en el tipo de acción y si tiene cancelación $status = 'Asignado'; $tipoTramite = ucfirst($log->action_type); $fecha = Carbon::parse($log->created_at); // Si el log tiene cancellation_at, es una cancelación/sustitución del tag if ($log->cancellation_at) { $status = 'Cancelado'; $fecha = Carbon::parse($log->cancellation_at); } elseif ($tag && $tag->status) { $status = $tag->status->name; } $excelRows[] = [ 'status' => $status, 'modulo' => $tag?->module?->name ?? $moduleName, 'operador' => $operador, 'tipo_tramite' => $tipoTramite, 'niv' => $niv, 'nrpv' => $nrpv, 'marca' => $marca, 'placa' => $placa, 'modelo' => $modelo, 'folio' => $tag?->folio ?? '', 'caja' => $tag?->package?->box_number ?? '', 'tag' => $tag?->tag_number ?? '', 'fecha' => $fecha->format('d/m/Y H:i'), 'sort_date' => $fecha->timestamp, ]; } // Si no hay logs pero hay tag asignado, agregar al menos una fila if ($vehicleLogs->isEmpty() && $vehicle->tag) { $currentTag = $vehicle->tag; $fechaActual = $record->created_at ? Carbon::parse($record->created_at) : now(); $excelRows[] = [ 'status' => $currentTag->status?->name ?? 'Asignado', 'modulo' => $currentTag->module?->name ?? $moduleName, 'operador' => $operador, 'tipo_tramite' => 'Inscripcion', 'niv' => $niv, 'nrpv' => $nrpv, 'marca' => $marca, 'placa' => $placa, 'modelo' => $modelo, 'folio' => $currentTag->folio ?? '', 'caja' => $currentTag->package?->box_number ?? '', 'tag' => $currentTag->tag_number ?? '', 'fecha' => $fechaActual->format('d/m/Y H:i'), 'sort_date' => $fechaActual->timestamp, ]; } } // Ordenar todas las filas por fecha ascendente usort($excelRows, function ($a, $b) { return $a['sort_date'] <=> $b['sort_date']; }); // Crear Excel $fileName = 'Busqueda_Registros_' . now()->format('Ymd_His') . '.xlsx'; $filePath = storage_path('app/temp/' . $fileName); if (!file_exists(dirname($filePath))) { mkdir(dirname($filePath), 0755, true); } $spreadsheet = new Spreadsheet(); $sheet = $spreadsheet->getActiveSheet(); $sheet->setTitle('Registros'); // Estilos $headerStyle = [ 'font' => ['bold' => true, 'size' => 11, 'color' => ['rgb' => 'FFFFFF']], 'fill' => ['fillType' => Fill::FILL_SOLID, 'startColor' => ['rgb' => '4472C4']], 'alignment' => ['horizontal' => Alignment::HORIZONTAL_CENTER, 'vertical' => Alignment::VERTICAL_CENTER, 'wrapText' => true], 'borders' => ['allBorders' => ['borderStyle' => PhpSpreadsheetBorder::BORDER_THIN]] ]; $cellStyle = [ 'borders' => ['allBorders' => ['borderStyle' => PhpSpreadsheetBorder::BORDER_THIN]], 'alignment' => ['vertical' => Alignment::VERTICAL_CENTER, 'wrapText' => true], ]; // Encabezados con todos los campos solicitados $headers = [ 'A' => 'Estatus', 'B' => 'Módulo', 'C' => 'Operador', 'D' => 'Tipo de Trámite', 'E' => 'NIV', 'F' => 'NRPV/NCI', 'G' => 'Marca', 'H' => 'Placa', 'I' => 'Año', 'J' => 'Folio', 'K' => 'Caja', 'L' => 'Tag ID', ]; $row = 1; foreach ($headers as $col => $text) { $sheet->setCellValue("{$col}{$row}", $text); $sheet->getStyle("{$col}{$row}")->applyFromArray($headerStyle); } $sheet->getRowDimension($row)->setRowHeight(30); // Datos $row = 2; foreach ($excelRows as $item) { $sheet->setCellValue('A' . $row, $item['status']); $sheet->setCellValue('B' . $row, $item['modulo']); $sheet->setCellValue('C' . $row, $item['operador']); $sheet->setCellValue('D' . $row, $item['tipo_tramite']); $sheet->setCellValue('E' . $row, $item['niv']); $sheet->setCellValue('F' . $row, $item['nrpv']); $sheet->setCellValue('G' . $row, $item['marca']); $sheet->setCellValue('H' . $row, $item['placa']); $sheet->setCellValue('I' . $row, $item['modelo']); $sheet->setCellValue('J' . $row, $item['folio']); $sheet->setCellValue('K' . $row, $item['caja']); $sheet->setCellValue('L' . $row, $item['tag']); // Aplicar estilos a todas las columnas foreach (range('A', 'L') as $col) { $sheet->getStyle("{$col}{$row}")->applyFromArray($cellStyle); } // Centrar columnas específicas $sheet->getStyle("A{$row}")->getAlignment()->setHorizontal(Alignment::HORIZONTAL_CENTER); $sheet->getStyle("D{$row}")->getAlignment()->setHorizontal(Alignment::HORIZONTAL_CENTER); $sheet->getStyle("I{$row}")->getAlignment()->setHorizontal(Alignment::HORIZONTAL_CENTER); $sheet->getStyle("J{$row}")->getAlignment()->setHorizontal(Alignment::HORIZONTAL_CENTER); $sheet->getStyle("K{$row}")->getAlignment()->setHorizontal(Alignment::HORIZONTAL_CENTER); $row++; } // Ajustar anchos de columna $sheet->getColumnDimension('A')->setWidth(15); // Estatus $sheet->getColumnDimension('B')->setWidth(25); // Módulo $sheet->getColumnDimension('C')->setWidth(20); // Operador $sheet->getColumnDimension('D')->setWidth(18); // Tipo de Trámite $sheet->getColumnDimension('E')->setWidth(22); // NIV $sheet->getColumnDimension('F')->setWidth(14); // NRPV/NCI $sheet->getColumnDimension('G')->setWidth(15); // Marca $sheet->getColumnDimension('H')->setWidth(12); // Placa $sheet->getColumnDimension('I')->setWidth(8); // Año $sheet->getColumnDimension('J')->setWidth(12); // Folio $sheet->getColumnDimension('K')->setWidth(8); // Caja $sheet->getColumnDimension('L')->setWidth(30); // Tag ID // Guardar archivo $writer = new Xlsx($spreadsheet); $writer->save($filePath); return response()->download($filePath, $fileName)->deleteFileAfterSend(true); } }