posts.

El chat siempre responde, y ese es el problema

Poner un chat delante del warehouse parece el último paso que faltaba. Cualquiera pregunta en su idioma, el modelo escribe el SQL, y por fin se acaba la cola de peticiones al equipo de datos.

Y entonces alguien pregunta cuántos clientes activos hubo el trimestre pasado, recibe un número, y lo lleva a una reunión. Nadie sabe qué contó ese número como cliente, ni como activo, ni si excluyó las cuentas de prueba.

La capa conversacional no falla en el lenguaje. Falla en el significado.

El fallo no se ve como error, se ve como una cifra

Un sistema que no entiende una pregunta puede hacer dos cosas: negarse, o responder algo. Un modelo de lenguaje casi siempre hace lo segundo, y lo hace con una prosa impecable.

dbt Labs lo formula así: con text-to-SQL, el fallo tiene la forma de una respuesta plausible pero incorrecta; con una capa declarada, tiene la forma de un mensaje de error.

Es una distinción útil, y conviene saber de quién viene: dbt Labs vende esa capa, y la página que la formula es su propio benchmark comparándose. Volveré sobre eso.

Pero la distinción se sostiene sin creerle ninguna cifra. Un error interrumpe a alguien y se arregla. Una cifra plausible entra en una diapositiva y se propaga.

Preguntar no es acordar

La pregunta en lenguaje natural no elimina las decisiones de modelado. Las desplaza a un sitio donde nadie las revisa.

flowchart TD
    Q["How many active customers<br/>last quarter?"] --> M{"Is 'active customer'<br/>declared anywhere?"}
    M -- yes --> S["Resolved against entity,<br/>grain and filters"]
    M -- no --> G["Inferred from column names<br/>and a best guess"]
    S --> A["One answer, or an error"]
    G --> P["A plausible number"]
theory of small decisions

La rama de la izquierda existe solo si alguien escribió antes qué es un cliente activo. Si nadie lo hizo, el modelo no se detiene a preguntarlo: elige por su cuenta, y esa elección no queda registrada en ningún sitio.

Es la misma tensión que discutí en la ontología de la capa semántica, con una diferencia incómoda. Allí el desacuerdo acababa saliendo a la luz porque dos equipos reportaban distinto.

Aquí cada uno pregunta solo, en su ventana de chat, y nadie compara.

Lo que miden los benchmarks, y lo que no

Conviene mirar los números que sí están medidos con método, y con cuidado de no pedirles lo que no dicen.

El benchmark BIRD, presentado en NeurIPS 2023 y construido sobre bases de datos grandes y sucias, midió que ChatGPT alcanzaba un 40,08% de precisión de ejecución frente al 92,96% de los humanos. Antes, Spider había establecido dónde estaba lo difícil: no en generar SQL, sino en generalizar a esquemas nunca vistos.

Los modelos de hoy puntúan bastante por encima de aquellos. Y el matiz que importa es otro: esos benchmarks no miden el problema de este post. Comparan la consulta generada contra un SQL de oro que un anotador escribió, lo que significa que alguien ya decidió qué quería decir la pregunta.

La ambigüedad de “cliente activo” es exactamente lo que queda fuera de esa medición. El laboratorio mide si el modelo traduce bien una intención ya fijada. En tu empresa, la intención es lo que nadie fijó.

Un agente no puede razonar sobre lo que nadie escribió

La alternativa no es prohibir el chat. Es escribir la definición en algún sitio, y que ese sitio sea el que el agente consulta.

# La pregunta del principio, resuelta contra algo escrito.
metric:
  name: active_customers
  entity: customer
  grain: "one row per account, not per contract"
  filters:
    - "status = 'active'"
    - "is_test = false"
  time_dimension: subscription_period

Diez líneas que un chat no puede inventar de tres maneras distintas. Con esto delante, la pregunta del primer párrafo tiene una sola resolución posible.

flowchart LR
    Q["active customers<br/>last quarter"] --> E["entity: customer"]
    E --> G["grain: one per account"]
    G --> F["filters: active,<br/>not test"]
    F --> T["time: subscription_period"]
    T --> A["One resolved query"]
theory of small decisions

Cada paso de esa cadena es una decisión que alguien tomó una vez. Sin el archivo, el modelo toma las cinco por su cuenta y en cada pregunta puede tomarlas distinto.

Conviene decir de dónde viene la evidencia de esta mitad del post. Cube y Malloy documentan este enfoque, y ambos venden productos que lo implementan. Open Semantic Interchange es un estándar neutral respecto al proveedor, pero neutral significa aquí neutral entre proveedores: lo impulsan Snowflake, dbt Labs, Databricks y Salesforce.

Cuatro de las siete fuentes de este post venden la solución que propone. No conozco una medición independiente y revisada por pares de cuánto mejora una capa declarada — incluido el benchmark de dbt que cité arriba. El argumento hay que juzgarlo por su mecanismo, no por sus cifras.

