add: actualizar .gitignore y agregar documentación técnica del backend
This commit is contained in:
parent
121e0ee109
commit
27807c1f29
1
.gitignore
vendored
1
.gitignore
vendored
@ -14,6 +14,7 @@
|
||||
.env.production
|
||||
.phpactor.json
|
||||
.phpunit.result.cache
|
||||
CLAUDE.md
|
||||
Homestead.json
|
||||
Homestead.yaml
|
||||
auth.json
|
||||
|
||||
@ -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
|
||||
|
||||
485
docs/DOCUMENTACION_TECNICA.md
Normal file
485
docs/DOCUMENTACION_TECNICA.md
Normal file
@ -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
|
||||
<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`
|
||||
|
||||
```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
|
||||
```
|
||||
Loading…
x
Reference in New Issue
Block a user