Estado derivado: no guardar lo que puedes calcular
La frontera entre lo que debes almacenar y lo que debes calcular decide la corrección de toda tu aplicación. El estado fuente es aquello que no puedes deducir de nada más; el derivado es todo lo demás. Guardar también lo derivado —el nombre completo, la lista filtrada, el total del carrito— introduce dos representaciones de la misma verdad, y dos verdades pueden divergir. Este es el principio fundacional del nivel: no dupliques la verdad, deriva.
Hay una pregunta que separa una arquitectura de estado sana de una plagada de bugs sutiles: para cada dato que guardas, ¿es una verdad primitiva o el resultado de calcular otras verdades? El estado fuente es aquello que no puedes deducir de nada más; el estado derivado es todo lo que sí. La tentación constante es guardar también lo derivado —el nombre completo junto al nombre y el apellido, la lista filtrada junto a la lista y el filtro, el total junto a las líneas del carrito— porque parece cómodo tenerlo ya calculado. Es un espejismo caro: en el instante en que existen dos representaciones de la misma verdad, existe la posibilidad de que discrepen, y esa discrepancia es la fábrica silenciosa de los bugs de estado más difíciles de reproducir. Este nivel entero gira sobre un solo eje —la derivación— y empieza por su principio fundacional: no guardes lo que puedes calcular.
- Distinguir con rigor el estado fuente del derivado y por qué esa frontera decide la corrección.
- Diagnosticar el antipatrón de duplicar la verdad y el mecanismo por el que dos copias divergen.
- Expresar derivaciones como funciones puras del estado fuente con los primitivos de 2026.
- Erradicar los efectos que sincronizan a mano un estado que debería derivarse.
Estado fuente y estado derivado
La distinción es más profunda de lo que parece. Llamamos estado fuente al conjunto mínimo de datos del que todo lo demás puede deducirse: es la entrada, la verdad primitiva, aquello que solo cambia cuando alguien lo cambia deliberadamente. El estado derivado es una proyección de esa entrada: una función pura que, dado el mismo estado fuente, devuelve siempre el mismo resultado y no tiene existencia independiente. La ecuación que gobierna todo el nivel es derivado = f(fuente), y su exigencia es que f sea determinista y sin efectos: nada de lecturas de reloj, de aleatoriedad ni de mutaciones. Es la misma normalización que aplica una base de datos relacional, donde las tablas base son la fuente y las vistas son consultas que nunca se materializan sin necesidad.
La prueba para clasificar un dato es directa y vale para cualquier framework: pregúntate si ese valor pudiera cambiar por su cuenta, sin que cambie ninguna de sus entradas. Si tal cambio sería un bug, el dato es derivado y no debe almacenarse; si fuera una operación legítima del usuario o del sistema, es fuente. El total de un carrito nunca debería cambiar sin que cambien sus líneas: es derivado. La cantidad de una línea sí cambia por sí sola cuando el usuario la edita: es fuente. Aplicar esta pregunta a cada campo de tu estado es el ejercicio más rentable de todo el diseño reactivo.
Fuente: la verdad primitiva
Lo que no puedes deducir de nada más y solo cambia por una acción deliberada. Es el conjunto mínimo que debes almacenar y proteger. Todo lo demás cuelga de aquí.
Derivado: una función pura
Una proyección determinista del estado fuente. Mismo input, mismo output, sin existencia propia. Calcularlo siempre es correcto; almacenarlo abre la puerta a la divergencia.
La prueba de clasificación
¿Podría este dato cambiar solo, sin que cambien sus entradas? Si eso sería un bug, es derivado. Si sería una acción válida, es fuente. Una pregunta por cada campo.
El pecado de duplicar la verdad
El daño de guardar lo derivado no está en la fórmula, sino en la redundancia. En el momento en que el nombre completo vive como un useState propio además del nombre y el apellido, la aplicación tiene dos lugares que afirman la misma cosa, y la corrección pasa a depender de que todas las rutas de mutación mantengan ambos al día. Basta un camino —un formulario secundario, una acción de deshacer, una carga desde el servidor— que actualice la fuente y olvide la copia, y las dos verdades divergen. El bug resultante es de los peores: intermitente, dependiente del orden de las acciones y casi imposible de reproducir, porque el estado inconsistente solo aparece tras una secuencia concreta que nadie recuerda.
La redundancia escala mal por naturaleza. Cada copia derivada que almacenas multiplica el número de sitios que debes recordar sincronizar, y ese número crece con cada nueva forma de mutar la fuente. Lo que empezó como una comodidad —leer el valor ya calculado— se convierte en una obligación permanente de mantenimiento que se paga en cada cambio futuro del código. La tabla siguiente reúne las duplicaciones más frecuentes y su fuente real; en todas, la columna izquierda no debería existir como estado.
| Lo que se guarda de más | La fuente real de la que deriva |
|---|---|
nombreCompleto |
nombre y apellido |
listaFiltrada |
lista y filtro |
total del carrito |
las líneas y sus cantidades |
estaVacio o contador |
la propia colección |
esValido de un formulario |
los valores de los campos |
| el objeto seleccionado | el id seleccionado y la colección |
flowchart TD S[estado fuente nombre y apellido] --> F[funcion pura de derivacion] F --> D[derivado nombre completo] S --> C[copia guardada del nombre completo] C --> X[riesgo de dos verdades que divergen] style D fill:#a6e3a1,color:#11111b style C fill:#fab387,color:#11111b style X fill:#f38ba8,color:#11111b
Almacenar un dato derivado no cuesta una línea, cuesta un invariante. A partir de ese momento, toda mutación de la fuente contrae la deuda de actualizar también la copia, y esa deuda se cobra en cada rama del código que toque la fuente, incluidas las que aún no has escrito. La única forma de no poder olvidar la sincronización es no tener nada que sincronizar.
Derivar en lugar de sincronizar
El antídoto es dejar de tratar lo derivado como estado y empezar a tratarlo como expresión. En React esto significa calcular durante el render, sin useState ni useEffect de por medio: el valor se recompone en cada render a partir del estado fuente y nunca puede quedar obsoleto. El efecto que sincroniza dos trozos de estado es uno de los antipatrones que la propia documentación de React desaconseja bajo el lema de que quizá no necesitas un efecto; su única función es reintroducir la posibilidad de divergir que la derivación directa elimina de raíz.
// Antipatron: estado derivado guardado y sincronizado con un efecto
function Perfil({ nombre, apellido }) {
const [completo, setCompleto] = useState(nombre + " " + apellido);
useEffect(() => {
setCompleto(nombre + " " + apellido); // pura ceremonia que puede desincronizar
}, [nombre, apellido]);
return <h1>{completo}</h1>;
}
// Derivado: una sola verdad, el resto se calcula al vuelo y nunca envejece
function Perfil({ nombre, apellido }) {
const completo = nombre + " " + apellido; // funcion pura del estado fuente
return <h1>{completo}</h1>;
}
Fuera de React, el ecosistema de 2026 ofrece primitivos que hacen de la derivación un ciudadano de primera clase, cacheado y recalculado solo cuando cambian sus entradas: el computed de Vue y de Angular, el rune $derived de Svelte 5, el createMemo de Solid, el Signal.Computed de la propuesta de señales del TC39 y el computed de MobX comparten la misma semántica. En todos, la derivación es un valor calculado que se declara una vez y se mantiene coherente por construcción, no un estado que tú sincronizas a mano.
// Vue y Angular: computed cachea y se recalcula solo si cambian sus dependencias
const completo = computed(() => `${nombre.value} ${apellido.value}`);
// Svelte 5: $derived es una expresion reactiva, no un estado que mantener
let completo = $derived(`${nombre} ${apellido}`);
// Solid y Signals TC39: la derivacion es un valor calculado, jamas almacenado
const completo = createMemo(() => `${nombre()} ${apellido()}`);
La heurística práctica es implacable: cuando encuentres un useEffect cuyo cuerpo se limita a llamar a un setter con un valor calculado a partir de otro estado, casi siempre puedes borrar el efecto y el estado, y sustituirlos por una constante calculada en el render. Estás cambiando dos fuentes de fallo —el estado redundante y el efecto que lo mantiene— por una expresión que no puede equivocarse.
El conjunto mínimo y la normalización
Llevar la regla al límite conduce a una pregunta de diseño: ¿cuál es el conjunto mínimo de estado del que todo lo demás se reconstruye? Responderla suele descubrir que gran parte de lo que guardabas era derivado. El caso más rentable es el estado de colecciones: en lugar de guardar la lista, la lista ordenada, la lista filtrada y el elemento seleccionado como cuatro estados, guardas la colección normalizada —las entidades indexadas por id— junto al puñado de valores fuente que gobiernan las vistas —el criterio de orden, el filtro, el id seleccionado— y derivas todo lo demás.
// Fuente minima: entidades por id, mas los controles de vista
const estado = {
porId: { 1: { id: 1, nombre: "Ada" }, 2: { id: 2, nombre: "Alan" } },
orden: "nombre",
seleccionadoId: 2,
};
// Todo lo demas se deriva de esa fuente, nunca se almacena
const lista = Object.values(estado.porId);
const ordenada = [...lista].sort(porCampo(estado.orden));
const seleccionado = estado.porId[estado.seleccionadoId] ?? null;
Hay un antipatrón vecino que la regla también proscribe: espejar las props en el estado. Copiar una prop a un useState en el inicializador crea una segunda verdad que no se actualiza cuando la prop cambia, el clásico estado que se queda pegado al valor inicial. Si el valor deriva de la prop, calcúlalo en el render; si de verdad necesitas reiniciar el estado interno cuando una prop cambia, la herramienta correcta es la key, no un efecto que copie el valor.
// Antipatron: espejar la prop en estado; se queda pegada al valor inicial
function Campo({ valorInicial }) {
const [valor, setValor] = useState(valorInicial); // no se actualiza si cambia la prop
return <input value={valor} onChange={(e) => setValor(e.target.value)} />;
}
La normalización es la regla de este nivel aplicada a las colecciones: una sola copia canónica de cada entidad, indexada por su identidad, y todas las vistas —ordenada, filtrada, agrupada, seleccionada— como derivaciones de esa copia. Es exactamente el modelo que imponen createEntityAdapter de Redux Toolkit y la cache normalizada de las librerías de datos, y por la misma razón: mantener muchas vistas coherentes es imposible; mantener una fuente y derivar muchas vistas de ella es gratis.
La idea que trasciende cualquier framework es que el estado no es un almacén de datos, sino la respuesta a una pregunta de diseño: ¿cuál es el conjunto mínimo de verdades del que todo lo observable puede deducirse? Ese conjunto mínimo es el estado fuente, y merece existir; cada byte que guardas fuera de él es una apuesta a que podrás mantenerlo coherente para siempre, una apuesta que la experiencia enseña que se pierde. Duplicar la verdad no es un problema de rendimiento ni de elegancia, es un problema de corrección: dos representaciones de un mismo hecho definen un invariante —que sean iguales— que nada en el lenguaje ni en el runtime obliga a cumplir, y los invariantes que dependen de la disciplina humana se rompen exactamente cuando más caro resulta, en producción y tras una secuencia de acciones irrepetible. Derivar, en cambio, hace de la coherencia una propiedad estructural en lugar de una esperanza: si el nombre completo es una función del nombre y el apellido, es imposible que discrepen, porque no hay dos cosas, hay una fuente y una vista de ella. Por eso el diseño reactivo maduro invierte el instinto del principiante: no se pregunta qué necesito mostrar y lo guarda, sino qué es irreducible y deriva el resto. Minimizar la fuente y maximizar la derivación no es una optimización tardía, es la decisión arquitectónica que determina cuántos de tus bugs futuros serán, literalmente, imposibles de escribir. Todo lo demás en este nivel —memoización, selectores, grafos de derivación— son técnicas para hacer que derivar sea también barato; pero la razón para derivar no es nunca el rendimiento, es la verdad única.
- Recorre el estado de un componente real y aplica a cada campo la prueba: ¿podría cambiar solo, sin que cambien sus entradas? Marca cuáles son, en realidad, derivados.
- Localiza un
useEffectcuyo único cometido sea sincronizar unuseStatecon otro estado, y reemplázalo por una constante calculada en el render. - Provoca a propósito la divergencia: actualiza la fuente por un camino que olvide la copia derivada y observa el estado inconsistente resultante.
- Reescribe una lista filtrada guardada como estado para que sea
computedo$derivedde la lista y el filtro, y comprueba que ya no puede quedar obsoleta. - Redacta, para tu módulo, la lista explícita de estado fuente y declara que todo lo demás debe derivarse de ella.