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/-4-5.md o skills/formats/.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', }, } ); }