fix: alinear KPIs con Ad Tracker por días calendario

KPIs filtraba por inicio de semana (omitía días de Sep en la semana
del 31-ago); ahora Mes/7d/30d suman ad_daily día a día como Ad Tracker.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
4Ventures
2026-09-23 12:38:45 +02:00
parent fba025da76
commit fb313fc38f
3 changed files with 126 additions and 50 deletions

View File

@@ -312,3 +312,84 @@ export function resolveWeeklyRows(
}
export { isoDate, weekStart };
/** Same calendar windows as Ad Tracker (inclusive day bounds, UTC). */
export type KpiRangeKey = '7d' | '30d' | 'month' | 'all';
export function resolveKpiDayRange(
range: KpiRangeKey,
now = new Date()
): { since: string; until: string } | null {
const today = new Date(
Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate())
);
const until = isoDate(today);
if (range === 'all') return null;
if (range === 'month') {
const since = new Date(Date.UTC(today.getUTCFullYear(), today.getUTCMonth(), 1));
return { since: isoDate(since), until };
}
const since = new Date(today);
// Inclusive windows: 7d = today-6..today, 30d = today-29..today
since.setUTCDate(since.getUTCDate() - (range === '7d' ? 6 : 29));
return { since: isoDate(since), until };
}
export function filterDailyByDayRange(
rows: AdDailyRow[],
since: string,
until: string
): AdDailyRow[] {
return rows.filter((r) => {
const d = (r.date || '').slice(0, 10);
return d >= since && d <= until;
});
}
/** Per-channel paid totals from ad_daily (calendar days). */
export function channelTotalsFromDaily(rows: AdDailyRow[]): {
channel: string;
spend: number;
leads: number;
deals: number;
revenue: number;
cpl: number;
roas: number;
}[] {
const map = new Map<string, { spend: number; leads: number }>();
for (const r of rows) {
const channel = (r.platform || 'otros').toLowerCase();
const cur = map.get(channel) || { spend: 0, leads: 0 };
cur.spend += n(r.spend);
cur.leads += n(r.platform_leads ?? r.conversions);
map.set(channel, cur);
}
return [...map.entries()]
.map(([channel, v]) => ({
channel,
spend: v.spend,
leads: v.leads,
deals: 0,
revenue: 0,
cpl: v.leads > 0 ? v.spend / v.leads : 0,
roas: 0,
}))
.sort((a, b) => b.spend - a.spend);
}
export function metricTotalsFromDaily(rows: AdDailyRow[]): MetricTotals {
const spend = rows.reduce((a, r) => a + n(r.spend), 0);
const leads = rows.reduce(
(a, r) => a + n(r.platform_leads ?? r.conversions),
0
);
return {
spend,
leads,
deals: 0,
revenue: 0,
cac: 0,
roas: 0,
cpl: leads > 0 ? spend / leads : 0,
};
}