arcos-backend/docs/DOCUMENTACION_TECNICA.md

15 KiB

Documentación Técnica — Arcos Backend

Índice

  1. Visión General
  2. Flujo de Detección RFID
  3. Gestión de Vehículos Robados
  4. Integraciones REPUVE
  5. Gestión de Arcos (Lectores RFID)
  6. Sistema de Alertas
  7. Registro de Detecciones
  8. Transmisión en Tiempo Real
  9. Esquema de Base de Datos
  10. 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

{
  "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}:
[
    '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ículoconsultarVehiculoPorTag():

Intenta en orden: tag_numberplacavin

Respuesta normalizada:

[
    '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:

<wsdl:doConsRepRobo>
    <arg0>{username}</arg0>
    <arg1>{password}</arg1>
    <arg2>{vin}|{placa}|||||</arg2>
</wsdl:doConsRepRobo>

Respuesta parseada desde <return>: OK:indicador|fecha|placa|vin|autoridad|acta|denunciante|fecha_acta

[
    '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:
{
  "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

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

id, arco_id (FK arcos), fast_id, vin, placa,
marca, modelo, color, antena, fecha_deteccion,
created_at, updated_at

daily_detections

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

id, fast_id, vin, placa,
fecha_robo, acta_robo, denunciante, fecha_acta,
fecha_recuperacion,
datos_robo_original JSON,
created_at, updated_at

alertas_robos

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.

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)

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:

php artisan queue:work                        # procesa detecciones encoladas
php artisan schedule:work                     # ejecuta tareas programadas
php artisan reverb:start                      # servidor WebSocket