Files
trenes/arquitectura-sistema-tracking-trenes.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

43 KiB
Raw Permalink Blame History

Arquitectura Sistema de Tracking de Trenes en Tiempo Real

Visión General

Sistema web para visualizar en tiempo real la posición de todos los trenes operados por Renfe en España, con capacidad de consultar histórico mediante un timeline slider.

Fuentes de Datos

📘 Nota: Para información detallada de todas las fuentes disponibles, ver FUENTES_DATOS.md

Nivel 1: Datos en Tiempo Real (Core)

GTFS-RT Vehicle Positions (IMPLEMENTADO)

  • URL: https://gtfsrt.renfe.com/vehicle_positions.pb
  • Formato: Protocol Buffer (GTFS Realtime)
  • Frecuencia: Cada 30 segundos
  • Información:
    • Posición GPS (latitud, longitud)
    • Estado del vehículo (parado, en movimiento)
    • Velocidad y dirección (bearing)
    • Identificadores (train_id, trip_id, route_id)
  • Servicios: Principalmente Cercanías
  • Uso: Visualización en tiempo real en mapa

GTFS-RT Trip Updates

  • URL: https://gtfsrt.renfe.com/trip_updates_cercanias.pb
  • Formato: Protocol Buffer (GTFS Realtime)
  • Frecuencia: Cada 30 segundos
  • Información:
    • Cancelaciones de servicios
    • Retrasos y adelantos
    • Cambios de horario
    • Predicciones de llegada/salida
  • Servicios: Cercanías + AVE + LD + MD
  • Uso: Alertas, predicciones, estadísticas de puntualidad

GTFS-RT Service Alerts

  • URL: https://gtfsrt.renfe.com/alerts.pb
  • Formato: Protocol Buffer (GTFS Realtime)
  • Frecuencia: Cada 30 segundos
  • Información:
    • Incidencias en la red
    • Servicios de autobús sustitutorio
    • Problemas de accesibilidad
    • Cierres de vías
  • Uso: Notificaciones push, panel de alertas

Nivel 2: Datos Estáticos (Enriquecimiento)

GTFS Static de Renfe

  • URL: https://data.renfe.com/dataset?res_format=GTFS/
  • Formato: ZIP con archivos CSV
  • Frecuencia de actualización: Semanal/Mensual
  • Vigencia actual: 18 marzo 2025 - 15 diciembre 2025
  • Cobertura: 366 rutas, 769 paradas
  • Archivos principales:
    • routes.txt: Rutas y líneas
    • stops.txt: Estaciones y paradas
    • trips.txt: Viajes programados
    • stop_times.txt: Horarios
    • calendar.txt: Días de operación
    • shapes.txt: Geometría de rutas (si disponible)
  • Uso:
    • Enriquecer nombres de rutas
    • Mostrar horarios programados vs reales
    • Calcular retrasos
    • Visualizar rutas en mapa

Estaciones de Renfe

  • URL: https://data.renfe.com/dataset (buscar "estaciones")
  • Formato: JSON, CSV, XML
  • Información:
    • Coordenadas GPS precisas
    • Servicios disponibles
    • Accesibilidad (Atendo)
    • Código de estación
    • Instalaciones
  • Uso:
    • Enriquecer mapa con marcadores de estaciones
    • Filtrar por estaciones accesibles
    • Mostrar servicios disponibles

Nivel 3: Infraestructura Ferroviaria

ADIF - IDEADIF (Web Map Service)

  • URL: https://ideadif.adif.es/
  • Protocolo: WMS (OGC 1.1.1, 1.3.0)
  • Normativa: INSPIRE (Transport Networks)
  • Información:
    • Trazado de vías
    • Topología de la red ferroviaria
    • Estaciones e instalaciones
    • Túneles y puentes
  • Uso:
    • Overlay en mapa mostrando vías
    • Visualización de infraestructura
    • Contexto geográfico de posiciones

ADIF - PISERVI

  • URL: https://www.adif.es/
  • Información:
    • Características técnicas de instalaciones
    • Terminales de mercancías
    • Instalaciones de mantenimiento
    • Cambiadores de ancho de vía
  • Uso: Información complementaria de estaciones

Nivel 4: Datos Regionales y Complementarios

Consorcios Regionales

  • CRTM (Madrid): Cercanías Madrid, Metro, Tren Ligero
  • CTB (Bilbao): Cercanías Bilbao, EuskoTren
  • TMB (Barcelona): Rodalies Barcelona, FGC
  • Formato: GTFS, GTFS-RT
  • Uso: Integración con redes locales

Datos Meteorológicos (Futuro)

  • AEMET OpenData: Condiciones climáticas
  • Uso: Correlación retrasos con meteorología

Datos Estadísticos

  • Observatorio del Ferrocarril: Informes anuales
  • Ministerio de Transportes: Estadísticas mensuales
  • Uso: Analytics, KPIs, tendencias

