Artículo

Aplicaciones web progresivas (PWA)

Aplicaciones web progresivas (PWA)

Una aplicación web progresiva (PWA) es una aplicación web que utiliza capacidades modernas del navegador para ofrecer una experiencia similar a la de una aplicación. Las PWA son confiables, rápidas y atractivas: combinan el alcance de la web con las capacidades de las aplicaciones móviles nativas.

5 min de lecturaIdioma: ES EspañolGratis0 aplausos0 comentarios
TecnologíaArtículos de ingenieríaAppsProgressiveWeb DevelopmentTechnologyEngineering ArticlesPWAs
Opciones de lectura

Introducción

Una aplicación web progresiva (PWA) es una aplicación web que utiliza capacidades modernas del navegador para ofrecer una experiencia similar a la de una aplicación. Las PWA son confiables, rápidas y atractivas: combinan el alcance de la web con las capacidades de las aplicaciones móviles nativas.

Las PWA resuelven una tensión fundamental: los usuarios quieren la disponibilidad instantánea de la web (sin instalación, vinculable, siempre actualizada) pero esperan el rendimiento y las características de las aplicaciones nativas (soporte sin conexión, notificaciones automáticas, presencia en la pantalla de inicio). Las PWA ofrecen ambas cosas.


Características principales

El término "aplicación web progresiva" fue acuñado por Alex Russell y Frances Berriman de Google en 2015. Una PWA debe cumplir estas 10 características:

# Principio Por qué es importante
1 Progresivo: funciona para todos Degradación elegante en navegadores más antiguos
2 Responsivo: se adapta a cualquier pantalla Escritorio, tableta, móvil
3 Independiente de la conectividad: funciona sin conexión Almacenamiento en caché del trabajador del servicio
4 Parecido a una aplicación: sensación de aplicación nativa Arquitectura de Shell, navegación fluida
5 Fresco: siempre actualizado Ciclo de vida de actualización del trabajador del servicio
6 Seguro: servido a través de HTTPS Previene la manipulación, necesaria para los trabajadores de servicios.
7 Descubrible: compatible con SEO Los motores de búsqueda indexan las PWA
8 Reactivable: notificaciones automáticas Vuelva a atraer a los usuarios como si fueran aplicaciones nativas
9 Instalable — Agregar a la pantalla de inicio Manifiesto de la aplicación web
10 Enlazable: compartir mediante URL No se requiere tienda de aplicaciones

Los tres pilares de la PWA

1. Trabajador de servicio: el motor

Un trabajador de servicio es un archivo JavaScript que se ejecuta en segundo plano, separado de la página web. Actúa como un proxy de red programable, interceptando solicitudes y habilitando funcionalidad fuera de línea, notificaciones automáticas y sincronización en segundo plano.

Ciclo de vida del trabajador de servicio

Installing → Installed → Activating → Activated → Idle → Terminated
                 │                                        │
                 └─── (new version detected) ──────────────┘
                 └─── Waiting (until all tabs close) ─────┘
// service-worker.js
const CACHE_NAME = 'my-pwa-v2';
const ASSETS_TO_CACHE = [
  '/',
  '/index.html',
  '/styles.css',
  '/app.js',
  '/offline.html',
  '/icons/icon-192.png'
];

// Install event — cache static assets
self.addEventListener('install', event => {
  event.waitUntil(
    caches.open(CACHE_NAME).then(cache => {
      console.log('Caching app shell');
      return cache.addAll(ASSETS_TO_CACHE);
    })
  );
  // Force new service worker to activate immediately
  self.skipWaiting();
});

// Activate event — clean old caches
self.addEventListener('activate', event => {
  event.waitUntil(
    caches.keys().then(cacheNames => {
      return Promise.all(
        cacheNames
          .filter(name => name !== CACHE_NAME)
          .map(name => caches.delete(name))
      );
    })
  );
  // Take control of all open pages immediately
  clients.claim();
});

// Fetch event — intercept network requests
self.addEventListener('fetch', event => {
  event.respondWith(
    caches.match(event.request).then(cachedResponse => {
      // Return cached response if available
      if (cachedResponse) {
        return cachedResponse;
      }

      // Otherwise fetch from network
      return fetch(event.request).then(networkResponse => {
        // Don't cache non-GET or error responses
        if (!event.request.url.includes('/api/')) {
          return networkResponse;
        }

        // Cache API responses for offline use
        const responseClone = networkResponse.clone();
        caches.open(CACHE_NAME).then(cache => {
          cache.put(event.request, responseClone);
        });

        return networkResponse;
      }).catch(() => {
        // Network failed — return offline fallback
        if (event.request.mode === 'navigate') {
          return caches.match('/offline.html');
        }
        return new Response('Offline', { status: 503 });
      });
    })
  );
});

