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 (