import { useState, useEffect, useRef } from "react"; /* ========================================================= CENTRAL DE MARKETING DO CORRETOR — v2 Perfil persistente · Histórico · Análise vinculada à campanha Desenvolvido por Neto Matos · netomatos.cloud ========================================================= */ const RAMOS = ["Auto", "Vida", "Saúde", "Empresarial", "Residencial", "Consórcio", "Viagem"]; const OBJETIVOS = [ { id: "whats", label: "Leads no WhatsApp", desc: "Cliente clica e cai no seu WhatsApp" }, { id: "direct", label: "Mensagens no Direct", desc: "Conversa dentro do Instagram" }, { id: "local", label: "Reconhecimento local", desc: "Ser lembrado na sua cidade" }, { id: "remkt", label: "Remarketing", desc: "Reimpactar quem já te viu" }, ]; const VERBAS = ["R$ 10/dia", "R$ 20/dia", "R$ 30/dia", "R$ 50/dia", "R$ 100+/dia"]; const FOCOS_SEMANA = [ { id: "leads", label: "Captar leads" }, { id: "autoridade", label: "Construir autoridade" }, { id: "relacionamento", label: "Relacionamento" }, { id: "misto", label: "Misto (recomendado)" }, ]; const FORMATOS_POST = [ { id: "reels", label: "Roteiro de Reels", desc: "Vídeo de 20–45s, cena a cena" }, { id: "carrossel", label: "Carrossel", desc: "7 slides prontos" }, { id: "legenda", label: "Legenda + ideia de imagem", desc: "Post único de feed" }, { id: "stories", label: "Sequência de Stories", desc: "3 a 5 stories" }, ]; const TONS = [ { id: "proximo", label: "Próximo e simples" }, { id: "consultivo", label: "Técnico e consultivo" }, { id: "descontraido", label: "Descontraído" }, ]; const SYSTEM_PROMPT = `Você é um especialista sênior em marketing digital para CORRETORES DE SEGUROS no Brasil (Instagram + Meta Ads). Escreve em português do Brasil, linguagem clara para leigos em marketing. REGRAS DE COMPLIANCE — OBRIGATÓRIAS EM TODO CONTEÚDO: - NUNCA prometa "cobertura garantida", "aprovação garantida", "menor preço garantido" ou resultado garantido. - Consórcio NÃO é investimento e NÃO tem rentabilidade garantida — nunca sugira isso. - Nunca cite seguradoras ou concorrentes pelo nome em comparativos. - Use linguagem possibilística: "pode", "ajuda a", "em muitos casos". Nunca certezas absolutas sobre preço, prazo ou cobertura. - Nunca sugira comprar listas de contatos ou disparos em massa (LGPD). - Todo criativo deve direcionar o lead a falar com o corretor (WhatsApp ou direct), nunca fechar venda no anúncio. - Ganchos devem usar dor concreta ou pergunta direta, nunca sensacionalismo com medo extremo. FORMATO DA RESPOSTA: Responda SOMENTE com JSON válido. Sem markdown, sem \`\`\`, sem texto antes ou depois do JSON. CONCISÃO É CRÍTICA: sua resposta será cortada se passar de ~900 tokens. Frases curtas. Sem quebras de linha dentro dos textos (exceto quando pedido). Nunca repita instruções do usuário.`; /* ---------- Perfil → bloco de contexto para os prompts ---------- */ function perfilBloco(p) { if (!p) return ""; const tom = TONS.find((t) => t.id === p.tom)?.label || "Próximo e simples"; return `PERFIL DO CORRETOR (personalize todo o conteúdo com base nisso): - Nome: ${p.nome}${p.corretora ? " | Corretora: " + p.corretora : ""} - Cidade/região de atuação: ${p.cidade} ${p.whatsapp ? "- WhatsApp para CTA: " + p.whatsapp + "\n" : ""}- Ramos que trabalha: ${(p.ramos || []).join(", ")} ${p.diferencial ? "- Diferencial: " + p.diferencial + "\n" : ""}- Tom de voz desejado: ${tom}`; } /* ---------- Reparo de JSON truncado ---------- */ function repairJson(s) { let out = ""; const stack = []; let inStr = false; let esc = false; for (const ch of s) { if (inStr) { out += ch; if (esc) esc = false; else if (ch === "\\") esc = true; else if (ch === '"') inStr = false; continue; } if (ch === '"') { inStr = true; out += ch; continue; } if (ch === "{") { stack.push("}"); out += ch; continue; } if (ch === "[") { stack.push("]"); out += ch; continue; } if (ch === "}" || ch === "]") { stack.pop(); out += ch; continue; } out += ch; } if (inStr) out += '"'; out = out.replace(/[\s]+$/, ""); if (out.endsWith(",")) out = out.slice(0, -1); if (out.endsWith(":")) out += " null"; while (stack.length) out += stack.pop(); return out; } /* ---------- Chamada à API ---------- */ async function callClaude(userPrompt) { let response; try { response = await fetch("https://api.anthropic.com/v1/messages", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ model: "claude-sonnet-4-6", max_tokens: 1000, system: SYSTEM_PROMPT, messages: [{ role: "user", content: userPrompt }], }), }); } catch { throw new Error("Sem conexão com a IA. Verifique sua internet e tente de novo."); } const data = await response.json(); if (data && data.error) { const t = (data.error.type || "") + " " + (data.error.message || ""); if (/rate|overload|limit/i.test(t)) throw new Error("Você atingiu o limite de gerações da sua conta Claude por agora. Aguarde alguns minutos e tente de novo."); throw new Error("O serviço de IA recusou a solicitação. Tente novamente em instantes."); } const text = (data.content || []) .filter((b) => b.type === "text") .map((b) => b.text) .join("\n"); if (!text.trim()) throw new Error("A IA respondeu vazio. Clique em tentar novamente."); const clean = text.replace(/```json|```/g, "").trim(); const start = clean.indexOf("{"); if (start === -1) throw new Error("A resposta veio em formato inesperado. Tente novamente."); const raw = clean.slice(start); try { const end = raw.lastIndexOf("}"); return JSON.parse(raw.slice(0, end + 1)); } catch { try { return JSON.parse(repairJson(raw)); } catch { throw new Error("A resposta veio incompleta e não deu para recuperar. Clique em tentar novamente."); } } } /* ---------- Chamada com conectores (Canva / Google Drive) ---------- */ const MCP_CANVA = [{ type: "url", url: "https://mcp.canva.com/mcp", name: "canva" }]; const MCP_DRIVE = [{ type: "url", url: "https://drivemcp.googleapis.com/mcp/v1", name: "gdrive" }]; async function callClaudeWithTools(userPrompt, servers) { let response; try { response = await fetch("https://api.anthropic.com/v1/messages", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ model: "claude-sonnet-4-6", max_tokens: 1000, messages: [{ role: "user", content: userPrompt }], mcp_servers: servers, }), }); } catch { throw new Error("Sem conexão. Verifique sua internet."); } const data = await response.json(); if (data && data.error) throw new Error("CONNECTOR_OFF"); return (data.content || []) .map((b) => { if (b.type === "text") return b.text || ""; if (b.type === "mcp_tool_result") { try { return (b.content || []).map((c) => c.text || "").join("\n"); } catch { return ""; } } return ""; }) .join("\n"); } function extractUrl(text, hostRe) { const urls = text.match(/https?:\/\/[^\s"'<>)\]}]+/g) || []; return urls.find((u) => hostRe.test(u)) || null; } const HINT_CANVA = "Não consegui acessar o Canva. Ative o conector do Canva no seu Claude (Configurações → Conectores) e tente de novo."; const HINT_DRIVE = "Não consegui acessar o Google Drive. Ative o conector do Google Drive no seu Claude (Configurações → Conectores) e tente de novo."; function McpActionBtn({ label, loadingLabel, doneLabel, buildPrompt, servers, hostRe, offHint }) { const [st, setSt] = useState({ loading: false, link: null, erro: null }); const run = async () => { setSt({ loading: true, link: null, erro: null }); try { const out = await callClaudeWithTools(buildPrompt(), servers); const link = extractUrl(out, hostRe); if (!link) throw new Error("CONNECTOR_OFF"); setSt({ loading: false, link, erro: null }); } catch (e) { const msg = e && e.message === "CONNECTOR_OFF" ? offHint : (e && e.message) || offHint; setSt({ loading: false, link: null, erro: msg }); } }; return ( {st.link ? ( {doneLabel} ↗ ) : ( )} {st.erro && {st.erro}} ); } /* ---------- Armazenamento persistente ---------- */ async function stGet(key) { try { const r = await window.storage.get(key); return r && r.value ? JSON.parse(r.value) : null; } catch { return null; } } async function stSet(key, val) { try { await window.storage.set(key, JSON.stringify(val)); return true; } catch { return false; } } /* ---------- Mensagens de carregamento ---------- */ const LOADING_MSGS = { campanha: ["Analisando o ramo escolhido…", "Montando a segmentação…", "Escrevendo seus criativos…", "Preparando o passo a passo…"], calendario: ["Planejando sua semana…", "Distribuindo os formatos…", "Escrevendo os ganchos…"], post: ["Buscando o melhor gancho…", "Escrevendo seu conteúdo…", "Finalizando legenda e hashtags…"], analise: ["Calculando suas métricas…", "Comparando com o mercado segurador…", "Identificando gargalos…", "Priorizando as melhorias…"], }; /* ---------- Copiar ---------- */ function useCopy() { const [copied, setCopied] = useState(null); const copy = async (key, text) => { try { await navigator.clipboard.writeText(text); } catch { const ta = document.createElement("textarea"); ta.value = text; document.body.appendChild(ta); ta.select(); document.execCommand("copy"); document.body.removeChild(ta); } setCopied(key); setTimeout(() => setCopied(null), 1800); }; return { copied, copy }; } /* ---------- Primitivas de UI ---------- */ function Chips({ options, value, onChange }) { return (
{options.map((opt) => { const label = typeof opt === "string" ? opt : opt.label; const id = typeof opt === "string" ? opt : opt.id; const desc = typeof opt === "string" ? null : opt.desc; const active = value === id; return ( ); })}
); } function ChipsMulti({ options, values, onToggle }) { return (
{options.map((id) => { const active = values.includes(id); return ( ); })}
); } function Field({ label, hint, children }) { return (
{label}
{hint &&
{hint}
} {children}
); } function CopyBtn({ id, text, copied, copy, small }) { return ( ); } function Loading({ mode }) { const msgs = LOADING_MSGS[mode]; const [i, setI] = useState(0); useEffect(() => { const t = setInterval(() => setI((v) => (v + 1) % msgs.length), 1800); return () => clearInterval(t); }, [msgs.length]); return (
{msgs[i]}
); } function ErrorBox({ msg, onRetry }) { return (
Algo falhou na geração
{typeof msg === "string" && msg ? msg : "Pode ser instabilidade momentânea. Aguarde alguns segundos e tente de novo."}
); } function DocHeader({ tag, title }) { return (
{tag}
{title}
); } function SavedBadge({ show }) { return show ? ✓ Salvo no histórico : null; } /* ========================================================= PERFIL (onboarding e edição) ========================================================= */ function PerfilForm({ initial, firstRun, onSave, onCancel }) { const [nome, setNome] = useState(initial?.nome || ""); const [corretora, setCorretora] = useState(initial?.corretora || ""); const [cidade, setCidade] = useState(initial?.cidade || ""); const [whatsapp, setWhatsapp] = useState(initial?.whatsapp || ""); const [ramos, setRamos] = useState(initial?.ramos || []); const [diferencial, setDiferencial] = useState(initial?.diferencial || ""); const [tom, setTom] = useState(initial?.tom || "proximo"); const toggleRamo = (r) => setRamos((v) => (v.includes(r) ? v.filter((x) => x !== r) : [...v, r])); const valido = nome.trim() && cidade.trim() && ramos.length > 0; return (
{firstRun && (
Bem-vindo! Vamos configurar seu perfil.
Você preenche uma única vez e toda campanha, calendário e post já sai personalizado com seus dados — a ferramenta lembra de você mesmo se fechar e voltar depois.
)}
setNome(e.target.value)} placeholder="Ex.: Carlos Silva" /> setCorretora(e.target.value)} placeholder="Ex.: Silva Seguros" />
setCidade(e.target.value)} placeholder="Ex.: Campinas e região" /> setWhatsapp(e.target.value)} placeholder="Ex.: (19) 99999-9999" />
setDiferencial(e.target.value)} placeholder="O que te torna diferente" /> {!valido &&
Preencha nome, cidade e ao menos um ramo.
} {!firstRun && ( )}
); } /* ========================================================= ABA 1 — CAMPANHA DE TRÁFEGO ========================================================= */ function AbaCampanha({ perfil, copied, copy, restore, addHist }) { const [ramo, setRamo] = useState(perfil.ramos?.[0] || "Auto"); const [objetivo, setObjetivo] = useState("whats"); const [verba, setVerba] = useState("R$ 20/dia"); const [regiao, setRegiao] = useState(perfil.cidade || ""); const [diferencial, setDiferencial] = useState(perfil.diferencial || ""); const [loading, setLoading] = useState(false); const [erro, setErro] = useState(null); const [res, setRes] = useState(null); const [saved, setSaved] = useState(false); const [done, setDone] = useState({}); const resultRef = useRef(null); useEffect(() => { if (restore && restore.tipo === "campanha") { const inp = restore.inputs || {}; if (inp.ramo) setRamo(inp.ramo); if (inp.objetivo) setObjetivo(inp.objetivo); if (inp.verba) setVerba(inp.verba); if (inp.regiao) setRegiao(inp.regiao); if (inp.diferencial !== undefined) setDiferencial(inp.diferencial); setRes(restore.payload); setSaved(true); setDone({}); setTimeout(() => resultRef.current?.scrollIntoView({ behavior: "smooth", block: "start" }), 150); } }, [restore && restore._k]); const objetivoLabel = OBJETIVOS.find((o) => o.id === objetivo)?.label; const gerar = async () => { setLoading(true); setErro(null); setRes(null); setSaved(false); setDone({}); const prompt = `${perfilBloco(perfil)} Crie uma campanha de tráfego pago no Instagram (Meta Ads) para este corretor. DADOS DA CAMPANHA: - Ramo: Seguro ${ramo} - Objetivo: ${objetivoLabel} - Verba: ${verba} - Região do anúncio: ${regiao || perfil.cidade} - Diferencial a destacar: ${diferencial || "atendimento próximo e consultivo"} Retorne EXATAMENTE este JSON, com 3 criativos de ângulos diferentes. LIMITES RÍGIDOS (obrigatórios para a resposta não ser cortada): copyPrimaria máx 180 caracteres; gancho, variacaoB, sugestaoVisual e dica máx 12 palavras cada; campanha/conjunto/anuncio máx 15 palavras cada; exatamente 5 passos de máx 15 palavras; exatamente 2 itens de compliance; exatamente 3 interesses. {"nomeCampanha":"","objetivoGerenciador":"objetivo a selecionar no Gerenciador + motivo, 1 frase","estrutura":{"campanha":"","conjunto":"","anuncio":""},"orcamento":{"comoDistribuir":"","duracaoTeste":"","expectativaRealista":"sem promessas"},"segmentacao":{"localizacao":"","idade":"","interesses":[""],"dica":""},"criativos":[{"nome":"Criativo 1","formato":"Reels ou Imagem","gancho":"","copyPrimaria":"","titulo":"máx 40 chars","descricao":"máx 30 chars","cta":"botão do anúncio","variacaoB":"","sugestaoVisual":""}],"passoAPasso":["onde colar cada coisa no Gerenciador"],"compliance":["o que NÃO fazer neste anúncio"]}`; try { const json = await callClaude(prompt); setRes(json); addHist({ tipo: "campanha", titulo: json.nomeCampanha || `Campanha Seguro ${ramo}`, inputs: { ramo, objetivo, verba, regiao, diferencial }, payload: json }); setSaved(true); setTimeout(() => resultRef.current?.scrollIntoView({ behavior: "smooth", block: "start" }), 100); } catch (e) { setErro(e && e.message ? e.message : "Erro inesperado. Tente novamente."); } finally { setLoading(false); } }; const criativoTexto = (c) => `${c.nome} — ${c.formato}\n\nGANCHO: ${c.gancho}\n\nCOPY PRIMÁRIA:\n${c.copyPrimaria}\n\nTÍTULO: ${c.titulo}\nDESCRIÇÃO: ${c.descricao}\nBOTÃO (CTA): ${c.cta}\n\nVARIAÇÃO B (teste): ${c.variacaoB}\nVISUAL: ${c.sugestaoVisual}`; return (
setRegiao(e.target.value)} placeholder="Onde você atende" /> setDiferencial(e.target.value)} placeholder="O que te torna diferente" />
{loading && } {erro && } {res && (
Objetivo no Gerenciador

{res.objetivoGerenciador}

Estrutura da campanha
Campanha{res.estrutura?.campanha}
Conjunto{res.estrutura?.conjunto}
Anúncio{res.estrutura?.anuncio}
Orçamento ({verba})

Distribuição: {res.orcamento?.comoDistribuir}

Teste: {res.orcamento?.duracaoTeste}

Expectativa realista: {res.orcamento?.expectativaRealista}

Segmentação

Local: {res.segmentacao?.localizacao}

Idade: {res.segmentacao?.idade}

Interesses: {(res.segmentacao?.interesses || []).join(" · ")}

{res.segmentacao?.dica}

Seus criativos prontos
{(res.criativos || []).map((c, i) => (
{c.nome} {c.formato}
“{c.gancho}”

{c.copyPrimaria}

Título: {c.titulo} Descrição: {c.descricao} Botão: {c.cta}
Variação B: {c.variacaoB} Visual: {c.sugestaoVisual}
`Crie um design no Canva (tipo Instagram Post, formato quadrado) para um anúncio de seguro${perfil.corretora ? " da corretora " + perfil.corretora : ""}. Use o Canva MCP disponível. Texto principal (gancho, grande e chamativo): "${c.gancho}" Texto de apoio: "${c.titulo}" Direção visual: ${c.sugestaoVisual || "cores azul e dourado, estilo profissional e confiável, ícone relacionado a proteção/seguro"} Ao final, responda apenas com o link do design criado.` } />
))}
Onde colar cada coisa no Gerenciador — marque conforme for fazendo
{(res.passoAPasso || []).map((p, i) => ( ))}
⚠ Compliance — não faça isso neste anúncio
    {(res.compliance || []).map((c, i) => (
  • {c}
  • ))}
Rodou a campanha? Depois de 7 dias, volte na aba 📊 Análise — ela já conhece esta campanha e vai te dizer criativo por criativo o que ajustar.
)}
); } /* ========================================================= ABA 2 — CALENDÁRIO ========================================================= */ function AbaCalendario({ perfil, copied, copy, restore, addHist }) { const [ramo, setRamo] = useState(perfil.ramos?.[0] || "Auto"); const [foco, setFoco] = useState("misto"); const [loading, setLoading] = useState(false); const [erro, setErro] = useState(null); const [res, setRes] = useState(null); const [saved, setSaved] = useState(false); const resultRef = useRef(null); useEffect(() => { if (restore && restore.tipo === "calendario") { const inp = restore.inputs || {}; if (inp.ramo) setRamo(inp.ramo); if (inp.foco) setFoco(inp.foco); setRes(restore.payload); setSaved(true); setTimeout(() => resultRef.current?.scrollIntoView({ behavior: "smooth", block: "start" }), 150); } }, [restore && restore._k]); const focoLabel = FOCOS_SEMANA.find((f) => f.id === foco)?.label; const gerar = async () => { setLoading(true); setErro(null); setRes(null); setSaved(false); const prompt = `${perfilBloco(perfil)} Crie um calendário editorial de 7 dias para o Instagram deste corretor. DADOS: - Ramo principal: Seguro ${ramo} - Foco da semana: ${focoLabel} Regras: priorize Reels (mínimo 3 na semana), no máximo 1 conteúdo motivacional (sempre ligado a proteção/planejamento), todo post educativo pede salvar ou compartilhar, e a semana precisa ter pelo menos 1 post de oferta direta com CTA para o WhatsApp do corretor. Retorne EXATAMENTE este JSON: {"tituloSemana":"","dias":[{"dia":"Segunda","formato":"","tema":"","gancho":"frase exata de abertura","cta":""}],"dicaDaSemana":""} Com os 7 dias, de Segunda a Domingo.`; try { const json = await callClaude(prompt); setRes(json); addHist({ tipo: "calendario", titulo: json.tituloSemana || `Semana Seguro ${ramo}`, inputs: { ramo, foco }, payload: json }); setSaved(true); setTimeout(() => resultRef.current?.scrollIntoView({ behavior: "smooth", block: "start" }), 100); } catch (e) { setErro(e && e.message ? e.message : "Erro inesperado. Tente novamente."); } finally { setLoading(false); } }; const calendarioTexto = () => !res ? "" : `${res.tituloSemana}\n\n` + (res.dias || []).map((d) => `${d.dia} — ${d.formato}\nTema: ${d.tema}\nGancho: ${d.gancho}\nCTA: ${d.cta}`).join("\n\n") + `\n\nDica da semana: ${res.dicaDaSemana}`; return (
{loading && } {erro && } {res && (
`Crie um arquivo Google Docs chamado "Calendário Instagram - ${res.tituloSemana}" usando o Google Drive MCP disponível, com este conteúdo:\n\n${calendarioTexto()}\n\nAo final, responda apenas com o link do arquivo criado.` } />
{(res.dias || []).map((d, i) => (
{d.dia} {d.formato}
{d.tema}
“{d.gancho}”
CTA: {d.cta}
))}
💡 {res.dicaDaSemana}
)}
); } /* ========================================================= ABA 3 — POST AVULSO ========================================================= */ function AbaPost({ perfil, copied, copy, restore, addHist }) { const [formato, setFormato] = useState("reels"); const [ramo, setRamo] = useState(perfil.ramos?.[0] || "Auto"); const [tema, setTema] = useState(""); const [loading, setLoading] = useState(false); const [erro, setErro] = useState(null); const [res, setRes] = useState(null); const [saved, setSaved] = useState(false); const resultRef = useRef(null); useEffect(() => { if (restore && restore.tipo === "post") { const inp = restore.inputs || {}; if (inp.formato) setFormato(inp.formato); if (inp.ramo) setRamo(inp.ramo); if (inp.tema !== undefined) setTema(inp.tema); setRes(restore.payload); setSaved(true); setTimeout(() => resultRef.current?.scrollIntoView({ behavior: "smooth", block: "start" }), 150); } }, [restore && restore._k]); const formatoLabel = FORMATOS_POST.find((f) => f.id === formato)?.label; const gerar = async () => { setLoading(true); setErro(null); setRes(null); setSaved(false); const instrucaoFormato = { reels: "Roteiro de Reels de 20 a 45 segundos: blocos = Cena 1 — Gancho (0-3s), Cena 2 — Problema, Cena 3 — Solução, Cena 4 — CTA. Em cada cena inclua a fala e o texto na tela.", carrossel: "Carrossel de 7 slides: blocos = Slide 1 — Gancho, Slide 2 — Problema, Slide 3 — Consequência, Slide 4 — Solução, Slide 5 — Exemplo prático, Slide 6 — Benefício, Slide 7 — CTA. Pouco texto por slide.", legenda: "Post único de feed: blocos = Ideia de imagem (descrição do visual) e Estrutura (gancho, desenvolvimento, exemplo). A legenda completa vai no campo legenda.", stories: "Sequência de 4 stories: blocos = Story 1 a Story 4, cada um com o texto na tela e o elemento interativo (enquete, caixa de pergunta ou link).", }[formato]; const prompt = `${perfilBloco(perfil)} Crie um conteúdo de Instagram para este corretor. DADOS: - Formato: ${formatoLabel} - Ramo: Seguro ${ramo} - Tema: ${tema || "escolha um tema de alta dor/desejo para este ramo"} ${instrucaoFormato} O CTA final deve pedir salvar/compartilhar E convidar para o WhatsApp do corretor. Retorne EXATAMENTE este JSON: {"titulo":"nome do conteúdo","blocos":[{"rotulo":"","texto":""}],"legenda":"legenda pronta para colar no Instagram","hashtags":["10 a 12 hashtags sem #"]}`; try { const json = await callClaude(prompt); setRes(json); addHist({ tipo: "post", titulo: json.titulo || `${formatoLabel} — Seguro ${ramo}`, inputs: { formato, ramo, tema }, payload: json }); setSaved(true); setTimeout(() => resultRef.current?.scrollIntoView({ behavior: "smooth", block: "start" }), 100); } catch (e) { setErro(e && e.message ? e.message : "Erro inesperado. Tente novamente."); } finally { setLoading(false); } }; const postTexto = () => !res ? "" : `${res.titulo}\n\n` + (res.blocos || []).map((b) => `${b.rotulo}\n${b.texto}`).join("\n\n") + `\n\nLEGENDA:\n${res.legenda}\n\n${(res.hashtags || []).map((h) => "#" + h.replace(/^#/, "")).join(" ")}`; return (
setTema(e.target.value)} placeholder="Ex.: por que o seguro auto nega sinistro" />
{loading && } {erro && } {res && (
`Crie um design no Canva (tipo Instagram Post, formato quadrado) para o Instagram de um corretor de seguros${perfil.corretora ? " (" + perfil.corretora + ")" : ""}. Use o Canva MCP disponível. Título/gancho do post: "${res.titulo}" Conteúdo base: ${(res.blocos || []).slice(0, 2).map((b) => b.texto).join(" | ").slice(0, 300)} Direção visual: cores azul-marinho e dourado, estilo profissional e confiável, tipografia forte. Ao final, responda apenas com o link do design criado.` } />
{(res.blocos || []).map((b, i) => (
{b.rotulo}
{b.texto}
))}
Legenda pronta{" "} "#" + h.replace(/^#/, "")).join(" ")} copied={copied} copy={copy} small />

{res.legenda}

{(res.hashtags || []).map((h, i) => ( #{h.replace(/^#/, "")} ))}
)}
); } /* ========================================================= ABA 4 — ANÁLISE DE CAMPANHA (vinculada ao histórico) ========================================================= */ const SEMAFORO = { verde: { label: "🟢 Campanha saudável", cls: "sem-verde" }, amarelo: { label: "🟡 Precisa de ajustes", cls: "sem-amarelo" }, vermelho: { label: "🔴 Correção urgente", cls: "sem-vermelho" }, }; function AbaAnalise({ perfil, copied, copy, hist, addHist, restore }) { const campanhas = hist.filter((h) => h.tipo === "campanha"); const [campId, setCampId] = useState(""); const [ramo, setRamo] = useState(perfil.ramos?.[0] || "Auto"); const [objetivo, setObjetivo] = useState("whats"); const [investido, setInvestido] = useState(""); const [dias, setDias] = useState(""); const [alcance, setAlcance] = useState(""); const [impressoes, setImpressoes] = useState(""); const [cliques, setCliques] = useState(""); const [leads, setLeads] = useState(""); const [extra, setExtra] = useState(""); const [loading, setLoading] = useState(false); const [erro, setErro] = useState(null); const [res, setRes] = useState(null); const [saved, setSaved] = useState(false); const resultRef = useRef(null); useEffect(() => { if (restore && restore.tipo === "analise") { const inp = restore.inputs || {}; if (inp.ramo) setRamo(inp.ramo); if (inp.investido) setInvestido(inp.investido); setRes(restore.payload); setSaved(true); setTimeout(() => resultRef.current?.scrollIntoView({ behavior: "smooth", block: "start" }), 150); } }, [restore && restore._k]); const campSel = campanhas.find((c) => c.id === campId) || null; const selecionarCampanha = (id) => { setCampId(id); const c = campanhas.find((x) => x.id === id); if (c && c.inputs) { if (c.inputs.ramo) setRamo(c.inputs.ramo); if (c.inputs.objetivo) setObjetivo(c.inputs.objetivo); } }; const contextoCampanha = () => { if (!campSel || !campSel.payload) return ""; const p = campSel.payload; const cri = (p.criativos || []).map((c, i) => `${i + 1}) gancho "${c.gancho}" (${c.formato})`).join(" "); return `\nCAMPANHA ANALISADA (criada nesta ferramenta — use para dar recomendações específicas): - Nome: ${p.nomeCampanha} - Criativos rodando: ${cri} - Segmentação usada: ${p.segmentacao?.localizacao || ""}, idade ${p.segmentacao?.idade || "?"}, interesses: ${(p.segmentacao?.interesses || []).join(", ")} Nas recomendações, refira-se aos criativos pelo número (ex.: "pause o criativo 2") quando fizer sentido.\n`; }; const objetivoLabel = OBJETIVOS.find((o) => o.id === objetivo)?.label; const gerar = async () => { setLoading(true); setErro(null); setRes(null); setSaved(false); const prompt = `${perfilBloco(perfil)} ${contextoCampanha()} Analise os resultados desta campanha de Meta Ads (Instagram) do corretor e diga o que melhorar. DADOS DA CAMPANHA: - Ramo: Seguro ${ramo} - Objetivo: ${objetivoLabel} - Investimento até agora: ${investido} - Dias rodando: ${dias || "não informado"} - Alcance: ${alcance || "não informado"} - Impressões: ${impressoes || "não informado"} - Cliques no link: ${cliques || "não informado"} - Conversas iniciadas / leads: ${leads || "não informado"} - Outros dados colados do Gerenciador: ${extra || "nenhum"} Calcule as métricas derivadas possíveis (CTR, CPM, CPC, custo por lead) e avalie contra referências realistas do mercado segurador brasileiro em Meta Ads. Se um dado importante estiver faltando, inclua isso como problema com a ação "passar a acompanhar essa métrica". Considere o tempo de campanha: menos de 7 dias ainda é fase de aprendizado. Retorne EXATAMENTE este JSON. LIMITES RÍGIDOS: resumo máx 2 frases; máx 4 metricas; comentario máx 10 palavras; máx 2 pontosFortes de máx 12 palavras; máx 3 problemas; causaProvavel e acao máx 15 palavras cada; proximoTeste máx 20 palavras; alerta 1 frase. {"notaGeral":"verde OU amarelo OU vermelho","resumo":"","metricas":[{"nome":"CTR","valor":"","avaliacao":"boa OU media OU ruim","comentario":""}],"pontosFortes":[""],"problemas":[{"problema":"","causaProvavel":"","acao":""}],"proximoTeste":"","alerta":"expectativa realista ou aviso de compliance"}`; try { const json = await callClaude(prompt); setRes(json); addHist({ tipo: "analise", titulo: `Análise — ${campSel ? campSel.titulo : "Seguro " + ramo}`, inputs: { ramo, investido }, payload: json }); setSaved(true); setTimeout(() => resultRef.current?.scrollIntoView({ behavior: "smooth", block: "start" }), 100); } catch (e) { setErro(e && e.message ? e.message : "Erro inesperado. Tente novamente."); } finally { setLoading(false); } }; const nota = res ? SEMAFORO[(res.notaGeral || "amarelo").toLowerCase()] || SEMAFORO.amarelo : null; const analiseTexto = () => !res ? "" : `DIAGNÓSTICO: ${nota.label}\n${res.resumo}\n\nMÉTRICAS:\n` + (res.metricas || []).map((m) => `${m.nome}: ${m.valor} (${m.avaliacao}) — ${m.comentario}`).join("\n") + `\n\nPONTOS FORTES:\n` + (res.pontosFortes || []).map((p) => `- ${p}`).join("\n") + `\n\nPROBLEMAS E AÇÕES:\n` + (res.problemas || []).map((p) => `- ${p.problema}\n Causa provável: ${p.causaProvavel}\n Ação: ${p.acao}`).join("\n") + `\n\nPRÓXIMO TESTE: ${res.proximoTeste}\n\n⚠ ${res.alerta}`; return (
{campanhas.length > 0 && ( )}
Valor investido *
setInvestido(e.target.value)} placeholder="R$ 140" />
Dias rodando
setDias(e.target.value)} placeholder="7" />
Alcance
setAlcance(e.target.value)} placeholder="8.500" />
Impressões
setImpressoes(e.target.value)} placeholder="15.200" />
Cliques no link
setCliques(e.target.value)} placeholder="230" />
Conversas / leads
setLeads(e.target.value)} placeholder="18" />