feat: Initial commit - Train tracking system
Some checks failed
Auto Tag on Merge to Main / auto-tag (push) Successful in 27s
CI - Lint and Build / lint-backend (push) Failing after 30s
CI - Lint and Build / lint-frontend (push) Failing after 2s
CI - Lint and Build / build-frontend (push) Has been skipped
CI - Lint and Build / docker-build-test (push) Has been skipped

Complete real-time train tracking system for Spanish railways (Renfe/Cercanías):

- Backend API (Node.js/Express) with GTFS-RT polling workers
- Frontend dashboard (React/Vite) with Leaflet maps
- Real-time updates via Socket.io WebSocket
- PostgreSQL/PostGIS database with Flyway migrations
- Redis caching layer
- Docker Compose configuration for development and production
- Gitea CI/CD workflows (lint, auto-tag, release)
- Production deployment with nginx + Let's Encrypt SSL

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Millaguie
2025-11-28 00:21:15 +01:00
commit 34c0cb50c7
64 changed files with 15577 additions and 0 deletions

703
FASE2-ENRIQUECIMIENTO.md Normal file
View File

@@ -0,0 +1,703 @@
# Fase 2: Enriquecimiento - GTFS Static, Trip Updates y Service Alerts
## Estado: 🚧 EN DESARROLLO
La Fase 2 añade enriquecimiento de datos mediante GTFS Static, actualizaciones de viajes en tiempo real (Trip Updates) y alertas de servicio (Service Alerts).
---
## ✨ Características Implementadas
### Backend
#### Base de Datos
- ✅ Migración V5: Tablas GTFS Static (trips, stop_times, calendar, shapes)
- ✅ Tablas para Trip Updates y Stop Time Updates
- ✅ Vistas: active_trips_today, delayed_trips
- ✅ Funciones: get_trip_schedule, get_next_departures
#### Workers
- ✅ GTFS Static Syncer: Sincronización diaria de datos estáticos
- ✅ Trip Updates Poller: Polling de retrasos y actualizaciones de viajes
- ✅ Service Alerts Poller: Polling de alertas e incidencias
#### API REST
- ✅ Endpoints de Alertas (`/alerts`)
- ✅ Endpoints de Trips y Delays (`/trips`)
### Frontend
- ⏳ Componente de Alertas (pendiente)
- ⏳ Monitor de Puntualidad (pendiente)
- ⏳ Timeline Funcional (pendiente)
---
## 📁 Nuevos Archivos Phase 2
### Base de Datos
```
database/migrations/
└── V5__gtfs_static_tables.sql # Tablas GTFS Static + Trip Updates
```
### Backend Workers
```
backend/src/worker/
├── gtfs-static-syncer.js # Sincronización GTFS Static
├── trip-updates-poller.js # Polling Trip Updates
└── alerts-poller.js # Polling Service Alerts
```
### Backend API
```
backend/src/api/routes/
├── alerts.js # Endpoints de alertas
└── trips.js # Endpoints de trips y delays
```
---
## 🚀 Ejecutar con Fase 2
### Usando Docker Compose
```bash
# 1. Ejecutar migraciones (incluye V5)
make migrate
# 2. Iniciar todos los servicios (incluye nuevos workers)
make start
# Los nuevos workers se inician automáticamente:
# - gtfs-static-syncer (sincronización diaria a las 3 AM)
# - trip-updates-poller (polling cada 30s)
# - alerts-poller (polling cada 30s)
```
### Desarrollo Local
```bash
cd backend
# Terminal 1: GTFS Static Syncer
npm run dev:gtfs-static
# Terminal 2: Trip Updates Poller
npm run dev:trip-updates
# Terminal 3: Service Alerts Poller
npm run dev:alerts
# Terminal 4: API Server
npm run dev
```
---
## 📡 Nuevos Endpoints API
### Alertas
#### GET /alerts
Obtener todas las alertas activas con filtros opcionales.
**Query Parameters:**
- `route_id` (opcional): Filtrar por ruta
- `severity` (opcional): Filtrar por severidad (LOW, MEDIUM, HIGH, CRITICAL)
- `type` (opcional): Filtrar por tipo (DELAY, CANCELLATION, INCIDENT, etc.)
**Ejemplo:**
```bash
# Todas las alertas activas
curl http://localhost:3000/alerts
# Alertas de una ruta específica
curl http://localhost:3000/alerts?route_id=AVE-MAD-BCN
# Alertas críticas
curl http://localhost:3000/alerts?severity=CRITICAL
# Alertas de cancelaciones
curl http://localhost:3000/alerts?type=CANCELLATION
```
**Respuesta:**
```json
[
{
"alert_id": 1,
"alert_type": "DELAY",
"severity": "MEDIUM",
"cause": "TECHNICAL_PROBLEM",
"effect": "SIGNIFICANT_DELAYS",
"header_text": "Retraso en AVE 03055",
"description_text": "Retraso de 15 minutos debido a problemas técnicos",
"url": null,
"route_id": "AVE-MAD-BCN",
"trip_id": "trip_12345",
"train_id": null,
"start_time": "2025-11-27T10:00:00Z",
"end_time": null,
"created_at": "2025-11-27T10:05:00Z",
"updated_at": "2025-11-27T10:05:00Z"
}
]
```
#### GET /alerts/:id
Obtener una alerta específica.
**Ejemplo:**
```bash
curl http://localhost:3000/alerts/1
```
#### GET /alerts/route/:routeId
Obtener todas las alertas activas de una ruta.
**Ejemplo:**
```bash
curl http://localhost:3000/alerts/route/AVE-MAD-BCN
```
#### GET /alerts/train/:trainId
Obtener todas las alertas activas de un tren.
**Ejemplo:**
```bash
curl http://localhost:3000/alerts/train/12345
```
---
### Trips y Delays
#### GET /trips
Obtener todos los viajes activos del día.
**Query Parameters:**
- `route_id` (opcional): Filtrar por ruta
- `service_id` (opcional): Filtrar por servicio
**Ejemplo:**
```bash
# Todos los viajes activos hoy
curl http://localhost:3000/trips
# Viajes de una ruta específica
curl http://localhost:3000/trips?route_id=AVE-MAD-BCN
```
**Respuesta:**
```json
[
{
"trip_id": "trip_12345",
"route_id": "AVE-MAD-BCN",
"service_id": "weekday",
"trip_headsign": "Barcelona Sants",
"direction_id": 0,
"block_id": null,
"shape_id": "shape_001"
}
]
```
#### GET /trips/:id
Obtener detalles completos de un viaje incluyendo su horario.
**Ejemplo:**
```bash
curl http://localhost:3000/trips/trip_12345
```
**Respuesta:**
```json
{
"trip_id": "trip_12345",
"route_id": "AVE-MAD-BCN",
"service_id": "weekday",
"trip_headsign": "Barcelona Sants",
"direction_id": 0,
"schedule": [
{
"stop_id": "MADRID-PUERTA-DE-ATOCHA",
"stop_sequence": 1,
"arrival_time": "08:00:00",
"departure_time": "08:00:00",
"stop_headsign": null
},
{
"stop_id": "ZARAGOZA-DELICIAS",
"stop_sequence": 2,
"arrival_time": "09:25:00",
"departure_time": "09:27:00",
"stop_headsign": null
},
{
"stop_id": "BARCELONA-SANTS",
"stop_sequence": 3,
"arrival_time": "10:45:00",
"departure_time": "10:45:00",
"stop_headsign": null
}
]
}
```
#### GET /trips/:id/updates
Obtener actualizaciones en tiempo real de un viaje (retrasos, cancelaciones).
**Ejemplo:**
```bash
curl http://localhost:3000/trips/trip_12345/updates
```
**Respuesta:**
```json
{
"trip_id": "trip_12345",
"has_updates": true,
"update_id": 1,
"delay_seconds": 900,
"schedule_relationship": "SCHEDULED",
"start_date": "20251127",
"received_at": "2025-11-27T10:15:00Z",
"stop_time_updates": [
{
"stop_sequence": 2,
"stop_id": "ZARAGOZA-DELICIAS",
"arrival_delay": 900,
"departure_delay": 900,
"schedule_relationship": "SCHEDULED"
},
{
"stop_sequence": 3,
"stop_id": "BARCELONA-SANTS",
"arrival_delay": 900,
"departure_delay": null,
"schedule_relationship": "SCHEDULED"
}
]
}
```
#### GET /trips/:id/delays
Obtener información resumida de retrasos de un viaje.
**Ejemplo:**
```bash
curl http://localhost:3000/trips/trip_12345/delays
```
**Respuesta:**
```json
{
"trip_id": "trip_12345",
"delay_status": "MODERATE_DELAY",
"delay_seconds": 900,
"delay_formatted": "15 min 0 s",
"schedule_relationship": "SCHEDULED",
"received_at": "2025-11-27T10:15:00Z"
}
```
**Estados de Delay:**
- `NO_DATA`: Sin información de retrasos
- `ON_TIME`: Puntual (0 segundos)
- `MINOR_DELAY`: Retraso menor (1-5 minutos)
- `MODERATE_DELAY`: Retraso moderado (5-15 minutos)
- `MAJOR_DELAY`: Retraso mayor (>15 minutos)
- `EARLY`: Adelantado
#### GET /trips/route/:routeId
Obtener todos los viajes de una ruta.
**Ejemplo:**
```bash
curl http://localhost:3000/trips/route/AVE-MAD-BCN
```
#### GET /trips/delayed/all
Obtener todos los viajes actualmente retrasados.
**Query Parameters:**
- `min_delay` (opcional): Retraso mínimo en segundos (default: 0)
**Ejemplo:**
```bash
# Todos los viajes retrasados
curl http://localhost:3000/trips/delayed/all
# Solo retrasos mayores a 5 minutos
curl http://localhost:3000/trips/delayed/all?min_delay=300
```
**Respuesta:**
```json
[
{
"trip_id": "trip_12345",
"route_id": "AVE-MAD-BCN",
"trip_headsign": "Barcelona Sants",
"delay_seconds": 900,
"schedule_relationship": "SCHEDULED",
"received_at": "2025-11-27T10:15:00Z"
}
]
```
---
## 🔌 Nuevos WebSocket Events (Planeados)
### Servidor → Cliente
```javascript
// Nueva alerta creada
socket.on('alert:new', (alert) => {
console.log('Nueva alerta:', alert);
});
// Alerta actualizada
socket.on('alert:update', (alert) => {
console.log('Alerta actualizada:', alert);
});
// Retraso detectado
socket.on('trip:delay', (delayInfo) => {
console.log('Retraso en viaje:', delayInfo);
});
// Cancelación de viaje
socket.on('trip:cancelled', (tripInfo) => {
console.log('Viaje cancelado:', tripInfo);
});
```
---
## 🗄️ Estructura de Datos
### Tablas GTFS Static
#### trips
Información de viajes planificados.
```sql
CREATE TABLE trips (
trip_id VARCHAR(100) PRIMARY KEY,
route_id VARCHAR(50),
service_id VARCHAR(50),
trip_headsign VARCHAR(200),
trip_short_name VARCHAR(50),
direction_id INTEGER,
block_id VARCHAR(50),
shape_id VARCHAR(100),
wheelchair_accessible INTEGER,
bikes_allowed INTEGER
);
```
#### stop_times
Horarios de parada de cada viaje.
```sql
CREATE TABLE stop_times (
trip_id VARCHAR(100),
arrival_time TIME,
departure_time TIME,
stop_id VARCHAR(100),
stop_sequence INTEGER,
stop_headsign VARCHAR(200),
pickup_type INTEGER,
drop_off_type INTEGER,
shape_dist_traveled FLOAT,
PRIMARY KEY (trip_id, stop_sequence)
);
```
#### calendar
Calendario de servicio (días de operación).
```sql
CREATE TABLE calendar (
service_id VARCHAR(50) PRIMARY KEY,
monday BOOLEAN,
tuesday BOOLEAN,
wednesday BOOLEAN,
thursday BOOLEAN,
friday BOOLEAN,
saturday BOOLEAN,
sunday BOOLEAN,
start_date DATE,
end_date DATE
);
```
#### shapes
Geometría de las rutas (trayectorias).
```sql
CREATE TABLE shapes (
shape_id VARCHAR(100),
shape_pt_lat DOUBLE PRECISION,
shape_pt_lon DOUBLE PRECISION,
shape_pt_sequence INTEGER,
shape_dist_traveled FLOAT,
PRIMARY KEY (shape_id, shape_pt_sequence)
);
```
### Tablas de Actualizaciones en Tiempo Real
#### trip_updates
Actualizaciones de viajes (retrasos, cancelaciones).
```sql
CREATE TABLE trip_updates (
update_id SERIAL PRIMARY KEY,
trip_id VARCHAR(100),
route_id VARCHAR(50),
start_date VARCHAR(10),
schedule_relationship VARCHAR(20),
delay_seconds INTEGER,
received_at TIMESTAMP DEFAULT NOW()
);
```
#### stop_time_updates
Actualizaciones de paradas específicas.
```sql
CREATE TABLE stop_time_updates (
update_id INTEGER REFERENCES trip_updates(update_id),
stop_sequence INTEGER,
stop_id VARCHAR(100),
arrival_delay INTEGER,
departure_delay INTEGER,
schedule_relationship VARCHAR(20),
PRIMARY KEY (update_id, stop_sequence)
);
```
---
## 🧪 Probar Phase 2
### 1. Verificar GTFS Static Sync
```bash
# Ver logs del syncer
docker-compose logs -f gtfs-static-syncer
# Deberías ver:
# "Starting GTFS Static synchronization..."
# "GTFS data downloaded successfully"
# "Imported X routes, Y trips, Z stops"
# Verificar datos en PostgreSQL
make psql
> SELECT COUNT(*) FROM trips;
> SELECT COUNT(*) FROM stop_times;
> SELECT * FROM active_trips_today LIMIT 5;
```
### 2. Verificar Trip Updates
```bash
# Ver logs del poller
docker-compose logs -f trip-updates-poller
# Deberías ver:
# "Polling Trip Updates..."
# "Processed X trip updates"
# Verificar en PostgreSQL
make psql
> SELECT * FROM delayed_trips;
> SELECT * FROM trip_updates ORDER BY received_at DESC LIMIT 5;
```
### 3. Verificar Service Alerts
```bash
# Ver logs del poller
docker-compose logs -f alerts-poller
# Deberías ver:
# "Polling Service Alerts..."
# "Processed X alerts"
# Probar API
curl http://localhost:3000/alerts | jq
curl http://localhost:3000/alerts?severity=HIGH | jq
```
### 4. Verificar Trip Delays API
```bash
# Obtener viajes retrasados
curl http://localhost:3000/trips/delayed/all | jq
# Obtener delay de un viaje específico
curl http://localhost:3000/trips/trip_12345/delays | jq
# Obtener schedule completo
curl http://localhost:3000/trips/trip_12345 | jq
```
---
## 🐛 Troubleshooting Phase 2
### No hay datos de GTFS Static
**Causa**: El syncer no se ha ejecutado o el ZIP no está disponible.
**Solución**:
```bash
# Ejecutar sync manual
docker-compose exec gtfs-static-syncer node src/worker/gtfs-static-syncer.js
# Verificar logs
docker-compose logs gtfs-static-syncer
# Verificar conectividad
curl -I https://data.renfe.com/dataset/horarios-trenes-largo-recorrido-ave/resource/horarios-trenes-largo-recorrido-ave-gtfs.zip
```
### No se reciben Trip Updates
**Causa**: Feed GTFS-RT no disponible o URL incorrecta.
**Solución**:
```bash
# Verificar logs
docker-compose logs trip-updates-poller
# Probar feed manualmente
curl https://gtfsrt.renfe.com/trip_updates.pb > /tmp/test.pb
file /tmp/test.pb # Debe ser "data" (Protocol Buffer)
# Verificar variables de entorno
docker-compose exec trip-updates-poller env | grep GTFS
```
### Alerts no aparecen
**Causa**: No hay alertas activas o el poller no está corriendo.
**Solución**:
```bash
# Verificar worker
docker-compose ps alerts-poller
# Ver logs
docker-compose logs alerts-poller
# Verificar en BD
make psql
> SELECT COUNT(*) FROM alerts WHERE end_time IS NULL OR end_time > NOW();
```
---
## 📊 Monitorización Phase 2
### Logs de Workers
```bash
# Todos los workers Phase 2
docker-compose logs -f gtfs-static-syncer trip-updates-poller alerts-poller
# Worker específico
docker-compose logs -f trip-updates-poller
```
### Estadísticas en Redis
```bash
make redis-cli
# Viajes retrasados
> SMEMBERS trips:delayed
# Alertas activas por ruta
> SMEMBERS alerts:route:AVE-MAD-BCN
# Última sincronización GTFS
> GET gtfs:last_sync
```
### Métricas en PostgreSQL
```sql
-- Total de viajes del día
SELECT COUNT(*) FROM active_trips_today;
-- Viajes retrasados
SELECT COUNT(*) FROM delayed_trips;
-- Alertas activas
SELECT alert_type, COUNT(*)
FROM alerts
WHERE end_time IS NULL OR end_time > NOW()
GROUP BY alert_type;
-- Retraso promedio
SELECT AVG(delay_seconds) / 60 as avg_delay_minutes
FROM trip_updates
WHERE received_at > NOW() - INTERVAL '1 hour';
```
---
## 🎯 Próximos Pasos
Funcionalidades pendientes de Phase 2:
- [ ] WebSocket events para alertas y delays en tiempo real
- [ ] Frontend: Componente de alertas
- [ ] Frontend: Monitor de puntualidad
- [ ] Frontend: Timeline funcional con reproducción histórica
- [ ] Frontend: Panel de incidencias
- [ ] Notificaciones push (opcional)
- [ ] Exportar reportes de puntualidad (opcional)
---
## 📝 Notas Técnicas
### Fuentes de Datos Phase 2
- **GTFS Static**: https://data.renfe.com/dataset/horarios-trenes-largo-recorrido-ave/resource/horarios-trenes-largo-recorrido-ave-gtfs.zip
- **Trip Updates**: https://gtfsrt.renfe.com/trip_updates.pb
- **Service Alerts**: https://gtfsrt.renfe.com/service_alerts.pb
### Frecuencias
- **GTFS Static Sync**: Diariamente a las 3 AM (configurable via `SYNC_SCHEDULE`)
- **Trip Updates**: Cada 30 segundos
- **Service Alerts**: Cada 30 segundos
### Retención de Datos
- **Trip Updates**: 7 días (usar función `cleanup_old_trip_updates()`)
- **Alerts**: Se mantienen hasta `end_time` + 7 días
- **GTFS Static**: Se sobrescribe en cada sync
---
## 📚 Documentación Relacionada
- [Arquitectura Completa](arquitectura-sistema-tracking-trenes.md)
- [Fase 1 - MVP](FASE1-MVP.md)
- [Fuentes de Datos](FUENTES_DATOS.md)
- [README Principal](README.md)
---
**Estado**: Fase 2 - Backend Completo, Frontend Pendiente
**Fecha**: 27 noviembre 2025
**Próxima Fase**: Frontend Phase 2 + Fase 3 (Analytics)