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
| Layer | Technology | Version | Role |
|---|---|---|---|
| Backend | Fastify | 5.0 | High-performance HTTP server |
| TypeScript | 5.5 | Base language (strict mode) | |
| Zod | 3.23 | Input and schema validation | |
| better-sqlite3 | — | Synchronous SQLite database | |
| jsonwebtoken + bcryptjs | — | JWT Auth | |
| Frontend | React | 18.3 | UI library |
| TypeScript | 5.6 | Type safety | |
| React Router | 6.26 | SPA navigation | |
| Tailwind CSS | 3.4 | Utility-first styling | |
| Vite | 5.4 | Build tool + HMR | |
| Lucide React | — | SVG icons | |
| Database | SQLite3 | — | database-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?