findOrFail($request->record_id); $vehicle = $record->vehicle; $tag = $vehicle->tag; // Validar que el vehículo tiene un tag asignado if (!$tag) { return ApiResponse::BAD_REQUEST->response([ 'message' => 'El vehículo no tiene un tag/constancia asignada.' ]); } // Validar que el tag está en estado activo if (!$tag->isAssigned()) { return ApiResponse::BAD_REQUEST->response([ 'message' => 'El tag ya no está activo. Status actual: ' . $tag->status->name ]); } // Validar que el registro no tenga errores previos if ($record->error_id !== null) { return ApiResponse::BAD_REQUEST->response([ 'message' => 'No se puede cancelar un registro que tiene errores. El vehículo no fue inscrito.', 'error_id' => $record->error_id ]); } // 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(), ]); // Actualizar estado del tag a 'cancelled' y desasignar vehículo $tag->markAsCancelled(); $newTag = null; $substitutionLog = null; $isSubstitution = $request->filled('new_tag_number'); if ($isSubstitution) { $newTag = Tag::where('tag_number', $request->new_tag_number)->first(); if(!$newTag){ DB::rollBack(); return ApiResponse::NOT_FOUND->response([ 'message' => 'El nuevo tag proporcionado no existe.', 'new_tag_number' => $request->new_tag_number, ]); } if(!$newTag->isAvailable()) { DB::rollBack(); return ApiResponse::BAD_REQUEST->response([ 'message' => 'El nuevo tag no está disponible para asignación', 'new_tag_number' => $request->new_tag_number, 'current_status' => $newTag->status->name, ]); } // Usar el folio del request $newTag->markAsAssigned($vehicle->id, $request->folio); $substitutionLog = VehicleTagLog::create([ 'vehicle_id' => $vehicle->id, 'tag_id' => $newTag->id, 'action_type' => 'sustitucion', 'cancellation_reason_id' => $request->cancellation_reason_id, 'cancellation_observations' => 'Tag sustituido. Tag anterior: ' . $oldTagNumber . ' (Folio: ' . $oldFolio . '). Motivo: ' . ($request->cancellation_observations ?? ''), 'performed_by' => Auth::id(), ]); $record->update(['folio' => $request->folio]); } DB::commit(); $message = $isSubstitution ? 'Tag cancelado y sustituido exitosamente' : 'Constancia cancelada exitosamente'; return ApiResponse::OK->response([ 'message' => $message, 'is_substitution' => $isSubstitution, 'cancellation' => [ 'id' => $cancellationLog->id, 'vehicle' => [ 'id' => $vehicle->id, 'placa' => $vehicle->placa, 'niv' => $vehicle->niv, ], 'old_tag' => [ 'id' => $tag->id, 'folio' => $tag->folio, 'tag_number' => $tag->tag_number, 'new_status' => 'Cancelado', ], 'new_tag' => $newTag ? [ 'id' => $newTag->id, 'folio' => $newTag->folio, 'tag_number' => $newTag->tag_number, 'status' => $newTag->status->name, ] : null, 'cancellation_reason_id' => $cancellationLog->cancellationReason->name, 'cancellation_observations' => $request->cancellation_observations, 'cancelled_at' => $cancellationLog->cancellation_at->toDateTimeString(), 'cancelled_by' => Auth::user()->name, ] ]); } catch (\Exception $e) { DB::rollBack(); Log::error('Error en cancelarConstancia: ' . $e->getMessage(), [ 'record_id' => $request->record_id ?? null, 'cancellation_reason' => $request->cancellation_reason ?? null, 'trace' => $e->getTraceAsString() ]); return ApiResponse::BAD_REQUEST->response([ 'message' => 'Error al cancelar la constancia', 'error' => $e->getMessage() ]); } } public function cancelarTagNoAsignado(Request $request) { try { $request->validate([ 'tag_number' => 'required|string|exists:tags,tag_number', 'cancellation_reason_id' => 'required|exists:catalog_cancellation_reasons,id', 'cancellation_observations' => 'nullable|string', ]); DB::beginTransaction(); $tag = Tag::where('tag_number', $request->tag_number)->first(); // Validar que el tag NO esté asignado if ($tag->isAssigned()) { return ApiResponse::BAD_REQUEST->response([ 'message' => 'Este tag está asignado a un vehículo. Usa el endpoint de cancelación de constancia.', 'tag_status' => $tag->status->name, ]); } // Validar que no esté ya cancelado if ($tag->isCancelled()) { return ApiResponse::BAD_REQUEST->response([ 'message' => 'El tag ya está cancelado.', ]); } // Crear log de cancelación ANTES de cancelar el tag $cancellationLog = TagCancellationLog::create([ 'tag_id' => $tag->id, 'cancellation_reason_id' => $request->cancellation_reason_id, 'cancellation_observations' => $request->cancellation_observations, 'cancellation_at' => now(), 'cancelled_by' => Auth::id(), ]); // Cancelar el tag $tag->markAsCancelled(); DB::commit(); return ApiResponse::OK->response([ 'message' => 'Tag cancelado exitosamente', 'tag' => [ 'id' => $tag->id, 'tag_number' => $tag->tag_number, 'folio' => $tag->folio, 'previous_status' => 'Disponible', 'new_status' => 'Cancelado', ], 'cancellation' => [ 'id' => $cancellationLog->id, 'reason' => $cancellationLog->cancellationReason->name, 'observations' => $cancellationLog->cancellation_observations, 'cancelled_at' => $cancellationLog->cancellation_at->toDateTimeString(), 'cancelled_by' => Auth::user()->name, ], ]); } catch (\Exception $e) { DB::rollBack(); return ApiResponse::BAD_REQUEST->response([ 'message' => 'Error al cancelar el tag', 'error' => $e->getMessage(), ]); } } }