Arquitectura Propuesta

Stack Tecnológico

Frontend

- Framework: React + TypeScript
- Mapa: Leaflet.js con tiles de OpenStreetMap
- UI Components: Tailwind CSS + shadcn/ui
- Gestión de estado: Zustand o Redux Toolkit
- Timeline: React Range Slider o Timeline personalizado
- Cliente WebSocket: native WebSocket API

Backend

- Runtime: Node.js
- Framework: Express.js o Fastify
- Base de datos: PostgreSQL + PostGIS (datos geoespaciales)
- Cache: Redis (últimas posiciones)
- WebSockets: Socket.io
- Parser GTFS-RT: gtfs-realtime-bindings (npm)

Infraestructura

- Hosting: Docker containers
- Reverse Proxy: Nginx
- Proceso de fondo: Node.js worker para polling

Diagrama de Componentes

┌─────────────────────────────────────────────────────────────┐
│                         FRONTEND                             │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐      │
│  │   Mapa OSM   │  │ Panel Info   │  │  Timeline    │      │
│  │  (Leaflet)   │  │    Tren      │  │   Slider     │      │
│  └──────────────┘  └──────────────┘  └──────────────┘      │
│         │                  │                  │              │
│         └──────────────────┴──────────────────┘              │
│                           │                                  │
│                    WebSocket Client                          │
└───────────────────────────┼──────────────────────────────────┘
                            │
                    ┌───────▼──────────┐
                    │   NGINX (proxy)  │
                    └───────┬──────────┘
                            │
┌───────────────────────────┼──────────────────────────────────┐
│                      BACKEND                                 │
│                                                              │
│  ┌─────────────────────────────────────────────────────┐   │
│  │            WebSocket Server (Socket.io)              │   │
│  │     - Broadcast posiciones en tiempo real            │   │
│  │     - Gestión de salas por región/tipo tren          │   │
│  └───────────────────────┬─────────────────────────────┘   │
│                          │                                  │
│  ┌───────────────────────┼─────────────────────────────┐   │
│  │          API REST (Express/Fastify)                  │   │
│  │  - GET /trains/current    (últimas posiciones)       │   │
│  │  - GET /trains/history    (histórico por rango)      │   │
│  │  - GET /trains/:id        (info específica)          │   │
│  │  - GET /routes            (rutas disponibles)        │   │
│  └───────────────────────┬─────────────────────────────┘   │
│                          │                                  │
│         ┌────────────────┼────────────────┐                │
│         │                │                │                │
│    ┌────▼─────┐    ┌────▼─────┐    ┌────▼─────┐          │
│    │  Redis   │    │PostgreSQL│    │  Worker  │          │
│    │  Cache   │    │ +PostGIS │    │  Process │          │
│    │          │    │          │    │          │          │
│    │ Últimas  │    │ Histórico│    │  Polling │          │
│    │Posiciones│    │ Posiciones│   │  GTFS-RT │          │
│    └──────────┘    │  Rutas   │    │          │          │
│                    │  Paradas │    └────┬─────┘          │
│                    └──────────┘         │                  │
└─────────────────────────────────────────┼──────────────────┘
                                          │
                            ┌─────────────▼─────────────┐
                            │  Renfe GTFS-RT Feed       │
                            │  vehicle_positions.pb     │
                            │  (polling cada 30 seg)    │
                            └───────────────────────────┘

Modelo de Datos

Base de Datos Principal (PostgreSQL + PostGIS)

Tabla: train_positions

CREATE TABLE train_positions (
    id BIGSERIAL PRIMARY KEY,
    train_id VARCHAR(50) NOT NULL,
    trip_id VARCHAR(100),
    route_id VARCHAR(50),
    position GEOGRAPHY(POINT, 4326) NOT NULL, -- PostGIS
    latitude DECIMAL(10, 7) NOT NULL,
    longitude DECIMAL(10, 7) NOT NULL,
    bearing DECIMAL(5, 2),                    -- Dirección 0-360°
    speed DECIMAL(5, 2),                      -- km/h
    status VARCHAR(20),                        -- STOPPED, MOVING, etc.
    timestamp TIMESTAMP NOT NULL,
    recorded_at TIMESTAMP DEFAULT NOW(),

    INDEX idx_train_timestamp (train_id, timestamp),
    INDEX idx_position (position) USING GIST,
    INDEX idx_recorded_at (recorded_at)
);

-- Particionamiento por fecha para optimizar consultas históricas
CREATE TABLE train_positions_2025_01 PARTITION OF train_positions
    FOR VALUES FROM ('2025-01-01') TO ('2025-02-01');

Tabla: trains

CREATE TABLE trains (
    train_id VARCHAR(50) PRIMARY KEY,
    route_id VARCHAR(50),
    train_type VARCHAR(50),              -- AVE, Cercanías, Regional, etc.
    service_name VARCHAR(100),
    first_seen TIMESTAMP,
    last_seen TIMESTAMP,
    is_active BOOLEAN DEFAULT true
);

