1/** 2 * âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 3 * CashFlow â Service Worker: CAPA DE DATOS de la caché offline (patrón cache-first) 4 * âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 5 * 6 * Servido desde /sw.js (raÃz) â scope '/', controla toda la app. 7 * 8 * ARQUITECTURA EN 3 CAPAS (mismo patrón que un Dexie/IndexedDB, adaptado): 9 * 1. Capa de datos (ESTE archivo) â define "qué tablas existen": los caches 10 * del navegador (Cache Storage) y la estrategia de lectura/escritura de cada uno. 11 * 2. Capa de acceso (assets/js/cache.js) â el "DTO": sabe hablar con este SW 12 * (mensajes SINCRONIZAR) y orquesta la precarga de páginas. 13 * 3. Capa de UI (botón Sincronizar del aside) â decide CUÃNDO sincronizar. 14 * 15 * ¿Por qué Cache Storage y no IndexedDB/Dexie? CashFlow es server-rendered 16 * (Handlebars): los datos NO llegan por una API JSON que se pueda guardar en 17 * tablas â llegan ya renderizados dentro del HTML. La "unidad de dato" acá es 18 * la página entera, y el almacén nativo del navegador para respuestas HTTP 19 * completas es Cache Storage (lo maneja el service worker). El patrón lógico 20 * es el mismo: guardar local, leer local cuando la red falta, pisar todo al 21 * sincronizar. 22 * 23 * LAS "TABLAS" (un cache por tipo de dato, versionadas): 24 * - cf-estaticos-vN â CSS/JS/imágenes/fuentes (propios y de CDNs permitidos). 25 * Estrategia: cache-first con revalidación en segundo plano 26 * (stale-while-revalidate). Es la ganancia de EFICIENCIA: estos archivos 27 * casi nunca cambian y hoy se re-piden en cada navegación. 28 * - cf-paginas-vN â el HTML renderizado de las pantallas (la "lista"). 29 * Estrategia: NETWORK-FIRST con fallback a caché. Distinto del ticket 30 * original a propósito: acá el dato es financiero y cambia con cada 31 * operación del usuario, asà que con internet SIEMPRE se muestra lo 32 * fresco; la caché solo aparece cuando no hay red. 33 * - cf-pdfs-vN â los PDFs de facturación (el "contenido on demand"): 34 * se guardan recién cuando el usuario abre ese PDF por primera vez. 35 * Estrategia: network-first con fallback (el PDF de un detalle cambia 36 * si cambia su estado, no podemos asumirlo inmutable). 37 * 38 * INVALIDACIÃN: por sincronización (mensaje {tipo:'SINCRONIZAR'} desde 39 * cache.js) â se borran cf-paginas y cf-pdfs y el cliente re-descarga todo. 40 * También se purga automáticamente al navegar a pages-login (= logout/cambio 41 * de usuario): jamás dejamos datos financieros cacheados de una sesión vieja. 42 *
43 * Para invalidar TODO en un deploy (cambió CSS/JS/estructura): subir VERSION. 44 * âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 45 */ 46'use strict'; 47 48// ââ Versión global de la caché: subirla invalida todos los caches viejos ââââââ 49// v5âv10 (2026-08-06): cambios en pages-tienda (imágenes solo por URL, spinner de 50// carga, destello de éxito, stock de variaciones "en stock" por defecto, guardia 51// anti-SKU duplicado, panel responsive con scroll/footer sticky, atributos GLOBALES 52// para swatches de YITH y generación de variaciones robusta [guarda atributos del 53// padre antes de generar + reporta errores]) â invalida el JS/CSS viejo cacheado. 54// v13 (2026-08-06): paneles off-canvas de tienda mobile-fix â safe-area-inset-top 55// en el header (no lo tapa el reloj/notch del iPhone), ancho edge-to-edge sin 56// desborde, X con área táctil grande y cierre con gesto (swipe a la derecha). 57// v14 (2026-08-08): módulo Agenda (CRM + Calendario) â nueva sección en el aside, 58// CSS nav-agenda + variable --mod-agenda, y pantallas pages-agenda-panel/ 59// contactos/calendario. Invalida el JS/CSS viejo cacheado (aside, variables.css). 60// v15 (2026-08-08): Facturación â comprobantes de cobro + constancia de recepción 61// (modal en pages-facturacion, uploader de firma en el perfil). Invalida el JS viejo. 62// v16 (2026-08-08): Agenda â Informes (editor Markdown + importar con IA + PDF de 63// marca en página blanca). Nueva pantalla pages-agenda-informes + nav. Invalida caché. 64// v17 (2026-08-08): Agenda â asistente "Jarvis" con IA en el panel (análisis de 65// contactos + agenda). Actualiza pages-agenda-panel. Invalida el JS viejo. 66// v18 (2026-08-08): Agenda â tipos de contacto configurables por el usuario 67// (modal "Tipos", filtros/badges/KPIs dinámicos) + Condición IVA como dropdown 68// con opción "Otro". Actualiza pages-agenda-contactos/panel. Invalida el JS viejo. 69// v19 (2026-08-08): Agenda â página pública de reservas (Calendly). Config en el 70// Panel + página pública /reserva/<slug> (fuera de caché). Invalida el JS del panel. 71// v20 (2026-08-08): Agenda â informes/reuniones en la ficha del contacto, permisos 72// por autor (solo el creador edita/borra), y estadÃsticas premium en el Panel 73// (gráficos Chart.js). Invalida el JS de contactos/informes/panel. 74// v21 (2026-08-08): Facturación ligada al contacto por contactoId (selector en el 75// form + vincular/desvincular en la ficha) + automatización de facturación mensual 76// (recurrencia por contacto, cron, link público /doc/:token, wa.me, semi-auto 77// "listo para facturar" al pagar). Invalida el JS de contactos/setFacturacion. 78// v22 (2026-08-08): acción "Asociar a contacto" por fila en la tabla de 79// Facturación (PUT /facturacion/:id/contacto + modal). Invalida pages-facturacion. 80// v23 (2026-08-08): panel lateral de acciones (Ãcono lápiz) que consolida los 81// Ãconos de cada fila â mobile-first (en mobile el row solo muestra el lápiz). 82// Invalida pages-facturacion. 83// v24 (2026-08-08): el row muestra SOLO el lápiz en TODOS los tamaños; las demás 84// acciones se acceden desde el panel lateral (también en desktop). Invalida CSS. 85// v25 (2026-08-08): vista propia del cliente con gráficos + análisis automático 86// (estable/riesgo) + BCRA cacheado; segmentación en el panel. Invalida contactos/panel. 87// v26 (2026-08-08): FIX routing â /pages-cliente?c=<id> (query, no ruta anidada) 88// para no romper los links relativos del aside; toasts + animaciones sutiles. 89// v27 (2026-08-08): segmentación del panel rediseñada (conversión % + donut + 90// chips/lista prolijos), base para el módulo de Marketing. Invalida panel. 91// v28 (2026-08-08): módulo Tickets (bandeja agnóstica + estadÃsticas + vinculación 92// Chatwoot open-source; webhook /webhooks/tickets/:token). Nueva pantalla + nav. 93// v29 (2026-08-08): Tickets â widget web PROPIO (/tickets/widget.js, un snippet 94// gratis sin terceros) + tickets asociables a contactos e informes + aparecen en 95// la ficha del cliente. Invalida pages-agenda-tickets/cliente. 96// v30 (2026-08-09): Facturación recurrente â tipo de documento + modo borrador con 97// cola "Pendientes de confirmar" (editar montos, enviar/almacenar) en Facturación 98// y en la ficha del cliente + accesos directos para generar detalle/factura/informe 99// desde el contacto. Invalida pages-facturacion/cliente/agenda-contactos/informes. 100// v32 (2026-08-13): Tickets â adjuntos (subir/descargar/eliminar imágenes y docs) + 101// resolución vÃa informe (botón "Crear informe de resolución" prellenado desde el 102// ticket) + envÃo del informe por mail con destinos rápidos (Cliente/Equipo/Otro) y 103// mail de equipo configurable. Invalida pages-agenda-tickets/informes. 104// v33 (2026-08-13): Operaciones y Servicios â filtro de perÃodo (semanal/mensual/ 105// anual/interanual/histórico) que gobierna el gasto total, el treemap/torta, Top 106// CategorÃas y el Resumen por CategorÃa (dinámico), con variación vs perÃodo previo 107// en cada categorÃa (estilo KPIs del index). Helpers de perÃodo + createPerio
107dControl 108// en ag-grid-shared.js/css. Invalida pages-operation/services + ag-grid-shared. 109// v35 (2026-08-15): Vista "Resumen" mobile-first (/auth/pages-resumen, misma 110// fuente de datos que index) + bienvenida reducida que deriva según dispositivo + 111// Ritmo de consumo con bandas ajustadas por inflación, meta de ahorro y cuota 112// proyectada. Invalida index, pages-welcome, pages-credit, setFacturacion, 113// setFacturaElectronica, pages-agenda-informes/calendario y el aside. 114// v36 (2026-08-15): Mi Tienda â lector de código de barras (pistola USB + cámara 115// con BarcodeDetector nativo) en el SKU del producto, y pestaña CAJA (punto de 116// venta: escaneo â ticket â QR de pago). Panel admin â nivel "oculto" y módulos 117// base de CashFlow configurables. Splash de marca en la bienvenida. 118// Invalida pages-tienda, pages-admin, pages-welcome, index y el aside. 119// v37 (2026-08-15): Ritmo â "ahorro proyectado" pasa a ser ingreso â objetivo 120// (lo que sobra), no el recorte contra el promedio. Caja â espejo opcional con 121// WooCommerce: la venta de mostrador crea el pedido, descuenta stock y entra a 122// CashFlow por el camino habitual. Invalida index, pages-resumen y pages-tienda. 123// v38 (2026-08-15): Mi Tienda â CAJA DE AUTOPAGO pública (/caja/<token>): el 124// cliente escanea sus productos en una pantalla del local, paga con QR de MP y 125// el webhook cierra la venta (pedido en WooCommerce + stock). Panel: activar la 126// pantalla, link rotable y logo de la tienda. Invalida pages-tienda. 127// v39 (2026-08-15): TICKET de compra (numeración por tienda, PDF 80mm, vista 128// pública por QR y envÃo por mail) + alta opt-in del cliente al CRM desde el 129// ticket + LIBRO DE OPERACIONES con totales y export CSV. Créditos: capital 130// tomado vs. revaluado. Todo documentado en Ayuda. 131// Invalida pages-tienda, pages-credit y pages-help. 132// v40 (2026-08-18): Ritmo de consumo â la meta de ahorro muestra en plata lo que 133// hace (gasto proyectado del mes y ahorro total sobre el ingreso), la barra 134// explica cada lÃnea con un globo propio (hover y tap) y pinta rayado el tramo 135// de la cuota con el % que ocupa del presupuesto. Invalida index. 136// v41 (2026-08-21): Facturación anti-duplicado â clave de idempotencia en los 137// formularios de emisión (Factura C y detalle de servicios), botón trabado 138// mientras hay un pedido en vuelo y aviso cuando el servidor frena un 139// duplicado. Invalida setFacturaElectronica, setFacturacion y pages-facturacion. 140// v45 (2026-08-21): Informes â editor WYSIWYG (TinyMCE) en lugar del textarea de 141// Markdown; el contenido se sigue guardando en Markdown (puente con turndown). 142// Invalida pages-agenda-informes, que quedó cacheada rota en v44 (se cargaba 143// tinymce.min.js dos veces y el editor no montaba). 144// v46 (2026-08-21): Informes â pegar Markdown crudo en el editor lo convierte a 145// formato real (marked en el paste) en vez de dejar los `##`/`**` literales. 146// v47 (2026-08-21): Fases 0 y 1 del reordenamiento modular. Dos cambios de URL 147// de assets, los dos obligan a invalidar (un cliente con caché v46 tiene las 148// URLs viejas guardadas y, sin subir la versión, las sigue pidiendo y come 404): 149// 1. Prefijo canónico único /assets/* â los alias /js/* y /vendor/* ya no 150// existen en el server. 151// 2. Mi Tienda pasó a src/modules/tienda/ y sus CSS/JS ahora se sirven en 152// /m/tienda/* en vez de /assets/{css,js}/pages/pages-tienda*. 153// Ver docs/ARQUITECTURA_MODULAR.md §4.3. 154// v48 (2026-08-21): Fases 2 a 5 â los 12 modulos pasaron a src/modules/*. Sus 155// CSS y JS ahora se sirven en /m/<modulo>/* (antes /assets/{css,js}/pages/*), y 156// se sumo la libreria compartida /assets/js/lib/*. Cambian URLs de assets en 157// TODAS las pantallas de modulo: invalidacion obligatoria. 158// v49-v50 (2026-08-25): Creditos â asistente contable con IA por credito (boton, 159// panel de indicadores y render de markdown en pages-credit) + linea "Prom. 12m 160// ahorrado" en los KPIs Neto Disponible y Remanente del index. Invalida el CSS 161// del modulo creditos y el JS del index. 162// v51 (2026-08-25): pages-login rediseñado con la estetica del splash de 163// bienvenida (fondo de marca, circuitos lime/violeta con trazado en bucle) y 164// baja del video de fondo + particulas. Invalida pages-login.css.
165// v52 (2026-08-25): Creditos â chat de seguimiento sobre el informe del 166// asistente contable (hilo, input y estilos nuevos en pages-credit). Invalida 167// el CSS/JS del modulo creditos. 168// v53 (2026-08-25): Creditos â boton Compartir del informe (CortexIA, WhatsApp, 169// mail, copiar, descargar .md) + pages-chat-ia levanta el informe enviado desde 170// otro modulo. Invalida el CSS/JS de creditos y la vista de CortexIA. 171// v54 (2026-08-25): aside â "Mi Tienda" pasa de Principal a Vinculaciones 172// (encabezando las tiendas vinculadas) y "Resumen" queda solo-mobile (en 173// escritorio duplicaba a "Mis finanzas"). Invalida el CSS del sidebar. 174// v55 (2026-08-26): login/registro/recuperacion â links en lila de marca (el 175// lime no se leia en tema claro) y la tarjeta se fuerza oscura sobre la regla 176// global `.card{background:var(--bg-card)!important}` de layout.css, que en 177// claro la volvia blanca con texto blanco. Invalida los 3 CSS de auth. 178// v56 (2026-08-26): KPIs del index con su detalle propio (posible ahorro del mes 179// / ahorro promedio mensual); libreria compartida assets/js/lib/voz.js (dictado 180// en cualquier input de IA) + su CSS en style.css; carátula del PDF de Informes 181// con el color Fondo del manual de marca; opcion "Guardar en Informes" en el 182// menu Compartir del asistente de creditos. Invalida style.css, el JS de lib y 183// el CSS/JS de creditos. 184// v69 (2026-09-01): selector de modelo de IA â chip con el LLM en uso, sus 185// limites y el cambio de modelo debajo de los inputs de IA (chat y panel 186// flotante), y los topes del plan + el modelo de cada key en el panel 187// "Disponibilidad de IA" del header. Assets nuevos: /assets/js/ai-modelo.js y 188// /assets/css/ai-modelo.css; invalida ai-estado.js, el CSS/JS de cortexia y 189// head/script. 190// v71 (2026-09-04): IA compartida administrable desde el panel Administrador 191// (interruptor + limite por usuario + techo global). El panel "Disponibilidad 192// de IA" y el chip de modelo ahora distinguen "apagada" de "sin cuota" e 193// "ilimitada": invalida ai-estado.js y ai-modelo.js. 194// v74 (2026-09-09): reuniones con recordatorio configurable ("avisame X antes") 195// y agendado desde la ficha del cliente. Assets nuevos: /m/agenda/js/reuniones.js; 196// invalida pages-agenda-calendario y pages-cliente. 197// v75 (2026-09-09): invitados por reunión (varios contactos del CRM + mails 198// sueltos), que viajan al link de Google y al recordatorio. Assets nuevos: 199// /m/agenda/css/reuniones.css; invalida reuniones.js y las dos pantallas. 200// v76 (2026-09-09): segundo camino para agendar â .ics generado en el navegador 201// (lo abre la app de calendario del sistema) junto al botón de Google. Invalida 202// reuniones.js, pages-cliente-reuniones.js y las dos pantallas. 203// v77 (2026-09-12): CRM · Análisis con actividad, registro de horas y demanda 204// por cliente (/m/agenda/css/pages-agenda-panel.css, /m/agenda/js/pages-agenda- 205// panel.js y pages-agenda-actividad.js); módulo Automatizaciones n8n (/m/n8n/*); 206// Ritmo de consumo con ritmo habitual del mes (/assets/js/ritmo-habitual.js, 207// invalida index, pages-resumen y pages-resumen.css). 208// v78 (2026-09-14): Ritmo de consumo â compara lo CARGADO con lo que se suele 209// tener cargado (curva por momento de carga), cierre Bornhuetter-Ferguson y 210// alerta por escenario más favorable, para que cargar tarde o de a tandas no 211// dispare la proyección. Invalida /assets/js/ritmo-habitual.js. 212// v79 (2026-09-16): diagramas Mermaid â /assets/js/lib/diagramas.js (nuevo, global), 213// /m/agenda/js/informe-diagramas.js e informe-preparar.js (nuevos), editor de 214// Informes, y el chat de CortexIA (cortex-chat.js, pages-chat-ia-0/1.js, 215// aianalytics.js). La página intermedia del PDF (/auth/crm/informe/:id/preparar) 216// no se cachea: sin red no tiene sentido y solo redirige. 217// v80 (2026-09-16): Ritmo de consumo â meta de ahorro fijada por MONTO (lápiz: 218// gasto máximo o ahorro, y el % sale solo). Invalida /assets/js/ritmo-habitual.js 219// (metaFijada / anclaEnPesos), index y pages-resumen. 220// v81 (2026-09-17): módulo Automatizaciones (n8n) completo â pantalla /pages-n8n 221// con sus 7 scripts y CSS en /m/n8n/*, entrada nueva en el aside y en el resumen 222// mobile, ficha en Ayuda y lÃmite por plan en el panel Administrador. Además, 223// opt-out del sonido global por request (fetch(url,{sonido:false})) para los
224// refrescos automáticos: invalida /assets/js/sound.js, el aside, index, 225// pages-resumen, pages-resumen.css, pages-help, pages-admin y cache.js. 226// v83 (2026-09-18): Informes con la sintaxis de Obsidian (callouts, ==resaltado==, 227// [[enlaces]], tareas, notas al pie, propiedades) y pantalla rediseñada mobile-first. 228// Nuevos: /m/agenda/js/obsidian-md.js, informe-obsidian.js, pages-agenda-informes.js 229// y /m/agenda/css/pages-agenda-informes.css; invalida pages-agenda-informes y pages-help. 230// v86âv88 (2026-10-02): Informes â barra de TinyMCE sticky por CSS, autoguardado, 231// vista previa del PDF sin guardar y vista gráfica de relaciones con el componente 232// nuevo /assets/js/lib/grafo.js (global, reutilizable). 233// v89 (2026-10-02): diagramas â /assets/js/lib/diagramas.js saca las marcas HTML de 234// formato y ajusta el ancho de las etiquetas a la palabra más larga; la vista previa 235// del PDF dibuja sus diagramas (informe-diagramas / pages-agenda-informes). 236// v90 (2026-10-02): Informes â importar una bóveda/carpeta/.zip/varios .md de una vez 237// (nuevo /m/agenda/js/informe-importar-boveda.js; JSZip lazy desde jsDelivr). 238// v91 (2026-10-02): tablas de Informes â el ancho de columna ajustado en el editor 239// viaja en la fila separadora del Markdown (obsidian-md.js) y lo respeta el PDF. 240var VERSION = 'v92'; 241var CACHE_ESTATICOS = 'cf-estaticos-' + VERSION; 242var CACHE_PAGINAS = 'cf-paginas-' + VERSION; 243var CACHE_PDFS = 'cf-pdfs-' + VERSION; 244// Lista completa de caches "nuestros" de esta versión (para limpiar los ajenos). 245var CACHES_VIGENTES = [CACHE_ESTATICOS, CACHE_PAGINAS, CACHE_PDFS]; 246 247// ââ CDNs de assets que sà cacheamos (jQuery, DataTables, fuentesâ¦) ââââââââââââ 248// Todo otro origen externo (PayPal, Gravitec, APIs) pasa directo a la red: 249// son dinámicos o llevan lógica de pago/push que no debe servirse vieja. 250var HOSTS_CDN_PERMITIDOS = [ 251 'code.jquery.com', 252 'cdn.datatables.net', 253 'cdn.jsdelivr.net', 254 'cdnjs.cloudflare.com', 255 'fonts.googleapis.com', 256 'fonts.gstatic.com' 257]; 258 259// ââ Páginas que NUNCA se cachean (auth, admin, errores) âââââââââââââââââââââââ 260// pages-login además dispara la purga total (es el logout de la app). 261var PAGINAS_EXCLUIDAS = [ 262 '/auth/pages-login', 263 '/auth/pages-register', 264 '/auth/pages-recovery', 265 '/auth/pages-confirm', 266 '/auth/pages-error-login', 267 '/auth/pages-error-404', 268 '/auth/pages-admin' 269]; 270 271// Extensiones que identifican un asset estático (se sirven desde varias rutas 272// raÃz por los múltiples express.static de index.js, asà que filtramos por 273// extensión y no por prefijo de carpeta). 274var REGEX_ESTATICO = /\.(css|js|mjs|png|jpe?g|gif|svg|webp|ico|woff2?|ttf|eot|map)$/i; 275 276/* âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 277 CICLO DE VIDA 278 âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ */ 279 280self.addEventListener('install', function () { 281 // Activar de inmediato la versión nueva sin esperar a que se cierren pestañas. 282 self.skipWaiting(); 283}); 284 285self.addEventListener('activate', function (event) { 286 event.waitUntil( 287 // Migración de versión: borrar cualquier cache nuestro que no sea de la 288 // versión vigente (equivalente a la "migración automática" de esquema). 289 caches.keys().then(function (nombres) { 290 return Promise.all( 291 nombres 292 .filter(function (n) { return n.indexOf('cf-') === 0 && CACHES_VIGENTES.indexOf(n) === -1; }) 293 .map(function (n) { return caches.delete(n); }) 294 ); 295 }).then(function () { 296 // Tomar control de las pestañas abiertas sin esperar una recarga. 297 return self.clients.claim(); 298 }) 299 ); 300}); 301 302/* âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 303 MENSAJES â la capa de acceso (cache.js) habla con el SW por acá 304 âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ */ 305 306self.addEventListener('message', function (event) { 307 var msg = event.data || {}; 308 309 // SINCRONIZAR = invalidación total de datos: se pisa lo viejo. 310 // (Los estáticos no se borran: se revalidan solos en segundo plano y borrar 311 // ~2 MB de vendors para re-bajarlos en cada sync serÃa puro desperdicio.) 312 if (msg.tipo === 'SINCRONIZAR') { 313 event.waitUntil( 314 purgarDatos().then(function () { 315 // Respondemos por el MessageChannel que abrió cache.js, para que la UI 316 // sepa que la purga terminó antes de arrancar la precarga. 317 if (event.ports && event.ports[0]) event.ports[0].postMessage({ ok: true }); 318 }) 319 ); 320 } 321}); 322 323/** Borra los caches de DATOS (páginas + PDFs). Los estáticos quedan. */ 324function purgarDatos() { 325 return Promise.all([caches.delete(CACHE_PAGINAS), caches.delete(CACHE_PDFS)]); 326} 327 328/* âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 329 FETCH â el "router" de estrategias: decide qué tabla y qué estrategia 330 corresponde a cada request 331 âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ */ 332 333self.addEventListener('fetch', function (event) { 334 var req = event.request; 335 336 // Solo GET: los POST/PUT (crear operaciones, emitir facturasâ¦) van SIEMPRE 337 // a la red â nunca hay que responder una escritura desde caché. 338 if (req.method !== 'GET') return; 339 340 var url; 341 try { url = new URL(req.url); } catch (e) { return; } 342 343 // Ignorar esquemas raros (chrome-extension://, etc.). 344 if (url.protocol !== 'http:' && url.protocol !== 'https:') return; 345
346 var esMismoOrigen = (url.origin === self.location.origin); 347 348 // El propio SW y el manifest se dejan al navegador (Chrome maneja su update). 349 if (esMismoOrigen && (url.pathname === '/sw.js')) return; 350 351 // uPrime embebido (/uPrime/*) es otra app con su propio service worker: este 352 // no lo toca (si no, cachearÃa sus páginas como si fueran de CashFlow). 353 if (esMismoOrigen && /^\/uprime(\/|$)/i.test(url.pathname)) return; 354 355 // ââ 1) PDFs de facturación â "contenido on demand" âââââââââââââââââââââââââ 356 // Se cachean recién cuando el usuario los abre (primer GET). Con red se 357 // sirve el fresco (el PDF de un detalle cambia con su estado); sin red, el 358 // último que vio. 359 if (esMismoOrigen && /^\/auth\/facturacion\/.+\/pdf$/.test(url.pathname)) { 360 event.respondWith(redPrimeroConFallback(req, CACHE_PDFS, false)); 361 return; 362 } 363 364 // ââ 2) Navegaciones (HTML de pantallas) â la "lista" âââââââââââââââââââââââ 365 if (req.mode === 'navigate' && esMismoOrigen) { 366 // Logout / vuelta al login: purgar los datos cacheados de la sesión. 367 // (waitUntil corre en paralelo, no demora la navegación.) 368 if (url.pathname === '/auth/pages-login') { 369 event.waitUntil(purgarDatos()); 370 return; // el login siempre va directo a la red, sin caché 371 } 372 // Páginas sensibles o de error: red directa, sin tocar caché. 373 if (PAGINAS_EXCLUIDAS.indexOf(url.pathname) !== -1) return; 374 // Página intermedia de los PDF con diagramas: solo dibuja y redirige. 375 if (/^\/auth\/crm\/informe\/[^/]+\/preparar$/.test(url.pathname)) return; 376 // Webhooks u otras rutas fuera de /auth y raÃz no son pantallas de la app. 377 if (url.pathname.indexOf('/auth/') !== 0 && url.pathname !== '/') return; 378 379 event.respondWith(redPrimeroConFallback(req, CACHE_PAGINAS, true)); 380 return; 381 } 382 383 // ââ 3) Assets estáticos (propios o de CDN permitida) â eficiencia ââââââââââ 384 var esCdnPermitida = HOSTS_CDN_PERMITIDOS.indexOf(url.hostname) !== -1; 385 if ((esMismoOrigen || esCdnPermitida) && REGEX_ESTATICO.test(url.pathname)) { 386 event.respondWith(cachePrimeroRevalidando(req)); 387 return; 388 } 389 390 // Todo lo demás (APIs JSON, orÃgenes externos no listados) pasa directo. 391}); 392 393/* âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 394 ESTRATEGIAS 395 âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ */ 396 397/** 398 * NETWORK-FIRST con fallback a caché (páginas y PDFs). 399 * Con red: se sirve lo fresco y se guarda copia local ("visitar = cachear"). 400 * Sin red: se sirve la última copia guardada; si tampoco hay, la pantalla 401 * offline embebida (solo para navegaciones). 402 */ 403function redPrimeroConFallback(req, nombreCache, esNavegacion) { 404 return fetch(req) 405 .then(function (respuesta) { 406 // Solo cacheamos respuestas 200 REALES de la página pedida: 407 // - `redirected` filtra el caso "sesión vencida â redirect al login" 408 // (si no, guardarÃamos el HTML del login bajo la URL de la pantalla). 409 if (respuesta.ok && !respuesta.redirected) { 410 var copia = respuesta.clone(); 411 caches.open(nombreCache).then(function (cache) { cache.put(req, copia); }); 412 } 413 return respuesta; 414 }) 415 .catch(function () { 416 // Falló la red â leer local (el corazón del modo offline). 417 return caches.match(req).then(function (guardada) { 418 if (guardada) return guardada; 419 if (esNavegacion) return paginaOffline(); 420 // Un PDF nunca visto y sin red: error de red genuino. 421 return Response.error(); 422 }); 423 }); 424} 425 426/** 427 * CACHE-FIRST con revalidación en segundo plano (stale-while-revalidate). 428 * Para estáticos: responde al instante desde el dispositivo y, en paralelo, 429 * baja la versión nueva para la próxima. Elimina los requests repetidos de 430 * CSS/JS/imágenes en cada navegación.
431 */ 432function cachePrimeroRevalidando(req) { 433 return caches.open(CACHE_ESTATICOS).then(function (cache) { 434 return cache.match(req).then(function (guardada) { 435 // cache:'no-cache' = revalidar contra el server salteando la caché HTTP 436 // del navegador (los estáticos se sirven con Cache-Control de 1 dÃa): 437 // si el archivo no cambió es un 304 sin body (baratÃsimo); si cambió 438 // (deploy nuevo) baja la versión fresca al instante en vez de esperar 439 // a que venza el maxAge. 440 var refresco = fetch(req, { cache: 'no-cache' }) 441 .then(function (respuesta) { 442 // Las respuestas de CDN sin CORS llegan "opaque" (status 0): también 443 // sirven para offline, asà que las guardamos igual. 444 if (respuesta && (respuesta.ok || respuesta.type === 'opaque')) { 445 cache.put(req, respuesta.clone()); 446 } 447 return respuesta; 448 }) 449 .catch(function () { return guardada; }); // sin red: no pasa nada, ya respondimos 450 return guardada || refresco; 451 }); 452 }); 453} 454 455/** 456 * Pantalla mÃnima para una navegación sin red Y sin copia local. 457 * HTML embebido para no depender de ningún archivo (estamos offline). 458 */ 459function paginaOffline() { 460 var html = 461 '<!doctype html><html lang="es"><head><meta charset="utf-8">' + 462 '<meta name="viewport" content="width=device-width,initial-scale=1">' + 463 '<title>Sin conexión â CashFlow</title>' + 464 '<style>body{margin:0;min-height:100vh;display:flex;align-items:center;justify-content:center;' + 465 'background:#0F1420;color:#E2E8F0;font-family:system-ui,-apple-system,sans-serif;text-align:center;padding:24px}' + 466 '.caja{max-width:340px}h1{font-size:1.35rem;margin:0 0 .5rem}p{color:#94A3B8;font-size:.9rem;line-height:1.55}' + 467 'button{margin-top:1.2rem;background:#A855F7;color:#fff;border:0;border-radius:10px;padding:.7rem 1.6rem;' + 468 'font-size:.95rem;font-weight:600;cursor:pointer}</style></head><body><div class="caja">' + 469 '<div style="font-size:2.4rem">ð¡</div>' + 470 '<h1>Sin conexión</h1>' + 471 '<p>Esta pantalla todavÃa no está guardada en tu dispositivo. Las pantallas que visitaste ' + 472 'o sincronizaste antes sà están disponibles offline.</p>' + 473 '<button onclick="location.reload()">Reintentar</button>' + 474 '</div></body></html>'; 475 return new Response(html, { status: 200, headers: { 'Content-Type': 'text/html; charset=utf-8' } }); 476}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.