← Todos los artículos y casos de estudio

Datos · Artículos y casos de estudio

De una API de 100 llamadas a un dashboard público: un pipeline ELT del clima de Bucaramanga en producción

Cómo construí un pipeline ELT del clima, probado y seguro, con Airflow, dbt, PostgreSQL y Superset sobre una API gratuita de 100 llamadas, y lo publiqué en un dashboard con marca propia e incrustable.

Por Henry Mariño ·

De una API de 100 llamadas a un dashboard público: un pipeline ELT del clima de Bucaramanga en producción

Quería un proyecto pequeño que se comportara como uno real: datos que llegan con horario, se transforman y se prueban, terminan en un dashboard que la gente puede usar, y todo corre en mi propio servidor sin que yo tenga que estar pendiente. El tema es el clima de Bucaramanga, la ciudad donde vivo. La restricción que marcó cada decisión: una API gratuita que permite 100 llamadas al mes.

En este artículo cuento qué construí, por qué tomé cada decisión, los errores que encontré en el camino y qué habilidades de ingeniería de datos demuestra cada parte. Al final está el dashboard en vivo.

El stack de un vistazo

                                           

Capa Herramienta Qué hace
Fuente API de Weatherstack (plan gratuito) Condiciones actuales, calidad del aire y astronomía de una ciudad
Ingesta Python + Apache Airflow 3 Llama a la API dos veces al día y guarda la respuesta cruda
Almacenamiento PostgreSQL Respuestas crudas en JSONB y los modelos de dbt, con roles de mínimo privilegio
Transformación dbt (ejecutado por Airflow en su propio contenedor) Modelo staging tipado, cuatro marts, pruebas de datos y pruebas unitarias
BI Apache Superset 6.1 Dashboard definido como código, con marca propia, público e incrustable
Acceso para IA Servidores MCP (Superset, Airflow, dbt, Postgres) Permiten que un asistente de IA consulte y opere el stack por un gateway privado
Plataforma Docker Compose en un VPS, desplegado con Coolify Merge en GitHub y el servidor se redespliega
Calidad GitHub Actions, Trivy, Dependabot Lint, pruebas, dbt parse, escaneo de seguridad y una corrida completa de punta a punta en cada push

1. Empezar por la restricción: auditar la API antes de escribir código

La primera versión del pipeline solo guardaba seis campos, y de Nueva York. Antes de ampliarlo probé con unas pocas llamadas reales qué permite de verdad el plan gratuito, en vez de confiar en la página de precios:

  • El endpoint /current funciona por HTTPS y devuelve unos 30 campos, incluidos calidad del aire (PM2.5, PM10, ozono, índice EPA de EE. UU.) y astronomía (amanecer, atardecer, fase lunar). Estábamos botando la mayoría.
  • Las consultas de varias ciudades en una sola llamada devuelven el error 604. Los pronósticos, el 609. Dos llamadas seguidas, el 106 rate_limit_reached.

Esa auditoría cambió el diagnóstico de "necesitamos un plan pago" a "estamos desperdiciando el 80 % de lo que ya nos da el plan gratis". También fijó el presupuesto: una ciudad, dos veces al día, a las 7 a. m. y 7 p. m. hora local. Son unas 62 llamadas al mes, con margen para reintentos.

2. Ingesta: guardar todo y decidir después

En lugar de agregar 25 columnas nuevas a la tabla cruda, la ingesta ahora guarda la respuesta completa de la API en una columna JSONB, más una marca source (api o mock). La migración es aditiva e idempotente: la tabla de producción se actualiza sola en la siguiente corrida y las filas viejas siguen siendo válidas.

ALTER TABLE dev.raw_weather_data ADD COLUMN IF NOT EXISTS payload JSONB;
ALTER TABLE dev.raw_weather_data ADD COLUMN IF NOT EXISTS source TEXT;

Es schema-on-read: si mañana quiero la visibilidad o la dirección del viento en un gráfico, el cambio es en dbt, no en la ingesta, y el histórico ya está guardado.

Dos protecciones cuidan el cupo:

  • Un control de presupuesto mensual. Antes de cada llamada real, el proceso cuenta las filas de la API del mes. Si llega a 90, deja una advertencia y se salta la llamada en vez de fallar. Las llamadas fallidas no insertan nada, por eso el límite queda por debajo de 100.
  • Una pausa entre llamadas cuando hay más de una ciudad configurada, porque el plan gratuito rechaza solicitudes seguidas.

