Correção falha na migration da ultima atualização
This commit is contained in:
@@ -9,7 +9,9 @@ class Admin::ConfiguracaoNotificacoesController < ApplicationController
|
|||||||
smtp_ativo smtp_address smtp_port smtp_username smtp_password smtp_domain
|
smtp_ativo smtp_address smtp_port smtp_username smtp_password smtp_domain
|
||||||
smtp_autenticacao remetente_email remetente_nome
|
smtp_autenticacao remetente_email remetente_nome
|
||||||
email_notificacoes_ativo email_admin
|
email_notificacoes_ativo email_admin
|
||||||
whatsapp_ativo twilio_account_sid twilio_auth_token twilio_from whatsapp_admin
|
whatsapp_ativo whatsapp_provedor whatsapp_intervalo_segundos whatsapp_admin
|
||||||
|
twilio_account_sid twilio_auth_token twilio_from
|
||||||
|
baileys_url baileys_token
|
||||||
].freeze
|
].freeze
|
||||||
|
|
||||||
def show
|
def show
|
||||||
@@ -60,6 +62,7 @@ class Admin::ConfiguracaoNotificacoesController < ApplicationController
|
|||||||
# tela nunca exibe o valor, então salvar sem redigitar não pode apagá-lo.
|
# tela nunca exibe o valor, então salvar sem redigitar não pode apagá-lo.
|
||||||
permitidos.delete(:smtp_password) if permitidos[:smtp_password].blank?
|
permitidos.delete(:smtp_password) if permitidos[:smtp_password].blank?
|
||||||
permitidos.delete(:twilio_auth_token) if permitidos[:twilio_auth_token].blank?
|
permitidos.delete(:twilio_auth_token) if permitidos[:twilio_auth_token].blank?
|
||||||
|
permitidos.delete(:baileys_token) if permitidos[:baileys_token].blank?
|
||||||
permitidos
|
permitidos
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -79,11 +82,15 @@ class Admin::ConfiguracaoNotificacoesController < ApplicationController
|
|||||||
email_admin: @config.email_admin,
|
email_admin: @config.email_admin,
|
||||||
email_notificacoes_ativo: @config.email_notificacoes_ativo,
|
email_notificacoes_ativo: @config.email_notificacoes_ativo,
|
||||||
whatsapp_ativo: @config.whatsapp_ativo,
|
whatsapp_ativo: @config.whatsapp_ativo,
|
||||||
|
whatsapp_provedor: @config.whatsapp_provedor,
|
||||||
|
whatsapp_intervalo: @config.whatsapp_intervalo_segundos,
|
||||||
twilio_account_sid: @config.twilio_account_sid,
|
twilio_account_sid: @config.twilio_account_sid,
|
||||||
twilio_from: @config.twilio_from,
|
twilio_from: @config.twilio_from,
|
||||||
whatsapp_admin: @config.whatsapp_admin,
|
whatsapp_admin: @config.whatsapp_admin,
|
||||||
|
baileys_url: @config.baileys_url,
|
||||||
smtp_password_definida: @config.smtp_password?,
|
smtp_password_definida: @config.smtp_password?,
|
||||||
twilio_auth_token_definido: @config.twilio_auth_token?
|
twilio_auth_token_definido: @config.twilio_auth_token?,
|
||||||
|
baileys_token_definido: @config.baileys_token?
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
# app/services/notificacao/teste_whatsapp.rb
|
# app/services/notificacao/teste_whatsapp.rb
|
||||||
#
|
#
|
||||||
# Dispara um WhatsApp de teste pelo Twilio para o número administrativo e
|
# Dispara um WhatsApp de teste para o número administrativo, pelo provedor
|
||||||
# traduz o código de erro da API para uma instrução acionável em português —
|
# escolhido na tela (QR/Baileys ou Twilio).
|
||||||
# os erros de Sandbox (opt-in) são de longe os mais comuns e o código cru
|
#
|
||||||
# ("63003") não diz nada para quem está configurando.
|
# No Twilio, traduz o código de erro da API para uma instrução acionável em
|
||||||
|
# português — os erros de Sandbox (opt-in) são de longe os mais comuns e o
|
||||||
|
# código cru ("63003") não diz nada para quem está configurando.
|
||||||
module Notificacao
|
module Notificacao
|
||||||
class TesteWhatsapp
|
class TesteWhatsapp
|
||||||
MENSAGENS = {
|
MENSAGENS = {
|
||||||
@@ -26,7 +28,8 @@ module Notificacao
|
|||||||
|
|
||||||
def initialize(config = ConfiguracaoNotificacao.instancia, destino: nil)
|
def initialize(config = ConfiguracaoNotificacao.instancia, destino: nil)
|
||||||
@config = config
|
@config = config
|
||||||
@destino = ConfiguracaoNotificacao.canal(destino.presence || config.whatsapp_admin)
|
@numero = ConfiguracaoNotificacao.normalizar_telefone(destino.presence || config.whatsapp_admin)
|
||||||
|
@destino = @numero && ConfiguracaoNotificacao.canal(@numero)
|
||||||
end
|
end
|
||||||
|
|
||||||
def executar
|
def executar
|
||||||
@@ -35,6 +38,21 @@ module Notificacao
|
|||||||
'(ex.: 11 92005-1157) antes de testar.')
|
'(ex.: 11 92005-1157) antes de testar.')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@config.baileys? ? via_qr : via_twilio
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
# Modo QR: quem responde é a ponte Baileys (container `whatsapp`). Os erros
|
||||||
|
# dela já vêm traduzidos pelo ClienteWhatsapp.
|
||||||
|
def via_qr
|
||||||
|
resposta = ClienteWhatsapp.padrao.enviar(para: @numero, texto: corpo)
|
||||||
|
return Resultado.falha(resposta.erro.to_s) unless resposta.ok?
|
||||||
|
|
||||||
|
Resultado.sucesso("WhatsApp de teste enviado para #{@numero} pela ponte QR.")
|
||||||
|
end
|
||||||
|
|
||||||
|
def via_twilio
|
||||||
credenciais = @config.credenciais_whatsapp
|
credenciais = @config.credenciais_whatsapp
|
||||||
if credenciais.blank?
|
if credenciais.blank?
|
||||||
return Resultado.falha('Marque "Ativar WhatsApp" e preencha Account SID, Auth Token e o ' \
|
return Resultado.falha('Marque "Ativar WhatsApp" e preencha Account SID, Auth Token e o ' \
|
||||||
@@ -63,11 +81,9 @@ module Notificacao
|
|||||||
Resultado.falha("Falha inesperada ao enviar (#{e.class}).", e.message)
|
Resultado.falha("Falha inesperada ao enviar (#{e.class}).", e.message)
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def corpo
|
def corpo
|
||||||
"✅ Teste do Reem Notas\n" \
|
"✅ Teste do Reem Notas\n" \
|
||||||
"Se você recebeu esta mensagem, a integração com o Twilio está funcionando.\n" \
|
"Se você recebeu esta mensagem, o envio de WhatsApp está funcionando.\n" \
|
||||||
"Enviado em #{Time.current.strftime('%d/%m/%Y às %H:%M')}."
|
"Enviado em #{Time.current.strftime('%d/%m/%Y às %H:%M')}."
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
<%= icone :email, espaco: false %> Notificações e E-mail
|
<%= icone :email, espaco: false %> Notificações e E-mail
|
||||||
</h1>
|
</h1>
|
||||||
<p class="text-gray-400 text-sm mt-0.5">
|
<p class="text-gray-400 text-sm mt-0.5">
|
||||||
Servidor de e-mail (SMTP) e WhatsApp (Twilio) usados pelo sistema
|
Servidor de e-mail (SMTP) e WhatsApp usados pelo sistema
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<%= link_to 'Voltar', admin_configuracoes_path,
|
<%= link_to 'Voltar', admin_configuracoes_path,
|
||||||
@@ -55,6 +55,7 @@
|
|||||||
<% ilegiveis = [] %>
|
<% ilegiveis = [] %>
|
||||||
<% ilegiveis << 'senha do SMTP' if @config.smtp_password_cifrado.present? && @config.smtp_password.blank? %>
|
<% ilegiveis << 'senha do SMTP' if @config.smtp_password_cifrado.present? && @config.smtp_password.blank? %>
|
||||||
<% ilegiveis << 'token do Twilio' if @config.twilio_auth_token_cifrado.present? && @config.twilio_auth_token.blank? %>
|
<% ilegiveis << 'token do Twilio' if @config.twilio_auth_token_cifrado.present? && @config.twilio_auth_token.blank? %>
|
||||||
|
<% ilegiveis << 'token da ponte do WhatsApp' if @config.baileys_token_cifrado.present? && @config.baileys_token.blank? %>
|
||||||
<% if ilegiveis.any? %>
|
<% if ilegiveis.any? %>
|
||||||
<div class="p-4 bg-yellow-900/30 border border-yellow-500/40 rounded-xl text-yellow-300 text-sm">
|
<div class="p-4 bg-yellow-900/30 border border-yellow-500/40 rounded-xl text-yellow-300 text-sm">
|
||||||
Não foi possível ler <%= ilegiveis.to_sentence %> que estava gravada no banco
|
Não foi possível ler <%= ilegiveis.to_sentence %> que estava gravada no banco
|
||||||
@@ -163,15 +164,15 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<%# ────────────────────────── WHATSAPP / TWILIO ───────────────────────── %>
|
<%# ────────────────────────────── WHATSAPP ───────────────────────────── %>
|
||||||
<div class="<%= card %>">
|
<div class="<%= card %>">
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<h2 class="text-lg font-semibold text-white flex items-center gap-2">
|
<h2 class="text-lg font-semibold text-white flex items-center gap-2">
|
||||||
<%= icone :telefone, espaco: false %> WhatsApp (Twilio)
|
<%= icone :telefone, espaco: false %> WhatsApp
|
||||||
</h2>
|
</h2>
|
||||||
<p class="text-gray-500 text-xs mt-0.5">
|
<p class="text-gray-500 text-xs mt-0.5">
|
||||||
Credenciais em Twilio Console → Account Info
|
Escolha quem entrega as mensagens
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<label class="relative inline-flex items-center cursor-pointer ml-4">
|
<label class="relative inline-flex items-center cursor-pointer ml-4">
|
||||||
@@ -180,6 +181,58 @@
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<%# Provedor: sem este seletor as colunas do Baileys existiam no banco mas
|
||||||
|
não havia como sair do Twilio pela tela — o pareamento por QR ficava
|
||||||
|
inalcançável. %>
|
||||||
|
<div class="grid grid-cols-1 sm:grid-cols-2 gap-6">
|
||||||
|
<div>
|
||||||
|
<%= f.label :whatsapp_provedor, 'Provedor', class: rotulo %>
|
||||||
|
<%= f.select :whatsapp_provedor,
|
||||||
|
[['QR code (grátis, não oficial)', 'baileys'], ['Twilio (oficial, pago)', 'twilio']],
|
||||||
|
{}, class: input %>
|
||||||
|
<p class="<%= ajuda %>">
|
||||||
|
No modo QR o pareamento é feito em
|
||||||
|
<%= link_to 'Notificações → WhatsApp', admin_whatsapp_sessao_path,
|
||||||
|
data: { turbo: false }, class: 'text-[#f97316] underline' %>.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<%= f.label :whatsapp_intervalo_segundos, 'Intervalo entre mensagens (segundos)', class: rotulo %>
|
||||||
|
<%= f.number_field :whatsapp_intervalo_segundos, min: 0, max: 300, class: input %>
|
||||||
|
<p class="<%= ajuda %>">
|
||||||
|
Disparo em rajada é o que mais causa banimento no modo QR. Abaixo de 5s, só com poucos contatos.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<%# ── Modo QR (Baileys) ───────────────────────────────── %>
|
||||||
|
<div class="p-4 bg-[#0a0a0a] border border-white/5 rounded-xl space-y-4">
|
||||||
|
<p class="text-gray-300 text-sm font-semibold">Modo QR code</p>
|
||||||
|
<p class="text-gray-400 text-xs leading-relaxed">
|
||||||
|
⚠️ Canal <strong class="text-amber-300">não oficial</strong> — a Meta pode banir o número.
|
||||||
|
Use um chip dedicado. Em branco, os dois campos abaixo usam
|
||||||
|
<code class="font-mono text-[#f97316]">BAILEYS_URL</code> e
|
||||||
|
<code class="font-mono text-[#f97316]">WHATSAPP_TOKEN</code> do <code class="font-mono">.env</code>.
|
||||||
|
</p>
|
||||||
|
<div class="grid grid-cols-1 sm:grid-cols-2 gap-6">
|
||||||
|
<div>
|
||||||
|
<%= f.label :baileys_url, 'Endereço da ponte', class: rotulo %>
|
||||||
|
<%= f.text_field :baileys_url, class: "#{input} font-mono text-sm",
|
||||||
|
placeholder: ConfiguracaoNotificacao::BAILEYS_URL_PADRAO %>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<%= f.label :baileys_token, 'Token da ponte', class: rotulo %>
|
||||||
|
<%= f.password_field :baileys_token, value: nil, autocomplete: 'new-password',
|
||||||
|
class: "#{input} font-mono text-sm",
|
||||||
|
placeholder: @config.baileys_token? ? '•••••••• (em branco mantém o atual)' : 'mesmo valor de WHATSAPP_TOKEN' %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p class="text-gray-300 text-sm font-semibold pt-2">Modo Twilio</p>
|
||||||
|
<p class="text-gray-500 text-xs -mt-2">Credenciais em Twilio Console → Account Info</p>
|
||||||
|
|
||||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-6">
|
<div class="grid grid-cols-1 sm:grid-cols-2 gap-6">
|
||||||
<div>
|
<div>
|
||||||
<%= f.label :twilio_account_sid, 'Account SID', class: rotulo %>
|
<%= f.label :twilio_account_sid, 'Account SID', class: rotulo %>
|
||||||
|
|||||||
Reference in New Issue
Block a user