add: colas y correción de detección

This commit is contained in:
Juan Felipe Zapata Moreno 2026-01-09 14:49:00 -06:00
parent c2171c1247
commit 0487421758
7 changed files with 354 additions and 62 deletions

View File

@ -194,7 +194,7 @@ public function deteccionesDelDia(Request $request, int $id)
// Filtrar solo VIN, placa, antena y si es robado // Filtrar solo VIN, placa, antena y si es robado
$deteccionesFiltradas = array_map(function($deteccion) { $deteccionesFiltradas = array_map(function($deteccion) {
return [ return [
'vin' => $deteccion['fast_id'] ?? null, 'vin' => $deteccion['vin'] ?? null,
'placa' => $deteccion['placa'] ?? null, 'placa' => $deteccion['placa'] ?? null,
'antena' => $deteccion['antena'] ?? null, 'antena' => $deteccion['antena'] ?? null,
'robado' => $deteccion['tiene_reporte_robo'] ?? false, 'robado' => $deteccion['tiene_reporte_robo'] ?? false,

View File

@ -3,6 +3,7 @@
namespace App\Http\Controllers\Api; namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Jobs\ProcesarDeteccionVehiculo;
use App\Models\Vehicle; use App\Models\Vehicle;
use App\Models\Detection; use App\Models\Detection;
use App\Services\VehicleService; use App\Services\VehicleService;
@ -396,22 +397,20 @@ public function buscarPorTag(Request $request)
// Obtener el arco autenticado del middleware // Obtener el arco autenticado del middleware
$arco = $request->get('arco_autenticado'); $arco = $request->get('arco_autenticado');
$resultado = $this->vehicleService->procesarDeteccion( ProcesarDeteccionVehiculo::dispatch(
$validated['fast_id'], $validated['fast_id'],
$arco->ip_address, $arco->id,
$validated['antena'] ?? null $validated['antena'] ?? null
); );
if (!$resultado['success']) {
return ApiResponse::OK->response([
'success' => false,
'message' => 'Error, NO se encontró el vehiculo'
]);
}
return ApiResponse::OK->response([ return ApiResponse::OK->response([
'success' => true, '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
]
]); ]);
} }
} }

View File

@ -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 // Agregar el arco al request para uso posterior
$request->merge(['arco_autenticado' => $arco]); $request->merge(['arco_autenticado' => $arco]);

View File

@ -0,0 +1,70 @@
<?php namespace App\Jobs;
use App\Services\VehicleService;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;
class ProcesarDeteccionVehiculo implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
/**
* Número de intentos antes de marcar como fallido
*/
public $tries = 3;
/**
* Timeout en segundos (2 minutos)
*/
public $timeout = 120;
/**
* Tiempo entre reintentos en segundos
*/
public $backoff = 10;
/**
* Constructor del Job
*/
public function __construct(
public string $fastId,
public int $arcoId,
public ?string $antena = null
) {}
/**
* Ejecutar el job
*/
public function handle(VehicleService $vehicleService): void
{
try {
// Procesar la detección usando el servicio
$vehicleService->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
]);
}
}

View File

@ -179,4 +179,192 @@ private function parseRoboResponse(string $soapResponse, string $valor)
'error' => 'Formato de respuesta no reconocido' '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 = <<<XML
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:wsdl="http://consultaRpv.org/wsdl">
<soapenv:Header/>
<soapenv:Body>
<wsdl:doConsRPV>
<arg0>{$this->username}</arg0>
<arg1>{$this->password}</arg1>
<arg2>{$arg2}</arg2>
</wsdl:doConsRPV>
</soapenv:Body>
</soapenv:Envelope>
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 <return>
preg_match('/<return>(.*?)<\/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' => []
];
}
} }

View File

