wandres.dev
AUTOMERGE · documentos e historia

Cuándo elegirlo: el perfil que encaja y el que no

Automerge encaja cuando el dato tiene forma de documento con frontera natural y la historia es una funcionalidad del producto, y se queda corto cuando hay que consultar colecciones grandes o controlar quién escribe qué.

⏱ 21 min

Después de cuatro lecciones sobre lo que la biblioteca hace bien, la más útil del nivel es la que delimita dónde deja de ser la respuesta correcta. Automerge no es una base de datos con sincronización ni un sustituto general de tu capa de persistencia: es un tipo de dato con forma de documento y con historia, y todo su perfil de aptitudes se deriva de esas dos palabras. Cuando tu problema tiene esa forma, la biblioteca resuelve de golpe cosas que de otro modo costarían meses de infraestructura: trabajo sin conexión, fusión garantizada, ramas, historial, funcionamiento entre pares sin servidor. Cuando tu problema no tiene esa forma, cada una de esas mismas propiedades se convierte en un impuesto que pagas sin recibir nada. Esta lección traza la frontera con precisión, la contrasta con las alternativas que el track ya ha recorrido y termina con un criterio de decisión que puedes aplicar en una tarde.

🎯 Al terminar esta lección sabrás
  • Caracterizar el perfil de aplicación donde Automerge supera claramente a sus alternativas.
  • Enumerar los tres límites estructurales del modelo de documento y por qué no se resuelven con configuración.
  • Contrastar la elección frente a SQLite en el cliente, frente a las bibliotecas centradas en edición en vivo y frente al servidor autoritativo.
  • Aplicar un criterio de decisión de cuatro preguntas antes de comprometer una arquitectura.

El perfil donde encaja

Hay cuatro rasgos que, cuando aparecen juntos, hacen de Automerge la mejor opción disponible y no solo una opción viable. El primero es que el dato tenga una frontera natural de documento: una nota, un diseño, un proyecto, un tablero, un cuaderno. Algo que una persona abre, que tiene un tamaño acotado por su propia naturaleza y que se comparte como unidad. Si al describir tu dominio usas espontáneamente la palabra archivo o la palabra documento, es buena señal.

El segundo es que la historia sea una funcionalidad y no un residuo. Si tu producto quiere mostrar versiones anteriores, comparar dos estados, atribuir cambios, permitir ramas o deshacer selectivamente, entonces estás obteniendo gratis lo que en otra arquitectura tendrías que construir con tablas de auditoría e instantáneas. Si tu producto no quiere nada de eso, estás pagando el almacenamiento de la historia a cambio de nada.

El tercero es que el trabajo sin conexión o con conexión mala sea normal y no excepcional. El cuarto es que la topología no esté fijada: que quieras poder sincronizar contra un servidor hoy, entre pares mañana y por un archivo compartido pasado, sin cambiar el modelo de datos. Los ejemplos reales del ecosistema ilustran bien el patrón: colaboración en proyectos de motores de videojuegos con ramas y fusiones que convive con el control de versiones tradicional, coedición en tiempo real de archivos de texto locales desde cualquier editor, o un rastreador de incidencias donde todas las incidencias de un proyecto viven en un único documento que muchos agentes editan en paralelo desde máquinas distintas.

📄

Frontera de documento clara

El dato se abre, se edita y se comparte como una unidad con tamaño acotado por su propia naturaleza.

🌿

La historia es producto

Versiones, comparaciones, ramas y atribución forman parte de lo que vendes, no del registro interno.

✈️

Sin conexión por defecto

Trabajar días desconectado y reintegrarse sin ceremonia es un requisito, no una degradación aceptable.

🕸️

Topología libre

Servidor, pares o archivo compartido: el modelo de datos no cambia porque el transporte no forma parte del argumento.

Hay un quinto rasgo que no suele aparecer en las listas y que en la práctica decide muchas adopciones: la longevidad. Un documento es un archivo binario autocontenido con un formato estable y documentado, que no necesita el servicio que lo creó para abrirse. Si tu producto promete que los datos del usuario le pertenecen —y ese es el compromiso que da nombre al movimiento local-first— entonces necesitas un formato que sobreviva a tu empresa, y muy pocas capas de sincronización te lo dan. Que la versión 3 pudiera rehacer el motor entero sin romper la compatibilidad de los archivos de la versión 2 es la mejor evidencia disponible de que esa promesa se puede sostener.

ℹ️
El rasgo que más veces falta es el segundo

