Módulo 1: From Flat Tables To Dimensional Models
Mini-proyecto: la declaración de grano de Kiosko
Descripción
Este proyecto cierra el módulo integrando los cuatro pasos completos: elegiste el proceso de negocio (lección 4), declaraste y verificaste el grano con una consulta real (lección 5), clasificaste con precisión hechos y dimensiones (lección 6), y probaste que esa declaración sobrevive a un cambio de negocio hipotético (lección 7). Lo que falta es reunir todo en una sola entrega formal: el documento de grano que los siete módulos que siguen —y cualquier persona nueva que se una al equipo de datos de Kiosko— van a dar por sentado sin volver a discutirlo.
El proyecto tiene tres partes. Primero, reconstruyes y verificas fact_orders de punta a punta, exactamente como en la lección 5, pero ahora como el paso final de entrega, no como un ejercicio aislado. Segundo, documentas los cuatro pasos en una estructura formal, reutilizable por el resto de la guía. Tercero, verificas contra foundations: confirmas que este fact_orders reconstruido, célula por célula, produce exactamente los mismos números que el pipeline original — la prueba final de que reconstruir el hecho en esta guía no introdujo ninguna diferencia silenciosa.
Conexión con el módulo. Este proyecto no introduce ningún concepto nuevo — es la integración final de las siete lecciones anteriores, con una sola pieza de pulido: empaquetar la declaración de grano como una estructura de datos formal (GRAIN_DECLARATION), en vez de dejarla dispersa en varias lecciones.
Una analogía: el acta de entrega, no un ensayo más
Cada lección de este módulo fue un ensayo de una pieza distinta del proceso de Kimball. Este proyecto es el acta de entrega: el documento único que reúne, en un solo lugar, la decisión final sobre qué proceso de negocio se modeló, qué representa cada fila, qué dimensiones y qué hechos tiene, y la evidencia de que todo eso se verificó contra el dato real — exactamente lo que un equipo de datos serio dejaría por escrito antes de que cualquier otra persona empiece a construir sobre ese hecho.
El material: todo lo que este módulo construyó, en un solo flujo
Necesitas, en la misma carpeta: kiosko.py (con DIM_STORE, DIM_PRODUCT, Order, transform_fact_orders, de la lección 5) y raw_orders.py (la semana fija de cuarenta órdenes, también de la lección 5).
La solución de referencia, verificada
Parte 1 — Reconstruir y verificar el grano, como entrega final
# grain_declaration.py
from datetime import datetime
import duckdb
from kiosko import DIM_PRODUCT, DIM_STORE, Order, transform_fact_orders
from raw_orders import RAW_ORDERS
orders = [
Order(order_id=r[0], store_id=r[1], product_id=r[2], quantity=r[3],
unit_price=r[4], order_ts=datetime.fromisoformat(r[5]))
for r in RAW_ORDERS
]
fact_orders = transform_fact_orders(orders, DIM_STORE, DIM_PRODUCT)
con = duckdb.connect()
con.execute("""
CREATE TABLE fact_orders (
order_id VARCHAR, store_id VARCHAR, product_id VARCHAR,
quantity INTEGER, unit_price DOUBLE, revenue DOUBLE, order_ts TIMESTAMP
)
""")
con.executemany(
"INSERT INTO fact_orders VALUES (?, ?, ?, ?, ?, ?, ?)",
[(r["order_id"], r["store_id"], r["product_id"], r["quantity"],
r["unit_price"], r["revenue"], r["order_ts"]) for r in fact_orders],
)
print("=== Kiosko: declaracion de grano, entrega final ===\n")
print("Parte 1 -- verificacion del grano")
grain_check = con.sql("""
SELECT COUNT(*) AS total_rows,
COUNT(DISTINCT order_id || '-' || product_id) AS distinct_lines
FROM fact_orders
""").fetchone()
total_rows, distinct_lines = grain_check
print(f"total_rows={total_rows} distinct_lines={distinct_lines}")
assert total_rows == distinct_lines, "el grano declarado no coincide con el dato real"
print("Verificacion: total_rows == distinct_lines -> OK, el grano declarado se sostiene\n")
Esta primera parte no es un ejercicio aislado — es la garantía que respalda todo lo que sigue: si el grano no se sostuviera con evidencia, no tendría sentido documentar una declaración formal en la Parte 2.
Parte 2 — Documentar los cuatro pasos como una estructura formal
GRAIN_DECLARATION = {
"business_process": "La venta de un producto en una tienda de Kiosko",
"grain": "Una fila representa un producto vendido dentro de una orden especifica (una linea de orden)",
"dimensions": ["dim_store", "dim_product", "order_id (dimension degenerada)"],
"facts": {
"aditivas": ["quantity", "revenue"],
"capturadas_no_aditivas": ["unit_price"],
},
"verified_row_count": total_rows,
}
print("Parte 2 -- la declaracion formal de grano de Kiosko")
for key, value in GRAIN_DECLARATION.items():
print(f"{key}: {value}")
Fíjate en que GRAIN_DECLARATION no es un objeto decorativo — cada uno de sus cinco campos corresponde, exactamente, a una lección de este módulo: business_process viene de la lección 4, grain de la lección 5, dimensions y facts de la lección 6, y verified_row_count es la evidencia numérica misma de la Parte 1. Esta estructura es, literalmente, el "contrato de grano" que los módulos 2 a 8 de esta guía van a dar por sentado sin volver a discutirlo.
Parte 3 — Verificar contra foundations, número por número
print("\nParte 3 -- verificacion cruzada contra foundations")
print(con.sql("SELECT ROUND(SUM(revenue), 2) AS total_revenue FROM fact_orders"))
print(con.sql("""
SELECT store_id, COUNT(*) AS order_count, SUM(quantity) AS total_units, ROUND(SUM(revenue), 2) AS revenue
FROM fact_orders
GROUP BY store_id
ORDER BY store_id
"""))
Qué esperar. Al correr python3 grain_declaration.py completo (las tres partes juntas), la salida es exactamente esta:
=== Kiosko: declaracion de grano, entrega final ===
Parte 1 -- verificacion del grano
total_rows=40 distinct_lines=40
Verificacion: total_rows == distinct_lines -> OK, el grano declarado se sostiene
Parte 2 -- la declaracion formal de grano de Kiosko
business_process: La venta de un producto en una tienda de Kiosko
grain: Una fila representa un producto vendido dentro de una orden especifica (una linea de orden)
dimensions: ['dim_store', 'dim_product', 'order_id (dimension degenerada)']
facts: {'aditivas': ['quantity', 'revenue'], 'capturadas_no_aditivas': ['unit_price']}
verified_row_count: 40
Parte 3 -- verificacion cruzada contra foundations
┌───────────────┐
│ total_revenue │
│ double │
├───────────────┤
│ 106.15 │
└───────────────┘
┌──────────┬─────────────┬─────────────┬─────────┐
│ store_id │ order_count │ total_units │ revenue │
│ varchar │ int64 │ int128 │ double │
├──────────┼─────────────┼─────────────┼─────────┤
│ S01 │ 16 │ 34 │ 38.3 │
│ S02 │ 13 │ 37 │ 38.8 │
│ S03 │ 11 │ 31 │ 29.05 │
└──────────┴─────────────┴─────────────┴─────────┘
Detente en la Parte 3, porque es la verificación que le da confianza a todo el proyecto: 106.15 de revenue total, y 38.3/38.8/29.05 por tienda —idénticos, hasta el último centavo, a los números que ya viste en el módulo 8 de foundations—. Esto confirma algo que ninguna de las siete lecciones anteriores probó de forma tan directa: reconstruir fact_orders en esta guía, desde datos fijos declarados en Python en vez de leer los archivos CSV originales, no introdujo ninguna diferencia — es, matemáticamente, el mismo hecho, solo que ahora con su grano declarado y verificado formalmente, algo que el fact_orders original de foundations nunca tuvo.
Diagrama: los cuatro pasos, cerrados con evidencia
flowchart TD
A["Paso 1 (L4): business_process =\n'La venta de un producto en una tienda de Kiosko'"] --> B
B["Paso 2 (L5): grain =\n'Una linea de orden', VERIFICADO con COUNT(*)==COUNT(DISTINCT...)"] --> C
C["Pasos 3-4 (L6): dimensions =\n[dim_store, dim_product, order_id degenerada]\nfacts = {aditivas, capturadas}"] --> D
D["L7: el grano sobrevive a un cambio\nhipotetico de negocio (verificado)"] --> E
E["GRAIN_DECLARATION\nel contrato formal que este proyecto entrega"]
E --> F["Modulos 2-8: dan por sentado\neste contrato sin volver a discutirlo"]
Cerrando el checklist de la lección 2, pieza por pieza
| Pieza del checklist (lección 2) | Estado al cerrar este módulo |
|---|---|
Grano de fact_orders declarado y verificado con una consulta | Resuelto — GRAIN_DECLARATION, verificado con COUNT(*) == COUNT(DISTINCT ...) sobre 40 filas |
Llaves sustitutas, dim_date, dimensiones conformadas | Pendiente — módulo 2 |
| Snowflake vs tabla ancha | Pendiente — módulo 3 |
| Historización (SCD) | Pendiente — módulo 4 |
| Join punto-en-el-tiempo, deduplicación | Pendiente — módulo 5 |
| Accumulating snapshot, cumulative design | Pendiente — módulo 6 |
| Dimensión junk, más de un hecho | Pendiente — módulo 7 |
Una sola fila de las doce del checklist de la lección 2 quedó marcada como resuelta — y es, exactamente, la que debía resolverse primero: sin un grano declarado y verificado, ninguna de las once piezas restantes tendría un cimiento confiable sobre el cual construirse.
Errores comunes
Entregar la declaración de grano sin la verificación de la Parte 1. Qué pasa: alguien, apurado por mostrar GRAIN_DECLARATION como resultado final, salta directo a la Parte 2 sin correr la verificación de la Parte 1 primero. Por qué pasa: la estructura de datos de la Parte 2 se ve más presentable como "el resultado", y la consulta de verificación se siente como un paso preliminar descartable. Cómo detectarlo: si tu entrega final no incluye ninguna evidencia ejecutada de que total_rows == distinct_lines, estás documentando una afirmación, no una declaración verificada — exactamente la trampa que la lección 1 de este módulo ya advirtió. Cómo corregirlo: la Parte 1 de este proyecto no es opcional — es la garantía que hace confiable todo lo que sigue en la Parte 2 y la Parte 3.
Copiar GRAIN_DECLARATION sin entender cada campo. Qué pasa: alguien reutiliza la estructura de GRAIN_DECLARATION en su propio proyecto, con sus propios datos, sin poder explicar qué significa cada campo o de dónde sale el valor. Por qué pasa: copiar una estructura que "ya funciona" es más rápido que construirla desde cero, pero puede hacerse sin comprensión real. Cómo detectarlo: si no puedes explicar, sin mirar el código, por qué facts tiene dos categorías (aditivas y capturadas_no_aditivas) en vez de una sola lista, te falta revisar la lección 6. Cómo corregirlo: cada campo de esta estructura tiene una lección completa detrás que lo justifica — antes de reutilizarla en un proyecto propio, confirma que puedes explicar cada campo con tus propias palabras.
Dar por "terminado el modelado" al ver que el grano quedó declarado. Qué pasa: alguien termina este proyecto, ve GRAIN_DECLARATION completo y verificado, y concluye que ya tiene un warehouse dimensional real. Por qué pasa: una declaración de grano bien hecha se siente como un logro completo, y es fácil olvidar que es solo el primer paso de ocho módulos. Cómo detectarlo: si no puedes nombrar, de memoria, al menos tres de las once piezas todavía pendientes en la tabla del checklist de esta lección, te falta releer la lección 2. Cómo corregirlo: fact_orders sigue usando llaves naturales, sigue sin dim_date, sigue sin historización — este proyecto cierra el primer paso de ocho, no la guía completa.
Ejercicios
Ejercicio 1 — Extiende GRAIN_DECLARATION con un campo de fecha de verificación. Sin usar datetime.now() (prohibido en esta guía para mantener reproducibilidad), agrega un campo fijo verified_on a GRAIN_DECLARATION con la fecha en que, narrativamente, Kiosko cerró este análisis — usa la fecha del último día de la semana de datos, "2026-08-09".
Ver solución
GRAIN_DECLARATION["verified_on"] = "2026-08-09"
print(f"verified_on: {GRAIN_DECLARATION['verified_on']}")
Salida esperada:
verified_on: 2026-08-09
Fíjate en que la fecha es un valor fijo, deliberado —el último día de la semana de datos que este proyecto usó para verificar—, no el resultado de datetime.now(). Esta es exactamente la disciplina de reproducibilidad que toda la guía exige: cualquier fecha que aparezca en un resultado debe poder reconstruirse, exactamente igual, en cualquier corrida futura.
Ejercicio 2 — Verifica el grano por producto, no solo por tienda. La Parte 3 del proyecto verificó revenue por tienda. Escribe la consulta equivalente agrupada por product_id, y confirma que los números coinciden con los del reporte de gold de foundations (P001: 16 órdenes, 61 unidades, revenue 33.55; P002: 10, 18, 21.6; P003: 7, 14, 10.5; P004: 7, 9, 40.5).
Ver solución
print(con.sql("""
SELECT product_id, COUNT(*) AS order_count, SUM(quantity) AS total_units, ROUND(SUM(revenue), 2) AS revenue
FROM fact_orders
GROUP BY product_id
ORDER BY product_id
"""))
Salida esperada:
┌────────────┬─────────────┬─────────────┬─────────┐
│ product_id │ order_count │ total_units │ revenue │
│ varchar │ int64 │ int64 │ double │
├────────────┼─────────────┼─────────────┼─────────┤
│ P001 │ 16 │ 61 │ 33.55 │
│ P002 │ 10 │ 18 │ 21.6 │
│ P003 │ 7 │ 14 │ 10.5 │
│ P004 │ 7 │ 9 │ 40.5 │
└────────────┴─────────────┴─────────────┴─────────┘
Los cuatro productos coinciden, número por número, con el reporte gold de foundations — la misma verificación cruzada de la Parte 3, ahora agrupada por la otra dimensión, con el mismo resultado de confianza.
Ejercicio 3 — Explica, de memoria, el camino completo de un producto de Kiosko a través de los ocho módulos que siguen. Sin mirar el diseño de la guía, elige uno de los cuatro productos de Kiosko (por ejemplo, P004, el cable cargador) y describe en un párrafo de 4-6 frases qué le va a pasar a lo largo de los ocho módulos de esta guía: cómo se representa hoy, y qué gana en cada módulo siguiente (star schema, snowflake, historización, deduplicación, accumulating snapshot, dimensión junk).
Ver solución
Hoy, P004 (el cable cargador) vive en dim_product con llave natural, sin historia — cuatro columnas fijas (product_id, product_name, category, unit_cost), y aparece en fact_orders como una línea de orden cada vez que se vende, con su grano ya declarado y verificado en este módulo. En el módulo 2, gana una llave sustituta y se conecta, por primera vez, a dim_date a través de fact_orders reconstruido con los tres joins completos. En el módulo 3, su categoría (electronics) se normaliza en una tabla dim_category aparte (snowflake), y también aparece denormalizado en mart_daily_sales_obt para el equipo de BI. En el módulo 4, si su precio o categoría cambiara, dim_product lo historizaría con SCD-2, preservando ambas versiones con valid_from/valid_to. En el módulo 5, cualquier venta de P004 se uniría a la versión correcta de la dimensión según la fecha exacta de la venta, no la versión actual. En los módulos 6 y 7, P004 seguiría apareciendo en el funnel de sesiones (si un cliente lo agregó al carrito antes de comprarlo) y en cualquier reporte multi-hecho que use dim_order_flags junto a la venta. El mismo producto, cada vez con más contexto histórico y estructural alrededor, sin que su identidad de negocio (product_id = "P004") cambie nunca.
Resumen y siguiente paso: el final del módulo 1
Con este mini-proyecto cierras el módulo 1 completo. Aplicaste, de punta a punta, el proceso de cuatro pasos de Ralph Kimball sobre fact_orders: elegiste el proceso de negocio (la venta de un producto en una tienda de Kiosko), declaraste y verificaste su grano con una consulta ejecutada en DuckDB (una línea de orden — total_rows == distinct_lines, 40 == 40), clasificaste con precisión sus dimensiones y hechos (llaves foráneas, dimensión degenerada, medidas aditivas y capturadas), y probaste que esa declaración sobrevive a un cambio de negocio hipotético. El resultado —GRAIN_DECLARATION, verificado, con revenue total 106.15 idéntico a foundations— es el contrato formal que el resto de esta guía da por sentado.
Diste el primer paso de un camino de ocho módulos: fact_orders sigue siendo, columna por columna, exactamente el mismo hecho que dejó foundations — lo que cambió es que ahora sabes, con evidencia y no con intuición, qué representa cada una de sus filas.
Hacia dónde sigues. El módulo 2 —the-star-schema-and-conformed-dimensions— construye, sobre este grano ya declarado, el star schema completo de Kiosko: llaves sustitutas, dim_date, y los tres JOIN que reconstruyen fact_orders conectado a sus tres dimensiones a la vez.
Recursos
- Kimball Group — "Four-Step Dimensional Design Process" — la fuente completa del proceso que este proyecto cierra, aplicado de punta a punta sobre Kiosko. kimballgroup.com/.../four-4-step-design-process. En inglés.
- "The Data Warehouse Toolkit", 3ra edición (Kimball & Ross, Wiley) — la referencia canónica que sostiene el vocabulario completo usado en este módulo, desde el grano hasta la clasificación de hechos y dimensiones. wiley.com/en-jp/The+Data+Warehouse+Toolkit. En inglés.
- DuckDB — documentación oficial del cliente Python, la herramienta que ejecutó cada verificación de este módulo. duckdb.org/docs/current/clients/python/overview. En inglés.
- Joe Reis & Matt Housley, Fundamentals of Data Engineering (O'Reilly, 2022) — el mismo marco que sostuvo foundations de principio a fin, ahora aplicado al primer paso de modelado dimensional de esta guía. oreilly.com/library/view/fundamentals-of-data/9781098108298. En inglés.