Antes de explicar cómo convertir una web en Progressive Web App (PWA), es importante definir qué es una PWA y por qué puede ser útil.

Una PWA (Progressive Web App) es una aplicación web que se comporta como una aplicación nativa y que se puede instalar en dispositivos móviles y ordenadores. Las PWA utilizan tecnologías web modernas para ofrecer una experiencia de usuario mejorada, como la capacidad de trabajar offline, el acceso a notificaciones push y el acceso a la cámara y el micrófono.
Convertir una web en PWA tiene una serie de ventajas:
- En primer lugar, las PWA se pueden instalar en el dispositivo del usuario, lo que significa que la aplicación estará siempre disponible, incluso si el usuario no tiene conexión a internet.
- En segundo lugar, las PWA ofrecen una experiencia de usuario más fluida y rápida, ya que se cargan más rápido que una página web tradicional. Además, las PWA tienen la capacidad de enviar notificaciones push, lo que permite a los usuarios estar al tanto de las actualizaciones y cambios en la web.
¿Como convertir una web a Progressive Web App (PWA)?
Para convertir una web en PWA, necesitamos seguir los siguientes pasos:
- Agregar un archivo de manifiesto de aplicación web (manifest.json) para definir las propiedades de la aplicación, como el nombre, el icono, la orientación, etc.
Para crear el archivo de manifiesto, podemos utilizar herramientas en línea como https://manifest-gen.netlify.app, que nos permiten generar un archivo de manifiesto JSON con imágenes de las dimensiones correspondientes.

Una vez generado el archivo, lo descargamos y lo colocamos en la raíz del sitio web.
- Crear un archivo que en nuestro caso llamaremos «regist_serviceWorker.js» el cual contendrá las instrucciones necesarias para registrar el Service Worker. Este archivo debe ser invocado desde la página de inicio.
- Agregar un Service Worker (Service_Worker.js) para habilitar la funcionalidad offline y cacheado de recursos.
Un Service Worker es el archivo que se encargará de almacenar en caché los archivos de la aplicación y servirlos al usuario incluso cuando no tenga conexión a internet. Para crear el archivo Service Worker, creamos un archivo llamado «Service_Worker.js» en la raíz del sitio web y lo configuramos de acuerdo a nuestras necesidades.
En el archivo Service Worker, definiremos un nombre y una versión de la caché, así como las URL que queremos que se almacenen en caché.
Una vez configurado todo, podemos probar nuestra aplicación como una PWA. Para hacerlo, abrimos las DevTools de nuestro navegador y accedemos a la pestaña «Auditoría». Allí, seleccionamos la opción «Progressive Web App» y hacemos clic en «Run Audit». Si todo está configurado correctamente, la auditoría debería pasar sin problemas.
Conversión de web a PWA: Ejemplo práctico paso a paso
A continuación, partiremos de la siguiente web y detallaremos los pasos que seguiremos para convertirla en una PWA funcional:
Añadir archivo manifest.json
Existen numerosas webs que nos permiten generar el archivo manifest.json, en el repositorio asociado a esta entrada, encontrarás algunas URL útiles. En nuestro ejemplo, usaremos https://manifest-gen.netlify.app.

En este archivo se deben especificar ciertos datos para definir la configuración de la aplicación PWA. Algunos de los datos que se pueden incluir son:
- «name»: El nombre completo de la aplicación.
- «short_name»: Un nombre corto o abreviado de la aplicación.
- «theme_color»: El color principal de la aplicación que se refleja en la barra de estado del dispositivo.
- «background_color»: El color de fondo de la aplicación cuando se carga.
- «display»: Define cómo se muestra la aplicación, puede ser «standalone», «fullscreen», «minimal-ui» o «browser».
- «orientation»: Define la orientación predeterminada de la aplicación, puede ser «portrait», «landscape», «portrait-primary», «portrait-secondary», «landscape-primary» o «landscape-secondary».
- «scope»: Define el alcance de la aplicación, es decir, qué URLs se consideran parte de la aplicación.
- «start_url»: La URL inicial de la aplicación.
- «icons»: Un conjunto de iconos en diferentes tamaños y formatos que se utilizan para representar la aplicación en diferentes lugares.
Después de completar los campos y cargar la imagen que se utilizará como icono de la aplicación (debe tener la misma altura que ancho), se descargará un archivo comprimido que incluye tanto el archivo manifest.json como los iconos en diferentes tamaños.