De los cinco, el que con más frecuencia se da por supuesto sin ser cierto es que la historia sea producto. Mucha gente adopta la biblioteca por la fusión, que es lo que le duele hoy, y descubre a los seis meses que está almacenando y sincronizando un historial completo que ningún usuario va a ver jamás. Si ese es tu caso, no significa que la elección sea mala, pero sí que estás pagando el precio principal por el beneficio secundario, y conviene comprobar si una biblioteca más ligera resuelve lo mismo por menos.

Dónde el modelo de documento se queda corto

Los límites son tres y ninguno se arregla con configuración, porque los tres son consecuencias directas de que la unidad sea el documento.

El primero es la granularidad. El documento se carga entero, se sincroniza entero y se comparte entero. No hay carga parcial de una sección, no hay consulta que devuelva un subconjunto sin materializar el resto, y no hay forma de compartir la mitad de un documento con alguien. En cuanto tu colección crece, la salida es trocear en muchos documentos pequeños con un documento índice que los referencia, y ahí empieza el problema de verdad: acabas reconstruyendo a mano lo que una base de datos te daba hecho, incluidos los índices secundarios, la paginación y la coherencia entre documentos. Los proyectos serios del ecosistema trabajan con miles de documentos abiertos a la vez, y eso funciona, pero es una arquitectura que hay que diseñar deliberadamente y no algo que emerja solo.

// El patron de documento indice, que acabaras escribiendo si creces.
// Un documento raiz que solo guarda referencias y metadatos ligeros.
const indice = A.from({
  proyectos: {
    // clave: identificador del documento hijo
    // valor: solo lo que necesitas para listar sin abrirlo
    "automerge:2akvofn6L1o4RMUEMQi7qzwRjKWZ": {
      nombre: new A.ImmutableString("Rediseno del portal"),
      actualizado: new Date(),
      archivado: false,
    },
  },
});

// Cada proyecto vive en su propio documento y se carga solo al abrirlo.
// El coste de esta arquitectura: los metadatos del indice estan
// duplicados y hay que mantenerlos coherentes a mano en cada escritura.

Ese fragmento parece inocente y contiene el trabajo entero. En cuanto duplicas el nombre del proyecto en el índice tienes dos fuentes de verdad que pueden divergir, y mantenerlas coherentes exige que cada escritura sobre el documento hijo actualice también el padre, con el problema añadido de que ambas escrituras no son atómicas entre sí. Cuando además quieras filtrar, ordenar por un campo que no está en el índice o contar elementos que viven dentro de los hijos, descubrirás que has empezado a escribir un motor de consultas. No es un argumento contra la arquitectura, es un aviso de a qué te estás comprometiendo.

El segundo es la ausencia de consultas. No hay lenguaje de consulta, no hay agregación entre documentos y no hay transacciones que abarquen más de uno. Si tu producto necesita responder «cuántos elementos cumplen esta condición en toda la colección», tendrás que mantener tú el índice, actualizarlo en cada cambio y asegurarte de que converge, lo cual es un CRDT adicional que ahora te toca diseñar. Este es el punto donde los niveles del track dedicados a SQLite en el cliente vuelven a ser relevantes, porque resuelven exactamente lo que aquí falta.

El tercero es el control de acceso. Cualquier réplica que tenga el documento puede escribir en él lo que quiera, y como vimos en la tercera lección la autoría registrada es una etiqueta que el propio autor se pone, no una afirmación verificable. No hay validación del lado del servidor de forma natural, porque no hay un lado del servidor privilegiado en el modelo. Existe una capa de control de acceso en desarrollo activo, con cifrado de extremo a extremo y con el trabajo de procedencia de autoría que hace posible revocar permisos, pero a día de hoy es trabajo en curso y no una casilla que se marque.

⚠️
Los tres límites se manifiestan tarde y a la vez

Lo peligroso de estos tres límites es su calendario. Ninguno molesta en el prototipo, porque con veinte documentos pequeños y tres usuarios de confianza todo funciona. Aparecen juntos cuando el producto tiene tracción: la colección crece y la carga se vuelve lenta, alguien pide un informe agregado y no hay forma de producirlo, y el primer cliente empresarial pregunta cómo se impide que un colaborador externo edite lo que no debe. Para entonces el modelo de datos lleva meses en producción. La mitigación no es técnica sino de calendario: decide el tamaño y la frontera de tus documentos, y tu postura sobre permisos, antes de la primera línea de código.

Frente a las alternativas

El track ha recorrido las opciones que compiten aquí, así que el contraste se puede hacer en términos ya conocidos. Frente a SQLite ejecutándose en el cliente, la diferencia es consultas contra fusión: la base de datos te da un lenguaje de consulta, índices y colecciones grandes, y te deja a ti el problema de la reconciliación; Automerge te da la reconciliación demostrada y te deja a ti el problema de las consultas. Elegir es decidir cuál de los dos problemas prefieres tener, y la respuesta honesta depende de si tu dominio es más parecido a un editor o más parecido a un catálogo.

