La tabla no es el concepto

Podés definir el concepto a la perfección y aun así modelarlo mal.

Brasil, 2018. Una cadena europea de electrónica y entretenimiento: partes electrónicas, libros, y CDs de música que no era la que sonaba en la radio. Decenas de miles de códigos de producto. No me acuerdo del número exacto, pero pasaba de cien mil sin esfuerzo.

Yo estaba armando una POC para Microsoft. Tecnología multidimensional, y el objetivo de esas pruebas era casi siempre el mismo: que el cliente viera consultas ad-hoc sobre sus propios datos a velocidad de pensamiento, y de ahí decidiera si eso le servía.

Vale aclarar cómo funciona una POC, porque el género importa para lo que sigue. No es un producto. Son un par de semanas para mostrar algo que se sienta propio del cliente —sus productos, sus propios números— y para lograrlo se toman muchos atajos deliberados. Nada pasó por QA. Y se advierte, siempre, con esas palabras: estos números todavía no son confiables.

También se advierte y no sirve de nada. Si acertás en casi todo y fallás en una cosa, la gente brinca por esa cosa. Con razón.

Hay otro detalle del formato que tardé años en ver. Los diez días de construcción los paso con TI. TI ejecuta, conoce las tablas, sabe dónde vive cada campo, y no sabe si un número es razonable. Nadie de ese lado te va a decir “ese margen no puede ser”. El que tiene ese criterio es el negocio, y el negocio aparece en la reunión final. La persona capaz de mirar un total y saber que está mal llega cuando el modelo ya está construido.

En esa reunión, navegando márgenes por categoría, el jefe de ventas en línea —creo que era ese el puesto— frenó la demo. Los números no le cuadraban. Cientos de productos vendiéndose por debajo del costo. Algunos sin precio de venta del todo, solo un costo interno y un margen negativo que hacía ver todo el dashboard de rentabilidad como si estuviera roto.

La primera hipótesis de la sala fue la obvia: alguien está poniendo precios para perder plata, o los datos de costo están mal, o las dos cosas.

Ninguna de las dos. La definición de “producto” estaba bien. Cualquiera en esa sala te podía decir con exactitud qué era un producto. El problema era la tabla.

La tabla Productos tenía productos, y también tenía cientos de códigos que no se le venden a nadie. Qué era exactamente cada uno de esos códigos no lo recuerdo, y para el reporte tampoco importaba: ninguno tenía precio de venta porque ninguno se vendía, y todos cargaban costo. Corré un reporte de márgenes sobre esa tabla y te salen cientos de “productos” sin ingreso y con costo completo. El número no estaba mal calculado. La población tenía la forma equivocada.

En un comercio la lista de intrusos es corta. En una fábrica —y a esa vuelvo más adelante, porque ahí encontré el arreglo de verdad— la tabla de productos es en realidad una tabla de ítems: materias primas, material en proceso, producto terminado, todo conviviendo bajo el mismo nombre y la misma llave.

Y no fue la única tabla de esa demo con el mismo problema. Los pedidos tampoco eran todos pedidos. Miles de transacciones abandonadas, y no abandonadas por un cliente que se arrepintió: para monitorear que el sitio estuviera de pie, un agente ejecutaba una compra cada quince minutos y la dejaba a medias. Filas legítimas, propósito legítimo, tabla equivocada. Dos tablas, la misma falla, la misma tarde.

Yo no tenía criterio para cuestionar ninguna de las dos, y esa es la parte incómoda. Llevaba años construyendo modelos así y nunca me había preguntado si las filas de una tabla de productos eran productos. Cargaba la tabla que me daban, la agregaba, la mostraba.

Este es el antipatrón que se esconde debajo del anterior. Podés ponerte de acuerdo en lo que una cosa es y aun así estar en desacuerdo (en silencio, de forma estructural) sobre cuáles filas son esa cosa. Ponerse de acuerdo en la definición es la parte fácil. La tabla codifica una respuesta distinta, y la tabla es la que tus reportes leen de verdad.

El flag que la demo sí acertó

