🏗️ Arquitectura de Sincronización Offline-First entre App Móvil y Backend
🎯 Objetivo
Diseñar un proceso completo y robusto para la sincronización de datos (catálogos y tablas transaccionales) entre una aplicación móvil y un sistema backend con soporte Offline-First.
🧩 Componentes de la Arquitectura
📱 App Móvil
- DB Local: (SQLite/Realm) con catálogos, transacciones y tablas de control (
pending_outbox,sync_state,oplog). - Outbox: registra todas las operaciones CRUD realizadas sin conexión.
- Motor de Sync: detecta conectividad, maneja colas y aplica deltas desde el backend.
- Conflict Resolver: gestiona conflictos (LWW o cliente-gana).
- Manejador de Push: escucha notificaciones silenciosas y dispara sincronizaciones automáticas.
🌐 API Backend
- Endpoints de sincronización (batch e idempotentes).
- Módulo de catálogos con deltas y versionado.
- Resolución de conflictos y validación de negocio.
- Control de dispositivos (tokens, versiones, sincronización remota).
☁️ Servicio de Push
- Usa FCM (Android) o APNs (iOS).
- Envía notificaciones silenciosas (
content-available=1) para iniciar sincronizaciones sin alertar al usuario.
🗄️ Base de Datos Servidor
- Tablas maestras y operacionales con campos
updated_at,version, oetag. - Control de versiones para catálogos y journaling para generar deltas.
🖥️ Servicio de Gestión de Dispositivos
- Almacena
device_id,push_token,app_version,last_seen,platform. - Permite sincronizaciones dirigidas a segmentos de usuarios o zonas específicas.
⚙️ Estrategias Técnicas
1️⃣ Detección de Cambios (Cliente → Servidor)
Tabla pending_outbox con:
id(UUID),op(CREATE/UPDATE/DELETE),entity,entity_id,payload,idempotency_key,status,retry_count,created_at_local.- La app siempre escribe localmente primero, luego registra en
pending_outbox. - El motor de sync procesa los registros
PENDINGen lotes.
2️⃣ Actualización de Catálogos (Servidor → Cliente)
- Modelo “delta by timestamp/version”:
- El cliente guarda
last_catalog_sync_atpor catálogo. - El backend expone
/catalogs/{name}/delta?since=timestamp. - Devuelve
upserts[]ytombstones[]conupdated_atoversion.
- El cliente guarda
3️⃣ Manejo de Lotes
- Tamaños configurables (50–200 items).
- Reintentos con backoff exponencial y jitter.
- Endpoint batch idempotente con
idempotency_keypersistido en backend (TTL 14 días).
4️⃣ Resolución de Conflictos
| Tipo de dato | Estrategia | Regla |
|---|---|---|
| Catálogos | LWW (Last Write Wins) | El servidor gana |
| Transacciones nuevas | Cliente gana | Se aceptan si no violan reglas de negocio |
| Updates sobre misma entidad | Optimistic Concurrency | If-Match: <etag> o version |
5️⃣ Atomicidad
- Transacción por item dentro del batch.
- Éxito parcial permitido, respuesta con matriz de resultados.
- Modo opcional
atomic=truepara lotes que deben fallar todos o ninguno.
6️⃣ Seguridad
- Autenticación OAuth2.0/JWT.
- Conexión segura TLS.
- Límite de payload (512KB–2MB) y compresión GZIP.
- Trazas con
idempotency_key,device_id,sync_session_id.
🧠 Diagrama de Flujo – Lógica del Cliente (Offline-First)
🔁 Diagrama de Secuencia – Sincronización de Transacciones
📡 Diagrama de Secuencia – Sincronización Forzada (Push Silenciosa)
🗃️ Esquema de Base de Datos Local
CREATE TABLE pending_outbox (
id TEXT PRIMARY KEY,
op TEXT NOT NULL,
entity TEXT NOT NULL,
entity_id TEXT NOT NULL,
payload TEXT NOT NULL,
idempotency_key TEXT NOT NULL,
created_at_local TEXT NOT NULL,
status TEXT NOT NULL DEFAULT 'PENDING',
retry_count INTEGER NOT NULL DEFAULT 0
);
CREATE TABLE sync_state (
key TEXT PRIMARY KEY,
last_sync_at TEXT,
last_version INTEGER
);
CREATE TABLE cat_products (
id TEXT PRIMARY KEY,
sku TEXT,
name TEXT,
price REAL,
updated_at TEXT,
version INTEGER
);
🧩 Endpoints REST Recomendados
Transacciones
POST /transactions/batch
Request:
{
"items": [
{
"idempotency_key": "uuid",
"op": "CREATE",
"entity": "order",
"client_id": "uuid",
"payload": { "field": "value" }
}
],
"device": { "id": "dev123", "app_version": "1.2.0" }
}
Response:
{
"results": [
{
"idempotency_key": "uuid",
"status": "OK",
"server_id": "srv-001",
"etag": "v2",
"message": "Success"
}
]
}
Catálogos
GET /catalogs/{name}/delta?since=2025-01-01T00:00:00Z
Response:
{
"upserts": [{ "id": "p1", "name": "Leche Alpura", "price": 22.5 }],
"tombstones": [{ "id": "p2", "deleted_at": "2025-10-01T00:00:00Z" }],
"server_time": "2025-10-22T10:00:00Z",
"version": 15
}
Catálogos
GET /catalogs/{name}/delta?since=2025-01-01T00:00:00Z
Response:
{
"upserts": [{ "id": "p1", "name": "Leche Alpura", "price": 22.5 }],
"tombstones": [{ "id": "p2", "deleted_at": "2025-10-01T00:00:00Z" }],
"server_time": "2025-10-22T10:00:00Z",
"version": 15
}
Sincronización
POST /sync/ack
Body:
{ "device_id": "dev123", "catalog": "products", "version": 15 }