Tabla: routes

CREATE TABLE routes (
    route_id VARCHAR(50) PRIMARY KEY,
    route_name VARCHAR(200),
    route_type VARCHAR(50),
    origin_station VARCHAR(100),
    destination_station VARCHAR(100),
    color VARCHAR(7)                     -- Para visualización en mapa
);

Tabla: stations

CREATE TABLE stations (
    station_id VARCHAR(50) PRIMARY KEY,
    station_name VARCHAR(200),
    position GEOGRAPHY(POINT, 4326),
    latitude DECIMAL(10, 7),
    longitude DECIMAL(10, 7),
    station_type VARCHAR(50)
);

Cache Redis

Estructura de claves:

trains:current:{train_id}     → JSON con última posición
trains:active                 → SET con IDs de trenes activos
trains:by_type:{type}         → SET con IDs por tipo
stats:last_update             → Timestamp última actualización

Flujo de Datos

1. Recolección de Datos (Worker Process)

// Pseudocódigo del worker
async function pollGTFSRT() {
    const response = await fetch('https://gtfsrt.renfe.com/vehicle_positions.pb');
    const buffer = await response.arrayBuffer();

    const feed = GtfsRealtimeBindings.transit_realtime.FeedMessage.decode(
        new Uint8Array(buffer)
    );

    for (const entity of feed.entity) {
        if (entity.vehicle) {
            const position = {
                train_id: entity.vehicle.vehicle.id,
                trip_id: entity.vehicle.trip?.trip_id,
                route_id: entity.vehicle.trip?.route_id,
                latitude: entity.vehicle.position.latitude,
                longitude: entity.vehicle.position.longitude,
                bearing: entity.vehicle.position.bearing,
                speed: entity.vehicle.position.speed,
                status: entity.vehicle.current_status,
                timestamp: new Date(entity.vehicle.timestamp * 1000)
            };

            // Guardar en Redis (tiempo real)
            await redis.setex(
                `trains:current:${position.train_id}`,
                300,
                JSON.stringify(position)
            );

            // Guardar en PostgreSQL (histórico)
            await db.insertTrainPosition(position);

            // Broadcast vía WebSocket
            io.emit('train:update', position);
        }
    }
}

// Ejecutar cada 30 segundos
setInterval(pollGTFSRT, 30000);

2. API REST Endpoints

// GET /api/trains/current
// Retorna todas las posiciones actuales desde Redis
router.get('/trains/current', async (req, res) => {
    const trainIds = await redis.smembers('trains:active');
    const positions = await Promise.all(
        trainIds.map(id => redis.get(`trains:current:${id}`))
    );
    res.json(positions.map(JSON.parse));
});

// GET /api/trains/history?from=timestamp&to=timestamp&train_id=xxx
// Consulta histórico de PostgreSQL
router.get('/trains/history', async (req, res) => {
    const { from, to, train_id } = req.query;
    const positions = await db.query(`
        SELECT * FROM train_positions
        WHERE timestamp BETWEEN $1 AND $2
        ${train_id ? 'AND train_id = $3' : ''}
        ORDER BY timestamp ASC
    `, [from, to, train_id].filter(Boolean));
    res.json(positions);
});

// GET /api/trains/:id
// Información detallada de un tren
router.get('/trains/:id', async (req, res) => {
    const train = await db.getTrainInfo(req.params.id);
    const currentPosition = await redis.get(`trains:current:${req.params.id}`);
    res.json({ ...train, current: JSON.parse(currentPosition) });
});

3. Frontend - Componentes Principales

Mapa (MapView.tsx)

import { MapContainer, TileLayer, Marker, Popup } from 'react-leaflet';

function MapView({ trains, selectedTrain, onTrainClick }) {
    return (
        <MapContainer center={[40.4168, -3.7038]} zoom={6}>
            <TileLayer
                url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
                attribution='&copy; OpenStreetMap contributors'
            />
            {trains.map(train => (
                <Marker
                    key={train.train_id}
                    position={[train.latitude, train.longitude]}
                    icon={getTrainIcon(train.train_type)}
                    eventHandlers={{
                        click: () => onTrainClick(train)
                    }}
                >
                    <Popup>
                        <TrainInfo train={train} />
                    </Popup>
                </Marker>
            ))}
        </MapContainer>
    );
}

Timeline Slider (TimelineSlider.tsx)

