From 633198e5ae3151a27cc4b515386709d55c727ae1 Mon Sep 17 00:00:00 2001 From: Juan Felipe Zapata Moreno Date: Thu, 19 Mar 2026 16:59:57 -0600 Subject: [PATCH] =?UTF-8?q?feat:=20mejora=20la=20consulta=20y=20presentaci?= =?UTF-8?q?=C3=B3n=20de=20datos=20en=20la=20generaci=C3=B3n=20de=20constan?= =?UTF-8?q?cias=20de=20sustituci=C3=B3n=20de=20TAGs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controllers/Repuve/ExcelController.php | 57 ++++++-- .../Controllers/Repuve/RecordController.php | 129 ++++++++++++------ .../views/pdfs/tag_sustitution.blade.php | 5 +- 3 files changed, 138 insertions(+), 53 deletions(-) diff --git a/app/Http/Controllers/Repuve/ExcelController.php b/app/Http/Controllers/Repuve/ExcelController.php index 98c84b2..279b7f3 100644 --- a/app/Http/Controllers/Repuve/ExcelController.php +++ b/app/Http/Controllers/Repuve/ExcelController.php @@ -59,7 +59,7 @@ public function vehicleActualizaciones(Request $request) $module = $moduleId ? Module::find($moduleId) : null; - // Consulta de Logs de actualizaciones + // 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) { @@ -68,8 +68,11 @@ public function vehicleActualizaciones(Request $request) }); }) ->whereBetween('created_at', [$fechaInicio, $fechaFin]) - ->orderBy('created_at', 'asc') - ->get(); + ->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); @@ -83,7 +86,7 @@ public function vehicleActualizaciones(Request $request) 'placa' => $log->vehicle->placa ?? '', 'modelo' => $log->vehicle->modelo ?? '', 'folio' => $log->tag->folio ?? '', - 'chip' => $log->tag->tag_number ?? '', + 'chip' => substr($log->tag->rfid ?? $log->tag->tag_number ?? '', 0, 24), 'fecha' => $log->created_at->format('d/m/Y'), ]; }); @@ -316,15 +319,26 @@ public function constanciasSustituidas(Request $request) // Consulta de Logs $logs = VehicleTagLog::with(['vehicle', 'tag', 'cancellationReason']) - ->where('action_type', 'sustitucion') - ->whereNotNull('cancellation_at') + ->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); }); }) - ->whereBetween('cancellation_at', [$fechaInicio, $fechaFin]) - ->orderBy('cancellation_at', 'asc') + ->orderByRaw('COALESCE(cancellation_at, created_at) ASC') ->get(); if ($logs->isEmpty()) { @@ -333,9 +347,26 @@ public function constanciasSustituidas(Request $request) // 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) - ->where('action_type', 'sustitucion') + ->whereIn('action_type', ['sustitucion_primera_vez', 'sustitucion']) ->whereNull('cancellation_at') ->where('id', '!=', $log->id) ->where('created_at', '>=', $log->created_at) @@ -350,7 +381,7 @@ public function constanciasSustituidas(Request $request) 'modelo' => $log->vehicle->modelo ?? '', 'folio_ant' => $log->tag->folio ?? '', 'folio_act' => $newTagLog?->tag?->folio ?? '', - 'chip' => $newTagLog?->tag?->rfid ?? $newTagLog?->tag?->tag_number ?? '', + '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 ?? '', ]; @@ -632,7 +663,7 @@ public function constanciasCanceladas(Request $request) $data = $allLogs->map(function ($log) { return [ 'folio' => $log->tag->folio ?? 'S/F', - 'tag_number' => $log->tag->tag_number ?? 'N/A', + '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', ]; @@ -799,10 +830,10 @@ public function constanciasCanceladas(Request $request) $sheet->setCellValue("G{$h1}", "ID DE LA\nCONSTANCIA (CHIP)"); $sheet->mergeCells("J{$h1}:K{$h2}"); - $sheet->setCellValue("J{$h1}", "FECHA DE\nCANCELACIÓN"); + $sheet->setCellValue("J{$h1}", "FECHA DE\nVINCULACIÓN"); $sheet->mergeCells("L{$h1}:N{$h2}"); - $sheet->setCellValue("L{$h1}", "MOTIVO DE\nCANCELACIÓN"); + $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); diff --git a/app/Http/Controllers/Repuve/RecordController.php b/app/Http/Controllers/Repuve/RecordController.php index 5640b87..3e6f893 100644 --- a/app/Http/Controllers/Repuve/RecordController.php +++ b/app/Http/Controllers/Repuve/RecordController.php @@ -6,6 +6,7 @@ use Barryvdh\DomPDF\Facade\Pdf; use App\Models\Record; use App\Models\Tag; +use App\Models\VehicleTagLog; use Carbon\Carbon; use Illuminate\Support\Facades\Storage; use Notsoweb\ApiResponse\Enums\ApiResponse; @@ -387,50 +388,80 @@ public function pdfCancelledTag(Tag $tag) public function pdfSubstitutedTag($recordId) { try { - // Validar que el tag tenga una sustitución registrada - $record = Record::with([ - 'vehicle.vehicleTagLogs' => function ($query){ - $query->where('action_type', 'sustitucion') - ->whereNotNull('cancellation_at') - ->latest(); - } - ])->findOrFail($recordId); + $record = Record::with(['vehicle.tag'])->findOrFail($recordId); - $oldTagLog = $record->vehicle->vehicleTagLogs->first(); - - if (!$oldTagLog) { - return ApiResponse::BAD_REQUEST->response([ - 'message' => 'No se encontró una sustitución registrada para este expediente.', - 'record' => $recordId, - ]); - } - - // Obtener datos de sustitución - $oldTag = Tag::with([ - 'vehicleTagLogs' => function($query){ - $query->where('action_type', 'sustitucion') - ->whereNotNull('cancellation_at') - ->with(['cancellationReason', 'cancelledBy', 'vehicle']) - ->latest(); - } - ])->findOrFail($oldTagLog->tag_id); - - $hasSubstitution = $oldTag->vehicleTagLogs() + // Sustitución real: existe un TAG anterior cancelado + $oldTagLog = VehicleTagLog::where('vehicle_id', $record->vehicle_id) ->where('action_type', 'sustitucion') ->whereNotNull('cancellation_at') - ->exists(); + ->latest() + ->first(); - if(!$hasSubstitution){ - return ApiResponse::BAD_REQUEST->response([ - 'message' => 'El tag no tiene sustitución registrada.', - 'tag' => $oldTag->folio, - ]); + // Primera vez: inscripción vía vehicleInscription (action_type puede ser + // 'sustitucion_primera_vez' o 'sustitucion' cuando el vehículo ya existe en REPUVE Nacional) + $primeraVezLog = !$oldTagLog + ? VehicleTagLog::where('vehicle_id', $record->vehicle_id) + ->whereIn('action_type', ['sustitucion_primera_vez', 'sustitucion']) + ->whereNull('cancellation_at') + ->with('performedBy') + ->latest() + ->first() + : null; + + if ($primeraVezLog) { + $newTag = $record->vehicle->tag; + + if (!$newTag) { + return ApiResponse::NOT_FOUND->response([ + 'message' => 'No se encontró el TAG asignado al vehículo.', + 'record' => $recordId, + ]); + } + + $substitutionData = $this->substitutionDataFirstTime($newTag, $primeraVezLog, $record->vehicle); + $isFirstTime = true; + $pdfFilename = 'constancia_primera_vez_' . $newTag->folio . '.pdf'; + } else { + // Sustitución regular: TAG anterior cancelado + + if (!$oldTagLog) { + $debugLogs = VehicleTagLog::where('vehicle_id', $record->vehicle_id)->get(['id', 'action_type', 'cancellation_at', 'vehicle_id', 'tag_id']); + return response()->json([ + 'debug_record_id' => $recordId, + 'debug_vehicle_id' => $record->vehicle_id, + 'debug_logs' => $debugLogs, + ]); + } + + $oldTag = Tag::with([ + 'vehicleTagLogs' => function ($query) { + $query->where('action_type', 'sustitucion') + ->whereNotNull('cancellation_at') + ->with(['cancellationReason', 'cancelledBy', 'vehicle']) + ->latest(); + } + ])->findOrFail($oldTagLog->tag_id); + + $hasSubstitution = $oldTag->vehicleTagLogs() + ->where('action_type', 'sustitucion') + ->whereNotNull('cancellation_at') + ->exists(); + + if (!$hasSubstitution) { + return ApiResponse::BAD_REQUEST->response([ + 'message' => 'El tag no tiene sustitución registrada.', + 'tag' => $oldTag->folio, + ]); + } + + $substitutionData = $this->substitutionData($oldTag); + $isFirstTime = false; + $pdfFilename = 'constancia_sustituida_' . $oldTag->folio . '.pdf'; } - $substitutionData = $this->substitutionData($oldTag); - $pdf = Pdf::loadView('pdfs.tag_sustitution', [ 'substitution' => $substitutionData, + 'is_first_time' => $isFirstTime, ]) ->setPaper('a4', 'portrait') ->setOptions([ @@ -439,7 +470,7 @@ public function pdfSubstitutedTag($recordId) 'isRemoteEnabled' => true, ]); - return $pdf->stream('constancia_sustituida_' . $oldTag->folio . '.pdf'); + return $pdf->stream($pdfFilename); } catch (\Exception $e) { return ApiResponse::INTERNAL_ERROR->response([ 'message' => 'Error al generar el PDF del tag sustituido.', @@ -600,7 +631,7 @@ private function substitutionData(Tag $tag) // datos del motivo y operador $data['fecha'] = $oldTagLog->cancellation_at->format('d/m/Y'); $data['motivo'] = $oldTagLog->cancellationReason->name ?? 'No especificado'; - $data['operador'] = $oldTagLog->cancelledBy->name ?? 'Sistema'; + $data['operador'] = $oldTagLog->cancelledBy?->full_name ?? 'Sistema'; // módulo del usuario if ($oldTagLog->cancelledBy) { @@ -621,6 +652,28 @@ private function substitutionData(Tag $tag) return $data; } + private function substitutionDataFirstTime(Tag $newTag, $log, $vehicle) + { + $data = [ + 'fecha' => $log->created_at->format('d/m/Y'), + 'folio' => $newTag->folio ?? '', + 'folio_sustituto' => 'N/A', + 'id_chip' => 'N/A', + 'placa' => $vehicle->placa ?? '', + 'niv' => $vehicle->niv ?? '', + 'motivo' => 'Sustitución primera vez', + 'operador' => $log->performedBy?->full_name ?? 'N/A', + 'modulo' => '', + 'ubicacion' => '', + ]; + + if ($log->performedBy) { + $this->loadUserModule($log->performedBy, $data); + } + + return $data; + } + /** * Cargar módulo del usuario */ diff --git a/resources/views/pdfs/tag_sustitution.blade.php b/resources/views/pdfs/tag_sustitution.blade.php index 3c910a7..4a6a05c 100644 --- a/resources/views/pdfs/tag_sustitution.blade.php +++ b/resources/views/pdfs/tag_sustitution.blade.php @@ -3,6 +3,7 @@ + Constancia Sustituida - REPUVE Tabasco