Files
trenes/FASE2-ENRIQUECIMIENTO.md
Millaguie 34c0cb50c7
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
feat: Initial commit - Train tracking system
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>
2025-11-28 00:21:15 +01:00

15 KiB

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

# 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

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:

# 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:

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

curl http://localhost:3000/alerts/1

GET /alerts/route/:routeId

Obtener todas las alertas activas de una ruta.

Ejemplo:

curl http://localhost:3000/alerts/route/AVE-MAD-BCN

GET /alerts/train/:trainId

Obtener todas las alertas activas de un tren.

Ejemplo:

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:

# 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:

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

curl http://localhost:3000/trips/trip_12345

Respuesta:

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

curl http://localhost:3000/trips/trip_12345/updates

Respuesta:

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

curl http://localhost:3000/trips/trip_12345/delays

Respuesta:

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

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:

# 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:

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

// 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.

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.

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).

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).

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).

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.

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

# 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

# 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

# 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

# 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:

# 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:

# 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:

# 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

# 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

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

-- 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

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


Estado: Fase 2 - Backend Completo, Frontend Pendiente Fecha: 27 noviembre 2025 Próxima Fase: Frontend Phase 2 + Fase 3 (Analytics)