Inicio/Blog/Sistemas offline-first
Arquitectura de Aplicaciones Resilientes

La brecha de uso del 32%: sistemas offline-first para cortes de internet móvil

GSMA reporta una brecha de uso móvil del 32% en América Latina: personas viven dentro de cobertura, pero no usan internet móvil. El software no resuelve solo precio, habilidades, confianza o desigualdad, pero puede dejar de asumir conexión perfecta, datos ilimitados y equipos premium.

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ón
01 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

TipoEstrategia útilEjemploAtajo inseguro
Evento append-onlyConservar 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 independientesMerge 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 colaborativaCRDT 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 finitaReserva 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 workflowValidar estado/versión y rechazar o transformar explícitamente.Cerrar ticket reasignado o cancelado.Reproducir ciegamente el orden del device.
Pago o seguridadExigir 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ó.