feat: app instalable desde el navegador (PWA)

vite-plugin-pwa genera manifest.webmanifest + service worker en el build. Iconos nuevos
generados a partir del logo actual (192, 512, maskable para Android). El service worker
precachea solo assets estaticos (JS/CSS/iconos) -- /api nunca se cachea, siempre va a la red, asi
los datos no quedan desactualizados. Se actualiza solo (autoUpdate) sin pedirle nada al usuario.

Probado: build genera sw.js/manifest correctamente, el service worker registra y activa en un
browser real, y la app (login, listado de proyectos, llamadas a /api) sigue funcionando igual
que antes -- sin regresiones.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Milton Gonzalez
2026-09-17 15:25:04 -05:00
parent d9dbb229ef
commit 08e6db0b6f
10 changed files with 4230 additions and 8 deletions
+4178 -3
View File
File diff suppressed because it is too large Load Diff
+3 -2
View File
@@ -1,7 +1,7 @@
{
"name": "project-linkedcorp-frontend",
"private": true,
"version": "1.21.1",
"version": "1.22.0",
"type": "module",
"scripts": {
"dev": "vite",
@@ -17,6 +17,7 @@
},
"devDependencies": {
"@vitejs/plugin-react": "^4.3.4",
"vite": "^6.4.3"
"vite": "^6.4.3",
"vite-plugin-pwa": "^1.3.0"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 149 KiB

+4
View File
@@ -1,11 +1,15 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import { registerSW } from 'virtual:pwa-register';
import App from './app/App.jsx';
import { installGlobalErrorReporter } from './utils/errorReporter.js';
import './styles/tokens.css';
import './styles/app.css';
installGlobalErrorReporter();
// autoUpdate: toma la version nueva del service worker sola en cuanto esta lista, sin pedirle
// nada al usuario (no hay riesgo de mostrar una version vieja de la UI por mucho tiempo).
registerSW({ immediate: true });
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
+32 -1
View File
@@ -1,8 +1,39 @@
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { VitePWA } from 'vite-plugin-pwa';
export default defineConfig({
plugins: [react()],
plugins: [
react(),
VitePWA({
registerType: 'autoUpdate',
includeAssets: ['favicon.svg', 'favicon.png', 'apple-touch-icon.png'],
manifest: {
id: '/',
lang: 'es-EC',
name: 'LinkedCorp Proyectos',
short_name: 'LinkedCorp',
description: 'Gestión de proyectos y actividades de TI',
theme_color: '#0078d4',
background_color: '#202020',
display: 'standalone',
start_url: '/',
icons: [
{ src: 'pwa-192x192.png', sizes: '192x192', type: 'image/png' },
{ src: 'pwa-512x512.png', sizes: '512x512', type: 'image/png' },
{ src: 'pwa-maskable-512x512.png', sizes: '512x512', type: 'image/png', purpose: 'maskable' },
],
},
workbox: {
// El service worker nunca debe interceptar /api — siempre a la red, nunca cache de
// respuestas viejas (datos de proyectos/actividades desactualizados seria peor que no
// tener PWA). navigateFallback (para que recargar una ruta como /proyectos/5 sirva el
// index.html cacheado) queda excluido de /api de forma explicita, aunque de por si esas
// llamadas no son navegaciones de pagina.
navigateFallbackDenylist: [/^\/api\//],
},
}),
],
server: {
host: '0.0.0.0',
port: 5196,