feat(presupuestos): monthly budget management + semantic HTML enforcement#7
feat(presupuestos): monthly budget management + semantic HTML enforcement#7DataDave-Dev wants to merge 4 commits into
Conversation
- Add jsx-a11y semantic rules as errors in eslint config - Fix heading hierarchy: shell header owns the single h1, views start at h2, wizard steps shift h3/h4 to h2/h3 - Add accessible names to account/budget list sections (sr-only h2) - Restructure modal backdrops: click-to-close on aria-hidden backdrop, dialog element keeps Escape handling - Movements feed renders as ul/li cards instead of div stacks - Sidebar expands on keyboard focus, not only hover
- Ignore .agents/** plugin template files in eslint - Allow underscore-prefixed unused args/vars/catch bindings - Drop dead startGoogleOAuth helper from login form
- budget domain: monthly limits per category with validation - budgets service: list/upsert/remove limits, spent aggregation, copy limits from previous month - API routes: GET/PUT /api/presupuestos, limit and copy handlers - presupuestos view: month navigation, limit list with consumption, limit editor and copy-from-previous dialogs - schema: budget_limits table (migration 0007) - wizard materialize: seed budget limits from wizard budget step
📝 WalkthroughWalkthroughThis change introduces monthly budget periods, database persistence, budget services, authenticated APIs, and a client budgeting interface. It also updates accessibility semantics and linting, adds a development launch configuration, and removes an unused OAuth helper. ChangesMonthly budget management
Accessibility and interface semantics
Development and authentication cleanup
Estimated code review effort: 4 (Complex) | ~60 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 Checkov (3.3.8).claude/launch.jsonTraceback (most recent call last): src/lib/db/migrations/meta/0007_snapshot.jsonTraceback (most recent call last): src/lib/db/migrations/meta/_journal.jsonTraceback (most recent call last): Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 6
🧹 Nitpick comments (2)
src/lib/finance/services/budgets.ts (1)
366-381: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick winRedundant full budget load to compute available categories.
listBudgetableCategoriescallsgetBudgetForMonth, which runs the limit join plus both spending-aggregation queries, only to build thetakenset of category ids. Callers (page.tsxLines 32-36 andbudgets-route.tsGET Lines 33-37) already callgetBudgetForMonthfor the same month, so the spending aggregation executes twice per request. Consider passing the already-loaded budget in, or selecting just the takencategoryIds directly frombudget_category_amounts.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/lib/finance/services/budgets.ts` around lines 366 - 381, Update listBudgetableCategories to avoid calling getBudgetForMonth solely to build the taken set; either accept and reuse the already-loaded budget from callers, or query only categoryId values from budget_category_amounts for the user and month. Preserve the existing category filtering and alphabetical ordering while eliminating duplicate spending-aggregation queries.src/app/(app)/presupuestos/presupuestos-client.tsx (1)
36-42: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winComments in
src/appmust be written in English. UI copy stays Spanish, but code comments should be English per the repo guideline; both files carry Spanish comments.
src/app/(app)/presupuestos/presupuestos-client.tsx#L36-L42: translate this and the other Spanish comments (Lines 58-61, 210, 226-227, 367, 607-608) to English.src/app/(app)/presupuestos/page.tsx#L26-L31: translate the Spanish comments at Lines 26-27, 31, and 40 to English.As per coding guidelines: "Routes, UI copy, and user-facing strings must be Spanish; code identifiers, comments, and commits must be English."
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/app/`(app)/presupuestos/presupuestos-client.tsx around lines 36 - 42, Translate all Spanish code comments to English without changing UI copy: update the comments at src/app/(app)/presupuestos/presupuestos-client.tsx lines 36-42, 58-61, 210, 226-227, 367, and 607-608, plus src/app/(app)/presupuestos/page.tsx lines 26-27, 31, and 40. Preserve the existing explanations and behavior while keeping routes, identifiers, and user-facing strings Spanish.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/app/`(app)/configuracion/perfil/profile-form-container.tsx:
- Around line 162-167: Replace the clickable backdrop div in the profile form
container with a non-focusable button that retains the onCancel behavior and
includes an accessible label, while preserving its existing full-screen backdrop
styling and aria-hidden semantics as appropriate.
In `@src/app/`(app)/presupuestos/presupuestos-client.tsx:
- Around line 399-403: Clamp the progressbar’s aria-valuenow in the component
rendering around percent so it never exceeds the fixed aria-valuemax of 100,
while preserving percent for the visual over-budget display. Use the existing
percent value as the source and ensure the ARIA value remains within the 0–100
range.
In `@src/components/app-shell/authenticated-shell.tsx`:
- Around line 39-40: Update the sidebar focus handling around onBlurCapture so
setIsSidebarHovered(false) runs only when focus moves outside the sidebar, using
the blur event’s relatedTarget to detect whether the newly focused element
remains within the sidebar. Preserve the existing focus-in behavior and avoid
collapsing while navigating between sidebar items.
In `@src/lib/finance/domain/budget.ts`:
- Around line 136-139: Update the amount validation in the budget record
validation flow to accept only values whose original type is number; remove the
Number(rec.amount) coercion so booleans, arrays, strings, and other JSON values
are rejected. Preserve the existing finite and greater-than-zero checks and
error message for invalid amounts.
- Around line 62-72: The date construction in budgetMonthFromKey must preserve
years 0000–0099 instead of allowing Date.UTC to remap them to 1900–1999. Update
periodStart and periodEnd creation to use a year-safe approach such as
setUTCFullYear, or reject those years explicitly if that matches the intended
contract; keep the existing key parsing and month-boundary behavior unchanged.
In `@src/lib/finance/services/budgets.ts`:
- Around line 335-356: Wrap the budget insert and the budgetCategoryAmounts
insert in a single synchronous db.transaction((tx) => { ... }) callback, using
tx for both writes. Preserve the existing values and selected mapping so the
budget row and its limits commit or roll back together.
---
Nitpick comments:
In `@src/app/`(app)/presupuestos/presupuestos-client.tsx:
- Around line 36-42: Translate all Spanish code comments to English without
changing UI copy: update the comments at
src/app/(app)/presupuestos/presupuestos-client.tsx lines 36-42, 58-61, 210,
226-227, 367, and 607-608, plus src/app/(app)/presupuestos/page.tsx lines 26-27,
31, and 40. Preserve the existing explanations and behavior while keeping
routes, identifiers, and user-facing strings Spanish.
In `@src/lib/finance/services/budgets.ts`:
- Around line 366-381: Update listBudgetableCategories to avoid calling
getBudgetForMonth solely to build the taken set; either accept and reuse the
already-loaded budget from callers, or query only categoryId values from
budget_category_amounts for the user and month. Preserve the existing category
filtering and alphabetical ordering while eliminating duplicate
spending-aggregation queries.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 79c242c3-d4ba-42e7-aa76-a4ef0048f82a
📒 Files selected for processing (34)
.claude/launch.jsoneslint.config.mjssrc/app/(app)/configuracion/perfil/profile-form-container.tsxsrc/app/(app)/configuracion/perfil/profile-form.tsxsrc/app/(app)/cuentas/cuentas-client.tsxsrc/app/(app)/movimientos/movimientos-client.tsxsrc/app/(app)/presupuestos/page.tsxsrc/app/(app)/presupuestos/presupuestos-client.test.tssrc/app/(app)/presupuestos/presupuestos-client.tsxsrc/app/(auth)/login/login-form.tsxsrc/app/api/presupuestos/copiar/route.tssrc/app/api/presupuestos/route.tssrc/app/api/presupuestos/topes/[categoryId]/route.tssrc/app/wizard/components/step-header.tsxsrc/app/wizard/steps/step-budget-goals.tsxsrc/app/wizard/steps/step-catalog.tsxsrc/app/wizard/steps/step-summary.tsxsrc/components/app-shell/authenticated-shell.tsxsrc/components/app-shell/placeholder-view.tsxsrc/lib/db/migrations/0007_busy_luke_cage.sqlsrc/lib/db/migrations/meta/0007_snapshot.jsonsrc/lib/db/migrations/meta/_journal.jsonsrc/lib/db/schema.tssrc/lib/finance/domain/budget.test.tssrc/lib/finance/domain/budget.tssrc/lib/finance/handlers/budget-copy-route.tssrc/lib/finance/handlers/budget-limit-route.tssrc/lib/finance/handlers/budgets-response.tssrc/lib/finance/handlers/budgets-route.test.tssrc/lib/finance/handlers/budgets-route.tssrc/lib/finance/services/budgets.test.tssrc/lib/finance/services/budgets.tssrc/lib/finance/services/wizard-materialize.test.tssrc/lib/finance/services/wizard-materialize.ts
💤 Files with no reviewable changes (1)
- src/app/(auth)/login/login-form.tsx
| <div className="fixed inset-0 z-50 flex items-center justify-center p-4"> | ||
| <div | ||
| className="w-full max-w-md rounded-lg border border-border bg-card p-6 shadow-xl" | ||
| onClick={(e) => e.stopPropagation()} | ||
| aria-hidden="true" | ||
| onClick={onCancel} | ||
| className="absolute inset-0 bg-black/50" | ||
| /> |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Use an interactive backdrop element.
This div has a click handler but remains non-interactive, and aria-hidden does not exempt it. The new eslint.config.mjs rules will make this file fail lint. Use a non-focusable <button> backdrop with an accessible label, or add keyboard-equivalent behavior with a narrow suppression.
Proposed fix
- <div
- aria-hidden="true"
- onClick={onCancel}
- className="absolute inset-0 bg-black/50"
- />
+ <button
+ type="button"
+ tabIndex={-1}
+ aria-label="Cerrar diálogo"
+ onClick={onCancel}
+ className="absolute inset-0 bg-black/50"
+ />📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <div className="fixed inset-0 z-50 flex items-center justify-center p-4"> | |
| <div | |
| className="w-full max-w-md rounded-lg border border-border bg-card p-6 shadow-xl" | |
| onClick={(e) => e.stopPropagation()} | |
| aria-hidden="true" | |
| onClick={onCancel} | |
| className="absolute inset-0 bg-black/50" | |
| /> | |
| <div className="fixed inset-0 z-50 flex items-center justify-center p-4"> | |
| <button | |
| type="button" | |
| tabIndex={-1} | |
| aria-label="Cerrar diálogo" | |
| onClick={onCancel} | |
| className="absolute inset-0 bg-black/50" | |
| /> |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/app/`(app)/configuracion/perfil/profile-form-container.tsx around lines
162 - 167, Replace the clickable backdrop div in the profile form container with
a non-focusable button that retains the onCancel behavior and includes an
accessible label, while preserving its existing full-screen backdrop styling and
aria-hidden semantics as appropriate.
| <div | ||
| role="progressbar" | ||
| aria-valuenow={percent} | ||
| aria-valuemin={0} | ||
| aria-valuemax={100} |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
aria-valuenow can exceed aria-valuemax on overruns.
When a category is over budget, percent is capped at 999 (Line 64) and passed to aria-valuenow, but aria-valuemax is fixed at 100. An out-of-range aria-valuenow is invalid per ARIA and assistive tech may report it inconsistently. Clamp the value or expose the overrun via aria-valuetext.
♿ Proposed fix
<div
role="progressbar"
- aria-valuenow={percent}
+ aria-valuenow={Math.min(100, percent)}
aria-valuemin={0}
aria-valuemax={100}
+ aria-valuetext={`${percent}%`}
aria-label={`Consumo de ${limit.categoryName}`}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <div | |
| role="progressbar" | |
| aria-valuenow={percent} | |
| aria-valuemin={0} | |
| aria-valuemax={100} | |
| <div | |
| role="progressbar" | |
| aria-valuenow={Math.min(100, percent)} | |
| aria-valuemin={0} | |
| aria-valuemax={100} | |
| aria-valuetext={`${percent}%`} | |
| aria-label={`Consumo de ${limit.categoryName}`} |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/app/`(app)/presupuestos/presupuestos-client.tsx around lines 399 - 403,
Clamp the progressbar’s aria-valuenow in the component rendering around percent
so it never exceeds the fixed aria-valuemax of 100, while preserving percent for
the visual over-budget display. Use the existing percent value as the source and
ensure the ARIA value remains within the 0–100 range.
| onFocusCapture={() => setIsSidebarHovered(true)} | ||
| onBlurCapture={() => setIsSidebarHovered(false)} |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Only collapse when focus leaves the sidebar.
onBlurCapture also runs when focus moves from one sidebar item to another. Clearing the state unconditionally can start the collapse transition while keyboard users tab within the navigation, causing flicker and unstable focus behavior. Check relatedTarget before collapsing.
Proposed fix
- onBlurCapture={() => setIsSidebarHovered(false)}
+ onBlurCapture={(event) => {
+ const next = event.relatedTarget;
+ if (!(next instanceof Node) || !event.currentTarget.contains(next)) {
+ setIsSidebarHovered(false);
+ }
+ }}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| onFocusCapture={() => setIsSidebarHovered(true)} | |
| onBlurCapture={() => setIsSidebarHovered(false)} | |
| onFocusCapture={() => setIsSidebarHovered(true)} | |
| onBlurCapture={(event) => { | |
| const next = event.relatedTarget; | |
| if (!(next instanceof Node) || !event.currentTarget.contains(next)) { | |
| setIsSidebarHovered(false); | |
| } | |
| }} |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/components/app-shell/authenticated-shell.tsx` around lines 39 - 40,
Update the sidebar focus handling around onBlurCapture so
setIsSidebarHovered(false) runs only when focus moves outside the sidebar, using
the blur event’s relatedTarget to detect whether the newly focused element
remains within the sidebar. Preserve the existing focus-in behavior and avoid
collapsing while navigating between sidebar items.
| export function budgetMonthFromKey(key: string): BudgetMonth | null { | ||
| const match = MONTH_PATTERN.exec(key); | ||
| if (!match) return null; | ||
| const year = Number(match[1]); | ||
| const month = Number(match[2]); | ||
| return { | ||
| key, | ||
| periodStart: new Date(Date.UTC(year, month - 1, 1, 0, 0, 0, 0)), | ||
| // Último instante del mes: el primer instante del siguiente, menos un milisegundo. | ||
| periodEnd: new Date(Date.UTC(year, month, 1, 0, 0, 0, 0) - 1), | ||
| }; |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== File outline ==\n'
ast-grep outline src/lib/finance/domain/budget.ts --view expanded || true
printf '\n== Relevant excerpt ==\n'
sed -n '1,140p' src/lib/finance/domain/budget.ts
printf '\n== JS Date.UTC probe ==\n'
node - <<'JS'
for (const y of [0, 1, 42, 99, 100, 1900]) {
const d = new Date(Date.UTC(y, 0, 1));
console.log(y, d.toISOString());
}
JSRepository: DataDave-Dev/valora-official
Length of output: 6152
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== Search for budgetMonthFromKey usages and tests ==\n'
rg -n "budgetMonthFromKey\(" src/lib/finance -g '!**/node_modules/**' || true
printf '\n== Search for month key format/pattern ==\n'
rg -n "MONTH_PATTERN|BudgetMonth|periodStart|periodEnd|0000|0099" src/lib/finance -g '!**/node_modules/**' || trueRepository: DataDave-Dev/valora-official
Length of output: 8739
Handle years 0000–0099 without Date.UTC remapping. MONTH_PATTERN accepts YYYY-MM, but Date.UTC() turns 0–99 into 1900–1999, so 0001-01 keeps the 0001-01 key while producing 1901 dates. Reject that range or build the dates with setUTCFullYear.
🧰 Tools
🪛 OpenGrep (1.25.0)
[ERROR] 63-63: Dynamic command passed to child_process.exec/execSync. Use child_process.execFile or spawn with an argument array instead.
(coderabbit.command-injection.exec-js)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/lib/finance/domain/budget.ts` around lines 62 - 72, The date construction
in budgetMonthFromKey must preserve years 0000–0099 instead of allowing Date.UTC
to remap them to 1900–1999. Update periodStart and periodEnd creation to use a
year-safe approach such as setUTCFullYear, or reject those years explicitly if
that matches the intended contract; keep the existing key parsing and
month-boundary behavior unchanged.
| const amount = typeof rec.amount === "number" ? rec.amount : Number(rec.amount); | ||
| if (!Number.isFinite(amount) || amount <= 0) { | ||
| errors.push({ field: "amount", message: "El monto debe ser mayor a cero." }); | ||
| } |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win
Do not coerce arbitrary JSON values into amounts.
Number(true) and Number([100]) are valid positive numbers, so malformed payloads can persist unintended limits. Require a JSON number at the API boundary.
Proposed fix
-const amount = typeof rec.amount === "number" ? rec.amount : Number(rec.amount);
-if (!Number.isFinite(amount) || amount <= 0) {
+const amount = rec.amount;
+if (typeof amount !== "number" || !Number.isFinite(amount) || amount <= 0) {📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const amount = typeof rec.amount === "number" ? rec.amount : Number(rec.amount); | |
| if (!Number.isFinite(amount) || amount <= 0) { | |
| errors.push({ field: "amount", message: "El monto debe ser mayor a cero." }); | |
| } | |
| const amount = rec.amount; | |
| if (typeof amount !== "number" || !Number.isFinite(amount) || amount <= 0) { | |
| errors.push({ field: "amount", message: "El monto debe ser mayor a cero." }); | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/lib/finance/domain/budget.ts` around lines 136 - 139, Update the amount
validation in the budget record validation flow to accept only values whose
original type is number; remove the Number(rec.amount) coercion so booleans,
arrays, strings, and other JSON values are rejected. Preserve the existing
finite and greater-than-zero checks and error message for invalid amounts.
| await db.insert(budgets).values({ | ||
| id: budgetId, | ||
| userId, | ||
| scope: source.scope, | ||
| monthlyTotal: source.monthlyTotal, | ||
| currency: source.currency, | ||
| periodStart: month.periodStart, | ||
| periodEnd: month.periodEnd, | ||
| createdAt: now, | ||
| updatedAt: now, | ||
| }); | ||
|
|
||
| await db.insert(budgetCategoryAmounts).values( | ||
| selected.map((limit) => ({ | ||
| id: randomUUID(), | ||
| budgetId, | ||
| categoryId: limit.categoryId, | ||
| amount: limit.amount, | ||
| createdAt: now, | ||
| updatedAt: now, | ||
| })), | ||
| ); |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🌐 Web query:
drizzle-orm 0.45 better-sqlite3 synchronous db.transaction API
💡 Result:
In Drizzle ORM 0.45.x, when using the better-sqlite3 driver, the db.transaction API is synchronous [1][2]. Because better-sqlite3 is a synchronous library, it does not support asynchronous callbacks within its transaction method [1][2][3]. Key points regarding this behavior: 1. Sync vs. Async: Unlike drivers for PostgreSQL or MySQL, better-sqlite3 handles database operations synchronously [1][2]. Passing an async function to db.transaction is unsupported and may throw an error (e.g., "Transaction function cannot return a promise") [1]. 2. Correct Usage: You must use a synchronous callback for db.transaction and ensure that all database operations inside the transaction use synchronous query methods (such as.run,.get,.all, or.values) [1][4]. Do not use await inside the transaction block [1][4]. Example of the correct synchronous pattern: db.transaction((tx) => { tx.insert(users).values({ name: 'Alice' }).run; tx.insert(posts).values({ userId: 1, title: 'Hello' }).run; }); Using await or returning a promise inside this block will interfere with the synchronous execution flow of the underlying better-sqlite3 driver, potentially causing transaction failures or improper rollback behavior [1][2][3].
Citations:
- 1: [BUG]:The better-sqlite3 driver throws an error when using transactions. drizzle-team/drizzle-orm#5063
- 2: [BUG]: sqlite transactions can't be async for 4 of 5 implementations drizzle-team/drizzle-orm#2275
- 3: [BUG]: Async SQLite transaction will not rollback drizzle-team/drizzle-orm#1472
- 4: TransactionRollbackError with BetterSQLite3 drizzle-team/drizzle-orm#1170
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect the target file around the cited lines
if [ -f src/lib/finance/services/budgets.ts ]; then
nl -ba src/lib/finance/services/budgets.ts | sed -n '280,390p'
else
echo "Missing src/lib/finance/services/budgets.ts" >&2
exit 1
fi
echo
echo "---- transaction usage in finance services ----"
rg -n "transaction\\(" src/lib/finance/services -g '*.ts' -g '*.tsx' || trueRepository: DataDave-Dev/valora-official
Length of output: 206
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "---- target excerpt ----"
sed -n '280,390p' src/lib/finance/services/budgets.ts | cat -n
echo
echo "---- transaction usage in finance services ----"
rg -n "transaction\\(" src/lib/finance/services -g '*.ts' -g '*.tsx' || trueRepository: DataDave-Dev/valora-official
Length of output: 4611
Wrap the budget and limit inserts in a transaction. If the second insert fails, the budget row survives on its own and the next copy hits the “Este mes ya tiene un presupuesto” conflict with no limits attached. Use a single synchronous db.transaction((tx) => { ... }) so both writes commit or roll back together.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/lib/finance/services/budgets.ts` around lines 335 - 356, Wrap the budget
insert and the budgetCategoryAmounts insert in a single synchronous
db.transaction((tx) => { ... }) callback, using tx for both writes. Preserve the
existing values and selected mapping so the budget row and its limits commit or
roll back together.
Summary
budget_limitstable (migration 0007), budgets service (list/upsert/remove, spent aggregation, copy from previous month), API routes under/api/presupuestos, and the presupuestos view with month navigation, consumption bars, limit editor and copy dialog. Wizard budget step now materializes limits.adopt-semantic-html) — jsx-a11y rules as errors, singleh1owned by shell header, corrected heading hierarchy in wizard and views,ul/limovement feed, accessible modal backdrops, keyboard-focus sidebar expansion..agents/**templates, allow_-prefixed unused bindings, drop dead OAuth helper.Test plan
pnpm lint— 0 errors, 0 warningspnpm typecheck— cleanpnpm test:unit— 191/191 passing (includes budget domain/service/handler tests)Summary by CodeRabbit