Phase 03 — Product Delivery
Awaiting HITLs #7–#10 Architect + Backend + Frontend + QA ← Ecosystem 🇧🇷 PT

Phase 03 — Product Delivery

Architecture, backend implementation (API + persistence), frontend (UI + integration) and quality gate.

68
Files created
35
API Endpoints
12
SQLite Tables
12
React Pages

Architecture

Technology Stack

LayerTechnologyVersionRole
BackendFastify5.0High-performance HTTP server
TypeScript5.5Base language (strict mode)
Zod3.23Input and schema validation
better-sqlite3Synchronous SQLite database
jsonwebtoken + bcryptjsJWT Auth
FrontendReact18.3UI library
TypeScript5.6Type safety
React Router6.26SPA navigation
Tailwind CSS3.4Utility-first styling
Vite5.4Build tool + HMR
Lucide ReactSVG icons
DatabaseSQLite3database-emps.sqlite (WAL mode)

Bounded Contexts (Domain-Driven Design)

6 Bounded Contexts in a modular Fastify monolith. Logical separation by modules with shared PF/PJ JWT.

classDiagram class IdentityPJ { +AuthPJ (switch PF-PJ) +RBAC Middleware +JWT shared with PF POST /auth/pj/switch GET /auth/pj/me } class CompanyMgmt { +Company (aggregate root) +TeamMember +CNPJ, LegalType POST /companies GET /companies/me PATCH /companies/me } class BankingPJ { +PJAccount (aggregate root) +PJTransaction +Money, Category GET /pj/accounts/balance POST /pj/accounts/transfer-pf } class PixPJ { +PJPixKey (aggregate root) +PixTransfer +PixKeyType, RateLimit POST /pj/pix/transfer CRUD /pj/pix/keys } class Billing { +Invoice (aggregate root) +Barcode, PixQRCode +InvoiceStatus POST /pj/invoices GET /pj/invoices } class CorporateCards { +CorporateCard (aggregate root) +CardPurchase, CardInvoice +CardLimit CRUD /pj/corporate-cards GET /pj/corporate-cards/:id/invoice } IdentityPJ --> CompanyMgmt : PJ context IdentityPJ --> BankingPJ : User authenticated CompanyMgmt --> BankingPJ : Company owns Account PixPJ --> BankingPJ : debit/credit Billing --> BankingPJ : credit on payment CorporateCards --> BankingPJ : debit on purchase

Sequence Diagrams

Switch PF → PJ — Shared Authentication

sequenceDiagram participant U as User (Browser) participant F as Frontend (React) participant A as Emps API (Fastify :3334) participant DB as SQLite (database-emps) Note over U,DB: User already logged in to Bank PF U->>F: Clicks "Switch to PJ" F->>A: POST /api/auth/pj/switch Note over A: Header: Authorization Bearer (PF JWT) A->>A: Validates JWT (shared secret with PF) A->>DB: SELECT company WHERE owner_user_id = ? DB-->>A: company { id, cnpj, razao_social } A->>DB: SELECT team_member WHERE user_id = ? AND company_id = ? DB-->>A: { role: "admin" } A->>A: Generates PJ context (companyId + role) A-->>F: { company, role, pjAccount } F->>F: Activates PJ mode in ProfileSwitcher F-->>U: PJ Dashboard loaded

PJ Pix — Business Transfer

sequenceDiagram participant U as User (Browser) participant F as Frontend (React) participant A as Emps API (Fastify) participant DB as SQLite U->>F: Enters destination Pix key F->>A: GET /api/pj/pix/lookup?key=... A->>DB: SELECT pix_key WHERE key_value = ? A-->>F: { name, keyType } F-->>U: Displays recipient U->>F: Enters amount (e.g. R$ 2,000) F->>A: POST /api/pj/pix/transfer Note over A: Middleware: JWT + requireRole(financial) A->>A: Check rate limit (20/hour) A->>DB: SELECT COUNT pj_pix_rate_limit (last hour) DB-->>A: count = 5 (OK) A->>A: Check PJ balance A->>DB: SELECT balance FROM pj_accounts DB-->>A: balance = 850000 (R$ 8,500) Note over A,DB: Atomic transaction (SQLite) A->>DB: BEGIN TRANSACTION A->>DB: UPDATE pj_accounts SET balance -= 200000 A->>DB: INSERT pj_transactions (debit, category: pix_out) A->>DB: INSERT pj_pix_rate_limit A->>DB: INSERT pj_audit_logs (action: pix_transfer) A->>DB: COMMIT A-->>F: { transactionId, status: "completed", balanceAfter } F-->>U: PJ transfer receipt