function TimelineSlider({ onTimeChange, minTime, maxTime }) {
    const [currentTime, setCurrentTime] = useState(Date.now());
    const [isPlaying, setIsPlaying] = useState(true);

    useEffect(() => {
        if (isPlaying) {
            const interval = setInterval(() => {
                setCurrentTime(Date.now());
                onTimeChange(Date.now());
            }, 1000);
            return () => clearInterval(interval);
        }
    }, [isPlaying]);

    return (
        <div className="timeline-container">
            <button onClick={() => setIsPlaying(!isPlaying)}>
                {isPlaying ? 'Pause' : 'Play'}
            </button>
            <input
                type="range"
                min={minTime}
                max={maxTime}
                value={currentTime}
                onChange={(e) => {
                    setIsPlaying(false);
                    setCurrentTime(Number(e.target.value));
                    onTimeChange(Number(e.target.value));
                }}
            />
            <span>{new Date(currentTime).toLocaleString()}</span>
        </div>
    );
}

WebSocket Hook (useTrainUpdates.ts)

function useTrainUpdates() {
    const [trains, setTrains] = useState<Train[]>([]);

    useEffect(() => {
        const socket = io('ws://localhost:3000');

        socket.on('train:update', (position) => {
            setTrains(prev => {
                const idx = prev.findIndex(t => t.train_id === position.train_id);
                if (idx >= 0) {
                    const updated = [...prev];
                    updated[idx] = position;
                    return updated;
                } else {
                    return [...prev, position];
                }
            });
        });

        return () => socket.disconnect();
    }, []);

    return trains;
}

Características del Sistema

Tiempo Real

  • Actualización automática cada 30 segundos desde el feed GTFS-RT
  • WebSocket para push de actualizaciones al frontend sin polling
  • Animación suave de movimiento de trenes entre actualizaciones

Navegación Temporal (Timeline)

  • Slider para seleccionar momento específico en el tiempo
  • Botones play/pause para reproducir histórico
  • Controles de velocidad de reproducción (1x, 2x, 5x)
  • Selección de rango de fechas

Información de Trenes

  • Popup al hacer clic: ID, ruta, velocidad, dirección, estado
  • Panel lateral con detalles extendidos
  • Histórico de posiciones del tren seleccionado (trail en el mapa)
  • Filtros por tipo de tren (AVE, Cercanías, Regional, etc.)

Optimizaciones

Backend

  • Particionamiento de tablas por fecha
  • Índices espaciales (PostGIS GIST)
  • Cache Redis para consultas frecuentes
  • Compresión de respuestas HTTP (gzip)
  • Rate limiting en API

Frontend

  • Clustering de markers cuando hay muchos trenes cercanos
  • Lazy loading de datos históricos
  • Debouncing en timeline slider
  • Virtual scrolling en listas de trenes
  • Service Worker para caché offline

Roadmap de Implementación

Fase 1: MVP (4-6 semanas)

  1. Semana 1-2: Backend

    • Setup PostgreSQL + PostGIS + Redis
    • Worker para polling GTFS-RT
    • API REST básica
  2. Semana 3-4: Frontend

    • Mapa con OpenStreetMap
    • Visualización de trenes en tiempo real
    • WebSocket client
  3. Semana 5-6: Timeline

    • Slider temporal
    • Consultas históricas
    • Testing y optimización

Fase 2: Mejoras (4-6 semanas)

  • Integración de feeds adicionales (data.renfe.com)
  • Filtros avanzados y búsqueda
  • Estadísticas y analytics
  • Exportación de datos

Fase 3: Avanzado

  • Predicción de posiciones usando ML
  • Alertas y notificaciones
  • API pública
  • Mobile app

Costes Estimados

Desarrollo

  • Solo: 2-3 meses (una persona)
  • Equipo pequeño: 1-1.5 meses (2-3 personas)

Infraestructura Mensual (estimación)

  • VPS (4GB RAM, 2 CPU): ~15-25€/mes
  • PostgreSQL gestionado (opcional): ~25-50€/mes
  • Redis gestionado (opcional): ~10-20€/mes
  • Dominio: ~10€/año
  • Total: 25-95€/mes (dependiendo de servicios gestionados)

Almacenamiento

  • ~1KB por posición
  • 1000 trenes × 2 updates/min × 60 min × 24 h = ~2.8M registros/día
  • ~2.8GB/día → ~84GB/mes
  • Retención de 3 meses: ~250GB

Consideraciones Técnicas

Desafíos

  1. Parsing de Protocol Buffers: Usar librerías oficiales gtfs-realtime-bindings
  2. Escalabilidad: Particionamiento de datos históricos, archivado a cold storage
  3. Precisión GPS: Los datos pueden tener errores, implementar filtrado de outliers
  4. Frecuencia de actualización: Balancear entre tiempo real y carga del servidor

Seguridad

  • Rate limiting en API
  • CORS configurado correctamente
  • Sanitización de inputs
  • HTTPS en producción
  • Autenticación para endpoints administrativos

Alternativas y Variantes

Stack Alternativo Ligero

  • Backend: Python + FastAPI + SQLite/DuckDB
  • Frontend: Vanilla JS + Leaflet (sin framework)
  • Deploy: Single VPS con systemd

Stack Más Robusto

  • Backend: Go + Gin + TimescaleDB
  • Message Queue: RabbitMQ o Kafka para procesamiento
  • Frontend: Next.js + React Query
  • Deploy: Kubernetes cluster

