Módulo 1: When Green Does Not Mean Correct

Las seis dimensiones de calidad de datos

Descripción

"Los datos están mal" no es una frase útil. No le dice a nadie qué revisar, ni qué tan grave es el problema, ni si dos personas que usan esa frase están hablando siquiera del mismo tipo de falla. Esta lección reemplaza esa frase vaga por seis palabras precisas —completeness, uniqueness, validity, consistency, freshness, accuracy— cada una con una definición exacta y un ejemplo ejecutable. A partir de aquí, en toda esta guía, "los datos están mal" se convierte siempre en una de estas seis afirmaciones específicas, nunca en una queja genérica.

Conexión con el módulo. Este vocabulario es la base de las lecciones 4 a 8 de este módulo, y de la guía completa: la lección 4 clasifica qué dimensiones cubre validate_orders() de foundations; las lecciones 6 y 7 corren esa función sobre el primer archivo real de S04 y clasifican, dimensión por dimensión, qué atrapa y qué no; y cada módulo siguiente de esta guía —Pandera (M2), consistencia (M3), contratos (M4), anomalías (M5), freshness y linaje (M6)— resuelve una o dos de estas seis dimensiones a fondo. Sin este vocabulario preciso, el resto de la guía sería una lista de herramientas sin ningún hilo que las conecte.

Una analogía: la lista de un inspector de restaurantes

Un inspector de sanidad que visita un restaurante no se pregunta "¿se ve rico?" — esa pregunta no le sirve a nadie para decidir si el restaurante es seguro. En vez de eso, trabaja con una lista de ítems concretos y verificables uno por uno: ¿la comida se guarda a la temperatura correcta? ¿los empleados se lavan las manos? ¿las fechas de vencimiento son legibles? ¿el registro de limpieza está completo, sin días faltantes? Cada ítem de esa lista es una pregunta específica, con una respuesta binaria o medible, no una impresión general.

Las seis dimensiones de calidad de datos son esa misma lista, aplicada a una tabla en vez de a una cocina. En vez de preguntar "¿los datos están bien?" —una pregunta tan vaga como "¿se ve rico?"—, un ingeniero de confiabilidad de datos pregunta, una por una: ¿está completo cada campo requerido? ¿cada identificador aparece una sola vez? ¿cada valor respeta el rango y el tipo que le corresponde? ¿cada referencia a otra tabla apunta a algo que existe de verdad? ¿los datos llegaron a tiempo? ¿el valor, aunque técnicamente válido, refleja la realidad? Seis preguntas concretas, cada una verificable por separado, en vez de una sola impresión difusa.

Ejemplo trabajado: las seis dimensiones, una fila rota por cada una

Cada una de las seis filas de este ejemplo tiene, a propósito, exactamente un problema — ninguna tiene dos a la vez, para que cada dimensión quede aislada y sea fácil de reconocer:

# six_dimensions.py
from datetime import datetime

KNOWN_PRODUCT_IDS = {"P001", "P002", "P003", "P004"}
REFERENCE_PRICE_P002 = 1.20  # precio habitual de P002 en la semana canonica de Kiosko
PIPELINE_RUN_AT = "2026-08-16T09:00:00"  # el "ahora" fijo de esta guia -- nunca datetime.now()

completeness_row = {"order_id": "ORD-D1", "unit_price": ""}
uniqueness_seen = {"ORD-D2"}  # order_id que ya se proceso antes en el mismo lote
uniqueness_row_id = "ORD-D2"
validity_row = {"order_id": "ORD-D3", "quantity": -1}
consistency_row = {"order_id": "ORD-D4", "product_id": "P099"}
freshness_row = {"order_id": "ORD-D5", "order_ts": "2026-08-14T09:25:00"}
accuracy_row = {"order_id": "ORD-D6", "product_id": "P002", "unit_price": 60.00}


def check_completeness(row: dict) -> bool:
    """True si el campo requerido esta vacio -- viola completeness."""
    return row["unit_price"] == ""


def check_uniqueness(order_id: str, seen: set[str]) -> bool:
    """True si el order_id ya aparecio antes -- viola uniqueness."""
    return order_id in seen


def check_validity(row: dict) -> bool:
    """True si el valor rompe una regla de rango -- viola validity."""
    return row["quantity"] <= 0


def check_consistency(row: dict) -> bool:
    """True si el product_id no existe en el catalogo -- viola consistency."""
    return row["product_id"] not in KNOWN_PRODUCT_IDS


