- apps/frontend: Next.js 15 (App Router) + Tailwind v4 + componentes estilo shadcn/ui sobre Radix UI + TanStack Query. Tema light/dark, logo processada. Menu completo (secao 52) com gating por permissao real. Todas as telas do checklist de aceite (secao 90) conectadas a endpoints reais (nao mockup): login, usuarios, perfis/permissoes, ramais/troncos, dialplan, filas/agentes, console do agente, campanhas (CPS/CSV/ iniciar/pausar), monitoramento ao vivo (polling, nao WebSocket real), TME/TMA, busca/export de chamadas, administracao do Asterisk, auditoria - infrastructure/nginx: reverse proxy colocando frontend+API na mesma origem (porta 80), antecipado da Fase 9 pois a API nao publica porta propria - apps/api: GET /api/monitoring/agents (estado corrente real via agent_state_events em aberto) e filtro queueId em GET /api/reports/calls Pendencia registrada: tela de Callbacks nao implementada (schema existe desde a Fase 6, mas nunca houve controller/service — construir a tela sem API real seria mockup). Verificacao visual em navegador nao foi possivel neste ambiente headless; validado via tsc/eslint/next build limpos + curl reproduzindo as chamadas do navegador (middleware de auth, 24 paginas protegidas via Nginx, endpoints de dados com cookie de sessao).
197 lines
9.9 KiB
Markdown
197 lines
9.9 KiB
Markdown
# B2BCall — Arquitetura
|
|
|
|
## 1. Contexto do servidor (levantado em 2026-08-27)
|
|
|
|
| Item | Valor |
|
|
|---|---|
|
|
| SO | Debian 13 (trixie) |
|
|
| Kernel | 6.12.94+deb13-amd64 |
|
|
| CPU | 2 vCPU |
|
|
| RAM | 1.9 GiB (+ 1.6 GiB swap) |
|
|
| Disco | 29 GiB (26 GiB livres) |
|
|
| IP | 10.10.32.142/24 (privado, sem IP público) |
|
|
| Hostname | Lab-FSCore-API |
|
|
|
|
**Alerta de capacidade:** 1.9 GiB de RAM é pouco para rodar simultaneamente Postgres +
|
|
Redis + Asterisk + API (NestJS) + worker + asterisk-events + scheduler + frontend
|
|
(Next.js) + Nginx. Decisões desta arquitetura foram tomadas para caber neste ambiente
|
|
de laboratório:
|
|
|
|
- Build do frontend fora do container em produção real (ou build multi-stage com
|
|
`node --max-old-space-size` limitado) — builds Next.js podem estourar RAM em VMs de
|
|
2 GiB.
|
|
- `docker-compose.yml` define `mem_limit`/`deploy.resources.limits` conservadores por
|
|
serviço.
|
|
- Antes de operação real de discagem em produção, **recomenda-se aumentar a VM para
|
|
pelo menos 4 vCPU / 8 GiB RAM**. Este ambiente serve para desenvolvimento e testes
|
|
funcionais (inclusive `DIALER_SIMULATION=true`), não para carga real de discador
|
|
preditivo com volume.
|
|
|
|
## 2. Estrutura do monorepo
|
|
|
|
```text
|
|
/opt/b2bcall
|
|
├── apps
|
|
│ ├── frontend # Next.js (App Router) + Tailwind + shadcn/ui
|
|
│ ├── api # NestJS (Fastify adapter) - REST + WebSocket gateway
|
|
│ ├── dialer-worker # PredictiveDialerEngine, CPS limiter, retry engine
|
|
│ ├── asterisk-events # Conector AMI dedicado -> normaliza eventos -> Postgres/Redis/WS
|
|
│ └── scheduler # Callbacks, horários de campanha, reconciliação, cron jobs
|
|
├── packages
|
|
│ ├── database # Prisma/Drizzle schema + migrations + client
|
|
│ ├── shared # utils comuns (logger, result types, error classes)
|
|
│ ├── types # tipos TS compartilhados (DTOs, enums de estado)
|
|
│ ├── ui # componentes de UI compartilhados (design system)
|
|
│ └── telephony # TelephonyProvider (interface) + AsteriskTelephonyProvider (AMI/ARI)
|
|
├── infrastructure
|
|
│ ├── asterisk # pjsip.conf, extensions, odbc, realtime, queues
|
|
│ ├── nginx # reverse proxy, websocket upgrade, TLS
|
|
│ ├── postgres # init scripts, schemas (application / asterisk)
|
|
│ └── docker # Dockerfiles por serviço
|
|
├── scripts # install.sh, update.sh, backup.sh, restore.sh, healthcheck.sh
|
|
├── docs
|
|
├── docker-compose.yml
|
|
├── .env.example
|
|
├── .gitignore
|
|
└── README.md
|
|
```
|
|
|
|
## 3. Decisões técnicas
|
|
|
|
### 3.1 Rede Docker / Asterisk
|
|
|
|
- Asterisk roda em `network_mode: host` — RTP usa uma faixa grande de portas UDP
|
|
(10000-20000 por padrão); mapear porta a porta em bridge network é inviável em
|
|
produção. **Impacto documentado:** o container do Asterisk enxerga a rede do host
|
|
diretamente (perde isolamento de rede do Docker); todas as portas do Asterisk
|
|
(SIP 5060/5061, RTP, AMI 5038, ARI 8088) ficam sujeitas apenas ao firewall do host
|
|
(nftables), não ao Docker networking. Por isso as regras de nftables (seção 8) são
|
|
a única linha de defesa e devem ser tratadas como obrigatórias, não opcionais.
|
|
- Todos os demais serviços (api, frontend, worker, asterisk-events, scheduler,
|
|
postgres, redis, nginx) ficam em uma rede Docker privada (`b2bcall-net`, bridge),
|
|
sem publicação de portas para o host exceto Nginx (80/443).
|
|
- Postgres e Redis não publicam portas no host; acessíveis somente dentro de
|
|
`b2bcall-net`.
|
|
|
|
### 3.2 Topologia SIP
|
|
|
|
```text
|
|
INTERNET → [OpenSIPS] → rede SIP privada → [Asterisk] → [B2BCall]
|
|
Asterisk → rede privada → Troncos SIP (operadoras)
|
|
```
|
|
|
|
O Asterisk nunca terá IP público. OpenSIPS e troncos são acessados via IP privado.
|
|
AMI/ARI nunca expostos além do host local (bind em 127.0.0.1 ou rede interna Docker
|
|
quando possível).
|
|
|
|
### 3.2.1 Asterisk (host network) ↔ Postgres/Redis (rede Docker interna)
|
|
|
|
Como o Asterisk usa `network_mode: host`, ele não enxerga a rede Docker
|
|
interna (`b2bcall-net`) por nome — DNS de containers só funciona para quem
|
|
está anexado à mesma rede definida pelo usuário. Solução adotada: o Postgres
|
|
publica a porta 5432 **somente em `127.0.0.1` do host**
|
|
(`127.0.0.1:5432:5432`, nunca `0.0.0.0`), e o Asterisk se conecta via
|
|
`ASTERISK_DB_HOST=127.0.0.1` (variável própria, distinta de
|
|
`POSTGRES_HOST=postgres` usada pelos demais serviços na rede interna). Isso
|
|
mantém o Postgres inacessível pela LAN/Internet, alcançável apenas por
|
|
processos no próprio host — incluindo um container em host network. Redis
|
|
não precisa disso: só a aplicação (bridge network) fala com Redis, nunca o
|
|
Asterisk diretamente.
|
|
|
|
### 3.3 Asterisk Realtime
|
|
|
|
- PJSIP objects (`ps_endpoints`, `ps_auths`, `ps_aors`, `ps_contacts`,
|
|
`ps_endpoint_id_ips`, `ps_registrations`) via `res_odbc` + `res_config_odbc`,
|
|
schema dedicado `asterisk` no mesmo Postgres.
|
|
- Domínio de aplicação (campanhas, leads, agentes, filas, etc.) vive no schema
|
|
`public`/`application`. Nunca misturar as duas responsabilidades na mesma tabela.
|
|
|
|
### 3.4 Camada de telefonia
|
|
|
|
- `packages/telephony`: interface `TelephonyProvider` (Originate, Hangup,
|
|
QueuePause, QueueAdd/Remove, ExtensionState, DeviceState, QueueStatus,
|
|
PJSIPShowEndpoints/Contacts, reload) + implementação `AsteriskTelephonyProvider`
|
|
(AMI para controle/eventos administrativos, ARI apenas quando controle fino de
|
|
canais/bridges for necessário). Nenhum comando AMI é chamado diretamente de
|
|
controllers da API.
|
|
|
|
### 3.5 Eventos em tempo real
|
|
|
|
- `apps/asterisk-events` mantém conexão persistente ao AMI, normaliza eventos,
|
|
persiste os relevantes e publica no `apps/api` (WebSocket gateway) via Redis
|
|
pub/sub. Frontend nunca faz polling do Asterisk.
|
|
|
|
### 3.6 Motor do discador preditivo
|
|
|
|
- `apps/dialer-worker`: `PredictiveDialerEngine` — decide continuamente quantas
|
|
chamadas originar, usando EWMA de answer rate, TMA, abandono, CPS disponível
|
|
(token bucket coordenado via Redis) e capacidade de agentes. Detalhado em
|
|
`docs/PREDICTIVE_DIALER.md` (a ser criado na fase correspondente).
|
|
- Reserva de leads via `SELECT ... FOR UPDATE SKIP LOCKED` no Postgres —
|
|
transição `READY → RESERVED → DIALING` atômica, com timeout de reservation.
|
|
- Lock distribuído por campanha via Redis (`dialer:campaign:{id}:lock`, TTL +
|
|
renewal + ownership token) para impedir dois workers controlando a mesma
|
|
campanha.
|
|
|
|
### 3.6.1 ORM / migrations (decisão técnica)
|
|
|
|
Escolhido **Prisma** (`packages/database`) em vez de Drizzle/TypeORM: migrations
|
|
versionadas e testáveis nativamente (`prisma migrate`), schema declarativo
|
|
único como fonte de verdade, e client tipado que reduz erro humano no domínio
|
|
RBAC/auditoria (muitas tabelas de relacionamento). Trade-off aceito: o engine
|
|
binário do Prisma adiciona overhead de build/memória, mitigado por rodar
|
|
`prisma generate` uma vez por build de imagem (não em runtime) e por ser um
|
|
ambiente com swap disponível. Consultas de alta performance do motor do
|
|
discador (ex.: `SELECT ... FOR UPDATE SKIP LOCKED`) usam `$queryRaw` do
|
|
Prisma em vez de tentar modelar lock otimista via ORM.
|
|
|
|
### 3.7 Banco de dados
|
|
|
|
- PostgreSQL 17.
|
|
- Migrations obrigatórias (nenhuma alteração manual de schema).
|
|
- Particionamento temporal de `calls`, `call_events`, `queue_events`, `audit_logs`
|
|
avaliado quando o volume justificar — não implementado prematuramente, mas
|
|
índices e chaves são desenhados para permitir particionamento futuro sem
|
|
migração destrutiva.
|
|
|
|
### 3.7.1 Frontend e reverse proxy (Fase 8)
|
|
|
|
- `apps/frontend`: Next.js 15 (App Router), React 19, Tailwind CSS v4,
|
|
componentes estilo shadcn/ui escritos à mão sobre Radix UI (sem depender
|
|
do CLI interativo do shadcn), TanStack Query para data-fetching/cache. SPA
|
|
client-rendered atrás de RBAC (permissões vêm de `/api/auth/me`, cada tela
|
|
usa `RequirePermission` além de já esconder itens de menu sem a permissão
|
|
— defesa em profundidade, a autorização real é sempre do backend).
|
|
- Build com `output: "standalone"` (Next.js) — a imagem de runtime
|
|
(`infrastructure/docker/frontend.Dockerfile`) não precisa de
|
|
pnpm/instalação nenhuma, só copia a árvore podada gerada pelo build.
|
|
Atenção: em monorepo pnpm os `node_modules` do standalone contêm symlinks
|
|
relativos para o pnpm store (`../../../node_modules/.pnpm/...`) — a
|
|
imagem copia a árvore `apps/frontend` inteira sem achatar diretórios,
|
|
senão os symlinks apontam para fora do container.
|
|
- **Nginx** (`infrastructure/nginx/nginx.conf`, imagem oficial `nginx:alpine`)
|
|
é o único serviço publicado para a LAN (porta 80, agente.md seção 87) —
|
|
faz proxy de `/api/*` para o container `api` e do restante para o
|
|
`frontend`, ambos na rede Docker interna sem portas publicadas
|
|
individualmente. Isso coloca frontend e API na mesma origem do ponto de
|
|
vista do navegador, eliminando CORS e problemas de cookie cross-origin
|
|
(o cliente da API em `apps/frontend/src/lib/api-client.ts` usa por padrão
|
|
a base relativa `/api`). HTTPS/TLS ainda não configurado — ver seção 87
|
|
do `agente.md` e pendências na Fase 9 do `TODO.md`.
|
|
- `next.config.ts` tem `eslint: { ignoreDuringBuilds: true }` — o lint roda
|
|
como etapa separada da pipeline de qualidade (`pnpm --filter
|
|
@b2bcall/frontend lint`), não durante o build de produção.
|
|
|
|
### 3.8 Segurança desde a arquitetura
|
|
|
|
- Segredos apenas em `.env` (nunca commitado). Credenciais de trunk SIP
|
|
criptografadas em repouso (AES-256-GCM, master key fora do banco).
|
|
- RBAC completo (users/roles/permissions/user_roles/role_permissions),
|
|
reforçado no backend independentemente do frontend.
|
|
- nftables com política default-deny para serviços administrativos/telefônicos;
|
|
SSH nunca bloqueado durante instalação.
|
|
|
|
## 4. Status
|
|
|
|
Ver `TODO.md` para o checklist vivo de implementação.
|