ba363e7e18
Approved plan/protocol for connecting a mobile app to the webapp: offline-first with device outbox, PULL (bundle/delta/versioned templates/tombstones), PUSH (/api/v1/sync idempotent by client uuid), media via multipart, conflict policy, schema additions, security, and phased webapp deliverables. Auth decided: Laravel Sanctum API tokens. No implementation yet. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
6.6 KiB
6.6 KiB
Protocolo de sincronización móvil offline-first
Estado: plan aprobado (2026-06-17). Auth decidida: Laravel Sanctum (API tokens). Alcance de este documento: lo necesario en la webapp para que una app móvil descargue plantillas/datos, trabaje sin conexión y sincronice al recuperar red. No cubre la implementación de la app móvil (la consume este contrato).
1. Modelo general
Offline-first con cola en el dispositivo (outbox) + sync bidireccional:
- PULL (descarga): la app baja un "paquete" del proyecto (estructura + plantillas + registros) para trabajar sin red.
- Trabajo offline: cada cambio se guarda local con un UUID generado en el móvil y se encola.
- PUSH (subida): al volver la conexión, la app envía la cola; el servidor hace upsert idempotente por UUID y responde resultado por ítem.
- Sincronización delta por
updated_at(solo lo cambiado desde el último sync).
2. Autenticación — Laravel Sanctum (decidido)
- Instalar
laravel/sanctum. Tokens personales por dispositivo (no SPA-cookie; modo API token). - Endpoints:
POST /api/v1/login—{ email, password, device_name }→{ token, user }.POST /api/v1/logout— revoca el token actual.GET /api/v1/me— usuario + permisos efectivos.
- El móvil envía
Authorization: Bearer <token>. - Token con abilities (p. ej.
mobile-sync) y registro de dispositivo (tabladevices) para revocar/caducar. - Caducidad de token configurable + endpoint de refresco o re-login.
3. Cambios de esquema
Añadir a las tablas sincronizables (features, inspections, issues, progress_updates, media):
uuidCHAR(36) único — lo genera el móvil; permite crear offline y upsert idempotente.updated_at(ya existe) — delta + last-write-wins.client_updated_atTIMESTAMP nullable — marca de tiempo del dispositivo (resolución de conflictos).- Soft-deletes (ya existen) — se exponen como tombstones (ids/uuids borrados) en el PULL.
Tablas nuevas:
devices(id, user_id, name, token_id, last_seen_at, …).sync_logs(auditoría: device, operación, entidad, uuid, resultado, timestamp).
4. API (routes/api.php, prefijo /api/v1, stateless + Sanctum)
Descarga / PULL
GET /api/v1/projects→ proyectos accesibles (reusaProject::accessibleBy).GET /api/v1/projects/{id}/bundle?since=<ISO8601>→ paquete offline (delta si vienesince).GET /api/v1/templates?since=<ISO8601>→ plantillas de inspección conversion/hash(descarga incremental).GET /api/v1/media/{id}o URLs firmadas dentro del bundle → adjuntos existentes.
Ejemplo de respuesta bundle:
{
"server_time": "2026-06-17T20:00:00Z",
"project": { "id": 1, "uuid": "…", "name": "…", "updated_at": "…" },
"phases": [ { "id": 4, "name": "…", "updated_at": "…" } ],
"layers": [ { "id": 4, "phase_id": 4, "name": "…", "updated_at": "…" } ],
"features": [ { "id": 5, "uuid": "…", "layer_id": 4, "geometry": {…}, "status": "in_progress", "progress": 40, "updated_at": "…" } ],
"templates":[ { "id": 1, "version": 3, "fields": [ … ] } ],
"inspections": [ … ],
"issues": [ … ],
"deleted": { "features": ["uuid…"], "inspections": ["uuid…"] }
}
Subida / PUSH
POST /api/v1/sync— lote de operaciones (idempotente poruuid):
{ "operations": [
{ "entity": "progress_update", "op": "create", "uuid": "…", "client_updated_at": "…", "data": { "phase_id": 4, "progress": 60, "comment": "…", "location": {…} } },
{ "entity": "inspection", "op": "create", "uuid": "…", "client_updated_at": "…", "data": { "feature_id": 5, "template_id": 1, "data": {…}, "result": "pass" } },
{ "entity": "feature", "op": "update", "uuid": "…", "client_updated_at": "…", "data": { "status": "completed", "progress": 100 } },
{ "entity": "issue", "op": "create", "uuid": "…", "client_updated_at": "…", "data": { "feature_id": 5, "title": "…", "priority": "high" } }
] }
Respuesta por operación:
{ "results": [
{ "uuid": "…", "status": "applied", "server_id": 123 },
{ "uuid": "…", "status": "duplicate", "server_id": 124 },
{ "uuid": "…", "status": "conflict", "server": { "status": "verified", "updated_at": "…" } },
{ "uuid": "…", "status": "error", "error": "validation: …" }
] }
POST /api/v1/media— subida de fotos por multipart (no base64), referenciando al padre poruuid(parent_entity,parent_uuid,file). Soporta reintento; troceado si el archivo es grande.
5. Idempotencia y conflictos
- Idempotencia: el
uuidevita duplicados si se reenvía la cola (re-sync seguro). - Append-only (sin conflicto):
progress_updates,inspections→ siempre insertan. - Editables (con política):
feature.status/progress,issue→ last-write-wins comparandoclient_updated_atvsupdated_atdel servidor. Si el servidor es más nuevo →conflicty se devuelve el valor del servidor para que el móvil decida/avise.
6. Seguridad
- Nunca
Model::create($payloadCliente)crudo. Usar FormRequests/DTO; fijarproject_id/user_iden el servidor desde el contexto autorizado; validar quefeature/phasepertenece a un proyecto del usuario (anti-IDOR). - Autorizar cada operación con permisos Spatie (
update progress,create inspections, …) + pertenencia al proyecto (accessibleBy). - Rate limiting, caducidad de token,
sync_logspara auditoría.
7. Versionado
- Prefijo
/api/v1; cabeceraX-App-Version; el servidor responde versión mínima soportada (forzar update del móvil). - Versión/hash por plantilla (descarga incremental).
8. Qué reutilizar / retirar
OfflineSyncController+PendingSync: el vocabulario de acciones (progress_update, inspection, feature_create, media_upload, task_complete) es buena base para las operaciones de/sync. Pero hay que: pasar a API+token, añadir uuid/validación/autorización, y mover la cola al dispositivo (laPendingSyncdel servidor deja de ser necesaria para el móvil; se puede retirar o reaprovechar comosync_logs).
9. Entregables en la webapp (por fases)
- Fase A — Auth & esqueleto API: Sanctum,
routes/api.php,login/logout/me, tabladevices, abilities. - Fase B — PULL:
projects,bundle+ delta,templatesversionadas, tombstones. - Fase C — PUSH:
/syncidempotente con validación/autorización/conflictos (recoge y endurece la lógica actual). - Fase D — Media: subida multipart + descarga.
- Fase E — Endurecimiento + Docs: rate-limit,
sync_logs, OpenAPI/Swagger como contrato para el equipo móvil.