def check_freshness(row: dict, run_at: str, sla_hours: int) -> bool:
    """True si paso mas tiempo del SLA desde la orden hasta que se corre el check -- viola freshness."""
    hours_elapsed = (datetime.fromisoformat(run_at) - datetime.fromisoformat(row["order_ts"])).total_seconds() / 3600
    return hours_elapsed > sla_hours


def check_accuracy(row: dict, reference: float, tolerance: float) -> bool:
    """True si el valor se aleja demasiado de la linea base -- viola accuracy."""
    return abs(row["unit_price"] - reference) / reference > tolerance


results = [
    ("completeness", "ORD-D1", check_completeness(completeness_row)),
    ("uniqueness", "ORD-D2", check_uniqueness(uniqueness_row_id, uniqueness_seen)),
    ("validity", "ORD-D3", check_validity(validity_row)),
    ("consistency", "ORD-D4", check_consistency(consistency_row)),
    ("freshness", "ORD-D5", check_freshness(freshness_row, PIPELINE_RUN_AT, 24)),
    ("accuracy", "ORD-D6", check_accuracy(accuracy_row, REFERENCE_PRICE_P002, 0.5)),
]

print(f"{'dimension':<14}{'order_id':<10}{'viola?'}")
for dim, oid, viola in results:
    print(f"{dim:<14}{oid:<10}{viola}")

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

dimension     order_id  viola?
completeness  ORD-D1    True
uniqueness    ORD-D2    True
validity      ORD-D3    True
consistency   ORD-D4    True
freshness     ORD-D5    True
accuracy      ORD-D6    True

Las seis filas violan su dimensión correspondiente, cada una por una razón distinta y verificada con una función distinta. Fíjate en que ninguna de las seis funciones de verificación se parece a las otras: check_completeness() pregunta si un campo está vacío; check_uniqueness() pregunta si un identificador ya se vio antes; check_validity() pregunta si un valor respeta un rango; check_consistency() pregunta si una referencia existe en otra tabla; check_freshness() compara dos momentos en el tiempo; check_accuracy() compara un valor contra una línea base de referencia. Seis preguntas, seis mecánicas distintas — y por eso ninguna herramienta única resuelve las seis a la vez sin pensarlas por separado, como vas a ver a lo largo de esta guía.

Las seis dimensiones, definidas con precisión

DimensiónPregunta que respondeNivelEjemplo de esta lección
Completeness¿Están presentes, con contenido real, todos los campos que deberían estarlo?Filaunit_price vacío
Uniqueness¿Cada identificador que debería ser único aparece exactamente una vez?Fila (contra el lote)order_id repetido
Validity¿Cada valor respeta el tipo y el rango que le corresponde?Filaquantity = -1
Consistency¿Cada referencia a otra tabla apunta a algo que existe de verdad ahí?Fila (contra otra tabla)product_id = "P099", inexistente
Freshness¿Los datos llegaron dentro de la ventana de tiempo pactada?Archivo / tabla completaorden del 14, revisada el 16
Accuracy¿El valor, aunque técnicamente válido, refleja lo que de verdad ocurrió?Fila, contra una línea baseunit_price = 60.00 en vez de ~1.20

Fíjate en la columna "Nivel": las primeras cuatro dimensiones —completeness, uniqueness, validity, consistency— se verifican fila por fila (aunque uniqueness y consistency necesitan mirar algo más que la fila sola: un conjunto de IDs ya vistos, o una tabla externa). Freshness, en cambio, es una propiedad del archivo o la tabla completa — no tiene sentido preguntar "¿esta fila individual es fresca?", la pregunta correcta es "¿este archivo, como unidad, llegó a tiempo?". Accuracy vive en un punto intermedio: se mide fila por fila, pero necesita una línea base externa —un precio de referencia, un rango histórico— que ninguna regla de tipo o rango puede derivar por sí sola.

Diagrama: dónde vive cada dimensión

flowchart TB
    subgraph FILA["Nivel fila -- una fila a la vez"]
        A["Completeness\n¿falta contenido?"]
        B["Uniqueness\n¿ya se vio este ID?"]
        C["Validity\n¿el tipo y el rango\nson correctos?"]
    end

    subgraph FILA_CONTRA_OTRA["Nivel fila, contra algo externo"]
        D["Consistency\n¿la referencia existe\nen otra tabla?"]
        E["Accuracy\n¿el valor tiene sentido\ncontra una linea base?"]
    end

    subgraph ARCHIVO["Nivel archivo / tabla completa"]
        F["Freshness\n¿llego a tiempo?"]
    end

