COMUNICADO_SYSTEM.md

πŸ“£ Sistema de Comunicados β€” Manual Tecnico

Public Markdown Document

πŸ“£ Sistema de Comunicados β€” Manual Tecnico

Painel de configuracao de notificacoes/alertas baseado em eventos. E mail nesta primeira fase, WhatsApp (via WAHA) na proxima. Segue o padrao do Fastapp Engine: o painel e um Object em app.objects[].

759 palavras18 headingsrepository root

πŸ“£ Sistema de Comunicados β€” Manual Tecnico

Painel de configuracao de notificacoes/alertas baseado em eventos. E-mail nesta primeira fase, WhatsApp (via WAHA) na proxima.
Segue o padrao do Fastapp Engine: o painel e um Object em app.objects[].


1. Conceito

"Quero receber um e-mail toda vez que alguem criar uma task nova no MEU app."

O usuario configura regras de comunicado no painel. Cada regra descreve:

  • Quando: qual objeto monitorar + qual evento (create/update/delete) + filtro opcional
  • Para onde: canal (email/whatsapp) + destinatario (email ou telefone)
  • O que: assunto + mensagem com placeholders {{record.<campo>}}

Quando um registro do objeto monitorado e criado/atualizado/excluido, o motor le as regras ativas e dispara as notificacoes.


2. Posicionamento Arquitetural

graph TB
    subgraph "Metadata Layer (App JSON)"
        OBJ_CFG["app.objects.comunicado_config<br/>(regras configuradas)"]
        OBJ_TASK["app.objects.task_item<br/>(registros monitorados)"]
    end
    subgraph "Engine Layer"
        POST["POST /api/v1/data/:app/:object<br/>(index.post.ts)"]
        DISPATCHER["dispatchComunicados()<br/>(2a fase)"]
    end
    subgraph "Infrastructure Layer"
        EMAIL["server/utils/email.ts<br/>(2a fase)"]
        WAHA["server/utils/waha.ts<br/>(ja existe)"]
    end

    POST -->|ao criar task| DISPATCHER
    DISPATCHER -->|le regras| OBJ_CFG
    DISPATCHER -->|canal=email| EMAIL
    DISPATCHER -->|canal=whatsapp| WAHA

    style OBJ_CFG fill:#0891b2,color:#fff
    style DISPATCHER fill:#7c3aed,color:#fff
    style WAHA fill:#25D366,color:#000

Decisao chave: o painel de configuracao e um Object normal, nao um sistema separado. Isso significa:

  • Persistencia: tabela Record (storage: generic) β€” zero DDL
  • UI: gerada automaticamente pelo CoreDynamicCRUD
  • API: /api/v1/data/:app/comunicado_config (CRUD ja pronto pelo motor)
  • Permissoes: passa pelo RBAC padrao
  • Snapshot: incluido no static-manifest.ts

3. Definicao do Object comunicado_config

Localizada em schemas/comunicado-config-object.json.

Atributo Valor
name comunicado_config
category configuration
nature physical
storage generic (tabela Record)
crud_mode modal
ui_mode list
key_property id
label_property name

Properties

Propriedade Tipo Funcao
name string (required) Nome amigavel da regra
is_active boolean Liga/desliga sem precisar excluir
trigger_object foreign_key -> object (required) Dropdown com todos os objects de todos os apps (alimentado por systemStore.getLookupData('object')). Valor salvo = o.name.
trigger_event enum: create/update/delete Quando disparar
trigger_filter json Filtro adicional. Ex: {"status":"TODO","prioridade":"ALTA"}
scope_app_id FK app Restringe a registros cujo app_origem_id coincide
channel enum: email/whatsapp/both Canal de envio
recipient_email string (semantic: email) Destinatario quando canal = email
recipient_phone string (semantic: phone) Destinatario quando canal = whatsapp ou both
waha_session string Session WAHA usada para enviar
subject string Assunto (suporta placeholders)
message_template textarea (required) Corpo da mensagem (suporta placeholders)
last_dispatched_at datetime Telemetria β€” preenchida pelo dispatcher
dispatch_count number Telemetria β€” contador

Placeholders

{{record.<campo>}} β€” substituido pelos campos do registro que disparou o evento. Ex: numa regra para task_item:

Subject: Nova task: {{record.titulo}}
Body:
Foi criada a task "{{record.titulo}}" com prioridade {{record.prioridade}}.
Status atual: {{record.status}}

4. Como instalar em um App

Opcao A β€” Script (recomendado)

# Apenas no app task-board:
node scripts/add-comunicado-config-to-app.mjs task-board

# Em todos os apps:
node scripts/add-comunicado-config-to-app.mjs --all

O script (scripts/add-comunicado-config-to-app.mjs) faz, de forma idempotente:

  1. Carrega o schema de schemas/comunicado-config-object.json
  2. Adiciona/atualiza em app.objects[]
  3. Garante que existe um module Configuracoes em app.modules[] referenciando o object (e o motor gera o item de menu automaticamente em /<app-slug>/configuracoes/comunicado_config)
  4. Limpa entradas legadas em app.pages[] deixadas por versoes anteriores do script (que causavam o erro "App comunicados nao encontrada no manifesto")
  5. Persiste via prisma.app.update

