Lanchas
Lanchas horarios · Delta del Tigre
EN VIVO
PRÓXIMA EN VIVO
↙ Llega a Tigre
17:30
Interisleña S.A.C.I. · Ramal 1 (Arroyo Arroyón)
↙ Llega a Tigre
17:30
Ramal 1 (Arroyo Arroyón)
Arroyo Arroyón
!
Los horarios son siempre desde/hacia Tigre. La duración aprox. de cada servicio (⏱) es la del tramo completo: sumala a la hora si es Ida (sale Tigre) o restala si es Vuelta (llega Tigre) para estimar cuándo pasa por un punto intermedio del recorrido.
HORA LÍNEA · SERVICIO lunmarmiejueviesabdomfer
10:00 Ramal 1 (Arroyo Arroyón) ⏱ 01:30 / 02:00 hs.
16:30 Ramal 1 (Arroyo Arroyón) ⏱ 01:30 / 02:00 hs.
17:30 Ramal 1 (Arroyo Arroyón) ⏱ 01:30 / 02:00 hs.
19:15 Ramal 1 (Arroyo Arroyón) ⏱ 01:30 / 02:00 hs.
// DETALLE TÉCNICO

Arquitectura e implementación

De 4 PDF oficiales del gobierno bonaerense a una tabla de horarios navegable: descarga, parsing y modelo de datos detrás de Lanchas.

orm Postgres Empresa→Línea→Servicio→Horario
bq BigQuery WRITE_TRUNCATE, full-refresh
dbt dbt staging → star schema
monthly Airflow orquesta y testea
Airflow: actualizar_pdfs, cargar_bigquery, dbt_run y dbt_test en verde
dbt: lineage de lanchas_raw.horarios → staging → dimensiones → fact_horario
fact_horario en producción: grano (horario, vía), 7.100 filas
Full-refresh, no MERGE

Un horario nuevo del gobierno reemplaza al anterior, no convive con él. Cada ejecución vuelca el horario completo a lanchas_raw.horarios con WRITE_TRUNCATE en lugar de upsert por clave: la tabla queda igual al cronograma vigente, sin versiones acumuladas.

Una fila por vía, no por horario

Un mismo horario de lancha pasa por varias vías durante el viaje, no por una sola. fact_horario repite ese horario una vez por cada vía de su recorrido, en lugar de una sola vez por servicio, para mostrar el horario real de paso en cualquier parada intermedia, no solo en el origen y destino.

+ en paralelo, sirviendo a quien visita la página ↓
pdf 4 PDF oficiales gob. Buenos Aires
sha Descarga hash SHA-256
py pdfplumber tablas + coordenadas
orm Django ORM Empresa→Línea→Servicio→Horario
ui Template HTML + JS + localStorage
CARGA

BigQuery

lanchas_raw.horarios - full-refresh (WRITE_TRUNCATE)

EN NÚMEROS

  • fact_horario: 7.100 filas (grano horario + vía)
  • dim_via 72 filas, dim_tipo_dia 8, dim_empresa y dim_linea 3 cada una
  • Cada ejecución reemplaza el dataset completo: no hay versión anterior que conservar
MODELADO

dbt

1 staging + 4 dimensiones + 1 fact, star schema

HACE

  • stg_lanchas_horarios limpia formato (trim), sin lógica de negocio
  • dim_empresa, dim_linea, dim_via, dim_tipo_dia + fact_horario (grano horario+vía)

TESTS

  • 31 tests genéricos: not_null, unique, relationships, accepted_values
  • accepted_values fija tipo_dia (8 valores), servicio_tipo y dirección
ORQUESTACIÓN

Airflow

Motor compartido con Mareas (repo hermano); el DAG vive en este repo

HACE

  • actualizar_pdfs → cargar_bigquery → dbt_run → dbt_test, una vez al mes
  • El repo hermano monta este código como volumen: código y credenciales, nada más
  • El DAG viaja por script (sync_dags.sh), con symlink o copia según el entorno
FUENTE OFICIAL

Ministerio de Infraestructura (GBA)

4 PDF publicados por el gobierno bonaerense, mismo motor de generación

minfra.gba.gob.ar/web/Transporte/Fluvial/
LINEA_450.pdf · LINEA_451.pdf · LINEA_452.pdf · LINEA_453.pdf

