From 44ef2f930629f811440e1c1d01bd6a653b968c72 Mon Sep 17 00:00:00 2001 From: Juan Felipe Zapata Moreno Date: Mon, 22 Dec 2025 14:57:46 -0600 Subject: [PATCH] =?UTF-8?q?feat:=20actualizar=20m=C3=A9todo=20de=20cancela?= =?UTF-8?q?ci=C3=B3n=20de=20constancias=20para=20incluir=20validaciones=20?= =?UTF-8?q?y=20soporte=20de=20sustituci=C3=B3n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Repuve/CancellationController.php | 130 +++++++++++++----- .../Repuve/CancelConstanciaRequest.php | 5 +- routes/api.php | 2 +- 3 files changed, 98 insertions(+), 39 deletions(-) diff --git a/app/Http/Controllers/Repuve/CancellationController.php b/app/Http/Controllers/Repuve/CancellationController.php index fc80b19..6646fb6 100644 --- a/app/Http/Controllers/Repuve/CancellationController.php +++ b/app/Http/Controllers/Repuve/CancellationController.php @@ -22,13 +22,21 @@ class CancellationController extends Controller /** * Cancelar la constancia/tag */ - public function cancelarConstancia(CancelConstanciaRequest $request) + public function cancelarConstancia(CancelConstanciaRequest $request, $recordId) { try { DB::beginTransaction(); // Buscar el expediente con sus relaciones - $record = Record::with('vehicle.tag.status')->findOrFail($request->record_id); + $record = Record::with('vehicle.tag.status')->find($recordId); + + if (!$record) { + return ApiResponse::NOT_FOUND->response([ + 'message' => 'El expediente no existe.', + 'record_id' => $recordId, + ]); + } + $vehicle = $record->vehicle; $tag = $vehicle->tag; @@ -39,44 +47,75 @@ public function cancelarConstancia(CancelConstanciaRequest $request) ]); } - // Validar que el tag está en estado activo - if (!$tag->isAssigned()) { + // Validar que el tag está en estado activo O cancelado (para permitir sustitución posterior) + if (!$tag->isAssigned() && !$tag->isCancelled()) { return ApiResponse::BAD_REQUEST->response([ - 'message' => 'El tag ya no está activo. Status actual: ' . $tag->status->name + 'message' => 'El tag debe estar asignado o cancelado. Status actual: ' . $tag->status->name ]); } + // Validar que se proporcionen los datos de sustitución + if (!$request->filled('new_folio') || !$request->filled('new_tag_number')) { + return ApiResponse::BAD_REQUEST->response([ + 'message' => 'Para cancelar la constancia, debe proporcionar: nuevo folio y numero de constancia.', + 'provided' => [ + 'new_folio' => $request->filled('new_folio'), + 'new_tag_number' => $request->filled('new_tag_number'), + ], + ]); + } + + // Validar que el tag_number tenga exactamente 17 caracteres + if (strlen($request->new_tag_number) !== 32) { + return ApiResponse::BAD_REQUEST->response([ + 'message' => 'El tag_number debe tener exactamente 32 caracteres', + 'provided_tag_number' => $request->new_tag_number, + 'length' => strlen($request->new_tag_number), + ]); + } + + $isSubstitution = true; + // Guardar información del tag anterior ANTES de cancelarlo $oldTagNumber = $tag->tag_number; $oldFolio = $tag->folio; - // Crear registro en el log de vehiculos - $cancellationLog = VehicleTagLog::create([ - 'vehicle_id' => $vehicle->id, - 'tag_id' => $tag->id, - 'action_type' => 'cancelacion', - 'cancellation_reason_id' => $request->cancellation_reason_id, - 'cancellation_observations' => $request->cancellation_observations, - 'cancellation_at' => now(), - 'cancelled_by' => Auth::id(), - 'performed_by' => Auth::id(), - ]); + // Crear registro en el log de vehículos + if ($tag->isAssigned()) { + $cancellationLog = VehicleTagLog::create([ + 'vehicle_id' => $vehicle->id, + 'tag_id' => $tag->id, + 'action_type' => 'cancelacion', + 'cancellation_reason_id' => $request->cancellation_reason_id, + 'cancellation_observations' => $request->cancellation_observations, + 'cancellation_at' => now(), + 'cancelled_by' => Auth::id(), + 'performed_by' => Auth::id(), + ]); - // Actualizar estado del tag a 'cancelled' y desasignar vehículo - $tag->markAsCancelled(); + // Actualizar estado del tag a 'cancelled' + $tag->markAsCancelled(); + } else { + // Si ya estaba cancelado, buscar el último log de cancelación + $cancellationLog = VehicleTagLog::where('vehicle_id', $vehicle->id) + ->where('tag_id', $tag->id) + ->where('action_type', 'cancelacion') + ->latest() + ->first(); + } $newTag = null; - $substitutionLog = null; - $isSubstitution = $request->filled('new_folio') && $request->filled('new_tag_number'); + $substitutionLog = null; if ($isSubstitution) { + // Buscar el nuevo tag por folio $newTag = Tag::where('folio', $request->new_folio)->first(); if (!$newTag) { DB::rollBack(); return ApiResponse::NOT_FOUND->response([ - 'message' => 'El nuevo tag proporcionado no existe.', - 'new_tag_number' => $request->new_tag_number, + 'message' => 'El tag con el folio proporcionado no existe.', + 'new_folio' => $request->new_folio, ]); } @@ -84,17 +123,22 @@ public function cancelarConstancia(CancelConstanciaRequest $request) DB::rollBack(); return ApiResponse::BAD_REQUEST->response([ 'message' => 'El nuevo tag no está disponible para asignación', - 'new_tag_number' => $request->new_tag_number, + 'new_folio' => $request->new_folio, 'current_status' => $newTag->status->name, ]); } + // Asignar tag_number al nuevo tag si no lo tiene if (!$newTag->tag_number) { - $existingTag = Tag::where('tag_number', $request->new_tag_number)->first(); - if ($existingTag && $existingTag->id !== $newTag->id) { + // Validar que el tag_number no esté usado por otro tag + $existingTag = Tag::where('tag_number', $request->new_tag_number) + ->where('id', '!=', $newTag->id) + ->first(); + + if ($existingTag) { DB::rollBack(); return ApiResponse::BAD_REQUEST->response([ - 'message' => 'El nuevo tag_number ya está asignado a otro folio.', + 'message' => 'El tag_number ya está asignado a otro tag.', 'new_tag_number' => $request->new_tag_number, 'folio_existente' => $existingTag->folio, ]); @@ -105,15 +149,19 @@ public function cancelarConstancia(CancelConstanciaRequest $request) } elseif ($newTag->tag_number !== $request->new_tag_number) { DB::rollBack(); return ApiResponse::BAD_REQUEST->response([ - 'message' => 'El nuevo tag ya tiene un tag_number diferente asignado.', + 'message' => 'El tag ya tiene un tag_number diferente asignado.', 'assigned_tag_number' => $newTag->tag_number, 'provided_tag_number' => $request->new_tag_number, ]); } - // Usar el folio del request - $newTag->markAsAssigned($vehicle->id, $request->folio); + // Desasignar el tag viejo para evitar conflicto de unique constraint + $tag->update(['vehicle_id' => null]); + // Asignar el nuevo tag al vehículo (usa el folio del tag encontrado) + $newTag->markAsAssigned($vehicle->id, $newTag->folio); + + // Crear log de sustitución $substitutionLog = VehicleTagLog::create([ 'vehicle_id' => $vehicle->id, 'tag_id' => $newTag->id, @@ -123,7 +171,8 @@ public function cancelarConstancia(CancelConstanciaRequest $request) 'performed_by' => Auth::id(), ]); - $record->update(['folio' => $request->folio]); + // Actualizar el folio del expediente con el folio del nuevo tag + $record->update(['folio' => $newTag->folio]); } DB::commit(); @@ -132,9 +181,22 @@ public function cancelarConstancia(CancelConstanciaRequest $request) ? 'Tag cancelado y sustituido exitosamente' : 'Constancia cancelada exitosamente'; + // Agregar alerta si NO hay sustitución + $alert = null; + if (!$isSubstitution) { + $alert = [ + 'type' => 'warning', + 'message' => 'El tag ha sido cancelado y necesita sustitución', + 'requires_action' => true, + 'cancellation_date' => $cancellationLog->cancellation_at->format('d/m/Y H:i:s'), + 'cancellation_reason' => $cancellationLog->cancellationReason->name, + ]; + } + return ApiResponse::OK->response([ 'message' => $message, 'is_substitution' => $isSubstitution, + 'alert' => $alert, 'cancellation' => [ 'id' => $cancellationLog->id, 'vehicle' => [ @@ -144,8 +206,8 @@ public function cancelarConstancia(CancelConstanciaRequest $request) ], 'old_tag' => [ 'id' => $tag->id, - 'folio' => $tag->folio, - 'tag_number' => $tag->tag_number, + 'folio' => $oldFolio, + 'tag_number' => $oldTagNumber, 'new_status' => 'Cancelado', ], 'new_tag' => $newTag ? [ @@ -154,7 +216,7 @@ public function cancelarConstancia(CancelConstanciaRequest $request) 'tag_number' => $newTag->tag_number, 'status' => $newTag->status->name, ] : null, - 'cancellation_reason_id' => $cancellationLog->cancellationReason->name, + 'cancellation_reason' => $cancellationLog->cancellationReason->name, 'cancellation_observations' => $request->cancellation_observations, 'cancelled_at' => $cancellationLog->cancellation_at->toDateTimeString(), 'cancelled_by' => Auth::user()->name, @@ -164,7 +226,7 @@ public function cancelarConstancia(CancelConstanciaRequest $request) DB::rollBack(); Log::error('Error en cancelarConstancia: ' . $e->getMessage(), [ - 'record_id' => $request->record_id ?? null, + 'record_id' => $recordId ?? null, 'cancellation_reason' => $request->cancellation_reason ?? null, 'trace' => $e->getTraceAsString() ]); diff --git a/app/Http/Requests/Repuve/CancelConstanciaRequest.php b/app/Http/Requests/Repuve/CancelConstanciaRequest.php index 43446f8..ecfc0c6 100644 --- a/app/Http/Requests/Repuve/CancelConstanciaRequest.php +++ b/app/Http/Requests/Repuve/CancelConstanciaRequest.php @@ -20,8 +20,7 @@ public function authorize(): bool public function rules(): array { return [ - 'record_id' => 'nullable|exists:records,id', - 'folio' => 'required|string', + 'new_folio' => 'required|string', 'cancellation_reason_id' => 'required|exists:catalog_cancellation_reasons,id', 'cancellation_observations' => 'nullable|string', 'new_tag_number' => 'nullable|exists:tags,tag_number', @@ -34,8 +33,6 @@ public function rules(): array public function messages(): array { return [ - 'record_id.required' => 'El id del expediente es obligatorio.', - 'record_id.integer' => 'El id del expediente debe ser un número entero.', 'record_id.exists' => 'El expediente especificado no existe.', 'cancellation_reason_id.required' => 'El motivo de cancelación es obligatorio.', diff --git a/routes/api.php b/routes/api.php index 82bb63b..c9021b8 100644 --- a/routes/api.php +++ b/routes/api.php @@ -55,7 +55,7 @@ // Rutas de cancelación de constancias Route::resource('/razones-cancelacion', CatalogController::class); - Route::delete('cancelacion', [CancellationController::class, 'cancelarConstancia']); + Route::post('{recordId}/cancelacion', [CancellationController::class, 'cancelarConstancia']); Route::post('tags/cancelar', [CancellationController::class, 'cancelarTagNoAsignado']); Route::get('excel/constancias-sustituidas', [ExcelController::class, 'constanciasSustituidas']); Route::get('excel/constancias-canceladas', [ExcelController::class, 'constanciasCanceladas']);