wandres.dev
NIVEL DIOS: LOCAL-FIRST · síntesis y futuro

El modelo mental completo: cuatro ideas explican el track entero

Cincuenta y ocho niveles se comprimen en cuatro ideas encadenadas: la autoridad se muda al cliente, la convergencia sustituye al arbitraje, el almacenamiento local dejó de ser un juguete y sincronizar era lo fácil.

⏱ 26 min

Un recorrido de cincuenta y ocho niveles corre siempre el mismo riesgo: quedar en la memoria como un inventario de siglas, algoritmos y bibliotecas que uno sabe nombrar pero no sabe ordenar. La prueba de que el track ha funcionado no es recordar qué hace un OR-Set ni cómo negocia una conexión un transporte sobre QUIC, sino poder reconstruir por qué esas piezas existen partiendo de un número muy pequeño de decisiones. Ese número es cuatro. Las cuatro ideas se enunciaron por separado, cada una con su nivel y sus ejemplos, y esta lección las pone juntas por primera vez para mostrar que no son cuatro temas sino una sola cadena: la segunda es obligatoria en cuanto aceptas la primera, la tercera es lo que hizo la primera viable fuera del laboratorio, y la cuarta es la factura que llega cuando las otras tres ya están en su sitio.

🎯 Al terminar esta lección sabrás
  • Enunciar las cuatro ideas del recorrido como una cadena de consecuencias y no como una lista de temas.
  • Explicar por qué mover la autoridad obliga a sustituir el arbitraje por la convergencia.
  • Situar el salto de capacidad del almacenamiento en el cliente como condición de posibilidad, no como detalle.
  • Reconocer dónde está de verdad el esfuerzo de ingeniería frente a dónde se anuncia.
  • Reconstruir cualquier nivel del track a partir de estas cuatro ideas, sin volver a consultarlo.

Primera idea: la autoridad se muda al cliente

Todo el recorrido descansa sobre una única decisión, tomada antes de escribir la primera línea de código: dónde reside la copia autoritativa del estado. En la arquitectura que la industria heredó, esa copia vive en el servidor y el dispositivo guarda una caché, algo derivado y desechable que ante la duda se descarta y se vuelve a pedir. Local-first invierte la relación: el dispositivo guarda una réplica completa, y la red pasa de ser fuente de verdad a ser canal de propagación entre iguales.

Cambiar esa palabra —de caché a réplica— reescribe el sistema entero. El camino de lectura pierde su estado de carga, sus reintentos y su tiempo de espera, porque ya no cruza ninguna frontera de proceso. El camino de escritura deja de pedir permiso: una escritura local es válida en el instante en que ocurre, aunque ninguna otra réplica la haya visto todavía. Y el modelo de identidad se rompe por completo, porque un identificador asignado por el servidor deja de ser posible en un sistema donde el objeto tiene que existir antes de que nadie lo bendiga.

Los niveles 1 a 5 del track no hacían filosofía cuando insistían en la propiedad del dato: estaban describiendo las consecuencias observables de esta primera decisión. La latencia, la disponibilidad sin red, la supervivencia de los datos a la empresa y la imposibilidad de que alguien te revoque el acceso a tu propio trabajo no son cuatro objetivos independientes que haya que perseguir uno a uno. Son cuatro lecturas distintas del mismo hecho técnico.

Conviene además notar por qué la decisión contraria se tomó de forma casi universal sin discutirse. Centralizar la autoridad resuelve de golpe tres problemas duros: da un orden total de eventos sin razonar sobre causalidad, da un punto único donde aplicar validación y permisos, y da un lugar obvio donde ejecutar lo que el cliente no puede. Es una solución excelente de ingeniería para el problema de la coordinación. Lo que el recorrido ha mostrado nivel a nivel es que su factura se paga en un presupuesto distinto y que llevaba una década sin contabilizarse.

// Camino de escritura con autoridad remota: la operacion existe cuando el servidor lo dice.
async function guardarComoCliente(cambio: Cambio) {
  const respuesta = await servidor.aplicar(cambio);   // puede rechazar
  if (!respuesta.ok) return revertirEnPantalla(cambio);
  estado.aplicar(respuesta.canonico);                 // el servidor decide el resultado
}

// Camino de escritura con autoridad local: la operacion ya ocurrio.
function guardarComoReplica(cambio: Cambio) {
  estado.aplicar(cambio);        // valida en el acto, sin permiso de nadie
  bitacora.encolar(cambio);      // la red se entera despues, cuando pueda
}

La segunda función no tiene rama de rechazo, no tiene reversión en pantalla y no distingue entre estar conectado y no estarlo. Esa ausencia de casuística es la primera idea escrita en código, y también es la señal más fiable para auditar un sistema ajeno: si el camino de escritura contempla un rechazo del servidor, la autoridad no se ha movido, por mucho que el producto se anuncie como local-first.

