Skip to content
Carlo Miguel Dy
← Back to the specimen catalogue

Acc. XV · Vitis collectiva — lending and collection ledger, cultivated 2026

Kolekta — multi-tenant lending and collection ledger

Technical founder · Product engineer · 2026 · Independent studio

A multi-tenant lending and collection system for Philippine SMEs covering loan books, amortization schedules, and offline-first field collections.

Vitis collectiva — lending and collection ledger, cultivated 2026

Field notes

The context

Lending and collection for Philippine SMEs typically runs on spreadsheets — amortization computed by hand, collections logged after the fact, and no enforced separation between what a field collector can touch and what an operator can override. Kolekta builds the loan book, the schedule engine, and the field-collection workflow as one system with an append-only ledger underneath.

Cultivation

What I built

  • Built the multi-tenant loan-book dashboard with borrower and loan CRUD, and amortization across four payment frequencies (daily, weekly, semi-monthly, monthly) supporting both add-on and diminishing interest methods.
  • Built the centavo-denominated integer money engine and penalty accrual logic, with SQL and TypeScript implementations kept in enforced parity for both fixed and percentage penalties.
  • Built collection tracking with receipts against an append-only audit ledger, where corrections are reversal entries rather than mutations.
  • Built the offline-first field-collector PWA with an IndexedDB outbox and background sync for collectors working without signal.
  • Built the operator super-admin panel with payment verification and a tenant state machine (trial → pending_payment → paid → grace → suspended), driven by subscription lifecycle automation on Vercel Cron.
  • Wrote 140 unit and property-based tests (Vitest + fast-check) pinning the money and schedule engines, plus Playwright coverage of the payment flow.

Root system

How it works

A single Next.js 16 App Router application on a pnpm workspace, using React 19 islands with RSC for first paint and Server Actions for mutations rather than a separate API layer, aside from cron and contact routes. Supabase Postgres enforces multi-tenancy at the database layer via tenant_id on every table, with RLS gating every read and write. Payment and schedule rows are append-only and corrected by reversal entries, never mutated in place. Vercel Cron drives the tenant state machine's subscription transitions.

Harvest

Outcomes

  • Build is complete: 11 migrations, 140/140 tests green, typecheck/lint/build clean, and the payment-flow E2E suite passing.
  • Not yet deployed to production and not serving any users — this is a finished build, not a live product.
  • Known gap: reversing a payment does not yet reopen the schedule allocations it affected.
  • Reviewed with dedicated Opus and Sonnet passes focused on security and PRD compliance before being called done.

Herbarium

The work itself

