diff --git a/src/components/AdTrackerClient.tsx b/src/components/AdTrackerClient.tsx index 67883b4..abd3dfe 100644 --- a/src/components/AdTrackerClient.tsx +++ b/src/components/AdTrackerClient.tsx @@ -123,12 +123,20 @@ function aggregate(rows: AdDailyRow[]): PlatformKpis { const impressions = active.reduce((a, r) => a + Number(r.impressions || 0), 0); const clicks = active.reduce((a, r) => a + Number(r.clicks || 0), 0); const conversions = active.reduce((a, r) => a + convOf(r), 0); + // Round once after sum (Ads Manager style; Google stores fractional daily). + const conversionsRounded = Math.round(conversions * 100) / 100; const ads = new Set( active .map((r) => String(r.ad_id || campaignName(r) || '').trim()) .filter((n) => n && n !== 'Sin nombre') ).size; - return rates({ spend, impressions, clicks, conversions, ads }); + return rates({ + spend, + impressions, + clicks, + conversions: Math.round(conversionsRounded), + ads, + }); } function byCampaign(rows: AdDailyRow[]): CampaignRow[] { @@ -161,6 +169,7 @@ function byCampaign(rows: AdDailyRow[]): CampaignRow[] { return [...map.values()] .map((c) => ({ ...c, + conversions: Math.round(c.conversions * 100) / 100, cpl: c.conversions > 0 ? c.spend / c.conversions : null, })) .sort((a, b) => b.spend - a.spend); diff --git a/src/lib/ad-creatives-meta.ts b/src/lib/ad-creatives-meta.ts index cb99033..0bc5408 100644 --- a/src/lib/ad-creatives-meta.ts +++ b/src/lib/ad-creatives-meta.ts @@ -2,6 +2,7 @@ import { resolveMetaAccessToken } from '@/lib/ads-discovery'; import { serviceHeaders, supabaseUrl } from '@/lib/agency'; +import { leadFromMetaActions } from '@/lib/meta-leads'; const META_GRAPH = 'https://graph.facebook.com/v21.0'; @@ -434,18 +435,9 @@ async function fetchMetaAdLevelInsights( for (const row of raw.data || []) { const adId = str(row.ad_id); if (!adId) continue; - let conversions = 0; - for (const a of row.actions || []) { - const t = (a.action_type || '').toLowerCase(); - if ( - t === 'offsite_conversion.fb_pixel_complete_registration' || - t === 'lead' || - t === 'onsite_conversion.lead_grouped' || - t.includes('complete_registration') - ) { - conversions += n(a.value); - } - } + // Same metric as ad_daily / Ads Manager «Clientes potenciales» + // (do not sum lead + lead_grouped + registration — they overlap ~3×). + const conversions = leadFromMetaActions(row.actions); out.push({ adId, adName: str(row.ad_name), diff --git a/src/lib/ads-sync.ts b/src/lib/ads-sync.ts index a65f26b..69ff32c 100644 --- a/src/lib/ads-sync.ts +++ b/src/lib/ads-sync.ts @@ -2,6 +2,7 @@ import { serviceHeaders, supabaseUrl } from '@/lib/agency'; import { resolveMetaAccessToken } from '@/lib/ads-discovery'; +import { leadFromMetaActions, nMeta } from '@/lib/meta-leads'; const META_GRAPH = 'https://graph.facebook.com/v21.0'; const GADS_API = 'https://googleads.googleapis.com/v22'; @@ -55,10 +56,7 @@ type AdDailyInsert = { platform_leads: number; }; -function n(v: unknown): number { - const x = Number(v); - return Number.isFinite(x) ? x : 0; -} +const n = nMeta; function isoWeekMonday(d: Date): string { const x = new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate())); @@ -67,19 +65,6 @@ function isoWeekMonday(d: Date): string { return x.toISOString().slice(0, 10); } -function leadFromMetaActions( - actions?: { action_type?: string; value?: string }[] -): number { - if (!actions?.length) return 0; - // Exact Ads Manager «Clientes potenciales» for Meta Leads CRM campaigns. - // Do not fall back to lead / lead_grouped / pixel — those inflate past the - // Ads Manager period total when mixed with registration actions. - const primary = actions.find( - (a) => a.action_type === 'offsite_complete_registration_add_meta_leads' - ); - return primary ? n(primary.value) : 0; -} - async function googleAccessToken(): Promise { const refresh = process.env.GOOGLE_ADS_REFRESH_TOKEN?.trim() || diff --git a/src/lib/meta-leads.ts b/src/lib/meta-leads.ts new file mode 100644 index 0000000..70ba1d3 --- /dev/null +++ b/src/lib/meta-leads.ts @@ -0,0 +1,20 @@ +/** Meta Ads «Clientes potenciales» ↔ platform_leads (same metric as Ads Manager). */ + +export function nMeta(v: unknown): number { + const x = Number(v); + return Number.isFinite(x) ? x : 0; +} + +/** + * Exact Ads Manager «Clientes potenciales» for Meta Leads CRM campaigns. + * Do not sum lead / lead_grouped / pixel — those overlap and inflate totals. + */ +export function leadFromMetaActions( + actions?: { action_type?: string; value?: string }[] +): number { + if (!actions?.length) return 0; + const primary = actions.find( + (a) => a.action_type === 'offsite_complete_registration_add_meta_leads' + ); + return primary ? nMeta(primary.value) : 0; +}