Estrategias de almacenamiento en caché

Estrategia Descripción Caso de uso
Caché primero Verificar caché, recurrir a la red Activos estáticos (CSS, JS, imágenes)
Red primero Pruebe la red, recurra al caché Llamadas API, contenido dinámico.
Obsoleto mientras se revalida Devolver caché, actualizar en segundo plano Feeds de noticias, publicaciones de blogs
Solo red Obtener siempre de la red Datos sensibles (bancarios)
Solo caché Nunca recuperar de la red shell de la aplicación
// Stale-while-revalidate strategy
self.addEventListener('fetch', event => {
  event.respondWith(
    caches.match(event.request).then(cachedResponse => {
      const fetchPromise = fetch(event.request).then(networkResponse => {
        caches.open(CACHE_NAME).then(cache => {
          cache.put(event.request, networkResponse.clone());
        });
        return networkResponse;
      }).catch(() => cachedResponse);

      return cachedResponse || fetchPromise;
    })
  );
});

2. Manifiesto de la aplicación web: la identidad

El manifiesto es un archivo JSON que controla cómo aparece la PWA cuando se instala:

{
  "name": "My Progressive Web App",
  "short_name": "MyPWA",
  "description": "An amazing PWA that works offline",
  "start_url": "/?source=pwa",
  "display": "standalone",
  "background_color": "#ffffff",
  "theme_color": "#3367D6",
  "orientation": "portrait-primary",
  "scope": "/",
  "icons": [
    {
      "src": "/icons/icon-192.png",
      "sizes": "192x192",
      "type": "image/png",
      "purpose": "any maskable"
    },
    {
      "src": "/icons/icon-512.png",
      "sizes": "512x512",
      "type": "image/png",
      "purpose": "any maskable"
    }
  ],
  "categories": ["productivity", "utilities"],
  "lang": "en-US",
  "dir": "ltr",
  "prefer_related_applications": false
}

Propiedades manifiestas explicadas:

  • display: standalone: se abre sin la interfaz de usuario del navegador (barra de direcciones, pestañas).
  • display: fullscreen: pantalla completa, sin navegador Chrome.
  • display: minimal-ui: controles mínimos del navegador.
  • scope: "/": qué URL forman parte de la PWA.
  • start_url: la página que se abre cuando se inicia la PWA.
  • Iconos enmascarables: íconos adaptables que funcionan con enmascaramiento a nivel del sistema operativo (redondeado, ondulado, etc.).

3. HTTPS: la base

Los trabajadores de servicios necesitan un contexto seguro. HTTPS es obligatorio para:

  • Registro de trabajadores de servicios.
  • Notificaciones push.
  • El mensaje de instalación (antes del evento de descarga).
# Redirect HTTP to HTTPS
# Nginx
server {
    listen 80;
    server_name example.com;
    return 301 https://$server_name$request_uri;
}

Creación de una PWA: paso a paso

1. Crea el Shell de la aplicación

El shell de la aplicación es el HTML, CSS y JavaScript mínimo necesarios para representar la interfaz de usuario. Se almacena en caché en la primera carga y sirve como base de la PWA.

<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta name="theme-color" content="#3367D6">
  <link rel="manifest" href="/manifest.json">
  <link rel="icon" href="/icons/icon-192.png" sizes="192x192">
  <link rel="apple-touch-icon" href="/icons/icon-192.png">
  <title>My PWA</title>
  <link rel="stylesheet" href="/styles.css">
</head>
<body>
  <header id="app-header">My PWA</header>
  <main id="app-content"></main>
  <footer id="app-footer">© 2026</footer>

  <!-- Register service worker -->
  <script>
    if ('serviceWorker' in navigator) {
      window.addEventListener('load', () => {
        navigator.serviceWorker.register('/service-worker.js')
          .then(reg => console.log('SW registered:', reg.scope))
          .catch(err => console.error('SW registration failed:', err));
      });
    }
  </script>
  <script src="/app.js"></script>
</body>
</html>

2. Implementar soporte sin conexión

// app.js — Fetch content with offline support
const CACHE_KEY = 'content-cache';

async function loadContent(url) {
  try {
    const response = await fetch(url);
    const data = await response.json();

    // Cache for offline
    const cache = await caches.open(CACHE_KEY);
    cache.put(url, new Response(JSON.stringify(data)));

    renderContent(data);
  } catch (error) {
    // Offline — load from cache
    const cached = await caches.match(url);
    if (cached) {
      const data = await cached.json();
      renderContent(data);
      showOfflineIndicator();
    } else {
      showOfflineMessage();
    }
  }
}

3. Agregar notificaciones automáticas

