/** * After agency Google OAuth: match Drive + GA4 + GSC (+ Google Ads if env) * into every client ficha and sync Orgánico. */ import { serviceHeaders, supabaseUrl } from '@/lib/agency'; import { applyMatches, getAgencyOAuthStatus, listDriveFolders, matchFoldersToTenants, } from '@/lib/google-drive'; import { applyOrganicMatches, listGa4Properties, listGscSites, matchOrganicToTenants, syncAllConnectedOrganic, } from '@/lib/google-organic'; import { discoverAndMatch, googleAdsConfigured, } from '@/lib/ads-discovery'; export type GoogleBootstrapResult = { ok: boolean; email?: string | null; drive?: { linked: number; folders: number; ambiguous: number; error?: string }; organic?: { ga4Linked: number; gscLinked: number; ga4Visible: number; gscVisible: number; daysWritten: number; tenantsSynced: number; error?: string; }; googleAds?: { linked: number; accounts: number; error?: string; }; message: string; }; export async function bootstrapAgencyGoogle(opts?: { /** Also pull Google Ads MCC accounts into fichas (needs GOOGLE_ADS_* env). */ includeGoogleAds?: boolean; /** Sync organic_daily after matching (default true). */ syncOrganic?: boolean; organicDays?: number; }): Promise { const status = await getAgencyOAuthStatus(); if (!status.connected) { return { ok: false, message: 'Conecta Google (marketing@) en Agencia primero.', }; } const headers = serviceHeaders(); if (!headers) { return { ok: false, message: 'no service role' }; } const tRes = await fetch( `${supabaseUrl()}/rest/v1/tenants?select=id,name,slug&order=name.asc`, { headers, cache: 'no-store' } ); if (!tRes.ok) { return { ok: false, email: status.email, message: `tenants: ${(await tRes.text()).slice(0, 160)}`, }; } const tenants = (await tRes.json()) as { id: string; name: string; slug: string; }[]; const result: GoogleBootstrapResult = { ok: true, email: status.email, message: '', }; // --- Drive --- try { const folders = await listDriveFolders(); const matches = await matchFoldersToTenants(tenants, folders); const applied = await applyMatches(matches); result.drive = { linked: applied.linked, folders: folders.length, ambiguous: matches.filter((m) => m.status === 'ambiguous').length, }; } catch (e) { result.drive = { linked: 0, folders: 0, ambiguous: 0, error: e instanceof Error ? e.message : 'drive error', }; } // --- GA4 + GSC --- try { const [ga4, gsc] = await Promise.all([ status.hasAnalytics !== false ? listGa4Properties() : Promise.resolve([]), status.hasSearchConsole !== false ? listGscSites() : Promise.resolve([]), ]); const matches = await matchOrganicToTenants(tenants, ga4, gsc); const applied = await applyOrganicMatches( matches.filter( (m) => (m.ga4 && m.ga4.score >= 50) || (m.gsc && m.gsc.score >= 40) ) ); let daysWritten = 0; let tenantsSynced = 0; if (opts?.syncOrganic !== false) { const sync = await syncAllConnectedOrganic({ days: opts?.organicDays ?? 28, }); daysWritten = sync.results.reduce((a, r) => a + (r.daysWritten || 0), 0); tenantsSynced = sync.synced; } result.organic = { ga4Linked: applied.ga4, gscLinked: applied.gsc, ga4Visible: ga4.length, gscVisible: gsc.length, daysWritten, tenantsSynced, }; } catch (e) { result.organic = { ga4Linked: 0, gscLinked: 0, ga4Visible: 0, gscVisible: 0, daysWritten: 0, tenantsSynced: 0, error: e instanceof Error ? e.message : 'organic error', }; } // --- Google Ads (separate token / MCC env) --- if (opts?.includeGoogleAds !== false && googleAdsConfigured()) { try { const disc = await discoverAndMatch({ apply: true, providers: ['google_ads'], }); const g = disc.google; result.googleAds = { linked: g?.linked || 0, accounts: g?.accounts || 0, error: g?.error, }; } catch (e) { result.googleAds = { linked: 0, accounts: 0, error: e instanceof Error ? e.message : 'gads error', }; } } const parts: string[] = []; if (result.drive) { parts.push( result.drive.error ? `Drive: ${result.drive.error}` : `Drive ${result.drive.linked}/${result.drive.folders}` ); } if (result.organic) { parts.push( result.organic.error ? `Orgánico: ${result.organic.error}` : `GA4 ${result.organic.ga4Linked} · GSC ${result.organic.gscLinked} · ${result.organic.daysWritten} días sync` ); } if (result.googleAds) { parts.push( result.googleAds.error ? `Ads: ${result.googleAds.error}` : `Google Ads ${result.googleAds.linked}/${result.googleAds.accounts}` ); } result.message = parts.join(' · ') || 'Sin cambios'; result.ok = !result.drive?.error || !result.organic?.error; // ok if at least one channel succeeded result.ok = Boolean( (result.drive && !result.drive.error) || (result.organic && !result.organic.error) || (result.googleAds && !result.googleAds.error) ); return result; }