Profundización: por qué "válido" y "correcto" no son sinónimos

La distinción más importante de esta lección —y la que más se malinterpreta— es la que separa validity de accuracy. Un valor puede ser perfectamente válido —del tipo correcto, dentro del rango permitido— y estar, al mismo tiempo, completamente equivocado. unit_price = 60.00 en accuracy_row de este ejemplo es exactamente ese caso: 60.00 es un número positivo, es un float legítimo, no rompe ningún rango declarado como "el precio no puede ser negativo". check_validity() —si se le aplicara a esta fila— no encontraría absolutamente nada mal. Y sin embargo, el valor está mal: nadie en Kiosko vende una Energy Bar a sesenta dólares.

Esta distinción no es un tecnicismo — es la razón de fondo por la que la mentira del checkmark verde (lección 2) es posible en primer lugar. Un sistema que solo verifica validity —tipo correcto, rango correcto— va a dejar pasar, con total limpieza, cualquier valor técnicamente posible pero prácticamente absurdo. Detectar eso necesita algo que validity, por diseño, no tiene: una línea base de lo que es normal, contra la cual comparar. Construir esa línea base, de forma determinista y sin Machine Learning, es exactamente el trabajo del módulo 5 de esta guía — pero antes de llegar ahí, necesitas poder nombrar la diferencia con precisión, que es lo que esta lección te dio.

Errores comunes

Tratar "completeness" y "validity" como si fueran la misma dimensión. Qué pasa: alguien ve un campo vacío y lo describe como "un valor inválido", mezclando las dos categorías. Por qué pasa: ambas suenan a "el dato está mal formado", y la diferencia se siente sutil. Cómo detectarlo: si tu reporte de calidad no distingue entre "este campo no tiene ningún valor" (completeness) y "este campo tiene un valor, pero del tipo o rango equivocado" (validity), estás perdiendo información que le importa a quien tiene que corregir el problema en el origen — son causas distintas, con arreglos distintos. Cómo corregirlo: recuerda la distinción exacta de check_nulls_and_types() en foundations M5 (lección 4 de ese módulo): primero se pregunta si el campo tiene contenido (completeness), después si ese contenido tiene el tipo correcto (validity) — son dos preguntas consecutivas, no una sola.

Confundir "consistency" con "accuracy". Qué pasa: alguien ve la fila con product_id = "P099" y la describe como "el precio está mal", o ve la fila con unit_price = 60.00 y la describe como "el producto no existe". Por qué pasa: las dos dimensiones comparten algo en común —ambas necesitan mirar más allá de la fila sola—, lo cual las hace fáciles de mezclar a primera vista. Cómo detectarlo: pregúntate qué tabla o línea base está en juego. Consistency siempre compara contra otra tabla (¿este product_id existe en el catálogo de productos?). Accuracy siempre compara contra una línea base de valores esperados (¿este precio se parece a lo que este producto normalmente cuesta?). Cómo corregirlo: nombra siempre la referencia exacta contra la que estás comparando — si es "otra tabla", es consistency; si es "un rango o promedio histórico", es accuracy.

Pensar que freshness se puede medir fila por fila. Qué pasa: alguien intenta escribir una regla de freshness que se aplique a cada fila individualmente, como si cada orden tuviera su propio SLA de llegada. Por qué pasa: las otras dimensiones de fila —completeness, uniqueness, validity— sí se evalúan una fila a la vez, así que parece natural extender ese patrón a freshness. Cómo detectarlo: si tu código intenta comparar el order_ts de una sola fila contra "ahora" para decidir si esa fila es fresca, estás resolviendo la pregunta equivocada — lo que importa es cuándo llegó el archivo completo, no cuándo ocurrió cada venta individual dentro de él. Cómo corregirlo: freshness se mide una vez por archivo o por corrida, no una vez por fila — el módulo 6 de esta guía construye ese check exactamente a ese nivel.

Ejercicios

Ejercicio 1 — Clasifica sin ejecutar código. Sin mirar el ejemplo trabajado de esta lección, clasifica cada uno de estos cuatro problemas en la dimensión correcta: (a) un archivo que debía llegar el lunes y llega el jueves; (b) una columna email que siempre tiene el formato usuario@dominio pero nunca se verificó si esa persona existe de verdad; (c) dos filas con el mismo número de factura; (d) una columna country_code con el valor "XX", que no es un código ISO real de ningún país.

Ver solución

