import { NextRequest, NextResponse } from 'next/server'; import { createMetaMCPClient } from '@/lib/mcp/client'; import { getCampaignDetails } from '@/lib/mcp/tools'; import { getAccessToken } from '@/lib/auth/session'; import { getAIClient } from '@/lib/ai/client'; const ANALYSIS_MODEL = 'gpt-5.4-mini'; // Extrai o valor de uma action pelo tipo function findAction(actions: any[], type: string): number { return parseInt(actions.find((a: any) => a.action_type === type)?.value || '0', 10); } // Rótulo legível do objetivo da campanha function objectiveLabel(objective: string): string { const map: Record = { OUTCOME_LEADS: 'Geração de Leads', OUTCOME_SALES: 'Vendas (Conversões)', OUTCOME_TRAFFIC: 'Tráfego', OUTCOME_AWARENESS: 'Reconhecimento de Marca', OUTCOME_ENGAGEMENT: 'Engajamento', OUTCOME_APP_PROMOTION: 'Promoção de App', }; return map[objective] || objective; } export async function POST(request: NextRequest) { try { const token = await getAccessToken(); if (!token) { return NextResponse.json({ success: false, error: 'Not authenticated' }, { status: 401 }); } const { campaignId } = await request.json(); if (!campaignId) { return NextResponse.json({ success: false, error: 'Campaign ID required' }, { status: 400 }); } const client = await createMetaMCPClient(token); let campaignData: any; try { const result = await getCampaignDetails(client, campaignId); campaignData = Array.isArray(result) ? result[0] : result; } finally { await client.close(); } if (!campaignData || !campaignData.id) { return NextResponse.json({ success: false, error: 'Campaign not found on Meta' }, { status: 404 }); } const insights = campaignData.insights?.data?.[0] || {}; const adsets = campaignData.adsets?.data || []; const actions = insights.actions || []; const objective = campaignData.objective || ''; const spend = parseFloat(insights.spend || '0'); const ctr = parseFloat(insights.ctr || '0'); const cpc = parseFloat(insights.cpc || '0'); const cpm = parseFloat(insights.cpm || '0'); const reach = parseInt(insights.reach || '0', 10); const impressions = parseInt(insights.impressions || '0', 10); const leads = findAction(actions, 'lead') || findAction(actions, 'onsite_conversion.lead_grouped'); const purchases = findAction(actions, 'omni_purchase'); const roasRaw = parseFloat(insights.purchase_roas?.[0]?.value || '0'); const cpl = leads > 0 ? spend / leads : 0; const cpa = purchases > 0 ? spend / purchases : 0; // Extract Ads & Creatives const adsToAnalyze: any[] = []; for (const adset of adsets) { for (const ad of adset.ads?.data || []) { const adIns = ad.insights?.data?.[0] || {}; const adActions = adIns.actions || []; const creative = ad.creative || {}; let body = creative.body || ''; let title = creative.title || ''; let imageUrl = creative.image_url || creative.thumbnail_url || ''; if (creative.object_story_spec?.link_data) { const ld = creative.object_story_spec.link_data; body = body || ld.message || ''; title = title || ld.name || ''; } if (creative.asset_feed_spec) { const feed = creative.asset_feed_spec; body = body || feed.bodies?.map((b: any) => b.text).join(' | ') || ''; title = title || feed.titles?.map((t: any) => t.text).join(' | ') || ''; if (!imageUrl && feed.images?.length > 0) { imageUrl = feed.images[0].url || feed.images[0].thumbnail_url || ''; } } const adLeads = findAction(adActions, 'lead') || findAction(adActions, 'onsite_conversion.lead_grouped'); const adPurchases = findAction(adActions, 'omni_purchase'); const adSpend = parseFloat(adIns.spend || '0'); adsToAnalyze.push({ ad_name: ad.name, spend: adSpend.toFixed(2), ctr: parseFloat(adIns.ctr || '0').toFixed(2) + '%', cpc: 'R$ ' + parseFloat(adIns.cpc || '0').toFixed(2), leads: adLeads, purchases: adPurchases, cpl: adLeads > 0 ? 'R$ ' + (adSpend / adLeads).toFixed(2) : null, cpa: adPurchases > 0 ? 'R$ ' + (adSpend / adPurchases).toFixed(2) : null, roas: adIns.purchase_roas?.[0]?.value ? parseFloat(adIns.purchase_roas[0].value).toFixed(2) + 'x' : null, body: body || '(sem texto)', title: title || '(sem título)', image_url: imageUrl, }); } } // Monta o contexto de métricas de forma objetiva para o tipo de campanha const isLeadGen = objective === 'OUTCOME_LEADS' || (leads > 0 && purchases === 0); const isSales = objective === 'OUTCOME_SALES' || purchases > 0; const metricsBlock = [ `Objetivo: ${objectiveLabel(objective)}`, `Gasto (30d): R$ ${spend.toFixed(2)}`, `Alcance: ${reach.toLocaleString('pt-BR')} pessoas`, `Impressões: ${impressions.toLocaleString('pt-BR')}`, `CTR: ${ctr.toFixed(2)}%`, `CPC: R$ ${cpc.toFixed(2)}`, `CPM: R$ ${cpm.toFixed(2)}`, leads > 0 ? `Leads gerados: ${leads} | CPL: R$ ${cpl.toFixed(2)}` : null, purchases > 0 ? `Compras: ${purchases} | CPA: R$ ${cpa.toFixed(2)} | ROAS: ${roasRaw.toFixed(2)}x` : null, ].filter(Boolean).join('\n'); const focusInstructions = isLeadGen ? 'Esta é uma campanha de LEADS. Priorize: CPL (custo por lead), volume de leads, qualidade do copy para conversão em formulário.' : isSales ? 'Esta é uma campanha de VENDAS. Priorize: ROAS, CPA (custo por compra), volume de compras e lucratividade.' : 'Esta é uma campanha de TRÁFEGO. Priorize: CTR, CPC, qualidade dos cliques e relevância do público.'; const openai = getAIClient(); const prompt = `Você é um Especialista Sênior em Tráfego Pago e Copywriting com foco no mercado brasileiro. Faça uma auditoria completa e honesta desta campanha no Meta Ads. ${focusInstructions} DADOS DA CAMPANHA: Nome: ${campaignData.name} ${metricsBlock} ANÚNCIOS (CRIATIVOS E MÉTRICAS INDIVIDUAIS): ${JSON.stringify(adsToAnalyze, null, 2)} Retorne SOMENTE um JSON válido, sem markdown, com esta estrutura exata: { "diagnostico_geral": "Parágrafo forte resumindo saúde da campanha, mencionando a métrica-chave do objetivo (CPL para leads, ROAS/CPA para vendas, CTR para tráfego).", "analise_copy": ["Ponto sobre texto do anúncio X...", "Sugestão específica de melhoria..."], "analise_imagem": ["Avaliação do visual...", "Sugestão de formato ou abordagem..."], "analise_metricas": ["Métrica X está Y — o que significa...", "Comparativo com benchmark..."], "acao_recomendada": ["Ação imediata 1...", "Ação 2..."], "nova_estrategia_sugerida": { "nicho_sugerido": "ecommerce / info_produto / negocios_locais / etc", "produto_foco": "Produto ou oferta que deve ser o foco", "publico_alvo": "Público ideal baseado nos dados", "angulos_de_venda": ["Ângulo 1", "Ângulo 2"] } }`; const completion = await openai.chat.completions.create({ model: ANALYSIS_MODEL, messages: [{ role: 'user', content: prompt }], response_format: { type: 'json_object' }, }); const textRes = completion.choices[0]?.message?.content || ''; let jsonRes: any; try { const cleaned = textRes.replace(/^```json/m, '').replace(/```$/m, '').trim(); jsonRes = JSON.parse(cleaned); } catch { console.error('[Analysis] Failed to parse AI response:', textRes); return NextResponse.json({ success: false, error: 'Failed to parse AI response' }, { status: 500 }); } return NextResponse.json({ success: true, data: { campaign_metrics: { spend: spend.toFixed(2), ctr: ctr.toFixed(2), cpc: cpc.toFixed(2), cpm: cpm.toFixed(2), reach, impressions, leads, purchases, cpl: cpl > 0 ? cpl.toFixed(2) : null, cpa: cpa > 0 ? cpa.toFixed(2) : null, roas: roasRaw > 0 ? roasRaw.toFixed(2) : null, objective, objective_label: objectiveLabel(objective), }, ads_count: adsToAnalyze.length, ads: adsToAnalyze, analysis: jsonRes, }, }); } catch (error) { console.error('[Analysis] API Error:', error); return NextResponse.json( { success: false, error: error instanceof Error ? error.message : 'Unknown error' }, { status: 500 } ); } }