diff --git a/frontend/PWA-UPDATE-GUIDE.md b/frontend/PWA-UPDATE-GUIDE.md new file mode 100644 index 0000000..3f5d643 --- /dev/null +++ b/frontend/PWA-UPDATE-GUIDE.md @@ -0,0 +1,168 @@ +# Sistema de Actualización PWA - AYUTEC + +## 🚀 Características + +- ✅ **Detección automática** de nuevas versiones +- ✅ **Modal de actualización** grande y visible +- ✅ **Service Worker** con estrategia Network-First +- ✅ **Cache inteligente** para funcionamiento offline +- ✅ **Actualización forzada** al usuario cuando hay nueva versión + +## 📱 Instalación como PWA + +### En Android/iOS: +1. Abre la app en Chrome/Safari +2. Toca el menú (⋮) +3. Selecciona "Agregar a pantalla de inicio" +4. Confirma la instalación + +### En Desktop: +1. Abre la app en Chrome/Edge +2. Haz clic en el ícono de instalación (➕) en la barra de direcciones +3. Confirma "Instalar" + +## 🔄 Proceso de Actualización + +### Para el Usuario: +1. Cuando hay una actualización, aparece automáticamente un **modal grande** +2. El modal muestra: "¡Nueva Actualización!" +3. Botón grande: **"🚀 ACTUALIZAR AHORA"** +4. Al presionar, la app se recarga con la nueva versión + +### Para el Desarrollador: + +#### Opción 1: Script Automático (Recomendado) +```powershell +cd frontend +.\update-version.ps1 +``` +Este script: +- Incrementa automáticamente la versión patch (1.0.87 → 1.0.88) +- Actualiza `package.json` +- Actualiza `public/service-worker.js` + +#### Opción 2: Manual +1. **Actualizar `package.json`:** + ```json + "version": "1.0.88" // Incrementar número + ``` + +2. **Actualizar `public/service-worker.js`:** + ```javascript + const CACHE_NAME = 'ayutec-v1.0.88'; // Mismo número + ``` + +3. **Hacer build y deploy:** + ```powershell + npm run build + docker build -t tu-registry/checklist-frontend:latest . + docker push tu-registry/checklist-frontend:latest + ``` + +## 🔧 Cómo Funciona + +### 1. Service Worker +- Registrado en `App.jsx` +- Cache con nombre versionado: `ayutec-v1.0.87` +- Estrategia: **Network First, Cache Fallback** +- Al cambiar la versión, se crea nuevo cache + +### 2. Detección de Actualización +```javascript +// En App.jsx +registration.addEventListener('updatefound', () => { + // Nueva versión detectada + setUpdateAvailable(true) +}) +``` + +### 3. Modal de Actualización +- Overlay negro semi-transparente (z-index: 9999) +- Modal animado con bounce +- Botón grande con gradiente +- **No se puede cerrar** - obliga a actualizar + +### 4. Aplicación de Actualización +```javascript +waitingWorker.postMessage({ type: 'SKIP_WAITING' }); +// Activa el nuevo service worker +// Recarga la página automáticamente +``` + +## 📊 Versionado + +Seguimos **Semantic Versioning**: +- **MAJOR**: Cambios incompatibles (1.0.0 → 2.0.0) +- **MINOR**: Nueva funcionalidad compatible (1.0.0 → 1.1.0) +- **PATCH**: Correcciones de bugs (1.0.0 → 1.0.1) + +El script `update-version.ps1` incrementa automáticamente **PATCH**. + +## 🧪 Probar Localmente + +1. **Compilar en modo producción:** + ```bash + npm run build + npm run preview + ``` + +2. **Simular actualización:** + - Abre la app en navegador + - Incrementa versión en `service-worker.js` + - Recarga la página (Ctrl+F5) + - Debe aparecer el modal de actualización + +## 🐛 Troubleshooting + +### El modal no aparece +- Verifica que el service worker esté registrado (F12 → Application → Service Workers) +- Asegúrate de cambiar el `CACHE_NAME` en `service-worker.js` +- Desregistra el SW antiguo: `Application → Service Workers → Unregister` + +### La app no se actualiza +- Fuerza actualización: Ctrl+Shift+R (hard reload) +- Limpia cache del navegador +- Verifica que la nueva versión esté deployada + +### PWA no se instala +- Verifica que `site.webmanifest` esté accesible +- Requiere HTTPS (excepto localhost) +- Verifica íconos en `/public/` + +## 📝 Checklist de Deploy + +- [ ] Incrementar versión con `update-version.ps1` +- [ ] Verificar que ambos archivos tengan la misma versión +- [ ] Hacer commit: `git commit -m "chore: bump version to X.X.X"` +- [ ] Build de producción: `npm run build` +- [ ] Build de Docker: `docker build -t frontend:vX.X.X .` +- [ ] Push a registry +- [ ] Deploy en servidor +- [ ] Verificar que usuarios vean el modal de actualización + +## 🎯 Mejores Prácticas + +1. **Siempre** incrementar versión antes de deploy +2. **Nunca** reutilizar números de versión +3. **Probar** localmente antes de deploy +4. **Documentar** cambios en commit message +5. **Notificar** a usuarios si es actualización crítica + +## 🔐 Seguridad + +- Service Worker solo funciona en HTTPS +- Manifest require `start_url` y `scope` correctos +- Cache no almacena datos sensibles (solo assets estáticos) + +## 📱 Compatibilidad + +- ✅ Chrome/Edge (Desktop y Mobile) +- ✅ Safari (iOS 11.3+) +- ✅ Firefox (Desktop y Mobile) +- ✅ Samsung Internet +- ⚠️ IE11 no soportado + +--- + +**Versión actual:** 1.0.87 +**Última actualización:** 2025-11-30 diff --git a/frontend/index.html b/frontend/index.html index 32cc655..4aa0ade 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -7,7 +7,11 @@ - + + + + + AYUTEC - Sistema Inteligente de Inspecciones diff --git a/frontend/package.json b/frontend/package.json index b3631f7..d562a47 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,7 +1,7 @@ { "name": "checklist-frontend", "private": true, - "version": "1.0.86", + "version": "1.0.87", "type": "module", "scripts": { "dev": "vite", diff --git a/frontend/public/service-worker.js b/frontend/public/service-worker.js new file mode 100644 index 0000000..f1653f2 --- /dev/null +++ b/frontend/public/service-worker.js @@ -0,0 +1,66 @@ +// Service Worker para PWA con detección de actualizaciones +// IMPORTANTE: Actualizar esta versión cada vez que se despliegue una nueva versión +const CACHE_NAME = 'ayutec-v1.0.87'; +const urlsToCache = [ + '/', + '/index.html' +]; + +// Instalación del service worker +self.addEventListener('install', (event) => { + console.log('Service Worker: Installing version', CACHE_NAME); + event.waitUntil( + caches.open(CACHE_NAME) + .then((cache) => { + console.log('Service Worker: Caching files'); + return cache.addAll(urlsToCache); + }) + .then(() => self.skipWaiting()) // Forzar activación inmediata + ); +}); + +// Activación del service worker +self.addEventListener('activate', (event) => { + console.log('Service Worker: Activating...'); + event.waitUntil( + caches.keys().then((cacheNames) => { + return Promise.all( + cacheNames.map((cacheName) => { + if (cacheName !== CACHE_NAME) { + console.log('Service Worker: Deleting old cache:', cacheName); + return caches.delete(cacheName); + } + }) + ); + }).then(() => self.clients.claim()) // Tomar control de todas las páginas + ); +}); + +// Estrategia: Network First, fallback to Cache +self.addEventListener('fetch', (event) => { + event.respondWith( + fetch(event.request) + .then((response) => { + // Clone la respuesta + const responseToCache = response.clone(); + + // Actualizar cache con la nueva respuesta + caches.open(CACHE_NAME).then((cache) => { + cache.put(event.request, responseToCache); + }); + + return response; + }) + .catch(() => { + // Si falla la red, usar cache + return caches.match(event.request); + }) + ); +}); + +// Mensaje para notificar actualización +self.addEventListener('message', (event) => { + if (event.data && event.data.type === 'SKIP_WAITING') { + self.skipWaiting(); + } +}); diff --git a/frontend/public/site.webmanifest b/frontend/public/site.webmanifest index 1b63714..d70c3bb 100644 --- a/frontend/public/site.webmanifest +++ b/frontend/public/site.webmanifest @@ -1,6 +1,8 @@ { "name": "AYUTEC - Sistema de Inspecciones", "short_name": "AYUTEC", + "start_url": "/", + "scope": "/", "icons": [ { "src": "/web-app-manifest-192x192.png", @@ -17,5 +19,6 @@ ], "theme_color": "#4f46e5", "background_color": "#ffffff", - "display": "standalone" + "display": "standalone", + "orientation": "portrait" } diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index b87d18c..8878e81 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -8,6 +8,56 @@ import QuestionAnswerInput from './QuestionAnswerInput' function App() { const [user, setUser] = useState(null) const [loading, setLoading] = useState(true) + const [updateAvailable, setUpdateAvailable] = useState(false) + const [waitingWorker, setWaitingWorker] = useState(null) + + // Detectar actualizaciones del Service Worker + useEffect(() => { + if ('serviceWorker' in navigator) { + // Registrar service worker + navigator.serviceWorker.register('/service-worker.js') + .then((registration) => { + console.log('✅ Service Worker registrado:', registration); + + // Verificar si hay actualización esperando + if (registration.waiting) { + setWaitingWorker(registration.waiting); + setUpdateAvailable(true); + } + + // Detectar cuando hay nueva versión instalándose + registration.addEventListener('updatefound', () => { + const newWorker = registration.installing; + console.log('🔄 Nueva versión detectada, instalando...'); + + newWorker.addEventListener('statechange', () => { + if (newWorker.state === 'installed' && navigator.serviceWorker.controller) { + // Hay nueva versión disponible + console.log('✨ Nueva versión lista!'); + setWaitingWorker(newWorker); + setUpdateAvailable(true); + } + }); + }); + }) + .catch((error) => { + console.error('❌ Error al registrar Service Worker:', error); + }); + + // Escuchar cambios de controlador (cuando se activa nueva versión) + navigator.serviceWorker.addEventListener('controllerchange', () => { + console.log('🔄 Controlador cambiado, recargando página...'); + window.location.reload(); + }); + } + }, []); + + // Función para actualizar la app + const handleUpdate = () => { + if (waitingWorker) { + waitingWorker.postMessage({ type: 'SKIP_WAITING' }); + } + }; useEffect(() => { // Verificar si hay token guardado @@ -31,6 +81,38 @@ function App() { return (
+ {/* Modal de actualización disponible */} + {updateAvailable && ( +
+
+
+
+
+ 🔄 +
+
+

+ ¡Nueva Actualización! +

+

+ Hay una nueva versión disponible con mejoras y correcciones. +
+ Por favor actualiza para continuar. +

+ +

+ La página se recargará automáticamente +

+
+
+
+ )} + {!user ? ( ) : ( diff --git a/frontend/update-version.ps1 b/frontend/update-version.ps1 new file mode 100644 index 0000000..5d368e0 --- /dev/null +++ b/frontend/update-version.ps1 @@ -0,0 +1,41 @@ +# Script para actualizar la versión del frontend y service worker automáticamente + +Write-Host "🔄 Actualizando versión del frontend..." -ForegroundColor Cyan + +# Leer package.json +$packageJson = Get-Content "package.json" -Raw | ConvertFrom-Json +$currentVersion = $packageJson.version +Write-Host "📦 Versión actual: $currentVersion" -ForegroundColor Yellow + +# Separar versión en partes (major.minor.patch) +$versionParts = $currentVersion -split '\.' +$major = [int]$versionParts[0] +$minor = [int]$versionParts[1] +$patch = [int]$versionParts[2] + +# Incrementar patch +$patch++ +$newVersion = "$major.$minor.$patch" + +Write-Host "✨ Nueva versión: $newVersion" -ForegroundColor Green + +# Actualizar package.json +$packageJsonContent = Get-Content "package.json" -Raw +$packageJsonContent = $packageJsonContent -replace """version"": ""$currentVersion""", """version"": ""$newVersion""" +Set-Content "package.json" -Value $packageJsonContent -NoNewline + +Write-Host "✅ package.json actualizado" -ForegroundColor Green + +# Actualizar service-worker.js +$swPath = "public\service-worker.js" +$swContent = Get-Content $swPath -Raw +$swContent = $swContent -replace "ayutec-v$currentVersion", "ayutec-v$newVersion" +Set-Content $swPath -Value $swContent -NoNewline + +Write-Host "✅ service-worker.js actualizado" -ForegroundColor Green +Write-Host "" +Write-Host "🎉 Versión actualizada exitosamente a: $newVersion" -ForegroundColor Magenta +Write-Host "" +Write-Host "📝 Recuerda hacer commit de los cambios:" -ForegroundColor Yellow +Write-Host " git add package.json public/service-worker.js" -ForegroundColor Gray +Write-Host " git commit -m 'chore: bump version to $newVersion'" -ForegroundColor Gray