Copiaremos el contenido en el directorio raíz de nuestro proyecto.

El archivo manifest.json resultante quedaría de la siguiente manera:
{
"name": "Ejemplo de web PWA",
"short_name": "xample-PWA",
"theme_color": "#000",
"background_color": "#fff",
"display": "standalone",
"orientation": "portrait",
"scope": "/",
"start_url": "/",
"icons": [
{
"src": "images/icons/icon-72x72.png",
"sizes": "72x72",
"type": "image/png"
},
{
"src": "images/icons/icon-96x96.png",
"sizes": "96x96",
"type": "image/png"
},
{
"src": "images/icons/icon-128x128.png",
"sizes": "128x128",
"type": "image/png"
},
{
"src": "images/icons/icon-144x144.png",
"sizes": "144x144",
"type": "image/png"
},
{
"src": "images/icons/icon-152x152.png",
"sizes": "152x152",
"type": "image/png"
},
{
"src": "images/icons/icon-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "images/icons/icon-384x384.png",
"sizes": "384x384",
"type": "image/png"
},
{
"src": "images/icons/icon-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
]
}
Este debe linkarse desde el <head>
de la página index.html y aprovecharemos para incluir tanto establecer un color de tema para la barra de direcciones
<!-- Añadido para PWA -->
<meta name="theme-color" content="#ffffff">
<link rel="apple-touch-icon" sizes="180x180" href="./images/icons/icon-192x192.png">
<link rel="manifest" href="./manifest.json" />
Fichero regist_serviceWorker.js para el registro del servicio
Incluiremos el código para registrar el Service Worker.
if ('serviceWorker' in navigator) {
window.addEventListener('load', function () {
navigator.serviceWorker.register('./Service_Worker.js').then(function (registration) {
console.log('ServiceWorker registration successful with scope: ', registration.scope);
}, function (err) {
console.log('ServiceWorker registration failed: ', err);
});
});
}
Primero se verifica si el objeto «serviceWorker» está disponible en el objeto «navigator» del navegador web del usuario. Si está disponible, registra el Service Worker especificado en el archivo «Service_Worker.js» ubicado en el mismo directorio que el archivo HTML que lo llama.
Una vez que se registra el Service Worker, se devuelve una promesa que puede ser manejada por los métodos «then» y «catch». Si se registra correctamente, el método «then» se ejecuta y muestra un mensaje en la consola del navegador web con el alcance del Service Worker registrado. Si falla, el método «catch» se ejecuta y muestra un mensaje de error en la consola con la información del error.
Este código se incluirá antes del cierre del body del archivo index.html.
<script src="./regist_serviceWorker.js"></script>
</body>
Agregar fichero Service_Worker.js para funcionalidad de caché
//asignar un nombre y versión al cache
const CACHE_NAME = 'xamplepwa',
urlsToCache = [
'.',
'./index.html',
]
//durante la fase de instalación, generalmente se almacena en caché los activos estáticos
self.addEventListener('install', e => {
e.waitUntil(
caches.open(CACHE_NAME)
.then(cache => {
return cache.addAll(urlsToCache)
.then(() => self.skipWaiting())
})
.catch(err => console.log('Falló registro de cache', err))
)
})
//una vez que se instala el SW, se activa y busca los recursos para hacer que funcione sin conexión
self.addEventListener('activate', e => {
const cacheWhitelist = [CACHE_NAME]
e.waitUntil(
caches.keys()
.then(cacheNames => {
return Promise.all(
cacheNames.map(cacheName => {
//Eliminamos lo que ya no se necesita en cache
if (cacheWhitelist.indexOf(cacheName) === -1) {
return caches.delete(cacheName)
}
})
)
})
// Le indica al SW activar el cache actual
.then(() => self.clients.claim())
)
})
//cuando el navegador recupera una url
self.addEventListener('fetch', e => {
//Responder ya sea con el objeto en caché o continuar y buscar la url real
e.respondWith(
caches.match(e.request)
.then(res => {
if (res) {
//recuperar del cache
return res
}
//recuperar de la petición a la url
return fetch(e.request)
})
)
})
El código en el archivo comienza asignando un nombre y una versión al caché (CACHE_NAME
) y especificando las URLs que se deben almacenar en el caché (urlsToCache
), que en este caso son la raíz del sitio (.
) y la página principal (./index.html
).
Luego, en el evento de instalación del Service Worker (install
), se crea el caché y se agregan las URLs especificadas. Si todo va bien, el Service Worker pasa a la fase de activación, en la cual se eliminan del caché los recursos que ya no son necesarios y se le indica al Service Worker que controle la página actual.
Por último, el Service Worker maneja el evento fetch
, el cual se dispara cuando el navegador intenta recuperar una URL. Si la URL solicitada se encuentra en el caché, se recupera del caché y se devuelve al navegador. Si no se encuentra en el caché, el Service Worker solicita la URL al servidor y la devuelve al navegador. De esta manera, el Service Worker ayuda a que las páginas se carguen más rápido y a reducir el consumo de datos al recuperar los recursos de la caché en lugar de hacer una petición al servidor cada vez que se accede a una página.
Comprobar que la web cumple con los estándares y funcionalidades de una PWA
Una PWA debe ser rápida, confiable y segura, y ofrecer una experiencia de usuario óptima en cualquier dispositivo y conexión. Para comprobar que cumple con los estándares y funcionalidades de una PWA seguiremos como mínimo los siguientes pasos:

- Utilizar la herramienta «Lighthouse» de Google Chrome: Esta herramienta permite auditar la PWA y ofrece un informe detallado de los aspectos que cumple y los que no.
- Comprobar que el sitio tiene un archivo «manifest.json«: Este archivo contiene información sobre la PWA, como su nombre, descripción, iconos, etc.
- Comprobar que el sitio utiliza un Service Worker: Este es un script que corre en segundo plano y permite que la PWA funcione sin conexión.
- Comprobar que el sitio está protegido con HTTPS: Es importante que la conexión sea segura para garantizar la privacidad y seguridad del usuario.

Una vez terminada la auditoría se indicará si es instalable o no y si existe algún tipo de error a solventar.
Instalación de la PWA en dispositivos móviles o de escritorio
A partir de ese momento la PWA se podría instalar tanto en dispositivos móviles como en ordenadores de escritorio. Sin embargo, la forma de instalarla puede variar ligeramente dependiendo del dispositivo y el navegador utilizado.
Para instalarla en un ordenador de escritorio desde Chrome, pulsaremos sobre los 3 puntos y buscaremos la opción «Instalar aplicación (o el nombre que le hayamos dato)». En otros navegadores, como Firefox o Edge, la opción puede estar en un menú diferente o tener un nombre ligeramente diferente, pero normalmente se encuentra en algún lugar del menú de configuración.



Para instalarla desde un dispositivo móvil, primero debes asegurarte de que el navegador que estás usando lo soporta. Luego, sigue los siguientes pasos:
- Abre la PWA en tu navegador móvil.
- Pulsa en el icono de «Compartir» o «Agregar a pantalla de inicio».
- Si no ves la opción de «Agregar a pantalla de inicio», busca la opción de «Compartir» y desde allí deberías encontrar la opción.
- Selecciona «Agregar a pantalla de inicio».
- Escribe un nombre para la PWA o déjalo como está y pulsa en «Agregar» o «Añadir».
- La PWA debería aparecer en tu pantalla de inicio, desde donde podrás acceder a ella como si fuera una app nativa.
Es importante destacar que el proceso puede variar dependiendo del navegador y del sistema operativo que estés utilizando.

Código en Github de este ejemplo de PWA
Puedes ver todo el código y animarte a probarlo en el repositorio de GitHub https://github.com/erabasco/xample-PWA.
Ejemplo de PWA GIS: https://idena.navarra.es/navegar/
Con «mapas sin conexión» dentro del menú «herramientas».
Gracias Carlos!
IDENA siempre TOP!! 😀