From 10d9bd209f586d3f07511a449d415883a58aff0c Mon Sep 17 00:00:00 2001 From: Carlos Escalante Date: Sat, 4 Jul 2026 11:02:26 -0600 Subject: [PATCH] Fix dashboard data findings from browser verification MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Próximas cuotas: headline is the upcoming billing batch (sum of every active plan's next cuota, using the rounding-absorbing last cuota when it's the pending one) — the in-cycle formula was ~always zero since cuotas bill on the 19th, day one of the next cycle. - useAgent must name the 'wealthysmart' agent (default crashed the page). - /transactions/recent excludes future-dated Tasa Cero cuotas. Co-Authored-By: Claude Opus 4.8 (1M context) --- backend/app/api/v1/endpoints/transactions.py | 8 +++- frontend/src/pages/Asistente.tsx | 2 +- frontend/src/pages/Inicio.tsx | 41 ++++++++++++-------- 3 files changed, 32 insertions(+), 19 deletions(-) diff --git a/backend/app/api/v1/endpoints/transactions.py b/backend/app/api/v1/endpoints/transactions.py index efc2027..c2d9c07 100644 --- a/backend/app/api/v1/endpoints/transactions.py +++ b/backend/app/api/v1/endpoints/transactions.py @@ -253,9 +253,15 @@ def recent_transactions( session: Session = Depends(get_session), _user: str = Depends(get_current_user), ): + from app.timeutil import utcnow + query = ( select(Transaction) - .where(Transaction.source == TransactionSource.CREDIT_CARD) + .where( + Transaction.source == TransactionSource.CREDIT_CARD, + # Tasa Cero generates future-dated cuotas; "recent" means billed. + Transaction.date <= utcnow(), + ) .order_by(col(Transaction.date).desc(), col(Transaction.id).desc()) .limit(limit) ) diff --git a/frontend/src/pages/Asistente.tsx b/frontend/src/pages/Asistente.tsx index 0a9723c..e259fc3 100644 --- a/frontend/src/pages/Asistente.tsx +++ b/frontend/src/pages/Asistente.tsx @@ -27,7 +27,7 @@ export default function Asistente() { // append it as a user message and run the agent once. const location = useLocation(); const navigate = useNavigate(); - const { agent } = useAgent(); + const { agent } = useAgent({ agentId: "wealthysmart" }); const { copilotkit } = useCopilotKit(); const sentRef = useRef(false); useEffect(() => { diff --git a/frontend/src/pages/Inicio.tsx b/frontend/src/pages/Inicio.tsx index c9d9964..d064023 100644 --- a/frontend/src/pages/Inicio.tsx +++ b/frontend/src/pages/Inicio.tsx @@ -132,18 +132,21 @@ function ProximasCuotasCard() { 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); + // One cuota per plan per cycle, so the sum of every active plan's next + // cuota is the load of the upcoming billing batch (next 19th). The last + // cuota absorbs BAC's rounding, so use it when it's the one pending. + const nextCuotaOf = (p: (typeof active)[number]) => + p.cuotas_billed === p.num_installments - 1 + ? p.last_installment_amount + : p.installment_amount; + const nextBatchTotal = active.reduce((s, p) => s + nextCuotaOf(p), 0); + const upcoming = [...active].sort( + (a, b) => + new Date(a.next_cuota_date!).getTime() - + new Date(b.next_cuota_date!).getTime(), + ); + const nextDate = upcoming[0]?.next_cuota_date; + const topThree = upcoming.slice(0, 3); return ( @@ -151,21 +154,25 @@ function ProximasCuotasCard() { Próximas cuotas - Tasa Cero en este ciclo + + {nextDate + ? `Tasa Cero · próximo cobro ${formatShortDate(nextDate)}` + : "Tasa Cero"} + - {upcoming.length > 0 && ( + {topThree.length > 0 && (
    - {upcoming.map((p) => ( + {topThree.map((p) => (
  • {p.merchant} {formatShortDate(p.next_cuota_date!)} ·{" "} - {formatAmount(p.installment_amount, p.currency)} + {formatAmount(nextCuotaOf(p), p.currency)}
  • ))}