Módulo 1: When Green Does Not Mean Correct

Corriendo la compuerta vieja sobre S04

Descripción

Es momento de abrir el archivo real. Esta lección presenta las doce líneas exactas de orders_2026-08-14.csv —el primer archivo de ventas de S04 como tienda ya reconocida— y corre validate_orders() de foundations, sin ningún cambio, de verdad, sobre ellas. No se trata de un ejercicio hipotético ni de una predicción: vas a ver la salida literal, byte a byte, de correr el mismo código que ya construiste en data-engineering-foundations-guide.

Conexión con el módulo. Esta lección confirma, con evidencia real, la predicción que armaste en la lección 4 (qué dimensiones cubre validate_orders()) sobre el caso concreto que presentó la lección 5 (S04 y su primer archivo). La lección 7 toma el resultado exacto de esta lección y lo analiza a fondo: qué se coló entre las filas que pasaron.

El material: orders_2026-08-14.csv, doce líneas, estructura fija

Guarda este archivo exactamente como está —doce líneas de datos, ninguna generada al azar— en la misma carpeta que tu kiosko.py:

order_id,store_id,product_id,quantity,unit_price,order_ts
ORD-9501,S04,P001,3,0.55,2026-08-14T08:05:00
ORD-9502,S04,P002,2,1.20,2026-08-14T08:12:00
ORD-9503,S04,P003,1,,2026-08-14T08:19:00
ORD-9504,S04,P004,1,4.50,2026-08-14T08:27:00
ORD-9505,S04,P001,2,0.55,2026-08-14T08:34:00
ORD-9506,S04,P003,3,0.75,2026-08-14T08:41:00
ORD-9507,S04,P001,-1,0.55,2026-08-14T08:49:00
ORD-9508,S04,P099,2,1.00,2026-08-14T08:56:00
ORD-9509,S04,P002,1,60.00,2026-08-14T09:04:00
ORD-9502,S04,P002,2,1.20,2026-08-14T09:11:00
ORD-9510,S04,P004,2,4.50,2026-08-14T09:18:00
ORD-9511,S04,P002,1,1.20,2026-08-14T09:25:00

Doce líneas, las doce de S04, ninguna de las otras tres tiendas mezclada. Antes de correr nada, vale la pena mirar el archivo con los ojos entrenados de las lecciones 2 a 5 —no para adivinar el resultado, sino para practicar la lectura crítica que esta guía completa entrena—:

  • ORD-9503 tiene una coma doble en unit_price (P003,1,,2026-08-14...) — el campo está presente en el esquema, pero vacío.
  • ORD-9507 tiene quantity=-1 — un valor negativo donde solo tiene sentido un entero positivo.
  • ORD-9508 tiene product_id=P099 — un producto que no aparece en el catálogo de cuatro productos de Kiosko (P001 a P004).
  • ORD-9509 tiene product_id=P002 con unit_price=60.00 — sesenta dólares por una Energy Bar.
  • ORD-9502 aparece dos veces: primero en la segunda línea del archivo, y otra vez en la décima — el mismo order_id, la misma tienda, el mismo producto, la misma cantidad, el mismo precio, con siete minutos de diferencia en order_ts — el patrón exacto de una retransmisión duplicada, no de dos ventas distintas.

Cinco problemas nombrados, sobre seis filas físicas (ORD-9502 cuenta dos veces, una por cada aparición) — exactamente la estructura de doce líneas, seis limpias y seis rotas, una por cada dimensión de calidad, que define esta guía. La sexta dimensión —freshness— ya la confirmaste en la lección 5: es una propiedad del archivo completo, no de ninguna fila individual, así que no aparece como una línea "marcada" dentro de este CSV.

Ejemplo trabajado: validate_orders(), corrida real sobre S04

# diagnose_s04.py
import csv
from kiosko import validate_orders, print_validation_report

with open("orders_2026-08-14.csv", newline="") as f:
    rows = list(csv.DictReader(f))

print(f"Filas leidas de orders_2026-08-14.csv: {len(rows)}\n")

valid, rejected = validate_orders(rows)
print_validation_report(valid, rejected)

Qué esperar. Al correr python3 diagnose_s04.py en la carpeta donde guardaste orders_2026-08-14.csv (con kiosko.py de la lección 4 en la misma carpeta), la salida es exactamente esta:

Filas leidas de orders_2026-08-14.csv: 12

=== Kiosko: reporte de validacion ===
Filas totales: 12
Validas: 9
Rechazadas: 3

=== Detalle de filas rechazadas ===
ORD-9503:
  - field 'unit_price' is null or empty
