feat: initial commit - finanzas app
Complete personal finance management application with: - Dashboard with financial metrics and alerts - Credit card management and payments - Fixed and variable debt tracking - Monthly budget planning - Intelligent alert system - Responsive design with Tailwind CSS Tech stack: Next.js 14, TypeScript, Zustand, Recharts 🤖 Generated with [Claude Code](https://claude.com/claude-code)
This commit is contained in:
189
lib/utils.ts
Normal file
189
lib/utils.ts
Normal file
@@ -0,0 +1,189 @@
|
||||
import { clsx, type ClassValue } from 'clsx'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import { FixedDebt, VariableDebt, CardPayment } from './types'
|
||||
|
||||
/**
|
||||
* Combina clases de Tailwind CSS usando clsx y tailwind-merge
|
||||
* Permite combinar múltiples clases condicionalmente
|
||||
*/
|
||||
export function cn(...inputs: ClassValue[]): string {
|
||||
return twMerge(clsx(inputs))
|
||||
}
|
||||
|
||||
/**
|
||||
* Formatea un número como moneda (pesos argentinos/USD)
|
||||
* Ejemplo: 1500.50 -> "$ 1.500,50"
|
||||
*/
|
||||
export function formatCurrency(amount: number): string {
|
||||
const formatter = new Intl.NumberFormat('es-AR', {
|
||||
style: 'currency',
|
||||
currency: 'ARS',
|
||||
minimumFractionDigits: 2,
|
||||
maximumFractionDigits: 2,
|
||||
})
|
||||
return formatter.format(amount)
|
||||
}
|
||||
|
||||
/**
|
||||
* Formatea una fecha en formato legible en español
|
||||
* Ejemplo: "28 de enero de 2026"
|
||||
*/
|
||||
export function formatDate(date: string | Date): string {
|
||||
const d = typeof date === 'string' ? new Date(date) : date
|
||||
const formatter = new Intl.DateTimeFormat('es-AR', {
|
||||
day: 'numeric',
|
||||
month: 'long',
|
||||
year: 'numeric',
|
||||
})
|
||||
return formatter.format(d)
|
||||
}
|
||||
|
||||
/**
|
||||
* Formatea una fecha en formato corto
|
||||
* Ejemplo: "28/01/2026"
|
||||
*/
|
||||
export function formatShortDate(date: string | Date): string {
|
||||
const d = typeof date === 'string' ? new Date(date) : date
|
||||
const formatter = new Intl.DateTimeFormat('es-AR', {
|
||||
day: '2-digit',
|
||||
month: '2-digit',
|
||||
year: 'numeric',
|
||||
})
|
||||
return formatter.format(d)
|
||||
}
|
||||
|
||||
/**
|
||||
* Calcula los días hasta una fecha específica
|
||||
* Retorna un número negativo si la fecha ya pasó
|
||||
*/
|
||||
export function getDaysUntil(date: string | Date): number {
|
||||
const targetDate = typeof date === 'string' ? new Date(date) : date
|
||||
const today = new Date()
|
||||
|
||||
// Reset hours to compare only dates
|
||||
const target = new Date(targetDate.getFullYear(), targetDate.getMonth(), targetDate.getDate())
|
||||
const current = new Date(today.getFullYear(), today.getMonth(), today.getDate())
|
||||
|
||||
const diffTime = target.getTime() - current.getTime()
|
||||
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24))
|
||||
|
||||
return diffDays
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtiene la próxima fecha para un día específico del mes
|
||||
* Si el día ya pasó este mes, devuelve el del mes siguiente
|
||||
*/
|
||||
export function getNextDateByDay(dayOfMonth: number): Date {
|
||||
const today = new Date()
|
||||
const currentYear = today.getFullYear()
|
||||
const currentMonth = today.getMonth()
|
||||
const currentDay = today.getDate()
|
||||
|
||||
let targetYear = currentYear
|
||||
let targetMonth = currentMonth
|
||||
|
||||
// Si el día ya pasó este mes, ir al siguiente mes
|
||||
if (currentDay > dayOfMonth) {
|
||||
targetMonth += 1
|
||||
if (targetMonth > 11) {
|
||||
targetMonth = 0
|
||||
targetYear += 1
|
||||
}
|
||||
}
|
||||
|
||||
// Ajustar si el día no existe en el mes objetivo (ej: 31 de febrero)
|
||||
const lastDayOfMonth = new Date(targetYear, targetMonth + 1, 0).getDate()
|
||||
const targetDay = Math.min(dayOfMonth, lastDayOfMonth)
|
||||
|
||||
return new Date(targetYear, targetMonth, targetDay)
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtiene el nombre del mes en español
|
||||
* El mes debe ser 1-12 (enero = 1)
|
||||
*/
|
||||
export function getMonthName(month: number): string {
|
||||
const monthNames = [
|
||||
'enero',
|
||||
'febrero',
|
||||
'marzo',
|
||||
'abril',
|
||||
'mayo',
|
||||
'junio',
|
||||
'julio',
|
||||
'agosto',
|
||||
'septiembre',
|
||||
'octubre',
|
||||
'noviembre',
|
||||
'diciembre',
|
||||
]
|
||||
|
||||
if (month < 1 || month > 12) {
|
||||
throw new Error('El mes debe estar entre 1 y 12')
|
||||
}
|
||||
|
||||
return monthNames[month - 1]
|
||||
}
|
||||
|
||||
/**
|
||||
* Calcula el total de deudas fijas no pagadas
|
||||
*/
|
||||
export function calculateTotalFixedDebts(debts: FixedDebt[]): number {
|
||||
return debts
|
||||
.filter((debt) => !debt.isPaid)
|
||||
.reduce((total, debt) => total + debt.amount, 0)
|
||||
}
|
||||
|
||||
/**
|
||||
* Calcula el total de deudas variables no pagadas
|
||||
*/
|
||||
export function calculateTotalVariableDebts(debts: VariableDebt[]): number {
|
||||
return debts
|
||||
.filter((debt) => !debt.isPaid)
|
||||
.reduce((total, debt) => total + debt.amount, 0)
|
||||
}
|
||||
|
||||
/**
|
||||
* Calcula el total de pagos de tarjeta
|
||||
* Opcionalmente filtrados por cardId
|
||||
*/
|
||||
export function calculateCardPayments(
|
||||
payments: CardPayment[],
|
||||
cardId?: string
|
||||
): number {
|
||||
const filteredPayments = cardId
|
||||
? payments.filter((payment) => payment.cardId === cardId)
|
||||
: payments
|
||||
|
||||
return filteredPayments.reduce((total, payment) => total + payment.amount, 0)
|
||||
}
|
||||
|
||||
/**
|
||||
* Calcula la próxima fecha de cierre de tarjeta
|
||||
* Si el día de cierre ya pasó este mes, devuelve el del mes siguiente
|
||||
*/
|
||||
export function calculateNextClosingDate(closingDay: number): Date {
|
||||
return getNextDateByDay(closingDay)
|
||||
}
|
||||
|
||||
/**
|
||||
* Calcula la próxima fecha de vencimiento de tarjeta
|
||||
* Si el día de vencimiento ya pasó este mes, devuelve el del mes siguiente
|
||||
*/
|
||||
export function calculateNextDueDate(dueDay: number): Date {
|
||||
return getNextDateByDay(dueDay)
|
||||
}
|
||||
|
||||
/**
|
||||
* Calcula el porcentaje de utilización de una tarjeta de crédito
|
||||
* Retorna un valor entre 0 y 100
|
||||
*/
|
||||
export function getCardUtilization(balance: number, limit: number): number {
|
||||
if (limit <= 0) {
|
||||
return 0
|
||||
}
|
||||
|
||||
const utilization = (balance / limit) * 100
|
||||
return Math.min(Math.max(utilization, 0), 100)
|
||||
}
|
||||
Reference in New Issue
Block a user