Acá viene la parte que debería doler, y me toca a mí primero.

La queja de siempre es que las demos de los fabricantes se saltan lo difícil: el camino feliz corre, los datos vienen limpios, y la realidad sucia en la que vivís no aparece en la diapositiva. Yo era el que hacía esas demos. La queja es sobre mí. Pero abrí AdventureWorks (la base de datos de ejemplo de Microsoft, la de todos los tutoriales) y mirá la tabla Product. Hay una columna que se llama FinishedGoodsFlag. La distinción está modelada. La base de juguete de la empresa para la que yo trabajaba sabía, de fábrica, que no toda fila en una tabla de productos es un producto vendible.

En Brasil no había ninguna bandera que ignorar. La distinción existía en la cabeza de la gente, con toda claridad, y no existía en ninguna columna.

Y donde el flag sí existe, las bases de datos de producción lo tiran a la basura. El flag está ahí, o una columna parecida, y nadie la llena, nadie filtra por ella, y en unos pocos años la tabla se llena de productos terminados, intermedios, muestras y códigos obsoletos, todo plano, todo igual de “producto”. La base de ejemplo modeló bien y yo me olvidé. Eso es lo contrario de lo que suelo argumentar: la herramienta te entregó la distinción y la disciplina la botó.

Miralo por el lado de la aritmética, que es donde se ve. Un material intermedio tiene costo y no tiene precio. Una muestra no tiene ninguno de los dos. Un código de bodega puede no tener ni un nombre presentable. Contalos como productos y tu conteo de productos se infla. Sacá un promedio de margen sobre ellos y el promedio es ficción. Armá un catálogo con ellos y la mitad no se puede vender. El concepto “producto que vendemos” nunca cambió. La extensión (las filas reales en la tabla) dejó de coincidir con él, sin que nadie se diera cuenta.

Nadie definió “producto” mal. La tabla dejó entrar cosas que el concepto excluye, y el que sabía cuál era la puerta no estaba en el cuarto todavía.

La falla opuesta: la tabla que borra la diferencia

La tabla de productos falló amontonando tipos distintos en una sola pila indiferenciada. La siguiente falla es su imagen en el espejo: colapsar conceptos genuinamente distintos en un solo tipo porque resulta que comparten algunas columnas.

Pensá en una universidad. Profesores y estudiantes comparten atributos: un ID, un nombre, un correo, una fecha de nacimiento. Así que la tentación es una sola tabla Persona que tenga a ambos. Más limpio, menos tablas, menos duplicación. Y cuando el diseño no es disciplinado, y muchas veces no lo es, ahí se queda: la diferencia entre un profesor y un estudiante deja de existir en cualquier parte del esquema.

Un profesor y un estudiante no son la misma cosa, aunque ambos sean personas. Tienen relaciones distintas con la institución, ciclos de vida distintos, reglas distintas, todo lo que importa distinto una vez que pasás de nombre y correo. El traslape termina en las columnas. Aplanalos en Persona y optimizaste para las columnas que comparten y botaste el significado de lo que cada uno es.

La reparación obvia es especializar: mantener Profesor y Estudiante como tablas propias con una llave foránea hacia Persona, atributos compartidos en el padre, distinción preservada. Esa es la respuesta que yo te habría dado, y es la respuesta que escribí primero en esta sección. Está mal, y lo que la delata es la misma regla que condena la fusión.

Los subtipos tienen que ser mutuamente excluyentes y exhaustivos: cada instancia del padre cae en exactamente uno, y ninguna se queda afuera. (Simsion y Witt lo argumentan en Data Modeling Essentials, y vale leer su versión.) Es una restricción estricta y se gana el sueldo, porque una partición limpia es lo que te permite decir algo del padre.

Ahora corré la universidad contra eso. ¿Una persona es exactamente uno de profesor o estudiante? No. El asistente de posgrado es los dos al mismo tiempo, y no es un caso borde que haya que manejar: es una población entera con oficina propia. ¿Toda persona es uno de los dos? Tampoco: la registradora, el conserje, el egresado, el postulante que rechazaron, el papá que paga la matrícula.

