Adição da função de configuração de email e whatts

This commit is contained in:
2026-08-11 23:42:00 -03:00
parent c86bae66a4
commit e3d4546c3c

158
README.md
View File

@@ -1786,3 +1786,161 @@ app/views/admin/configuracoes/index.html.erb # idem (arquivo distint
</details> </details>
---
<details>
<summary><strong>✉️ Notificações e E-mail configuráveis pela tela — SMTP + WhatsApp (11/08/2026)</strong></summary>
> ⚠️ **STATUS: implementado, ainda NÃO executado.** Nada aqui foi rodado contra um banco nem
> contra os servidores reais (Gmail/Twilio) — não há Ruby nem Postgres na máquina de
> desenvolvimento. Foi conferida a sintaxe de todos os arquivos `.rb` e `.erb` e validado o
> algoritmo de normalização de telefone em Ruby puro. **A migration, a suíte e o envio real
> continuam pendentes** — roteiro no fim desta seção.
### 🎯 O problema
Servidor de e-mail e credenciais do Twilio viviam **só no `.env`**: trocar a senha de app do Gmail
ou o número remetente exigia editar o arquivo no servidor e **reiniciar o container**. O ADM não
tinha como fazer nada disso pela interface.
Pior: as chaves `notificacao_whatsapp` e `notificacao_email` existiam em `configuracoes` mas
**nunca tiveram UI** — `Admin::ConfiguracoesController#index` filtra por `CHAVES_MOEDA`. Ligar
notificação só era possível pelo `rails console`.
### 🆕 A tela
**Configurações → card "Notificações e E-mail"** (`/admin/configuracao_notificacao`).
**Só `admin`** — `ConfiguracaoNotificacaoPolicy` é mais restrita que `ConfiguracaoPolicy`, que
libera `index?` para gerente: aqui ficam senha de e-mail e token do Twilio.
O acesso é **exclusivamente pelo card dentro de Configurações** — de propósito não há item no menu
lateral, para não expor um atalho de credenciais na navegação de todo dia.
| Bloco | Campos |
|---|---|
| **Servidor de e-mail (SMTP)** | ativo, servidor, porta, usuário, senha, autenticação, domínio, e-mail e nome do remetente + toggle "avisar motoristas por e-mail" |
| **WhatsApp (Twilio)** | ativo, Account SID, Auth Token, número remetente |
| **Destinatários administrativos** | e-mail do admin, WhatsApp do admin |
Três botões: **Salvar**, **Salvar e enviar e-mail de teste**, **Salvar e enviar WhatsApp de teste**.
### ⚙️ Como funciona (técnico) — os 6 pontos não-óbvios
**1. Hierarquia banco > `.env`, sem quebrar nada.** `ConfiguracaoNotificacao#smtp_settings` devolve
**`nil`** quando não está pronto. O ActionMailer faz `.merge(options || {})` por cima do que o
`config/initializers/smtp.rb` montou no boot — então o fallback para o `.env` é **automático**.
Enquanto os toggles estiverem desligados, o comportamento é **idêntico ao de antes desta tela**.
**2. `default delivery_method_options:`, NÃO um `before_action`.** Um callback que mexesse em
`message.delivery_method` seria **descartado**: `ActionMailer::Base#mail` roda *depois* dos
callbacks e chama `wrap_delivery_behavior!`, que reconfigura a mensagem. O
`delivery_method_options` é lido *dentro* do próprio `mail()`.
**3. `proc`, NÃO lambda.** O Devise avalia o `default from:` com `instance_eval(&proc)`, que passa
1 argumento. Um `-> { }` de aridade 0 estouraria **`ArgumentError` em todo "esqueci minha senha"**.
**4. `config.parent_mailer = 'ApplicationMailer'`** no `devise.rb` — sem isso o reset de senha
continuaria preso ao `.env`. Efeito colateral aceito: os e-mails do Devise passam a usar
`app/views/layouts/mailer.html.erb`.
**5. Segredos cifrados sem `master.key`.** Senha SMTP e Auth Token vão para colunas
`*_cifrado` (AES-256-GCM) via `AtributoCifrado`, com chave derivada do `secret_key_base`. **Não** se
usou ActiveRecord Encryption: exigiria 3 chaves novas, dependeria da ordem dos initializers e
estouraria `Errors::Decryption` na leitura. Aqui o reader faz `rescue → nil`, o app **degrada para
o `.env`** e a tela mostra um banner amarelo pedindo para redigitar.
**6. Testes com `deliver_now` e `raise_delivery_errors = true` forçado.** O `development.rb` define
`raise_delivery_errors = false` e o adapter do ActiveJob é `:async` (thread in-process) — com
`deliver_later` **o teste "passaria" em silêncio mesmo com a senha errada**.
### 🩹 Bug pré-existente corrigido de passagem
`NotificacaoService` mandava `to: "whatsapp:#{user.telefone}"` com o telefone **cru do cadastro**.
Um telefone gravado como `(11) 92005-1157` vira `whatsapp:(11) 92005-1157`, o Twilio devolve
**21211** — e o `rescue` engolia. **Provavelmente nenhum WhatsApp a motorista jamais chegou.**
Agora passa por `ConfiguracaoNotificacao.canal`, que normaliza para E.164.
> Casos cobertos: `11 920051157`, `(11) 92005-1157`, `011 …`, `+55 11 …`, `whatsapp:+55…` →
> `+5511920051157`. O prefixo `55` só é removido quando sobra número demais — senão quebraria o
> **DDD 55** (Santa Maria/RS), onde `55991234567` já é o número completo.
### 🔐 Segurança
- A senha gravada **nunca volta para o HTML** (`password_field value: nil`). Campo em branco
significa "mantenha a atual" — salvar sem redigitar não apaga o que está lá.
- `AuditoriaLog` registra a mudança com `acao: 'editar_notificacoes'`, mas grava apenas
`smtp_password_definida: true/false`. **Nunca a senha nem o token** — `dados_novos` é exibido em
`/admin/auditoria_logs`, que **gerente também acessa**.
- Os erros do Twilio vêm traduzidos (63003/63015 = falta o `join <palavra>` do sandbox, 21608 =
conta trial só envia a número verificado, 20003 = SID/token inválidos…), para o ADM resolver
sozinho sem abrir o log do container.
### 🆕 Migration adicionada (rodar `db:migrate`)
```
20260811000001_create_configuracao_notificacoes.rb
```
Tabela **singleton** (índice único em `singleton_guard` — impede dois workers Puma criarem linhas
concorrentes). Copia as flags antigas de `configuracoes` e **não** importa credenciais do `.env`:
os campos nascem vazios e o fallback segue mandando até alguém preencher a tela.
### 📂 Arquivos
```
db/migrate/20260811000001_create_configuracao_notificacoes.rb # NOVO — tabela singleton
app/models/configuracao_notificacao.rb # NOVO — smtp_settings, credenciais, E.164
app/models/concerns/atributo_cifrado.rb # NOVO — AES-256-GCM sem master.key
app/policies/configuracao_notificacao_policy.rb # NOVO — show?/update? = admin
app/controllers/admin/configuracao_notificacoes_controller.rb # NOVO — show/update + params[:acao]
app/views/admin/configuracao_notificacoes/show.html.erb # NOVO — a tela
app/services/notificacao/resultado.rb # NOVO — ok?/mensagem/detalhe → flash
app/services/notificacao/teste_email.rb # NOVO — erros SMTP em português
app/services/notificacao/teste_whatsapp.rb # NOVO — códigos Twilio em português
app/services/notificacao/cliente_twilio.rb # NOVO — client com timeout de 15s
app/mailers/teste_mailer.rb · app/views/teste_mailer/teste.html.erb # NOVO
app/services/notificacao_service.rb # lê do banco; unifica os 2 pares duplicados
app/mailers/application_mailer.rb # default from: / delivery_method_options: proc
app/mailers/consolidacao_mailer.rb # removido o `default from:` que anulava o proc
config/initializers/devise.rb # + parent_mailer
config/initializers/smtp.rb # vira fallback (só comentário)
config/initializers/inflections.rb # + irregular 'notificacao'
config/routes.rb # + resource :configuracao_notificacao
app/views/admin/configuracoes/index.html.erb # + card "Notificações e E-mail" (só admin)
app/models/configuracao.rb # marca notificacao_* como obsoletas
.env.example # hierarquia banco > .env + NOTIFICACAO_SECRET
spec/models/configuracao_notificacao_spec.rb # NOVO
spec/policies/configuracao_notificacao_policy_spec.rb # NOVO
spec/requests/admin/configuracao_notificacoes_spec.rb # NOVO
spec/models/table_names_spec.rb # + ConfiguracaoNotificacao
```
### ⏳ Pendente de execução — roteiro
```bash
# 1. Migrar
docker-compose exec app bundle exec rails db:migrate
# 2. Suíte
docker-compose exec app bundle exec rspec spec/models spec/policies spec/requests
# 3. Confirmar a assinatura da gem (não pôde ser verificada fora do container)
docker-compose exec app bundle exec rails runner \
'p Twilio::HTTP::Client.instance_method(:initialize).parameters'
```
4. **Permissão:** logar como **gerente** → o card não aparece e `/admin/configuracao_notificacao`
redireciona com "Você não tem permissão". Como **admin** → a tela abre.
5. **E-mail:** `smtp.gmail.com`, porta 587, **Senha de app de 16 caracteres** (não a senha da
conta), marcar "Ativar envio de e-mail" → **Salvar e enviar e-mail de teste**. Conferir o spam.
6. **WhatsApp:** SID / Auth Token / número remetente do Twilio, marcar "Ativar WhatsApp" →
**Salvar e enviar WhatsApp de teste**. ⚠️ **No sandbox, o número que vai RECEBER precisa antes
mandar `join <sua-palavra>`** para o número do sandbox — sem esse opt-in volta 63003/63015.
7. **Não regressão:** com os toggles desligados, finalizar uma consolidação e conferir que nada
mudou; e testar o **"Esqueci minha senha"**, que trocou de mailer pai.
### ⚠️ Alertas
- **Rotação do `SECRET_KEY_BASE` torna senha e token ilegíveis.** O sistema não quebra (volta ao
`.env` e avisa na tela), mas os dois campos precisam ser redigitados. Para desacoplar, defina
**`NOTIFICACAO_SECRET`** no `.env` com uma string longa e **fixa**.
- **Não cachear a config em `Rails.cache`:** o `production.rb` usa `:memory_store`, que é por
processo — a tela pareceria "não salvar" para os outros workers. É 1 `SELECT` por e-mail.
- `app/views/configuracoes/index.html.erb` (fora do `admin/`) **não recebeu o card**: não tem rota
e é código morto — o vivo é `app/views/admin/configuracoes/index.html.erb`.
> **Migration nova** (`db:migrate` obrigatório) e **sem gem nova** — `twilio-ruby` já estava no
> Gemfile. ⚠️ Reiniciar o Puma após o deploy.
</details>