# 📣 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

```mermaid
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`](./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)

```bash
# 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`](./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)

- [x] Object schema `comunicado_config`
- [x] Script de migracao (Add to App)
- [x] Documentacao

### Fase 2 — Dispatcher + Email (✓ entregue)

- [x] `server/utils/email.ts` — wrapper SMTP (Nodemailer, Gmail por padrao, SSL/TLS)
- [x] `server/utils/comunicado-dispatcher.ts` — le regras, filtra (trigger_object/event, trigger_filter, scope_app_id), renderiza template, dispara
- [x] Hook fire-and-forget em `server/api/v1/data/[app]/[object]/index.post.ts` (4 caminhos: external, native delegate, native table, generic Record)
- [x] Hook em `[id].patch.ts` para evento `update` (mesmos 4 caminhos)
- [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)
- [x] `GET /api/email/verify` — health check do SMTP sem enviar
- [x] `POST /api/email/test` — envia e-mail de teste para um destinatario
- [x] 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`:
   ```env
   SMTP_HOST=smtp.gmail.com
   SMTP_PORT=465
   SMTP_USER=seuemail@gmail.com
   SMTP_PASS=abcdefghijklmnop      # 16 chars da senha de app
   SMTP_FROM=seuemail@gmail.com
   ```
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":"voce@dominio.com"}'`

### 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)

```mermaid
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 gestor@empresa.com 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:

```json
{
  "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": "gestor@empresa.com",
  "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`
