# Documentación Técnica — Arcos Backend ## Índice 1. [Visión General](#visión-general) 2. [Flujo de Detección RFID](#flujo-de-detección-rfid) 3. [Gestión de Vehículos Robados](#gestión-de-vehículos-robados) 4. [Integraciones REPUVE](#integraciones-repuve) 5. [Gestión de Arcos (Lectores RFID)](#gestión-de-arcos-lectores-rfid) 6. [Sistema de Alertas](#sistema-de-alertas) 7. [Registro de Detecciones](#registro-de-detecciones) 8. [Transmisión en Tiempo Real](#transmisión-en-tiempo-real) 9. [Esquema de Base de Datos](#esquema-de-base-de-datos) 10. [Comandos y Tareas Programadas](#comandos-y-tareas-programadas) --- ## Visión General **Arcos** es una API REST construida en Laravel 12 para la detección de vehículos robados mediante lectores RFID ("arcos"). El sistema conecta lectores de hardware en campo con registros federales y estatales de robo vehicular (REPUVE), registra cada detección en base de datos y emite alertas en tiempo real vía WebSocket cuando un vehículo reportado como robado es detectado. ### Stack tecnológico relevante | Componente | Tecnología | |---|---| | Framework | Laravel 12 / PHP 8.3 | | Base de datos principal | MySQL 8.0 | | Almacenamiento en caliente | Redis | | Cola de trabajos | Laravel Queue (driver: database) | | WebSockets | Laravel Reverb | | Autenticación hardware | Token AES-256-CBC propio | | Autenticación usuarios | Laravel Passport (OAuth 2.0) | --- ## Flujo de Detección RFID Este es el flujo central del sistema. Ocurre cada vez que un lector RFID detecta una etiqueta vehicular. ``` Lector RFID (hardware) │ ▼ POST /api/vehicles/buscar Authorization: Bearer {arco_token} { fast_id, antena?, timestamp? } │ ▼ ArcoTokenMiddleware ├─ Desencripta token del encabezado y lo compara contra arcos.api_token ├─ Verifica que el arco esté activo (activo = true) └─ Inyecta el objeto Arco en $request->get('arco_autenticado') │ ▼ VehicleController::buscarPorTag() └─ Despacha ProcesarDeteccionVehiculo al queue │ (responde 200 OK inmediato al hardware) │ ▼ (proceso asíncrono) Job: ProcesarDeteccionVehiculo Reintentos: 3 | Backoff: 10 s | Timeout: 120 s │ ▼ VehicleService::procesarDeteccion($fastId, $arcoId, $antena) │ ├─ ¿fast_id existe en Redis como robado? │ │ │ ├─ SÍ ──► verificarVehiculoRobado() │ │ ├─ Incrementa contador en Redis │ │ ├─ Crea registro en alertas_robos │ │ ├─ Emite evento WebSocket VehiculoRobadoDetectado │ │ └─ Registra en detections + daily_detections (estado: ROBADO) │ │ │ └─ NO ──► consultarNuevoVehiculo() │ ├─ Consulta ConsultaRepuveConstancia (API REST) │ │ └─ Si responde → datos del vehículo │ ├─ Si no encontrado → Consulta ReporteRoboService (SOAP federal) │ └─ Registra en detections + daily_detections (estado: LIBRE / DESCONOCIDO) ``` ### Autenticación del hardware (ArcoTokenMiddleware) Cada lector RFID tiene un `api_token` único almacenado encriptado en la tabla `arcos`. El middleware: 1. Extrae el Bearer token del encabezado `Authorization` 2. Itera los arcos activos comparando el token desencriptado con `EncryptionHelper::encryptWithCustomKey()` 3. Rechaza con `401` si el token no corresponde a ningún arco 4. Rechaza con `403` si el arco está inactivo (`activo = false`) La clave de encriptación proviene de `ARCO_TOKEN_ENCRYPTION_KEY` en `.env`. --- ## Gestión de Vehículos Robados El registro activo de vehículos robados vive en **Redis**, no en MySQL. Esto permite búsquedas en tiempo O(1) durante cada detección. ### Registrar vehículo como robado **`POST /api/vehicles/consultar`** — requiere `auth:api` ```json { "placa": "ABC1234", "vin": "1G1FB1RX1234567890", "fecha_robo": "2025-02-01", "autoridad": "PROCURADURÍA", "acta": "ACT-2025-001", "denunciante": "Nombre Denunciante", "fecha_acta": "2025-02-01" } ``` El proceso interno: 1. Consulta el `fast_id` (tag RFID) del vehículo via `VehicleService::consultarVehiculoPorTag()` usando placa o VIN como llave de búsqueda en REPUVE Constancia 2. Verifica que no esté ya registrado en Redis: `Redis::exists("vehiculo:robado:{fastId}")` 3. Almacena la entrada en Redis con la clave `vehiculo:robado:{fastId}`: ```php [ 'fast_id' => string, 'vin' => string, 'placa' => string, 'marca' => string, 'modelo' => string, 'color' => string, 'fecha_robo' => string, 'autoridad' => string, 'acta' => string, 'denunciante' => string, 'fecha_acta' => string, 'primera_deteccion' => ISO8601, 'ultima_deteccion' => ISO8601, 'detecciones' => int // contador incremental ] ``` ### Recuperar vehículo **`POST /api/vehicles/recuperar`** — requiere `auth:api` 1. Busca el vehículo en Redis por `placa` o `vin` iterando claves `vehiculo:robado:*` 2. Crea un registro permanente en MySQL (`vehicles`) con los datos de robo y la fecha de recuperación 3. Elimina la clave de Redis: `Redis::del("vehiculo:robado:{fastId}")` ### Consultar estado de un vehículo **`GET /api/vehicles/detectar?placa=...`** o **`GET /api/vehicles/robado?vin=...`** Busca primero en Redis y luego consulta las APIs REPUVE según sea necesario. ### Listar vehículos robados activos **`GET /api/vehicles/robados?placa=...&vin=...`** Lee todas las claves `vehiculo:robado:*` de Redis. Soporta filtro por `placa` y `vin`. ### Listar vehículos recuperados **`GET /api/vehicles`** Lee la tabla `vehicles` en MySQL (archivo histórico de recuperaciones). --- ## Integraciones REPUVE El sistema consume tres APIs externas de registros vehiculares. Las credenciales se configuran en `.env` y se exponen en `config/services.php`. ### 1. REPUVE Constancia (`ConsultaRepuveConstancia`) API REST con autenticación JWT. Es la fuente principal de datos vehiculares. **Variables de entorno:** ``` REPUVE_CONSTANCIA_BASE_URL REPUVE_CONSTANCIA_LOGIN_ENDPOINT REPUVE_CONSTANCIA_CONSULTA_ENDPOINT REPUVE_CONSTANCIA_EMAIL REPUVE_CONSTANCIA_PASSWORD REPUVE_CONSTANCIA_TOKEN_TTL # TTL del token en caché (segundos) ``` **Flujo de autenticación:** El servicio obtiene un JWT via email/contraseña y lo almacena en caché por `TOKEN_TTL` segundos. Se renueva automáticamente al expirar. **Búsqueda de vehículo** — `consultarVehiculoPorTag()`: Intenta en orden: `tag_number` → `placa` → `vin` **Respuesta normalizada:** ```php [ 'tag_number' => string, // fast_id / folio RFID 'folio_tag' => string, 'vin' => string, 'placa' => string, 'marca' => string, 'modelo' => string, 'linea' => string, 'sublinea' => string, 'color' => string, 'numero_motor' => string, 'clase_veh' => string, 'tipo_servicio' => string, 'reporte_robo' => bool, // indicador de robo en REPUVE 'propietario' => [ 'id', 'nombre_completo', 'rfc', 'curp', 'telefono', 'direccion' ], 'tag_status' => string // ACTIVO / INACTIVO ] ``` --- ### 2. REPUVE Federal — Reporte de Robo (`ReporteRoboService`) Servicio SOAP sobre cURL. Consulta el padrón federal de vehículos robados. **Variables de entorno:** ``` REPUVE_FED_BASE_URL REPUVE_FED_USERNAME REPUVE_FED_PASSWORD ``` **Método `consultarRobado($vin, $placa)`** Protocolo SOAP; el cuerpo de la solicitud: ```xml {username} {password} {vin}|{placa}||||| ``` Respuesta parseada desde ``: `OK:indicador|fecha|placa|vin|autoridad|acta|denunciante|fecha_acta` ```php [ 'tiene_reporte' => bool, // true si indicador == '1' 'datos' => [ 'indicador' => '1', // 1 = robado, 0 = no robado 'fecha_robo' => 'YYYY-MM-DD', 'placa' => string, 'vin' => string, 'autoridad' => string, 'acta' => string, 'denunciante' => string, 'fecha_acta' => 'YYYY-MM-DD', ] ] ``` **Método `consultarVehiculo($vin, $placa)`** Consulta el padrón nacional para obtener datos del vehículo (sin estado de robo). --- ### 3. Padrón Estatal (`ConsultaEstatalService`) Servicio SOAP que responde en JSON. Consulta el registro estatal de vehículos. **Variable de entorno:** `REPUVE_EST_URL` --- ### Jerarquía de consulta durante una detección ``` fast_id detectado │ ├─ 1° Redis (robados en caliente) → respuesta inmediata ├─ 2° REPUVE Constancia (datos + robo) → API REST / JWT └─ 3° REPUVE Federal (backup) → SOAP ``` --- ## Gestión de Arcos (Lectores RFID) Un **arco** representa un lector RFID físico instalado en campo. ### Modelo `Arco` Campos relevantes de la tabla `arcos`: | Campo | Tipo | Descripción | |---|---|---| | `nombre` | string | Nombre descriptivo del arco | | `ip_address` | string (unique) | IP del lector en la red | | `ubicacion` | string | Descripción de la ubicación física | | `activo` | boolean | Habilita/deshabilita el arco | | `antena_1..4` | string | Etiquetas para cada antena | | `api_token` | string | Token encriptado AES-256-CBC | **Generación de token:** Al crear un arco se genera automáticamente un token de 64 caracteres aleatorios, encriptado con `EncryptionHelper::encryptWithCustomKey()` antes de persistir en BD. ### Endpoints | Método | Ruta | Acción | |---|---|---| | `GET` | `/api/arcos` | Listar arcos (filtros: `activo`, `ip`) | | `POST` | `/api/arcos` | Crear arco | | `GET` | `/api/arcos/{id}` | Detalle de un arco | | `PUT/PATCH` | `/api/arcos/{id}` | Actualizar arco | | `DELETE` | `/api/arcos/{id}` | Eliminar arco | | `PATCH` | `/api/arcos/{id}/toggle-estado` | Activar / desactivar | | `GET` | `/api/arcos/{id}/detecciones/dia` | Detecciones del día para ese arco | --- ## Sistema de Alertas Una alerta se genera automáticamente cuando el job de detección identifica que el `fast_id` detectado corresponde a un vehículo registrado como robado en Redis. ### Generación (`VehicleService::crearAlertaRobo()`) 1. Inserta registro en `alertas_robos` 2. Emite evento WebSocket `VehiculoRobadoDetectado` al canal `alertas-robos` ### Confirmar alerta **`PUT /api/alertas/{id}/confirmar`** — requiere `auth:api` - Si ya fue confirmada (`visto = true`), devuelve `400` con los datos del usuario que la confirmó - Si no, actualiza: `visto = true`, `usuario_id = auth()->id()`, `fecha_confirmacion = now()` ### Endpoints de consulta | Endpoint | Descripción | |---|---| | `GET /api/alertas/pendientes` | Sin confirmar, ordenadas por más recientes | | `GET /api/alertas` | Todas, filtrable por `visto`, `arco_id`, `placa`, `vin`, rango de fechas | | `GET /api/alertas/{id}` | Detalle de una alerta | --- ## Registro de Detecciones Cada detección se persiste en **dos tablas** con propósitos distintos. ### `detections` — Historial completo Registro individual e inmutable de cada evento RFID. | Campo | Descripción | |---|---| | `arco_id` | FK al arco que detectó | | `fast_id` | Tag RFID leído | | `vin`, `placa`, `marca`, `modelo`, `color` | Datos del vehículo al momento de detección | | `antena` | Número de antena del arco | | `fecha_deteccion` | Timestamp exacto | ### `daily_detections` — Resumen diario Incluye información adicional de estado, útil para dashboards y reportes. | Campo | Descripción | |---|---| | `arco_nombre` | Nombre del arco (desnormalizado) | | `estado` | `LIBRE` \| `ROBADO` \| `DESCONOCIDO` | | `tiene_reporte_robo` | Boolean — si REPUVE reporta robo | | `fecha_dia` | Columna virtual: `DATE(fecha_deteccion)` | Índices: `arco_id`, `fecha_dia`, `[arco_id + fecha_dia]`, `fast_id` ### Endpoints de consulta | Endpoint | Descripción | |---|---| | `GET /api/vehicles/detecciones` | Historial paginado (filtros: `placa`, `vin`) | | `GET /api/vehicles/detecciones/dia?fecha=YYYY-MM-DD` | Resumen diario (ventana de últimos 10 s) | | `GET /api/arcos/{id}/detecciones/dia?fecha=YYYY-MM-DD` | Resumen diario por arco | --- ## Transmisión en Tiempo Real **Servidor:** Laravel Reverb (WebSocket) Cuando se detecta un vehículo robado, se emite el evento `VehiculoRobadoDetectado`: - **Canal:** `alertas-robos` (público) - **Nombre en frontend:** `vehiculo.robado.detectado` - **Payload:** ```json { "alerta_id": 42, "fast_id": "1234567890", "vin": "1G1FB1RX1234567890", "placa": "ABC1234", "marca": "Chevrolet", "modelo": "Cruze", "color": "BLANCO", "arco_id": 3, "arco_nombre": "Arco Reforma Norte", "antena": "1", "fecha_deteccion": "2025-02-01T14:30:00Z", "mensaje": "🚨 VEHÍCULO ROBADO DETECTADO: ABC1234 en Arco Reforma Norte" } ``` El frontend conecta vía **Laravel Echo + Pusher.js**. --- ## Esquema de Base de Datos ### `arcos` ```sql id, nombre, ip_address (UNIQUE), ubicacion, descripcion, activo BOOLEAN DEFAULT true, antena_1, antena_2, antena_3, antena_4, api_token, created_at, updated_at ``` ### `detections` ```sql id, arco_id (FK arcos), fast_id, vin, placa, marca, modelo, color, antena, fecha_deteccion, created_at, updated_at ``` ### `daily_detections` ```sql id, arco_id (FK arcos), arco_nombre, antena, fast_id, vin, placa, marca, modelo, color, estado ENUM('LIBRE','ROBADO','DESCONOCIDO'), tiene_reporte_robo BOOLEAN, fecha_deteccion, fecha_dia (virtual: DATE(fecha_deteccion)), created_at, updated_at ``` ### `vehicles` — Archivo de recuperaciones ```sql id, fast_id, vin, placa, fecha_robo, acta_robo, denunciante, fecha_acta, fecha_recuperacion, datos_robo_original JSON, created_at, updated_at ``` ### `alertas_robos` ```sql id, fast_id, vin, placa, marca, modelo, color, arco_id (FK arcos), arco_nombre, antena, fecha_deteccion, visto BOOLEAN DEFAULT false, usuario_id (FK users, NULLABLE), fecha_confirmacion NULLABLE, created_at, updated_at ``` --- ## Comandos y Tareas Programadas ### `php artisan detecciones:limpiar {--dias=1}` Elimina registros de `daily_detections` con más de N días de antigüedad (default: 1 día). Se puede ejecutar manualmente o agregar al scheduler. ```bash php artisan detecciones:limpiar # elimina detecciones de más de 1 día php artisan detecciones:limpiar --dias=7 # elimina detecciones de más de 7 días ``` ### Servicios en producción (PM2) ```bash composer run services:start # inicia queue worker, scheduler y Reverb composer run services:stop # detiene todos los servicios composer run services:status # muestra estado de cada proceso PM2 ``` Procesos individuales: ```bash php artisan queue:work # procesa detecciones encoladas php artisan schedule:work # ejecuta tareas programadas php artisan reverb:start # servidor WebSocket ```