Files

232 lines
14 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Tarefas — Alemão Conveniências Admin
> Última atualização: 2026-04-26
---
## Legenda
- `[ ]` Pendente
- `[~]` Em andamento
- `[x]` Concluído
---
## ✅ Concluído
### Correções de Auth
- [x] Corrigir 401 em `/api/admin/nw/auto-reply-mode` para funcionários (`verifyAnyToken`)
- [x] Corrigir 401 nas rotas `/api/admin/inbox/*` para funcionários
- [x] Login de funcionário (secretaria Monique) — senha redefinida pelo painel admin
### WhatsApp Inbox
- [x] Nome do colaborador em **negrito** no início de cada mensagem WhatsApp enviada
- [x] Pausa automática da IA ao enviar mensagem (handoff dispara sem clicar no botão)
- [x] Botão de handoff fica laranja imediatamente após envio
- [x] Timer do handoff corrigido para **15 minutos** (estava exibindo 30 por erro de cálculo)
- [x] Handoff salvo no banco local (`nw_handoff`) — independente do motor NewWhats
- [x] Webhook-receiver verifica handoff local antes de chamar a IA
### Inbox Interna
- [x] Cores ajustadas para padrão branco/cinza do WhatsApp
- [x] Layout coluna única (lista ou mensagens, sem duas colunas lado a lado)
### Fase 1 — Escalação & Protocolos
- [x] Botão "Assumir" no toast de escalação — cria thread + pausa IA + agenda expiração BullMQ
- [x] `ref_id` preenchido automaticamente ao assumir (protocolo ↔ Inbox Interna)
- [x] Transferência de protocolo entre atendentes — modal com seleção de conta e mensagem de contexto
- [x] Timer de expiração do handoff via BullMQ (15 min) com notificação SSE ao frontend
### Fase 2 — IA & Conhecimento
- [x] Brain do setor injetado no contexto da IA (`sector_brain_nodes``systemExtra` no motor)
- [x] Human API com timeout — IA solicita resposta humana, BullMQ expira em 5 min, SSE notifica
- [x] Endpoint de resposta Human API — colaborador responde pela Inbox Interna, entrega à IA
- [x] Setor identificado pela IA sincronizado de volta ao banco local (`nw_chat_sectors`)
- [x] SLA por setor — cron a cada minuto, alerta SSE `sla.breach` quando ultrapassado
---
## 🔴 Alta Prioridade
- [x] Quando human_api.timeout: notificar motor para retomar conversa sem resposta humana
---
## 🟢 Relatórios & Métricas de Atendimento
### Fase 3 — Concluído
- [x] **Contador de atendimentos por colaborador** — escalações assumidas, msgs enviadas, Human API respondidas
- [x] **Ranking de atendimentos** — tabela por colaborador com tempo médio de 1ª resposta
- [x] **SLA breach por setor** — contagem de escalações fora do prazo por setor
- [x] **Dashboard de atendimento** — aba "Métricas" com cards resumo + tabelas por colaborador e setor
- [x] **Filtro por período** — Hoje / 7 dias / 30 dias
- [x] **Human API stats** — total, respondidas, timeout, % de resposta
### Fase 4 — Concluído
- [x] **Nota por atendimento** — cliente avalia 15 após encerramento via WhatsApp; capturado no webhook
- [x] **Registro de reclamações / sugestões / elogios** — botão Flag no ThreadPanel com nota opcional
- [x] **Encerrar protocolo** — botão no ThreadPanel envia pesquisa + zera handoff + retoma IA
- [x] **Aprendizado de conhecimento** — botão "Brain" no ThreadPanel salva resposta; opção de promover para `sector_brain_nodes`
- [x] **Gráfico temporal** — barras de escalações por dia no MetricasTab
- [x] **Métricas de flags e ratings** — cards de satisfação, reclamações, sugestões, elogios no dashboard
---
## 🔵 Baixa Prioridade
- [x] Tempo médio de resolução — `AVG(pr.sent_at - esc.first_escalation)` no endpoint `/api/admin/metrics/attendance`; card "Tempo médio de resolução" no MetricasTab
- [x] Multi-tenant — `tenant_id` adicionado a `nw_event_logs` e `nw_auto_replies` via migration; queries de auto-reply filtradas por tenant; `protocol_ratings`/`protocol_flags`/`internal_messages` já isoladas
- [x] Histórico de avaliações de satisfação — endpoint `/api/admin/metrics/ratings-history` com paginação e filtro por nota; tabela paginada com estrelas, status e atendente no MetricasTab
---
## 📝 Notas Técnicas
### Infraestrutura
- Motor NewWhats: `http://127.0.0.1:8008` — IA, conversas e envio de mensagens
- Servidor satélite: `node server.js` em `/sistema-nuvem/` na porta **4001**
- Frontend buildado em `/sistema-nuvem/public/admin-v2/assets/`
- Banco: PostgreSQL `alemao`, usuário `newwhats`, senha em `.env``PG_PASSWORD`
- Redis: `127.0.0.1:6379` — usado pelo BullMQ (configurável via `REDIS_HOST`/`REDIS_PORT`)
### Jobs BullMQ (Redis)
| Queue | Trigger | Delay | Ação |
|---|---|---|---|
| `handoff-expire` | PATCH `/api/nw/handoff/:chatId` com `mode=humano` ou botão Assumir | 15 min | Zera `nw_handoff.human_until`; SSE `handoff.expired` |
| `human-api-timeout` | POST `/api/human-api/request` (interno, via webhook-receiver) | 5 min | Marca `human_api_requests.status='timeout'`; insere msg sistema; SSE `human_api.timeout` |
### Crons (node-cron, `server.js`)
| Schedule | O que faz |
|---|---|
| `* * * * *` (todo minuto) | Verifica escalações abertas sem resposta humana que ultrapassaram `sectors.sla_minutes`; emite SSE `sla.breach` para todos os clientes conectados |
### Eventos SSE (`/api/sse`)
| Evento | Payload | Quem consome |
|---|---|---|
| `handoff.expired` | `{ chatId }` | WhatsAppInbox → toast + refresh HandoffButton |
| `human_api.new` | `{ requestId, threadId, question, sectorName }` | WhatsAppInbox → toast com botão "Ver Inbox" |
| `human_api.answered` | `{ requestId, threadId, answer }` | — (reservado) |
| `human_api.timeout` | `{ requestId, threadId }` | WhatsAppInbox → informativo |
| `sla.breach` | `{ threadId, sectorId, sectorName, slaMins, elapsedMins }` | WhatsAppInbox → toast de alerta |
### Tabelas criadas / alteradas (Fases 14)
| Tabela | Propósito |
|---|---|
| `nw_handoff` | Pausa da IA por chat (`human_until TIMESTAMPTZ`) |
| `nw_chat_sectors` | Setor identificado pela IA por chat (`chat_id → sector_id`) |
| `human_api_requests` | Pedidos da IA para resposta humana (status, timeout_at, answer) |
| `internal_messages` | Inbox interna — threads de escalação, colaboração, human_api, ajuda |
| `sector_brain_nodes` | Base de conhecimento por setor, injetada no system prompt da IA |
| `protocol_ratings` | Avaliações de satisfação (15) enviadas ao cliente ao encerrar protocolo |
| `protocol_flags` | Flags de protocolo: reclamação, sugestão ou elogio registrado pelo atendente |
| `knowledge_learning` | Respostas humanas salvas para promoção à base de conhecimento |
### Fluxo Human API
1. Motor retorna `{ humanApiRequest: { question, sectorName } }` em `/secretaria/ask`
2. `webhook-receiver.js` chama `POST /api/human-api/request` (interno)
3. Banco: cria `human_api_requests` + thread `internal_messages` (type=`human_api`)
4. BullMQ agenda timeout de 5 min; SSE `human_api.new` notifica colaboradores
5. Colaborador responde pela Inbox Interna → `POST /api/human-api/answer/:id`
6. Banco: atualiza status para `answered`; cancela job BullMQ; SSE `human_api.answered`
7. *(próximo passo)* Resposta re-injetada na conversa do motor via `/secretaria/ask`
---
## 🐛 Revisão de Bugs — 2026-04-20
> Revisão completa do código encontrou **40 bugs**. Organizados abaixo por prioridade.
### 🔴 Crítico — Segurança Grave (9)
- [x] **BUG-06** — Adicionar verificação HMAC no webhook Asaas de rifas (`controllers/webhookController.js:8`) — fraude de pagamento
- [x] **BUG-07** — Adicionar verificação HMAC no webhook Asaas do clube (`controllers/clubController.js:445`) — fraude de assinatura
- [x] **BUG-04** — Trocar JWT secrets fracos no `.env` por valores fortes e aleatórios (`.env:13,23`)
- [x] **BUG-01** — Eliminar interpolação direta em queries SQL; usar apenas parâmetros `$N` (`server.js:863`, `models/Analytics.js:6`, `models/ClubMember.js:99`)
- [x] **BUG-02** — Configurar CORS com origens explícitas (`server.js:28`)
- [x] **BUG-03** — Adicionar `secure: true` e `sameSite: 'lax'` em todos os cookies (`controllers/adminController.js:37`, `controllers/usersController.js:12`)
- [x] **BUG-05** — Substituir autenticação do webhook interno: header `x-internal-secret` com `INTERNAL_WEBHOOK_SECRET` do env (`server.js:373,454`)
- [x] **BUG-08** — Corrigir skip do rate limiter: verifica JWT com `jwt.verify()` antes de bypassar (`server.js:40`)
- [x] **BUG-09**`.gitignore` criado em `sistema-nuvem/` protegendo `.env`, `node_modules` e logs
### 🟠 Alto — Bugs Funcionais (13)
- [x] **BUG-10** — Corrigir `JWT_SECRET` indefinido em `signTeamToken` → usa `TEAM_SECRET` (`middleware/teamAuth.js:75`)
- [x] **BUG-13** — Corrigir `.rows` duplicado no `webhookController``query()` já retorna array (`controllers/webhookController.js:31`)
- [x] **BUG-23** — Corrigir campos no `context-builder.js`: `name`/`phone``nome`/`telefone`; `findActivePlan` usa `club_plans` e `user_cpf` (`plugins/newwhats/context-builder.js:42`)
- [x] **BUG-30** — Proteger `GET /api/raffle/tickets` com `verifyToken` (`routes/apiRaffle.js:9`)
- [x] **BUG-33** — Substituir `localhost:4001` por `process.env.APP_URL` (`controllers/webhookController.js`)
- [x] **BUG-21** — Cleanup de `Order` órfã com `Order.delete()` se `Ticket.reserve` falhar (`controllers/raffleController.js:83`)
- [x] **BUG-11** — Incluir `offset` na query SQL de `ClubMember.listByClub` (`models/ClubMember.js:55`)
- [x] **BUG-12** — Adicionar `titulo` à query do sorteio em `RaffleResult.draw` (`models/RaffleResult.js:12`)
- [x] **BUG-16** — Adicionar filtro `tenant_id` em `adminUpdateJob`, `adminDeleteJob`, `adminToggleJob` (`controllers/jobController.js`)
- [x] **BUG-17** — Adicionar filtro `tenant_id` em `cancelMyApplication` (`controllers/jobController.js:189`)
- [x] **BUG-18** — Adicionar filtro `tenant_id` em `adminGetApplication` e `adminUpdateApplication` (`controllers/jobController.js`)
- [x] **BUG-19** — Verificação de tenant em `adminSaveContent` (`controllers/clubController.js:351`)
- [x] **BUG-20** — Permite reativação de membros `suspended`/`pending_payment`; só bloqueia status `active` (`controllers/clubController.js:65`)
### 🟡 Médio — Qualidade e Tratamento de Erros (11)
- [x] **BUG-25**`tenantMiddleware` retorna 503 em vez de `next()` sem `req.tenant` (`middleware/tenant.js:66`)
- [x] **BUG-29**`clubAsaasService` aceita `apiKey` por chamada; `clubController` passa `req.tenant.asaas_key` (`services/clubAsaasService.js`)
- [x] **BUG-15** — Comparação corrigida: `active === 'true'` (`controllers/equipeController.js:54`)
- [x] **BUG-22** — Notificação ao ganhador movida para fora da transação com `setImmediate` após COMMIT (`models/RaffleResult.js`)
- [x] **BUG-26**`validateCPF` adicionada e aplicada em `subscribe` e `getCard` (`controllers/clubController.js`)
- [x] **BUG-27** — Mínimo de senha de admins elevado para 8 caracteres em `createAdmin` e `updateAdmin`
- [x] **BUG-28** — Verifica `searchRes.ok` antes de acessar `searchData.data` (`services/asaasService.js:30`)
- [x] **BUG-31**`limit` máximo de 1000; `offset` mínimo de 0 em `notification-errors` (`server.js`)
- [x] **BUG-32**`REDIS_PORT` parseado com `parseInt` (`services/whatsappService.js:10`)
- [x] **BUG-24**`verifyAnyToken` rejeita imediatamente com 401 se `cloud_token` inválido (`middleware/teamAuth.js`)
- [x] **BUG-35** — Promoção de conhecimento sem `sector_id` retorna 400 (`server.js`)
### 🟢 Baixo — Melhorias (7)
- [x] **BUG-36** — Usar `crypto.timingSafeEqual` na comparação de chaves em `nwAuth` (`plugins/newwhats/auth.js:27`)
- [x] **BUG-37** — Adicionar filtro `tenant_id` em `deleteTicket` (`controllers/raffleController.js:211`)
- [x] **BUG-38** — Não assumir opt-in quando usuário com CPF não é encontrado (`services/whatsappService.js:244`)
- [x] **BUG-39** — Substituir interpolação de `promoted` por parâmetro `$2` (`server.js:641`)
- [x] **BUG-40** — Reduzir expiração do cookie de usuário e adicionar revogação de token (`controllers/usersController.js:12`)
- [x] **BUG-14** — Refatorar declaração de `sseHandoffQueue` para antes das rotas que a usam (`server.js:271`)
- [x] **BUG-34** — Escapar caracteres especiais de LIKE (`%`, `_`) no `context-builder.js` (`plugins/newwhats/context-builder.js:45`)
### Resumo
| Prioridade | Total |
|------------|-------|
| 🔴 Crítico | 9 |
| 🟠 Alto | 13 |
| 🟡 Médio | 11 |
| 🟢 Baixo | 7 |
| **Total** | **40** |
---
## 📬 Inbox Interna — v2 (Pendente)
> Referência: `inbox-interna.md`
> Contexto: rota `/admin-v2/cotacao/inbox`
### INB-SSE — SSE real-time para DMs
- [x] **INB-SSE-01** Adicionar evento `direct_message.new` ao endpoint SSE existente (`/api/sse`) — emitir para o destinatário ao receber nova DM
- [x] **INB-SSE-02** No `CollaboratorsPanel.tsx`, subscrever SSE e atualizar badge de não-lidas em tempo real (substituir o auto-refresh de 30s)
- [x] **INB-SSE-03** No `DMConversationView.tsx`, subscrever SSE e appendar nova mensagem em tempo real quando a conversa está aberta
- [x] **INB-SSE-04** Testar isolamento: evento SSE deve chegar apenas ao `to_account` correto (não broadcast global)
### INB-GC — Group Chat por Setor (v2.2)
- [x] **INB-GC-01** Criar tabela `group_chats` (`id`, `tenant_id`, `sector_id`, `name`, `created_at`)
- [x] **INB-GC-02** Membros via `team_members` existente — sem tabela extra necessária
- [x] **INB-GC-03** Criar tabela `group_messages` (`id`, `tenant_id`, `group_chat_id`, `from_account`, `content`, `created_at`)
- [x] **INB-GC-04** Endpoints: `GET /groups`, `GET /groups/:id/messages`, `POST /groups/:id/message`, `GET /groups/:id/members`
- [x] **INB-GC-05** Evento SSE `group_message.new` — notifica membros do setor (via `sseSend` por membro)
- [ ] **INB-GC-06** Suporte a `@menção` — detectar `@nome` no content e emitir SSE `group_mention` para o mencionado
- [x] **INB-GC-07** Frontend: `GroupChatView.tsx` + tab "Grupos" no `InboxInternaTab`
- [x] **INB-GC-08** Seed automático: grupo criado para cada setor ativo no `initialize()`
### INB-BC — Broadcast (v2.2)
- [x] **INB-BC-01** Criar tabela `broadcasts` (`id`, `tenant_id`, `from_account`, `content`, `created_at`)
- [x] **INB-BC-02** Endpoint `POST /api/admin/inbox/broadcast` — restrito a roles `owner` e `manager`
- [x] **INB-BC-03** Evento SSE `broadcast.sent` via `sseBroadcastTenant` — notifica todos do tenant
- [x] **INB-BC-04** Frontend: tab "Avisos" visível para todos; campo de envio apenas para owner/manager com confirmação
- [x] **INB-BC-05** Broadcasts exibidos como cards destacados com borda âmbar no `BroadcastPanel`