diff --git a/frontend/src/components/AgentHomeClient.tsx b/frontend/src/components/AgentHomeClient.tsx
new file mode 100644
index 0000000..69a249b
--- /dev/null
+++ b/frontend/src/components/AgentHomeClient.tsx
@@ -0,0 +1,39 @@
+"use client";
+
+import { CopilotChat } from "@copilotkit/react-ui";
+import { Sparkles } from "lucide-react";
+
+const SUGGESTIONS = [
+ { title: "¿Cuál es mi saldo neto hoy?", message: "¿Cuál es mi saldo neto hoy?" },
+ { title: "¿Cuánto gasté en el ciclo anterior?", message: "¿Cuánto gasté en el ciclo anterior?" },
+ { title: "Últimas 10 transacciones", message: "Muéstrame mis últimas 10 transacciones" },
+ { title: "¿Cómo va mi pensión?", message: "¿Cómo va mi pensión?" },
+];
+
+export default function AgentHomeClient() {
+ return (
+
+
+
+
+ Asistente
+
+
+ Pregúntale a WealthySmart sobre tus finanzas.
+
+
+
+
+
+
+
+ );
+}
diff --git a/frontend/src/components/chat/ChatCards.tsx b/frontend/src/components/chat/ChatCards.tsx
new file mode 100644
index 0000000..d52fdf5
--- /dev/null
+++ b/frontend/src/components/chat/ChatCards.tsx
@@ -0,0 +1,456 @@
+import { TrendingDown, TrendingUp, Wallet, ArrowRightLeft } from "lucide-react";
+
+// ── helpers ──────────────────────────────────────────────────────────────────
+
+function fmtCRC(n: number | undefined | null) {
+ if (n == null) return "₡0";
+ return `₡${Math.round(n).toLocaleString("es-CR")}`;
+}
+
+function fmtCurrency(amount: number | undefined | null, currency: string | undefined | null) {
+ if (amount == null) return "₡0";
+ if (currency === "USD") return `$${amount.toFixed(2)}`;
+ if (currency === "EUR") return `€${amount.toFixed(2)}`;
+ return fmtCRC(amount);
+}
+
+function sourceLabel(source: string | undefined | null) {
+ if (!source) return "Otro";
+ const map: Record = {
+ CREDIT_CARD: "Tarjeta",
+ CASH: "Efectivo",
+ TRANSFER: "Transferencia",
+ SINPE: "SINPE",
+ OTHER: "Otro",
+ };
+ return map[source] ?? source.replace(/_/g, " ");
+}
+
+// ── Spinner ───────────────────────────────────────────────────────────────────
+
+function Spinner() {
+ return (
+
+ );
+}
+
+// ── SpendingSummaryCard ───────────────────────────────────────────────────────
+
+export interface SpendingBySource {
+ source: string;
+ total_crc: number;
+ count: number;
+}
+
+export interface SpendingByCategory {
+ category: string;
+ amount_crc: number;
+ count: number;
+}
+
+export interface SpendingSummaryArgs {
+ title?: string;
+ period?: string;
+ total_crc?: number;
+ by_source?: SpendingBySource[];
+ by_category?: SpendingByCategory[];
+}
+
+export function SpendingSummaryCard({
+ args,
+ status,
+}: {
+ args: SpendingSummaryArgs;
+ status: string;
+}) {
+ const { title, period, total_crc, by_source = [], by_category = [] } = args;
+ const max = Math.max(...by_category.map((c) => c.amount_crc), 1);
+
+ const PALETTE = [
+ "bg-primary",
+ "bg-chart-1",
+ "bg-chart-2",
+ "bg-chart-3",
+ "bg-chart-4",
+ "bg-chart-5",
+ ];
+
+ return (
+
+ {/* Header */}
+
+
+
+ {title ?? "Resumen de gastos"}
+
+ {period && (
+
{period}
+ )}
+
+
+
{fmtCRC(total_crc)}
+
total gastado
+
+
+
+ {/* By source */}
+ {by_source.length > 0 && (
+
+ {by_source.filter((s) => s?.source != null).map((s) => (
+
+
+ {sourceLabel(s.source)}
+
+
+ {fmtCRC(s.total_crc)}
+
+
+ {s.count} mov.
+
+
+ ))}
+
+ )}
+
+ {/* By category */}
+ {by_category.length > 0 && (
+
+
+ Por categoría
+
+
+ {by_category.filter((c) => c?.category != null).slice(0, 7).map((c, i) => (
+
+
+ {c.category}
+
+ {fmtCRC(c.amount_crc)}
+ ({c.count})
+
+
+
+
+ ))}
+
+
+ )}
+
+ {status === "inProgress" && (
+
+ Obteniendo datos…
+
+ )}
+
+ );
+}
+
+// ── TransactionListCard ───────────────────────────────────────────────────────
+
+export interface TransactionRow {
+ date: string;
+ merchant: string;
+ amount: number;
+ currency: string;
+ category: string | null;
+ source: string;
+}
+
+export interface TransactionListArgs {
+ title?: string;
+ transactions?: TransactionRow[];
+}
+
+const SOURCE_ICON: Record = {
+ CREDIT_CARD: ,
+ TRANSFER: ,
+};
+
+export function TransactionListCard({
+ args,
+ status,
+}: {
+ args: TransactionListArgs;
+ status: string;
+}) {
+ const { title, transactions = [] } = args;
+
+ return (
+
+
+ {title ?? "Transacciones"}
+
+
+ {transactions.length === 0 && status !== "inProgress" && (
+
Sin transacciones.
+ )}
+
+
+ {transactions.map((t, i) => (
+
+
+
+ {SOURCE_ICON[t.source] ?? }
+
+
+
{t.merchant}
+
+ {t.date}
+ {t.category && ` · ${t.category}`}
+
+
+
+
+ {fmtCurrency(t.amount, t.currency)}
+
+
+ ))}
+
+
+ {status === "inProgress" && (
+
+ Cargando…
+
+ )}
+
+ );
+}
+
+// ── NetWorthCard ──────────────────────────────────────────────────────────────
+
+export interface AccountRow {
+ bank: string;
+ label: string;
+ balance_crc: number;
+ account_type: string;
+ currency: string;
+}
+
+export interface NetWorthArgs {
+ total_assets_crc?: number;
+ total_liabilities_crc?: number;
+ net_worth_crc?: number;
+ accounts?: AccountRow[];
+}
+
+export function NetWorthCard({
+ args,
+ status,
+}: {
+ args: NetWorthArgs;
+ status: string;
+}) {
+ const {
+ total_assets_crc = 0,
+ total_liabilities_crc = 0,
+ net_worth_crc = 0,
+ accounts = [],
+ } = args;
+
+ const isPositive = net_worth_crc >= 0;
+
+ return (
+
+ {/* Net worth headline */}
+
+
+
+ Patrimonio neto
+
+
+ {isPositive ? (
+
+ ) : (
+
+ )}
+
+ {fmtCRC(net_worth_crc)}
+
+
+
+
+
+ Activos
+
+ {fmtCRC(total_assets_crc)}
+
+
+
+ Pasivos
+
+ {fmtCRC(total_liabilities_crc)}
+
+
+
+
+
+ {/* Asset bar */}
+ {total_assets_crc + total_liabilities_crc > 0 && (
+
+ )}
+
+ {/* Accounts */}
+ {accounts.length > 0 && (
+
+ {accounts.map((a, i) => (
+
+
+
{a.label || a.bank}
+
+ {a.bank} · {a.account_type} · {a.currency}
+
+
+
= 0 ? "text-green-500" : "text-destructive"}`}
+ >
+ {fmtCRC(a.balance_crc)}
+
+
+ ))}
+
+ )}
+
+ {status === "inProgress" && (
+
+ Calculando…
+
+ )}
+
+ );
+}
+
+// ── BudgetMonthCard ───────────────────────────────────────────────────────────
+
+const MONTH_NAMES = [
+ "", "Enero", "Febrero", "Marzo", "Abril", "Mayo", "Junio",
+ "Julio", "Agosto", "Septiembre", "Octubre", "Noviembre", "Diciembre",
+];
+
+export interface BudgetMonthArgs {
+ year?: number;
+ month?: number;
+ projected_income_crc?: number;
+ projected_expenses_crc?: number;
+ actual_total_crc?: number;
+ net_balance_crc?: number;
+}
+
+export function BudgetMonthCard({
+ args,
+ status,
+}: {
+ args: BudgetMonthArgs;
+ status: string;
+}) {
+ const {
+ year,
+ month,
+ projected_income_crc = 0,
+ projected_expenses_crc = 0,
+ actual_total_crc = 0,
+ net_balance_crc = 0,
+ } = args;
+
+ const usedPct =
+ projected_expenses_crc > 0
+ ? Math.min(Math.round((actual_total_crc / projected_expenses_crc) * 100), 100)
+ : 0;
+
+ const isOver = actual_total_crc > projected_expenses_crc;
+
+ return (
+
+
+
+
+ Presupuesto
+
+ {month != null && year != null && (
+
+ {MONTH_NAMES[month]} {year}
+
+ )}
+
+
= 0 ? "text-green-500" : "text-destructive"}`}
+ >
+ {fmtCRC(net_balance_crc)}
+
+ balance neto
+
+
+
+
+ {/* Rows */}
+
+
+ Ingresos proyectados
+
+ {fmtCRC(projected_income_crc)}
+
+
+
+ Gastos proyectados
+
+ {fmtCRC(projected_expenses_crc)}
+
+
+
+ Gastado real
+
+ {fmtCRC(actual_total_crc)}
+
+
+
+
+ {/* Progress bar */}
+ {projected_expenses_crc > 0 && (
+
+
+ Ejecución presupuestaria
+
+ {usedPct}%
+
+
+
+
+ )}
+
+ {status === "inProgress" && (
+
+ Cargando…
+
+ )}
+
+ );
+}
diff --git a/frontend/src/pages/Asistente.tsx b/frontend/src/pages/Asistente.tsx
new file mode 100644
index 0000000..1bebb47
--- /dev/null
+++ b/frontend/src/pages/Asistente.tsx
@@ -0,0 +1,80 @@
+import { CopilotChat, useConfigureSuggestions } from "@copilotkit/react-core/v2";
+import { useCopilotAction } from "@copilotkit/react-core";
+import { Sparkles } from "lucide-react";
+import { SpendingSummaryCard, type SpendingSummaryArgs } from "@/components/chat/ChatCards";
+
+const STATIC_SUGGESTIONS = {
+ available: "before-first-message" as const,
+ suggestions: [
+ { title: "¿Cuál es mi saldo neto hoy?", message: "¿Cuál es mi saldo neto hoy?" },
+ { title: "¿Cuánto gasté en el ciclo anterior?", message: "¿Cuánto gasté en el ciclo anterior?" },
+ { title: "Últimas 10 transacciones", message: "Muéstrame mis últimas 10 transacciones" },
+ { title: "¿Cómo va mi pensión?", message: "¿Cómo va mi pensión?" },
+ ],
+};
+
+export default function Asistente() {
+ useConfigureSuggestions(STATIC_SUGGESTIONS);
+
+ useCopilotAction({
+ name: "render_spending_summary",
+ description:
+ "Render a visual spending summary card with source breakdown and category progress bars. " +
+ "Call this for any cycle summary, spending totals, or category breakdown.",
+ parameters: [
+ { name: "title", type: "string", description: "Card title (e.g. 'Ciclo actual')" },
+ { name: "period", type: "string", description: "Human-readable period (e.g. '18 mar → 18 abr 2026')" },
+ { name: "total_crc", type: "number", description: "Total spend in CRC" },
+ {
+ name: "by_source",
+ type: "object[]",
+ description: "Breakdown by payment source",
+ attributes: [
+ { name: "source", type: "string" },
+ { name: "total_crc", type: "number" },
+ { name: "count", type: "number" },
+ ],
+ },
+ {
+ name: "by_category",
+ type: "object[]",
+ required: false,
+ description: "Top spending categories with CRC amounts",
+ attributes: [
+ { name: "category", type: "string" },
+ { name: "amount_crc", type: "number" },
+ { name: "count", type: "number" },
+ ],
+ },
+ ],
+ handler: async () => "ok",
+ render: (props) => (
+
+ ),
+ });
+
+ return (
+
+
+
+
+ Asistente
+
+
+ Pregúntale a WealthySmart sobre tus finanzas.
+
+
+
+
+
+
+
+ );
+}