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,224 @@
'use client'
import { useState } from 'react'
import { useFinanzasStore } from '@/lib/store'
import { FixedDebt, VariableDebt } from '@/lib/types'
import { DebtCard } from './DebtCard'
import { FixedDebtForm } from './FixedDebtForm'
import { VariableDebtForm } from './VariableDebtForm'
import { Plus, Wallet } from 'lucide-react'
import { cn, formatCurrency, calculateTotalFixedDebts, calculateTotalVariableDebts } from '@/lib/utils'
type DebtType = 'fixed' | 'variable'
export function DebtSection() {
const [activeTab, setActiveTab] = useState<DebtType>('fixed')
const [isModalOpen, setIsModalOpen] = useState(false)
const [editingDebt, setEditingDebt] = useState<FixedDebt | VariableDebt | null>(null)
const {
fixedDebts,
variableDebts,
addFixedDebt,
updateFixedDebt,
deleteFixedDebt,
toggleFixedDebtPaid,
addVariableDebt,
updateVariableDebt,
deleteVariableDebt,
toggleVariableDebtPaid,
} = useFinanzasStore()
const currentDebts = activeTab === 'fixed' ? fixedDebts : variableDebts
const totalUnpaid = activeTab === 'fixed'
? calculateTotalFixedDebts(fixedDebts)
: calculateTotalVariableDebts(variableDebts)
const handleAdd = () => {
setEditingDebt(null)
setIsModalOpen(true)
}
const handleEdit = (debt: FixedDebt | VariableDebt) => {
setEditingDebt(debt)
setIsModalOpen(true)
}
const handleCloseModal = () => {
setIsModalOpen(false)
setEditingDebt(null)
}
const handleSubmitFixed = (data: Omit<FixedDebt, 'id' | 'isPaid'>) => {
if (editingDebt?.id) {
updateFixedDebt(editingDebt.id, data)
} else {
addFixedDebt({ ...data, isPaid: false })
}
handleCloseModal()
}
const handleSubmitVariable = (data: Omit<VariableDebt, 'id' | 'isPaid'>) => {
if (editingDebt?.id) {
updateVariableDebt(editingDebt.id, data)
} else {
addVariableDebt({ ...data, isPaid: false })
}
handleCloseModal()
}
const handleDelete = (debt: FixedDebt | VariableDebt) => {
if (confirm('¿Estás seguro de que deseas eliminar esta deuda?')) {
if (activeTab === 'fixed') {
deleteFixedDebt(debt.id)
} else {
deleteVariableDebt(debt.id)
}
}
}
const handleTogglePaid = (debt: FixedDebt | VariableDebt) => {
if (activeTab === 'fixed') {
toggleFixedDebtPaid(debt.id)
} else {
toggleVariableDebtPaid(debt.id)
}
}
const paidCount = currentDebts.filter(d => d.isPaid).length
const unpaidCount = currentDebts.filter(d => !d.isPaid).length
return (
<div className="bg-slate-900 min-h-screen p-6">
<div className="max-w-4xl mx-auto">
{/* Header */}
<div className="flex items-center justify-between mb-6">
<div>
<h1 className="text-2xl font-bold text-white">Deudas</h1>
<p className="text-slate-400 text-sm mt-1">
Gestiona tus gastos fijos y variables
</p>
</div>
<button
onClick={handleAdd}
className={cn(
'flex items-center gap-2 px-4 py-2 bg-blue-600 text-white rounded-lg font-medium',
'hover:bg-blue-500 transition-colors'
)}
>
<Plus className="w-4 h-4" />
Agregar
</button>
</div>
{/* Summary Cards */}
<div className="grid grid-cols-3 gap-4 mb-6">
<div className="bg-slate-800 border border-slate-700/50 rounded-lg p-4">
<p className="text-slate-400 text-sm">Total pendiente</p>
<p className="text-xl font-mono font-semibold text-emerald-400 mt-1">
{formatCurrency(totalUnpaid)}
</p>
</div>
<div className="bg-slate-800 border border-slate-700/50 rounded-lg p-4">
<p className="text-slate-400 text-sm">Pagadas</p>
<p className="text-xl font-semibold text-blue-400 mt-1">
{paidCount}
</p>
</div>
<div className="bg-slate-800 border border-slate-700/50 rounded-lg p-4">
<p className="text-slate-400 text-sm">Pendientes</p>
<p className="text-xl font-semibold text-orange-400 mt-1">
{unpaidCount}
</p>
</div>
</div>
{/* Tabs */}
<div className="flex gap-2 mb-6">
<button
onClick={() => setActiveTab('fixed')}
className={cn(
'px-4 py-2 rounded-lg font-medium transition-colors',
activeTab === 'fixed'
? 'bg-blue-600 text-white'
: 'bg-slate-800 text-slate-400 hover:bg-slate-700 hover:text-white'
)}
>
Fijas ({fixedDebts.length})
</button>
<button
onClick={() => setActiveTab('variable')}
className={cn(
'px-4 py-2 rounded-lg font-medium transition-colors',
activeTab === 'variable'
? 'bg-blue-600 text-white'
: 'bg-slate-800 text-slate-400 hover:bg-slate-700 hover:text-white'
)}
>
Variables ({variableDebts.length})
</button>
</div>
{/* Debt List */}
<div className="space-y-3">
{currentDebts.length === 0 ? (
<div className="text-center py-16 bg-slate-800/50 border border-slate-700/50 rounded-lg">
<Wallet className="w-12 h-12 text-slate-600 mx-auto mb-4" />
<h3 className="text-lg font-medium text-slate-300">
No hay deudas {activeTab === 'fixed' ? 'fijas' : 'variables'}
</h3>
<p className="text-slate-500 mt-2">
Haz clic en "Agregar" para crear una nueva deuda
</p>
</div>
) : (
currentDebts.map((debt) => (
<DebtCard
key={debt.id}
debt={debt}
type={activeTab}
onTogglePaid={() => handleTogglePaid(debt)}
onEdit={() => handleEdit(debt)}
onDelete={() => handleDelete(debt)}
/>
))
)}
</div>
</div>
{/* Modal */}
{isModalOpen && (
<div className="fixed inset-0 z-50 flex items-center justify-center p-4">
<div
className="absolute inset-0 bg-black/60 backdrop-blur-sm"
onClick={handleCloseModal}
/>
<div className="relative bg-slate-900 border border-slate-700 rounded-xl shadow-2xl w-full max-w-md max-h-[90vh] overflow-y-auto">
<div className="p-6">
<h2 className="text-xl font-bold text-white mb-4">
{editingDebt
? 'Editar deuda'
: activeTab === 'fixed'
? 'Nueva deuda fija'
: 'Nueva deuda variable'}
</h2>
{activeTab === 'fixed' ? (
<FixedDebtForm
initialData={editingDebt as Partial<FixedDebt> | undefined}
onSubmit={handleSubmitFixed}
onCancel={handleCloseModal}
/>
) : (
<VariableDebtForm
initialData={editingDebt as Partial<VariableDebt> | undefined}
onSubmit={handleSubmitVariable}
onCancel={handleCloseModal}
/>
)}
</div>
</div>
</div>
)}
</div>
)
}