(a) Freshness — es una propiedad del archivo completo, sobre cuándo llegó respecto a cuándo debía llegar. (b) Ninguna de las seis dimensiones de esta lección lo cubre directamente con los datos que tienes: el formato correcto (usuario@dominio) sería validity, pero confirmar que la persona "existe de verdad" necesitaría una fuente externa que este ejemplo no describe — es una trampa útil para notar que las seis dimensiones no agotan cada pregunta posible sobre un dato, solo las más comunes y accionables. (c) Uniqueness — el mismo identificador (número de factura) apareciendo más de una vez. (d) Validity —si "XX" no está en el conjunto de códigos ISO válidos, es un valor que no respeta el rango/formato permitido para esa columna—, aunque también podría enmarcarse como consistency si existiera una tabla externa de países válidos contra la cual se estuviera comparando; la línea entre las dos depende de si la regla vive "adentro" de la columna (una lista fija de valores permitidos, validity) o se verifica contra una tabla separada (consistency).

Ejercicio 2 — Construye tu propia fila de accuracy. Usando check_accuracy() del ejemplo trabajado, construye una fila con unit_price=1.35 para P002 (cuya referencia es 1.20) y corre la verificación con tolerance=0.5 (50%). ¿Se marca como violación? Después, prueba con unit_price=2.00. Explica la diferencia.

Ver solución
row_1_35 = {"order_id": "ORD-D7", "product_id": "P002", "unit_price": 1.35}
row_2_00 = {"order_id": "ORD-D8", "product_id": "P002", "unit_price": 2.00}

print("1.35:", check_accuracy(row_1_35, REFERENCE_PRICE_P002, 0.5))
print("2.00:", check_accuracy(row_2_00, REFERENCE_PRICE_P002, 0.5))

Salida esperada:

1.35: False
2.00: True

1.35 se desvía de 1.20 en 0.125 ((1.35 - 1.20) / 1.20 = 0.125, o 12.5%), muy por debajo del 50% de tolerancia — no se marca como anomalía, porque una pequeña variación de precio (una promoción, un ajuste menor) es esperable y no debería generar una alerta. 2.00 se desvía en 0.667 (66.7%), por encima del 50% — sí se marca. Este ejercicio demuestra por qué la tolerancia importa tanto como la línea base misma: sin ella, cualquier variación de precio, por mínima que sea, dispararía una alerta — el módulo 5 de esta guía vuelve sobre esta misma idea con el caso real de S04.

Ejercicio 3 — Argumenta cuál dimensión es la más difícil de automatizar, y por qué. De las seis dimensiones de esta lección, ¿cuál crees que es la más difícil de verificar con una regla simple, sin intervención humana? Justifica en 2-3 frases, sin mirar todavía el módulo 5 de esta guía.

Ver solución

Accuracy es, con evidencia consistente en toda esta guía, la más difícil. Las otras cinco dimensiones tienen una respuesta binaria y objetiva que no depende de contexto de negocio —un campo está vacío o no lo está, un ID se repite o no, un tipo es correcto o no—, pero accuracy necesita una línea base externa (¿cuál es el precio "normal"?) y un umbral de tolerancia (¿cuánta desviación es aceptable antes de considerarla sospechosa?), y ambas decisiones requieren juicio de negocio, no solo lógica de programación. Es, precisamente, la razón por la que esta guía le dedica un módulo completo (el 5) solo a esta dimensión, mientras que completeness y validity ya se resolvieron, en gran parte, desde foundations M5.

Resumen y siguiente paso

En esta lección construiste el vocabulario preciso de las seis dimensiones de calidad de datos —completeness, uniqueness, validity, consistency, freshness, accuracy—, cada una con una definición exacta, un ejemplo ejecutado y una función de verificación distinta. Viste, con evidencia directa, por qué "válido" y "correcto" no son sinónimos —la distinción exacta que hace posible la mentira del checkmark verde— y por qué freshness se mide a nivel de archivo, nunca de fila individual.

Antes de avanzar deberías poder: nombrar las seis dimensiones sin ayuda; clasificar un problema de datos nuevo en la dimensión correcta; y explicar, con el ejemplo de unit_price=60.00, por qué un valor puede ser válido y estar mal al mismo tiempo.

Con el vocabulario completo en mano, la lección 4 vuelve a validate_orders() de data-engineering-foundations-guide —la herramienta que ya construiste— y la clasifica, dimensión por dimensión: qué de las seis preguntas responde de verdad, y cuáles deja completamente sin contestar.

Recursos