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:
4Ventures
2026-09-24 10:40:12 +02:00
parent 8fda2996b6
commit 21051afda9
20 changed files with 383 additions and 184 deletions

View File

@@ -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;