Serverless

  • Backend: AWS Lambda + API Gateway
  • DB: DynamoDB + S3 para histórico
  • Frontend: Vercel/Netlify
  • Real-time: AWS IoT Core o Pusher

Referencias


Integración de Fuentes de Datos

Estrategia de Sincronización

Workers de Recolección

// Arquitectura de workers propuesta

const workers = {
  // Worker 1: GTFS-RT en tiempo real (alta frecuencia)
  realtimeWorker: {
    interval: 30000, // 30 segundos
    sources: [
      {
        name: 'vehicle_positions',
        url: 'https://gtfsrt.renfe.com/vehicle_positions.pb',
        parser: parseVehiclePositions,
        destination: ['postgresql.train_positions', 'redis.current_positions']
      },
      {
        name: 'trip_updates',
        url: 'https://gtfsrt.renfe.com/trip_updates_cercanias.pb',
        parser: parseTripUpdates,
        destination: ['postgresql.trip_updates', 'redis.delays']
      },
      {
        name: 'service_alerts',
        url: 'https://gtfsrt.renfe.com/alerts.pb',
        parser: parseServiceAlerts,
        destination: ['postgresql.alerts', 'redis.active_alerts']
      }
    ]
  },

  // Worker 2: GTFS Static (baja frecuencia)
  staticDataWorker: {
    interval: 86400000, // 24 horas
    schedule: '0 3 * * *', // 3:00 AM diaria
    sources: [
      {
        name: 'gtfs_static',
        url: 'https://data.renfe.com/api/gtfs/latest.zip',
        parser: parseGTFSZip,
        files: ['routes', 'stops', 'trips', 'stop_times', 'calendar', 'shapes'],
        destination: 'postgresql',
        onUpdate: (changes) => {
          // Detectar cambios en rutas o estaciones
          notifyAdmins(changes);
          invalidateCache(['routes', 'stations']);
        }
      }
    ]
  },

  // Worker 3: Estaciones y metadatos (muy baja frecuencia)
  metadataWorker: {
    interval: 604800000, // 7 días
    schedule: '0 2 * * 0', // Domingo 2:00 AM
    sources: [
      {
        name: 'renfe_stations',
        url: 'https://data.renfe.com/api/stations.json',
        parser: parseStations,
        destination: 'postgresql.stations',
        merge: true // Merge con datos existentes
      },
      {
        name: 'adif_infrastructure',
        type: 'wms',
        url: 'https://ideadif.adif.es/wms',
        parser: extractStationMetadata,
        destination: 'postgresql.stations',
        merge: true
      }
    ]
  }
};

Pipeline de Procesamiento de Datos

┌─────────────────────────────────────────────────────────────┐
│                  INGESTA DE DATOS                           │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  ┌──────────────┐   ┌──────────────┐   ┌──────────────┐   │
│  │  GTFS-RT     │   │  GTFS Static │   │  Metadatos   │   │
│  │  (30 seg)    │   │  (24 horas)  │   │  (7 días)    │   │
│  └──────┬───────┘   └──────┬───────┘   └──────┬───────┘   │
│         │                  │                  │            │
└─────────┼──────────────────┼──────────────────┼────────────┘
          │                  │                  │
          ▼                  ▼                  ▼
┌─────────────────────────────────────────────────────────────┐
│                  VALIDACIÓN Y TRANSFORMACIÓN                │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  • Decodificar Protocol Buffers                            │
│  • Validar campos requeridos                               │
│  • Normalizar formatos (fechas, coordenadas)               │
│  • Detectar outliers (posiciones GPS inválidas)            │
│  • Enriquecer con datos estáticos (nombres, rutas)         │
│                                                             │
└─────────────────────┬───────────────────────────────────────┘
                      │
                      ▼
┌─────────────────────────────────────────────────────────────┐
│                  ALMACENAMIENTO                             │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  ┌──────────────────┐              ┌──────────────────┐    │
│  │  PostgreSQL      │              │  Redis           │    │
│  │  + PostGIS       │              │                  │    │
│  ├──────────────────┤              ├──────────────────┤    │
│  │ • train_positions│◄─────────────┤ • current_pos    │    │
│  │ • trip_updates   │              │ • active_alerts  │    │
│  │ • alerts         │              │ • delay_cache    │    │
│  │ • routes         │              │ • stats_cache    │    │
│  │ • stations       │              └──────────────────┘    │
│  │ • trips          │                                      │
│  └──────────────────┘                                      │
│                                                             │
└─────────────────────┬───────────────────────────────────────┘
                      │
                      ▼
┌─────────────────────────────────────────────────────────────┐
│                  DISTRIBUCIÓN                               │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  ┌──────────────┐   ┌──────────────┐   ┌──────────────┐   │
│  │  WebSocket   │   │  API REST    │   │  Webhooks    │   │
│  │  (push)      │   │  (pull)      │   │  (eventos)   │   │
│  └──────────────┘   └──────────────┘   └──────────────┘   │
│                                                             │
└─────────────────────────────────────────────────────────────┘

