wandres.dev
NIVEL DIOS: SÍNTESIS · la teoría del estado

El ecosistema entero en el plano

Con los dos ejes ya definidos, esta lección puebla el mapa: ubica signals, observables RxJS, stores como Zustand y Jotai, máquinas de XState, Redux, Elm, TCA, MVI y CRDTs en el plano de reactividad frente a disciplina de mutación. Cada herramienta recibe sus dos coordenadas con la razón de cada una, y de ese ejercicio salen los descubrimientos que un catálogo alfabético esconde: que Redux es disciplinado pero de reactividad gruesa, que un signal es lo contrario, que XState no fija reactividad sino que la enchufas, que Jotai y Zustand se separan en la granularidad y no en la disciplina, que Elm y TCA llevan la disciplina hasta el efecto, y que un CRDT no está en el plano sino que lo reescribe, añadiendo un tercer eje de distribución. El resultado es un mapa donde los vecinos inesperados se explican y las rivalidades de foro se disuelven en malentendidos de coordenadas.

⏱ 19 min

En la lección anterior levantamos el plano: dos ejes ortogonales, reactividad y disciplina de mutación, y la promesa de que toda herramienta cae en algún punto. Ahora cumplimos la promesa poblándolo. Vamos a tomar signals, observables de RxJS, stores como Zustand y Jotai, máquinas de XState, Redux, Elm, TCA, MVI y CRDTs, y a clavar cada uno en su coordenada con la razón de las dos. Del ejercicio saldrán descubrimientos que un catálogo ordenado por nombre esconde: que Redux es disciplinado pero de reactividad gruesa, que un signal es exactamente lo contrario, que una máquina no fija su reactividad sino que te deja enchufarla, que Jotai y Zustand se separan por granularidad y no por contrato, y que un CRDT no ocupa un punto del plano, sino que lo reescribe. Cuando termines, tendrás un mapa en el que los vecinos raros se explican y las rivalidades de foro se disuelven.

🎯 Al terminar esta lección sabrás
  • Asignar dos coordenadas —reactividad y disciplina— a cada familia del ecosistema con su justificación.
  • Descubrir los vecinos inesperados que el plano revela y que un catálogo alfabético oculta.
  • Entender por qué una máquina no fija reactividad y por qué un CRDT reescribe el plano.
  • Salir con un mapa mental poblado que sirva de referencia para diseñar y para elegir.

El eje de la reactividad, poblado de arriba abajo

Recorramos primero el eje de la propagación, de menos a más automático. Abajo del todo viven el observer manual y el pub-sub: tú suscribes y tú notificas.

Cada dependencia que olvidas es un dato rancio. Es el punto de partida del track, hoy raro como mecanismo principal pero omnipresente por debajo de todo lo demás, porque toda reactividad automática es, en el fondo, suscripción y notificación escondidas.

Nada de lo que viene después escapa a esta verdad, y por eso empezamos por aquí: entender el suelo del eje es entender qué automatizan, exactamente, todos los pisos de arriba.

Situar algo en este eje no es un adorno taxonómico: predice su comportamiento bajo carga. Cuanto más abajo, más trabajo manual y más riesgo de olvido; cuanto más arriba, más te cubre el runtime y menos control explícito tienes sobre cuándo se recalcula.

Un peldaño arriba está la reactividad gruesa de los stores clásicos. Redux con store.subscribe y el Zustand de base emiten una única señal de cambio; cada suscriptor relee y compara para decidir si le toca.

La mejora sobre lo manual es que la notificación es automática. El coste es que despierta a muchos que no cambiaron, y por eso se le añade una capa de selectores que corta lo que no varió antes de que provoque un render.

Más arriba está el grano fino. Los átomos de Jotai y Recoil propagan por unidad mínima: quien lee un átomo se recomputa solo cuando ese átomo cambia.

Y en la cima están los signals —Solid, Preact, Angular, Vue, la propuesta de TC39— con su grafo de dependencias que descubre quién lee qué y notifica solo a lo afectado, recalculando cada nodo a lo sumo una vez.

Que los signals ganaran la última década no fue casualidad. La interfaz declarativa recomputa vistas a partir del estado, y un mecanismo que recomputa solo lo justo encaja con ella como una llave en su cerradura.

