Módulo 8: Project Kioskos Analytics Warehouse

El brief: Kiosko necesita un warehouse real, no siete demos

Descripción

La gerencia de Kiosko —la misma gerente de operaciones que en foundations pedía un reporte semanal a mano— ahora tiene un problema distinto. Los siete módulos anteriores le dieron, cada uno por separado, una pieza impecable: un grano declarado, un star schema, una dimensión historizada, un join corregido, un funnel de sesiones, una actividad acumulada. Pero cuando le pidió al equipo de datos "un reporte que combine todo eso", la respuesta fue "no se puede todavía — cada pieza vive en su propio script, con su propia conexión de DuckDB, y ninguna sabe que las otras existen". Esta lección convierte esa frustración en un brief concreto: qué preguntas exactas tiene que responder el warehouse integrado, con qué evidencia, y quién lo va a consultar.

Conexión con el módulo. Igual que en foundations, esta lección no construye ninguna capa todavía —eso empieza en la lección 3—. Su trabajo es traducir una frustración de negocio en una lista de exigencias verificables, cada una mapeada a la lección de este módulo que la resuelve.

Una analogía: el arquitecto con siete planos sueltos, sin plano maestro

Imagina un arquitecto que diseñó, por separado y con excelencia técnica, los cimientos de un edificio, la estructura de cada piso, el sistema eléctrico, la plomería, y el diseño de la fachada — cinco planos, cada uno perfecto en su propia hoja de papel. El cliente, al ver los cinco planos sobre la mesa, pregunta algo razonable: "¿dónde está el plano que muestra cómo encajan todos juntos, en el mismo edificio, sin que la tubería de plomería atraviese una columna estructural?". Esa pregunta no cuestiona la calidad de ningún plano individual — cuestiona si alguien ya verificó que, juntos, forman un edificio construible.

El "plano maestro" de esta lección es, exactamente, ese documento que le falta a Kiosko: no un plano nuevo, sino la confirmación de que los siete planos ya dibujados encajan entre sí, en el orden correcto, sin que ninguna pieza rompa a otra al construirse junto a ella.

El brief, en las palabras de la gerencia

"Cada módulo me mostró algo distinto, y cada uno me convenció por separado. Pero necesito una sola cosa: abrir un warehouse, correr unas pocas consultas, y responder tres preguntas que mi equipo me hace cada semana. Primero, ¿cuánto vendimos, de verdad, por categoría de producto — incluyendo lo que pasó cuando renombramos las barras energéticas en agosto? Segundo, ¿qué tan bien convierten las sesiones de nuestra app, desde que alguien mira un producto hasta que lo compra? Tercero, ¿qué tan activa estuvo cada tienda esta semana y en el último mes? Y necesito que el equipo de BI pueda consultar todo esto sin tener que entender SCD, ni JOIN punto-en-el-tiempo, ni ninguna de las palabras técnicas que ustedes usan."

Cuatro exigencias concretas salen de ese párrafo, y cada una mapea a una pieza que ya construiste en un módulo anterior:

Exigencia de la gerenciaPieza que ya construisteMódulo
"revenue por categoría, incluyendo el cambio de agosto"dim_product_scd + join punto-en-el-tiempoMódulos 4 y 5
"qué tan bien convierten las sesiones"fact_sessions, el accumulating snapshotMódulo 6
"qué tan activa estuvo cada tienda"fact_store_activity, el cumulative designMódulo 6
"sin tener que entender SCD ni JOIN punto-en-el-tiempo"una tabla ancha ya resuelta, sin que BI escriba el BETWEENMódulo 3, reconstruida en este módulo

La última fila es la que este módulo agrega de verdad: ninguna lección anterior sirvió el resultado del join punto-en-el-tiempo ya resuelto en una tabla ancha, lista para que alguien sin conocimiento de SCD la consulte con un GROUP BY simple. Eso es, exactamente, lo que la lección 6 de este módulo construye.

Ejemplo trabajado: confirmando qué material tienes antes de integrar

Antes de prometerle nada a la gerencia, confirma que las siete piezas de los módulos anteriores están disponibles, con los números exactos que ya conoces, para que la integración de este módulo no dependa de memoria sino de evidencia.

