feat: LinkedIn editorial, accesos agencia estables y deshacer en historial

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>
This commit is contained in:
4Ventures
2026-10-16 04:15:51 +02:00
parent 3b57cdce53
commit 4a1abd3318
54 changed files with 6400 additions and 527 deletions

View File

@@ -0,0 +1,96 @@
/**
* Asegura app_metadata.role=admin (o team) para operadores @4ventures.es.
* npx tsx scripts/heal_agency_roles.ts
*/
import { config } from 'dotenv';
import { resolve } from 'path';
import { createClient } from '@supabase/supabase-js';
config({ path: resolve(process.cwd(), '.env.local') });
const HUB_TENANT = '0b3c5fc0-6901-4caf-b26a-b6a0201d8541';
const COMPANY = '4Ventures';
const SLUG = '4ventures-hub';
const FORCE_ADMIN = [
'marketing@4ventures.es',
'marcos@4ventures.es',
'belen@4ventures.es',
'rebecca@4ventures.es',
'victor@4ventures.es',
'agencia@4ventures.es',
'ops@4ventures.es',
];
async function main() {
const url = process.env.NEXT_PUBLIC_SUPABASE_URL?.trim();
const key = process.env.SUPABASE_SERVICE_ROLE_KEY?.trim();
if (!url || !key) throw new Error('Faltan env Supabase');
const admin = createClient(url, key, {
auth: { persistSession: false, autoRefreshToken: false },
});
const emails = new Set(FORCE_ADMIN.map((e) => e.toLowerCase()));
let page = 1;
let fixed = 0;
let scanned = 0;
for (;;) {
const { data, error } = await admin.auth.admin.listUsers({
page,
perPage: 200,
});
if (error) throw error;
const users = data.users || [];
if (!users.length) break;
for (const u of users) {
scanned += 1;
const email = (u.email || '').toLowerCase();
if (!email.endsWith('@4ventures.es') && !emails.has(email)) continue;
const appRole = String(u.app_metadata?.role || '');
if (appRole === 'admin' || appRole === 'team') {
// Ya OK; repara tenant/company si faltan
if (u.app_metadata?.tenant_id && u.app_metadata?.company) continue;
}
if (appRole === 'client') {
console.log(`skip client explícito: ${email}`);
continue;
}
const role = emails.has(email) ? 'admin' : 'team';
const { error: updErr } = await admin.auth.admin.updateUserById(u.id, {
app_metadata: {
...(u.app_metadata || {}),
role,
tenant_id: u.app_metadata?.tenant_id || HUB_TENANT,
company: u.app_metadata?.company || COMPANY,
slug: u.app_metadata?.slug || SLUG,
},
user_metadata: {
...(u.user_metadata || {}),
role,
company: COMPANY,
},
});
if (updErr) {
console.error(`FAIL ${email}:`, updErr.message);
} else {
fixed += 1;
console.log(`OK ${email} → app_metadata.role=${role}`);
}
}
if (users.length < 200) break;
page += 1;
}
console.log(`Scanned ${scanned}, fixed ${fixed}`);
}
main().catch((e) => {
console.error(e);
process.exit(1);
});