# Arquitetura — Backend ## Visão geral O backend segue uma arquitetura em camadas com bootstrap por factories. Cada domínio de negócio é encapsulado em um conjunto padronizado de arquivos. ``` index.js └── CoreFactory.Iniciar() ├── BancoFactory → conecta ao MySQL, valida conexão └── ServerFactory → cria o Express app, registra middlewares, sobe o servidor, │ agenda os jobs em background (src/jobs/) ├── /webhooks/whatsapp (fora de /api, sem JWT, antes de express.json()) └── Roteamento.IniciarRoteamento(app) └── Domínios: Auth · Chat · Conversations · Documents · Ingest · Search · Usuario · Atendimentos · Conversas (WhatsApp) · WhatsappConexao · IxcIntegrador (+ IxcBi) └── *.Rotas.js → *.Controller.js → *Service.js → Model (Objection) ``` ## Camadas ### Factories (`src/factories/`) Responsáveis pelo bootstrap da aplicação. Chamadas em sequência por `index.js` na raiz. | Factory | Responsabilidade | |---|---| | `Core.factory.js` | Orquestra Banco + Server | | `Banco.factory.js` | Conecta ao MySQL e valida a conexão | | `Server.factory.js` | Cria o app Express, registra middlewares globais, sobe o servidor HTTP | ### Roteamento (`src/routes/index.js`) A classe `Roteamento` registra todos os domínios no Express via `IniciarRoteamento(app)`. Cada domínio tem seu próprio arquivo `*.Rotas.js` que define as rotas e middlewares daquele domínio. Todos os domínios são montados sob o prefixo `/api`, que recebe **dois middlewares globais** antes de qualquer rota: `rateLimitMiddleware` (`RateLimit.js`) e `authMiddleware` (`Validajwt.js`). Ou seja, o JWT é decodificado globalmente; os middlewares `requireUser`/`requireAdmin` por rota apenas verificam o resultado. Ver [auth.md](auth.md) para o fluxo completo e os níveis de acesso. ### Controllers (`src/controllers/`) Recebem a requisição HTTP, extraem os dados necessários (params, body, user) e delegam ao Service correspondente. Não contêm lógica de negócio. ### Services (`src/services/`) Contêm toda a lógica de negócio. São chamados pelos controllers. Podem chamar Models (Objection) ou clients externos (Qdrant, Ollama). ### Models (`src/models/`) Modelos Objection.js. Mapeiam as tabelas do MySQL e definem relacionamentos. | Model | Tabela | |---|---| | `Usuario.model.js` | `usuarios` | | `RefreshToken.model.js` | `refresh_tokens` | | `SolicitacaoResetSenha.model.js` | `solicitacoes_reset_senha` | | `Conversation.model.js` | `conversations` | | `Message.model.js` | `messages` | | `Atendimento.model.js` | atendimentos sincronizados do ifbot | | `AtendimentoMensagem.model.js` | mensagens de um atendimento | | `AtendimentoCliente.model.js` | vínculo atendimento ↔ cliente/contrato IXC | | `AtendimentoAvaliacao.model.js` | resultado da avaliação por LLM de um atendimento | | `AtendimentoGoldenLabel.model.js` | rótulos de referência (golden set) para calibrar a avaliação | | `AtendimentoRagIndex.model.js` | controle de ingestão do RAG de atendimentos | | `WhatsappConversa.model.js` | conversas do chat de atendente via WhatsApp Cloud API | | `WhatsappMensagem.model.js` | mensagens de uma `WhatsappConversa` | | `Integracao.model.js` | credenciais/token do integrador IXC (Alfred), cacheadas no banco | ### Middleware (`src/middleware/`) | Arquivo | Função | |---|---| | `Validajwt.js` | Verifica e decodifica o JWT do header Authorization | | `RequireUser.js` | Exige usuário autenticado (qualquer nível) | | `RequireAdmin.js` | Exige nível "3" (administrador) | | `Validate.js` | Valida body/params/query com schema zod (`schema.parse()`) | | `ParseId.js` | Converte `req.params.id` para número e valida | | `RateLimit.js` | Rate limiting por IP | | `ErrorHandler.js` | Captura erros e formata a resposta de erro | ### Schemas de validação (`src/middleware/schemas/`) Schemas **zod** usados com o middleware `Validate` (que chama `schema.parse()`). Um arquivo por domínio que exige validação: | Arquivo | Domínio | |---|---| | `Auth.Schema.js` | Login, refresh, solicitação de reset de senha | | `Chat.Schema.js` | Corpo do chat | | `Ingest.Schema.js` | Corpo do ingest | | `Search.Schema.js` | Corpo da busca | | `Usuario.Schema.js` | Criar, atualizar e alterar senha de usuário | | `Atendimento.Schema.js` | Filtros e ações sobre atendimentos/avaliações | | `Conversas.Schema.js` | Corpo das rotas do chat de atendente (WhatsApp) | | `IxcIntegrador.Schema.js` | Corpo/query das buscas no integrador IXC (clientes, contratos, OS) | ## Padrão de arquivos por domínio Todo domínio segue a convenção de nomenclatura abaixo (passo a passo completo, com exemplo, em [conventions.md](conventions.md)): ``` src/ ├── routes/ NomeDoModulo.Rotas.js ├── controllers/ NomeDoModulo.Controller.js ├── services/ nomeDoModuloService.js ├── models/ NomeDoModulo.model.js (se houver tabela) └── middleware/ └── schemas/ NomeDoModulo.Schema.js (se houver validação) ``` ## Alias ESM (`#*`) O `package.json` define um alias de importação para evitar caminhos relativos longos: ```json "imports": { "#*": "./src/*" } ``` Como ficaria: ```js import { UsuarioController } from "#controllers/Usuario.Controller.js"; import { db } from "#config/db.config.js"; ``` > **Estado atual:** o alias está definido e funciona, mas **ainda não é usado** em nenhum arquivo do `src/` — todo o código usa imports relativos (`../config/db.config.js`). Foi adicionado na refatoração para espelhar o `@` do `minha_back`, mas a migração dos imports não foi feita. ## Domínios existentes Referência completa de endpoints, bodies e retornos em [api.md](api.md). | Domínio | Prefix da rota | Descrição | |---|---|---| | Auth | `/api/auth` | Login, logout, refresh de token | | Chat | `/api/chat` | Envio de mensagens ao LLM (pipeline em `src/chat/`, ver abaixo) | | Conversations | `/api/conversations` | CRUD de conversas do chat persistidas | | Documents | `/api/documents` | Listagem e exclusão de documentos | | Ingest | `/api/ingest` | Ingestão de conteúdo na base RAG de documentos | | Search | `/api/search` | Busca semântica nos documentos | | Usuario | `/api/users` | CRUD de usuários do sistema | | Atendimentos | `/api/atendimentos` | Sincronização, avaliação por LLM, golden set e ranking de atendentes sobre atendimentos de suporte | | Conversas | `/api/conversas` | Chat de atendente via WhatsApp (mensagens, sugestão de resposta) | | WhatsappConexao | `/api/whatsapp` | Status da conexão com a WhatsApp Cloud API | | IxcIntegrador | `/api/ixc` | Proxy para o integrador Alfred/IXC (clientes, contratos, OS); sub-roteia `/api/ixc/bi/*` para `IxcBi.Rotas.js` (BI do Alfred) | O webhook do WhatsApp (`/webhooks/whatsapp`) não é um domínio sob `/api` — é montado direto no `ServerFactory`, fora do middleware de auth, autenticado por `hub.verify_token` (GET, verificação) e assinatura HMAC opcional (`WHATSAPP_APP_SECRET`, POST, recebimento). ## Pipeline do chat (`src/chat/`) Uma mensagem recebida em `/api/chat` passa por três estágios, nessa ordem: 1. **Comandos explícitos** (`commands.js`) — antes de qualquer LLM, tenta casar a mensagem com um comando estruturado que consulta o ERP (Alfred/IXC) diretamente: `/cto ` (`ctoCommand.js`) e `/` (`loginCommand.js`, fallback genérico — por isso vem por último no array `COMMANDS`, depois de comandos mais específicos). Sem chamada de LLM, sem risco de alucinação em dado técnico. 2. **Roteamento por LLM** (`chatRouter.js`) — se nenhum comando bateu, `classificarModoChat()` classifica a intenção em um de três modos: `documentos` (RAG sobre a base de conhecimento), `atendimentos` (RAG sobre atendimentos de suporte já registrados) ou `geral`. Um regex de código de protocolo (`CODIGO_PROTOCOLO_RE`) bypassa o LLM quando a mensagem já traz um protocolo explícito. O resultado é cacheado (TTL 15 min) quando não há histórico de conversa. 3. **Chain de resposta** (`chatChain.js`) — monta o RAG do modo escolhido: rewrite de query (resolve follow-ups usando o histórico), HyDE opcional (gera uma passagem hipotética para melhorar o embedding de busca), busca no Qdrant, rerank via LLM e geração da resposta final — streaming SSE (`answerWithContextStream`) ou não-streaming (`answerWithContext`). Ao adicionar um novo comando explícito, siga o padrão `parse`/`resolve` de `ctoCommand.js`/`loginCommand.js` e registre-o no array `COMMANDS`. ## RAG de atendimentos vs. RAG de documentos Dois pipelines RAG paralelos e independentes, com collections Qdrant separadas: - **Documentos**: ingestão manual/upload via domínio `Ingest`, collection `QDRANT_COLLECTION`. - **Atendimentos**: ingestão automática via job (`ingestAtendimentosRag.js`), a partir de conversas sincronizadas do ifbot (`syncAtendimentos.js`), collection `ATENDIMENTOS_RAG_COLLECTION`. Enriquecido com dados da avaliação por IA (score/resumo/sugestões) via `atendimentoRagService.js`. A busca vetorial nesse modo é opcional, controlada por `ATENDIMENTOS_RAG_SEARCH_ENABLED`. Atendimentos passam por avaliação automática via LLM-como-juiz (`atendimentoAvaliacaoService.js`, job `avaliarAtendimentos.js`), sem golden set formal — `AtendimentoGoldenLabel` existe para começar a calibrar isso. ## Integração com o ERP (Alfred/IXC) `ixcIntegradorService.js` fala com um integrador externo (repositório separado, roda em `http://localhost:4001`) que expõe Cliente/Contrato/OS do IXC. Credenciais (`Url`/`Login`/`Senha`/`Token`) ficam na tabela `integracao` (model `Integracao`), não no `.env` — só o seed (`npm run seed:run`) lê do `.env` para provisionar a linha inicial; o token sobrevive a restart e só é invalidado rodando o seed de novo com credenciais diferentes. ## Jobs em background (`src/jobs/`) Agendados pelo `ServerFactory`, intervalo configurável por env var (`0` desativa; variáveis completas em [setup.md](setup.md)): | Job | Intervalo | Função | |---|---|---| | `cleanupTokens.js` | fixo, 6h (não configurável por env) | Remove refresh tokens expirados | | `syncAtendimentos.js` | `ATENDIMENTOS_SYNC_INTERVAL_MINUTES` | Importa atendimentos novos do ifbot | | `avaliarAtendimentos.js` | `ATENDIMENTOS_AVALIACAO_INTERVAL_MINUTES` | Avalia atendimentos pendentes via LLM | | `ingestAtendimentosRag.js` | `ATENDIMENTOS_RAG_INTERVAL_MINUTES` | Ingere atendimentos avaliados no Qdrant | ## Health check `GET /health` — retorna o status de todos os serviços: ```json { "ok": true, "checks": { "api": true, "db": true, "qdrant": true, "ollama": true } } ```