# confirm_material.py
MODULE_DELIVERABLES = [
    ("Modulo 1", "fact_orders declarado",        "40 filas, revenue 106.15"),
    ("Modulo 2", "star schema completo",          "dim_store (3), dim_date (31), 3 JOIN, 40==40"),
    ("Modulo 3", "star vs snowflake vs OBT",       "106.15 identico en las 3 formas"),
    ("Modulo 4", "dim_product_scd historizada",    "5 filas, P002 en 2 versiones (snacks->health-snacks)"),
    ("Modulo 5", "revenue historico correcto",     "snacks/10.8 margen (correcto) vs health-snacks/9.36 (roto)"),
    ("Modulo 6", "fact_sessions + fact_store_activity", "funnel 17->9->6 (35.3%), activos 7d/30d"),
    ("Modulo 7", "dim_order_flags + contrato Medallion", "6 filas, 4 tablas gold, 0 discrepancias"),
]

print("=== Kiosko: confirmando el material de los 7 modulos, antes de integrar ===\n")
for module, deliverable, evidence in MODULE_DELIVERABLES:
    print(f"{module:10} {deliverable:38} {evidence}")

print("\nFaltante real: NINGUNA pieza nueva de modelado -- falta ensamblarlas en un solo warehouse.")
print("Lo que este modulo agrega: mart_daily_sales_obt con el join punto-en-el-tiempo ya resuelto.")

Qué esperar. Al correr python3 confirm_material.py, la salida es exactamente esta:

=== Kiosko: confirmando el material de los 7 modulos, antes de integrar ===

Modulo 1   fact_orders declarado                  40 filas, revenue 106.15
Modulo 2   star schema completo                   dim_store (3), dim_date (31), 3 JOIN, 40==40
Modulo 3   star vs snowflake vs OBT                106.15 identico en las 3 formas
Modulo 4   dim_product_scd historizada             5 filas, P002 en 2 versiones (snacks->health-snacks)
Modulo 5   revenue historico correcto              snacks/10.8 margen (correcto) vs health-snacks/9.36 (roto)
Modulo 6   fact_sessions + fact_store_activity      funnel 17->9->6 (35.3%), activos 7d/30d
Modulo 7   dim_order_flags + contrato Medallion     6 filas, 4 tablas gold, 0 discrepancias

Faltante real: NINGUNA pieza nueva de modelado -- falta ensamblarlas en un solo warehouse.
Lo que este modulo agrega: mart_daily_sales_obt con el join punto-en-el-tiempo ya resuelto.

Siete filas, siete evidencias numéricas concretas, ninguna inventada de nuevo aquí — cada una es, literalmente, el resultado ya verificado del proyecto de cierre de su propio módulo. Fíjate en el patrón: este brief no pide "construir algo nuevo" en el sentido de un concepto — pide reunir lo que ya existe, de forma que responda tres preguntas de negocio concretas sin que nadie del lado de BI tenga que escribir un MERGE INTO o un BETWEEN valid_from AND valid_to por su cuenta.

Diagrama: de la frustración de negocio al brief técnico

flowchart TD
    A["'Cada modulo me convencio\npor separado, pero necesito\nUN warehouse, no siete demos'"] --> B["Brief tecnico (esta leccion)"]
    B --> C["revenue por categoria, con el cambio\nde agosto -> dim_product_scd + join PIT\n(modulos 4-5)"]
    B --> D["conversion del funnel\n-> fact_sessions (modulo 6)"]
    B --> E["actividad por tienda\n-> fact_store_activity (modulo 6)"]
    B --> F["consultable sin SCD ni JOIN PIT\n-> mart_daily_sales_obt (NUEVO, leccion 6)"]
    C --> G["Warehouse integrado, un solo flujo"]
    D --> G
    E --> G
    F --> G

Profundización: por qué "sin tener que entender SCD" es la exigencia más importante del brief

