Implantação da geração de mensagem de forma livre e mudança na engine de mensagem

This commit is contained in:
2026-08-24 17:32:16 -03:00
parent 94bbe7556f
commit 88f19dd8f5
77 changed files with 4650 additions and 25 deletions

View File

@@ -13,10 +13,12 @@
class NotificacaoService
EVENTOS = {
fechado: { mailer: :pagamento_fechado,
chave: 'consolidacao_finalizada',
emoji: '🚚',
titulo: 'Seu pagamento de entregas foi fechado.',
cta: 'Acesse seu painel para baixar o extrato:' },
pago: { mailer: :pagamento_efetuado,
chave: 'pagamento_efetuado',
emoji: '✅',
titulo: 'Seu pagamento foi efetuado.',
cta: 'Acesse seu painel para conferir:' }
@@ -48,10 +50,70 @@ class NotificacaoService
def notificar(cm, evento)
user = motorista_de(cm)
return unless user
enviar_whatsapp(user, cm, evento) if @config.whatsapp_habilitado?
enviar_email(user, cm, evento) if @config.email_habilitado? && user.email.present?
# Aviso pessoal ao motorista: continua com o e-mail formatado do
# ConsolidacaoMailer (não vira texto puro) e passa a usar o provedor de
# WhatsApp escolhido na tela — Baileys por QR ou Twilio.
if user
enviar_whatsapp(user, cm, evento) if @config.whatsapp_habilitado?
enviar_email(user, cm, evento) if @config.email_habilitado? && user.email.present?
end
# Cópia para os grupos que assinam este evento (diretoria, operação...).
# `envolvido: nil` de propósito: o motorista já foi avisado acima e receberia
# a mensagem duas vezes.
avisar_grupos(cm, evento, user)
end
# O corpo aqui é o mesmo texto do WhatsApp — na etapa do editor de blocos ele
# passa a vir do template configurado pelo ADM.
def avisar_grupos(cm, evento, user)
evt = EVENTOS.fetch(evento)
Notificacao::Despachante.disparar(
chave: evt[:chave],
dados: variaveis(cm, user),
assunto: "#{evt[:titulo]}#{@consolidacao.nome}",
corpo: mensagem_para_grupos(cm, evento, user)
)
end
# Contexto das {{variaveis}} do editor de blocos. As chaves são as mesmas de
# Notificacao::Variaveis::FECHAMENTO — é o que o ADM vê na paleta da tela.
def variaveis(cm, user)
Notificacao::Variaveis.comuns_reais.merge(
'motorista' => user&.nome.presence || cm.motorista_nome,
'consolidacao' => @consolidacao.nome,
'periodo' => "#{l_data(@consolidacao.data_inicio)} a #{l_data(@consolidacao.data_fim)}",
'valor' => moeda(cm.valor_total),
# ConsolidacaoMotorista não guarda contagem — só valor_total. As entregas
# do motorista são as linhas classificadas na consolidação (mesma fonte do
# #recalcular_valor), descontando os lançamentos de desconto, que não são
# entregas.
'entregas' => entregas_do_motorista(cm).to_s,
'link_painel' => "https://#{ENV.fetch('APP_HOST', 'localhost:3000')}/motorista"
)
end
def l_data(data)
data&.strftime('%d/%m/%Y').to_s
end
def entregas_do_motorista(cm)
@consolidacao.consolidacao_entregas
.where(motorista_nome: cm.motorista_nome)
.where.not(tipo: :desconto)
.count
rescue StandardError => e
Rails.logger.warn("[Notificacao] contagem de entregas indisponível: #{e.class}: #{e.message}")
nil
end
def mensagem_para_grupos(cm, evento, user)
evt = EVENTOS.fetch(evento)
"#{evt[:emoji]} #{evt[:titulo]}\n" \
"👤 #{user&.nome || cm.motorista_nome}\n" \
"📋 #{@consolidacao.nome}\n" \
"💰 Valor: #{moeda(cm.valor_total)}"
end
def motorista_de(cm)
@@ -59,34 +121,57 @@ class NotificacaoService
end
def enviar_whatsapp(user, cm, evento)
# O telefone no cadastro pode estar como "(11) 92005-1157"; mandar esse
# texto cru para o Twilio devolve erro 21211 (destino inválido), que o
# rescue abaixo esconderia. Normalizar para E.164 é o que faz o envio
# realmente funcionar.
destino = ConfiguracaoNotificacao.canal(user.telefone)
# O telefone no cadastro pode estar como "(11) 92005-1157"; a normalização
# para E.164 acontece dentro de Notificacao::Whatsapp — mandar o texto cru
# devolve erro de destino inválido que o rescue esconderia.
destino = ConfiguracaoNotificacao.normalizar_telefone(user.telefone)
return if destino.blank?
credenciais = @config.credenciais_whatsapp
return if credenciais.blank? || credenciais[:from].blank?
texto = mensagem(user, cm, evento)
envio = registrar_envio(evento, canal: 'whatsapp', destino: destino, user: user, corpo: texto)
Notificacao::ClienteTwilio
.montar(credenciais[:sid], credenciais[:token])
.messages.create(from: credenciais[:from], to: destino, body: mensagem(user, cm, evento))
Rails.logger.info("[Notificacao] WhatsApp (#{evento}) enviado para #{user.nome}")
rescue StandardError => e
Rails.logger.error("[Notificacao] Falha WhatsApp (#{evento}) #{user.nome}: #{e.class}: #{e.message}")
resposta = Notificacao::Whatsapp.enviar(para: destino, texto: texto, config: @config)
if resposta.ok?
envio&.marcar_enviado!
Rails.logger.info("[Notificacao] WhatsApp (#{evento}) enviado para #{user.nome}")
else
envio&.marcar_falha!(resposta.erro)
Rails.logger.error("[Notificacao] Falha WhatsApp (#{evento}) #{user.nome}: #{resposta.erro}")
end
end
def enviar_email(user, cm, evento)
ConsolidacaoMailer
.public_send(EVENTOS.fetch(evento)[:mailer], user, @consolidacao, cm)
.deliver_later
evt = EVENTOS.fetch(evento)
envio = registrar_envio(evento, canal: 'email', destino: user.email, user: user,
corpo: mensagem(user, cm, evento), assunto: evt[:titulo])
ConsolidacaoMailer.public_send(evt[:mailer], user, @consolidacao, cm).deliver_later
# `deliver_later` só ENFILEIRA: marcar como enviado aqui significa "saiu do
# nosso lado". Uma falha do SMTP aparece no log do Rails, não aqui.
envio&.marcar_enviado!
Rails.logger.info("[Notificacao] Email (#{evento}) agendado para #{user.email}")
rescue StandardError => e
envio&.marcar_falha!("#{e.class}: #{e.message}")
Rails.logger.error("[Notificacao] Falha email (#{evento}) #{user.email}: #{e.class}: #{e.message}")
end
# Log do aviso pessoal. Nunca levanta: se a tabela ainda não existir (deploy
# antes do migrate), o envio precisa seguir mesmo sem registro.
def registrar_envio(evento, canal:, destino:, user:, corpo:, assunto: nil)
NotificacaoEnvio.create!(
evento_notificacao: evento_registrado(evento), user: user, canal: canal,
destino: destino, assunto: assunto, corpo: corpo, status: 'pendente'
)
rescue StandardError => e
Rails.logger.warn("[Notificacao] sem log de envio (#{e.class}: #{e.message})")
nil
end
def evento_registrado(evento)
@eventos_cache ||= {}
@eventos_cache[evento] ||= EventoNotificacao.find_by(chave: EVENTOS.fetch(evento)[:chave])
end
def mensagem(user, cm, evento)
evt = EVENTOS.fetch(evento)
"Olá #{user.nome.split.first}! #{evt[:emoji]}\n" \