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:
2026-08-28 04:43:02 +00:00
parent 04de7d703f
commit 9e04b76616
16 changed files with 697 additions and 4 deletions

View File

@@ -24,6 +24,8 @@ export interface ResumenReportes {
total: number
disponibles: number
porDesarrollo: { desarrollo: string; total: number; porStatus: Record<string, number> }[]
/** Conteo por etapa del pipeline de venta, en el orden oficial de las 10 etapas */
porEtapa: { etapa: string; total: number }[]
}
cobranza: {
planes: { activos: number; liquidados: number; cancelados: number }
@@ -74,6 +76,19 @@ export interface EtapaFunnel {
valor: number
}
/**
* Datos de la gráfica "Pipeline de ventas por etapa": las 10 etapas del
* pipeline de unidad en su orden oficial (el API ya las manda ordenadas).
*/
export function datosPipeline(resumen: ResumenReportes): EtapaFunnel[] {
return resumen.unidades.porEtapa.map((e) => ({ etapa: e.etapa, valor: e.total }))
}
/** Unidades actualmente en una etapa dada del pipeline (p. ej. 'Notaría'); 0 si no hay */
export function unidadesEnEtapa(resumen: ResumenReportes, etapa: string): number {
return resumen.unidades.porEtapa.find((e) => e.etapa === etapa)?.total ?? 0
}
/**
* Datos del funnel comercial: Leads → Oportunidades → Aparto o más → Ganado.
* "Aparto o más" es acumulado (Aparto + Ganado) para que el funnel no suba.