context: el estado extendido
Una maquina tiene dos memorias que colaboran: los estados finitos, que capturan el modo cualitativo del sistema —un conjunto pequeno y enumerable de situaciones que cambian su comportamiento— y el context, el estado extendido que guarda los datos cuantitativos —contadores, valores de formulario, respuestas del servidor— cuyo dominio es demasiado grande para enumerarlo como estados. Esta leccion define la frontera exacta entre ambos, da un criterio operativo para decidir donde vive cada dato, y muestra por que confundirlos produce la explosion combinatoria de estados que las maquinas nacieron justamente para evitar.
Una maquina de estados finita, tomada al pie de la letra, solo sabe en cual de un punado de modos se encuentra. Pero ningun sistema real vive de modos puros: un reproductor recuerda el segundo exacto, un formulario acumula lo que escribes, una peticion cuenta cuantas veces reintento. Ese dato no cabe en un conjunto finito de estados —hay infinitos segundos posibles— y sin embargo la maquina debe recordarlo. La respuesta de los statecharts es una segunda memoria, ortogonal a la primera: el context, o estado extendido. Entender que toda maquina lleva DOS memorias —una cualitativa y finita, otra cuantitativa y abierta— y saber exactamente que va en cada una es la diferencia entre modelar un sistema y ahogarlo en estados.
- Distinguir el estado finito, cualitativo y enumerable, del
context, cuantitativo y de dominio abierto. - Formular el estado total de una maquina como el par formado por el valor finito y el
context. - Aplicar un criterio operativo para decidir si un dato debe ser un estado o vivir en el
context. - Reconocer la explosion combinatoria de estados como el sintoma de haber metido datos cuantitativos donde no debian.
Dos memorias: lo cualitativo y lo cuantitativo
Harel, al inventar los statecharts, separo de forma deliberada dos clases de informacion que las maquinas ingenuas mezclaban. Los estados finitos describen el modo del sistema: son cualitativos, mutuamente excluyentes y enumerables —inactivo, cargando, exito, error—. El estado extendido guarda los datos que acompanan a ese modo: son cuantitativos y de dominio abierto —cuantos reintentos llevas, que devolvio el servidor, que texto hay en el campo—. XState llama context a esta segunda memoria.
import { setup, assign } from 'xstate'
const recurso = setup({
types: {
context: {} as { intentos: number; datos: string | null; error: string | null },
events: {} as
| { type: 'PIDE' }
| { type: 'RESUELVE'; datos: string }
| { type: 'RECHAZA'; error: string },
},
}).createMachine({
id: 'recurso',
context: { intentos: 0, datos: null, error: null }, // estado extendido inicial
initial: 'inactivo',
states: {
inactivo: { on: { PIDE: 'cargando' } },
cargando: { on: { RESUELVE: 'exito', RECHAZA: 'error' } },
exito: {},
error: { on: { PIDE: 'cargando' } },
},
})
El valor finito de esta maquina toma cuatro valores; su context toma infinitos, porque intentos es un entero sin cota y datos es cualquier cadena. Las dos memorias avanzan a la vez pero por reglas distintas: el valor finito solo cambia por una transicion declarada, mientras que el context solo cambia cuando una action lo reescribe.
El termino no lo invento XState. En el articulo fundacional de los statecharts de 1987, Harel distingue el control state —el modo finito— del extended state —las variables cuantitativas que el sistema arrastra—. La teoria de automatas llama a esto una maquina de estados finita extendida, o EFSM. XState solo le pone nombre concreto: context. Reconocer que estas ante una idea de cuatro decadas, y no ante un detalle de una libreria de JavaScript, te ayuda a fiarte de la frontera: existe porque separa dos cosas que son distintas en su naturaleza, no por comodidad de implementacion.
El estado total es un par
Aqui esta el punto que casi todos pasan por alto: el estado real de la maquina no es su valor finito, sino el par formado por el valor finito y el context. Dos actores en el modo cargando pero con intentos distintos NO estan en el mismo estado total; comparten modo pero difieren en memoria extendida. El snapshot que devuelve XState lo refleja: snapshot.value es el modo cualitativo y snapshot.context es la memoria cuantitativa, y solo juntos determinan que hara la maquina ante el proximo evento.
import { createActor } from 'xstate'
const actor = createActor(recurso).start()
actor.send({ type: 'PIDE' })
actor.send({ type: 'RECHAZA', error: 'timeout' })
const s = actor.getSnapshot()
s.value // 'error' -> el modo cualitativo
s.context.error // 'timeout' -> el dato cuantitativo que lo acompana
s.matches('error') // true
Esta dualidad explica por que una maquina con pocos estados puede modelar un sistema riquisimo: los cuatro modos capturan las situaciones que cambian el comportamiento, y el context carga con todo el detalle que no lo cambia. La finitud vive en el control; la infinitud, en la memoria.
El criterio: estado finito o context
La pregunta practica en cada dato es: donde lo pongo. El criterio decisivo no es el tipo del dato sino su papel en el comportamiento.
Un dato es un estado finito si sus valores posibles son pocos, mutuamente excluyentes, y cada uno habilita transiciones o comportamientos distintos: abierto frente a cerrado, autenticado frente a anonimo. Un dato va en el context si su dominio es grande o infinito, o si su valor concreto no cambia por si mismo que transiciones estan disponibles: un saldo, un texto, un contador, una lista. Regla de bolsillo: si puedes dibujar los valores como cajas en un diagrama y trazar flechas entre ellas, es estado; si tendrias que dibujar una caja por cada numero, es context.
Modo de conexion: estado
conectado, desconectado y reconectando son pocos, excluyentes y cambian que puedes hacer. Enumerables y con comportamiento propio: son estados finitos.
Contador de reintentos: context
El numero de intentos es un entero sin cota util. No lo enumeras como estados: vive en el context y lo consulta un guard.
Texto de un campo: context
El contenido de un input tiene dominio infinito. Jamas es un estado; es memoria cuantitativa pura que se reescribe con assign.
Sesion: estado, no flag suelto
anonimo frente a autenticado habilita rutas y acciones distintas. Aunque parezca un flag, es un modo cualitativo: modelalo como estado.
Un caso limite aclara la frontera. El saldo de una cuenta es cuantitativo y vive en el context. Pero la distincion cualitativa “hay saldo o no lo hay” SI es un modo, si de ella depende que transiciones existan. La solucion elegante no es duplicar el dato en dos sitios: es dejar el numero en el context y expresar la condicion cualitativa con un guard que lo lea en el momento de transitar —el tema de la leccion 3—. El dato vive una sola vez; la maquina lo consulta cuando la cualidad importa.
La explosion combinatoria y su cura
Que pasa si ignoras la frontera y modelas datos cuantitativos como estados. Imagina un contador de 0 a 100 codificado como cien estados cuenta0, cuenta1, hasta cuenta100, cada uno con sus transiciones. Ahora cruzalo con tres modos de un formulario y con un flag de carga: los estados se multiplican. Ese es el pecado que las maquinas nacieron para evitar y en el que caen quienes olvidan el context.
// ANTIPATRON: codificar una cantidad como estados finitos
states: {
intento0: { on: { FALLA: 'intento1' } },
intento1: { on: { FALLA: 'intento2' } },
intento2: { on: { FALLA: 'agotado' } }, // ...y asi sin fin
}
// CURA: un solo estado y la cantidad en el context
states: {
cargando: {
on: {
FALLA: {
target: 'cargando', // se queda; solo cambia la memoria
actions: assign({ intentos: ({ context }) => context.intentos + 1 }),
},
},
},
}
Los mismos reintentos, dos modelos opuestos: arriba, un estado nuevo por cada numero, un grafo que crece sin fin y transiciones duplicadas; abajo, un unico estado que incrementa un entero. La cantidad pertenece a la memoria, no al control, y en cuanto la devuelves a su sitio el diagrama recupera su tamano.
flowchart LR I[inactivo] -->|PIDE| C[cargando] C -->|RESUELVE| E[exito] C -->|RECHAZA| F[error] F -->|PIDE| C style I fill:#89b4fa,color:#11111b style E fill:#a6e3a1,color:#11111b style F fill:#f38ba8,color:#11111b
En el diagrama solo hay cuatro cajas: los modos que de verdad cambian el comportamiento. Los intentos, el error textual, los datos devueltos —todo lo cuantitativo— no aparece como caja porque viaja en el context, invisible en el grafo pero presente en cada snapshot. Anadir un quinto reintento no crea un estado nuevo: solo incrementa un entero.
Si al modelar te descubres creando estados casi identicos que solo difieren en una cantidad —unIntento, dosIntentos, tresIntentos—, has puesto en el control lo que pertenece a la memoria. La cura es siempre la misma: colapsa esos estados en uno solo y mueve la cantidad al context. La maquina se encoge, el diagrama vuelve a ser legible, y la logica que dependia del numero pasa a un guard. Una maquina sana tiene tantos estados como modos cualitativos tenga el problema, ni uno mas.
La leccion profunda es que “maquina de estados FINITA” nunca significo que el sistema tenga finita informacion —tiene infinita, arrastra numeros sin cota— sino que su CONTROL es finito. Separar context de estados finitos es un acto de compresion deliberada: decides que un conjunto pequeno de modos cualitativos gobierne el comportamiento, y relegas todo lo cuantitativo a una memoria que acompana pero no ramifica. Esa decision es lo que hace tratable el sistema, porque puedes razonar exhaustivamente sobre un grafo de cuatro cajas de un vistazo, cosa imposible sobre un espacio de estados infinito. Cuando un dato exige mirarlo como modo, promuevelo a estado; cuando solo exige recordarlo, dejalo en el context y consultalo con un guard. Dominar esta frontera no es una convencion de XState: es la tecnica central de toda la ingenieria de estados, la que convierte la complejidad accidental de mil combinaciones en la complejidad esencial de unos pocos modos y una memoria que los acompana. El resto de este nivel —assign, guard, entry, exit— son las herramientas para operar esa memoria sin romper jamas la finitud del control.
- Toma un reproductor de video y lista sus datos: modo de reproduccion, segundo actual, volumen, si esta a pantalla completa. Clasifica cada uno como estado finito o
contextjustificando con la prueba de la enumerabilidad. - Escribe la maquina con
setupycreateMachine: pon los modos como estados y todo lo cuantitativo en elcontexttipado. - Envia dos secuencias de eventos que dejen la maquina en el mismo
valuepero distintocontext, e imprime ambos snapshots para verlo. - Encuentra en tu codigo, o inventa, un caso de explosion: estados que solo difieren en una cantidad. Colapsalos en uno y mueve el numero al
context. - Argumenta en dos frases por que el estado total es el par valor mas
contexty no solo elvalue.