Add Zona de marca brandbooks and strategist calendar generation.

Per-tenant brandbooks power ads, RRSS calendars, LinkedIn B2B and blog; Nov calendars now create from trends + client DNA instead of copying history.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
4Ventures
2026-10-10 01:05:15 +02:00
parent 59f42eafe0
commit e49111f63e
13 changed files with 2907 additions and 500 deletions

View File

@@ -0,0 +1,143 @@
/**
* Seed brandbook for every tenant + regenerate Nov 2026 editorial strategy.
* Usage: npx tsx scripts/seed_brandbooks_regen_nov.ts
* Optional: MONTH=2026-11 FORCE_BRANDBOOK=1
*/
import { config } from 'dotenv';
import { resolve } from 'path';
config({ path: resolve(process.cwd(), '.env.local') });
import type { RrssMonthPlan } from '../src/components/RrssMonthView';
import { generateMonthPreview } from '../src/lib/calendar-generate';
import {
BRANDBOOK_DOC_TITLE,
BRANDBOOK_DOC_TYPE,
parseBrandbook,
seedBrandbook,
} from '../src/lib/brandbook';
import { monthLabel } from '../src/lib/calendar-import';
const ym = process.env.MONTH || '2026-11';
const forceBook = process.env.FORCE_BRANDBOOK === '1';
async function main() {
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',
};
const tenants = (await (
await fetch(
`${url}/rest/v1/tenants?select=id,slug,name&order=name.asc`,
{ headers: h }
)
).json()) as { id: string; slug: string; name: string }[];
console.log(`Tenants: ${tenants.length} · month ${ym} · forceBook=${forceBook}`);
for (const t of tenants) {
// --- brandbook ---
const bbRes = await fetch(
`${url}/rest/v1/documents?tenant_id=eq.${t.id}&type=eq.${BRANDBOOK_DOC_TYPE}&select=id,content&order=updated_at.desc&limit=1`,
{ headers: h }
);
const bbRows = (await bbRes.json()) as { id: string; content: unknown }[];
const existing = parseBrandbook(bbRows[0]?.content);
let book = existing;
if (!existing || forceBook) {
book = seedBrandbook(t.slug, t.name);
if (bbRows[0]?.id) {
await fetch(`${url}/rest/v1/documents?id=eq.${bbRows[0].id}`, {
method: 'PATCH',
headers: { ...h, Prefer: 'return=minimal' },
body: JSON.stringify({
content: book,
title: BRANDBOOK_DOC_TITLE,
updated_at: new Date().toISOString(),
}),
});
console.log(`[${t.slug}] brandbook updated`);
} else {
await fetch(`${url}/rest/v1/documents`, {
method: 'POST',
headers: { ...h, Prefer: 'return=minimal' },
body: JSON.stringify({
tenant_id: t.id,
type: BRANDBOOK_DOC_TYPE,
title: BRANDBOOK_DOC_TITLE,
status: 'approved',
content: book,
}),
});
console.log(`[${t.slug}] brandbook created`);
}
} else {
console.log(`[${t.slug}] brandbook exists (skip)`);
}
// --- calendars ---
const calRes = await fetch(
`${url}/rest/v1/documents?tenant_id=eq.${t.id}&type=eq.calendar&select=id,content&order=created_at.desc&limit=48`,
{ headers: h }
);
const docs = (await calRes.json()) as { id: string; content: unknown }[];
const history: RrssMonthPlan[] = [];
let novId: string | null = null;
for (const d of docs) {
const c =
typeof d.content === 'string' ? JSON.parse(d.content) : d.content;
const plan = c as RrssMonthPlan;
if (plan?.month === ym) novId = d.id;
else if (plan?.month && Array.isArray(plan.pieces)) history.push(plan);
}
const result = await generateMonthPreview({
ym,
slug: t.slug,
label: t.name,
history,
brandbook: book,
});
const title = `RRSS ${monthLabel(ym)}`;
if (novId) {
const patch = await fetch(`${url}/rest/v1/documents?id=eq.${novId}`, {
method: 'PATCH',
headers: { ...h, Prefer: 'return=minimal' },
body: JSON.stringify({
content: result.plan,
title,
updated_at: new Date().toISOString(),
}),
});
console.log(
`[${t.slug}] ${ym} patched ${patch.status} · ${result.plan.pieces.length} pcs · ${result.source}`
);
} else {
const ins = await fetch(`${url}/rest/v1/documents`, {
method: 'POST',
headers: { ...h, Prefer: 'return=minimal' },
body: JSON.stringify({
tenant_id: t.id,
type: 'calendar',
title,
status: 'draft',
content: result.plan,
}),
});
console.log(
`[${t.slug}] ${ym} created ${ins.status} · ${result.plan.pieces.length} pcs · ${result.source}`
);
}
}
console.log('Done.');
}
main().catch((e) => {
console.error(e);
process.exit(1);
});