Billing Invoice Issuance

sequenceDiagram participant U as User (Browser) participant F as Frontend (React) participant A as Emps API (Fastify) participant DB as SQLite U->>F: Fills in charge details Note over F: customer_name, amount, due_date, type F->>A: POST /api/pj/invoices Note over A: Middleware: JWT + requireRole(financial) A->>A: Validate data (Zod schema) A->>A: Generate FEBRABAN barcode (mock) A->>A: Generate Pix QR Code (copy-and-paste) A->>DB: INSERT invoices (status: pending) DB-->>A: { invoiceId } A->>DB: INSERT pj_notifications (new charge created) A-->>F: { id, barcode, pixQrcode, status: "pending" } F-->>U: Invoice preview + Pix QR Code

PJ Dashboard — Initial Load

sequenceDiagram participant U as User (Browser) participant F as Frontend (React) participant A as Emps API (Fastify) participant DB as SQLite U->>F: Accesses /pj/dashboard F->>F: Verifies JWT + PJ context par Parallel calls F->>A: GET /api/pj/dashboard A->>DB: SELECT balance FROM pj_accounts A->>DB: SELECT SUM(amount) GROUP BY direction (cash flow) A->>DB: SELECT COUNT invoices BY status (charges) A-->>F: { balance, cashFlow, invoiceSummary } and F->>A: GET /api/pj/transactions?limit=5 A->>DB: SELECT * FROM pj_transactions ORDER BY created_at DESC A-->>F: { transactions: [...] } and F->>A: GET /api/pj/notifications/unread-count A->>DB: SELECT COUNT(*) WHERE is_read = 0 A-->>F: { count: 7 } end F->>F: Renders full PJ dashboard F-->>U: Balance + Cash Flow + Charges + Transactions + Notifications

Architectural Decision Records (ADRs)

ADR-001Accepted
SQLite as a separate PJ database
Decision: Use database-emps.sqlite separate from database.sqlite (PF), on the same server, with WAL mode enabled.
Positive: PF/PJ data isolation, independent backup, zero additional infra, consistent with ECP ecosystem.
Negative: SQLite single-writer may cause contention at scale. Acceptable for MVP (500 companies).
ADR-002Accepted
Shared JWT between PF and PJ
Decision: Share JWT_SECRET between ecp-digital-bank and ecp-digital-bank-emps. The auth-pj module validates PF tokens and adds PJ context (companyId, role).
Positive: Native SSO, zero friction in PF↔PJ toggle, no new login.
Negative: Compromise of one key compromises both. Acceptable for MVP; evolve to auth service in production.
ADR-003Accepted
RBAC with 3 hierarchical profiles
Decision: Admin > Financial > Viewer. requireRole() middleware validates hierarchy by route. Every action records the operator’s userId.
Positive: Sufficient granularity for MVP, complete audit trail, simple to understand.
Negative: No customizable permissions per resource. Sufficient for 3 profiles; expand if needed.
ADR-004Accepted
Invoice with mock FEBRABAN barcode
Decision: Generate barcode and typed line with valid structure but without real registration at CIP (registrar). Mock for MVP.
Positive: Enables complete development of the billing flow without external dependency.
Negative: Invoices are not payable at other banks. Real integration required for production.
ADR-005Accepted
Monetary values in cents (integer)
Decision: All financial values stored as INTEGER in cents. Frontend converts for display. money.ts utilities for conversion.
Positive: Zero rounding errors, absolute precision, fintech industry standard.
Negative: Requires conversion in every display. Minimal cost vs. enormous benefit.

Data Model (12 tables + 22 indexes)