// Request permission
async function requestNotificationPermission() {
  const permission = await Notification.requestPermission();
  if (permission === 'granted') {
    // Get push subscription
    const registration = await navigator.serviceWorker.ready;
    const subscription = await registration.pushManager.subscribe({
      userVisibleOnly: true,
      applicationServerKey: urlBase64ToUint8Array(publicKey)
    });

    // Send subscription to server
    await fetch('/api/push/subscribe', {
      method: 'POST',
      body: JSON.stringify(subscription)
    });
  }
}
// service-worker.js — Handle push events
self.addEventListener('push', event => {
  const data = event.data.json();

  const options = {
    body: data.body,
    icon: '/icons/icon-192.png',
    badge: '/icons/badge-72.png',
    vibrate: [100, 50, 100],
    data: { url: data.url },
    actions: [
      { action: 'open', title: 'Open' },
      { action: 'close', title: 'Dismiss' }
    ]
  };

  event.waitUntil(
    self.registration.showNotification(data.title, options)
  );
});

self.addEventListener('notificationclick', event => {
  event.notification.close();

  if (event.action === 'open' || !event.action) {
    clients.openWindow(event.notification.data.url);
  }
});

PWA versus aplicación nativa

Característica PWA Aplicación nativa
Distribución URL (no se requiere tienda) Tienda de aplicaciones, Google Play
Instalar fricción Un toque (mensaje del navegador) Descargar, instalar, permisos
Tamaño de archivo KB a MB 10 MB a 500 MB+
Actualizaciones Instantáneo (trabajador de servicio) A través de la revisión de la tienda de aplicaciones
Sin conexión Sí (trabajador de servicios) Sí (integrado)
Notificaciones push si si
API de dispositivo Cámara, GPS, acelerómetro, pago Acceso completo a todas las API del dispositivo
Bluetooth Web Bluetooth (limitado) API Bluetooth completa
Tareas en segundo plano Sincronización en segundo plano (limitada) Ejecución completa en segundo plano
Rendimiento Casi nativo (JavaScript/WebAssembly) Nativo (código compilado)
SEO Indexable por los motores de búsqueda No indexable
Costo de desarrollo Base de código única iOS + Android (o multiplataforma)
Retención de usuarios Más bajo (más fácil de salir) Superior (instalado = comprometido)

Cuándo elegir PWA en lugar de nativa

  • Aplicaciones centradas en contenido: noticias, blogs, revistas.
  • Comercio electrónico: sitios de compras (PWA de Pinterest: 40 % más de tiempo invertido).
  • Aplicaciones de utilidad: calculadoras, conversores, clima.
  • Aplicaciones de baja participación: aplicaciones que los usuarios visitan con poca frecuencia.
  • Mercados emergentes: usuarios con almacenamiento y datos limitados.
  • Mejora progresiva: experiencia completa en dispositivos modernos, funcional en dispositivos más antiguos.

Cuando la aplicación nativa es mejor

  • Juegos de alto rendimiento: más de 60 FPS, renderizado 3D complejo.
  • Integración profunda de hardware: periféricos Bluetooth, NFC, ARKit/ARCore.
  • Procesamiento en segundo plano: transmisión de música, seguimiento del estado físico.
  • Se necesita una alta participación del usuario: redes sociales, mensajería.

Puntos de referencia de rendimiento

Métrica Antes de la PWA Después de la PWA Mejora
Primera carga 5s 1,2s 76% más rápido
Cargas posteriores 3s 0,3 s 90% más rápido
Tasa de rebote 45% 20% 55% de reducción
Tasa de conversión 2% 3.5% 75% de aumento
Páginas por sesión 3 5 66% de aumento

Resultados del mundo real:

Empresa Métrica clave Mejora
Twitter (X) Lite Tweets enviados por sesión 75% de aumento
Pinterest tiempo invertido 40% de aumento
Úber Finalización de la reserva Suave incluso en 2G
Starbucks Usuarios activos diarios aumento 2x
Alibaba Tasa de conversión 76% de aumento

Pruebas y auditorías de PWA

Auditoría de PWA del faro

# Run Lighthouse from CLI
npx lighthouse https://example.com --view --preset=desktop

# PWA-specific checks:
# - ✅ Registers a service worker
# - ✅ Responds with 200 when offline
# - ✅ Web app manifest exists
# - ✅ Manifest has display: standalone
# - ✅ Served over HTTPS
# - ✅ Redirects HTTP to HTTPS
# - ✅ Configurable start URL
# - ✅ Icons provided (192px and 512px)

Lista de verificación de pruebas manuales