Sincronización GTFS Static

// Módulo de sincronización GTFS Static

class GTFSStaticSyncer {
  async syncGTFSStatic() {
    const zipUrl = await this.getLatestGTFSUrl();
    const localPath = await this.downloadGTFS(zipUrl);
    const extracted = await this.extractZip(localPath);

    // Calcular checksum para detectar cambios
    const newChecksum = this.calculateChecksum(extracted);
    const oldChecksum = await this.getStoredChecksum();

    if (newChecksum === oldChecksum) {
      console.log('GTFS sin cambios');
      return;
    }

    // Procesar archivos GTFS
    const data = {
      routes: await this.parseGTFSFile(extracted, 'routes.txt'),
      stops: await this.parseGTFSFile(extracted, 'stops.txt'),
      trips: await this.parseGTFSFile(extracted, 'trips.txt'),
      stopTimes: await this.parseGTFSFile(extracted, 'stop_times.txt'),
      calendar: await this.parseGTFSFile(extracted, 'calendar.txt'),
      shapes: await this.parseGTFSFile(extracted, 'shapes.txt')
    };

    // Detectar cambios
    const changes = await this.detectChanges(data);

    // Actualizar base de datos en transacción
    await this.db.transaction(async (trx) => {
      // Insertar/actualizar rutas
      await this.upsertRoutes(trx, data.routes);

      // Insertar/actualizar estaciones
      await this.upsertStops(trx, data.stops);

      // Insertar/actualizar viajes
      await this.upsertTrips(trx, data.trips);

      // Insertar/actualizar horarios
      await this.upsertStopTimes(trx, data.stopTimes);

      // Insertar/actualizar formas de rutas
      if (data.shapes) {
        await this.upsertShapes(trx, data.shapes);
      }

      // Guardar checksum
      await this.storeChecksum(trx, newChecksum);
    });

    // Invalidar cachés
    await this.invalidateCaches(['routes', 'stations', 'trips']);

    // Notificar cambios
    await this.notifyChanges(changes);

    console.log('GTFS actualizado:', changes);
  }

  async detectChanges(newData) {
    const changes = {
      newRoutes: [],
      updatedRoutes: [],
      deletedRoutes: [],
      newStations: [],
      updatedStations: [],
      deletedStations: []
    };

    // Comparar con datos existentes
    const oldRoutes = await this.db.select('*').from('routes');

    // Detectar rutas nuevas
    for (const route of newData.routes) {
      const existing = oldRoutes.find(r => r.route_id === route.route_id);
      if (!existing) {
        changes.newRoutes.push(route);
      } else if (JSON.stringify(existing) !== JSON.stringify(route)) {
        changes.updatedRoutes.push(route);
      }
    }

    // Detectar rutas eliminadas
    for (const oldRoute of oldRoutes) {
      const existing = newData.routes.find(r => r.route_id === oldRoute.route_id);
      if (!existing) {
        changes.deletedRoutes.push(oldRoute);
      }
    }

    // Similar para estaciones...

    return changes;
  }
}

Enriquecimiento de Datos en Tiempo Real

// Enriquecer posiciones con datos estáticos

class PositionEnricher {
  async enrichPosition(position) {
    // Datos básicos del feed GTFS-RT
    const enriched = {
      ...position,
      // Añadir información de la ruta
      route: await this.getRoute(position.route_id),
      // Añadir información del viaje
      trip: await this.getTrip(position.trip_id),
      // Calcular estación más cercana
      nearestStation: await this.findNearestStation(
        position.latitude,
        position.longitude
      ),
      // Calcular retraso si hay trip_update
      delay: await this.calculateDelay(position.trip_id),
      // Añadir alertas activas
      alerts: await this.getActiveAlerts(position.route_id),
      // Información de ocupación (si disponible)
      occupancy: this.mapOccupancyStatus(position.occupancy_status)
    };

    return enriched;
  }

  async findNearestStation(lat, lon) {
    // Usar PostGIS para encontrar estación más cercana
    const result = await this.db.raw(`
      SELECT
        station_id,
        station_name,
        latitude,
        longitude,
        ST_Distance(
          position,
          ST_SetSRID(ST_MakePoint(?, ?), 4326)::geography
        ) as distance
      FROM stations
      ORDER BY distance
      LIMIT 1
    `, [lon, lat]);

    return result.rows[0];
  }

  async calculateDelay(tripId) {
    // Buscar trip_update más reciente
    const update = await this.redis.get(`trip_update:${tripId}`);
    if (!update) return null;

    const parsed = JSON.parse(update);
    // Calcular diferencia entre horario programado y predicción
    const scheduledArrival = await this.getScheduledArrival(tripId);
    const predictedArrival = parsed.stop_time_update?.arrival?.time;

    if (scheduledArrival && predictedArrival) {
      return (predictedArrival - scheduledArrival) / 60; // Minutos
    }

    return null;
  }
}