Loan detail page inside tenant 'Demo Lending Co. STARTER' with sidebar Dashboard / Borrowers / Loans / Collections / Reports / Settings, heading 'Mga Loan', and a trial banner reading '14 araw na natitira sa trial mo' with GCash reference code KOL-DEM001; the loan is titled 'Ricardo Villaruel' with an 'Aktibo' badge and subtitle 'Monthly Diminishing 12 Months · Na-release May 17, 2026 ni Rosario Bacus'; stat cards show Principal ₱50,000.00, Outstanding ₱62,866.48, Overdue ₱11,124.53 in red, and Next due Jun 17, 2026 ₱5,691.62; a Terms card lists Interest type Diminishing, Rate 3.50%/buwan, Dalas / Termino Buwanan × 12, First due Jun 17, 2026, Kabuuang interest ₱12,090.35, Kabuuang babayaran ₱62,090.35, Service fee ₱1,000.00 and Collector Emilio Nacino; below it a twelve-row Schedule table with columns #, Due date, Principal, Interest, 'Penalty (as of today)', Nabayaran, Natitira and Status, where rows 1 and 2 carry penalties ₱517.42 and ₱258.71 with red 'Overdue' badges and rows 3–12 are 'Paparating'.
Fig. 1A loan's twelve-period schedule is derived from its terms, with interest falling against the diminishing balance and penalty accruing per missed row as of today.
Collections page subtitled 'Due ngayon at overdue — lahat ng dapat singilin.' with 'Lahat ng area' and 'Lahat ng collector' dropdown filters plus 'Sync conflicts' and 'Mga area' links; an 'Overdue 10' heading precedes ten rows, each showing a borrower name, a barangay · collector · due-date line, a days-late badge and an arrears amount in red — Maria Fe Santos (Poblacion, due 2026-05-19) 81D LATE ₱136,082.86; Juan Carlos Dela Cruz (Tibanga) 81D LATE ₱141,999.29; Aminah Macapodi (Pala-o) 58D LATE ₱70,549.83; Rosario Bacus-Villanueva (Tomas Cabili) 58D LATE ₱80,749.83; Abdulraof Disomimba (Buru-un) 55D LATE ₱22,933.28; Josefina Malinao (Hinaplanon) 55D LATE ₱32,600.00; Ricardo Villaruel (Suarez) 52D LATE ₱11,124.53; Bai Sittie Alonto (Mahayahay) 52D LATE ₱6,674.73; Norhata Guro (Del Carmen) 42D LATE ₱14,787.50; and Emilio Nacino, Jr. (Dalipuga, due 2026-07-17) 22D LATE ₱4,346.33 — all assigned to collector Emilio Nacino.
Fig. 2Every account past due rolls into one worklist ranked by days late, filterable by area and by the collector it is assigned to.
Mobile payment-capture screen in the collector app with a 'Bumalik' back link, borrower 'Ricardo Villaruel' and 'Balanse: ₱62,866.48'; a large 'Halaga' amount field reading ₱5691.62 above two quick-fill chips 'Eksaktong due (₱5,691.62)' and 'Kabuuang overdue (₱11,124.53)'; a 'Klase' toggle with 'Regular' selected beside 'Advance'; an empty 'Note (opsyonal)' box; a 'PREVIEW NG ALLOCATION' card breaking Row #1 into Penalty ₱517.42 in red, Interest ₱1,750.00 and Principal ₱3,424.20; a full-width 'I-save ang bayad' button; and a bottom app nav of Today / Borrowers / a '+' Record button / More.
Fig. 3The collector sees where a payment will land before saving it — the penalty, then interest, then principal waterfall, resolved per schedule row.
Loan-product editor headed 'I-edit ang Daily Add-On 100 Days' warning that changes do not affect existing loans because their terms are snapshotted at creation; a Pangalan field reading 'Daily Add-On 100 Days'; an Interest section with 'Add-on (Flat)' selected against 'Diminishing', each option carrying a plain-Taglish explanation of how interest is computed, Rate 20 % per month noted as the standard Philippine quoting convention, Dalas ng koleksyon 'Araw-araw (Lunes–Sabado)' and Bilang ng termino 100 days; a Penalty section set to 'Fixed na piso bawat naligtaang term' at ₱50; a 'Service fee at limitasyon ng principal' section with Service fee 5 %, Min principal ₱5000 and Max principal ₱50000; an 'Aktibo' toggle switched on; and a 'Preview ng schedule (sample: ₱10,000)' card showing Kabuuang interest ₱6,666.66, Kabuuang babayaran ₱16,666.66, six daily rows from Aug 10 to Aug 15, 2026 at ₱100.00 principal and ₱66.66 interest each, and a footnote '+ 94 pang termino (100 total sa buong schedule)'.
Fig. 4A product carries its own interest method, collection frequency, penalty rule and principal limits, and the schedule engine previews a sample loan the moment any of them change.
Reports page subtitled 'Overdue aging at buod ng koleksyon' with three export buttons — 'I-export ang overdue (CSV)', 'I-export ang loans (CSV)' and 'I-export ang payments (CSV)'; a 'Buod ng koleksyon' card with a Simula 07/10/2026 to Hanggang 08/08/2026 date range and an 'I-apply' button, reporting 'Total na nakolekta ₱0.00' and 'Bilang ng bayad 0' for that window; and an 'Overdue aging' table bucketing ten borrowers across columns Current, 1-7 araw, 8-30 araw, 31-90 araw, 90+ araw, Total and SOA — Ricardo Villaruel ₱62,090.35 down to Aminah Macapodi ₱13,333.33, each row ending in a 'Print SOA' link, with a footer row totalling ₱199,196.59 current, ₱13,154.02, ₱53,646.76, ₱65,602.80, ₱0.00 and ₱331,600.17 overall.
Fig. 5Arrears bucket into aging bands per borrower off the same ledger that feeds the CSV exports and the printable statement of account.
Collector home screen dated 'SATURDAY, AUGUST 8' headed 'Simulan ang araw ko' with an 'I-save offline' download button; a tab pair 'Due ngayon 0' and 'Overdue 10' with Overdue active; then ten borrower rows, each with a red dot, name, barangay and days late, and the amount owed in red — Maria Fe Santos (Poblacion, 81d late) ₱136,082.86; Juan Carlos Dela C… (Tibanga, 81d late) ₱141,999.29; Aminah Macapodi (Pala-o, 58d late) ₱70,549.83; Rosario Bacus-Villan… (Tomas Cabili, 58d late) ₱80,749.83; Abdulraof Disomimba (Buru-un, 55d late) ₱22,933.28; Josefina Malinao (Hinaplanon, 55d late) ₱32,600.00; Ricardo Villaruel (Suarez, 52d late) ₱11,124.53; Bai Sittie Alonto (Mahayahay, 52d late) ₱6,674.73; Norhata Guro (Del Carmen, 42d late) ₱14,787.50; and Emilio Nacino, Jr. (Dalipuga, 22d late) ₱4,346.33 — over a bottom nav of Today / Borrowers / a '+' Record button / More.
Fig. 6The collector's round starts as a ranked worklist that can be saved to the device first, so the day keeps working where there is no signal.
Pressed leaf — what it taught
Enforcing SQL/TypeScript parity on the penalty engine as a tested contract, rather than trusting the two implementations to stay in sync by convention, is what made 140/140 mean something. Append-only ledgers push complexity into reversal logic up front — the unhandled reversal-reopens-allocations case is exactly where that trade surfaces.