Qué es una máquina de estados finita
Una máquina de estados finita es el modelo de cómputo más humilde de la teoría de autómatas: una quíntupla formada por un conjunto finito de estados, un alfabeto de eventos, una función de transición que lleva cada par estado-evento a un estado destino, un estado inicial y un conjunto de estados finales. Su garantía fundacional es que en todo instante el sistema habita exactamente un estado y solo los eventos declarados pueden moverlo, y solo por las aristas declaradas. Esta lección levanta la definición desde Kleene y Rabin-Scott y la aterriza en la interfaz.
Antes de que existieran los ordenadores con memoria abundante, la teoría de la computación ya había definido su máquina más humilde: un dispositivo con un número finito de configuraciones que salta de una a otra según lo que lee. Ese autómata finito resultó ser, décadas después, la herramienta más infravalorada del desarrollo de interfaces. Su idea es casi ofensiva de tan simple: en todo momento el sistema está en un estado y solo uno, y solo un puñado de eventos declarados puede sacarlo de ahí, solo hacia destinos declarados. Todo lo que esta lección construye —y todo el nivel— sale de tomarse esa frase completamente en serio.
- Definir una FSM como la quíntupla
estados, eventos, transición, inicial, finalesy entender cada componente. - Comprender por qué “finita” es la palabra decisiva: el estado actual ES toda la memoria de la máquina.
- Distinguir el determinismo del no determinismo y ver por qué la interfaz quiere el primero.
- Situar las máquinas de Mealy y de Moore según dónde colocan la salida.
La quíntupla que define el modelo
En la teoría de autómatas, una máquina de estados finita determinista se define formalmente como una quíntupla M = (Q, Σ, δ, q0, F). Q es el conjunto finito de estados; Σ es el alfabeto finito de entradas —lo que en la interfaz llamamos eventos—; δ es la función de transición, el corazón de todo; q0 pertenece a Q y es el estado inicial; y F, subconjunto de Q, reúne los estados finales o de aceptación. No es folclore académico: cada pieza tiene una lectura directa en tu código.
La función de transición es la que merece atención. Su firma es δ: Q × Σ → Q: dado el estado actual y el evento que llega, devuelve el estado siguiente. Nada más. No consulta variables externas, no acumula historia, no cuenta: mira dónde estás y qué pasó, y dice a dónde vas.
type Estado = "idle" | "loading" | "success" | "error"
type Evento = "FETCH" | "RESOLVE" | "REJECT" | "RETRY"
// delta: Estado x Evento -> Estado (la funcion de transicion)
type Transicion = (estado: Estado, evento: Evento) => Estado
Este modelo es el escalón más bajo de la jerarquía de Chomsky: reconoce exactamente los lenguajes regulares, por debajo de los autómatas con pila y de la máquina de Turing. Nace con McCulloch y Pitts en 1943, se formaliza con Kleene y alcanza su forma canónica con Rabin y Scott en 1959. Que sea el modelo más débil no es un defecto: es justo la restricción que lo vuelve analizable.
Conviene una distinción que Harel y Pnueli hicieron célebre. Un sistema transformacional recibe una entrada, calcula y termina —el autómata que valida una cadena y responde acepta o rechaza—; ahí los estados finales F importan, porque marcan el veredicto. Un sistema reactivo, en cambio, no termina: se queda a la escucha de eventos indefinidamente, como toda interfaz. En ese régimen el conjunto F suele quedar vacío o volverse anecdótico, y lo que importa es que la máquina siempre esté en un estado válido esperando el próximo evento. La FSM sirve a ambos mundos con la misma quíntupla; solo cambia el peso que tiene F. Casi todas las máquinas de una aplicación son reactivas, y por eso a menudo verás definiciones que omiten F sin remordimiento.
Q: estados
El conjunto finito de situaciones en las que el sistema puede estar. Enumerable, revisable, cerrado.
Σ y δ: eventos y transición
El alfabeto de eventos que el sistema entiende, y la función que dice a qué estado lleva cada par estado-evento.
q0 y F: inicio y fin
El estado en el que arranca la máquina y los estados terminales que marcan un cómputo concluido.
Una sutileza sobre q0: el estado inicial no es un valor cualquiera, es parte de la definición de la máquina. Cambiar q0 cambia la máquina, igual que cambiar una transición. En una interfaz q0 suele ser idle o un estado de carga inicial, y elegirlo mal —arrancar en success sin datos, por ejemplo— es un bug de diseño tan real como una arista equivocada, aunque se disfrace de simple valor por defecto.
“Finita”: el estado es toda la memoria
La palabra “finita” hace todo el trabajo. Q es finito y no hay nada más: la máquina no tiene contador, ni pila, ni variable auxiliar. Su memoria completa es el estado en el que se encuentra. Por eso una expresión regular —que es una FSM disfrazada— no puede emparejar paréntesis anidados a profundidad arbitraria ni validar HTML: contar requiere memoria ilimitada, y una FSM no la tiene por definición.
Lejos de ser una limitación molesta, esa cota es el regalo. Si toda la memoria del sistema es su estado actual, entonces el conjunto de situaciones posibles es enumerable y cabe en una revisión. Puedes mirar la máquina entera y preguntar, para cada estado, qué eventos acepta. El torniquete del metro es el ejemplo fundacional precisamente porque cabe en la cabeza.
stateDiagram-v2 [*] --> bloqueado bloqueado --> desbloqueado : MONEDA bloqueado --> bloqueado : EMPUJON desbloqueado --> bloqueado : EMPUJON desbloqueado --> desbloqueado : MONEDA
Dos estados, dos eventos, cuatro transiciones. La máquina no recuerda cuántas monedas metiste ni cuántas veces empujaste: su único conocimiento del mundo es si la barra está bloqueado o desbloqueado. Empujar estando bloqueado no hace nada; meter otra moneda estando desbloqueado tampoco. Esas transiciones que “vuelven al mismo sitio” son tan parte del modelo como las que cambian de estado, y declararlas explícitamente es lo que elimina la ambigüedad.
Que la máquina no tenga más memoria que su estado tiene una consecuencia operativa preciosa: reconstruir el estado actual es plegar la secuencia de eventos desde el inicial con la función de transición. No necesitas persistir el estado; te basta con el log de eventos y δ.
// el estado es un pliegue (reduce) de los eventos sobre delta
const eventos: Evento[] = ["FETCH", "REJECT", "RETRY", "RESOLVE"]
const estadoFinal = eventos.reduce(transicion, "idle") // -> "success"
Esta propiedad es la raíz del event sourcing y del viaje en el tiempo de los depuradores: si el estado es una función pura del historial, rebobinar es volver a plegar hasta un punto anterior. La misma idea que en el nivel 8 hacía determinista la propagación reaparece aquí como reproducibilidad: dado el estado inicial y la lista de eventos, el estado final está unívocamente determinado.
Esa enumerabilidad tiene un dividendo humano que a menudo se pasa por alto: una máquina finita cabe en una revisión de código. Un revisor puede leer los estados uno a uno, recorrer las transiciones y preguntarse por cada una si es correcta, algo imposible con un enjambre de banderas cuyas combinaciones nadie ha listado jamás. Lo finito no es solo una propiedad matemática; es lo que vuelve el modelo comunicable entre personas, y por tanto revisable, discutible y mantenible.
Determinismo: una salida por cada entrada
Que δ sea una función tiene una consecuencia enorme: para cada par estado-evento hay como mucho un destino. Eso es un autómata determinista, un DFA. Su primo, el autómata no determinista o NFA, permite que un mismo par lleve a varios destinos a la vez, o que existan transiciones que no consumen entrada. El teorema de Rabin y Scott demuestra que ambos reconocen exactamente los mismos lenguajes: el no determinismo no añade poder, solo comodidad al describir ciertos patrones.
Un NFA puede, por ejemplo, ofrecer varias transiciones para el mismo evento y explorarlas todas a la vez, o incluir transiciones épsilon que cambian de estado sin consumir evento alguno. Convertirlo en el DFA equivalente puede multiplicar los estados —en el peor caso de forma exponencial, la construcción de subconjuntos—, pero el resultado es una máquina que, ante cada evento, sabe exactamente a dónde ir. Esa certeza es justo lo que una interfaz necesita y lo que la teoría de parsing puede permitirse relajar.
En la práctica del desarrollo de interfaces esto se condensa en una regla simple: define δ como una función determinista y trata cualquier evento inesperado como un no-op explícito. Esa disciplina —que el nivel 9.3 formaliza como la tabla de transiciones— convierte la reproducibilidad teórica en una garantía operativa: puedes grabar la sesión de un usuario como una lista de eventos y reproducir su bug exactamente, sin adivinar ningún estado intermedio.
El no determinismo es una herramienta teórica y de parsing, no un modelo para gobernar una pantalla. Una interfaz determinista cumple una propiedad que vale oro al depurar: el mismo evento, en el mismo estado, produce siempre el mismo estado siguiente. La secuencia de eventos —el log— reconstruye el estado con exactitud, y un bug es reproducible por construcción. Una máquina no determinista rompería esa promesa: el usuario ejecutaría los mismos pasos y a veces acabaría en un sitio distinto. Por eso XState v5, Robot, Zag.js y cualquier motor serio de 2026 construyen máquinas deterministas.
Merece la pena fijar qué es un evento, porque marca la frontera con el nivel 4. Un evento es discreto y tiene nombre: FETCH, RESOLVE, un clic, la llegada de una respuesta. No es un valor que varía de forma continua como un signal, sino un suceso puntual que ocurre en un instante y dispara a lo sumo una transición. Una máquina no observa valores cambiantes; consume una secuencia de sucesos nombrados. Por eso el vocabulario importa: los eventos se nombran como algo que pasó o una orden que se da, y jamás describen el estado, que es competencia exclusiva de Q.
Tener una variable status con valor loading no te da una máquina de estados; te da una etiqueta. La diferencia está en δ: una máquina define qué transiciones desde loading son legales y prohíbe el resto, mientras que una cadena suelta acepta que le asignes cualquier valor desde cualquier sitio. El estado de una FSM es un nodo de un grafo de transiciones declaradas; una cadena de estado es solo un nombre sin reglas. Confundirlos es la razón por la que muchos proyectos creen tener máquinas de estado y en realidad tienen banderas con nombres más bonitos.
Mealy y Moore: dónde vive la salida
Un autómata que solo reconoce no basta para una interfaz: necesitamos que además haga cosas —lanzar una petición, enfocar un campo—. Ahí entran los dos modelos clásicos de máquina con salida. En una máquina de Moore, la salida depende únicamente del estado en el que estás: al entrar en loading, disparas la petición. En una máquina de Mealy, la salida depende del estado y del evento juntos, es decir, va pegada a la transición: al ir de idle a loading por el evento FETCH, ejecutas la acción.
Son equivalentes en poder expresivo —cualquier máquina de Mealy se traduce a una de Moore y viceversa— y difieren solo en ergonomía. Los motores modernos ofrecen las dos: las acciones entry y exit, ligadas al estado, son estilo Moore; las acciones colgadas de una transición concreta son estilo Mealy. Saber cuál es cuál te evita el error clásico de disparar un efecto en la transición cuando querías dispararlo en cada entrada al estado, sin importar de dónde vinieras.
Un ejemplo diminuto fija la diferencia. Al modelar un menú desplegable, enfocar el primer elemento al abrir es Moore —ocurre por estar en abierto, vengas del teclado o del ratón—, mientras que reproducir un sonido solo al abrir con teclado es Mealy —depende del evento concreto que provocó la transición—.
const desplegable = {
cerrado: {
on: {
ABRIR_RATON: { destino: "abierto" },
ABRIR_TECLADO: { destino: "abierto", accion: "sonar" }, // Mealy: en la transicion
},
},
abierto: {
entry: "enfocarPrimero", // Moore: al entrar, venga de donde venga
on: { CERRAR: { destino: "cerrado" } },
},
}
Una ventaja que solo tiene un sistema con estados finitos y explícitos: puedes generar los tests desde el modelo. Si Q y δ están declarados, una herramienta recorre todos los caminos del grafo —cada transición, cada estado— y produce casos de prueba que cubren la máquina entera, incluidas las secuencias que a mano jamás se te ocurrirían. Es el testing basado en modelos, y en 2026 forma parte del utillaje alrededor de XState. Un puñado de banderas booleanas no ofrece nada parecido, porque no hay grafo que recorrer: solo variables sueltas cuyos caminos legales nunca se declararon en ninguna parte.
La tentación es leer una FSM como una forma ordenada de guardar en qué punto va la aplicación. La idea profunda es la contraria: al nombrar el conjunto Q conviertes el espacio infinito de combinaciones de variables en un conjunto finito, cerrado y auditable de situaciones legales, y al declarar δ afirmas un teorema sobre qué puede pasar y qué no puede pasar jamás. El estado actual deja de ser un dato que consultas y pasa a ser una prueba: mientras la máquina esté en loading, tienes la certeza matemática de que no está simultáneamente en error, porque la definición prohíbe estar en dos estados a la vez. Esa exclusión mutua no la garantizas tú con disciplina ni con tests; la garantiza la estructura. Cuando interiorizas que “finita” significa que toda la memoria del sistema es un único valor de un conjunto enumerable, dejas de perseguir combinaciones imposibles de banderas booleanas y empiezas a razonar sobre transiciones. La máquina no describe tu programa: lo restringe, y en esa restricción está toda su potencia. Los cuatro niveles que siguen no hacen más que explotar consecuencias de esta única frase.
- Escribe la quíntupla
(Q, Σ, δ, q0, F)completa del torniquete: enumera estados, eventos, transiciones, inicial y finales. - Añade al torniquete un estado
averiadoy el eventoAVERIA; redibuja el diagrama y comprueba que sigues teniendo exactamente un estado activo. - Toma un semáforo de tres luces y decide: ¿es más natural modelarlo como Moore —la salida es el color del estado— o como Mealy? Justifícalo.
- Explica con tus palabras por qué una FSM no puede validar que cada
(tenga su), y qué le falta respecto a un autómata con pila. - Coge una pantalla real que conozcas y lista sus estados. Si te salen más de siete u ocho, sospecha: quizá estás mezclando dimensiones independientes, el problema del nivel 9.5.