Skip to content

Comunicação — Hub Operacional

SSOT da seção /admin/comunicacao — centro único de envio e análise de toda comunicação saída pela plataforma (e-mail, WhatsApp, SMS, notificação in-app). Substitui a antiga aba "E-mails" do Financeiro e a aba "Notificações" do Monitoramento.

1. Escopo

Estrutura de tabs (v3, rev. 2026-05-09):

TabO que mostraFonte de dados
Visão geralKPIs cross-canal (volume, entrega, abertura, clique, bounce, fila/DLQ)email_outbox, fatura_cobrancas, notificacoes, email_unsubscribes
EnviarLanding 6 cards (canal × modo) → composer dedicado por canalendpoints enviar.* + notificacoes/create_batch
HistóricoLog unificado paginado com timeline por mensagemmerge dos 3 stores acima + email_eventos
CanaisConfiguração técnica/operacional por canal (E-mail, WhatsApp, Push)ver §"Tab Canais"

A seção NÃO duplica logs_transacoes. Auditoria de quem disparou ou editou continua em admin-logs — aqui medimos estado de entrega, não ação administrativa.

1.1 Tab Canais (Hub-Shell pattern)

Substituiu as antigas tabs Templates | Remetentes | Supressões (que eram todas domínio E-mail) por uma estrutura por canal. Padrão visual idêntico à tab Enviar — landing N cards → painel da entidade com pills internas. Ver docs/development/FRONTEND_UI_STANDARDS.md §Hub-Shell pattern.

CanalPills (seções)Fonte de dados
E-mailRemetentes · Templates · Supressões · Domínio · Reputaçãoremetentes_email, email_templates, email_unsubscribes
WhatsAppSessão · Rate-limit · Opt-out (read-only)_shared/messaging-rate-limits.ts (espelho)
PushEsqueleto "Em breve" — Tópicos / Quiet hours / Retry

Compat de URL legada (replaceState automático em index.tsx):

URL antigaURL nova
?tab=envios?tab=historico
?tab=notificacoes?tab=enviar&composer=notificacao&modo=massa
?tab=templates?tab=canais&canal=email&secao=templates
?tab=remetentes?tab=canais&canal=email&secao=remetentes
?tab=supressoes?tab=canais&canal=email&secao=supressoes

2. Arquitetura

text
Frontend                          Edge function                  Banco
─────────                         ───────────────                ─────
admin-comunicacao/                admin-comunicacao              email_outbox
  index.tsx       ──invokeEdge──▶   action: kpis           ────▶ fatura_cobrancas
  visao-geral-tab                   action: envios_lista   ────▶ notificacoes
  envios-unificados-tab             action: envio_detalhe  ────▶ email_eventos
  supressoes-tab                    action: supressoes_lista ──▶ email_unsubscribes
useAdminComunicacao.ts (RQ keys: ['admin-comunicacao', ...])

Decisão: sem view materializada. 3 selects paralelos + merge ordenado por criado_em DESC no servidor. Quando volume passar de ~50k linhas/7d, extrair RPC sem mudar contrato.

3. Contrato da edge function

3.1 Filtro de Status (dropdown contextual)

A aba Envios usa um Select cuja lista de status muda conforme o canal selecionado (STATUS_POR_CANAL em envios-unificados-tab.tsx). Tokens enviados ao backend permanecem canônicos; rótulos exibidos são em PT. Todos é representado pelo sentinel __all__ no estado e undefined no payload.

3.2 Webhook Resend (KPIs e saúde)

KPIs de e-mail (entrega, abertura, clique, bounce) só são populados quando o webhook do Resend está apontando para a edge resend-webhook. A Visão geral exibe um card Webhook Resend com a última callback recebida (max(email_eventos.criado_em)) e popover com as instruções de configuração:

  • URL: https://mjvuzsizjlcalyfmbquy.functions.supabase.co/resend-webhook
  • Eventos: email.sent, email.delivered, email.delivery_delayed, email.bounced, email.complained, email.opened, email.clicked, email.failed
  • Secret: RESEND_WEBHOOK_SECRET (formato whsec_...) — já configurado no projeto

Saudável = última callback ≤ 6h.

3.3 Modal "Detalhe do envio" (humanizado)

action=envio_detalhe retorna 4 blocos: resumo (campos resolvidos sem UUIDs — escola_nome, numero_fatura, enviado_por, etc.), mensagem (payload de preview), timeline (eventos Resend para e-mail) e registro (raw, exibido apenas dentro do collapsible "Detalhes técnicos"). O preview renderiza:

  • WhatsApp: bolha verde com markdown leve (*bold*, _italic_, ~strike~, `code`, \n).
  • SMS: bolha neutra texto puro.
  • E-mail: card com assunto + render dos corpo_blocos (saudacao/paragrafo/cta/rodape) quando presentes em payload.blocos.
  • In-app: card com título + mensagem.

