Hub de Operação
V4
COMPANY
Hub de Operação

Bem-vindo, Sandro.

Escolha um assistente para começar. Cada área foi desenhada para uma etapa específica da sua rotina comercial.

🎯
Proposta
01 · PROPOSTA COMERCIAL
Cole a transcrição da reunião e gere análise SPICED + landing page pronta para enviar ao cliente.
0
Propostas geradas · time
📞
Ligação
02 · ASSISTENTE DE TRANSCRIÇÃO DE LIGAÇÃO
Transcreva o áudio ou cole o texto da reunião — receba um brief estruturado do lead para o closer.
0
Ligações analisadas · time
🤝
Handoff
03 · PÓS-VENDA
Após fechar a venda — gere score do cliente, mensagem para WhatsApp e Plano de ROI em um clique.
0
Handoffs realizados · time
🎯
01 · ASSISTENTE DE PRÉ-VENDAS

Proposta por Transcrição

Cole a transcrição da reunião e gere uma proposta completa com análise SPICED e landing page pronta para publicar.

0
Salvas
Propostas geradas
0
Última geração
Status
Aguardando
🏢
Dados do cliente
Campos estruturados para uma proposta mais precisa
Cole a transcrição — empresa e contato serão preenchidos automaticamente.
0 caracteres
⚡ Extraído automaticamente da transcrição
✍️ Preencha manualmente
👁️ Pouca visibilidade online 📥 Sem geração de leads 🤝 Alta dependência de indicação 🌐 Dependência de portais 📉 Baixa taxa de conversão 📋 Sem CRM / processo 💰 Concorrência por preço 📊 Crescimento estagnado
Preencha os campos e clique em Gerar Proposta.
📞
02 · ASSISTENTE DE TRANSCRIÇÃO DE LIGAÇÃO

Validação de Ligação

Suba o áudio ou cole a transcrição da reunião. A IA estrutura um brief completo para o closer continuar o ciclo.

0
Salvas
🎙️
Nova análise
Envie o áudio ou cole a transcrição para gerar o brief de lead
🎙️
Clique para enviar áudio da ligação
MP3, M4A, WAV, MP4 — transcrição automática via IA
ou cole a transcrição
Aguardando transcrição para analisar.
🗂️
Ligações analisadas
0 ligações salvas
Nenhuma ligação analisada ainda.
🤝
03 · PASSAGEM PARA OPERAÇÃO

Handoff Comercial → Operação

Após o fechamento, gere o score do cliente, a mensagem para o grupo da operação e o Plano de ROI em um clique.

