const CACHE_NAME = 'onapb-v4'; // Recursos estáticos que se cachean en la instalación const STATIC_ASSETS = [ '/', '/eventos', '/torneos', '/offline', '/manifest.json', ]; // ── Install: pre-cachear recursos estáticos ── self.addEventListener('install', (event) => { self.skipWaiting(); event.waitUntil( caches.open(CACHE_NAME).then((cache) => { return cache.addAll(STATIC_ASSETS).catch(() => { // Si algún recurso falla, no bloquear la instalación console.warn('[SW] Algún recurso estático no pudo cachearse.'); }); }) ); }); // ── Activate: limpiar caches viejas ── self.addEventListener('activate', (event) => { event.waitUntil( caches.keys().then((keys) => Promise.all( keys .filter((key) => key !== CACHE_NAME) .map((key) => caches.delete(key)) ) ).then(() => self.clients.claim()) ); }); // ── Fetch: Network-first para páginas, Cache-first para assets ── self.addEventListener('fetch', (event) => { const { request } = event; const url = new URL(request.url); // Solo manejar peticiones del mismo origen if (url.origin !== location.origin) return; // Ignorar peticiones POST, PUT, DELETE, admin, notificaciones en tiempo real if (request.method !== 'GET') return; if (url.pathname.startsWith('/admin')) return; if (url.pathname.startsWith('/notificaciones/count')) return; // Assets estáticos (JS, CSS, imágenes, fuentes): Cache-first if ( url.pathname.match(/\.(js|css|png|jpg|jpeg|gif|svg|webp|woff2?|ico)$/) ) { event.respondWith( caches.match(request).then((cached) => { if (cached) return cached; return fetch(request).then((response) => { if (response && response.status === 200) { const clone = response.clone(); caches.open(CACHE_NAME).then((cache) => cache.put(request, clone)); } return response; }); }) ); return; } // Páginas HTML: Network-first, fallback a cache, luego /offline event.respondWith( fetch(request) .then((response) => { if (response && response.status === 200) { const clone = response.clone(); caches.open(CACHE_NAME).then((cache) => cache.put(request, clone)); } return response; }) .catch(() => caches.match(request).then( (cached) => cached || caches.match('/offline') ) ) ); }); // ── Web Push Notifications ── self.addEventListener('push', (event) => { let data = { title: 'OnAPB', body: 'Tenés una nueva notificación' }; if (event.data) { try { data = event.data.json(); } catch (e) { data.body = event.data.text(); } } const options = { body: data.body, icon: '/icons/icon-192.png?v=4', badge: '/icons/icon-72.png?v=4', data: { url: data.url || '/' }, vibrate: [100, 50, 100] }; event.waitUntil( self.registration.showNotification(data.title, options) ); }); self.addEventListener('notificationclick', (event) => { event.notification.close(); const targetUrl = event.notification.data.url || '/'; event.waitUntil( clients.matchAll({ type: 'window', includeUncontrolled: true }).then((clientList) => { // Si ya hay una ventana abierta con la misma URL, enfocarla for (const client of clientList) { if (client.url === targetUrl && 'focus' in client) { return client.focus(); } } // Si no, abrir una nueva if (clients.openWindow) { return clients.openWindow(targetUrl); } }) ); });