Lloyd Tabb, que creó LookML y Looker antes que Malloy, expone el mecanismo en un seminario de investigación y no en una demo de producto.

Su argumento es que SQL obliga a reconstruir el significado en cada consulta, y que declararlo una vez cambia quién tiene que acertar.

Modos de fallo que conviene nombrar

SíntomaSuele significar
Dos personas preguntan lo mismo y obtienen cifras distintasCada consulta reinventó la definición, y nadie las comparó
El chat nunca dice “no sé”No hay nada declarado contra lo que fallar
La respuesta cambia al reformular la preguntaEl modelo está infiriendo el filtro del enunciado
Nadie sabe qué contó como “cliente” en una cifra que ya circulóLa decisión se tomó en una ventana de chat, no en el modelo
El equipo de datos audita respuestas en vez de definicionesSe revisa la salida porque la entrada no está escrita
Funciona en la demo y falla con el esquema realLa demo no tenía columnas ambiguas que desempatar

El patrón es el mismo en las seis: se pide al modelo que decida algo que nadie acordó.

Por dónde empezar

No conectes el chat todavía. Toma las tres preguntas que más te llegan por Slack y escribe para cada una un bloque como el de arriba: qué entidad responde, con qué grano y con qué filtros.

Si al escribirlas descubres que dos personas del equipo no coinciden, acabas de encontrar lo que el chat habría respondido igualmente, sin avisar a nadie.

Referencias

  1. dbt Labs — Semantic Layer vs. Text-to-SQL: 2026 Benchmark Update, dbt Developer Blog
  2. Cube Dev — Introduction, Cube Documentation
  3. Jinyang Li et al. — Can LLM Already Serve as A Database Interface? A BIg Bench for Large-Scale Database Grounded Text-to-SQLs, NeurIPS 2023
  4. Tao Yu et al. — Spider: A Large-Scale Human-Labeled Dataset for Complex and Cross-Domain Semantic Parsing and Text-to-SQL Task, EMNLP 2018
  5. Malloy Data — Querying a Semantic Model, Malloy Documentation
  6. Open Semantic Interchange — Introduction, OSI specification
  7. Lloyd Tabb — Malloy: A Modern Open Source Language for Analyzing, Transforming, and Modeling Data, CMU Database Group, 2025
Capa semántica - Text-to-SQL - Producto de datos - LLM

Gold es una ubicación, no una garantía

La arquitectura medallion ordena el lakehouse en tres capas y cualquiera sabe leerlas sin que se las expliquen: bronze es lo que llegó, silver lo que ya está limpio, gold lo que se puede usar.

Y entonces dos equipos consultan la misma tabla de gold y reportan cifras distintas. O alguien descubre, en mitad de un incidente, que nadie sabe quién aprueba un cambio ahí.

La capa dice dónde está el dato. No dice qué promete.

Lo que la documentación promete, y lo que no

Conviene leer la fuente antes de discutir con ella. La documentación de Databricks es explícita sobre el estatus del patrón: seguirlo es “a recommended best practice but not a requirement”. Una convención de organización, no un mecanismo.

Sobre las garantías, esa misma página afirma que “this architecture guarantees atomicity, consistency, isolation, and durability”. Y ahí hay algo que merece detenerse.

ACID no lo da la disposición en capas. Lo da el formato de tabla que hay debajo, que es una pieza distinta y sustituible. Nombrar tres carpetas no aporta atomicidad, ni aislamiento, ni durabilidad. La frase atribuye a la arquitectura una propiedad que provee otra cosa, y la página no dice cuál.

Es el mismo desplazamiento que el resto del post va a describir, cometido en la documentación oficial. Y aun concediéndolo entero, ACID no habla de que el número sea correcto, de que el grano sea el que crees, ni de que alguien responda por él.

La validación sí aparece, pero repartida: bronze se describe con validación mínima, y es en silver donde entran la aplicación de esquema, la deduplicación y las comprobaciones de calidad. De gold, esa página dice que representa vistas muy refinadas que alimentan análisis, dashboards y aplicaciones. Refinar no es certificar.

La confianza se infiere de la ruta

El problema no está en las capas. Está en lo que el nombre provoca en quien lee, y el salto entero cabe en cuatro pasos.

flowchart LR
    Q{"Can I trust<br/>this GOLD table?"} --> A["It sits in GOLD"]
    A --> U["Which checks ran?<br/>Who owns it?<br/>What grain?"]
    U --> N["Unstated"]
theory of small decisions

Nadie defiende ese razonamiento en voz alta; simplemente ocurre, porque una palabra como gold hace un trabajo retórico que la documentación nunca le pidió. Ananth Packkildurai, que escribe sobre plataformas de datos en Data Engineering Weekly, lo dice sin rodeos: “data architecture is not a medal competition.”