Frente a las bibliotecas centradas en edición en vivo, la diferencia es la historia. Esas bibliotecas son más pequeñas, arrancan más rápido y en muchos casos consumen menos, porque tratan la historia como algo prescindible una vez fusionado. Si lo único que necesitas es que varios cursores editen a la vez y nadie va a pedir nunca una versión anterior, esa renuncia es una ventaja y no un defecto. Si en cambio la historia es parte del producto, reconstruirla encima de una biblioteca que no la conserva es un proyecto en sí mismo.

Frente al servidor autoritativo con última escritura ganadora, la pregunta es si la autoridad central existe de verdad. Si tu dominio ya tiene un árbitro legítimo —un sistema de pagos, un inventario, un expediente regulado— entonces el modelo sin coordinación te está resolviendo un problema que no tienes y complicándote uno que sí tienes. Y frente a los algoritmos de la familia de los registros de eventos que vimos en el nivel anterior, la diferencia está en el perfil de memoria en reposo para documentos de texto con historias muy largas, un terreno donde aquellos siguen teniendo ventaja estructural aunque la versión 3 haya recortado buena parte de la distancia.

Conviene añadir un eje de comparación que las tablas técnicas omiten y que pesa mucho en un proyecto real: qué te dan hecho más allá del tipo de dato. Aquí el ecosistema incluye una capa de repositorio que resuelve almacenamiento, compactación y transporte con adaptadores intercambiables, integración con interfaces reactivas, un sistema de sincronización de servidor en evolución con transportes tanto cliente-servidor como entre pares, y herramientas de diagnóstico como el anonimizador que permite compartir un documento problemático conservando su estructura exacta pero eliminando su contenido. Ese último detalle dice más sobre la madurez operativa del proyecto que cualquier banco de pruebas: existe porque alguien tuvo que depurar documentos reales de usuarios reales que no se podían enseñar.

flowchart TD
A[tiene el dato frontera natural de documento] -->|no| B[base de datos en el cliente con capa de sincronizacion]
A -->|si| C[la historia es parte del producto]
C -->|no| D[biblioteca de edicion en vivo mas ligera]
C -->|si| E[existe una autoridad central legitima]
E -->|si| F[servidor autoritativo con cache optimista]
E -->|no| G[Automerge]
style G fill:#a6e3a1,color:#11111b
style B fill:#89b4fa,color:#11111b
style D fill:#89b4fa,color:#11111b
style F fill:#89b4fa,color:#11111b
💡
La elección no tiene por qué ser única dentro de una misma aplicación

El árbol anterior se aplica por tipo de dato, no por producto. Una aplicación puede llevar sus documentos de contenido en Automerge, porque ahí la fusión y la historia valen oro, y a la vez llevar su catálogo, sus permisos y su facturación contra un servidor autoritativo con una base de datos convencional. De hecho es la arquitectura más común entre los productos maduros de esta familia. Forzar una sola tecnología para todo el estado de una aplicación es el error de diseño que más veces convierte una buena elección en una mala.

La pregunta correcta no es qué tecnología de sincronización es mejor, sino dónde vive la autoridad sobre cada dato

