From 27807c1f29e05c17ee0f153c21cacf89c798ce1b Mon Sep 17 00:00:00 2001 From: Juan Felipe Zapata Moreno Date: Fri, 27 Feb 2026 15:38:58 -0600 Subject: [PATCH] =?UTF-8?q?add:=20actualizar=20.gitignore=20y=20agregar=20?= =?UTF-8?q?documentaci=C3=B3n=20t=C3=A9cnica=20del=20backend?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + docker-compose.yml | 25 -- docs/DOCUMENTACION_TECNICA.md | 485 ++++++++++++++++++++++++++++++++++ 3 files changed, 486 insertions(+), 25 deletions(-) create mode 100644 docs/DOCUMENTACION_TECNICA.md diff --git a/.gitignore b/.gitignore index 8a0ab32..c18a242 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,7 @@ .env.production .phpactor.json .phpunit.result.cache +CLAUDE.md Homestead.json Homestead.yaml auth.json diff --git a/docker-compose.yml b/docker-compose.yml index 4f917c5..83c6c5c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -58,19 +58,6 @@ services: timeout: 15s retries: 10 - phpmyadmin: - image: phpmyadmin/phpmyadmin - environment: - PMA_HOST: mysql - PMA_PORT: 3306 - ports: - - '${PMA_PORT}:80' - depends_on: - - mysql - networks: - - arcos-network - mem_limit: 400m - redis: image: redis:alpine ports: @@ -85,18 +72,6 @@ services: timeout: 5s retries: 5 - redis-commander: - image: rediscommander/redis-commander:latest - environment: - - REDIS_HOSTS=local:redis:6379 - ports: - - "${REDIS_COMMANDER_PORT}:8081" - networks: - - arcos-network - mem_limit: 256m - depends_on: - - redis - volumes: nginx_data: driver: local diff --git a/docs/DOCUMENTACION_TECNICA.md b/docs/DOCUMENTACION_TECNICA.md new file mode 100644 index 0000000..4d3983b --- /dev/null +++ b/docs/DOCUMENTACION_TECNICA.md @@ -0,0 +1,485 @@ +# 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 +```