From 8c6afe40bc5e913c8c686ff6ea8c36e701ceebee Mon Sep 17 00:00:00 2001 From: Juan Felipe Zapata Moreno Date: Fri, 5 Dec 2025 21:55:00 -0600 Subject: [PATCH] =?UTF-8?q?ADD:=20Implementar=20generaci=C3=B3n=20de=20PDF?= =?UTF-8?q?=20para=20tags=20sustituidos,=20correci=C3=B3n=20de=20Reporte?= =?UTF-8?q?=20Robo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Repuve/InscriptionController.php | 57 ------ .../Controllers/Repuve/RecordController.php | 90 ++++++++++ app/Services/RepuveService.php | 167 +++++++++++++++++- .../views/pdfs/tag_sustitution.blade.php | 155 ++++++++++++++++ routes/api.php | 1 + 5 files changed, 406 insertions(+), 64 deletions(-) create mode 100644 resources/views/pdfs/tag_sustitution.blade.php diff --git a/app/Http/Controllers/Repuve/InscriptionController.php b/app/Http/Controllers/Repuve/InscriptionController.php index 70f6a87..5d24243 100644 --- a/app/Http/Controllers/Repuve/InscriptionController.php +++ b/app/Http/Controllers/Repuve/InscriptionController.php @@ -283,63 +283,6 @@ private function checkIfStolen(string $niv) return $this->repuveService->verificarRobo($niv); } - public function stolen(Request $request) - { - $request->validate([ - 'folio' => ['sometimes', 'string', 'max:50'], - 'tag_number' => ['sometimes', 'string', 'exists:tags,tag_number'], - 'vin' => ['sometimes', 'string', 'max:30'], - ]); - - $folio = $request->input('folio'); - $tagNumber = $request->input('tag_number'); - $vin = $request->input('vin'); - - // Validar que se proporcione al menos un criterio de búsqueda - if (!$folio && !$tagNumber && !$vin) { - return ApiResponse::BAD_REQUEST->response([ - 'message' => 'Debe proporcionar al menos un criterio de búsqueda: folio y tag_number, o vin', - ]); - } - $query = Record::with(['vehicle.owner', 'vehicle.tag']); - - // Búsqueda por folio ó tag_number - if ($folio && !$tagNumber) { - $query->where('folio', $folio); - } elseif (!$folio && $tagNumber) { - $query->whereHas('vehicle.tag', function ($q) use ($tagNumber) { - $q->where('tag_number', $tagNumber); - }); - } // Búsqueda solo por VIN - elseif ($vin) { - $query->whereHas('vehicle', function ($q) use ($vin) { - $q->where('niv', $vin); - }); - } - - $record = $query->first(); - - if (!$record) { - return ApiResponse::NOT_FOUND->response([ - 'stolen' => false, - 'message' => 'No se encontró ningún registro con los criterios proporcionados', - ]); - } - - return ApiResponse::OK->response([ - 'stolen' => true, - 'vehicle' => [ - 'folio' => $record->folio, - 'placa' => $record->vehicle->placa, - 'tag_number' => $record->vehicle->tag?->tag_number, - 'vin' => $record->vehicle->niv, - 'marca' => $record->vehicle->marca, - 'modelo' => $record->vehicle->linea, - 'año' => $record->vehicle->modelo, - ], - ]); - } - public function searchRecord(Request $request) { $request->validate([ diff --git a/app/Http/Controllers/Repuve/RecordController.php b/app/Http/Controllers/Repuve/RecordController.php index 80575f9..42a95c0 100644 --- a/app/Http/Controllers/Repuve/RecordController.php +++ b/app/Http/Controllers/Repuve/RecordController.php @@ -280,6 +280,45 @@ public function pdfCancelledTag(Tag $tag) } } + public function pdfSubstitutedTag(Tag $tag) + { + try { + // Validar que el tag tenga una sustitución registrada + $hasSubstitution = $tag->vehicleTagLogs() + ->where('action_type', 'sustitucion') + ->whereNotNull('cancellation_at') + ->exists(); + + if (!$hasSubstitution) { + return ApiResponse::BAD_REQUEST->response([ + 'message' => 'Solo se puede generar PDF para tags sustituidos.', + 'tag_folio' => $tag->folio, + ]); + } + + // Obtener datos de sustitución + $substitutionData = $this->substitutionData($tag); + + $pdf = Pdf::loadView('pdfs.tag_substituted', [ + 'substitution' => $substitutionData, + ]) + ->setPaper('a4', 'portrait') + ->setOptions([ + 'defaultFont' => 'sans-serif', + 'isHtml5ParserEnabled' => true, + 'isRemoteEnabled' => true, + ]); + + return $pdf->stream('constancia_sustituida_' . $tag->folio . '.pdf'); + } catch (\Exception $e) { + return ApiResponse::INTERNAL_ERROR->response([ + 'message' => 'Error al generar el PDF.', + 'error' => $e->getMessage(), + ]); + } + } + + private function cancellationData(Tag $tag) { $data = [ @@ -347,6 +386,57 @@ private function cancellationData(Tag $tag) return $data; } + private function substitutionData(Tag $tag) + { + $data = [ + 'fecha' => now()->format('d/m/Y'), + 'folio' => $tag->folio ?? '', + 'folio_sustituto' => '', + 'id_chip' => '', + 'placa' => '', + 'niv' => '', + 'motivo' => 'N/A', + 'operador' => 'N/A', + 'modulo' => '', + 'ubicacion' => '', + ]; + + // log de CANCELACIÓN del tag original + $oldTagLog = $tag->vehicleTagLogs() + ->where('action_type', 'sustitucion') + ->whereNotNull('cancellation_at') + ->with(['cancellationReason', 'cancelledBy', 'vehicle']) + ->latest() + ->first(); + + if (!$oldTagLog) { + return $data; // No se encontró sustitución + } + + // 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'; + + // módulo del usuario + if ($oldTagLog->cancelledBy) { + $this->loadUserModule($oldTagLog->cancelledBy, $data); + } + + // datos del vehículo + if ($oldTagLog->vehicle) { + $data['id_chip'] = $oldTagLog->vehicle->id_chip ?? ''; + $data['placa'] = $oldTagLog->vehicle->placa ?? ''; + $data['niv'] = $oldTagLog->vehicle->niv ?? ''; + + // tag NUEVO + $newTag = $oldTagLog->vehicle->tag; + $data['folio_sustituto'] = $newTag?->folio ?? ''; + } + + return $data; + } + /** * Cargar módulo del usuario */ diff --git a/app/Services/RepuveService.php b/app/Services/RepuveService.php index 4f374a3..81f4670 100644 --- a/app/Services/RepuveService.php +++ b/app/Services/RepuveService.php @@ -167,25 +167,83 @@ private function parseVehicleResponse(string $soapResponse, string $niv) /** * Verifica si un vehículo está reportado como robado */ - public function verificarRobo(string $niv) + public function verificarRobo(string $niv): bool { try { - $resultado = $this->consultarPadron($niv); + $url = $this->baseUrl . $this->roboEndpoint; - if ($resultado['has_error']) { + $arg2 = $niv . str_repeat('|', 8); + + $soapBody = << + + + + {$this->username} + {$this->password} + {$arg2} + + + + XML; + + $ch = curl_init($url); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_POSTFIELDS, $soapBody); + curl_setopt($ch, CURLOPT_HTTPHEADER, [ + 'Content-Type: text/xml; charset=utf-8', + 'SOAPAction: "doConsRepRobo"', + 'Content-Length: ' . strlen($soapBody), + ]); + + $response = curl_exec($ch); + $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); + $error = curl_error($ch); + curl_close($ch); + + // Si hay error de conexión, asumir que NO está robado + if ($error) { + logger()->error('REPUVE verificarRobo: Error de conexión', [ + 'error' => $error, + 'niv' => $niv, + ]); return false; } - $estatus = $resultado['repuve_response']['estatus_registro'] ?? null; + // Si hay error HTTP, asumir que NO está robado + if ($httpCode !== 200) { + logger()->error('REPUVE verificarRobo: HTTP error', [ + 'http_code' => $httpCode, + 'niv' => $niv, + ]); + return false; + } + + // Parsear respuesta + $resultado = $this->parseRoboResponse($response, $niv); + + // Si hubo error al parsear, asumir que NO está robado + if ($resultado['has_error'] ?? true) { + logger()->warning('REPUVE verificarRobo: Error al parsear respuesta', [ + 'niv' => $niv, + 'error' => $resultado['error_message'] ?? 'Desconocido', + ]); + return false; + } + + // Retornar si está robado o no + return $resultado['is_robado'] ?? false; - return in_array(strtoupper($estatus), ['ROBADO', 'ROBO']); } catch (Exception $e) { - logger()->error("Error al verificar robo en REPUVE: " . $e->getMessage()); + logger()->error('REPUVE verificarRobo: Excepción capturada', [ + 'niv' => $niv, + 'exception' => $e->getMessage(), + ]); return false; } } - public function inscribirVehiculo(array $datos) { $url = $this->baseUrl . $this->inscripcionEndpoint; @@ -389,4 +447,99 @@ private function parsearRespuestaInscripcion(string $soapResponse) ], ]; } + + private function parseRoboResponse(string $soapResponse, string $valor): array + { + // Extraer contenido del tag + preg_match('/(.*?)<\/return>/s', $soapResponse, $matches); + + if (!isset($matches[1])) { + logger()->error('REPUVE parseRoboResponse: No se encontró tag ', [ + 'soap_response' => substr($soapResponse, 0, 500), + 'valor' => $valor, + ]); + + return [ + 'has_error' => true, + 'is_robado' => false, + 'error_message' => 'Respuesta SOAP inválida', + ]; + } + + $contenido = trim($matches[1]); + + // Verificar si hay error de REPUVE Nacional (ERR: o ERROR:) + if (preg_match('/(ERR|ERROR|err|error):(-?\d+)/i', $contenido, $errorMatch)) { + $errorCode = $errorMatch[2]; + + logger()->warning('REPUVE parseRoboResponse: Servicio retornó error', [ + 'error_code' => $errorCode, + 'contenido' => $contenido, + 'valor' => $valor, + ]); + + return [ + 'has_error' => true, + 'is_robado' => false, + 'error_code' => $errorCode, + 'error_message' => "Error REPUVE código {$errorCode}", + 'raw_response' => $contenido, + ]; + } + + // Si empieza con OK:, parsear los datos de robo + if (str_starts_with($contenido, 'OK:')) { + $datos = str_replace('OK:', '', $contenido); + $valores = explode('|', $datos); + + // 1 = robado, 0 = no robado + $indicador = $valores[0] ?? '0'; + $isRobado = ($indicador === '1'); + + $roboData = [ + 'has_error' => false, + 'is_robado' => $isRobado, + 'indicador' => $indicador, + 'fecha_robo' => $valores[1] ?? null, + 'placa' => $valores[2] ?? null, + 'niv' => $valores[3] ?? null, + 'autoridad' => $valores[4] ?? null, + 'acta' => $valores[5] ?? null, + 'denunciante' => $valores[6] ?? null, + 'fecha_acta' => $valores[7] ?? null, + 'raw_response' => $contenido, + ]; + + // Log importante si está robado + if ($isRobado) { + logger()->warning('REPUVE: Vehículo reportado como ROBADO', [ + 'valor_buscado' => $valor, + 'niv' => $roboData['niv'], + 'placa' => $roboData['placa'], + 'autoridad' => $roboData['autoridad'], + 'acta' => $roboData['acta'], + 'denunciante' => $roboData['denunciante'], + ]); + } else { + logger()->info('REPUVE: Vehículo NO reportado como robado', [ + 'valor_buscado' => $valor, + ]); + } + + return $roboData; + } + + // Si no tiene formato reconocido + logger()->error('REPUVE parseRoboResponse: Formato desconocido', [ + 'contenido' => $contenido, + 'valor' => $valor, + ]); + + return [ + 'has_error' => true, + 'is_robado' => false, + 'error_message' => 'Formato de respuesta no reconocido', + 'raw_response' => $contenido, + ]; + } } diff --git a/resources/views/pdfs/tag_sustitution.blade.php b/resources/views/pdfs/tag_sustitution.blade.php new file mode 100644 index 0000000..263ee34 --- /dev/null +++ b/resources/views/pdfs/tag_sustitution.blade.php @@ -0,0 +1,155 @@ + + + + + Constancia Sustituida - REPUVE Tabasco + + + +
+ +
+ REPUVE TABASCO +
+ + +
+ CONSTANCIA SUSTITUIDA +
+ +
+
+ PEGAR CONSTANCIA +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FECHA:{{ $substitution['fecha'] }}
FOLIO:{{ $substitution['folio'] ?? '' }}
ID CHIP:{{ $substitution['id_chip'] ?? '' }}
FOLIO QUE SUSTITUYE:{{ $substitution['folio_sustituto'] ?? '' }}
PLACAS:{{ $substitution['placa'] ?? '' }}
VIN:{{ $substitution['niv'] ?? '' }}
MOTIVO:{{ strtoupper($substitution['motivo'] ?? '') }}
OPERADOR:{{ strtoupper($substitution['operador'] ?? '') }}
MÓDULO:{{ $substitution['modulo']}}
UBICACIÓN:{{ $substitution['ubicacion'] }}
+
+ + diff --git a/routes/api.php b/routes/api.php index 06bd098..6b1f75f 100644 --- a/routes/api.php +++ b/routes/api.php @@ -42,6 +42,7 @@ Route::get('expediente/{id}/pdfConstancia', [RecordController::class, 'generatePdfConstancia']); Route::get('expediente/{id}/pdfImagenes', [RecordController::class, 'generatePdfImages']); Route::get('tags/{tag}/pdfTag-cancelado', [RecordController::class, 'pdfCancelledTag']); + Route::get('tags/{tag}/pdfTag-sustituido', [RecordController::class, 'pdfSubstitutedTag']); Route::get('expediente/{id}/pdfFormulario', [RecordController::class, 'generatePdfForm']); Route::get('RecordErrors', [RecordController::class, 'errors']);