La reactividad fina, en otras palabras, no es una moda de sintaxis: es la propagación que la era del render por reconciliación acabó pidiendo. Su ascenso en el eje coincide punto por punto con el ascenso de la UI declarativa.

RxJS merece una nota aparte, porque no es un punto más alto del mismo eje, sino un dialecto distinto de reactividad: modela el tiempo como ciudadano de primera, con flujos que emiten muchos valores y operadores que los transforman.

La distinción importa porque el ecosistema la difumina. Un signal pregunta cuánto vale esto ahora y quién depende de ello; un observable pregunta qué secuencia de valores emite esto a lo largo del tiempo.

Ambos van arriba en reactividad, pero mezclarlos sin criterio produce código que pelea contra su propia herramienta: acabas modelando un valor como si fuera un stream, o un stream como si fuera un valor.

En 2026 RxJS conserva su nicho —eventos complejos en el tiempo, coordinación de asincronía— pero ha cedido el terreno del estado de UI a los signals. La razón cabe en el plano: la mayoría del estado de una pantalla es un valor que se deriva, no una secuencia que se compone.

El eje de la disciplina, poblado de menos a más

Ahora el otro eje, de menos reglas a más. En el suelo está el signal crudo o la referencia mutable: propaga de maravilla, pero cualquiera lo escribe cuando quiere. Reactividad altísima, disciplina cero; es el vecino raro que el plano hace visible.

Zustand y Jotai suben un poco: garantizan fuente única —un store, un átomo— pero dejan la escritura bastante abierta, disciplinada por convención más que por el tipo. Son deliberadamente ligeros, y esa ligereza es precisamente una posición baja en el eje de la disciplina, no un defecto.

Esa ligereza es una decisión de diseño, no una carencia. Zustand renunció a la ceremonia de Redux a propósito, apostando a que la mayoría de los stores no necesitan la red de seguridad completa y sí necesitan escribirse rápido.

En el plano, esa apuesta es una posición baja en disciplina elegida con los ojos abiertos, y por eso Zustand conviene justo cuando el estado es pequeño, de vida corta o de bajo riesgo.

El salto grande de disciplina lo da la máquina de estado. XState solo permite las transiciones que declaraste desde los estados que declaraste, y con ello los estados imposibles dejan de ser representables.

Su disciplina es estructural: no te pide que respetes un flujo, te impide salirte de un grafo. Por eso ocupa una posición altísima en este eje, y de una clase distinta a la del flujo unidireccional.

La diferencia entre las dos clases de disciplina alta merece una frase. La máquina restringe qué estados existen; el flujo unidireccional restringe cómo se llega de uno a otro.

Una prohíbe destinos imposibles, el otro prohíbe caminos sin nombre, y una app madura a menudo quiere las dos cosas a la vez: una máquina que declara los estados y, en las transiciones, la disciplina de que cada cambio tenga una causa nombrada.

Por encima, en disciplina procedimental, está la familia unidireccional. Redux, Elm, TCA y MVI exigen que el estado solo cambie por hechos con nombre, mediante una función pura, en un solo sentido.

Y dentro de la familia, Elm con su Cmd y TCA con su Effect aprietan una vuelta más: describen incluso los efectos como valores que un runtime ejecuta, para que ni la frontera con lo impuro escape al contrato.

Fíjate en que este eje no mide cantidad de código ni dificultad, sino cuántas garantías te da por construcción. Una unión discriminada de TypeScript bien elegida impone muchísima disciplina con cero runtime; un signal crudo es poquísimo código y ninguna garantía. Confundir esfuerzo con disciplina es un error de lectura del plano.

Guarda esa idea, porque impide dos errores simétricos: rechazar una herramienta disciplinada por creerla pesada, y adoptar una pesada creyendo que su peso ya es, por sí solo, disciplina.

flowchart TD
PLANO[Plano del estado] --> AR[Alta reactividad]
PLANO --> BR[Baja reactividad]
AR --> ARBD[Baja disciplina: signals, atomos Jotai, RxJS]
AR --> ARAD[Alta disciplina: store de signals con reducer, XState observado]
BR --> BRBD[Baja disciplina: objeto mutable, observer manual]
BR --> BRAD[Alta disciplina: Redux, Elm, TCA, MVI]
style PLANO fill:#cba6f7,color:#11111b
style AR fill:#89b4fa,color:#11111b
style BR fill:#89b4fa,color:#11111b
style ARAD fill:#a6e3a1,color:#11111b
style BRAD fill:#a6e3a1,color:#11111b

