From 0be583a088d5b23d3ee7f3ff82a3ad4e85c7c9b2 Mon Sep 17 00:00:00 2001 From: Juan Felipe Zapata Moreno Date: Fri, 5 Dec 2025 11:27:08 -0600 Subject: [PATCH] =?UTF-8?q?Correci=C3=B3n=20Excel=20Canceladas,=20Correci?= =?UTF-8?q?=C3=B3n=20Search=20Record,=20Creaci=C3=B3n=20del=20Actualizaci?= =?UTF-8?q?=C3=B3n=20(tagSubstitution)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controllers/Repuve/ExcelController.php | 32 +++- .../Repuve/InscriptionController.php | 25 ++- .../Controllers/Repuve/UpdateController.php | 178 ++++++++++++------ 3 files changed, 163 insertions(+), 72 deletions(-) diff --git a/app/Http/Controllers/Repuve/ExcelController.php b/app/Http/Controllers/Repuve/ExcelController.php index 6a2e3b3..98c7808 100644 --- a/app/Http/Controllers/Repuve/ExcelController.php +++ b/app/Http/Controllers/Repuve/ExcelController.php @@ -11,6 +11,7 @@ use App\Models\VehicleTagLog; use App\Models\Tag; use App\Models\Module; +use App\Models\TagCancellationLog; use Carbon\Carbon; use Notsoweb\ApiResponse\Enums\ApiResponse; use PhpOffice\PhpSpreadsheet\Spreadsheet; @@ -229,7 +230,7 @@ public function constanciasCanceladas(Request $request) $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(); @@ -237,7 +238,7 @@ public function constanciasCanceladas(Request $request) $moduleId = $request->module_id; // Obtener información del módulo - $module = Module::findOrFail($moduleId); + $module = $moduleId ? Module::find($moduleId) : null; // Obtener logs de cancelación en el rango de fechas $logs = VehicleTagLog::with([ @@ -246,14 +247,28 @@ public function constanciasCanceladas(Request $request) 'cancellationReason' ]) ->where('action_type', 'cancelacion') - ->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('cancellation_at', [$fechaInicio, $fechaFin]) ->orderBy('cancellation_at', 'asc') ->get(); - if ($logs->isEmpty()) { + $logsT = TagCancellationLog::with([ + 'tag', + 'cancellationReason' + ])->when($moduleId, function ($query) use ($moduleId) { + $query->whereHas('tag', function ($q) use ($moduleId) { + $q->where('module_id', $moduleId); + }); + }) + ->whereBetween('cancellation_at', [$fechaInicio, $fechaFin]) + ->orderBy('cancellation_at', 'asc') + ->get(); + + if ($logs->isEmpty() && $logsT->isEmpty()) { return ApiResponse::NOT_FOUND->response([ 'message' => 'No se encontraron constancias canceladas en el periodo especificado', 'fecha_inicio' => $fechaInicio->format('Y-m-d'), @@ -263,7 +278,8 @@ public function constanciasCanceladas(Request $request) } // Preparar datos para el Excel - $data = $this->prepareExcelDataCanceladas($logs); + $allLogs = $logs->concat($logsT)->sortBy('cancellation_at'); + $data = $this->prepareExcelDataCanceladas($allLogs); // Generar archivo Excel $fileName = 'Constancias_Canceladas_' . $fechaInicio->format('Ymd') . '_' . $fechaFin->format('Ymd') . '.xlsx'; @@ -316,7 +332,7 @@ public function constanciasCanceladas(Request $request) // MÓDULO $sheet->setCellValue('B' . $row, 'MÓDULO:'); $sheet->mergeCells('C' . $row . ':K' . $row); - $sheet->setCellValue('C' . $row, $module->name ?? 'MÓDULO 1. BASE 4'); + $sheet->setCellValue('C' . $row, $module?->name ?? 'S/N MODULO'); $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); @@ -461,7 +477,7 @@ private function prepareExcelDataCanceladas($logs) $no = 1; foreach ($logs as $log) { - $vehicle = $log->vehicle; + $vehicle = $log->vehicle ?? $log->tag->vehicle; $tag = $log->tag; $data[] = [ diff --git a/app/Http/Controllers/Repuve/InscriptionController.php b/app/Http/Controllers/Repuve/InscriptionController.php index a49fc6d..70f6a87 100644 --- a/app/Http/Controllers/Repuve/InscriptionController.php +++ b/app/Http/Controllers/Repuve/InscriptionController.php @@ -350,7 +350,9 @@ public function searchRecord(Request $request) 'action_type' => 'nullable|string|in:inscripcion,actualizacion,sustitucion,cancelacion', 'status' => 'nullable|string', ], [ - 'required_without_all' => 'Debe proporcionar al menos uno de los siguientes: folio, placa o vin.' + 'folio.required_without_all' => 'Se requiere al menos un criterio de búsqueda.', + 'placa.required_without_all' => 'Se requiere al menos un criterio de búsqueda.', + 'vin.required_without_all' => 'Se requiere al menos un criterio de búsqueda.', ]); $records = Record::with([ @@ -376,7 +378,9 @@ public function searchRecord(Request $request) 'error:id,code,description', // Log de acciones - 'vehicle.vehicleTagLogs:id,vehicle_id,tag_id,action_type,performed_by,created_at' + 'vehicle.vehicleTagLogs' => function ($q) { + $q->orderBy('created_at', 'DESC'); + }, ])->orderBy('id', 'ASC'); if ($request->filled('folio')) { @@ -394,25 +398,36 @@ public function searchRecord(Request $request) $q->where('niv', 'LIKE', '%' . $request->input('vin') . '%'); }); } + // Filtro por módulo if ($request->filled('module_id')) { $records->where('module_id', $request->input('module_id')); } + + // Filtro por tipo de acción if ($request->filled('action_type')) { $records->whereHas('vehicle.vehicleTagLogs', function ($q) use ($request) { - $q->where('action_type', $request->input('action_type')); + $q->where('action_type', $request->input('action_type')) + ->whereRaw('id = ( + SELECT MAX(id) + FROM vehicle_tags_logs + WHERE vehicle_id = vehicle.id + )'); }); } + + // Filtro por status del tag if ($request->filled('status')) { $records->whereHas('vehicle.tag.status', function ($q) use ($request) { $q->where('code', $request->input('status')); }); } + // Paginación $paginatedRecords = $records->paginate(config('app.pagination')); + // Transformación de datos $paginatedRecords->getCollection()->transform(function ($record) { - // Obtener el último log de acción - $latestLog = $record->vehicle->vehicleTagLogs->sortByDesc('created_at')->first(); + $latestLog = $record->vehicle->vehicleTagLogs->first(); return [ 'id' => $record->id, diff --git a/app/Http/Controllers/Repuve/UpdateController.php b/app/Http/Controllers/Repuve/UpdateController.php index 1c38940..b147f02 100644 --- a/app/Http/Controllers/Repuve/UpdateController.php +++ b/app/Http/Controllers/Repuve/UpdateController.php @@ -14,6 +14,7 @@ use App\Models\Owner; use App\Models\CatalogNameImg; use App\Models\VehicleTagLog; +use App\Models\Tag; use App\Services\RepuveService; use App\Services\PadronEstatalService; use Exception; @@ -33,96 +34,155 @@ public function __construct( $this->padronEstatalService = $padronEstatalService; } - public function vehicleData(Request $request) + /** + * Sustitución de TAG + * Proceso: Buscar TAG actual por folio del expediente, cancelarlo y asignar un nuevo TAG al vehículo + */ + public function tagSubstitution(Request $request) { try { $request->validate([ 'folio' => 'required|string|exists:records,folio', - 'tag_number' => 'required|string|exists:tags,tag_number', - 'niv' => 'required|string|exists:vehicle,niv' + 'new_tag_folio' => 'required|string|exists:tags,folio', + 'cancellation_reason_id' => 'nullable|exists:catalog_cancellation_reasons,id', + 'cancellation_observations' => 'nullable|string|max:500', ]); $folio = $request->input('folio'); - $tagNumber = $request->input('tag_number'); - $niv = $request->input('niv'); - - $isStolen = $this->checkIfStolen($niv); - - if ($isStolen) { - return ApiResponse::FORBIDDEN->response([ - 'message' => 'El vehículo reporta robo. No se puede continuar con la actualización.', - ]); - } + $newTagFolio = $request->input('new_tag_folio'); + $cancellationReasonId = $request->input('cancellation_reason_id'); + $cancellationObservations = $request->input('cancellation_observations'); + // Buscar el expediente por folio $record = Record::with([ - 'vehicle:id,owner_id,placa,niv,marca,linea,modelo,color', - 'vehicle.owner:id,name,paternal,maternal,rfc', - 'vehicle.tag:id,vehicle_id,folio,tag_number,status_id', - 'files:id,record_id,name_id,path,md5', - 'files.catalogName:id,name', - '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(); + 'vehicle.tag.status', + 'vehicle.owner', + ])->where('folio', $folio)->first(); if (!$record) { return ApiResponse::NOT_FOUND->response([ - 'message' => 'No se encontró el expediente', + 'message' => 'No se encontró el expediente con el folio proporcionado', + 'folio' => $folio, ]); } $vehicle = $record->vehicle; - $tag = $vehicle->tag; + $oldTag = $vehicle->tag; - if (!$tag || $tag->tag_number !== $tagNumber) { - return ApiResponse::BAD_REQUEST->response([ - 'message' => 'El tag_number no coincide con el registrado en el expediente', + if (!$oldTag) { + return ApiResponse::NOT_FOUND->response([ + 'message' => 'El vehículo no tiene un TAG asignado actualmente', + 'folio' => $folio, ]); } - if ($vehicle->niv !== $niv) { + // Verificar que el TAG antiguo esté asignado + if (!$oldTag->isAssigned()) { return ApiResponse::BAD_REQUEST->response([ - 'message' => 'El NIV no coincide con el registrado en el expediente', + 'message' => 'El TAG actual no está en estado asignado', + 'tag_folio' => $oldTag->folio, + 'current_status' => $oldTag->status->name, ]); } - // Consultar REPUVE Estatal para obtener datos actualizados - $vehicleDataEstatal = $this->getVehicle($niv); - $ownerDataEstatal = $this->getOwner($niv); + // Buscar el nuevo TAG por folio + $newTag = Tag::with('status') + ->where('folio', $newTagFolio) + ->first(); - // Guardar en caché por 30 minutos - Cache::put("update_vehicle_{$niv}", $vehicleDataEstatal, 1800); - Cache::put("update_owner_{$niv}", $ownerDataEstatal, 1800); + if (!$newTag) { + return ApiResponse::NOT_FOUND->response([ + 'message' => 'El nuevo TAG no existe', + 'new_tag_folio' => $newTagFolio, + ]); + } - // Detectar si hay cambios entre BD y padrón estatal - $hasVehicleChanges = $this->detectVehicleChanges($vehicle, $vehicleDataEstatal); - $hasOwnerChanges = $this->detectOwnerChanges($vehicle->owner, $ownerDataEstatal); + // Verificar que el nuevo TAG esté disponible + if (!$newTag->isAvailable()) { + return ApiResponse::BAD_REQUEST->response([ + 'message' => 'El nuevo TAG no está disponible para asignación', + 'new_tag_folio' => $newTagFolio, + 'current_status' => $newTag->status->name, + ]); + } + + // Verificar robo del vehículo + $isStolen = $this->checkIfStolen($vehicle->niv); + + if ($isStolen) { + return ApiResponse::FORBIDDEN->response([ + 'message' => 'El vehículo reporta robo. No se puede continuar con la sustitución.', + 'niv' => $vehicle->niv, + ]); + } + + DB::beginTransaction(); + + // Cancelar el TAG anterior + $oldTag->markAsCancelled(); + + // Registrar log de cancelación del TAG anterior + VehicleTagLog::create([ + 'vehicle_id' => $vehicle->id, + 'tag_id' => $oldTag->id, + 'action_type' => 'sustitucion', + 'cancellation_reason_id' => $cancellationReasonId, + 'cancellation_observations' => $cancellationObservations, + 'cancellation_at' => now(), + 'cancelled_by' => Auth::id(), + 'performed_by' => Auth::id(), + ]); + + // Actualizar el folio del record con el folio del nuevo TAG + $record->update([ + 'folio' => $newTagFolio + ]); + + // Asignar el nuevo TAG al vehículo (usa el nuevo folio) + $newTag->markAsAssigned($vehicle->id, $newTagFolio); + + // 5. Registrar log de asignación del nuevo TAG + VehicleTagLog::create([ + 'vehicle_id' => $vehicle->id, + 'tag_id' => $newTag->id, + 'action_type' => 'sustitucion', + 'performed_by' => Auth::id(), + ]); + + // 6. Enviar a REPUVE Nacional + $datosCompletos = $this->prepararDatosParaInscripcion($vehicle->niv); + ProcessRepuveResponse::dispatch($record->id, $datosCompletos); + + DB::commit(); + + // Recargar relaciones para la respuesta + $record->load(['vehicle.tag.status', 'vehicle.owner']); return ApiResponse::OK->response([ - 'message' => 'Datos del vehículo obtenidos exitosamente', - 'current_data' => $record, - 'estatal_data' => [ - 'vehicle' => $vehicleDataEstatal, - 'owner' => $ownerDataEstatal, - ], - 'has_changes' => $hasVehicleChanges || $hasOwnerChanges, - 'changes_detail' => [ - 'vehicle' => $hasVehicleChanges, - 'owner' => $hasOwnerChanges, + 'message' => 'Sustitución de TAG realizada exitosamente', + 'substitution_details' => [ + 'old_folio' => $folio, + 'new_folio' => $newTagFolio, + 'old_tag' => [ + 'folio' => $oldTag->folio, + 'tag_number' => $oldTag->tag_number, + 'status' => 'cancelled', + ], + 'new_tag' => [ + 'folio' => $newTag->folio, + 'tag_number' => $newTag->tag_number, + 'status' => 'assigned', + ], + 'performed_by' => Auth::user()->name, + 'performed_at' => now()->format('Y-m-d H:i:s'), ], + 'record' => $record, ]); } catch (Exception $e) { - return ApiResponse::BAD_REQUEST->response([ - 'message' => 'Error de validación', + DB::rollBack(); + + return ApiResponse::INTERNAL_ERROR->response([ + 'message' => 'Error al realizar la sustitución del TAG', 'error' => $e->getMessage(), ]); }