20 ago 2025·8 min de lectura

Resolución de conflictos en formularios offline-first con Kotlin y SQLite

Aprende a resolver conflictos en formularios offline-first: reglas de fusión claras, un flujo de sincronización simple en Kotlin + SQLite y patrones de UX prácticos para ediciones en conflicto.

Resolución de conflictos en formularios offline-first con Kotlin y SQLite

¿Qué ocurre realmente cuando dos personas editan sin conexión?

Los formularios offline-first permiten ver y editar datos incluso cuando la red es lenta o no está disponible. En lugar de esperar al servidor, la app escribe los cambios primero en una base local SQLite y luego sincroniza después.

Eso se siente instantáneo, pero crea una realidad simple: dos dispositivos pueden cambiar el mismo registro sin conocerse.

Un conflicto típico se ve así: un técnico abre una orden de trabajo en una tablet en un sótano sin señal. Marca el estado como "Done" y añade una nota. Al mismo tiempo, un supervisor en otro teléfono actualiza la misma orden, la reasigna y edita la fecha de vencimiento. Ambos pulsan Guardar. Ambos guardados tienen éxito localmente. Nadie hizo nada mal.

Cuando ocurre la sincronización, el servidor tiene que decidir cuál es el registro "real". Si no manejas los conflictos explícitamente, normalmente terminas con uno de estos resultados:

  • Last write wins: la sincronización más tardía sobrescribe cambios anteriores y alguien pierde datos.
  • Fallo duro: la sincronización rechaza una actualización y la app muestra un error poco útil.
  • Registros duplicados: el sistema crea una copia secundaria para evitar sobrescribir y los reportes se complican.
  • Fusión silenciosa: el sistema combina cambios, pero mezcla campos de formas que los usuarios no esperan.

Los conflictos no son un bug. Son el resultado predecible de permitir que la gente trabaje sin conexión en vivo, que es justamente el objetivo de offline-first.

La meta es doble: proteger los datos y mantener la app fácil de usar. Eso suele significar reglas de fusión claras (a menudo a nivel de campo) y una experiencia que interrumpa a la gente solo cuando realmente importa. Si dos ediciones tocan campos distintos, a menudo puedes fusionar en segundo plano. Si dos personas cambian el mismo campo de formas diferentes, la app debe hacerlo visible y ayudar a alguien a elegir el resultado correcto.

Elige una estrategia de conflictos que coincida con tus datos

Los conflictos no son primero un problema técnico. Son una decisión de producto sobre qué significa “correcto” cuando dos personas cambiaron el mismo registro antes de sincronizar.

Tres estrategias cubren la mayoría de apps offline:

  • Last write wins (LWW): acepta la edición más reciente y sobrescribe la anterior.
  • Revisión manual: detiene y pide a un humano que elija qué conservar.
  • Fusión a nivel de campo: combina cambios por campo y solo pregunta cuando dos personas tocaron el mismo campo.

LWW puede estar bien cuando la velocidad importa más que la precisión perfecta y el costo de equivocarse es bajo. Piensa en notas internas, etiquetas no críticas o un estado borrador que se pueda editar de nuevo.

La revisión manual es la opción más segura para campos de alto impacto donde la app no debería adivinar: textos legales, confirmaciones de cumplimiento, montos de nómina y facturación, detalles bancarios, instrucciones de medicación y cualquier cosa que pueda crear responsabilidad legal.

La fusión a nivel de campo suele ser la mejor por defecto para formularios donde distintos roles actualizan diferentes partes. Un agente de soporte edita la dirección mientras ventas actualiza la fecha de renovación. Una fusión por campo conserva ambos cambios sin molestar a nadie. Pero si ambos usuarios editaron la fecha de renovación, ese campo debe disparar una decisión.

Antes de implementar nada, escribe qué significa “correcto” para tu negocio. Un checklist rápido ayuda:

  • ¿Qué campos deben reflejar siempre el valor real más reciente (por ejemplo, el status actual)?
  • ¿Qué campos son históricos y nunca deben sobrescribirse (por ejemplo, submitted_at)?
  • ¿Quién puede cambiar cada campo (rol, propiedad, aprobaciones)?
  • ¿Cuál es la fuente de verdad cuando los valores discrepan (dispositivo, servidor, aprobación de un gerente)?
  • ¿Qué ocurre si eliges mal (molestia menor vs impacto financiero o legal)?

Cuando estas reglas están claras, el código de sincronización tiene un solo trabajo: hacerlas cumplir.

