feat(cotizador): historial, compartir WhatsApp, folio+QR, comparador de unidades, alertas de inventario

- Mis cotizaciones (guardar con cliente, retomar, nueva) asociadas al vendedor del token
- Compartir: resumen copiable + deep link wa.me; PDF con folio, vigencia y QR
- GET /api/cotizaciones/:id/publico (público, datos mínimos sin PII del cliente)
- Comparador lado a lado de 2 unidades (mismo o distinto desarrollo)
- POST /api/cotizaciones/alertas-inventario: avisa al vendedor si una unidad
  cotizada cambia de estatus (dedup; wire con syncs documentado)
- Fix: labels de propuesta mapeados a los reales del board Oportunidades
- Tests: api 201, cotizador 82
This commit is contained in:
2026-08-28 06:58:10 +00:00
parent bc0f9da8fe
commit 4f31d1c171
24 changed files with 1938 additions and 20 deletions

View File

@@ -12,6 +12,13 @@ const RUTAS_PUBLICAS: ReadonlyArray<{ metodo: string; ruta: string }> = [
{ metodo: 'POST', ruta: '/webhooks/monday' },
]
// Rutas públicas con parámetros (se comparan contra req.path relativo a /api).
// El endpoint público de cotización es el destino del QR del PDF: el cliente
// final lo abre sin sesión; devuelve solo datos comerciales, nunca del cliente.
const PATRONES_PUBLICOS: ReadonlyArray<{ metodo: string; patron: RegExp }> = [
{ metodo: 'GET', patron: /^\/cotizaciones\/[^/]+\/publico$/ },
]
declare module 'express-serve-static-core' {
interface Request {
usuarioToken?: TokenPayload
@@ -28,7 +35,9 @@ export function authMiddleware(req: Request, res: Response, next: NextFunction):
return
}
const publica = RUTAS_PUBLICAS.some((r) => r.metodo === req.method && req.path === r.ruta)
const publica =
RUTAS_PUBLICAS.some((r) => r.metodo === req.method && req.path === r.ruta) ||
PATRONES_PUBLICOS.some((r) => r.metodo === req.method && r.patron.test(req.path))
if (publica) {
next()
return