companies
Company registration (MEI, EI, EIRELI, LTDA, SLU)
id, owner_user_id, cnpj, razao_social, nome_fantasia, natureza_juridica, endereco, status, created_at, updated_at, deleted_at
pj_accounts
PJ accounts with balance in cents
id, company_id, agency(0001), number, balance, daily_transfer_limit, status
team_members
Multi-user with RBAC
id, company_id, user_id, role(admin|financial|viewer), status(active|invited|removed)
pj_transactions
PJ transactions with categorization
id, account_id, operator_id, type, category, amount, balance_after, direction, reference_id(idempotency)
pj_pix_keys
PJ Pix keys (max 20)
id, company_id, account_id, type(cnpj|email|phone|random), value, status
invoices
Issued invoices (charges)
id, company_id, customer_name, amount, due_date, barcode, pix_qrcode, status(pending|paid|overdue|cancelled), type(single|installment|recurring)
corporate_cards
Virtual corporate cards
id, company_id, holder_id, last4, limit_cents, used_cents, due_day, status
corporate_card_purchases
Corporate card purchases
id, card_id, merchant_name, merchant_category, amount, status
corporate_invoices
Card invoices
id, card_id, reference_month, total_cents, due_date, status(open|closed|paid|overdue)
pj_notifications
Company notifications
id, company_id, user_id, title, body, type, is_read
pj_pix_rate_limit
Pix rate limiting (20/hour)
id, account_id, window_start, transfer_count
pj_audit_logs
Complete audit trail
id, company_id, user_id, action, resource, resource_id, metadata, ip_address

Entity-Relationship Diagram

12 tables, 22 indexes. SQLite3 with WAL mode and foreign keys enabled. Monetary values in cents (INTEGER).

