Los errores de modelado más caros y cómo detectarlos midiendo
Los dos errores que arruinan más proyectos de modelado son duales entre sí: la máquina que en realidad era un formulario con un índice, y el contexto que en realidad era un conjunto de estados sin nombre. Esta lección los diagnostica con pruebas objetivas en lugar de con intuición, añade los tres errores de coste alto que los acompañan —el nombre de implementación, la máquina Dios y la jerarquía decorativa—, y cierra con un protocolo de revisión de seis medidas con umbrales concretos que cualquiera puede aplicar a un modelo ajeno sin conocer el dominio.
Los errores de modelado no se pagan cuando se cometen: se pagan cuando alguien intenta cambiar algo. Un modelo mal planteado funciona perfectamente el primer trimestre, porque el código que genera es correcto y las pruebas pasan; su coste aparece el día en que hay que añadir un caso y resulta que hay que tocar catorce sitios, o el día en que alguien pregunta qué configuraciones puede alcanzar el sistema y nadie sabe responder aunque el diagrama esté ahí. Por eso los dos errores más caros son también los más difíciles de discutir: no producen fallos, producen parálisis. Y por eso este catálogo no se apoya en el gusto ni en la experiencia, sino en pruebas que se pueden contar, con umbrales, para que la conversación sea sobre números y no sobre opiniones.
- Diagnosticar con una prueba objetiva la máquina que es un formulario con un índice disfrazado.
- Detectar los estados ocultos en el contexto contando configuraciones alcanzables en lugar de leer código.
- Reconocer los tres errores acompañantes: el nombre de implementación, la máquina Dios y la jerarquía decorativa.
- Aplicar un protocolo de revisión de seis medidas con umbrales a un modelo que no escribiste tú.
La máquina que es un formulario disfrazado
El caso tiene una forma tan reconocible que casi puede dibujarse de memoria: estados llamados paso1, paso2, paso3 y paso4, dos eventos llamados SIGUIENTE y ATRAS, y ninguna transición que no vaya a un vecino inmediato. Eso no es una máquina de estado: es un entero entre uno y cuatro, con cuatrocientas líneas de ceremonia alrededor y un diagrama que no informa de nada porque su forma es una recta.
Lo que hace este caso tan persistente es que se comete por la razón correcta. Alguien decide modelar el asistente porque le han enseñado que los flujos de varios pasos son el ejemplo canónico de máquina de estado, y lo es; lo que nadie le dijo es que el ejemplo canónico lo es por sus validaciones y sus ramas, no por sus pasos. Modelar la secuencia y olvidar las reglas produce el peor de los dos mundos: todo el coste del formalismo y ninguna de sus garantías.
La prueba es objetiva y se responde en dos minutos. Primera pregunta: ¿existe alguna transición que no sea al sucesor o al predecesor inmediato? Segunda: ¿existe algún par de estados en los que el mismo evento haga cosas distintas? Tercera: ¿hay algún estado desde el que un evento sea ilegal y desde otro no? Si las tres respuestas son negativas, el grafo no contiene información: cualquier índice entero con dos operaciones lo reproduce exactamente, y el modelo es puro coste.
Conviene el matiz, porque el error contrario también existe y es igual de caro. Un asistente de varios pasos sí es una máquina legítima en el momento en que aparece cualquiera de estas cuatro cosas: validación asíncrona que puede fallar y retener el avance, ramas condicionales según lo que el usuario responda, la posibilidad de reanudar el flujo días después en el punto donde se dejó, o un envío final con estados de error y reintento. Lo que decide no es que haya pasos, es que haya modos con reglas distintas.
El refactor es siempre el mismo movimiento: el índice baja al contexto y la máquina asciende a la dimensión que sí tiene modos.
// Antes: cuatro estados que son un entero, y ningun modo real.
states: {
paso1: { on: { SIGUIENTE: 'paso2' } },
paso2: { on: { SIGUIENTE: 'paso3', ATRAS: 'paso1' } },
paso3: { on: { SIGUIENTE: 'paso4', ATRAS: 'paso2' } },
paso4: { on: { ATRAS: 'paso3' } },
}
// Despues: el paso es un dato; los estados son los modos que si difieren.
context: { paso: 0, respuestas: {} },
states: {
editando: {
on: {
ATRAS: { actions: 'retroceder', guard: 'noEsElPrimero' },
SIGUIENTE: { target: 'validando' },
},
},
validando: {
invoke: { src: 'validarPaso', onDone: 'decidiendo', onError: 'editando' },
},
decidiendo: {
always: [{ target: 'enviando', guard: 'eraElUltimo' }, { target: 'editando', actions: 'avanzar' }],
},
enviando: { invoke: { src: 'enviar', onDone: 'enviado', onError: 'fallo_envio' } },
enviado: { type: 'final' },
fallo_envio: { on: { REINTENTAR: 'enviando' } },
}
El modelo pasó de cuatro estados sin información a seis estados donde cada uno responde a una regla distinta, y el número de pasos dejó de estar cableado en la estructura: añadir un paso séptimo ya no toca el grafo.
Obsérvese que el refactor no reduce el tamaño: hay más estados que antes. Esa es la señal de que se hizo bien. Un modelo sano no es el que tiene pocas cajas, es aquel en el que cada caja responde a una pregunta que las demás no responden; el formulario disfrazado tenía cuatro cajas y una sola pregunta repetida cuatro veces, que es el peor rendimiento posible por caja dibujada.
Un formulario disfrazado de máquina hace más daño del que parece porque enseña la lección equivocada a todo el equipo. La gente mira ese diagrama recto, comprueba que no le dice nada que no supiera, y concluye razonablemente que modelar es ceremonia. A partir de ahí la siguiente propuesta de modelar algo —esta vez un flujo que sí lo necesitaba desesperadamente— se recibe con la resistencia acumulada del caso anterior. La forma más eficaz de matar la disciplina en una organización no es argumentar en contra: es aplicarla donde no aportaba nada y dejar que la evidencia hable.
El contexto que es un estado oculto
El error dual es más silencioso y bastante más caro. El diagrama muestra tres estados y parece impecable; el contexto contiene esValido, haIntentado, estaBloqueado y modoEdicion, y hay guardas que los leen para decidir a dónde ir. El sistema no tiene tres configuraciones: tiene tres por dieciséis, es decir cuarenta y ocho, de las cuales el diagrama documenta tres y las cuarenta y cinco restantes no existen para nadie —ni para el diagrama, ni para las herramientas, ni para quien revisa el código—.
La prueba de detección es de una línea y no admite discusión: todo valor del contexto que una guarda lee para elegir un destino es un estado al que no le pusieron nombre. No es una heurística; es una identidad. Si el destino depende de ese valor, ese valor pertenece a la función de transición, y la función de transición es exactamente lo que el grafo dibuja. Lo que no aparece en el grafo no se puede recorrer, ni cubrir, ni discutir con producto.
El matiz que salva el criterio de volverse absurdo es que no todo dato leído por una guarda es un estado oculto. Un umbral numérico —tres intentos, cien euros, treinta días— es una comparación sobre una magnitud, no un modo; el modo es el resultado de la comparación, y si ese resultado cambia lo que el usuario puede hacer, entonces el estado que falta es el de haber cruzado el umbral, no el número. La regla operativa es que los booleanos del contexto son sospechosos por defecto y las magnitudes no, porque un booleano ya es la respuesta a una pregunta binaria y una pregunta binaria que cambia el comportamiento es una partición del espacio de estados.
| Campo del contexto | Lo lee una guarda | Veredicto | Refactor |
|---|---|---|---|
esValido: boolean |
sí, para decidir destino | estado oculto | estados valido | invalido |
estaBloqueado: boolean |
sí, para bloquear eventos | estado oculto | estado padre bloqueado |
modoEdicion: boolean |
sí, cambia qué es legal | estado oculto | región paralela de edición |
intentos: number |
sí, contra un umbral | magnitud legítima | se queda, con estado al cruzarlo |
nombreUsuario: string |
no, solo se muestra | dato legítimo | se queda |
El coste de dejarlo así no es teórico y se puede enunciar con precisión: todas las herramientas que este track ha enseñado dejan de funcionar sobre la parte oculta. El recorrido exhaustivo del grafo visita tres estados y cree haber cubierto el sistema. El diagrama que se enseña en la revisión con producto describe tres situaciones cuando hay cuarenta y ocho. La inspección en vivo muestra un modo estable mientras el comportamiento cambia por debajo. Y la conversación sobre qué debería pasar en tal caso se vuelve imposible, porque el caso no tiene nombre y por tanto no se puede señalar. Un modelo con estados ocultos es peor que no tener modelo, ya que produce la confianza de tenerlo sin ninguna de sus garantías.
Los tres errores que siempre acompañan
El nombre de implementación
esperandoAlServicioDeFacturacionV2 describe el andamio, no el dominio. Envejece con el proveedor, expulsa a producto de la revisión y obliga a renombrar estados en cada migración.
La máquina Dios
Un solo grafo con cuarenta estados para todo el módulo. Nadie la modifica sin miedo, ninguna herramienta la dibuja legible, y su verdadero problema es que oculta que había cuatro entidades dentro.
La jerarquía decorativa
Estados anidados cuyo padre no declara ninguna transición propia. La jerarquía existe para factorizar lo repetido; si no factoriza nada, solo añade un nivel de ruta que complica cada matches.
El agujero negro
Un estado no final sin ninguna transición de salida. El sistema entra y no sale, y como no está marcado como final, ni la máquina ni el equipo saben que ese flujo terminó.
La máquina Dios merece un párrafo propio porque su refactor no es evidente y suele hacerse mal. La reacción instintiva es partirla por tamaño, cortando por donde el archivo se hace incómodo, y eso produce dos mitades acopladas que se envían eventos constantemente. El corte correcto se hace por entidad y por ciclo de vida: un grupo de estados que nace y muere junto, que responde a eventos que nadie más entiende y que podría seguir existiendo si el resto desapareciera, es una máquina propia y debe invocarse como actor. La prueba de que el corte fue bueno es que el número de eventos que cruzan la frontera es pequeño y que cada uno tiene nombre de dominio; si el puente entre las dos mitades transporta quince eventos con nombres internos, cortaste por el sitio equivocado.
El nombre de implementación parece el error más inofensivo de los cuatro y es el que más fricción genera a medio plazo, porque los nombres de los estados se filtran a sitios que nadie inventaría el primer día: registros de inspección, columnas de base de datos en máquinas persistidas, nombres de pruebas, mensajes de soporte y cuadros de mando. Cuando un estado se llama como el proveedor que había cuando se escribió, cambiar de proveedor deja de ser una sustitución de un módulo y pasa a ser una migración de datos con retrocompatibilidad. Elegir un nombre de dominio el primer día cuesta treinta segundos; corregirlo el año siguiente cuesta un plan.
Los cuatro comparten una raíz común que merece decirse en voz alta: son fallos de nombrar y de recortar, no de conocimiento técnico. Nadie los comete por ignorar la semántica de las transiciones internas; se cometen por escribir deprisa un modelo que después nadie vuelve a leer entero. Esa es también la razón por la que un protocolo de revisión funciona tan bien contra ellos: los cuatro son visibles desde fuera, sin conocer el dominio, con solo contar.
El protocolo de revisión en seis medidas
Revisar un modelo ajeno no requiere entender el negocio; requiere contar seis cosas y comparar con un umbral. Si algo se sale, se pregunta; si no, se aprueba. Esa independencia del dominio es lo que hace el protocolo aplicable de verdad, porque la revisión de modelos suele fracasar por un motivo social y no técnico: quien podría revisar no conoce el flujo, y quien lo conoce lo escribió. Con seis medidas objetivas cualquiera puede hacer la primera pasada, y la conversación de dominio queda reservada para un modelo que ya está estructuralmente sano.
flowchart TD A[el grafo es una linea con dos eventos] -->|si| B[formulario disfrazado: el indice baja al contexto] A -->|no| C[hay booleanos del contexto leidos por guardas] C -->|si| D[estados ocultos: dales nombre en el grafo] C -->|no| E[hay estados padre sin transicion propia] E -->|si| F[jerarquia decorativa: aplana ese nivel] E -->|no| G[hay estados no finales sin salida] G -->|si| H[agujero negro: marcalo final o dale salida] G -->|no| I[modelo apto para revision de dominio] style B fill:#f38ba8,color:#11111b style D fill:#f38ba8,color:#11111b style I fill:#a6e3a1,color:#11111b
| Medida | Umbral sano | Qué delata pasarse |
|---|---|---|
| Booleanos del contexto leídos por guardas | cero | estados ocultos sin nombre |
| Aristas por nodo, media | entre uno y tres | por debajo, una línea; por encima, falta jerarquía |
| Estados inalcanzables desde el inicial | cero | restos de un rediseño a medias |
| Estados no finales sin salida | cero | agujeros negros y flujos que no cierran |
| Estados padre sin transición propia | cero | jerarquía decorativa |
| Estados con nombre de tecnología | cero | el modelo caducará con el proveedor |
La medida de aristas por nodo es la más informativa de las seis y también la más fácil de malinterpretar, así que conviene leerla con cuidado. Una media por debajo de uno significa que el grafo es prácticamente una secuencia y que probablemente estás ante el primer error de esta lección. Una media muy por encima de tres casi nunca indica un dominio complejo: indica que hay transiciones repetidas en muchos estados que deberían haberse factorizado hacia un padre. Es decir, el mismo indicador señala los dos errores opuestos según hacia qué lado se desvíe, lo cual es una virtud, porque un solo número vigila las dos direcciones del eje.
Hay además una medida que no está en la tabla porque no se calcula sobre el código, y que conviene registrar de todos modos: cuántas preguntas hace una persona ajena al dominio al leer los nombres de los estados. Cero preguntas significa que el vocabulario es el del negocio. Más de dos o tres significa que el modelo habla el idioma de quien lo escribió, y que la próxima persona que lo mantenga empezará por traducirlo mentalmente, que es la primera fase de dejar de leerlo.
Las tres primeras medidas se automatizan sin esfuerzo desde la definición de la máquina, que es un valor inspeccionable, y merece la pena hacerlo: una prueba que falle cuando aparece un estado inalcanzable o un agujero negro cuesta veinte líneas y evita una clase entera de regresiones silenciosas. La cuarta y la quinta también. La sexta necesita un humano, y por eso es la única que conviene revisar en pareja.
Merece la pena ver que el formulario disfrazado y el contexto con estados ocultos no son dos patologías distintas, sino una sola desviación medida en direcciones opuestas respecto al mismo eje. Ese eje es la pregunta que el modelado existe para responder: cuánta de la información sobre el comportamiento del sistema está en la estructura y cuánta está en los datos. Un modelo sano coloca en la estructura exactamente aquello que determina qué es legal, y en los datos exactamente aquello que el sistema recuerda pero que no altera lo permitido. El formulario disfrazado empuja hacia la estructura algo que no determina nada —el número de paso, que es un dato puro— y obtiene un grafo grande y vacío. El contexto oculto empuja hacia los datos algo que determina todo —qué se puede hacer ahora— y obtiene un grafo pequeño y mentiroso. Y esto explica por qué ninguno de los dos se detecta discutiendo si el diseño es bonito: los dos parecen razonables desde dentro, ambos fueron escritos por gente competente, y ambos producen software que funciona el primer día. Solo se detectan preguntando, campo por campo y estado por estado, si esa pieza cambia lo permitido. Esa pregunta —hecha con la disciplina de aplicarla a todo y no solo a lo dudoso— es más valiosa que cualquier catálogo de anti-patrones que se pueda memorizar, porque los catálogos envejecen con las librerías y ella no. La madurez en modelado no consiste en reconocer las formas malas de lejos, sino en tener automatizada la pregunta que las genera todas, y en aceptar sin drama que la respuesta correcta cambia con el tiempo: lo que hoy es un dato puede ascender a modo mañana, cuando el negocio decida que ahí hay una regla, y un modelo sano es el que hace barato ese ascenso en lugar de esconderlo.
- Elige la máquina más antigua de tu código y aplica la prueba de las tres preguntas del formulario disfrazado. Si las tres son negativas, sustitúyela por un entero y mide cuánto código desaparece.
- Lista todos los campos del contexto y marca cuáles lee alguna guarda para elegir destino. Cada uno es un estado sin nombre: dáselo y dibújalo.
- Calcula el número real de configuraciones alcanzables multiplicando estados por combinaciones de booleanos, y compáralo con el número de cajas del diagrama.
- Escribe una prueba automática que falle si aparece un estado inalcanzable o un estado no final sin salida.
- Recorre los estados padre y comprueba si alguno no declara transición propia. Aplana los que no factoricen nada.
- Pasa el modelo a alguien que no conozca el dominio con la tabla de umbrales y pídele solo que cuente. Lo que encuentre sin entender el negocio es exactamente la deuda estructural.