Las dos coordenadas de cada familia y sus vecinos raros

Puestos los ejes, clavar cada herramienta en su punto es cuestión de leer las dos coordenadas a la vez. Aquí es donde el mapa paga: revela que Redux y un signal, que los foros presentan como alternativas, ni siquiera compiten en el mismo terreno.

Redux es disciplina alta con reactividad gruesa: su fuerza es el contrato de mutación, su punto débil es que notifica en bloque y necesita selectores para afinar. Un signal es lo simétrico: reactividad máxima, disciplina mínima.

No son rivales; son esquinas opuestas del plano, y elegir entre ellos es elegir qué eje te importa más en este problema. Compararlos de frente es medir la cerradura de una puerta contra la velocidad de otra.

Este es el malentendido de coordenadas más repetido de todo el frontend, y explica años de discusiones estériles. Cuando alguien proclama que los signals hacen obsoleto a Redux, anuncia que la velocidad de una puerta dejó anticuada la cerradura de otra.

Son frases que suenan a argumento y no lo son, porque comparan lecturas de ejes distintos. El plano las desactiva sin necesidad de tomar partido: basta con señalar que hablan de dimensiones que no se tocan.

Signals · Jotai · RxJS

Reactividad alta, disciplina baja. Propagan con precisión pero no gobiernan la mutación. Perfectos cuando el dolor es la sincronización fina y la libertad no molesta.

🐻

Zustand · Redux Toolkit

Reactividad gruesa afinada con selectores. Zustand con disciplina ligera; RTK con la disciplina unidireccional completa. Lo que los separa es casi pura posición en el eje de la mutación.

🔀

XState

Disciplina estructural máxima: transiciones declaradas, estados imposibles fuera. Su reactividad no está fijada; la enchufas con la suscripción que quieras, fina o gruesa.

🎯

Elm · TCA · MVI

Disciplina procedimental máxima: reducer puro, un solo sentido y el efecto descrito como valor. La reactividad la pone la plataforma: el diff del runtime, StateFlow, Observation.

El vecino más instructivo es Zustand frente a Redux Toolkit: están casi a la misma altura de reactividad, y lo que de verdad los separa es su posición en el eje de la disciplina. Elegir entre ellos no es elegir rendimiento, es elegir cuánta disciplina quieres que el tipo te imponga.

El segundo vecino sorprendente es Jotai frente a Zustand. Los dos son ligeros en disciplina, así que no se separan ahí; se separan en la reactividad, porque Jotai propaga por átomo y Zustand por store con selector. Elegir entre ellos es elegir la granularidad de la propagación, no el contrato de la mutación.

Y el tercero, el que más descoloca: useReducer y Redux comparten casi entera la coordenada de disciplina, porque un useReducer es un Redux local sin store global. Lo que los separa no es la gramática de la mutación, es el alcance: dónde vive el estado y quién puede alcanzarlo.

Estos tres vecinos enseñan a leer el mapa en las dos direcciones. A veces dos herramientas comparten un eje y difieren en el otro; a veces comparten casi todo y difieren en algo que ni siquiera es el plano, como el alcance. Nombrar en cuál de los tres casos estás es media decisión ya tomada.

El caso que no cabe: el CRDT reescribe el plano

Queda XState, que enseña algo sutil: una máquina de estado no trae una reactividad propia. Fija disciplina —el grafo de transiciones— y luego se conecta a React, a signals o a lo que sea para propagar. Es la prueba viva de que los ejes son independientes: puedes mover su reactividad sin tocar un ápice de su disciplina.

Y queda el CRDT, que no encaja como un punto más porque juega en otro tablero. La disciplina que estudiamos hasta aquí —transiciones, flujo único— presupone que hay un orden de los cambios y que hay que respetarlo.

Un CRDT ataca desde otro ángulo: diseña las mutaciones para que sean conmutativas, de modo que dos réplicas que reciben los mismos cambios en distinto orden convergen al mismo estado sin árbitro. Su disciplina no es un solo sentido, sino que el orden deje de importar.

