import { useState } from "react"; import { Link, useNavigate } from "react-router-dom"; import { ArrowRight, Sparkles } from "lucide-react"; import { useQuery } from "@tanstack/react-query"; import { getInstallmentPlans, getMonthlyDetail, getPensionFundSummary, getRecentTransactions, } from "@/lib/api"; import { formatAmount } from "@/lib/format"; import { MONTH_NAMES_ES_SHORT, currentBudgetCycle, formatShortDate, } from "@/lib/dates"; import { cn } from "@/lib/utils"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from "@/components/ui/card"; import { Input } from "@/components/ui/input"; import { Skeleton } from "@/components/ui/skeleton"; const CYCLE = currentBudgetCycle(); function StatCardShell({ to, ariaLabel, children, }: { to: string; ariaLabel: string; children: React.ReactNode; }) { return ( {children} ); } function StatValue({ value, loading, className, }: { value: string | null; loading: boolean; className?: string; }) { if (loading) return ; return ( {value ?? "—"} ); } function GastoDelCicloCard() { const q = useQuery({ queryKey: ["budget", "month", CYCLE.year, CYCLE.month], queryFn: () => getMonthlyDetail(CYCLE.year, CYCLE.month).then((r) => r.data), }); const spend = q.data ? q.data.actuals_by_source.reduce((sum, a) => sum + a.net, 0) : null; const balance = q.data?.net_balance ?? null; return ( Gasto del ciclo Ciclo al 18 {MONTH_NAMES_ES_SHORT[CYCLE.month].toLowerCase()} {balance !== null && (

Balance proyectado:{" "} = 0 ? "text-primary" : "text-destructive", )} > {balance >= 0 ? "+" : "-"} {formatAmount(balance, "CRC")}

)} {q.isError && (

No se pudo cargar el ciclo.

)}
); } function ProximasCuotasCard() { const q = useQuery({ queryKey: ["installment-plans"], queryFn: () => getInstallmentPlans().then((r) => r.data), }); const active = (q.data?.plans ?? []).filter( (p) => !p.is_completed && p.next_cuota_date, ); const inCycle = active.filter((p) => { const d = new Date(p.next_cuota_date!); return d >= CYCLE.start && d < CYCLE.end; }); const cycleTotal = inCycle.reduce((s, p) => s + p.installment_amount, 0); const upcoming = [...active] .sort( (a, b) => new Date(a.next_cuota_date!).getTime() - new Date(b.next_cuota_date!).getTime(), ) .slice(0, 3); return ( Próximas cuotas Tasa Cero en este ciclo {upcoming.length > 0 && (
    {upcoming.map((p) => (
  • {p.merchant} {formatShortDate(p.next_cuota_date!)} ·{" "} {formatAmount(p.installment_amount, p.currency)}
  • ))}
)} {q.data && (

Restante total:{" "} {formatAmount(q.data.total_remaining, "CRC")}

)}
); } function PensionCard() { const q = useQuery({ queryKey: ["pension-fund-summary"], queryFn: () => getPensionFundSummary().then((r) => r.data), }); const total = q.data ? q.data.reduce((s, f) => s + f.saldo_final, 0) : null; return ( Pensión Saldo total de fondos {q.data && q.data.length > 0 && (
    {q.data.map((f) => (
  • {f.fund} {formatAmount(f.saldo_final, "CRC")}
  • ))}
)}
); } function AskAssistantCard() { const [question, setQuestion] = useState(""); const navigate = useNavigate(); const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); const ask = question.trim(); navigate("/asistente", ask ? { state: { ask } } : undefined); }; return (
setQuestion(e.target.value)} placeholder="¿Cuánto gasté este ciclo?" aria-label="Pregúntale al asistente" className="flex-1" />
); } function RecentTransactionsCard() { const q = useQuery({ queryKey: ["transactions", "recent"], queryFn: () => getRecentTransactions(5).then((r) => r.data), }); return ( Últimas transacciones Ver todo {q.isPending ? (
{Array.from({ length: 5 }, (_, i) => ( ))}
) : q.isError ? (

No se pudieron cargar las transacciones.

) : (
    {(q.data ?? []).map((tx) => (
  • {tx.merchant} {formatShortDate(tx.date)} {tx.transaction_type === "COMPRA" ? "-" : "+"} {formatAmount(tx.amount, tx.currency)}
  • ))}
)}
); } export default function Inicio() { return (

Inicio

Tu panorama financiero de hoy.

); }