04 / Escritura
Publicado 19 ago 2026 · Actualizado 19 ago 2026 · 4 min de lectura
Entiende el grain antes de modelar
La mayoría de los bugs de warehouse son bugs de grain. Una forma práctica de nombrar la entidad, el tiempo y la decisión que una tabla puede responder.
DATA
Si pudiera guardar un hábito de la física y del analytics engineering, sería este: nombra la unidad antes de escribir la transformación. En un laboratorio, mezclar “por muestra” con “por lote” arruina un experimento. En un warehouse, mezclar “por evento” con “por cliente-día” arruina una métrica — en silencio, durante meses.
El grain no es un gusto de estilo en dbt. Es el contrato que dice qué significa una fila. Hasta que esa frase sea aburridamente precisa, los joins se verán correctos y seguirán estando mal.
Una fila es una afirmación
Cada tabla afirma algo como: esta fila es una conversación, o una cuenta al día operativo, o una promesa de pago. Si dos personas del mismo equipo dibujarían llaves distintas, la tabla no tiene grain. Tiene forma de spreadsheet.
Escribo la afirmación arriba del modelo, en un lenguaje que un product manager pueda cuestionar:
Una fila = una conversación outbound, identificada por
conversation_id, válida en el timestampclosed_at.
No: “conversaciones más unos campos de usuario.” Los campos extra son atributos de ese grain, o pertenecen a otra tabla.
El fan-out es cómo se inflan las métricas
El fallo clásico es unir un fact a grain de evento con una dimensión que en realidad es un fact más grueso, o al revés. Promesas de pago ligadas a una conversación, luego unidas a cada mensaje del hilo: el monto prometido aparece cinco veces. Un dashboard de “total prometido” se ve sano. Operaciones no puede reconciliarlo con caja.
Trato un conteo de filas inesperado después de un join como señal de parar. Confiabilidad antes que ingenio: un distinct en la herramienta de BI no es un modelo. Es una disculpa.
Cuando necesito ambos grains, guardo dos tablas. Un mart de conversación responde “qué pasó en este diálogo.” Un mart de cuenta-día responde “a quién llamar hoy.” Juntarlos en una tabla “ancha de todo” se siente eficiente y se vuelve una fuente de fan-out silencioso.
El tiempo es parte del grain
“Cliente” no es un grain. “Cliente a una fecha” sí. Los atributos que cambian despacio (segmento, agente, tier) hacen que un join sin tiempo sea una apuesta.
La corrección point-in-time no exige un Kimball de libro el día uno. Sí exige no unir la dimensión de hoy a eventos del mes pasado y llamarlo historia. Si la decisión es retrospectiva (“qué creíamos cuando prometimos”), el modelo necesita ventanas de validez o snapshots. Si la decisión es solo operativa hoy, dilo — y no reutilices la tabla para el review del trimestre pasado.
Los facts tardíos lo empeoran. Un evento que llega el miércoles con event_at del lunes no pertenece a la partición incremental del martes solo porque ahí se ingirió. Tiempo de llegada y tiempo de negocio son grains distintos. Mezclarlos produce números que no se pueden reproducir en una segunda corrida.
Tests que sí protegen el grain
Me importan menos un muro de tests de schema que unos pocos que codifican la afirmación:
- Unique key = el grain que escribiste en el comentario.
- Not null en esa llave.
- Una relación de conteo con un upstream conocido (una conversación tiene N mensajes; las promesas no deberían superar a las conversaciones salvo que lo hayas diseñado así).
- Una query de reconciliación que un humano pueda correr: total del warehouse versus el sistema operativo para un día.
Si eso falla, no agrego un mart nuevo. Arreglo la afirmación.
Un dashboard no rescata un grain borroso
Un filtro, un promedio móvil o un gráfico más bonito no reparan el doble conteo. Construye para decisiones, no para dashboards: si la decisión es “detener outreach después de una promesa,” la tabla debe poder decir si esta cuenta, hoy, tiene una promesa abierta — una fila, una respuesta.
Por eso prefiero retrasar un dashboard a publicar una métrica cuyo grain no puedo defender. Sistemas > herramientas: Metabase, Tableau y un notebook SQL se ven convincentes sobre un join incorrecto.
Cómo aparece en la práctica
En datos con forma de cobranza me ha servido separar al menos:
- Grain de evento / mensaje — depuración, diseño de conversación, features de modelo.
- Grain de conversación — resultado de un diálogo (promesa, ya pagué, rechazo).
- Grain de cuenta-día — quién está en el libro y qué deberíamos hacer esta mañana.
Cada capa puede ser incremental. Cada una tiene una cadencia distinta. Ninguna debería fingir ser las otras.
La misma separación aparece en trabajo científico: una formulación no es una corrida de laboratorio; una corrida no es un candidato recomendado. polymer-ml-lab es una versión pequeña de esa disciplina — primero restricciones, luego una unidad que puedes puntuar, luego una decisión sobre qué merece el siguiente experimento.
Si un archivo de modelo no puede enunciar su grain, no confío en su ref(). Todo lo de abajo es comentario sobre una frase que nunca escribimos.