Fíjate en la última frase de la cita de la gerencia: "sin tener que entender SCD, ni JOIN punto-en-el-tiempo". Esa frase, casi de pasada, es la que justifica por completo la lección 6 de este módulo. Todo lo que construiste en los módulos 4 y 5 —dim_product_scd, el BETWEEN valid_from AND valid_to— es exactamente correcto, pero exige que quien escriba la consulta sepa que existe una dimensión historizada y sepa evitar el error de unir solo por is_current. Eso es razonable pedírselo a alguien del equipo de datos, que ya pasó por los módulos 4 y 5 de esta guía. No es razonable pedírselo al equipo de BI, que solo quiere abrir un dashboard y arrastrar category a una columna.

Esta es la razón de fondo por la que una tabla ancha (OBT) tiene valor real, más allá de la comparación de rendimiento que ya viste en el módulo 3: la OBT no solo es más rápida de consultar —también encapsula una decisión de modelado correcta (el join punto-en-el-tiempo) para que la persona que la consulta no tenga que tomarla de nuevo, ni arriesgarse a tomarla mal. mart_daily_sales_obt, publicada con el join correcto ya resuelto, es, en un sentido muy concreto, una forma de que el equipo de BI se beneficie de todo lo que aprendiste en el módulo 5 sin tener que aprenderlo ellos mismos.

Errores comunes

Interpretar el brief como una petición de código nuevo de modelado. Qué pasa: alguien, al leer "necesito un warehouse real", asume que el brief pide inventar una técnica de modelado adicional que los siete módulos anteriores no cubrieron. Por qué pasa: "warehouse real" suena a algo más grande que lo ya construido, y es fácil confundir "más grande" con "más técnicas". Cómo detectarlo: si tu plan para este módulo incluye investigar una técnica de modelado dimensional que no aparece en ningún módulo anterior, te desviaste del brief — relee la tabla de esta lección, las siete filas ya cubren todo lo que el negocio pidió. Cómo corregirlo: el brief pide integración, no invención — las cuatro exigencias de la gerencia se resuelven combinando piezas ya construidas, no diseñando una octava.

Asumir que "sin SCD ni JOIN punto-en-el-tiempo" significa que la OBT puede ignorar la historización. Qué pasa: alguien, apurado por simplificar, construye mart_daily_sales_obt uniendo contra dim_product (la versión estática del módulo 2), razonando que así "no hace falta lidiar con SCD". Por qué pasa: la versión estática es, literalmente, más simple de unir —un solo JOIN por product_id, sin ningún BETWEEN—. Cómo detectarlo: si tu OBT muestra P002 siempre como health-snacks (la categoría actual), sin importar la fecha de la venta, cometiste exactamente el error que el módulo 5 enseñó a evitar — solo que ahora escondido dentro de una tabla ancha en vez de un reporte directo. Cómo corregirlo: "sin que el equipo de BI tenga que entender SCD" no significa "sin aplicar SCD" — significa que quien construye la OBT (tú, en la lección 6) aplica el join punto-en-el-tiempo por ellos, para que la tabla que consultan ya tenga la categoría correcta sin que tengan que escribir el JOIN ellos mismos.

Tratar las tres preguntas del brief como independientes entre sí. Qué pasa: alguien construye tres reportes completamente separados —uno para revenue, uno para el funnel, uno para actividad—, sin ninguna relación entre ellos, perdiendo la oportunidad de mostrar cómo se conectan en el mismo warehouse. Por qué pasa: cada pregunta del brief menciona una tabla distinta (mart_daily_sales_obt, fact_sessions, fact_store_activity), así que tratarlas como tres entregables separados parece natural. Cómo detectarlo: si tu warehouse final no tiene ninguna dimensión compartida entre las tres respuestas —por ejemplo, si fact_sessions y mart_daily_sales_obt no comparten store_id de la misma dim_store—, perdiste la propiedad más valiosa de un warehouse dimensional real: las dimensiones conformadas que el módulo 2 enseñó a construir. Cómo corregirlo: las tres respuestas del brief comparten dim_store y, en espíritu, el mismo calendario de dim_date — constrúyelas sobre las mismas dimensiones conformadas, no como tres silos aislados.

Ejercicios

Ejercicio 1 — Traduce una cuarta pregunta hipotética. Imagina que la gerencia agrega una quinta exigencia: "también quiero saber si las tiendas con mejor conversión de sesiones son también las de mayor revenue". Sin escribir código, di qué dos tablas de este warehouse (ya construidas en módulos anteriores) tendrías que combinar para responder esa pregunta, y por qué llave las conectarías.