LA APP DESCARGA

  • Jilguero (450), Interisleña (451+452 combinada) y Delta Argentino (453)
  • Hash SHA-256 por archivo (combinado para el par 451/452) para detectar cambios reales
PARSING Y NORMALIZACIÓN

Un motor común, reglas explícitas

BaseGobiernoParser extrae los 3 PDF; cada ajuste sigue un criterio documentado, no una inferencia automática

Extracción por color, no por texto Dirección (ida/vuelta) por color de fila, condiciones por triángulos de color - pdfplumber lee tablas por coordenadas.
Vías equivalentes "Boca Arroyo Durazno" y "Boca de Arroyo Durazno" se reconocen como el mismo lugar por nombre normalizado, en lugar de duplicarse.
Feriados y formatos inconsistentes Interisleña resuelve sus feriados según el día de la semana; typos de hora como "13;30" se normalizan a HH:MM.
ARQUITECTURA

Django ORM - PostgreSQL

Sin cache de archivos: los horarios se normalizan y persisten en modelos reales

Empresa → Línea → Servicio → Horario Vías (ríos/arroyos) asociadas por M2M vía RecorridoTramo, con orden de recorrido.
ActualizacionLog Auditoría de cada ejecución del pipeline: errores, notas y conteos por línea.
STACK TÉCNICO

Tecnologías aplicadas

Python + Django

ORM, views, service layer y management commands.

PostgreSQL

Mismo motor en dev y prod, vía DATABASE_URL.

BigQuery + dbt

Star schema full-refresh sobre lanchas_raw.horarios, en paralelo a la app.

Airflow

Orquestación mensual en un repo hermano, compartido con Mareas.

pdfplumber

Extracción de tablas por coordenadas, no texto plano.

Docker + docker-compose

Entorno de desarrollo reproducible: web + db con un solo comando.

GitHub Actions

CI en cada push/PR y smoke test semanal contra las fuentes reales.

pytest / pytest-django

Testing con foco en services y parsers.

Vanilla JS

Acordeón de líneas, buscador y filtros, sin dependencias.

localStorage

Favoritos 100% client-side, sin cuentas ni sesión.

CSS custom properties

Temas Día / Noche en tiempo real.

HERRAMIENTAS CLI

Management commands

Actualización y validación del catálogo sin depender de la interfaz web.

lanchas_inicializar Crea Empresa/Línea en la base - se corre una sola vez.
lanchas_descargar_pdfs [--linea N] Descarga los PDF oficiales del gobierno.
lanchas_parsear_pdfs [--linea N] [--forzar] Parsea y carga los horarios en la base.
lanchas_actualizar [--linea N] [--forzar] Descarga + parseo en un solo paso - cron semanal.
lanchas_validar Chequeo de integridad del catálogo cargado.
lanchas_listar [--empresa] [--via] Exploración rápida de los datos cargados.
lanchas_cargar_bigquery Sube el horario vigente a BigQuery (WRITE_TRUNCATE, sin histórico).
TESTING Y CI

Cobertura sobre parsers y services

Suite automatizada ejecutada en cada cambio, separada del chequeo contra las fuentes reales

pytest + pytest-django Cubre los 3 parsers por empresa: errores explícitos vía ParserError y casos borde como horarios superpuestos o typos de hora (ej. "13;30").
PDFs sintéticos Generados con reportlab para reproducir el layout de cada fuente. No se distribuyen los PDF reales de las empresas.
Coverage Enfocado en services/ y parsers/, el código con más lógica de negocio.
GitHub Actions CI corre la suite en cada push/PR. Un smoke test separado, en cron semanal, valida contra las fuentes reales del gobierno.
CÓMO CORRERLO LOCAL

Un comando, datos reales incluidos

El primer boot corre migraciones y carga el fixture con datos reales automáticamente, sin pasos manuales adicionales.

cp .env.example .env Variables de entorno de ejemplo, listas para levantar el proyecto.
docker compose up --build Levanta web + db, corre migraciones y carga datos reales de ejemplo.
// REPOSITORIO

github.com/chipap-dev/lanchas

Repositorio público, standalone, clonable sin credenciales - instrucciones de instalación con Docker incluidas.

Ver en GitHub →