DOCS: Adicionado README file
Some checks are pending
Deploy Dev / deploy (push) Waiting to run

This commit is contained in:
Rafael Alves Lopes 2026-05-27 17:23:05 -03:00
parent cb08a9f48a
commit b2aa1a7712
4 changed files with 87 additions and 751 deletions

87
README.md Normal file
View File

@ -0,0 +1,87 @@
# Omnichannel Backend
![Node.js](https://img.shields.io/badge/Node.js-20.x-green)
![NestJS](https://img.shields.io/badge/NestJS-11.x-red)
![TypeScript](https://img.shields.io/badge/TypeScript-6.x-blue)
![PostgreSQL](https://img.shields.io/badge/Database-PostgreSQL-blue)
![Socket.IO](https://img.shields.io/badge/Realtime-Socket.IO-black)
![JWT](https://img.shields.io/badge/Auth-JWT-orange)
![WhatsApp](https://img.shields.io/badge/Channel-WhatsApp-25D366)
Backend da plataforma Omnichannel da Sothis, responsavel por autenticacao, atendimento, integracao com WhatsApp, gestao administrativa, contatos, base de conhecimento e servicos de apoio ao painel web.
Este repositorio contem apenas a API. Para subir o projeto completo com frontend, backend e configuracao de deploy, utilize o repositorio de orquestracao:
https://chaleiradev.sothistelecom.com/Sothis/omnichannel-deploy
## Stack
- Node.js
- NestJS
- TypeScript
- PostgreSQL
- Socket.IO
- whatsapp-web.js
- JSON Web Token para autenticacao
- LDAP/AD e Microsoft OAuth como provedores de login configuraveis
## Documentacao da API
A documentacao detalhada das rotas fica disponivel no Swagger do backend no ambiente publicado.
Para documentacao tecnica complementar do backend, decisoes, modulos e operacao, acesse a wiki:
https://chaleiradev.sothistelecom.com/Sothis/omnichannel-backend/wiki
## Execucao local
Instale as dependencias:
```bash
npm install
```
Crie o arquivo de ambiente a partir do exemplo:
```bash
cp .env.example .env.development
```
Configure as variaveis de banco, JWT, CORS e integracoes no `.env.development`.
Execute em modo desenvolvimento:
```bash
npm run dev
```
Por padrao, a API usa a porta configurada em `PORT` ou `BACKEND_PORT`. Se nenhuma variavel for definida, utiliza `3001`.
## Build e producao
Gere o build:
```bash
npm run build
```
Execute a versao compilada:
```bash
npm start
```
## Estrutura principal
- `src/infra`: configuracao, banco de dados, logger e infraestrutura compartilhada.
- `src/modules/auth`: autenticacao, JWT, LDAP/AD e Microsoft OAuth.
- `src/modules/admin`: recursos administrativos, base de conhecimento, contatos e controles de acesso.
- `src/modules/whatsapp`: conexao WhatsApp, QR Code, mensagens, transferencia e eventos em tempo real.
- `src/modules/attendance`: apoio as regras de atendimento.
- `src/modules/call`, `src/modules/chat` e `src/modules/home`: modulos de apoio as telas e fluxos do frontend.
## Observacoes
- O banco de dados nao e criado por este repositorio. A configuracao deve apontar para uma instancia PostgreSQL existente.
- Arquivos locais como `.env*`, logs, `dist`, dumps e sessoes do WhatsApp ficam fora do Git.
- Para operacao completa, deploy e atualizacao em producao, consulte o repositorio `omnichannel-deploy`.

View File

@ -1,351 +0,0 @@
# Controle de Acesso, Areas e Usuarios
## Visao geral
O backend agora usa o banco PostgreSQL para manter o usuario interno da aplicacao e suas atribuicoes operacionais.
A autenticacao continua sendo feita por provedores externos:
- LDAP / Active Directory
- Microsoft OAuth
Depois que o provedor confirma a identidade, o backend sincroniza esse usuario no banco e consulta:
- perfis de acesso
- areas vinculadas
- area principal
- status de acesso
Esse status determina qual experiencia o frontend deve renderizar em `/home`.
---
## Fluxo de login com banco
```text
Frontend
-> POST /auth/login ou OAuth Microsoft
-> Backend autentica no provedor externo
-> Backend cria/atualiza usuarios
-> Backend cria/atualiza usuarios_provedores
-> Backend consulta usuarios_perfis e usuarios_areas
-> Backend emite JWT com perfis/areas
-> Frontend salva authToken/authUser
-> Frontend navega para /home
```
O provedor externo valida identidade. O banco define acesso dentro do Omnichannel.
---
## Tabelas usadas
### `usuarios`
Representa uma pessoa autenticada no produto.
Campos principais:
- `id`
- `nome`
- `email`
- `ativo`
- `created_at`
- `updated_at`
### `usuarios_provedores`
Relaciona o usuario interno com o provedor externo usado no login.
Exemplos:
- `ldap` + username do AD
- `microsoft` + userPrincipalName/email
### `perfis_acesso`
Define os niveis de acesso.
Perfis iniciais:
- `Agente`
- `Supervisor`
- `Admin`
### `usuarios_perfis`
Relaciona usuarios e perfis.
Hoje o frontend usa o perfil principal para decidir a experiencia em `/home`.
### `areas`
Representa as areas operacionais.
Areas iniciais:
- `Suporte`
- `Financeiro`
- `Comercial`
### `usuarios_areas`
Relaciona usuarios e areas.
Um usuario pode ter mais de uma area, mas a regra atual permite no maximo uma area principal por usuario.
---
## Status de acesso
O backend devolve `accessStatus` no usuario autenticado.
Valores:
- `assigned`: usuario tem pelo menos um perfil e uma area
- `unassigned`: usuario existe/autenticou, mas ainda nao tem perfil ou area
### Comportamento no frontend
```text
assigned + Admin -> /home renderiza painel Admin
assigned + Supervisor -> /home renderiza painel Supervisor
assigned + Agente -> /home renderiza tela do Atendente
unassigned -> /home renderiza tela de acesso aguardando configuracao
```
Esse comportamento permite que um usuario novo entre via AD/OAuth, seja criado no banco e fique bloqueado ate um Admin atribuir perfil e area.
---
## Payload do usuario autenticado
Exemplo de resposta do login:
```json
{
"token": "jwt",
"user": {
"id": "1",
"databaseId": 1,
"name": "Atendente Demo",
"email": "atendente@sothis.com.br",
"username": "atendente",
"provider": "ldap",
"perfis": ["Agente"],
"profiles": ["Agente"],
"areas": ["Suporte"],
"areaPrincipal": "Suporte",
"accessStatus": "assigned"
}
}
```
No JWT tambem entram:
- `name`
- `email`
- `provider`
- `username`
- `perfis`
- `profiles`
- `areas`
- `areaPrincipal`
- `accessStatus`
---
## Variaveis de ambiente
O backend le as variaveis de banco pelo `.env.development` em desenvolvimento.
```env
DB_HOST=10.0.120.75
DB_PORT=5432
DB_USER=desenvolvimento
DB_PASSWORD=********
DB_NAME=omnichannel-dev
```
Observacao:
- `npm run dev` usa `NODE_ENV=development` e carrega `.env.development`.
- `npm run start` usa `NODE_ENV=production`; nesse caso, configure `.env.production` ou variaveis do ambiente.
---
## Endpoints administrativos
Base path:
```text
/admin/access
```
### Listar opcoes de acesso
```http
GET /admin/access/options
```
Resposta:
```json
{
"profiles": [
{ "id": 3, "nome": "Admin" },
{ "id": 1, "nome": "Agente" },
{ "id": 2, "nome": "Supervisor" }
],
"areas": [
{ "id": 3, "nome": "Comercial" },
{ "id": 2, "nome": "Financeiro" },
{ "id": 1, "nome": "Suporte" }
]
}
```
### Listar usuarios
```http
GET /admin/access/users
```
Resposta:
```json
[
{
"id": 1,
"nome": "Admin Demo",
"email": "admin@sothis.com.br",
"ativo": true,
"perfis": [{ "id": 3, "nome": "Admin" }],
"areas": [{ "id": 1, "nome": "Suporte", "principal": true }],
"perfilPrincipal": { "id": 3, "nome": "Admin" },
"areaPrincipal": { "id": 1, "nome": "Suporte", "principal": true },
"accessStatus": "assigned"
}
]
```
### Atualizar acesso de usuario
```http
PUT /admin/access/users/:id
Content-Type: application/json
```
Body:
```json
{
"perfilId": 2,
"areaId": 1
}
```
Comportamento atual:
- remove perfis anteriores do usuario
- remove areas anteriores do usuario
- cria o novo perfil informado
- cria a nova area como principal
- retorna o usuario atualizado
Para remover atribuicoes:
```json
{
"perfilId": null,
"areaId": null
}
```
---
## Usuarios de demonstracao
A migration `003_demo_access.sql` cria usuarios iniciais para demonstracao:
| Usuario | Email | Perfil | Area |
|---|---|---|---|
| Admin Demo | `admin@sothis.com.br` | Admin | Suporte |
| Supervisor Demo | `supervisor@sothis.com.br` | Supervisor | Suporte |
| Atendente Demo | `atendente@sothis.com.br` | Agente | Suporte |
Esses usuarios existem no banco, mas nao substituem a autenticacao real.
Para logar pela tela de login, o usuario precisa existir no AD ou no provedor Microsoft configurado.
---
## Como testar
### Backend
```bash
cd backend
npm run dev
```
Endpoints de validacao:
```text
http://localhost:3001/admin/access/options
http://localhost:3001/admin/access/users
```
### Frontend sem depender de AD
No console do navegador:
```js
localStorage.setItem('authUser', JSON.stringify({
username: 'admin@sothis.com.br',
name: 'Admin Demo',
email: 'admin@sothis.com.br',
perfis: ['Admin'],
profiles: ['Admin'],
areas: ['Suporte'],
accessStatus: 'assigned'
}));
localStorage.setItem('authToken', 'dev-token');
location.href = '/home';
```
Para usuario sem atribuicao:
```js
localStorage.setItem('authUser', JSON.stringify({
username: 'novo.usuario',
name: 'Novo Usuario',
email: 'novo.usuario@sothis.com.br',
perfis: [],
profiles: [],
areas: [],
accessStatus: 'unassigned'
}));
localStorage.setItem('authToken', 'dev-token');
location.href = '/home';
```
---
## Limitacoes atuais
- Os endpoints `/admin/access/*` ainda nao possuem guard JWT nem checagem de perfil Admin.
- A alteracao de acesso substitui perfil/area atuais por um unico perfil e uma unica area principal.
- Ainda nao ha auditoria das alteracoes de acesso.
- O painel Admin consome os endpoints reais, mas ainda possui fallback visual mockado se o backend estiver indisponivel.
- A senha do banco nao deve ser commitada; arquivos `.env.*` seguem ignorados pelo Git.
---
## Proximos passos sugeridos
- Adicionar `AuthGuard` JWT.
- Proteger `/admin/access/*` para `Admin`.
- Registrar auditoria em `logs_auditoria`.
- Criar endpoints CRUD completos para `areas`.
- Permitir multiplas areas por usuario na UI, mantendo uma area principal.

View File

@ -1,268 +0,0 @@
# Módulo de Autenticação
## Visão geral
O módulo `auth` centraliza toda a lógica de autenticação do Omnichannel. Ele suporta múltiplos provedores de identidade e emite JWT próprio da aplicação, independente de qual provedor foi usado.
Provedores implementados:
- **LDAP / Active Directory** — login com usuário e senha do AD corporativo
- **Microsoft OAuth (Entra ID)** — login via conta Microsoft com redirect OAuth 2.0
A arquitetura foi desenhada para facilitar a adição de novos provedores no futuro.
---
## Estrutura de arquivos
```
src/modules/auth/
├── auth.module.ts # Registro do módulo no NestJS
├── auth.controller.ts # Rotas HTTP
├── auth.service.ts # Fachada — delega para os providers
├── auth.config.ts # Leitura de variáveis de ambiente
├── auth-token.service.ts # Emissão de JWT da aplicação
├── auth.types.ts # Interfaces TypeScript compartilhadas
└── providers/
├── ldap-auth.provider.ts # Autenticação LDAP/AD
├── microsoft-oauth.provider.ts # Autenticação Microsoft OAuth
└── oauth-state.service.ts # Proteção CSRF para OAuth
```
---
## Rotas disponíveis
| Método | Rota | Descrição |
|--------|---------------------------------|------------------------------------------------|
| GET | `/auth/config` | Retorna quais provedores estão habilitados |
| POST | `/auth/login` | Login com usuário e senha (LDAP/AD) |
| GET | `/auth/oauth/microsoft/start` | Inicia o fluxo OAuth com a Microsoft |
| GET | `/auth/oauth/microsoft/callback`| Callback que a Microsoft chama após o login |
---
## Variáveis de ambiente
```env
# Servidor
PORT=3001
FRONTEND_URL=http://localhost:3000
# JWT
JWT_SECRET=uma-chave-longa-e-aleatoria
JWT_EXPIRES_IN=8h
# Provedores ativos (separados por vírgula)
AUTH_PROVIDERS=ldap
# LDAP / Active Directory
LDAP_ENABLED=true
LDAP_URL=ldaps://servidor-ad:636
LDAP_DOMAIN=empresa.com.br
LDAP_USER_DN_TEMPLATE={{username}}@empresa.com.br
LDAP_SEARCH_BASE=DC=empresa,DC=com
LDAP_SEARCH_FILTER=(&(objectClass=user)(sAMAccountName={{username}}))
LDAP_TIMEOUT_MS=5000
# Microsoft Entra ID (desabilitado por padrão)
MICROSOFT_ENABLED=false
MICROSOFT_TENANT_ID=common
MICROSOFT_CLIENT_ID=
MICROSOFT_CLIENT_SECRET=
MICROSOFT_REDIRECT_URI=http://localhost:3001/auth/oauth/microsoft/callback
MICROSOFT_SUCCESS_REDIRECT_URL=http://localhost:3000/login
```
> `JWT_SECRET` deve ser uma string longa e aleatória. Em produção, nunca use o valor padrão do `.env.development`.
---
## Fluxo LDAP / Active Directory
```
Frontend
→ POST /auth/login { username, password }
→ AuthController
→ AuthService.loginWithLdap()
→ LdapAuthProvider.authenticate()
→ Conecta no servidor AD (LDAP_URL)
→ Faz bind com o usuário e senha
→ Se o bind falhar: UnauthorizedException
→ Busca dados do usuário no diretório (se LDAP_SEARCH_BASE configurado)
→ Monta objeto AuthenticatedUser
→ AuthTokenService.issueToken()
→ Gera JWT assinado com JWT_SECRET
→ Retorna { token, user } para o frontend
```
O AD apenas valida a identidade. O JWT emitido é da aplicação, não do AD.
---
## Fluxo Microsoft OAuth
```
1. Frontend redireciona para GET /auth/oauth/microsoft/start
→ Backend gera um state assinado (proteção CSRF)
→ Backend redireciona para login.microsoftonline.com
2. Usuário autentica na Microsoft
3. Microsoft chama GET /auth/oauth/microsoft/callback?code=...&state=...
→ Backend valida o state (assinatura + expiração)
→ Backend troca o code por access_token (chamada server-to-server)
→ Backend consulta Microsoft Graph /me para obter dados do usuário
→ AuthTokenService.issueToken() gera JWT próprio
→ Backend redireciona para MICROSOFT_SUCCESS_REDIRECT_URL?token=...
4. Frontend salva o token e navega para /home
```
---
## Proteção CSRF com OAuth State
O `OAuthStateService` protege o fluxo OAuth contra ataques de CSRF.
**Como funciona:**
1. No início do fluxo, o backend cria um state:
- Gera um nonce aleatório + timestamp
- Converte para base64url
- Assina com HMAC-SHA256 usando o `JWT_SECRET`
- Formato final: `payload.assinatura`
2. No callback, o backend verifica:
- O state tem os dois pedaços (`payload.assinatura`)
- A assinatura é válida (recalcula e compara com `timingSafeEqual`)
- O state não expirou (padrão: 10 minutos, configurável via `MICROSOFT_STATE_MAX_AGE_MS`)
Se qualquer verificação falhar, o callback é rejeitado com `400 Bad Request`.
---
## JWT da aplicação
Após qualquer autenticação bem-sucedida, o `AuthTokenService` emite um JWT com o seguinte payload:
```json
{
"sub": "identificador-do-usuario",
"name": "Nome Completo",
"email": "usuario@empresa.com",
"provider": "ldap",
"username": "usuario"
}
```
O `sub` é atualmente o email ou identificador externo. Quando houver banco de dados, deve ser substituído pelo ID interno da tabela `users`.
---
## Como adicionar um novo provedor
1. Crie o arquivo em `src/modules/auth/providers/novo-provedor.provider.ts`:
```typescript
import { Injectable } from '@nestjs/common';
import { AuthConfigService } from '../auth.config';
import { AuthTokenService } from '../auth-token.service';
import { AuthResult } from '../auth.types';
@Injectable()
export class NovoProvedorProvider {
constructor(
private readonly authConfig: AuthConfigService,
private readonly authToken: AuthTokenService,
) {}
async authenticate(/* dados necessários */): Promise<AuthResult> {
// 1. Valide as credenciais no provedor externo
// 2. Monte o objeto AuthenticatedUser
// 3. Emita o token com this.authToken.issueToken(user)
// 4. Retorne { token, user }
}
}
```
2. Registre o provider em `auth.module.ts`:
```typescript
providers: [
AuthConfigService,
AuthService,
AuthTokenService,
LdapAuthProvider,
MicrosoftOAuthProvider,
OAuthStateService,
NovoProvedorProvider, // adicione aqui
],
```
3. Injete no `AuthService` e exponha o método necessário:
```typescript
constructor(
private readonly authConfig: AuthConfigService,
private readonly ldapAuthProvider: LdapAuthProvider,
private readonly microsoftOAuthProvider: MicrosoftOAuthProvider,
private readonly novoProvedorProvider: NovoProvedorProvider, // injete aqui
) {}
loginComNovoProvedor(dados: any) {
return this.novoProvedorProvider.authenticate(dados);
}
```
4. Adicione a rota no `AuthController`.
5. Se o provedor precisar de configuração, adicione as variáveis no `AuthConfigService` e no `.env`.
---
## Diagnóstico de problemas
### Login LDAP falha com `UnauthorizedException`
- Verifique se `LDAP_URL` está acessível a partir do servidor backend
- Confirme que `LDAP_DOMAIN` ou `LDAP_USER_DN_TEMPLATE` está correto
- Teste a conectividade: `ldapsearch -H ldaps://servidor:636 -x`
- Verifique `LDAP_TIMEOUT_MS` — servidores lentos podem estar expirando
- O erro é genérico intencionalmente para não vazar informações. Adicione um `console.log(_error)` temporário no `catch` do `ldap-auth.provider.ts` para ver o erro real
### Login Microsoft falha com `400 Bad Request`
- O state expirou (padrão: 10 minutos). Se o usuário demorou muito na tela da Microsoft, repita o fluxo
- Verifique se `MICROSOFT_REDIRECT_URI` no `.env` é idêntico ao cadastrado no Azure App Registration
- Confirme que `MICROSOFT_CLIENT_ID` e `MICROSOFT_CLIENT_SECRET` estão corretos e não expiraram
### Token inválido no frontend
- Verifique se `JWT_SECRET` não mudou entre deploys — isso invalida todos os tokens emitidos anteriormente
- Confirme que o frontend está enviando o header `Authorization: Bearer <token>`
### `GET /auth/config` retorna os provedores errados
- Verifique `LDAP_ENABLED` e `MICROSOFT_ENABLED` no `.env`
- Reinicie o servidor — variáveis de ambiente são lidas na inicialização
---
## O que ainda falta para produção
- [ ] Tabela `users` no banco de dados
- [ ] Tabela `auth_identities` para vincular provedores externos ao usuário interno
- [ ] `sub` do JWT usando ID interno do banco, não email externo
- [ ] Guard NestJS para proteger rotas privadas (`@UseGuards(AuthGuard)`)
- [ ] Roles e permissões
- [ ] Auditoria de login
- [ ] Trocar token na query string por cookie HTTP-only (reduz exposição no browser)
---
## Documentacao complementar
A sincronizacao de usuarios autenticados com o banco, os perfis de acesso, as areas operacionais e os endpoints administrativos estao documentados em:
- [`access-control.md`](./access-control.md)

View File

@ -1,132 +0,0 @@
# Modulo de Chat WhatsApp (Backend)
## Visao geral
O modulo de WhatsApp do backend e desenvolvido em **NestJS** e utiliza a biblioteca **whatsapp-web.js** (que roda uma instancia headless do Chromium via **Puppeteer**) para integrar a aplicacao diretamente com o WhatsApp Web em tempo real.
A arquitetura e constituida por quatro pilares:
1. **Puppeteer Client**: Controla o WhatsApp Web, gera o QR Code para autenticacao e escuta eventos de novas mensagens.
2. **WebSocket Gateway**: Transmite eventos em tempo real (novas mensagens, atualizacao de status) para o frontend usando Socket.io.
3. **Persistencia Hibrida**: Mantem conversas indexadas localmente via JSON para performance e o status de atribuicao de atendimento gravado no banco relacional (PostgreSQL).
4. **Servico de Atribuicao**: Gerencia o vinculo dos chats com os atendentes (`usuarios`) e suas respectivas `areas` operacionais.
---
## Fluxo de Eventos e Mensagens
### 1. Inicializacao e Conexao
* O backend inicia o cliente do `whatsapp-web.js` na porta configurada.
* Se nenhuma sessao ativa for encontrada na pasta `/whatsapp-session`, o cliente gera um QR Code em formato Base64.
* Esse QR Code e enviado via WebSocket (`qr`) para o frontend configurar o dispositivo.
* Uma vez conectado, o status muda para `CONNECTED` e a pasta de sessao e gravada localmente.
### 2. Captura de Mensagens (`message_create`)
Utilizamos o evento global `message_create` para capturar tanto mensagens recebidas do cliente quanto mensagens enviadas pelo proprio atendente (seja pela tela ou por outro dispositivo sincronizado):
```text
Puppeteer (Message)
-> Trata de-duplicacao ou broadcast
-> Se possuir midia, baixa os buffers em tempo real
-> Transmite via WebSocket 'message' para o frontend
-> Salva ou atualiza a conversa na persistencia local JSON
```
---
## Persistencia e Banco de Dados
### 1. Cache Local JSON (`whatsapp-chats-persist.json`)
Para evitar sobrecarregar o banco relacional e garantir latencias imperceptiveis de scroll, os chats e seus metadados de visualizacao sao armazenados de forma persistente em um arquivo JSON local na raiz do backend.
### 2. Tabela de Atribuicao de Chat (`whatsapp_chat_atribuicoes`)
O controle de quem esta atendendo qual chat e estritamente transacional e reside no banco PostgreSQL.
A estrutura da tabela e criada pela migration `004_whatsapp.sql`:
```sql
CREATE TABLE IF NOT EXISTS whatsapp_chat_atribuicoes (
id SERIAL PRIMARY KEY,
chat_id VARCHAR(255) NOT NULL,
user_id SERIAL REFERENCES usuarios(id) ON DELETE CASCADE,
area_id SERIAL REFERENCES areas(id) ON DELETE SET NULL,
assigned_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
UNIQUE(chat_id)
);
```
#### Regras de Atribuicao:
* `chat_id`: Identificador unico da conversa no WhatsApp (ex: `5511999999999@c.us` ou `...@lid`).
* `user_id`: ID inteiro (`usuarios.id`) do atendente que assumiu.
* `area_id`: ID inteiro (`areas.id`) do setor sob o qual o atendimento esta sendo prestado (ex: `1` para Suporte).
---
## Endpoints do Modulo
Base path: `/whatsapp`
### 1. Enviar Mensagem
```http
POST /whatsapp/send
Content-Type: application/json
```
**Payload**:
```json
{
"to": "5511999999999@c.us",
"message": "Ola, tudo bem?",
"media": {
"data": "base64String...",
"mimetype": "image/png",
"filename": "comprovante.png"
}
}
```
### 2. Atribuir Chat
```http
POST /whatsapp/assign
Content-Type: application/json
```
**Payload**:
```json
{
"chatId": "5511999999999@c.us",
"userId": 4,
"areaId": 1
}
```
### 3. Liberar Chat
```http
DELETE /whatsapp/release/:chatId
```
---
## Limitacoes e Solucoes de Bugs
### 1. Payload Too Large (Upload de Midia)
Para permitir o envio de imagens, videos e audios pesados codificados em Base64, o limite padrao de payload do NestJS foi estendido para **50MB** no `main.ts` tanto para formato JSON quanto para `urlencoded`.
### 2. Nomes Numericos do WhatsApp (Smart Name Resolution)
Devido a latencias do WhatsApp Web, as mensagens recebidas as vezes reportam apenas o telefone/JID como nome do contato. O servico de WhatsApp contem uma camada reativa de reparacao automatica:
* Checa se o nome e puramente numerico.
* Se for, faz uma chamada em background para o Puppeteer (`client.getContactById`) para obter o `notifyName` real do cliente e atualizar o cache local.
---
## Como Rodar e Testar
### Requisitos locais
* Node.js v18+
* PostgreSQL ativo com as migrations executadas
* Google Chrome ou Chromium instalado (o Puppeteer tentara usar o bundle local se omitido)
### Executando em desenvolvimento
```bash
cd backend
npm run dev
```
Os logs do NestJS no terminal indicarao as fases de inicializacao do Puppeteer, geracao de QR Code ou recuperacao de sessao ativa.