Un modo de prueba (mock) devuelve una respuesta real de Bucaramanga con la hora actual. Así, el CI, el desarrollo local y el primer despliegue corren el pipeline completo sin gastar una sola llamada.

3. Transformación con dbt: tipada, probada y documentada

El modelo staging convierte el JSON en columnas con tipo. Weatherstack manda algunos números como texto ("7.2") y algunas horas como texto ("05:41 AM", o "No moonrise" los días en que la luna no sale). Dos macros pequeñas los interpretan de forma defensiva y devuelven NULL en vez de romper la corrida:

{{ safe_numeric("payload #>> '{current,air_quality,pm2_5}'") }} as aq_pm2_5,
{{ safe_time("payload #>> '{current,astro,moonrise}'") }}      as moonrise,

Las filas anteriores al cambio no tienen payload, así que el modelo usa las columnas antiguas y el histórico de Nueva York sigue funcionando. Sobre staging hay cuatro marts: observaciones, promedios diarios (temperatura, humedad, lluvia, UV, PM2.5), calidad del aire diaria y astronomía diaria (duración del día, fase lunar).

Las pruebas son donde dbt rinde de verdad:

  •  Pruebas unitarias: le pasan al modelo staging un JSON real y una fila antigua, y verifican la salida exacta: texto convertido a número, "yes" a booleano, "No moonrise" a NULL.
  •  Pruebas de datos: revisan rangos (humedad y nubosidad entre 0 y 100, índice EPA entre 1 y 6, UV no negativo) y que cada ciudad tenga una sola fila por día en los marts diarios.
  •  Frescura de la fuente: se revisa después de cada build y falla si la observación más reciente es demasiado vieja.

4. Orquestación y CI: probar que funciona antes de desplegar

Airflow corre tres tareas: ingesta, dbt build y la revisión de frescura. Si una tarea falla, se envía una alerta por webhook. En cada push, GitHub Actions corre:

  1.  Ruff y 57 pruebas de pytest (ingesta, control de presupuesto, migración, definición del dashboard).
    1.  dbt parse.
  2.  Un escaneo con Trivy de dependencias vulnerables, secretos en el código y configuraciones inseguras de contenedores.
  3.  Un job de integración que levanta Postgres y Airflow desde cero, corre todo el DAG en modo mock y verifica que cada mart tenga datos. Al final saca un backup y lo restaura para demostrar que sirve.

El CI atrapó dos errores reales en el camino. Unas comillas dobles dentro de una descripción en YAML rompieron dbt parse. Una prueba unitaria comparaba 7.13 con el numérico 7.130, que dbt considera distintos. Cuando no pude usar Docker en local, reproduje el CI con un PostgreSQL embebido y dbt en un entorno virtual aislado: 43 de 43 nodos de dbt en verde antes de volver a subir el cambio.

5. El dashboard como código, sin quitárselo a la interfaz

Superset se configura con un script que registra la base de datos, los datasets, los gráficos y el dashboard por su API REST. Corre solo después de cada despliegue, en un contenedor de un solo uso. Tres decisiones lo hicieron práctico:

  •  Idempotente y consciente de renombres. Cuando traduje todos los títulos al español, los gráficos existentes se renombraron en su lugar, sin duplicarse.
  • Después de crearlo, manda la interfaz. El script crea lo que falta, pero no sobrescribe gráficos, layout ni colores editados a mano. Una bandera de reinicio restaura la versión del repositorio cuando eso es lo que quiero.
  • Etiquetas para personas. Las columnas tienen nombres legibles en español y con unidades, como "Lluvia total (mm)" o "PM2.5 promedio (µg/m³)".

6. Storytelling con datos: el tema del Atlético Bucaramanga

Un dashboard del clima de Bucaramanga tenía que sentirse local, así que el tema toma la identidad del Atlético Bucaramanga, el equipo de la ciudad. Los temas de Superset 6 son tokens de Ant Design, así que la marca se volvió un sistema de diseño y no un montón de CSS suelto:

  •  Roles de color, no solo colores. El amarillo del escudo, #FFED00, no se lee como texto ni como línea de gráfico sobre fondo claro. Por eso quedó como acento de marca (banda del encabezado, borde de las tarjetas). El verde de la camiseta alterna, #00522C, es el color primario para acciones y títulos, y la tinta #1A1A2C va en el texto.
  •  Una paleta de gráficos que sigue siendo legible: el amarillo se oscurece a dorado, y se acompaña del verde del escudo, un oliva y un gris.
  • Tipografía deportiva: Bebas Neue para el titular y las cifras grandes, Oswald para los títulos de los gráficos y Montserrat para todo lo demás.
  • El escudo se sirve desde nuestro CMS Directus, y la política de seguridad de contenido (CSP) permite exactamente ese dominio.

