fase 3.4: PWA instalable (manifest e iconos, service worker propio con app shell precacheado y cache versionada por build, API nunca cacheada, fallback offline, aviso de instalacion con guia iOS y banner sin conexion) + branding: perfiles de ticket por instancia (defaults neutros, sin filtrar datos de SKEEN) y Dashboard sin nombre hardcodeado

This commit is contained in:
2026-10-11 13:23:56 +00:00
parent 3c4f1b14d9
commit ab9d84cb67
19 changed files with 574 additions and 17 deletions

View File

@@ -1,10 +1,63 @@
import { defineConfig } from 'vite'
import { defineConfig, type Plugin, type ResolvedConfig } from 'vite'
import react from '@vitejs/plugin-react'
import tailwindcss from '@tailwindcss/vite'
import { createHash } from 'node:crypto'
import { existsSync, readFileSync, readdirSync, writeFileSync } from 'node:fs'
import { resolve } from 'node:path'
// PWA: inyecta en dist/sw.js (1) una versión de caché única por build y
// (2) la lista de assets con hash a precachear. La versión es un hash del
// index.html, el manifest y los nombres de los assets, así que cambia
// exactamente cuando cambia el bundle (y el SW descarta las cachés viejas
// en `activate`). No toca el pipeline `tsc -b && vite build`.
function pwaServiceWorker(): Plugin {
let outDir = 'dist'
return {
name: 'skeen-pwa-service-worker',
apply: 'build',
configResolved(config: ResolvedConfig) {
outDir = config.build.outDir
},
closeBundle() {
const dir = resolve(process.cwd(), outDir)
const swPath = resolve(dir, 'sw.js')
if (!existsSync(swPath)) return
const listAssets = (sub: string): string[] => {
const abs = resolve(dir, sub)
if (!existsSync(abs)) return []
return readdirSync(abs)
.filter((f) => /\.(js|css|woff2?|png|jpe?g|svg|webp|ico|json)$/i.test(f))
.map((f) => `/${sub}/${f}`)
.sort()
}
// Solo /assets (hash): los iconos ya van listados explícitamente en sw.js.
const assets = listAssets('assets')
const read = (file: string): Buffer =>
existsSync(resolve(dir, file)) ? readFileSync(resolve(dir, file)) : Buffer.from('')
const version = createHash('sha256')
.update(read('index.html'))
.update(read('manifest.webmanifest'))
.update(assets.join('|'))
.digest('hex')
.slice(0, 12)
let sw = readFileSync(swPath, 'utf8')
sw = sw.replace('__BUILD_VERSION__', version)
// JSON dentro de un literal de una sola comilla: se quitan comillas
// simples por seguridad (los nombres de Vite no las contienen).
sw = sw.replace('__PRECACHE_ASSETS__', JSON.stringify(assets).replace(/'/g, ''))
writeFileSync(swPath, sw)
this.warn(`PWA service worker: versión de caché ${version}, ${assets.length} assets precacheados`)
},
}
}
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react(), tailwindcss()],
plugins: [react(), tailwindcss(), pwaServiceWorker()],
server: {
port: 5173,
proxy: {