/* ============================================================ KeS Metas — E-MAILS ------------------------------------------------------------ • SEQUÊNCIA POR ETAPA (drip): para cada etapa do funil, uma régua de e-mails com tempo próprio (ex.: na hora, +2 dias, +5 dias). Quando o lead ENTRA na etapa, os e-mails são agendados na fila. • ENVIO ESPECÍFICO: um e-mail avulso para um lead (modal). • A fila (outbox) é lida pelo n8n, que envia pelo Brevo (remetente comercial@kesassessoria.com.br) e marca "enviado". O app NUNCA guarda senha/API — igual ao WhatsApp. ============================================================ */ /* ---- Variáveis do texto: {nome} {empresa} {responsavel} ---- */ function aplicarVars(txt, lead, users) { const resp = lead && lead.responsavelId ? (users || []).find((u) => u.id === lead.responsavelId) : null; return String(txt || "") .replace(/\{nome\}/gi, (lead && lead.nome) || "") .replace(/\{empresa\}/gi, (lead && lead.empresa) || "") .replace(/\{responsavel\}/gi, (resp && resp.nome) || "KeS Assessoria"); } /* ---- Texto simples -> HTML (escapa e mantém parágrafos/quebras) ---- */ function corpoParaHtml(txt) { const esc = String(txt || "") .replace(/&/g, "&").replace(//g, ">"); return esc.split(/\n{2,}/).map((par) => '

' + par.replace(/\n/g, "
") + "

").join(""); } /* ---- TEMPLATES: monta o HTML "de site" a partir das opções escolhidas ---- O lugar onde a mensagem digitada entra é o marcador {mensagem}. */ function montarTemplateHtml(p) { p = p || {}; const cor = p.cor || "#132a4a"; const logo = p.logoUrl ? '' : ""; const cab = p.cabecalho || "KeS Assessoria"; const titulo = p.titulo ? '

' + p.titulo + "

" : ""; const botao = (p.botaoTexto && p.botaoLink) ? '
' + p.botaoTexto + "
" : ""; const rodape = p.rodape || "KeS Assessoria · comercial@kesassessoria.com.br"; const base = p.base || "classico"; const abre = '
' + '
'; const fecha = '
' + rodape + "
"; const corpoMeio = '
' + titulo + "{mensagem}" + botao + "
"; if (base === "moderno") { return abre + '
' + (logo ? logo + "

" : "") + '
' + cab + "
" + corpoMeio + fecha; } if (base === "minimal") { return abre.replace("border:1px solid #e3e7ee", "border:1px solid #e3e7ee;border-top:4px solid " + cor) + '
' + (logo ? logo + " " : "") + cab + "
" + corpoMeio + fecha; } // clássico: faixa de cor no topo com logo + nome return abre + '
' + (logo ? logo + " " : "") + cab + "
" + corpoMeio + fecha; } /* ---- Junta template + mensagem digitada + variáveis do lead ---- */ function renderEmailHtml(template, corpoTexto, lead, users) { if (!template || !template.html) return ""; const html = String(template.html).replace(/\{mensagem\}/gi, corpoParaHtml(corpoTexto)); return aplicarVars(html, lead, users); } /* ---- Monta os e-mails de uma sequência para 1 lead (sem enviar) ---- */ function montarSequencia(seq, lead, users, stageId, deId, templates) { if (!seq || !seq.ativo || !Array.isArray(seq.emails) || !lead || !lead.email) return []; const agora = Date.now(); return seq.emails.filter((e) => (e.assunto || "").trim() && (e.corpo || "").trim()).map((e, i) => { const tpl = e.templateId ? (templates || []).find((t) => t.id === e.templateId) : null; const m = { para: String(lead.email).trim().toLowerCase(), paraNome: lead.nome || "", leadId: lead.id, assunto: aplicarVars(e.assunto, lead, users), corpo: aplicarVars(e.corpo, lead, users), ts: agora, agendadoPara: agora + (parseInt(e.dias, 10) || 0) * 86400000, status: "pendente", origem: "sequencia", seqStage: stageId, seqIndex: i, de: deId || null, }; if (tpl) { m.templateId = tpl.id; m.html = renderEmailHtml(tpl, e.corpo, lead, users); } if (e.cc && String(e.cc).trim()) m.cc = String(e.cc).trim(); if (Array.isArray(e.anexos) && e.anexos.length) m.anexos = e.anexos.map((a) => ({ nome: a.nome, url: a.url })); return m; }); } window.KESMAIL = { aplicarVars, montarSequencia, montarTemplateHtml, renderEmailHtml, corpoParaHtml }; /* ============================ TELA ============================ */ function EmailScreen({ state, dispatch, viewer }) { const { useState, useEffect } = React; const DB = window.KESDB; const tenantId = tenantIdAtual(state); const [aba, setAba] = useState("seq"); // seq | fila const [fila, setFila] = useState([]); const [erroBanco, setErroBanco] = useState(false); useEffect(() => { setFila([]); setErroBanco(false); if (!DB || !DB.mailListen || !tenantId) return; return DB.mailListen(tenantId, {}, (lista, err) => { if (err) { setErroBanco(true); return; } setFila(lista || []); }); }, [tenantId]); const seqs = (state.config && state.config.emailSeqs) || {}; const salvarSeqs = (novo) => dispatch({ type: "SET_CONFIG", patch: { emailSeqs: novo } }); const etapasAtivas = Object.keys(seqs).filter((k) => seqs[k] && seqs[k].ativo).length; const pend = fila.filter((m) => m.status === "pendente"); const env = fila.filter((m) => m.status === "enviado"); const err = fila.filter((m) => m.status === "erro"); return (

E-mails {etapasAtivas > 0 && {etapasAtivas} sequência(s) ativa(s)}

Régua de e-mails por etapa do funil + envios avulsos. Remetente: comercial@kesassessoria.com.br

setAba("seq")}>Sequências setAba("modelos")}>Modelos setAba("fila")}>Fila {pend.length ? "(" + pend.length + ")" : ""}
{erroBanco && (
O banco recusou a leitura da fila de e-mails — falta publicar as regras.
)} {aba === "seq" ? ( ) : aba === "modelos" ? ( ) : ( )}
); } /* -------- Sequências por etapa (a régua) -------- */ function SequenciasPorEtapa({ state, seqs, onSalvar, dispatch }) { const { useState } = React; const stages = state.stages || []; const templates = (state.config && state.config.emailTemplates) || []; const tenantId = tenantIdAtual(state); const [aberta, setAberta] = useState(stages[0] ? stages[0].id : null); const seq = seqs[aberta] || { ativo: false, emails: [] }; const setSeq = (novo) => onSalvar(Object.assign({}, seqs, { [aberta]: novo })); const setEmail = (i, campo, val) => { const emails = (seq.emails || []).slice(); emails[i] = Object.assign({}, emails[i], { [campo]: val }); setSeq(Object.assign({}, seq, { emails })); }; const addEmail = () => setSeq(Object.assign({}, seq, { emails: (seq.emails || []).concat([{ assunto: "", corpo: "", dias: (seq.emails || []).length === 0 ? 0 : 2 }]) })); const delEmail = (i) => setSeq(Object.assign({}, seq, { emails: (seq.emails || []).filter((_, x) => x !== i) })); return (
{/* etapas */}
Etapas do funil
{stages.map((s) => { const q = (seqs[s.id] && seqs[s.id].emails || []).length; const on = seqs[s.id] && seqs[s.id].ativo; return ( ); })}
{/* régua da etapa */}
{!aberta ?
Escolha uma etapa
: ( <>

Régua de “{(stages.find((s) => s.id === aberta) || {}).nome}”

Os e-mails são agendados quando o lead entra nesta etapa.

{(seq.emails || []).length === 0 && (
Nenhum e-mail nesta etapa ainda.
)} {(seq.emails || []).map((e, i) => (
E-mail {i + 1}
Enviar setEmail(i, "dias", ev.target.value)} /> {Number(e.dias) === 0 ? "dias depois (na hora)" : "dias após entrar na etapa"}
setEmail(i, "assunto", ev.target.value)} placeholder="Ex.: {nome}, tudo bem?" />