75 lines
2.6 KiB
JavaScript
75 lines
2.6 KiB
JavaScript
// Cálculos portados de herramientas/sync_pipe.js (ETL estático) — misma lógica, servidor en vivo.
|
|
|
|
export const CARTERA = ["Up-Selling", "Cross-Selling", "Plan de Cuenta"];
|
|
|
|
export const PESO_PLAN = {
|
|
"Plan A - En firma": 0.9,
|
|
"Plan B - En Negociación": 0.6,
|
|
"Plan C - En seguimiento": 0.3,
|
|
"Plan D - Recalificar": 0.15,
|
|
"Sin plan de cierre": 0.1,
|
|
};
|
|
|
|
// Los 4 tipos que cuentan para cumplimiento de meta (ver CLAUDE.md — se excluyen renovaciones y SAC).
|
|
export const TIPOS_META = ["Venta - Cliente Nuevo", "Up-Selling", "Cross-Selling", "Plan de Cuenta"];
|
|
|
|
// Ejecutivos de venta reales (ver CLAUDE.md). Gabriela Sánchez y Ashley Bermúdez aparecen
|
|
// en los datos de Dynamics pero no son ejecutivas de venta — se excluyen del selector.
|
|
export const EJECUTIVOS_VENTA = [
|
|
"Mirna Obando Morras",
|
|
"Maria Auxiliadora Marenco Abea",
|
|
"Denis Urroz Manzanares",
|
|
"Daybellys Perez Sequeira",
|
|
"Martha Somarriba Sirias",
|
|
];
|
|
|
|
export function segmentoDe(tipo) {
|
|
return CARTERA.includes(tipo) ? "Cartera Activa" : "Clientes Nuevos";
|
|
}
|
|
|
|
// Agrupa arr por o[key], contando y sumando valfn(o). Ordena por conteo descendente.
|
|
export function agg(arr, key, valfn) {
|
|
const m = new Map();
|
|
arr.forEach((o) => {
|
|
const k = o[key] || "(sin dato)";
|
|
if (!m.has(k)) m.set(k, { n: 0, v: 0 });
|
|
const e = m.get(k);
|
|
e.n++;
|
|
e.v += valfn ? valfn(o) : 0;
|
|
});
|
|
return [...m.entries()].map(([k, x]) => ({ k, ...x })).sort((a, b) => b.n - a.n);
|
|
}
|
|
|
|
export function bucketActividad(diasSinActividad) {
|
|
if (diasSinActividad == null) return "Sin dato";
|
|
if (diasSinActividad <= 14) return "Activa";
|
|
if (diasSinActividad <= 30) return "Tibia";
|
|
if (diasSinActividad <= 60) return "Fría";
|
|
return "Congelada";
|
|
}
|
|
|
|
// row: objeto ya con etiquetas resueltas (tipo, plan como texto, no option-set numérico).
|
|
// faseInfo: { fase, started } | undefined, de alfa_stagedurations.
|
|
// today: Date, para calcular días en fase de forma determinística.
|
|
export function derivePipelineRow(row, faseInfo, today) {
|
|
const plan = row.plan || "Sin plan de cierre";
|
|
const peso = PESO_PLAN[plan] ?? 0.1;
|
|
const est = row.est || 0;
|
|
const diasEnFase = faseInfo?.started
|
|
? Math.round((today - new Date(faseInfo.started)) / 86400000)
|
|
: null;
|
|
const estanc = (row.dsa != null && row.dsa > 14) || (diasEnFase != null && diasEnFase > 30);
|
|
|
|
return {
|
|
...row,
|
|
plan,
|
|
peso,
|
|
fc: +(est * peso).toFixed(2),
|
|
estanc,
|
|
faseActual: faseInfo?.fase || null,
|
|
diasEnFase,
|
|
actividad: bucketActividad(row.dsa),
|
|
segmento: segmentoDe(row.tipo),
|
|
mesCierre: row.close ? row.close.slice(0, 7) : null,
|
|
};
|
|
}
|