/** Date presets for Ad Tracker. Data ends at yesterday unless preset is «Hoy». */ export type RangePreset = | 'today' | 'yesterday' | 'last_7d' | 'last_14d' | 'last_28d' | 'last_30d' | 'this_week' | 'last_week' | 'this_month' | 'last_month' | 'custom'; export type DateRange = { since: string; until: string; label: string }; const QUICK: { id: RangePreset; label: string }[] = [ { id: 'today', label: 'Hoy' }, { id: 'yesterday', label: 'Ayer' }, { id: 'last_7d', label: '7 días' }, { id: 'last_14d', label: '14 días' }, { id: 'last_28d', label: '28 días' }, { id: 'last_30d', label: '30 días' }, { id: 'this_week', label: 'Esta semana' }, { id: 'last_week', label: 'Semana pasada' }, { id: 'this_month', label: 'Este mes' }, { id: 'last_month', label: 'Mes pasado' }, ]; export { QUICK as AD_TRACKER_PRESETS }; function iso(d: Date): string { return d.toISOString().slice(0, 10); } /** UTC calendar day for «today». */ export function utcToday(now = new Date()): Date { return new Date( Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate()) ); } /** UTC calendar day for «yesterday». */ export function utcYesterday(now = new Date()): Date { const d = utcToday(now); d.setUTCDate(d.getUTCDate() - 1); return d; } export function isoToday(now = new Date()): string { return iso(utcToday(now)); } export function isoYesterday(now = new Date()): string { return iso(utcYesterday(now)); } /** * Max date allowed in the picker / default until. * Only «Hoy» may include today; everything else ends at yesterday. */ export function maxDataDate(preset: RangePreset, now = new Date()): string { return preset === 'today' ? isoToday(now) : isoYesterday(now); } function startOfWeekMon(d: Date): Date { const x = new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate())); const day = x.getUTCDay(); const diff = day === 0 ? -6 : 1 - day; x.setUTCDate(x.getUTCDate() + diff); return x; } const MONTHS_ES = [ 'ene', 'feb', 'mar', 'abr', 'may', 'jun', 'jul', 'ago', 'sep', 'oct', 'nov', 'dic', ]; export function formatRangeLabel(since: string, until: string): string { const a = since.split('-').map(Number); const b = until.split('-').map(Number); if (a.length !== 3 || b.length !== 3) return `${since} - ${until}`; return `${a[2]} ${MONTHS_ES[a[1] - 1]} ${a[0]} - ${b[2]} ${MONTHS_ES[b[1] - 1]} ${b[0]}`; } function parseIsoDay(s: string): Date | null { if (!/^\d{4}-\d{2}-\d{2}$/.test(s)) return null; const d = new Date(s + 'T00:00:00Z'); return Number.isNaN(d.getTime()) ? null : d; } /** * Resolve a date range. * All presets except «today» end at yesterday (ads warehouse lag). * Custom ranges are clamped so until ≤ yesterday. */ export function resolveAdRange( preset: RangePreset, custom?: { since?: string; until?: string }, now = new Date() ): DateRange { const today = utcToday(now); const yesterday = utcYesterday(now); /** Anchor end for non-today presets. */ const end = yesterday; let since = new Date(end); let until = new Date(end); switch (preset) { case 'today': since = new Date(today); until = new Date(today); break; case 'yesterday': since = new Date(yesterday); until = new Date(yesterday); break; case 'last_7d': since = new Date(end); since.setUTCDate(since.getUTCDate() - 6); until = new Date(end); break; case 'last_14d': since = new Date(end); since.setUTCDate(since.getUTCDate() - 13); until = new Date(end); break; case 'last_28d': since = new Date(end); since.setUTCDate(since.getUTCDate() - 27); until = new Date(end); break; case 'last_30d': since = new Date(end); since.setUTCDate(since.getUTCDate() - 29); until = new Date(end); break; case 'this_week': since = startOfWeekMon(today); until = new Date(end); if (since.getTime() > until.getTime()) { since = new Date(until); } break; case 'last_week': { const thisMon = startOfWeekMon(today); until = new Date(thisMon); until.setUTCDate(until.getUTCDate() - 1); since = startOfWeekMon(until); break; } case 'this_month': since = new Date(Date.UTC(today.getUTCFullYear(), today.getUTCMonth(), 1)); until = new Date(end); if (since.getTime() > until.getTime()) { // 1st of month = today → no complete days yet; show yesterday of prev month since = new Date(until); } break; case 'last_month': { const firstThis = new Date( Date.UTC(today.getUTCFullYear(), today.getUTCMonth(), 1) ); until = new Date(firstThis); until.setUTCDate(until.getUTCDate() - 1); since = new Date(Date.UTC(until.getUTCFullYear(), until.getUTCMonth(), 1)); break; } case 'custom': { const maxUntil = yesterday; let s = parseIsoDay(custom?.since || '') || new Date(end); let u = parseIsoDay(custom?.until || '') || new Date(end); // Clamp: never past yesterday for custom if (u.getTime() > maxUntil.getTime()) u = new Date(maxUntil); if (s.getTime() > u.getTime()) s = new Date(u); since = s; until = u; break; } } const s = iso(since); const u = iso(until); return { since: s, until: u, label: formatRangeLabel(s, u) }; } export function normalizePlatform(p?: string): 'meta' | 'google' | 'other' { const x = (p || '').toLowerCase(); if ( x.includes('meta') || x.includes('facebook') || x.includes('instagram') || x === 'fb' || x === 'ig' ) { return 'meta'; } if (x.includes('google') || x.includes('adwords') || x === 'gads') { return 'google'; } return 'other'; }