Overview
BudgetView’s budget management system helps you control spending by setting monthly limits for expense categories. Track progress in real-time with visual indicators and alerts when approaching or exceeding limits.
Category Budgets Set spending limits for individual expense categories
Monthly Periods Budgets are configured per month for precise tracking
Real-Time Tracking See live spending progress against your limits
Alert System Get warnings at 90% and alerts when exceeding budgets
Creating Budgets
Click 'Nuevo Presupuesto'
Open the budget creation dialog from the budgets page.
Select Category
Choose an expense category (only “gasto” categories are available).
Set Limit Amount
Enter the maximum spending limit in USD for this category.
Choose Period
Select the month using the month/year picker.
Save Budget
The budget is created and immediately starts tracking expenses.
Period Restrictions:
Can only create budgets for current or future months
Past months are disabled to prevent backdating
One budget per category per month
Budget Structure
type BudgetWithStats = {
id : string
categoriaId : string
categoriaNombre : string
limit : number // Budget limit in USD
spent : number // Amount spent in period
available : number // Remaining budget (limit - spent)
percentage : number // Spent / limit * 100
exceeded : boolean // True if spent > limit
nearLimit : boolean // True if percentage >= 90%
periodISO : string // YYYY-MM-DD format
}
Period Selection
Budgets use a custom month/year picker component:
const MonthYearPicker = ({
selected ,
onSelect ,
fromYear = 2018 ,
toYear = 2035 ,
minMonth ,
}) => {
// Renders grid of months with year selector
// Disables months before minMonth if provided
}
Current Month
Future Months
Past Budgets
Most common use case - set budgets for the current month.
Plan ahead by creating budgets for upcoming months.
View historical budgets but cannot create new ones for past periods.
Budget Calculation
Spending is calculated by aggregating expense transactions for the budget period:
const loadBudgets = async ( selectedMonth : string ) => {
const periodDateOnly = ` ${ selectedMonth } -01`
// Load budgets for period
const { data : budgetsData } = await supabase
. from ( "presupuestos" )
. select ( "id,monto,periodo,categoria_id,categorias(id,nombre)" )
. eq ( "periodo" , periodDateOnly )
// Calculate spending for each category
const start = new Date ( ` ${ selectedMonth } -01T00:00:00.000Z` )
const end = new Date ( start )
end . setMonth ( end . getMonth () + 1 )
const { data : expensesData } = await supabase
. from ( "transacciones" )
. select ( "categoria_id,monto" )
. in ( "categoria_id" , categoryIds )
. eq ( "tipo" , "gasto" )
. gte ( "fecha_transaccion" , start . toISOString ())
. lt ( "fecha_transaccion" , end . toISOString ())
// Aggregate spending by category
const spendMap = new Map < string , number >()
expensesData . forEach ( row => {
const amount = Number ( row . monto ?? 0 )
spendMap . set (
row . categoria_id ,
( spendMap . get ( row . categoria_id ) ?? 0 ) + amount
)
})
}
Budget Status Indicators
Budgets display different visual states based on spending:
Disponible (Available)
90% del límite (Warning)
Excedido (Exceeded)
Percentage < 90%
Green color scheme
Status: “Disponible”
Safe spending level
Percentage >= 90% and < 100%
Amber/yellow color scheme
Status: “90% del límite”
Approaching limit
Percentage >= 100%
Red color scheme
Status: “Excedido”
Over budget
const statusLabel = budget . exceeded
? "Excedido"
: budget . nearLimit
? "90% del límite"
: "Disponible"
const statusColor = budget . exceeded
? "bg-red-100 text-red-700"
: budget . nearLimit
? "bg-amber-100 text-amber-700"
: "bg-emerald-100 text-emerald-700"
Summary Cards
The budget page displays four key metrics:
Presupuesto Total Sum of all budget limits for the selected period
Gastado Total amount spent across all budgeted categories
Disponible Remaining budget (can be negative if exceeded)
Estado Count of budgets in control vs. exceeded
const summary = useMemo (() => {
const totalBudget = budgets . reduce (( acc , budget ) => acc + budget . limit , 0 )
const totalSpent = budgets . reduce (( acc , budget ) => acc + budget . spent , 0 )
const available = totalBudget - totalSpent
const controlCount = budgets . filter (( budget ) => ! budget . exceeded ). length
const exceededCount = budgets . length - controlCount
return {
totalBudget ,
totalSpent ,
available ,
controlCount ,
exceededCount ,
}
}, [ budgets ])
Budget Cards
Each budget is displayed in a detailed card showing:
Category Name & Icon : First letter in circular badge
Status Badge : Color-coded status indicator
Progress Bar : Visual representation of spending percentage
Spent vs Limit : Amounts in USD with BCV conversion
Available/Exceeded : Remaining budget or overspending amount
Edit & Delete Actions : Manage budget settings
Progress Bar Colors
const progressBarColor = budget . exceeded
? "bg-red-500"
: budget . nearLimit
? "bg-amber-500"
: "bg-emerald-500"
Editing Budgets
Click 'Editar'
Open the edit dialog from any budget card.
Modify Fields
Update category, amount, or period as needed.
Validate Period
Cannot move budgets to past months.
Save Changes
Updates apply immediately and recalculate progress.
Edit Validation
const originalPeriod = editingBudget ? extractMonthValue ( editingBudget . periodISO ) : null
if ( isPastMonthValue ( formPeriod ) && ( ! editingBudget || formPeriod !== originalPeriod )) {
setFormError (
editingBudget
? "Solo puedes mover el presupuesto a meses actuales o futuros."
: "Solo puedes crear presupuestos desde el mes actual en adelante."
)
return
}
Duplicate Prevention
The system prevents creating multiple budgets for the same category in the same month:
// Check for duplicates before insert/update
const { data : duplicateRows } = await supabase
. from ( "presupuestos" )
. select ( "id" )
. eq ( "categoria_id" , formCategoryId )
. eq ( "periodo" , normalizedPeriod )
. neq ( "id" , editingBudget ?. id ) // Exclude current budget when editing
. limit ( 1 )
if ( duplicateRows && duplicateRows . length > 0 ) {
setFormError ( "Ya tienes un presupuesto para esa categoría en el periodo seleccionado." )
return
}
Deleting Budgets
Deletion Warning:
Deleting budgets is permanent and cannot be undone
Confirmation dialog appears before deletion
Historical spending data remains intact
const handleDeleteBudget = async ( budgetId : string ) => {
const confirmed = window . confirm (
"¿Eliminar este presupuesto? Esta acción no se puede deshacer."
)
if ( ! confirmed ) return
const { error } = await supabase
. from ( "presupuestos" )
. delete ()
. eq ( "id" , budgetId )
if ( ! error ) {
await loadBudgets () // Refresh budget list
}
}
Real-Time Updates
Budgets automatically refresh when transactions are created or edited:
React . useEffect (() => {
const handleTransactionsChange = () => {
loadBudgets () // Recalculate spending
}
window . addEventListener ( "transactions:updated" , handleTransactionsChange )
return () => {
window . removeEventListener ( "transactions:updated" , handleTransactionsChange )
}
}, [ loadBudgets ])
BCV Conversion
All budget amounts show BCV equivalents:
const spentBcv = formatBcvAmount ( budget . spent )
const limitBcv = formatBcvAmount ( budget . limit )
const availableBcv = formatBcvAmount ( Math . abs ( budget . available ))
BCV conversions update automatically when the exchange rate refreshes.
Best Practices
Begin with conservative budget limits and adjust based on actual spending patterns.
Check budget performance at month-end and adjust for the next period.
Focus on Major Categories
Set budgets for your highest-spending categories first.
When a budget reaches 90%, review spending and adjust behavior.
Create budgets for future months to prepare for upcoming expenses.
Database Schema
CREATE TABLE presupuestos (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
categoria_id UUID NOT NULL REFERENCES categorias(id),
monto DECIMAL ( 12 , 2 ) NOT NULL ,
periodo DATE NOT NULL ,
usuario_id UUID NOT NULL REFERENCES auth . users (id),
created_at TIMESTAMP WITH TIME ZONE DEFAULT now (),
UNIQUE (categoria_id, periodo, usuario_id)
);
The unique constraint prevents duplicate budgets per category/period combination.