Módulo 3: Consistency And Referential Checks
Qué significa consistencia entre tablas
Descripción
La lección 3 del módulo 1 ya definió las seis dimensiones de calidad de datos, y ya le puso a consistency una definición precisa: "¿Cada referencia a otra tabla apunta a algo que existe de verdad ahí?", verificada a "nivel fila, contra otra tabla". Esta lección se detiene en esa definición y la desarrolla a fondo —qué es exactamente una "referencia", por qué el nombre técnico correcto es integridad referencial, y por qué la palabra "consistencia" en el vocabulario general de bases de datos es más amplia que lo que este módulo construye—, porque el resto de esta guía usa este vocabulario con precisión, sin volver a explicarlo.
Conexión con el módulo. La lección 1 mostró la distinción con una analogía (el boleto de avión). Esta lección le pone el vocabulario técnico correcto a esa misma distinción, y la lección 3 demuestra, con evidencia ejecutada sobre orders_s04 real, por qué ningún esquema de una sola tabla puede cerrarla.
Consistencia, en el sentido amplio, contra consistencia en el sentido de esta guía
Vale la pena una aclaración antes de seguir, porque "consistency" es una palabra que también aparece en otros contextos de ingeniería de datos con significados distintos. En el mundo de bases de datos transaccionales, la C de ACID (Atomicity, Consistency, Isolation, Durability) se refiere a que una transacción nunca deja la base de datos en un estado que viole sus propias reglas declaradas —tipos, restricciones UNIQUE, restricciones CHECK—; es una garantía del motor de base de datos sobre transacciones individuales. En streaming, "consistencia eventual" contra "consistencia fuerte" describe qué tan rápido todos los nodos de un sistema distribuido ven el mismo valor después de una escritura. Ninguna de esas dos ideas es la que trabaja este módulo.
La consistency de esta guía —una de las seis dimensiones de calidad de datos definidas en el módulo 1— es más específica: es la propiedad de que una referencia declarada en una tabla apunte a algo que existe de verdad en otra tabla (o, como vas a ver en la lección 5, que dos columnas de la misma fila no se contradigan entre sí). El nombre técnico preciso para el primer caso —el que domina este módulo— es integridad referencial (referential integrity), un término que viene directo del modelo relacional de bases de datos, más antiguo que cualquier herramienta de esta guía. product_id="P099" en ORD-9508 es, con precisión de vocabulario, una violación de integridad referencial: orders_s04 tiene una columna que se supone representa un producto, y ese producto no existe en dim_product, la tabla que debería contenerlo.
Una analogía: la llave y la cerradura, no la llave sola
Piensa en una llave física, recién cortada en una ferretería. Puedes verificar, sin necesitar ninguna puerta, que la llave tiene la forma correcta: el material es metal, el tamaño del diente es el esperado, no está doblada. Eso es validity —una propiedad de la llave, aislada—. Pero hay una pregunta completamente distinta que ninguna inspección de la llave sola puede contestar: ¿esta llave abre alguna cerradura real? Para responder eso, necesitas la cerradura —la otra mitad de la relación—. Una llave perfectamente bien cortada, que no corresponde a ninguna cerradura de tu casa, es una llave inútil, aunque sea, en sí misma, un objeto perfecto.
product_id es la llave. dim_product es el llavero completo de cerraduras reales que existen en Kiosko. Un product_id bien formado —una cadena de texto, con el patrón P seguido de tres dígitos— es una llave bien cortada. Pero "bien cortada" no es lo mismo que "abre algo real". La integridad referencial es, exactamente, la pregunta de si la llave abre una cerradura que de verdad existe.
Ejemplo trabajado: la relación entre orders y dim_product, explicada con el vocabulario correcto
En el vocabulario del modelo relacional, dim_product.product_id es la llave primaria (primary key) de la tabla dim_product —el identificador que distingue, sin ambigüedad, cada fila de esa tabla—. orders_s04.product_id es una llave foránea (foreign key): una columna que, por diseño, se espera que contenga solo valores que también existan como llave primaria en dim_product. Este código, sin ninguna dependencia todavía —ni Polars, ni DuckDB—, expresa esa relación de la forma más simple posible, con estructuras de Python puro:
# foreign_key_vocabulary.py
dim_product = [
{"product_id": "P001", "product_name": "Bottled Water 600ml"},
{"product_id": "P002", "product_name": "Energy Bar"},
{"product_id": "P003", "product_name": "Instant Coffee Sachet"},
{"product_id": "P004", "product_name": "Phone Charger Cable"},
]
orders_sample = [
{"order_id": "ORD-9501", "product_id": "P001"},
{"order_id": "ORD-9508", "product_id": "P099"},
]
primary_keys = {row["product_id"] for row in dim_product}
print(f"Llave primaria de dim_product (primary_keys): {sorted(primary_keys)}\n")
for order in orders_sample:
fk_value = order["product_id"]
is_valid_reference = fk_value in primary_keys
print(f"{order['order_id']}: product_id (llave foranea) = '{fk_value}' -> "
f"{'apunta a una fila real' if is_valid_reference else 'NO EXISTE en dim_product'}")
Qué esperar. Al correr python3 foreign_key_vocabulary.py, la salida es exactamente esta:
Llave primaria de dim_product (primary_keys): ['P001', 'P002', 'P003', 'P004']
ORD-9501: product_id (llave foranea) = 'P001' -> apunta a una fila real
ORD-9508: product_id (llave foranea) = 'P099' -> NO EXISTE en dim_product
ORD-9501 tiene integridad referencial: su llave foránea (P001) apunta a una fila que existe de verdad en dim_product. ORD-9508 la viola: su llave foránea (P099) no tiene ninguna fila correspondiente. La mecánica completa de la integridad referencial —el corazón técnico de este módulo— es exactamente esta comparación de conjuntos, fk_value in primary_keys, aplicada a cada fila. La lección 4 hace lo mismo, pero de forma vectorizada, sobre un DataFrame completo de Polars, en vez de un bucle for fila por fila.
Diagrama: dos tablas, una flecha que puede apuntar a la nada
flowchart LR
subgraph orders_s04
O1["ORD-9501\nproduct_id: P001"]
O2["ORD-9508\nproduct_id: P099"]
end
subgraph dim_product
P1["P001\nBottled Water 600ml"]
P2["P002\nEnergy Bar"]
P3["P003\nInstant Coffee Sachet"]
P4["P004\nPhone Charger Cable"]
end
O1 -->|"referencia valida"| P1
O2 -.->|"referencia rota\n(P099 no existe)"| X["? (nada)"]
La flecha sólida de ORD-9501 llega a un destino real —P001, una fila que existe en dim_product—. La flecha punteada de ORD-9508 no llega a ningún lado: P099 no es una fila de dim_product, es solo una cadena de texto que parece una referencia, sin serlo de verdad. Esa es la imagen completa de una violación de integridad referencial: no un valor "roto" en el sentido de tener el tipo incorrecto, sino una flecha que apunta al vacío.
Profundización: por qué "referencia rota" es distinto de "referencia vacía"
Vale la pena distinguir dos formas en las que una llave foránea puede fallar, porque este módulo solo se ocupa de una de ellas. Si product_id estuviera vacío o nulo —el problema que ya resolvió completeness en el módulo 2, con Field(nullable=False)—, no habría ninguna referencia que verificar: no hay flecha que trazar desde la nada. La integridad referencial, en cambio, presupone que sí hay un valor presente, y pregunta si ese valor específico corresponde a algo real. ORD-9508 no tiene un product_id vacío —tiene uno perfectamente presente, "P099"—; el problema es que ese valor concreto no tiene contraparte. Esta distinción importa en la práctica: un pipeline bien diseñado corre completeness antes que consistency (exactamente el orden de los módulos 2 y 3 de esta guía), porque no tiene sentido preguntar "¿esta referencia existe?" sobre un campo que ni siquiera tiene un valor que buscar.
Errores comunes
Usar "consistency" y "referential integrity" como si fueran sinónimos en cualquier contexto. Qué pasa: alguien, después de esta lección, empieza a llamar "integridad referencial" a cualquier tipo de inconsistencia, incluidas las que la lección 5 va a llamar cross-column. Por qué pasa: esta lección presenta la integridad referencial como el caso principal de consistency, y es fácil generalizar de más. Cómo detectarlo: si estás describiendo un problema que no involucra ninguna segunda tabla —por ejemplo, dos columnas de la misma fila que se contradicen—, "integridad referencial" no es el término correcto, aunque siga siendo una violación de consistency en el sentido amplio del módulo 1. Cómo corregirlo: reserva "integridad referencial" específicamente para el caso llave-foránea-contra-llave-primaria de esta lección; la lección 5 le da su propio nombre a las reglas dentro de una sola tabla.
Pensar que la integridad referencial es exclusiva de bases de datos relacionales "de verdad" (Postgres, MySQL). Qué pasa: alguien asume que este problema solo existe en sistemas con FOREIGN KEY declarado explícitamente, y que un CSV, por no tener ese mecanismo, está exento del concepto. Por qué pasa: el vocabulario (llave primaria, llave foránea) viene históricamente de sistemas relacionales con restricciones activas. Cómo detectarlo: orders_2026-08-14.csv es un archivo de texto plano — no tiene ningún motor de base de datos protegiéndolo, y aun así ORD-9508 viola integridad referencial de la misma forma exacta que la violaría en cualquier tabla con un FOREIGN KEY mal configurado. Cómo corregirlo: el concepto —una referencia que no apunta a nada real— es independiente del formato de almacenamiento. Lo que cambia entre un CSV y una base de datos con FOREIGN KEY activo no es si el problema puede existir, es quién lo detecta y cuándo — la lección 3 de este módulo profundiza exactamente en esa diferencia.
Confundir "llave primaria" con "cualquier columna que identifica algo". Qué pasa: alguien, al leer dim_product, asume que product_name también podría servir como llave primaria, porque también identifica al producto de forma reconocible. Por qué pasa: product_name sí es único en el catálogo actual de cuatro productos de Kiosko. Cómo detectarlo: pregúntate si esa columna está garantizada a ser única e inmutable con el tiempo — product_name podría cambiarse (un rebrandeo, una corrección de ortografía) sin que el producto deje de ser el mismo producto, mientras que product_id está diseñado, por convención, para no cambiar nunca. Cómo corregirlo: la llave primaria de una tabla dimensional es, casi siempre, el identificador técnico —product_id, store_id—, no el nombre legible para humanos; esto ya lo estableció data-engineering-foundations-guide (módulo 4) al diseñar dim_product por primera vez.
Ejercicios
Ejercicio 1 — Agrega un tercer pedido a orders_sample y confirma su integridad referencial. Usando el ejemplo trabajado de esta lección, agrega {"order_id": "ORD-9504", "product_id": "P004"} a orders_sample y corre el bucle de nuevo. ¿Qué resultado esperas, y por qué?
Ver solución
orders_sample.append({"order_id": "ORD-9504", "product_id": "P004"})
for order in orders_sample:
fk_value = order["product_id"]
is_valid_reference = fk_value in primary_keys
print(f"{order['order_id']}: product_id (llave foranea) = '{fk_value}' -> "
f"{'apunta a una fila real' if is_valid_reference else 'NO EXISTE en dim_product'}")
Salida esperada (la línea nueva, agregada al final):
ORD-9504: product_id (llave foranea) = 'P004' -> apunta a una fila real
P004 está en primary_keys (Phone Charger Cable), así que ORD-9504 tiene integridad referencial completa. Este ejercicio confirma que la mecánica del ejemplo trabajado —fk_value in primary_keys— generaliza sin cambios a cualquier fila nueva, sin importar si termina siendo válida o no.
Ejercicio 2 — Construye una violación de integridad referencial en la dirección opuesta. Todo el ejemplo trabajado verifica que cada product_id de orders_sample exista en dim_product. En 2-3 frases, explica por qué la dirección opuesta —verificar que cada product_id de dim_product aparezca en orders_sample— no sería una violación de integridad referencial, aunque también compare las dos mismas tablas.
Ver solución
La integridad referencial tiene una dirección definida por cuál columna es la llave foránea y cuál es la llave primaria: orders_s04.product_id (foránea) debe apuntar a dim_product.product_id (primaria), nunca al revés. Que un producto exista en dim_product sin que ninguna orden lo haya comprado todavía —por ejemplo, un producto nuevo recién agregado al catálogo, sin ventas registradas— es perfectamente normal y no viola ninguna regla: dim_product no tiene ninguna llave foránea que dependa de orders_s04. Confundir las dos direcciones es un error común: la integridad referencial protege que las referencias salientes de la tabla de hechos (orders) sean válidas, no que la tabla de dimensión (dim_product) esté "completamente usada".
Ejercicio 3 — Nombra la llave primaria y la llave foránea en una relación distinta de Kiosko, sin código. orders_s04 también tiene una columna store_id, y Kiosko tiene una tabla dim_store (S01 a S04). En una frase, nombra cuál es la llave primaria y cuál es la llave foránea en esa relación, siguiendo el mismo patrón que product_id/dim_product.
Ver solución
dim_store.store_id es la llave primaria (identifica, sin ambigüedad, cada tienda de Kiosko); orders_s04.store_id es la llave foránea (cada fila de orders_s04 declara, a través de ese campo, a qué tienda pertenece la venta, y esa declaración solo tiene sentido si el valor existe de verdad en dim_store). Vale la pena notar, como conexión con el módulo 1 de esta guía: la primera vez que Kiosko violó esta relación exacta —S04 en una orden, sin existir todavía en dim_store— fue el incidente de foundations M7 (ValueError: unknown store_id: S04), antes de que S04 se diera de alta formalmente. Este módulo se enfoca en product_id/dim_product porque es la violación que sigue sin resolverse en el archivo actual de S04, pero la misma mecánica de integridad referencial aplicaría igual si store_id volviera a fallar en el futuro.
Resumen y siguiente paso
En esta lección le pusiste vocabulario técnico preciso a la distinción que ya viste en la lección 1: llave primaria, llave foránea, integridad referencial — los términos que el modelo relacional de bases de datos usa desde hace décadas para describir exactamente el problema de ORD-9508. Distinguiste consistency del sentido amplio de "consistencia" en otros contextos (ACID, sistemas distribuidos), y viste, con un ejemplo mínimo en Python puro, la mecánica central que sostiene todo el módulo: comparar un valor contra un conjunto de referencias válidas.
Antes de avanzar deberías poder: usar los términos "llave primaria" y "llave foránea" correctamente, sin confundirlos; y explicar por qué una llave foránea vacía (completeness) es un problema distinto de una llave foránea presente pero inexistente (consistency).
La lección 3 toma este vocabulario y lo enfrenta contra el OrdersSchema real del módulo 2 — con evidencia ejecutada de por qué, sin importar cuántas reglas declarativas le agregues, un esquema de una sola tabla nunca puede cerrar esta brecha.
Recursos
- Wikipedia — "Referential integrity" (definición estándar del término, su origen en el modelo relacional de E.F. Codd). en.wikipedia.org/wiki/Referential_integrity. En inglés.
- DuckDB — documentación oficial, sección de restricciones (
PRIMARY KEY,FOREIGN KEY,REFERENCES) — la sintaxis SQL estándar que expresa esta misma relación dentro del motor. duckdb.org/docs/current/sql/constraints.html. En inglés. data-engineering-foundations-guide, módulo 4 — fuente original dedim_product(product_id,product_name,category,unit_cost) y dedim_store, las dos tablas dimensionales que esta lección usa como ejemplo de llave primaria.src/guides/data-engineering-foundations-guide/workbook/module-04-modeling-your-first-tables/es/. En español.- Módulo 1, lección 3, de esta misma guía — la definición original de las seis dimensiones de calidad de datos, incluida consistency, que esta lección desarrolla a fondo.
src/guides/data-reliability-and-governance-guide/workbook/module-01-when-green-does-not-mean-correct/es/03-six-dimensions-of-data-quality.md. En español. - DISEÑO de esta guía.
src/guides/data-reliability-and-governance-guide/DISENO.md. En español.