Progressive Web Apps (PWA) — Referencia Detallada
Referencia Detallada para construir Progressive Web Apps: service workers, soporte offline, Web App Manifest, notificaciones push e instalabilidad.
Visión General
Las Progressive Web Apps (PWA) usan capacidades web modernas para ofrecer una experiencia similar a una app: acceso offline, notificaciones push, instalación en la pantalla de inicio y sincronización en segundo plano. A diferencia de las apps nativas, funcionan en el navegador y no requieren aprobación de una tienda de apps. A continuación: las tecnologías principales y lo que funciona para construir PWAs de producción.
Cuándo Usar
-
For alternatives, see JavaScript Service Worker Offline Caching for PWA.
-
Necesitas funcionalidad offline para una aplicación web
-
Quieres reducir la fricción de las tiendas de apps mientras proporcionas UX similar a nativa
-
Tus usuarios están en móviles con conectividad intermitente
-
Necesitas notificaciones push sin construir apps nativas separadas
-
Quieres mejorar el engagement con prompts de agregar a pantalla de inicio
Tecnologías Principales
| Tecnología | Propósito | Capacidad Clave |
|---|---|---|
| Service Worker | Proxy en segundo plano para solicitudes de red | Caché offline, sincronización en segundo plano |
| Web App Manifest | Describe metadatos de la app para instalación | Iconos, modo de visualización, color del tema |
| HTTPS | Requisito de origen seguro para capacidades PWA | Requerido para service workers |
| Push API | Mensajes iniciados por el servidor | Re-engagement de usuarios incluso cuando la app está cerrada |
| Background Sync | Diferir acciones hasta que la conectividad regrese | Encolar envíos de formularios offline |
Service Workers
Registro
Registra el service worker al iniciar la app.
// main.js
if ('serviceWorker' in navigator) {
window.addEventListener('load', async () => {
try {
const registration = await navigator.serviceWorker.register('/sw.js');
console.log('SW registrado:', registration.scope);
} catch (error) {
console.error('Fallo en registro de SW:', error);
}
});
}
Estrategias de Caché
Elige la estrategia correcta para cada tipo de recurso.
// sw.js — Caché-Primero para assets estáticos
self.addEventListener('fetch', (event) => {
if (event.request.destination === 'image') {
event.respondWith(cacheFirst(event.request));
} else if (event.request.destination === 'document') {
event.respondWith(networkFirst(event.request));
}
});
async function cacheFirst(request) {
const cached = await caches.match(request);
return cached || fetch(request).then(response => {
caches.open('v1').then(cache => cache.put(request, response.clone()));
return response;
});
}
async function networkFirst(request) {
try {
const networkResponse = await fetch(request);
const cache = await caches.open('v1');
cache.put(request, networkResponse.clone());
return networkResponse;
} catch {
return caches.match(request);
}
}
Resumen de Estrategias de Caché
| Estrategia | Mejor Para | Comportamiento |
|---|---|---|
| Caché Primero | Assets estáticos (CSS, JS, imágenes) | Servir desde caché; fallback a red |
| Red Primero | Documentos HTML, llamadas API | Intentar red primero; fallback a caché |
| Stale-While-Revalidate | Contenido frecuentemente actualizado | Servir versión en caché; refrescar en segundo plano |
| Solo Red | Datos en tiempo real (chat, precios) | Siempre obtener de la red |
| Solo Caché | App shell pre-cacheada | Nunca consultar la red |
Web App Manifest
El manifest habilita agregar a pantalla de inicio y define la experiencia de la app.
{
"name": "Task Manager Pro",
"short_name": "Tasks",
"start_url": "/",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#3b82f6",
"icons": [
{ "src": "/icon-192.png", "sizes": "192x192", "type": "image/png" },
{ "src": "/icon-512.png", "sizes": "512x512", "type": "image/png" }
],
"screenshots": [
{ "src": "/screenshot-1.png", "sizes": "1280x720", "type": "image/png", "form_factor": "wide" },
{ "src": "/screenshot-2.png", "sizes": "750x1334", "type": "image/png", "form_factor": "narrow" }
]
}
Experiencia Offline
Patrón de Página Offline
Muestra una página offline personalizada en lugar de la predeterminada del navegador.
// sw.js
const OFFLINE_PAGE = '/offline.html';
self.addEventListener('fetch', (event) => {
if (event.request.mode === 'navigate') {
event.respondWith(
fetch(event.request).catch(() => caches.match(OFFLINE_PAGE))
);
}
});
Sincronización en Segundo Plano
Encola acciones realizadas offline y reintenta cuando la conectividad regresa.
// Encolar una sincronización en segundo plano
async function submitForm(data) {
try {
await fetch('/api/submit', { method: 'POST', body: JSON.stringify(data) });
} catch {
// Guardar en IndexedDB y registrar para sincronización
await db.syncQueue.add(data);
const registration = await navigator.serviceWorker.ready;
await registration.sync.register('submit-form');
}
}
// Service Worker maneja el evento de sincronización
self.addEventListener('sync', (event) => {
if (event.tag === 'submit-form') {
event.waitUntil(processSyncQueue());
}
});
Notificaciones Push
Suscribirse a Push
async function subscribeToPush() {
const registration = await navigator.serviceWorker.ready;
const subscription = await registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array(VAPID_PUBLIC_KEY)
});
// Enviar suscripción al servidor
await fetch('/api/push-subscribe', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(subscription)
});
}
Mostrar Notificaciones
// sw.js
self.addEventListener('push', (event) => {
const data = event.data.json();
event.waitUntil(
self.registration.showNotification(data.title, {
body: data.body,
icon: '/icon-192.png',
badge: '/badge-72.png',
data: { url: data.url },
actions: [
{ action: 'open', title: 'Abrir App' },
{ action: 'dismiss', title: 'Descartar' }
]
})
);
});
self.addEventListener('notificationclick', (event) => {
event.notification.close();
if (event.action === 'open' || !event.action) {
event.waitUntil(clients.openWindow(event.notification.data.url));
}
});
Prompt de Instalación
Prompt a los usuarios para instalar la PWA en navegadores compatibles.
let deferredPrompt;
window.addEventListener('beforeinstallprompt', (e) => {
e.preventDefault();
deferredPrompt = e;
showInstallButton();
});
async function installPWA() {
if (!deferredPrompt) return;
deferredPrompt.prompt();
const { outcome } = await deferredPrompt.userChoice;
if (outcome === 'accepted') {
console.log('Usuario instaló la PWA');
}
deferredPrompt = null;
}
Pruebas y Depuración
| Herramienta | Propósito |
|---|---|
| Chrome DevTools > Application | Inspeccionar service workers, manifests, cachés |
| Lighthouse | Auditar cumplimiento PWA |
| WebPageTest | Probar comportamiento offline en dispositivos reales |
| ngrok | Probar funciones que requieren HTTPS localmente |
| Workbox | Librería para simplificar patrones de service worker |
Errores Comunes
- No manejar actualizaciones de caché — los usuarios pueden ver contenido obsoleto indefinidamente
- Cachear respuestas de API sin versionado — datos obsoletos después de despliegues
- Ignorar cuotas de almacenamiento — los navegadores pueden eliminar tu caché
- Sobre-cachear contenido en vivo — usa Red Primero para datos específicos del usuario
- Faltar HTTPS — service workers y push requieren un origen seguro
Troubleshooting
- Component does not re-render: verify state reference, props, and memoization. A mutated object can bypass change detection.
- Style does not apply in production: check that CSS is loaded, class names are not mangled, and specificity wins. Purge unused styles carefully.
- Build fails after dependency update: read the changelog, pin versions, and clean the lock file.
- Accessibility audit fails: add labels, landmarks, focus management, and color contrast.
- Hydration mismatch: ensure server and client render the same initial HTML. random, or window during SSR.
Referencia Rápida
- Comando principal: ejecuta la solución base del artículo y verifica el resultado esperado.
- Validación: confirma que los tests pasan y que las métricas clave no se degradaron.
- Rollback: si algo falla, revierte el cambio y consulta la sección de Troubleshooting.
Lectura Adicional
- Documentación oficial: consulta la referencia actualizada del framework o herramienta utilizada.
- Guías relacionadas: explora las guías de pwa y service-worker para profundizar.
- Patrones complementarios: revisa los patrones de diseño aplicables a tu stack tecnológico.
- Postmortems públicos: estudia incidentes reales de equipos que enfrentaron problemas similares en producción.
Notas de Producción
- Despliega gradualmente usando canary o blue-green para detectar regresiones temprano.
- Configura alertas para errores, latencia p99 y tasa de fallos antes de habilitar en producción.
- Documenta el rollback en el runbook; prueba el procedimiento en staging al menos una vez por trimestre.
- Revisa logs estructurados con correlation IDs para trazar requests end-to-end en incidentes.
Puntos Clave
- Aplica progressive web apps (pwa) — referencia detallada cuando necesites una solución práctica para tu caso de uso.
- Monitorea el rendimiento después de implementar; mide latencia, errores y uso de recursos antes y después.
- Revisa la sección de Troubleshooting ante errores comunes; la mayoría tienen causa raíz documentada con solución.
- Mantén dependencias actualizadas y ejecuta tests en CI para prevenir regresiones en producción.
Errores Comunes en Producción
- Tratar la guía como un checklist para completar una vez en lugar de una práctica por evolucionar.
- Adoptar cada recomendación de golpe en lugar de comenzar con un cambio medido.
- Saltar la evaluación de madurez e imponer prácticas avanzadas a un equipo no preparado.
- No actualizar runbooks y expectativas de guardia al introducir nuevas prácticas.
- Ignorar datos reales de incidentes al priorizar qué partes de la guía aplicar primero.
- No asignar un responsable que revise decisiones trimestralmente.
- Copiar ejemplos sin adaptarlos a las herramientas y restricciones reales del equipo.
- Olvidar medir resultados antes de agregar la siguiente mejora.
Preguntas frecuentes
¿Cómo empiezo con esto en un proyecto existente?
Empieza con una parte pequeña y aislada de tu codebase. Aplica los conceptos de esta guía a un módulo o servicio. Mide el impacto, luego expande a otras áreas.
¿Qué herramientas necesito?
Las herramientas mencionadas throughout esta guía se listan en cada sección. La mayoría son open-source y ampliamente adoptadas. Consulta los recursos relacionados para instrucciones de setup.
¿Cómo mido el éxito después de implementar esto?
Define métricas claras antes de empezar: benchmarks de rendimiento, tasas de error o indicadores de mantenibilidad. Compara antes y después. Itera basándote en datos, no en suposiciones.
Recursos Relacionados
WCAG 2.2 Accesibilidad — Guía para Desarrolladores
Guía práctica de cumplimiento WCAG 2.2: perceptible, operable, comprensible y confiable, con ejemplos de código para desarrollo web accesible.
GuideWeb Components — Custom Elements, Shadow DOM y Templates
Guía práctica de Web Components: crear elementos personalizables reutilizables, encapsular estilos con Shadow DOM y componer con templates HTML.
PatternPatrón Circuit Breaker
Previene fallos en cascada deteniendo solicitudes a servicios que están fallando. Un patrón arquitectural para sistemas distribuidos resilientes.