Integra estrategias LinkedIn en calendarios (backlog publicado vs pendiente), unifica roles/cookies de vista agencia y permite deshacer cambios de brandbook dejando constancia en el historial. Co-authored-by: Cursor <cursoragent@cursor.com>
273 lines
9.3 KiB
TypeScript
273 lines
9.3 KiB
TypeScript
/**
|
|
* 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<T = unknown>(
|
|
path: string,
|
|
init?: RequestInit
|
|
): Promise<T> {
|
|
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<string, unknown>
|
|
): Promise<void> {
|
|
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<string, unknown>
|
|
): Promise<void> {
|
|
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);
|
|
});
|