ORD-9507:
  - quantity must be > 0, got -1
ORD-9502:
  - duplicate order_id 'ORD-9502'

Lee este resultado con el mismo cuidado que ya entrenaste en foundations M5: doce filas leídas, nueve terminan en valid, tres en rejected, cada una con su motivo exacto. ORD-9503 se rechaza por unit_price vacío — exactamente la verificación de check_nulls_and_types() que ya conoces, cubriendo completeness. ORD-9507 se rechaza por quantity must be > 0, got -1check_business_rules(), cubriendo validity. Y ORD-9502 se rechaza por duplicate order_id — pero fíjate en un detalle importante: solo una de las dos apariciones de ORD-9502 aparece en rejected. La primera (línea 2 del archivo, 08:12:00) pasó sin problema, porque en ese momento seen_order_ids todavía no la conocía. La segunda (línea 10, 09:11:00) es la que se marca — exactamente el mismo comportamiento que ya confirmaste con ORD-8001 en el proyecto de foundations M5.

El detalle que importa: qué quedó adentro de valid

Validas: 9 sin ningún detalle adicional no es suficiente para diagnosticar nada — es exactamente el tipo de conteo desnudo contra el que ya advirtió la lección 7 de foundations M5. Vale la pena mirar, fila por fila, qué son esas nueve:

# inspect_valid.py -- agregado al final de diagnose_s04.py
print("\n=== Las 9 filas que quedaron en 'valid' ===")
for row in valid:
    print(f"  {row['order_id']} | product_id={row['product_id']} | unit_price={row['unit_price']} | quantity={row['quantity']}")

Qué esperar. Agregando este bloque al final de diagnose_s04.py y corriéndolo de nuevo, después del reporte ya conocido, la salida adicional es exactamente esta:

=== Las 9 filas que quedaron en 'valid' ===
  ORD-9501 | product_id=P001 | unit_price=0.55 | quantity=3
  ORD-9502 | product_id=P002 | unit_price=1.20 | quantity=2
  ORD-9504 | product_id=P004 | unit_price=4.50 | quantity=1
  ORD-9505 | product_id=P001 | unit_price=0.55 | quantity=2
  ORD-9506 | product_id=P003 | unit_price=0.75 | quantity=3
  ORD-9508 | product_id=P099 | unit_price=1.00 | quantity=2
  ORD-9509 | product_id=P002 | unit_price=60.00 | quantity=1
  ORD-9510 | product_id=P004 | unit_price=4.50 | quantity=2
  ORD-9511 | product_id=P002 | unit_price=1.20 | quantity=1

Detente en dos de estas nueve líneas antes de seguir. ORD-9508, con product_id=P099 — ya sabes, porque lo leíste en "El material" de esta lección, que P099 no existe en el catálogo de cuatro productos de Kiosko. Y sin embargo, ahí está, adentro de valid, sin ninguna marca, sin ningún motivo de rechazo adjunto. ORD-9509, con unit_price=60.00 para una Energy Bar (P002) — el mismo producto que, en las otras tres apariciones de este mismo archivo (ORD-9502 dos veces, ORD-9511), se vendió a 1.20. Una diferencia de cincuenta veces el precio habitual, dentro del mismo archivo, del mismo producto, el mismo día — y también sin ninguna marca. Estas dos filas son la confirmación exacta, con datos reales, de la predicción que armaste en la lección 4: validate_orders() nunca preguntó nada sobre consistencia referencial ni sobre precios anómalos, así que estas dos filas pasan con la misma limpieza que cualquier fila perfectamente correcta.

Diagrama: las doce filas, clasificadas por resultado real

flowchart TD
    A["12 filas de orders_2026-08-14.csv"] --> B["validate_orders()"]
    B --> C["rejected: 3 filas"]
    B --> D["valid: 9 filas"]

    C --> C1["ORD-9503: completeness"]
    C --> C2["ORD-9507: validity"]
    C --> C3["ORD-9502 (2a aparicion): uniqueness"]

    D --> D1["6 filas realmente limpias"]
    D --> D2["ORD-9502 (1a aparicion):\nparte del par duplicado,\npero esta SI es valida"]
    D --> D3["ORD-9508: product_id=P099\n(consistency, sin marcar)"]
    D --> D4["ORD-9509: unit_price=60.00\n(accuracy, sin marcar)"]

