Ctrl K
ComunidadGuías…

CLAUDE.md

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project Overview

QuickYNAB is a TypeScript-based tool for importing bank transactions to YNAB with two interfaces:

- **Web App**: Fastify server with Vite frontend (drag-and-drop CSV upload)
- **CLI**: Commander.js-based command-line tool for automation

The project auto-detects 124+ bank CSV formats using configs fetched from [bank2ynab](https://github.com/bank2ynab/bank2ynab).

## Essential Commands

```bash
# Development
bun run dev              # Run both frontend (Vite) and backend (bun --watch) concurrently
bun run dev:vite         # Frontend only (http://localhost:5173)
bun run dev:server       # Backend only (http://localhost:3000)

# Building
bun run build            # Full build: fetch configs → build frontend → build backend → copy configs
bun run build:frontend   # Vite build only (outputs to dist/public/)
bun run build:backend    # TypeScript compile with bun x tsc (outputs to dist/)

# Testing
bun test                 # Run all tests with Bun's test runner
bun test --watch         # Watch mode
bun test --coverage      # Coverage report
bun run typecheck        # Type check sources + tests (used in CI)

# Other
bun run format           # Format with Biome
bun run format:check     # Check formatting (used in CI)
bun run start            # Run CLI (bun dist/cli.js)
bun run web              # Run web server (bun dist/server.js)
```

## Architecture

### Build Process Flow

1. **`bun run build`** triggers 4 sequential steps:
   - `fetch:configs` - Downloads bank2ynab.conf from GitHub using Bun, parses INI format, saves as `lib/parsers/bank2ynab-configs.json`
   - `build:frontend` - Vite bundles React/TS frontend to `dist/public/`
   - `build:backend` - TypeScript compiles backend to `dist/` using `bun x tsc`
   - `copy:configs` - Copies JSON config to `dist/lib/parsers/`

**Critical:** Bank configs are fetched at **build time**, not runtime. This keeps the bundle small and startup fast.

**Runtime:** The project runs on Bun, which provides faster startup times and native TypeScript support compared to Node.js.

### CSV Parsing Pipeline

The parsing flow differs for CLI vs Web App but uses the same core logic:

```
parseCSV(filePath, originalFilename?)
  ↓
getBank2YnabConfigs() - loads bundled configs from JSON
  ↓
findMatchingConfig(filename) - regex or string matching against patterns
  ↓
[IF MATCH] → parseBank2YnabCSV() - handles semicolons, headers, footers, date formats
[IF NO MATCH] → parseYnabCSV() - standard YNAB format (Date, Payee, Outflow, Inflow)
  ↓
Returns Transaction[] with normalized format
```

**Web App caveat:** The server saves uploads as temp files with random prefixes (`ynab-{hash}-{originalFilename}.csv`). The `originalFilename` parameter is essential for pattern matching since bank configs use `^` anchors that expect specific filename patterns (e.g., `^(\d{4})_(\d{1,2})_account_statements` for CH Neon).

### Transaction Upload Flow

```
uploadTransactions(transactions, config, accountIdOverride?, budgetIdOverride?)
  ↓
Resolve budget ID (override > config > auto-select if only 1 > error)
  ↓
Resolve account ID (override > config > auto-select if only 1 > error)
  ↓
Generate import_id for each transaction (MD5 hash of account:date:amount:payee)
  ↓
Convert amounts to milliunits (multiply by 1000, round)
  ↓
ynabAPI.transactions.createTransactions()
  ↓
Returns { imported, duplicates } - YNAB skips transactions with duplicate import_ids
```

### File Structure

```
cli.ts                   # CLI entry point (Commander.js)
server.ts                # Web server (Fastify + multipart uploads)
src/                     # Frontend (Vite + TypeScript + Tailwind)
  ├── main.ts           # Frontend logic (drag-drop, preview, upload)
  └── index.html        # Entry HTML
lib/
  ├── config.ts         # Loads from ~/.quickynab/config or .env
  ├── converter.ts      # Main CSV parsing entry point
  ├── uploader.ts       # YNAB API interaction
  └── parsers/
      ├── bank2ynab-fetcher.ts       # Config loading & pattern matching
      ├── bank2ynab-generic.ts       # Generic bank CSV parser
      └── bank2ynab-configs.json     # Generated at build time
scripts/
  └── fetch-bank-configs.ts          # Downloads & parses bank2ynab.conf
```

## Key Implementation Details

### Bank Format Detection

Pattern matching happens in `findMatchingConfig()`:

1. Iterates through all configs in order (order matters - more specific patterns should come first)
2. For each config, checks `useRegex` flag:
   - `true`: Tests filename against regex pattern
   - `false`: Checks if filename includes pattern string
3. Returns first match or null

**When adding/fixing banks:** Check the pattern in `bank2ynab-configs.json`. If a pattern is too generic (e.g., `[0-9]{10}`), it may match before more specific patterns.

### Configuration Loading

Two separate config systems:

- **CLI**: Reads `~/.quickynab/config` (created by `ynab init` command)
- **Web**: Reads `.env` file or environment variables
- Both use `lib/config.ts` which exports `getConfig()`

**Budget/Account preselection:**

- **CLI**: Automatically uses `YNAB_BUDGET_ID` and `YNAB_ACCOUNT_ID` from `~/.quickynab/config` (created by `ynab init`). Can be overridden with `--budget-id` and `--account-id` flags.
- **Web App**: If `YNAB_BUDGET_ID` and/or `YNAB_ACCOUNT_ID` are set in `.env`, the web app will automatically preselect them in the dropdowns on page load. Users can still change the selections if desired.

### Frontend State Management

No React/Vue framework - vanilla TypeScript with DOM manipulation:

- Global state vars (`currentFile`, `previewData`, `selectedBudgetId`, `selectedAccountId`)
- Event listeners on drop-zone, file input, budget select, account select
- Dynamic HTML generation for preview and results

**Dark mode:** Uses Tailwind's `dark:` classes with `media` strategy (system preference)

### Security (Web App)

- Helmet for security headers (CSP, XSS protection)
- Rate limiting (100 req/15min global, 10 req/1min for uploads)
- File validation (max 10MB, CSV only, malicious content detection)
- Temp files with crypto-random names, cleaned up in finally blocks
- **Alpine-based Docker image** - Minimal base image with only required dependencies
- Built-in health checks for container orchestration
- **Zod validation** for runtime type safety on API inputs

## Testing Strategy

Tests use Bun's built-in test runner (imported from `bun:test`) with 71 tests across 7 files:

- `bank2ynab-fetcher.test.ts` - Pattern matching (regex vs string, edge cases)
- `bank2ynab-generic.test.ts` - CSV parsing (delimiters, skip rows, sanitization)
- `date-parser.test.ts` - Date format detection (DD.MM.YYYY, YYYY-MM-DD, etc.)
- `uploader.test.ts` - Milliunits conversion, import_id generation
- `converter.test.ts` - Full CSV parsing flow
- `config.test.ts` - Configuration loading and validation
- `server.test.ts` - Web server endpoints and security

**When modifying parsers:** Run `bun test --watch` and ensure existing tests pass. Add tests for new bank formats.

**Performance:** Bun's test runner is significantly faster than Vitest (~340ms vs ~919ms for the full suite).

## Publishing

### Release process

1. Bump `version` in `package.json`
2. Commit, tag and push:

   ```bash
   git add package.json
   git commit -m "chore: bump version to x.y.z"
   git tag vx.y.z
   git push && git push --tags
   ```
3. Publish to npm manually:

   ```bash
   npm publish
   ```

   (runs `prepublishOnly` → `check && typecheck && test && build` automatically)

### What's automated

- **Docker:** `.github/workflows/docker-publish.yml` triggers on `v*` tag push
  - Builds multi-platform (linux/amd64, linux/arm64)
  - Pushes to both Docker Hub and GHCR
  - Uses official Bun Alpine image

### What's manual

- **npm:** No CI workflow — run `npm publish` locally after tagging

## Common Gotchas

1. **Bank configs not updating?** Run `bun run fetch:configs` to re-download from GitHub
2. **Web app not detecting bank?** Check that original filename is passed to `parseCSV()` - temp file prefix breaks pattern matching
3. **Date parsing fails?** The `date-parser.ts` tries multiple formats - add new format if needed
4. **Duplicate transactions?** import_id must be consistent - check hash generation in `uploader.ts`
5. **Frontend changes not showing?** Run `bun run build:frontend` (dev server runs on :5173, production on :3000)
6. **Bun compatibility issues?** Bun has excellent Node.js compatibility, but check https://bun.sh/docs/runtime/nodejs-apis for any edge cases
7. **Fastify/zod in `optionalDependencies`?** Deliberate, don't "fix" it. Only `server.ts` needs them, so `npm i -g quickynab --omit=optional` gives a working CLI at 5.7 MB instead of 34 MB. The trade-off: a partial install fails at `bun run web` with a plain `Cannot find package 'fastify'`, because static ESM imports cannot be guarded without splitting the server entry point.