232 lines
14 KiB
Markdown
232 lines
14 KiB
Markdown
# 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 1–5 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 1–4)
|
||
| 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 (1–5) 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`
|