{"id":1464,"date":"2026-10-13T16:00:33","date_gmt":"2026-10-13T16:00:33","guid":{"rendered":"https:\/\/javierladino.com\/es\/?post_type=rara-portfolio&#038;p=1464"},"modified":"2026-10-14T06:20:02","modified_gmt":"2026-10-14T06:20:02","slug":"las-aves-de-nantes-portal-de-datos-sobre-la-biodiversidad","status":"publish","type":"rara-portfolio","link":"https:\/\/javierladino.com\/es\/portfolio\/las-aves-de-nantes-portal-de-datos-sobre-la-biodiversidad\/","title":{"rendered":"Las aves de Nantes \u2014 Portal de datos sobre la biodiversidad"},"content":{"rendered":"\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"https:\/\/javierladino.com\/fr\/wp-content\/uploads\/2026\/10\/powerbi-dashboard-1024x571.png\" alt=\"Las aves de Nantes \u2014 Portal de datos sobre la biodiversidad\"\/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\"><br><strong>Un pipeline ELT de extremo a extremo en AWS que cada ma\u00f1ana convierte las observaciones naturalistas de iNaturalist en un dashboard de Power BI.<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><code>AWS Lambda<\/code> <code>S3<\/code> <code>Athena<\/code> <code>dbt<\/code> <code>Apache Airflow<\/code> <code>Terraform<\/code> <code>Power BI<\/code> <code>GitHub Actions<\/code><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Proyecto personal \u00b7 Dise\u00f1o, desarrollo y despliegue \u00b7  <a href=\"https:\/\/github.com\/javiladino\/oiseaux_nantes\" target=\"_blank\" rel=\"noopener\">C\u00f3digo en GitHub<\/a><br><\/p>\n\n\n\n<figure class=\"wp-block-video\"><video controls src=\"https:\/\/javierladino.com\/fr\/wp-content\/uploads\/2026\/10\/oiseaux_nantes_motion.mp4\"><\/video><\/figure>\n\n\n\n<h2 class=\"wp-block-heading has-vivid-cyan-blue-color has-text-color has-link-color wp-elements-1\">Contexto y problema<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Miles de naturalistas comparten sus observaciones de aves en iNaturalist. Pero esos datos en bruto (archivos JSON voluminosos y anidados) no permiten responder f\u00e1cilmente a preguntas como: <em>\u00bfqu\u00e9 especies se observan alrededor de Nantes? \u00bfEn qu\u00e9 \u00e9poca del a\u00f1o? \u00bfD\u00f3nde?<\/em><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">El objetivo era construir un pipeline fiable, automatizado y de bajo coste que recoja esos datos cada d\u00eda, garantice su calidad y los ponga a disposici\u00f3n de una herramienta de an\u00e1lisis, aplicando las pr\u00e1cticas del modern data stack.<\/p>\n\n\n\n<h2 class=\"wp-block-heading has-vivid-cyan-blue-color has-text-color has-link-color wp-elements-2\">La soluci\u00f3n<\/h2>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"841\" height=\"600\" src=\"https:\/\/javierladino.com\/fr\/wp-content\/uploads\/2026\/10\/image-1024x291.png\" alt=\"Diagrama de arquitectura del pipeline ELT en AWS: Lambda, S3, Athena y dbt, orquestados por Airflow, hasta Power BI\" class=\"wp-image-1188\"\/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\"><br>El pipeline sigue una arquitectura <strong>ELT<\/strong>: los datos se cargan primero en bruto y despu\u00e9s se transforman dentro del data lake.<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Ingesta:<\/strong>\u00a0una funci\u00f3n AWS Lambda consulta la API REST de iNaturalist de forma incremental (solo las observaciones nuevas desde la \u00faltima ejecuci\u00f3n) y guarda el JSON en bruto en S3.<\/li>\n\n\n\n<li><strong>Transformaci\u00f3n:<\/strong>\u00a0dbt, ejecutado sobre Amazon Athena, limpia y estructura los datos en una arquitectura medall\u00f3n (raw \u2192 staging \u2192 marts) hasta un\u00a0<strong>modelo en estrella<\/strong>\u00a0en Parquet.<\/li>\n\n\n\n<li><strong>Orquestaci\u00f3n:<\/strong>\u00a0Apache Airflow 3 encadena cada ma\u00f1ana la ingesta y despu\u00e9s cada modelo dbt con sus tests, con reintentos autom\u00e1ticos y alertas por email.<\/li>\n\n\n\n<li><strong>Visualizaci\u00f3n:<\/strong>\u00a0Power BI se conecta a las tablas finales: mapa de densidad, estacionalidad, rankings y fotos de las especies.<\/li>\n<\/ol>\n\n\n\n<h2 class=\"wp-block-heading has-vivid-cyan-blue-color has-text-color has-link-color wp-elements-3\">Stack y mi papel<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Capa<\/th><th>Herramientas<\/th><\/tr><\/thead><tbody><tr><td>Ingesta<\/td><td>AWS Lambda (Python), SSM Parameter Store<\/td><\/tr><tr><td>Almacenamiento<\/td><td>Amazon S3 (data lake), AWS Glue Data Catalog<\/td><\/tr><tr><td>Transformaci\u00f3n<\/td><td>dbt Core, Amazon Athena, Parquet<\/td><\/tr><tr><td>Orquestaci\u00f3n<\/td><td>Apache Airflow 3, Astronomer Cosmos, EC2<\/td><\/tr><tr><td>Infraestructura<\/td><td>Terraform, IAM (m\u00ednimo privilegio), Session Manager<\/td><\/tr><tr><td>Calidad y CI<\/td><td>77 tests de dbt, pytest, GitHub Actions<\/td><\/tr><tr><td>Visualizaci\u00f3n<\/td><td>Power BI<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Mi papel:<\/strong> proyecto realizado de principio a fin: especificaci\u00f3n, modelado de datos, desarrollo, infraestructura como c\u00f3digo, despliegue en producci\u00f3n y documentaci\u00f3n.<\/p>\n\n\n\n<h2 class=\"wp-block-heading has-vivid-cyan-blue-color has-text-color has-link-color wp-elements-4\">Detalles t\u00e9cnicos<\/h2>\n\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"https:\/\/javierladino.com\/fr\/wp-content\/uploads\/2026\/10\/airflow-dag-graph-1024x584.png\" alt=\"DAG de Airflow nantes_birds_pipeline: tarea de ingesta seguida del grupo de tareas de dbt, que se ejecuta cada d\u00eda a las 05:00 UTC\" class=\"wp-image-1189\"\/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\"><br><strong>Ingesta incremental e idempotente.<\/strong> Un <em>watermark<\/em> guardado en SSM recuerda la \u00faltima ejecuci\u00f3n correcta y solo avanza despu\u00e9s de una escritura exitosa en S3. Si algo falla, la siguiente ejecuci\u00f3n retoma desde el mismo punto, sin perder datos ni duplicarlos.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>La calidad de los datos como barrera.<\/strong> Cada modelo dbt va seguido de sus tests (unicidad, valores nulos, \u00e1rea geogr\u00e1fica, integridad referencial). Un test fallido bloquea autom\u00e1ticamente todos los modelos que dependen de \u00e9l: un dato inv\u00e1lido nunca llega al dashboard.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Orquestaci\u00f3n detallada.<\/strong> Gracias a Cosmos, cada modelo dbt se convierte en una pareja de tareas de Airflow (ejecuci\u00f3n + test): 13 tareas visibles y relanzables una a una.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Seguridad y costes.<\/strong> Instancia EC2 sin ning\u00fan puerto abierto (acceso mediante Session Manager), roles IAM de m\u00ednimo privilegio, Parquet comprimido y particionado. Coste total: unos 20 USD al mes.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"https:\/\/javierladino.com\/fr\/wp-content\/uploads\/2026\/10\/powerbi-model-1024x515.png\" alt=\"Modelo en estrella en Power BI: tabla de hechos fct_observations relacionada con las dimensiones de especies, observadores y fechas\" class=\"wp-image-1190\"\/><\/figure>\n\n\n\n<h2 class=\"wp-block-heading has-vivid-cyan-blue-color has-text-color has-link-color wp-elements-5\">Retos y aprendizajes<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">El despliegue en condiciones reales sac\u00f3 a la luz problemas que ning\u00fan entorno de pruebas habr\u00eda mostrado. Correg\u00ed y document\u00e9 doce, entre ellos:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Una tabla incremental peligrosa:<\/strong>\u00a0la estrategia\u00a0<code>insert_overwrite<\/code>\u00a0habr\u00eda borrado cada d\u00eda los datos del mes en curso. Con unas 18.000 filas, reconstruir la tabla completa a diario result\u00f3 m\u00e1s simple, siempre correcto e igual de barato.<\/li>\n\n\n\n<li><strong>Fechas ISO 8601 con zona horaria<\/strong>\u00a0que Athena rechazaba, corregidas con\u00a0<code>from_iso8601_timestamp<\/code>.<\/li>\n\n\n\n<li><strong>Una carga inicial demasiado larga para Lambda<\/strong>\u00a0(l\u00edmite de 3 minutos): el hist\u00f3rico se carg\u00f3 una \u00fanica vez fuera de Lambda y, desde entonces, el pipeline solo procesa lo nuevo.<\/li>\n\n\n\n<li><strong>Decisiones de calidad de datos:<\/strong>\u00a077 observaciones sin fecha se conservan en staging con un aviso, pero se excluyen del modelo anal\u00edtico.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"678\" height=\"547\" src=\"https:\/\/javierladino.com\/fr\/wp-content\/uploads\/2026\/10\/airflow-dag-graph2-1024x594.png\" alt=\"Historial de Airflow: tres ejecuciones fallidas durante el despliegue y, despu\u00e9s, todas las ejecuciones correctas\" class=\"wp-image-1191\"\/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\"><br>Lo que me llevo: un pipeline no est\u00e1 terminado hasta que funciona solo en producci\u00f3n, y saber cu\u00e1ndo <em>no<\/em> usar una t\u00e9cnica avanzada tambi\u00e9n forma parte del oficio.<br><\/p>\n\n\n\n<h2 class=\"wp-block-heading has-vivid-cyan-blue-color has-text-color has-link-color wp-elements-6\">Resultados<\/h2>\n\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"https:\/\/javierladino.com\/fr\/wp-content\/uploads\/2026\/10\/powerbi-dashboard-1-1024x571.png\" alt=\"Dashboard final: 4.726 observaciones en 2026, 306 especies, 484 observadores y 83,7 % con grado de investigaci\u00f3n\" class=\"wp-image-1192\"\/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\"><br><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>\u2705\u00a0<strong>En producci\u00f3n<\/strong>\u00a0desde octubre de 2026, con ejecuci\u00f3n autom\u00e1tica cada ma\u00f1ana<\/li>\n\n\n\n<li><strong>18.454 observaciones<\/strong>,\u00a0<strong>583 taxones<\/strong>,\u00a0<strong>1.874 observadores<\/strong>\u00a0(de 1970 a hoy)<\/li>\n\n\n\n<li><strong>77 tests<\/strong>\u00a0de calidad de datos en cada ejecuci\u00f3n<\/li>\n\n\n\n<li><strong>~20 USD al mes<\/strong>\u00a0de infraestructura<\/li>\n\n\n\n<li>Documentaci\u00f3n completa: gu\u00eda de despliegue paso a paso, operaci\u00f3n y resultados de validaci\u00f3n<\/li>\n<\/ul>\n\n\n\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" src=\"https:\/\/javierladino.com\/fr\/wp-content\/uploads\/2026\/10\/athena-validation.png\" alt=\"Consulta de validaci\u00f3n en Athena: 18.454 filas y 18.454 observaciones distintas, sin duplicados, 583 taxones y 1.874 observadores\" class=\"wp-image-1193\"\/><\/figure>\n\n\n\n<h2 class=\"wp-block-heading has-vivid-cyan-blue-color has-text-color has-link-color wp-elements-7\">Pr\u00f3ximos pasos y enlaces<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Pr\u00f3ximos pasos:<\/strong> historial SCD Tipo 2 con snapshots de dbt, tests de dbt contra Athena en la CI (OIDC), despliegue continuo y an\u00e1lisis horario a partir de la hora real de observaci\u00f3n.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\ud83d\udd17 <a href=\"https:\/\/github.com\/javiladino\/oiseaux_nantes\" target=\"_blank\" rel=\"noopener\">C\u00f3digo y documentaci\u00f3n en GitHub<\/a> \u00b7 <a href=\"https:\/\/www.linkedin.com\/posts\/jladino_dataengineering-analyticsengineering-dbt-ugcPost-7515770152739659776-o55f\/\" target=\"_blank\" rel=\"noopener\">Publicaci\u00f3n en LinkedIn<\/a><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\ud83d\udcbc <strong>Estoy abierto a oportunidades en Data Engineering \/ Analytics Engineering.<\/strong> <br><a href=\"mailto:hello@javierladino.com\" target=\"_blank\" rel=\"noopener\">Cont\u00e1ctame<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Un pipeline ELT de extremo a extremo en AWS que cada ma\u00f1ana convierte las observaciones naturalistas de iNaturalist en un dashboard de Power BI. AWS Lambda S3 Athena dbt Apache Airflow Terraform Power BI GitHub Actions Proyecto personal \u00b7 Dise\u00f1o, desarrollo y despliegue \u00b7 C\u00f3digo en GitHub Contexto y problema Miles de naturalistas comparten sus &hellip; <\/p>\n","protected":false},"author":1,"featured_media":1465,"comment_status":"open","ping_status":"closed","template":"","meta":{"ngg_post_thumbnail":0},"rara_portfolio_categories":[35],"class_list":["post-1464","rara-portfolio","type-rara-portfolio","status-publish","has-post-thumbnail","hentry","rara_portfolio_categories-data-engineer"],"_links":{"self":[{"href":"https:\/\/javierladino.com\/es\/wp-json\/wp\/v2\/rara-portfolio\/1464","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/javierladino.com\/es\/wp-json\/wp\/v2\/rara-portfolio"}],"about":[{"href":"https:\/\/javierladino.com\/es\/wp-json\/wp\/v2\/types\/rara-portfolio"}],"author":[{"embeddable":true,"href":"https:\/\/javierladino.com\/es\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/javierladino.com\/es\/wp-json\/wp\/v2\/comments?post=1464"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/javierladino.com\/es\/wp-json\/wp\/v2\/media\/1465"}],"wp:attachment":[{"href":"https:\/\/javierladino.com\/es\/wp-json\/wp\/v2\/media?parent=1464"}],"wp:term":[{"taxonomy":"rara_portfolio_categories","embeddable":true,"href":"https:\/\/javierladino.com\/es\/wp-json\/wp\/v2\/rara_portfolio_categories?post=1464"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}