feat(bank): chequera register module (plan step 7)

Adds the office's own bank-register browser over the migrated SCOTHIA
data (22,354 bank_transactions), the last self-contained feature module.

API (apps/api/src/bank):
- GET /bank        register browser: search over concepto/reference/notes/
                   amountInWords; direction (income|expense|void), cleared and
                   date-range filters; 5 sorts; income/expense/net totals for
                   the whole filtered set, not just the page
- GET /bank/stats  headline income/expense/net + counts, date span, pending
- GET /bank/facets year list for the period filter
- GET /bank/summary  year and month rollups with a running net-movement figure

Web (/banco): "Movimientos" register + "Resumen por periodo" with year->month
drill-down; added to the AppShell nav as "Chequera".

Deliberately kept OUT of /estado-cuenta: this is the office's own money, not
customer balances, and the two are never summed or shown together.

No category/ramo dimension, and the deferred concept->ramo classifier is
dropped as won't-build: concepto is a payee name (0 of 22,354 match a
category) and TABLA RAMODOS is an expense chart of accounts + owner names,
not the insurance/servicios/fideicomiso split it was assumed to be, so a
classifier would invent data. Single currency (MXN); the "acumulado" is net
movement since the register opened (no opening balance in the source), not a
bank balance. Verified end-to-end in the browser; totals reconcile.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-23 11:32:05 -07:00
co-authored by Claude Opus 4.8
parent 12a1523073
commit db862df8fe
11 changed files with 1434 additions and 14 deletions
+48 -14
View File
@@ -153,9 +153,12 @@ the reconciliation pass (done, then corrected) are all closed. See §3 and §8.
3. **Old external-DB credential** — the old repo's `dbConnection.php` has a hardcoded
plaintext MySQL password committed to git history. Not carried into the new platform,
but rotate it regardless; it is already exposed.
4. **`bank_transactions.categoryId` is null on all 22354 rows**the concept→ramo
classifier was deferred. Needed before any "insurance vs utilities vs trust" split of
the office's own bank activity.
4. **`bank_transactions.categoryId` is null on all 22354 rows — RESOLVED as won't-build**
(plan step 7). The concept→ramo classifier was investigated and dropped: `concepto` is a
payee name (0 of 22354 match a category), and TABLA RAMODOS is a property-management
expense chart of accounts + owner names, not the insurance/servicios/fideicomiso split it
was assumed to be — so a classifier would invent data rather than produce a business-line
view. The `/banco` module intentionally has no category dimension. See §8 step 7(b).
5. **`TRASPASOS PAYPAL` is a clearing account, not a customer** — carries -7.03M MXN over
309 movements and therefore tops the adeudo worklist. Deliberately not special-cased in
code; needs a business decision on how to model it.
@@ -321,6 +324,40 @@ for what's actually next.
(d) The biggest debtor by far is **"TRASPASOS PAYPAL"** (-7.03M MXN over 309 movements) —
a house/clearing account, not a person. Left in rather than special-cased, but it will
head the adeudo worklist until someone decides how to model it.
- **Bank register module (plan step 7) — DONE**: `apps/api/src/bank/`
(`GET /bank` register browser with search over concepto/beneficiario, cheque
`reference`, `notes` and `amountInWords`; filters for direction
(income|expense|void), cleared status and a from/to date range, 5 sorts, and
**income/expense/net totals for the whole filtered set**; `/bank/stats`,
`/bank/facets` (year list), `/bank/summary` year/month rollup with a running
net figure) + web `/banco` (two tabs: "Movimientos" register and "Resumen por
periodo" with clickable year→month drill-down). Added to the AppShell nav as
"Chequera". Verified end-to-end in the browser: totals reconcile
(6948 income + 14615 expense + 791 void = 22354; net +899,375.77 matches
stats; 2013 months open at $0 and close at the year's net $794,295.78).
**Design decisions / data findings:**
(a) **Kept separate from `/estado-cuenta` on purpose** — this is the office's
own chequera, not customer money; the two ledgers are never summed or shown
together. Distinct nav entry, distinct page, distinct API module.
(b) **No category/ramo dimension, and the concept→ramo classifier was NOT
built** (closes open item §6.4): the data cannot support it. `DATOS E/I` have
no ramo column to migrate; `concepto` is a *payee* name (PAYPAL, CFE, ~1,900
individuals), and 0 of 22354 concepts match a `business_line_categories` name;
and the 66 TABLA RAMODOS rows are a property-management expense chart of
accounts (Payroll, Pool Labor, Gardening) + owner names, *not* the
insurance/servicios/fideicomiso split the migration comment implied. A
classifier would invent data, so `categoryId` stays null and the module does
not filter on it. Register is browsable by date/payee/amount/cheque instead.
(c) **Single currency (MXN).** `bank_transactions` has no currency column and
every `amountInWords` is spelled out in PESOS — so, unlike the customer
ledger, everything here is one currency and not split per-currency.
(d) **The "acumulado" is net movement since the register opened, not a bank
balance** — SCOTHIA carries no opening balance (its `ban` table holds only the
bank's name), so the running total starts at 0 in 2013. Labelled as such in
the UI so it is never read as a statement balance.
(e) Sign convention (from `transform_bank.py`): positive = ingreso,
negative = egreso, exactly zero = a cancelled/void cheque (787 of 791 say
CANCELADO/VOID) — voids are excluded from both the income and expense sides.
- Full pipeline reproducible in one command: `run_all.py --env <env>` runs customers →
properties → policies → transactions → prune → bank → blobs in order (all idempotent);
add `--stage` to re-extract from the Access files first. Verified end-to-end against dev.
@@ -331,21 +368,18 @@ for what's actually next.
`deploy/jorgecuadros-db.stack.yml` deploys prod from the same file as
`jorgecuadros-prod-db` on :3306. MinIO for documents deployed as `jorgecuadros-dev-minio`.
6. **Staff web UI****DONE** for all four modules (§8 step 4: clientes, polizas, servicios,
estado-cuenta). Spanish-first, session-cookie auth against the API, verified against real
migrated data.
6. **Staff web UI****DONE** for all five modules (§8 step 4 + step 7: clientes, polizas,
servicios, estado-cuenta, banco). Spanish-first, session-cookie auth against the API,
verified against real migrated data. **All app-layer feature work is complete.**
---
**NEXT — where to pick up:**
- **Plan step 7: bank register module.** SCOTHIA data is already migrated (22354
`bank_transactions` + 66 `business_line_categories`) and has **no customer FK**, so it is
self-contained and low-risk — API + `/banco` browser only. Blocked on nothing.
Ties into open item §6.4 (`categoryId` is null on every row).
- **Plan step 89: VPS + sync worker.** Blocked on VPS provisioning (§6.1) — the only real
external dependency left.
external dependency left. Pure ops: provider, size, Tailscale, MySQL replica.
- **Plan step 10: reports / email campaigns / admin.**
- **Uncommitted work:** the billing module and the ledger de-dup fix are written, built and
verified but **not committed**`git status` is dirty at the time of writing. The browser
visual pass on `/estado-cuenta` was also never completed (it needs an interactive login).
- **Small / open:** (a) `TRASPASOS PAYPAL` clearing account still tops the adeudo worklist
(§6.4d) — a business modelling call, not code. (b) Credential rotation on the old repo's
exposed MySQL password. (c) The `/estado-cuenta` browser visual pass — `/banco` was verified
in-browser this session; `/estado-cuenta` still worth a look.