La partición falla en las dos pruebas, y ese fallo es lo más útil de todo el ejercicio. Te está diciendo que las cosas que llamaste subtipos nunca fueron tipos del padre. Profesor y Estudiante no son especies de persona. Son roles que una persona sostiene respecto de la institución: adquiridos en una fecha, sostenidos por un período, sostenidos junto con otros roles, entregados y a veces retomados. Un subtipo es lo que algo es. Un rol es lo que algo está haciendo, por ahora, en relación con alguien más.

Modelá el rol y todo lo que te estaba peleando se calla. Persona guarda al ser humano: el nombre, el ID, la fecha de nacimiento, los hechos que siguen siendo ciertos exista o no la universidad. Matrícula y Nombramiento guardan los roles, cada uno con su inicio, su fin, sus reglas, su ciclo de vida. El asistente de posgrado saca dos filas y ninguna contradicción. El conserje saca un tercer tipo de rol sin deformar ninguno de los dos primeros. A nadie hay que archivarlo mal para que quepa en la tabla.

“Entonces nunca subtipo personas.” No. El supertipo nunca fue el enemigo. Hace la prueba sobre otro para de entidades y pasa sin problemas, usando casi la misma palabra.

Casi todos los sistemas legales parten al sujeto de derecho en persona física (o natural) y persona jurídica. El ser humano de un lado; del otro, la entidad que existe porque alguien la inscribió. Esa partición no se traslapa, y no es cuestión de gusto ni de conveniencia: un sujeto es un ser humano o es una ficción legal, nunca los dos, ningún día, en ninguna jurisdicción. Nada se pasa de un lado al otro. Y los subtipos cargan peso real, que es la señal de que se están ganando el lugar: fecha de nacimiento y cédula de un lado, fecha de constitución y registro fiscal del otro, nada compartido y nada puesto en nulo porque el otro tipo no lo usa. La exhaustividad es la mitad que tu jurisdicción sí puede doblar, así que verificala en lugar de asumirla: los fideicomisos y los patrimonios autónomos son donde viven las sorpresas. Lo de no traslaparse aguanta igual.

El empresario individual parece el contra-ejemplo y termina siendo el argumento. Se siente como que debería ser los dos a la vez. No lo es: el sujeto de derecho es la persona física, y “el negocio” es un rol que esa persona sostiene. La excepción aparente es el modelo de roles llegando por otro camino.

La diferencia nunca fue generalizar contra especializar. Era el nivel de abstracción, y el test es lo que lo encontró. Profesor y estudiante eran los subtipos equivocados porque nunca fueron tipos de nada. Física y jurídica son los correctos porque no son otra cosa.

Y fijate qué te abre el subtipo legítimo. Una vez que la empresa está modelada como lo que es, una persona jurídica, quedás libre de modelar aparte lo que te está haciendo. Que lo vas a necesitar, porque las organizaciones rompen la regla del traslape con la misma constancia que las personas. Una empresa en tus datos es cliente y proveedor y competidor, muchas veces en el mismo trimestre. Tipificala como uno de los tres y vas a estar manteniendo la excepción mientras el modelo viva. Eso no son tipos de empresa. Son roles que sostiene hacia vos, y los roles son la parte de la que de verdad tenés datos.

El mismo instinto tiene su primo en el mundo de las tablas de catálogo: una sola tabla todo-propósito donde caben todos los códigos y todas las categorías del sistema. Cómoda para el que la construye, ruinosa para todos los que la leen. (Los más veteranos van a reconocer la One True Lookup Table: Paul Keister le puso el nombre, Joe Celko hizo famosa la crítica.) No es exactamente la misma falla, porque una tabla de catálogo no es una tabla de entidad. Pero el instinto sí lo es: una tabla para gobernarlas a todas.