Por que nao adicionar em app.pages[]?
O item de sidebar vem do modules[].objects[] β€” adicionar em pages[] cria duplicacao e, se o path for absoluto, conflita com a rota dinamica [app]/index.vue.

Apos rodar, recarregue o app β€” o item "Comunicados" aparece no menu lateral em Configuracoes.

Opcao B β€” Manual via Schema Manager

  1. Abra /fastapp/schema-v3?app=<id>
  2. Importe o JSON de schemas/comunicado-config-object.json
  3. Adicione module/page conforme padrao

5. Roadmap β€” Fases

Fase 1 β€” Painel (βœ“ entregue)

  • Object schema comunicado_config
  • Script de migracao (Add to App)
  • Documentacao

Fase 2 β€” Dispatcher + Email (βœ“ entregue)

  • server/utils/email.ts β€” wrapper SMTP (Nodemailer, Gmail por padrao, SSL/TLS)
  • server/utils/comunicado-dispatcher.ts β€” le regras, filtra (trigger_object/event, trigger_filter, scope_app_id), renderiza template, dispara
  • Hook fire-and-forget em server/api/v1/data/[app]/[object]/index.post.ts (4 caminhos: external, native delegate, native table, generic Record)
  • Hook em [id].patch.ts para evento update (mesmos 4 caminhos)
  • Hook em [id].delete.ts para evento delete (mesmos 4 caminhos; passa o existing lido antes do delete para o template ter acesso aos campos)
  • GET /api/email/verify β€” health check do SMTP sem enviar
  • POST /api/email/test β€” envia e-mail de teste para um destinatario
  • Telemetria β€” last_dispatched_at e dispatch_count atualizados na propria regra

Configuracao de SMTP (Gmail)

  1. Ativar Verificacao em 2 etapas: https://myaccount.google.com/security
  2. Gerar Senha de app: https://myaccount.google.com/apppasswords (cole sem espacos)
  3. .env:
    SMTP_HOST=smtp.gmail.com
    SMTP_PORT=465
    [email protected]
    SMTP_PASS=abcdefghijklmnop      # 16 chars da senha de app
    [email protected]
    
  4. Validar: curl http://localhost:3000/api/email/verify β†’ { ok: true, configured: true }
  5. Enviar teste: curl -X POST http://localhost:3000/api/email/test -H "Content-Type: application/json" -d '{"to":"[email protected]"}'

Fase 3 β€” WhatsApp (βœ“ entregue)

  • Reaproveita server/utils/waha.ts
  • O dispatcher decide canal pelo campo channel
  • Opcoes de canal: email, whatsapp e both (envia pelos dois canais)
  • recipient_phone aceita multiplos numeros separados por ;, , ou quebra de linha, sem mascara de telefone
  • Se uma regra antiga estiver como email, mas tiver recipient_phone preenchido, o dispatcher tambem envia WhatsApp
  • waha_session usa default quando vazio

Fase 4 β€” UI Refinada

  • View kanban-like agrupando por canal/objeto
  • Preview de mensagem com placeholders renderizados
  • Teste de envio manual ("Enviar teste agora")
  • Painel de telemetria (taxa de entrega, ultimos disparos)

6. Como funciona o gatilho (vista geral, fase 2)

sequenceDiagram
    participant User
    participant API as POST /api/v1/data/<br/>:app/task_item
    participant DB as createRecord()
    participant Dispatcher as dispatchComunicados()
    participant Rules as app.objects.<br/>comunicado_config
    participant Email as email.ts
    participant WAHA as waha.ts

    User->>API: cria task
    API->>DB: insert Record
    DB-->>API: novo registro
    API->>Dispatcher: (objectName="task_item", event="create", record)
    Dispatcher->>Rules: filtra is_active + trigger_object + trigger_event
    Dispatcher->>Dispatcher: aplica trigger_filter
    Dispatcher->>Dispatcher: aplica scope_app_id (se setado)
    par para cada regra ativa
        alt channel = email
            Dispatcher->>Email: send(to, subject, body)
        else channel = whatsapp/both
            Dispatcher->>WAHA: sendText(session, chatId, text)
        end
    end
    Dispatcher->>Rules: incrementa dispatch_count + last_dispatched_at
    API-->>User: registro criado

PrincΓ­pio: o dispatcher e fire-and-forget (nunca bloqueia o response da API). Falhas viram log + telemetria, nao quebram a criacao do registro.


7. Exemplo Completo (caso do usuario)

"Quero receber alerta no email [email protected] toda vez que alguem criar uma task nova para o app task-board."

Apos rodar node scripts/add-comunicado-config-to-app.mjs task-board, navegar ate /task-board/configuracoes/comunicado_config e criar:

{
  "name": "Alerta de nova task β€” gestor",
  "is_active": true,
  "trigger_object": "task_item",
  "trigger_event": "create",
  "trigger_filter": null,
  "scope_app_id": 25,
  "channel": "email",
  "recipient_email": "[email protected]",
  "subject": "Nova task: {{record.titulo}}",
  "message_template": "Uma nova task foi criada:\n\nTitulo: {{record.titulo}}\nPrioridade: {{record.prioridade}}\nStatus: {{record.status}}"
}

Quando a fase 2 estiver implementada, isso bastara β€” sem codigo customizado no app.


Status: Fase 1, Fase 2 e Fase 3 entregues. WhatsApp via WAHA ativo no dispatcher.
Branch: feat/Panel-Comunication