feat(unidades): pipeline de 10 etapas por unidad + sync desde Reporte_EstatusVenta
- Columna Etapa en Monday Unidades (10 labels oficiales), 155 unidades pobladas - sync-etapas.ts idempotente (--archivo/--dry-run/--semilla) con traspasos Vendido→Escrituración y Escriturado→SAC como actividades (sin retroactivas) - API: etapa en unidades + porEtapa en /api/reportes/resumen - Dashboard: gráfica pipeline por etapa + KPI en notaría; cotizador muestra etapa - 28 cambios aplicados desde Reporte_EstatusVenta (8 avances, 20 nuevas); contradicciones retenidas para revisión humana (documentadas) - Tests: api 168, dashboards 9, cotizador 71
This commit is contained in:
@@ -22,7 +22,7 @@ function op(id: string, etapa: string): OportunidadMonday {
|
||||
return { id, nombre: `Cliente ${id}`, createdAt: '2026-08-01T09:00:00Z', etapa, unidadCodigo: 'U-101', precioFinal: 0 }
|
||||
}
|
||||
|
||||
function unidad(codigo: string, desarrollo: string, status: string): Unidad {
|
||||
function unidad(codigo: string, desarrollo: string, status: string, etapa: string | null = null): Unidad {
|
||||
return {
|
||||
codigo,
|
||||
modelo: 'M1',
|
||||
@@ -35,6 +35,7 @@ function unidad(codigo: string, desarrollo: string, status: string): Unidad {
|
||||
status,
|
||||
desarrollo,
|
||||
mesesParaFirma: null,
|
||||
etapa,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -149,6 +150,44 @@ describe('construirResumen', () => {
|
||||
])
|
||||
})
|
||||
|
||||
it('cuenta unidades por etapa del pipeline, en el orden oficial de las 10 etapas', () => {
|
||||
const r = construirResumen(
|
||||
{
|
||||
...DATOS_BASE,
|
||||
unidades: [
|
||||
unidad('U-101', 'ATELIER', 'VENDIDO', 'Notaría'),
|
||||
unidad('U-102', 'ATELIER', 'VENDIDO', 'Notaría'),
|
||||
unidad('U-103', 'ATELIER', 'ESCRITURADO', 'Escriturado'),
|
||||
unidad('U-104', 'ATELIER', 'APARTADO', 'Apartado'),
|
||||
unidad('U-105', 'ATELIER', 'DISPONIBLE'), // sin etapa: no cuenta
|
||||
],
|
||||
},
|
||||
AHORA,
|
||||
)
|
||||
expect(r.unidades.porEtapa).toEqual([
|
||||
{ etapa: 'Apartado', total: 1 },
|
||||
{ etapa: 'Reserva', total: 0 },
|
||||
{ etapa: 'Vendido', total: 0 },
|
||||
{ etapa: 'Integración', total: 0 },
|
||||
{ etapa: 'En Análisis', total: 0 },
|
||||
{ etapa: 'Autorizado', total: 0 },
|
||||
{ etapa: 'Notaría', total: 2 },
|
||||
{ etapa: 'Escriturado', total: 1 },
|
||||
{ etapa: 'Entrega', total: 0 },
|
||||
{ etapa: 'PostVenta', total: 0 },
|
||||
])
|
||||
})
|
||||
|
||||
it('manda al final de porEtapa los labels que no son del pipeline oficial', () => {
|
||||
const r = construirResumen(
|
||||
{ ...DATOS_BASE, unidades: [unidad('U-101', 'ATELIER', 'VENDIDO', 'Etapa rara')] },
|
||||
AHORA,
|
||||
)
|
||||
const porEtapa = r.unidades.porEtapa
|
||||
expect(porEtapa).toHaveLength(11)
|
||||
expect(porEtapa[10]).toEqual({ etapa: 'Etapa rara', total: 1 })
|
||||
})
|
||||
|
||||
it('cobra: planes por status, pagos vencidos y monto en mora de planes activos', () => {
|
||||
const r = construirResumen(
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user