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 ]); } // 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_folio') && $request->filled('new_tag_number'); if ($isSubstitution) { $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, ]); } 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, ]); } if (!$newTag->tag_number) { $existingTag = Tag::where('tag_number', $request->new_tag_number)->first(); if ($existingTag && $existingTag->id !== $newTag->id) { DB::rollBack(); return ApiResponse::BAD_REQUEST->response([ 'message' => 'El nuevo tag_number ya está asignado a otro folio.', 'new_tag_number' => $request->new_tag_number, 'folio_existente' => $existingTag->folio, ]); } $newTag->tag_number = $request->new_tag_number; $newTag->save(); } 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.', 'assigned_tag_number' => $newTag->tag_number, 'provided_tag_number' => $request->new_tag_number, ]); } // 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([ 'folio' => 'required|string|exists:tags,folio', 'cancellation_reason_id' => 'required|exists:catalog_cancellation_reasons,id', 'cancellation_observations' => 'nullable|string', 'id_chip' => 'nullable|string|max:255', 'module_id' => 'nullable|exists:modules,id', ]); DB::beginTransaction(); $tag = Tag::where('folio', $request->folio)->first(); if (!$tag) { DB::rollBack(); return ApiResponse::NOT_FOUND->response([ 'message' => 'No se encontró el tag con el folio proporcionado.', 'folio' => $request->folio, ]); } // 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.', ]); } $additionalInfo = []; if ($request->filled('id_chip')) { $additionalInfo[] = "ID CHIP: {$request->id_chip}"; } $observations = $request->cancellation_observations; if (!empty($additionalInfo)) { $observations = ($observations ? $observations . ' | ' : '') . implode(' | ', $additionalInfo); } $cancellationLog = TagCancellationLog::create([ 'tag_id' => $tag->id, 'cancellation_reason_id' => $request->cancellation_reason_id, 'cancellation_observations' => $observations, 'cancellation_at' => now(), 'cancelled_by' => Auth::id(), ]); // Actualizar el módulo del tag si se proporciona if ($request->filled('module_id')) { $tag->module_id = $request->module_id; $tag->save(); } // Cancelar el tag $tag->markAsDamaged(); DB::commit(); try { // Recargar el tag con sus relaciones actualizadas $tag->load(['status', 'cancellationLogs.cancellationReason', 'cancellationLogs.cancelledBy', 'module']); // Obtener datos de cancelación del último log $lastCancellation = $tag->cancellationLogs() ->with(['cancellationReason', 'cancelledBy']) ->latest() ->first(); // Preparar datos para el PDF $cancellationData = [ 'fecha' => $lastCancellation ? $lastCancellation->cancellation_at->format('d/m/Y') : now()->format('d/m/Y'), 'folio' => $tag->folio ?? '', 'id_chip' => $request->id_chip ?? '', 'motivo' => $lastCancellation && $lastCancellation->cancellationReason ? $lastCancellation->cancellationReason->name : 'No especificado', 'operador' => $lastCancellation && $lastCancellation->cancelledBy ? $lastCancellation->cancelledBy->name : 'Sistema', 'modulo' => $tag->module ? $tag->module->name : 'No especificado', 'ubicacion' => $tag->module ? $tag->module->address : 'No especificado', ]; $pdf = Pdf::loadView('pdfs.tag', [ 'cancellation' => $cancellationData, ]) ->setPaper('a4', 'portrait') ->setOptions([ 'defaultFont' => 'sans-serif', 'isHtml5ParserEnabled' => true, 'isRemoteEnabled' => true, ]); return $pdf->stream('constancia_cancelada_' . ($tag->tag_number ?? $tag->folio) . '.pdf'); } catch (\Exception $e) { // Si falla la generación del PDF, devolver respuesta JSON Log::error('Error al generar PDF de tag cancelado: ' . $e->getMessage()); return ApiResponse::OK->response([ 'message' => 'Tag cancelado exitosamente (Error al generar PDF)', 'tag' => [ 'id' => $tag->id, 'tag_number' => $tag->tag_number, 'folio' => $tag->folio, 'previous_status' => 'Disponible', 'new_status' => 'Dañado', ], 'cancellation' => [ 'id' => $cancellationLog->id, 'reason' => $cancellationLog->cancellationReason->name, 'observations' => $cancellationLog->cancellation_observations, 'cancelled_at' => $cancellationLog->cancellation_at->toDateTimeString(), 'cancelled_by' => Auth::user()->name, ], 'pdf_error' => $e->getMessage(), ]); } } catch (\Exception $e) { DB::rollBack(); return ApiResponse::BAD_REQUEST->response([ 'message' => 'Error al cancelar el tag', 'error' => $e->getMessage(), ]); } } }