La versión que más veces me tocó desarmar tiene nombre y número de archivo. En JDE, los Códigos Definidos por el Usuario: la F0004 guarda los encabezados de cada tipo de código y la F0005 guarda los valores. Ahí adentro vive arriba del 85% de lo que uno anda buscando cuando está construyendo un modelo dimensional. Todo lo que el negocio clasifica —cosas de naturaleza completamente distinta entre sí— comparte la misma tabla, la misma llave compuesta y la misma columna de descripción. El sistema operativo funciona igual. El que tiene que reconstruir de ahí qué es cada cosa sos vos, años después, sin nadie a quién preguntarle.

Y escuchá cómo lo dice el dominio, que es la prueba más barata de todas. Nadie en una universidad dice “es una persona de tipo profesor”. Dice que enseña, y que el otro se matricula. El negocio estaba hablando en roles desde el principio; el modelo fue el que dejó de escuchar. Cuando “profesor” y “estudiante” se vuelven una “persona” indiferenciada, el esquema se volvió más simple y el significado se escapó, y una tabla Persona que no te puede decir quién enseña y quién se matricula es un punto débil escondido a plena vista. (Los más académicos reconocerán el lenguaje del dominio de Eric Evans; su formulación es que el uso insistente de un lenguaje compartido obliga a los puntos débiles del modelo a salir a la luz.)

Mismo eje, extremos opuestos

Da un paso atrás y las dos fallas son el mismo error apuntando en direcciones opuestas.

Qué hace El costo
La tabla de productos No especializa: amontona tipos distintos Conteos y márgenes incluyen cosas que no son productos
La tabla de personas Tipifica un rol como si fuera un tipo de cosa Pierde el significado de profesor vs. estudiante

Una se niega a trazar una línea que existe. La otra borra una línea que importa. Las dos aplanan una taxonomía real en una sola tabla indiferenciada, y las dos te dejan con una tabla cuya forma ya no coincide con el concepto que dice contener.

Hay un lenguaje viejo para lo que está pasando. La intensión de un concepto es lo que significa: qué hace que algo sea un producto, o un profesor. La extensión es la población real: cuáles filas existen, y de qué tipo. Ponerse de acuerdo en la intensión no hace nada por la extensión. Podés escribir la definición perfecta y aun así construir una tabla que no esté de acuerdo con ella.

Porque acá está lo que una tabla realmente es: una afirmación sobre lo que existe. Cada fila asegura “esto es uno de estos”. Aplaná la tabla y hiciste una afirmación que nunca quisiste hacer (que los intermedios son productos, que los profesores son intercambiables con los estudiantes), y cada query río abajo toma esa afirmación al pie de la letra.

Toda métrica hereda la forma de la tabla

Por esto no es un problema cosmético. Una métrica es tan honesta como la población que tiene debajo. Conteo de productos, margen promedio, headcount, total de clientes activos: ninguno se calcula desde la definición de tu glosario. Se calculan desde las filas de la tabla. Si la forma de la tabla está mal, la métrica está mal, y no porque alguien haya cometido un error de aritmética. El error se cometió en tiempo de diseño y se ha heredado desde entonces.

Y como la mayoría de estos problemas, sobrevive porque se ve normal. La tabla siempre tuvo esta forma. Nadie recuerda haberlo decidido; así funciona el sistema y ya. Eso no es un hecho técnico, es deuda de modelado que se endureció hasta volverse una convención que nadie vuelve a examinar.

La cura: modelá la ontología, no la conveniencia

Las dos fallas se arreglan con la misma disciplina: modelá los límites reales del concepto en lugar de las columnas que casualmente se traslapan.

Para la tabla de productos, eso significa especializar. Producto terminado, material intermedio, muestra: tipos distintos bajo un padre común, cada uno con sus propias reglas sobre qué es y qué podés hacer con él. El FinishedGoodsFlag es la versión mínima viable de esto; una verdadera jerarquía de tipos de producto es el arreglo de fondo. De cualquier forma, la regla es la misma: un reporte de márgenes debería consultar “productos terminados”, no “todo lo que terminó en la tabla de productos”.

Y hay una salida que casi nunca se considera, y es la más barata de las dos: cambiar el nombre.

