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>
20 KiB
Fase 3: Analytics y Exploración Avanzada
Estado: ✅ IMPLEMENTADO (Backend)
La Fase 3 añade capacidades avanzadas de análisis, exploración de rutas, planificación de viajes y exportación de datos.
✨ Características Implementadas
Backend
Base de Datos
- ✅ Migración V6: Vistas materializadas para analytics
- ✅ Funciones para heatmaps, estadísticas y análisis
- ✅ Tablas para cache de exportaciones
- ✅ Vistas de sistema: traffic_by_hour, traffic_by_route, daily_statistics, route_performance
Workers
- ✅ Analytics Refresher: Refresco automático de vistas materializadas cada 15 minutos
API REST
- ✅ Analytics API (
/analytics) - Heatmaps, estadísticas, performance - ✅ Explorer API (
/explorer) - Explorador de rutas, planificador de viajes, búsqueda
Frontend
- ⏳ Componentes de Analytics (pendiente)
- ⏳ Heatmap de tráfico (pendiente)
- ⏳ Dashboard de estadísticas (pendiente)
- ⏳ Planificador de viajes UI (pendiente)
📁 Nuevos Archivos Phase 3
Base de Datos
database/migrations/
└── V6__analytics_and_statistics.sql # Vistas y funciones de analytics
Backend Workers
backend/src/worker/
└── analytics-refresher.js # Refresco de vistas materializadas
Backend API
backend/src/api/routes/
├── analytics.js # Endpoints de analytics y exportación
└── explorer.js # Explorador de rutas y planificador
🚀 Ejecutar con Fase 3
Usando Docker Compose
# 1. Ejecutar migraciones (incluye V6)
make migrate
# 2. Iniciar todos los servicios (incluye analytics-refresher)
make start
# El worker analytics-refresher se inicia automáticamente
# y refresca las vistas cada 15 minutos
Desarrollo Local
cd backend
# Terminal 1: Analytics Refresher
npm run dev:analytics
# Terminal 2: API Server
npm run dev
📡 Nuevos Endpoints API
Analytics - Traffic
GET /analytics/traffic/heatmap
Obtener datos de heatmap de tráfico.
Query Parameters:
start_date(opcional): Fecha inicio (ISO 8601)end_date(opcional): Fecha fin (ISO 8601)grid_size(opcional): Tamaño de celda en grados (default: 0.1 ≈ 11km)
Ejemplo:
# Heatmap últimos 7 días
curl http://localhost:3000/analytics/traffic/heatmap
# Heatmap con grid más fino
curl http://localhost:3000/analytics/traffic/heatmap?grid_size=0.05
# Rango personalizado
curl "http://localhost:3000/analytics/traffic/heatmap?start_date=2025-11-20T00:00:00Z&end_date=2025-11-27T23:59:59Z"
Respuesta:
[
{
"lat": 40.4,
"lon": -3.7,
"intensity": 125,
"avgSpeed": 78.5
},
{
"lat": 41.3,
"lon": 2.1,
"intensity": 98,
"avgSpeed": 65.3
}
]
GET /analytics/traffic/hourly
Obtener patrón de tráfico por hora del día.
Query Parameters:
days(opcional): Número de días para analizar (default: 7)
Ejemplo:
curl http://localhost:3000/analytics/traffic/hourly?days=30
Respuesta:
[
{
"hour_of_day": 0,
"avg_trains": 15.3,
"avg_speed": 45.2,
"total_observations": 45230
},
{
"hour_of_day": 7,
"avg_trains": 87.5,
"avg_speed": 68.7,
"total_observations": 125890
}
]
GET /analytics/traffic/by-hour
Obtener estadísticas de tráfico agregadas por hora.
Query Parameters:
limit(opcional): Número de horas (default: 24)
Ejemplo:
curl http://localhost:3000/analytics/traffic/by-hour?limit=48
GET /analytics/traffic/by-route
Obtener estadísticas de tráfico por ruta.
Ejemplo:
curl http://localhost:3000/analytics/traffic/by-route
Respuesta:
[
{
"route_id": "AVE-MAD-BCN",
"route_name": "Madrid - Barcelona",
"route_type": "HIGH_SPEED",
"total_trains": 234,
"active_days": 30,
"avg_speed": 185.4,
"total_positions": 125890
}
]
Analytics - Statistics
GET /analytics/statistics/daily
Obtener estadísticas diarias del sistema.
Query Parameters:
days(opcional): Número de días (default: 30)
Ejemplo:
curl http://localhost:3000/analytics/statistics/daily?days=90
Respuesta:
[
{
"date": "2025-11-27",
"unique_trains": 456,
"total_positions": 65432,
"avg_speed": 72.3,
"stopped_count": 12890,
"moving_count": 52542
}
]
GET /analytics/statistics/system
Obtener estado actual del sistema.
Ejemplo:
curl http://localhost:3000/analytics/statistics/system
Respuesta:
{
"active_trains": 234,
"active_alerts": 5,
"delayed_trips": 12,
"avg_delay_seconds": 450,
"active_routes": 45,
"last_update": "2025-11-27T15:30:00Z"
}
Analytics - Performance
GET /analytics/performance/routes
Obtener métricas de rendimiento de rutas (puntualidad, retrasos).
Query Parameters:
limit(opcional): Número de rutas (default: 20)
Ejemplo:
curl http://localhost:3000/analytics/performance/routes?limit=10
Respuesta:
[
{
"route_id": "AVE-MAD-BCN",
"route_name": "Madrid - Barcelona",
"total_trips": 345,
"delayed_trips": 23,
"on_time_trips": 322,
"avg_delay_seconds": 180,
"median_delay_seconds": 120,
"max_delay_seconds": 1800,
"punctuality_percentage": 93.33
}
]
GET /analytics/performance/route/:routeId
Obtener estadísticas detalladas de una ruta específica.
Query Parameters:
days(opcional): Número de días para analizar (default: 7)
Ejemplo:
curl http://localhost:3000/analytics/performance/route/AVE-MAD-BCN?days=30
Respuesta:
{
"total_trips": 234,
"unique_trains": 45,
"avg_speed": 185.4,
"max_speed": 298.7,
"total_distance_km": 98765.4,
"avg_delay_seconds": 120,
"on_time_percentage": 92.5
}
Analytics - Delays
GET /analytics/delays/top-routes
Obtener rutas con más retrasos.
Ejemplo:
curl http://localhost:3000/analytics/delays/top-routes
Respuesta:
[
{
"route_id": "MD-MAD-VAL",
"route_name": "Madrid - Valencia",
"delayed_count": 45,
"avg_delay": 600,
"max_delay": 2400
}
]
Analytics - Stations
GET /analytics/stations/busiest
Obtener estaciones más transitadas.
Query Parameters:
limit(opcional): Número de estaciones (default: 20)
Ejemplo:
curl http://localhost:3000/analytics/stations/busiest?limit=10
Respuesta:
[
{
"stop_id": "MADRID-PUERTA-DE-ATOCHA",
"stop_name": "Madrid Puerta de Atocha",
"daily_trips": 456,
"routes_count": 34
}
]
GET /analytics/stations/:stationId/statistics
Obtener estadísticas de una estación específica.
Query Parameters:
days(opcional): Número de días (default: 7)
Ejemplo:
curl http://localhost:3000/analytics/stations/MADRID-PUERTA-DE-ATOCHA/statistics?days=30
Respuesta:
{
"total_departures": 3450,
"total_arrivals": 3420,
"unique_routes": 45,
"avg_delay_minutes": 2.5,
"busiest_hour": 8
}
Analytics - Trains
GET /analytics/trains/:trainId/distance
Calcular distancia recorrida por un tren.
Query Parameters:
start_time(opcional): Timestamp inicioend_time(opcional): Timestamp fin
Ejemplo:
curl "http://localhost:3000/analytics/trains/12345/distance?start_time=2025-11-27T00:00:00Z&end_time=2025-11-27T23:59:59Z"
Respuesta:
{
"train_id": "12345",
"start_time": "2025-11-27T00:00:00Z",
"end_time": "2025-11-27T23:59:59Z",
"distance_km": 1234.56
}
Analytics - Export
GET /analytics/export
Exportar datos en diferentes formatos.
Query Parameters:
table(requerido): Tabla a exportarformat(opcional): json, csv, geojson (default: json)start_date(opcional): Fecha inicioend_date(opcional): Fecha finlimit(opcional): Límite de registros (default: 1000)
Tablas permitidas:
- train_positions
- trains
- routes
- stations
- alerts
- trip_updates
- traffic_by_hour
- daily_statistics
Ejemplo JSON:
curl "http://localhost:3000/analytics/export?table=trains&format=json"
Ejemplo CSV:
curl "http://localhost:3000/analytics/export?table=train_positions&format=csv&limit=5000" > positions.csv
Ejemplo GeoJSON:
curl "http://localhost:3000/analytics/export?table=stations&format=geojson" > stations.geojson
Analytics - Refresh
POST /analytics/refresh
Refrescar manualmente las vistas materializadas.
Ejemplo:
curl -X POST http://localhost:3000/analytics/refresh
Respuesta:
{
"success": true,
"message": "Analytics views refreshed successfully",
"timestamp": "2025-11-27T15:30:00Z"
}
📍 Explorer - Route Explorer
GET /explorer/routes/:routeId
Obtener información completa de una ruta (trips, stops, shape).
Ejemplo:
curl http://localhost:3000/explorer/routes/AVE-MAD-BCN
Respuesta:
{
"route": {
"route_id": "AVE-MAD-BCN",
"route_name": "Madrid - Barcelona",
"route_type": "HIGH_SPEED"
},
"trips": [
{
"trip_id": "trip_001",
"trip_headsign": "Barcelona Sants",
"direction_id": 0
}
],
"stops": [
{
"stop_id": "MADRID-PUERTA-DE-ATOCHA",
"stop_name": "Madrid Puerta de Atocha",
"stop_lat": 40.4067,
"stop_lon": -3.6906
}
],
"shape": {
"shape_id": "shape_001",
"points": [
{
"lat": 40.4067,
"lon": -3.6906,
"sequence": 1,
"distance": 0
}
]
},
"total_trips": 45,
"total_stops": 8
}
GET /explorer/trips/:tripId/schedule
Obtener horario completo de un viaje.
Ejemplo:
curl http://localhost:3000/explorer/trips/trip_12345/schedule
Respuesta:
[
{
"stop_id": "MADRID-PUERTA-DE-ATOCHA",
"stop_name": "Madrid Puerta de Atocha",
"arrival_time": "08:00:00",
"departure_time": "08:00:00",
"stop_sequence": 1
},
{
"stop_id": "BARCELONA-SANTS",
"stop_name": "Barcelona Sants",
"arrival_time": "10:45:00",
"departure_time": "10:45:00",
"stop_sequence": 3
}
]
🚉 Explorer - Stations
GET /explorer/stations/:stationId
Obtener información completa de una estación.
Ejemplo:
curl http://localhost:3000/explorer/stations/MADRID-PUERTA-DE-ATOCHA
Respuesta:
{
"station": {
"stop_id": "MADRID-PUERTA-DE-ATOCHA",
"stop_name": "Madrid Puerta de Atocha",
"stop_lat": 40.4067,
"stop_lon": -3.6906
},
"next_departures": [
{
"trip_id": "trip_001",
"route_id": "AVE-MAD-BCN",
"route_name": "Madrid - Barcelona",
"headsign": "Barcelona Sants",
"scheduled_departure": "15:00:00",
"estimated_delay": 180,
"status": "DELAYED"
}
],
"routes": [
{
"route_id": "AVE-MAD-BCN",
"route_name": "Madrid - Barcelona"
}
],
"statistics": {
"total_departures": 456,
"total_arrivals": 450,
"unique_routes": 34,
"avg_delay_minutes": 2.5,
"busiest_hour": 8
}
}
GET /explorer/stations/:stationId/nearby
Obtener estaciones cercanas.
Query Parameters:
radius(opcional): Radio en km (default: 5)
Ejemplo:
curl "http://localhost:3000/explorer/stations/MADRID-PUERTA-DE-ATOCHA/nearby?radius=10"
Respuesta:
[
{
"stop_id": "MADRID-CHAMARTIN",
"stop_name": "Madrid Chamartín",
"stop_lat": 40.4728,
"stop_lon": -3.6797,
"distance_km": 7.8
}
]
🗺️ Explorer - Trip Planner
GET /explorer/planner
Planificador de viajes entre dos estaciones.
Query Parameters:
origin(requerido): ID de estación origendestination(requerido): ID de estación destinotime(opcional): Hora de salida (HH:MM:SS)date(opcional): Fecha (YYYY-MM-DD)
Ejemplo:
curl "http://localhost:3000/explorer/planner?origin=MADRID-PUERTA-DE-ATOCHA&destination=BARCELONA-SANTS&time=08:00:00"
Respuesta:
{
"origin": "MADRID-PUERTA-DE-ATOCHA",
"destination": "BARCELONA-SANTS",
"requested_time": "08:00:00",
"requested_date": "today",
"direct_trips": [
{
"trip_id": "trip_001",
"route_id": "AVE-MAD-BCN",
"route_name": "Madrid - Barcelona",
"trip_headsign": "Barcelona Sants",
"origin_departure": "08:00:00",
"destination_arrival": "10:45:00",
"duration_minutes": 165,
"delay": {
"delay_seconds": 180,
"schedule_relationship": "SCHEDULED"
}
}
],
"trips_with_transfer": [
{
"trip1_id": "trip_002",
"route1_name": "Madrid - Zaragoza",
"trip2_id": "trip_003",
"route2_name": "Zaragoza - Barcelona",
"origin_departure": "08:30:00",
"transfer_arrival": "09:50:00",
"transfer_departure": "10:10:00",
"destination_arrival": "11:30:00",
"transfer_station": "ZARAGOZA-DELICIAS",
"transfer_station_name": "Zaragoza Delicias",
"total_duration_minutes": 180
}
],
"total_options": 3
}
GET /explorer/routes/between
Encontrar rutas que conectan dos estaciones.
Query Parameters:
origin(requerido): ID de estación origendestination(requerido): ID de estación destino
Ejemplo:
curl "http://localhost:3000/explorer/routes/between?origin=MADRID-PUERTA-DE-ATOCHA&destination=BARCELONA-SANTS"
Respuesta:
[
{
"route_id": "AVE-MAD-BCN",
"route_name": "Madrid - Barcelona",
"route_type": "HIGH_SPEED",
"route_color": "FF6600",
"daily_trips": 25
}
]
🔍 Explorer - Search
GET /explorer/search
Buscar estaciones por nombre.
Query Parameters:
query(requerido): Término de búsqueda (mínimo 2 caracteres)limit(opcional): Número de resultados (default: 10)
Ejemplo:
curl "http://localhost:3000/explorer/search?query=madrid&limit=5"
Respuesta:
[
{
"stop_id": "MADRID-PUERTA-DE-ATOCHA",
"stop_name": "Madrid Puerta de Atocha",
"stop_lat": 40.4067,
"stop_lon": -3.6906,
"location_type": 1,
"parent_station": null
},
{
"stop_id": "MADRID-CHAMARTIN",
"stop_name": "Madrid Chamartín",
"stop_lat": 40.4728,
"stop_lon": -3.6797,
"location_type": 1,
"parent_station": null
}
]
🗄️ Vistas Materializadas
traffic_by_hour
Tráfico agregado por hora (últimos 30 días).
Columnas:
- hour: Hora (timestamp)
- active_trains: Número de trenes activos
- total_positions: Total de posiciones registradas
- avg_speed: Velocidad promedio
- median_speed: Velocidad mediana
- max_speed: Velocidad máxima
traffic_by_route
Tráfico por ruta (últimos 30 días).
Columnas:
- route_id, route_name, route_type
- total_trains: Total de trenes diferentes
- active_days: Días con actividad
- avg_speed: Velocidad promedio
- total_positions: Posiciones registradas
daily_statistics
Estadísticas diarias del sistema (últimos 90 días).
Columnas:
- date: Fecha
- unique_trains: Trenes únicos
- total_positions: Posiciones totales
- avg_speed: Velocidad promedio
- stopped_count: Posiciones paradas
- moving_count: Posiciones en movimiento
route_performance
Rendimiento y puntualidad por ruta (últimos 30 días).
Columnas:
- route_id, route_name
- total_trips: Total de viajes
- delayed_trips: Viajes retrasados
- on_time_trips: Viajes puntuales
- avg_delay_seconds: Retraso promedio
- median_delay_seconds: Retraso mediano
- max_delay_seconds: Retraso máximo
- punctuality_percentage: % de puntualidad
Refresco: Cada 15 minutos automáticamente via analytics-refresher worker.
🧪 Probar Phase 3
1. Verificar Analytics Refresher
# Ver logs del worker
docker-compose logs -f analytics-refresher
# Deberías ver cada 15 minutos:
# "Starting analytics views refresh..."
# "Analytics views refreshed successfully"
2. Probar Analytics API
# Sistema general
curl http://localhost:3000/analytics/statistics/system | jq
# Heatmap
curl http://localhost:3000/analytics/traffic/heatmap | jq
# Performance de rutas
curl http://localhost:3000/analytics/performance/routes | jq
# Rutas más retrasadas
curl http://localhost:3000/analytics/delays/top-routes | jq
# Estaciones más transitadas
curl http://localhost:3000/analytics/stations/busiest | jq
3. Probar Explorer API
# Buscar estación
curl "http://localhost:3000/explorer/search?query=madrid" | jq
# Info de estación
curl http://localhost:3000/explorer/stations/MADRID-PUERTA-DE-ATOCHA | jq
# Info de ruta
curl http://localhost:3000/explorer/routes/AVE-MAD-BCN | jq
# Planificar viaje
curl "http://localhost:3000/explorer/planner?origin=MADRID-PUERTA-DE-ATOCHA&destination=BARCELONA-SANTS" | jq
4. Probar Exportación
# Exportar a CSV
curl "http://localhost:3000/analytics/export?table=routes&format=csv" > routes.csv
# Exportar a GeoJSON
curl "http://localhost:3000/analytics/export?table=stations&format=geojson" > stations.geojson
# Exportar posiciones filtradas
curl "http://localhost:3000/analytics/export?table=train_positions&start_date=2025-11-27T00:00:00Z&limit=1000" > positions.json
📊 Use Cases
Dashboard de Control
# Obtener datos para dashboard
curl http://localhost:3000/analytics/statistics/system
curl http://localhost:3000/analytics/traffic/by-route
curl http://localhost:3000/analytics/delays/top-routes
curl http://localhost:3000/analytics/stations/busiest?limit=5
Análisis de Rendimiento
# Análisis de una ruta específica
curl http://localhost:3000/analytics/performance/route/AVE-MAD-BCN?days=30
# Estadísticas diarias últimos 30 días
curl http://localhost:3000/analytics/statistics/daily?days=30
# Distancia recorrida por tren
curl "http://localhost:3000/analytics/trains/12345/distance?start_time=2025-11-01T00:00:00Z"
Planificación de Viajes
# Buscar estación
curl "http://localhost:3000/explorer/search?query=barcelona"
# Ver info completa de estación
curl http://localhost:3000/explorer/stations/BARCELONA-SANTS
# Planificar viaje
curl "http://localhost:3000/explorer/planner?origin=MADRID-PUERTA-DE-ATOCHA&destination=BARCELONA-SANTS&time=08:00:00"
# Ver rutas que conectan dos puntos
curl "http://localhost:3000/explorer/routes/between?origin=MADRID-PUERTA-DE-ATOCHA&destination=BARCELONA-SANTS"
🐛 Troubleshooting Phase 3
Las vistas materializadas están vacías
Causa: No hay suficientes datos históricos o no se han refrescado.
Solución:
# Refrescar manualmente
curl -X POST http://localhost:3000/analytics/refresh
# Verificar en PostgreSQL
make psql
> SELECT COUNT(*) FROM traffic_by_hour;
> SELECT COUNT(*) FROM traffic_by_route;
El planificador no encuentra viajes
Causa: Datos GTFS Static no cargados o formato de parámetros incorrecto.
Solución:
# Verificar que hay trips y stop_times
make psql
> SELECT COUNT(*) FROM trips;
> SELECT COUNT(*) FROM stop_times;
# Verificar formato de parámetros
curl "http://localhost:3000/explorer/planner?origin=STOP_ID_ORIGIN&destination=STOP_ID_DEST"
Export devuelve error
Causa: Tabla no permitida o parámetros inválidos.
Solución:
# Ver tablas permitidas
curl "http://localhost:3000/analytics/export" | jq '.allowed_tables'
# Usar tabla válida
curl "http://localhost:3000/analytics/export?table=routes&format=json"
📝 Próximos Pasos
Funcionalidades pendientes de Phase 3:
- Frontend: Heatmap de tráfico en mapa
- Frontend: Dashboard de estadísticas con gráficos
- Frontend: UI del planificador de viajes
- Frontend: Explorador de rutas interactivo
- WebSocket para updates de analytics en tiempo real
- Overlay de infraestructura ADIF (WMS)
- Predicciones ML (Fase 4)
📚 Documentación Relacionada
Estado: Fase 3 - Backend Completo, Frontend Pendiente Fecha: 27 noviembre 2025 Próxima Fase: Frontend Phase 3 + Fase 4 (ML y Predicciones)