Si esta lección tiene una sola idea que sobreviva a la próxima generación de bibliotecas, es esta, y conviene enunciarla con cuidado porque reorganiza toda la decisión. Discutir CRDT contra transformación operacional, o esta biblioteca contra aquella, es discutir mecanismos, y los mecanismos cambian cada pocos años; lo que no cambia es una propiedad de tu dominio que no elige el ingeniero sino la realidad: para cada dato de tu sistema existe o no existe un árbitro legítimo, y esa respuesta no depende de la tecnología. El saldo de una cuenta tiene un árbitro y siempre lo tendrá, porque la institución que responde de él es la que decide qué es verdad; ninguna elegancia algorítmica va a hacer que dos saldos divergentes sean ambos correctos, y aplicar fusión automática ahí no resuelve un conflicto sino que lo esconde. El párrafo de una nota compartida no tiene árbitro, porque no existe ninguna autoridad que pueda decidir cuál de dos redacciones simultáneas es la verdadera; imponer un servidor que arbitre ahí no aporta corrección, solo destruye trabajo del usuario que llegó segundo. Casi todos los fracasos de arquitectura que verás en este espacio son un desajuste entre esas dos categorías: gente que pone fusión automática sobre datos con árbitro y descubre demasiado tarde que ha creado dinero de la nada, y gente que pone un servidor autoritativo sobre datos sin árbitro y pasa años recibiendo quejas de que la aplicación borra lo que la gente escribe. La segunda mitad, que es donde está el valor práctico: una vez clasificado cada dato, la elección de biblioteca se vuelve casi mecánica y, lo que es más importante, reversible. Los datos sin árbitro admiten varias implementaciones que compiten en rendimiento y ergonomía, y cambiar de una a otra es un proyecto acotado porque la semántica —converger sin coordinar— es la misma. Los datos con árbitro admiten cualquier base de datos convencional y su semántica también es estable. Lo que no es reversible, ni barato, ni acotado, es haber clasificado mal un dato: eso no se corrige migrando una biblioteca, se corrige rediseñando el producto, porque las expectativas de los usuarios se construyeron sobre la clasificación equivocada. Por eso el trabajo que de verdad importa en un proyecto local-first no es escoger tecnología sino recorrer el modelo de datos campo por campo preguntando quién tiene derecho a decidir cuando dos personas discrepan. Si la respuesta es «nadie, ambas versiones son legítimas», estás ante el terreno de Automerge. Si la respuesta es «esta autoridad y solo ella», ninguna cantidad de CRDT te va a servir, y reconocerlo pronto es la decisión de arquitectura más rentable que vas a tomar en todo el proyecto.

El criterio de decisión

Cuatro preguntas bastan, y conviene responderlas por escrito y por tipo de dato antes de comprometer nada. La primera: ¿el dato tiene una frontera natural de documento y un tamaño acotado por su naturaleza? Si para responder tienes que inventar una partición artificial, la respuesta es no. La segunda: ¿la historia es algo que el producto muestra o algo que el sistema soporta? Si nadie va a mirarla nunca, estás pagando por almacenarla sin razón.

La tercera: ¿existe una autoridad legítima que pueda decidir en caso de discrepancia? Si existe, revisa muy en serio si necesitas fusión sin coordinación. La cuarta: ¿puedes escribir hoy tu política de retención y de permisos y comprobar que es realizable sobre una estructura que no olvida y que no autoriza? Si esa comprobación falla, el problema no es de implementación y no va a mejorar con el tiempo.

// Una plantilla para responderlas por escrito, entidad por entidad.
const clasificacion = [
  {
    entidad: "documento de nota",
    arbitro: null,              // nadie puede decidir por los dos
    frontera: "la nota",        // natural y acotada
    historiaVisible: true,      // el usuario ve versiones
    retencionExigida: false,    // no hay datos personales dentro
    decision: "Automerge",
  },
  {
    entidad: "linea de facturacion",
    arbitro: "servicio de cobros",
    frontera: null,             // habria que inventarla
    historiaVisible: false,
    retencionExigida: true,     // hay obligacion de suprimir
    decision: "base de datos con servidor autoritativo",
  },
];

La columna que más discusión genera es la del árbitro, y es la que hay que rellenar primero porque anula a todas las demás. Si existe un árbitro legítimo, ninguna de las otras respuestas importa. Solo cuando la casilla queda vacía tiene sentido seguir bajando por la lista, y esa es la razón de que el árbol de decisión de esta lección la ponga al final y no al principio: para llegar a ella ya has descartado los casos donde la pregunta no procede.

Termina con una nota de contexto para calibrar el riesgo de adopción. El proyecto ronda las ochenta y cinco mil descargas semanales, tiene un núcleo mantenido activamente, una capa de sincronización en evolución, una capa de control de acceso en desarrollo y un ecosistema pequeño pero real de productos que lo usan en serio. Es una tecnología madura en lo que lleva años estabilizado —el modelo de datos, el formato, la semántica de fusión— y todavía en movimiento en lo que rodea a esos cimientos. Adoptarla es una apuesta razonable si tu dependencia principal está en la parte estable y no en la que aún se está escribiendo.

⚔️ Clasifica tu modelo de datos antes de elegir
  1. Lista cada entidad de tu producto y anota si tiene o no una autoridad legítima en caso de discrepancia.
  2. Para las que no la tienen, decide cuál es la frontera de documento y estima su tamaño máximo realista.
  3. Escribe qué funcionalidad de historia va a ver el usuario y descarta la biblioteca si la lista queda vacía.
  4. Diseña el documento índice que necesitarías si tu colección creciera cien veces y evalúa si sigue siendo razonable.
  5. Redacta tu política de permisos y de supresión y comprueba si es realizable con lo que la biblioteca ofrece hoy.
  6. Elige una entidad de cada categoría y prototipa ambas rutas, midiendo carga, fusión y complejidad del código.