7. Público, incrustable y aun así cerrado

Para publicar el dashboard en este artículo evité el atajo común de darles a los visitantes anónimos el rol amplio "Gamma":

  • Los visitantes anónimos reciben el rol Public mínimo que trae Superset.
  • Con acceso por dashboard (DASHBOARD_RBAC), solo los dashboards marcados con ese rol, y los datos detrás de sus gráficos, se ven sin iniciar sesión. SQL Lab, los demás dashboards y los datasets crudos siguen privados.
  • Una regla CSP frame-ancestors deja que solo este sitio lo incruste.
  • Del lado del sitio web, el HTML de los artículos se sanea, y los iframes solo se permiten desde una lista explícita de dominios y rutas.

8. Operarlo como producción

  • Mínimo privilegio: roles de Postgres separados para el pipeline, para los metadatos propios de Airflow y de Superset, y un lector de solo lectura que usan tanto los dashboards como las herramientas de IA.
  • Privado por defecto: solo el dashboard tiene dominio público. Airflow, Postgres y los servidores MCP solo se alcanzan por un túnel SSH. El servidor acepta únicamente llaves SSH y los puertos que no se usan están cerrados.
  • Recuperable: backups diarios de la base de datos que se guardan 7 días, más una prueba de restauración en el CI.
  • Mantenido: Dependabot propone actualizaciones de imágenes solo de parches, y los límites de recursos evitan que un servicio deje sin memoria a los demás.
  • Depurar con evidencia. Cuando el dashboard mostró "ChunkLoadError", los logs del servidor probaron que el navegador nunca pidió el archivo que fallaba. Era caché viejo después de un redespliegue, no una versión rota.

9. Trabajar con agentes de IA, con una persona al mando

Construí esto en pareja con Claude Code. Usé agentes en paralelo para la ingesta, dbt y Superset, cada uno trabajando con un contrato explícito de nombres de columnas. Los servidores MCP le permiten al asistente consultar Postgres, lanzar Airflow y editar gráficos de Superset directamente. La IA aceleró la escritura de código. El trabajo de ingeniería siguió siendo el mismo: definir las restricciones, escribir los contratos, revisar cada cambio, exigir pruebas y no desplegar hasta tener el CI en verde. Cuando el trabajo de un agente rompió el build, lo atraparon las pruebas, no los usuarios del dashboard.

Habilidades que demuestra este proyecto

                                               

Habilidad Evidencia en este proyecto
Integración de APIs con restricciones Auditoría del plan gratuito con llamadas reales; control de presupuesto, pausa por límite de solicitudes, modo mock
Modelado de datos JSONB crudo con staging tipado y cuatro marts; schema-on-read; migración compatible hacia atrás
dbt Macros, pruebas unitarias, pruebas de datos genéricas y puntuales, frescura de fuentes, documentación
Orquestación DAG de Airflow 3 con reintentos, timeouts, alertas de fallo y dbt en un contenedor aislado
Pruebas y CI/CD 57 pruebas de Python, corrida completa del DAG y prueba de restauración de backup en cada push
BI como código y storytelling Setup idempotente de Superset, tokens de diseño, paleta accesible, etiquetas en español
Seguridad Roles de mínimo privilegio, CSP, RBAC por dashboard, saneamiento de HTML, Trivy, servicios privados
DevOps Docker Compose, despliegues con Coolify desde GitHub, endurecimiento del VPS, backups
Ingeniería asistida por IA Servidores MCP para el stack de datos, agentes en paralelo, revisión humana y pruebas como filtro

El dashboard en vivo

Este es el dashboard real. Se actualiza a las 7 a. m. y 7 p. m., hora de Bogotá. Usa el filtro para explorarlo o ábrelo en su propia página.

   
Dashboard de Superset en vivo: temperatura, humedad, lluvia, UV, calidad del aire, sol y luna en Bucaramanga.

El código es abierto y está en GitHub, con los modelos de dbt, el DAG de Airflow, el script de Superset y el pipeline de CI: henrymarinho90/weather-data-project.

WhatsApp