O problema
Uma factory financeira rodava a operação de empréstimos em planilhas e ferramentas improvisadas. O volume já era expressivo — centenas de tomadores, milhares de contratos, ciclos mensais de fatura, conciliação manual de mora e dunning — e cada passo manual era um lugar onde dinheiro escapava ou compliance quebrava.
O sistema precisava cobrir o ciclo completo: onboarding de cliente, fluxo de aprovação de pedido, geração automática de parcelas, acumulação de juros e mora diária, dunning via WhatsApp, fluxos de renegociação, e trilha de auditoria CRM-like por tomador.
Arquitetura
Monorepo com API em NestJS 11 e frontend em Next.js 15, compartilhando banco PostgreSQL via Prisma 6. Trabalho de background em BullMQ + Redis; mensagens WhatsApp via Evolution API.
┌─ Next.js 15 + React 19 ─────────────────────────┐
│ TanStack Query · Radix UI · Tailwind 4 │
│ JWT em cookies HTTP-only (withCredentials) │
└──────────────────┬──────────────────────────────┘
│ axios (tipado via Zod)
┌─ API NestJS 11 ──┴──────────────────────────────┐
│ Auth · Clientes · Pedidos · Faturas · Pagtos │
│ Renegociações · Comunicação · Dashboard │
└──────┬───────────────────────────────┬──────────┘
│ │
PostgreSQL BullMQ + Redis ─→ Evolution API
(Prisma 6) (fila whatsapp-messages)
├─ delay random 5–15s
├─ 3 retries, backoff exp.
└─ CommunicationLog
Jobs diários (timezone Brasil)
O scheduler roda três jobs em America/Sao_Paulo, em ordem:
- 05:00 — processamento de inadimplência: faturas com
dueDate < hojerecebem olateFee%configurado, status viraUNPAID, totais do pedido atualizam. - 05:01 — acumulação de mora:
(lateFeeMora% / 30) × valor_original × dias_em_atrasosomado diariamente. Renegociações sempre compõem sobre o valor do pedido original, não o renegociado — regra financeira não-óbvia que importa pra compliance. - 06:00 (somente dias úteis) — mensagens de cobrança: agrupa faturas em atraso e vencendo hoje por pedido, enfileira lembretes WhatsApp via Evolution API.
Cada um dos três tem entrypoint manual de recovery pra replays sem disparar efeitos colaterais acidentais.
Modelagem de domínio
A máquina de estados financeira mapeia pra um enum tipado do Prisma: OPEN → APPROVED → ACTIVE → PAID / RENEGOTIATED / UNAPPROVED / LOST. O dashboard lê agregados cacheados (cachedTotalValuePaid, cachedTotalValueToBreakeven, metricsLastUpdated) — views de portfólio ficam sub-100ms mesmo com milhares de contratos ativos.
Controle de acesso por departamento — LOAN_MANAGEMENT, STREET_SUPERVISOR, RENEGOTIATION_ANALYST, com SUPER_ADMIN override. Roles passam por AuthGuard + RolesGuard em cada controller.
Resultado
Em produção. A factory opera inteiramente dentro do sistema — sem planilhas — e as filas dão conta do ciclo diário sem intervenção humana. Os números acima são contagens vivas.