Por eso no lo colocamos en el plano de dos ejes: es como si añadiera un tercero —el de la distribución— donde la pregunta ya no es quién puede mutar, sino cómo fusionar mutaciones que ocurrieron a la vez y sin coordinación. El plano de dos ejes gobierna el estado en un proceso; el CRDT gobierna el estado repartido entre muchos.

La distinción no es académica: explica por qué una app de colaboración en tiempo real necesita ambas cosas a la vez. Dentro de cada cliente, un flujo unidireccional gobierna la mutación local; entre clientes, un CRDT fusiona lo que cada uno hizo sin hablar con los otros.

Quien cree que el CRDT sustituye al store confunde los dos tableros: son capas, no rivales. Verlo así cambia cómo diseñas.

Primero decides el gobierno local de cada cliente con los dos ejes de siempre, y solo después, si hay concurrencia real entre usuarios, añades la capa de convergencia. Meter el CRDT antes de tiempo es pagar metadatos y complejidad de fusión por un problema que quizá ni tienes.

Con esto el mapa queda completo: nueve familias, dos ejes y una capa extra para cuando el estado se reparte entre procesos. La próxima lección lo baja al terreno y lo pone a trabajar sobre una app entera.

⚠️
El mapa ubica, no puntúa

El error al usar este mapa es leerlo como un ranking, con la esquina de arriba a la derecha como el trono. No lo es. Una posición alta en disciplina que tu problema no necesita es ceremonia pura: pagas boilerplate por garantías que no te hacían falta. Una posición alta en reactividad que tu problema no necesita es complejidad de grafo donde bastaba un useState. El mapa sirve para ubicar tu problema primero y leer después qué punto lo cubre sin sobrar ni faltar. Un formulario efímero no quiere Redux; un editor colaborativo no se salva con un signal. El punto correcto es el que coincide con la coordenada de tu problema, no el que queda más arriba.

El mapa disuelve las guerras de framework

Casi todas las guerras de estado del ecosistema son un malentendido de coordenadas, y este mapa las apaga una a una. Redux frente a signals no es una rivalidad: son esquinas opuestas que optimizan ejes distintos, y quien los enfrenta compara la disciplina de uno con la reactividad del otro, que es comparar la cerradura de una puerta con la velocidad de otra. Zustand frente a Redux Toolkit sí comparten reactividad, así que ahí la pregunta legítima es solo cuánta disciplina quieres, y se responde mirando el tamaño y la vida del estado, no leyendo tuits. XState no compite con ninguno de ellos en el eje que la gente cree, porque no trae reactividad: se combina con cualquiera. Y los que proclaman que los CRDTs hacen obsoleto a Redux confunden dos tableros distintos: uno gobierna la mutación dentro de un proceso, el otro la fusión entre procesos, y una app real de colaboración usa los dos a la vez. Cuando internalizas el mapa, dejas de tener opiniones sobre librerías y empiezas a tener criterios sobre coordenadas. Ya no preguntas cuál gana, porque la pregunta no está bien formada: preguntas qué punto del plano necesita esta parte concreta de esta app concreta, y la respuesta cambia de una parte a otra dentro del mismo proyecto. Esa es la diferencia entre discutir herramientas y diseñar arquitecturas, y es también la vacuna contra la próxima moda: la que venga tendrá dos coordenadas como todas, y tú sabrás leerlas antes de que el marketing te diga qué sentir.

⚔️ Puebla tu propio mapa
  1. Dibuja el plano en un papel y clava en él las nueve familias de la lección con sus dos coordenadas y una frase que justifique cada una.
  2. Localiza el par de herramientas que compartan casi la misma reactividad y explica qué las separa de verdad en el otro eje.
  3. Demuestra con XState que la reactividad y la disciplina son independientes: describe dos maneras de propagar su estado sin cambiar su grafo de transiciones.
  4. Explica el vecino useReducer frente a Redux: qué comparten en el plano y en qué dimensión ajena al plano se separan.
  5. Argumenta por qué un CRDT no cabe como un punto del plano de dos ejes y qué tercera pregunta introduce.
  6. Toma una guerra de framework que hayas visto en un foro y reescríbela como un malentendido de coordenadas.