121 lines
3.6 KiB
TypeScript
121 lines
3.6 KiB
TypeScript
import { NextRequest, NextResponse } from 'next/server';
|
|
import { authenticateIntegrationRequest } from '@/lib/integrations-auth';
|
|
import { createClient } from '@supabase/supabase-js';
|
|
import { normalizeCreativeStyle } from '@/lib/creative-style';
|
|
import {
|
|
compileClientSkillFromBrandbook,
|
|
parseBrandbook,
|
|
seedBrandbook,
|
|
type Brandbook,
|
|
} from '@/lib/brandbook';
|
|
import fs from 'fs';
|
|
import path from 'path';
|
|
|
|
export const dynamic = 'force-dynamic';
|
|
|
|
export async function GET(req: NextRequest) {
|
|
const auth = await authenticateIntegrationRequest(req, { requireTenant: true });
|
|
if (!auth.ok) {
|
|
return auth.errorResponse!;
|
|
}
|
|
|
|
const tenantId = auth.tenantId!;
|
|
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!;
|
|
const serviceRoleKey = process.env.SUPABASE_SERVICE_ROLE_KEY!;
|
|
const adminClient = createClient(supabaseUrl, serviceRoleKey);
|
|
|
|
// 1. Obtener slug y creative_style del cliente desde BD
|
|
const { data: tenant, error: tErr } = await adminClient
|
|
.from('tenants')
|
|
.select('id, name, slug, creative_style, settings')
|
|
.eq('id', tenantId)
|
|
.maybeSingle();
|
|
|
|
if (tErr || !tenant) {
|
|
return NextResponse.json({ error: 'Tenant not found' }, { status: 404 });
|
|
}
|
|
|
|
const slug = (tenant.slug || 'bps').toLowerCase().trim();
|
|
const formatParam = (req.nextUrl.searchParams.get('format') || 'estatico').toLowerCase().trim();
|
|
|
|
// 2. Obtener brandbook de documents (fuente central de verdad: Zona de Marca)
|
|
const { data: brandDoc } = await adminClient
|
|
.from('documents')
|
|
.select('content')
|
|
.eq('tenant_id', tenantId)
|
|
.eq('type', 'brandbook')
|
|
.order('updated_at', { ascending: false })
|
|
.limit(1)
|
|
.maybeSingle();
|
|
|
|
let brandbook: Brandbook | null = null;
|
|
if (brandDoc?.content) {
|
|
try {
|
|
const parsedContent =
|
|
typeof brandDoc.content === 'string'
|
|
? JSON.parse(brandDoc.content)
|
|
: brandDoc.content;
|
|
brandbook = parseBrandbook(parsedContent);
|
|
} catch {
|
|
brandbook = null;
|
|
}
|
|
}
|
|
|
|
if (!brandbook) {
|
|
brandbook = seedBrandbook(slug, tenant.name || 'Cliente');
|
|
}
|
|
|
|
// 3. Obtener configuración operativa de Ads (Freepik Mystic, Canva y Reglas de campaña)
|
|
const rawDbStyle =
|
|
tenant.creative_style && Object.keys(tenant.creative_style).length > 0
|
|
? tenant.creative_style
|
|
: (tenant.settings as any)?.creative_style;
|
|
|
|
const operativeStyle = normalizeCreativeStyle(rawDbStyle, tenant.name || 'Cliente', slug);
|
|
|
|
// 4. Compilar skill de cliente a partir de Zona de Marca + Operativa Ads
|
|
let clientSkill = compileClientSkillFromBrandbook(
|
|
brandbook,
|
|
{
|
|
image_ai: operativeStyle.image_ai,
|
|
campaign_rules: operativeStyle.campaign_rules,
|
|
},
|
|
tenant.name || 'Cliente',
|
|
slug
|
|
);
|
|
|
|
// 5. Resolver skill del formato (skills/formats/<format>-4-5.md o skills/formats/<format>.md, fallback a estatico)
|
|
const skillsDir = path.join(process.cwd(), 'skills');
|
|
const formatCandidates = [
|
|
path.join(skillsDir, 'formats', `${formatParam}-4-5.md`),
|
|
path.join(skillsDir, 'formats', `${formatParam}.md`),
|
|
path.join(skillsDir, 'formats', 'estatico-4-5.md'),
|
|
path.join(skillsDir, 'formats', 'estatico.md'),
|
|
];
|
|
|
|
let formatSkill = '';
|
|
for (const p of formatCandidates) {
|
|
if (fs.existsSync(p)) {
|
|
formatSkill = fs.readFileSync(p, 'utf-8');
|
|
break;
|
|
}
|
|
}
|
|
|
|
return NextResponse.json(
|
|
{
|
|
data: {
|
|
client_skill: clientSkill,
|
|
format_skill: formatSkill,
|
|
},
|
|
// Compatibilidad en primer nivel
|
|
client_skill: clientSkill,
|
|
format_skill: formatSkill,
|
|
},
|
|
{
|
|
headers: {
|
|
'Cache-Control': 'no-store, no-cache, must-revalidate, proxy-revalidate',
|
|
},
|
|
}
|
|
);
|
|
}
|