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:
renato97
2026-01-29 00:00:32 +00:00
commit 712b06f118
65 changed files with 8556 additions and 0 deletions

View File

@@ -0,0 +1,168 @@
'use client'
import { ArrowDownLeft, ArrowUpRight, CreditCard, Wallet } from 'lucide-react'
import { useFinanzasStore } from '@/lib/store'
import { formatCurrency, formatShortDate } from '@/lib/utils'
import { cn } from '@/lib/utils'
interface RecentActivityProps {
limit?: number
}
interface ActivityItem {
id: string
type: 'fixed_debt' | 'variable_debt' | 'card_payment'
title: string
amount: number
date: string
description?: string
}
export function RecentActivity({ limit = 5 }: RecentActivityProps) {
const { fixedDebts, variableDebts, cardPayments, creditCards } =
useFinanzasStore()
// Combinar todas las actividades
const activities: ActivityItem[] = [
// Deudas fijas recientes
...fixedDebts.slice(0, limit).map((debt) => ({
id: debt.id,
type: 'fixed_debt' as const,
title: debt.name,
amount: debt.amount,
date: new Date().toISOString(), // Usar fecha actual ya que fixedDebt no tiene fecha de creación
description: `Vence el día ${debt.dueDay}`,
})),
// Deudas variables recientes
...variableDebts.slice(0, limit).map((debt) => ({
id: debt.id,
type: 'variable_debt' as const,
title: debt.name,
amount: debt.amount,
date: debt.date,
description: debt.notes,
})),
// Pagos de tarjetas recientes
...cardPayments.slice(0, limit).map((payment) => {
const card = creditCards.find((c) => c.id === payment.cardId)
return {
id: payment.id,
type: 'card_payment' as const,
title: `Pago - ${card?.name || 'Tarjeta'}`,
amount: payment.amount,
date: payment.date,
description: payment.description,
}
}),
]
// Ordenar por fecha (más recientes primero)
const sortedActivities = activities
.sort(
(a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()
)
.slice(0, limit)
// Configuración por tipo de actividad
const activityConfig = {
fixed_debt: {
icon: Wallet,
label: 'Deuda Fija',
color: 'text-amber-400',
bgColor: 'bg-amber-400/10',
},
variable_debt: {
icon: ArrowUpRight,
label: 'Gasto',
color: 'text-rose-400',
bgColor: 'bg-rose-400/10',
},
card_payment: {
icon: CreditCard,
label: 'Pago Tarjeta',
color: 'text-blue-400',
bgColor: 'bg-blue-400/10',
},
}
if (sortedActivities.length === 0) {
return (
<div className="rounded-xl border border-slate-700 bg-slate-800 p-6">
<h3 className="mb-4 text-lg font-semibold text-white">
Actividad Reciente
</h3>
<div className="flex h-32 items-center justify-center">
<p className="text-slate-500">No hay actividad reciente</p>
</div>
</div>
)
}
return (
<div className="rounded-xl border border-slate-700 bg-slate-800 p-6">
<h3 className="mb-4 text-lg font-semibold text-white">
Actividad Reciente
</h3>
<div className="space-y-3">
{sortedActivities.map((activity) => {
const config = activityConfig[activity.type]
const Icon = config.icon
return (
<div
key={activity.id}
className="flex items-center gap-4 rounded-lg border border-slate-700/50 bg-slate-700/30 p-4 transition-colors hover:bg-slate-700/50"
>
{/* Icono */}
<div
className={cn(
'flex h-10 w-10 items-center justify-center rounded-full',
config.bgColor
)}
>
<Icon className={cn('h-5 w-5', config.color)} />
</div>
{/* Contenido */}
<div className="flex-1 min-w-0">
<div className="flex items-center justify-between gap-2">
<h4 className="truncate font-medium text-white">
{activity.title}
</h4>
<span
className={cn(
'shrink-0 font-mono font-medium',
activity.type === 'card_payment'
? 'text-emerald-400'
: 'text-rose-400'
)}
>
{activity.type === 'card_payment' ? '+' : '-'}
{formatCurrency(activity.amount)}
</span>
</div>
<div className="mt-1 flex items-center gap-2 text-sm text-slate-400">
<span className={cn('text-xs', config.color)}>
{config.label}
</span>
<span></span>
<span>{formatShortDate(activity.date)}</span>
{activity.description && (
<>
<span></span>
<span className="truncate">{activity.description}</span>
</>
)}
</div>
</div>
</div>
)
})}
</div>
</div>
)
}