Features Avanzadas por Fuente de Datos

Features con GTFS-RT Vehicle Positions

1. Mapa en Tiempo Real

  • Descripción: Visualización de todos los trenes en movimiento
  • Implementación:
    • Marcadores animados en Leaflet
    • Colores por tipo de servicio (AVE, Cercanías, etc.)
    • Rotación del icono según bearing
    • Clustering para alta densidad
  • Datos utilizados: latitude, longitude, bearing, train_type

2. Trail de Movimiento

  • Descripción: Estela que muestra trayectoria reciente del tren
  • Implementación:
    • Polyline con últimas 10 posiciones
    • Desvanecimiento gradual (opacity)
    • Código de colores por velocidad
  • Datos utilizados: Histórico de train_positions (últimos 5 min)

3. Heatmap de Tráfico

  • Descripción: Mapa de calor mostrando zonas con más tráfico ferroviario
  • Implementación:
    • Leaflet.heat plugin
    • Agregación por celdas geográficas
    • Filtros por hora del día / día de la semana
  • Datos utilizados: Agregación de train_positions

4. Timeline Playback

  • Descripción: Reproducir histórico de posiciones
  • Implementación:
    • Slider temporal
    • Controles play/pause/velocidad
    • Sincronización de múltiples trenes
  • Datos utilizados: train_positions con timestamp

5. Seguimiento de Tren Específico

  • Descripción: Centrar cámara en un tren y seguirlo
  • Implementación:
    • Auto-pan del mapa
    • Panel lateral con información detallada
    • Notificaciones de cambios de estado
  • Datos utilizados: train_positions + enriched data

Features con GTFS-RT Trip Updates

6. Monitor de Puntualidad

  • Descripción: Dashboard de retrasos y cancelaciones
  • Implementación:
    • Gráficos de puntualidad por línea
    • Ranking de rutas más puntuales
    • Tendencias temporales
  • Datos utilizados: trip_updates, delay calculations

7. Predicciones de Llegada

  • Descripción: Mostrar hora estimada de llegada a próximas estaciones
  • Implementación:
    • Lista de próximas paradas
    • Comparación horario programado vs predicho
    • Indicador visual de retraso/adelanto
  • Datos utilizados: stop_time_updates

8. Alertas de Cancelación

  • Descripción: Notificaciones push de trenes cancelados
  • Implementación:
    • WebSocket event al detectar cancelación
    • Notificación en navegador
    • Email/SMS para suscripciones
  • Datos utilizados: trip_updates con schedule_relationship=CANCELED

9. Análisis de Retrasos

  • Descripción: Identificar patrones de retrasos
  • Implementación:
    • Análisis por hora del día
    • Análisis por estación
    • Correlación con meteorología
    • Predicción ML de retrasos
  • Datos utilizados: Histórico de delays, weather data

Features con GTFS-RT Service Alerts

10. Panel de Incidencias

  • Descripción: Listado de alertas activas
  • Implementación:
    • Categorización por severidad
    • Filtros por línea/estación
    • Mapa con marcadores de incidencias
  • Datos utilizados: alerts table

11. Notificaciones Personalizadas

  • Descripción: Alertas para rutas favoritas
  • Implementación:
    • Sistema de suscripciones
    • Push notifications
    • Filtros por tipo de alerta
  • Datos utilizados: alerts + user preferences

12. Histórico de Incidencias

  • Descripción: Análisis de incidencias pasadas
  • Implementación:
    • Timeline de alertas
    • Estadísticas por tipo
    • Zonas problemáticas
  • Datos utilizados: alerts historical data

Features con GTFS Static

13. Explorador de Rutas

  • Descripción: Visualización de rutas completas
  • Implementación:
    • Trazar ruta en mapa usando shapes.txt
    • Mostrar todas las paradas
    • Horarios completos del servicio
  • Datos utilizados: routes, trips, shapes, stop_times

14. Planificador de Viajes

  • Descripción: Calcular mejor ruta entre dos estaciones
  • Implementación:
    • Algoritmo A* o Dijkstra
    • Considerar transbordos
    • Mostrar horarios disponibles
  • Datos utilizados: routes, trips, stop_times, calendar

15. Información de Estaciones

  • Descripción: Detalles completos de estaciones
  • Implementación:
    • Servicios disponibles
    • Trenes que paran
    • Próximas salidas/llegadas
    • Accesibilidad
  • Datos utilizados: stops, stop_times, station metadata

16. Comparador Horario vs Real

  • Descripción: Overlay de horario programado sobre posiciones reales
  • Implementación:
    • Mostrar ruta teórica vs real
    • Calcular desviaciones
    • Métricas de adherencia al horario
  • Datos utilizados: stop_times + train_positions

