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:
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user