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.loading ? loadingLabel : label}
)}
{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 (
onChange(id)}>
{label}
{desc && {desc} }
);
})}
);
}
function ChipsMulti({ options, values, onToggle }) {
return (
{options.map((id) => {
const active = values.includes(id);
return (
onToggle(id)}>
{active ? "✓ " : ""}{id}
);
})}
);
}
function Field({ label, hint, children }) {
return (
{label}
{hint &&
{hint}
}
{children}
);
}
function CopyBtn({ id, text, copied, copy, small }) {
return (
copy(id, text)}>
{copied === id ? "✓ Copiado!" : "Copiar"}
);
}
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 (
);
}
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."}
Tentar novamente
);
}
function DocHeader({ tag, title }) {
return (
);
}
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 (
);
}
/* =========================================================
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 (
{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.`
}
/>
))}
⚠ 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 ? "Gerando…" : "Gerar calendário da semana"}
{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 ? "Gerando…" : "Gerar conteúdo"}
{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) => (
))}
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 && (
selecionarCampanha(e.target.value)}>
Outra campanha (não criada aqui)
{campanhas.map((c) => (
{c.titulo} · {c.data}
))}
)}
{loading ? "Analisando…" : "Analisar minha campanha"}
{!investido.trim() &&
Preencha ao menos o valor investido para analisar.
}
{loading &&
}
{erro &&
}
{res && (
{nota.label}
Suas métricas
{(res.metricas || []).map((m, i) => (
{m.nome}
{m.valor}
{m.comentario}
))}
{(res.pontosFortes || []).length > 0 && (
O que está funcionando
{(res.pontosFortes || []).map((p, i) => (
{p}
))}
)}
O que corrigir (em ordem de prioridade)
{(res.problemas || []).map((p, i) => (
{i + 1}. {p.problema}
Causa provável: {p.causaProvavel}
→ {p.acao}
))}
Próximo teste recomendado
{res.proximoTeste}
{res.alerta && (
)}
)}
);
}
/* =========================================================
HISTÓRICO
========================================================= */
const TIPO_META = {
campanha: { icon: "🎯", label: "Campanha" },
calendario: { icon: "📅", label: "Calendário" },
post: { icon: "✍️", label: "Post" },
analise: { icon: "📊", label: "Análise" },
};
function Historico({ hist, onReabrir, onExcluir }) {
if (!hist.length)
return (
📂
Nada por aqui ainda
Tudo que você gerar fica salvo aqui automaticamente — campanhas, calendários, posts e análises. Fecha e volta quando quiser.
);
return (
Suas últimas gerações ({hist.length})
{hist.map((h) => {
const m = TIPO_META[h.tipo] || { icon: "📄", label: h.tipo };
return (
{m.icon}
{h.titulo}
{m.label} · {h.data}
onReabrir(h)}>Reabrir
onExcluir(h.id)} title="Excluir">✕
);
})}
);
}
/* =========================================================
APP
========================================================= */
export default function App() {
const [ready, setReady] = useState(false);
const [perfil, setPerfil] = useState(null);
const [hist, setHist] = useState([]);
const [tab, setTab] = useState("campanha");
const [editandoPerfil, setEditandoPerfil] = useState(false);
const [restore, setRestore] = useState(null);
const { copied, copy } = useCopy();
useEffect(() => {
(async () => {
const p = await stGet("perfil");
const h = await stGet("historico");
if (p) setPerfil(p);
if (h) setHist(h);
setReady(true);
})();
}, []);
const salvarPerfil = async (p) => {
setPerfil(p);
setEditandoPerfil(false);
await stSet("perfil", p);
};
const addHist = (item) => {
setHist((h) => {
const nh = [{ ...item, id: String(Date.now()), data: new Date().toLocaleDateString("pt-BR") }, ...h].slice(0, 20);
stSet("historico", nh);
return nh;
});
};
const delHist = (id) => {
setHist((h) => {
const nh = h.filter((x) => x.id !== id);
stSet("historico", nh);
return nh;
});
};
const reabrir = (item) => {
setEditandoPerfil(false);
setTab(item.tipo);
setRestore({ ...item, _k: Date.now() });
};
const TABS = [
{ id: "campanha", label: "Campanha de tráfego", icon: "🎯", desc: "Anúncio completo pro Gerenciador" },
{ id: "calendario", label: "Calendário de posts", icon: "📅", desc: "Sua semana de conteúdo" },
{ id: "post", label: "Post avulso", icon: "✍️", desc: "Reels, carrossel ou legenda" },
{ id: "analise", label: "Análise de campanha", icon: "📊", desc: "Cole os números e receba o diagnóstico" },
];
const CSS = `
:root {
--ink: #13273d; --ink-soft: #47607a; --bg: #eef2f6; --card: #ffffff; --line: #d8e0e8;
--gold: #f2b200; --gold-deep: #b98600; --blue: #1d6fb8; --blue-deep: #14507f;
--ok: #1e8e5a; --danger: #b3382c;
}
* { box-sizing: border-box; }
.app {
min-height: 100vh; background: var(--bg);
background-image: radial-gradient(circle at 85% -10%, rgba(29,111,184,.10), transparent 45%);
font-family: 'Archivo', ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
color: var(--ink); padding: 0 16px 48px;
}
.shell { max-width: 880px; margin: 0 auto; }
.hero { padding: 34px 0 18px; }
.hero-top { display: flex; justify-content: space-between; align-items: flex-start; gap: 12px; flex-wrap: wrap; }
.hero-eyebrow {
display: inline-flex; align-items: center; gap: 8px; font-size: 11px; font-weight: 700;
letter-spacing: .14em; text-transform: uppercase; color: var(--blue-deep);
background: rgba(29,111,184,.09); border: 1px solid rgba(29,111,184,.22);
padding: 5px 12px; border-radius: 999px;
}
.hero-eyebrow::before { content: ""; width: 7px; height: 7px; border-radius: 50%; background: var(--gold); }
.hero h1 { margin: 14px 0 6px; font-size: clamp(24px, 5vw, 36px); line-height: 1.08; letter-spacing: -0.02em; font-weight: 800; }
.hero h1 em { font-style: normal; color: var(--blue-deep); }
.hero p { margin: 0; color: var(--ink-soft); font-size: 14.5px; max-width: 56ch; }
.hero-user { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.user-chip {
font-size: 12.5px; font-weight: 700; background: var(--card); border: 1px solid var(--line);
border-radius: 999px; padding: 7px 14px;
}
.btn-mini {
border: 1px solid var(--line); background: var(--card); color: var(--ink); font-weight: 700;
font-size: 12px; padding: 7px 12px; border-radius: 999px; cursor: pointer;
}
.btn-mini:hover { border-color: var(--ink); }
.btn-mini.on { background: var(--ink); color: #fff; border-color: var(--ink); }
.tabs { display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px; margin: 18px 0 18px; }
@media (max-width: 820px) { .tabs { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 440px) { .tabs { grid-template-columns: 1fr; } }
.tab {
text-align: left; border: 1px solid var(--line); background: var(--card); border-radius: 14px;
padding: 13px 15px; cursor: pointer; transition: transform .12s ease, box-shadow .12s ease, border-color .12s ease;
}
.tab:hover { transform: translateY(-1px); box-shadow: 0 6px 18px rgba(19,39,61,.08); }
.tab:focus-visible { outline: 3px solid var(--gold); outline-offset: 2px; }
.tab-on { border-color: var(--ink); box-shadow: inset 0 -3px 0 var(--gold), 0 6px 18px rgba(19,39,61,.08); }
.tab-icon { font-size: 19px; }
.tab-label { font-weight: 700; font-size: 14px; margin-top: 5px; }
.tab-desc { font-size: 11.5px; color: var(--ink-soft); margin-top: 2px; }
.panel { background: var(--card); border: 1px solid var(--line); border-radius: 16px; padding: 22px; box-shadow: 0 1px 2px rgba(19,39,61,.04); }
.field { margin-bottom: 18px; }
.field-label { font-weight: 700; font-size: 14px; margin-bottom: 3px; }
.field-hint { font-size: 12.5px; color: var(--ink-soft); margin-bottom: 8px; }
.center-hint { margin-top: 8px; text-align: center; }
.field-label + .chips, .field-label + .input { margin-top: 8px; }
.grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
@media (max-width: 640px) { .grid-2 { grid-template-columns: 1fr; } }
.grid-4 { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; }
@media (max-width: 640px) { .grid-4 { grid-template-columns: repeat(2, 1fr); } }
.mini-label { font-size: 11.5px; font-weight: 700; color: var(--ink-soft); margin-bottom: 4px; }
.chips { display: flex; flex-wrap: wrap; gap: 8px; }
.chip {
border: 1.5px solid var(--line); background: #f7fafc; color: var(--ink); border-radius: 10px;
padding: 8px 13px; cursor: pointer; text-align: left; transition: border-color .1s, background .1s;
display: flex; flex-direction: column; gap: 1px;
}
.chip:hover { border-color: var(--blue); }
.chip:focus-visible { outline: 3px solid var(--gold); outline-offset: 1px; }
.chip-on { border-color: var(--ink); background: var(--ink); color: #fff; }
.chip-on .chip-desc { color: rgba(255,255,255,.72); }
.chip-label { font-weight: 600; font-size: 13.5px; }
.chip-desc { font-size: 11.5px; color: var(--ink-soft); }
.input { width: 100%; border: 1.5px solid var(--line); border-radius: 10px; padding: 11px 13px; font-size: 14px; background: #f7fafc; color: var(--ink); }
.input:focus { outline: none; border-color: var(--blue); background: #fff; }
.textarea { min-height: 90px; resize: vertical; font-family: inherit; line-height: 1.5; }
.select { appearance: auto; cursor: pointer; }
.btn-main {
width: 100%; margin-top: 4px; border: none; cursor: pointer; background: var(--ink); color: #fff;
font-weight: 800; font-size: 15.5px; padding: 15px; border-radius: 12px; letter-spacing: .01em;
box-shadow: inset 0 -3px 0 var(--gold); transition: transform .1s ease, opacity .1s;
}
.btn-main:hover:not(:disabled) { transform: translateY(-1px); }
.btn-main:disabled { opacity: .6; cursor: default; }
.btn-main:focus-visible { outline: 3px solid var(--gold); outline-offset: 2px; }
.btn-secondary { border: 1.5px solid var(--ink); background: transparent; color: var(--ink); font-weight: 700; padding: 9px 16px; border-radius: 10px; cursor: pointer; }
.btn-ghost { width: 100%; margin-top: 10px; border: none; background: transparent; color: var(--ink-soft); font-weight: 700; font-size: 13px; padding: 8px; cursor: pointer; }
.loading { margin: 22px 0; text-align: center; }
.loading-bar { height: 6px; border-radius: 99px; background: var(--line); overflow: hidden; max-width: 340px; margin: 0 auto; }
.loading-fill { height: 100%; width: 40%; border-radius: 99px; background: linear-gradient(90deg, var(--blue), var(--gold)); animation: slide 1.1s ease-in-out infinite alternate; }
@keyframes slide { from { transform: translateX(-30%);} to { transform: translateX(220%);} }
@media (prefers-reduced-motion: reduce) { .loading-fill { animation: none; width: 100%; } }
.loading-msg { margin-top: 10px; font-size: 13.5px; color: var(--ink-soft); font-weight: 600; }
.error-box { margin-top: 20px; background: #fdf1ef; border: 1px solid #ebc4be; border-radius: 14px; padding: 18px; text-align: center; }
.error-title { font-weight: 800; color: var(--danger); }
.error-text { font-size: 13.5px; color: var(--ink-soft); margin: 6px 0 12px; }
.doc { margin-top: 22px; background: var(--card); border: 1px solid var(--line); border-radius: 16px; padding: 26px; position: relative; overflow: hidden; box-shadow: 0 10px 30px rgba(19,39,61,.08); }
.doc::before { content: ""; position: absolute; top: 0; left: 0; right: 0; height: 5px; background: repeating-linear-gradient(90deg, var(--gold) 0 26px, var(--ink) 26px 52px); }
.doc-header { padding-top: 6px; margin-bottom: 14px; }
.doc-stamp { display: inline-block; font-size: 10.5px; font-weight: 800; letter-spacing: .16em; color: var(--gold-deep); border: 1.5px solid var(--gold); padding: 4px 10px; border-radius: 4px; transform: rotate(-1.2deg); background: rgba(242,178,0,.07); }
.doc-title { font-size: 22px; font-weight: 800; letter-spacing: -0.01em; margin-top: 10px; line-height: 1.2; }
.doc-actions { margin: 0 0 16px; display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
.saved-badge { font-size: 12px; font-weight: 700; color: var(--ok); background: rgba(30,142,90,.08); border: 1px solid rgba(30,142,90,.3); padding: 5px 11px; border-radius: 999px; }
.doc-section { margin-bottom: 22px; }
.sec-title { font-size: 11.5px; font-weight: 800; letter-spacing: .12em; text-transform: uppercase; color: var(--blue-deep); margin-bottom: 8px; display: flex; align-items: center; gap: 10px; }
.sec-text { font-size: 14px; line-height: 1.55; margin: 0 0 6px; color: var(--ink); }
.sec-dica { color: var(--ink-soft); font-style: italic; }
.tree { display: flex; flex-direction: column; gap: 8px; }
.tree-item { display: flex; gap: 10px; align-items: baseline; font-size: 14px; line-height: 1.5; }
.tree-ind1 { padding-left: 18px; } .tree-ind2 { padding-left: 36px; }
.tree-tag { flex-shrink: 0; font-size: 10.5px; font-weight: 800; text-transform: uppercase; letter-spacing: .08em; background: var(--ink); color: #fff; border-radius: 4px; padding: 2px 7px; }
.criativo { border: 1px solid var(--line); border-radius: 12px; padding: 16px; margin-bottom: 12px; background: #fbfcfd; }
.criativo-head { display: flex; justify-content: space-between; align-items: center; gap: 10px; margin-bottom: 8px; }
.criativo-nome { font-weight: 800; font-size: 14px; }
.criativo-fmt { font-weight: 700; font-size: 11px; color: var(--blue-deep); background: rgba(29,111,184,.1); padding: 2px 8px; border-radius: 99px; margin-left: 6px; }
.criativo-gancho { font-weight: 700; font-size: 15px; margin-bottom: 6px; color: var(--ink); }
.criativo-meta, .criativo-extra { display: flex; flex-wrap: wrap; gap: 6px 16px; font-size: 12.5px; color: var(--ink-soft); margin-top: 8px; }
.criativo-extra { border-top: 1px dashed var(--line); padding-top: 8px; }
.criativo-canva { margin-top: 10px; border-top: 1px dashed var(--line); padding-top: 10px; }
.mcp-wrap { display: inline-flex; flex-direction: column; gap: 4px; }
.mcp-link { display: inline-flex; align-items: center; gap: 4px; border: 1.5px solid var(--ok); color: var(--ok); background: rgba(30,142,90,.07); font-weight: 700; font-size: 12.5px; padding: 7px 14px; border-radius: 8px; text-decoration: none; }
.mcp-link:hover { background: var(--ok); color: #fff; }
.mcp-erro { font-size: 11.5px; color: var(--danger); max-width: 320px; line-height: 1.4; }
.passos-check { display: flex; flex-direction: column; gap: 8px; }
.passo-item { display: flex; gap: 10px; align-items: flex-start; font-size: 14px; line-height: 1.5; cursor: pointer; padding: 9px 12px; border: 1px solid var(--line); border-radius: 10px; background: #fbfcfd; transition: background .1s, border-color .1s; }
.passo-item input { margin-top: 3px; accent-color: var(--ok); cursor: pointer; }
.passo-item:hover { border-color: var(--blue); }
.passo-ok { background: rgba(30,142,90,.06); border-color: rgba(30,142,90,.35); }
.passo-ok span { text-decoration: line-through; color: var(--ink-soft); }
.compliance { background: #fff8e6; border: 1px solid #ecd48f; border-radius: 12px; padding: 14px 16px; }
.compliance-title { font-weight: 800; font-size: 13px; color: var(--gold-deep); margin-bottom: 6px; }
.compliance ul { margin: 0; padding-left: 18px; font-size: 13px; line-height: 1.55; color: var(--ink); }
.proximo-passo { margin-top: 16px; font-size: 13.5px; background: rgba(29,111,184,.06); border-radius: 10px; padding: 12px 14px; line-height: 1.5; }
.cal-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(230px, 1fr)); gap: 10px; }
.cal-card { border: 1px solid var(--line); border-radius: 12px; padding: 13px 14px; background: #fbfcfd; }
.cal-day { font-weight: 800; font-size: 13px; margin-bottom: 4px; }
.cal-fmt { font-weight: 700; font-size: 10.5px; color: var(--blue-deep); background: rgba(29,111,184,.1); padding: 2px 7px; border-radius: 99px; margin-left: 5px; }
.cal-tema { font-size: 13px; font-weight: 600; margin-bottom: 4px; }
.cal-gancho { font-size: 12.5px; color: var(--ink-soft); font-style: italic; margin-bottom: 5px; }
.cal-cta { font-size: 11.5px; font-weight: 700; color: var(--ok); }
.dica-semana { margin-top: 14px; font-size: 13.5px; background: rgba(29,111,184,.06); border-radius: 10px; padding: 12px 14px; }
.bloco { border-left: 3px solid var(--gold); padding: 4px 0 4px 14px; margin-bottom: 14px; }
.bloco-rotulo { font-size: 11px; font-weight: 800; text-transform: uppercase; letter-spacing: .1em; color: var(--blue-deep); }
.bloco-texto { font-size: 14px; line-height: 1.55; margin-top: 3px; white-space: pre-line; }
.legenda-box { background: #fbfcfd; border: 1px solid var(--line); border-radius: 10px; padding: 12px 14px; white-space: pre-line; }
.hashtags { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
.hashtags span { font-size: 12px; font-weight: 600; color: var(--blue-deep); background: rgba(29,111,184,.08); padding: 3px 9px; border-radius: 99px; }
.semaforo { display: inline-flex; align-items: center; font-weight: 800; padding: 7px 14px; border-radius: 999px; font-size: 13px; }
.sem-verde { background: rgba(30,142,90,.1); color: var(--ok); border: 1.5px solid var(--ok); }
.sem-amarelo { background: rgba(242,178,0,.12); color: var(--gold-deep); border: 1.5px solid var(--gold); }
.sem-vermelho { background: rgba(179,56,44,.08); color: var(--danger); border: 1.5px solid var(--danger); }
.resumo-analise { font-size: 15px; font-weight: 600; }
.met-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 10px; }
.met-card { border: 1px solid var(--line); border-radius: 12px; padding: 12px 13px; background: #fbfcfd; }
.met-nome { font-size: 10.5px; font-weight: 800; letter-spacing: .1em; text-transform: uppercase; color: var(--ink-soft); }
.met-valor { font-size: 21px; font-weight: 800; margin: 3px 0 2px; }
.met-boa { color: var(--ok); } .met-media { color: var(--gold-deep); } .met-ruim { color: var(--danger); }
.met-coment { font-size: 11.5px; color: var(--ink-soft); line-height: 1.4; }
.fortes { margin: 0; padding-left: 18px; font-size: 14px; line-height: 1.6; }
.fortes li::marker { color: var(--ok); }
.prob { border: 1px solid var(--line); border-left: 3px solid var(--danger); border-radius: 10px; padding: 12px 14px; margin-bottom: 10px; background: #fbfcfd; }
.prob-titulo { font-weight: 800; font-size: 13.5px; }
.prob-linha { font-size: 13px; margin-top: 4px; color: var(--ink); }
.prob-acao { font-size: 13.5px; margin-top: 7px; font-weight: 700; color: var(--blue-deep); }
.perfil-panel { max-width: 640px; margin: 0 auto; }
.perfil-intro { margin-bottom: 20px; }
.perfil-titulo { font-size: 20px; font-weight: 800; }
.perfil-sub { font-size: 13.5px; color: var(--ink-soft); margin-top: 5px; line-height: 1.5; }
.hist-head { font-weight: 800; font-size: 14px; margin-bottom: 12px; }
.hist-item { display: flex; align-items: center; gap: 12px; padding: 11px 12px; border: 1px solid var(--line); border-radius: 12px; margin-bottom: 8px; background: #fbfcfd; }
.hist-icon { font-size: 18px; }
.hist-info { flex: 1; min-width: 0; }
.hist-titulo { font-weight: 700; font-size: 13.5px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.hist-meta { font-size: 11.5px; color: var(--ink-soft); }
.hist-del { border: none; background: transparent; color: var(--ink-soft); cursor: pointer; font-size: 14px; padding: 6px; }
.hist-del:hover { color: var(--danger); }
.hist-vazio { text-align: center; padding: 40px 24px; }
.hist-vazio-icon { font-size: 34px; }
.hist-vazio-titulo { font-weight: 800; font-size: 16px; margin-top: 8px; }
.hist-vazio-sub { font-size: 13.5px; color: var(--ink-soft); margin-top: 6px; max-width: 46ch; margin-left: auto; margin-right: auto; line-height: 1.5; }
.copy-btn { border: 1.5px solid var(--ink); background: #fff; color: var(--ink); font-weight: 700; font-size: 12.5px; padding: 7px 14px; border-radius: 8px; cursor: pointer; transition: background .1s; }
.copy-btn:hover { background: var(--ink); color: #fff; }
.copy-sm { padding: 4px 10px; font-size: 11.5px; }
.footer { text-align: center; margin-top: 34px; font-size: 12.5px; color: var(--ink-soft); }
.footer b { color: var(--ink); }
.footer .foot-note { display: block; margin-top: 4px; font-size: 11.5px; }
`;
if (!ready)
return (
);
if (!perfil)
return (
Desenvolvido por Neto Matos · netomatos.cloud — automações com IA para o seu negócio
);
const mostrandoAbas = !editandoPerfil && tab !== "historico";
return (
Ferramenta do corretor de seguros
👤 {perfil.nome}{perfil.corretora ? " · " + perfil.corretora : ""}
{ setEditandoPerfil((v) => !v); }}>
✎ Perfil
{ setEditandoPerfil(false); setTab("historico"); }}>
📂 Histórico ({hist.length})
Central de Marketing do Corretor
Escolha, clique e copie — tudo já sai personalizado com o seu perfil e fica salvo no histórico.
{editandoPerfil &&
setEditandoPerfil(false)} />}
{!editandoPerfil && (
<>
{TABS.map((t) => (
setTab(t.id)}>
{t.icon}
{t.label}
{t.desc}
))}
{tab === "historico" && }
>
)}
Desenvolvido por Neto Matos · netomatos.cloud — automações com IA para o seu negócio
Conteúdo gerado por IA. Revise antes de publicar e siga as normas do seu mercado.
Os botões de Canva e Google Drive exigem esses conectores ativados no seu Claude (Configurações → Conectores).
);
}