Y como la confianza se infiere en vez de declararse, no hay nada que falle cuando deja de ser cierta. Una tabla de gold que ya no cumple lo que alguien supuso sigue estando en gold.

Una garantía se declara, no se ubica

La alternativa no es otra capa. Es escribir lo que la tabla promete, en un sitio donde una máquina pueda leerlo.

Eso es un contrato de datos. Bitol, el estándar en incubación en la Linux Foundation, lo define como un acuerdo entre quien produce el dato y quienes lo consumen, que declara las reglas de calidad que debe cumplir, los niveles de servicio, los roles y la propiedad. La diferencia con la capa es que aquí hay un sujeto: alguien se compromete a algo concreto.

El mismo movimiento existe al nivel de la aserción. Great Expectations formaliza las expectativas como afirmaciones verificables sobre un dataset — literalmente “unit tests for your data”. Una expectativa se cumple o no se cumple, y eso es exactamente lo que una carpeta no puede hacer.

Conviene no repetir aquí el error que se critica. Un contrato declara, y su cumplimiento depende de las herramientas que lo ejecuten. El archivo YAML no es la garantía.

Pero una promesa escrita y comprobable es una categoría distinta de una promesa inferida de una ruta.

Esa distinción es todo el argumento. Es la misma tensión que discutí en la ontología de la capa semántica: lo visible no es lo que sostiene el peso.

Gunnar Morling, que trabajó en Debezium, desarrolla la idea desde el lado del productor.

El productor no expone su esquema interno esperando que nadie se apoye en él: decide qué promete y mantiene esa promesa estable aunque cambie por dentro.

Promover en vez de depositar

Hay un patrón que convierte esa promesa en un paso del pipeline. La documentación de Apache Iceberg especifica write-audit-publish: las escrituras van a una rama de auditoría aislada del historial principal, un flujo de validación comprueba ese estado, y solo entonces se promueve.

flowchart LR
    W["Write to audit branch"] --> V{"Run declared<br/>expectations"}
    V -- pass --> P["Publish to main"]
    V -- fail --> D["Discard branch"]
    P --> R["Consumer reads<br/>a checked promise"]
theory of small decisions

El verbo es lo que cambia. El dato no se deposita en un sitio de confianza: se gana el derecho a estar ahí. La garantía es el paso de validación, no el destino.

Modos de fallo que conviene nombrar

SíntomaSuele significar
Dos equipos leen de gold y reportan distintoLa capa no declara grano ni filtros; cada uno asume los suyos
Nadie sabe quién aprueba un cambio en una tabla goldHay una ruta, no un propietario
El consumidor detecta el fallo antes que el productorNo hay expectativas verificadas antes de promover
En el postmortem aparece “está en gold, debía estar bien”Confianza inferida de la ubicación
Se propone añadir una capa platinumSe busca en la disposición lo que solo puede estar en la promesa
La tabla tiene contrato y aun así sorprende a quien la usaDeclara expectativas que nadie ejecuta
Un dato correcto llega tarde y nadie se enteraLa capa habla de calidad, no de frescura ni de SLA

El patrón es el mismo en las siete: alguien confía en una promesa que nadie está comprobando.

Por dónde empezar

No migres nada. Toma la tabla de gold que más gente consulta y escribe, en un archivo junto a ella, tres frases: qué significa una fila, qué garantiza sobre frescura, y quién responde cuando deja de cumplirse.

Luego convierte una de las tres en una comprobación que se ejecute con cada carga. Cuál elijas importa menos que el hecho de que pueda fallar sola: hasta que algo falle, sigues teniendo una ubicación con buen nombre.

Lakehouse - Contratos de datos - Calidad de datos - Apache Iceberg

C4 para arquitectura de datos: el contenedor equivocado

Adoptar C4 se vende como el final del caos de diagramas. Cuatro niveles, cada uno con su audiencia, y por fin todo el mundo dibuja la misma cosa.

En una plataforma de datos funciona a medias. El diagrama sale, la gente lo aprueba en la revisión, y seis meses después nadie lo abre para decidir nada.

El nivel de detalle no era el problema.

Todos los diagramas de plataforma de datos se parecen

Dibuja el nivel de contenedores de tres plataformas de tres empresas distintas y saldrán tres versiones del mismo dibujo: una capa de ingesta, un almacenamiento de objetos, un motor de procesamiento, un almacén analítico, un orquestador y una herramienta de BI.

flowchart LR
    F["Source systems"] --> I["Ingestion"]
    I --> L["Object storage"]
    L --> P["Processing engine"]
    P --> W["Analytical warehouse"]
    W --> B["BI and consumption"]
    O["Orchestrator"] -.-> I
    O -.-> P
theory of small decisions

