PWA (Progressive Web App): qué son, cuándo usarlas y cómo construir una que de verdad convierta

Autor: Jeffry Chaves, Ing. en Sistemas – Diccionario Informático

¿Qué es una PWA y qué la hace diferente?

Una PWA (Progressive Web App) es, en esencia, una web que se comporta como app: se instala (sin pasar por una tienda, salvo que quieras empaquetarla), funciona offline hasta cierto punto gracias a un Service Worker, y ofrece UX tipo app (pantalla completa, icono, splash, performance consistente). La gracia es que parte de una base universal —la web— y progresa según las capacidades del dispositivo y el navegador.

Cuándo me compensa optar por PWA

  • Si tu producto es contenido o CRUD (ecommerce ligero, catalogación, dashboards, reservas, educación, medios): la instalación y el offline marcan diferencia.

  • Si tu presupuesto es ajustado y quieres una sola base de código sin duplicar nativo.

  • Si priorizas SEO, compartibilidad por URL y time-to-market.
    En mi experiencia revisando proyectos de la comunidad, las PWA brillan cuando la app no depende de APIs nativas avanzadas o multimedia pesada en segundo plano.

Qué la hace distinta de una web normal

  • Web App Manifest (iconos, nombre, modo de pantalla) → permite instalación.

  • Service Worker → intercepta peticiones; cachea y sirve recursos sin red.

  • Buenas prácticas de UX y rendimiento (app shell, precarga, navegación instantánea).

Ventajas y límites en 2025: lo bueno, lo no tan bueno y lo que nadie te cuenta

Ventajas clave

  • Instalación rápida: menos fricción que una tienda, sobre todo en escritorio.

  • Offline y rendimiento: con un app shell bien cacheado, la app “abre al toque”.

  • Menos costes: una base de código, despliegue contínuo, analítica y SEO web.

  • Entrega continua: mejoras sin pasar por procesos de revisión de stores.

Limitaciones que conviene aceptar desde el inicio

  • iOS ha mejorado (push y A2HS más maduros), pero ciertas capacidades en segundo plano siguen más limitadas que en Android/escritorio.

  • Multimedia y juegos pesados: si dependes de autoplay robusto, procesos largos en background o acceso profundo a hardware, quizá te compense nativo.

  • Descubrimiento en tiendas: no es automático; si te interesa, deberás empaquetar (por ejemplo con Bubblewrap o PWABuilder).

Lecciones de la comunidad (que yo aplico)

  • Ajustar expectativas con notificaciones y almacenamiento en iOS.

  • Educar a la persona usuaria con un micro-tour: “Añade a pantalla de inicio”, “Funciona sin conexión”, “Así llegan las notificaciones”.

Instalación y notificaciones: estado real en iOS y Android

  • Android/Chrome: banner nativo de instalación muy fluido; Web Push sólido.

  • iOS/Safari: instalación vía Añadir a pantalla de inicio; notificaciones requieren A2HS y permiso explícito. Conviene medir el opt-in y mostrar un pre-prompt explicativo.

Rendimiento, almacenamiento y trabajo en segundo plano

  • Objetivo de rendimiento: TTI bajo, navegación instantánea tras la primera carga.

  • Almacenamiento: usa IndexedDB para datos; asume posibles políticas de evicción si el dispositivo va justo de espacio.

  • Background: ideal para sincronizaciones pequeñas o diferidas; evita tareas largas en iOS.

Checklist de instalabilidad y UX tipo app

Instalable en 5 minutos (checklist rápido)

  • Sitio HTTPS.

  • manifest.json con name, short_name, start_url, scope, display (standalone), background_color, theme_color, iconos en 192/512px.

  • Service Worker activo con al menos precache del app shell.

  • Página responsiva, splash coherente, favicon y maskable icon.

Manifest.json bien hecho (plantilla base)

{
"name": "Mi App Increíble",
"short_name": "MiApp",
"start_url": "/?source=pwa",
"scope": "/",
"display": "standalone",
"background_color": "#0B132B",
"theme_color": "#3A506B",
"icons": [
{"src": "/icons/icon-192.png", "sizes": "192x192", "type": "image/png", "purpose": "maskable any"},
{"src": "/icons/icon-512.png", "sizes": "512x512", "type": "image/png", "purpose": "maskable any"}
]
}

App Shell + rutas: base para velocidad y consistencia

  • Mantén un shell mínimo (header, nav, layout) y carga el contenido como vistas.

  • Precarga rutas críticas y defer lo no esencial.

  • Usa skeletons para estados intermedios y sensaciones “nativas”.

Offline de verdad: estrategias de caché con Workbox

Para mí, la forma más robusta y mantenible de gestionar offline es Workbox, que ofrece estrategias listas: NetworkFirst, CacheFirst, StaleWhileRevalidate y más.

Tabla rápida de estrategias

EstrategiaÚsala para…ProsContras
NetworkFirstHTML, APIs donde quieres datos frescosMuestra lo último si hay red; cae a caché sin redPrimer byte puede ser más lento
CacheFirstImágenes, fuentes, librerías pesadasRendimiento top; menos peticionesRiesgo de “quedarte viejo” si no versionas
StaleWhileRevalidateCSS/JS y APIs tolerantesRespuesta inmediata + actualización en backgroundPuede mostrar contenido no tan reciente
CacheOnly / NetworkOnlyCasos extremos/testControl totalPoco práctico en producción

Service Worker con Workbox (ejemplo mínimo)

