From 048742175802647f53c95b675239d3a80cea6af7 Mon Sep 17 00:00:00 2001 From: Juan Felipe Zapata Moreno Date: Fri, 9 Jan 2026 14:49:00 -0600 Subject: [PATCH] =?UTF-8?q?add:=20colas=20y=20correci=C3=B3n=20de=20detecc?= =?UTF-8?q?i=C3=B3n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Http/Controllers/Api/ArcoController.php | 2 +- .../Controllers/Api/VehicleController.php | 25 ++- app/Http/Middleware/ArcoTokenMiddleware.php | 19 -- app/Jobs/ProcesarDeteccionVehiculo.php | 70 +++++++ app/Services/ReporteRoboService.php | 188 ++++++++++++++++++ app/Services/VehicleService.php | 110 +++++++--- docker-compose.yml | 2 +- 7 files changed, 354 insertions(+), 62 deletions(-) create mode 100644 app/Jobs/ProcesarDeteccionVehiculo.php diff --git a/app/Http/Controllers/Api/ArcoController.php b/app/Http/Controllers/Api/ArcoController.php index bb84827..409a1a9 100644 --- a/app/Http/Controllers/Api/ArcoController.php +++ b/app/Http/Controllers/Api/ArcoController.php @@ -194,7 +194,7 @@ public function deteccionesDelDia(Request $request, int $id) // Filtrar solo VIN, placa, antena y si es robado $deteccionesFiltradas = array_map(function($deteccion) { return [ - 'vin' => $deteccion['fast_id'] ?? null, + 'vin' => $deteccion['vin'] ?? null, 'placa' => $deteccion['placa'] ?? null, 'antena' => $deteccion['antena'] ?? null, 'robado' => $deteccion['tiene_reporte_robo'] ?? false, diff --git a/app/Http/Controllers/Api/VehicleController.php b/app/Http/Controllers/Api/VehicleController.php index 91659eb..97d954a 100644 --- a/app/Http/Controllers/Api/VehicleController.php +++ b/app/Http/Controllers/Api/VehicleController.php @@ -3,6 +3,7 @@ namespace App\Http\Controllers\Api; use App\Http\Controllers\Controller; +use App\Jobs\ProcesarDeteccionVehiculo; use App\Models\Vehicle; use App\Models\Detection; use App\Services\VehicleService; @@ -396,22 +397,20 @@ public function buscarPorTag(Request $request) // Obtener el arco autenticado del middleware $arco = $request->get('arco_autenticado'); - $resultado = $this->vehicleService->procesarDeteccion( - $validated['fast_id'], - $arco->ip_address, - $validated['antena'] ?? null - ); - - if (!$resultado['success']) { - return ApiResponse::OK->response([ - 'success' => false, - 'message' => 'Error, NO se encontró el vehiculo' - ]); - } + ProcesarDeteccionVehiculo::dispatch( + $validated['fast_id'], + $arco->id, + $validated['antena'] ?? null + ); return ApiResponse::OK->response([ 'success' => true, - 'message' => 'Vehiculo registrado exitosamente' + 'message' => 'Detección recibida y en proceso', + 'fast_id' => $validated['fast_id'], + 'arco' => [ + 'id' => $arco->id, + 'nombre' => $arco->nombre + ] ]); } } diff --git a/app/Http/Middleware/ArcoTokenMiddleware.php b/app/Http/Middleware/ArcoTokenMiddleware.php index 020cb55..cf8e086 100644 --- a/app/Http/Middleware/ArcoTokenMiddleware.php +++ b/app/Http/Middleware/ArcoTokenMiddleware.php @@ -38,25 +38,6 @@ public function handle(Request $request, Closure $next): Response ]); } - // Validación de IP deshabilitada para desarrollo - // PROBLEMA: En Docker, $request->ip() retorna la IP del gateway (172.x.x.x), no la IP real del cliente - // SOLUCIÓN PRODUCCIÓN: Usar un proxy inverso externo (nginx/traefik) que pase X-Forwarded-For correctamente - // Para desarrollo, la validación de token es suficiente - /* - $requestIp = $request->ip(); - if ($arco->ip_address !== $requestIp) { - return ApiResponse::FORBIDDEN->response([ - 'message' => 'Token no autorizado para esta IP', - 'detail' => [ - 'ip_registrada_arco' => $arco->ip_address, - 'ip_del_request' => $requestIp, - 'arco_nombre' => $arco->nombre - ] - ]); - } - */ - - // Agregar el arco al request para uso posterior $request->merge(['arco_autenticado' => $arco]); diff --git a/app/Jobs/ProcesarDeteccionVehiculo.php b/app/Jobs/ProcesarDeteccionVehiculo.php new file mode 100644 index 0000000..52dfeb4 --- /dev/null +++ b/app/Jobs/ProcesarDeteccionVehiculo.php @@ -0,0 +1,70 @@ +procesarDeteccion( + $this->fastId, + $this->arcoId, + $this->antena + ); + } catch (\Exception $e) { + // Re-lanzar la excepción para que Laravel reintente el job + throw $e; + } + } + + /** + * Manejar fallo del job después de todos los reintentos + */ + public function failed(\Throwable $exception): void + { + Log::error('ProcesarDeteccionVehiculo: FALLIDO después de todos los intentos', [ + 'fast_id' => $this->fastId, + 'arco_id' => $this->arcoId, + 'antena' => $this->antena, + 'error' => $exception->getMessage(), + 'intentos_realizados' => $this->tries + ]); + } +} diff --git a/app/Services/ReporteRoboService.php b/app/Services/ReporteRoboService.php index 6c5b4ab..5fdc083 100644 --- a/app/Services/ReporteRoboService.php +++ b/app/Services/ReporteRoboService.php @@ -179,4 +179,192 @@ private function parseRoboResponse(string $soapResponse, string $valor) 'error' => 'Formato de respuesta no reconocido' ]; } + + /** + * Consultar datos del vehículo (sin reporte de robo) + */ + public function consultarVehiculo(?string $niv = null, ?string $placa = null): array + { + try { + if (empty($niv) && empty($placa)) { + Log::warning('ReporteRoboService: consultarVehiculo sin NIV ni PLACA'); + return [ + 'success' => false, + 'has_error' => true, + 'error_message' => 'Debe proporcionar al menos NIV o PLACA', + 'datos' => [] + ]; + } + + $url = $this->baseUrl . '/jaxws-consultarpv/ConsultaRpv'; + + // Construir arg2 + if ($placa) { + $arg2 = ($niv ?? '') . '|' . $placa . str_repeat('|', 5); + } else { + $arg2 = ($niv ?? '') . str_repeat('|', 7); + } + + $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_TIMEOUT, 30); + curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10); + curl_setopt($ch, CURLOPT_HTTPHEADER, [ + 'Content-Type: text/xml; charset=utf-8', + 'SOAPAction: "doConsRPV"', + 'Content-Length: ' . strlen($soapBody), + ]); + + try { + $response = curl_exec($ch); + $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); + $error = curl_error($ch); + + if ($error) { + Log::error('ReporteRoboService: consultarVehiculo - Error de conexión', [ + 'error' => $error, + 'niv' => $niv, + 'placa' => $placa + ]); + return [ + 'success' => false, + 'has_error' => true, + 'error_message' => 'Error de conexión', + 'datos' => [] + ]; + } + + if ($httpCode !== 200) { + Log::error('ReporteRoboService: consultarVehiculo - HTTP error', [ + 'http_code' => $httpCode, + 'niv' => $niv, + 'placa' => $placa + ]); + return [ + 'success' => false, + 'has_error' => true, + 'error_message' => "Error HTTP {$httpCode}", + 'datos' => [] + ]; + } + + return $this->parseConsultaVehiculoResponse($response); + + } finally { + unset($ch); + } + } catch (\Exception $e) { + Log::error('ReporteRoboService: consultarVehiculo - Excepción', [ + 'niv' => $niv, + 'placa' => $placa, + 'exception' => $e->getMessage() + ]); + return [ + 'success' => false, + 'has_error' => true, + 'error_message' => $e->getMessage(), + 'datos' => [] + ]; + } + } + + /** + * Parsear respuesta de consulta de vehículo + * Extrae solo: VIN, placa, marca, modelo y color + */ + private function parseConsultaVehiculoResponse(string $soapResponse): array + { + // Extraer contenido del tag + preg_match('/(.*?)<\/return>/s', $soapResponse, $matches); + + if (!isset($matches[1])) { + Log::error('ReporteRoboService: parseConsultaVehiculoResponse - Respuesta inválida'); + return [ + 'success' => false, + 'has_error' => true, + 'error_message' => 'Respuesta inválida del servicio', + 'datos' => [] + ]; + } + + $contenido = trim($matches[1]); + + // Verificar si hay error + if (preg_match('/(ERR|ERROR):(-?\d+)/i', $contenido, $errorMatch)) { + $errorCode = $errorMatch[2]; + Log::warning('ReporteRoboService: parseConsultaVehiculoResponse - Error del servicio', [ + 'error_code' => $errorCode + ]); + return [ + 'success' => false, + 'has_error' => true, + 'error_message' => "Error REPUVE código {$errorCode}", + 'datos' => [] + ]; + } + + // Si empieza con OK:, parsear los datos + if (str_starts_with($contenido, 'OK:')) { + $datos = str_replace('OK:', '', $contenido); + $valores = explode('|', $datos); + + // Estructura esperada del REPUVE Nacional + // Posiciones aproximadas basadas en el servicio + $datosVehiculo = [ + 'vin' => $valores[0] ?? null, + 'placa' => $valores[1] ?? null, + 'marca' => $valores[2] ?? null, + 'modelo' => $valores[3] ?? null, + 'color' => $valores[4] ?? null, + ]; + + // Filtrar valores vacíos + $datosVehiculo = array_filter($datosVehiculo, fn($v) => !empty($v)); + + if (empty($datosVehiculo)) { + return [ + 'success' => false, + 'has_error' => false, + 'error_message' => 'No se encontró información del vehículo', + 'datos' => [] + ]; + } + + Log::info('ReporteRoboService: Vehículo encontrado en REPUVE Nacional', [ + 'vin' => $datosVehiculo['vin'] ?? null, + 'placa' => $datosVehiculo['placa'] ?? null + ]); + + return [ + 'success' => true, + 'has_error' => false, + 'datos' => $datosVehiculo + ]; + } + + Log::error('ReporteRoboService: parseConsultaVehiculoResponse - Formato no reconocido', [ + 'contenido' => $contenido + ]); + return [ + 'success' => false, + 'has_error' => true, + 'error_message' => 'Formato de respuesta no reconocido', + 'datos' => [] + ]; + } } diff --git a/app/Services/VehicleService.php b/app/Services/VehicleService.php index c6a090a..badf570 100644 --- a/app/Services/VehicleService.php +++ b/app/Services/VehicleService.php @@ -11,20 +11,17 @@ class VehicleService { public function __construct( - private ConsultaRepuveConstancia $consultaRepuveCons + private ConsultaRepuveConstancia $consultaRepuveCons, + private ReporteRoboService $reporteRoboService // Agregar esta dependencia ) {} /** * Procesar detección de vehículo por fast_id */ - public function procesarDeteccion(string $fastId, string $arcoIp, ?string $antena = null): array + public function procesarDeteccion(string $fastId, int $arcoId, ?string $antena = null): array { $key = "vehiculo:robado:{$fastId}"; - // Buscar arco por IP - $arco = Arco::buscarPorIp($arcoIp); - $arcoId = $arco?->id; - // Verificar si está en Redis $enRedis = Redis::get($key); @@ -47,22 +44,49 @@ public function procesarDeteccion(string $fastId, string $arcoIp, ?string $anten public function consultarVehiculoPorTag(string $fastId) { try { + //Intentar con REPUVE Constancia primero $vehiculoExterno = $this->consultaRepuveCons->consultarVehiculoPorTag($fastId); if ($vehiculoExterno) { - Log::info('VehicleService: Vehículo encontrado', [ + Log::info('VehicleService: Vehículo encontrado en REPUVE Constancia', [ 'fast_id' => $fastId, 'tag_number' => $vehiculoExterno['tag_number'] ?? null, 'placa' => $vehiculoExterno['placa'] ?? null, 'vin' => $vehiculoExterno['vin'] ?? null ]); - } else { - Log::info('VehicleService: Vehículo no encontrado', [ - 'fast_id' => $fastId - ]); + return $vehiculoExterno; } - return $vehiculoExterno; + // Si no se encuentra, intentar con REPUVE Nacional + Log::info('VehicleService: Intentando consulta en REPUVE Nacional', [ + 'fast_id' => $fastId + ]); + + $resultadoNacional = $this->reporteRoboService->consultarVehiculo($fastId); + + if ($resultadoNacional['success'] && !empty($resultadoNacional['datos'])) { + Log::info('VehicleService: Vehículo encontrado en REPUVE Nacional', [ + 'fast_id' => $fastId, + 'datos' => $resultadoNacional['datos'] + ]); + + // Adaptar formato de REPUVE Nacional al formato esperado + $datos = $resultadoNacional['datos']; + return [ + 'tag_number' => $fastId, + 'vin' => $datos['vin'] ?? null, + 'placa' => $datos['placa'] ?? null, + 'marca' => $datos['marca'] ?? null, + 'modelo' => $datos['modelo'] ?? null, + 'color' => $datos['color'] ?? null, + 'origen' => 'REPUVE_NACIONAL' + ]; + } + + Log::info('VehicleService: Vehículo no encontrado en ningún servicio', [ + 'fast_id' => $fastId + ]); + return null; } catch (\Exception $e) { Log::error('VehicleService: Error en consulta', [ @@ -162,31 +186,61 @@ public function listarDeteccionesDelDiaPorArco(int $arcoId, ?string $fecha = nul */ private function consultarNuevoVehiculo(string $fastId): array { - // Consultar con FastID (tag_number) $datosVehiculo = $this->consultaRepuveCons->consultarVehiculoPorTag($fastId); - if (!$datosVehiculo || !$datosVehiculo['vin']) { - Log::warning('Vehículo NO encontrado.', [ - 'fast_id' => $fastId + if ($datosVehiculo && isset($datosVehiculo['vin'])) { + Log::info('Vehículo encontrado en RepuveConstancia - LIBRE', [ + 'fast_id' => $fastId, + 'vin' => $datosVehiculo['vin'], + 'placa' => $datosVehiculo['placa'] ?? null, + 'fuente' => 'RepuveConstancia' ]); return [ - 'success' => false, - 'message' => 'No se encontró información del vehículo.' + 'success' => true, + 'tiene_reporte_robo' => false, + 'estado' => 'LIBRE', + 'vehiculo' => [ + 'vin' => $datosVehiculo['vin'] ?? null, + 'placa' => $datosVehiculo['placa'] ?? null, + 'marca' => $datosVehiculo['marca'] ?? null, + 'modelo' => $datosVehiculo['modelo'] ?? null, + 'color' => $datosVehiculo['color'] ?? null, + ], + 'fuente' => 'RepuveConstancia' + ]; + } + $resultadoNacional = $this->reporteRoboService->consultarVehiculo($fastId, null); + + if ($resultadoNacional['success'] && !empty($resultadoNacional['datos'])) { + $datos = $resultadoNacional['datos']; + + Log::info('Vehículo encontrado en RepuveNacional - LIBRE', [ + 'fast_id' => $fastId, + 'vin' => $datos['vin'] ?? null, + 'placa' => $datos['placa'] ?? null, + 'fuente' => 'RepuveNacional' + ]); + + return [ + 'success' => true, + 'tiene_reporte_robo' => false, + 'estado' => 'LIBRE', + 'vehiculo' => [ + 'vin' => $datos['vin'] ?? null, + 'placa' => $datos['placa'] ?? null, + 'marca' => $datos['marca'] ?? null, + 'modelo' => $datos['modelo'] ?? null, + 'color' => $datos['color'] ?? null, + ], + 'fuente' => 'RepuveNacional' ]; } - Log::info('Vehículo detectado - LIBRE (no está en Redis de robados)', [ - 'fast_id' => $fastId, - 'vin' => $datosVehiculo['vin'], - 'placa' => $datosVehiculo['placa'] - ]); - return [ - 'success' => true, - 'tiene_reporte_robo' => false, - 'estado' => 'LIBRE', - 'vehiculo' => $datosVehiculo + 'success' => false, + 'message' => 'No se encontró información del vehículo en ningún servicio', + 'fuente' => 'ninguno' ]; } diff --git a/docker-compose.yml b/docker-compose.yml index 4f09e94..c9d8f4a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -45,7 +45,7 @@ services: MYSQL_PASSWORD: ${DB_PASSWORD} MYSQL_USER: ${DB_USERNAME} ports: - - ${DB_PORT}:${DB_PORT} + - ${DB_PORT}:3306 volumes: - mysql_data:/var/lib/mysql networks: