# 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`