From aa74c984d0749e114b4f96548c7a33772b401db4 Mon Sep 17 00:00:00 2001 From: Ayron Santos Date: Wed, 17 Jun 2026 17:27:40 -0300 Subject: [PATCH] =?UTF-8?q?Corre=C3=A7=C3=B5es?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/controllers/chatController.js | 3 +- src/controllers/evolutionController.js | 78 ++++++++++++++------------ src/public/chat.html | 24 +++++++- src/public/client-detail.html | 65 +++++++++++++++++---- 4 files changed, 123 insertions(+), 47 deletions(-) diff --git a/src/controllers/chatController.js b/src/controllers/chatController.js index ad15f0f..22b943f 100644 --- a/src/controllers/chatController.js +++ b/src/controllers/chatController.js @@ -611,7 +611,8 @@ class ChatController { if (user.length > 0) { const nomeUser = (user[0].USU_NOME || '').trim(); if (nomeUser && textoFinal) { - textoFinal = nomeUser + ': ' + textoFinal; + // Padrão WhatsApp: *Nome:* em negrito + quebra de linha + mensagem + textoFinal = '*' + nomeUser + ':*\n' + textoFinal; } } } diff --git a/src/controllers/evolutionController.js b/src/controllers/evolutionController.js index 4468749..7b71983 100644 --- a/src/controllers/evolutionController.js +++ b/src/controllers/evolutionController.js @@ -440,10 +440,10 @@ class EvolutionController { // Reaproveita a funcao de atualizar foto var numero = conv[0].CON_NUMERO || ''; var empresaId = conv[0].CON_EMPRESA_ID; - // A funcao atualizarFotoContato esta no escopo do processWebhook, entao recriamos a logica aqui - await EvolutionController.atualizarFotoContato(alias, conv[0].CON_CLIENTE_ID, numero, empresaId); + // Ação explícita do usuário: força a busca (ignora a flag CFE_FOTO_CELULAR) + const atualizou = await EvolutionController.atualizarFotoContato(alias, conv[0].CON_CLIENTE_ID, numero, empresaId, true); - res.json({ success: true, message: 'Foto atualizada.' }); + res.json({ success: true, atualizada: !!atualizou, message: atualizou ? 'Foto atualizada.' : 'Nenhuma foto encontrada para este contato.' }); } catch (err) { res.status(500).json({ success: false, error: err.message }); } @@ -452,12 +452,14 @@ class EvolutionController { /** * Versão estática de atualizarFotoContato para uso externo */ - static async atualizarFotoContato(alias, clienteId, numero, empresaId) { - if (!clienteId) return; + static async atualizarFotoContato(alias, clienteId, numero, empresaId, forcar) { + if (!clienteId) return false; try { - const cfg = await db.query(alias, - "SELECT CFE_FOTO_CELULAR FROM CHATC2_CONFIGURACOES_EMPRESA WHERE CFE_EMPRESA_ID = ?", [empresaId]); - if (cfg.length === 0 || cfg[0].CFE_FOTO_CELULAR !== 'S') return; + if (!forcar) { + const cfg = await db.query(alias, + "SELECT CFE_FOTO_CELULAR FROM CHATC2_CONFIGURACOES_EMPRESA WHERE CFE_EMPRESA_ID = ?", [empresaId]); + if (cfg.length === 0 || cfg[0].CFE_FOTO_CELULAR !== 'S') return false; + } const inst = await db.query(alias, "SELECT * FROM CHATC2_INSTANCIAS WHERE INS_EMPRESA_ID = ? AND INS_SITUACAO = 'A' FETCH FIRST 1 ROWS ONLY", [empresaId]); @@ -468,29 +470,34 @@ class EvolutionController { const instanceName = (inst[0].INS_INSTANCE_NAME || '').trim(); if (!url || !apiKey || !instanceName) return; - // Evolution API 2.4.0 NAO possui endpoint getProfile (disponivel apenas a partir da v2.5+) - console.log('[Foto] Evolution API ' + url + ' - getProfile nao disponivel nesta versao. A foto do WhatsApp sera usada quando atualizar a Evolution.'); + // Busca a foto de perfil via Evolution: POST /chat/fetchProfile/ { number } - // Mesmo sem o endpoint, tentamos alguns formatos conhecidos - var profileEndpoints = [ - '/chat/getProfile/' + encodeURIComponent(instanceName) + '?number=' + numero, - '/chat/getProfile/' + encodeURIComponent(instanceName) + '/' + numero, - '/contact/getProfile/' + encodeURIComponent(instanceName) + '?number=' + numero, - ]; - - var profile = null; - for (var ep of profileEndpoints) { - try { - profile = await evolutionRequest(url, apiKey, ep, 'GET'); - if (profile) break; - } catch(e) { - profile = null; + // Normaliza o numero para o padrao WhatsApp BR: 55(DDI) + DDD + 9XXXXXXXX + function normalizarNumero(n) { + var d = String(n || '').replace(/\D/g, ''); + if (!d) return ''; + if (!d.startsWith('55')) d = '55' + d; // garante DDI Brasil + var resto = d.slice(2); // DDD + assinante + if (resto.length === 10) { // DDD(2) + 8 digitos -> insere o 9 (celular) + resto = resto.slice(0, 2) + '9' + resto.slice(2); } + return '55' + resto; + } + var numeroFmt = normalizarNumero(numero); + if (!numeroFmt) return false; + + var profile = null; + try { + profile = await evolutionRequest(url, apiKey, + '/chat/fetchProfile/' + encodeURIComponent(instanceName), 'POST', { number: numeroFmt }); + } catch (e) { + console.log('[Foto] fetchProfile falhou:', (e.message || '').substring(0, 120)); + return false; } if (!profile) { - console.log('[Foto] Nenhum endpoint getProfile disponivel. Evolution API precisa ser atualizada.'); - return; + console.log('[Foto] Nenhum perfil retornado para', numeroFmt); + return false; } let fotoUrl = null; @@ -505,31 +512,32 @@ class EvolutionController { fotoUrl = profile.response.profilePicUrl || profile.response.picUrl || null; } - if (fotoUrl && fotoUrl.startsWith('http')) { - console.log('[Foto] Baixando foto de:', fotoUrl.substring(0, 80)); + if (fotoUrl && String(fotoUrl).startsWith('http')) { + console.log('[Foto] Baixando foto de:', String(fotoUrl).substring(0, 80)); try { - var http = require('http'); - var https = require('https'); var fotoBuffer = await new Promise(function(resolve, reject) { var u = new URL(fotoUrl); var lib = u.protocol === 'https:' ? https : http; - lib.get(fotoUrl, { timeout: 15000, headers: { 'apikey': apiKey } }, function(imgRes) { + lib.get(fotoUrl, { timeout: 15000 }, function(imgRes) { var chunks = []; imgRes.on('data', function(c) { chunks.push(c); }); imgRes.on('end', function() { resolve(Buffer.concat(chunks)); }); }).on('error', reject).on('timeout', function() { this.destroy(); reject(new Error('Timeout')); }); }); if (fotoBuffer && fotoBuffer.length > 100) { - var imgData = fotoBuffer.toString('base64'); await db.execute(alias, 'UPDATE CLIENTES SET CLI_FOTO = ? WHERE CLI_CODIGO_ID = ?', - [imgData, clienteId]); - console.log('[Foto] Foto atualizada para cliente', clienteId, '- tamanho:', fotoBuffer.length); + [fotoBuffer.toString('base64'), clienteId]); + console.log('[Foto] Foto atualizada para cliente', clienteId, '- bytes:', fotoBuffer.length); + return true; } } catch(imgErr) { - console.error('[Foto] Erro ao baixar imagem:', imgErr.message.substring(0, 100)); + console.error('[Foto] Erro ao baixar imagem:', (imgErr.message || '').substring(0, 120)); } + } else { + console.log('[Foto] Perfil sem foto disponivel para', numeroFmt); } + return false; } catch(e) { console.error('[Foto] Erro:', e.message); } diff --git a/src/public/chat.html b/src/public/chat.html index 4899737..5375136 100644 --- a/src/public/chat.html +++ b/src/public/chat.html @@ -986,7 +986,8 @@ function renderInfoConversa(conv) { (c.matricula ? '
Matrícula
' + esc(c.matricula) + '
' : '') + (c.plano ? '
Plano
' + esc(c.plano) + '
' : '') + '
Contato
' + esc(c.celular || c.telefone || conv.numero || '-') + '
' + - '
' + inadLabel + '
'; + '
' + inadLabel + '
' + + ''; } else if (conv.dependente) { var dep = conv.dependente; fotoContainer.textContent = (dep.nome || '?').charAt(0).toUpperCase(); @@ -1487,6 +1488,27 @@ window.toggleInfo = function() { document.body.classList.toggle('info-aberto'); }; +// Busca a foto do cliente via Evolution (fetchProfile) e atualiza o painel +window.atualizarFotoCliente = async function() { + if (!conversaAtiva) return; + var btn = document.getElementById('btnAtualizarFoto'); + if (btn) { btn.disabled = true; btn.textContent = '⏳ Buscando...'; } + try { + var r = await (await fetch('/api/' + alias + '/evolution/refresh-photo/' + conversaAtiva, { + method: 'POST', headers: { 'Authorization': 'Bearer ' + token } + })).json(); + if (r.success && r.atualizada) { + abrirConversa(conversaAtiva); // recarrega para exibir a nova foto + } else if (btn) { + btn.disabled = false; + btn.textContent = r.message || 'Nenhuma foto encontrada'; + setTimeout(function(){ btn.textContent = '🔄 Atualizar foto (WhatsApp)'; }, 2500); + } + } catch(e) { + if (btn) { btn.disabled = false; btn.textContent = '🔄 Atualizar foto (WhatsApp)'; } + } +}; + window.togglePrivada = function() { document.getElementById('privadaToggle').classList.toggle('ativa'); var btn = document.getElementById('privadaToggle'); diff --git a/src/public/client-detail.html b/src/public/client-detail.html index 06d00ff..303f83f 100644 --- a/src/public/client-detail.html +++ b/src/public/client-detail.html @@ -452,8 +452,8 @@ window.darkModeIsDark=function(){return localStorage.getItem('chatc2_dark_mode') } else { html += ''; } - // Conteudo Itens - html += '
'; + // Conteudo Itens (oculto por padrão quando há boletos — Boletos é a aba ativa) + html += '
'; if (temItens) { html += '' + '' + @@ -492,19 +492,64 @@ window.darkModeIsDark=function(){return localStorage.getItem('chatc2_dark_mode') if (cItens) cItens.style.display = prefixo === 'itens' ? '' : 'none'; }; + // Edição INLINE do telefone do dependente (sem prompt/alert) window.editarTelDep = function(id) { - var btn = document.querySelector('button[onclick="editarTelDep(' + id + ')"]'); - var telAtual = btn ? (btn.getAttribute('data-tel') || '') : ''; - var novo = prompt('Editar telefone do dependente:', telAtual || ''); - if (novo === null || novo.trim() === telAtual) return; + var span = document.getElementById('depTel_' + id); + if (!span || span.getAttribute('data-editing') === '1') return; + var btnEdit = document.querySelector('button[onclick="editarTelDep(' + id + ')"]'); + var telAtual = btnEdit ? (btnEdit.getAttribute('data-tel') || '') : ''; + span.setAttribute('data-editing', '1'); + span.setAttribute('data-original', span.innerHTML); + if (btnEdit) btnEdit.style.display = 'none'; + span.innerHTML = + '' + + ' ' + + ' '; + var inp = document.getElementById('depTelInput_' + id); + if (inp) { + inp.focus(); + inp.addEventListener('keydown', function(e) { + if (e.key === 'Enter') { e.preventDefault(); confirmarTelDep(id); } + else if (e.key === 'Escape') { cancelarTelDep(id); } + }); + } + }; + + window.cancelarTelDep = function(id) { + var span = document.getElementById('depTel_' + id); + if (!span) return; + span.innerHTML = span.getAttribute('data-original') || '-'; + span.setAttribute('data-editing', ''); + var btnEdit = document.querySelector('button[onclick="editarTelDep(' + id + ')"]'); + if (btnEdit) btnEdit.style.display = ''; + }; + + window.confirmarTelDep = function(id) { + var inp = document.getElementById('depTelInput_' + id); + var span = document.getElementById('depTel_' + id); + if (!inp || !span) return; + var novo = (inp.value || '').trim(); + var btnEdit = document.querySelector('button[onclick="editarTelDep(' + id + ')"]'); + inp.disabled = true; fetch('/api/' + alias + '/dependents/' + id + '/phone', { method: 'PUT', headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + token }, - body: JSON.stringify({ telefone: novo.trim() }) + body: JSON.stringify({ telefone: novo }) }).then(function(r) { return r.json(); }).then(function(d) { - if (d.success) { document.getElementById('depTel_' + id).textContent = novo.trim(); alert('Telefone atualizado!'); } - else alert('Erro: ' + d.error); - }).catch(function(e) { alert('Erro: ' + e.message); }); + if (d.success) { + span.setAttribute('data-editing', ''); + span.textContent = novo || '-'; + if (btnEdit) { btnEdit.setAttribute('data-tel', novo); btnEdit.style.display = ''; } + } else { + inp.disabled = false; + inp.style.borderColor = '#ef4444'; + inp.title = d.error || 'Erro ao salvar'; + } + }).catch(function(e) { + inp.disabled = false; + inp.style.borderColor = '#ef4444'; + inp.title = e.message; + }); }; // ============================================================
ProdutoQuantidade