La meta: minimizar, aislar, explicitar
Si no podemos eliminar el estado, al menos podemos domesticarlo. Tres principios guían toda buena arquitectura de estado: minimizarlo (cuanto menos estado, menos bugs), aislarlo (contenerlo para que su radio de impacto sea pequeño) y explicitarlo (nada escondido). Y una técnica por encima de todas: derivar en vez de duplicar. Aquí se desarrollan los cuatro y se muestra que en el fondo son uno solo, encoger la superficie de lo mutable y lo compartido.
Las tres lecciones anteriores fueron un diagnóstico: el estado es difícil, casi todo el que escribimos es accidental, y compartido más mutable es tóxico. Toca la receta. Si no podemos eliminar el estado del todo, sí podemos domesticarlo, y toda buena arquitectura lo hace con las mismas tres armas —minimizar, aislar y explicitar— más una técnica que las une a todas: derivar en vez de duplicar.
- Minimizar el estado: cada variable que eliminas es una clase de bugs que desaparece.
- Aislar el estado: reducir su radio de impacto acotando quién puede tocarlo.
- Explicitar el estado: lo escondido es lo más difícil de depurar.
- Derivar en vez de duplicar: calcular lo calculable en lugar de almacenarlo.
Minimizar: el mejor estado es el que no existe
Cada pieza de estado es un pasivo, no un activo. Añade una dimensión al espacio de estados, un invariante que mantener, una cosa que puede quedar obsoleta. La primera meta, por tanto, no es gestionar bien el estado sino tener poco. Colapsa los flags redundantes en una unión discriminada (lección 2), borra lo que puedas derivar, y no adelantes cachés que aún no necesitas. Menos variables mutables es, directa y literalmente, menos combinaciones que pueden salir mal.
Hay una asimetría que conviene grabar: eliminar una variable de estado no reduce los bugs de forma lineal, sino que borra una rama entera del árbol de posibilidades. Como el espacio crece de forma exponencial con el número de variables (2^n), cada eliminación tiene un efecto desproporcionado. Es la refactorización con mejor relación esfuerzo-recompensa que existe, y casi nunca aparece en las métricas, porque los bugs que previene nunca llegan a escribirse.
Minimizar tiene además un efecto sobre la comprensión que ninguna herramienta iguala. Un módulo con dos variables de estado se sostiene entero en la cabeza; con diez, ya no, y empiezas a razonar por fe. Reducir el número de variables no solo previene bugs: recupera la capacidad de tener el sistema completo en la mente, que es la condición para diseñarlo bien. El estado que no existe no hay que entenderlo, ni documentarlo, ni migrarlo, ni explicárselo a quien llegue después.
En la práctica, minimizar tiene tres movimientos recurrentes: fusionar los flags que describen una misma fase en una unión discriminada, borrar los campos derivables para calcularlos al vuelo, y retrasar toda caché hasta que un perfilado demuestre que hace falta. Ninguno requiere una librería; los tres son decisiones de modelado que caben en una tarde y pagan durante años.
Hay algo contraintuitivo en todo esto: eliminar estado se siente como perder control, cuando es justo lo contrario. Cada variable que quitas es una cosa menos que puede sorprenderte, un grado de libertad menos que el sistema puede usar para ponerse en un estado que no previste. El código que menos estados alcanza es el que más firmemente gobiernas, aunque a primera vista tenga menos palancas.
Aislar: reducir el radio de impacto
El segundo principio es la localidad. Un estado que vive dentro de un módulo o un componente y nunca escapa solo puede romperse desde dentro: su radio de impacto es el propio módulo. Un estado global y mutable, en cambio, puede alterarse desde cualquier archivo, y su radio de impacto es todo el programa. Encapsular no es una ceremonia académica: es reducir la superficie desde la que un dato puede corromperse.
De aquí viene la regla práctica de la colocación: mantén cada estado tan cerca como puedas de donde se usa, y elévalo solo hasta el ancestro común que de verdad lo necesita, ni un nivel más. Subir estado a un store global “por si acaso” es el error inverso al que parece: no da flexibilidad, ensancha el radio de impacto.
// Mal: estado subido al tope por si acaso. Radio de impacto: toda la app.
// store.menuUsuarioAbierto = false
// Bien: el estado vive donde se usa. Radio de impacto: este componente.
function MenuUsuario() {
const [abierto, setAbierto] = useState(false);
// nadie fuera puede corromper "abierto": no existe fuera de aqui
}
La regla no es tacañería: es que cada nivel al que subes un estado multiplica el número de sitios desde los que puede leerse y escribirse. Elevar es a veces necesario —dos hermanos que deben coordinarse—, pero cada elevación se paga en superficie, y la superficie se paga en bugs. Los actores, los módulos y los componentes son, todos, mecanismos para aislar estado; su valor es exactamente cuánto contienen.
Hay una tensión honesta entre aislar y compartir: bajar demasiado un estado obliga a pasarlo por muchas manos cuando de verdad se necesita en varios sitios. La respuesta no es subirlo por defecto, sino subirlo solo cuando aparece la necesidad real, y ni un momento antes. El diseño correcto es el mínimo alcance que satisface a todos los que de verdad usan el dato; ese punto casi nunca es la cima del árbol.
Minimizar
Borra todo estado que puedas. Cada variable de menos poda una rama del árbol de combinaciones y una clase entera de bugs.
Aislar
Acota quién puede tocar cada dato. El estado local solo se rompe desde dentro; el global, desde todas partes.
Explicitar
Nada escondido. Estado que puedes nombrar, loguear y serializar es estado que puedes depurar y auditar.
Derivar
No guardes lo calculable. Lo derivado es consistente por construcción; lo copiado es un invariante frágil que se romperá.
Explicitar: nada escondido
El tercer principio: el estado debe poder verse, nombrarse, registrarse y serializarse. Estado explícito es un objeto que puedes imprimir, inspeccionar en un devtool, guardar en disco y restaurar; estado implícito es una variable atrapada en un cierre, un campo mutable enterrado en lo hondo de un objeto, una caché que nadie declaró. Lo implícito es invisible, y lo invisible es indepurable: no puedes arreglar un estado que no puedes mirar.
La prueba del algodón de la explicitud es la serialización. Si puedes convertir todo el estado de una pantalla a JSON y reconstruirla idéntica desde ese JSON, tu estado es explícito de verdad; si algo se pierde en el viaje —una variable de cierre, un temporizador, una posición interna—, ese algo era estado implícito escondido. Persistir, hidratar, sincronizar entre pestañas y depurar viajando en el tiempo son la misma capacidad: solo funcionan sobre estado que existe como dato y no como efecto lateral.
| Estado explícito | Estado implícito (escondido) |
|---|---|
| Un objeto en un store, serializable | Una variable atrapada en un cierre |
| Un campo con nombre que puedes loguear | Un flag mutable enterrado en un objeto |
| El resultado de un reducer | El orden tácito en que corren unos efectos |
| Se reconstruye desde JSON | Se pierde al recargar la página |
Esta es, entera, la propuesta de valor de Redux y de las arquitecturas unidireccionales que verás más adelante: no inventan reactividad, imponen que todo el estado sea explícito y que todo cambio pase por un punto observable. El precio es ceremonia; el premio es que el estado del programa cabe en una pantalla y que cada transición deja registro. Cuando el estado es explícito, el time-travel debugging de la lección 1 deja de ser magia y se vuelve una consecuencia.
Explicitar tiene un coste real —más código, más verbosidad— y no todo estado lo merece: la posición de un tooltip no necesita ser serializable. El criterio es el valor del dato: cuanto más central, más caro es un bug en él y más vale la pena que sea explícito. Reserva la ceremonia para el estado que, si se corrompe, arruina la sesión, y deja respirar al estado trivial. Explicitar no es un dogma, es una inversión que se hace donde rinde.
Nombrar es el primer acto de explicitar. Un estado sin nombre —un booleano suelto, un índice mágico— es difícil de discutir, de loguear y de buscar; en cuanto le pones un nombre honesto pasa a existir para el equipo, no solo para el compilador. Modelar el estado con tipos y nombres precisos no es decoración: es convertir suposiciones tácitas en contratos que cualquiera puede leer.
Derivar en vez de duplicar
La cuarta idea es una técnica, y la más poderosa de todas. Si un valor puede calcularse a partir de otro, calcúlalo; no lo guardes. Duplicar un dato crea un invariante —“esta copia debe coincidir con el original”— que solo tu disciplina sostiene y que, a escala, siempre se rompe. Derivar elimina el invariante de raíz: lo derivado no puede discrepar del original porque no existe por separado.
// Duplicar: hay que mantener sincronizado a mano. Se romperá.
const usuario = { nombre: "Ada", apellido: "Lovelace", completo: "Ada Lovelace" };
usuario.nombre = "Grace"; // "completo" ahora miente: sigue diciendo Ada
// Derivar: imposible que discrepe, porque no se almacena.
const nombreCompleto = (u: { nombre: string; apellido: string }) =>
`${u.nombre} ${u.apellido}`;
nombreCompleto({ nombre: "Grace", apellido: "Hopper" }); // siempre coherente
flowchart LR F[estado fuente: minimo y esencial] --> D1[derivado: total] F --> D2[derivado: lista filtrada] F --> D3[derivado: nombre completo] style F fill:#89b4fa,color:#11111b style D1 fill:#a6e3a1,color:#11111b style D2 fill:#a6e3a1,color:#11111b style D3 fill:#a6e3a1,color:#11111b
Esto es exactamente lo que hacen los memos, los computed y los selectores que estudiarás en el nivel 6: estado derivado, recalculado cuando cambian sus entradas y memoizado para que sea barato. El deber del arquitecto es trazar la frontera entre el estado fuente —lo mínimo, esencial e irreducible— y todo lo demás, que debe fluir de él por derivación. Cuanto más ancha la zona derivada y más estrecha la fuente, menos hay que sincronizar y menos puede romperse.
Hay un límite que conviene nombrar: derivar cuesta cálculo, y a veces ese cálculo es caro. La respuesta no es volver a duplicar, sino memoizar —cachear el resultado de la derivación e invalidarlo solo cuando cambian sus entradas—, que es una copia honesta con reglas explícitas, no un dato duplicado a la deriva. Esa diferencia la desarrolla el nivel 6; por ahora basta con saber que derivar sigue ganando, y que su único coste tiene una cura que no reintroduce el problema original.
Derivar es, además, el puente natural hacia la última lección. Si todo lo calculable se calcula desde unas pocas fuentes, esas fuentes son las que de verdad hay que gobernar, y la pregunta se desplaza de “cómo mantengo todo sincronizado” a “quién es el dueño de cada fuente”. Minimizar, aislar y explicitar preparan el terreno; derivar traza el camino directo hacia la fuente única de verdad.
La derivación se encadena: de la fuente sale un derivado, y de ese derivado, otro. Una lista de tareas deriva las pendientes; de las pendientes deriva su número; del número deriva el texto de la insignia. Mientras cada eslabón calcule y no almacene, toda la cascada permanece coherente sin una sola línea de sincronización. Es el nivel 6 en germen, y la razón de que derivar escale donde duplicar colapsa.
Parecen cuatro reglas independientes, pero son cuatro caras del mismo gesto: encoger la superficie de lo mutable y lo compartido, la pareja tóxica de la lección anterior. Minimizar reduce cuánto estado mutable existe. Aislar reduce quién puede mutarlo. Explicitar reduce cuánto de él está oculto y, por tanto, fuera de control. Derivar reduce cuánto hay que mantener sincronizado, que es la forma más insidiosa de compartir: dos copias del mismo hecho. Cada principio ataca un flanco de la misma bestia. Por eso una gran arquitectura de estado no es la que usa la librería más lista, sino aquella en la que hay poco estado, vive en pocos lugares, nada está escondido y todo lo calculable se calcula. Cuando eso está bien hecho, la elección de librería es casi irrelevante: el noventa por ciento del trabajo ya está resuelto antes de importar nada, y el signal o el store que elijas solo administra el pequeño núcleo esencial que quedó. Los principiantes debaten herramientas; los expertos primero encogen la superficie y solo entonces eligen con qué cubrir lo poco que queda.
Antes de escribir código, dibuja dos conjuntos: el estado fuente (lo que de verdad guardas) y el derivado (lo que calculas de él). Casi todo diseño de estado mejora al mover una pieza de fuente a derivado. Si dudas de dónde va algo, pregúntate: ¿puede reconstruirse de lo demás? Si la respuesta es sí, es derivado, y guardarlo fue un reflejo, no una decisión.
- Toma un objeto de estado y elimina un campo derivable, calculándolo al vuelo. Comprueba que un bug de sincronización que antes era posible ya no puede ocurrir.
- Busca un estado global de tu app que solo use un rincón de la interfaz. Bájalo hasta ese rincón (colocación) y mide cuánto se estrechó su radio de impacto.
- Encuentra estado implícito —una variable de cierre, un campo mutable escondido— y hazlo explícito: sácalo a un objeto que puedas imprimir y serializar.
- Dibuja la frontera fuente/derivado de un módulo. ¿Qué proporción es fuente? Intenta mover una pieza más al lado derivado sin perder ninguna funcionalidad.