El dibujo es correcto y no dice nada. Dos plataformas con esa misma forma pueden ser opuestas en lo único que importa: quién es dueño de cada dato, a qué grano vive, qué se promete sobre él y qué se rompe cuando alguien lo cambia.

Un diagrama que no distingue dos arquitecturas distintas no está describiendo ninguna de las dos.

Lo que C4 sí resuelve, y no son las cajas

Vale la pena separar lo que C4 aporta de lo que se le atribuye. Su aporte real es el alcance: cada diagrama tiene un solo nivel de abstracción y una sola audiencia, y eso elimina el diagrama de cuarenta cajas donde conviven un equipo, un endpoint y una tabla.

Además es independiente de la notación y de la herramienta, lo cual importa más de lo que parece: la discusión sobre qué forma tiene cada caja consume más tiempo del que nadie admite.

La charla insiste en algo que se olvida al adoptarlo: los niveles son mapas a distintas escalas del mismo territorio. Si el territorio que te importa no son los procesos que corren, el mapa te va a salir vacío por mucho que hagas zoom.

El contenedor de C4 es un sitio, no un dato

La definición es explícita: un contenedor es una aplicación o un almacén de datos, algo que necesita estar ejecutándose para que el sistema funcione. Una base de datos cuenta. Un sistema de archivos cuenta.

Esa es una abstracción de runtime, y por eso la plantilla se llena con Kafka, Spark y el warehouse. Pero las decisiones que definen una arquitectura de datos no están en el runtime: la identidad de una entidad, el grano de una tabla y el contrato de un dataset no necesitan estar ejecutándose para existir, y son lo que en silencio sostiene cada métrica.

C4 no tiene una caja para eso. No es un defecto del modelo: es que fue diseñado para sistemas donde el comportamiento vive en el código y el dato es un detalle de persistencia. En una plataforma de datos esa relación está invertida.

Elegir el dataset como contenedor, y lo que cuesta

La alternativa es hacer una sustitución deliberada: el contenedor pasa a ser el producto de datos, los componentes son los modelos que lo construyen, y el runtime baja al diagrama de despliegue, que es donde C4 ya lo esperaba.

flowchart TB
    subgraph D1["Domain: Sales"]
        PD1["Data product: Orders<br/>grain: one row per order"]
        M1["model: orders_clean"] --> PD1
        M2["model: orders_enriched"] --> PD1
    end
    subgraph D2["Domain: Finance"]
        PD2["Data product: Revenue<br/>grain: one row per line"]
    end
    PD1 -->|"contract v2<br/>daily SLA"| PD2
    PD2 --> C["Consumers"]
theory of small decisions

Este diagrama sí cambia cuando cambia la arquitectura, porque las flechas son promesas entre equipos y no rutas de red. Y tiene un coste real: pierdes de vista la operación. Nadie mira este dibujo para entender por qué falló el job de anoche, y quien busque eso va a sentir que el diagrama le miente.

Merece la pena mientras la pregunta que llega sea de diseño. Cuando la pregunta es de guardia, el diagrama que sirve es otro.

El nivel 4 no se dibuja, se genera

C4 ya dice que el nivel de código es opcional y debería salir de la herramienta, no de la mano de nadie. En datos esa recomendación es más fuerte todavía, porque el nivel 4 es el linaje, y el linaje cambia cada semana.

Un dbt project ya lo produce: dbt docs generate compila el proyecto y exporta el DAG completo desde el repositorio. Un diagrama de linaje dibujado a mano es incorrecto el mismo mes en que se dibuja, y peor que no tenerlo, porque alguien lo va a creer.

Modos de fallo que conviene nombrar

SíntomaSuele significar
El diagrama sirve para cualquier empresa del sectorSe modeló el runtime, no la arquitectura
Cada equipo mantiene su propia versión del mismo dibujoNunca se acordó qué es un contenedor aquí
El diagrama se actualiza solo antes de una auditoríaNo se usa para decidir, se usa para justificar
El linaje dibujado no coincide con el warehouseNivel 4 hecho a mano en lugar de generado
“¿Dónde va esta tabla?” preguntado en cada revisiónFalta el nivel de dominio entre sistema y modelo
Nadie sabe a quién pedirle un cambioLas cajas no tienen dueño, solo tecnología

El patrón en las seis: el diagrama describe dónde corre el dato en lugar de qué promete.

Por dónde empezar

No adoptando C4 entero. Toma un solo dominio, dibuja su nivel de contenedores dos veces —una con las tecnologías y otra con los productos de datos— y enséñaselas a alguien que no trabaje en él.

La segunda le va a provocar preguntas y la primera no. Esa diferencia es todo el argumento, y descubrirla cuesta una tarde en lugar de un trimestre de estandarización.

C4 - Modelado de datos - dbt - Contratos de datos