{"id":"repo/comunicado-system","source":"repo","sourceLabel":"repository root","sourcePath":"COMUNICADO_SYSTEM.md","routePath":"/fastapp/public-docs/page/repo/comunicado-system","rawPath":"/fastapp/public-docs/raw/repo/comunicado-system","slug":["repo","comunicado-system"],"slugPath":"repo/comunicado-system","title":"📣 Sistema de Comunicados — Manual Tecnico","description":"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[].","headings":["📣 Sistema de Comunicados — Manual Tecnico","1. Conceito","2. Posicionamento Arquitetural","3. Definicao do Object comunicado config","Properties","Placeholders","4. Como instalar em um App","Opcao A — Script (recomendado)","Apenas no app task board:","Em todos os apps:","Opcao B — Manual via Schema Manager","5. Roadmap — Fases","Fase 1 — Painel (✓ entregue)","Fase 2 — Dispatcher + Email (✓ entregue)","Fase 3 — WhatsApp (✓ entregue)","Fase 4 — UI Refinada","6. Como funciona o gatilho (vista geral, fase 2)","7. Exemplo Completo (caso do usuario)"],"wordCount":759,"markdown":"# 📣 Sistema de Comunicados — Manual Tecnico\r\n\r\n> Painel de configuracao de notificacoes/alertas baseado em eventos. E-mail nesta primeira fase, WhatsApp (via WAHA) na proxima.\r\n> Segue o padrao do Fastapp Engine: o painel **e** um Object em `app.objects[]`.\r\n\r\n---\r\n\r\n## 1. Conceito\r\n\r\n> \"Quero receber um e-mail toda vez que alguem criar uma task nova no MEU app.\"\r\n\r\nO usuario configura **regras de comunicado** no painel. Cada regra descreve:\r\n\r\n- **Quando**: qual objeto monitorar + qual evento (create/update/delete) + filtro opcional\r\n- **Para onde**: canal (email/whatsapp) + destinatario (email ou telefone)\r\n- **O que**: assunto + mensagem com placeholders `{{record.<campo>}}`\r\n\r\nQuando um registro do objeto monitorado e criado/atualizado/excluido, o motor le as regras ativas e dispara as notificacoes.\r\n\r\n---\r\n\r\n## 2. Posicionamento Arquitetural\r\n\r\n```mermaid\r\ngraph TB\r\n    subgraph \"Metadata Layer (App JSON)\"\r\n        OBJ_CFG[\"app.objects.comunicado_config<br/>(regras configuradas)\"]\r\n        OBJ_TASK[\"app.objects.task_item<br/>(registros monitorados)\"]\r\n    end\r\n    subgraph \"Engine Layer\"\r\n        POST[\"POST /api/v1/data/:app/:object<br/>(index.post.ts)\"]\r\n        DISPATCHER[\"dispatchComunicados()<br/>(2a fase)\"]\r\n    end\r\n    subgraph \"Infrastructure Layer\"\r\n        EMAIL[\"server/utils/email.ts<br/>(2a fase)\"]\r\n        WAHA[\"server/utils/waha.ts<br/>(ja existe)\"]\r\n    end\r\n\r\n    POST -->|ao criar task| DISPATCHER\r\n    DISPATCHER -->|le regras| OBJ_CFG\r\n    DISPATCHER -->|canal=email| EMAIL\r\n    DISPATCHER -->|canal=whatsapp| WAHA\r\n\r\n    style OBJ_CFG fill:#0891b2,color:#fff\r\n    style DISPATCHER fill:#7c3aed,color:#fff\r\n    style WAHA fill:#25D366,color:#000\r\n```\r\n\r\n**Decisao chave**: o painel de configuracao e um Object normal, nao um sistema separado. Isso significa:\r\n- Persistencia: tabela `Record` (storage: `generic`) — zero DDL\r\n- UI: gerada automaticamente pelo `CoreDynamicCRUD`\r\n- API: `/api/v1/data/:app/comunicado_config` (CRUD ja pronto pelo motor)\r\n- Permissoes: passa pelo RBAC padrao\r\n- Snapshot: incluido no `static-manifest.ts`\r\n\r\n---\r\n\r\n## 3. Definicao do Object `comunicado_config`\r\n\r\nLocalizada em [`schemas/comunicado-config-object.json`](./schemas/comunicado-config-object.json).\r\n\r\n| Atributo | Valor |\r\n|---|---|\r\n| `name` | `comunicado_config` |\r\n| `category` | `configuration` |\r\n| `nature` | `physical` |\r\n| `storage` | `generic` (tabela `Record`) |\r\n| `crud_mode` | `modal` |\r\n| `ui_mode` | `list` |\r\n| `key_property` | `id` |\r\n| `label_property` | `name` |\r\n\r\n### Properties\r\n\r\n| Propriedade | Tipo | Funcao |\r\n|---|---|---|\r\n| `name` | string (required) | Nome amigavel da regra |\r\n| `is_active` | boolean | Liga/desliga sem precisar excluir |\r\n| `trigger_object` | foreign_key -> object (required) | Dropdown com todos os objects de todos os apps (alimentado por `systemStore.getLookupData('object')`). Valor salvo = `o.name`. |\r\n| `trigger_event` | enum: create/update/delete | Quando disparar |\r\n| `trigger_filter` | json | Filtro adicional. Ex: `{\"status\":\"TODO\",\"prioridade\":\"ALTA\"}` |\r\n| `scope_app_id` | FK app | Restringe a registros cujo `app_origem_id` coincide |\r\n| `channel` | enum: email/whatsapp/both | Canal de envio |\r\n| `recipient_email` | string (semantic: email) | Destinatario quando canal = email |\r\n| `recipient_phone` | string (semantic: phone) | Destinatario quando canal = whatsapp ou both |\r\n| `waha_session` | string | Session WAHA usada para enviar |\r\n| `subject` | string | Assunto (suporta placeholders) |\r\n| `message_template` | textarea (required) | Corpo da mensagem (suporta placeholders) |\r\n| `last_dispatched_at` | datetime | Telemetria — preenchida pelo dispatcher |\r\n| `dispatch_count` | number | Telemetria — contador |\r\n\r\n### Placeholders\r\n\r\n`{{record.<campo>}}` — substituido pelos campos do registro que disparou o evento. Ex: numa regra para `task_item`:\r\n\r\n```\r\nSubject: Nova task: {{record.titulo}}\r\nBody:\r\nFoi criada a task \"{{record.titulo}}\" com prioridade {{record.prioridade}}.\r\nStatus atual: {{record.status}}\r\n```\r\n\r\n---\r\n\r\n## 4. Como instalar em um App\r\n\r\n### Opcao A — Script (recomendado)\r\n\r\n```bash\r\n# Apenas no app task-board:\r\nnode scripts/add-comunicado-config-to-app.mjs task-board\r\n\r\n# Em todos os apps:\r\nnode scripts/add-comunicado-config-to-app.mjs --all\r\n```\r\n\r\nO script ([`scripts/add-comunicado-config-to-app.mjs`](./scripts/add-comunicado-config-to-app.mjs)) faz, de forma idempotente:\r\n\r\n1. Carrega o schema de `schemas/comunicado-config-object.json`\r\n2. Adiciona/atualiza em `app.objects[]`\r\n3. 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`)\r\n4. Limpa entradas legadas em `app.pages[]` deixadas por versoes anteriores do script (que causavam o erro \"App comunicados nao encontrada no manifesto\")\r\n5. Persiste via `prisma.app.update`\r\n\r\n> **Por que nao adicionar em `app.pages[]`?**\r\n> 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`.\r\n\r\nApos rodar, recarregue o app — o item \"Comunicados\" aparece no menu lateral em Configuracoes.\r\n\r\n### Opcao B — Manual via Schema Manager\r\n\r\n1. Abra `/fastapp/schema-v3?app=<id>`\r\n2. Importe o JSON de `schemas/comunicado-config-object.json`\r\n3. Adicione module/page conforme padrao\r\n\r\n---\r\n\r\n## 5. Roadmap — Fases\r\n\r\n### Fase 1 — Painel (✓ entregue)\r\n\r\n- [x] Object schema `comunicado_config`\r\n- [x] Script de migracao (Add to App)\r\n- [x] Documentacao\r\n\r\n### Fase 2 — Dispatcher + Email (✓ entregue)\r\n\r\n- [x] `server/utils/email.ts` — wrapper SMTP (Nodemailer, Gmail por padrao, SSL/TLS)\r\n- [x] `server/utils/comunicado-dispatcher.ts` — le regras, filtra (trigger_object/event, trigger_filter, scope_app_id), renderiza template, dispara\r\n- [x] Hook fire-and-forget em `server/api/v1/data/[app]/[object]/index.post.ts` (4 caminhos: external, native delegate, native table, generic Record)\r\n- [x] Hook em `[id].patch.ts` para evento `update` (mesmos 4 caminhos)\r\n- [x] 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)\r\n- [x] `GET /api/email/verify` — health check do SMTP sem enviar\r\n- [x] `POST /api/email/test` — envia e-mail de teste para um destinatario\r\n- [x] Telemetria — `last_dispatched_at` e `dispatch_count` atualizados na propria regra\r\n\r\n#### Configuracao de SMTP (Gmail)\r\n\r\n1. Ativar **Verificacao em 2 etapas**: https://myaccount.google.com/security\r\n2. Gerar **Senha de app**: https://myaccount.google.com/apppasswords (cole sem espacos)\r\n3. `.env`:\r\n   ```env\r\n   SMTP_HOST=smtp.gmail.com\r\n   SMTP_PORT=465\r\n   SMTP_USER=seuemail@gmail.com\r\n   SMTP_PASS=abcdefghijklmnop      # 16 chars da senha de app\r\n   SMTP_FROM=seuemail@gmail.com\r\n   ```\r\n4. Validar: `curl http://localhost:3000/api/email/verify` → `{ ok: true, configured: true }`\r\n5. Enviar teste: `curl -X POST http://localhost:3000/api/email/test -H \"Content-Type: application/json\" -d '{\"to\":\"voce@dominio.com\"}'`\r\n\r\n### Fase 3 — WhatsApp (✓ entregue)\r\n\r\n- Reaproveita `server/utils/waha.ts`\r\n- O dispatcher decide canal pelo campo `channel`\r\n- Opcoes de canal: `email`, `whatsapp` e `both` (envia pelos dois canais)\r\n- `recipient_phone` aceita multiplos numeros separados por `;`, `,` ou quebra de linha, sem mascara de telefone\r\n- Se uma regra antiga estiver como `email`, mas tiver `recipient_phone` preenchido, o dispatcher tambem envia WhatsApp\r\n- `waha_session` usa `default` quando vazio\r\n\r\n### Fase 4 — UI Refinada\r\n\r\n- View kanban-like agrupando por canal/objeto\r\n- Preview de mensagem com placeholders renderizados\r\n- Teste de envio manual (\"Enviar teste agora\")\r\n- Painel de telemetria (taxa de entrega, ultimos disparos)\r\n\r\n---\r\n\r\n## 6. Como funciona o gatilho (vista geral, fase 2)\r\n\r\n```mermaid\r\nsequenceDiagram\r\n    participant User\r\n    participant API as POST /api/v1/data/<br/>:app/task_item\r\n    participant DB as createRecord()\r\n    participant Dispatcher as dispatchComunicados()\r\n    participant Rules as app.objects.<br/>comunicado_config\r\n    participant Email as email.ts\r\n    participant WAHA as waha.ts\r\n\r\n    User->>API: cria task\r\n    API->>DB: insert Record\r\n    DB-->>API: novo registro\r\n    API->>Dispatcher: (objectName=\"task_item\", event=\"create\", record)\r\n    Dispatcher->>Rules: filtra is_active + trigger_object + trigger_event\r\n    Dispatcher->>Dispatcher: aplica trigger_filter\r\n    Dispatcher->>Dispatcher: aplica scope_app_id (se setado)\r\n    par para cada regra ativa\r\n        alt channel = email\r\n            Dispatcher->>Email: send(to, subject, body)\r\n        else channel = whatsapp/both\r\n            Dispatcher->>WAHA: sendText(session, chatId, text)\r\n        end\r\n    end\r\n    Dispatcher->>Rules: incrementa dispatch_count + last_dispatched_at\r\n    API-->>User: registro criado\r\n```\r\n\r\n**Princípio**: o dispatcher e fire-and-forget (nunca bloqueia o response da API). Falhas viram log + telemetria, nao quebram a criacao do registro.\r\n\r\n---\r\n\r\n## 7. Exemplo Completo (caso do usuario)\r\n\r\n> \"Quero receber alerta no email gestor@empresa.com toda vez que alguem criar uma task nova para o app task-board.\"\r\n\r\nApos rodar `node scripts/add-comunicado-config-to-app.mjs task-board`, navegar ate `/task-board/configuracoes/comunicado_config` e criar:\r\n\r\n```json\r\n{\r\n  \"name\": \"Alerta de nova task — gestor\",\r\n  \"is_active\": true,\r\n  \"trigger_object\": \"task_item\",\r\n  \"trigger_event\": \"create\",\r\n  \"trigger_filter\": null,\r\n  \"scope_app_id\": 25,\r\n  \"channel\": \"email\",\r\n  \"recipient_email\": \"gestor@empresa.com\",\r\n  \"subject\": \"Nova task: {{record.titulo}}\",\r\n  \"message_template\": \"Uma nova task foi criada:\\n\\nTitulo: {{record.titulo}}\\nPrioridade: {{record.prioridade}}\\nStatus: {{record.status}}\"\r\n}\r\n```\r\n\r\nQuando a fase 2 estiver implementada, isso bastara — sem codigo customizado no app.\r\n\r\n---\r\n\r\n**Status**: Fase 1, Fase 2 e Fase 3 entregues. WhatsApp via WAHA ativo no dispatcher.\r\n**Branch**: `feat/Panel-Comunication`\r\n"}