La tabla se llama Productos y contiene ítems. Podés pelearte con la población durante años, o podés admitir que el nombre está mintiendo y que el padre común que andás buscando ya existe en tus datos, solo que nunca lo nombraste. En unos clientes ese padre es Ítems. En otros, donde lo que se vende no siempre es una cosa, es Productos y Servicios. El ejercicio es corto y bastante incómodo: ¿qué nombre haría verdadera a la tabla que ya tenés? Si la respuesta es un nombre más genérico que el que le pusiste, la tabla no está sucia. Está mal rotulada, y lleva años haciendo una afirmación que nadie revisó.

Y una vez renombrada, el concepto que te interesaba vuelve como lo que siempre fue: un subconjunto declarado. Un producto es el ítem que el negocio decidió vender. Ahí es donde entra la lista de precios.

En el sistema operativo casi nunca vas a poder renombrar nada: la F0004 se va a llamar F0004 hasta que la empresa cambie de ERP. En la capa analítica sí, y es ahí donde se hace.

Una jerarquía de ítems trabajada se ve más o menos así, y lo que importa de cada fila no es el nombre: es la afirmación que hace.

Tipo Qué afirma cada fila Quién lo sabe
Materia prima se compra, no se vende compras y producción
Material en proceso ni se compra ni se vende producción
Producto terminado podría venderse producción, ingeniería de producto
Muestra o exhibición se entrega, no se factura mercadeo
Servicio se vende, no se inventaría comercial

Y fijate qué no aparece ahí: “producto vendible”. No es un tipo. Un producto terminado con precio de lista vigente es vendible; el mismo producto sin precio, o con el precio vencido, no lo es; y el mes entrante puede volver a serlo. Tiene fechas, se adquiere y se entrega. Eso es un estado, no una especie, y si lo modelás como subtipo estás cometiendo el error de la tabla de personas, el mismo exacto, del otro lado del post. Los tipos van en la jerarquía de ítems. Lo vendible sale de la lista de precios.

Y vale medir las herramientas contra eso, porque la industria por fin está construyendo para esto. Microsoft viene metiendo un ítem de Ontology en Fabric: tipos de entidad, propiedades, relaciones, todo amarrado a datos que ya viven en OneLake. El diagnóstico detrás es exactamente el correcto, y es el diagnóstico de este post: un concepto merece modelarse por encima de cualquier tabla individual.

La implementación, a agosto de 2026, está en preview, y cae del lado equivocado de este argumento en dos puntos. Un tipo de entidad acepta un solo binding de datos estáticos, y no podés combinar datos estáticos de más de una fuente en él. Tampoco hay un paso donde acotés la fuente a un subconjunto de sus filas: escogés una tabla y te llevás la tabla. Así que amarrá Product a esa tabla Productos y tu tipo de entidad Product va a contener los intermedios y las muestras, fielmente. Generá la ontología desde un semantic model y obtenés un tipo de entidad por tabla, por diseño. Y no hay subtipo (un tipo de entidad acepta un nombre y unas propiedades, sin padre), así que “producto terminado bajo producto” no es algo que hoy podás decir.

La mitad de la cura sí sobrevive, y no es la mitad que yo esperaba. Las relaciones cargan atributos, vigencia incluida, que es el modelo de roles funcionando como debe.

La vuelta que hay que darle es la parte que vale la pena pensar. Construís tablas filtradas y modeladas a propósito, puramente para alimentar la ontología. Eso funciona. Y fijate lo que significa: el modelado no subió a la capa semántica. Se quedó abajo en la tabla, donde siempre estuvo, y la ontología heredó la forma que le entregaste.

Nada de esto es un veredicto. Microsoft suele sacar una versión mínima viable y le va agregando funcionalidades, esto es temprano en ese ciclo, y espero que estos huecos se cierren. La dirección es buena noticia de verdad para cualquiera que lleve años argumentando que los conceptos van por encima de las tablas. Solo que todavía es muy temprano para apoyarse en eso. Y ninguna versión, por completa que llegue a ser, te va a eximir del modelado.

Eso hicimos a mano, años antes de que existiera un ítem de ontología al que alimentar. En Brasil la reparación fue rápida y deliberadamente sucia: teníamos que dejar al cliente jugando con la herramienta esa misma semana. En la capa analítica, incluir solo los productos que alguna vez se hubieran facturado.

