/** * Portfolio fix: * - Menttum: confirm archived (no longer a client) * - De Tú a Tú Formación ↔ ECP: separate as two active clients (move ECP social/ads) * - Create Talento Scholar Program as sibling of Talento y Deporte * - Sync tenants.drive_folder_id from google_drive integrations * - Seed DNA + enrich brandbooks for affected + active portfolio * * npx tsx scripts/split_tenants_and_fix.ts */ import { config } from 'dotenv'; import { resolve } from 'path'; config({ path: resolve(process.cwd(), '.env.local') }); import { enrichTenantBrandbook } from '../src/lib/brandbook-enrich'; import { ensureCreativesDriveFolders } from '../src/lib/creatives-drive'; const url = process.env.NEXT_PUBLIC_SUPABASE_URL!; const key = process.env.SUPABASE_SERVICE_ROLE_KEY!; const h = { apikey: key, Authorization: `Bearer ${key}`, 'Content-Type': 'application/json', Prefer: 'return=representation', }; async function rest( path: string, init?: RequestInit ): Promise { const r = await fetch(`${url}/rest/v1/${path}`, { ...init, headers: { ...h, ...(init?.headers || {}) }, }); const text = await r.text(); if (!r.ok) throw new Error(`${path} ${r.status}: ${text.slice(0, 400)}`); if (!text) return [] as T; return JSON.parse(text) as T; } async function patchTenant( id: string, patch: Record ): Promise { await rest(`tenants?id=eq.${id}`, { method: 'PATCH', headers: { Prefer: 'return=minimal' }, body: JSON.stringify(patch), }); } async function patchIntegration( tenantId: string, provider: string, patch: Record ): Promise { await rest( `integrations?tenant_id=eq.${tenantId}&provider=eq.${provider}`, { method: 'PATCH', headers: { Prefer: 'return=minimal' }, body: JSON.stringify({ ...patch, updated_at: new Date().toISOString() }), } ); } async function main() { const today = new Date().toISOString().slice(0, 10); const tenants = await rest< { id: string; slug: string; name: string; agency_followed?: boolean; agency_notes?: string | null; drive_folder_id?: string | null; }[] >('tenants?select=id,slug,name,agency_followed,agency_notes,drive_folder_id&order=name.asc'); const bySlug = Object.fromEntries(tenants.map((t) => [t.slug, t])); // ——— 1. Menttum: archived, not a client ——— const menttum = bySlug['menttum']; if (menttum) { await patchTenant(menttum.id, { agency_followed: false, agency_notes: `[archivado ${today}] Ya no es cliente 4Ventures. No enriquecer / no sincronizar creatividades.\n${menttum.agency_notes || ''}`.slice( 0, 4000 ), creatives_enabled: false, }); for (const p of [ 'meta_ads', 'facebook', 'instagram', 'google_ads', 'google_drive', 'ga4', 'search_console', ]) { await patchIntegration(menttum.id, p, { status: 'disconnected', enabled: false, }).catch(() => undefined); } console.log('[menttum] archived + integrations disconnected'); } // ——— 2. Separate De Tú a Tú vs ECP ——— const detu = bySlug['de-tu-a-tu-formacion']; const ecp = bySlug['ecp']; if (!detu || !ecp) throw new Error('missing de-tu-a-tu-formacion or ecp'); // Activate ECP as its own client await patchTenant(ecp.id, { agency_followed: true, name: 'ECP · Escuela de Cuidadores', creatives_enabled: false, default_landing_url: 'https://escueladecuidadoresprofesionales.com', meta_ad_account_id: 'act_1320801442642004', meta_page_id: '111175967913754', meta_ig_user_id: '17841453624785447', agency_notes: `[${today}] Cliente separado de De Tú a Tú Formación. Marca: Escuela de Cuidadores Profesionales (ECP). Meta act_1320801442642004 · IG @escuelaecp · web escueladecuidadoresprofesionales.com. Drive: buscar carpeta cliente ECP/Cuidadores (evitar «Navidad ecp» si no es raíz).`, }); // Move ECP social/ads ownership onto ECP tenant (connect ECP rows) await patchIntegration(ecp.id, 'meta_ads', { status: 'connected', enabled: true, external_account_id: 'act_1320801442642004', label: 'ECP', }); await patchIntegration(ecp.id, 'facebook', { status: 'connected', enabled: true, external_account_id: '111175967913754', label: 'Escuela de cuidadores profesionales', }); await patchIntegration(ecp.id, 'instagram', { status: 'connected', enabled: true, external_account_id: '17841453624785447', label: '@escuelaecp', }); await patchIntegration(ecp.id, 'ga4', { status: 'connected', enabled: true, external_account_id: 'G-82HW868X9L', label: 'Web escueladecuidadoresprofesionales.com - GA4', }); // Detach ECP assets from De Tú a Tú await patchIntegration(detu.id, 'meta_ads', { status: 'disconnected', enabled: false, external_account_id: 'act_413393941247349', label: 'Detuatuformacion (pendiente permiso API system user)', }); await patchIntegration(detu.id, 'facebook', { status: 'disconnected', enabled: false, external_account_id: null, label: 'Facebook Page (pendiente página propia De Tú a Tú)', }); await patchIntegration(detu.id, 'instagram', { status: 'disconnected', enabled: false, external_account_id: null, label: 'Instagram (pendiente cuenta propia De Tú a Tú)', }); await patchTenant(detu.id, { agency_followed: true, name: 'De Tú a Tú Formación', creatives_enabled: false, meta_ad_account_id: 'act_413393941247349', meta_page_id: null, meta_ig_user_id: null, default_landing_url: null, drive_folder_id: '1PWUL2sn18n9bIURwfuc8YIKfhbl1BQfg', agency_notes: `[${today}] Separado de ECP. Marca De Tú a Tú Formación. Google Ads 3183140753 · Drive 08-Detuatu · Meta act_413393941247349 (otorgar ads_read al system user API 4V2030). RRSS/Meta page propias pendientes; ECP ya no se gestiona aquí.`, }); console.log('[detuatu / ecp] separated'); // ——— 3. Create Talento Scholar Program ——— let scholar = bySlug['talento-scholar-program']; if (!scholar) { const created = await rest< { id: string; slug: string; name: string }[] >('tenants', { method: 'POST', body: JSON.stringify({ slug: 'talento-scholar-program', name: 'Talento Scholar Program', agency_followed: true, creatives_enabled: false, agency_notes: `[${today}] Separado de Talento y Deporte. Línea académica / Scholar (becas de estudio USA). Emparejar Meta Ads, IG y Drive propios cuando existan.`, default_landing_url: 'https://talentoydeporte.com', }), }); scholar = created[0]; console.log('[talento-scholar-program] created', scholar?.id); } else { await patchTenant(scholar.id, { agency_followed: true, name: 'Talento Scholar Program', agency_notes: `[${today}] Cliente separado de Talento y Deporte · línea Scholar / becas académicas.\n${scholar.agency_notes || ''}`.slice( 0, 4000 ), }); console.log('[talento-scholar-program] already existed — activated'); } // Seed empty integrations row set is usually done by app on first open; // ensure notes on Talento y Deporte clarify split const tyd = bySlug['talento-y-deporte']; if (tyd) { await patchTenant(tyd.id, { agency_followed: true, name: 'Talento y Deporte', drive_folder_id: '1IwXdvcVFKpHta4jf3nxT7k7qltzSufuL', default_landing_url: 'https://talentoydeporte.com', meta_ad_account_id: 'act_1188310622211934', meta_page_id: '189855164776887', meta_ig_user_id: '17841405373441110', agency_notes: `[${today}] Línea deportiva / becas deportivas USA. Sibling: Talento Scholar Program (académico). Drive 06-Talento y Deporte · IG @talentoydeporte.`, }); console.log('[talento-y-deporte] notes + ids synced'); } // ——— 4. Sync drive_folder_id from google_drive for all followed tenants ——— const driveRows = await rest< { tenant_id: string; external_account_id: string; label?: string }[] >( 'integrations?provider=eq.google_drive&status=eq.connected&select=tenant_id,external_account_id,label' ); for (const row of driveRows) { // Skip bad ECP christmas folder as root — keep label but don't force as client root if name is Navidad if (/navidad/i.test(row.label || '')) { console.log( `[drive] skip suspicious folder for tenant ${row.tenant_id}: ${row.label}` ); continue; } await patchTenant(row.tenant_id, { drive_folder_id: row.external_account_id, }); } console.log(`[drive] synced folder ids for ${driveRows.length} tenants`); // ——— 5. Enrich brandbooks for active portfolio (skip archived) ——— const active = await rest<{ id: string; slug: string; name: string }[]>( 'tenants?agency_followed=neq.false&select=id,slug,name&order=name.asc' ); for (const t of active) { const r = await enrichTenantBrandbook({ tenantId: t.id, forceSeed: true }); const drive = await ensureCreativesDriveFolders(t.id, t.slug); console.log( `[enrich] ${t.slug} ${r.completenessBefore}→${r.completenessAfter} drive=${drive.ok ? 'ok' : drive.warning}` ); } console.log('Done · active tenants', active.length); } main().catch((e) => { console.error(e); process.exit(1); });