AAMLabs (now Forge Labs) · Full-stack engineer, backend lead

FortCred Financial

Loan-contract management for a finance factory — 394+ clients, 1,100+ contracts, ~20K invoices, R$1M+ transacted in production.

Client
AAMLabs (now Forge Labs)
Role
Full-stack engineer, backend lead
Period
2024 – 2025

The problem

A finance factory was running its loan operation on spreadsheets and ad-hoc tools. The volume was already meaningful — hundreds of borrowers, thousands of contracts, monthly invoice cycles, manual mora and dunning reconciliation — and every manual step was a place where money slipped or compliance broke.

The system had to cover the full lifecycle: client onboarding, loan request approval workflow, automatic installment generation, late-fee + daily-mora accumulation, dunning via WhatsApp, renegotiation flows, and a CRM-grade audit trail per borrower.

Architecture

A monorepo with a NestJS 11 API and a Next.js 15 frontend, sharing a PostgreSQL database via Prisma 6. Background work runs on BullMQ + Redis; WhatsApp messages go out through the Evolution API.

   ┌─ Next.js 15 + React 19 ─────────────────────────┐
   │  TanStack Query · Radix UI · Tailwind 4         │
   │  JWT via HTTP-only cookies (withCredentials)    │
   └──────────────────┬──────────────────────────────┘
                      │ axios (typed via Zod)
   ┌─ NestJS 11 API ──┴──────────────────────────────┐
   │  Auth · Clients · Requests · Invoices · Payments│
   │  Renegotiations · Communications · Dashboard    │
   └──────┬───────────────────────────────┬──────────┘
          │                               │
     PostgreSQL                  BullMQ + Redis ─→ Evolution API
     (Prisma 6)                  (whatsapp-messages queue)
                                  ├─ random delay 5–15s
                                  ├─ 3 retries, exp. backoff
                                  └─ CommunicationLog records

Daily cron jobs (Brazil timezone)

The scheduler runs three jobs in America/Sao_Paulo, in order:

  • 05:00 — overdue processing: invoices past dueDate get the configured lateFee% applied, status flips to UNPAID, request totals are updated.
  • 05:01 — mora accumulation: (lateFeeMora% / 30) × original_value × days_overdue is added daily. Renegotiations always compound against the original request’s value, not the renegotiated one — a non-obvious financial rule that matters for compliance.
  • 06:00 (weekdays only) — charge messages: groups today’s-and-overdue invoices by request, enqueues per-borrower WhatsApp reminders via Evolution API.

Each of the three has a manual recovery entrypoint for replays without re-triggering accidental side effects.

Domain modeling

The financial state machine maps to a typed Prisma enum: OPEN → APPROVED → ACTIVE → PAID / RENEGOTIATED / UNAPPROVED / LOST. The dashboard reads cached aggregates (cachedTotalValuePaid, cachedTotalValueToBreakeven, metricsLastUpdated) so portfolio-level views stay sub-100ms even with thousands of active contracts.

Access control is department-scoped — LOAN_MANAGEMENT, STREET_SUPERVISOR, RENEGOTIATION_ANALYST, plus a SUPER_ADMIN override. Roles are wired through AuthGuard + RolesGuard decorators on every controller.

Outcome

In production. The factory operates entirely inside the system — no spreadsheets — and the queues handle the daily invoice cycle without human intervention. The numbers above are live counts.

Numbers

  • R$1M+

    Transacted

  • ~20K

    Invoices generated

  • 1,100+

    Contracts registered

  • 394+

    Clients onboarded

Stack

  • NestJS 11
  • Next.js 15
  • React 19
  • Prisma 6
  • PostgreSQL
  • BullMQ
  • Redis
  • Evolution API (WhatsApp)
  • JWT + HTTP-only cookies
  • TanStack Query
  • Radix UI
  • Tailwind 4