ADD: Implementa servicios para consulta y registro de vehículos robados
This commit is contained in:
parent
80bd249176
commit
f648ea39eb
308
app/Http/Controllers/Api/VehicleController.php
Normal file
308
app/Http/Controllers/Api/VehicleController.php
Normal file
@ -0,0 +1,308 @@
|
|||||||
|
<?php namespace App\Http\Controllers\Api;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Models\Vehicle;
|
||||||
|
use App\Services\VehicleService;
|
||||||
|
use App\Services\ConsultaEstatalService;
|
||||||
|
use App\Services\ReporteRoboService;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Redis;
|
||||||
|
use Illuminate\Support\Facades\Validator;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
|
||||||
|
class VehicleController extends Controller
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private VehicleService $vehicleService,
|
||||||
|
private ConsultaEstatalService $consultaEstatal,
|
||||||
|
private ReporteRoboService $reporteRobo
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Consultar y registrar vehículo por PLACA o VIN
|
||||||
|
*/
|
||||||
|
public function consultarVehiculo(Request $request): JsonResponse
|
||||||
|
{
|
||||||
|
$validator = Validator::make($request->all(), [
|
||||||
|
'placa' => 'required_without:vin|string|nullable',
|
||||||
|
'vin' => 'required_without:placa|string|nullable',
|
||||||
|
], [
|
||||||
|
'placa.required_without' => 'Debe proporcionar al menos placa o VIN',
|
||||||
|
'vin.required_without' => 'Debe proporcionar al menos placa o VIN',
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($validator->fails()) {
|
||||||
|
return response()->json([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Datos inválidos',
|
||||||
|
'errors' => $validator->errors()
|
||||||
|
], 422);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$placa = $request->placa;
|
||||||
|
$vin = $request->vin;
|
||||||
|
|
||||||
|
// Consultar padrón estatal (obtener datos completos del vehículo)
|
||||||
|
Log::info('Consultando padrón estatal', ['placa' => $placa, 'vin' => $vin]);
|
||||||
|
|
||||||
|
// Si viene VIN, usar VIN, si no usar placa
|
||||||
|
$criterio = $vin ?: $placa;
|
||||||
|
$datosEstatal = $this->consultaEstatal->consultarPorEpc($criterio);
|
||||||
|
|
||||||
|
if (!$datosEstatal || (!$datosEstatal['vin'] && !$datosEstatal['placa'])) {
|
||||||
|
return response()->json([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'No se encontró información del vehículo en el padrón estatal'
|
||||||
|
], 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Obtener VIN y Placa completos
|
||||||
|
$vinCompleto = $datosEstatal['vin'] ?: $vin;
|
||||||
|
$placaCompleta = $datosEstatal['placa'] ?: $placa;
|
||||||
|
|
||||||
|
Log::info('Datos del padrón estatal obtenidos', [
|
||||||
|
'vin' => $vinCompleto,
|
||||||
|
'placa' => $placaCompleta
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Consultar REPUVE
|
||||||
|
$reporteRobo = $this->reporteRobo->consultarPorVin($vinCompleto, $placaCompleta);
|
||||||
|
|
||||||
|
$tieneReporte = $reporteRobo['tiene_reporte'] ?? false;
|
||||||
|
|
||||||
|
// Si tiene reporte de robo → Guardar en Redis
|
||||||
|
if ($tieneReporte) {
|
||||||
|
$epc = 'MANUAL_' . ($vinCompleto ?: $placaCompleta);
|
||||||
|
|
||||||
|
$this->guardarEnRedis($epc, $datosEstatal, $reporteRobo['datos']);
|
||||||
|
|
||||||
|
Log::warning('¡VEHÍCULO CON REPORTE DE ROBO REGISTRADO!', [
|
||||||
|
'vin' => $vinCompleto,
|
||||||
|
'placa' => $placaCompleta,
|
||||||
|
'autoridad' => $reporteRobo['datos']['autoridad'] ?? null
|
||||||
|
]);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'success' => true,
|
||||||
|
'tiene_reporte_robo' => true,
|
||||||
|
'message' => '¡ALERTA! El vehículo tiene reporte de robo',
|
||||||
|
'vehiculo' => [
|
||||||
|
'vin' => $vinCompleto,
|
||||||
|
'placa' => $placaCompleta,
|
||||||
|
],
|
||||||
|
'reporte_robo' => $reporteRobo['datos']
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// No tiene reporte de robo
|
||||||
|
Log::info('Vehículo consultado sin reporte de robo', [
|
||||||
|
'vin' => $vinCompleto,
|
||||||
|
'placa' => $placaCompleta
|
||||||
|
]);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'success' => true,
|
||||||
|
'tiene_reporte_robo' => false,
|
||||||
|
'message' => 'Vehículo sin reporte de robo',
|
||||||
|
'vehiculo' => [
|
||||||
|
'vin' => $vinCompleto,
|
||||||
|
'placa' => $placaCompleta,
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
Log::error('Error al consultar vehículo', [
|
||||||
|
'placa' => $request->placa,
|
||||||
|
'vin' => $request->vin,
|
||||||
|
'error' => $e->getMessage()
|
||||||
|
]);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Error al consultar vehículo',
|
||||||
|
'error' => $e->getMessage()
|
||||||
|
], 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Guardar vehículo robado en Redis
|
||||||
|
*/
|
||||||
|
private function guardarEnRedis(string $epc, array $datosEstatal, array $datosRobo): void
|
||||||
|
{
|
||||||
|
$key = "vehiculo:robado:{$epc}";
|
||||||
|
|
||||||
|
$datos = [
|
||||||
|
'epc' => $epc,
|
||||||
|
'vin' => $datosEstatal['vin'],
|
||||||
|
'placa' => $datosEstatal['placa'],
|
||||||
|
'fecha_robo' => $datosRobo['fecha_robo'] ?? null,
|
||||||
|
'autoridad' => $datosRobo['autoridad'] ?? null,
|
||||||
|
'acta' => $datosRobo['acta'] ?? null,
|
||||||
|
'denunciante' => $datosRobo['denunciante'] ?? null,
|
||||||
|
'fecha_acta' => $datosRobo['fecha_acta'] ?? null,
|
||||||
|
'primera_deteccion' => now()->toIso8601String(),
|
||||||
|
'ultima_deteccion' => now()->toIso8601String(),
|
||||||
|
'detecciones' => 0,
|
||||||
|
'origen' => 'CONSULTA_MANUAL'
|
||||||
|
];
|
||||||
|
|
||||||
|
Redis::set($key, json_encode($datos));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dar de baja (registrar como recuperado) un vehículo por PLACA o VIN
|
||||||
|
*/
|
||||||
|
public function recuperarVehiculo(Request $request): JsonResponse
|
||||||
|
{
|
||||||
|
$validator = Validator::make($request->all(), [
|
||||||
|
'placa' => 'required_without:vin|string|nullable',
|
||||||
|
'vin' => 'required_without:placa|string|nullable',
|
||||||
|
], [
|
||||||
|
'placa.required_without' => 'Debe proporcionar al menos placa o VIN',
|
||||||
|
'vin.required_without' => 'Debe proporcionar al menos placa o VIN',
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($validator->fails()) {
|
||||||
|
return response()->json([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Datos inválidos',
|
||||||
|
'errors' => $validator->errors()
|
||||||
|
], 422);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$placa = $request->placa;
|
||||||
|
$vin = $request->vin;
|
||||||
|
|
||||||
|
Log::info('Buscando vehículo robado para recuperar', ['placa' => $placa, 'vin' => $vin]);
|
||||||
|
|
||||||
|
// 1. Buscar en Redis (vehículos robados activos)
|
||||||
|
$vehiculoEncontrado = $this->buscarEnRedis($vin, $placa);
|
||||||
|
|
||||||
|
if (!$vehiculoEncontrado) {
|
||||||
|
return response()->json([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'El vehículo no se encuentra en la lista de robados activos'
|
||||||
|
], 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
$datosRedis = $vehiculoEncontrado['datos'];
|
||||||
|
$epc = $vehiculoEncontrado['epc'];
|
||||||
|
|
||||||
|
Log::info('Vehículo encontrado en Redis', [
|
||||||
|
'epc' => $epc,
|
||||||
|
'vin' => $datosRedis['vin'],
|
||||||
|
'placa' => $datosRedis['placa']
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 2. Guardar en MySQL como recuperado
|
||||||
|
$vehiculoRecuperado = Vehicle::create([
|
||||||
|
'epc' => $epc,
|
||||||
|
'vin' => $datosRedis['vin'],
|
||||||
|
'placa' => $datosRedis['placa'],
|
||||||
|
'fecha_robo' => $datosRedis['fecha_robo'],
|
||||||
|
'autoridad_robo' => $datosRedis['autoridad'],
|
||||||
|
'acta_robo' => $datosRedis['acta'],
|
||||||
|
'denunciante' => $datosRedis['denunciante'],
|
||||||
|
'fecha_acta' => $datosRedis['fecha_acta'] ?? null,
|
||||||
|
'fecha_recuperacion' => now(),
|
||||||
|
'datos_robo_original' => $datosRedis
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 3. Eliminar de Redis
|
||||||
|
Redis::del("vehiculo:robado:{$epc}");
|
||||||
|
|
||||||
|
Log::info('Vehículo registrado como recuperado', [
|
||||||
|
'id' => $vehiculoRecuperado->id,
|
||||||
|
'vin' => $vehiculoRecuperado->vin,
|
||||||
|
'placa' => $vehiculoRecuperado->placa
|
||||||
|
]);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'success' => true,
|
||||||
|
'message' => 'Vehículo registrado como recuperado exitosamente',
|
||||||
|
'vehiculo' => [
|
||||||
|
'id' => $vehiculoRecuperado->id,
|
||||||
|
'epc' => $vehiculoRecuperado->epc,
|
||||||
|
'vin' => $vehiculoRecuperado->vin,
|
||||||
|
'placa' => $vehiculoRecuperado->placa,
|
||||||
|
'fecha_robo' => $vehiculoRecuperado->fecha_robo,
|
||||||
|
'fecha_recuperacion' => $vehiculoRecuperado->fecha_recuperacion,
|
||||||
|
'autoridad_robo' => $vehiculoRecuperado->autoridad_robo,
|
||||||
|
'acta_robo' => $vehiculoRecuperado->acta_robo,
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
Log::error('Error al recuperar vehículo', [
|
||||||
|
'placa' => $request->placa,
|
||||||
|
'vin' => $request->vin,
|
||||||
|
'error' => $e->getMessage()
|
||||||
|
]);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Error al registrar vehículo como recuperado',
|
||||||
|
'error' => $e->getMessage()
|
||||||
|
], 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Buscar vehículo en Redis por VIN o Placa
|
||||||
|
*/
|
||||||
|
private function buscarEnRedis(?string $vin, ?string $placa): ?array
|
||||||
|
{
|
||||||
|
$keys = Redis::keys('vehiculo:robado:*');
|
||||||
|
|
||||||
|
foreach ($keys as $key) {
|
||||||
|
$datos = Redis::get($key);
|
||||||
|
if (!$datos) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$vehiculo = json_decode($datos, true);
|
||||||
|
|
||||||
|
// Buscar por VIN o Placa
|
||||||
|
if (($vin && $vehiculo['vin'] === $vin) || ($placa && $vehiculo['placa'] === $placa)) {
|
||||||
|
// Extraer el EPC del key
|
||||||
|
$epc = str_replace('vehiculo:robado:', '', $key);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'epc' => $epc,
|
||||||
|
'datos' => $vehiculo
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listar todos los vehículos robados activos
|
||||||
|
* GET /api/vehicles/robados
|
||||||
|
*/
|
||||||
|
public function listarRobados(): JsonResponse
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$vehiculos = $this->vehicleService->listarVehiculosRobados();
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'success' => true,
|
||||||
|
'total' => count($vehiculos),
|
||||||
|
'vehiculos' => $vehiculos
|
||||||
|
]);
|
||||||
|
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return response()->json([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Error al listar vehículos robados',
|
||||||
|
'error' => $e->getMessage()
|
||||||
|
], 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
31
app/Models/Vehicle.php
Normal file
31
app/Models/Vehicle.php
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
<?php namespace App\Models;
|
||||||
|
/**
|
||||||
|
* @copyright (c) 2025 Notsoweb Software (https://notsoweb.com) - All Rights Reserved
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
|
||||||
|
class Vehicle extends Model
|
||||||
|
{
|
||||||
|
protected $fillable = [
|
||||||
|
'epc',
|
||||||
|
'vin',
|
||||||
|
'placa',
|
||||||
|
'fecha_robo',
|
||||||
|
'autoridad_robo',
|
||||||
|
'acta_robo',
|
||||||
|
'denunciante',
|
||||||
|
'fecha_acta',
|
||||||
|
'fecha_recuperacion',
|
||||||
|
'datos_robo_original'
|
||||||
|
];
|
||||||
|
|
||||||
|
protected $casts = [
|
||||||
|
'fecha_robo' => 'date',
|
||||||
|
'fecha_acta' => 'date',
|
||||||
|
'fecha_recuperacion' => 'datetime',
|
||||||
|
'datos_robo_original' => 'array',
|
||||||
|
];
|
||||||
|
}
|
||||||
133
app/Services/ConsultaEstatalService.php
Normal file
133
app/Services/ConsultaEstatalService.php
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Services;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
|
||||||
|
class ConsultaEstatalService
|
||||||
|
{
|
||||||
|
private string $soapUrl;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->soapUrl = config('services.padron_estatal.url');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Consulta vehículo
|
||||||
|
*/
|
||||||
|
public function consultarPorEpc(string $epc): ?array
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$datos = $this->consultarPadron('niv', $epc);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'placa' => $datos['placa'] ?? null,
|
||||||
|
'vin' => $datos['niv'] ?? null,
|
||||||
|
];
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
Log::error("Error consultando padrón por EPC: {$epc} - " . $e->getMessage());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Consulta el padrón vehicular estatal vía SOAP
|
||||||
|
*/
|
||||||
|
private function consultarPadron(string $tipo, string $valor): array
|
||||||
|
{
|
||||||
|
// Construir el Data en formato JSON
|
||||||
|
$data = json_encode([
|
||||||
|
'tipo' => $tipo,
|
||||||
|
'valor' => $valor
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Construir el cuerpo SOAP
|
||||||
|
$soapBody = <<<XML
|
||||||
|
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:wsdl="http://mx/tgc/ConsultaPadronVehicular.wsdl">
|
||||||
|
<soapenv:Header/>
|
||||||
|
<soapenv:Body>
|
||||||
|
<wsdl:getVehiculosRepuve>
|
||||||
|
<Data>{$data}</Data>
|
||||||
|
</wsdl:getVehiculosRepuve>
|
||||||
|
</soapenv:Body>
|
||||||
|
</soapenv:Envelope>
|
||||||
|
XML;
|
||||||
|
|
||||||
|
// Configurar cURL
|
||||||
|
$ch = curl_init($this->soapUrl);
|
||||||
|
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: ""',
|
||||||
|
'Content-Length: ' . strlen($soapBody)
|
||||||
|
]);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Ejecutar la petición
|
||||||
|
$response = curl_exec($ch);
|
||||||
|
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||||
|
$error = curl_error($ch);
|
||||||
|
|
||||||
|
if ($error) {
|
||||||
|
throw new Exception("Error en la petición: {$error}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($httpCode !== 200) {
|
||||||
|
throw new Exception("Error HTTP {$httpCode}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parsear la respuesta
|
||||||
|
return $this->parsearRespuesta($response);
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
unset($ch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parsea la respuesta y extrae los datos del vehículo
|
||||||
|
*/
|
||||||
|
private function parsearRespuesta(string $soapResponse): array
|
||||||
|
{
|
||||||
|
// Extraer el contenido del tag <result>
|
||||||
|
preg_match('/<result>(.*?)<\/result>/s', $soapResponse, $matches);
|
||||||
|
|
||||||
|
if (!isset($matches[1])) {
|
||||||
|
throw new Exception("No se pudo extraer el resultado del padrón estatal");
|
||||||
|
}
|
||||||
|
|
||||||
|
$jsonContent = trim($matches[1]);
|
||||||
|
|
||||||
|
// Decodificar el JSON
|
||||||
|
$result = json_decode($jsonContent, true);
|
||||||
|
|
||||||
|
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||||
|
throw new Exception("Error al decodificar JSON: " . json_last_error_msg());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($result[0])) {
|
||||||
|
throw new Exception("Formato de respuesta inesperado");
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = $result[0];
|
||||||
|
|
||||||
|
// Verificar si hay error
|
||||||
|
if ($data['error'] !== 0) {
|
||||||
|
throw new Exception("Error en consulta: código {$data['error']}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verificar si hay datos
|
||||||
|
if (!isset($data['datos'][0])) {
|
||||||
|
throw new Exception("No se encontraron datos del vehículo");
|
||||||
|
}
|
||||||
|
|
||||||
|
return $data['datos'][0];
|
||||||
|
}
|
||||||
|
}
|
||||||
187
app/Services/ReporteRoboService.php
Normal file
187
app/Services/ReporteRoboService.php
Normal file
@ -0,0 +1,187 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Services;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
|
||||||
|
class ReporteRoboService
|
||||||
|
{
|
||||||
|
private string $baseUrl;
|
||||||
|
private string $roboEndpoint;
|
||||||
|
private string $username;
|
||||||
|
private string $password;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->baseUrl = config('services.repuve_federal.base_url');
|
||||||
|
$this->roboEndpoint = config('services.repuve_federal.robo_endpoint');
|
||||||
|
$this->username = config('services.repuve_federal.username');
|
||||||
|
$this->password = config('services.repuve_federal.password');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function consultarPorVin(?string $vin = null, ?string $placa = null): array
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
if (empty($vin) && empty($placa)) {
|
||||||
|
Log::warning('ReporteRoboService: No se proporcionó VIN ni PLACA');
|
||||||
|
return [
|
||||||
|
'tiene_reporte' => false,
|
||||||
|
'datos' => [],
|
||||||
|
'error' => 'Debe proporcionar al menos VIN o PLACA'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$url = $this->baseUrl . $this->roboEndpoint;
|
||||||
|
|
||||||
|
// Construir arg2 según los parámetros
|
||||||
|
if (!empty($vin) && !empty($placa)) {
|
||||||
|
$arg2 = $vin . '|' . $placa . str_repeat('|', 5);
|
||||||
|
} elseif (!empty($vin)) {
|
||||||
|
$arg2 = $vin . str_repeat('|', 7);
|
||||||
|
} else {
|
||||||
|
$arg2 = '||' . $placa . str_repeat('|', 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
Log::info('ReporteRoboService: Consultando REPUVE', [
|
||||||
|
'vin' => $vin,
|
||||||
|
'placa' => $placa
|
||||||
|
]);
|
||||||
|
|
||||||
|
$soapBody = <<<XML
|
||||||
|
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:wsdl="http://consultaRpv.org/wsdl">
|
||||||
|
<soapenv:Header/>
|
||||||
|
<soapenv:Body>
|
||||||
|
<wsdl:doConsRepRobo>
|
||||||
|
<arg0>{$this->username}</arg0>
|
||||||
|
<arg1>{$this->password}</arg1>
|
||||||
|
<arg2>{$arg2}</arg2>
|
||||||
|
</wsdl:doConsRepRobo>
|
||||||
|
</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: "doConsRepRobo"',
|
||||||
|
'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: Error de conexión', ['error' => $error]);
|
||||||
|
return [
|
||||||
|
'tiene_reporte' => false,
|
||||||
|
'datos' => [],
|
||||||
|
'error' => 'Error de conexión con REPUVE'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($httpCode !== 200) {
|
||||||
|
Log::error('ReporteRoboService: HTTP error', ['http_code' => $httpCode]);
|
||||||
|
return [
|
||||||
|
'tiene_reporte' => false,
|
||||||
|
'datos' => [],
|
||||||
|
'error' => "Error HTTP {$httpCode}"
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->parseRoboResponse($response, $vin ?? $placa);
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
unset($ch);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
Log::error('ReporteRoboService: Excepción', ['message' => $e->getMessage()]);
|
||||||
|
return [
|
||||||
|
'tiene_reporte' => false,
|
||||||
|
'datos' => [],
|
||||||
|
'error' => $e->getMessage()
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parsea la respuesta del servicio de robo
|
||||||
|
*/
|
||||||
|
private function parseRoboResponse(string $soapResponse, string $valor): array
|
||||||
|
{
|
||||||
|
// Extraer contenido del tag <return>
|
||||||
|
preg_match('/<return>(.*?)<\/return>/s', $soapResponse, $matches);
|
||||||
|
|
||||||
|
if (!isset($matches[1])) {
|
||||||
|
Log::error('ReporteRoboService: No se encontró tag <return>');
|
||||||
|
return [
|
||||||
|
'tiene_reporte' => false,
|
||||||
|
'datos' => [],
|
||||||
|
'error' => 'Respuesta SOAP inválida'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$contenido = trim($matches[1]);
|
||||||
|
|
||||||
|
// Verificar si hay error (ERR: o ERROR:)
|
||||||
|
if (preg_match('/(ERR|ERROR|err|error):(-?\d+)/i', $contenido, $errorMatch)) {
|
||||||
|
$errorCode = $errorMatch[2];
|
||||||
|
Log::warning('ReporteRoboService: Error del servicio', ['error_code' => $errorCode]);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'tiene_reporte' => false,
|
||||||
|
'datos' => ['error_code' => $errorCode],
|
||||||
|
'error' => "Error REPUVE código {$errorCode}"
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Si empieza con OK:, parsear los datos
|
||||||
|
if (str_starts_with($contenido, 'OK:')) {
|
||||||
|
$datos = str_replace('OK:', '', $contenido);
|
||||||
|
$valores = explode('|', $datos);
|
||||||
|
|
||||||
|
// 1 = robado, 0 = no robado
|
||||||
|
$indicador = $valores[0] ?? '0';
|
||||||
|
$tieneReporte = ($indicador === '1');
|
||||||
|
|
||||||
|
$datosRobo = [
|
||||||
|
'indicador' => $indicador,
|
||||||
|
'fecha_robo' => $valores[1] ?? null,
|
||||||
|
'placa' => $valores[2] ?? null,
|
||||||
|
'vin' => $valores[3] ?? null,
|
||||||
|
'autoridad' => $valores[4] ?? null,
|
||||||
|
'acta' => $valores[5] ?? null,
|
||||||
|
'denunciante' => $valores[6] ?? null,
|
||||||
|
'fecha_acta' => $valores[7] ?? null,
|
||||||
|
];
|
||||||
|
|
||||||
|
if ($tieneReporte) {
|
||||||
|
Log::warning('ReporteRoboService: ¡VEHÍCULO ROBADO DETECTADO!', [
|
||||||
|
'vin' => $datosRobo['vin'],
|
||||||
|
'placa' => $datosRobo['placa'],
|
||||||
|
'autoridad' => $datosRobo['autoridad']
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'tiene_reporte' => $tieneReporte,
|
||||||
|
'datos' => $datosRobo
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
Log::error('ReporteRoboService: Formato no reconocido', ['contenido' => $contenido]);
|
||||||
|
return [
|
||||||
|
'tiene_reporte' => false,
|
||||||
|
'datos' => [],
|
||||||
|
'error' => 'Formato de respuesta no reconocido'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
187
app/Services/VehicleService.php
Normal file
187
app/Services/VehicleService.php
Normal file
@ -0,0 +1,187 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Services;
|
||||||
|
|
||||||
|
use App\Models\Vehicle;
|
||||||
|
use Illuminate\Support\Facades\Redis;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
|
||||||
|
class VehicleService
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private ConsultaEstatalService $consultaEstatal,
|
||||||
|
private ReporteRoboService $reporteRobo
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public function procesarDeteccion(string $epc, ?int $arcoId = null): array
|
||||||
|
{
|
||||||
|
$key = "vehiculo:robado:{$epc}";
|
||||||
|
|
||||||
|
// Verificar si está en Redis
|
||||||
|
$enRedis = Redis::get($key);
|
||||||
|
|
||||||
|
if ($enRedis) {
|
||||||
|
// Ya está marcado como robado, verificar si sigue así
|
||||||
|
return $this->verificarVehiculoRobado($epc, $arcoId, json_decode($enRedis, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
// No está en Redis, consultar servicios
|
||||||
|
return $this->consultarNuevoVehiculo($epc, $arcoId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function consultarNuevoVehiculo(string $epc, ?int $arcoId): array
|
||||||
|
{
|
||||||
|
// Consultar padrón estatal
|
||||||
|
$datosEstatal = $this->consultaEstatal->consultarPorEpc($epc);
|
||||||
|
|
||||||
|
if (!$datosEstatal || !$datosEstatal['vin']) {
|
||||||
|
return [
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'No se encontró información del vehículo'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Consultar REPUVE
|
||||||
|
$reporteRobo = $this->reporteRobo->consultarPorVin(
|
||||||
|
$datosEstatal['vin'],
|
||||||
|
$datosEstatal['placa']
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($reporteRobo['tiene_reporte']) {
|
||||||
|
// Está robado → Guardar en Redis
|
||||||
|
$this->guardarEnRedis($epc, $datosEstatal, $reporteRobo['datos']);
|
||||||
|
|
||||||
|
Log::warning('¡VEHÍCULO ROBADO DETECTADO!', [
|
||||||
|
'epc' => $epc,
|
||||||
|
'vin' => $datosEstatal['vin'],
|
||||||
|
'placa' => $datosEstatal['placa']
|
||||||
|
]);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'success' => true,
|
||||||
|
'tiene_reporte_robo' => true,
|
||||||
|
'estado' => 'ROBADO',
|
||||||
|
'accion' => 'GUARDADO_EN_REDIS',
|
||||||
|
'vehiculo' => array_merge($datosEstatal, $reporteRobo['datos'])
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// No está robado, no hacer nada
|
||||||
|
return [
|
||||||
|
'success' => true,
|
||||||
|
'tiene_reporte_robo' => false,
|
||||||
|
'estado' => 'LIBRE',
|
||||||
|
'vehiculo' => $datosEstatal
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function verificarVehiculoRobado(string $epc, ?int $arcoId, array $datosRedis): array
|
||||||
|
{
|
||||||
|
// Consultar REPUVE para verificar estado actual
|
||||||
|
$reporteRobo = $this->reporteRobo->consultarPorVin(
|
||||||
|
$datosRedis['vin'],
|
||||||
|
$datosRedis['placa']
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!$reporteRobo['tiene_reporte']) {
|
||||||
|
// No tiene reporte robo
|
||||||
|
$this->registrarRecuperacion($epc, $arcoId, $datosRedis);
|
||||||
|
|
||||||
|
Log::info('¡VEHÍCULO RECUPERADO!', [
|
||||||
|
'epc' => $epc,
|
||||||
|
'vin' => $datosRedis['vin'],
|
||||||
|
'placa' => $datosRedis['placa']
|
||||||
|
]);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'success' => true,
|
||||||
|
'tiene_reporte_robo' => false,
|
||||||
|
'estado' => 'RECUPERADO',
|
||||||
|
'accion' => 'GUARDADO_EN_MYSQL_Y_ELIMINADO_DE_REDIS',
|
||||||
|
'vehiculo' => $datosRedis
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sigue robado → Actualizar contador en Redis
|
||||||
|
$this->actualizarDeteccionRedis($epc, $datosRedis);
|
||||||
|
|
||||||
|
Log::warning('Vehículo robado detectado nuevamente', [
|
||||||
|
'epc' => $epc,
|
||||||
|
'detecciones' => $datosRedis['detecciones'] + 1
|
||||||
|
]);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'success' => true,
|
||||||
|
'tiene_reporte_robo' => true,
|
||||||
|
'estado' => 'ROBADO',
|
||||||
|
'accion' => 'ACTUALIZADO_EN_REDIS',
|
||||||
|
'vehiculo' => $datosRedis
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function guardarEnRedis(string $epc, array $datosEstatal, array $datosRobo): void
|
||||||
|
{
|
||||||
|
$key = "vehiculo:robado:{$epc}";
|
||||||
|
|
||||||
|
$datos = [
|
||||||
|
'epc' => $epc,
|
||||||
|
'vin' => $datosEstatal['vin'],
|
||||||
|
'placa' => $datosEstatal['placa'],
|
||||||
|
'fecha_robo' => $datosRobo['fecha_robo'] ?? null,
|
||||||
|
'autoridad' => $datosRobo['autoridad'] ?? null,
|
||||||
|
'acta' => $datosRobo['acta'] ?? null,
|
||||||
|
'denunciante' => $datosRobo['denunciante'] ?? null,
|
||||||
|
'fecha_acta' => $datosRobo['fecha_acta'] ?? null,
|
||||||
|
'primera_deteccion' => now()->toIso8601String(),
|
||||||
|
'ultima_deteccion' => now()->toIso8601String(),
|
||||||
|
'detecciones' => 1
|
||||||
|
];
|
||||||
|
|
||||||
|
Redis::set($key, json_encode($datos));
|
||||||
|
}
|
||||||
|
|
||||||
|
private function actualizarDeteccionRedis(string $epc, array $datosActuales): void
|
||||||
|
{
|
||||||
|
$key = "vehiculo:robado:{$epc}";
|
||||||
|
|
||||||
|
$datosActuales['ultima_deteccion'] = now()->toIso8601String();
|
||||||
|
$datosActuales['detecciones'] = ($datosActuales['detecciones'] ?? 0) + 1;
|
||||||
|
|
||||||
|
Redis::set($key, json_encode($datosActuales));
|
||||||
|
}
|
||||||
|
|
||||||
|
private function registrarRecuperacion(string $epc, ?int $arcoId, array $datosRedis): void
|
||||||
|
{
|
||||||
|
// Guardar en MySQL
|
||||||
|
Vehicle::create([
|
||||||
|
'epc' => $epc,
|
||||||
|
'vin' => $datosRedis['vin'],
|
||||||
|
'placa' => $datosRedis['placa'],
|
||||||
|
'fecha_robo' => $datosRedis['fecha_robo'],
|
||||||
|
'autoridad_robo' => $datosRedis['autoridad'],
|
||||||
|
'acta_robo' => $datosRedis['acta'],
|
||||||
|
'denunciante' => $datosRedis['denunciante'],
|
||||||
|
'fecha_recuperacion' => now(),
|
||||||
|
'fecha_acta' => $datosRedis['fecha_acta'] ?? null,
|
||||||
|
'datos_robo_original' => $datosRedis
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Eliminar de Redis
|
||||||
|
Redis::del("vehiculo:robado:{$epc}");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function listarVehiculosRobados(): array
|
||||||
|
{
|
||||||
|
$keys = Redis::keys('vehiculo:robado:*');
|
||||||
|
$vehiculos = [];
|
||||||
|
|
||||||
|
foreach ($keys as $key) {
|
||||||
|
$datos = Redis::get($key);
|
||||||
|
if ($datos) {
|
||||||
|
$vehiculos[] = json_decode($datos, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $vehiculos;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -35,4 +35,16 @@
|
|||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|
||||||
|
'repuve_federal' => [
|
||||||
|
'base_url' => env('REPUVE_FED_BASE_URL'),
|
||||||
|
'robo_endpoint' => '/jaxws-consultarpv/ConsultaRpv',
|
||||||
|
'inscripcion_endpoint' => '/jaxrpc-inscripcion/Inscripcion?WSDLs=',
|
||||||
|
'username' => env('REPUVE_FED_USERNAME'),
|
||||||
|
'password' => env('REPUVE_FED_PASSWORD'),
|
||||||
|
],
|
||||||
|
|
||||||
|
'padron_estatal' => [
|
||||||
|
'url' => env('REPUVE_EST_URL'),
|
||||||
|
],
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|||||||
@ -0,0 +1,46 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('vehicles', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
|
||||||
|
// Identificadores del vehículo
|
||||||
|
$table->string('epc')->unique();
|
||||||
|
$table->string('vin')->nullable();
|
||||||
|
$table->string('placa')->nullable();
|
||||||
|
|
||||||
|
// Datos del reporte de robo
|
||||||
|
$table->date('fecha_robo')->nullable();
|
||||||
|
$table->string('autoridad_robo')->nullable();
|
||||||
|
$table->string('acta_robo')->nullable();
|
||||||
|
$table->string('denunciante')->nullable();
|
||||||
|
$table->date('fecha_acta')->nullable();
|
||||||
|
|
||||||
|
// Datos de recuperación
|
||||||
|
$table->timestamp('fecha_recuperacion')->nullable();
|
||||||
|
|
||||||
|
// Datos completos del robo original (JSON) - Para auditoría
|
||||||
|
$table->json('datos_robo_original')->nullable();
|
||||||
|
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('vehicles');
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -66,11 +66,39 @@ services:
|
|||||||
- arcos-network
|
- arcos-network
|
||||||
mem_limit: 400m
|
mem_limit: 400m
|
||||||
|
|
||||||
|
redis:
|
||||||
|
image: redis:alpine
|
||||||
|
ports:
|
||||||
|
- "${REDIS_PORT}:6379"
|
||||||
|
volumes:
|
||||||
|
- redis_data:/data
|
||||||
|
networks:
|
||||||
|
- arcos-network
|
||||||
|
mem_limit: 256m
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "redis-cli", "ping"]
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
|
||||||
|
redis-commander:
|
||||||
|
image: rediscommander/redis-commander:latest
|
||||||
|
environment:
|
||||||
|
- ${REDIS_HOST}:redis:6379
|
||||||
|
ports:
|
||||||
|
- "${REDIS_COMMANDER_PORT}:8081"
|
||||||
|
networks:
|
||||||
|
- arcos-network
|
||||||
|
mem_limit: 256m
|
||||||
|
depends_on:
|
||||||
|
- redis
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
nginx_data:
|
nginx_data:
|
||||||
driver: local
|
driver: local
|
||||||
mysql_data:
|
mysql_data:
|
||||||
driver: local
|
driver: local
|
||||||
|
redis_data:
|
||||||
|
driver: local
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
arcos-network:
|
arcos-network:
|
||||||
|
|||||||
@ -1,23 +1,28 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
use App\Http\Controllers\Api\VehicleController;
|
||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Rutas del núcleo de la aplicación.
|
* Rutas del núcleo de la aplicación.
|
||||||
*
|
*
|
||||||
* Se recomienda que no se modifiquen estas rutas a menos que sepa lo que está haciendo.
|
* Se recomienda que no se modifiquen estas rutas a menos que sepa lo que está haciendo.
|
||||||
*/
|
*/
|
||||||
require('core.php');
|
require('core.php');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Rutas de tu aplicación.
|
* Rutas de tu aplicación.
|
||||||
*
|
*
|
||||||
* Estas rutas son de la aplicación AP I que desarrollarás. Siéntete libre de agregar lo que consideres necesario.
|
* Estas rutas son de la aplicación AP I que desarrollarás. Siéntete libre de agregar lo que consideres necesario.
|
||||||
* Procura revisar que no existan rutas que entren en conflicto con las rutas del núcleo.
|
* Procura revisar que no existan rutas que entren en conflicto con las rutas del núcleo.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/** Rutas protegidas (requieren autenticación) */
|
/** Rutas protegidas (requieren autenticación) */
|
||||||
Route::middleware('auth:api')->group(function() {
|
Route::middleware('auth:api')->group(function() {
|
||||||
// Tus rutas protegidas
|
|
||||||
|
Route::post('/vehicles/consultar', [VehicleController::class, 'consultarVehiculo']);
|
||||||
|
Route::post('/vehicles/recuperar', [VehicleController::class, 'recuperarVehiculo']);
|
||||||
|
Route::get('/vehicles/robados', [VehicleController::class, 'listarRobados']);
|
||||||
});
|
});
|
||||||
|
|
||||||
/** Rutas públicas */
|
/** Rutas públicas */
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user