feat(mejoras): 14 mejoras al CRM Frasa

Fase 1 - UI: dark mode, skeletons, toasts
Fase 2 - Filtros: avanzados Mi día, búsqueda global Ctrl+K
Fase 3 - Datos: export Excel, notas apartados, auditoría
Fase 4 - Dashboard: gráficas vendedor + métricas uso
Fase 5 - Responsive: tablets optimizadas
Fase 6 - PWA: IndexedDB offline, push notifications

Backend: 3 tablas nuevas, 6 services, 6 endpoints nuevos
721 API tests + fronts verdes.
This commit is contained in:
2026-09-07 06:04:49 +00:00
parent 388e1da338
commit c77b4c0628
170 changed files with 5802 additions and 637 deletions

View File

@@ -1,6 +1,7 @@
import type { Unidad, TipoPropuesta } from '../types'
import { INVENTARIO_SEED } from '../data/unidades'
import { conAuth, manejar401 } from './auth'
import { guardarUnidades, cargarUnidadesLocal } from './offline-db'
// TODO produccion: '/api' same-origin detras de NGINX
const API_URL = import.meta.env.VITE_API_URL ?? '/api'
@@ -24,9 +25,15 @@ export async function getUnidades(): Promise<GetUnidadesResult> {
manejar401(resp)
if (!resp.ok) throw new Error(`HTTP ${resp.status}`)
const unidades = (await resp.json()) as Unidad[]
// Cache en IDB para uso offline
void guardarUnidades(unidades)
return { unidades, usandoSeed: false }
} catch (err) {
console.warn('[api] No se pudo obtener el inventario del API; usando seed local.', err)
console.warn('[api] No se pudo obtener el inventario del API; intentando cache offline.', err)
const cached = await cargarUnidadesLocal()
if (cached && Array.isArray(cached) && cached.length > 0) {
return { unidades: cached as Unidad[], usandoSeed: true }
}
return { unidades: INVENTARIO_SEED, usandoSeed: true }
}
}

View File

@@ -0,0 +1,33 @@
import { openDB, type IDBPDatabase } from 'idb'
const DB_NAME = 'frasa-cotizador'
const DB_VERSION = 1
async function getDB(): Promise<IDBPDatabase> {
return openDB(DB_NAME, DB_VERSION, {
upgrade(db) {
if (!db.objectStoreNames.contains('unidades')) db.createObjectStore('unidades')
if (!db.objectStoreNames.contains('cotizaciones')) db.createObjectStore('cotizaciones')
},
})
}
export async function guardarUnidades(unidades: unknown[]) {
const db = await getDB()
await db.put('unidades', unidades, 'actual')
}
export async function cargarUnidadesLocal() {
const db = await getDB()
return db.get('unidades', 'actual')
}
export async function guardarCotizaciones(cotizaciones: unknown[]) {
const db = await getDB()
await db.put('cotizaciones', cotizaciones, 'historial')
}
export async function cargarCotizacionesLocal() {
const db = await getDB()
return db.get('cotizaciones', 'historial')
}