Introducción a las Progressive Web Apps (PWA)
¿Te imaginas que tu sitio web pudiera funcionar offline, recibir notificaciones push, instalarse en el dispositivo del usuario como una app nativa, y todo esto sin pasar por ninguna tienda de aplicaciones? Bienvenido al mundo de las Progressive Web Apps (PWA).
Las PWAs representan el futuro del desarrollo web: combinan lo mejor de las aplicaciones web y las apps nativas para ofrecer experiencias rápidas, confiables y atractivas. En este artículo, aprenderás qué son, cómo funcionan y cómo crear tu primera PWA.
¿Qué es una Progressive Web App?
Una PWA es una aplicación web que utiliza capacidades modernas del navegador para ofrecer una experiencia similar a una app nativa. Las características principales incluyen:
- Instalable: Los usuarios pueden añadirla a su pantalla de inicio
- Funciona offline: Gracias a Service Workers
- Responsive: Se adapta a cualquier dispositivo
- Rápida: Carga instantánea incluso con conexiones lentas
- Segura: Solo funciona con HTTPS
- Actualizable: Se actualiza automáticamente en segundo plano
- Notificaciones push: Puede enviar notificaciones aunque no esté abierta
Componentes esenciales de una PWA
Para convertir tu sitio web en una PWA necesitas tres elementos fundamentales:
- Manifest (manifest.json): Define cómo se ve y comporta la app
- Service Worker: Habilita funcionalidad offline y caché
- HTTPS: Conexión segura obligatoria
Vamos a implementar cada uno paso a paso.
1. El Web App Manifest
El manifest es un archivo JSON que describe tu aplicación. Crea un archivo manifest.json en la raíz de tu proyecto:
{
"name": "Mi Progressive Web App",
"short_name": "MiPWA",
"description": "Una increíble PWA de ejemplo",
"start_url": "/",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#2196F3",
"orientation": "portrait-primary",
"icons": [
{
"src": "/icons/icon-72x72.png",
"sizes": "72x72",
"type": "image/png",
"purpose": "any"
},
{
"src": "/icons/icon-96x96.png",
"sizes": "96x96",
"type": "image/png",
"purpose": "any"
},
{
"src": "/icons/icon-128x128.png",
"sizes": "128x128",
"type": "image/png",
"purpose": "any"
},
{
"src": "/icons/icon-144x144.png",
"sizes": "144x144",
"type": "image/png",
"purpose": "any"
},
{
"src": "/icons/icon-152x152.png",
"sizes": "152x152",
"type": "image/png",
"purpose": "any"
},
{
"src": "/icons/icon-192x192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "any maskable"
},
{
"src": "/icons/icon-384x384.png",
"sizes": "384x384",
"type": "image/png",
"purpose": "any"
},
{
"src": "/icons/icon-512x512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "any maskable"
}
]
}
Explicación de las propiedades clave:
- name: Nombre completo de la app (se muestra en la instalación)
- short_name: Nombre corto (se muestra debajo del icono)
- start_url: URL que se abre al iniciar la app
- display: Cómo se muestra la app
standalone: Como app nativa (sin barra del navegador)fullscreen: Pantalla completaminimal-ui: Mínimos controles del navegadorbrowser: Navegador normal
- theme_color: Color de la barra de estado (Android)
- background_color: Color de fondo de la splash screen
- icons: Array de iconos en diferentes tamaños
Vincula el manifest en tu HTML:
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Mi PWA</title>
<!-- Manifest -->
<link rel="manifest" href="/manifest.json">
<!-- Theme color para navegadores -->
<meta name="theme-color" content="#2196F3">
<!-- iOS específico -->
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="apple-mobile-web-app-title" content="MiPWA">
<link rel="apple-touch-icon" href="/icons/icon-192x192.png">
</head>
<body>
<h1>¡Mi Primera PWA!</h1>
<p>Esta app funciona offline y es instalable.</p>
</body>
</html>
2. Service Worker: El corazón de la PWA
El Service Worker es un script que el navegador ejecuta en segundo plano, separado de la página web. Permite interceptar peticiones de red, cachear recursos y funcionar offline.
Registrar el Service Worker
Crea un archivo sw.js en la raíz de tu proyecto y regístralo en tu HTML:
<script>
// Verificar si el navegador soporta Service Workers
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/sw.js')
.then(registration => {
console.log('Service Worker registrado:', registration);
})
.catch(error => {
console.error('Error al registrar Service Worker:', error);
});
});
}
</script>
Implementar el Service Worker básico
Aquí está el código completo de sw.js:
const CACHE_NAME = 'mi-pwa-v1';
const urlsToCache = [
'/',
'/index.html',
'/styles.css',
'/script.js',
'/icons/icon-192x192.png',
'/icons/icon-512x512.png'
];
// Evento de instalación - cachear recursos
self.addEventListener('install', event => {
console.log('Service Worker: Instalando...');
event.waitUntil(
caches.open(CACHE_NAME)
.then(cache => {
console.log('Service Worker: Cacheando archivos');
return cache.addAll(urlsToCache);
})
.then(() => self.skipWaiting()) // Activar inmediatamente
);
});
// Evento de activación - limpiar cachés antiguas
self.addEventListener('activate', event => {
console.log('Service Worker: Activando...');
event.waitUntil(
caches.keys().then(cacheNames => {
return Promise.all(
cacheNames.map(cacheName => {
if (cacheName !== CACHE_NAME) {
console.log('Service Worker: Eliminando caché antigua:', cacheName);
return caches.delete(cacheName);
}
})
);
}).then(() => self.clients.claim()) // Tomar control inmediato
);
});
// Interceptar peticiones de red
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request)
.then(response => {
// Si está en caché, devolverlo
if (response) {
return response;
}
// Si no, hacer petición de red
return fetch(event.request)
.then(response => {
// Verificar si es una respuesta válida
if (!response || response.status !== 200 || response.type !== 'basic') {
return response;
}
// Clonar la respuesta
const responseToCache = response.clone();
// Añadir al caché
caches.open(CACHE_NAME)
.then(cache => {
cache.put(event.request, responseToCache);
});
return response;
});
})
.catch(() => {
// Si falla, mostrar página offline (opcional)
return caches.match('/offline.html');
})
);
});
3. Estrategias de caché
Existen diferentes estrategias para manejar el caché. Aquí las más comunes:
Cache First (Caché primero)
Ideal para recursos estáticos que no cambian:
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request)
.then(response => response || fetch(event.request))
);
});
Network First (Red primero)
Ideal para contenido dinámico, con fallback a caché:
self.addEventListener('fetch', event => {
event.respondWith(
fetch(event.request)
.catch(() => caches.match(event.request))
);
});
Stale While Revalidate
.jpg?alt=media&token=b8a82bd7-5a41-4a3d-8e85-a73a249ba2d4)
Devuelve el caché mientras actualiza en segundo plano:
self.addEventListener('fetch', event => {
event.respondWith(
caches.open(CACHE_NAME).then(cache => {
return cache.match(event.request).then(response => {
const fetchPromise = fetch(event.request).then(networkResponse => {
cache.put(event.request, networkResponse.clone());
return networkResponse;
});
return response || fetchPromise;
});
})
);
});
4. Añadir botón de instalación
Mejora la experiencia permitiendo que el usuario instale la PWA con un botón:
<button id="installBtn" style="display: none;">
Instalar App
</button>
<script>
let deferredPrompt;
const installBtn = document.getElementById('installBtn');
// Capturar el evento de instalación
window.addEventListener('beforeinstallprompt', (e) => {
// Prevenir el prompt automático
e.preventDefault();
deferredPrompt = e;
// Mostrar el botón
installBtn.style.display = 'block';
});
// Manejar el click del botón
installBtn.addEventListener('click', async () => {
if (!deferredPrompt) return;
// Mostrar el prompt
deferredPrompt.prompt();
// Esperar la respuesta del usuario
const { outcome } = await deferredPrompt.userChoice;
console.log(`Usuario ${outcome === 'accepted' ? 'aceptó' : 'rechazó'} la instalación`);
// Limpiar
deferredPrompt = null;
installBtn.style.display = 'none';
});
// Detectar si ya está instalada
window.addEventListener('appinstalled', () => {
console.log('¡PWA instalada exitosamente!');
deferredPrompt = null;
});
</script>
5. Notificaciones Push (opcional)
Para implementar notificaciones push necesitas:
// Solicitar permiso
async function requestNotificationPermission() {
const permission = await Notification.requestPermission();
if (permission === 'granted') {
console.log('Permiso de notificaciones concedido');
// Crear una notificación de prueba
new Notification('¡Hola!', {
body: 'Esta es una notificación de prueba',
icon: '/icons/icon-192x192.png',
badge: '/icons/icon-72x72.png'
});
}
}
// En el Service Worker
self.addEventListener('push', event => {
const data = event.data.json();
const options = {
body: data.body,
icon: '/icons/icon-192x192.png',
badge: '/icons/icon-72x72.png',
vibrate: [200, 100, 200]
};
event.waitUntil(
self.registration.showNotification(data.title, options)
);
});
6. Testing y debugging
Chrome DevTools
- Abre DevTools (F12)
- Ve a la pestaña "Application"
- Verás secciones para:
- Manifest: Inspeccionar el manifest.json
- Service Workers: Ver estado y forzar actualización
- Cache Storage: Ver el contenido del caché
- Clear storage: Limpiar todo
Lighthouse
Ejecuta una auditoría PWA:
- Abre DevTools
- Pestaña "Lighthouse"
- Selecciona "Progressive Web App"
- Click en "Analyze page load"
7. Checklist de PWA
✅ Manifest configurado con todos los iconos ✅ Service Worker registrado y funcionando ✅ HTTPS activo (obligatorio en producción) ✅ Responsive design en todos los dispositivos ✅ Funciona offline o con conexión lenta ✅ Carga rápida (< 3 segundos) ✅ Instalable con prompt de instalación ✅ Lighthouse score > 90 en PWA
Estructura de archivos recomendada
mi-pwa/
├── index.html
├── manifest.json
├── sw.js
├── styles.css
├── script.js
├── offline.html
└── icons/
├── icon-72x72.png
├── icon-96x96.png
├── icon-128x128.png
├── icon-144x144.png
├── icon-152x152.png
├── icon-192x192.png
├── icon-384x384.png
└── icon-512x512.png
Herramientas útiles
- PWA Builder: Genera manifest y service worker
- Workbox: Librería de Google para Service Workers
- Maskable.app: Editor para iconos maskable
- Favicon Generator: Genera todos los iconos necesarios
Frameworks y PWA
Muchos frameworks modernos tienen soporte nativo para PWA:
Next.js
npm install next-pwa
Create React App
npx create-react-app my-pwa --template cra-template-pwa
Vue CLI
vue add pwa
Casos de uso reales
Las PWAs son perfectas para:
- E-commerce: Mejora conversión con instalación y notificaciones
- Noticias y blogs: Lectura offline de artículos
- Redes sociales: Experiencia similar a app nativa
- Productividad: Funcionamiento sin conexión constante
- Juegos casuales: Instalación sin tiendas de apps
Ventajas de las PWAs
✅ Sin tiendas de aplicaciones: Distribución directa ✅ Actualizaciones instantáneas: Sin aprobación de tiendas ✅ Un solo código: Web, iOS, Android ✅ Menor tamaño: Más ligeras que apps nativas ✅ SEO-friendly: Indexables por buscadores ✅ Menor costo: Un equipo de desarrollo
Conclusión
Las Progressive Web Apps representan una evolución natural del desarrollo web. Con relativamente poco esfuerzo, puedes transformar tu sitio web en una aplicación instalable, rápida y que funciona offline.
Los pasos básicos son:
- Crear un
manifest.jsoncompleto - Implementar un Service Worker con estrategia de caché
- Servir tu app sobre HTTPS
- Optimizar para dispositivos móviles
- Pasar la auditoría Lighthouse
Siguiente paso: Toma tu proyecto web actual y conviértelo en PWA. Empieza con el manifest, añade un Service Worker básico, y verás cómo la experiencia de usuario mejora dramáticamente.
¿Listo para construir tu primera PWA?
¿Necesitas ayuda con este tema?
No dudes en contactarme para una asesoría personalizada. ¡Estoy aquí para ayudarte a llevar tu proyecto al siguiente nivel! 🚀