// sw.js
import {precacheAndRoute} from 'workbox-precaching';
import {registerRoute} from 'workbox-routing';
import {NetworkFirst, StaleWhileRevalidate, CacheFirst} from 'workbox-strategies';
import {ExpirationPlugin} from 'workbox-expiration';
precacheAndRoute(self.__WB_MANIFEST || []);

// HTML y navegación: lo más fresco posible
registerRoute(
({request}) => request.mode === ‘navigate’,
new NetworkFirst({ cacheName: ‘html-pages’, networkTimeoutSeconds: 3 })
);

// Estáticos versionados: rápido y seguro
registerRoute(
({request}) => [‘style’, ‘script’, ‘worker’].includes(request.destination),
new StaleWhileRevalidate({ cacheName: ‘static-resources’ })
);

// Imágenes: rendimiento con caducidad
registerRoute(
({request}) => request.destination === ‘image’,
new CacheFirst({
cacheName: ‘images’,
plugins: [new ExpirationPlugin({ maxEntries: 100, maxAgeSeconds: 60 * 60 * 24 * 30 })]
})
);

Fallbacks offline (HTML, assets críticos, errores de red)

  • Entrega un offline.html con instrucciones claras (“vuelve a intentar”, “descarga X”).

  • Para imágenes críticas, usa un placeholder cacheado.

  • Para APIs, considera respuesta cacheada o modo lectura cuando no haya red.

Actualizaciones sin sorpresas: cómo evitar romper tu PWA

Lo que más veo en proyectos de la comunidad es confusión con las actualizaciones del Service Worker. Mi receta:

  1. Versiona tus cachés (app-v3, static-v8) y limpia en activate.

  2. Evita activar sin avisar: muestra un snackbar “Hay una nueva versión” con botón Actualizar que haga registration.waiting.postMessage({type: 'SKIP_WAITING'}) y window.location.reload().

  3. Usa clientsClaim y skipWaiting con cabeza: geniales para hotfixes, mejor opt-in para releases normales.

Snippet (cliente) para avisar de nueva versión

if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js').then(reg => {
if (reg.waiting) showUpdateUI(reg);
reg.addEventListener('updatefound', () => {
const newSW = reg.installing;
newSW?.addEventListener('statechange', () => {
if (newSW.state === 'installed' && navigator.serviceWorker.controller) {
showUpdateUI(reg);
}
});
});
});
}
function showUpdateUI(reg) {
// Muestra UI propia; al confirmar:
reg.waiting?.postMessage({ type: ‘SKIP_WAITING’ });
// Tras SKIP_WAITING, recarga:
navigator.serviceWorker.addEventListener(‘controllerchange’, () => window.location.reload());
}

SEO y analítica en PWAs: indexación, medición y KPIs que importan

  • Indexación: asegúrate de no bloquear rutas en robots.txt y usa renderizado estático/SSR o hydration para contenido crítico.

  • Medición: implementa Web Vitals (LCP, INP, CLS) y eventos clave: instalaciones, uso offline, push opt-in, tiempo a interacción.

  • Deep links: cada vista importante debe tener URL compartible.

  • Lighthouse PWA: apunta a 100 en PWA e investiga avisos de “no se encontró manifiesto”, “SW no controla la página”, “página no responde offline”.

Casos y aprendizajes de la comunidad: qué funciona y qué evitar

  • Ecommerce ligero: la instalación sube el retorno de usuarios; los carritos offline (con sync posterior) reducen fricción en zonas con mala conectividad.

  • Medios/educación: lectura offline y descargas controladas aumentan la retención.

  • Evitar: cachear HTML con CacheFirst sin invalidación; mezclar assets con nombres sin hash; olvidar fallbacks; no comunicar la actualización.

En mi día a día, lo que más rendimiento da es combinar app shell + SW bien “tuneado” + microinteracciones (skeletons, prefetch inteligente) y medir. Lo que no se mide no se mejora.

Guía rápida para decidir: PWA vs nativa vs híbrida

  • PWA: una base web, buen SEO, entrega continua, costes bajos. Ideal para contenido, formularios, ecommerce ligero, herramientas internas.

  • Nativa: máximo acceso a hardware y background, mejor para multimedia avanzada, juegos, sensores complejos.

  • Híbrida/Multiplataforma (p. ej., React Native, Flutter): experiencia nativa con compartición de código; coste intermedio; ciclo de publicación de tiendas.

Recursos para ir más lejos (recomendados)

  • web.dev PWA (patrones, casos)

  • MDN Web Docs (referencias y guías)

  • Workbox (estrategias y plugins)

  • Herramientas: Lighthouse, PWABuilder, Bubblewrap


Conclusión

Si tuviera que resumir: PWA funciona de maravilla cuando quieres velocidad, instalación sin fricción, offline pragmático y entrega continua. La clave está en no sobreprometer (especialmente en iOS), diseñar para offline, versionar bien y medir todo: instalaciones, opt-ins, tiempos reales y errores de red. Con esa base, tu app web se sentirá como app… y convertirá.


FAQs

¿Qué necesita una PWA para ser instalable?
HTTPS, manifest.json completo, Service Worker activo y UX mínima pulida (iconos, colores, start_url bien definido).

¿Funcionan las PWA en iPhone?
Sí. Instalación vía Añadir a pantalla de inicio y push disponibles con requisitos. Aun así, asume límites en background y ajusta UX.

¿Qué estrategia de caché uso para APIs?
NetworkFirst con fallback a caché si no hay red; añade stale-while-revalidate para recursos tolerantes a “ligeramente desactualizados”.

¿Cómo evito problemas al actualizar el Service Worker?
Versiona cachés, muestra un banner de actualización, usa skipWaiting bajo confirmación y recarga controlada.

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *