Commit Graph

120 Commits

Author SHA1 Message Date
Carlos Escalante
7ba70ae524 Asistente: fill viewport via flex chain instead of vh math
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-04 10:47:26 -06:00
Carlos Escalante
d9039c76b9 Replace hand-rolled shell with shadcn sidebar + breadcrumb header
AppSidebar (icon-collapsible, sync warning badge, brand header),
NavUser footer (avatar + privacy/theme/logout dropdown), and a thin
header with SidebarTrigger, breadcrumbs, and quick toggles. Mobile
drawer and cmd+B come from the sidebar primitives.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-04 10:46:34 -06:00
Carlos Escalante
d117fb79ea Add nav config module, budget-cycle helper, me/recent API helpers
navigation.ts becomes the single source of truth for sidebar and
breadcrumbs (Inicio entry added; '/' is exact-match).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-04 10:43:26 -06:00
Carlos Escalante
d2610b2eef Add shadcn sidebar/breadcrumb/collapsible/avatar/tooltip (base-nova)
Registry-installed Base UI flavor; existing ui components untouched.
Also drops the meaningless empty registries key from components.json.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-04 10:42:20 -06:00
Carlos Escalante
3d71234a61 Budget: unify transactions section into one card
Toolbar (source tabs, search, CSV, add) and a summary strip (billing
cycle range, count, per-currency total) now live inside the table card
instead of five loose control rows. Cycle range 18-to-18 is finally
visible. Stray English strings translated; back-button spacing fixed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-04 09:38:36 -06:00
Carlos Escalante
72a5840752 Proyecciones: clicking a month row opens that month in Budget
The clicked year/month now travel in the navigation state; Budget
seeds useBudget with them instead of always defaulting to today.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 20:40:09 -06:00
Carlos Escalante
f832138b41 Pensions: compute current age from birthdate instead of hardcoded 30
FCL projection target defaults to the next 5-year withdrawal window
(CURRENT_AGE + 5) rather than a fixed 35.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 20:38:44 -06:00
Carlos Escalante
5296230416 Show net total of listed transactions above the table
Compras minus devoluciones, one figure per currency, excluding Tasa
Cero anchors (their cuotas are what count). Tooltip explains the math.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 20:17:55 -06:00
Carlos Escalante
f2bcce702a CI: pin dependency resolution with constraints.txt
All checks were successful
Deploy to VPS / test (push) Successful in 1m32s
Deploy to VPS / deploy (push) Successful in 2m56s
Fresh-venv installs started failing with pip ResolutionTooDeep because
requirements.txt is unpinned. Constrain CI and image builds to the
frozen, test-passing local set; requirements.txt stays the source of
intent.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 19:52:39 -06:00
Carlos Escalante
d3b5188b67 Tasa Cero: badges, convert dialog, Financiamientos page
Some checks failed
Deploy to VPS / test (push) Failing after 56m9s
Deploy to VPS / deploy (push) Has been skipped
Violet 'Tasa Cero'/'Cuota' badges (anchor amount struck through like
deferred), convert/edit action on CC purchases with a live cuota
preview, and a Financiamientos page mirroring the bank's tab (progress,
monto cuota, saldo inicial/faltante, totals).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 16:13:18 -06:00
Carlos Escalante
c563618957 Tasa Cero: regenerate API types + installment-plan client helpers
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 16:13:18 -06:00
Carlos Escalante
3cd2927b5f Tasa Cero: backend tests (rounding, scheduling, exclusion, lifecycle)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 16:13:00 -06:00
Carlos Escalante
3b147bf8be Tasa Cero: auto-detect 'CC ' vouchers + /installment-plans API
Ingestion auto-converts CC-prefixed credit-card purchases (default 3
cuotas, push note appended). CRUD endpoints cover manual convert (e.g.
ALMACENES SIMAN), edit-with-regeneration, and unconvert; anchor deletion
tears down the plan; cuotas cannot be deferred.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 16:13:00 -06:00
Carlos Escalante
8b4961d257 Tasa Cero: exclude anchors from budget/analytics aggregates
The anchor stays visible in listings but only its cuotas count toward
actuals — future cuotas intentionally show up in future months as
committed spend.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 16:13:00 -06:00
Carlos Escalante
088062f757 Tasa Cero: InstallmentPlan model, cuota service, migration
BAC zero-interest financing: an anchor purchase splits into N monthly
cuota transactions (truncate-to-0.10 rule, last cuota absorbs rounding;
one cuota per 18th-cut billing cycle on the 19th). Pinned against real
Financiamientos data.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 16:13:00 -06:00
Carlos Escalante
248417dbab PROGRESS: deferred items and wishlist complete — nothing remains
All checks were successful
Deploy to VPS / test (push) Successful in 1m43s
Deploy to VPS / deploy (push) Successful in 2m20s
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 18:07:31 -06:00
Carlos Escalante
fc3461c377 PWA installability + working service worker (wishlist #8)
manifest.webmanifest with the existing icons, apple-touch-icon, and SW
registration at startup. The old sw.js was a self-unregistering cleanup
stub — meaning serviceWorker.ready never resolved and web push has been
silently dead; the new worker handles push display and notification
clicks, and makes the app installable.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 18:07:02 -06:00
Carlos Escalante
c3c14f54c5 What-if savings planner page (wishlist #7)
New Planificador page: the current savings pace (prefilled from the
live budget projection's monthly net, editable) versus a scenario with
extra monthly savings, both compounded at a configurable annual rate
over a 1-40 year horizon — side-by-side totals and growth curves.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 18:07:02 -06:00
Carlos Escalante
84f0256b7c Rate history chart and pension contribution calculator
Analytics gains a 90-day USD/CRC buy/sell chart from the existing
history endpoint (wishlist: rate history). Pensiones gains a
Calculadora de Aporte: fund + target amount + target age → required
monthly contribution via the annuity future-value formula, using the
fund's live balance and configured rate (wishlist: contribution
calculator).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 16:59:29 -06:00
Carlos Escalante
fe62e50a85 Cosmetics: shared PeriodNavigator, breadcrumb, meter labels
One chevron component now drives year/month stepping in Budget and
Proyecciones (UX-03). Jumping from Proyecciones to a Budget month shows
a back affordance (UX-22). Water meters can be named (Casa, Cochera…)
via a settings-persisted dialog; the chart legend uses the names
(UX-24).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 15:27:06 -06:00
Carlos Escalante
7dfe2da2a9 OpenAPI type codegen with CI drift gate (plan 3.4, ARCH-16, FE-14)
Every dict-returning route the SPA consumes now declares a response
model (auth, bulk, sync-status, notifications). api-types.gen.ts is
generated from app.openapi() via openapi-typescript, and the 22 hand-
written read interfaces in api.ts are now aliases onto the generated
schemas — backend schema drift becomes a typecheck failure, and CI
regenerates the file and fails the deploy if it's stale. The aliasing
immediately caught two real drifts: RecurringItemType was missing
SAVINGS, and raw_charges (untyped JSON column) is now explicitly
shaped at the boundary.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 07:53:00 -06:00
Carlos Escalante
3ea1ef0fa0 Record Phase 4 iteration 2 — improvement plan complete
All checks were successful
Deploy to VPS / test (push) Successful in 1m51s
Deploy to VPS / deploy (push) Successful in 1m51s
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 21:15:17 -06:00
Carlos Escalante
8713eaa4d8 Pension assumptions editable + dirty-form guard + empty-state CTA
Per-fund contribution and annual-rate assumptions are now editable in a
Supuestos dialog on Pensiones and persisted server-side in UserSettings
so projections survive devices and reloads; saldo inicial keeps coming
from the latest snapshot (UX-12). The recurring-item dialog confirms
before discarding unsaved edits, with the dirty baseline computed from
the same values the populate effect sets (UX-06). Empty transaction
lists now offer an inline add button (UX-14).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 21:14:44 -06:00
Carlos Escalante
0dfac6a285 Bulk selection UI and import-review modal
Transactions gain a checkbox column with select-all; a bulk bar offers
assign-category, defer/restore and delete (confirmed) via the new bulk
endpoint, with toasts and invalidation. The paste-import modal now
lists each skipped duplicate with the matched existing row and offers
'Importar duplicados de todos modos' — re-importing ONLY the skipped
lines so first-pass rows can't double-import (UX-20). Two real fixes
found en route: the mobile list ignored undo-pending deletes and used
en-US dates; the desktop date column also lost its en-US stray. Note:
UX-11 (no mobile layout) was a false positive — a card layout already
existed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 21:00:19 -06:00
Carlos Escalante
ce3cc69846 Import dedup transparency + override; bulk transaction actions
Paste-import now returns each skipped duplicate WITH the matched
existing row (id, merchant, date, amount, source) instead of a bare
count, and accepts allow_duplicates to force-import false positives —
the practical fix for same-day identical purchases hashing together
(UX-20; supersedes the BE-08 hash change, which would have orphaned
every existing hash). POST /transactions/bulk applies
delete/set_category/set_deferred to up to 500 ids atomically. 82 tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 20:46:23 -06:00
Carlos Escalante
5612f69f11 Record Phase 4 iteration 1 in PROGRESS.md
All checks were successful
Deploy to VPS / test (push) Successful in 1m47s
Deploy to VPS / deploy (push) Successful in 1m4s
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 18:04:33 -06:00
Carlos Escalante
b3a7f5185a Polish: aria-pressed toggles, deferred row styling, override hint
Fund visibility toggles and the header privacy/theme buttons expose
aria-pressed (FE-16); deferred transactions render struck-through and
muted so the state is visible at a glance (UX-05); the balance-override
input documents its keyboard contract (UX-21). The Budget CSV button
rides in the transactions tab header.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 18:03:55 -06:00
Carlos Escalante
629d1181ae Undo grace for transaction deletes (UX-10)
After confirming a delete the row disappears immediately but the API
call waits 5 seconds behind a Deshacer toast action; undo restores the
row without any server round-trip. A failed commit restores the row
and reports the error.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 18:03:55 -06:00
Carlos Escalante
571428f5ac CSV export for transactions with download buttons (ARCH-18)
GET /api/v1/transactions/export streams a CSV (optional source/type/
date filters), cookie-authenticated so window.open downloads work.
Buttons on the Budget transactions tab (all) and Salarios (SALARY
only). Quoting and category resolution covered by tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 18:03:55 -06:00
Carlos Escalante
4ceb67564a Sync Status: per-source ingestion health page with nav warning badge
The n8n flows fail invisibly from the app's perspective (review UX-17,
the top trust gap). /api/v1/sync-status derives last-received + cadence
thresholds for BAC, salary, municipal, pensions and exchange rate from
existing data — no n8n integration needed. New Sincronización page
shows per-source freshness and a hint to check n8n; the sidebar item
gets an amber dot when any source is stale. Verified live against the
dev DB (correctly flags its stale BAC data at 43 days).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 18:03:55 -06:00
Carlos Escalante
8b7f3dff40 Localization sweep: shared es-CR dates module, no more en-US strays
src/lib/dates.ts is the single source for month names and date
formatting; the three per-page month arrays are gone (with an
off-by-one guard — the shared array is 1-indexed). formatDate and the
Analytics chart tooltips now speak es-CR, Analytics headings are
Spanish, and the cycle selector says 'Todo el período' (UX-19, FE-17,
UX-07).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 17:59:16 -06:00
Carlos Escalante
cdfa4ad90a Record Phase 3 completion in PROGRESS.md
All checks were successful
Deploy to VPS / test (push) Successful in 1m37s
Deploy to VPS / deploy (push) Successful in 2m6s
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 17:31:17 -06:00
Carlos Escalante
b5bb2e8562 Auth probe retries network failures; lazy context init; CK repair logging
A thrown fetch in the auth probe is a network failure, not a 401 — it
now retries once before treating the user as logged out, and logs the
real cause (FE-15). Theme and privacy state initialize lazily from
localStorage so neither the wrong theme nor unmasked sensitive values
flash on reload (FE-07). The CopilotKit orphan-tool-call repair logs
when it has to skip instead of failing silently (FE-11).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 17:30:26 -06:00
Carlos Escalante
2a68a05ffd Mutation feedback: toasts on transaction save/delete, confirm override clear
Transaction create/update/delete now confirm success or failure with a
toast (UX-01); save failures always show a user-visible message instead
of console-only (FE-06, with a typed error guard replacing the any
cast). Clearing a balance override — which recalculates every following
month — now requires confirmation like the other destructive actions
(UX-15).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 17:28:21 -06:00
Carlos Escalante
7a1733bf7e Migrate Analytics, Salarios, Pensions, ServiciosMunicipales, Proyecciones to queries
Every page now goes through TanStack Query with cancellation,
placeholder data on filter changes, and a visible ErrorState with retry
instead of console.error/silent-empty rendering (FE-02, UX-02).
Analytics keeps the trend query on its own key so cycle changes don't
refetch it. Pensions/Servicios upload-triggered refreshes are bounded
by the api-level 30s timeout (FE-05); pension upload results cap at 10
rows with an overflow note (FE-19). Note: the Pensions ROI fallback
already guarded short chart data — FE-04 was already handled.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 17:23:52 -06:00
Carlos Escalante
65dffdac06 Adopt TanStack Query: provider, timeouts, useBudget, Budget page
QueryClientProvider (30s staleTime, 1 retry) + Sonner Toaster at the
root. api.ts gains AbortSignal support and a default 30s timeout
(FE-24); query cancellation kills the stale-response races (FE-01,
FE-03). useBudget is now queries + mutations with targeted ['budget']
invalidation and success/error toasts on every mutation (ARCH-13,
UX-01); month navigation keeps the previous month visible while
fetching. Budget's transaction list is a cancellable query with
placeholder data; deferred-toggle gets feedback (UX-05); both panels
render a shared ErrorState with retry instead of failing silently.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 16:24:48 -06:00
Carlos Escalante
396c492f27 Record Phase 2 completion in PROGRESS.md
All checks were successful
Deploy to VPS / test (push) Successful in 1m40s
Deploy to VPS / deploy (push) Successful in 2m26s
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 15:31:42 -06:00
Carlos Escalante
440da3e394 Consistency sweep: enum members, keyword status codes, SimpleCookie
analytics.py filters by TransactionType.COMPRA instead of the string
literal; budget.py uses keyword HTTPException args; the agent
middleware parses cookies with stdlib SimpleCookie instead of a regex
(ARCH-10); auth.py's lazy imports are hoisted — there was never an
actual auth/db cycle (ARCH-20); transactions.py hoists or_/and_.
Verified: dev container boots, agent auth paths return 401 for
missing/garbage cookies.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 15:23:21 -06:00
Carlos Escalante
82f10a5d7c Error visibility and agent tool cleanup
Exchange-rate fetchers log every source failure instead of silently
passing (BE-18); the rate tool exposes fetched_at so staleness is
visible (BE-20). Agent: unbound-session failures raise a clear
RuntimeError (BE-12); net worth converts via get_crc_multipliers with
last-known fallbacks instead of a hardcoded 600 CRC / 1.08 EUR guess,
and reports accounts it cannot convert rather than inventing numbers
(BE-24); budget tool enforces MIN/MAX_YEAR (BE-16); municipal receipts
gain offset paging (BE-17); category analytics prefetches names; the
module docstring pins the read-only tool policy (SEC-09). Note: the
refresh loop never swallowed CancelledError (BaseException since 3.8) —
ARCH-08 was a false positive.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 15:23:21 -06:00
Carlos Escalante
82a7bfc4c5 Harden PDF uploads: size cap, magic-byte check, non-blocking parse
read_pdf_upload() bounds reads at 10 MB and requires the %PDF magic
before anything reaches pdftotext (SEC-06); rejections land in the
per-file errors list with the filename. Parsing now runs via
asyncio.to_thread so a slow PDF no longer blocks the event loop for up
to 30s (BE-14). Review notes: temp files were already context-managed
(BE-09 overstated) and the pension batch already commits atomically
(BE-10 overstated). 69 tests green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 15:00:49 -06:00
Carlos Escalante
3cdd7d9eab Constraint and query pass: FK delete rules, grouped queries, tiebreakers
- FK rules via migration 7884505b16b3 (verified on dev with a rolled-
  back probe): transactions/recurring items SET NULL on category delete
  (this also fixes the 500 that DELETE /categories raised for in-use
  categories), water readings CASCADE with their receipt. Models declare
  ondelete to stay in sync. (ARCH-09, BE-11)
- compute_actuals_by_source: 3 grouped queries replace 10 single-
  aggregate round-trips; behavior pinned by the existing cycle suite.
  (BE-21)
- compute_cc_by_category prefetches category names (ARCH-15); agent
  pension latest-per-fund resolved in SQL (BE-04); municipal water
  consumption batched into one grouped query (BE-05).
- Deterministic pagination: date DESC, id DESC on all transaction
  listings. (ARCH-12)
- New agent-tool tests (session binding, JSON-safe output, batched
  queries): 64 tests green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 14:45:46 -06:00
Carlos Escalante
74886fbf98 Migrate all 29 money columns from float to NUMERIC/Decimal (BE-02)
Models use Money = Annotated[Decimal, PlainSerializer(float)] so storage
is exact NUMERIC(15,2) (rates 15,6) while JSON keeps emitting plain
numbers — the frontend contract is unchanged, pinned by
test_models_serialization.py. Service layers coerce to float at their
boundaries (projection math, rate multipliers, agent tool output, the
savings constants become Decimal) so no naive Decimal/float mixing can
raise. Migration 7f567c8bafdb applied to dev: per-row values and the
table sum byte-identical before/after; endpoints verified live on the
new schema. 59/59 tests green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 13:38:28 -06:00
Carlos Escalante
881d879ed1 Gate deploys on backend tests and frontend typecheck; record Phase 1
All checks were successful
Deploy to VPS / test (push) Successful in 1m51s
Deploy to VPS / deploy (push) Successful in 1m4s
The new test job runs the 55-test pytest suite and pnpm typecheck
before the deploy job (needs: test) — a red suite now blocks prod.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-09 20:59:34 -06:00
Carlos Escalante
49b96ed49c Fix all 20 pre-existing type errors; typecheck now green
- ui/chart.tsx: recharts 3 stopped exposing tooltip/legend content props
  on its public types; declare the ChartPayloadItem shape we consume
- base-ui Select onValueChange passes string|null: guard null in
  BillingCycleSelector, PasteImportModal, TransactionModal
- type the untyped api.get calls (cycles, categories)
- push-notifications: back the VAPID key with an explicit ArrayBuffer
  so it satisfies BufferSource under TS 5.9

Unblocks gating CI on pnpm typecheck.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-09 20:55:07 -06:00
Carlos Escalante
9a25c4baab Adopt Alembic: autogenerated baseline, stamp-or-upgrade startup
The baseline (c3da001a0eb3) was autogenerated against an empty Postgres
and schema-diffed against the live dev schema until column-identical
(two model fixes fell out: deferred_to_next_cycle now declares its
server_default, usersettings.data is JSONB on Postgres to match the
live type). Startup replaces init_db/run_migrations with
run_alembic_upgrade(): pre-Alembic databases that already match the
baseline are adopted via stamp; fresh databases build from the
migration. The section is serialized with a pg advisory lock because
prod uvicorn runs 2 workers, each executing the lifespan. Verified:
adoption + idempotent re-run on the dev DB, fresh 13-table build on an
empty DB, full container boot, 55/55 tests. (ARCH-02, BE-19)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-09 20:31:55 -06:00
Carlos Escalante
c725c1487d Add pytest harness and 55-test suite for core backend logic
Covers: billing-cycle characterization (the BE-06 convention, boundary
instants, year wraps, deferred transactions), projection engine
(recurring schedules, no-double-count, cumulative balances, overrides,
fresh start), auth (JWT roundtrip/expiry, API tokens, constant-time
creds, rate-limit window), fail-fast settings, and the BAC paste
parser. SQLite in-memory fixtures, exchange rates pinned — no network.
Also doubles as the float-behavior baseline for the Phase 2 Decimal
migration (plan 1.5). config.py moves to SettingsConfigDict to clear
the pydantic deprecation warning from test output.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-09 19:46:54 -06:00
Carlos Escalante
996bd437a1 Replace deprecated datetime.utcnow with shared naive-UTC helper
All 29 call sites now use app.timeutil.utcnow(), which returns naive
UTC via the non-deprecated API. Semantics are unchanged on purpose:
every datetime column is TIMESTAMP WITHOUT TIME ZONE, so going aware
here would poison naive/aware comparisons. The TIMESTAMPTZ migration
(Phase 2) now has a single place to change. (BE-01)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-09 19:46:54 -06:00
Carlos Escalante
0a4c3e9436 Validate month input and document cycle labeling convention
Review finding BE-06 (suspected off-by-one in get_cycle_range) is a
FALSE POSITIVE: budget month M composes get_previous_cycle with
get_cycle_range to cover [(M-1)/18, M/18), matching both the code
comment and the frontend convention. Verified against prod data: May
2026 cycle-window SQL sum matches the API (95 txs exactly, refunds to
the colón, 0.13%% delta from one EUR tx). The docstring now spells out
the labeling convention, and month is validated 1-12 (BE-07).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-09 19:46:54 -06:00
Carlos Escalante
fa19ac1988 Document Phase 0 completion in codebase-review/PROGRESS.md
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-09 19:34:48 -06:00
Carlos Escalante
d874cf540d Fix CI frontend build: pin pnpm, approve dependency build scripts
All checks were successful
Deploy to VPS / deploy (push) Successful in 2m41s
The Docker build's unpinned corepack resolved a newer pnpm that hard-
errors on unreviewed dependency build scripts (ERR_PNPM_IGNORED_BUILDS),
failing deploy task 80. Pin packageManager to the locally-verified
pnpm 10.33.1 and review the build scripts: @swc/core and esbuild are
approved (vite needs their native binaries), @scarf/scarf is ignored.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-09 19:22:57 -06:00