flowchart LR
S[Autoridad en el servidor] --> S1[El cliente pregunta]
S1 --> S2[Rueda de carga y modo error]
L[Autoridad en el cliente] --> L1[La replica responde sola]
L1 --> L2[La red solo propaga]
L2 --> L3[Hace falta converger sin arbitro]
style L fill:#a6e3a1,color:#11111b
style L3 fill:#f9e2af,color:#11111b
ℹ️
El desplazamiento se ve en una sola función

Una lectura contra una caché tiene que contemplar el fallo de red, el estado intermedio y la revalidación. Una lectura contra una réplica es una llamada a memoria: estado.obtener(id). Esa asimetría no es un simplificación del ejemplo, es la primera idea escrita en código. Toda la casuística que desaparece del camino de lectura reaparece más tarde en el camino de reconciliación, y ese traslado es exactamente el objeto del resto del track.

Segunda idea: la convergencia sustituye al arbitraje

En cuanto la autoridad se reparte, nadie puede impedir que dos copias diverjan. El conflicto deja de ser un error que se previene y pasa a ser un caso que hay que definir. Aquí es donde el recorrido dejó de hablar de arquitectura y empezó a hablar de álgebra, en los niveles 20 a 26.

Aquí conviene precisar algo que se pierde con facilidad. Un conflicto no es que dos personas escriban parecido ni que dos valores no coincidan: es que dos operaciones son concurrentes, es decir, que ninguna de las dos había visto a la otra cuando se produjo. Esa definición es causal y no temporal, y la diferencia importa porque dos escrituras separadas por horas pueden ser concurrentes si los dispositivos estuvieron aislados, mientras que dos escrituras separadas por milisegundos no lo son si la segunda vio a la primera. Toda la maquinaria de relojes lógicos del recorrido existe para poder afirmar eso con precisión sin recurrir a la hora del sistema, que en un parque de dispositivos reales deriva, se ajusta y salta hacia atrás.

La cadena es rígida y conviene recitarla entera. Sin un servidor no hay orden total de eventos, luego hacen falta relojes lógicos que capturen la causalidad real en lugar de la hora del sistema, que es inservible. Con causalidad parcial hay operaciones genuinamente concurrentes, luego hace falta una regla para mezclarlas. Y como esa regla tiene que dar el mismo resultado en cada dispositivo sin hablar con los demás, no puede ser una regla de negocio escrita dentro de un condicional: tiene que ser una operación conmutativa, asociativa e idempotente sobre un semirretículo. Esa exigencia algebraica es lo que define un CRDT, y por eso el track dedicó quince niveles a ellos antes de mencionar ninguna biblioteca.

Esa exigencia se puede escribir en tres líneas, y hacerlo ayuda a ver que no es una metáfora sino una condición comprobable con una prueba automática.

// Las tres leyes que una funcion de mezcla tiene que cumplir para que
// dos replicas converjan sin hablar entre ellas.
mezclar(a, b) === mezclar(b, a);                    // conmutativa
mezclar(mezclar(a, b), c) === mezclar(a, mezclar(b, c));  // asociativa
mezclar(a, a) === a;                                 // idempotente

Quien haya hecho el ejercicio de los niveles 37 a 39 reconocerá que esas tres igualdades no se comprueban con ejemplos escritos a mano sino generando historias concurrentes al azar, aplicándolas en órdenes distintos y exigiendo que el resultado sea idéntico. Ese cambio de método —de probar casos a probar propiedades— es en sí mismo una de las cosas que el track deja, y se traslada tal cual a cualquier problema donde el orden de llegada no esté garantizado.

La consecuencia práctica más importante es que la corrección deja de ser una propiedad del código de aplicación y pasa a ser una propiedad de la estructura de datos. Un sistema con autoridad central puede tener bugs de fusión; un sistema construido sobre un tipo replicado que satisface las tres propiedades no puede divergir, aunque sí puede converger a algo que el usuario considere absurdo. Esa distinción entre convergencia y sentido, que el nivel de la intercalación puso sobre la mesa, es una de las más finas del recorrido: dos réplicas pueden terminar idénticas y aun así entregar un párrafo con las frases de dos personas entrelazadas letra a letra.

🔱

Convergencia

Propiedad matemática: dos réplicas que han visto las mismas operaciones terminan idénticas, sea cual sea el orden de llegada. Se demuestra.

🧠

Sentido

Propiedad humana: el resultado de la fusión es lo que la persona esperaba. No se demuestra, se prueba con usuarios y con propiedades como la no intercalación.

⚖️

Arbitraje

