El futuro del estado
Esta lección cierra el nivel y el track entero mostrando la consecuencia más profunda de local-first: la disolución de la frontera entre cliente y servidor que organizó el desarrollo web durante veinte años. Cuando una réplica local es la fuente de verdad y un motor de sincronización la mantiene fresca, la capa de API desaparece, sincronizar sustituye a pedir, y la distinción entre estado de cliente y estado de servidor —el eje que estructuró todo el track— se revela como un artefacto de la red y no como una verdad sobre los datos. Pero el futuro no es una utopía resuelta, y la lección es honesta sobre los tres retos que aún frenan la adopción masiva: la evolución del esquema cuando miles de réplicas ejecutan versiones distintas de la app, la autorización y los permisos cuando el cliente sostiene los datos y puede estar desconectado, y el tamaño cuando los metadatos de los CRDTs y la historia crecen sin una recolección de basura segura. El cierre sintetiza el viaje completo del track —de la fuente única de verdad a la reactividad, del flujo unidireccional a los CRDTs— y sitúa al lector ante la pregunta que define la próxima década del estado.
Has recorrido un track largo. Empezó con una pregunta engañosamente simple —qué es el estado— y te llevó por la fuente única de verdad, la reactividad y los signals, el flujo unidireccional de Redux, las máquinas de estados, el estado de servidor y su caché, la URL como fuente de verdad, los formularios, las actualizaciones optimistas, la persistencia y el modo offline, hasta desembocar aquí, en los CRDTs y los motores de sincronización. Si miras atrás, todo el viaje giró en torno a una frontera que nunca cuestionamos: la que separa el cliente del servidor. Distinguíamos estado de cliente de estado de servidor, elegíamos herramientas distintas para cada lado, y organizábamos nuestra arquitectura alrededor de esa línea. Esta última lección sostiene una tesis incómoda: local-first no mejora esa frontera, la disuelve. Cuando los datos viven en el dispositivo y un motor los sincroniza solo, deja de tener sentido preguntar de qué lado está un dato, porque el estado del servidor se ha vuelto una consulta local más. Pero disolver una frontera de veinte años no es gratis ni está terminado, y esta lección es tan honesta con la promesa como con los tres muros que aún la frenan: el esquema, los permisos y el tamaño. Es el cierre del track, y su pregunta abierta.
- Comprender por qué local-first disuelve la frontera cliente/servidor y convierte sincronizar en el nuevo pedir.
- Ver la distinción estado de cliente frente a estado de servidor como un artefacto de la red, no como una verdad sobre los datos.
- Analizar los tres retos abiertos: evolución del esquema, autorización y tamaño de los datos.
- Sintetizar el arco completo del track y situar la pregunta que define el futuro del estado.
La frontera que se disuelve
Durante veinte años la web se construyó sobre una división del trabajo tan asumida que rara vez se enunciaba: el servidor posee los datos y el cliente los pinta. Entre ambos, una capa de API —REST, GraphQL, RPC— traducía las intenciones del cliente en operaciones sobre la base de datos del servidor, y a su alrededor floreció todo un ecosistema para paliar la distancia: cachés, invalidación, serialización, estados de carga, reintentos, deduplicación. Ese aparato entero existía por una sola razón: los datos estaban lejos, al otro lado de una red poco fiable, y había que ir a buscarlos. Local-first elimina la razón. Si una réplica de los datos vive en el dispositivo y un motor la mantiene fresca, ya no hay que ir a ninguna parte: el dato está aquí. Y cuando el dato está aquí, la capa de API no se optimiza, se evapora.
// El stack de ayer: pedir, esperar, cachear, invalidar a mano
const { data, isLoading, error } = useQuery(['tareas'], () =>
fetch('/api/tareas').then((r) => r.json())
)
// El stack de manana: declaras lo que quieres; el motor lo mantiene vivo
const tareas = useQuery(z.query.tarea.where('activa', '=', true))
// sin isLoading perpetuo, sin invalidar: la consulta local ya esta fresca
flowchart TB subgraph Antes C1[cliente] -->|fetch| API[capa de API] API --> CACHE[cache e invalidacion] CACHE --> DB1[base de datos remota] end subgraph Despues C2[cliente] -->|consulta local| REP[replica local] REP <-->|sincroniza en segundo plano| DB2[base de datos del servidor] end style API fill:#f38ba8,color:#11111b style CACHE fill:#f38ba8,color:#11111b style REP fill:#a6e3a1,color:#11111b
El eslogan que resume el cambio es que sincronizar es el nuevo pedir. En el viejo modelo, cada dato que querías mostrar era una petición explícita que tú orquestabas; en el nuevo, declaras qué datos necesitas —una shape, una consulta— y el motor se encarga de que estén y sigan estando. La consecuencia para el track es demoledora: la distinción entre estado de cliente y estado de servidor, que estructuró niveles enteros y te hizo elegir entre Zustand y TanStack Query, se revela como lo que siempre fue —un artefacto de la red—. No era una propiedad de los datos que unos vivieran en el cliente y otros en el servidor; era una consecuencia de dónde estaban físicamente y de lo caro que era moverlos. Borra ese coste y la distinción se desvanece: todo el estado es, sencillamente, estado, consultable localmente y sincronizado por debajo.
Programar contra datos, no contra la red
El cambio no es solo de arquitectura, sino de la postura mental de quien programa. Durante veinte años programaste contra la red: pensabas en peticiones, en cuándo dispararlas, en qué mostrar mientras llegaban, en cómo cachear la respuesta y cuándo invalidarla. La red era un actor de primera fila en tu código, con su latencia, sus fallos y sus estados intermedios metidos dentro de cada componente. En un mundo local-first programas contra datos: declaras qué información necesita una vista y la lees como si siempre hubiera estado ahí, porque lo está. La pregunta cuándo pido esto desaparece, y con ella la maquinaria mental que la rodeaba.
Esa postura reabre un tema de los primeros niveles con un alcance nuevo: la reactividad deja de detenerse en el navegador. Ya no basta con que un signal reaccione a otro dentro del cliente; ahora un cambio en el servidor, propagado por el motor, actualiza tu réplica local, que a su vez dispara la reactividad de tu interfaz, sin que escribas una sola línea de sincronización. El grafo de dependencias que estudiaste al principio se extiende más allá de la pestaña y abarca la red entera. Programar deja de ser orquestar movimientos de datos y pasa a ser declarar relaciones entre ellos, dejando que la infraestructura los mantenga ciertos.
El giro mental más profundo es que el grafo reactivo pierde sus fronteras. En los primeros niveles, un cambio de estado se propagaba dentro del cliente; ahora se propaga a través del motor de sincronización hasta el servidor y de vuelta a todas las demás réplicas. Lo que aprendiste sobre signals, derivaciones y efectos no se queda pequeño: se convierte en la teoría local de un fenómeno global. El mismo modelo mental que explicaba por qué se recalcula un valor derivado explica ahora por qué se actualiza la pantalla de otro usuario al otro lado del mundo.
Tres retos sin resolver
Sería deshonesto pintar esto como una utopía consumada. Local-first funciona espléndidamente para una clase de aplicaciones —colaborativas, con datos acotados por usuario, tolerantes a la consistencia eventual— y choca con tres muros duros que explican por qué no es todavía el modo por defecto de construir software.
Evolución del esquema
Miles de réplicas ejecutan versiones distintas de tu app, con esquemas distintos, quizá durante meses. Migrar un campo, renombrarlo o cambiar su tipo sin romper a los clientes viejos ni corromper la fusión de CRDTs es un problema abierto de compatibilidad hacia atrás y hacia delante.
Autorización y permisos
Cuando el cliente sostiene los datos y puede estar desconectado, no puedes validar cada petición en el servidor como en REST. ¿Quién puede leer o escribir qué? La seguridad a nivel de fila, la replicación parcial y las capacidades tienen que sustituir a la comprobación por petición, y el servidor aún debe validar cada escritura.
Tamaño de los datos
Los CRDTs cargan metadatos por cada operación histórica y dejan lápidas al borrar; la historia crece sin fin. La recolección de basura es insegura mientras exista una réplica desconectada que quizá no ha visto un cambio. Y como la base entera no cabe en el cliente, la replicación parcial es obligatoria, no opcional.
De los tres, el del esquema es el más subestimado y quizá el más difícil. En una arquitectura clásica migras el esquema en un sitio —el servidor— y todos los clientes ven el cambio a la vez. En local-first el esquema vive replicado en miles de dispositivos que actualizan la app cuando les apetece, así que en cualquier momento coexisten versiones incompatibles editando y fundiendo los mismos datos. Un documento CRDT codifica su esquema de forma implícita en su estructura, y evolucionarlo exige que las versiones nuevas lean lo que escribieron las viejas y viceversa, sin que la fusión produzca estados absurdos. Es el problema de la compatibilidad bidireccional llevado al extremo, y no tiene aún una solución general.
// El cliente nuevo debe leer datos escritos por el viejo, y viceversa
function leerTitulo(doc: DocTarea): string {
// la version 2 renombro 'nombre' a 'titulo'; ambas deben coexistir
return doc.titulo ?? doc.nombre ?? 'sin titulo'
}
// migrar de golpe romperia a quien aun corre v1 y sigue escribiendo 'nombre'
El de los permisos es igual de arduo por una razón simétrica. En REST, cada petición pasa por el servidor, que comprueba en ese instante si el usuario puede hacer lo que pide; la autorización es un guardián en la puerta. En local-first el usuario ya tiene los datos en su dispositivo y puede editarlos sin conexión, así que no hay puerta donde plantar al guardián en el momento de la escritura. La autorización debe repartirse en dos: decidir qué se replica a cada quién —eso es la replicación parcial, que impide leer lo que no te toca— y validar cada escritura cuando por fin llega al servidor, que sigue siendo el árbitro último de lo permitido. Modelos como los grupos de Jazz o la seguridad a nivel de fila de Postgres son intentos serios, pero ninguno iguala todavía la simplicidad del guardián único.
El del tamaño es el más físico y el que más pronto se topa uno en la práctica. Un CRDT paga la convergencia en metadatos, y esos metadatos crecen con cada operación histórica, no con el tamaño del dato visible; un documento pequeño con un millón de ediciones puede pesar muchísimo más que su contenido. Compactar esa historia es peligroso, porque no puedes descartar con seguridad lo que una réplica desconectada quizá aún necesita para fundir, y esa réplica podría reaparecer meses después. A la vez, como la base de datos completa jamás cabe en un teléfono, la replicación parcial deja de ser una optimización y se vuelve un requisito de arranque. Gestionar el crecimiento y decidir qué vive en cada dispositivo es ingeniería que aún se está inventando.
Hay aplicaciones que no encajan y probablemente nunca encajen en local-first, y reconocerlo es signo de criterio, no de derrota. Un dato que debe ser instantáneamente consistente para todos —el saldo de una cuenta, el inventario de la última entrada disponible— no tolera la consistencia eventual. Un conjunto de datos gigantesco que ningún cliente puede replicar ni en parte útil. Un dominio donde la autoridad del servidor es un requisito legal y no una decisión técnica. Para esos casos, la arquitectura cliente/servidor con su capa de API sigue siendo la respuesta correcta. Local-first amplía el repertorio; no lo reemplaza.
Qué queda del track
Mira el arco completo y verás que no fue una lista de herramientas, sino la persecución de una sola idea a través de contextos cada vez más difíciles: una fuente única de verdad para cada dato. La buscaste en un componente, en un store global, en la URL, en el caché del servidor, y en cada nivel la misma idea se dobló para caber en un problema nuevo. Los CRDTs son el capítulo final de esa búsqueda, y su respuesta es la más radical: cuando hay muchas réplicas y ninguna manda, la fuente única de verdad no es un lugar sino una propiedad matemática —la convergencia—, una garantía de que todas las copias, por caminos distintos, llegan al mismo sitio.
Ninguna de las técnicas que aprendiste se descarta en este mundo nuevo; todas se recontextualizan. La reactividad que estudiaste al principio reaparece aquí como la sincronización que propaga cambios entre dispositivos. El flujo unidireccional reaparece como el log de operaciones que un CRDT funde. Las actualizaciones optimistas dejan de ser un truco para volverse el modo por defecto de una app cuyo estado es local. Y la disciplina de la inmutabilidad, que parecía un detalle de rendimiento de React, resulta ser la condición que hace posible razonar sobre versiones e historias en un sistema distribuido. El track no te enseñó herramientas sueltas: te enseñó variaciones de un mismo tema, y este último nivel las reúne en un mundo sin centro.
Si hay una sola cosa que llevarte de todo el track, es esta: gestionar estado nunca fue un problema técnico sobre variables y renders, sino un problema epistemológico y político sobre la verdad y el poder. Epistemológico, porque cada patrón que estudiaste —la fuente única de verdad, el flujo unidireccional, los reducers puros, los CRDTs— es una respuesta distinta a la misma pregunta: cómo sabe un sistema qué es cierto cuando la información está dispersa y cambia. Y político, porque dónde reside esa verdad decide quién manda. Cuando la verdad vive en el servidor, manda el proveedor y el usuario pide permiso; cuando vive en una réplica local que converge con las demás sin árbitro, manda el usuario y el servidor se vuelve un mensajero. Local-first no es, en el fondo, una moda de arquitectura: es la aplicación al software de una convicción sobre a quién deben pertenecer los datos, y los CRDTs son la matemática que por fin la hace viable sin sacrificar la colaboración. Por eso este track terminó donde terminó y no en otra librería de moda. Te enseñó a gestionar estado, sí, pero lo que de verdad te entrenó fue el ojo para ver, detrás de cada decisión sobre dónde vive un dato y quién puede cambiarlo, una decisión sobre quién tiene el poder. Esa mirada no caduca con la próxima librería. La frontera cliente/servidor se disolverá, aparecerán abstracciones que hoy no imaginamos, y la pregunta seguirá siendo la misma que abrió el track: dónde vive la verdad, quién la custodia y a quién sirve. Sabrás responderla porque ahora entiendes que esa, y no la sintaxis de ningún hook, era siempre la pregunta.
- Recorre mentalmente el track de principio a fin y escribe, en una sola frase por nivel, qué respuesta daba cada uno a la pregunta dónde vive la fuente de verdad.
- Toma una app real que uses y decide con criterio si local-first le conviene o si la arquitectura cliente/servidor es la correcta para ella. Justifícalo con los tres retos.
- Diseña sobre papel cómo evolucionarías el esquema de un CRDT —añadir un campo obligatorio— sin romper a los clientes que ejecutan la versión anterior.
- Explica en tres frases por qué la autorización por petición de REST no basta cuando el cliente sostiene los datos y puede estar desconectado.
- Calcula el crecimiento de metadatos de un CRDT de texto tras un millón de pulsaciones de tecla y argumenta por qué la recolección de basura es insegura.
- Escribe tu propia tesis, en un párrafo, sobre qué significará gestionar estado dentro de diez años, apoyándote en la idea de que la frontera cliente/servidor se disuelve.