diff --git a/backend/app/api/v1/endpoints/budget.py b/backend/app/api/v1/endpoints/budget.py index 3712de7..f20f63c 100644 --- a/backend/app/api/v1/endpoints/budget.py +++ b/backend/app/api/v1/endpoints/budget.py @@ -39,7 +39,9 @@ def list_recurring_items( session: Session = Depends(get_session), _user: str = Depends(get_current_user), ): - query = select(RecurringItem) + query = select(RecurringItem).where( + RecurringItem.item_type != RecurringItemType.SAVINGS + ) if item_type: query = query.where(RecurringItem.item_type == item_type) if is_active is not None: @@ -101,7 +103,6 @@ class MonthlyProjectionResponse(BaseModel): year: int projected_income: float projected_fixed_expenses: float - projected_savings: float actual_credit_card: float actual_cash: float actual_transfers: float @@ -118,7 +119,6 @@ class YearlyProjectionResponse(BaseModel): months: list[MonthlyProjectionResponse] annual_income: float annual_expenses: float - annual_savings: float annual_net: float @@ -138,7 +138,6 @@ def get_yearly_projection( months = [] annual_income = 0.0 annual_expenses = 0.0 - annual_savings = 0.0 annual_net = 0.0 for data in months_data: @@ -147,7 +146,6 @@ def get_yearly_projection( year=data["year"], projected_income=data["projected_income"], projected_fixed_expenses=data["projected_fixed_expenses"], - projected_savings=data["projected_savings"], actual_credit_card=data["actual_credit_card"], actual_cash=data["actual_cash"], actual_transfers=data["actual_transfers"], @@ -161,7 +159,6 @@ def get_yearly_projection( months.append(monthly) annual_income += data["projected_income"] annual_expenses += data["gran_total_egresos"] - annual_savings += data["projected_savings"] annual_net += data["net_balance"] return YearlyProjectionResponse( @@ -169,7 +166,6 @@ def get_yearly_projection( months=months, annual_income=annual_income, annual_expenses=annual_expenses, - annual_savings=annual_savings, annual_net=annual_net, ) @@ -204,11 +200,9 @@ class MonthlyDetailResponse(BaseModel): month: int income_items: list[RecurringItemDetail] expense_items: list[RecurringItemDetail] - savings_items: list[RecurringItemDetail] actuals_by_source: list[ActualsBySource] total_projected_income: float total_projected_expenses: float - total_projected_savings: float uncovered_actual: float gran_total_egresos: float net_balance: float @@ -228,11 +222,9 @@ def get_monthly_detail( month=data["month"], income_items=[RecurringItemDetail(**i) for i in data["income_items"]], expense_items=[RecurringItemDetail(**i) for i in data["expense_items"]], - savings_items=[RecurringItemDetail(**i) for i in data["savings_items"]], actuals_by_source=[ActualsBySource(**a) for a in data["actuals_by_source"]], total_projected_income=data["projected_income"], total_projected_expenses=data["projected_fixed_expenses"], - total_projected_savings=data["projected_savings"], uncovered_actual=data["uncovered_actual"], gran_total_egresos=data["gran_total_egresos"], net_balance=data["net_balance"], diff --git a/backend/app/api/v1/endpoints/salarios.py b/backend/app/api/v1/endpoints/salarios.py index 0772adc..c9c0ba4 100644 --- a/backend/app/api/v1/endpoints/salarios.py +++ b/backend/app/api/v1/endpoints/salarios.py @@ -12,6 +12,8 @@ from app.services.exchange_rate import get_converted_amount_expr router = APIRouter(prefix="/salarios", tags=["salarios"]) +SALARIO_TYPES = (TransactionType.SALARY, TransactionType.DEPOSITO) + class SalariosSummary(BaseModel): count: int @@ -28,7 +30,7 @@ def list_salarios( ): query = ( select(Transaction) - .where(Transaction.transaction_type == TransactionType.DEPOSITO) + .where(col(Transaction.transaction_type).in_(SALARIO_TYPES)) .order_by(col(Transaction.date).desc()) .offset(offset) .limit(limit) @@ -47,7 +49,7 @@ def salarios_summary( func.count(), func.coalesce(func.sum(amount_crc), 0), func.max(Transaction.date), - ).where(Transaction.transaction_type == TransactionType.DEPOSITO) + ).where(col(Transaction.transaction_type).in_(SALARIO_TYPES)) ).first() return SalariosSummary( count=result[0] if result else 0, diff --git a/backend/app/api/v1/endpoints/savings_accrual.py b/backend/app/api/v1/endpoints/savings_accrual.py new file mode 100644 index 0000000..7b24705 --- /dev/null +++ b/backend/app/api/v1/endpoints/savings_accrual.py @@ -0,0 +1,83 @@ +from datetime import datetime + +from fastapi import APIRouter, Depends, HTTPException, Path +from sqlmodel import Session, col, select + +from app.auth import get_current_user +from app.db import get_session +from app.models.models import ( + SavingsAccrual, + SavingsAccrualCreate, + SavingsAccrualRead, + SavingsAccrualUpdate, +) + +router = APIRouter(prefix="/savings-accrual", tags=["savings-accrual"]) + + +@router.get("/", response_model=list[SavingsAccrualRead]) +def list_accruals( + session: Session = Depends(get_session), + _user: str = Depends(get_current_user), +): + query = select(SavingsAccrual).order_by( + col(SavingsAccrual.year).desc(), col(SavingsAccrual.month).desc() + ) + return session.exec(query).all() + + +@router.post("/", response_model=SavingsAccrualRead, status_code=201) +def create_accrual( + data: SavingsAccrualCreate, + session: Session = Depends(get_session), + _user: str = Depends(get_current_user), +): + existing = session.exec( + select(SavingsAccrual).where( + SavingsAccrual.year == data.year, + SavingsAccrual.month == data.month, + ) + ).first() + if existing: + raise HTTPException( + status_code=409, + detail=f"Accrual for {data.year}-{data.month:02d} already exists (id={existing.id})", + ) + accrual = SavingsAccrual.model_validate(data) + accrual.applied_at = datetime.utcnow() + session.add(accrual) + session.commit() + session.refresh(accrual) + return accrual + + +@router.patch("/{accrual_id}", response_model=SavingsAccrualRead) +def update_accrual( + accrual_id: int, + data: SavingsAccrualUpdate, + session: Session = Depends(get_session), + _user: str = Depends(get_current_user), +): + accrual = session.get(SavingsAccrual, accrual_id) + if not accrual: + raise HTTPException(status_code=404, detail="Accrual not found") + update_data = data.model_dump(exclude_unset=True) + for key, value in update_data.items(): + setattr(accrual, key, value) + session.add(accrual) + session.commit() + session.refresh(accrual) + return accrual + + +@router.delete("/{accrual_id}", status_code=204) +def delete_accrual( + accrual_id: int = Path(...), + session: Session = Depends(get_session), + _user: str = Depends(get_current_user), +): + accrual = session.get(SavingsAccrual, accrual_id) + if not accrual: + raise HTTPException(status_code=404, detail="Accrual not found") + session.delete(accrual) + session.commit() diff --git a/backend/app/api/v1/endpoints/transactions.py b/backend/app/api/v1/endpoints/transactions.py index e2d8ddd..6d2fd35 100644 --- a/backend/app/api/v1/endpoints/transactions.py +++ b/backend/app/api/v1/endpoints/transactions.py @@ -199,14 +199,20 @@ def create_transaction( symbols = {Currency.CRC: "₡", Currency.USD: "$", Currency.EUR: "€"} symbol = symbols.get(tx.currency, tx.currency.value) amount_str = f"{symbol}{tx.amount:,.0f}" if tx.currency == Currency.CRC else f"{symbol}{tx.amount:,.2f}" - is_deposit = tx.transaction_type == TransactionType.DEPOSITO + is_income = tx.transaction_type in (TransactionType.DEPOSITO, TransactionType.SALARY) + is_salary = tx.transaction_type == TransactionType.SALARY + label = "salario" if is_salary else ("depósito" if is_income else tx.transaction_type.value.lower()) send_push_to_all( session, - title=f"{'🏦' if is_deposit else '💳'} {tx.merchant}", - body=f"{amount_str} — {tx.bank.value} {'depósito' if is_deposit else tx.transaction_type.value.lower()}", - url="/salarios" if is_deposit else "/budget", + title=f"{'🏦' if is_income else '💳'} {tx.merchant}", + body=f"{amount_str} — {tx.bank.value} {label}", + url="/salarios" if is_income else "/budget", ) + if is_salary: + from app.services.savings_accrual import maybe_apply_monthly_savings + maybe_apply_monthly_savings(session, tx) + return tx diff --git a/backend/app/api/v1/router.py b/backend/app/api/v1/router.py index 9b55042..8427c50 100644 --- a/backend/app/api/v1/router.py +++ b/backend/app/api/v1/router.py @@ -12,6 +12,7 @@ from app.api.v1.endpoints import ( notifications, pensions, salarios, + savings_accrual, settings, tokens, transactions, @@ -32,3 +33,4 @@ api_router.include_router(notifications.router) api_router.include_router(salarios.router) api_router.include_router(pensions.router) api_router.include_router(municipal_receipts.router) +api_router.include_router(savings_accrual.router) diff --git a/backend/app/db.py b/backend/app/db.py index d3c7525..80abf89 100644 --- a/backend/app/db.py +++ b/backend/app/db.py @@ -29,6 +29,52 @@ def run_migrations(): except Exception: conn.rollback() + try: + conn.execute( + text("ALTER TYPE transactiontype ADD VALUE IF NOT EXISTS 'SALARY'") + ) + conn.commit() + except Exception: + conn.rollback() + + try: + conn.execute( + text( + """ + CREATE TABLE IF NOT EXISTS savingsaccrual ( + id SERIAL PRIMARY KEY, + year INTEGER NOT NULL, + month INTEGER NOT NULL, + memp_amount DOUBLE PRECISION NOT NULL DEFAULT 200000, + mpat_amount DOUBLE PRECISION NOT NULL DEFAULT 200000, + trigger_transaction_id INTEGER, + applied_at TIMESTAMP NOT NULL DEFAULT NOW(), + notes TEXT, + CONSTRAINT savingsaccrual_year_month_key UNIQUE (year, month) + ) + """ + ) + ) + conn.commit() + except Exception: + conn.rollback() + + try: + conn.execute( + text( + """ + INSERT INTO savingsaccrual (year, month, memp_amount, mpat_amount, notes) + VALUES + (2026, 2, 200000, 200000, 'Seeded: historical baseline'), + (2026, 3, 200000, 200000, 'Seeded: historical baseline') + ON CONFLICT (year, month) DO NOTHING + """ + ) + ) + conn.commit() + except Exception: + conn.rollback() + def get_session(): with Session(engine) as session: diff --git a/backend/app/models/models.py b/backend/app/models/models.py index 5d5edb4..0e457bd 100644 --- a/backend/app/models/models.py +++ b/backend/app/models/models.py @@ -24,6 +24,7 @@ class TransactionType(str, enum.Enum): COMPRA = "COMPRA" DEVOLUCION = "DEVOLUCION" DEPOSITO = "DEPOSITO" + SALARY = "SALARY" class TransactionSource(str, enum.Enum): @@ -363,6 +364,39 @@ class BalanceOverrideRead(SQLModel): updated_at: datetime +# --- Savings Accrual --- + + +class SavingsAccrualBase(SQLModel): + year: int + month: int + memp_amount: float = 200000.0 + mpat_amount: float = 200000.0 + trigger_transaction_id: Optional[int] = None + notes: Optional[str] = None + + +class SavingsAccrual(SavingsAccrualBase, table=True): + __table_args__ = (UniqueConstraint("year", "month"),) + id: Optional[int] = Field(default=None, primary_key=True) + applied_at: datetime = Field(default_factory=datetime.utcnow) + + +class SavingsAccrualCreate(SavingsAccrualBase): + pass + + +class SavingsAccrualRead(SavingsAccrualBase): + id: int + applied_at: datetime + + +class SavingsAccrualUpdate(SQLModel): + memp_amount: Optional[float] = None + mpat_amount: Optional[float] = None + notes: Optional[str] = None + + # --- Municipal Receipt --- diff --git a/backend/app/services/budget_projection.py b/backend/app/services/budget_projection.py index d0ee40d..695cec4 100644 --- a/backend/app/services/budget_projection.py +++ b/backend/app/services/budget_projection.py @@ -1,7 +1,7 @@ import calendar from datetime import datetime -from sqlmodel import Session, func, select +from sqlmodel import Session, col, func, select from app.models.models import ( BalanceOverride, @@ -20,6 +20,9 @@ MAX_YEAR = 2030 FRESH_START_YEAR = 2026 FRESH_START_MONTH = 3 +# Income-like transaction types that should never be counted as expenses +INCOME_TYPES = (TransactionType.DEPOSITO, TransactionType.SALARY) + def get_effective_amount(item: RecurringItem, month: int, year: int) -> float | None: """Return the effective amount for a recurring item in a given month, or None if inactive.""" @@ -158,7 +161,7 @@ def compute_actuals_by_source( Transaction.date >= start, Transaction.date < end, Transaction.source == source, - Transaction.transaction_type != TransactionType.DEPOSITO, + col(Transaction.transaction_type).notin_(INCOME_TYPES), Transaction.deferred_to_next_cycle == False, # noqa: E712 ) ).one() @@ -167,7 +170,7 @@ def compute_actuals_by_source( Transaction.date >= prev_start, Transaction.date < prev_end, Transaction.source == source, - Transaction.transaction_type != TransactionType.DEPOSITO, + col(Transaction.transaction_type).notin_(INCOME_TYPES), Transaction.deferred_to_next_cycle == True, # noqa: E712 ) ).one() @@ -203,7 +206,7 @@ def compute_actuals_by_source( Transaction.date >= cal_start, Transaction.date < cal_end, Transaction.source == source, - Transaction.transaction_type != TransactionType.DEPOSITO, + col(Transaction.transaction_type).notin_(INCOME_TYPES), ) ).one() @@ -257,7 +260,7 @@ def compute_actuals_by_category( Transaction.date < cc_end, Transaction.source == TransactionSource.CREDIT_CARD, Transaction.category_id.is_not(None), # type: ignore[union-attr] - Transaction.transaction_type != TransactionType.DEPOSITO, + col(Transaction.transaction_type).notin_(INCOME_TYPES), Transaction.deferred_to_next_cycle == False, # noqa: E712 ) .group_by(Transaction.category_id, Transaction.transaction_type) @@ -277,7 +280,7 @@ def compute_actuals_by_category( Transaction.date < prev_end, Transaction.source == TransactionSource.CREDIT_CARD, Transaction.category_id.is_not(None), # type: ignore[union-attr] - Transaction.transaction_type != TransactionType.DEPOSITO, + col(Transaction.transaction_type).notin_(INCOME_TYPES), Transaction.deferred_to_next_cycle == True, # noqa: E712 ) .group_by(Transaction.category_id, Transaction.transaction_type) @@ -297,7 +300,7 @@ def compute_actuals_by_category( Transaction.date < cal_end, Transaction.source != TransactionSource.CREDIT_CARD, Transaction.category_id.is_not(None), # type: ignore[union-attr] - Transaction.transaction_type != TransactionType.DEPOSITO, + col(Transaction.transaction_type).notin_(INCOME_TYPES), ) .group_by(Transaction.category_id, Transaction.transaction_type) ).all() @@ -338,7 +341,7 @@ def compute_cc_by_category( Transaction.date >= cc_start, Transaction.date < cc_end, Transaction.source == TransactionSource.CREDIT_CARD, - Transaction.transaction_type != TransactionType.DEPOSITO, + col(Transaction.transaction_type).notin_(INCOME_TYPES), Transaction.deferred_to_next_cycle == False, # noqa: E712 ) .group_by(Transaction.category_id, Transaction.transaction_type) @@ -356,7 +359,7 @@ def compute_cc_by_category( Transaction.date >= prev_start, Transaction.date < prev_end, Transaction.source == TransactionSource.CREDIT_CARD, - Transaction.transaction_type != TransactionType.DEPOSITO, + col(Transaction.transaction_type).notin_(INCOME_TYPES), Transaction.deferred_to_next_cycle == True, # noqa: E712 ) .group_by(Transaction.category_id, Transaction.transaction_type) @@ -385,7 +388,10 @@ def compute_monthly_projection( ) -> dict: """Compute full monthly projection with no-double-count logic.""" items = session.exec( - select(RecurringItem).where(RecurringItem.is_active == True) # noqa: E712 + select(RecurringItem).where( + RecurringItem.is_active == True, # noqa: E712 + RecurringItem.item_type != RecurringItemType.SAVINGS, + ) ).all() actuals_by_source = compute_actuals_by_source(session, year, month) @@ -393,11 +399,9 @@ def compute_monthly_projection( income_items = [] expense_items = [] - savings_items = [] total_income = 0.0 total_fixed_expenses = 0.0 - total_savings = 0.0 for item in items: effective = get_effective_amount(item, month, year) @@ -431,10 +435,6 @@ def compute_monthly_projection( total_fixed_expenses += effective expense_items.append(detail) - elif item.item_type == RecurringItemType.SAVINGS: - savings_items.append(detail) - total_savings += effective - # Sum actuals from sources for categories NOT covered by recurring items covered_category_ids = { item.category_id @@ -476,7 +476,7 @@ def compute_monthly_projection( Transaction.date < cc_end, Transaction.source == TransactionSource.CREDIT_CARD, Transaction.category_id.is_(None), # type: ignore[union-attr] - Transaction.transaction_type != TransactionType.DEPOSITO, + col(Transaction.transaction_type).notin_(INCOME_TYPES), Transaction.deferred_to_next_cycle == False, # noqa: E712 ) .group_by(Transaction.transaction_type) @@ -491,7 +491,7 @@ def compute_monthly_projection( Transaction.date < prev_end, Transaction.source == TransactionSource.CREDIT_CARD, Transaction.category_id.is_(None), # type: ignore[union-attr] - Transaction.transaction_type != TransactionType.DEPOSITO, + col(Transaction.transaction_type).notin_(INCOME_TYPES), Transaction.deferred_to_next_cycle == True, # noqa: E712 ) .group_by(Transaction.transaction_type) @@ -506,7 +506,7 @@ def compute_monthly_projection( Transaction.date < cal_end, Transaction.source != TransactionSource.CREDIT_CARD, Transaction.category_id.is_(None), # type: ignore[union-attr] - Transaction.transaction_type != TransactionType.DEPOSITO, + col(Transaction.transaction_type).notin_(INCOME_TYPES), ) .group_by(Transaction.transaction_type) ).all() @@ -518,8 +518,6 @@ def compute_monthly_projection( cc_by_category = compute_cc_by_category(session, year, month) gran_total = total_fixed_expenses + uncovered_actual - # Savings are NOT deducted — they are already deducted from gross salary - # (the income amounts are net, post-savings) net_balance = total_income - gran_total return { @@ -527,7 +525,6 @@ def compute_monthly_projection( "month": month, "projected_income": total_income, "projected_fixed_expenses": total_fixed_expenses, - "projected_savings": total_savings, "actual_credit_card": actual_credit_card, "actual_cash": actual_cash, "actual_transfers": actual_transfers, @@ -536,7 +533,6 @@ def compute_monthly_projection( "net_balance": net_balance, "income_items": income_items, "expense_items": expense_items, - "savings_items": savings_items, "actuals_by_source": list(actuals_by_source.values()), "cc_by_category": cc_by_category, } diff --git a/backend/app/services/savings_accrual.py b/backend/app/services/savings_accrual.py new file mode 100644 index 0000000..b593c5d --- /dev/null +++ b/backend/app/services/savings_accrual.py @@ -0,0 +1,62 @@ +from sqlmodel import Session, select + +from app.models.models import ( + Account, + AccountType, + Bank, + SavingsAccrual, + Transaction, +) + +MEMP_MONTHLY = 200000.0 +MPAT_MONTHLY = 200000.0 + + +def _get_savings_account(session: Session, bank: Bank) -> Account | None: + return session.exec( + select(Account).where( + Account.account_type == AccountType.SAVINGS, + Account.bank == bank, + ) + ).first() + + +def maybe_apply_monthly_savings(session: Session, tx: Transaction) -> SavingsAccrual | None: + """Apply monthly savings contribution if this is the first salary of the month. + + Idempotent: if a SavingsAccrual row already exists for (year, month), do nothing. + Bumps MEMP and MPAT savings account balances and records the accrual. + """ + year = tx.date.year + month = tx.date.month + + existing = session.exec( + select(SavingsAccrual).where( + SavingsAccrual.year == year, + SavingsAccrual.month == month, + ) + ).first() + if existing: + return None + + memp = _get_savings_account(session, Bank.MEMP) + mpat = _get_savings_account(session, Bank.MPAT) + if memp is None or mpat is None: + return None + + memp.balance += MEMP_MONTHLY + mpat.balance += MPAT_MONTHLY + session.add(memp) + session.add(mpat) + + accrual = SavingsAccrual( + year=year, + month=month, + memp_amount=MEMP_MONTHLY, + mpat_amount=MPAT_MONTHLY, + trigger_transaction_id=tx.id, + ) + session.add(accrual) + session.commit() + session.refresh(accrual) + return accrual diff --git a/frontend/src/api.ts b/frontend/src/api.ts index b8586f2..7a650fa 100644 --- a/frontend/src/api.ts +++ b/frontend/src/api.ts @@ -160,7 +160,7 @@ export interface Transaction { // --- Budget / Recurring Items --- -export type RecurringItemType = 'INCOME' | 'EXPENSE' | 'SAVINGS'; +export type RecurringItemType = 'INCOME' | 'EXPENSE'; export type RecurringFrequency = 'WEEKLY' | 'MONTHLY' | 'QUARTERLY' | 'BIANNUAL' | 'YEARLY'; export interface RecurringItem { @@ -233,7 +233,6 @@ export interface MonthlyProjection { year: number; projected_income: number; projected_fixed_expenses: number; - projected_savings: number; actual_credit_card: number; actual_cash: number; actual_transfers: number; @@ -250,7 +249,6 @@ export interface YearlyProjection { months: MonthlyProjection[]; annual_income: number; annual_expenses: number; - annual_savings: number; annual_net: number; } @@ -259,17 +257,52 @@ export interface MonthlyDetail { 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; cc_by_category: { category_name: string; amount: number }[]; } +// --- Savings Accrual --- + +export interface SavingsAccrual { + id: number; + year: number; + month: number; + memp_amount: number; + mpat_amount: number; + trigger_transaction_id: number | null; + applied_at: string; + notes: string | null; +} + +export interface SavingsAccrualCreate { + year: number; + month: number; + memp_amount?: number; + mpat_amount?: number; + trigger_transaction_id?: number | null; + notes?: string | null; +} + +export interface SavingsAccrualUpdate { + memp_amount?: number; + mpat_amount?: number; + notes?: string | null; +} + +export const getSavingsAccruals = () => + api.get('/savings-accrual/'); +export const createSavingsAccrual = (data: SavingsAccrualCreate) => + api.post('/savings-accrual/', data); +export const updateSavingsAccrual = (id: number, data: SavingsAccrualUpdate) => + api.patch(`/savings-accrual/${id}`, data); +export const deleteSavingsAccrual = (id: number) => + api.delete(`/savings-accrual/${id}`); + // Budget API functions export const getRecurringItems = (params?: { item_type?: string; is_active?: boolean }) => api.get('/budget/recurring', { params }); diff --git a/frontend/src/components/budget/MonthlyDetail.tsx b/frontend/src/components/budget/MonthlyDetail.tsx index ce386f9..a5e2c4d 100644 --- a/frontend/src/components/budget/MonthlyDetail.tsx +++ b/frontend/src/components/budget/MonthlyDetail.tsx @@ -16,7 +16,6 @@ import { import { TrendingUp, TrendingDown, - PiggyBank, CreditCard, Banknote, ArrowLeftRight, @@ -346,8 +345,8 @@ export default function MonthlyDetail({ detail, loading, onNavigateToTransaction )} - {/* Actuals + Savings + Summary */} -
+ {/* Actuals + Summary */} +
{/* Cash & Transfer Actuals Card */} @@ -401,33 +400,6 @@ export default function MonthlyDetail({ detail, loading, onNavigateToTransaction - {/* Savings */} - {detail.savings_items.length > 0 && ( - - - - - Ahorro - - - - {detail.savings_items.map((item) => ( -
- {item.name} - {formatAmount(item.amount, 'CRC')} -
- ))} - -
- Total Ahorro - - {formatAmount(detail.total_projected_savings, 'CRC')} - -
-
-
- )} - {/* Summary */}
-
- Ahorro - - -{formatAmount(detail.total_projected_savings, 'CRC')} - -
Balance Neto diff --git a/frontend/src/components/budget/RecurringItemDialog.tsx b/frontend/src/components/budget/RecurringItemDialog.tsx index 9feb6b0..5585375 100644 --- a/frontend/src/components/budget/RecurringItemDialog.tsx +++ b/frontend/src/components/budget/RecurringItemDialog.tsx @@ -29,7 +29,6 @@ import { Plus, Trash2 } from 'lucide-react'; const TYPE_OPTIONS: { value: RecurringItemType; label: string }[] = [ { value: 'INCOME', label: 'Ingreso' }, { value: 'EXPENSE', label: 'Egreso' }, - { value: 'SAVINGS', label: 'Ahorro' }, ]; const FREQ_OPTIONS: { value: RecurringFrequency; label: string }[] = [ diff --git a/frontend/src/components/budget/RecurringItemsManager.tsx b/frontend/src/components/budget/RecurringItemsManager.tsx index 3cff817..ab1b2c0 100644 --- a/frontend/src/components/budget/RecurringItemsManager.tsx +++ b/frontend/src/components/budget/RecurringItemsManager.tsx @@ -17,7 +17,6 @@ import ConfirmDialog from '@/components/ConfirmDialog'; const TYPE_LABELS: Record = { INCOME: { label: 'Ingreso', variant: 'default' }, EXPENSE: { label: 'Egreso', variant: 'secondary' }, - SAVINGS: { label: 'Ahorro', variant: 'outline' }, }; const FREQ_LABELS: Record = { diff --git a/frontend/src/components/budget/YearlyOverview.tsx b/frontend/src/components/budget/YearlyOverview.tsx index 7b56acc..4b025a6 100644 --- a/frontend/src/components/budget/YearlyOverview.tsx +++ b/frontend/src/components/budget/YearlyOverview.tsx @@ -89,7 +89,6 @@ export default function YearlyOverview({ Egresos Fijos Otros Gastos Gran Total - Ahorro Acum. Anterior Neto Mes Balance Acum. @@ -132,9 +131,6 @@ export default function YearlyOverview({ {formatAmount(m.gran_total_egresos, 'CRC')} - - {formatAmount(m.projected_savings, 'CRC')} - +

Ingresos Anuales

@@ -63,14 +63,6 @@ export default function Proyecciones() {

- - -

Ahorro Anual

-

- {formatAmount(projection.annual_savings, 'CRC')} -

-
-

Balance Neto Anual