feat: mejora la consulta y presentación de datos en la generación de constancias de sustitución de TAGs

This commit is contained in:
Juan Felipe Zapata Moreno 2026-03-19 16:59:57 -06:00
parent f5c4fce98a
commit 633198e5ae
3 changed files with 138 additions and 53 deletions

View File

@ -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);

View File

@ -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
*/

View File

@ -3,6 +3,7 @@
<head>
<meta charset="UTF-8">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Constancia Sustituida - REPUVE Tabasco</title>
<style>
@page {
@ -139,11 +140,11 @@
</tr>
<tr class="data-row">
<td class="data-label">MOTIVO:</td>
<td class="data-value">{{ strtoupper($substitution['motivo'] ?? '') }}</td>
<td class="data-value">{{ mb_strtoupper($substitution['motivo'] ?? '', 'UTF-8') }}</td>
</tr>
<tr class="data-row">
<td class="data-label">OPERADOR:</td>
<td class="data-value">{{ strtoupper($substitution['operador'] ?? '') }}</td>
<td class="data-value">{{ mb_strtoupper($substitution['operador'] ?? '', 'UTF-8') }}</td>
</tr>
<tr class="data-row">
<td class="data-label">MÓDULO:</td>