fix: KPIs — ocultar WoW >7d, YoY con histórico completo, ventas en nº
Pagina ad_daily (~420d) para que YoY no quede vacío; Ventas muestra conteo de deals (importe Clientify en hint/columna Importe). Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -214,7 +214,7 @@ export function totalsForDayRange(
|
||||
|
||||
/**
|
||||
* WoW / MoM / YoY relative to the selected date range (not “today”).
|
||||
* - WoW: previous window of the same length
|
||||
* - WoW: previous window of the same length (only meaningful if span ≤ 7d)
|
||||
* - MoM: previous calendar month if the range is a full month; else prior equal-length window
|
||||
* - YoY: same calendar dates one year earlier
|
||||
*/
|
||||
@@ -273,10 +273,11 @@ export function buildRangeDeltas(
|
||||
const deltas = {} as Record<keyof MetricTotals, Delta>;
|
||||
for (const k of keys) {
|
||||
deltas[k] = {
|
||||
wow: pctChange(cur[k], wow[k]),
|
||||
// WoW only when the selected window is ≤ 7 days
|
||||
wow: span <= 7 ? pctChange(cur[k], wow[k]) : null,
|
||||
mom: pctChange(cur[k], mom[k]),
|
||||
yoy: pctChange(cur[k], yoy[k]),
|
||||
wowBase: wow[k],
|
||||
wowBase: span <= 7 ? wow[k] : null,
|
||||
momBase: mom[k],
|
||||
yoyBase: yoy[k],
|
||||
};
|
||||
@@ -284,6 +285,11 @@ export function buildRangeDeltas(
|
||||
return deltas;
|
||||
}
|
||||
|
||||
/** Day count of an inclusive ISO range (for UI comparative rules). */
|
||||
export function rangeDaySpan(since: string, until: string): number {
|
||||
return inclusiveDaySpan(since, until);
|
||||
}
|
||||
|
||||
export function formatEur(v: number): string {
|
||||
return `${v.toLocaleString('es-ES', {
|
||||
minimumFractionDigits: 0,
|
||||
@@ -836,20 +842,20 @@ export type AttributionChannelRow = {
|
||||
gap: number;
|
||||
cplPlatform: number;
|
||||
cplCrm: number;
|
||||
/** Contacts marked cliente (by CRM source) in the period. */
|
||||
cierres: number;
|
||||
/** Won-deal revenue attributed to channel (or sin-atribuir). */
|
||||
/** Won deals count (Clientify stage=won) attributed to channel. */
|
||||
ventas: number;
|
||||
/** Ventas ÷ inversión (0 if no spend). */
|
||||
/** Won-deal revenue (€) attributed to channel (or sin-atribuir). */
|
||||
importe: number;
|
||||
/** Importe ÷ inversión (0 if no spend). */
|
||||
roas: number;
|
||||
/** Inversión ÷ cierres (0 if no cierres). */
|
||||
/** Inversión ÷ ventas (0 if no ventas). */
|
||||
cac: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* Cross paid platform leads (ad_daily) vs CRM-attributed leads (Clientify).
|
||||
* Gap = CRM − platform (positive ⇒ CRM claims more than the ad UI).
|
||||
* Ventas come from won deals; without deal.source they land in `sin-atribuir`.
|
||||
* Ventas/importe come from won deals; without deal.source they land in `sin-atribuir`.
|
||||
*/
|
||||
export function attributionCross(
|
||||
daily: AdDailyRow[],
|
||||
@@ -862,8 +868,8 @@ export function attributionCross(
|
||||
spend: number;
|
||||
platformLeads: number;
|
||||
crmLeads: number;
|
||||
cierres: number;
|
||||
ventas: number;
|
||||
importe: number;
|
||||
}
|
||||
>();
|
||||
|
||||
@@ -874,8 +880,8 @@ export function attributionCross(
|
||||
spend: 0,
|
||||
platformLeads: 0,
|
||||
crmLeads: 0,
|
||||
cierres: 0,
|
||||
ventas: 0,
|
||||
importe: 0,
|
||||
};
|
||||
map.set(channel, cur);
|
||||
}
|
||||
@@ -893,7 +899,6 @@ export function attributionCross(
|
||||
const channel = normalizeLeadChannel(r.source);
|
||||
const cur = ensure(channel);
|
||||
cur.crmLeads += 1;
|
||||
if (isClienteStatus(r.status)) cur.cierres += 1;
|
||||
}
|
||||
|
||||
for (const d of deals) {
|
||||
@@ -901,7 +906,8 @@ export function attributionCross(
|
||||
const raw = (d.source || '').trim();
|
||||
const channel = raw ? normalizeLeadChannel(raw) : 'sin-atribuir';
|
||||
const cur = ensure(channel);
|
||||
cur.ventas += n(d.amount);
|
||||
cur.ventas += 1;
|
||||
cur.importe += n(d.amount);
|
||||
}
|
||||
|
||||
return [...map.entries()]
|
||||
@@ -913,15 +919,15 @@ export function attributionCross(
|
||||
gap: v.crmLeads - v.platformLeads,
|
||||
cplPlatform: v.platformLeads > 0 ? v.spend / v.platformLeads : 0,
|
||||
cplCrm: v.crmLeads > 0 ? v.spend / v.crmLeads : 0,
|
||||
cierres: v.cierres,
|
||||
ventas: v.ventas,
|
||||
roas: v.spend > 0 ? v.ventas / v.spend : 0,
|
||||
cac: v.cierres > 0 ? v.spend / v.cierres : 0,
|
||||
importe: v.importe,
|
||||
roas: v.spend > 0 ? v.importe / v.spend : 0,
|
||||
cac: v.ventas > 0 ? v.spend / v.ventas : 0,
|
||||
}))
|
||||
.sort(
|
||||
(a, b) =>
|
||||
b.spend - a.spend ||
|
||||
b.ventas - a.ventas ||
|
||||
b.importe - a.importe ||
|
||||
b.crmLeads + b.platformLeads - (a.crmLeads + a.platformLeads)
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user