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)
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 |
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 para o fluxo completo e os níveis de acesso.
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.
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).
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 |
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 |
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) |
Todo domínio segue a convenção de nomenclatura abaixo (passo a passo completo, com exemplo, em 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)
#*)O package.json define um alias de importação para evitar caminhos relativos longos:
"imports": {
"#*": "./src/*"
}
Como ficaria:
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@dominha_back, mas a migração dos imports não foi feita.
Referência completa de endpoints, bodies e retornos em 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).
src/chat/)Uma mensagem recebida em /api/chat passa por três estágios, nessa ordem:
commands.js) — antes de qualquer LLM, tenta casar a mensagem com um comando estruturado que consulta o ERP (Alfred/IXC) diretamente: /cto <busca> (ctoCommand.js) e /<pppoe> (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.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.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.
Dois pipelines RAG paralelos e independentes, com collections Qdrant separadas:
Ingest, collection QDRANT_COLLECTION.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.
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.
src/jobs/)Agendados pelo ServerFactory, intervalo configurável por env var (0 desativa; variáveis completas em 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 |
GET /health — retorna o status de todos os serviços:
{
"ok": true,
"checks": {
"api": true,
"db": true,
"qdrant": true,
"ollama": true
}
}