Modo Offline (PWA)
El sistema debe permitir que los dueños de canchas sigan registrando reservas aunque no tengan conexión a internet. Cuando la conexión se restablece, los datos se sincronizan automáticamente con el servidor.
Componentes
Section titled “Componentes”Service Worker
Section titled “Service Worker”Cachea los assets estáticos (HTML, CSS, JS, fuentes) para que la app cargue sin conexión. Estrategia: Cache First para assets, Network First para API calls.
// sw.ts — Service Workerself.addEventListener("fetch", (event) => { const { request } = event;
// API calls → siempre intentar red if (request.url.includes("/api/")) { event.respondWith(networkFirst(request)); return; }
// Assets → cache primero, red como fallback event.respondWith(cacheFirst(request));});IndexedDB local (Dexie.js)
Section titled “IndexedDB local (Dexie.js)”Base de datos en el navegador que espeja las tablas críticas del dueño:
| Tabla local | Equivalente servidor | Sincronización |
|---|---|---|
localReservas |
reservas |
Bidireccional |
localCanchas |
canchas |
Solo lectura (cache) |
localClientes |
users (rol PLAYER) |
Solo lectura (cache) |
syncQueue |
— | Cola de operaciones pendientes |
Sync Engine
Section titled “Sync Engine”Cuando el dueño crea/reserva offline:
- Se guarda en
localReservasconsincronizada = false - Se encola en
syncQueuecomo operación pendiente - Al detectar conexión, el Sync Engine procesa la cola en orden FIFO
- El servidor responde con el
idreal ycreated_atdel servidor - Se actualiza el registro local con los datos del servidor
Conflict Resolution
Section titled “Conflict Resolution”Regla: el timestamp del servidor gana.
Dueño offline crea reserva slot 18:00 → timestamp local 14:05Jugador online reserva slot 18:00 → timestamp servidor 14:06
Resultado: GANA el jugador (14:06 > 14:05)El dueño recibe notificación de conflicto al sincronizar.Detección de conectividad
Section titled “Detección de conectividad”// Hook: useOnlineStatusconst isOnline = useOnlineStatus();
if (!isOnline) { // Dueño: puede seguir operando // Jugador: "El sistema está temporalmente sin conexión. // Contactá al dueño por WhatsApp para reservar."}Estados de UI
Section titled “Estados de UI”Vista del dueño (offline)
Section titled “Vista del dueño (offline)”- ✅ Puede ver sus canchas (datos cacheados)
- ✅ Puede crear reservas (cola local)
- ✅ Puede ver clientes frecuentes (datos cacheados)
- ⚠️ Banner amarillo: “Sin conexión — los cambios se sincronizarán al reconectar”
- ❌ No puede ver disponibilidad actualizada de otros jugadores
Vista del jugador (offline)
Section titled “Vista del jugador (offline)”- ❌ Bloqueado: vista de “Sin conexión”
- 📱 Mensaje: “El sistema no está disponible en este momento. Contactá al dueño por WhatsApp para reservar.”
- 🔗 Link directo a WhatsApp del dueño
Stack PWA
Section titled “Stack PWA”| Capa | Tecnología |
|---|---|
| Service Worker | next-pwa o manual con Workbox |
| IndexedDB | Dexie.js (API reactiva sobre IndexedDB) |
| Sincronización | Custom Sync Engine |
| Detección | navigator.onLine + online/offline events |
| Manifest | manifest.json + metadatos PWA |
Secuencia de sincronización
Section titled “Secuencia de sincronización”sequenceDiagram participant Owner as Dueño (offline) participant SW as Service Worker participant IDB as IndexedDB participant Sync as Sync Engine participant API as Servidor
Owner->>IDB: Crear reserva local IDB->>Sync: Encolar operación
Note over Owner,API: ... vuelve la conexión ...
Sync->>API: POST /api/reservas (con operaciones pendientes) API->>API: Validar disponibilidad alt Slot disponible API-->>Sync: 201 Created + server_id Sync->>IDB: Actualizar registro local Sync->>Owner: Notificación: "Reserva sincronizada ✅" else Conflicto API-->>Sync: 409 Conflict Sync->>IDB: Marcar como conflicto Sync->>Owner: Notificación: "Conflicto detectado ⚠️" endMVP vs Completo
Section titled “MVP vs Completo”| Feature | MVP (Fase 1) | Completo (Fase 1.1) |
|---|---|---|
| Service Worker + cache | ✅ | ✅ |
| Detección online/offline | ✅ | ✅ |
| Registro offline de reservas | ✅ | ✅ |
| Sincronización automática | ✅ (básica) | ✅ (con reintentos y backoff) |
| Conflict resolution manual | ✅ | — |
| Conflict resolution automática | — | ✅ |
| Soporte offline para edición | — | ✅ |