Add budget module and push notifications for transactions
All checks were successful
Deploy to VPS / deploy (push) Successful in 34s

Budget: recurring items CRUD, yearly/monthly projections with no-double-count
logic, and full UI (overview, monthly detail, recurring items manager).

Push notifications: Web Push via VAPID keys, triggered on transaction creation
from n8n. Includes service worker handlers, frontend subscription flow, and
a test button on the Dashboard (temporary).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Carlos Escalante
2026-03-26 22:28:14 -06:00
parent 2cd0d3b2e1
commit 8d76059ae8
25 changed files with 2225 additions and 13 deletions

View File

@@ -103,3 +103,125 @@ export interface Transaction {
category: Category | null;
created_at: string;
}
// --- Budget / Recurring Items ---
export type RecurringItemType = 'INCOME' | 'EXPENSE' | 'SAVINGS';
export type RecurringFrequency = 'WEEKLY' | 'MONTHLY' | 'QUARTERLY' | 'BIANNUAL' | 'YEARLY';
export interface RecurringItem {
id: number;
name: string;
amount: number;
currency: string;
item_type: RecurringItemType;
frequency: RecurringFrequency;
day_of_month: number | null;
month_of_year: number | null;
override_amounts: Record<string, number> | null;
category_id: number | null;
is_active: boolean;
notes: string | null;
created_at: string;
category: Category | null;
}
export interface RecurringItemCreate {
name: string;
amount: number;
currency?: string;
item_type: RecurringItemType;
frequency?: RecurringFrequency;
day_of_month?: number | null;
month_of_year?: number | null;
override_amounts?: Record<string, number> | null;
category_id?: number | null;
is_active?: boolean;
notes?: string | null;
}
export interface RecurringItemUpdate {
name?: string;
amount?: number;
currency?: string;
item_type?: RecurringItemType;
frequency?: RecurringFrequency;
day_of_month?: number | null;
month_of_year?: number | null;
override_amounts?: Record<string, number> | null;
category_id?: number | null;
is_active?: boolean;
notes?: string | null;
}
export interface RecurringItemDetail {
id: number;
name: string;
amount: number;
projected_amount: number | null;
used_actual: boolean;
item_type: string;
frequency: string;
category_name: string | null;
category_id: number | null;
}
export interface ActualsBySource {
source: string;
total_compra: number;
total_devolucion: number;
net: number;
count: number;
}
export interface MonthlyProjection {
month: number;
year: number;
projected_income: number;
projected_fixed_expenses: number;
projected_savings: number;
actual_credit_card: number;
actual_cash: number;
actual_transfers: number;
uncovered_actual: number;
gran_total_egresos: number;
net_balance: number;
}
export interface YearlyProjection {
year: number;
months: MonthlyProjection[];
annual_income: number;
annual_expenses: number;
annual_savings: number;
annual_net: number;
}
export interface MonthlyDetail {
year: number;
month: number;
income_items: RecurringItemDetail[];
expense_items: RecurringItemDetail[];
savings_items: RecurringItemDetail[];
actuals_by_source: ActualsBySource[];
total_projected_income: number;
total_projected_expenses: number;
total_projected_savings: number;
uncovered_actual: number;
gran_total_egresos: number;
net_balance: number;
}
// Budget API functions
export const getRecurringItems = (params?: { item_type?: string; is_active?: boolean }) =>
api.get<RecurringItem[]>('/budget/recurring', { params });
export const createRecurringItem = (data: RecurringItemCreate) =>
api.post<RecurringItem>('/budget/recurring', data);
export const updateRecurringItem = (id: number, data: RecurringItemUpdate) =>
api.patch<RecurringItem>(`/budget/recurring/${id}`, data);
export const deleteRecurringItem = (id: number) =>
api.delete(`/budget/recurring/${id}`);
export const getYearlyProjection = (year: number) =>
api.get<YearlyProjection>(`/budget/projection/${year}`);
export const getMonthlyDetail = (year: number, month: number) =>
api.get<MonthlyDetail>(`/budget/month/${year}/${month}`);