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

@@ -0,0 +1,25 @@
import { describe, expect, it } from 'vitest'
import { folioCotizacion, vigenciaCotizacion, VIGENCIA_DIAS } from './folio.js'
describe('folioCotizacion', () => {
it('deriva COT-XXXXXX (6 hex mayúsculas) del uuid', () => {
expect(folioCotizacion('3f8a1b2c-9d4e-4f5a-b6c7-d8e9f0a1b2c3')).toBe('COT-3F8A1B')
})
it('es determinista: mismo id, mismo folio', () => {
const id = 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee'
expect(folioCotizacion(id)).toBe(folioCotizacion(id))
})
it('tolera ids sin hex suficiente usando el id en mayúsculas', () => {
expect(folioCotizacion('xyz')).toBe('COT-XYZ')
})
})
describe('vigenciaCotizacion', () => {
it('suma VIGENCIA_DIAS días naturales a la emisión', () => {
const creada = new Date('2026-08-28T12:00:00')
const limite = vigenciaCotizacion(creada)
expect(limite.getDate() - creada.getDate()).toBe(VIGENCIA_DIAS)
})
})

View File

@@ -0,0 +1,18 @@
/**
* Folio corto de cotización, derivado de forma determinista del id (uuid).
* Espejo de apps/cotizador/src/lib/folio.ts (mantener sincronizados).
*/
export function folioCotizacion(id: string): string {
const hex = id.replace(/[^0-9a-fA-F]/g, '').toUpperCase()
return `COT-${(hex || id.toUpperCase()).slice(0, 6)}`
}
/** Días naturales de vigencia de la cotización (igual que en el PDF del cotizador) */
export const VIGENCIA_DIAS = 3
/** Fecha límite de vigencia: emisión + VIGENCIA_DIAS días naturales */
export function vigenciaCotizacion(creada: Date): Date {
const limite = new Date(creada)
limite.setDate(limite.getDate() + VIGENCIA_DIAS)
return limite
}