El diagrama separa, dentro del bloque valid de nueve filas, tres categorías: las seis genuinamente correctas, la primera aparición de ORD-9502 (que es válida por sí sola — el problema es que se repite, no que ella individualmente esté mal), y las dos filas con problemas reales que la compuerta no detecta. Fíjate en que, desde la perspectiva de validate_orders(), las nueve filas de valid son indistinguibles entre sí — la función no tiene ninguna forma de decirte "estas seis están genuinamente bien, mientras que estas otras tres solo pasaron porque no les pregunté lo correcto". Ese es, precisamente, el problema que la lección 7 analiza a fondo.

Profundización: por qué el conteo 9/12 ya es, en sí mismo, información incompleta

Alguien que solo mira Validas: 9, Rechazadas: 3 —sin nunca imprimir el detalle de valid, como hizo esta lección— tiene una impresión razonablemente tranquilizadora: 75% de las filas pasaron limpio. Esa impresión es matemáticamente correcta y, al mismo tiempo, engañosa en el sentido más importante: de esas nueve filas "limpias", dos tienen problemas reales de negocio —un producto inexistente, un precio con un error de dos órdenes de magnitud— que ningún número en el reporte de print_validation_report() revela. El reporte no miente; simplemente no fue diseñado para hacer las preguntas que revelarían esos dos problemas.

Esta es, con un caso real y ejecutado, la mentira del checkmark verde que nombró la lección 2 de este módulo: Validas: 9 es un dato honesto sobre cuántas filas pasaron las cuatro verificaciones que validate_orders() sabe hacer. No es, ni pretende ser, una afirmación sobre si esas nueve filas son correctas en el sentido más amplio del negocio. La distancia entre esas dos afirmaciones —"pasó las verificaciones que existen" contra "es correcto"— es exactamente el espacio que el resto de esta guía llena, módulo por módulo.

Errores comunes

Detenerse en Rechazadas: 3 y no revisar el contenido de valid. Qué pasa: alguien corre diagnose_s04.py, ve el reporte de rechazos, y da por completo el diagnóstico del archivo sin llegar a imprimir ni inspeccionar las nueve filas de valid. Por qué pasa: print_validation_report() ya se siente como "el reporte completo" — es fácil olvidar que solo describe la mitad rechazada, nunca la mitad aceptada. Cómo detectarlo: si tu resumen del archivo de S04 es "tres filas rotas, nueve limpias", sin ningún matiz adicional, te falta la observación central de esta lección — dos de esas nueve "limpias" tienen problemas reales. Cómo corregirlo: cualquier vez que uses validate_orders() sobre datos que no conoces a fondo, inspecciona también valid, no solo rejected — el bloque de código de esta lección que imprime cada fila de valid es exactamente ese hábito.

Sorprenderse de que ORD-9502 no aparezca dos veces en rejected. Qué pasa: alguien espera ver ambas apariciones de ORD-9502 en el detalle de filas rechazadas, y se confunde al ver solo una. Por qué pasa: intuitivamente, si un order_id está "duplicado", parece que ambas copias deberían tratarse igual. Cómo detectarlo: repasa el diagrama de flujo de validate_orders() en foundations M5 (lección 6 de ese módulo) — la verificación de duplicados compara contra seen_order_ids, que se llena mientras la función procesa filas en orden. La primera aparición no tiene nada contra qué compararse todavía. Cómo corregirlo: recuerda la regla exacta, ya establecida en foundations: la función siempre marca como duplicada la aparición posterior, nunca la primera — el mismo comportamiento que ya viste con ORD-8001 en esa guía, ahora confirmado de nuevo con ORD-9502.

Culpar al archivo CSV por tener "datos mal escritos". Qué pasa: alguien describe orders_2026-08-14.csv como un archivo con errores de formato o de escritura, como si el problema fuera sintáctico. Por qué pasa: doce líneas con seis problemas se siente, a primera vista, como "un archivo descuidado". Cómo detectarlo: revisa cada una de las seis filas problemáticas de "El material" de esta lección — ninguna tiene un error de sintaxis CSV (comillas mal cerradas, columnas de más o de menos). Cada una es sintácticamente perfecta y, aun así, tiene un problema de contenido: un campo vacío, un valor fuera de rango, una referencia inexistente, un precio anómalo, una retransmisión. Cómo corregirlo: distingue siempre entre "el archivo está mal formado" (un problema de sintaxis, que ni siquiera csv.DictReader podría leer) y "el archivo está bien formado pero tiene contenido incorrecto" (el problema real de esta lección, y de toda esta guía) — son categorías completamente distintas de falla.

Ejercicios

Ejercicio 1 — Confirma el conteo de filas rotas por dimensión, a mano. Sin volver a correr ningún código, cuenta cuántas de las doce filas de "El material" de esta lección corresponden a cada una de las seis dimensiones de calidad de la lección 3 (incluida freshness, aunque no sea una fila individual). Confirma que el total de filas "con problema" (sin contar freshness, que es del archivo completo) es seis.

