Módulo 8: Project Kioskos Analytics Warehouse
Presentación del módulo: el capstone de la guía completa
Por qué existe este módulo
Detente un momento a mirar hacia atrás. En el módulo 1 declaraste el grano de fact_orders con una consulta —COUNT(*) contra COUNT(DISTINCT order_id || '-' || product_id)— y confirmaste que cada fila representa una línea de orden. En el módulo 2 construiste el star schema completo: llaves sustitutas, dim_date, los tres JOIN. En el módulo 3 comparaste star, snowflake y tabla ancha con evidencia, no con moda. En el módulo 4 historizaste dim_product con SCD tipo 2, usando MERGE INTO de verdad. En el módulo 5 descubriste que un JOIN mal escrito contra esa dimensión historizada corrompe la categoría de un producto, aunque el revenue total nunca mienta. En el módulo 6 construiste dos patrones de tabla de hechos que ningún hecho transaccional puede resolver: el accumulating snapshot del funnel de sesiones, y el cumulative table design de la actividad diaria por tienda. En el módulo 7 le pusiste nombre a un dominio con tres hechos y cuatro dimensiones, y escribiste el primer contrato de esquema verificable de esta guía.
Siete módulos, siete piezas, cada una construida y verificada por separado, en su propio script, con su propio assert. Lo que falta —y es exactamente lo que hace este módulo, el último de la guía— es dejar de tratarlas como siete piezas. Este es el capstone: un solo warehouse, construido de punta a punta en un único flujo, desde el dato crudo de foundations hasta el reporte que la gerencia de Kiosko puede leer directamente. No hay ningún concepto nuevo de fondo en este módulo —no hay una octava idea escondida que no hayas visto ya—; hay una sola idea nueva, y es de integración: bronze, silver y las cuatro capas gold que construiste (star historizado, accumulating snapshot, cumulative, tabla ancha) tienen que convivir en la misma conexión de DuckDB, en el mismo orden de dependencias, produciendo los mismos números que ya conoces, pero ahora todos a la vez.
Conexión con el módulo. Cada lección de este módulo reutiliza, sin cambiar una sola línea de su lógica interna, código que ya construiste: transform_fact_orders() (módulo 1), generate_date_dim() (módulo 2), MERGE INTO sobre dim_product_scd (módulo 4), el join punto-en-el-tiempo BETWEEN valid_from AND valid_to (módulo 5), fact_sessions y fact_store_activity (módulo 6), y validate_gold_schema() (módulo 7). Este módulo las llama en el orden correcto, dentro de un solo script, y agrega la pieza final que ningún módulo anterior necesitó todavía: publicar mart_daily_sales_obt —la tabla ancha del módulo 3— usando el join correcto contra la dimensión historizada, no contra la versión estática que usó su propio módulo de origen.
Una analogía: la orquesta completa, no siete solistas por separado
Piensa en siete músicos talentosos que, cada uno por su cuenta, ensayó su parte de una sinfonía en su propia sala de práctica: el violinista tocó su parte a la perfección, el chelista la suya, el pianista la suya. Cada ensayo individual sonó impecable. Pero una sinfonía no es la suma de siete grabaciones separadas reproducidas al mismo tiempo desde salas distintas —necesita que los siete músicos toquen juntos, en el mismo escenario, en el mismo tempo, respondiendo en tiempo real a lo que hacen los demás—. El director de orquesta no le enseña a nadie una nota nueva en el ensayo general: su trabajo es hacer que las siete partes, ya dominadas por separado, suenen como una sola pieza coherente.
Este módulo es el ensayo general de Kiosko. fact_orders, dim_product_scd, fact_sessions, fact_store_activity, mart_daily_sales_obt — cada una sonó perfecta en su propio módulo. Aquí no aprendes ninguna nota nueva; aprendes a hacer que las cinco suenen juntas, en el mismo warehouse, sin que ninguna pise a la otra.
Ejemplo trabajado: el mapa del warehouse completo, antes de construir nada
Antes de escribir la primera línea de este módulo, vale la pena ver el destino completo: qué tabla construye cada capa, y en qué orden depende una de la otra.
# warehouse_preview.py
WAREHOUSE_LAYERS = [
("bronze", "bronze_orders, bronze_events",
"crudo, sin transformar (equivalente a los CSV de foundations)"),
("silver", "fact_orders (via validate_orders + transform_fact_orders)",
"validado + modelado, 40 filas, revenue 106.15"),
("gold: star",
"dim_store, dim_date, dim_product_scd (historizada, MERGE INTO x2)",
"modulos 2 y 4, el star con historia real"),
("gold: funnel + cumulative",
"fact_sessions, fact_store_activity",
"modulo 6, dos patrones de hecho no transaccional"),
("gold: BI",
"mart_daily_sales_obt",
"modulo 3 + 5 integrados: tabla ancha con join punto-en-el-tiempo"),
]
print("=== Kiosko: el warehouse que integra este modulo ===\n")
for layer, tables, description in WAREHOUSE_LAYERS:
print(f"{layer:24} -> {tables}")
print(f"{'':24} {description}\n")
print("Entrada: 40 ordenes + 32 eventos (fijos, de foundations y del modulo 6).")
print("Salida: revenue historico correcto, conversion del funnel, activos 7/30d, OBT para BI.")
Qué esperar. Al correr python3 warehouse_preview.py, la salida es exactamente esta:
=== Kiosko: el warehouse que integra este modulo ===
bronze -> bronze_orders, bronze_events
crudo, sin transformar (equivalente a los CSV de foundations)
silver -> fact_orders (via validate_orders + transform_fact_orders)
validado + modelado, 40 filas, revenue 106.15
gold: star -> dim_store, dim_date, dim_product_scd (historizada, MERGE INTO x2)
modulos 2 y 4, el star con historia real
gold: funnel + cumulative -> fact_sessions, fact_store_activity
modulo 6, dos patrones de hecho no transaccional
gold: BI -> mart_daily_sales_obt
modulo 3 + 5 integrados: tabla ancha con join punto-en-el-tiempo
Entrada: 40 ordenes + 32 eventos (fijos, de foundations y del modulo 6).
Salida: revenue historico correcto, conversion del funnel, activos 7/30d, OBT para BI.
Ningún número de negocio todavía —esta lección es, deliberadamente, el mapa antes del territorio—, pero fíjate en el orden de las cinco filas: no es alfabético, es el orden real de dependencias. silver necesita bronze; el star necesita silver (fact_orders ya construido); mart_daily_sales_obt, la última fila, necesita tres cosas a la vez —fact_orders, dim_store/dim_date, y dim_product_scd—, porque es la primera tabla de esta guía que integra el join punto-en-el-tiempo del módulo 5 dentro de la tabla ancha del módulo 3, algo que ningún módulo anterior hizo por separado.
Diagrama: la arquitectura completa del warehouse de Kiosko
flowchart TD
subgraph Bronze["BRONZE (leccion 3)"]
A["bronze_orders (40)\nbronze_events (32)"]
end
subgraph Silver["SILVER (leccion 3)"]
B["validate_orders()\ncompuerta de calidad"]
C["transform_fact_orders()\nfact_orders, 40 filas, 106.15"]
end
subgraph GoldStar["GOLD: STAR (leccion 4)"]
D["dim_store (3)\ndim_date (31)"]
E["dim_product_scd (5)\nMERGE INTO x2, P002 historizado"]
F["JOIN punto-en-el-tiempo\nrevenue historico CORRECTO"]
end
subgraph GoldFunnel["GOLD: FUNNEL + CUMULATIVE (leccion 5)"]
G["fact_sessions (17)\nfunnel 17->9->6, 35.3%"]
H["fact_store_activity (21)\nactivos 7d/30d por tienda"]
end
subgraph GoldBI["GOLD: BI (leccion 6)"]
I["mart_daily_sales_obt\njoin punto-en-el-tiempo, 39 filas"]
end
A --> B --> C
C --> D
C --> E
D --> F
E --> F
C --> G
C --> H
C --> I
D --> I
E --> I
F -.confirma la categoria correcta.-> I
Fíjate en la flecha punteada: no es una dependencia de datos, es una advertencia. La lección 4 demuestra, con números, que el join punto-en-el-tiempo es el único correcto contra dim_product_scd; la lección 6 no vuelve a demostrarlo — simplemente lo aplica, confiando en la evidencia que la lección 4 ya construyó. Ese es, con precisión, el espíritu de todo este módulo: nada se reexplica desde cero, todo se integra sobre lo ya verificado.
El mapa de este módulo
Leccion Que construye
──────── ──────────────────────────────────────────────────────────────
L1 (esta) El mapa del warehouse completo, antes de construir nada
L2 El brief: por que 7 piezas sueltas no son un warehouse
L3 Bronze y silver reconstruidos: fact_orders, 40 filas, 106.15
L4 El star con dim_product_scd historizada + join punto-en-el-tiempo
L5 fact_sessions y fact_store_activity: funnel y actividad 7/30d
L6 mart_daily_sales_obt: la OBT publicada, con el join correcto
L7 Que le falta todavia a Kiosko -- el mapa de las guias hermanas
L8 Proyecto: el primer warehouse analitico completo de Kiosko
Las lecciones 3, 4, 5 y 6 construyen el warehouse por capas, en el orden exacto del diagrama de arriba —cada una con su propia verificación, igual que ya hiciste en los siete módulos anteriores—. La lección 7 da un paso atrás, con el warehouse ya terminado: nombra, una por una, las guías hermanas de data-engineering-ecosystem y qué resuelve cada una sobre lo que este warehouse deja pendiente. Y la lección 8 —el mini-proyecto final, no solo de este módulo sino de toda la guía— reconstruye las cinco capas una última vez, en un solo script, con el reporte final que cierra data-modeling-for-analytics-guide por completo.
Profundización: por qué integrar es un trabajo distinto a construir
Vale la pena ser explícitos sobre algo que este módulo no repite de los siete anteriores: construir una pieza aislada y verificarla con su propio assert es más fácil que integrarla con las demás, aunque ambos trabajos usen exactamente el mismo código. La razón es que la integración expone dependencias que un test aislado nunca ve. dim_product_scd, construida sola en el módulo 4, no necesita saber nada de fact_sessions. Pero en este módulo, ambas viven en la misma conexión de DuckDB que también tiene fact_orders, dim_store, dim_date y mart_daily_sales_obt — y el orden en que se crean sí importa: mart_daily_sales_obt no puede construirse antes de que dim_product_scd tenga sus cinco filas finales, porque un MERGE INTO corrido a mitad de camino dejaría la OBT con una versión incompleta de la historia de P002.
Esta es, en el fondo, la misma lección que ya nombró el módulo 7 sobre los contratos Medallion: silver depende de bronze, gold depende de silver, y ninguna capa puede construirse fuera de ese orden sin arriesgar un resultado silenciosamente incorrecto. Este módulo no inventa esa regla — la aplica, por primera vez, sobre las cinco capas completas a la vez, no sobre una tabla aislada.
Errores comunes
Pensar que "integrar" significa "copiar y pegar los siete scripts anteriores, uno después del otro". Qué pasa: alguien, al empezar este módulo, toma los ocho archivos .py de los mini-proyectos de los módulos 1 a 7 y los concatena en un solo archivo gigante, esperando que el resultado sea el warehouse integrado. Por qué pasa: cada mini-proyecto anterior ya es un script completo y verificado, así que pegarlos uno tras otro parece el camino más corto. Cómo detectarlo: si tu script resultante define con = duckdb.connect() siete veces, o reconstruye fact_orders cinco veces con datos ligeramente distintos, no integraste nada — solo concatenaste. Cómo corregirlo: un warehouse integrado usa una sola conexión, construye cada tabla una sola vez, y respeta el orden real de dependencias del diagrama de esta lección — no el orden en que los módulos aparecieron en la guía.
Esperar un concepto nuevo de modelado dimensional en este módulo. Qué pasa: alguien llega a este módulo buscando la "octava técnica" de modelado dimensional que todavía no vio, asumiendo que el capstone de una guía siempre agrega algo de fondo. Por qué pasa: los ocho módulos anteriores, cada uno, introdujo al menos una idea nueva —grano, star, snowflake/OBT, SCD, punto-en-el-tiempo, accumulating/cumulative, junk/degenerada—, así que parece razonable esperar una más. Cómo detectarlo: si buscas en este módulo una técnica de modelado que no puedas nombrar de los siete módulos anteriores, no la vas a encontrar — no existe. Cómo corregirlo: este módulo integra, no enseña de nuevo. La única pieza genuinamente nueva es la publicación de mart_daily_sales_obt con el join correcto —una combinación de dos técnicas ya conocidas (módulos 3 y 5), no una tercera.
Subestimar cuánto puede fallar solo por el orden de construcción. Qué pasa: alguien construye mart_daily_sales_obt antes de correr el segundo MERGE INTO sobre dim_product_scd, y se sorprende cuando la OBT no refleja el cambio de P002. Por qué pasa: en los módulos 4 y 5, dim_product_scd siempre apareció ya completa —cinco filas, P002 historizado—, así que es fácil olvidar que esa tabla se construye en dos pasos (MERGE #1, MERGE #2) y que cualquier tabla derivada que se construya entre esos dos pasos queda con una versión a medio camino. Cómo detectarlo: si tu mart_daily_sales_obt tiene categorías inesperadas, o le falta alguna, revisa en qué punto exacto del script la construiste respecto a los dos MERGE. Cómo corregirlo: la lección 4 de este módulo corre los dos MERGE completos, con su verificación, antes de que la lección 6 construya la OBT — ese orden no es arbitrario, es la garantía de que la dimensión está en su estado final antes de que cualquier tabla derivada la consulte.
Ejercicios
Ejercicio 1 — Ordena las cinco capas de memoria. Sin mirar el diagrama de esta lección, escribe en orden las cinco capas del warehouse de Kiosko (bronze, silver, gold: star, gold: funnel + cumulative, gold: BI) y, para cada una, nombra al menos una tabla que construye.
Ver solución
- Bronze:
bronze_orders,bronze_events— crudo, sin transformar. - Silver:
fact_orders— validado convalidate_orders(), modelado contransform_fact_orders(). - Gold: star:
dim_store,dim_date,dim_product_scd— el star con la dimensión historizada. - Gold: funnel + cumulative:
fact_sessions,fact_store_activity— accumulating snapshot y cumulative design. - Gold: BI:
mart_daily_sales_obt— la tabla ancha publicada, con el join punto-en-el-tiempo aplicado.
El orden importa porque cada capa depende, literalmente en el código, de que la anterior ya exista y esté completa — silver no puede construirse sin bronze, y la OBT del punto 5 necesita tanto el star del punto 3 como fact_orders del punto 2.
Ejercicio 2 — Identifica la única pieza genuinamente nueva de este módulo. De las cinco capas del diagrama, cuatro reutilizan código ya construido sin ningún cambio de fondo. Identifica cuál combinación específica es nueva en este módulo, y por qué ningún módulo anterior pudo construirla.
Ver solución
La pieza nueva es mart_daily_sales_obt construida con el join punto-en-el-tiempo contra dim_product_scd, en vez del join simple contra dim_product (estática) que usó el módulo 3. Ningún módulo anterior pudo construir esta versión porque dim_product_scd no existía todavía cuando el módulo 3 construyó su propia OBT —esa dimensión historizada se construyó recién en el módulo 4—, y el módulo 5, que sí construyó el join punto-en-el-tiempo, nunca lo aplicó a una tabla ancha, solo a reportes agregados por categoría. Este módulo es el primero en tener, a la vez, la OBT (módulo 3) y la dimensión historizada con su join correcto (módulos 4 y 5) disponibles para combinarlas.
Ejercicio 3 — Explica, de memoria, por qué la flecha punteada del diagrama de esta lección no representa una dependencia de datos. En 2-3 frases, explica qué tipo de relación representa esa flecha entre "JOIN punto-en-el-tiempo" y "mart_daily_sales_obt", si no es una dependencia técnica de construcción.
Ver solución
La flecha punteada representa una dependencia de evidencia, no de datos: la lección 4 demuestra, con un assert que compara el join roto (is_current = true) contra el correcto (BETWEEN valid_from AND valid_to), que solo el segundo produce la categoría correcta para P002. La lección 6 no repite esa demostración — construye mart_daily_sales_obt directamente con el patrón ya verificado, confiando en que la lección 4 ya probó que es el correcto. Si se invirtiera el orden —construir la OBT antes de haber demostrado cuál join es el correcto—, la lección 6 estaría tomando una decisión sin la evidencia que la justifica, exactamente el error que esta guía advirtió desde el módulo 1: nunca declarar algo sin verificarlo primero.
Resumen y siguiente paso
En esta lección viste el mapa completo del último módulo de la guía: cinco capas —bronze, silver, gold: star (con dim_product_scd historizada), gold: funnel + cumulative, gold: BI—, cada una construida ya en un módulo anterior, ahora integradas en un solo warehouse, en el orden exacto que sus dependencias reales exigen. Ningún concepto nuevo de modelado dimensional aparece aquí — la única pieza nueva es la combinación de la tabla ancha del módulo 3 con el join punto-en-el-tiempo del módulo 5, algo que ningún módulo anterior pudo construir por separado.
Antes de avanzar deberías poder: nombrar las cinco capas del warehouse y al menos una tabla de cada una; explicar por qué el orden de construcción importa, no solo el código de cada tabla; y anticipar que la única pieza genuinamente nueva de este módulo es mart_daily_sales_obt con el join correcto.
La lección 2 convierte este mapa en un brief concreto: qué le pediría, en sus propias palabras, la gerencia de Kiosko a un warehouse analítico real —y por qué siete piezas sueltas, cada una perfecta en su propio módulo, todavía no son ese warehouse.
Recursos
- Kimball Group — "Star Schema / OLAP Cube" — el vocabulario dimensional completo que este módulo integra de punta a punta: grano, star, SCD, dimensiones conformadas, accumulating snapshot. kimballgroup.com/data-warehouse-business-intelligence-resources/kimball-techniques/dimensional-modeling-techniques/star-schema-olap-cube. En inglés.
- Databricks — "What is the medallion lakehouse architecture?" — el marco bronze/silver/gold que organiza el orden de dependencias de todo este módulo. docs.databricks.com/aws/en/lakehouse/medallion. En inglés.
- "The Data Warehouse Toolkit", 3ra edición (Kimball & Ross, Wiley) — la referencia canónica que sostiene, de principio a fin, el modelo dimensional que este capstone integra. wiley.com/en-jp/The+Data+Warehouse+Toolkit. En inglés.
- DuckDB — documentación oficial del cliente Python, la interfaz que ejecuta cada consulta de este módulo. duckdb.org/docs/current/clients/python/overview. En inglés.