erDiagram companies { TEXT id PK TEXT owner_user_id FK TEXT cnpj UK TEXT razao_social TEXT nome_fantasia TEXT natureza_juridica TEXT endereco TEXT status TEXT created_at } pj_accounts { TEXT id PK TEXT company_id FK "UK" TEXT agency TEXT number UK INTEGER balance INTEGER daily_transfer_limit TEXT status } team_members { TEXT id PK TEXT company_id FK TEXT user_id FK TEXT role "admin|financial|viewer" TEXT status "active|invited|removed" } pj_transactions { TEXT id PK TEXT account_id FK TEXT operator_id FK TEXT type TEXT category INTEGER amount INTEGER balance_after TEXT direction "in|out" TEXT reference_id UK } pj_pix_keys { TEXT id PK TEXT company_id FK TEXT account_id FK TEXT type "cnpj|email|phone|random" TEXT value UK TEXT status } invoices { TEXT id PK TEXT company_id FK TEXT customer_name INTEGER amount TEXT due_date TEXT barcode TEXT pix_qrcode TEXT status "pending|paid|overdue|cancelled" TEXT type "single|installment|recurring" } corporate_cards { TEXT id PK TEXT company_id FK TEXT holder_id FK TEXT last4 INTEGER limit_cents INTEGER used_cents INTEGER due_day TEXT status } corporate_card_purchases { TEXT id PK TEXT card_id FK TEXT merchant_name TEXT merchant_category INTEGER amount TEXT status } corporate_invoices { TEXT id PK TEXT card_id FK TEXT reference_month INTEGER total_cents TEXT due_date TEXT status "open|closed|paid|overdue" } pj_notifications { TEXT id PK TEXT company_id FK TEXT user_id FK TEXT title TEXT body TEXT type INTEGER is_read } pj_audit_logs { TEXT id PK TEXT company_id FK TEXT user_id FK TEXT action TEXT resource TEXT resource_id TEXT metadata TEXT ip_address } companies ||--|| pj_accounts : "has" companies ||--o{ team_members : "has" companies ||--o{ pj_pix_keys : "registers" companies ||--o{ invoices : "issues" companies ||--o{ corporate_cards : "owns" companies ||--o{ pj_notifications : "receives" companies ||--o{ pj_audit_logs : "logs" pj_accounts ||--o{ pj_transactions : "records" corporate_cards ||--o{ corporate_card_purchases : "has" corporate_cards ||--o{ corporate_invoices : "generates"

API Modules (35 endpoints)

auth-pj
Auth proxy + PF→PJ switch + RBAC
2 endpoints: POST /auth/pj/switch, GET /auth/pj/me
auth-pj.routes.ts · auth-pj.service.ts · auth-pj.schema.ts
companies
Company registration and management
3 endpoints: POST, GET /me, PATCH /me
companies.routes.ts · companies.service.ts · companies.schema.ts
pj-accounts
PJ account + balance + PF↔PJ transfer
3 endpoints: GET /me, GET /balance, POST /transfer-pf
pj-accounts.routes.ts · pj-accounts.service.ts · pj-accounts.schema.ts
pj-pix
Full business Pix
6 endpoints: transfer, keys (CRUD), qrcode, lookup
pj-pix.routes.ts · pj-pix.service.ts · pj-pix.schema.ts
invoices
Billing invoices
6 endpoints: create, list, detail, cancel, resend, summary
invoices.routes.ts · invoices.service.ts · invoices.schema.ts
pj-transactions
PJ statement + categorization
3 endpoints: list (cursor), detail, summary
pj-transactions.routes.ts · pj-transactions.service.ts · pj-transactions.schema.ts
corporate-cards
Corporate cards + invoices
7 endpoints: CRUD, limit, block, invoice, purchases
corporate-cards.routes.ts · corporate-cards.service.ts · corporate-cards.schema.ts
team
Multi-user + invites + RBAC
4 endpoints: invite, list, change role, remove
team.routes.ts · team.service.ts · team.schema.ts
pj-notifications
Company notifications
4 endpoints: list, unread-count, mark-read, read-all
pj-notifications.routes.ts · pj-notifications.service.ts · pj-notifications.schema.ts
pj-dashboard
Aggregated dashboard data
1 endpoint: GET /pj/dashboard
pj-dashboard.routes.ts · pj-dashboard.service.ts

React Pages (12 screens)

/pj/dashboard
PJ Dashboard
Balance, cash flow, charges, quick actions
/pj/extrato
PJ Statement
Transactions with categories + filters
/pj/pix/enviar
Pix Send
4-step wizard + limits
/pj/pix/receber
Pix Receive
QR Code + copy and paste
/pj/pix/chaves
Pix Keys
Keys CRUD (max 20)
/pj/cobrancas/nova
New Charge
2-step form + invoice preview
/pj/cobrancas
Charges List
Table + badges + filters
/pj/cobrancas/:id
Charge Detail
Timeline + barcode + actions
/pj/cartoes
Corporate Cards
Visual card + limit bar
/pj/cartoes/:id/fatura
Card Invoice
Purchases + total + due date
/pj/time
Team Management
Members + invite + roles
/pj/empresa
Company Data
Registration + edit (admin)

Folder Structure

ecp-digital-emps/ ├── server/ │ ├── src/ │ │ ├── app.ts ← Fastify + plugins + routes │ │ ├── server.ts ← Entry point (port 3334) │ │ ├── database/ │ │ │ ├── connection.ts ← SQLite3 + WAL mode │ │ │ ├── migrations/ ← 001-initial.sql (12 tables) │ │ │ └── seed.ts ← Demo data AB Design Studio │ │ ├── modules/ ← 10 modules (30 files) │ │ │ ├── auth-pj/ companies/ pj-accounts/ │ │ │ ├── pj-pix/ invoices/ pj-transactions/ │ │ │ ├── corporate-cards/ team/ │ │ │ ├── pj-notifications/ pj-dashboard/ │ │ ├── shared/ │ │ │ ├── errors/ middleware/ utils/ │ │ └── types/ │ └── package.json ├── web/ │ ├── src/ │ │ ├── App.tsx ← Layout + Router │ │ ├── routes/ ← 12 PJ pages │ │ ├── components/ │ │ │ ├── ui/ ← Button, Card, Input, Modal, Badge │ │ │ └── layout/ ← SidebarPJ, HeaderPJ, ProfileSwitcher │ │ ├── hooks/ services/ lib/ styles/ │ └── package.json ├── package.json ← Root (npm run dev) └── .env.example
HITLs #7 to #10 — Phase 03 Approval Decision
#7: Solid architecture? Contracts ready? • #8: Correct APIs? Persistence validated? • #9: Front integrated? Design faithful to design_spec? • #10: Quality approved? Ready to operate?