POST /functions/v1/admin-comunicacao — body com action. Auth: somente principal_role = 'administrador' (especialista terá hub próprio).

actionBody extraRetorno
kpisintervalo_dias (1–90), escola_id?{ email, cobranca, notificacao, supressoes, serie_diaria }
envios_listacanal, status?, escola_id?, busca?, intervalo_dias, referencia_tipo?, referencia_id?, page, pageSize{ items: EnvioUnificado[], total, page, pageSize }
envio_detalhecanal, id{ canal, registro, timeline }
supressoes_listabusca?, origem?, page, pageSize{ items, total, page, pageSize }

Todos os campos PII (to_email, destinatario) são mascarados server-side via _shared/pii-helpers.ts antes de sair. Frontend nunca recebe e-mail/telefone bruto.

4. Mapeamento de coluna por tabela (importante)

TabelaTimestamp de criação
email_outboxcriado_em
fatura_cobrancascriado_em
notificacoescriada_em (fem.)
email_unsubscribescriado_em

Esquecer o criada_em quebra todas as queries de notificação com erro PostgREST 42703. Já documentado neste SSOT para evitar regressão.

5. Filtro de origem do e-mail

origem no log unificado é inferido (não é coluna):

  • referencia_tipo === 'fatura'cobranca
  • template_slug começa com manual_campanha
  • caso contrário → transacional

E-mails de cobrança aparecem somente em email_outbox (a coluna canal='email' em fatura_cobrancas é filtrada fora para evitar duplicação no log unificado).

6. Atalhos contextuais (entram em outras telas)

  • admin-financeiro › Faturas → "Ver e-mails enviados" → /admin/comunicacao aba Envios.
  • admin-financeiro › Cobranças → drilldown filtra referencia_tipo=fatura.
  • Monitoramento › Notificações → removido; passou a viver no Hub.

7. Fases

  • Fase A — Reorganização (Templates/Remetentes migrados de Financeiro, Notificações migradas de Monitoramento, sidebar e roteamento).
  • Fase B — KPIs + Envios unificados + drawer de detalhe com timeline email_eventos.
  • Fase C — Supressões (email_unsubscribes) + correção de notificacoes.criada_em no envios_lista/kpis.
  • Fase D (futuro) — Campanhas (Fases 1–5 de docs/plans/COMUNICACAO_CAMPANHAS.md), unificação de cobranca_templates + templates_mensagem na aba Templates.

8. Padrões aplicados

  • Lazy Mount Once nas tabs (cache RQ preservado).
  • keepPreviousData em paginação.
  • Anti-Flash: isLoading && !data → Skeleton.
  • PII masking server-side obrigatório.
  • queryKey ['admin-comunicacao', …] — escopo global de admin (sem escola_id por padrão; quando filtro de escola é aplicado, entra como parâmetro da key).

8.1 Abertura e fail-safe de tabs

Lições aprendidas no incidente de 2026-05-09 (React error #31 derrubando a seção inteira):

  • Allowlist da aba inicial: AdminComunicacao valida sessionStorage.admin_comunicacao_return.tab contra a lista canônica (visao | envios | notificacoes | templates | remetentes | supressoes). Valor inválido/legado cai em envios (default seguro).
  • Boundary local por aba (SectionErrorBoundary): cada tab é embrulhada por um boundary próprio. Uma aba que quebrar (ex: payload inesperado, campo objeto onde se esperava string) exibe fallback localizado e mantém as outras abas operacionais. Não usar o ErrorBoundary global para isso — ele derruba a seção inteira.
  • Renderização defensiva (toText): campos vindos de payloads dinâmicos (status, origem, template_slug, assunto_ou_titulo, timeline.tipo) passam por toText(value, fallback) antes de virarem children React. Isso elimina a janela em que o React error #31 pode ocorrer.
  • Anti-padrão: confiar no tipo TypeScript do payload (string | null) sem normalização runtime — o backend pode retornar objeto em colunas JSONB recém-introduzidas, e isso quebra silenciosamente.

9. Referências cruzadas

  • docs/plans/COMUNICACAO_CAMPANHAS.md — backlog de campanhas (Fase D).
  • docs/features/billing/email-billing.md — pipeline email_outbox + Resend.
  • docs/features/billing/cobrancas-templates.md — régua WhatsApp/SMS.
  • docs/architecture/observability-and-logging-standard (memória) — por que NÃO usar logs_transacoes para análise de entrega.