Define reglas de fusión por campo, no por pantalla

Cuando ocurre un conflicto, raramente afecta todo el formulario por igual. Un usuario puede actualizar un número de teléfono mientras otro añade una nota. Si tratas el registro entero como todo o nada, fuerzas a las personas a rehacer trabajo bueno.

La fusión a nivel de campo es más predecible porque cada campo tiene un comportamiento conocido. La UX se mantiene tranquila y rápida.

Una forma simple de empezar es separar campos en categorías “usualmente seguros” y “usualmente inseguros”.

Usualmente seguros para fusionar automáticamente: notas y comentarios internos, etiquetas, adjuntos (a menudo unión) y timestamps como last_contacted (a menudo conservar el más reciente).

Usualmente inseguros para fusionar automáticamente: status/estado, assignee/asignado, totales/precios, flags de aprobación y conteos de inventario.

Luego elige una regla de prioridad por campo. Opciones comunes son server wins, client wins, role wins (por ejemplo, el gerente sobreescribe al agente) o un desempate determinista como la versión de servidor más nueva.

La pregunta clave es qué pasa cuando ambos lados cambiaron el mismo campo. Para cada campo, elige un comportamiento:

  • Auto-merge con una regla clara (por ejemplo, las tags son una unión)
  • Conservar ambos valores (por ejemplo, anexar notes con autor y hora)
  • Marcar para revisión (por ejemplo, status y assignee requieren una elección)

Ejemplo: dos reps de soporte editan el mismo ticket offline. El Rep A cambia status de Open a Pending. El Rep B cambia notes y añade la tag refund. Al sincronizar, puedes fusionar sin problemas notes y tags, pero no deberías fusionar silenciosamente status. Solo solicita elección para status, con todo lo demás ya fusionado.

Para evitar debates más tarde, documenta cada regla en una frase por campo:

  • "notes: conservar ambos, anexar el más reciente al final, incluir autor y hora."
  • "tags: unión; eliminar solo si se elimina explícitamente en ambos lados."
  • "status: si se cambió en ambos lados, requerir elección del usuario."
  • "assignee: el gerente gana; si no hay, gana el servidor."

Esa frase se convierte en la fuente de verdad para el código Kotlin, las consultas SQLite y la UI de conflictos.

Fundamentos del modelo de datos: versiones y campos de auditoría en SQLite

Si quieres que los conflictos sean predecibles, añade un pequeño conjunto de columnas de metadatos a cada tabla sincronizada. Sin ellas, no puedes saber si estás viendo una edición reciente, una copia antigua o dos ediciones que necesitan fusión.

Un mínimo práctico para cada registro sincronizado con el servidor:

  • id (clave primaria estable): nunca lo reutilices
  • version (entero): incrementa en cada escritura exitosa en el servidor
  • updated_at (timestamp): cuándo se cambió por última vez el registro
  • updated_by (texto o id de usuario): quién hizo el último cambio

En el dispositivo, añade campos solo locales para rastrear cambios que no han sido confirmados por el servidor:

  • dirty (0/1): existen cambios locales
  • pending_sync (0/1): en cola para subir, pero no confirmado
  • last_synced_at (timestamp): última vez que esta fila coincidió con el servidor
  • sync_error (texto, opcional): última razón de fallo para mostrar en la UI

La concurrencia optimista es la regla más simple que evita sobrescrituras silenciosas: cada actualización incluye la versión que crees estar editando (un expected_version). Si el registro en el servidor sigue en esa versión, la actualización se acepta y el servidor devuelve la nueva versión. Si no, es un conflicto.

Ejemplo: el Usuario A y el Usuario B descargaron ambos version = 7. A sincroniza primero; el servidor sube a 8. Cuando B intenta sincronizar con expected_version = 7, el servidor rechaza con un conflicto para que la app de B fusione en vez de sobrescribir.

Para una buena pantalla de conflicto, guarda el punto de partida compartido: desde qué registro el usuario originalmente editó. Dos enfoques comunes:

  • Guardar una snapshot del último registro sincronizado (una columna JSON o una tabla paralela).
  • Guardar un change log (fila por edición o campo por edición).

Las snapshots son más simples y a menudo suficientes para formularios. Los change logs son más pesados, pero pueden explicar exactamente qué cambió, campo por campo.

De cualquier forma, la UI debe poder mostrar tres valores por campo: la edición del usuario, el valor actual del servidor y el punto de partida compartido.

Snapshots de registro vs change logs: elige un enfoque

