Targ Apps Docs

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

  1. src/main.js llama await hydratePersist() y después importa App (render(App, #app)).
  2. App anida ToastProvider, SettingsProvider y FinanceProvider, luego monta AppShell.
  3. AppShell invoca useRouter(). El guard vive en FinanceProvider y useRouter (syncAppRoute() al montar y en hashchange). Orden: hash vacío → #/auth sin sesión → #/onboarding sin cuentas → #/dashboard.
  4. Layout monta NavBar, un Show por ruta (cada página se crea una vez bajo untracked mientras FinanceProvider sigue en el stack) y <Toast/>.
  5. Las páginas consumen useFinance(), useSettings() y derivados useMemo del 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 addAccount llama markOnboardingComplete(); el flag no se borra al eliminar cuentas después.

Onboarding llama addAccount({ isDefault: true }) y, si result.valid, navigateAfterOnboarding().

Stores

StoreArchivoPersistenciaRol
Financelogic/store/financeStore.jsChroma-Fs vía persist.js + repositoryCRUD de colecciones + dominio de cuentas (accountDomain.js) + selectores useMemo
Settingslogic/store/settingsStore.jsSnapshot settings (Chroma-Fs)defaultCurrency, locale, weekStartsOn, theme — reglas en logic/settings.js
Toastlogic/store/toastStore.jsEn memoriashowToast(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.
  • createAccount incluye isDefault y archived. 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 (excluye transfer).
  • 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 en test/logic/reportCalculations.test.js).
  • Wrappers: src/components/charts/ montan con echarts.init en un ref Chroma y chart.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.js desde ledger.js.

Flujo E2E verificado

test/logic/integration.test.js simula el camino crítico sin DOM ni WASM:

  1. Hidratar — hydratePersist({ storage: mock Chroma-Fs }) carga el snapshot vacío.
  2. Onboarding — addAccountToList crea la primera cuenta; markOnboardingComplete() persiste el flag.
  3. Segunda cuenta — necesaria para transferencias entre cuentas distintas.
  4. Transacción — prepareAddTransaction + transactionsRepo.insert.
  5. Transferencia — prepareAddTransfer crea dos patas vinculadas; los saldos se verifican con computeAccountBalance.
  6. Persistir — flushPersist() escribe finance/snapshot.json en el mock.
  7. 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.

On this page