La brecha de uso no es una falla de cobertura
GSMA separa coverage gap de usage gap. Una persona cubierta aún puede enfrentar equipo o plan caro, baja alfabetización digital, inseguridad, poco contenido local, hardware compartido o experiencia inestable. Offline-first trata solo una parte: reduce transferencia, espera, trabajo perdido y dependencia de sesiones continuas.
El objetivo no es “la app abre sin internet”. El usuario debe comprender los datos disponibles, completar trabajo crítico localmente, ver lo pendiente, reconectar sin duplicar efectos, resolver conflictos y confiar en que ninguna acción desapareció.
Presupuesto de datosInstalación, payload por ruta, media, tráfico background, batch de sync y consumo mensual.
Presupuesto de tiempoPrimera pantalla útil, confirmación local, retraso máximo, deadline de retry y conflicto.
Presupuesto de storageAssets, registros, adjuntos, outbox, logs, cifrado y política de eviction.
Presupuesto de confianzaQué se puede leer o cambiar offline, token, sensibilidad, reautenticación y auditoría.
La base local debe dirigir la interfaz
La guía offline-first de Android recomienda una fuente local canónica para las lecturas. Lo mismo vale en web con IndexedDB u otra base durable. La UI observa el estado local; las respuestas de red actualizan ese estado; la pantalla no alterna entre modelos separados de caché y servidor.
Usa Cache API y service workers para assets versionados. Usa IndexedDB o una base embebida para datos estructurados, índices, operaciones y adjuntos. Un shell HTML en caché no es una aplicación sincronizada y localStorage no es una base transaccional.
Representa writes como operaciones durables
Al enviar formulario, inspección, tarea o evidencia, guarda el cambio y una operación outbox en la misma transacción local. La UI muestra “guardado en este dispositivo” y conserva “enviado a la organización” como otro estado.
Cada operación necesita ID estable, entidad, actor, device, secuencia, versión base, schema, hora, dependencias, payload, intentos, próximo retry, expiración y clase de policy. El servidor debe aceptar idempotency key para evitar pedidos, visitas, tickets o pagos duplicados.
Máquina de estados de sincronización01 Local listoLee sin redRenderiza snapshot local con frescura, permisos, storage e indicadores de datos ausentes.
02 Commit localGuarda atómicamenteEscribe cambio y outbox juntos; informa que la acción está almacenada en el device.
03 En colaEspera sync elegibleRespeta conexión, batería, red medida, tamaño, elección, dependencias y deadline.
04 EnviandoPush idempotenteAutentica, envía batch limitado, incluye versiones y captura resultado por operación.
05 AceptadoAplica el ACKGuarda IDs, versiones, timestamps y campos canónicos; elimina solo entradas reconocidas.
06 ConflictoEjecuta policy de dominioMerge seguro, conserva revisiones, pide decisión o rechaza una acción stale sensible.
07 Retry o bloqueoClasifica el falloBackoff para transitorios; detente en auth, validación, cuota, policy, expiración o fallo permanente.
08 ReconciliadoObtén cambios incrementalesUsa cursors y tombstones, actualiza local, muestra outcomes y conserva auditoría mínima.
Sincronización es un protocolo
Define push y pull, orden, paginación, cursors, versiones, borrado, adjuntos, éxito parcial, expiración de auth, migración y compatibilidad. La respuesta clasifica cada operación: aceptada, duplicada, conflicto, inválida, no autorizada, expirada, limitada o temporal.
Obtén cambios por cursor estable. Mantén tombstones para devices que regresan semanas después. No confíes en relojes baratos; usa versión del servidor, secuencia lógica o causalidad cuando el orden importa.
La política de conflicto pertenece al dominio
| Tipo | Estrategia útil | Ejemplo | Atajo inseguro |
|---|
| Evento append-only | Conservar eventos, deduplicar por operation ID y ordenar por secuencia. | Inspección, nota de campo, scan de entrega, telemetría. | Sobrescribir porque el timestamp local es posterior. |
| Campos independientes | Merge por campo con procedencia y validación. | Nota de dirección y preferencia telefónica cambiadas por separado. | Last-write-wins del registro completo. |
| Texto o colección colaborativa | CRDT o merge por operaciones cuando la semántica lo justifica. | Notas, checklist y anotaciones compartidas. | Usar CRDT en inventario, dinero o autorización sin reglas. |
| Inventario o capacidad finita | Reserva server-authoritative, conditional write, espera o compensación. | Stock, cita, asiento o crédito. | Aceptar todos los decrementos y producir inventario negativo. |
| Transición de workflow | Validar estado/versión y rechazar o transformar explícitamente. | Cerrar ticket reasignado o cancelado. | Reproducir ciegamente el orden del device. |
| Pago o seguridad | Exigir autorización actual, límites online, idempotencia, expiración y servidor. | Transferencia, reset, grant o aprobación sensible. | Ejecutar automáticamente una acción días después. |
Reintenta solo fallos que pueden mejorar
Usa timeout, backoff exponencial, jitter, límites y deadline. Evita tormentas cuando miles de devices reconectan. Agrupa con límites de bytes e items, manteniendo resultados individuales.
No reintentes validación, credenciales revocadas, acciones prohibidas, operaciones expiradas, schema incompatible o conflictos abiertos. Muestra el trabajo bloqueado y su reparación.
La UX de bajo consumo empieza en el payload
Define presupuestos de bytes. Entrega shell y datos críticos, pagina historial, compacta schemas, comprime, pide deltas, redimensiona imágenes, separa thumbnail y original y hace explícita la media en redes medidas. Cancela requests obsoletos.
Distingue datos frescos, stale, ausentes, modificados, queued, syncing, en conflicto, rechazados y sincronizados. “3 inspecciones guardadas; último sync hace 2 días” es mejor que un punto verde ambiguo.
Background sync es una optimización
Service workers cachean assets e interceptan requests. Background Sync puede reintentar cuando vuelve la red, pero soporte y scheduling varían; periodic sync sigue experimental en algunos entornos. Sincroniza también en launch, resume, acción explícita y foreground.
Los jobs nativos tienen límites de batería, datos y sistema. La outbox es dueña del trabajo; el scheduler solo dispara intentos.
La autenticación offline tiene límites
Autorización en caché es una decisión de riesgo. Permite lecturas o drafts de bajo riesgo por tiempo limitado, pero no uses un token antiguo para una acción crítica. Minimiza datos, cifra con mecanismos de plataforma, separa usuarios y limpia al cerrar sesión o hacer wipe.
Exige autorización online para pagos, privilegios, recuperación, comandos destructivos o policy cambiante. Sin verificar policy, conserva un draft.
Prueba la máquina de estados
Prueba red lenta, pérdida, DNS, captive portal, timeout después del commit, duplicados, batch parcial, reconnect storm, token expirado, clock skew, upgrade, borrado, storage lleno, eviction, proceso muerto, update, device compartido y conflictos tras ausencia.
Usa tests deterministas de merge, property tests de orden y duplicación, integración entre versiones y device tests con network shaping. Una captura en airplane mode demuestra poco.
Observa sync como workflow
Mide latencia local, profundidad y edad de outbox, bytes, éxito, retries, bloqueos, conflictos, ACKs duplicados, cursor lag, storage, eviction, edad de datos, jobs y retries manuales. Segmenta por versión, device, país, operador, conexión y workflow.
Los outcomes son tareas terminadas, trabajo recuperado, abandono, soporte, costo de datos y batería y tiempo hasta confirmación. 99,9% de éxito puede ocultar las operaciones más antiguas e importantes.
Qué construiría
Construiría una capa reutilizable con base relacional local, repositories observables, outbox transaccional, protocolo versionado, adjuntos, scheduler limitado, handlers de conflicto, cifrado, migración y dashboard.
Cada módulo declararía qué se lee y escribe offline, edad máxima, merge, autorización online, presupuestos y estados UX. Offline deja de estar disperso en handlers de error.
El principio de diseño
Offline-first no es fingir que el servidor no existe. Es mantener coherente el trabajo local cuando desaparece la red y tratar sync como protocolo distribuido con estado visible, conflictos de dominio, riesgo limitado y evidencia de que ninguna acción se perdió.