Lanza una UI amigable con conflictos
Crea UIs web y móviles nativas que mantengan “Guardado localmente” y “Sincronizado” como estados separados.
Construir app

Cuando sincronizas formularios offline-first, puedes subir el registro completo (una snapshot) o subir una lista de operaciones (un change log). Ambos funcionan con Kotlin y SQLite, pero se comportan distinto cuando dos personas editan el mismo registro.

Opción A: Snapshots del registro completo

Con snapshots, cada guardado escribe el estado completo más reciente (todos los campos). Al sincronizar, envías el registro más un número de versión. Si el servidor ve que la versión está desactualizada, tienes un conflicto.

Esto es simple de construir y rápido de leer, pero a menudo crea conflictos mayores de lo necesario. Si el Usuario A edita el teléfono mientras el Usuario B edita la dirección, un enfoque de snapshot puede tratarlo como un gran choque aunque las ediciones no se solapen.

Opción B: Change logs (operaciones)

Con change logs, guardas qué cambió, no el registro entero. Cada edición local se convierte en una operación que puedes reproducir sobre el estado más reciente del servidor.

Operaciones que suelen ser más fáciles de fusionar:

  • Establecer un valor de campo (set email a un nuevo valor)
  • Anexar una nota (añade un nuevo ítem de nota)
  • Añadir una tag (añade una tag a un conjunto)
  • Eliminar una tag (elimina una tag de un conjunto)
  • Marcar un checkbox como completo (set isDone true con un timestamp)

Los operation logs pueden reducir conflictos porque muchas acciones no se solapan. Anexar notas rara vez entra en conflicto con que otra persona anexe una nota distinta. Las adiciones/quitas de tags pueden fusionarse como álgebra de conjuntos. Para campos de valor único, aún necesitas reglas por campo cuando compiten dos ediciones distintas.

El intercambio es la complejidad: IDs de operación estables, orden (secuencia local y tiempo del servidor) y reglas para operaciones que no conmutan.

Limpieza: compactar tras una sincronización exitosa

Los operation logs crecen, así que planifica cómo reducirlos.

Un enfoque común es la compactación por registro: una vez que todas las operaciones hasta una versión de servidor conocida son reconocidas, pliega esas operaciones en una nueva snapshot y borra las operaciones más antiguas. Mantén solo una cola corta si necesitas undo, auditoría o depuración más fácil.

Flujo de sincronización paso a paso para Kotlin + SQLite

Prueba la sincronización correctamente
Crea una prueba de sincronización reproducible con dos dispositivos para que las fusiones sigan siendo predecibles al iterar.
Comenzar a crear

Una buena estrategia de sincronización consiste principalmente en ser estricto con lo que envías y lo que aceptas de vuelta. La meta es simple: nunca sobrescribir datos más nuevos por accidente y hacer los conflictos evidentes cuando no se pueden fusionar de forma segura.

Un flujo práctico:

  1. Escribe cada edición en SQLite primero. Guarda cambios en una transacción local y marca el registro como pending_sync = 1. Almacena local_updated_at y la última server_version conocida.

  2. Envía un parche, no el registro completo. Cuando vuelve la conectividad, envía el id del registro más solo los campos que cambiaron, junto con expected_version.

  3. Deja que el servidor rechace versiones que no coincidan. Si la versión actual del servidor no coincide con expected_version, devuelve un payload de conflicto (registro del servidor, cambios propuestos y qué campos difieren). Si las versiones coinciden, aplica el parche, incrementa la versión y devuelve el registro actualizado.

  4. Aplica auto-merge primero y luego pide al usuario. Ejecuta las reglas de fusión por campo. Trata campos seguros como notes distinto de campos sensibles como status, precio o assignee.

  5. Confirma el resultado final y limpia las flags de pending. Ya sea que se haya auto-fusionado o resuelto manualmente, escribe el registro final de vuelta en SQLite, actualiza server_version, pon pending_sync = 0 y registra suficientes datos de auditoría para explicar lo ocurrido más tarde.

Ejemplo: dos reps de ventas editan la misma orden offline. El Rep A cambia la fecha de entrega. El Rep B cambia el teléfono del cliente. Con patches, el servidor puede aceptar ambos cambios sin problemas. Si ambos cambiaron la fecha de entrega, muestras una decisión clara en vez de forzar reingreso completo.

Mantén la promesa de la UI consistente: "Guardado" debería significar guardado localmente. "Sincronizado" debe ser un estado separado y explícito.

Patrones de UX para resolver conflictos en formularios