## PWA Pre-Flight Checklist
- [ ] HTTPS enabled
- [ ] Service worker registered and active
- [ ] Pages load offline (test with airplane mode)
- [ ] Manifest configured with correct icons
- [ ] "Add to Home Screen" prompt appears
- [ ] PWA opens in standalone mode with no address bar
- [ ] Splash screen displays correctly
- [ ] Push notifications work
- [ ] All pages are responsive (mobile, tablet, desktop)
- [ ] Performance budget met (Lighthouse score > 90)
- [ ] Cross-browser tested (Chrome, Firefox, Safari, Edge)
- [ ] Accessibility audit passes

Funciones avanzadas de PWA

Sincronización en segundo plano

// Register a sync event
async function scheduleSync() {
  const registration = await navigator.serviceWorker.ready;
  await registration.sync.register('sync-pending-data');
}

// Service worker handles sync
self.addEventListener('sync', event => {
  if (event.tag === 'sync-pending-data') {
    event.waitUntil(syncPendingData());
  }
});

Sincronización periódica en segundo plano

// Request periodic sync (requires user engagement)
const registration = await navigator.serviceWorker.ready;
await registration.periodicSync.register('update-content', {
  minInterval: 24 * 60 * 60 * 1000 // Once per day
});

API para compartir web

// Share content using native share sheet
async function shareContent(title, text, url) {
  if (navigator.share) {
    await navigator.share({ title, text, url });
  } else {
    // Fallback to custom share UI
    copyToClipboard(url);
  }
}

API de insignias

// Set app icon badge
if (navigator.setAppBadge) {
  await navigator.setAppBadge(5); // Show "5" on icon
} else if (navigator.clearAppBadge) {
  await navigator.clearAppBadge();
}

Errores comunes

trampa problema Solución
Sobrealmacenamiento en caché La aplicación ofrece contenido obsoleto Cachés de versión, implementar caché primero para shell, red primero para contenido
Sin respaldo sin conexión Pantalla blanca sin conexión Siempre almacene en caché /offline.html con un mensaje significativo
Errores en la destrucción de caché El viejo trabajador de servicios persiste Utilice skipWaiting() y client.claim()
Problemas con la pantalla de presentación Icono o color de tema incorrecto Configure el manifiesto correctamente, use íconos enmascarables
Compatibilidad con safaris Algunas funciones son limitadas (push, sincronización periódica) Mejora progresiva: degradar con gracia
Tamaño de caché grande Se superó la cuota de almacenamiento (50 MB por origen) Eliminar cachés antiguos y limitar el tamaño de la caché
Falta la URL de inicio La pantalla de inicio abre una página incorrecta Especifique siempre start_url en el manifiesto

Conclusión

Las aplicaciones web progresivas representan lo mejor de ambos mundos: el alcance y la accesibilidad de la web combinados con las capacidades y la experiencia del usuario de las aplicaciones nativas.

Cuándo construir una PWA

  1. Quieres el máximo alcance: una aplicación para todas las plataformas.
  2. El usuario necesita acceso sin conexión: almacenamiento en caché del trabajador del servicio.
  3. Quieres carga instantánea: arquitectura de shell de la aplicación.
  4. Quieres volver a interactuar: notificaciones automáticas sin tienda de aplicaciones.
  5. Tienes una aplicación basada en contenido: noticias, comercio electrónico, servicios públicos.

Empezando

  1. Agregue un manifest.json e íconos.
  2. Cree un trabajador de servicio con respaldo sin conexión.
  3. Ejecute una auditoría Lighthouse y solucione problemas críticos.
  4. Pruebe en dispositivos reales (Android, iOS, escritorio).
  5. Implementar y medir la participación de los usuarios.

Las PWA no reemplazan a las aplicaciones nativas en todos los escenarios, pero para la mayoría de las aplicaciones de utilidad y basadas en contenido, brindan una experiencia de usuario superior con una fracción del costo de desarrollo.

Artículos destacados

6 patrones de programación que aprendí de ingenieros senior
EditorialES
5 minGratis

6 patrones de programación que aprendí de ingenieros senior

TecnologíaCodeProgrammingCoding
0 aplausos
Leer
Crea un asistente privado de notas con IA
DestacadoES
9 minGratis

Crea un asistente privado de notas con IA

Convierte notas Markdown en un asistente consultable sin exponer borradores privados.

TecnologíaGuías de plataformaEscritura con IAAIAutomationData
0 aplausos
Leer
Automatiza la extracción de datos de facturas con OCR
EditorialES
8 minGratis

Automatiza la extracción de datos de facturas con OCR

Crea un flujo que lee facturas, extrae campos y marca valores dudosos para revisión.

TecnologíaArtículos de ingenieríaEscritura con IAAIAutomationData
0 aplausos
Leer

Comentarios

0 comentarios

Todavía no hay comentarios aprobados. Las respuestas nuevas pueden esperar moderación.