- 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).
9.9 KiB
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-sizelimitado) — builds Next.js podem estourar RAM em VMs de 2 GiB. docker-compose.ymldefinemem_limit/deploy.resources.limitsconservadores 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
/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
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) viares_odbc+res_config_odbc, schema dedicadoasteriskno 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: interfaceTelephonyProvider(Originate, Hangup, QueuePause, QueueAdd/Remove, ExtensionState, DeviceState, QueueStatus, PJSIPShowEndpoints/Contacts, reload) + implementaçãoAsteriskTelephonyProvider(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-eventsmantém conexão persistente ao AMI, normaliza eventos, persiste os relevantes e publica noapps/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 emdocs/PREDICTIVE_DIALER.md(a ser criado na fase correspondente).- Reserva de leads via
SELECT ... FOR UPDATE SKIP LOCKEDno Postgres — transiçãoREADY → RESERVED → DIALINGatô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_logsavaliado 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 usaRequirePermissionalé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 osnode_modulesdo standalone contêm symlinks relativos para o pnpm store (../../../node_modules/.pnpm/...) — a imagem copia a árvoreapps/frontendinteira sem achatar diretórios, senão os symlinks apontam para fora do container. - Nginx (
infrastructure/nginx/nginx.conf, imagem oficialnginx:alpine) é o único serviço publicado para a LAN (porta 80, agente.md seção 87) — faz proxy de/api/*para o containerapie do restante para ofrontend, 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 emapps/frontend/src/lib/api-client.tsusa por padrão a base relativa/api). HTTPS/TLS ainda não configurado — ver seção 87 doagente.mde pendências na Fase 9 doTODO.md. next.config.tstemeslint: { 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.