Los conflictos deben ser la excepción, no el flujo normal. Empieza fusionando automáticamente lo que sea seguro y luego pregunta al usuario solo cuando una decisión sea realmente necesaria.

Haz que los conflictos sean raros con valores predeterminados seguros

Si dos personas editan campos distintos, fusiona sin mostrar un modal. Conserva ambos cambios y muestra un pequeño mensaje "Actualizado después de sincronizar".

Reserva prompts para colisiones reales: el mismo campo cambiado en ambos dispositivos, o un cambio que dependa de otro campo (como status más razón de estado).

Cuando debas preguntar, haz que sea rápido de terminar

Una pantalla de conflicto debe responder dos cosas: qué cambió y qué se guardará. Compara valores uno al lado del otro: "Tu edición", "Su edición" y "Resultado guardado". Si solo hay dos campos en conflicto, no muestres todo el formulario. Salta directamente a esos campos y deja el resto en solo lectura.

Limita las acciones a lo que la gente realmente necesita:

  • Conservar la mía
  • Conservar la de ellos
  • Editar el resultado final
  • Revisar campo por campo (solo si es necesario)

Las fusiones parciales son donde la UX se complica. Resalta solo los campos en conflicto y etiqueta la fuente claramente ("Tuyo" y "Suyo"). Preselecciona la opción más segura para que el usuario pueda confirmar y seguir.

Fija expectativas para que los usuarios no se sientan atrapados. Diles qué pasa si se van: por ejemplo, "Conservaremos tu versión localmente y reintentaremos sincronizar más tarde" o "Este registro permanecerá en Necesita revisión hasta que elijas". Haz ese estado visible en la lista para que los conflictos no se pierdan.

Si construyes este flujo en AppMaster, el mismo enfoque de UX aplica: fusiona automáticamente campos seguros primero y luego muestra un paso de revisión enfocado solo cuando campos específicos colisionen.

Casos complicados: borrados, duplicados y registros “faltantes”

Obtén código fuente real
Exporta código fuente real cuando necesites control total sobre Kotlin, SwiftUI, Go y Vue3.
Crear ahora

La mayoría de problemas de sincronización que parecen aleatorios vienen de tres situaciones: alguien borra mientras otro edita, dos dispositivos crean la “misma” cosa offline, o un registro desaparece y luego reaparece. Estos necesitan reglas explícitas porque LWW suele sorprender a la gente.

Borrar vs editar: ¿quién gana?

Decide si un borrado es más fuerte que una edición. En muchas apps de negocio, borrar gana porque los usuarios esperan que un registro eliminado siga eliminado.

Un conjunto práctico de reglas:

  • Si un registro se elimina en cualquier dispositivo, trátalo como eliminado en todas partes, incluso si hay ediciones posteriores.
  • Si los borrados deben ser reversibles, convierte "borrar" en un estado archivado en lugar de un borrado duro.
  • Si llega una edición para un registro borrado, conserva la edición en el historial para auditoría, pero no restaures el registro.

Colisiones en creaciones offline y borradores duplicados

Los formularios offline-first suelen crear IDs temporales (por ejemplo UUID) antes de que el servidor asigne un id final. Ocurren duplicados cuando los usuarios crean dos borradores para la misma cosa real (el mismo recibo, el mismo ticket, el mismo ítem).

Si tienes una clave natural estable (número de recibo, código de barras, email más fecha), úsala para detectar colisiones. Si no la tienes, acepta que aparecerán duplicados y ofrece una opción simple de fusión más tarde.

Consejo de implementación: guarda tanto local_id como server_id en SQLite. Cuando el servidor responda, escribe un mapeo y mantenlo al menos hasta estar seguro de que no hay cambios en cola que aún referencien el id local.

Evitar la “resurrección” tras sincronizar

La resurrección ocurre cuando el Dispositivo A borra un registro, pero el Dispositivo B está offline y luego sube una copia antigua como un upsert, recreándolo.

La solución es un tombstone. En vez de borrar la fila inmediatamente, márcala como eliminada con deleted_at (y a menudo deleted_by y delete_version). Durante la sincronización, trata los tombstones como cambios reales que pueden sobreescribir estados no eliminados más antiguos.

Decide cuánto tiempo conservar tombstones. Si los usuarios pueden estar offline por semanas, consérvalos más tiempo que eso. Purga solo después de estar seguro de que los dispositivos activos sincronizaron más allá del borrado.

Si soportas deshacer, trata el undo como otro cambio: limpia deleted_at y sube la versión.

Errores comunes que causan pérdida de datos o frustración

Construye tu app offline-first
Crea un flujo de formularios offline-first con estados de sincronización claros y manejo de conflictos en un solo proyecto.
Probar AppMaster

Muchas fallas de sincronización vienen de pequeñas suposiciones que silenciosamente sobrescriben datos buenos.

Error 1: Confiar en la hora del dispositivo para ordenar ediciones

Los teléfonos pueden tener relojes equivocados, las zonas horarias cambian y los usuarios pueden ajustar la hora manualmente. Si ordenas cambios por timestamps del dispositivo, acabarás aplicando ediciones en el orden incorrecto.

Prefiere versiones emitidas por el servidor (serverVersion) y trata las marcas de tiempo del cliente como solo para mostrar. Si debes usar tiempo, añade salvaguardas y reconcilia en el servidor.

Error 2: LWW accidental en campos sensibles

LWW parece simple hasta que golpea campos que no deberían “ganar” por quien sincroniza más tarde. Estado, totales, aprobaciones y asignaciones suelen necesitar reglas explícitas.

Lista de verificación de seguridad para campos de alto riesgo:

  • Trata transiciones de status como una máquina de estados, no como texto libre.
  • Recalcula totales a partir de las líneas. No mezcles totales como números crudos.
  • Para contadores, fusiona aplicando deltas, no eligiendo un ganador.
  • Para propiedad o assignee, requiere confirmación explícita en conflictos.

Error 3: Sobrescribir valores de servidor más nuevos con caché obsoleta

Esto ocurre cuando el cliente edita una snapshot antigua y luego sube el registro completo. El servidor lo acepta y los cambios más recientes del servidor desaparecen.

Arregla la forma de lo que envías: manda solo campos cambiados (o un change log), más la versión base que editaste. Si la versión base está atrasada, el servidor rechaza o fuerza una fusión.

Error 4: No guardar “quién cambió qué” en el historial

Cuando ocurren conflictos, los usuarios quieren una respuesta: ¿qué cambié yo y qué cambió la otra persona? Sin identidad del editor y cambios por campo, la pantalla de conflicto se vuelve conjetural.

Guarda updatedBy, tiempo de actualización en servidor si lo tienes, y al menos un rastro ligero de auditoría por campo.

Error 5: UI de conflicto que fuerza comparaciones de registro completo

Hacer que la gente compare registros enteros es agotador. La mayoría de conflictos son solo uno a tres campos. Muestra solo los campos en conflicto, preselecciona la opción más segura y permite que el usuario acepte lo demás automáticamente.

Si estás construyendo formularios en una herramienta no-code como AppMaster, apunta al mismo resultado: resuelve conflictos a nivel de campo para que los usuarios tomen una decisión clara en lugar de desplazarse por todo el formulario.

Lista rápida y próximos pasos

Si quieres que las ediciones offline se sientan seguras, trata los conflictos como un estado normal, no como un error. Los mejores resultados vienen de reglas claras, pruebas repetibles y una UX que explique lo ocurrido en lenguaje llano.

Antes de añadir más características, asegúrate de que estas bases estén aseguradas:

  • Para cada tipo de registro, asigna una regla de fusión por campo (LWW, conservar max/min, anexar, unión o siempre preguntar).
  • Guarda una versión controlada por el servidor más un updated_at que controles y valídalos durante la sincronización.
  • Ejecuta una prueba con dos dispositivos donde ambos editen el mismo registro offline y luego sincronicen en ambos órdenes (A luego B, B luego A). El resultado debe ser predecible.
  • Prueba los conflictos difíciles: borrar vs editar, y editar vs editar en distintos campos.
  • Haz el estado obvio: muestra Sincronizado, Pendiente de subir y Necesita revisión.

Prototipa el flujo completo de extremo a extremo con un formulario real, no una pantalla demo. Usa un escenario realista: un técnico de campo actualiza una nota de trabajo en un teléfono mientras un despachador edita el mismo título de trabajo en una tablet. Si tocan campos distintos, auto-fusiona y muestra un pequeño aviso "Actualizado desde otro dispositivo". Si tocan el mismo campo, redirige a una pantalla de revisión simple con dos opciones y una vista previa clara.

Cuando estés listo para construir la app móvil completa y las APIs del backend juntas, AppMaster (appmaster.io) puede ayudar. Puedes modelar datos, definir lógica de negocio y construir UIs web y nativas en un mismo lugar, y luego desplegar o exportar código fuente cuando tus reglas de sincronización estén sólidas.

FAQ

What is an offline sync conflict, in plain terms?

Un conflicto ocurre cuando dos dispositivos cambian el mismo registro respaldado por el servidor mientras están offline (o antes de que cualquiera haya sincronizado), y el servidor más tarde ve que ambas actualizaciones se basaron en una versión anterior. El sistema debe entonces decidir cuál será el valor final de cada campo que difiere.

Which conflict strategy should I choose: last write wins, manual review, or field-level merge?

Empieza con fusión a nivel de campo como predeterminado en la mayoría de formularios empresariales, porque diferentes roles suelen editar campos distintos y puedes conservar ambos cambios sin molestar a nadie. Usa revisión manual solo para campos que pueden causar daño real si se adivina mal (dinero, aprobaciones, cumplimiento). Usa last write wins solo para campos de bajo riesgo donde perder un edit anterior sea aceptable.

When should the app ask the user to resolve a conflict?

Si dos ediciones tocan campos diferentes, normalmente puedes fusionar automáticamente y mantener la UI silenciosa. Si dos ediciones cambian el mismo campo a valores distintos, ese campo debe disparar una decisión, porque cualquier elección automática puede sorprender a alguien. Mantén el alcance de la decisión pequeño mostrando solo los campos en conflicto, no todo el formulario.

How do record versions prevent silent overwrites?

Trata version como el contador monótono del servidor para el registro y exige que el cliente envíe un expected_version con cada actualización. Si la versión actual del servidor no coincide, rechaza con una respuesta de conflicto en lugar de sobrescribir. Esta regla evita la “pérdida silenciosa de datos” incluso cuando dos dispositivos sincronizan en distinto orden.

What metadata should every synced SQLite table include?

Un mínimo práctico es un id estable, un version controlado por el servidor y updated_at/updated_by controlados por el servidor para poder explicar qué cambió. En el dispositivo, rastrea si la fila fue cambiada y está esperando subir (por ejemplo pending_sync) y guarda la última versión de servidor sincronizada. Sin esto no puedes detectar conflictos ni mostrar una resolución útil.

Should I sync the whole record or only changed fields?

Envía solo los campos que cambiaron (un patch) más la expected_version base. Las subidas de registros completos convierten ediciones pequeñas y no solapadas en conflictos innecesarios y aumentan la posibilidad de sobrescribir un valor más nuevo del servidor con datos en caché obsoletos. Los patches también dejan claro qué campos necesitan reglas de fusión.

Is it better to store snapshots or a change log for offline edits?

Un snapshot es más simple: guardas el registro completo y lo comparas después con el servidor. Un change log es más flexible: guardas operaciones como “set field” o “append note” y las reproduces sobre el estado más reciente del servidor, lo que suele fusionar mejor para notas, tags y actualizaciones aditivas. Elige snapshots para implementar rápido; elige change logs si las fusiones son frecuentes y necesitas detalle claro de “quién cambió qué”.

How should I handle delete vs edit conflicts?

Decide de antemano si eliminar es más fuerte que editar, porque la gente espera comportamiento consistente. Por defecto seguro en muchas apps de negocio es tratar los borrados como tombstones (marcar eliminado con deleted_at y una versión) para que un upsert offline más antiguo no traiga el registro de vuelta. Si necesitas reversibilidad, usa un estado “archivado” en vez de borrado duro.

What are the most common mistakes that cause offline sync data loss?

No ordenes escrituras críticas por la hora del dispositivo, porque los relojes se desajustan y cambian las zonas horarias; usa versiones del servidor para ordenar y comprobar conflictos. Evita last-write-wins en campos sensibles como status, assignee y totales; dales reglas explícitas o revisión manual. Tampoco muestres una pantalla de conflicto de registro completo cuando solo uno o dos campos colisionan, porque aumenta errores y frustración.

How can I implement a conflict-friendly UX when building with AppMaster?

Mantén la promesa de que “Guardado” significa guardado localmente y muestra un estado separado para “Sincronizado” para que los usuarios entiendan qué está pasando. Si lo construyes en AppMaster, apunta a la misma estructura: define reglas de fusión por campo como parte de la lógica del producto, fusiona automáticamente campos seguros y lleva solo las colisiones verdaderas a un paso de revisión pequeño. Prueba con dos dispositivos editando el mismo registro offline y sincronizando en ambos órdenes para confirmar que los resultados son predecibles.

Fácil de empezar
Crea algo sorprendente

Experimente con AppMaster con plan gratuito.
Cuando esté listo, puede elegir la suscripción adecuada.

Empieza