@ -11,20 +11,17 @@
class VehicleService class VehicleService
{ {
public function __construct( public function __construct(
private ConsultaRepuveConstancia $consultaRepuveCons private ConsultaRepuveConstancia $consultaRepuveCons,
private ReporteRoboService $reporteRoboService // Agregar esta dependencia
) {} ) {}
/** /**
* Procesar detección de vehículo por fast_id * 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}"; $key = "vehiculo:robado:{$fastId}";
// Buscar arco por IP
$arco = Arco::buscarPorIp($arcoIp);
$arcoId = $arco?->id;
// Verificar si está en Redis // Verificar si está en Redis
$enRedis = Redis::get($key); $enRedis = Redis::get($key);
@ -47,22 +44,49 @@ public function procesarDeteccion(string $fastId, string $arcoIp, ?string $anten
public function consultarVehiculoPorTag(string $fastId) public function consultarVehiculoPorTag(string $fastId)
{ {
try { try {
//Intentar con REPUVE Constancia primero
$vehiculoExterno = $this->consultaRepuveCons->consultarVehiculoPorTag($fastId); $vehiculoExterno = $this->consultaRepuveCons->consultarVehiculoPorTag($fastId);
if ($vehiculoExterno) { if ($vehiculoExterno) {
Log::info('VehicleService: Vehículo encontrado', [ Log::info('VehicleService: Vehículo encontrado en REPUVE Constancia', [
'fast_id' => $fastId, 'fast_id' => $fastId,
'tag_number' => $vehiculoExterno['tag_number'] ?? null, 'tag_number' => $vehiculoExterno['tag_number'] ?? null,
'placa' => $vehiculoExterno['placa'] ?? null, 'placa' => $vehiculoExterno['placa'] ?? null,
'vin' => $vehiculoExterno['vin'] ?? null 'vin' => $vehiculoExterno['vin'] ?? null
]); ]);
} else { return $vehiculoExterno;
Log::info('VehicleService: Vehículo no encontrado', [
'fast_id' => $fastId
]);
} }
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) { } catch (\Exception $e) {
Log::error('VehicleService: Error en consulta', [ Log::error('VehicleService: Error en consulta', [
@ -162,31 +186,61 @@ public function listarDeteccionesDelDiaPorArco(int $arcoId, ?string $fecha = nul
*/ */
private function consultarNuevoVehiculo(string $fastId): array private function consultarNuevoVehiculo(string $fastId): array
{ {
// Consultar con FastID (tag_number)
$datosVehiculo = $this->consultaRepuveCons->consultarVehiculoPorTag($fastId); $datosVehiculo = $this->consultaRepuveCons->consultarVehiculoPorTag($fastId);
if (!$datosVehiculo || !$datosVehiculo['vin']) { if ($datosVehiculo && isset($datosVehiculo['vin'])) {
Log::warning('Vehículo NO encontrado.', [ Log::info('Vehículo encontrado en RepuveConstancia - LIBRE', [
'fast_id' => $fastId 'fast_id' => $fastId,
'vin' => $datosVehiculo['vin'],
'placa' => $datosVehiculo['placa'] ?? null,
'fuente' => 'RepuveConstancia'
]); ]);
return [ return [
'success' => false, 'success' => true,
'message' => 'No se encontró información del vehículo.' '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 [ return [
'success' => true, 'success' => false,
'tiene_reporte_robo' => false, 'message' => 'No se encontró información del vehículo en ningún servicio',
'estado' => 'LIBRE', 'fuente' => 'ninguno'
'vehiculo' => $datosVehiculo
]; ];
} }

View File

@ -45,7 +45,7 @@ services:
MYSQL_PASSWORD: ${DB_PASSWORD} MYSQL_PASSWORD: ${DB_PASSWORD}
MYSQL_USER: ${DB_USERNAME} MYSQL_USER: ${DB_USERNAME}
ports: ports:
- ${DB_PORT}:${DB_PORT} - ${DB_PORT}:3306
volumes: volumes:
- mysql_data:/var/lib/mysql - mysql_data:/var/lib/mysql
networks: networks: