Mucha gente dice "mi app es PWA". Pruébala en avión. Si no carga, no es PWA offline-real, es una web responsive. Lo digo sin acritud porque se confunde mucho. Una PWA offline-real cumple 4 cosas:
- 1Carga la UI sin red (cached por Service Worker).
- 2Lee y escribe datos locales sin red (IndexedDB).
- 3Encola operaciones para sincronizar después.
- 4Cuando vuelve la red, sincroniza sin pisar lo nuevo.
El stack que uso en 2026
| Pieza | Tecnología | Por qué |
|---|---|---|
| Framework | Next.js 15 App Router | SSR para el primer load + RSC + cache |
| SW | next-pwa o serwist | Wrappers maduros sobre Workbox |
| DB local | IndexedDB con Dexie.js | API decente, transacciones, queries |
| Sync queue | Custom o background-sync API | Si el navegador soporta, mejor; fallback custom |
| Conflictos | Last-write-wins o CRDT (Yjs) | CRDT solo si edita varia gente la misma entidad |
| Backend | Supabase (PostgreSQL) | API REST + Realtime gratis |
Lo que se rompe en producción (y nadie cuenta)
1. iOS Safari mata la base de datos a los 7 días sin uso
Sí, así de literal. Apple aplica "Intelligent Tracking Prevention" que borra IndexedDB de PWAs no instaladas si pasan 7 días sin abrirla. Mitigación: educar al usuario para "Añadir a pantalla de inicio" (la app instalada queda exenta) o sincronizar agresivamente al primer load tras red.
2. Service Worker queda en versión zombie
Si actualizas la app pero el SW antiguo está cacheado, los usuarios ven la versión vieja durante días. Mitigación: estrategia "skipWaiting" + notificación al usuario "Hay una versión nueva, recarga".
3. Foto se pierde al cerrar pestaña
Las URLs blob: son temporales. Si guardas la foto como URL.createObjectURL en IndexedDB, al cerrar la pestaña la URL muere. Mitigación: convertir a base64 o guardar el File/Blob raw en IndexedDB.
4. GPS denegado y no vuelves a pedirlo
Si el usuario deniega GPS la primera vez, el navegador no vuelve a preguntar. Mitigación: detectar permission state, mostrar mensaje claro de cómo reactivarlo en ajustes, y siempre tener un fallback (entrada manual de coordenadas).
5. Sincronización pisa cambios remotos
Last-write-wins es simple pero peligroso si el usuario edita offline algo que otro editó online. Si el caso de uso es 1 usuario por entrada (como muestras de campo), LWW va bien. Si es colaborativo (varios usuarios editando lo mismo), CRDT con Yjs.
Cómo lo testeo antes de entregar
- 1Modo avión durante 30 minutos seguidos generando datos.
- 2Cerrar y abrir la pestaña en modo avión: ¿la app carga?
- 3Volver al wifi: ¿sincroniza sin perder nada?
- 4Modo avión, generar datos, modo avión otra dispositivo, sincronizar ambos: ¿no se pisan?
- 5iOS real (no simulator) durante 24h: ¿funciona al día siguiente sin abrir?
- 6Móvil viejo Android 8: ¿el SW se registra? ¿IndexedDB funciona?
Cuánto tarda construir una PWA offline-real
Una PWA offline simple (1 formulario + lista + sincronización) son 1.5–2 semanas. Una con foto + GPS + map + export CSV son 2.5–3 semanas. Una colaborativa con CRDT son 5–6 semanas. El mayor consumidor de tiempo es el testing en condiciones reales (campo de verdad, no devtools).
FAQ
¿Es lo mismo PWA que app móvil?
No. App móvil nativa (Android/iOS) la pasas por App Store. PWA es web instalable: abres URL, "Añadir a pantalla de inicio", queda como app. La PWA NO necesita store pero le falta acceso a algunas APIs del móvil (notificaciones push en iOS son limitadas, por ejemplo).
¿La PWA funciona en iPhone?
Sí, con limitaciones. iOS Safari soporta PWA desde 2018 pero con peor soporte de notificaciones push y storage. Para una PWA de captura de datos en campo, va bien. Para una PWA con notificaciones críticas, mejor app nativa.
¿Cuánto cuesta una PWA offline?
Para un solo usuario (doctorando, técnico): 600–1.500 €. Para un equipo pequeño con sincronización: 1.500–3.000 €. Colaborativa con CRDT: 3.000–6.000 €.
¿Necesito Apple Developer Account o Google Play?
No. La PWA se distribuye por URL. Si quieres llegar a las stores, hay wrappers (PWABuilder, Capacitor) que empaquetan la PWA en una app nativa para subir.