Lo que hacía el servidor: imponer un orden y rechazar lo que llegaba tarde. Es simple y correcto, y cuesta latencia, disponibilidad y autonomía.

🧩

Elección

Convergencia y arbitraje no son excluyentes por sistema, sino por entidad. Casi toda arquitectura seria usa las dos en partes distintas del modelo.

Tercera idea: el almacenamiento local dejó de ser un juguete

Las dos primeras ideas estaban disponibles en la literatura desde hace décadas. Lo que cambió, y lo que explica que este campo pasara del artículo académico al producto, es que la máquina donde vive la réplica se volvió capaz de sostenerla.

El track recorrió ese ascenso en orden: los cinco megabytes de localStorage, que solo servían para preferencias; IndexedDB, la única base de datos que todos los navegadores traen de serie y también la más incomprendida, con su coste por transacción y su serialización estructurada; el sistema de ficheros privado del origen, que dio acceso a bloques reales y a escritura síncrona dentro de un worker; y finalmente SQLite compilado a WebAssembly sobre esa capa, que convirtió una pestaña en un motor relacional capaz de mover gigabytes con consultas de verdad. En paralelo, los niveles de workers, bloqueos y elección de líder describieron el precio de esa potencia: una base local seria no vive en la pestaña, vive en un hilo aparte con un protocolo de mensajes por encima y un mecanismo para decidir quién manda cuando hay cuatro pestañas abiertas.

Esa escalera merece verse ordenada, porque cada peldaño resolvió una limitación concreta del anterior y ninguno la resolvió del todo.

  1. Cookies y localStorage. Síncronos, diminutos y bloqueantes. Sirven para preferencias y para nada más.
  2. IndexedDB. Transaccional, asíncrona, universal y lenta por motivos concretos: el clonado estructurado y el coste por transacción, no el disco.
  3. El sistema de ficheros del origen. Bloques reales, sin diálogos de permiso, con escritura síncrona disponible solo dentro de un worker.
  4. SQLite sobre esa capa. Consultas de verdad, índices y transacciones sobre gigabytes, a cambio de una arquitectura de hilos y bloqueos.
  5. Motores especializados. Analítica columnar y almacenes propios de los motores de sincronización, para formas de acceso concretas.

Cada peldaño de esa escalera trajo consigo una restricción arquitectónica, y la más importante es la del peldaño tres: el acceso síncrono solo existe dentro de un worker. Esa única línea de especificación es la razón por la que una aplicación local-first seria no tiene su base de datos en la pestaña, sino en un hilo aparte, con un protocolo de mensajes por encima, un mecanismo de liderazgo entre pestañas y un arranque en frío que hay que diseñar. Los niveles 16 a 19 no eran un desvío técnico: eran la consecuencia inevitable de haber subido hasta ese peldaño.

Conviene ser exacto sobre qué tipo de afirmación es esta. No es que el navegador se haya vuelto un buen lugar para una base de datos; es que dejó de ser un lugar imposible. Sigue habiendo cuota por origen, desalojo silencioso bajo presión de disco y un modelo de persistencia que hay que pedir explícitamente y que puede negarse. La tercera idea no dice que el problema de almacenamiento esté resuelto, dice que dejó de ser el cuello de botella que impedía intentar las dos primeras.

💡
El listón que hay que medir antes de diseñar

Antes de elegir motor de convergencia, mide tres números en el dispositivo más modesto que tu producto vaya a tocar: cuánto tarda un arranque en frío con el conjunto de datos completo, cuánta memoria ocupa ese conjunto en caliente y qué pasa exactamente cuando el navegador desaloja el origen. Los tres condicionan la decisión más que cualquier comparativa de bibliotecas, y los tres son baratos de medir.

Cuarta idea: lo difícil no es sincronizar

La cuarta idea es la que más contradice a la intuición y la que más caro se paga aprender tarde. Sincronizar dos réplicas es, hoy, un problema con solución conocida y bibliotecas maduras. Lo que no está resuelto es todo lo demás que hace falta para que un producto sincronizado funcione durante años.

El recorrido lo demostró por acumulación. El historial de un CRDT crece para siempre, y compactarlo obliga a responder qué historia puedes tirar sin romper a un dispositivo que lleva ocho meses apagado y que volverá. La identidad, que en cliente-servidor es una tabla de sesiones, se convierte en un problema de identificadores descentralizados y cadenas de delegación que tienen que verificarse sin conexión. El cifrado extremo a extremo, que es un requisito y no un extra en cuanto los datos viajan por infraestructura ajena, choca de frente con la capacidad del servidor de indexar, buscar o previsualizar. Y la migración de esquema, que en un servidor es un despliegue coordinado, aquí es un problema de compatibilidad en las dos direcciones contra un parque de dispositivos que no controlas.

Esa es la lista de facturas, y no es casual que ocupe los últimos niveles del track. Aparecen al final porque solo aparecen al final: en el prototipo no existen, en la primera versión son teóricas, y en el segundo año son la mitad del trabajo del equipo. Un ingeniero que solo se lleve las tres primeras ideas construirá una demostración impecable y un producto frágil.

Vale la pena cuantificar el reparto, aunque sea de forma aproximada, porque contradice la intuición de manera útil. En los proyectos que han contado públicamente su experiencia, la parte estrictamente de convergencia —elegir un motor, modelar los datos con sus tipos y hacer que dos réplicas coincidan— es una fracción menor del esfuerzo total. La mayoría del trabajo se va en el almacenamiento local y su ciclo de vida, en el modelo de permisos y claves, en las migraciones y en la parte de interfaz que decide cómo se le cuenta al usuario lo que la fusión resolvió. Dicho de otro modo: el tema que da nombre al campo no es donde está el trabajo.

Hay una razón estructural para eso, y es que la convergencia es la única de las cuatro áreas que se puede empaquetar en una biblioteca. Las otras tres dependen del producto concreto, de sus usuarios y de sus compromisos, y por eso no hay ninguna dependencia que instalar. Esa asimetría explica por qué el campo puede tener a la vez herramientas excelentes y proyectos que fracasan: las herramientas resuelven la parte empaquetable, que es real pero es la pequeña.

⚠️
El síntoma de haberse llevado solo tres ideas

Se reconoce sin ambigüedad: la demostración con dos pestañas funciona a la perfección, la colaboración en vivo es fluida, y sin embargo nadie del equipo sabe responder qué ocurre cuando un usuario reinstala la aplicación, cuando alguien deja el equipo y hay que quitarle el acceso a un documento que ya descargó, o cuando el modelo de datos cambia y hay clientes de la versión anterior escribiendo todavía. Esas tres preguntas, hechas al principio, ahorran una reescritura.

Las cuatro ideas son una sola cadena, y ese es todo el track

Si este nivel deja una sola cosa, que sea el orden de las cuatro, porque el orden es el argumento. La primera idea es una decisión de topología: dónde vive la copia autoritativa. Es la única que se toma libremente, y todo lo demás se deduce de ella. La segunda es una obligación matemática: en cuanto hay más de una copia autoritativa, ningún árbitro puede decidir el orden, de modo que la corrección tiene que estar garantizada por propiedades algebraicas de la estructura de datos y no por reglas escritas en el código de aplicación. No es una preferencia de diseño ni una moda académica; es lo que queda cuando quitas el árbitro. La tercera es una condición de posibilidad, y explica el calendario más que ninguna otra: las dos primeras ideas eran conocidas desde los años noventa, y lo que cambió entre el artículo fundacional de Ink and Switch en 2019 y una sala de desarrolladores entera dedicada al tema en FOSDEM 2026 no fue la teoría, fue que el cliente se volvió capaz de sostener una réplica de verdad, con SQLite sobre el sistema de ficheros del origen, con motores de convergencia que redujeron su consumo de memoria un orden de magnitud, y con transportes que perforan NAT con tasas de éxito publicables. Y la cuarta es la factura: al mover la autoridad no eliminas complejidad, la trasladas de un sitio donde estaba resuelta y era aburrida a un sitio donde está abierta y es interesante. Ese traslado es un buen negocio cuando el producto lo justifica y una ruina cuando no. Por eso el criterio profesional que este recorrido intenta dejar no es cómo hacer local-first, que es lo que enseñan las herramientas, sino cuánta autoridad necesita de verdad cada entidad de tu modelo, que es lo que no enseña nadie. La respuesta casi nunca es todo ni nada: es que un puñado pequeño de entidades exige arbitraje —el pago, la reserva del asiento, la unicidad del nombre de usuario— y el resto, que suele ser el noventa por ciento del producto, puede converger sola. Un diseño que sabe distinguir esas dos listas puede cambiar de biblioteca, de transporte y hasta de algoritmo sin rehacerse. Un diseño que no las distinguió descubrirá la frontera tarde, en forma de servidor obligatorio que nadie había planeado.

⚔️ Reconstruye el track desde las cuatro ideas
  1. Toma cinco niveles cualesquiera del recorrido y explica cada uno como consecuencia de una de las cuatro ideas, nombrando cuál.
  2. Escribe en dos frases por qué la segunda idea es obligatoria y no opcional una vez aceptada la primera.
  3. Enumera qué habría sido imposible construir en 2015 por culpa exclusiva de la tercera idea.
  4. Divide las entidades de un producto que conozcas en dos listas: las que exigen arbitraje y las que pueden converger.
  5. Responde las tres preguntas del aviso anterior para ese mismo producto y anota cuál te resulta más incómoda.