Features con Infraestructura ADIF

17. Visualización de Vías

  • Descripción: Overlay de red ferroviaria
  • Implementación:
    • WMS layer de IDEADIF
    • Estilos por tipo de vía (AVE, convencional)
    • Toggle on/off
  • Datos utilizados: ADIF WMS service

18. Información de Infraestructura

  • Descripción: Detalles técnicos de vías y estaciones
  • Implementación:
    • Click en vía para ver características
    • Tipo de electrificación
    • Ancho de vía
    • Velocidad máxima
  • Datos utilizados: ADIF PISERVI data

Features con Datos Históricos

19. Análisis de Tráfico

  • Descripción: Patrones de movimiento a lo largo del tiempo
  • Implementación:
    • Gráficos de volumen por hora/día
    • Identificar horas punta
    • Rutas más transitadas
  • Datos utilizados: Agregaciones de train_positions

20. Estadísticas de Rendimiento

  • Descripción: KPIs del sistema ferroviario
  • Implementación:
    • Puntualidad promedio
    • Distancia recorrida
    • Velocidad promedio por ruta
    • Número de servicios diarios
  • Datos utilizados: calculate_train_statistics function

21. Exportación de Datos

  • Descripción: Descarga de datasets para análisis externo
  • Implementación:
    • Export a CSV/JSON/GeoJSON
    • API para acceso programático
    • Filtros temporales y geográficos
  • Datos utilizados: Cualquier tabla

Features Avanzadas Multi-Fuente

22. Dashboard de Control

  • Descripción: Vista general del sistema
  • Implementación:
    • Número de trenes activos
    • Alertas activas
    • Puntualidad global
    • Gráficos en tiempo real
  • Datos utilizados: Todas las fuentes

23. Sistema de Recomendaciones

  • Descripción: Sugerir mejor momento para viajar
  • Implementación:
    • Análisis de patrones históricos de retrasos
    • Ocupación típica por horario
    • Condiciones meteorológicas
  • Datos utilizados: Historical delays + weather + occupancy

24. Detección de Anomalías

  • Descripción: Identificar comportamientos inusuales
  • Implementación:
    • ML para detectar patrones anormales
    • Alertas automáticas a operadores
    • Análisis de velocidad inusual
    • Detección de paradas no programadas
  • Datos utilizados: train_positions + trip_updates + ML model

25. API Pública

  • Descripción: Acceso programático a datos
  • Implementación:
    • REST API documentada
    • GraphQL endpoint
    • Webhooks para eventos
    • Rate limiting y autenticación
  • Datos utilizados: Todas las fuentes

26. Integración con Asistentes

  • Descripción: Comandos de voz y chatbots
  • Implementación:
    • "¿Cuándo llega el próximo tren a Madrid?"
    • "¿Hay retrasos en la línea C-1?"
    • Integración con Alexa/Google Assistant
  • Datos utilizados: NLP + trip_updates + train_positions

27. Gamificación

  • Descripción: Elementos lúdicos para engagement
  • Implementación:
    • Badges por trenes observados
    • Ranking de usuarios
    • Challenges ("visita 10 estaciones diferentes")
    • Compartir en redes sociales
  • Datos utilizados: User data + train_positions

Roadmap de Features

Fase 1: MVP (4-6 semanas)

  • Arquitectura Docker
  • Worker GTFS-RT Vehicle Positions
  • Mapa en tiempo real básico
  • Panel de información de tren
  • Timeline básico
  • API REST core

Fase 2: Enriquecimiento (4-6 semanas)

  • Integración GTFS Static
  • Sincronización semanal automática
  • Trip Updates y Alerts
  • Monitor de puntualidad
  • Predicciones de llegada
  • Panel de incidencias

Fase 3: Features Avanzadas (8-12 semanas)

  • Heatmap de tráfico
  • Análisis de retrasos
  • Explorador de rutas
  • Planificador de viajes
  • Overlay infraestructura ADIF
  • Dashboard de control

Fase 4: Analytics y ML (8-12 semanas)

  • Análisis histórico de tráfico
  • Estadísticas de rendimiento
  • Predicción ML de retrasos
  • Detección de anomalías
  • Sistema de recomendaciones
  • Correlación con meteorología

Fase 5: Ecosistema (Continuo)

  • API pública documentada
  • Webhooks
  • Integraciones con asistentes
  • Mobile app (iOS/Android)
  • Notificaciones push
  • Sistema de suscripciones

Próximos Pasos

  1. Arquitectura Docker Compose completa
  2. Base de datos con PostGIS y particiones
  3. Sistema de migraciones Flyway
  4. Identificación de fuentes de datos
  5. Implementar worker GTFS-RT Vehicle Positions
  6. Desarrollar API REST básica
  7. Crear frontend con mapa Leaflet
  8. Implementar WebSocket para tiempo real
  9. Sincronización GTFS Static
  10. Trip Updates y Service Alerts