Tres antipatrones que arruinan la arquitectura
Toda regla de arquitectura tiene su reverso: el error que la viola de forma tan característica que merece un nombre. Esta lección diseca los tres antipatrones que arruinan la arquitectura de estado con más frecuencia, cada uno como la negación de una decisión de las lecciones anteriores. El primero, meterlo todo en un store global, viola la separación de clases: convierte el store en un vertedero donde apariencia efímera, dato de dominio y verdad remota se pudren juntos. El segundo, poner el estado del servidor en Redux, cruza la frontera sagrada y reconstruye a mano una cache peor y con bugs. El tercero, sobre-arquitecturar lógica trivial, invierte la escalera de disciplina y paga ceremonia por un beneficio inexistente. Para cada uno nombra el síntoma que lo delata, la causa que lo origina y la corrección concreta, y cierra con la raíz común de los tres: tratar el estado como homogéneo.
Las lecciones anteriores construyeron reglas: separa las clases de estado, elige la disciplina que el problema merece, empieza simple y sube cuando duela. Esta lección las mira por su reverso, porque las reglas se entienden mejor a través de sus violaciones más características. Un antipatrón no es cualquier error: es un error tan común, tan tentador y tan destructivo que la comunidad le ha puesto nombre para poder señalarlo de lejos. Los tres que siguen —el store global que lo devora todo, el servidor metido en Redux, la arquitectura formal sobre un booleano— no son casos raros de mala praxis, sino los tres modos en que aplicaciones enteras, escritas por gente competente, se vuelven imposibles de mantener. Reconocerlos por su síntoma es la otra mitad de saber diseñar: la teoría dice qué hacer, los antipatrones dicen qué te delatará cuando dejes de hacerlo.
- Reconocer los tres antipatrones por el síntoma concreto que los delata en el código.
- Diagnosticar la causa de cada uno como la negación de una decisión de arquitectura.
- Aplicar la corrección específica que devuelve cada pieza a su sitio correcto.
- Identificar la raíz común de los tres: tratar el estado como una cosa homogénea.
Todo en un store global
Síntoma: un árbol de estado que no cabe en la cabeza, con apariencia efímera, sesión y datos de servidor revueltos. Niega que el estado tenga clases.
El servidor dentro de Redux
Síntoma: acciones y reducers cuyo único trabajo es pedir a la API y sincronizar a mano. Niega que la propiedad del dato importe.
Sobre-arquitecturar lo trivial
Síntoma: mucha ceremonia rodeando muy poca lógica. Niega que la forma del problema deba dictar la de la solución.
Antipatrón uno: todo en un store global
El primero es el más difuso y por eso el más extendido: el store global que empieza guardando algo legítimo y termina siendo el vertedero donde vive todo el estado de la app, sin distinción de clase. El síntoma que lo delata es un store cuyo árbol de estado no cabe en la cabeza: apariencia efímera de un desplegable junto a la sesión del usuario, junto a una lista traída del servidor, junto al borrador de un formulario. Cuatro clases con cuatro naturalezas, pudriéndose en el mismo cajón.
La causa es la negación de la primera lección: no clasificar. Cuando el store global es lo primero que montas, todo tiende a caer en él por gravedad, porque es el sitio más cómodo para poner cualquier cosa. El coste llega despacio y luego de golpe: cada componente se re-renderiza por cambios que no le incumben, nadie sabe qué parte del estado es dueña de su verdad y qué parte es copia, y el store se vuelve un acoplamiento global donde cualquier pieza puede depender de cualquier otra. La corrección es la de la lección uno: saca cada pieza a la herramienta de su clase —la apariencia efímera a useState, el servidor a la cache, lo navegable a la URL— y observa cuánto encoge lo que de verdad merecía estar en el store.
La prueba rápida para detectar este antipatrón: intenta describir en voz alta todo lo que vive en tu store global. Si la lista mezcla cosas de naturalezas distintas —lo que el usuario posee con lo que solo lee, lo durable con lo efímero— o si simplemente es demasiado larga para recitarla, el store ya es un vertedero. Un store sano guarda una sola clase de estado y su contenido se enumera en una respiración. Cuando necesitas un mapa para navegar tu propio estado global, el problema no es que falte documentación: es que metiste en un cajón lo que pedía cuatro.
Antipatrón dos: el servidor dentro de Redux
El segundo es el más específico y el más caro, y define toda una era de la disciplina: guardar el estado del servidor en el store de cliente. El síntoma es inconfundible: acciones, reducers y thunks cuyo único trabajo es pedir un dato a la API, guardarlo, y mantenerlo sincronizado a mano. Cientos de líneas que no expresan ninguna regla de tu dominio, solo la mecánica de una cache reimplementada peor de lo que ya existe.
La causa es cruzar la frontera sagrada de la lección uno, la que separa lo que posees de lo que solo lees. El estado del servidor es una cache con problemas de obsolescencia, revalidación y deduplicación, y un store de cliente no sabe nada de eso: te obliga a reconstruir a mano toda la maquinaria que TanStack Query te da hecha. La corrección es quirúrgica y de altísimo rendimiento: identifica cada dato que vino de una petición HTTP, sácalo del store, y dáselo a una cache dedicada. Con él se van los reducers y thunks que solo existían para leerlo.
// Antipatron: el servidor vive en el store, sincronizado a mano.
const slice = createSlice({
name: 'usuarios',
initialState: { lista: [], cargando: false, error: null },
reducers: {
peticionIniciada: (s) => { s.cargando = true; },
peticionExito: (s, a) => { s.lista = a.payload; s.cargando = false; },
peticionFallo: (s, a) => { s.error = a.payload; s.cargando = false; },
}, // una cache casera, peor y con bugs de obsolescencia
});
// Correccion: una cache de verdad se encarga de todo eso.
const { data, isPending, isError } = useQuery({
queryKey: ['usuarios'],
queryFn: traerUsuarios,
staleTime: 60_000, // frescura, revalidacion y deduplicacion incluidas
});
No fue un error de programadores descuidados: fue el patrón por defecto que la industria enseñó entre 2015 y 2020. La documentación, los tutoriales y las plantillas asumían que un dato del servidor se guardaba en Redux, porque no existía aún una herramienta dedicada a la cache de servidor. Cuando TanStack Query y SWR maduraron y se llevaron esa responsabilidad, buena parte del estado global de las apps se quedó sin trabajo, y ahí se explica en gran medida la caída de Redux entre 2020 y 2026. El antipatrón no era estupidez: era la mejor práctica de una época sin la herramienta correcta. Perpetuarlo hoy, con la herramienta ya disponible, sí es el error.
Antipatrón tres: sobre-arquitecturar lo trivial
El tercero es el reverso exacto del segundo en el eje de la disciplina: en lugar de meter demasiado en una herramienta, se pone demasiada herramienta alrededor de muy poco. El síntoma es la ceremonia sin lógica: un archivo de acciones, un reducer, un selector y un test, todo para alternar un booleano que un useState resolvía en una línea. Mucha infraestructura rodeando una lógica que cabría en un renglón.
La causa es invertir la escalera de la lección tres: adoptar el peldaño alto por si acaso, en vez de subir a él cuando el dolor lo pide. Se disfraza de rigor —usar la herramienta seria se siente profesional— pero es un impuesto que pagan a diario todos los que leen y modifican ese código, a cambio de garantías que un booleano no necesita. La corrección es bajar de peldaño sin miedo: si no hay estados imposibles que prevenir, no hace falta máquina; si no hay dominio que auditar, no hace falta arquitectura formal. La herramienta correcta para lógica trivial es la trivial.
flowchart TD P1[todo en un store global] -->|corregir| F1[una herramienta por clase de estado] P2[servidor dentro de Redux] -->|corregir| F2[cache dedicada TanStack Query] P3[arquitectura formal sobre un booleano] -->|corregir| F3[useState o store ligero] style P1 fill:#f38ba8,color:#11111b style P2 fill:#f38ba8,color:#11111b style P3 fill:#f38ba8,color:#11111b style F1 fill:#a6e3a1,color:#11111b style F2 fill:#a6e3a1,color:#11111b style F3 fill:#a6e3a1,color:#11111b
El antipatrón que finge no serlo: guardar lo derivable
Hay un cuarto error que merece mención porque se disfraza de estado legítimo mejor que los otros tres: guardar en el estado un valor que podría calcularse a partir de otro. El total de un carrito junto a sus líneas, el contador de no leídos junto a la lista de mensajes, el nombre completo junto al nombre y el apellido. Parece estado y no lo es: es una consecuencia, y guardarla crea la obligación de recalcularla en cada mutación de su fuente, con el riesgo permanente de que las dos versiones discrepen.
// Antipatron: el total vive en el estado y hay que recalcularlo a mano.
type Carrito = { lineas: Linea[]; total: number };
function anadir(c: Carrito, l: Linea): Carrito {
return { lineas: [...c.lineas, l], total: c.total + l.precio }; // dos fuentes de verdad
}
// Correccion: el total se deriva de las lineas y no puede desincronizarse.
type CarritoSano = { lineas: Linea[] };
const total = (c: CarritoSano) => c.lineas.reduce((s, l) => s + l.precio, 0);
El síntoma es el bug de sincronización interno: el total dice diez y las líneas suman doce porque alguien actualizó unas sin la otra. La corrección la dio el bloque de Niveles 6 al 8: no lo guardes, derívalo. Un valor computado a partir de su fuente no puede quedar desincronizado, porque no tiene una existencia propia que haya que mantener al día. Antes de asignarle herramienta a cualquier dato, pregúntate si es fuente o consecuencia; si se deduce de otro, no es estado y no entra en ninguna clase.
Fíjate en que los tres antipatrones tienen correcciones que apuntan en direcciones distintas —repartir, extraer, adelgazar— pero nacen del mismo gesto equivocado, y esa raíz común es lo que conviene grabar por encima de los tres casos concretos.
Es tentador aprender estos tres errores como una lista de cosas prohibidas y guardarlos por separado, pero eso sería perderse la lección que los une. Los tres son la misma falta —tratar el estado como una cosa homogénea— cometida en tres direcciones. Meterlo todo en un store global es negar que el estado tenga clases: se asume que un solo cajón sirve para cualquier dato porque en el fondo todo el estado se cree igual. Poner el servidor en Redux es negar que la propiedad importe: se trata una verdad prestada como si fuera propia porque no se distingue leer de poseer. Sobre-arquitecturar lo trivial es negar que la forma del problema deba dictar la de la solución: se aplica la misma disciplina a todo porque se ignora que un booleano y un flujo de pago tienen naturalezas distintas. En los tres casos el error de raíz es idéntico y precede a cualquier decisión de herramienta: creer que el estado es uniforme y que basta una buena receta —un store, Redux, la arquitectura seria— para gobernarlo entero. Todo el track ha sido, visto así, una larga refutación de esa creencia: el estado no es una cosa, son varias con dueños y formas incompatibles, y cada decisión de arquitectura no es más que el reconocimiento honesto de esa pluralidad. Por eso los antipatrones no se evitan memorizando la lista, sino interiorizando su negación: clasifica antes de guardar, distingue lo que posees de lo que lees, deja que la forma del problema elija la disciplina. Quien hace eso no necesita recordar los tres errores porque ninguno le resulta ya tentador; y quien no lo hace los reinventará todos, uno a uno, por mucho que se sepa la lista de memoria. La diferencia entre coleccionar prohibiciones y entender el principio que las genera es, otra vez, la diferencia entre seguir recetas y diseñar sistemas.
- Enumera el contenido de tu store global en voz alta; si mezcla clases o no cabe en una respiración, tienes el primer antipatrón.
- Busca en el store cualquier dato que haya venido de una petición HTTP: cada uno es una instancia del segundo antipatrón esperando corrección.
- Extrae ese dato a una cache dedicada y cuenta cuántas líneas de acciones, reducers y thunks mueren con él.
- Localiza la pieza de estado con más ceremonia por unidad de lógica; si la infraestructura supera a la lógica que gobierna, es el tercer antipatrón.
- Baja esa pieza al peldaño que su forma merece y verifica que la lógica de dominio sobrevive intacta al quitarle la maquinaria.
- Para cada antipatrón que encuentres, escribe en una frase qué decisión de las lecciones previas estabas negando al cometerlo.