Architecture
Diseño interno de Targ-Apps-Finance — capas, flujo de render, router y stores.
Visión general
┌─ index.html ────────────────────────┐
│ <div id="app"> │
│ <script src="./src/main.js"> │
└──────────────┬──────────────────────┘
│ hydratePersist() then dynamic import App
│ render(App, #app)
┌──────────────┴──────────────────────┐
│ src/App.js (shell) │
│ ToastProvider → SettingsProvider │
│ → FinanceProvider → AppShell │
│ ├─ useRouter() │
│ └─ Layout + Show por ruta (páginas │
│ montadas una vez, dentro del │
│ Provider) │
└──────────────┬──────────────────────┘
│
┌────────┴────────┐
│ │
┌─────┴─────┐ ┌───────┴────────┐
│ logic/ │ │ pages/ │
│ router.js │ │ Dashboard.js │
│ store/ │ │ Transactions… │
│ persist. │ │ (store-backed) │
└───────────┘ └────────────────┘
│
▼
pkg/chroma.js → chroma.wasm (Rust)Flujo de render
src/main.jsllamaawait hydratePersist()y después importaApp(render(App, #app)).AppanidaToastProvider,SettingsProvideryFinanceProvider, luego montaAppShell.AppShellinvocauseRouter(). El guard vive enFinanceProvideryuseRouter(syncAppRoute()al montar y enhashchange). Orden: hash vacío →#/authsin sesión →#/onboardingsin cuentas →#/dashboard.LayoutmontaNavBar, unShowpor ruta (cada página se crea una vez bajountrackedmientrasFinanceProvidersigue en el stack) y<Toast/>.- Las páginas consumen
useFinance(),useSettings()y derivadosuseMemodel store. No hay child reactivo() => e(Page): re-invocaría el componente fuera de la ventana de contexto.
Capas y dependencias
Router y guard de onboarding
useRouter() mantiene hookState sincronizado con hashchange. Mapa estático #/dashboard → Dashboard, etc.
El guard vive en logic/onboarding.js y se ejecuta desde FinanceProvider (montaje + hashchange + cambios en accounts) y desde useRouter en cada hashchange:
- Si el onboarding no está completo, fuerza
#/onboarding. - Si ya está completo y la ruta es
#/onboarding, redirige a#/dashboard. - La primera
addAccountllamamarkOnboardingComplete(); el flag no se borra al eliminar cuentas después.
Onboarding llama addAccount({ isDefault: true }) y, si result.valid, navigateAfterOnboarding().
Stores
| Store | Archivo | Persistencia | Rol |
|---|---|---|---|
| Finance | logic/store/financeStore.js | Chroma-Fs vía persist.js + repository | CRUD de colecciones + dominio de cuentas (accountDomain.js) + selectores useMemo |
| Settings | logic/store/settingsStore.js | Snapshot settings (Chroma-Fs) | defaultCurrency, locale, weekStartsOn, theme — reglas en logic/settings.js |
| Toast | logic/store/toastStore.js | En memoria | showToast(message, variant) + auto-dismiss 4s |
financeStore dispara toasts en cada acción CRUD (éxito/error). exportData / importData en Settings usan showToast y refreshFromStorage.
Las acciones de transacción (addTransaction, addTransfer, updateTransaction, removeTransaction) se exponen en useFinance(). Los cuerpos están en logic/store/transactions.js y logic/store/ledger.js; el provider persiste y actualiza signals.
Cuentas (TAF-006)
Dominio en logic/store/accountDomain.js, cableado por FinanceProvider:
- Alta / edición / archivo / restauración / default (
setDefaultAccount=selectDefaultAccount). accounts()es la colección completa;activeAccounts()/archivedAccounts()/defaultAccount()son derivados.- Hard-delete (
removeAccount) bloqueado si hay transacciones; archivar no borra el historial. createAccountincluyeisDefaultyarchived. Datos viejos se hidratan al montar.
Cálculos del Dashboard (TAF-012)
Derivados en financeStore con useMemo:
balanceByCurrency— suma por moneda (no mezcla monedas distintas).monthlyTotals— ingresos/gastos/neto del mes (excluyetransfer).topExpenseCategories— top 5 gastos del mes.recentTransactions— últimas 5 por fecha.
El dashboard también monta TrendChart y NetWorthChart (ECharts CDN) alimentados por logic/reportCalculations.js.
Fórmulas canónicas: logic/store/ledger.js (reexportadas por logic/utils/calculations.js).
Transferencias: Ledger y movimientos.
Gráficos y reportes (TAF-013)
- ECharts 6.1.0 solo vía CDN en
index.html(globalThis.echarts; sin React). - Cálculos de series:
logic/reportCalculations.js(tests entest/logic/reportCalculations.test.js). - Wrappers:
src/components/charts/montan conecharts.initen unrefChroma ychart.dispose()al desmontar.
Presupuestos (TAF-014)
- CRUD:
addBudget,updateBudget,removeBudget. - Duplicados bloqueados por
categoryId+period(monthly). - Progreso:
getBudgetSpent+getBudgetProgress(ok/warning≥80% /exceeded>100%). - Helpers de dominio reexportados en
logic/budgets.jsdesdeledger.js.
Flujo E2E verificado
test/logic/integration.test.js simula el camino crítico sin DOM ni WASM:
- Hidratar —
hydratePersist({ storage: mock Chroma-Fs })carga el snapshot vacío. - Onboarding —
addAccountToListcrea la primera cuenta;markOnboardingComplete()persiste el flag. - Segunda cuenta — necesaria para transferencias entre cuentas distintas.
- Transacción —
prepareAddTransaction+transactionsRepo.insert. - Transferencia —
prepareAddTransfercrea dos patas vinculadas; los saldos se verifican concomputeAccountBalance. - Persistir —
flushPersist()escribefinance/snapshot.jsonen el mock. - Reload —
resetPersistForTests()+hydratePersist()de nuevo; cuentas, transacciones y balances coinciden.
npm test ejecuta 11 archivos (persist, onboarding, settings, export/import, integration, currency, filterTransactions, accountDomain, ledger, transactions) — 83 pruebas, sin navegador.