/* Shell do aplicativo: login + abas + configurações + usuários. */ (async function init() { // sessao (o servidor ja bloqueia "/" sem cookie, mas precisamos dos dados do usuario) let usuarioAtual; try { ({ user: usuarioAtual } = await API.get('/api/auth/me')); } catch { window.location.href = '/login.html'; return; } el('usuario-logado').textContent = `${usuarioAtual.username} (${usuarioAtual.role === 'admin' ? 'admin' : 'comercial'})`; el('btn-usuarios').classList.toggle('hidden', usuarioAtual.role !== 'admin'); el('btn-sair').addEventListener('click', async () => { await API.post('/api/auth/logout'); window.location.href = '/login.html'; }); // usuarios (somente admin) async function carregarListaUsuarios() { const { users } = await API.get('/api/users'); el('lista-usuarios').replaceChildren(...users.map((u) => h('div', { class: 'row space-between' }, h('span', {}, u.username), h('span', { class: 'muted' }, u.role === 'admin' ? 'Administrador' : 'Comercial')))); } el('btn-usuarios').addEventListener('click', async () => { el('modal-usuarios').showModal(); try { await carregarListaUsuarios(); } catch (error) { toast(`Erro: ${error.message}`); } }); el('btn-fechar-usuarios').addEventListener('click', () => el('modal-usuarios').close()); el('form-novo-usuario').addEventListener('submit', async (event) => { event.preventDefault(); try { await API.post('/api/users', { username: el('novo-usuario-username').value, password: el('novo-usuario-password').value, role: el('novo-usuario-role').value }); el('novo-usuario-username').value = ''; el('novo-usuario-password').value = ''; await carregarListaUsuarios(); toast('Usuário criado'); } catch (error) { toast(`Erro: ${error.message}`); } }); // abas for (const tab of document.querySelectorAll('.tab')) { tab.addEventListener('click', () => { for (const t of document.querySelectorAll('.tab')) { t.classList.toggle('active', t === tab); t.setAttribute('aria-selected', t === tab ? 'true' : 'false'); } for (const panel of document.querySelectorAll('.tab-panel')) { panel.classList.toggle('active', panel.id === `tab-${tab.dataset.tab}`); } if (tab.dataset.tab === 'tratamento') Tratamento.ativar(); if (tab.dataset.tab === 'campanhas') Campanhas.ativar(); }); } // configurações let WABA_TEMPLATE_LIBRARY = []; let COMMERCIAL_VARIABLES = []; let focusedVariableInput = null; let manualLeadContext = null; async function carregarVariaveisComerciais() { const { variables } = await API.get('/api/outreach/variables'); COMMERCIAL_VARIABLES = variables || []; const picker = el('manual-variable-picker'); if (picker) { picker.replaceChildren( h('option', { value: '' }, 'Inserir variável da Máquina Comercial...'), ...COMMERCIAL_VARIABLES.map((variable) => h('option', { value: `{{${variable.key}}}` }, `{{${variable.key}}} - ${variable.label}`)) ); } } async function carregarBibliotecaWaba() { const { templates, lastSync } = await API.get('/api/outreach/waba-templates'); WABA_TEMPLATE_LIBRARY = templates || []; const select = el('manual-template-biblioteca'); if (select) select.replaceChildren(...templateOptions(select.value)); const statusEl = el('waba-template-library-status'); if (statusEl) { statusEl.textContent = lastSync ? `${WABA_TEMPLATE_LIBRARY.length} templates carregados. Última atualização: ${dataBr(lastSync)}` : `${WABA_TEMPLATE_LIBRARY.length} templates locais carregados. Para trazer todos do GChat, preencha o Token GChat e clique em Atualizar.`; } } function templateLabel(template) { return `${template.name} (${template.language})`; } function templateOptions(selected = '') { return [h('option', { value: '' }, 'Selecione um template...')].concat( WABA_TEMPLATE_LIBRARY.map((template) => h('option', { value: template.name, selected: template.name === selected ? '' : null }, templateLabel(template))) ); } function preencherVariaveis(inputs, variables, fallback = []) { inputs.forEach((input, index) => { input.value = variables[index] || fallback[index] || ''; }); } function templateVariableCount(template) { if (!template) return 0; const fromVariables = Array.isArray(template.variables) ? template.variables.length : 0; const matches = String(template.text || '').match(/{{\s*\d+\s*}}/g) || []; const fromText = Math.max(0, ...matches.map((match) => Number(match.replace(/\D/g, '')))); return Math.min(5, Math.max(fromVariables, fromText)); } function setVariableFields(container, template, inputs, fallback = []) { const count = templateVariableCount(template); inputs.forEach((input, index) => { const field = input.closest('.template-var-field') || input.closest('label'); const visible = index < count; if (field) field.classList.toggle('hidden', !visible); if (!visible) input.value = ''; else if (!input.value) input.value = template?.variables?.[index] || fallback[index] || ''; }); const empty = container?.querySelector?.('.template-vars-empty'); if (empty) empty.classList.toggle('hidden', count > 0); } function previewTemplateText(template, inputs) { const text = String(template?.text || '').trim(); if (!template) return 'Selecione um template para ver a pré-visualização.'; if (!text) return 'Pré-visualização indisponível para este template local.'; return text.replace(/{{\s*(\d+)\s*}}/g, (_, index) => { const input = inputs[Number(index) - 1]; const value = resolverVariavelManual(input?.value?.trim()); return value ? `[${value}]` : `[Variável ${index}]`; }); } function atualizarPreview(previewEl, template, inputs) { if (previewEl) previewEl.textContent = previewTemplateText(template, inputs); } function collectVisibleVariables(inputs) { return inputs .filter((input) => !(input.closest('.template-var-field') || input.closest('label'))?.classList.contains('hidden')) .map((input) => input.value.trim()); } function collectVisibleVariablesResolvidas(inputs) { return collectVisibleVariables(inputs).map(resolverVariavelManual); } function valoresVariaveisManuais(lead = manualLeadContext) { const empresa = lead?.empresa || lead?.legal_name || lead?.businessName || lead?.name || 'Empresa Teste'; const produto = lead?.produto || lead?.produto_principal || lead?.interesse || 'Atendimento comercial'; const segmento = lead?.segmento_label || lead?.segmento || 'Suporte'; const name = lead?.name || empresa; return { name, empresa, businessName: empresa, firstName: String(name).split(/\s+/).filter(Boolean)[0] || empresa, lastName: lead?.lastName || '', produto, protocol: lead?.protocol || produto, cidade: lead?.cidade || 'Salvador', uf: lead?.uf || 'BA', segmento, cnpj: lead?.cnpj || '00.000.000/0001-00', email: lead?.email || 'cliente@exemplo.com.br', phoneNumber: lead?.whatsapp || '5575999091780', whatsapp: lead?.whatsapp || '5575999091780', kanban: lead?.kanban || produto, user: lead?.user || 'Grupo GTI', userEmail: lead?.userEmail || 'comercial@gtigrupo.com.br', greeting: 'Olá' }; } function resolverVariavelManual(value) { const values = valoresVariaveisManuais(); let output = String(value || 'Informação') .replaceAll('{empresa}', values.empresa) .replaceAll('{produto}', values.produto) .replaceAll('{cidade}', values.cidade) .replaceAll('{segmento}', values.segmento); for (const [key, variableValue] of Object.entries(values)) { output = output.replaceAll(`{{${key}}}`, variableValue); } for (const variable of COMMERCIAL_VARIABLES) { if (!output.includes(`{{${variable.key}}}`)) continue; output = output.replaceAll(`{{${variable.key}}}`, values[variable.key] || variable.label); } return output; } const variavelExemplo = resolverVariavelManual; function missingVisibleVariables(inputs) { return inputs .filter((input) => !(input.closest('.template-var-field') || input.closest('label'))?.classList.contains('hidden')) .filter((input) => !input.value.trim()); } function aplicarTemplateBiblioteca(template, { nomeEl, idiomaEl, routeEl, variableInputs }) { if (!template) return; nomeEl.value = template.name; idiomaEl.value = template.language; if (routeEl) routeEl.value = template.route; preencherVariaveis(variableInputs, template.variables); } function variablePickerEl() { return h('select', { class: 'variable-picker' }, h('option', { value: '' }, 'Inserir variável da Máquina Comercial...'), ...COMMERCIAL_VARIABLES.map((variable) => h('option', { value: `{{${variable.key}}}` }, `{{${variable.key}}} - ${variable.label}`)) ); } async function carregarOutreach() { const { settings } = await API.get('/api/settings'); el('outreach-ativo').checked = settings['outreach.ativo'] === 'true'; el('outreach-limite').value = settings['outreach.mensagensPorDia'] || 10; const [{ templates }, status] = await Promise.all([ API.get('/api/outreach/templates'), API.get('/api/outreach/status') ]); el('outreach-status').textContent = `Enviado hoje: ${status.enviadosHoje}/${status.mensagensPorDia}`; el('outreach-templates').replaceChildren(...templates.map((t) => { const oficial = t.canal === 'oficial'; const mensagemEl = h('textarea', { class: 'outreach-template-mensagem', rows: 2, style: oficial ? 'display:none' : '' }, t.mensagem); const nomeEl = h('input', { type: 'text', class: 'outreach-template-waba-nome', value: t.template_waba_nome || '' }); const idiomaEl = h('input', { type: 'text', class: 'outreach-template-waba-idioma', value: t.template_waba_idioma || 'pt_BR' }); let variaveisSalvas = []; try { variaveisSalvas = JSON.parse(t.template_waba_variaveis_json || '[]'); } catch { variaveisSalvas = []; } let templateSelecionado = WABA_TEMPLATE_LIBRARY.find((item) => item.name === t.template_waba_nome) || null; const variaveisEls = Array.from({ length: 5 }, (_, index) => { const input = h('input', { type: 'text', class: 'outreach-template-waba-var', value: variaveisSalvas[index] || (index === 0 ? '{empresa}' : index === 1 ? '{produto}' : ''), placeholder: index === 0 ? '{empresa}' : index === 1 ? '{produto}' : '' }); input.addEventListener('focus', () => { focusedVariableInput = input; }); return h('label', { class: 'template-var-field' }, `Variável {{${index + 1}}}`, input); }); const bibliotecaEl = h('select', { class: 'outreach-template-biblioteca' }, ...templateOptions(t.template_waba_nome || '')); const variablePicker = variablePickerEl(); const previewEl = h('div', { class: 'template-preview' }, 'Selecione um template para ver a pré-visualização.'); variablePicker.addEventListener('change', () => { if (!focusedVariableInput || !oficialEl.contains(focusedVariableInput) || !variablePicker.value) return; focusedVariableInput.value = variablePicker.value; focusedVariableInput.dispatchEvent(new Event('input')); variablePicker.value = ''; }); bibliotecaEl.addEventListener('change', () => { templateSelecionado = WABA_TEMPLATE_LIBRARY.find((item) => item.name === bibliotecaEl.value); const inputs = Array.from(oficialEl.querySelectorAll('.outreach-template-waba-var')); aplicarTemplateBiblioteca(templateSelecionado, { nomeEl, idiomaEl, variableInputs: inputs }); setVariableFields(oficialEl, templateSelecionado, inputs, ['{empresa}', '{produto}']); atualizarPreview(previewEl, templateSelecionado, inputs); atualizarAviso(); }); const avisoEl = h('p', { class: 'muted outreach-template-aviso hidden' }, '⚠️ Preencha o nome do template pra essa automação funcionar.'); const oficialEl = h('div', { class: 'outreach-template-oficial', style: oficial ? '' : 'display:none' }, h('label', {}, 'Escolher modelo salvo', bibliotecaEl), h('div', { class: 'row' }, h('label', {}, 'Nome do template aprovado', nomeEl), h('label', {}, 'Idioma', idiomaEl)), h('div', { class: 'template-vars outreach-template-vars' }, h('label', {}, 'Variáveis automáticas'), h('p', { class: 'muted' }, 'Use placeholders como {empresa}, {produto}, {cidade}, {segmento}.'), h('div', { class: 'variable-helper' }, variablePicker, h('span', { class: 'muted' }, 'Clique em um campo abaixo e escolha uma variável.')), h('p', { class: 'muted template-vars-empty hidden' }, 'Este template não exige variáveis.'), h('div', { class: 'template-vars-grid' }, ...variaveisEls), previewEl), h('a', { href: 'https://business.facebook.com/wa/manage/message-templates/', target: '_blank', rel: 'noopener', class: 'outreach-template-link' }, '↗ Ver/criar templates no Meta Business Manager'), avisoEl); const atualizarAviso = () => { avisoEl.classList.toggle('hidden', radioOficial.checked ? Boolean(nomeEl.value.trim()) : true); }; nomeEl.addEventListener('input', atualizarAviso); for (const input of variaveisEls.map((label) => label.querySelector('input'))) { input.addEventListener('input', () => atualizarPreview(previewEl, templateSelecionado, Array.from(oficialEl.querySelectorAll('.outreach-template-waba-var')))); } const radioNaoOficial = h('input', { type: 'radio', name: `canal-${t.produto}`, class: 'outreach-template-canal', value: 'nao_oficial', checked: oficial ? null : '' }); const radioOficial = h('input', { type: 'radio', name: `canal-${t.produto}`, class: 'outreach-template-canal', value: 'oficial', checked: oficial ? '' : null }); const alternarCanal = () => { const usaOficial = radioOficial.checked; mensagemEl.style.display = usaOficial ? 'none' : ''; oficialEl.style.display = usaOficial ? '' : 'none'; atualizarAviso(); }; radioNaoOficial.addEventListener('change', alternarCanal); radioOficial.addEventListener('change', alternarCanal); atualizarAviso(); queueMicrotask(() => { const inputs = Array.from(oficialEl.querySelectorAll('.outreach-template-waba-var')); setVariableFields(oficialEl, templateSelecionado, inputs, ['{empresa}', '{produto}']); atualizarPreview(previewEl, templateSelecionado, inputs); }); return h('div', { class: 'item outreach-template', 'data-produto': t.produto }, h('div', { class: 'row space-between' }, h('strong', {}, t.produto), h('label', { class: 'radio' }, h('input', { type: 'checkbox', class: 'outreach-template-ativo', checked: t.ativo ? '' : null }), ' Ativo')), h('div', { class: 'row' }, h('label', { class: 'radio' }, radioNaoOficial, ' Não-oficial (texto livre)'), h('label', { class: 'radio' }, radioOficial, ' Oficial (Meta, template aprovado)')), mensagemEl, oficialEl); })); el('outreach-ultimos').replaceChildren(...status.ultimos.map((s) => { const quem = s.origem === 'manual' ? (s.numero || 'número avulso') : `${s.empresa || '?'} (${s.cidade || '?'})`; const rotulo = s.origem === 'manual' ? `Manual → ${quem}` : `${s.produto} → ${quem}`; const statusIcone = s.status === 'enviado' ? '✅' : '❌'; return h('div', { class: 'item' }, h('div', {}, `${statusIcone} ${rotulo}`), h('div', { class: 'sub' }, `${dataBr(s.sent_at)} · canal ${s.canal === 'oficial' ? 'oficial' : 'não-oficial'}${s.erro ? ` · ${s.erro}` : ''}`)); })); if (!status.ultimos.length) { el('outreach-ultimos').replaceChildren(h('p', { class: 'muted' }, 'Nenhum envio ainda.')); } } el('btn-settings').addEventListener('click', async () => { const { settings } = await API.get('/api/settings'); el('cfg-pipeline').value = settings['zpro.pipelineId'] || 1; el('cfg-stage').value = settings['zpro.stageId'] || 1; el('cfg-responsible').value = settings['zpro.responsibleId'] || 15; el('cfg-ticket-queue').value = settings['zpro.ticketQueueId'] || 12; el('cfg-forecast').value = settings['zpro.closingForecastDays'] || 30; el('cfg-zpro-baseurl').value = settings['zpro.baseUrl'] || ''; el('cfg-zpro-apiid').value = settings['zpro.apiId'] || ''; el('cfg-zpro-token').value = settings['zpro.bearerToken'] || ''; el('cfg-zpro-whatsappid').value = settings['zpro.whatsappId'] || ''; el('cfg-gchat-templates-url').value = settings['gchat.templatesUrl'] || ''; el('cfg-gchat-token').value = settings['gchat.bearerToken'] || ''; el('zpro-credenciais-status').textContent = ''; el('modal-settings').showModal(); try { await carregarBibliotecaWaba(); await carregarVariaveisComerciais(); await carregarOutreach(); } catch (error) { toast(`Erro ao carregar automação: ${error.message}`); } }); el('btn-testar-zpro').addEventListener('click', async () => { const statusEl = el('zpro-credenciais-status'); statusEl.textContent = 'Salvando e testando...'; try { await API.put('/api/settings', { 'zpro.baseUrl': el('cfg-zpro-baseurl').value.trim(), 'zpro.apiId': el('cfg-zpro-apiid').value.trim(), 'zpro.bearerToken': el('cfg-zpro-token').value.trim(), 'zpro.whatsappId': el('cfg-zpro-whatsappid').value.trim(), 'gchat.templatesUrl': el('cfg-gchat-templates-url').value.trim(), 'gchat.bearerToken': el('cfg-gchat-token').value.trim(), 'outreach.ativo': el('outreach-ativo').checked ? 'true' : 'false', 'outreach.mensagensPorDia': el('outreach-limite').value }); const resultado = await API.post('/api/outreach/testar-conexao'); const diag = resultado.diagnostico ? `\nBase: ${resultado.diagnostico.baseUrl || '-'} | API ID: ${resultado.diagnostico.apiId || '-'} | Token: ${resultado.diagnostico.token || '-'} | WhatsApp ID: ${resultado.diagnostico.whatsappId || '-'}` : ''; statusEl.textContent = resultado.ok ? `✅ Conexão OK${diag}` : `❌ ${resultado.erro}${diag}`; } catch (error) { statusEl.textContent = `❌ ${error.message}`; } }); el('btn-sync-gchat-templates').addEventListener('click', async () => { await sincronizarTemplatesGchat(); }); el('btn-sync-gchat-templates-inline').addEventListener('click', async () => { await sincronizarTemplatesGchat(); }); async function sincronizarTemplatesGchat() { const statusEl = el('gchat-template-sync-status'); const inlineStatusEl = el('waba-template-library-status'); statusEl.textContent = 'Sincronizando...'; if (inlineStatusEl) inlineStatusEl.textContent = 'Sincronizando com GChat...'; try { await API.put('/api/settings', { 'gchat.templatesUrl': el('cfg-gchat-templates-url').value.trim(), 'gchat.bearerToken': el('cfg-gchat-token').value.trim() }); const result = await API.post('/api/outreach/waba-templates/sync'); statusEl.textContent = result.ok ? `✅ ${result.total} templates sincronizados` : `❌ ${result.erro}`; if (inlineStatusEl) inlineStatusEl.textContent = result.ok ? `✅ ${result.total} templates sincronizados do GChat` : `❌ ${result.erro}. Preencha o Token GChat opcional em Credenciais Z-PRO.`; await carregarBibliotecaWaba(); await carregarOutreach(); } catch (error) { statusEl.textContent = `❌ ${error.message}`; if (inlineStatusEl) inlineStatusEl.textContent = `❌ ${error.message}`; } } el('btn-salvar-outreach').addEventListener('click', async () => { try { await API.put('/api/settings', { 'outreach.ativo': el('outreach-ativo').checked ? 'true' : 'false', 'outreach.mensagensPorDia': el('outreach-limite').value }); for (const item of el('outreach-templates').children) { const canalEl = item.querySelector('.outreach-template-canal:checked'); await API.put(`/api/outreach/templates/${encodeURIComponent(item.dataset.produto)}`, { mensagem: item.querySelector('.outreach-template-mensagem').value, ativo: item.querySelector('.outreach-template-ativo').checked, canal: canalEl ? canalEl.value : 'nao_oficial', templateWabaNome: item.querySelector('.outreach-template-waba-nome').value, templateWabaIdioma: item.querySelector('.outreach-template-waba-idioma').value, templateWabaVariaveis: collectVisibleVariables(Array.from(item.querySelectorAll('.outreach-template-waba-var'))) }); } await carregarOutreach(); toast('Automação salva'); } catch (error) { toast(`Erro: ${error.message}`); } }); function alternarCanalManual() { const oficial = el('manual-canal-oficial').checked; el('manual-mensagem-wrap').classList.toggle('hidden', oficial); el('manual-oficial-campos').classList.toggle('hidden', !oficial); } el('manual-canal-naooficial').addEventListener('change', alternarCanalManual); el('manual-canal-oficial').addEventListener('change', alternarCanalManual); el('manual-template-biblioteca').replaceChildren(...templateOptions()); el('manual-template-biblioteca').addEventListener('change', () => { const template = WABA_TEMPLATE_LIBRARY.find((item) => item.name === el('manual-template-biblioteca').value); const inputs = Array.from(document.querySelectorAll('.manual-template-var')); aplicarTemplateBiblioteca(template, { nomeEl: el('manual-template-nome'), idiomaEl: el('manual-template-idioma'), routeEl: el('manual-template-rota'), variableInputs: inputs }); setVariableFields(el('manual-oficial-campos'), template, inputs); atualizarPreview(el('manual-template-preview'), template, inputs); }); for (const input of document.querySelectorAll('.manual-template-var')) { input.addEventListener('focus', () => { focusedVariableInput = input; }); input.addEventListener('input', () => { const template = WABA_TEMPLATE_LIBRARY.find((item) => item.name === el('manual-template-biblioteca').value); atualizarPreview(el('manual-template-preview'), template, Array.from(document.querySelectorAll('.manual-template-var'))); }); } el('manual-variable-picker').addEventListener('change', () => { if (!focusedVariableInput || !el('manual-variable-picker').value) return; focusedVariableInput.value = el('manual-variable-picker').value; focusedVariableInput.dispatchEvent(new Event('input')); el('manual-variable-picker').value = ''; }); setVariableFields(el('manual-oficial-campos'), null, Array.from(document.querySelectorAll('.manual-template-var'))); el('btn-testar-template-biblioteca').addEventListener('click', async () => { const statusEl = el('manual-status'); const template = WABA_TEMPLATE_LIBRARY.find((item) => item.name === el('manual-template-biblioteca').value); const numero = el('manual-numero').value.trim(); statusEl.classList.remove('status-ok', 'status-error'); if (!template) { statusEl.textContent = '❌ Selecione um modelo salvo primeiro.'; statusEl.classList.add('status-error'); return; } if (!numero) { statusEl.textContent = '❌ Informe um número de teste acima.'; statusEl.classList.add('status-error'); return; } aplicarTemplateBiblioteca(template, { nomeEl: el('manual-template-nome'), idiomaEl: el('manual-template-idioma'), routeEl: el('manual-template-rota'), variableInputs: Array.from(document.querySelectorAll('.manual-template-var')) }); const variaveis = template.variables.map(resolverVariavelManual); preencherVariaveis(Array.from(document.querySelectorAll('.manual-template-var')), variaveis); setVariableFields(el('manual-oficial-campos'), template, Array.from(document.querySelectorAll('.manual-template-var'))); atualizarPreview(el('manual-template-preview'), template, Array.from(document.querySelectorAll('.manual-template-var'))); statusEl.textContent = `Testando ${template.name}...`; try { const resultado = await API.post('/api/outreach/enviar-manual', { numero, canal: 'oficial', mensagem: '', templateWabaNome: template.name, templateWabaIdioma: template.language, templateRota: template.route, templateVariaveis: variaveis }); statusEl.textContent = resultado.ok ? `✅ ${template.name} funcionou${resultado.ticketId ? ` — ticket ${resultado.ticketId}` : ''}` : `❌ ${template.name} falhou: ${resultado.erro}`; statusEl.classList.toggle('status-ok', Boolean(resultado.ok)); statusEl.classList.toggle('status-error', !resultado.ok); await carregarOutreach(); } catch (error) { statusEl.textContent = `❌ ${template.name} falhou: ${error.message}`; statusEl.classList.add('status-error'); } }); el('btn-toggle-outreach-log').addEventListener('click', () => { const log = el('outreach-ultimos'); const hidden = log.classList.toggle('hidden'); el('btn-toggle-outreach-log').textContent = hidden ? 'Ver log de envios' : 'Ocultar log de envios'; }); el('btn-enviar-manual').addEventListener('click', async () => { const statusEl = el('manual-status'); const oficial = el('manual-canal-oficial').checked; statusEl.classList.remove('status-ok', 'status-error'); if (oficial) { const faltando = missingVisibleVariables(Array.from(document.querySelectorAll('.manual-template-var'))); if (faltando.length) { statusEl.textContent = `❌ Preencha todas as variáveis do template (${faltando.length} pendente${faltando.length > 1 ? 's' : ''}).`; statusEl.classList.add('status-error'); return; } } statusEl.textContent = 'Enviando...'; try { const resultado = await API.post('/api/outreach/enviar-manual', { numero: el('manual-numero').value.trim(), canal: oficial ? 'oficial' : 'nao_oficial', mensagem: el('manual-mensagem').value, templateWabaNome: el('manual-template-nome').value.trim(), templateWabaIdioma: el('manual-template-idioma').value.trim(), templateVariaveis: collectVisibleVariablesResolvidas(Array.from(document.querySelectorAll('.manual-template-var'))), templateRota: el('manual-template-rota').value, lead: manualLeadContext }); statusEl.textContent = resultado.ok ? `✅ Enviado${resultado.ticketId ? ` — ticket ${resultado.ticketId}` : ''}` : `❌ ${resultado.erro}`; statusEl.classList.toggle('status-ok', Boolean(resultado.ok)); statusEl.classList.toggle('status-error', !resultado.ok); await carregarOutreach(); } catch (error) { statusEl.textContent = `❌ ${error.message}`; statusEl.classList.add('status-error'); } }); window.GTISelecionarLeadManual = (lead) => { manualLeadContext = lead || null; const template = WABA_TEMPLATE_LIBRARY.find((item) => item.name === el('manual-template-biblioteca').value); atualizarPreview(el('manual-template-preview'), template, Array.from(document.querySelectorAll('.manual-template-var'))); }; el('btn-salvar-settings').addEventListener('click', async (event) => { event.preventDefault(); try { await API.put('/api/settings', { 'zpro.pipelineId': el('cfg-pipeline').value, 'zpro.stageId': el('cfg-stage').value, 'zpro.responsibleId': el('cfg-responsible').value, 'zpro.ticketQueueId': el('cfg-ticket-queue').value, 'zpro.closingForecastDays': el('cfg-forecast').value, 'zpro.baseUrl': el('cfg-zpro-baseurl').value.trim(), 'zpro.apiId': el('cfg-zpro-apiid').value.trim(), 'zpro.bearerToken': el('cfg-zpro-token').value.trim(), 'zpro.whatsappId': el('cfg-zpro-whatsappid').value.trim(), 'gchat.templatesUrl': el('cfg-gchat-templates-url').value.trim(), 'gchat.bearerToken': el('cfg-gchat-token').value.trim(), 'outreach.ativo': el('outreach-ativo').checked ? 'true' : 'false', 'outreach.mensagensPorDia': el('outreach-limite').value }); await carregarOutreach(); el('modal-settings').close(); toast('Configurações salvas'); } catch (error) { toast(`Erro: ${error.message}`); } }); // limpar toda a base (zona de risco) el('btn-limpar-base').addEventListener('click', () => { el('reset-base-senha').value = ''; el('reset-base-erro').classList.add('hidden'); el('modal-settings').close(); el('modal-reset-base').showModal(); }); el('btn-cancelar-reset-base').addEventListener('click', () => { el('modal-reset-base').close(); }); // Erro aqui precisa aparecer DENTRO do modal (nao so no toast): um // aberto via showModal() fica na "top layer" do navegador, acima de // qualquer div normal — inclusive do toast, que ficava invisivel atras do // modal quando a senha estava errada, dando a impressao de que nada // acontecia (bug reportado: "limpeza nao foi feita mesmo pondo a senha"). el('btn-confirmar-reset-base').addEventListener('click', async () => { const erroEl = el('reset-base-erro'); const senha = el('reset-base-senha').value; if (!senha) { erroEl.textContent = 'Informe a senha de segurança'; erroEl.classList.remove('hidden'); return; } try { await API.post('/api/admin/reset', { senha }); el('modal-reset-base').close(); toast('Base de leads limpa com sucesso — recarregando...'); setTimeout(() => window.location.reload(), 1200); } catch (error) { erroEl.textContent = `Erro: ${error.message}`; erroEl.classList.remove('hidden'); } }); // fecha modais clicando fora for (const dialog of document.querySelectorAll('dialog')) { dialog.addEventListener('click', (event) => { if (event.target === dialog) dialog.close(); }); } Tratamento.bindEvents(); try { await Captura.init(); } catch (error) { toast(`Erro ao carregar: ${error.message}`, 8000); } })();