feat: Zona de marca tipo Estilo/Brief + brandbooks desde ads/RRSS

UI con nav lateral numerada y panel editable; parse de brandbook JSON string;
enrich desde Meta/Google/IG/FB/web/CSS para que app.4ventures.es muestre el kit.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
4Ventures
2026-10-15 03:23:23 +02:00
parent 102c100427
commit 3cb3ad1db1
10 changed files with 3967 additions and 69 deletions

View File

@@ -0,0 +1,107 @@
/**
* POST — auto-fill Zona de marca for one or all tenants from available sources.
* Body: { tenant_id?, slug?, all?: true, force_seed?: true, dry_run?: true }
*/
import { NextRequest, NextResponse } from 'next/server';
import { createClient } from '@/lib/supabase/server';
import { isAgencyRole, userRole } from '@/lib/agency';
import { resolveActiveTenant } from '@/lib/tenant-context';
import {
enrichAllTenantBrandbooks,
enrichTenantBrandbook,
} from '@/lib/brandbook-enrich';
import { ensureCreativesDriveFolders } from '@/lib/creatives-drive';
import { serviceHeaders, supabaseUrl } from '@/lib/agency';
export const dynamic = 'force-dynamic';
export const maxDuration = 120;
export async function POST(req: NextRequest) {
const supabase = await createClient();
const {
data: { user },
} = await supabase.auth.getUser();
if (!user) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
if (!isAgencyRole(userRole(user))) {
return NextResponse.json({ error: 'Solo agencia' }, { status: 403 });
}
let body: {
tenant_id?: string;
slug?: string;
all?: boolean;
force_seed?: boolean;
dry_run?: boolean;
ensure_drive?: boolean;
} = {};
try {
body = await req.json();
} catch {
body = {};
}
const forceSeed = Boolean(body.force_seed);
const dryRun = Boolean(body.dry_run);
const ensureDrive = body.ensure_drive !== false;
if (body.all) {
const results = await enrichAllTenantBrandbooks({
forceSeed,
dryRun,
slugFilter: body.slug,
});
if (ensureDrive && !dryRun) {
for (const r of results) {
if (!r.ok) continue;
await ensureCreativesDriveFolders(r.tenantId, r.slug);
}
}
return NextResponse.json({
ok: true,
count: results.length,
results,
});
}
let tenantId = body.tenant_id?.trim() || '';
if (!tenantId && body.slug) {
const headers = serviceHeaders();
const base = supabaseUrl();
if (headers && base) {
const r = await fetch(
`${base}/rest/v1/tenants?slug=eq.${encodeURIComponent(
body.slug
)}&select=id&limit=1`,
{ headers, cache: 'no-store' }
);
const rows = (await r.json()) as { id: string }[];
tenantId = rows[0]?.id || '';
}
}
if (!tenantId) {
const ctx = await resolveActiveTenant(user);
tenantId = ctx.tenantId || '';
}
if (!tenantId) {
return NextResponse.json(
{ error: 'Indica tenant_id, slug, all:true o selecciona cliente' },
{ status: 400 }
);
}
const result = await enrichTenantBrandbook({
tenantId,
forceSeed,
dryRun,
});
let drive: { ok: boolean; ads?: string; rrss?: string; warning?: string } | null =
null;
if (ensureDrive && !dryRun && result.ok) {
drive = await ensureCreativesDriveFolders(result.tenantId, result.slug);
}
return NextResponse.json({ ok: result.ok, result, drive });
}