fix: cohort CAC/ROAS desde deals Clientify y más contraste en dashboards
Los totales de Métricas usaban solo ad_daily (cohort a 0). Ahora suman deals won; selectores y tipografía ganan contraste sobre fondo blanco. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -402,6 +402,83 @@ export function metricTotalsFromDaily(rows: AdDailyRow[]): MetricTotals {
|
||||
};
|
||||
}
|
||||
|
||||
/** Won Clientify deals — source of truth for cohort CAC / revenue / ROAS. */
|
||||
export type DealWonRow = {
|
||||
amount?: number | null;
|
||||
won_at?: string | null;
|
||||
stage?: string | null;
|
||||
tenant_id?: string | null;
|
||||
};
|
||||
|
||||
export function filterDealsForRange(
|
||||
deals: DealWonRow[],
|
||||
since: string | null,
|
||||
until: string | null
|
||||
): DealWonRow[] {
|
||||
const won = deals.filter((d) => !d.stage || d.stage === 'won');
|
||||
if (!since || !until) return won;
|
||||
return won.filter((d) => {
|
||||
const day = (d.won_at || '').slice(0, 10);
|
||||
if (!day) return false;
|
||||
return day >= since && day <= until;
|
||||
});
|
||||
}
|
||||
|
||||
/** Overlay CRM won deals onto ad spend/leads totals. */
|
||||
export function withCohortFromDeals(
|
||||
base: MetricTotals,
|
||||
deals: DealWonRow[]
|
||||
): MetricTotals {
|
||||
const dealCount = deals.length;
|
||||
const revenue = deals.reduce((a, d) => a + n(d.amount), 0);
|
||||
return {
|
||||
...base,
|
||||
deals: dealCount,
|
||||
revenue,
|
||||
cac: dealCount > 0 ? base.spend / dealCount : 0,
|
||||
roas: base.spend > 0 ? revenue / base.spend : 0,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Inject won-deal cohort into weekly rows so WoW/MoM/YoY include CRM closes.
|
||||
* Ads sync leaves cohort_* at 0; without this, CAC/ROAS deltas stay empty.
|
||||
*/
|
||||
export function mergeDealsIntoWeekly(
|
||||
weekly: KpiWeeklyRow[],
|
||||
deals: DealWonRow[]
|
||||
): KpiWeeklyRow[] {
|
||||
const byWeek = new Map<string, { deals: number; revenue: number }>();
|
||||
for (const d of deals) {
|
||||
if (d.stage && d.stage !== 'won') continue;
|
||||
const day = (d.won_at || '').slice(0, 10);
|
||||
if (!day) continue;
|
||||
const dt = new Date(`${day}T00:00:00Z`);
|
||||
if (Number.isNaN(dt.getTime())) continue;
|
||||
const week = isoDate(weekStart(dt));
|
||||
const cur = byWeek.get(week) || { deals: 0, revenue: 0 };
|
||||
cur.deals += 1;
|
||||
cur.revenue += n(d.amount);
|
||||
byWeek.set(week, cur);
|
||||
}
|
||||
if (!byWeek.size) return weekly;
|
||||
|
||||
const out = weekly.map((r) => ({ ...r }));
|
||||
for (const [week, v] of byWeek) {
|
||||
out.push({
|
||||
week: `${week}T00:00:00+00:00`,
|
||||
channel: 'crm',
|
||||
total_spend: 0,
|
||||
total_leads: 0,
|
||||
cohort_deals_won: v.deals,
|
||||
cohort_revenue_won: Math.round(v.revenue * 100) / 100,
|
||||
cac_cohort: 0,
|
||||
roas_cohort: 0,
|
||||
});
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/** CRM lead row used for channel attribution cross. */
|
||||
export type CrmLeadRow = {
|
||||
source?: string | null;
|
||||
|
||||
Reference in New Issue
Block a user