feat: RRSS hub estilo Miia + Ad Tracker por cliente
Replica agua-miia/rrss (hub, archivo, octubre 2026 con 27 piezas) y añade Ad Tracker por tenant al estilo Brava (periodos, Meta+Google, CPL/clics, campañas). Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
153
src/lib/ad-tracker-range.ts
Normal file
153
src/lib/ad-tracker-range.ts
Normal file
@@ -0,0 +1,153 @@
|
||||
/** Date presets for Ad Tracker (Brava-style). No date-fns dependency. */
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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]}`;
|
||||
}
|
||||
|
||||
export function resolveAdRange(
|
||||
preset: RangePreset,
|
||||
custom?: { since?: string; until?: string }
|
||||
): DateRange {
|
||||
const now = new Date();
|
||||
const today = new Date(
|
||||
Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate())
|
||||
);
|
||||
|
||||
let since = today;
|
||||
let until = today;
|
||||
|
||||
switch (preset) {
|
||||
case 'today':
|
||||
break;
|
||||
case 'yesterday':
|
||||
since = new Date(today);
|
||||
since.setUTCDate(since.getUTCDate() - 1);
|
||||
until = new Date(since);
|
||||
break;
|
||||
case 'last_7d':
|
||||
since = new Date(today);
|
||||
since.setUTCDate(since.getUTCDate() - 6);
|
||||
break;
|
||||
case 'last_14d':
|
||||
since = new Date(today);
|
||||
since.setUTCDate(since.getUTCDate() - 13);
|
||||
break;
|
||||
case 'last_28d':
|
||||
since = new Date(today);
|
||||
since.setUTCDate(since.getUTCDate() - 27);
|
||||
break;
|
||||
case 'last_30d':
|
||||
since = new Date(today);
|
||||
since.setUTCDate(since.getUTCDate() - 29);
|
||||
break;
|
||||
case 'this_week':
|
||||
since = startOfWeekMon(today);
|
||||
until = today;
|
||||
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 = today;
|
||||
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':
|
||||
since = custom?.since ? new Date(custom.since + 'T00:00:00Z') : since;
|
||||
until = custom?.until ? new Date(custom.until + 'T00:00:00Z') : until;
|
||||
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';
|
||||
}
|
||||
@@ -18,6 +18,12 @@ export type AdDailyRow = {
|
||||
platform_leads?: number;
|
||||
impressions?: number;
|
||||
clicks?: number;
|
||||
platform?: string;
|
||||
campaign_name?: string;
|
||||
campaign?: string;
|
||||
ad_name?: string;
|
||||
conversions?: number;
|
||||
tenant_id?: string;
|
||||
};
|
||||
|
||||
export type MetricTotals = {
|
||||
|
||||
Reference in New Issue
Block a user