0
Salvos
📝
Nova venda fechada
Cole a transcrição da reunião de venda e preencha os dados
📋 Dados da venda
Cole a transcrição e preencha os dados para gerar o handoff.
🗂️
Handoffs gerados
0 handoffs salvos
Nenhum handoff gerado ainda.
Copiado com sucesso!
INSTRUÇÕES FINAIS: 1. Substitua TODOS os [PLACEHOLDERS] por conteúdo real da transcrição. 2. NÃO mude o HTML/CSS. NÃO adicione comentários. 3. Para o HERO_TITULO: monte uma frase de 4-7 palavras usando o nome ${clienteName} com a palavra-chave principal em destaque. Ex: "${clienteName} sem teto de crescimento" ou "O próximo nível para a ${clienteName}". 4. Para FRASE_FINAL: use 1 frase curta de impacto com destaque dourado em alguma palavra-chave. 5. Em VALOR_NUM: APENAS o número (ex: "2.000" não "R$ 2.000"). 6. Em CONDICOES_PAGAMENTO: ex: "em até 12x sem juros". 7. Retorne APENAS o HTML completo final, começando em .`; try { // Call 1: SPICED analysis (Modelo Sandro) const r1 = await fetch("https://v4-api.sandronotari-3.workers.dev", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ max_tokens: 3500, messages: [{ role: "user", content: promptSPICED }] }) }); if (!r1.ok) { const errText = await r1.text(); throw new Error('Backend retornou ' + r1.status + ': ' + errText.slice(0, 300)); } const d1 = await r1.json(); currentSPICED = d1.content?.[0]?.text || ''; setStatus('loading', 'Análise SPICED concluída. Gerando landing page...'); // Call 2: HTML landing const r2 = await fetch("https://v4-api.sandronotari-3.workers.dev", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ max_tokens: 7500, messages: [{ role: "user", content: promptHTML }] }) }); if (!r2.ok) { const errText = await r2.text(); throw new Error('Backend retornou ' + r2.status + ' na geração do HTML: ' + errText.slice(0, 300)); } const d2 = await r2.json(); let rawHTML = d2.content?.[0]?.text || ''; rawHTML = rawHTML.replace(/```html\n?/gi, '').replace(/```\n?/gi, '').trim(); currentHTML = rawHTML; // Update UI document.getElementById('spicedOutput').textContent = currentSPICED; document.getElementById('htmlOutput').textContent = currentHTML; // Preview const blob = new Blob([currentHTML], { type: 'text/html' }); const blobUrl = URL.createObjectURL(blob); document.getElementById('previewFrame').src = blobUrl; document.getElementById('previewUrlBar').textContent = `proposta-${clienteName.toLowerCase().replace(/\s+/g, '-')}.html`; // Show output document.getElementById('outputSection').style.display = 'flex'; // Save to history proposalCount++; const now = new Date(); localStorage.setItem('v4_count', proposalCount); document.getElementById('counterPropostas').textContent = proposalCount; document.getElementById('lastGenTime').textContent = now.toLocaleTimeString('pt-BR', {hour:'2-digit', minute:'2-digit'}); document.getElementById('statusLabel').textContent = 'Concluído'; const propostaItem = { id: Date.now() + '-' + Math.random().toString(36).slice(2, 8), name: clienteName, segmento, time: now.toLocaleString('pt-BR'), html: currentHTML, spiced: currentSPICED }; history.unshift(propostaItem); if (history.length > 50) history.pop(); localStorage.setItem('v4_history', JSON.stringify(history)); renderHistory(); // Salva online sem bloquear UI salvarPropostaOnline(propostaItem); setStatus('done', `✅ Proposta para ${clienteName} gerada com sucesso! Veja SPICED, Preview e HTML acima.`); } catch(e) { const msg = (e && e.message) ? e.message : String(e); setStatus('error', '❌ ' + msg); console.error('gerarProposta falhou:', e); alert('Erro ao gerar proposta:\n\n' + msg + '\n\nAbra o DevTools (F12) → Network → veja a chamada ao Worker pra ver o status real.'); } document.getElementById('btnGerar').disabled = false; document.getElementById('pulseDot').style.display = 'none'; } // ────────────────────────────────────────── // HELPERS // ────────────────────────────────────────── function setStatus(type, msg) { const bar = document.getElementById('statusBar'); bar.className = 'status-bar ' + type; document.getElementById('statusMsg').textContent = msg; } function copiarSPICED() { if (!currentSPICED) return; navigator.clipboard.writeText(currentSPICED); toast('📋 Análise SPICED copiada!'); } function copiarHTML() { if (!currentHTML) return; navigator.clipboard.writeText(currentHTML); toast('📋 HTML copiado para a área de transferência!'); } function baixarHTML() { if (!currentHTML) return; const nome = document.getElementById('clienteName').value.trim().toLowerCase().replace(/\s+/g, '-') || 'proposta'; const blob = new Blob([currentHTML], { type: 'text/html' }); const a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = `proposta-${nome}.html`; a.click(); toast('⬇ HTML baixado!'); } function abrirNovaAba() { if (!currentHTML) return; const blob = new Blob([currentHTML], { type: 'text/html' }); window.open(URL.createObjectURL(blob), '_blank'); } function limparFormulario() { document.getElementById('clienteName').value = ''; document.getElementById('clienteContato').value = ''; document.getElementById('segmento').value = ''; document.getElementById('faturamento').value = ''; document.getElementById('transcricao').value = ''; document.getElementById('charCount').textContent = '0'; document.querySelectorAll('#dorChips .chip').forEach(c => c.classList.remove('selected')); document.getElementById('outputSection').style.display = 'none'; document.getElementById('statusLabel').textContent = 'Aguardando'; setStatus('idle', 'Preencha os campos e clique em Gerar Proposta.'); currentHTML = ''; currentSPICED = ''; } function renderHistory() { const el = document.getElementById('historyList'); if (!history.length) { el.innerHTML = '
Nenhuma proposta ainda.
'; return; } el.innerHTML = history.map((h, i) => `
${h.name}
${h.segmento || ''} · ${h.time}
`).join(''); } function loadHistory(i) { const h = history[i]; if (!h) return; currentHTML = h.html; currentSPICED = h.spiced; document.getElementById('spicedOutput').textContent = currentSPICED; document.getElementById('htmlOutput').textContent = currentHTML; const blob = new Blob([currentHTML], { type: 'text/html' }); document.getElementById('previewFrame').src = URL.createObjectURL(blob); document.getElementById('previewUrlBar').textContent = `proposta-${h.name.toLowerCase().replace(/\s+/g,'-')}.html`; document.getElementById('outputSection').style.display = 'flex'; document.getElementById('clienteName').value = h.name; setStatus('done', `📂 Proposta de ${h.name} carregada do histórico.`); switchTab('proposta'); toast(`📂 Proposta de ${h.name} carregada`); } function exportarHistorico() { if (!history.length) { toast('⚠️ Nenhuma proposta para exportar.'); return; } const data = { exportado_em: new Date().toLocaleString('pt-BR'), versao: 'v4', propostas: history }; const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' }); const a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = `propostas-v4-${new Date().toISOString().slice(0,10)}.json`; a.click(); toast(`⬇ ${history.length} proposta(s) exportada(s)!`); } function importarHistorico(event) { const file = event.target.files[0]; if (!file) return; const reader = new FileReader(); reader.onload = function(e) { try { const data = JSON.parse(e.target.result); const importadas = data.propostas || (Array.isArray(data) ? data : null); if (!importadas || !importadas.length) { toast('⚠️ Arquivo inválido ou sem propostas.'); return; } // Mescla: importadas na frente, remove duplicatas pelo nome+time const existentes = new Set(history.map(h => h.name + '|' + h.time)); const novas = importadas.filter(h => !existentes.has(h.name + '|' + h.time)); history = [...novas, ...history].slice(0, 50); localStorage.setItem('v4_history', JSON.stringify(history)); renderHistory(); toast(`⬆ ${novas.length} proposta(s) importada(s)!`); } catch(err) { toast('❌ Erro ao ler o arquivo JSON.'); } }; reader.readAsText(file); event.target.value = ''; } function toast(msg) { const t = document.getElementById('toast'); document.getElementById('toastMsg').textContent = msg; t.classList.add('show'); setTimeout(() => t.classList.remove('show'), 2800); } // ────────────────────────────────────────── // VALIDAÇÃO DE LIGAÇÃO // ────────────────────────────────────────── let currentBrief = ''; let currentTranscricao = ''; let activeUser = localStorage.getItem('v4_active_user') || 'sandro'; let callsHistory = JSON.parse(localStorage.getItem(`v4_calls_history_${activeUser}`) || '[]'); const CALLS_API_BASE = 'https://v4-api.sandronotari-3.workers.dev/calls'; const USERS = { sandro: 'Sandro Notari', wlieli: 'Wlieli', luis: 'Luis', william: 'William', pamela: 'Pamela', }; function callsApi() { return `${CALLS_API_BASE}?user=${activeUser}`; } async function carregarLigacoesOnline() { try { const r = await fetch(callsApi()); if (!r.ok) throw new Error('HTTP ' + r.status); const d = await r.json(); if (Array.isArray(d.calls)) { callsHistory = d.calls; localStorage.setItem(`v4_calls_history_${activeUser}`, JSON.stringify(callsHistory)); renderCallsHistory(); atualizarStats(); } } catch(e) { console.warn('Falha ao carregar ligações online, usando cache local.', e); } } async function salvarLigacaoOnline(item) { try { const r = await fetch(callsApi(), { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(item), }); if (!r.ok) throw new Error('HTTP ' + r.status); return true; } catch(e) { console.warn('Falha ao salvar online, mantém só local.', e); return false; } } async function excluirLigacaoOnline(id) { try { await fetch(`${CALLS_API_BASE}/${id}?user=${activeUser}`, { method: 'DELETE' }); } catch(e) { console.warn('Falha ao excluir online.', e); } } async function trocarUsuario(novoUser) { if (!USERS[novoUser]) return; activeUser = novoUser; localStorage.setItem('v4_active_user', activeUser); document.getElementById('sidebarUserName').textContent = USERS[activeUser]; callsHistory = JSON.parse(localStorage.getItem(`v4_calls_history_${activeUser}`) || '[]'); handoffsHistory = JSON.parse(localStorage.getItem(`v4_handoffs_history_${activeUser}`) || '[]'); history = []; // limpa propostas — será preenchido com as do servidor para o usuário novo renderCallsHistory(); renderHandoffsHistory(); renderHistory(); toast(`👤 Trocado para ${USERS[activeUser]}`); await Promise.all([carregarLigacoesOnline(), carregarPropostasOnline(), carregarHandoffsOnline()]); } function setValidacaoStatus(type, msg) { const bar = document.getElementById('validacaoStatusBar'); const dot = document.getElementById('validacaoPulseDot'); const txt = document.getElementById('validacaoStatusMsg'); bar.className = 'status-bar ' + type; txt.textContent = msg; dot.style.display = type === 'loading' ? 'block' : 'none'; } function extrairEmpresaContato(briefTexto) { let empresa = 'Empresa'; let contato = 'Sem contato'; const reEmpresa = /Resumo de Lead:\s*([^\n(]+?)(?:\s*\(|$)/i; const mE = briefTexto.match(reEmpresa); if (mE && mE[1]) empresa = mE[1].trim().replace(/[🍽️🏭🏪🏥🏗️🚚🛍️💼🏢🏨📱]/g, '').trim(); const reContato = /Contato:\s*([^\n(]+?)(?:\s*\(|\.|$)/i; const mC = briefTexto.match(reContato); if (mC && mC[1]) contato = mC[1].trim(); return { empresa, contato }; } async function analisarLigacao() { const transcricao = document.getElementById('ligacaoTranscricao').value.trim(); if (!transcricao) { setValidacaoStatus('error', '⚠️ Cole a transcrição da ligação para continuar.'); return; } document.getElementById('btnAnalisar').disabled = true; setValidacaoStatus('loading', 'Analisando ligação e gerando brief de lead...'); document.getElementById('validacaoOutput').style.display = 'none'; currentTranscricao = transcricao; const prompt = `Você é um analista comercial sênior da V4 Company. Analise a transcrição desta ligação de prospecção e gere um brief de lead estruturado. TRANSCRIÇÃO: ${transcricao.substring(0, 10000)} Gere o brief EXATAMENTE neste formato (substitua os campos com dados reais da transcrição, use emojis adequados ao segmento): Resumo de Lead: [Tipo de negócio e segmento] ([Cidade/Região se mencionada]) [emoji do segmento] Contato: [Nome completo] ([Cargo e se é decisor]). Cenário Atual: [2-3 frases descrevendo a operação atual do negócio com detalhes específicos da ligação]. Faturamento e Metas: • Atual: [valor mensal bruto mencionado ou "não informado"] • Meta: [valor ou objetivo declarado ou "não informado"] Principal Dor: [Título curto da dor]. [2-4 frases detalhando a dor específica, com dados e contexto extraídos da ligação]. Investimento em Marketing: [Como está o marketing atual — agência, tráfego pago, redes sociais, quanto investe]. Oportunidade V4: [2-3 frases diretas para o closer — o que focar, o ângulo de abordagem, o argumento mais forte para fechar]. --- Pontos de atenção: [máx 3 bullets com objeções, riscos ou sinais de alerta identificados na ligação] Próximo passo recomendado: [ação concreta e imediata] Responda APENAS o brief, sem introdução, sem explicação, sem markdown extra.`; try { const r = await fetch('https://v4-api.sandronotari-3.workers.dev', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ max_tokens: 1500, messages: [{ role: 'user', content: prompt }] }) }); if (!r.ok) throw new Error('Erro ' + r.status); const d = await r.json(); currentBrief = d.content?.[0]?.text || ''; document.getElementById('briefOutput').textContent = currentBrief; document.getElementById('validacaoOutput').style.display = 'block'; // Salva no histórico const { empresa, contato } = extrairEmpresaContato(currentBrief); const item = { id: Date.now() + '-' + Math.random().toString(36).slice(2, 8), empresa, contato, time: new Date().toLocaleString('pt-BR'), transcricao: currentTranscricao, brief: currentBrief, }; callsHistory.unshift(item); if (callsHistory.length > 100) callsHistory.pop(); localStorage.setItem(`v4_calls_history_${activeUser}`, JSON.stringify(callsHistory)); renderCallsHistory(); // Salva online (não bloqueia) salvarLigacaoOnline(item).then(ok => { if (ok) setValidacaoStatus('done', `✅ Brief gerado e salvo online: ${empresa} · ${contato}`); }); setValidacaoStatus('done', `✅ Brief gerado e salvo: ${empresa} · ${contato}`); } catch(e) { setValidacaoStatus('error', '❌ Falha ao analisar: ' + e.message); } document.getElementById('btnAnalisar').disabled = false; } function renderCallsHistory() { const el = document.getElementById('callsHistoryList'); const count = document.getElementById('callsHistoryCount'); count.textContent = callsHistory.length === 0 ? '0 ligações salvas' : callsHistory.length === 1 ? '1 ligação salva' : `${callsHistory.length} ligações salvas`; if (!callsHistory.length) { el.innerHTML = '
Nenhuma ligação analisada ainda.
'; return; } el.innerHTML = callsHistory.map((c, i) => `
${escapeHtml(c.empresa)}: ${escapeHtml(c.contato)}
${c.time}
${escapeHtml(c.brief)}
${escapeHtml(c.transcricao)}
`).join(''); } function escapeHtml(s) { if (!s) return ''; return String(s).replace(/&/g,'&').replace(//g,'>').replace(/"/g,'"'); } function toggleCall(id) { const el = document.getElementById('call-item-' + id); if (el) el.classList.toggle('open'); } function copiarTexto(id, campo) { const c = callsHistory.find(x => String(x.id) === String(id)); if (!c) return; navigator.clipboard.writeText(c[campo] || ''); toast(campo === 'brief' ? '📋 Brief copiado!' : '📋 Transcrição copiada!'); } function recarregarLigacao(id) { const c = callsHistory.find(x => String(x.id) === String(id)); if (!c) return; document.getElementById('ligacaoTranscricao').value = c.transcricao; currentBrief = c.brief; currentTranscricao = c.transcricao; document.getElementById('briefOutput').textContent = c.brief; document.getElementById('validacaoOutput').style.display = 'block'; setValidacaoStatus('done', `📂 Carregado: ${c.empresa} · ${c.contato}`); document.getElementById('callsHistoryCard').scrollIntoView({ behavior: 'smooth', block: 'start' }); setTimeout(() => document.getElementById('validacaoOutput').scrollIntoView({ behavior: 'smooth', block: 'start' }), 100); toast(`📂 ${c.empresa} carregada`); } function excluirLigacao(id) { if (!confirm('Excluir esta ligação do histórico? Não pode ser desfeito.')) return; callsHistory = callsHistory.filter(x => String(x.id) !== String(id)); localStorage.setItem(`v4_calls_history_${activeUser}`, JSON.stringify(callsHistory)); renderCallsHistory(); excluirLigacaoOnline(id); toast('🗑 Ligação excluída.'); } function exportarLigacoes() { if (!callsHistory.length) { toast('⚠️ Nenhuma ligação para exportar.'); return; } const data = { exportado_em: new Date().toLocaleString('pt-BR'), versao: 'v4-calls', ligacoes: callsHistory }; const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' }); const a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = `ligacoes-v4-${new Date().toISOString().slice(0,10)}.json`; a.click(); toast(`⬇ ${callsHistory.length} ligação(ões) exportada(s)!`); } function importarLigacoes(event) { const file = event.target.files[0]; if (!file) return; const reader = new FileReader(); reader.onload = function(e) { try { const data = JSON.parse(e.target.result); const importadas = data.ligacoes || (Array.isArray(data) ? data : null); if (!importadas || !importadas.length) { toast('⚠️ Arquivo inválido.'); return; } const existentes = new Set(callsHistory.map(c => c.id)); const novas = importadas.filter(c => c.id && !existentes.has(c.id)); callsHistory = [...novas, ...callsHistory].slice(0, 50); localStorage.setItem(`v4_calls_history_${activeUser}`, JSON.stringify(callsHistory)); renderCallsHistory(); toast(`⬆ ${novas.length} ligação(ões) importada(s)!`); } catch(err) { toast('❌ Erro ao ler o JSON.'); } }; reader.readAsText(file); event.target.value = ''; } function copiarBrief() { if (!currentBrief) return; navigator.clipboard.writeText(currentBrief); toast('📋 Brief copiado!'); } function limparValidacao() { document.getElementById('ligacaoTranscricao').value = ''; document.getElementById('validacaoOutput').style.display = 'none'; document.getElementById('audioFileName').style.display = 'none'; currentBrief = ''; setValidacaoStatus('idle', 'Aguardando transcrição para analisar.'); } async function handleAudioUpload(event) { const file = event.target.files[0]; if (!file) return; const nameEl = document.getElementById('audioFileName'); nameEl.textContent = `🎙️ ${file.name} (${(file.size/1024/1024).toFixed(1)} MB)`; nameEl.style.display = 'block'; if (file.size > 25 * 1024 * 1024) { setValidacaoStatus('error', '⚠️ Arquivo muito grande. Máximo 25 MB. Converta para MP3 antes.'); return; } setValidacaoStatus('loading', 'Transcrevendo áudio via IA... aguarde.'); document.getElementById('btnAnalisar').disabled = true; try { const formData = new FormData(); formData.append('audio', file); const r = await fetch('https://v4-api.sandronotari-3.workers.dev/transcribe', { method: 'POST', body: formData, }); if (!r.ok) throw new Error('Erro ' + r.status); const d = await r.json(); const texto = d.text || ''; if (!texto) throw new Error('Transcrição vazia'); document.getElementById('ligacaoTranscricao').value = texto; setValidacaoStatus('done', '✅ Áudio transcrito! Clique em "Analisar ligação" para gerar o brief.'); } catch(e) { setValidacaoStatus('error', '❌ Falha na transcrição: ' + e.message); } document.getElementById('btnAnalisar').disabled = false; event.target.value = ''; } // ────────────────────────────────────────── // HANDOFF COMERCIAL → OPERAÇÃO // ────────────────────────────────────────── const HANDOFFS_API_BASE = 'https://v4-api.sandronotari-3.workers.dev/handoffs'; const WHATSAPP_DESTINO = '5511917161816'; const CRITERIOS_SCORE = [ { key: 'segmento', nome: 'Segmento', desc: 'Legal/moral, dominado pela V4' }, { key: 'faturamento', nome: 'Faturamento/Margem', desc: 'Capacidade financeira do cliente' }, { key: 'engajamento', nome: 'Engajamento', desc: 'SLA e facilidade de contato' }, { key: 'historico', nome: 'Histórico de Performance', desc: 'Já investiu em marketing/tráfego' }, { key: 'vendas', nome: 'Estrutura de vendas', desc: 'Time comercial e CRM' }, { key: 'midia', nome: 'Investimento em mídia', desc: 'Verba mensal para ads' }, { key: 'equipes', nome: 'Equipes paralelas', desc: 'Conflito ou complemento ao escopo' }, { key: 'expectativas', nome: 'Expectativas fora do escopo', desc: 'Alinhamento com o contratado' }, { key: 'fee', nome: 'Valor do fee', desc: 'Faixa de investimento V4' }, { key: 'restricoes', nome: 'Restrições/Processos', desc: 'Ativos publicitários e segmento' }, ]; let currentHandoff = null; let handoffsHistory = JSON.parse(localStorage.getItem(`v4_handoffs_history_${activeUser}`) || '[]'); function handoffsApi() { return `${HANDOFFS_API_BASE}?user=${activeUser || 'sandro'}`; } function setHandoffStatus(type, msg) { document.getElementById('handoffStatusBar').className = 'status-bar ' + type; document.getElementById('handoffStatusMsg').textContent = msg; document.getElementById('handoffPulseDot').style.display = type === 'loading' ? 'block' : 'none'; } function toggleProdutoOutro() { const sel = document.getElementById('handoffProduto').value; const inp = document.getElementById('handoffProdutoOutro'); if (sel === 'Outro') { inp.style.display = 'block'; inp.focus(); } else { inp.style.display = 'none'; inp.value = ''; } } function getProdutoFinal() { const sel = document.getElementById('handoffProduto').value; if (sel === 'Outro') { return document.getElementById('handoffProdutoOutro').value.trim() || 'Outro'; } return sel; } async function gerarHandoff() { const transcricao = document.getElementById('handoffTranscricao').value.trim(); if (!transcricao) { setHandoffStatus('error', '⚠️ Cole a transcrição da reunião para continuar.'); return; } const dados = { produto: getProdutoFinal(), instagram: document.getElementById('handoffInstagram').value.trim(), linkReuniao: document.getElementById('handoffLinkReuniao').value.trim(), linkResumo: document.getElementById('handoffLinkResumo').value.trim(), linkContrato: document.getElementById('handoffLinkContrato').value.trim(), dataVenda: document.getElementById('handoffDataVenda').value, dataInicio: document.getElementById('handoffDataInicio').value, }; document.getElementById('btnGerarHandoff').disabled = true; setHandoffStatus('loading', 'Analisando reunião, calculando score e gerando handoff...'); document.getElementById('handoffOutput').style.display = 'none'; const formatData = (d) => d ? new Date(d + 'T00:00:00').toLocaleDateString('pt-BR') : '___/___/___'; const prompt = `Você é um analista comercial sênior da V4 Company. Analise a transcrição da reunião de venda fechada abaixo e retorne um JSON com a análise estruturada. DADOS COMPLEMENTARES JÁ COLETADOS: - Produto contratado: ${dados.produto || 'não informado'} - Instagram: ${dados.instagram || 'não informado'} - Link reunião: ${dados.linkReuniao || 'não informado'} - Link resumo: ${dados.linkResumo || 'não informado'} - Data venda: ${formatData(dados.dataVenda)} - Data início: ${formatData(dados.dataInicio)} TRANSCRIÇÃO: ${transcricao.substring(0, 10000)} CRITÉRIOS DE SCORE (escala 0-5 cada, fórmula final = média × 20): 1. SEGMENTO (segmento legal/moral, dominado pela V4): 0=Ilegal | 1=Imorais | 2=Sem histórico | 3=Breve histórico | 4=Histórico na unidade | 5=Nicho super dominado 2. FATURAMENTO/MARGEM (faturamento mensal do cliente): 0=R$79k | 1=R$89k | 2=R$100k | 3=R$150k | 4=R$350k | 5=acima R$351k 3. ENGAJAMENTO (SLA e facilidade de contato): 0=SLA 60d e difícil | 1=SLA 45d | 2=SLA 30d | 3=SLA 20d | 4=SLA 15d | 5=SLA 10d e muito engajado 4. HISTÓRICO DE PERFORMANCE (já investiu em marketing): 0=Sem presença digital | 1=Nunca investiu em mkt | 2=Nunca investiu em tráfego | 3=Investiu sem dados | 4=Investiu +R$30k com histórico | 5=+R$30k com histórico + CRM 5. ESTRUTURA DE VENDAS: 0=Sem vendedores | 1=Delega para não-vendedor | 2=Poucos com múltiplas funções | 3=Vendedores dedicados aquisição | 4=Aquisição + monetização | 5=Inside Sales estruturado com CRM 6. INVESTIMENTO EM MÍDIA (verba mensal em ads): 0=Menos de R$1k | 1=A partir R$1,5k | 2=A partir R$2k | 3=A partir R$3k | 4=A partir R$5k | 5=Mais de R$7k 7. EQUIPES PARALELAS: 0=Tem outro fornecedor mkt | 1=Time conflitante com escopo | 2=Sem conflito | 3=Sem times ou complementar | 4=Time neutro | 5=Time complementar e promotor V4 8. EXPECTATIVAS FORA DO ESCOPO: 0=Não acordada | 1=Acordada mas sem know-how | 2=Acordada sem performance | 3=Entregas acordadas | 4=100% acordo com escopo | 5=Contratou 7 produtos com expectativa 100% alinhada 9. VALOR DO FEE: 0=Abaixo R$3,5k | 1=R$4k | 2=R$4,5k | 3=R$5k | 4=Até R$6,1k | 5=A partir R$6,1k 10. CLIENTE POSSUI RESTRIÇÕES/PROCESSO: 0=Restrição + nicho black | 1=Restrição publicitária | 2=Restrição modelo negócio | 3=Sem restrição | 4=Sem restrição + sem problemas | 5=Saudável + possibilidades expansão RETORNE APENAS ESTE JSON (sem markdown, sem comentários): { "nome_cliente": "nome completo da pessoa decisora", "nome_empresa": "razão social ou nome fantasia da empresa", "negocio_resumo": "1 frase descrevendo o que a empresa faz", "resultados_esperados": "2-3 frases sobre o que cliente quer alcançar com a V4", "premissas_riscos": "principais riscos/premissas identificados na conversa (2-3 frases)", "bonus": "se foi prometido algum bônus (ex: 2 redes sociais grátis, mês extra), ou null", "score": { "segmento": { "nota": 0-5, "justificativa": "1 frase" }, "faturamento": { "nota": 0-5, "justificativa": "1 frase" }, "engajamento": { "nota": 0-5, "justificativa": "1 frase" }, "historico": { "nota": 0-5, "justificativa": "1 frase" }, "vendas": { "nota": 0-5, "justificativa": "1 frase" }, "midia": { "nota": 0-5, "justificativa": "1 frase" }, "equipes": { "nota": 0-5, "justificativa": "1 frase" }, "expectativas": { "nota": 0-5, "justificativa": "1 frase" }, "fee": { "nota": 0-5, "justificativa": "1 frase" }, "restricoes": { "nota": 0-5, "justificativa": "1 frase" } } }`; try { const r = await fetch('https://v4-api.sandronotari-3.workers.dev', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ max_tokens: 2500, messages: [{ role: 'user', content: prompt }] }) }); if (!r.ok) throw new Error('Erro ' + r.status); const d = await r.json(); let raw = d.content?.[0]?.text || '{}'; raw = raw.replace(/```json\n?/gi,'').replace(/```\n?/gi,'').trim(); // Extrai apenas o JSON entre { e } (tolerante a texto extra) const first = raw.indexOf('{'); const last = raw.lastIndexOf('}'); if (first >= 0 && last > first) raw = raw.substring(first, last + 1); const analise = JSON.parse(raw); // Calcula score final const notas = CRITERIOS_SCORE.map(c => Number(analise.score?.[c.key]?.nota) || 0); const scoreFinal = Math.round((notas.reduce((a,b)=>a+b, 0) / notas.length) * 20); // Monta mensagem WhatsApp const mensagem = montarMensagemWhatsapp(analise, dados, scoreFinal); // Monta Plano de ROI const planoROI = montarPlanoROI(analise, dados, scoreFinal); currentHandoff = { id: Date.now() + '-' + Math.random().toString(36).slice(2, 8), time: new Date().toLocaleString('pt-BR'), nome_cliente: analise.nome_cliente || 'Sem nome', nome_empresa: analise.nome_empresa || 'Sem empresa', score: scoreFinal, analise, dados, mensagem, planoROI, transcricao, }; renderHandoffOutput(currentHandoff); // Salva no histórico handoffsHistory.unshift(currentHandoff); if (handoffsHistory.length > 100) handoffsHistory.pop(); localStorage.setItem(`v4_handoffs_history_${activeUser}`, JSON.stringify(handoffsHistory)); renderHandoffsHistory(); salvarHandoffOnline(currentHandoff); setHandoffStatus('done', `✅ Handoff gerado · Score ${scoreFinal} · ${analise.nome_empresa || 'cliente'}`); } catch(e) { setHandoffStatus('error', '❌ Falha: ' + e.message); console.error('gerarHandoff falhou:', e); } document.getElementById('btnGerarHandoff').disabled = false; } function montarMensagemWhatsapp(a, d, score) { const formatData = (s) => s ? new Date(s + 'T00:00:00').toLocaleDateString('pt-BR') : ''; const emoji = score >= 60 ? '🟢' : score >= 50 ? '🟡' : '🔴'; const linhaContrato = d.linkContrato ? `- Contrato: ${d.linkContrato}` : `- Enviar contrato em PDF`; return `✅ NOVA VENDA Nome do cliente: ${a.nome_cliente || ''} Nome da empresa: ${a.nome_empresa || ''} Instagram: ${d.instagram || ''} Link da Reunião de Vendas: ${d.linkReuniao || ''} Link do Resumo: ${d.linkResumo || ''} Link do Contrato: ${d.linkContrato || ''} O que contratou? ${d.produto || ''} *Bônus:* ${a.bonus || '—'} Data da venda: ${formatData(d.dataVenda)} Data agendada para início: ${formatData(d.dataInicio)} ${emoji} *Score do cliente: ${score}/100* ${score < 60 ? '⚠️ Abaixo do mínimo (60)' : ''} ${linhaContrato} - Enviar plano de ROI`; } function montarPlanoROI(a, d, score) { const formatData = (s) => s ? new Date(s + 'T00:00:00').toLocaleDateString('pt-BR') : '—'; return `MODELO DO PLANO DE ROI ▸ VISÃO GERAL / INFORMAÇÕES Nome: ${a.nome_empresa || '—'} Stakeholder: ${a.nome_cliente || '—'} Instagram: ${d.instagram || '—'} Negócio: ${a.negocio_resumo || '—'} ▸ RESULTADOS ESPERADOS ${a.resultados_esperados || '—'} ▸ O QUE FOI COMPRADO ${d.produto || '—'} Contrato: ${d.linkContrato || '—'} ▸ PRÉVIA DO PROJETO Início do projeto: ${formatData(d.dataInicio)} Data da venda: ${formatData(d.dataVenda)} ▸ FUNÇÕES & RESPONSABILIDADES Stakeholder do cliente: ${a.nome_cliente || '—'} ▸ BÔNUS / PACOTES ADICIONAIS ${a.bonus || '—'} ▸ PREMISSAS E RISCOS ${a.premissas_riscos || '—'} ▸ SCORE DO CLIENTE ${score}/100 — ${score >= 60 ? '✅ Aprovado para operação' : '⚠️ Abaixo do mínimo (60)'}`; } function renderHandoffOutput(h) { // Score const badge = document.getElementById('handoffScoreBadge'); badge.textContent = h.score; if (h.score >= 60) { badge.style.background = 'rgba(34,197,94,0.15)'; badge.style.color = 'var(--green)'; badge.style.border = '1px solid rgba(34,197,94,0.4)'; } else if (h.score >= 50) { badge.style.background = 'rgba(234,179,8,0.15)'; badge.style.color = 'var(--yellow)'; badge.style.border = '1px solid rgba(234,179,8,0.4)'; } else { badge.style.background = 'rgba(212,168,95,0.15)'; badge.style.color = '#f87171'; badge.style.border = '1px solid rgba(212,168,95,0.45)'; } // Critérios const cont = document.getElementById('handoffScoreCriterios'); cont.innerHTML = CRITERIOS_SCORE.map(c => { const item = h.analise.score?.[c.key] || { nota: 0, justificativa: '—' }; const nota = Number(item.nota) || 0; const cor = nota >= 4 ? 'var(--green)' : nota >= 2 ? 'var(--yellow)' : '#f87171'; return `
${nota}
${c.nome}
${escapeHtml(item.justificativa || '—')}
`; }).join(''); // Mensagem e Plano document.getElementById('handoffMsgOutput').textContent = h.mensagem; document.getElementById('handoffROIOutput').textContent = h.planoROI; document.getElementById('handoffOutput').style.display = 'flex'; } function copiarMsgWhatsapp() { if (!currentHandoff) return; navigator.clipboard.writeText(currentHandoff.mensagem); toast('📋 Mensagem copiada!'); } function copiarPlanoROI() { if (!currentHandoff) return; navigator.clipboard.writeText(currentHandoff.planoROI); toast('📋 Plano de ROI copiado!'); } function enviarWhatsapp() { if (!currentHandoff) return; const msg = encodeURIComponent(currentHandoff.mensagem); window.open(`https://wa.me/${WHATSAPP_DESTINO}?text=${msg}`, '_blank'); } function limparHandoff() { document.getElementById('handoffTranscricao').value = ''; document.getElementById('handoffProduto').value = ''; document.getElementById('handoffProdutoOutro').value = ''; document.getElementById('handoffProdutoOutro').style.display = 'none'; document.getElementById('handoffInstagram').value = ''; document.getElementById('handoffLinkReuniao').value = ''; document.getElementById('handoffLinkResumo').value = ''; document.getElementById('handoffLinkContrato').value = ''; document.getElementById('handoffDataVenda').value = ''; document.getElementById('handoffDataInicio').value = ''; document.getElementById('handoffOutput').style.display = 'none'; currentHandoff = null; setHandoffStatus('idle', 'Cole a transcrição e preencha os dados para gerar o handoff.'); } function renderHandoffsHistory() { const el = document.getElementById('handoffHistoryList'); const count = document.getElementById('handoffHistoryCount'); count.textContent = handoffsHistory.length === 0 ? '0 handoffs salvos' : handoffsHistory.length === 1 ? '1 handoff salvo' : `${handoffsHistory.length} handoffs salvos`; if (!handoffsHistory.length) { el.innerHTML = '
Nenhum handoff gerado ainda.
'; return; } el.innerHTML = handoffsHistory.map(h => { const cor = h.score >= 60 ? 'var(--green)' : h.score >= 50 ? 'var(--yellow)' : '#f87171'; return `
${escapeHtml(h.nome_empresa || '—')}: ${escapeHtml(h.nome_cliente || '—')}
${h.score} ${h.time}
${escapeHtml(h.mensagem)}
${escapeHtml(h.planoROI)}
`; }).join(''); } function toggleHandoff(id) { const el = document.getElementById('hand-item-' + id); if (el) el.classList.toggle('open'); } function copiarHandoffSalvo(id, campo) { const h = handoffsHistory.find(x => String(x.id) === String(id)); if (!h) return; navigator.clipboard.writeText(h[campo] || ''); toast(campo === 'mensagem' ? '📋 Mensagem copiada!' : '📋 Plano de ROI copiado!'); } function enviarHandoffSalvo(id) { const h = handoffsHistory.find(x => String(x.id) === String(id)); if (!h) return; const msg = encodeURIComponent(h.mensagem); window.open(`https://wa.me/${WHATSAPP_DESTINO}?text=${msg}`, '_blank'); } function recarregarHandoff(id) { const h = handoffsHistory.find(x => String(x.id) === String(id)); if (!h) return; currentHandoff = h; document.getElementById('handoffTranscricao').value = h.transcricao || ''; // Produto: se está na lista padrão, seleciona; senão, marca "Outro" e preenche o campo livre const produtoSalvo = h.dados?.produto || ''; const sel = document.getElementById('handoffProduto'); const opcoes = [...sel.options].map(o => o.value); if (opcoes.includes(produtoSalvo)) { sel.value = produtoSalvo; document.getElementById('handoffProdutoOutro').style.display = 'none'; document.getElementById('handoffProdutoOutro').value = ''; } else if (produtoSalvo) { sel.value = 'Outro'; const inp = document.getElementById('handoffProdutoOutro'); inp.style.display = 'block'; inp.value = produtoSalvo; } else { sel.value = ''; document.getElementById('handoffProdutoOutro').style.display = 'none'; } document.getElementById('handoffInstagram').value = h.dados?.instagram || ''; document.getElementById('handoffLinkReuniao').value = h.dados?.linkReuniao || ''; document.getElementById('handoffLinkResumo').value = h.dados?.linkResumo || ''; document.getElementById('handoffLinkContrato').value = h.dados?.linkContrato || ''; document.getElementById('handoffDataVenda').value = h.dados?.dataVenda || ''; document.getElementById('handoffDataInicio').value = h.dados?.dataInicio || ''; renderHandoffOutput(h); setHandoffStatus('done', `📂 Carregado: ${h.nome_empresa} · Score ${h.score}`); toast(`📂 ${h.nome_empresa} carregado`); } function excluirHandoff(id) { if (!confirm('Excluir este handoff do histórico? Não pode ser desfeito.')) return; handoffsHistory = handoffsHistory.filter(x => String(x.id) !== String(id)); localStorage.setItem(`v4_handoffs_history_${activeUser}`, JSON.stringify(handoffsHistory)); renderHandoffsHistory(); excluirHandoffOnline(id); toast('🗑 Handoff excluído.'); } async function carregarHandoffsOnline() { try { const r = await fetch(handoffsApi()); if (!r.ok) throw new Error('HTTP ' + r.status); const d = await r.json(); if (Array.isArray(d.handoffs)) { handoffsHistory = d.handoffs; localStorage.setItem(`v4_handoffs_history_${activeUser}`, JSON.stringify(handoffsHistory)); renderHandoffsHistory(); atualizarStats(); } } catch(e) { console.warn('Falha ao carregar handoffs online, usando cache local.', e); } } async function salvarHandoffOnline(item) { try { const r = await fetch(handoffsApi(), { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(item), }); if (!r.ok) throw new Error('HTTP ' + r.status); return true; } catch(e) { console.warn('Falha ao salvar handoff online.', e); return false; } } async function excluirHandoffOnline(id) { if (!id) return; try { await fetch(`${HANDOFFS_API_BASE}/${id}?user=${activeUser || 'sandro'}`, { method: 'DELETE' }); } catch(e) {} } // ────────────────────────────────────────── // PUBLICAR LANDING (Cloudflare Pages API) // ────────────────────────────────────────── async function publicarLanding() { if (!currentHTML) { toast('⚠️ Nenhuma landing gerada ainda.'); return; } const cliente = document.getElementById('clienteName').value.trim() || 'proposta'; const slugSugerido = cliente.toLowerCase() .normalize('NFD').replace(/[\u0300-\u036f]/g, '') .replace(/[^a-z0-9-]+/g, '-').replace(/^-+|-+$/g, '').slice(0, 40) || 'proposta'; const slug = prompt( 'Slug da URL (vai virar https://prop-.pages.dev):', slugSugerido ); if (!slug) return; const btn = document.getElementById('btnPublicar'); if (btn) { btn.disabled = true; btn.textContent = '⏳ Publicando...'; } setStatus('loading', 'Subindo landing pro Cloudflare Pages...'); try { const r = await fetch('/api/publish', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ slug, html: currentHTML }), }); const data = await r.json(); if (!r.ok || !data.ok) { throw new Error(data.error + (data.detail ? ': ' + data.detail : '')); } // Usa SEMPRE a production_url (URL fixa). A preview_url é temporária e some. const url = data.production_url || data.url; setStatus('done', `✅ Publicado! URL: ${url}`); toast(`🚀 Landing online: ${url}`); // Aguarda propagação (production URL leva ~10s pra estabilizar no Cloudflare) setTimeout(async () => { try { await fetch(url, { mode: 'no-cors', cache: 'no-store' }); } catch(_) {} }, 8000); // Mostra modal/banner com link clicável mostrarLinkPublicado(url, data.preview_url); // Salva no histórico if (history[0]) { history[0].published_url = url; localStorage.setItem('v4_history', JSON.stringify(history)); renderHistory(); } } catch(e) { setStatus('error', '❌ Falha ao publicar: ' + e.message); console.error('publicarLanding falhou:', e); alert('Erro ao publicar:\n\n' + e.message + '\n\nVerifique se CF_ACCOUNT_ID e CF_API_TOKEN estão configurados nas env vars do Cloudflare Pages.'); } finally { if (btn) { btn.disabled = false; btn.textContent = '🚀 Publicar online'; } } } function mostrarLinkPublicado(url, previewUrl) { // Cria/atualiza um banner no topo do outputSection com o link publicado let banner = document.getElementById('publishedBanner'); if (!banner) { banner = document.createElement('div'); banner.id = 'publishedBanner'; banner.style.cssText = 'background:rgba(34,197,94,0.08); border:1px solid rgba(34,197,94,0.3); border-radius:8px; padding:14px 18px; display:flex; align-items:center; gap:12px; flex-wrap:wrap;'; const out = document.getElementById('outputSection'); out.insertBefore(banner, out.firstChild); } banner.innerHTML = ` 🚀
Landing publicada
${url}
Aguarde 10-30s para o DNS propagar antes de abrir.
`; }