Páginas core
Dashboard, Transactions y Accounts en Targ-Apps-Finance — wireframes y lógica implementada.
Dashboard (pages/Dashboard.js, TAF-012)
Vista de resumen con jerarquía tipográfica (sin tarjetas KPI de color). Estilos en finance.css con prefijo .taf-dashboard-*.
| Sección | Contenido |
|---|---|
| Header | PageHeader — eyebrow «Resumen», título «Posición financiera», contexto del mes actual y CTA «Registrar movimiento» |
| Balance (firma visual) | Bloque .taf-dashboard-balance con borde cobre lateral (ledger): balance multi-moneda vía balanceByCurrency() |
| Flujo del mes | Tres columnas tipográficas: ingresos, gastos y resultado del mes (monthlyTotals()), tono positive/negative/neutral |
| Accesos rápidos | Enlaces a transacciones, reportes y cuentas con iconos Lucide (Icon) |
| Gráficos | Paneles .taf-dashboard-panel con ECharts embebidos (embedded): ingresos vs gastos por día del mes actual (granularity: "day") y patrimonio diario del mes (TrendChart, NetWorthChart) |
| Top categorías | Lista con barras de progreso cobre derivadas de topExpenseCategories |
| Últimos movimientos | Hasta cinco TransactionRow y enlace «Ver historial» (#/transactions) |
Componentes compartidos:
src/components/PageHeader.js— eyebrow, título,descriptiony slot de acciones (data-component="page-header").src/components/LucideIcon.js— iconos Lucide SVG inline (sinlucide-react).src/components/charts/TrendChart.jsysrc/components/charts/NetWorthChart.js— ECharts con tokens--cs-*; propembedded(sin doble.cs-carddentro del panel;figurea ancho completo con márgenes simétricos víabuildEmbeddedChartGrid()) ygranularity: "day" | "month";ResizeObserver+resize()post-layout enmountEcharts.js.
SummaryCard.js sigue disponible para otras vistas; el dashboard ya no lo usa.
Transactions (pages/Transactions.js, TAF-008 / TAF-009 / TAF-010 / TAF-011 / TAF-016)
Vista principal del historial con UI Chroma-Style, filtros reactivos y formularios separados para movimientos y transferencias.
| Bloque | Comportamiento |
|---|---|
| Header | PageHeader — título, lead descriptivo y CTA «Nuevo movimiento» (icono Lucide plus) |
| Filtros | TransactionFilters — barra compacta .taf-transactions-filters (Desde/Hasta/Cuenta/Categoría/Tipo/Buscar); oculta mientras no haya movimientos; botón «Limpiar» solo con filtros activos; estado en #/transactions?…; cada filtro es hookState; DatePicker enlaza min/max entre Desde/Hasta |
| Modal | TransactionModal — pestaña por defecto «Ingreso / Gasto»; formularios en modal con Select en posición fija (taf-select-menu--fixed) para evitar recorte y solapamiento |
| Formulario gasto/ingreso | TransactionForm + FormField; errores solo tras tocar el campo o intentar guardar; submit deshabilitado si hay errores; helpers de cuenta resuelta declarados antes de formData / useMemo |
| Formulario transferencia | TransferForm (TAF-016) — advertencia de saldo insuficiente sin bloquear submit; misma convención de orden y deps de señales que TransactionForm |
| Listado vacío | EmptyState (context: transactions) — «Sin movimientos» + CTA «Registrar primer movimiento»; visible sin depender de data-animate (opacidad base en CSS + animateEmptyState) |
| Grupos | Orden descendente por fecha; etiquetas «Hoy», «Ayer» o fecha legible (.taf-transactions-groups); filteredTransactions → listState con cadena useMemo (getters en deps); el DOM del listado usa For sobre listState().groups para re-renderizar al cambiar filtros (no capturar el array en un hijo de Show montado una vez) |
| Filas | TransactionRow (.taf-transactions-row) — monto prominente con tabular-nums; categoría con punto de color; transferencias con icono arrowLeftRight; acciones editar/eliminar con Lucide |
| Paginación | «Cargar más movimientos» incrementa el límite visible (20 por página) |
| Animaciones | Anime.js: animateListStagger en filas cuando cambian filteredTransactions / listState (useLayoutEffect con las mismas deps); refreshAnimations al cambiar de ruta en App.js; formularios (animateFormEnter), shake en campo inválido (shakeField) |
| Eliminación | ConfirmDialog → removeTransaction (borra el par si es transferencia) |
Componentes:
src/components/PageHeader.js— cabecera compartida (título, lead, acciones)src/components/LucideIcon.js— iconos SVG inline (sin lucide-react)src/components/FormField.js— label + control + error (Chroma-Style)src/components/TransactionForm.js— soloincome/expensesrc/components/TransferForm.js— transferencias entre cuentassrc/components/TransactionFilters.js— barra de filtrossrc/components/ConfirmDialog.js— diálogo reutilizable conShow; overlay a pantalla completa (backdrop+panelcentrado), botonescs-btn-outline/cs-btn-destructive, cierre con Escape o clic en el fondo
Estilos: src/styles/finance.css — namespace .taf-transactions-*.
Utilidades de fecha en logic/utils/dates.js: localDateKey, formatGroupDateLabel, todayISO.
Reactividad de filtros (Transactions)
Patrón canónico Chroma (useMemo encadenados + For con getter) — sin API computed/watch.
Causa del bug corregido: Show monta su función children una vez (rama bajo untracked). El listado capturaba listState().groups en ese primer montaje → snapshot estático. Los memos (filteredTransactions, listState) y el useLayoutEffect de animación sí se actualizaban; las filas en pantalla no.
Fix en pages/Transactions.js:
const filteredTransactions = useMemo(
() => filterTransactions(finance.transactions(), { /* getters de filtro */ }),
[finance.transactions, filterDateFrom, filterDateTo, filterCategoryId, filterAccountId, filterType, filterSearch],
);
const listState = useMemo(
() => groupVisibleTransactions(filteredTransactions(), visibleLimit()),
[filteredTransactions, visibleLimit],
);
useLayoutEffect(() => { /* animateListStagger */ }, [listState, filteredTransactions]);
// Show solo como compuerta vacío/con datos; el listado va en For:
e(For, { each: () => listState().groups, children: (group) => … });| Capa | Qué hace |
|---|---|
| Fuente | hookState por campo de filtro; TransactionFilters enlaza value: filterDateFrom (getter) |
| Derivado | filteredTransactions → listState — deps son getters, no valores |
| Vista | For({ each: () => listState().groups }) — binding reactivo al signal del memo |
| Efectos | Animación con deps [listState, filteredTransactions] |
Chroma: Derived State & Dynamic Updates · pitfall Show vs listas: Common Pitfalls §6.
Accounts (pages/Accounts.js, TAF-006)
Lista y formulario cableados a useFinance(). El saldo de cada tarjeta sale de getAccountBalance. Estilos en .taf-accounts-*.
| Bloque | Contenido |
|---|---|
| Cabecera | PageHeader + «Nueva cuenta» (plus) y toggle «Archivadas» (archive) si aplica |
| Lista | Grid .taf-accounts-grid con AccountCard (.taf-accounts-card) |
| Tarjeta | Icono Lucide por tipo (wallet, building2, creditCard), saldo tabular, badge «Principal» |
| Vacío | EmptyState con icono wallet y CTA «Crear primera cuenta» |
| Formulario | Panel .taf-accounts-form-panel → AccountForm → addAccount / updateAccount |
API de store para esta página (y para un listado filtrado futuro): activeAccounts, archivedAccounts, archiveAccount, restoreAccount, setDefaultAccount. removeAccount sigue bloqueado si la cuenta tiene transacciones.
Componentes: AccountCard.js y AccountForm.js (submit/inputs ya conectados).
Convención
- Atributos
data-page,data-slotydata-componentmarcan secciones para pruebas y estilos. - La lógica de negocio vive en
logic/store/(ledger.js,transactions.js,financeStore.js) y se consume conuseFinance()+useMemodonde aplica.