Ver solución
  • Completeness: ORD-9503 (1 fila).
  • Uniqueness: las dos apariciones de ORD-9502 (2 filas).
  • Validity: ORD-9507 (1 fila).
  • Consistency: ORD-9508 (1 fila).
  • Accuracy: ORD-9509 (1 fila).
  • Freshness: no es una fila — es una propiedad del archivo completo, ya confirmada en la lección 5.

Total de filas físicas involucradas en algún problema: 1 + 2 + 1 + 1 + 1 = 6, exactamente la mitad de las doce líneas del archivo — la estructura de "seis limpias, seis rotas" que define esta guía.

Ejercicio 2 — Reescribe el reporte para contar duplicados sin doble conteo. El reporte de print_validation_report() cuenta ORD-9502 una sola vez en rejected (la segunda aparición). Escribe un pequeño script que cuente cuántos order_id distintos aparecen en las doce filas del archivo, y compáralo con el total de filas (12).

Ver solución
order_ids = [row["order_id"] for row in rows]
distinct_order_ids = set(order_ids)
print(f"Filas totales: {len(rows)}")
print(f"order_id distintos: {len(distinct_order_ids)}")

Salida esperada:

Filas totales: 12
order_id distintos: 11

Doce filas, once identificadores distintos — la diferencia de uno confirma, por un camino de conteo completamente separado del de validate_orders(), que exactamente un order_id (ORD-9502) se repite. Esta es una buena práctica general: cuando sea posible, confirma un resultado de calidad de datos con un método de conteo independiente del que ya usaste — si los dos coinciden, tienes más confianza en el resultado.

Ejercicio 3 — Argumenta por qué ORD-9502 (primera aparición) NO debería considerarse "sospechosa" por sí sola. En 2-3 frases, explica por qué sería un error tratar la primera aparición de ORD-9502 —la que sí queda en valid— como si tuviera, individualmente, algún problema de calidad.

Ver solución

La primera aparición de ORD-9502 (línea 2 del archivo, 08:12:00) es, por sí sola, una fila perfectamente correcta: todos los campos presentes, tipos correctos, product_id válido, precio dentro de lo esperado. El problema de uniqueness no es una propiedad de esa fila individual — es una propiedad de la relación entre dos filas (esta y su segunda aparición, en 09:11:00). Tratar la primera aparición como "sospechosa" confundiría una fila individualmente correcta con el patrón más amplio del que forma parte, exactamente el mismo error conceptual que ya advirtió el Ejercicio 1 de la lección 6 de foundations M5 sobre cuál aparición se marca como duplicada.

Resumen y siguiente paso

En esta lección corriste validate_orders() de verdad, sin ningún cambio, sobre las doce líneas reales de orders_2026-08-14.csv: nueve filas válidas, tres rechazadas, cada una con su motivo exacto. Y, yendo más allá del conteo desnudo, inspeccionaste el contenido de esas nueve "válidas" y confirmaste, con evidencia directa, que dos de ellas —ORD-9508 (P099) y ORD-9509 (60.00)— tienen problemas de negocio reales que ninguna de las cuatro verificaciones de la compuerta pudo detectar.

Antes de avanzar deberías poder: reproducir el reporte exacto de esta lección corriendo el código tú mismo; explicar por qué ORD-9502 aparece una sola vez en rejected, no dos; y nombrar, sin mirar el código de nuevo, cuáles dos filas de valid tienen problemas reales sin estar marcadas.

Tienes la evidencia completa. La lección 7 analiza a fondo esas dos filas silenciosas —por qué específicamente se le escapan a validate_orders(), y qué tipo de herramienta necesitaría existir para atraparlas—, cerrando el diagnóstico completo de este módulo.

Recursos

  • data-engineering-foundations-guide, módulo 5 (data-quality-gates) — fuente de validate_orders(), corrida sin ningún cambio en esta lección. src/guides/data-engineering-foundations-guide/workbook/module-05-data-quality-gates/es/. En español.
  • Python — documentación oficial de csv.DictReader, la herramienta de lectura usada en el ejemplo trabajado. docs.python.org/3/library/csv.html. En inglés.
  • Joe Reis & Matt Housley, Fundamentals of Data Engineering (O'Reilly, 2022) — el marco de calidad de datos que sostiene por qué un conteo agregado nunca reemplaza la inspección del contenido real. oreilly.com/library/view/fundamentals-of-data/9781098108298. En inglés.