Ver solución

Se combinarían fact_sessions (que tiene store_id y is_converted, para calcular la tasa de conversión por tienda) y mart_daily_sales_obt o fact_orders (que tiene store_id y revenue, para calcular el revenue por tienda). La llave de conexión sería store_id, agregando ambas tablas por separado a nivel de tienda —GROUP BY store_id— y después comparando los dos resultados lado a lado, sin necesitar un JOIN fila por fila entre ellas, porque fact_sessions (grano: sesión) y fact_orders/mart_daily_sales_obt (grano: línea de orden o día+tienda+producto) tienen grano distinto y no deberían unirse directamente sin agregar primero, exactamente como advirtió el ejercicio 3 de la lección 1 del módulo 7.

Ejercicio 2 — Encuentra la exigencia implícita sobre reproducibilidad. Vuelve a leer la cita completa de la gerencia. Sin mirar la tabla de "El brief", identifica una exigencia implícita que no está en la tabla, relacionada con la confiabilidad de los números a lo largo del tiempo, y a qué principio de esta guía correspondería.

Ver solución

Una respuesta razonable: si la gerencia va a consultar este warehouse "cada semana", como sugiere el tono de la cita, necesita que los mismos datos produzcan siempre los mismos números — la misma disciplina de reproducibilidad que esta guía exigió desde su diseño (prohibido random, datetime.now(), fechas siempre fijas). Esa exigencia implícita no aparece explícita en las palabras de la gerencia, pero es la que justifica por qué este módulo, igual que los siete anteriores, nunca usa una fecha calculada dinámicamente — cada assert de este warehouse tiene que poder repetirse, byte a byte, la próxima vez que alguien lo corra.

Ejercicio 3 — Argumenta por qué una OBT resuelve mejor "sin tener que entender SCD" que capacitar al equipo de BI en SQL avanzado. En 2-3 frases, compara las dos alternativas —publicar una OBT con el join ya resuelto, vs. enseñarle a todo el equipo de BI a escribir el BETWEEN valid_from AND valid_to ellos mismos— y explica por qué la primera es la elección correcta para este brief específico.

Ver solución

Capacitar a todo el equipo de BI en SCD y joins punto-en-el-tiempo resolvería el problema una sola vez por persona, pero dejaría la corrección del reporte dependiendo de que cada analista recuerde aplicar el patrón correcto cada vez que escriba una consulta nueva — exactamente el tipo de dependencia frágil que un error humano rompe tarde o temprano. Publicar la OBT con el join ya resuelto traslada esa responsabilidad, una sola vez, al equipo que construye el warehouse —que ya pasó por los módulos 4 y 5 de esta guía—, y deja al equipo de BI con una tabla donde la pregunta "¿la until_price o la categoría son correctas?" ya no depende de que recuerden un patrón SQL específico. Es la misma lógica detrás de cualquier capa de abstracción bien diseñada: mover la complejidad correcta a donde vive el conocimiento, no repartirla a quien no la necesita.

Resumen y siguiente paso

En esta lección tradujiste una frustración de negocio —"cada módulo me convenció por separado, pero necesito un solo warehouse"— en un brief con cuatro exigencias concretas, cada una mapeada a una pieza que ya construiste en los siete módulos anteriores. Confirmaste, con confirm_material.py, que las siete piezas están listas, con sus números exactos, antes de empezar a integrarlas. Y viste por qué la única pieza genuinamente nueva de este módulo —mart_daily_sales_obt con el join correcto ya resuelto— existe específicamente para que el equipo de BI no tenga que aprender SCD para confiar en el reporte.

Antes de avanzar deberías poder: repetir, de memoria, las cuatro exigencias del brief y a qué módulo corresponde cada una; explicar por qué una OBT con el join ya resuelto es mejor solución que capacitar a todo el equipo de BI; y nombrar la única pieza de este módulo que no existía, tal cual, en ningún módulo anterior.

La lección 3 empieza a construir: reconstruye bronze y silver desde foundations —el mismo fact_orders de siempre, 40 filas, revenue 106.15—, ahora como el primer paso formal de este warehouse integrado, no como un ejercicio aislado.

Recursos