select distinct ProductCode from Sales

Funcionó. Los códigos que no se vendían se fueron, y el reporte de márgenes dejó de reportar crímenes.

También tuvo un efecto colateral que yo no había previsto. Un producto perfectamente vendible que todavía no se vendió no está en esa lista. Los artículos nuevos, los de temporada, aquellos a los que el catálogo le está apostando para el próximo trimestre: todos se caían de un reporte sobre los productos que vendemos. La POC siguió con esa limitación, el cliente la conocía, y para lo que ahí se estaba evaluando alcanzaba. Pero mirá lo que había pasado. Arreglé una población demasiado ancha volviéndola demasiado angosta. La extensión seguía sin coincidir con el concepto. Solo que ahora fallaba para el otro lado.

Vale la pena nombrar ese error, porque es fácil de repetir. Filtrar por historial de ventas define el concepto por su extensión, por lo que pasó, cuando lo que yo necesitaba era su criterio. Haber vendido es evidencia de que un producto es vendible. No es lo que lo hace vendible.

Lo que finalmente lo arregló fue otra tabla, y no lo aprendí en Brasil. Fue años después, en otro cliente en Costa Rica: un proyecto más formal, sin la excusa de la POC, y una fábrica, o sea la versión difícil, donde la tabla de productos también carga materia prima y material en proceso. Ahí la condición de pertenencia estaba en la lista de precios. Un producto con precio es uno que el negocio decidió vender, lo haya comprado alguien o no. Eso es una condición de pertenencia, mantenida por gente que la mantiene por sus propias razones, y dice lo que el FinishedGoodsFlag siempre estuvo tratando de decir. La población dejó de ser un residuo de transacciones pasadas y pasó a ser una afirmación sobre la intención.

Para la tabla de personas, significa correr el test de partición antes de decidir dónde va cualquier cosa. Mutuamente excluyentes, exhaustivos: ¿toda instancia cae en exactamente una cubeta, y toda instancia cae en alguna? Persona física contra persona jurídica pasa. Profesor contra estudiante no. Si pasa, especializá: subtipos distintos bajo un padre común, atributos compartidos en el padre, y el supertipo es legítimo precisamente porque los subtipos le sobreviven. Si falla, dejá de construir subtipos. Nunca estabas viendo tipos de una cosa. Modelá los roles: el sujeto en un lado, cada rol que sostiene como entidad propia con sus fechas y sus reglas. El test toma un minuto y te dice en cuál de los dos modelos estás de verdad.

Esa es la regla entera, y tiene tres filos: especializá lo que genuinamente difiere; generalizá solo lo que es genuinamente igual; y cuando ninguna de las dos calza porque las categorías se siguen traslapando, lo que tenés en la mano es un rol, no un tipo. Modelá los límites del concepto, no la conveniencia del esquema.

Cierre

La definición vive en el diccionario. El significado vive en la forma de la tabla.

Podés correr un taller impecable, hacer que cada stakeholder se ponga de acuerdo en qué es un producto y qué es una persona, escribirlo todo en un glosario limpio, y aun así entregar un modelo que contradice calladamente cada palabra de eso. Porque el acuerdo fue sobre lo que los conceptos significan, y la tabla es una afirmación sobre cuáles filas son esos conceptos. Esas son afirmaciones distintas, y solo una de ellas es la que tus reportes leen cada mañana.

Una tabla que respeta los límites reales del concepto dice la verdad por construcción. Una tabla que los aplana dice una mentira que nadie eligió decir, y la sigue diciendo, fila tras fila, hasta que alguien abre un reporte de márgenes y se encuentra con una escena del crimen que nunca fue un crimen.

Antes de cerrar la pestaña: contá cuántas filas de tu tabla de productos tienen precio de venta. Y agarrá el último supertipo que modelaste y corré el test de partición: mutuamente excluyentes, exhaustivos. Los dos ejercicios toman un minuto, y los dos te van a decir algo que preferirías no saber.

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *