wandres.dev
CUÁNDO NO HACERLO · el criterio inverso

El punto medio: dónde se traza la frontera

Local-first no es una propiedad de una aplicación sino de cada colección que contiene: cómo se decide dato a dato y cómo se evita acabar con lo peor de los dos mundos.

⏱ 21 min

Las cuatro lecciones anteriores han construido, deliberadamente, el argumento contrario al del resto del track: hay invariantes que no se replican, datos que no pueden bajar, permisos que no se pueden retirar y un coste de ingeniería que no todo el mundo debería pagar. Si el nivel terminase ahí sería tan deshonesto como el entusiasmo que pretendía corregir, porque de esos cuatro límites no se sigue que haya que renunciar, sino algo mucho más útil: que la unidad de decisión estaba mal elegida desde el principio. Nadie debería preguntarse si su aplicación es local-first. La pregunta correcta se formula colección a colección, y su respuesta produce una frontera dentro del sistema. Esta lección cierra el nivel enseñando a trazarla, y sobre todo a no trazarla mal, porque una frontera mal puesta produce lo peor de los dos mundos y esa combinación existe, es frecuente y tiene una firma reconocible.

🎯 Al terminar esta lección sabrás
  • Cambiar la unidad de decisión de la aplicación al conjunto de datos.
  • Clasificar cada colección con los criterios acumulados en el nivel.
  • Aplicar la regla de una única autoridad por hecho para trazar la frontera.
  • Reconocer las cuatro formas de acabar con lo peor de los dos mundos.

La unidad de decisión no es la app, es el dato

Casi todas las discusiones sobre esta arquitectura empiezan mal porque tratan el enfoque como una identidad del producto, cuando es una propiedad que se concede o se niega a cada conjunto de datos por separado. Un gestor de proyectos puede tener las tareas y sus comentarios en réplica local, la facturación estrictamente en el servidor, el catálogo de plantillas como caché de solo lectura y los permisos de equipo verificados en cada operación. No es un compromiso tibio ni una arquitectura a medias: es el resultado correcto de aplicar cuatro criterios distintos a cuatro dominios distintos.

Los criterios ya están todos sobre la mesa y se pueden formular como cuatro preguntas cerradas. Porta esta colección una invariante global que no admite fusión. Cabe su conjunto de trabajo y tiene permiso legal y contractual para residir en el dispositivo. Es su audiencia estable a lo largo de la vida del dato. Y es la interacción instantánea o sin red un motivo real por el que alguien elegiría este producto. Cuatro noes cualesquiera bastan para dejar la colección en el servidor; cuatro síes la convierten en candidata clara. Las mezclas son donde vive el diseño interesante, y también donde conviene ser conservador.

Hay una razón adicional para preferir esta granularidad, y es que se corresponde con cómo cambian las restricciones en la vida real. La ley cambia para un tipo de dato, no para un producto entero. Un proveedor endurece su licencia sobre un catálogo concreto. Una reorganización altera la audiencia de una colección y deja intactas las demás. Si la arquitectura se decidió a nivel de aplicación, cualquiera de esos cambios obliga a replantearlo todo; si se decidió colección a colección, el cambio se absorbe donde ocurrió. La granularidad correcta de una decisión es aquella a la que llegan las noticias.

flowchart TB
A[una coleccion concreta] --> B[porta invariante global no fusionable]
B --> C[si: servidor autoritativo]
B --> D[no: sigue]
D --> E[cabe y tiene permiso para residir en el dispositivo]
E --> F[no: servidor, con cache de lectura si compensa]
E --> G[si: sigue]
G --> H[audiencia estable durante la vida del dato]
H --> I[no: cache de sesion con caducidad corta]
H --> J[si: replica local completa]
style C fill:#f38ba8,color:#11111b
style F fill:#fab387,color:#11111b
style I fill:#fab387,color:#11111b
style J fill:#a6e3a1,color:#11111b

Conviene fijarse en que el diagrama tiene tres destinos y no dos, y que el intermedio es el más habitual en productos reales. Entre la réplica completa y la consulta remota existe un territorio amplio de cachés de lectura, índices locales de metadatos y ámbitos de sincronización acotados. Ese territorio no es un fracaso del ideal: es donde la mayoría de las aplicaciones obtienen la mayor parte del beneficio percibido. Negarse a habitarlo por purismo es la forma más elegante de no entregar nada.

También conviene resistir la tentación contraria, que es más sutil y más frecuente en equipos cansados: usar el territorio intermedio como excusa para no decidir. Una caché sin política de caducidad escrita, un ámbito de sincronización que crece porque nadie lo revisa o una proyección que alguien empezó a escribir no son puntos medios, son fronteras sin trazar. La diferencia entre un diseño intermedio y una indecisión es que el primero se puede explicar en una frase y el segundo solo se puede describir enumerando excepciones.

Los cuatro grupos y dónde cae cada cosa

Aplicado a productos reales, el resultado de esa clasificación se repite con una regularidad que permite convertirlo en un mapa aproximado. Sirve como punto de partida y no como respuesta, porque el mismo nombre de colección puede caer en grupos distintos según el negocio.

🚫

Identidad, permisos y facturación

Nunca local. Portan invariantes globales, definen quién puede ver qué y su corrupción no se compensa. Verificación en cada operación, sin excepciones ni cachés escribibles.

🚫

Datos de referencia y catálogos

Grandes, de terceros, de solo lectura y cambiantes. Caché con caducidad, nunca réplica bidireccional: sincronizar en ambos sentidos algo que el usuario no edita es coste sin beneficio.

🚫

Registros transaccionales

Pedidos confirmados, pagos, reservas. El servidor decide y el cliente observa. La copia local del histórico ya liquidado sí es válida, porque el pasado no tiene invariantes vivas.

🚫

Trabajo en curso del usuario

Aquí es donde el enfoque brilla y donde hay que aplicarlo sin complejos: borradores, notas, anotaciones, lienzos, preferencias, estado de la interfaz. Su audiencia es estable y su valor es la inmediatez.

Grupo Autoridad Sin conexión Caducidad
Identidad y permisos Servidor No disponible Verificación por operación
Catálogos y referencia Tercero o servidor Solo lectura de lo cacheado Horas o días, según licencia
Transacciones confirmadas Servidor Solo lectura del histórico El pasado no caduca
Trabajo en curso Réplica del usuario Lectura y escritura completas Sin caducidad

Esta tabla es el artefacto que hay que producir, no el que hay que copiar. Su valor no está en las filas concretas sino en la disciplina de tener que rellenar cada celda: hay equipos que descubren al escribirla que dos colecciones muy parecidas tienen autoridades distintas por motivos que nadie recordaba, y otros que descubren que no saben decir qué pasa sin conexión con la mitad de su sistema. Ambos descubrimientos valen mucho más que la tabla.

La regla que ordena las cuatro tarjetas y que resuelve casi todas las dudas de frontera cabe en cuatro palabras: una autoridad por hecho. Cada afirmación del sistema tiene exactamente un lugar donde es verdad, y todos los demás lugares donde aparece son proyecciones de esa verdad, marcadas como tales y no editables. El precio unitario de un producto es autoritativo en el servidor; la copia que hay en el carrito local es una proyección con fecha. El texto de un documento es autoritativo en la réplica del usuario; lo que hay en el servidor es una copia de sincronización que nadie edita a mano. En cuanto un mismo hecho tiene dos autoridades, aparece una reconciliación permanente que ningún algoritmo resuelve porque el problema no es técnico sino de modelado.

La regla admite una comprobación muy rápida que conviene incorporar a las revisiones de código. Ante cualquier campo, pregunta quién lo escribe cuando el usuario no está mirando. Si la respuesta incluye dos actores —el usuario en su dispositivo y un proceso del servidor— el campo tiene dos autoridades y va a divergir; lo que hay que hacer entonces no es elegir un algoritmo de fusión más listo, sino partir el campo en dos con nombres distintos y dejar claro cuál manda para qué. La mayoría de los conflictos irresolubles que se atribuyen a la sincronización son, vistos de cerca, este error de modelado con otro disfraz.

💡
El eje intención frente a compromiso trocea colecciones que parecían indivisibles

Muchas colecciones que a primera vista deben ir enteras al servidor se dividen limpiamente por este eje. Una reserva tiene una parte que es la intención del usuario —fechas tanteadas, habitaciones comparadas, notas, acompañantes— que es suya, no porta invariante alguna y merece ser local e instantánea; y otra que es el compromiso del sistema, que decide el servidor y no se negocia. Separar ambas convierte un no rotundo en un sí parcial muy valioso, siempre que la interfaz distinga con palabras distintas el borrador de lo confirmado.

Trazar la frontera sin duplicar la verdad

Trazada la clasificación, queda materializarla, y ahí hay tres decisiones concretas que determinan si el resultado será mantenible. La primera es que las proyecciones de datos del servidor deben ser de solo lectura de forma estructural y no por convención: si el modelo local permite escribir en ellas, alguien lo hará en un momento de prisa y la duplicación de autoridad entrará por esa puerta. La segunda es que las referencias cruzan la frontera por identificador y nunca por copia embebida: la tarea local guarda el identificador del cliente, no su nombre, su dirección y su tarifa, porque esos campos tienen otra autoridad y otro ciclo de vida.

// La frontera hecha explicita en el modelo de datos.
type TareaLocal = {
  id: string;
  titulo: string;              // autoridad: esta replica
  notas: string;               // autoridad: esta replica
  clienteId: string;           // referencia, no copia
  facturaId: string | null;    // referencia a algo que decide el servidor
};

type ClienteProyectado = Readonly<{
  id: string;
  nombre: string;              // autoridad: el servidor
  sincronizadoEn: number;      // la proyeccion declara su antiguedad
}>;
// Si algun dia alguien anade nombreCliente a TareaLocal, la frontera se rompe
// y nadie se dara cuenta hasta que los dos nombres discrepen en produccion.

La tercera decisión es la más olvidada: la frontera debe seguir el dominio y no el marco de trabajo. Mover una colección de un lado a otro más adelante es una migración de datos costosa y arriesgada, así que conviene trazarla donde el negocio ya tiene una junta natural —el proyecto, el expediente, el documento, el espacio de trabajo— y no donde resulte cómodo para la biblioteca elegida esta temporada. Las bibliotecas se cambian en un trimestre; las fronteras de datos sobreviven a varias reescrituras de la interfaz.

Hay una cuarta decisión que no es de modelado sino de interfaz y que suele decidir la percepción del producto entero: la frontera tiene que ser visible para el usuario. No con jerga técnica, sino con señales consistentes que le permitan saber, sin pensarlo, en qué lado está lo que tiene delante. Un indicador de guardado en el dispositivo distinto del de confirmado, una zona de la aplicación que se atenúa al perder la red, una etiqueta de antigüedad en los datos proyectados. Si el usuario no puede inferir la frontera desde la interfaz, la arquitectura puede ser impecable y aun así producir la sensación de que la aplicación se comporta de forma caprichosa, que es el único juicio que acaba importando.

Merece la pena anticipar también el caso en que la frontera tenga que moverse, porque a veces ocurre por motivos legítimos: cambia la regulación, cambia el proveedor de datos, crece el volumen. Mover una colección del servidor a la réplica es relativamente asumible, porque es añadir una copia. Moverla en sentido contrario es doloroso, porque hay que reconciliar historias divergentes que llevan tiempo acumulándose y decidir qué versión gana en cada caso. Esa asimetría es un buen argumento para empezar conservador: dejar en el servidor lo dudoso y liberarlo más adelante es reversible, mientras que replicar lo dudoso y arrepentirse no lo es.

Lo peor de los dos mundos, y cómo se llega ahí

Existe una combinación que reúne los costes de ambas arquitecturas y las ventajas de ninguna, y merece describirse con precisión porque es el desenlace por defecto cuando la frontera no se traza a conciencia. Se llega por cuatro caminos, y ninguno de ellos parece un error mientras se recorre; al contrario, todos parecen decisiones razonables tomadas de una en una, que es precisamente lo que los hace peligrosos.

El primero es sincronizar en ambos sentidos datos que tienen una invariante en el servidor: se pagan la fusión y el rechazo a la vez, y el cliente tiene que saber deshacer cosas que el usuario ya vio confirmadas. El segundo es replicar por si acaso colecciones que el usuario solo lee: se asume todo el coste de almacenamiento, cuota y migración sin obtener ninguna capacidad nueva. El tercero es dejar que un mismo hecho tenga dos autoridades, que produce una reconciliación eterna disfrazada de errores esporádicos que nadie consigue reproducir. Y el cuarto, el más dañino para el producto, es un modo sin conexión parcial e impredecible, donde el usuario no puede anticipar qué funcionará al perder la red.

Los cuatro comparten un origen común que conviene nombrar porque es lo que permite prevenirlos: en todos ellos la decisión sobre la frontera se tomó por funcionalidad y en el momento de implementarla, en lugar de por colección y antes de empezar. Cuando la pregunta se hace tarde, la respuesta la dicta la comodidad del código que ya existe, y la comodidad casi siempre recomienda replicar un poco más, copiar un campo más y añadir una excepción más. Ninguna de esas decisiones individuales justifica una discusión; su suma, al cabo de un año, es una arquitectura que nadie eligió y que nadie sabe describir.

⚠️
La predictibilidad vale más que la cobertura

Entre una aplicación que funciona sin red en el noventa por ciento de los casos de forma impredecible y otra que funciona en el cuarenta por ciento pero siempre en los mismos casos, la segunda es mejor producto. El usuario construye un modelo mental de lo que puede hacer sin cobertura y organiza su trabajo alrededor de él; la impredecibilidad destruye ese modelo y con él la confianza, y a partir de ahí el usuario deja de fiarse también de lo que sí funciona. Un modo sin conexión pequeño, estable y anunciado supera siempre a uno grande y errático.

El antídoto contra los cuatro caminos es un documento corto que casi ningún equipo escribe y que cuesta una tarde: una tabla con todas las colecciones del sistema, y para cada una su autoridad, su comportamiento sin conexión y su política de caducidad. Ese documento hace visible la frontera, convierte cada nueva funcionalidad en una pregunta con respuesta y detecta la duplicación de autoridad en el momento de proponerla y no dos años después. Es, con diferencia, el artefacto con mejor relación entre esfuerzo y daño evitado de todo este nivel.

Conviene cerrar con una advertencia sobre el propio ejercicio, para no sustituir un dogma por otro. Este esquema de clasificación es una herramienta de pensamiento, no una autoridad: hay productos donde la respuesta correcta contradice el mapa por razones que solo el equipo conoce, y hay casos donde replicar algo formalmente dudoso es la decisión acertada porque el negocio ha aceptado explícitamente el riesgo. Lo que no es aceptable es contradecir el criterio sin haberlo aplicado. La diferencia entre una excepción y un descuido es que la excepción está escrita, argumentada y firmada por alguien, y por eso puede revisarse cuando cambien las circunstancias.

Vale la pena señalar, además, que esta forma de razonar sobrevive a la tecnología concreta que hayas estudiado en el track. Los motores de sincronización de hoy serán otros dentro de cinco años, los formatos cambiarán y aparecerán primitivos que hoy no existen. Las cuatro preguntas de clasificación no dependen de ninguno de ellos: dependen del dominio, de la ley, de la organización y del modelo de negocio, que son cosas que cambian mucho más despacio. Un criterio que sobrevive al recambio de herramientas es lo más parecido a conocimiento duradero que ofrece esta disciplina.

Y así se cierra el contrapeso que este nivel se propuso ser. Un track que solo hubiera enumerado virtudes habría producido entusiastas capaces de defender la arquitectura y no de decidirla, que es una forma sofisticada de no saber. Después de cuatro lecciones de límites, el enfoque no queda debilitado sino delimitado, que es la única condición en la que una herramienta se puede usar bien: sabiendo con precisión dónde deja de funcionar y por qué motivo exacto lo hace.

Local-first no es una arquitectura, es una propiedad que se concede dato a dato

El error que atraviesa casi todas las discusiones sobre este tema es tratar el enfoque como una identidad —somos una empresa local-first— cuando en realidad es un atributo que se otorga o se niega a cada conjunto de datos según cuatro propiedades suyas que ya no son opinables: si porta una invariante global no fusionable, si puede residir legalmente en el dispositivo, si su audiencia es estable y si su inmediatez es un motivo real de elección del producto. Formulada así, la pregunta deja de tener una respuesta binaria y pasa a tener una respuesta por colección, que es exactamente el nivel de granularidad al que existen las restricciones reales. De esa reformulación se sigue todo lo demás: que un sistema maduro es un gradiente y no una religión, con el trabajo en curso del usuario replicado y libre, el histórico ya liquidado replicado en solo lectura, los catálogos en caché con caducidad y el núcleo transaccional firmemente central; que la frontera entre esas zonas se traza con una única regla —una autoridad por hecho— y se defiende impidiendo estructuralmente que una proyección se convierta en original; y que el fracaso característico de esta arquitectura no es elegir mal el lado, sino no elegir, dejar que la frontera se erosione funcionalidad a funcionalidad hasta que nadie sabe dónde vive la verdad de cada dato. El ingeniero que termina este nivel no sale con una preferencia sino con un criterio, y el criterio se demuestra en la capacidad de decir con la misma soltura esto sí va al cliente y esto no baja de aquí, defendiendo ambas afirmaciones con el mismo argumento. Ahí acaba la propaganda y empieza la ingeniería.

⚔️ Traza la frontera de un producto real
  1. Lista todas las colecciones de un sistema y responde las cuatro preguntas de clasificación para cada una. No dejes ninguna sin respuesta explícita.
  2. Escribe la tabla de fronteras: autoridad, comportamiento sin conexión y política de caducidad por colección. Que quepa en una pantalla.
  3. Busca en tu modelo actual todos los campos copiados que ya tienen autoridad en otro sitio. Cada uno es una duplicación de verdad esperando a divergir.
  4. Aplica el eje intención frente a compromiso a la colección más central del producto y comprueba si se puede trocear en dos.
  5. Redacta en tres frases lo que tu producto promete sin conexión, en el lenguaje de un usuario. Si no puedes escribirlo sin condicionales, la frontera aún no está trazada.