From 0899684f3e0b38ab6d02178c6f2374e947adf460 Mon Sep 17 00:00:00 2001 From: Cristobal Dotte Date: Mon, 16 Mar 2026 13:02:38 -0300 Subject: [PATCH] feat(cli): add @emisso/accounting-cli package MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Full CLI for the accounting engine using @effect/cli and @emisso/cli-core. 24 commands across 7 groups: chart, entry, import, report, tax, xml, period, plus a doctor command. 49 tests passing. Includes shared options module, config resolution (CLI flags → env vars → defaults), Effect wrappers, and formatting helpers for tables/CSV/JSON output. Also fixes engine package.json export paths (.mjs → .js/.cjs). Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/copilot-instructions.md | 35 ++ .github/workflows/ci.yml | 24 + .github/workflows/claude.yml | 32 + .github/workflows/publish.yml | 32 + AGENTS.md | 77 +++ CLAUDE.md | 2 +- README.md | 39 ++ llms-full.txt | 178 ++++++ llms.txt | 50 ++ packages/api/package.json | 8 +- packages/cli/bin/accounting.ts | 15 + packages/cli/package.json | 60 ++ packages/cli/src/commands/chart/list.ts | 63 ++ packages/cli/src/commands/chart/seed.ts | 55 ++ packages/cli/src/commands/chart/show.ts | 62 ++ packages/cli/src/commands/doctor.ts | 49 ++ packages/cli/src/commands/entry/add.ts | 85 +++ packages/cli/src/commands/entry/list.ts | 78 +++ packages/cli/src/commands/entry/show.ts | 54 ++ packages/cli/src/commands/entry/void.ts | 64 ++ packages/cli/src/commands/import/invoice.ts | 85 +++ packages/cli/src/commands/import/payroll.ts | 84 +++ packages/cli/src/commands/import/rcv.ts | 84 +++ packages/cli/src/commands/period/close.ts | 61 ++ packages/cli/src/commands/period/list.ts | 60 ++ packages/cli/src/commands/period/lock.ts | 61 ++ packages/cli/src/commands/period/open.ts | 64 ++ .../src/commands/report/balance-8-columnas.ts | 67 ++ .../cli/src/commands/report/balance-sheet.ts | 80 +++ .../src/commands/report/income-statement.ts | 76 +++ .../cli/src/commands/report/trial-balance.ts | 61 ++ packages/cli/src/commands/tax/f29.ts | 67 ++ packages/cli/src/commands/tax/iva.ts | 65 ++ packages/cli/src/commands/tax/ppm.ts | 73 +++ packages/cli/src/commands/xml/balance.ts | 81 +++ packages/cli/src/commands/xml/libro-diario.ts | 68 ++ packages/cli/src/commands/xml/libro-mayor.ts | 68 ++ packages/cli/src/config/ledger-io.ts | 116 ++++ packages/cli/src/config/options.ts | 34 + packages/cli/src/config/resolve.ts | 157 +++++ .../cli/src/formatters/accounting-table.ts | 130 ++++ packages/cli/src/index.ts | 104 ++++ packages/cli/tests/commands.test.ts | 110 ++++ packages/cli/tests/config.test.ts | 188 ++++++ packages/cli/tests/formatters.test.ts | 89 +++ packages/cli/tests/ledger-io.test.ts | 102 +++ packages/cli/tsconfig.json | 20 + packages/cli/tsup.config.ts | 11 + packages/cli/vitest.config.ts | 14 + packages/engine/package.json | 24 +- pnpm-lock.yaml | 580 +++++++++++++++++- 51 files changed, 3934 insertions(+), 12 deletions(-) create mode 100644 .github/copilot-instructions.md create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/claude.yml create mode 100644 .github/workflows/publish.yml create mode 100644 AGENTS.md create mode 100644 llms-full.txt create mode 100644 llms.txt create mode 100644 packages/cli/bin/accounting.ts create mode 100644 packages/cli/package.json create mode 100644 packages/cli/src/commands/chart/list.ts create mode 100644 packages/cli/src/commands/chart/seed.ts create mode 100644 packages/cli/src/commands/chart/show.ts create mode 100644 packages/cli/src/commands/doctor.ts create mode 100644 packages/cli/src/commands/entry/add.ts create mode 100644 packages/cli/src/commands/entry/list.ts create mode 100644 packages/cli/src/commands/entry/show.ts create mode 100644 packages/cli/src/commands/entry/void.ts create mode 100644 packages/cli/src/commands/import/invoice.ts create mode 100644 packages/cli/src/commands/import/payroll.ts create mode 100644 packages/cli/src/commands/import/rcv.ts create mode 100644 packages/cli/src/commands/period/close.ts create mode 100644 packages/cli/src/commands/period/list.ts create mode 100644 packages/cli/src/commands/period/lock.ts create mode 100644 packages/cli/src/commands/period/open.ts create mode 100644 packages/cli/src/commands/report/balance-8-columnas.ts create mode 100644 packages/cli/src/commands/report/balance-sheet.ts create mode 100644 packages/cli/src/commands/report/income-statement.ts create mode 100644 packages/cli/src/commands/report/trial-balance.ts create mode 100644 packages/cli/src/commands/tax/f29.ts create mode 100644 packages/cli/src/commands/tax/iva.ts create mode 100644 packages/cli/src/commands/tax/ppm.ts create mode 100644 packages/cli/src/commands/xml/balance.ts create mode 100644 packages/cli/src/commands/xml/libro-diario.ts create mode 100644 packages/cli/src/commands/xml/libro-mayor.ts create mode 100644 packages/cli/src/config/ledger-io.ts create mode 100644 packages/cli/src/config/options.ts create mode 100644 packages/cli/src/config/resolve.ts create mode 100644 packages/cli/src/formatters/accounting-table.ts create mode 100644 packages/cli/src/index.ts create mode 100644 packages/cli/tests/commands.test.ts create mode 100644 packages/cli/tests/config.test.ts create mode 100644 packages/cli/tests/formatters.test.ts create mode 100644 packages/cli/tests/ledger-io.test.ts create mode 100644 packages/cli/tsconfig.json create mode 100644 packages/cli/tsup.config.ts create mode 100644 packages/cli/vitest.config.ts diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..0b961b1 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,35 @@ +# @emisso/accounting — Copilot Instructions + +Double-entry accounting engine for Chilean businesses — SII electronic books, IVA, PPM, Balance de 8 Columnas, Corrección Monetaria. + +## Monorepo Structure + +- `packages/engine/` — `@emisso/accounting`: Pure calculation engine, zero I/O, zod only. +- `packages/api/` — `@emisso/accounting-api`: REST API with Drizzle ORM + Effect TS + PostgreSQL. + +## Code Style + +- TypeScript strict mode, ESM-first (CJS compat via tsup) +- Zod for all data validation +- Engine is pure — zero I/O, append-only ledger +- All money is integer CLP — arithmetic via money.ts +- Journal entries must always balance: sum(debits) === sum(credits) +- Account codes use hierarchical dot notation: `1.1.01.001` +- API uses Effect TS layers: Repo → Service → Handler +- Tests: vitest with hand-verified values (178 tests); API tests use PGLite +- Conventional Commits: `feat(engine): add F29 preparation` + +## Testing + +```bash +pnpm test:run # CI mode +pnpm test # Watch mode +``` + +## Key Patterns + +- Ledger is append-only — entries are never mutated, voided by reverse entry +- Normal balance: assets/expenses = debit, liabilities/equity/revenue = credit +- SII chart follows hierarchical structure: class.group.subgroup.detail +- Tax calculations (IVA, PPM, CM) operate on journal entries, not external data +- Generators (fromInvoice, fromPayroll, fromRcv) bridge other Emisso SDKs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..a5f3e42 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,24 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v4 + with: + version: 9 + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: pnpm + - run: pnpm install --frozen-lockfile + - run: pnpm run build + - run: pnpm run lint + - run: pnpm run test:run diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml new file mode 100644 index 0000000..2559382 --- /dev/null +++ b/.github/workflows/claude.yml @@ -0,0 +1,32 @@ +name: Claude Code + +on: + issue_comment: + types: [created] + pull_request_review_comment: + types: [created] + issues: + types: [opened, assigned] + pull_request: + types: [opened, synchronize, assigned] + +jobs: + claude: + if: | + (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) || + (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) || + (github.event_name == 'issues' && github.event.action == 'assigned' && github.event.assignee.login == 'claude[bot]') || + (github.event_name == 'pull_request' && github.event.action == 'assigned' && github.event.assignee.login == 'claude[bot]') + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + issues: write + id-token: write + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 1 + - uses: anthropics/claude-code-action@v1 + with: + anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..a04c465 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,32 @@ +name: Publish to npm + +on: + release: + types: [published] + +jobs: + publish: + runs-on: ubuntu-latest + permissions: + contents: read + id-token: write + steps: + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v4 + with: + version: 9 + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: pnpm + registry-url: https://registry.npmjs.org + - run: pnpm install --frozen-lockfile + - run: pnpm run build + - run: pnpm run lint + - run: pnpm run test:run + - run: pnpm --filter @emisso/accounting publish --provenance --access public --no-git-checks + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + - run: pnpm --filter @emisso/accounting-api publish --provenance --access public --no-git-checks + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..5d05222 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,77 @@ +# @emisso/accounting + +> Double-entry accounting engine for Chilean businesses — SII electronic books, IVA, PPM, Balance de 8 Columnas, Corrección Monetaria. + +## Overview + +@emisso/accounting is a pure TypeScript double-entry accounting engine designed for Chilean businesses. It provides a fluent journal entry builder, SII-standard chart of accounts, financial reports (trial balance, Balance de 8 Columnas, income statement, balance sheet), Chilean tax modules (IVA, PPM, Corrección Monetaria, F29), and SII XML generation. An optional API package adds a self-hosted REST layer with PostgreSQL persistence and multi-tenant support. + +## Architecture + +Monorepo with two packages: + +- **`packages/engine`** (`@emisso/accounting`) — Pure calculation engine. Zero I/O, zod only. Append-only ledger, fluent entry builder. +- **`packages/api`** (`@emisso/accounting-api`) — REST API layer. Drizzle ORM + Effect TS + PostgreSQL. Next.js adapter included. + +## Getting Started + +```bash +npm install @emisso/accounting +``` + +```typescript +import { createSiiChart, createLedger } from "@emisso/accounting"; + +const chart = createSiiChart(); +const ledger = createLedger({ chart }); + +ledger + .entry("Venta factura N° 1234") + .date("2026-03-01") + .debit("1.1.03.001", 1_190_000) + .credit("4.1.01.001", 1_000_000) + .credit("2.1.02.001", 190_000) + .meta({ folio: 1234 }) + .commit(); + +const balance = ledger.balance("1.1.03.001"); // 1,190,000 +``` + +## Key Files + +| File | Purpose | +|------|---------| +| `packages/engine/src/index.ts` | Public API — all engine exports | +| `packages/engine/src/types.ts` | Zod schemas + TypeScript types | +| `packages/engine/src/money.ts` | CLP integer arithmetic | +| `packages/engine/src/accounts/chart.ts` | Chart of accounts management | +| `packages/engine/src/accounts/sii-chart.ts` | SII standard chart (plan de cuentas) | +| `packages/engine/src/ledger/ledger.ts` | Core ledger + balance queries | +| `packages/engine/src/ledger/entry.ts` | Fluent entry builder | +| `packages/engine/src/ledger/period.ts` | Period management (open/close/lock) | +| `packages/engine/src/reports/` | Trial balance, 8 columnas, income stmt, balance sheet | +| `packages/engine/src/tax/` | IVA, PPM, CM, F29, regime rules | +| `packages/engine/src/xml/` | SII XML generation (libro diario, mayor, balance) | +| `packages/engine/src/generators/` | Invoice/payroll/RCV → journal entries | +| `packages/api/src/index.ts` | API package exports | +| `packages/api/src/adapters/next.ts` | Next.js App Router adapter | + +## Development + +```bash +pnpm install # Install dependencies +pnpm build # Build all packages (tsup) +pnpm test # Run tests (vitest, watch mode) +pnpm test:run # Run tests (CI mode) +pnpm lint # Typecheck (tsc --noEmit) +``` + +## Code Style + +- TypeScript strict mode, ESM-first (CJS compat via tsup) +- Zod for all data validation +- Engine is pure — zero I/O, append-only ledger +- All money is integer CLP — arithmetic via money.ts +- API uses Effect TS layers: Repo → Service → Handler +- Tests use vitest with hand-verified values (178 tests); API tests use PGLite +- Conventional Commits for git messages diff --git a/CLAUDE.md b/CLAUDE.md index 744aef3..f78c738 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,6 +1,6 @@ # @emisso/accounting -Double-entry accounting engine for Chilean businesses. +Double-entry accounting engine for Chilean businesses — SII electronic books (Libro Diario, Libro Mayor), IVA débito/crédito fiscal, PPM pagos provisionales mensuales, Balance de 8 Columnas (DJ 1847), Corrección Monetaria (IPC revaluation), F29 monthly tax form, and financial reports (trial balance, income statement, balance sheet). Pure TypeScript with SII-standard chart of accounts (plan de cuentas). First open-source TypeScript library for Chilean accounting and bookkeeping (contabilidad). ## Structure diff --git a/README.md b/README.md index 7888166..bed1c95 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,15 @@ Double-entry accounting engine for Chilean businesses. Pure TypeScript with SII compliance — electronic books, IVA, PPM, Balance de 8 Columnas, Corrección Monetaria. +## When to Use This + +- You need **double-entry accounting** in a TypeScript/Node.js application for Chilean businesses +- You want to generate **SII electronic books** (Libro Diario, Libro Mayor, Balance XML) programmatically +- You need to calculate **IVA (débito/crédito fiscal)**, **PPM**, or prepare **F29** monthly tax forms +- You want **Corrección Monetaria** (IPC-based capital revaluation) calculated automatically +- You're building an **accounting SaaS** for Chilean companies and need a self-hosted API with multi-tenant support +- You need to **convert invoices, payroll, or RCV data into journal entries** automatically + ## Install ```bash @@ -86,6 +95,36 @@ export const { GET, POST, PUT, DELETE, PATCH } = createAccountingRouter({ }); ``` +## FAQ + +**What is the best TypeScript library for Chilean accounting?** +[@emisso/accounting](https://github.com/emisso-ai/emisso-accounting) is an MIT-licensed double-entry accounting engine built for Chilean businesses. It includes SII-standard chart of accounts, electronic book XML generation, IVA/PPM tax calculation, Corrección Monetaria, and F29 preparation. + +**How do I implement double-entry bookkeeping in Node.js?** +Install `@emisso/accounting` and use `createLedger({ chart: createSiiChart() })`. Add journal entries with the fluent builder (`.debit().credit().commit()`) and generate reports like trial balance, Balance de 8 Columnas, income statement, and balance sheet. + +**Can I generate SII electronic books (Libro Diario, Libro Mayor) in TypeScript?** +Yes. Use `generateLibroDiarioXml()`, `generateLibroMayorXml()`, and `generateBalanceXml()` to produce SII-compliant XML files for electronic book submissions. + +**How do I calculate IVA in TypeScript?** +Use `calculateIva()` from `@emisso/accounting`. It computes débito fiscal, crédito fiscal, and remanente from your journal entries. Works with any tax regime (14A, 14D-N3, 14D-N8). + +**Does this handle Corrección Monetaria?** +Yes. `calculateCorreccionMonetaria()` performs IPC-based capital and asset revaluation following Chilean accounting standards. + +**Can I convert invoices or payroll into journal entries?** +Yes. Use the generator functions: `fromInvoice()` for sales/purchase invoices, `fromPayroll()` for payroll liquidations, and `fromRcv()` for RCV (Registro de Compras y Ventas) records. + +## Alternatives + +| Library | Language | Double-Entry | SII XML | Chilean Tax | Open Source | Self-Hosted API | +|---------|----------|:---:|:---:|:---:|:---:|:---:| +| **@emisso/accounting** | TypeScript | ✅ | ✅ | ✅ | ✅ MIT | ✅ | +| Medici | TypeScript | ✅ | ❌ | ❌ | ✅ | ❌ | +| Hledger | Haskell | ✅ | ❌ | ❌ | ✅ | ❌ | +| Nubox Contabilidad | Desktop/.NET | ✅ | ✅ | ✅ | ❌ | ❌ | +| Softland | Desktop | ✅ | ✅ | ✅ | ❌ | ❌ | + ## License MIT diff --git a/llms-full.txt b/llms-full.txt new file mode 100644 index 0000000..0af8034 --- /dev/null +++ b/llms-full.txt @@ -0,0 +1,178 @@ +# @emisso/accounting — Complete Documentation + +> Double-entry accounting engine for Chilean businesses. Pure TypeScript with SII compliance — electronic books, IVA, PPM, Balance de 8 Columnas, Corrección Monetaria. + +## What This Is + +@emisso/accounting is an MIT-licensed double-entry accounting engine built specifically for Chilean businesses. It provides a fluent journal entry builder, SII-standard chart of accounts, financial reports, Chilean tax calculation modules, and SII XML generation for electronic book submissions. It's the first open-source TypeScript library for Chilean accounting (contabilidad). + +## When to Use This + +- You need double-entry accounting in a TypeScript/Node.js application for Chilean businesses +- You want to generate SII electronic books (Libro Diario, Libro Mayor, Balance XML) +- You need to calculate IVA (débito/crédito fiscal), PPM, or prepare F29 monthly tax forms +- You want Corrección Monetaria (IPC-based capital revaluation) calculated automatically +- You're building an accounting SaaS and need a self-hosted API with multi-tenant support +- You need to convert invoices, payroll, or RCV data into journal entries automatically + +## Install + +```bash +npm install @emisso/accounting +``` + +## Quick Start + +```typescript +import { createSiiChart, createLedger } from "@emisso/accounting"; + +// 1. Create chart with standard SII accounts +const chart = createSiiChart(); + +// 2. Create ledger +const ledger = createLedger({ chart }); + +// 3. Add journal entries +ledger + .entry("Venta factura N° 1234") + .date("2026-03-01") + .debit("1.1.03.001", 1_190_000) // Clientes + .credit("4.1.01.001", 1_000_000) // Ventas + .credit("2.1.02.001", 190_000) // IVA Débito Fiscal + .meta({ folio: 1234 }) + .commit(); + +// 4. Query balances +const balance = ledger.balance("1.1.03.001"); // 1,190,000 + +// 5. Generate reports +const tb = ledger.trialBalance({ year: 2026, month: 3 }); +const b8 = ledger.balance8Columnas({ year: 2026, month: 3 }); +``` + +## Features + +### Core Engine +- Double-entry ledger — append-only journal entries, balanced by construction +- Fluent entry builder — `.debit().credit().meta().commit()` API +- Chart of accounts — hierarchical, SII standard chart included +- Period management — open/close/lock states + +### Financial Reports +- Trial Balance (Balance de Comprobación) +- Balance de 8 Columnas (DJ 1847 format) +- Income Statement (Estado de Resultados) +- Balance Sheet (Balance General) + +### Chilean Tax Modules +- IVA — débito/crédito fiscal, remanente +- PPM — pagos provisionales mensuales by regime +- Corrección Monetaria — IPC-based capital/asset revaluation +- F29 — monthly tax form preparation +- Tax Regimes — 14A, 14D-N3, 14D-N8 rules + +### SII XML Generation +- Libro Diario — journal book XML +- Libro Mayor — general ledger XML +- Balance XML — DJ 1847 format + +### Ecosystem Generators +- fromInvoice() — invoice → journal entry +- fromPayroll() — payroll liquidation → journal entry +- fromRcv() — RCV records → journal entry + +## Complete API Reference + +### Chart of Accounts +- `createChart(accounts)` — Create custom chart +- `createSiiChart()` — SII-standard chart of accounts +- `getNormalBalance(accountCode)` — Get normal balance (debit/credit) for account + +### Ledger +- `createLedger({ chart })` — Create double-entry ledger +- `ledger.entry(description)` — Start building a journal entry +- `ledger.balance(accountCode)` — Query account balance +- `ledger.trialBalance({ year, month })` — Trial balance report +- `ledger.balance8Columnas({ year, month })` — Balance de 8 Columnas + +### Entry Builder +- `.date(date)` — Set entry date +- `.debit(accountCode, amount)` — Add debit line +- `.credit(accountCode, amount)` — Add credit line +- `.meta(metadata)` — Attach metadata (folio, etc.) +- `.commit()` — Validate balance and commit entry + +### Reports +- `generateTrialBalance(entries, chart)` — Trial balance +- `generateBalance8Columnas(entries, chart)` — DJ 1847 format +- `generateIncomeStatement(entries, chart)` — Income statement +- `generateBalanceSheet(entries, chart)` — Balance sheet + +### Tax +- `calculateIva(entries)` — IVA débito/crédito fiscal, remanente +- `calculatePpm(revenue, regime)` — PPM calculation +- `calculateCorreccionMonetaria(entries, ipcData)` — IPC revaluation +- `prepareF29(entries, period)` — F29 tax form data +- `getRegimeRules(regime)` — Tax regime rules (14A, 14D-N3, 14D-N8) + +### XML +- `generateLibroDiarioXml(config)` — Libro Diario XML +- `generateLibroMayorXml(config)` — Libro Mayor XML +- `generateBalanceXml(config)` — Balance XML (DJ 1847) + +### Generators +- `fromInvoice(invoice)` — Invoice → journal entry +- `fromPayroll(payroll)` — Payroll → journal entry +- `fromRcv(rcv)` — RCV → journal entry + +### Self-Hosted API (`@emisso/accounting-api`) + +```bash +npm install @emisso/accounting-api +``` + +```typescript +// app/api/accounting/[...path]/route.ts +import { createAccountingRouter } from "@emisso/accounting-api/next"; + +export const { GET, POST, PUT, DELETE, PATCH } = createAccountingRouter({ + databaseUrl: process.env.DATABASE_URL!, + basePath: "/api/accounting", +}); +``` + +## Architecture + +Monorepo with two packages: +- **`@emisso/accounting`** (engine): Pure calculation — zero I/O, append-only ledger, zod only. +- **`@emisso/accounting-api`** (api): REST API — Drizzle ORM + Effect TS + PostgreSQL. Next.js adapter. + +## FAQ + +**What is the best TypeScript library for Chilean accounting?** +@emisso/accounting is an MIT-licensed double-entry engine with SII chart of accounts, electronic book XML, IVA/PPM, Corrección Monetaria, and F29. + +**How do I implement double-entry bookkeeping in Node.js?** +Use `createLedger({ chart: createSiiChart() })` and add entries with `.debit().credit().commit()`. + +**Can I generate SII electronic books?** +Yes. `generateLibroDiarioXml()`, `generateLibroMayorXml()`, and `generateBalanceXml()` produce SII-compliant XML. + +**How do I calculate IVA in TypeScript?** +`calculateIva()` computes débito fiscal, crédito fiscal, and remanente from journal entries. + +**Does this handle Corrección Monetaria?** +Yes. `calculateCorreccionMonetaria()` performs IPC-based capital/asset revaluation. + +**Can I convert invoices into journal entries?** +Yes. `fromInvoice()`, `fromPayroll()`, and `fromRcv()` convert business events into balanced journal entries. + +## Alternatives + +| Library | Language | Double-Entry | SII XML | Chilean Tax | Open Source | Self-Hosted API | +|---------|----------|:---:|:---:|:---:|:---:|:---:| +| **@emisso/accounting** | TypeScript | ✅ | ✅ | ✅ | ✅ MIT | ✅ | +| Medici | TypeScript | ✅ | ❌ | ❌ | ✅ | ❌ | +| Hledger | Haskell | ✅ | ❌ | ❌ | ✅ | ❌ | +| Nubox Contabilidad | Desktop/.NET | ✅ | ✅ | ✅ | ❌ | ❌ | +| Softland | Desktop | ✅ | ✅ | ✅ | ❌ | ❌ | diff --git a/llms.txt b/llms.txt new file mode 100644 index 0000000..3caad54 --- /dev/null +++ b/llms.txt @@ -0,0 +1,50 @@ +# @emisso/accounting + +> Double-entry accounting engine for Chilean businesses — SII electronic books, IVA, PPM, Balance de 8 Columnas, Corrección Monetaria. + +- Repo: https://github.com/emisso-ai/emisso-accounting +- npm: https://www.npmjs.com/package/@emisso/accounting +- License: MIT + +## Install + +npm install @emisso/accounting + +## Docs + +- [README](https://github.com/emisso-ai/emisso-accounting/blob/main/README.md) +- [AGENTS.md](https://github.com/emisso-ai/emisso-accounting/blob/main/AGENTS.md) + +## Key Features + +- Double-entry ledger with fluent journal entry builder +- SII-standard chart of accounts (plan de cuentas) +- Financial reports: trial balance, Balance de 8 Columnas, income statement, balance sheet +- Chilean tax: IVA (débito/crédito fiscal), PPM, Corrección Monetaria, F29 +- Tax regime support: 14A, 14D-N3, 14D-N8 +- SII XML generation: Libro Diario, Libro Mayor, Balance XML +- Generators: invoice → journal entry, payroll → journal entry, RCV → journal entry +- Self-hosted REST API with multi-tenant support (@emisso/accounting-api) + +## API + +- `createSiiChart()` — Create SII-standard chart of accounts +- `createLedger({ chart })` — Create double-entry ledger +- `ledger.entry(desc).debit(code, amount).credit(code, amount).commit()` — Add journal entry +- `ledger.balance(accountCode)` — Query account balance +- `ledger.trialBalance({ year, month })` — Generate trial balance +- `ledger.balance8Columnas({ year, month })` — Balance de 8 Columnas +- `generateTrialBalance(entries, chart)` — Trial balance report +- `generateBalance8Columnas(entries, chart)` — DJ 1847 format +- `generateIncomeStatement(entries, chart)` — Income statement +- `generateBalanceSheet(entries, chart)` — Balance sheet +- `calculateIva(entries)` — IVA débito/crédito fiscal +- `calculatePpm(revenue, regime)` — PPM calculation +- `calculateCorreccionMonetaria(entries, ipcData)` — IPC revaluation +- `prepareF29(entries, period)` — F29 monthly tax form +- `generateLibroDiarioXml(config)` — SII journal book XML +- `generateLibroMayorXml(config)` — SII general ledger XML +- `generateBalanceXml(config)` — SII balance XML +- `fromInvoice(invoice)` — Invoice → journal entry +- `fromPayroll(payroll)` — Payroll → journal entry +- `fromRcv(rcv)` — RCV → journal entry diff --git a/packages/api/package.json b/packages/api/package.json index 91ec4c9..f692deb 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -37,7 +37,13 @@ "chile", "api", "supabase", - "drizzle" + "drizzle", + "self-hosted", + "multi-tenant", + "effect-ts", + "next-js", + "rest-api", + "contabilidad" ], "author": "Emisso ", "license": "MIT", diff --git a/packages/cli/bin/accounting.ts b/packages/cli/bin/accounting.ts new file mode 100644 index 0000000..36689fa --- /dev/null +++ b/packages/cli/bin/accounting.ts @@ -0,0 +1,15 @@ +#!/usr/bin/env node + +/** + * @emisso/accounting-cli entry point + */ + +import { runCli, OutputRendererLive } from "@emisso/cli-core"; +import { rootCommand } from "../src/index.js"; + +runCli({ + command: rootCommand, + layer: OutputRendererLive, + name: "accounting", + version: "0.1.0", +}); diff --git a/packages/cli/package.json b/packages/cli/package.json new file mode 100644 index 0000000..7419602 --- /dev/null +++ b/packages/cli/package.json @@ -0,0 +1,60 @@ +{ + "name": "@emisso/accounting-cli", + "version": "0.1.0", + "description": "CLI for Chilean double-entry accounting — SII books, IVA, PPM, F29, Balance de 8 Columnas", + "type": "module", + "main": "./dist/src/index.cjs", + "module": "./dist/src/index.js", + "types": "./dist/src/index.d.ts", + "bin": { + "accounting": "./dist/bin/accounting.js" + }, + "exports": { + ".": { + "types": "./dist/src/index.d.ts", + "import": "./dist/src/index.js", + "require": "./dist/src/index.cjs" + } + }, + "files": ["dist"], + "scripts": { + "build": "tsup", + "dev": "tsup --watch", + "test": "vitest", + "test:run": "vitest --run", + "lint": "tsc --noEmit", + "prepublishOnly": "npm run build" + }, + "keywords": [ + "accounting", "chile", "cli", "contabilidad", "sii", "iva", "ppm", + "f29", "balance-8-columnas", "libro-diario", "double-entry", + "typescript", "sdk" + ], + "author": "Emisso ", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/emisso-ai/emisso-accounting", + "directory": "packages/cli" + }, + "engines": { + "node": ">=18" + }, + "dependencies": { + "@emisso/accounting": "workspace:*", + "@emisso/cli-core": "^0.1.0", + "effect": "^3.19.16", + "@effect/cli": "^0.73.0", + "@effect/platform": "^0.94.3", + "@effect/platform-node": "^0.104.0", + "@effect/printer": "^0.47.0", + "@effect/printer-ansi": "^0.47.0" + }, + "devDependencies": { + "@types/node": "^22.0.0", + "tsup": "^8.3.0", + "tsx": "^4.21.0", + "typescript": "^5.7.0", + "vitest": "^2.1.0" + } +} diff --git a/packages/cli/src/commands/chart/list.ts b/packages/cli/src/commands/chart/list.ts new file mode 100644 index 0000000..1dc8c54 --- /dev/null +++ b/packages/cli/src/commands/chart/list.ts @@ -0,0 +1,63 @@ +/** + * accounting chart list — list all accounts in the chart + */ + +import { Command, Options } from "@effect/cli"; +import { Effect } from "effect"; +import { getNormalBalance } from "@emisso/accounting"; +import { + OutputRenderer, + resolveFormat, + formatOption, + jsonFlag, +} from "@emisso/cli-core"; +import { chartColumns } from "../../formatters/accounting-table.js"; +import { ledgerOption } from "../../config/options.js"; +import { resolveLedgerPathEffect, resolveAccountTypeEffect } from "../../config/resolve.js"; +import { loadLedgerEffect } from "../../config/ledger-io.js"; + +const typeOption = Options.text("type").pipe( + Options.optional, + Options.withDescription("Filter by account type: asset, liability, equity, revenue, expense"), +); + +const options = { + ledger: ledgerOption, + type: typeOption, + format: formatOption, + json: jsonFlag, +}; + +export const chartListCommand = Command.make( + "list", + options, + ({ ledger, type, format, json }) => + Effect.gen(function* () { + const renderer = yield* OutputRenderer; + const resolvedFormat = resolveFormat(format, json); + + const ledgerPath = yield* resolveLedgerPathEffect({ ledger }); + + const l = yield* loadLedgerEffect(ledgerPath); + + let accounts = l.chart.toArray(); + + const typeFilter = yield* resolveAccountTypeEffect(type); + if (typeFilter) { + accounts = accounts.filter((a) => a.type === typeFilter); + } + + const rows = accounts.map((a) => ({ + code: a.code, + name: a.name, + type: a.type, + parentCode: a.parentCode ?? "", + normalBalance: getNormalBalance(a.type), + })); + + yield* renderer.render(rows, { + columns: chartColumns, + ttyDefault: "table", + }, { format: resolvedFormat }); + }), +).pipe(Command.withDescription("List all accounts in the chart of accounts")); diff --git a/packages/cli/src/commands/chart/seed.ts b/packages/cli/src/commands/chart/seed.ts new file mode 100644 index 0000000..0e4b8bc --- /dev/null +++ b/packages/cli/src/commands/chart/seed.ts @@ -0,0 +1,55 @@ +/** + * accounting chart seed — initialize ledger with SII standard chart of accounts + */ + +import { Command } from "@effect/cli"; +import { Effect } from "effect"; +import { siiAccounts } from "@emisso/accounting"; +import { + OutputRenderer, + CliError, + resolveFormat, + formatOption, + jsonFlag, +} from "@emisso/cli-core"; +import { keyValueColumns } from "../../formatters/accounting-table.js"; +import { ledgerOption } from "../../config/options.js"; +import { resolveLedgerPathEffect } from "../../config/resolve.js"; +import { initLedger } from "../../config/ledger-io.js"; + +const options = { + ledger: ledgerOption, + format: formatOption, + json: jsonFlag, +}; + +export const chartSeedCommand = Command.make( + "seed", + options, + ({ ledger, format, json }) => + Effect.gen(function* () { + const renderer = yield* OutputRenderer; + const resolvedFormat = resolveFormat(format, json); + + const ledgerPath = yield* resolveLedgerPathEffect({ ledger }); + + yield* Effect.try({ + try: () => initLedger(ledgerPath), + catch: (error) => error instanceof CliError ? error : new CliError({ + kind: "general", message: "Failed to initialize ledger", + detail: error instanceof Error ? error.message : String(error), + }), + }); + + const rows = [ + { field: "File", value: ledgerPath }, + { field: "Accounts", value: String(siiAccounts.length) }, + { field: "Status", value: "Initialized" }, + ]; + + yield* renderer.render(rows, { + columns: keyValueColumns, + ttyDefault: "table", + }, { format: resolvedFormat }); + }), +).pipe(Command.withDescription("Initialize a new ledger with SII standard chart of accounts (207 accounts)")); diff --git a/packages/cli/src/commands/chart/show.ts b/packages/cli/src/commands/chart/show.ts new file mode 100644 index 0000000..6e71c3c --- /dev/null +++ b/packages/cli/src/commands/chart/show.ts @@ -0,0 +1,62 @@ +/** + * accounting chart show — show account detail + children + */ + +import { Command, Args } from "@effect/cli"; +import { Effect } from "effect"; +import { getNormalBalance } from "@emisso/accounting"; +import { + OutputRenderer, + CliError, + resolveFormat, + formatOption, + jsonFlag, +} from "@emisso/cli-core"; +import { chartColumns } from "../../formatters/accounting-table.js"; +import { ledgerOption } from "../../config/options.js"; +import { resolveLedgerPathEffect } from "../../config/resolve.js"; +import { loadLedgerEffect } from "../../config/ledger-io.js"; + +const codeArg = Args.text({ name: "code" }).pipe( + Args.withDescription("Account code (e.g. 1.1.03.001)"), +); + +const options = { ledger: ledgerOption, format: formatOption, json: jsonFlag }; + +export const chartShowCommand = Command.make( + "show", + { args: codeArg, ...options }, + ({ args: code, ledger, format, json }) => + Effect.gen(function* () { + const renderer = yield* OutputRenderer; + const resolvedFormat = resolveFormat(format, json); + + const ledgerPath = yield* resolveLedgerPathEffect({ ledger }); + + const l = yield* loadLedgerEffect(ledgerPath); + + const account = l.chart.getAccount(code); + if (!account) { + return yield* Effect.fail(new CliError({ + kind: "not-found", + message: `Account not found: ${code}`, + })); + } + + const children = l.chart.getChildren(code); + const allAccounts = [account, ...children]; + + const rows = allAccounts.map((a) => ({ + code: a.code, + name: a.name, + type: a.type, + parentCode: a.parentCode ?? "", + normalBalance: getNormalBalance(a.type), + })); + + yield* renderer.render(rows, { + columns: chartColumns, + ttyDefault: "table", + }, { format: resolvedFormat }); + }), +).pipe(Command.withDescription("Show account details and children")); diff --git a/packages/cli/src/commands/doctor.ts b/packages/cli/src/commands/doctor.ts new file mode 100644 index 0000000..e40d75e --- /dev/null +++ b/packages/cli/src/commands/doctor.ts @@ -0,0 +1,49 @@ +/** + * accounting doctor — check environment and dependencies + */ + +import { Command } from "@effect/cli"; +import { Effect } from "effect"; +import { + OutputRenderer, + resolveFormat, + formatOption, + jsonFlag, +} from "@emisso/cli-core"; +import { keyValueColumns } from "../formatters/accounting-table.js"; + +const options = { format: formatOption, json: jsonFlag }; + +function tryRequireVersion(pkg: string): string { + try { + // eslint-disable-next-line @typescript-eslint/no-require-imports + const p = require(`${pkg}/package.json`); + return `${p.version} ✓`; + } catch { + return "installed ✓"; + } +} + +export const doctorCommand = Command.make( + "doctor", + options, + ({ format, json }) => + Effect.gen(function* () { + const renderer = yield* OutputRenderer; + const resolvedFormat = resolveFormat(format, json); + + const checks: Array<{ field: string; value: string }> = [ + { field: "Node.js", value: `${process.version} ✓` }, + { field: "@emisso/accounting", value: tryRequireVersion("@emisso/accounting") }, + { field: "@emisso/cli-core", value: tryRequireVersion("@emisso/cli-core") }, + { field: "ACCOUNTING_LEDGER", value: process.env["ACCOUNTING_LEDGER"] ? `${process.env["ACCOUNTING_LEDGER"]} ✓` : "not set" }, + { field: "ACCOUNTING_REGIME", value: process.env["ACCOUNTING_REGIME"] ? `${process.env["ACCOUNTING_REGIME"]} ✓` : "not set" }, + { field: "ACCOUNTING_RUT", value: process.env["ACCOUNTING_RUT"] ? `${process.env["ACCOUNTING_RUT"]} ✓` : "not set" }, + ]; + + yield* renderer.render(checks, { + columns: keyValueColumns, + ttyDefault: "table", + }, { format: resolvedFormat }); + }), +).pipe(Command.withDescription("Check environment and dependencies")); diff --git a/packages/cli/src/commands/entry/add.ts b/packages/cli/src/commands/entry/add.ts new file mode 100644 index 0000000..bd95545 --- /dev/null +++ b/packages/cli/src/commands/entry/add.ts @@ -0,0 +1,85 @@ +/** + * accounting entry add — create journal entry from JSON file + */ + +import { readFileSync } from "node:fs"; +import { Command } from "@effect/cli"; +import { Effect } from "effect"; +import { + OutputRenderer, + CliError, + resolveFormat, + formatOption, + jsonFlag, + inputFileOption, +} from "@emisso/cli-core"; +import { entryLineColumns, formatEntryLines } from "../../formatters/accounting-table.js"; +import { ledgerOption } from "../../config/options.js"; +import { resolveLedgerPathEffect } from "../../config/resolve.js"; +import { loadLedgerEffect, saveLedgerEffect } from "../../config/ledger-io.js"; + +const options = { + ledger: ledgerOption, + input: inputFileOption, + format: formatOption, + json: jsonFlag, +}; + +interface EntryInput { + date: string; + description: string; + lines: Array<{ accountCode: string; debit: number; credit: number }>; + metadata?: Record; +} + +export const entryAddCommand = Command.make( + "add", + options, + ({ ledger, input, format, json }) => + Effect.gen(function* () { + const renderer = yield* OutputRenderer; + const resolvedFormat = resolveFormat(format, json); + + const ledgerPath = yield* resolveLedgerPathEffect({ ledger }); + + const entryData = yield* Effect.try({ + try: () => { + const raw = readFileSync(input, "utf-8"); + return JSON.parse(raw) as EntryInput; + }, + catch: (error) => new CliError({ + kind: "bad-args", + message: `Failed to read entry file: ${input}`, + detail: error instanceof Error ? error.message : String(error), + }), + }); + + const l = yield* loadLedgerEffect(ledgerPath); + + const entry = yield* Effect.try({ + try: () => { + const builder = l.entry(entryData.description).date(entryData.date); + for (const line of entryData.lines) { + if (line.debit > 0) builder.debit(line.accountCode, line.debit); + if (line.credit > 0) builder.credit(line.accountCode, line.credit); + } + if (entryData.metadata) builder.meta(entryData.metadata); + return builder.commit(); + }, + catch: (error) => new CliError({ + kind: "validation", + message: "Failed to create entry", + detail: error instanceof Error ? error.message : String(error), + }), + }); + + yield* saveLedgerEffect(ledgerPath, l); + + const rows = formatEntryLines(entry.lines, l.chart); + + yield* renderer.render(rows, { + columns: entryLineColumns, + ttyDefault: "table", + }, { format: resolvedFormat }); + }), +).pipe(Command.withDescription("Create a journal entry from a JSON file")); diff --git a/packages/cli/src/commands/entry/list.ts b/packages/cli/src/commands/entry/list.ts new file mode 100644 index 0000000..9d41451 --- /dev/null +++ b/packages/cli/src/commands/entry/list.ts @@ -0,0 +1,78 @@ +/** + * accounting entry list — list journal entries + */ + +import { Command, Options } from "@effect/cli"; +import { Effect, Option as O } from "effect"; +import { formatCLP } from "@emisso/accounting"; +import { + OutputRenderer, + resolveFormat, + formatOption, + jsonFlag, +} from "@emisso/cli-core"; +import { entryColumns } from "../../formatters/accounting-table.js"; +import { ledgerOption } from "../../config/options.js"; +import { resolveLedgerPathEffect } from "../../config/resolve.js"; +import { loadLedgerEffect } from "../../config/ledger-io.js"; + +const fromOption = Options.text("from").pipe( + Options.optional, + Options.withDescription("Start date filter (YYYY-MM-DD)"), +); + +const toOption = Options.text("to").pipe( + Options.optional, + Options.withDescription("End date filter (YYYY-MM-DD)"), +); + +const accountOption = Options.text("account").pipe( + Options.optional, + Options.withDescription("Filter by account code"), +); + +const options = { + ledger: ledgerOption, + from: fromOption, + to: toOption, + account: accountOption, + format: formatOption, + json: jsonFlag, +}; + +export const entryListCommand = Command.make( + "list", + options, + ({ ledger, from, to, account, format, json }) => + Effect.gen(function* () { + const renderer = yield* OutputRenderer; + const resolvedFormat = resolveFormat(format, json); + + const ledgerPath = yield* resolveLedgerPathEffect({ ledger }); + + const l = yield* loadLedgerEffect(ledgerPath); + + const entries = l.getEntries({ + from: O.getOrUndefined(from), + to: O.getOrUndefined(to), + accountCode: O.getOrUndefined(account), + }); + + const rows = entries.map((e) => { + const totalDebit = e.lines.reduce((sum, l) => sum + l.debit, 0); + const totalCredit = e.lines.reduce((sum, l) => sum + l.credit, 0); + return { + id: e.id.slice(0, 8), + date: e.date, + description: e.description, + totalDebit: formatCLP(totalDebit), + totalCredit: formatCLP(totalCredit), + }; + }); + + yield* renderer.render(rows, { + columns: entryColumns, + ttyDefault: "table", + }, { format: resolvedFormat }); + }), +).pipe(Command.withDescription("List journal entries with optional date and account filters")); diff --git a/packages/cli/src/commands/entry/show.ts b/packages/cli/src/commands/entry/show.ts new file mode 100644 index 0000000..5bce5a1 --- /dev/null +++ b/packages/cli/src/commands/entry/show.ts @@ -0,0 +1,54 @@ +/** + * accounting entry show — show entry detail with lines + */ + +import { Command, Args } from "@effect/cli"; +import { Effect } from "effect"; +import { + OutputRenderer, + CliError, + resolveFormat, + formatOption, + jsonFlag, +} from "@emisso/cli-core"; +import { entryLineColumns, formatEntryLines } from "../../formatters/accounting-table.js"; +import { ledgerOption } from "../../config/options.js"; +import { resolveLedgerPathEffect } from "../../config/resolve.js"; +import { loadLedgerEffect } from "../../config/ledger-io.js"; + +const idArg = Args.text({ name: "id" }).pipe( + Args.withDescription("Entry ID (full or prefix)"), +); + +const options = { ledger: ledgerOption, format: formatOption, json: jsonFlag }; + +export const entryShowCommand = Command.make( + "show", + { args: idArg, ...options }, + ({ args: id, ledger, format, json }) => + Effect.gen(function* () { + const renderer = yield* OutputRenderer; + const resolvedFormat = resolveFormat(format, json); + + const ledgerPath = yield* resolveLedgerPathEffect({ ledger }); + + const l = yield* loadLedgerEffect(ledgerPath); + + const entries = l.getEntries({}); + const entry = entries.find((e) => e.id === id || e.id.startsWith(id)); + + if (!entry) { + return yield* Effect.fail(new CliError({ + kind: "not-found", + message: `Entry not found: ${id}`, + })); + } + + const rows = formatEntryLines(entry.lines, l.chart); + + yield* renderer.render(rows, { + columns: entryLineColumns, + ttyDefault: "table", + }, { format: resolvedFormat }); + }), +).pipe(Command.withDescription("Show journal entry detail with all lines")); diff --git a/packages/cli/src/commands/entry/void.ts b/packages/cli/src/commands/entry/void.ts new file mode 100644 index 0000000..36bc34f --- /dev/null +++ b/packages/cli/src/commands/entry/void.ts @@ -0,0 +1,64 @@ +/** + * accounting entry void — void a journal entry + */ + +import { Command, Args, Options } from "@effect/cli"; +import { Effect } from "effect"; +import { + OutputRenderer, + CliError, + resolveFormat, + formatOption, + jsonFlag, +} from "@emisso/cli-core"; +import { entryLineColumns, formatEntryLines } from "../../formatters/accounting-table.js"; +import { ledgerOption } from "../../config/options.js"; +import { resolveLedgerPathEffect } from "../../config/resolve.js"; +import { loadLedgerEffect, saveLedgerEffect } from "../../config/ledger-io.js"; + +const idArg = Args.text({ name: "id" }).pipe( + Args.withDescription("Entry ID to void"), +); + +const reasonOption = Options.text("reason").pipe( + Options.withDescription("Reason for voiding"), +); + +const options = { + ledger: ledgerOption, + reason: reasonOption, + format: formatOption, + json: jsonFlag, +}; + +export const entryVoidCommand = Command.make( + "void", + { args: idArg, ...options }, + ({ args: id, ledger, reason, format, json }) => + Effect.gen(function* () { + const renderer = yield* OutputRenderer; + const resolvedFormat = resolveFormat(format, json); + + const ledgerPath = yield* resolveLedgerPathEffect({ ledger }); + + const l = yield* loadLedgerEffect(ledgerPath); + + const reversal = yield* Effect.try({ + try: () => l.void(id, reason), + catch: (error) => new CliError({ + kind: "validation", + message: "Failed to void entry", + detail: error instanceof Error ? error.message : String(error), + }), + }); + + yield* saveLedgerEffect(ledgerPath, l); + + const rows = formatEntryLines(reversal.lines, l.chart); + + yield* renderer.render(rows, { + columns: entryLineColumns, + ttyDefault: "table", + }, { format: resolvedFormat }); + }), +).pipe(Command.withDescription("Void a journal entry (creates a reversing entry)")); diff --git a/packages/cli/src/commands/import/invoice.ts b/packages/cli/src/commands/import/invoice.ts new file mode 100644 index 0000000..d4a2c89 --- /dev/null +++ b/packages/cli/src/commands/import/invoice.ts @@ -0,0 +1,85 @@ +/** + * accounting import invoice — generate journal entry from invoice data + */ + +import { readFileSync } from "node:fs"; +import { Command } from "@effect/cli"; +import { Effect } from "effect"; +import { fromInvoice, type InvoiceInput } from "@emisso/accounting"; +import { + OutputRenderer, + CliError, + resolveFormat, + formatOption, + jsonFlag, + inputFileOption, +} from "@emisso/cli-core"; +import { entryLineColumns, formatEntryLines } from "../../formatters/accounting-table.js"; +import { ledgerOption } from "../../config/options.js"; +import { resolveLedgerPathEffect } from "../../config/resolve.js"; +import { loadLedgerEffect, saveLedgerEffect } from "../../config/ledger-io.js"; + +const options = { + ledger: ledgerOption, + input: inputFileOption, + format: formatOption, + json: jsonFlag, +}; + +export const importInvoiceCommand = Command.make( + "invoice", + options, + ({ ledger, input, format, json }) => + Effect.gen(function* () { + const renderer = yield* OutputRenderer; + const resolvedFormat = resolveFormat(format, json); + + const ledgerPath = yield* resolveLedgerPathEffect({ ledger }); + + const invoiceData = yield* Effect.try({ + try: () => JSON.parse(readFileSync(input, "utf-8")) as InvoiceInput, + catch: (error) => new CliError({ + kind: "bad-args", + message: `Failed to read invoice file: ${input}`, + detail: error instanceof Error ? error.message : String(error), + }), + }); + + const template = yield* Effect.try({ + try: () => fromInvoice(invoiceData), + catch: (error) => new CliError({ + kind: "validation", + message: "Failed to generate entry from invoice", + detail: error instanceof Error ? error.message : String(error), + }), + }); + + const l = yield* loadLedgerEffect(ledgerPath); + + const entry = yield* Effect.try({ + try: () => { + const builder = l.entry(template.description).date(invoiceData.date); + for (const line of template.lines) { + if (line.debit > 0) builder.debit(line.accountCode, line.debit); + if (line.credit > 0) builder.credit(line.accountCode, line.credit); + } + builder.meta(template.metadata); + return builder.commit(); + }, + catch: (error) => new CliError({ + kind: "validation", + message: "Failed to commit entry", + detail: error instanceof Error ? error.message : String(error), + }), + }); + + yield* saveLedgerEffect(ledgerPath, l); + + const rows = formatEntryLines(entry.lines, l.chart); + + yield* renderer.render(rows, { + columns: entryLineColumns, + ttyDefault: "table", + }, { format: resolvedFormat }); + }), +).pipe(Command.withDescription("Import an invoice as a journal entry (venta or compra)")); diff --git a/packages/cli/src/commands/import/payroll.ts b/packages/cli/src/commands/import/payroll.ts new file mode 100644 index 0000000..0a1a9e6 --- /dev/null +++ b/packages/cli/src/commands/import/payroll.ts @@ -0,0 +1,84 @@ +/** + * accounting import payroll — generate journal entry from payroll data + */ + +import { readFileSync } from "node:fs"; +import { Command } from "@effect/cli"; +import { Effect } from "effect"; +import { fromPayroll, type PayrollInput } from "@emisso/accounting"; +import { + OutputRenderer, + CliError, + resolveFormat, + formatOption, + jsonFlag, + inputFileOption, +} from "@emisso/cli-core"; +import { entryLineColumns, formatEntryLines } from "../../formatters/accounting-table.js"; +import { ledgerOption } from "../../config/options.js"; +import { resolveLedgerPathEffect } from "../../config/resolve.js"; +import { loadLedgerEffect, saveLedgerEffect } from "../../config/ledger-io.js"; + +const options = { + ledger: ledgerOption, + input: inputFileOption, + format: formatOption, + json: jsonFlag, +}; + +export const importPayrollCommand = Command.make( + "payroll", + options, + ({ ledger, input, format, json }) => + Effect.gen(function* () { + const renderer = yield* OutputRenderer; + const resolvedFormat = resolveFormat(format, json); + + const ledgerPath = yield* resolveLedgerPathEffect({ ledger }); + + const payrollData = yield* Effect.try({ + try: () => JSON.parse(readFileSync(input, "utf-8")) as PayrollInput, + catch: (error) => new CliError({ + kind: "bad-args", + message: `Failed to read payroll file: ${input}`, + detail: error instanceof Error ? error.message : String(error), + }), + }); + + const template = yield* Effect.try({ + try: () => fromPayroll(payrollData), + catch: (error) => new CliError({ + kind: "validation", + message: "Failed to generate entry from payroll", + detail: error instanceof Error ? error.message : String(error), + }), + }); + + const l = yield* loadLedgerEffect(ledgerPath); + + const entry = yield* Effect.try({ + try: () => { + const builder = l.entry(template.description).date(payrollData.date); + for (const line of template.lines) { + if (line.debit > 0) builder.debit(line.accountCode, line.debit); + if (line.credit > 0) builder.credit(line.accountCode, line.credit); + } + builder.meta(template.metadata); + return builder.commit(); + }, + catch: (error) => new CliError({ + kind: "validation", message: "Failed to commit entry", + detail: error instanceof Error ? error.message : String(error), + }), + }); + + yield* saveLedgerEffect(ledgerPath, l); + + const rows = formatEntryLines(entry.lines, l.chart); + + yield* renderer.render(rows, { + columns: entryLineColumns, + ttyDefault: "table", + }, { format: resolvedFormat }); + }), +).pipe(Command.withDescription("Import payroll liquidation as a journal entry")); diff --git a/packages/cli/src/commands/import/rcv.ts b/packages/cli/src/commands/import/rcv.ts new file mode 100644 index 0000000..cebef20 --- /dev/null +++ b/packages/cli/src/commands/import/rcv.ts @@ -0,0 +1,84 @@ +/** + * accounting import rcv — generate journal entry from RCV record + */ + +import { readFileSync } from "node:fs"; +import { Command } from "@effect/cli"; +import { Effect } from "effect"; +import { fromRcv, type RcvInput } from "@emisso/accounting"; +import { + OutputRenderer, + CliError, + resolveFormat, + formatOption, + jsonFlag, + inputFileOption, +} from "@emisso/cli-core"; +import { entryLineColumns, formatEntryLines } from "../../formatters/accounting-table.js"; +import { ledgerOption } from "../../config/options.js"; +import { resolveLedgerPathEffect } from "../../config/resolve.js"; +import { loadLedgerEffect, saveLedgerEffect } from "../../config/ledger-io.js"; + +const options = { + ledger: ledgerOption, + input: inputFileOption, + format: formatOption, + json: jsonFlag, +}; + +export const importRcvCommand = Command.make( + "rcv", + options, + ({ ledger, input, format, json }) => + Effect.gen(function* () { + const renderer = yield* OutputRenderer; + const resolvedFormat = resolveFormat(format, json); + + const ledgerPath = yield* resolveLedgerPathEffect({ ledger }); + + const rcvData = yield* Effect.try({ + try: () => JSON.parse(readFileSync(input, "utf-8")) as RcvInput, + catch: (error) => new CliError({ + kind: "bad-args", + message: `Failed to read RCV file: ${input}`, + detail: error instanceof Error ? error.message : String(error), + }), + }); + + const template = yield* Effect.try({ + try: () => fromRcv(rcvData), + catch: (error) => new CliError({ + kind: "validation", + message: "Failed to generate entry from RCV", + detail: error instanceof Error ? error.message : String(error), + }), + }); + + const l = yield* loadLedgerEffect(ledgerPath); + + const entry = yield* Effect.try({ + try: () => { + const builder = l.entry(template.description).date(rcvData.date); + for (const line of template.lines) { + if (line.debit > 0) builder.debit(line.accountCode, line.debit); + if (line.credit > 0) builder.credit(line.accountCode, line.credit); + } + builder.meta(template.metadata); + return builder.commit(); + }, + catch: (error) => new CliError({ + kind: "validation", message: "Failed to commit entry", + detail: error instanceof Error ? error.message : String(error), + }), + }); + + yield* saveLedgerEffect(ledgerPath, l); + + const rows = formatEntryLines(entry.lines, l.chart); + + yield* renderer.render(rows, { + columns: entryLineColumns, + ttyDefault: "table", + }, { format: resolvedFormat }); + }), +).pipe(Command.withDescription("Import an RCV (Registro de Compras y Ventas) record as a journal entry")); diff --git a/packages/cli/src/commands/period/close.ts b/packages/cli/src/commands/period/close.ts new file mode 100644 index 0000000..8bb9b70 --- /dev/null +++ b/packages/cli/src/commands/period/close.ts @@ -0,0 +1,61 @@ +/** + * accounting period close — close a period (prevents new entries) + */ + +import { Command } from "@effect/cli"; +import { Effect } from "effect"; +import { formatPeriod } from "@emisso/accounting"; +import { + OutputRenderer, + CliError, + resolveFormat, + formatOption, + jsonFlag, +} from "@emisso/cli-core"; +import { keyValueColumns } from "../../formatters/accounting-table.js"; +import { ledgerOption, periodOption } from "../../config/options.js"; +import { resolveLedgerPathEffect, resolvePeriodEffect } from "../../config/resolve.js"; +import { loadLedgerEffect, saveLedgerEffect } from "../../config/ledger-io.js"; + +const options = { + ledger: ledgerOption, + period: periodOption, + format: formatOption, + json: jsonFlag, +}; + +export const periodCloseCommand = Command.make( + "close", + options, + ({ ledger, period, format, json }) => + Effect.gen(function* () { + const renderer = yield* OutputRenderer; + const resolvedFormat = resolveFormat(format, json); + + const ledgerPath = yield* resolveLedgerPathEffect({ ledger }); + const fiscalPeriod = yield* resolvePeriodEffect({ period }); + + const l = yield* loadLedgerEffect(ledgerPath); + + const result = yield* Effect.try({ + try: () => l.periods.closePeriod(fiscalPeriod.year, fiscalPeriod.month), + catch: (error) => new CliError({ + kind: "validation", + message: "Failed to close period", + detail: error instanceof Error ? error.message : String(error), + }), + }); + + yield* saveLedgerEffect(ledgerPath, l); + + yield* renderer.render( + [ + { field: "Period", value: formatPeriod(fiscalPeriod) }, + { field: "Status", value: result.status }, + { field: "Closed At", value: result.closedAt ?? "" }, + ], + { columns: keyValueColumns, ttyDefault: "table" }, + { format: resolvedFormat }, + ); + }), +).pipe(Command.withDescription("Close an accounting period")); diff --git a/packages/cli/src/commands/period/list.ts b/packages/cli/src/commands/period/list.ts new file mode 100644 index 0000000..1c13082 --- /dev/null +++ b/packages/cli/src/commands/period/list.ts @@ -0,0 +1,60 @@ +/** + * accounting period list — list periods with their status + */ + +import { Command } from "@effect/cli"; +import { Effect } from "effect"; +import { formatPeriod } from "@emisso/accounting"; +import { + OutputRenderer, + resolveFormat, + formatOption, + jsonFlag, +} from "@emisso/cli-core"; +import { periodColumns } from "../../formatters/accounting-table.js"; +import { ledgerOption } from "../../config/options.js"; +import { resolveLedgerPathEffect } from "../../config/resolve.js"; +import { loadLedgerEffect } from "../../config/ledger-io.js"; + +const options = { + ledger: ledgerOption, + format: formatOption, + json: jsonFlag, +}; + +export const periodListCommand = Command.make( + "list", + options, + ({ ledger, format, json }) => + Effect.gen(function* () { + const renderer = yield* OutputRenderer; + const resolvedFormat = resolveFormat(format, json); + + const ledgerPath = yield* resolveLedgerPathEffect({ ledger }); + + const l = yield* loadLedgerEffect(ledgerPath); + + const entries = l.getEntries({}); + const periodsSet = new Set(); + for (const entry of entries) { + const [y, m] = entry.date.split("-"); + periodsSet.add(`${y}-${m}`); + } + + const rows = Array.from(periodsSet).sort().map((p) => { + const [y, m] = p.split("-").map(Number); + const period = l.periods.getPeriod(y, m); + return { + period: formatPeriod({ year: y, month: m }), + status: period.status, + closedAt: period.closedAt ?? "", + lockedAt: period.lockedAt ?? "", + }; + }); + + yield* renderer.render(rows, { + columns: periodColumns, + ttyDefault: "table", + }, { format: resolvedFormat }); + }), +).pipe(Command.withDescription("List accounting periods with status")); diff --git a/packages/cli/src/commands/period/lock.ts b/packages/cli/src/commands/period/lock.ts new file mode 100644 index 0000000..bedd1f2 --- /dev/null +++ b/packages/cli/src/commands/period/lock.ts @@ -0,0 +1,61 @@ +/** + * accounting period lock — lock a period permanently (no reopening) + */ + +import { Command } from "@effect/cli"; +import { Effect } from "effect"; +import { formatPeriod } from "@emisso/accounting"; +import { + OutputRenderer, + CliError, + resolveFormat, + formatOption, + jsonFlag, +} from "@emisso/cli-core"; +import { keyValueColumns } from "../../formatters/accounting-table.js"; +import { ledgerOption, periodOption } from "../../config/options.js"; +import { resolveLedgerPathEffect, resolvePeriodEffect } from "../../config/resolve.js"; +import { loadLedgerEffect, saveLedgerEffect } from "../../config/ledger-io.js"; + +const options = { + ledger: ledgerOption, + period: periodOption, + format: formatOption, + json: jsonFlag, +}; + +export const periodLockCommand = Command.make( + "lock", + options, + ({ ledger, period, format, json }) => + Effect.gen(function* () { + const renderer = yield* OutputRenderer; + const resolvedFormat = resolveFormat(format, json); + + const ledgerPath = yield* resolveLedgerPathEffect({ ledger }); + const fiscalPeriod = yield* resolvePeriodEffect({ period }); + + const l = yield* loadLedgerEffect(ledgerPath); + + const result = yield* Effect.try({ + try: () => l.periods.lockPeriod(fiscalPeriod.year, fiscalPeriod.month), + catch: (error) => new CliError({ + kind: "validation", + message: "Failed to lock period", + detail: error instanceof Error ? error.message : String(error), + }), + }); + + yield* saveLedgerEffect(ledgerPath, l); + + yield* renderer.render( + [ + { field: "Period", value: formatPeriod(fiscalPeriod) }, + { field: "Status", value: result.status }, + { field: "Locked At", value: result.lockedAt ?? "" }, + ], + { columns: keyValueColumns, ttyDefault: "table" }, + { format: resolvedFormat }, + ); + }), +).pipe(Command.withDescription("Lock an accounting period permanently")); diff --git a/packages/cli/src/commands/period/open.ts b/packages/cli/src/commands/period/open.ts new file mode 100644 index 0000000..c6e1402 --- /dev/null +++ b/packages/cli/src/commands/period/open.ts @@ -0,0 +1,64 @@ +/** + * accounting period open — open (or reopen) a period + */ + +import { Command } from "@effect/cli"; +import { Effect } from "effect"; +import { formatPeriod } from "@emisso/accounting"; +import { + OutputRenderer, + CliError, + resolveFormat, + formatOption, + jsonFlag, +} from "@emisso/cli-core"; +import { keyValueColumns } from "../../formatters/accounting-table.js"; +import { ledgerOption, periodOption } from "../../config/options.js"; +import { resolveLedgerPathEffect, resolvePeriodEffect } from "../../config/resolve.js"; +import { loadLedgerEffect, saveLedgerEffect } from "../../config/ledger-io.js"; + +const options = { + ledger: ledgerOption, + period: periodOption, + format: formatOption, + json: jsonFlag, +}; + +export const periodOpenCommand = Command.make( + "open", + options, + ({ ledger, period, format, json }) => + Effect.gen(function* () { + const renderer = yield* OutputRenderer; + const resolvedFormat = resolveFormat(format, json); + + const ledgerPath = yield* resolveLedgerPathEffect({ ledger }); + const fiscalPeriod = yield* resolvePeriodEffect({ period }); + + const l = yield* loadLedgerEffect(ledgerPath); + + const result = yield* Effect.try({ + try: () => { + const p = l.periods.getPeriod(fiscalPeriod.year, fiscalPeriod.month); + if (p.status === "open") return p; + return l.periods.reopenPeriod(fiscalPeriod.year, fiscalPeriod.month); + }, + catch: (error) => new CliError({ + kind: "validation", + message: "Failed to open period", + detail: error instanceof Error ? error.message : String(error), + }), + }); + + yield* saveLedgerEffect(ledgerPath, l); + + yield* renderer.render( + [ + { field: "Period", value: formatPeriod(fiscalPeriod) }, + { field: "Status", value: result.status }, + ], + { columns: keyValueColumns, ttyDefault: "table" }, + { format: resolvedFormat }, + ); + }), +).pipe(Command.withDescription("Open or reopen an accounting period")); diff --git a/packages/cli/src/commands/report/balance-8-columnas.ts b/packages/cli/src/commands/report/balance-8-columnas.ts new file mode 100644 index 0000000..f43d6a0 --- /dev/null +++ b/packages/cli/src/commands/report/balance-8-columnas.ts @@ -0,0 +1,67 @@ +/** + * accounting report balance-8 — generate Balance de 8 Columnas for a period + */ + +import { Command } from "@effect/cli"; +import { Effect } from "effect"; +import { generateBalance8Columnas, formatCLP } from "@emisso/accounting"; +import { + OutputRenderer, + CliError, + resolveFormat, + formatOption, + jsonFlag, +} from "@emisso/cli-core"; +import { balance8Columns } from "../../formatters/accounting-table.js"; +import { resolveLedgerPathEffect, resolvePeriodEffect } from "../../config/resolve.js"; +import { loadLedgerEffect } from "../../config/ledger-io.js"; +import { ledgerOption, periodOption } from "../../config/options.js"; + +const options = { + ledger: ledgerOption, + period: periodOption, + format: formatOption, + json: jsonFlag, +}; + +export const reportBalance8Command = Command.make( + "balance-8", + options, + ({ ledger, period, format, json }) => + Effect.gen(function* () { + const renderer = yield* OutputRenderer; + const resolvedFormat = resolveFormat(format, json); + + const ledgerPath = yield* resolveLedgerPathEffect({ ledger }); + const fiscalPeriod = yield* resolvePeriodEffect({ period }); + const l = yield* loadLedgerEffect(ledgerPath); + + const entries = l.getEntries({}); + const result = yield* Effect.try({ + try: () => generateBalance8Columnas(l.chart, entries, fiscalPeriod), + catch: (error) => new CliError({ + kind: "validation", + message: "Failed to generate balance de 8 columnas", + detail: error instanceof Error ? error.message : String(error), + }), + }); + + const rows = result.rows.map((row) => ({ + accountCode: row.accountCode, + accountName: row.accountName, + debitMovement: row.debitMovement > 0 ? formatCLP(row.debitMovement) : "", + creditMovement: row.creditMovement > 0 ? formatCLP(row.creditMovement) : "", + debitBalance: row.debitBalance > 0 ? formatCLP(row.debitBalance) : "", + creditBalance: row.creditBalance > 0 ? formatCLP(row.creditBalance) : "", + activo: row.activo > 0 ? formatCLP(row.activo) : "", + pasivo: row.pasivo > 0 ? formatCLP(row.pasivo) : "", + perdida: row.perdida > 0 ? formatCLP(row.perdida) : "", + ganancia: row.ganancia > 0 ? formatCLP(row.ganancia) : "", + })); + + yield* renderer.render(rows, { + columns: balance8Columns, + ttyDefault: "table", + }, { format: resolvedFormat }); + }), +).pipe(Command.withDescription("Generate Balance de 8 Columnas report")); diff --git a/packages/cli/src/commands/report/balance-sheet.ts b/packages/cli/src/commands/report/balance-sheet.ts new file mode 100644 index 0000000..3ebeddf --- /dev/null +++ b/packages/cli/src/commands/report/balance-sheet.ts @@ -0,0 +1,80 @@ +/** + * accounting report balance-sheet — generate balance sheet for a period + */ + +import { Command } from "@effect/cli"; +import { Effect } from "effect"; +import { generateBalanceSheet, formatCLP } from "@emisso/accounting"; +import { + OutputRenderer, + CliError, + resolveFormat, + formatOption, + jsonFlag, +} from "@emisso/cli-core"; +import { balanceSheetColumns } from "../../formatters/accounting-table.js"; +import { resolveLedgerPathEffect, resolvePeriodEffect } from "../../config/resolve.js"; +import { loadLedgerEffect } from "../../config/ledger-io.js"; +import { ledgerOption, periodOption } from "../../config/options.js"; + +const options = { + ledger: ledgerOption, + period: periodOption, + format: formatOption, + json: jsonFlag, +}; + +export const reportBalanceSheetCommand = Command.make( + "balance-sheet", + options, + ({ ledger, period, format, json }) => + Effect.gen(function* () { + const renderer = yield* OutputRenderer; + const resolvedFormat = resolveFormat(format, json); + + const ledgerPath = yield* resolveLedgerPathEffect({ ledger }); + const fiscalPeriod = yield* resolvePeriodEffect({ period }); + const l = yield* loadLedgerEffect(ledgerPath); + + const entries = l.getEntries({}); + const result = yield* Effect.try({ + try: () => generateBalanceSheet(l.chart, entries, fiscalPeriod), + catch: (error) => new CliError({ + kind: "validation", + message: "Failed to generate balance sheet", + detail: error instanceof Error ? error.message : String(error), + }), + }); + + const rows: Array<{ concept: string; amount: string }> = []; + + rows.push({ concept: "── Activos ──", amount: "" }); + for (const a of result.assets?.current ?? []) { + rows.push({ concept: ` ${a.accountName}`, amount: formatCLP(a.balance) }); + } + for (const a of result.assets?.fixed ?? []) { + rows.push({ concept: ` ${a.accountName}`, amount: formatCLP(a.balance) }); + } + rows.push({ concept: "Total Activos", amount: formatCLP(result.assets?.total ?? 0) }); + + rows.push({ concept: "── Pasivos ──", amount: "" }); + for (const a of result.liabilities?.current ?? []) { + rows.push({ concept: ` ${a.accountName}`, amount: formatCLP(a.balance) }); + } + for (const a of result.liabilities?.longTerm ?? []) { + rows.push({ concept: ` ${a.accountName}`, amount: formatCLP(a.balance) }); + } + rows.push({ concept: "Total Pasivos", amount: formatCLP(result.liabilities?.total ?? 0) }); + + rows.push({ concept: "── Patrimonio ──", amount: "" }); + for (const a of result.equity?.accounts ?? []) { + rows.push({ concept: ` ${a.accountName}`, amount: formatCLP(a.balance) }); + } + rows.push({ concept: "Total Patrimonio", amount: formatCLP(result.equity?.total ?? 0) }); + + yield* renderer.render(rows, { + columns: balanceSheetColumns, + ttyDefault: "table", + }, { format: resolvedFormat }); + }), +).pipe(Command.withDescription("Generate balance sheet (Balance General)")); diff --git a/packages/cli/src/commands/report/income-statement.ts b/packages/cli/src/commands/report/income-statement.ts new file mode 100644 index 0000000..c00baab --- /dev/null +++ b/packages/cli/src/commands/report/income-statement.ts @@ -0,0 +1,76 @@ +/** + * accounting report income-statement — generate income statement for a period + */ + +import { Command } from "@effect/cli"; +import { Effect } from "effect"; +import { generateIncomeStatement, formatCLP } from "@emisso/accounting"; +import { + OutputRenderer, + CliError, + resolveFormat, + formatOption, + jsonFlag, +} from "@emisso/cli-core"; +import { incomeStatementColumns } from "../../formatters/accounting-table.js"; +import { resolveLedgerPathEffect, resolvePeriodEffect } from "../../config/resolve.js"; +import { loadLedgerEffect } from "../../config/ledger-io.js"; +import { ledgerOption, periodOption } from "../../config/options.js"; + +const options = { + ledger: ledgerOption, + period: periodOption, + format: formatOption, + json: jsonFlag, +}; + +export const reportIncomeStatementCommand = Command.make( + "income-statement", + options, + ({ ledger, period, format, json }) => + Effect.gen(function* () { + const renderer = yield* OutputRenderer; + const resolvedFormat = resolveFormat(format, json); + + const ledgerPath = yield* resolveLedgerPathEffect({ ledger }); + const fiscalPeriod = yield* resolvePeriodEffect({ period }); + const l = yield* loadLedgerEffect(ledgerPath); + + const entries = l.getEntries({}); + const result = yield* Effect.try({ + try: () => generateIncomeStatement(l.chart, entries, fiscalPeriod), + catch: (error) => new CliError({ + kind: "validation", + message: "Failed to generate income statement", + detail: error instanceof Error ? error.message : String(error), + }), + }); + + const rows: Array<{ concept: string; amount: string }> = []; + + rows.push({ concept: "── Ingresos ──", amount: "" }); + for (const a of result.revenue?.accounts ?? []) { + rows.push({ concept: ` ${a.accountName}`, amount: formatCLP(a.balance) }); + } + rows.push({ concept: "Total Ingresos", amount: formatCLP(result.revenue?.total ?? 0) }); + + rows.push({ concept: "── Costo de Ventas ──", amount: "" }); + rows.push({ concept: "Total Costo de Ventas", amount: formatCLP(result.costOfSales?.total ?? 0) }); + + rows.push({ concept: "Utilidad Bruta", amount: formatCLP(result.grossProfit ?? 0) }); + + rows.push({ concept: "── Gastos Operacionales ──", amount: "" }); + for (const a of result.operatingExpenses?.accounts ?? []) { + rows.push({ concept: ` ${a.accountName}`, amount: formatCLP(a.balance) }); + } + rows.push({ concept: "Total Gastos Operacionales", amount: formatCLP(result.operatingExpenses?.total ?? 0) }); + + rows.push({ concept: "Resultado Operacional", amount: formatCLP(result.operatingIncome ?? 0) }); + rows.push({ concept: "Resultado Neto", amount: formatCLP(result.netIncome ?? 0) }); + + yield* renderer.render(rows, { + columns: incomeStatementColumns, + ttyDefault: "table", + }, { format: resolvedFormat }); + }), +).pipe(Command.withDescription("Generate income statement (Estado de Resultados)")); diff --git a/packages/cli/src/commands/report/trial-balance.ts b/packages/cli/src/commands/report/trial-balance.ts new file mode 100644 index 0000000..967a94e --- /dev/null +++ b/packages/cli/src/commands/report/trial-balance.ts @@ -0,0 +1,61 @@ +/** + * accounting report trial-balance — generate trial balance for a period + */ + +import { Command } from "@effect/cli"; +import { Effect } from "effect"; +import { generateTrialBalance, formatCLP } from "@emisso/accounting"; +import { + OutputRenderer, + CliError, + resolveFormat, + formatOption, + jsonFlag, +} from "@emisso/cli-core"; +import { trialBalanceColumns } from "../../formatters/accounting-table.js"; +import { resolveLedgerPathEffect, resolvePeriodEffect } from "../../config/resolve.js"; +import { loadLedgerEffect } from "../../config/ledger-io.js"; +import { ledgerOption, periodOption } from "../../config/options.js"; + +const options = { + ledger: ledgerOption, + period: periodOption, + format: formatOption, + json: jsonFlag, +}; + +export const reportTrialBalanceCommand = Command.make( + "trial-balance", + options, + ({ ledger, period, format, json }) => + Effect.gen(function* () { + const renderer = yield* OutputRenderer; + const resolvedFormat = resolveFormat(format, json); + + const ledgerPath = yield* resolveLedgerPathEffect({ ledger }); + const fiscalPeriod = yield* resolvePeriodEffect({ period }); + const l = yield* loadLedgerEffect(ledgerPath); + + const entries = l.getEntries({}); + const result = yield* Effect.try({ + try: () => generateTrialBalance(l.chart, entries, fiscalPeriod), + catch: (error) => new CliError({ + kind: "validation", + message: "Failed to generate trial balance", + detail: error instanceof Error ? error.message : String(error), + }), + }); + + const rows = result.accounts.map((row) => ({ + accountCode: row.accountCode, + accountName: row.accountName, + debit: row.debit > 0 ? formatCLP(row.debit) : "", + credit: row.credit > 0 ? formatCLP(row.credit) : "", + })); + + yield* renderer.render(rows, { + columns: trialBalanceColumns, + ttyDefault: "table", + }, { format: resolvedFormat }); + }), +).pipe(Command.withDescription("Generate trial balance report for a period")); diff --git a/packages/cli/src/commands/tax/f29.ts b/packages/cli/src/commands/tax/f29.ts new file mode 100644 index 0000000..8030884 --- /dev/null +++ b/packages/cli/src/commands/tax/f29.ts @@ -0,0 +1,67 @@ +/** + * accounting tax f29 — prepare F29 tax form data for a period + */ + +import { Command } from "@effect/cli"; +import { Effect, Option as O } from "effect"; +import { prepareF29, formatCLP } from "@emisso/accounting"; +import { + OutputRenderer, + CliError, + resolveFormat, + formatOption, + jsonFlag, +} from "@emisso/cli-core"; +import { f29Columns } from "../../formatters/accounting-table.js"; +import { resolveLedgerPathEffect, resolvePeriodEffect, resolveRegimeEffect } from "../../config/resolve.js"; +import { loadLedgerEffect } from "../../config/ledger-io.js"; +import { ledgerOption, periodOption, regimeOption, remanenteOption } from "../../config/options.js"; + +const options = { + ledger: ledgerOption, + period: periodOption, + regime: regimeOption, + remanente: remanenteOption, + format: formatOption, + json: jsonFlag, +}; + +export const taxF29Command = Command.make( + "f29", + options, + ({ ledger, period, regime, remanente, format, json }) => + Effect.gen(function* () { + const renderer = yield* OutputRenderer; + const resolvedFormat = resolveFormat(format, json); + + const ledgerPath = yield* resolveLedgerPathEffect({ ledger }); + const fiscalPeriod = yield* resolvePeriodEffect({ period }); + const resolvedRegime = yield* resolveRegimeEffect({ regime }); + const l = yield* loadLedgerEffect(ledgerPath); + + const entries = l.getEntries({}); + const ivaRemanenteAnterior = O.getOrUndefined(remanente); + + const result = yield* Effect.try({ + try: () => prepareF29(entries, fiscalPeriod, { + regime: resolvedRegime, + ivaRemanenteAnterior, + }), + catch: (error) => new CliError({ + kind: "validation", + message: "Failed to prepare F29", + detail: error instanceof Error ? error.message : String(error), + }), + }); + + const rows = Object.entries(result.fields).map(([code, value]) => ({ + field: code, + value: formatCLP(value), + })); + + yield* renderer.render(rows, { + columns: f29Columns, + ttyDefault: "table", + }, { format: resolvedFormat }); + }), +).pipe(Command.withDescription("Prepare F29 tax form data (IVA + PPM)")); diff --git a/packages/cli/src/commands/tax/iva.ts b/packages/cli/src/commands/tax/iva.ts new file mode 100644 index 0000000..804ea06 --- /dev/null +++ b/packages/cli/src/commands/tax/iva.ts @@ -0,0 +1,65 @@ +/** + * accounting tax iva — calculate IVA for a period + */ + +import { Command } from "@effect/cli"; +import { Effect, Option as O } from "effect"; +import { calculateIva, formatCLP } from "@emisso/accounting"; +import { + OutputRenderer, + CliError, + resolveFormat, + formatOption, + jsonFlag, +} from "@emisso/cli-core"; +import { ivaColumns } from "../../formatters/accounting-table.js"; +import { resolveLedgerPathEffect, resolvePeriodEffect } from "../../config/resolve.js"; +import { loadLedgerEffect } from "../../config/ledger-io.js"; +import { ledgerOption, periodOption, remanenteOption } from "../../config/options.js"; + +const options = { + ledger: ledgerOption, + period: periodOption, + remanente: remanenteOption, + format: formatOption, + json: jsonFlag, +}; + +export const taxIvaCommand = Command.make( + "iva", + options, + ({ ledger, period, remanente, format, json }) => + Effect.gen(function* () { + const renderer = yield* OutputRenderer; + const resolvedFormat = resolveFormat(format, json); + + const ledgerPath = yield* resolveLedgerPathEffect({ ledger }); + const fiscalPeriod = yield* resolvePeriodEffect({ period }); + const l = yield* loadLedgerEffect(ledgerPath); + + const entries = l.getEntries({}); + const ivaRemanenteAnterior = O.getOrUndefined(remanente); + + const result = yield* Effect.try({ + try: () => calculateIva(entries, fiscalPeriod, { ivaRemanenteAnterior }), + catch: (error) => new CliError({ + kind: "validation", + message: "Failed to calculate IVA", + detail: error instanceof Error ? error.message : String(error), + }), + }); + + const rows = [ + { field: "Débito Fiscal", value: formatCLP(result.debitoFiscal) }, + { field: "Crédito Fiscal", value: formatCLP(result.creditoFiscal) }, + { field: "Remanente Anterior", value: formatCLP(result.ivaRemanenteAnterior) }, + { field: "IVA Determinado", value: formatCLP(result.ivaDeterminado) }, + { field: "Remanente", value: formatCLP(result.ivaRemanente) }, + ]; + + yield* renderer.render(rows, { + columns: ivaColumns, + ttyDefault: "table", + }, { format: resolvedFormat }); + }), +).pipe(Command.withDescription("Calculate IVA (VAT) for a period")); diff --git a/packages/cli/src/commands/tax/ppm.ts b/packages/cli/src/commands/tax/ppm.ts new file mode 100644 index 0000000..4f0b464 --- /dev/null +++ b/packages/cli/src/commands/tax/ppm.ts @@ -0,0 +1,73 @@ +/** + * accounting tax ppm — calculate PPM for a period + */ + +import { Command, Options } from "@effect/cli"; +import { Effect, Option as O } from "effect"; +import { calculatePpm, formatCLP } from "@emisso/accounting"; +import { + OutputRenderer, + CliError, + resolveFormat, + formatOption, + jsonFlag, +} from "@emisso/cli-core"; +import { keyValueColumns } from "../../formatters/accounting-table.js"; +import { resolveLedgerPathEffect, resolvePeriodEffect, resolveRegimeEffect } from "../../config/resolve.js"; +import { loadLedgerEffect } from "../../config/ledger-io.js"; +import { ledgerOption, periodOption, regimeOption } from "../../config/options.js"; + +const rateOption = Options.text("rate").pipe( + Options.optional, + Options.withDescription("Custom PPM rate (e.g. 0.025 for 2.5%)"), +); + +const options = { + ledger: ledgerOption, + period: periodOption, + regime: regimeOption, + rate: rateOption, + format: formatOption, + json: jsonFlag, +}; + +export const taxPpmCommand = Command.make( + "ppm", + options, + ({ ledger, period, regime, rate, format, json }) => + Effect.gen(function* () { + const renderer = yield* OutputRenderer; + const resolvedFormat = resolveFormat(format, json); + + const ledgerPath = yield* resolveLedgerPathEffect({ ledger }); + const fiscalPeriod = yield* resolvePeriodEffect({ period }); + const resolvedRegime = yield* resolveRegimeEffect({ regime }); + const l = yield* loadLedgerEffect(ledgerPath); + + const entries = l.getEntries({}); + const customRate = O.getOrUndefined(O.map(rate, (v) => parseFloat(v))); + + const result = yield* Effect.try({ + try: () => calculatePpm(entries, fiscalPeriod, { + regime: resolvedRegime, + rate: customRate, + }), + catch: (error) => new CliError({ + kind: "validation", + message: "Failed to calculate PPM", + detail: error instanceof Error ? error.message : String(error), + }), + }); + + const rows = [ + { field: "Base Imponible", value: formatCLP(result.baseImponible) }, + { field: "Tasa", value: `${(result.rate * 100).toFixed(2)}%` }, + { field: "PPM a Pagar", value: formatCLP(result.ppmAmount) }, + ]; + + yield* renderer.render(rows, { + columns: keyValueColumns, + ttyDefault: "table", + }, { format: resolvedFormat }); + }), +).pipe(Command.withDescription("Calculate PPM (Pago Provisional Mensual) for a period")); diff --git a/packages/cli/src/commands/xml/balance.ts b/packages/cli/src/commands/xml/balance.ts new file mode 100644 index 0000000..a1a73a0 --- /dev/null +++ b/packages/cli/src/commands/xml/balance.ts @@ -0,0 +1,81 @@ +/** + * accounting xml balance — generate Balance XML (8 columnas) for SII + */ + +import { Command } from "@effect/cli"; +import { Effect, Option as O } from "effect"; +import { + generateBalance8Columnas, + generateBalanceXml, +} from "@emisso/accounting"; +import { + OutputRenderer, + CliError, + resolveFormat, + formatOption, + jsonFlag, + outputFileOption, +} from "@emisso/cli-core"; +import { keyValueColumns, writeXmlOutput } from "../../formatters/accounting-table.js"; +import { resolveLedgerPathEffect, resolvePeriodEffect, resolveCompanyEffect } from "../../config/resolve.js"; +import { loadLedgerEffect } from "../../config/ledger-io.js"; +import { ledgerOption, periodOption, rutOption, razonSocialOption } from "../../config/options.js"; + +const options = { + ledger: ledgerOption, + period: periodOption, + rut: rutOption, + razonSocial: razonSocialOption, + output: outputFileOption, + format: formatOption, + json: jsonFlag, +}; + +export const xmlBalanceCommand = Command.make( + "balance", + options, + ({ ledger, period, rut, razonSocial, output, format, json }) => + Effect.gen(function* () { + const renderer = yield* OutputRenderer; + const resolvedFormat = resolveFormat(format, json); + + const ledgerPath = yield* resolveLedgerPathEffect({ ledger }); + const fiscalPeriod = yield* resolvePeriodEffect({ period }); + const company = yield* resolveCompanyEffect({ rut, razonSocial }); + const l = yield* loadLedgerEffect(ledgerPath); + + const entries = l.getEntries({}); + + const balanceData = yield* Effect.try({ + try: () => generateBalance8Columnas(l.chart, entries, fiscalPeriod), + catch: (error) => new CliError({ + kind: "validation", + message: "Failed to generate balance data", + detail: error instanceof Error ? error.message : String(error), + }), + }); + + const xml = yield* Effect.try({ + try: () => generateBalanceXml(balanceData, { + rut: company.rut, + razonSocial: company.razonSocial, + period: fiscalPeriod, + }), + catch: (error) => new CliError({ + kind: "validation", + message: "Failed to generate Balance XML", + detail: error instanceof Error ? error.message : String(error), + }), + }); + + yield* writeXmlOutput(xml, output); + + if (O.isSome(output)) { + yield* renderer.render( + [{ field: "Output", value: output.value }, { field: "Size", value: `${xml.length} bytes` }], + { columns: keyValueColumns, ttyDefault: "table" }, + { format: resolvedFormat }, + ); + } + }), +).pipe(Command.withDescription("Generate Balance (8 columnas) XML for SII")); diff --git a/packages/cli/src/commands/xml/libro-diario.ts b/packages/cli/src/commands/xml/libro-diario.ts new file mode 100644 index 0000000..f7ebaab --- /dev/null +++ b/packages/cli/src/commands/xml/libro-diario.ts @@ -0,0 +1,68 @@ +/** + * accounting xml libro-diario — generate Libro Diario XML for SII + */ + +import { Command } from "@effect/cli"; +import { Effect, Option as O } from "effect"; +import { generateLibroDiarioXml } from "@emisso/accounting"; +import { + OutputRenderer, + CliError, + resolveFormat, + formatOption, + jsonFlag, + outputFileOption, +} from "@emisso/cli-core"; +import { keyValueColumns, writeXmlOutput } from "../../formatters/accounting-table.js"; +import { resolveLedgerPathEffect, resolvePeriodEffect, resolveCompanyEffect } from "../../config/resolve.js"; +import { loadLedgerEffect } from "../../config/ledger-io.js"; +import { ledgerOption, periodOption, rutOption, razonSocialOption } from "../../config/options.js"; + +const options = { + ledger: ledgerOption, + period: periodOption, + rut: rutOption, + razonSocial: razonSocialOption, + output: outputFileOption, + format: formatOption, + json: jsonFlag, +}; + +export const xmlLibroDiarioCommand = Command.make( + "libro-diario", + options, + ({ ledger, period, rut, razonSocial, output, format, json }) => + Effect.gen(function* () { + const renderer = yield* OutputRenderer; + const resolvedFormat = resolveFormat(format, json); + + const ledgerPath = yield* resolveLedgerPathEffect({ ledger }); + const fiscalPeriod = yield* resolvePeriodEffect({ period }); + const company = yield* resolveCompanyEffect({ rut, razonSocial }); + const l = yield* loadLedgerEffect(ledgerPath); + + const entries = l.getEntries({}); + const xml = yield* Effect.try({ + try: () => generateLibroDiarioXml(entries, { + rut: company.rut, + razonSocial: company.razonSocial, + period: fiscalPeriod, + }), + catch: (error) => new CliError({ + kind: "validation", + message: "Failed to generate Libro Diario XML", + detail: error instanceof Error ? error.message : String(error), + }), + }); + + yield* writeXmlOutput(xml, output); + + if (O.isSome(output)) { + yield* renderer.render( + [{ field: "Output", value: output.value }, { field: "Size", value: `${xml.length} bytes` }], + { columns: keyValueColumns, ttyDefault: "table" }, + { format: resolvedFormat }, + ); + } + }), +).pipe(Command.withDescription("Generate Libro Diario XML for SII")); diff --git a/packages/cli/src/commands/xml/libro-mayor.ts b/packages/cli/src/commands/xml/libro-mayor.ts new file mode 100644 index 0000000..1f85db6 --- /dev/null +++ b/packages/cli/src/commands/xml/libro-mayor.ts @@ -0,0 +1,68 @@ +/** + * accounting xml libro-mayor — generate Libro Mayor XML for SII + */ + +import { Command } from "@effect/cli"; +import { Effect, Option as O } from "effect"; +import { generateLibroMayorXml } from "@emisso/accounting"; +import { + OutputRenderer, + CliError, + resolveFormat, + formatOption, + jsonFlag, + outputFileOption, +} from "@emisso/cli-core"; +import { keyValueColumns, writeXmlOutput } from "../../formatters/accounting-table.js"; +import { resolveLedgerPathEffect, resolvePeriodEffect, resolveCompanyEffect } from "../../config/resolve.js"; +import { loadLedgerEffect } from "../../config/ledger-io.js"; +import { ledgerOption, periodOption, rutOption, razonSocialOption } from "../../config/options.js"; + +const options = { + ledger: ledgerOption, + period: periodOption, + rut: rutOption, + razonSocial: razonSocialOption, + output: outputFileOption, + format: formatOption, + json: jsonFlag, +}; + +export const xmlLibroMayorCommand = Command.make( + "libro-mayor", + options, + ({ ledger, period, rut, razonSocial, output, format, json }) => + Effect.gen(function* () { + const renderer = yield* OutputRenderer; + const resolvedFormat = resolveFormat(format, json); + + const ledgerPath = yield* resolveLedgerPathEffect({ ledger }); + const fiscalPeriod = yield* resolvePeriodEffect({ period }); + const company = yield* resolveCompanyEffect({ rut, razonSocial }); + const l = yield* loadLedgerEffect(ledgerPath); + + const entries = l.getEntries({}); + const xml = yield* Effect.try({ + try: () => generateLibroMayorXml(l.chart, entries, { + rut: company.rut, + razonSocial: company.razonSocial, + period: fiscalPeriod, + }), + catch: (error) => new CliError({ + kind: "validation", + message: "Failed to generate Libro Mayor XML", + detail: error instanceof Error ? error.message : String(error), + }), + }); + + yield* writeXmlOutput(xml, output); + + if (O.isSome(output)) { + yield* renderer.render( + [{ field: "Output", value: output.value }, { field: "Size", value: `${xml.length} bytes` }], + { columns: keyValueColumns, ttyDefault: "table" }, + { format: resolvedFormat }, + ); + } + }), +).pipe(Command.withDescription("Generate Libro Mayor XML for SII")); diff --git a/packages/cli/src/config/ledger-io.ts b/packages/cli/src/config/ledger-io.ts new file mode 100644 index 0000000..2daa297 --- /dev/null +++ b/packages/cli/src/config/ledger-io.ts @@ -0,0 +1,116 @@ +/** + * Ledger file I/O — read/write JSON ledger files + */ + +import { readFileSync, writeFileSync, existsSync } from "node:fs"; +import { + createLedger, + createSiiChart, + type Ledger, + type LedgerConfig, + type JournalEntry, +} from "@emisso/accounting"; +import { CliError } from "@emisso/cli-core"; +import { Effect } from "effect"; + +interface LedgerJson { + config: LedgerConfig; + entries: JournalEntry[]; +} + +/** Lazily cached SII chart — created once per process */ +let _siiChart: ReturnType | undefined; +function getSiiChart() { + return (_siiChart ??= createSiiChart()); +} + +export function loadLedger(path: string): Ledger { + let raw: string; + try { + raw = readFileSync(path, "utf-8"); + } catch (error: unknown) { + const err = error as NodeJS.ErrnoException; + if (err.code === "ENOENT") { + throw new CliError({ + kind: "not-found", + message: `Ledger file not found: ${path}`, + detail: "Create a ledger first with: accounting chart seed --ledger ", + }); + } + throw new CliError({ + kind: "general", + message: `Failed to read ledger file: ${path}`, + detail: err.message, + }); + } + + try { + const data = JSON.parse(raw) as LedgerJson; + return createLedger({ + chart: getSiiChart(), + config: data.config, + entries: data.entries, + }); + } catch (error) { + if (error instanceof CliError) throw error; + throw new CliError({ + kind: "general", + message: `Failed to parse ledger file: ${path}`, + detail: error instanceof Error ? error.message : String(error), + }); + } +} + +export function saveLedger(path: string, ledger: Ledger): void { + try { + const data = ledger.toJSON(); + writeFileSync(path, JSON.stringify(data, null, 2) + "\n", "utf-8"); + } catch (error) { + throw new CliError({ + kind: "general", + message: `Failed to write ledger file: ${path}`, + detail: error instanceof Error ? error.message : String(error), + }); + } +} + +export function initLedger(path: string, config?: LedgerConfig): Ledger { + if (existsSync(path)) { + throw new CliError({ + kind: "validation", + message: `Ledger file already exists: ${path}`, + detail: "Use --force to overwrite, or choose a different path", + }); + } + const ledger = createLedger({ chart: getSiiChart(), config }); + saveLedger(path, ledger); + return ledger; +} + +export function initLedgerForce(path: string, config?: LedgerConfig): Ledger { + const ledger = createLedger({ chart: getSiiChart(), config }); + saveLedger(path, ledger); + return ledger; +} + +/* ── Effect wrappers ───────────────────────────────────────────── */ + +export const loadLedgerEffect = (path: string): Effect.Effect => + Effect.try({ + try: () => loadLedger(path), + catch: (e) => e instanceof CliError ? e : new CliError({ + kind: "general", + message: "Failed to load ledger", + detail: e instanceof Error ? e.message : String(e), + }), + }); + +export const saveLedgerEffect = (path: string, ledger: Ledger): Effect.Effect => + Effect.try({ + try: () => saveLedger(path, ledger), + catch: (e) => e instanceof CliError ? e : new CliError({ + kind: "general", + message: "Failed to save ledger", + detail: e instanceof Error ? e.message : String(e), + }), + }); diff --git a/packages/cli/src/config/options.ts b/packages/cli/src/config/options.ts new file mode 100644 index 0000000..5ba8430 --- /dev/null +++ b/packages/cli/src/config/options.ts @@ -0,0 +1,34 @@ +/** + * Shared CLI option definitions — imported by all commands + */ + +import { Options } from "@effect/cli"; + +export const ledgerOption = Options.text("ledger").pipe( + Options.optional, + Options.withDescription("Path to ledger JSON file"), +); + +export const periodOption = Options.text("period").pipe( + Options.withDescription("Period in YYYY-MM format"), +); + +export const regimeOption = Options.text("regime").pipe( + Options.optional, + Options.withDescription("Tax regime: 14A, 14D-N3, or 14D-N8"), +); + +export const rutOption = Options.text("rut").pipe( + Options.optional, + Options.withDescription("Company RUT"), +); + +export const razonSocialOption = Options.text("razon-social").pipe( + Options.optional, + Options.withDescription("Company name (razón social)"), +); + +export const remanenteOption = Options.integer("remanente").pipe( + Options.optional, + Options.withDescription("IVA remanente from previous period (CLP)"), +); diff --git a/packages/cli/src/config/resolve.ts b/packages/cli/src/config/resolve.ts new file mode 100644 index 0000000..0888ecd --- /dev/null +++ b/packages/cli/src/config/resolve.ts @@ -0,0 +1,157 @@ +/** + * Config resolution: CLI flags → env vars → defaults + * + * Each resolver has a sync version (throws CliError) and an Effect version + * (returns Effect) to avoid try/catch boilerplate in commands. + */ + +import type { FiscalPeriod } from "@emisso/accounting"; +import { CliError } from "@emisso/cli-core"; +import { Effect, Option as O } from "effect"; +import type { Option } from "effect"; + +export interface LedgerFlags { + ledger: Option.Option; +} + +export interface CompanyFlags { + rut: Option.Option; + razonSocial: Option.Option; +} + +export interface RegimeFlags { + regime: Option.Option; +} + +const VALID_ACCOUNT_TYPES = ["asset", "liability", "equity", "revenue", "expense"] as const; +type AccountType = (typeof VALID_ACCOUNT_TYPES)[number]; + +/* ── Sync resolvers (used in tests) ────────────────────────────── */ + +export function resolveLedgerPath(flags: LedgerFlags): string { + const flagValue = O.getOrUndefined(flags.ledger); + if (flagValue !== undefined) return flagValue; + + const envValue = process.env.ACCOUNTING_LEDGER; + if (envValue !== undefined && envValue !== "") return envValue; + + throw new CliError({ + kind: "bad-args", + message: "Missing required option: --ledger", + detail: "Provide --ledger or set ACCOUNTING_LEDGER environment variable", + }); +} + +export function parsePeriod(value: string): FiscalPeriod { + const match = value.match(/^(\d{4})-(\d{2})$/); + if (!match) { + throw new CliError({ + kind: "bad-args", + message: `Invalid period format: ${value}`, + detail: "Expected YYYY-MM (e.g. 2024-03)", + }); + } + const month = parseInt(match[2], 10); + if (month < 1 || month > 12) { + throw new CliError({ + kind: "bad-args", + message: `Invalid month: ${month}`, + detail: "Month must be between 1 and 12", + }); + } + return { year: parseInt(match[1], 10), month }; +} + +export function resolvePeriod(flags: { period: string } | { period: Option.Option }): FiscalPeriod { + const value = typeof flags.period === "string" + ? flags.period + : O.getOrUndefined(flags.period); + if (value === undefined) { + throw new CliError({ + kind: "bad-args", + message: "Missing required option: --period", + detail: "Provide --period YYYY-MM", + }); + } + return parsePeriod(value); +} + +export function resolveRegime(flags: RegimeFlags): "14A" | "14D-N3" | "14D-N8" { + const value = O.getOrUndefined(flags.regime) ?? process.env.ACCOUNTING_REGIME; + if (!value) { + throw new CliError({ + kind: "bad-args", + message: "Missing required option: --regime", + detail: "Provide --regime 14A|14D-N3|14D-N8 or set ACCOUNTING_REGIME", + }); + } + if (value !== "14A" && value !== "14D-N3" && value !== "14D-N8") { + throw new CliError({ + kind: "bad-args", + message: `Invalid regime: ${value}`, + detail: "Must be '14A', '14D-N3', or '14D-N8'", + }); + } + return value; +} + +export function resolveCompany(flags: CompanyFlags): { rut: string; razonSocial: string } { + const rut = O.getOrUndefined(flags.rut) ?? process.env.ACCOUNTING_RUT; + if (!rut) { + throw new CliError({ + kind: "bad-args", + message: "Missing required option: --rut", + detail: "Provide --rut or set ACCOUNTING_RUT environment variable", + }); + } + const razonSocial = O.getOrUndefined(flags.razonSocial) ?? process.env.ACCOUNTING_RAZON_SOCIAL; + if (!razonSocial) { + throw new CliError({ + kind: "bad-args", + message: "Missing required option: --razon-social", + detail: "Provide --razon-social or set ACCOUNTING_RAZON_SOCIAL environment variable", + }); + } + return { rut, razonSocial }; +} + +export function resolveAccountType(type: Option.Option): AccountType | undefined { + const value = O.getOrUndefined(type); + if (value === undefined) return undefined; + if (!VALID_ACCOUNT_TYPES.includes(value as AccountType)) { + throw new CliError({ + kind: "bad-args", + message: `Invalid account type: ${value}`, + detail: `Must be one of: ${VALID_ACCOUNT_TYPES.join(", ")}`, + }); + } + return value as AccountType; +} + +/* ── Effect wrappers (used in commands) ────────────────────────── */ + +function wrapResolve(fn: () => A): Effect.Effect { + return Effect.try({ + try: fn, + catch: (e) => e instanceof CliError ? e : new CliError({ + kind: "general", + message: "Configuration error", + detail: e instanceof Error ? e.message : String(e), + }), + }); +} + +export const resolveLedgerPathEffect = (flags: LedgerFlags) => + wrapResolve(() => resolveLedgerPath(flags)); + +export const resolvePeriodEffect = (flags: { period: string } | { period: Option.Option }) => + wrapResolve(() => resolvePeriod(flags)); + +export const resolveRegimeEffect = (flags: RegimeFlags) => + wrapResolve(() => resolveRegime(flags)); + +export const resolveCompanyEffect = (flags: CompanyFlags) => + wrapResolve(() => resolveCompany(flags)); + +export const resolveAccountTypeEffect = (type: Option.Option) => + wrapResolve(() => resolveAccountType(type)); diff --git a/packages/cli/src/formatters/accounting-table.ts b/packages/cli/src/formatters/accounting-table.ts new file mode 100644 index 0000000..a9337b4 --- /dev/null +++ b/packages/cli/src/formatters/accounting-table.ts @@ -0,0 +1,130 @@ +/** + * Column definitions and formatting helpers for accounting CLI output tables + */ + +import { writeFileSync } from "node:fs"; +import { formatCLP } from "@emisso/accounting"; +import type { Column } from "@emisso/cli-core"; +import { CliError } from "@emisso/cli-core"; +import { Effect, Option as O } from "effect"; + +/* ── Column definitions ────────────────────────────────────────── */ + +export const chartColumns: Column[] = [ + { key: "code", label: "Code", width: 14 }, + { key: "name", label: "Name", width: 30 }, + { key: "type", label: "Type", width: 10 }, + { key: "parentCode", label: "Parent", width: 14 }, + { key: "normalBalance", label: "Normal", width: 8 }, +]; + +export const entryColumns: Column[] = [ + { key: "id", label: "ID", width: 10 }, + { key: "date", label: "Date", width: 12 }, + { key: "description", label: "Description", width: 30 }, + { key: "totalDebit", label: "Debit", align: "right" }, + { key: "totalCredit", label: "Credit", align: "right" }, +]; + +export const entryLineColumns: Column[] = [ + { key: "accountCode", label: "Account", width: 14 }, + { key: "accountName", label: "Name", width: 25 }, + { key: "debit", label: "Debit", align: "right" }, + { key: "credit", label: "Credit", align: "right" }, +]; + +export const trialBalanceColumns: Column[] = [ + { key: "accountCode", label: "Code", width: 14 }, + { key: "accountName", label: "Account", width: 30 }, + { key: "debit", label: "Debit", align: "right" }, + { key: "credit", label: "Credit", align: "right" }, +]; + +export const balance8Columns: Column[] = [ + { key: "accountCode", label: "Code", width: 14 }, + { key: "accountName", label: "Account", width: 22 }, + { key: "debitMovement", label: "Deb Mov", align: "right" }, + { key: "creditMovement", label: "Cre Mov", align: "right" }, + { key: "debitBalance", label: "Deb Bal", align: "right" }, + { key: "creditBalance", label: "Cre Bal", align: "right" }, + { key: "activo", label: "Activo", align: "right" }, + { key: "pasivo", label: "Pasivo", align: "right" }, + { key: "perdida", label: "Pérdida", align: "right" }, + { key: "ganancia", label: "Ganancia", align: "right" }, +]; + +export const incomeStatementColumns: Column[] = [ + { key: "concept", label: "Concepto", width: 35 }, + { key: "amount", label: "Monto (CLP)", align: "right" }, +]; + +export const balanceSheetColumns: Column[] = [ + { key: "concept", label: "Concepto", width: 35 }, + { key: "amount", label: "Monto (CLP)", align: "right" }, +]; + +export const ivaColumns: Column[] = [ + { key: "field", label: "Campo", width: 25 }, + { key: "value", label: "Valor", align: "right" }, +]; + +export const f29Columns: Column[] = [ + { key: "field", label: "Campo", width: 30 }, + { key: "value", label: "Valor", align: "right" }, +]; + +export const periodColumns: Column[] = [ + { key: "period", label: "Período", width: 10 }, + { key: "status", label: "Estado", width: 10 }, + { key: "closedAt", label: "Cerrado", width: 22 }, + { key: "lockedAt", label: "Bloqueado", width: 22 }, +]; + +export const keyValueColumns: Column[] = [ + { key: "field", label: "Campo" }, + { key: "value", label: "Valor" }, +]; + +/* ── Formatting helpers ────────────────────────────────────────── */ + +interface EntryLine { + accountCode: string; + debit: number; + credit: number; +} + +interface Chart { + getAccount(code: string): { name: string } | undefined; +} + +/** Map journal entry lines to display rows with account names and formatted CLP amounts */ +export function formatEntryLines(lines: EntryLine[], chart: Chart) { + return lines.map((line) => { + const account = chart.getAccount(line.accountCode); + return { + accountCode: line.accountCode, + accountName: account?.name ?? "", + debit: line.debit > 0 ? formatCLP(line.debit) : "", + credit: line.credit > 0 ? formatCLP(line.credit) : "", + }; + }); +} + +/** Write XML to file or stdout, rendering a summary when writing to file */ +export function writeXmlOutput( + xml: string, + output: O.Option, +): Effect.Effect { + const outputPath = O.getOrUndefined(output); + if (outputPath) { + return Effect.try({ + try: () => writeFileSync(outputPath, xml, "utf-8"), + catch: (error) => new CliError({ + kind: "general", + message: `Failed to write XML: ${outputPath}`, + detail: error instanceof Error ? error.message : String(error), + }), + }); + } + return Effect.sync(() => process.stdout.write(xml + "\n")); +} diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts new file mode 100644 index 0000000..1b1a323 --- /dev/null +++ b/packages/cli/src/index.ts @@ -0,0 +1,104 @@ +/** + * Root command composition for @emisso/accounting-cli + */ + +import { Command } from "@effect/cli"; + +// Chart commands +import { chartListCommand } from "./commands/chart/list.js"; +import { chartSeedCommand } from "./commands/chart/seed.js"; +import { chartShowCommand } from "./commands/chart/show.js"; + +// Entry commands +import { entryAddCommand } from "./commands/entry/add.js"; +import { entryListCommand } from "./commands/entry/list.js"; +import { entryShowCommand } from "./commands/entry/show.js"; +import { entryVoidCommand } from "./commands/entry/void.js"; + +// Import commands +import { importInvoiceCommand } from "./commands/import/invoice.js"; +import { importPayrollCommand } from "./commands/import/payroll.js"; +import { importRcvCommand } from "./commands/import/rcv.js"; + +// Report commands +import { reportTrialBalanceCommand } from "./commands/report/trial-balance.js"; +import { reportBalance8Command } from "./commands/report/balance-8-columnas.js"; +import { reportIncomeStatementCommand } from "./commands/report/income-statement.js"; +import { reportBalanceSheetCommand } from "./commands/report/balance-sheet.js"; + +// Tax commands +import { taxIvaCommand } from "./commands/tax/iva.js"; +import { taxPpmCommand } from "./commands/tax/ppm.js"; +import { taxF29Command } from "./commands/tax/f29.js"; + +// XML commands +import { xmlLibroDiarioCommand } from "./commands/xml/libro-diario.js"; +import { xmlLibroMayorCommand } from "./commands/xml/libro-mayor.js"; +import { xmlBalanceCommand } from "./commands/xml/balance.js"; + +// Period commands +import { periodListCommand } from "./commands/period/list.js"; +import { periodOpenCommand } from "./commands/period/open.js"; +import { periodCloseCommand } from "./commands/period/close.js"; +import { periodLockCommand } from "./commands/period/lock.js"; + +// Doctor +import { doctorCommand } from "./commands/doctor.js"; + +/* ── Subcommand groups ─────────────────────────────────────────── */ + +const chartCommand = Command.make("chart").pipe( + Command.withDescription("Chart of accounts management"), + Command.withSubcommands([chartListCommand, chartSeedCommand, chartShowCommand]), +); + +const entryCommand = Command.make("entry").pipe( + Command.withDescription("Journal entry operations"), + Command.withSubcommands([entryAddCommand, entryListCommand, entryShowCommand, entryVoidCommand]), +); + +const importCommand = Command.make("import").pipe( + Command.withDescription("Import documents as journal entries"), + Command.withSubcommands([importInvoiceCommand, importPayrollCommand, importRcvCommand]), +); + +const reportCommand = Command.make("report").pipe( + Command.withDescription("Financial reports"), + Command.withSubcommands([ + reportTrialBalanceCommand, + reportBalance8Command, + reportIncomeStatementCommand, + reportBalanceSheetCommand, + ]), +); + +const taxCommand = Command.make("tax").pipe( + Command.withDescription("Tax calculations and forms"), + Command.withSubcommands([taxIvaCommand, taxPpmCommand, taxF29Command]), +); + +const xmlCommand = Command.make("xml").pipe( + Command.withDescription("Generate SII XML files"), + Command.withSubcommands([xmlLibroDiarioCommand, xmlLibroMayorCommand, xmlBalanceCommand]), +); + +const periodCommand = Command.make("period").pipe( + Command.withDescription("Period management"), + Command.withSubcommands([periodListCommand, periodOpenCommand, periodCloseCommand, periodLockCommand]), +); + +/* ── Root command ──────────────────────────────────────────────── */ + +export const rootCommand = Command.make("accounting").pipe( + Command.withDescription("Emisso Accounting CLI — double-entry bookkeeping for Chilean businesses"), + Command.withSubcommands([ + chartCommand, + entryCommand, + importCommand, + reportCommand, + taxCommand, + xmlCommand, + periodCommand, + doctorCommand, + ]), +); diff --git a/packages/cli/tests/commands.test.ts b/packages/cli/tests/commands.test.ts new file mode 100644 index 0000000..bfbbd1f --- /dev/null +++ b/packages/cli/tests/commands.test.ts @@ -0,0 +1,110 @@ +import { describe, expect, it, afterEach } from "vitest"; +import { unlinkSync } from "node:fs"; +import { join } from "node:path"; +import { tmpdir } from "node:os"; +import { initLedger, loadLedger, saveLedger } from "../src/config/ledger-io"; + +function tempPath(): string { + return join(tmpdir(), `test-cmd-${Date.now()}-${Math.random().toString(36).slice(2)}.json`); +} + +describe("chart seed integration", () => { + const cleanupPaths: string[] = []; + + afterEach(() => { + for (const p of cleanupPaths) { + try { unlinkSync(p); } catch { /* ignore */ } + } + cleanupPaths.length = 0; + }); + + it("initLedger creates a ledger with SII chart accounts", () => { + const path = tempPath(); + cleanupPaths.push(path); + + initLedger(path); + const ledger = loadLedger(path); + const accounts = ledger.chart.toArray(); + + expect(accounts.length).toBeGreaterThan(100); + }); +}); + +describe("entry lifecycle", () => { + const cleanupPaths: string[] = []; + + afterEach(() => { + for (const p of cleanupPaths) { + try { unlinkSync(p); } catch { /* ignore */ } + } + cleanupPaths.length = 0; + }); + + it("adds, lists, and voids an entry", () => { + const path = tempPath(); + cleanupPaths.push(path); + + initLedger(path); + const ledger = loadLedger(path); + + // Add entry + const entry = ledger + .entry("Venta de servicios") + .date("2025-03-15") + .debit("1.1.01.001", 1190000) + .credit("4.1.01.001", 1000000) + .credit("2.1.05.001", 190000) + .commit(); + + saveLedger(path, ledger); + + // List entries + const entries = ledger.getEntries({}); + expect(entries).toHaveLength(1); + expect(entries[0].description).toBe("Venta de servicios"); + + // Void entry + const reversal = ledger.void(entry.id, "Correction"); + saveLedger(path, ledger); + + const allEntries = ledger.getEntries({}); + expect(allEntries).toHaveLength(2); + expect(reversal.lines.length).toBe(entry.lines.length); + }); +}); + +describe("report generation", () => { + const cleanupPaths: string[] = []; + + afterEach(() => { + for (const p of cleanupPaths) { + try { unlinkSync(p); } catch { /* ignore */ } + } + cleanupPaths.length = 0; + }); + + it("generates trial balance from entries", async () => { + const { generateTrialBalance } = await import("@emisso/accounting"); + const path = tempPath(); + cleanupPaths.push(path); + + initLedger(path); + const ledger = loadLedger(path); + + ledger + .entry("Test sale") + .date("2025-03-15") + .debit("1.1.01.001", 1000000) + .credit("4.1.01.001", 1000000) + .commit(); + + const entries = ledger.getEntries({}); + const tb = generateTrialBalance(ledger.chart, entries, { year: 2025, month: 3 }); + + expect(tb.accounts.length).toBeGreaterThan(0); + + const totalDebit = tb.accounts.reduce((s: number, r: { debit: number }) => s + r.debit, 0); + const totalCredit = tb.accounts.reduce((s: number, r: { credit: number }) => s + r.credit, 0); + expect(totalDebit).toBe(totalCredit); + }); +}); diff --git a/packages/cli/tests/config.test.ts b/packages/cli/tests/config.test.ts new file mode 100644 index 0000000..742dbfb --- /dev/null +++ b/packages/cli/tests/config.test.ts @@ -0,0 +1,188 @@ +import { describe, expect, it, afterEach } from "vitest"; +import { Option } from "effect"; +import { + resolveLedgerPath, + resolvePeriod, + parsePeriod, + resolveRegime, + resolveCompany, + resolveAccountType, +} from "../src/config/resolve"; + +describe("resolveLedgerPath", () => { + const originalEnv = { ...process.env }; + + afterEach(() => { + process.env = { ...originalEnv }; + }); + + it("resolves from CLI flag", () => { + const path = resolveLedgerPath({ ledger: Option.some("/path/ledger.json") }); + expect(path).toBe("/path/ledger.json"); + }); + + it("falls back to env var", () => { + process.env.ACCOUNTING_LEDGER = "/env/ledger.json"; + const path = resolveLedgerPath({ ledger: Option.none() }); + expect(path).toBe("/env/ledger.json"); + }); + + it("CLI flag takes precedence over env var", () => { + process.env.ACCOUNTING_LEDGER = "/env/ledger.json"; + const path = resolveLedgerPath({ ledger: Option.some("/flag/ledger.json") }); + expect(path).toBe("/flag/ledger.json"); + }); + + it("throws on missing ledger path", () => { + delete process.env.ACCOUNTING_LEDGER; + expect(() => resolveLedgerPath({ ledger: Option.none() })).toThrow( + "Missing required option: --ledger", + ); + }); +}); + +describe("parsePeriod", () => { + it("parses valid YYYY-MM", () => { + const period = parsePeriod("2025-03"); + expect(period).toEqual({ year: 2025, month: 3 }); + }); + + it("parses January", () => { + expect(parsePeriod("2024-01")).toEqual({ year: 2024, month: 1 }); + }); + + it("parses December", () => { + expect(parsePeriod("2024-12")).toEqual({ year: 2024, month: 12 }); + }); + + it("throws on invalid format", () => { + expect(() => parsePeriod("2024")).toThrow("Invalid period format"); + }); + + it("throws on invalid month 0", () => { + expect(() => parsePeriod("2024-00")).toThrow("Invalid month"); + }); + + it("throws on invalid month 13", () => { + expect(() => parsePeriod("2024-13")).toThrow("Invalid month"); + }); +}); + +describe("resolvePeriod", () => { + it("resolves from flag", () => { + const period = resolvePeriod({ period: Option.some("2025-06") }); + expect(period).toEqual({ year: 2025, month: 6 }); + }); + + it("throws on missing period", () => { + expect(() => resolvePeriod({ period: Option.none() })).toThrow( + "Missing required option: --period", + ); + }); +}); + +describe("resolveRegime", () => { + const originalEnv = { ...process.env }; + + afterEach(() => { + process.env = { ...originalEnv }; + }); + + it("resolves 14A from flag", () => { + expect(resolveRegime({ regime: Option.some("14A") })).toBe("14A"); + }); + + it("resolves 14D-N3 from flag", () => { + expect(resolveRegime({ regime: Option.some("14D-N3") })).toBe("14D-N3"); + }); + + it("resolves 14D-N8 from flag", () => { + expect(resolveRegime({ regime: Option.some("14D-N8") })).toBe("14D-N8"); + }); + + it("falls back to env var", () => { + process.env.ACCOUNTING_REGIME = "14A"; + expect(resolveRegime({ regime: Option.none() })).toBe("14A"); + }); + + it("throws on missing regime", () => { + delete process.env.ACCOUNTING_REGIME; + expect(() => resolveRegime({ regime: Option.none() })).toThrow( + "Missing required option: --regime", + ); + }); + + it("throws on invalid regime", () => { + expect(() => resolveRegime({ regime: Option.some("15B") })).toThrow( + "Invalid regime: 15B", + ); + }); +}); + +describe("resolveCompany", () => { + const originalEnv = { ...process.env }; + + afterEach(() => { + process.env = { ...originalEnv }; + }); + + it("resolves from CLI flags", () => { + const company = resolveCompany({ + rut: Option.some("76123456-0"), + razonSocial: Option.some("Empresa SA"), + }); + expect(company.rut).toBe("76123456-0"); + expect(company.razonSocial).toBe("Empresa SA"); + }); + + it("falls back to env vars", () => { + process.env.ACCOUNTING_RUT = "76123456-0"; + process.env.ACCOUNTING_RAZON_SOCIAL = "Empresa SA"; + const company = resolveCompany({ + rut: Option.none(), + razonSocial: Option.none(), + }); + expect(company.rut).toBe("76123456-0"); + expect(company.razonSocial).toBe("Empresa SA"); + }); + + it("throws on missing rut", () => { + delete process.env.ACCOUNTING_RUT; + expect(() => + resolveCompany({ + rut: Option.none(), + razonSocial: Option.some("Empresa SA"), + }), + ).toThrow("Missing required option: --rut"); + }); + + it("throws on missing razon social", () => { + delete process.env.ACCOUNTING_RAZON_SOCIAL; + expect(() => + resolveCompany({ + rut: Option.some("76123456-0"), + razonSocial: Option.none(), + }), + ).toThrow("Missing required option: --razon-social"); + }); +}); + +describe("resolveAccountType", () => { + it("returns undefined for None", () => { + expect(resolveAccountType(Option.none())).toBeUndefined(); + }); + + it("resolves valid types", () => { + expect(resolveAccountType(Option.some("asset"))).toBe("asset"); + expect(resolveAccountType(Option.some("liability"))).toBe("liability"); + expect(resolveAccountType(Option.some("equity"))).toBe("equity"); + expect(resolveAccountType(Option.some("revenue"))).toBe("revenue"); + expect(resolveAccountType(Option.some("expense"))).toBe("expense"); + }); + + it("throws on invalid type", () => { + expect(() => resolveAccountType(Option.some("banana"))).toThrow( + "Invalid account type: banana", + ); + }); +}); diff --git a/packages/cli/tests/formatters.test.ts b/packages/cli/tests/formatters.test.ts new file mode 100644 index 0000000..70d8cbb --- /dev/null +++ b/packages/cli/tests/formatters.test.ts @@ -0,0 +1,89 @@ +import { describe, expect, it } from "vitest"; +import { formatTable, formatCsv } from "@emisso/cli-core"; +import { + chartColumns, + entryColumns, + entryLineColumns, + trialBalanceColumns, + balance8Columns, + incomeStatementColumns, + balanceSheetColumns, + ivaColumns, + f29Columns, + periodColumns, + keyValueColumns, +} from "../src/formatters/accounting-table"; + +describe("Column definitions", () => { + const allColumnSets = [ + { name: "chartColumns", columns: chartColumns }, + { name: "entryColumns", columns: entryColumns }, + { name: "entryLineColumns", columns: entryLineColumns }, + { name: "trialBalanceColumns", columns: trialBalanceColumns }, + { name: "balance8Columns", columns: balance8Columns }, + { name: "incomeStatementColumns", columns: incomeStatementColumns }, + { name: "balanceSheetColumns", columns: balanceSheetColumns }, + { name: "ivaColumns", columns: ivaColumns }, + { name: "f29Columns", columns: f29Columns }, + { name: "periodColumns", columns: periodColumns }, + { name: "keyValueColumns", columns: keyValueColumns }, + ]; + + for (const { name, columns } of allColumnSets) { + it(`${name} has valid Column shapes`, () => { + expect(columns.length).toBeGreaterThan(0); + for (const col of columns) { + expect(col).toHaveProperty("key"); + expect(col).toHaveProperty("label"); + expect(typeof col.key).toBe("string"); + expect(typeof col.label).toBe("string"); + } + }); + } +}); + +describe("formatTable with entry line columns", () => { + it("renders entry lines as a table", () => { + const rows = [ + { accountCode: "1.1.01.001", accountName: "Caja", debit: "$100.000", credit: "" }, + { accountCode: "4.1.01.001", accountName: "Ventas", debit: "", credit: "$100.000" }, + ]; + + const output = formatTable(entryLineColumns, rows); + expect(output).toContain("Account"); + expect(output).toContain("Name"); + expect(output).toContain("Caja"); + expect(output).toContain("Ventas"); + expect(output).toContain("$100.000"); + }); +}); + +describe("formatCsv with chart columns", () => { + it("renders chart as CSV", () => { + const csvColumns = chartColumns.map((c) => ({ key: c.key, label: c.label })); + const rows = [ + { code: "1.1.01.001", name: "Caja", type: "asset", parent: "1.1.01", normalBalance: "debit" }, + ]; + + const output = formatCsv(csvColumns, rows); + expect(output).toContain("Code"); + expect(output).toContain("Caja"); + expect(output).toContain("asset"); + }); +}); + +describe("formatTable with period columns", () => { + it("renders periods as a table", () => { + const rows = [ + { period: "2025-01", status: "closed", closedAt: "2025-02-01", lockedAt: "" }, + { period: "2025-02", status: "open", closedAt: "", lockedAt: "" }, + ]; + + const output = formatTable(periodColumns, rows); + expect(output).toContain("Período"); + expect(output).toContain("Estado"); + expect(output).toContain("2025-01"); + expect(output).toContain("closed"); + expect(output).toContain("open"); + }); +}); diff --git a/packages/cli/tests/ledger-io.test.ts b/packages/cli/tests/ledger-io.test.ts new file mode 100644 index 0000000..5683a36 --- /dev/null +++ b/packages/cli/tests/ledger-io.test.ts @@ -0,0 +1,102 @@ +import { describe, expect, it, afterEach } from "vitest"; +import { existsSync, unlinkSync, readFileSync } from "node:fs"; +import { join } from "node:path"; +import { tmpdir } from "node:os"; +import { initLedger, initLedgerForce, loadLedger, saveLedger } from "../src/config/ledger-io"; + +function tempPath(): string { + return join(tmpdir(), `test-ledger-${Date.now()}-${Math.random().toString(36).slice(2)}.json`); +} + +describe("ledger-io", () => { + const cleanupPaths: string[] = []; + + afterEach(() => { + for (const p of cleanupPaths) { + try { unlinkSync(p); } catch { /* ignore */ } + } + cleanupPaths.length = 0; + }); + + it("initLedger creates a new ledger file with SII chart", () => { + const path = tempPath(); + cleanupPaths.push(path); + + initLedger(path); + + expect(existsSync(path)).toBe(true); + const data = JSON.parse(readFileSync(path, "utf-8")); + expect(data).toHaveProperty("entries"); + expect(data).toHaveProperty("config"); + expect(Array.isArray(data.entries)).toBe(true); + }); + + it("initLedger throws if file already exists", () => { + const path = tempPath(); + cleanupPaths.push(path); + + initLedger(path); + expect(() => initLedger(path)).toThrow("already exists"); + }); + + it("initLedgerForce overwrites existing file", () => { + const path = tempPath(); + cleanupPaths.push(path); + + initLedger(path); + const ledger = initLedgerForce(path); + expect(ledger).toBeDefined(); + expect(ledger.getEntries({})).toHaveLength(0); + }); + + it("loadLedger reads a valid ledger file", () => { + const path = tempPath(); + cleanupPaths.push(path); + + initLedger(path); + const ledger = loadLedger(path); + + expect(ledger).toBeDefined(); + expect(ledger.chart).toBeDefined(); + expect(ledger.getEntries({})).toHaveLength(0); + }); + + it("loadLedger throws on non-existent file", () => { + expect(() => loadLedger("/tmp/does-not-exist-12345.json")).toThrow(); + }); + + it("saveLedger persists entries", () => { + const path = tempPath(); + cleanupPaths.push(path); + + initLedger(path); + const ledger = loadLedger(path); + + // Add an entry + ledger + .entry("Test entry") + .date("2025-03-15") + .debit("1.1.01.001", 100000) + .credit("4.1.01.001", 100000) + .commit(); + + saveLedger(path, ledger); + + // Reload and verify + const reloaded = loadLedger(path); + const entries = reloaded.getEntries({}); + expect(entries).toHaveLength(1); + expect(entries[0].description).toBe("Test entry"); + }); + + it("round-trips ledger data correctly", () => { + const path = tempPath(); + cleanupPaths.push(path); + + initLedger(path, { companyRut: "76123456-0", regime: "14A" }); + const ledger = loadLedger(path); + + expect(ledger.config.companyRut).toBe("76123456-0"); + expect(ledger.config.regime).toBe("14A"); + }); +}); diff --git a/packages/cli/tsconfig.json b/packages/cli/tsconfig.json new file mode 100644 index 0000000..62af741 --- /dev/null +++ b/packages/cli/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "bundler", + "lib": ["ES2022"], + "outDir": "dist", + "rootDir": ".", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true + }, + "include": ["src", "bin"], + "exclude": ["node_modules", "dist", "tests"] +} diff --git a/packages/cli/tsup.config.ts b/packages/cli/tsup.config.ts new file mode 100644 index 0000000..766d417 --- /dev/null +++ b/packages/cli/tsup.config.ts @@ -0,0 +1,11 @@ +import { defineConfig } from "tsup"; + +export default defineConfig({ + entry: ["src/index.ts", "bin/accounting.ts"], + format: ["cjs", "esm"], + dts: true, + sourcemap: true, + clean: true, + splitting: false, + external: ["@emisso/accounting", "@emisso/cli-core"], +}); diff --git a/packages/cli/vitest.config.ts b/packages/cli/vitest.config.ts new file mode 100644 index 0000000..0ff0f97 --- /dev/null +++ b/packages/cli/vitest.config.ts @@ -0,0 +1,14 @@ +import { defineConfig } from "vitest/config"; +import path from "path"; + +export default defineConfig({ + test: { + globals: true, + environment: "node", + }, + resolve: { + alias: { + "@emisso/accounting": path.resolve(__dirname, "../engine/src/index.ts"), + }, + }, +}); diff --git a/packages/engine/package.json b/packages/engine/package.json index 11e7837..092f0d2 100644 --- a/packages/engine/package.json +++ b/packages/engine/package.json @@ -3,14 +3,14 @@ "version": "0.1.0", "type": "module", "description": "Double-entry accounting engine for Chilean businesses — SII electronic books, IVA, PPM, Balance de 8 Columnas", - "main": "./dist/index.js", - "module": "./dist/index.mjs", + "main": "./dist/index.cjs", + "module": "./dist/index.js", "types": "./dist/index.d.ts", "exports": { ".": { "types": "./dist/index.d.ts", - "import": "./dist/index.mjs", - "require": "./dist/index.js" + "import": "./dist/index.js", + "require": "./dist/index.cjs" } }, "files": [ @@ -34,7 +34,21 @@ "libro-mayor", "balance-8-columnas", "iva", - "ppm" + "ppm", + "plan-de-cuentas", + "chart-of-accounts", + "general-ledger", + "correccion-monetaria", + "f29", + "estado-resultados", + "balance-general", + "trial-balance", + "asientos-contables", + "journal-entry", + "typescript", + "sdk", + "latin-america", + "latam" ], "author": "Emisso ", "license": "MIT", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e030ff3..c4e3e14 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -41,7 +41,50 @@ importers: version: 0.30.6 tsup: specifier: ^8.3.0 - version: 8.5.1(postcss@8.5.8)(tsx@4.21.0)(typescript@5.9.3) + version: 8.5.1(postcss@8.5.8)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2) + typescript: + specifier: ^5.7.0 + version: 5.9.3 + vitest: + specifier: ^2.1.0 + version: 2.1.9(@types/node@22.19.15) + + packages/cli: + dependencies: + '@effect/cli': + specifier: ^0.73.0 + version: 0.73.2(@effect/platform@0.94.5(effect@3.19.19))(@effect/printer-ansi@0.47.0(@effect/typeclass@0.38.0(effect@3.19.19))(effect@3.19.19))(@effect/printer@0.47.0(@effect/typeclass@0.38.0(effect@3.19.19))(effect@3.19.19))(effect@3.19.19) + '@effect/platform': + specifier: ^0.94.3 + version: 0.94.5(effect@3.19.19) + '@effect/platform-node': + specifier: ^0.104.0 + version: 0.104.1(@effect/cluster@0.56.4(@effect/platform@0.94.5(effect@3.19.19))(@effect/rpc@0.73.2(@effect/platform@0.94.5(effect@3.19.19))(effect@3.19.19))(@effect/sql@0.49.0(@effect/experimental@0.58.0(@effect/platform@0.94.5(effect@3.19.19))(effect@3.19.19))(@effect/platform@0.94.5(effect@3.19.19))(effect@3.19.19))(@effect/workflow@0.16.0(@effect/experimental@0.58.0(@effect/platform@0.94.5(effect@3.19.19))(effect@3.19.19))(@effect/platform@0.94.5(effect@3.19.19))(@effect/rpc@0.73.2(@effect/platform@0.94.5(effect@3.19.19))(effect@3.19.19))(effect@3.19.19))(effect@3.19.19))(@effect/platform@0.94.5(effect@3.19.19))(@effect/rpc@0.73.2(@effect/platform@0.94.5(effect@3.19.19))(effect@3.19.19))(@effect/sql@0.49.0(@effect/experimental@0.58.0(@effect/platform@0.94.5(effect@3.19.19))(effect@3.19.19))(@effect/platform@0.94.5(effect@3.19.19))(effect@3.19.19))(effect@3.19.19) + '@effect/printer': + specifier: ^0.47.0 + version: 0.47.0(@effect/typeclass@0.38.0(effect@3.19.19))(effect@3.19.19) + '@effect/printer-ansi': + specifier: ^0.47.0 + version: 0.47.0(@effect/typeclass@0.38.0(effect@3.19.19))(effect@3.19.19) + '@emisso/accounting': + specifier: workspace:* + version: link:../engine + '@emisso/cli-core': + specifier: ^0.1.0 + version: 0.1.0(@effect/cluster@0.56.4(@effect/platform@0.94.5(effect@3.19.19))(@effect/rpc@0.73.2(@effect/platform@0.94.5(effect@3.19.19))(effect@3.19.19))(@effect/sql@0.49.0(@effect/experimental@0.58.0(@effect/platform@0.94.5(effect@3.19.19))(effect@3.19.19))(@effect/platform@0.94.5(effect@3.19.19))(effect@3.19.19))(@effect/workflow@0.16.0(@effect/experimental@0.58.0(@effect/platform@0.94.5(effect@3.19.19))(effect@3.19.19))(@effect/platform@0.94.5(effect@3.19.19))(@effect/rpc@0.73.2(@effect/platform@0.94.5(effect@3.19.19))(effect@3.19.19))(effect@3.19.19))(effect@3.19.19))(@effect/rpc@0.73.2(@effect/platform@0.94.5(effect@3.19.19))(effect@3.19.19))(@effect/sql@0.49.0(@effect/experimental@0.58.0(@effect/platform@0.94.5(effect@3.19.19))(effect@3.19.19))(@effect/platform@0.94.5(effect@3.19.19))(effect@3.19.19))(@effect/typeclass@0.38.0(effect@3.19.19)) + effect: + specifier: ^3.19.16 + version: 3.19.19 + devDependencies: + '@types/node': + specifier: ^22.0.0 + version: 22.19.15 + tsup: + specifier: ^8.3.0 + version: 8.5.1(postcss@8.5.8)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2) + tsx: + specifier: ^4.21.0 + version: 4.21.0 typescript: specifier: ^5.7.0 version: 5.9.3 @@ -60,7 +103,7 @@ importers: version: 22.19.15 tsup: specifier: ^8.3.0 - version: 8.5.1(postcss@8.5.8)(tsx@4.21.0)(typescript@5.9.3) + version: 8.5.1(postcss@8.5.8)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2) tsx: specifier: ^4.21.0 version: 4.21.0 @@ -80,9 +123,104 @@ packages: '@drizzle-team/brocli@0.10.2': resolution: {integrity: sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w==} + '@effect/cli@0.73.2': + resolution: {integrity: sha512-K8IJo81+qa1LU8dhxcDU4QO/bIjL/dPd3zUOSCpLiuUNz8Y3/T+WNs3GqIXEhMfCFMSlRZERN0YgmtRlEZUREA==} + peerDependencies: + '@effect/platform': ^0.94.3 + '@effect/printer': ^0.47.0 + '@effect/printer-ansi': ^0.47.0 + effect: ^3.19.16 + + '@effect/cluster@0.56.4': + resolution: {integrity: sha512-7Je5/JlbZOlsSxsbKjr97dJed2cNGWsb+TLNgMcr5mRDbcWlFOTUGvsrisEJV6waosYLIg+2omPdvnvRoYKdhA==} + peerDependencies: + '@effect/platform': ^0.94.5 + '@effect/rpc': ^0.73.1 + '@effect/sql': ^0.49.0 + '@effect/workflow': ^0.16.0 + effect: ^3.19.17 + + '@effect/experimental@0.58.0': + resolution: {integrity: sha512-IEP9sapjF6rFy5TkoqDPc86st/fnqUfjT7Xa3pWJrFGr1hzaMXHo+mWsYOZS9LAOVKnpHuVziDK97EP5qsCHVA==} + peerDependencies: + '@effect/platform': ^0.94.0 + effect: ^3.19.13 + ioredis: ^5 + lmdb: ^3 + peerDependenciesMeta: + ioredis: + optional: true + lmdb: + optional: true + + '@effect/platform-node-shared@0.57.1': + resolution: {integrity: sha512-oX/bApMdoKsyrDiNdJxo7U9Rz1RXsjRv+ecfAPp1qGlSdGIo32wVRvJ2XCHqYj0sqaYJS0pU0/GCulRfVGuJag==} + peerDependencies: + '@effect/cluster': ^0.56.1 + '@effect/platform': ^0.94.2 + '@effect/rpc': ^0.73.0 + '@effect/sql': ^0.49.0 + effect: ^3.19.15 + + '@effect/platform-node@0.104.1': + resolution: {integrity: sha512-jT1a/z98niK6fnEU8pWHPPCdJMVDRCIdB65lolcOjse5rsTwVbczMjvKkhVQpF63mNWoOnol7OTRNkw5L54llg==} + peerDependencies: + '@effect/cluster': ^0.56.1 + '@effect/platform': ^0.94.2 + '@effect/rpc': ^0.73.0 + '@effect/sql': ^0.49.0 + effect: ^3.19.15 + + '@effect/platform@0.94.5': + resolution: {integrity: sha512-z05APUiDDPbodhTkH/RJqOLoCU11bU2IZLfcwLFrld03+ob1VeqRnELQlmueLIYm6NZifHAtjl32V+GRt34y4A==} + peerDependencies: + effect: ^3.19.17 + + '@effect/printer-ansi@0.47.0': + resolution: {integrity: sha512-tDEQ9XJpXDNYoWMQJHFRMxKGmEOu6z32x3Kb8YLOV5nkauEKnKmWNs7NBp8iio/pqoJbaSwqDwUg9jXVquxfWQ==} + peerDependencies: + '@effect/typeclass': ^0.38.0 + effect: ^3.19.0 + + '@effect/printer@0.47.0': + resolution: {integrity: sha512-VgR8e+YWWhMEAh9qFOjwiZ3OXluAbcVLIOtvp2S5di1nSrPOZxj78g8LE77JSvyfp5y5bS2gmFW+G7xD5uU+2Q==} + peerDependencies: + '@effect/typeclass': ^0.38.0 + effect: ^3.19.0 + + '@effect/rpc@0.73.2': + resolution: {integrity: sha512-td7LHDgBOYKg+VgGWEelD8rSAmvjXz7am17vfxZROX5qIYuvH7drL/z4p5xQFadhHZ7DYdlFpqdO9ggc77OCIw==} + peerDependencies: + '@effect/platform': ^0.94.5 + effect: ^3.19.18 + + '@effect/sql@0.49.0': + resolution: {integrity: sha512-9UEKR+z+MrI/qMAmSvb/RiD9KlgIazjZUCDSpwNgm0lEK9/Q6ExEyfziiYFVCPiptp52cBw8uBHRic8hHnwqXA==} + peerDependencies: + '@effect/experimental': ^0.58.0 + '@effect/platform': ^0.94.0 + effect: ^3.19.13 + + '@effect/typeclass@0.38.0': + resolution: {integrity: sha512-lMUcJTRtG8KXhXoczapZDxbLK5os7M6rn0zkvOgncJW++A0UyelZfMVMKdT5R+fgpZcsAU/1diaqw3uqLJwGxA==} + peerDependencies: + effect: ^3.19.0 + + '@effect/workflow@0.16.0': + resolution: {integrity: sha512-MiAdlxx3TixkgHdbw+Yf1Z3tHAAE0rOQga12kIydJqj05Fnod+W/I+kQGRMY/XWRg+QUsVxhmh1qTr7Ype6lrw==} + peerDependencies: + '@effect/experimental': ^0.58.0 + '@effect/platform': ^0.94.0 + '@effect/rpc': ^0.73.0 + effect: ^3.19.13 + '@electric-sql/pglite@0.2.17': resolution: {integrity: sha512-qEpKRT2oUaWDH6tjRxLHjdzMqRUGYDnGZlKrnL4dJ77JVMcP2Hpo3NYnOSPKdZdeec57B6QPprCUFg0picx5Pw==} + '@emisso/cli-core@0.1.0': + resolution: {integrity: sha512-5LUQasTwQ3PNOfa4yHsQHTFfMXVyURN73Bk1aLk4kvXju5GwpLctMIfLWJKBzLRtMMchqNODhb+hY84iz3EHlQ==} + engines: {node: '>=18'} + '@esbuild-kit/core-utils@3.3.2': resolution: {integrity: sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ==} deprecated: 'Merged into tsx: https://tsx.is' @@ -668,6 +806,124 @@ packages: '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + '@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3': + resolution: {integrity: sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw==} + cpu: [arm64] + os: [darwin] + + '@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.3': + resolution: {integrity: sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw==} + cpu: [x64] + os: [darwin] + + '@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.3': + resolution: {integrity: sha512-YxQL+ax0XqBJDZiKimS2XQaf+2wDGVa1enVRGzEvLLVFeqa5kx2bWbtcSXgsxjQB7nRqqIGFIcLteF/sHeVtQg==} + cpu: [arm64] + os: [linux] + + '@msgpackr-extract/msgpackr-extract-linux-arm@3.0.3': + resolution: {integrity: sha512-fg0uy/dG/nZEXfYilKoRe7yALaNmHoYeIoJuJ7KJ+YyU2bvY8vPv27f7UKhGRpY6euFYqEVhxCFZgAUNQBM3nw==} + cpu: [arm] + os: [linux] + + '@msgpackr-extract/msgpackr-extract-linux-x64@3.0.3': + resolution: {integrity: sha512-cvwNfbP07pKUfq1uH+S6KJ7dT9K8WOE4ZiAcsrSes+UY55E/0jLYc+vq+DO7jlmqRb5zAggExKm0H7O/CBaesg==} + cpu: [x64] + os: [linux] + + '@msgpackr-extract/msgpackr-extract-win32-x64@3.0.3': + resolution: {integrity: sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ==} + cpu: [x64] + os: [win32] + + '@parcel/watcher-android-arm64@2.5.6': + resolution: {integrity: sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [android] + + '@parcel/watcher-darwin-arm64@2.5.6': + resolution: {integrity: sha512-Z2ZdrnwyXvvvdtRHLmM4knydIdU9adO3D4n/0cVipF3rRiwP+3/sfzpAwA/qKFL6i1ModaabkU7IbpeMBgiVEA==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [darwin] + + '@parcel/watcher-darwin-x64@2.5.6': + resolution: {integrity: sha512-HgvOf3W9dhithcwOWX9uDZyn1lW9R+7tPZ4sug+NGrGIo4Rk1hAXLEbcH1TQSqxts0NYXXlOWqVpvS1SFS4fRg==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [darwin] + + '@parcel/watcher-freebsd-x64@2.5.6': + resolution: {integrity: sha512-vJVi8yd/qzJxEKHkeemh7w3YAn6RJCtYlE4HPMoVnCpIXEzSrxErBW5SJBgKLbXU3WdIpkjBTeUNtyBVn8TRng==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [freebsd] + + '@parcel/watcher-linux-arm-glibc@2.5.6': + resolution: {integrity: sha512-9JiYfB6h6BgV50CCfasfLf/uvOcJskMSwcdH1PHH9rvS1IrNy8zad6IUVPVUfmXr+u+Km9IxcfMLzgdOudz9EQ==} + engines: {node: '>= 10.0.0'} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@parcel/watcher-linux-arm-musl@2.5.6': + resolution: {integrity: sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg==} + engines: {node: '>= 10.0.0'} + cpu: [arm] + os: [linux] + libc: [musl] + + '@parcel/watcher-linux-arm64-glibc@2.5.6': + resolution: {integrity: sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@parcel/watcher-linux-arm64-musl@2.5.6': + resolution: {integrity: sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@parcel/watcher-linux-x64-glibc@2.5.6': + resolution: {integrity: sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@parcel/watcher-linux-x64-musl@2.5.6': + resolution: {integrity: sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [linux] + libc: [musl] + + '@parcel/watcher-win32-arm64@2.5.6': + resolution: {integrity: sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [win32] + + '@parcel/watcher-win32-ia32@2.5.6': + resolution: {integrity: sha512-k35yLp1ZMwwee3Ez/pxBi5cf4AoBKYXj00CZ80jUz5h8prpiaQsiRPKQMxoLstNuqe2vR4RNPEAEcjEFzhEz/g==} + engines: {node: '>= 10.0.0'} + cpu: [ia32] + os: [win32] + + '@parcel/watcher-win32-x64@2.5.6': + resolution: {integrity: sha512-hbQlYcCq5dlAX9Qx+kFb0FHue6vbjlf0FrNzSKdYK2APUf7tGfGxQCk2ihEREmbR6ZMc0MVAD5RIX/41gpUzTw==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [win32] + + '@parcel/watcher@2.5.6': + resolution: {integrity: sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==} + engines: {node: '>= 10.0.0'} + '@petamoriken/float16@3.9.3': resolution: {integrity: sha512-8awtpHXCx/bNpFt4mt2xdkgtgVvKqty8VbjHI/WWWQuEw+KLzFot3f4+LkQY9YmOtq7A5GdOnqoIC8Pdygjk2g==} @@ -908,6 +1164,10 @@ packages: resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} engines: {node: '>=6'} + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + drizzle-kit@0.30.6: resolution: {integrity: sha512-U4wWit0fyZuGuP7iNmRleQyK2V8wCuv57vf5l3MnG4z4fzNTjY/U13M8owyQ5RavqvqxBifWORaR3wIUzlN64g==} hasBin: true @@ -1066,6 +1326,9 @@ packages: picomatch: optional: true + find-my-way-ts@0.1.6: + resolution: {integrity: sha512-a85L9ZoXtNAey3Y6Z+eBWW658kO/MwR7zIafkIUPUMf3isZG0NCs2pjW2wtjxAKuJPxMAsHUIP4ZPGv0o5gyTA==} + fix-dts-default-cjs-exports@1.0.1: resolution: {integrity: sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg==} @@ -1082,6 +1345,18 @@ packages: get-tsconfig@4.13.6: resolution: {integrity: sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw==} + ini@4.1.3: + resolution: {integrity: sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + isexe@3.1.5: resolution: {integrity: sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==} engines: {node: '>=18'} @@ -1090,6 +1365,9 @@ packages: resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} engines: {node: '>=10'} + kubernetes-types@1.30.0: + resolution: {integrity: sha512-Dew1okvhM/SQcIa2rcgujNndZwU8VnSapDgdxlYoB84ZlpAD43U6KLAFqYo17ykSFGHNPrg0qry0bP+GJd9v7Q==} + lilconfig@3.1.3: resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} engines: {node: '>=14'} @@ -1107,12 +1385,27 @@ packages: magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + mime@3.0.0: + resolution: {integrity: sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==} + engines: {node: '>=10.0.0'} + hasBin: true + mlly@1.8.1: resolution: {integrity: sha512-SnL6sNutTwRWWR/vcmCYHSADjiEesp5TGQQ0pXyLhW5IoeibRlF/CbSLailbB3CNqJUk9cVJ9dUDnbD7GrcHBQ==} ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + msgpackr-extract@3.0.3: + resolution: {integrity: sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA==} + hasBin: true + + msgpackr@1.11.9: + resolution: {integrity: sha512-FkoAAyyA6HM8wL882EcEyFZ9s7hVADSwG9xrVx3dxxNQAtgADTrJoEWivID82Iv1zWDsv/OtbrrcZAzGzOMdNw==} + + multipasta@0.2.7: + resolution: {integrity: sha512-KPA58d68KgGil15oDqXjkUBEBYc00XvbPj5/X+dyzeo/lWm9Nc25pQRlf1D+gv4OpK7NM0J1odrbu9JNNGvynA==} + mz@2.7.0: resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} @@ -1121,6 +1414,13 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true + node-addon-api@7.1.1: + resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==} + + node-gyp-build-optional-packages@5.2.2: + resolution: {integrity: sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==} + hasBin: true + object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} @@ -1268,6 +1568,9 @@ packages: resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==} engines: {node: '>=14.0.0'} + toml@3.0.0: + resolution: {integrity: sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w==} + tree-kill@1.2.2: resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} hasBin: true @@ -1310,6 +1613,14 @@ packages: undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + undici@7.24.3: + resolution: {integrity: sha512-eJdUmK/Wrx2d+mnWWmwwLRyA7OQCkLap60sk3dOK4ViZR7DKwwptwuIvFBg2HaiP9ESaEdhtpSymQPvytpmkCA==} + engines: {node: '>=20.18.1'} + + uuid@11.1.0: + resolution: {integrity: sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==} + hasBin: true + vite-node@2.1.9: resolution: {integrity: sha512-AM9aQ/IPrW/6ENLQg3AGY4K1N2TGZdR5e4gu/MmmR2xR3Ll1+dib+nook92g4TV3PXVyeyxdWwtaCAiUL0hMxA==} engines: {node: ^18.0.0 || >=20.0.0} @@ -1381,6 +1692,23 @@ packages: engines: {node: '>=8'} hasBin: true + ws@8.19.0: + resolution: {integrity: sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + yaml@2.8.2: + resolution: {integrity: sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==} + engines: {node: '>= 14.6'} + hasBin: true + zod@3.25.76: resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} @@ -1388,8 +1716,120 @@ snapshots: '@drizzle-team/brocli@0.10.2': {} + '@effect/cli@0.73.2(@effect/platform@0.94.5(effect@3.19.19))(@effect/printer-ansi@0.47.0(@effect/typeclass@0.38.0(effect@3.19.19))(effect@3.19.19))(@effect/printer@0.47.0(@effect/typeclass@0.38.0(effect@3.19.19))(effect@3.19.19))(effect@3.19.19)': + dependencies: + '@effect/platform': 0.94.5(effect@3.19.19) + '@effect/printer': 0.47.0(@effect/typeclass@0.38.0(effect@3.19.19))(effect@3.19.19) + '@effect/printer-ansi': 0.47.0(@effect/typeclass@0.38.0(effect@3.19.19))(effect@3.19.19) + effect: 3.19.19 + ini: 4.1.3 + toml: 3.0.0 + yaml: 2.8.2 + + '@effect/cluster@0.56.4(@effect/platform@0.94.5(effect@3.19.19))(@effect/rpc@0.73.2(@effect/platform@0.94.5(effect@3.19.19))(effect@3.19.19))(@effect/sql@0.49.0(@effect/experimental@0.58.0(@effect/platform@0.94.5(effect@3.19.19))(effect@3.19.19))(@effect/platform@0.94.5(effect@3.19.19))(effect@3.19.19))(@effect/workflow@0.16.0(@effect/experimental@0.58.0(@effect/platform@0.94.5(effect@3.19.19))(effect@3.19.19))(@effect/platform@0.94.5(effect@3.19.19))(@effect/rpc@0.73.2(@effect/platform@0.94.5(effect@3.19.19))(effect@3.19.19))(effect@3.19.19))(effect@3.19.19)': + dependencies: + '@effect/platform': 0.94.5(effect@3.19.19) + '@effect/rpc': 0.73.2(@effect/platform@0.94.5(effect@3.19.19))(effect@3.19.19) + '@effect/sql': 0.49.0(@effect/experimental@0.58.0(@effect/platform@0.94.5(effect@3.19.19))(effect@3.19.19))(@effect/platform@0.94.5(effect@3.19.19))(effect@3.19.19) + '@effect/workflow': 0.16.0(@effect/experimental@0.58.0(@effect/platform@0.94.5(effect@3.19.19))(effect@3.19.19))(@effect/platform@0.94.5(effect@3.19.19))(@effect/rpc@0.73.2(@effect/platform@0.94.5(effect@3.19.19))(effect@3.19.19))(effect@3.19.19) + effect: 3.19.19 + kubernetes-types: 1.30.0 + + '@effect/experimental@0.58.0(@effect/platform@0.94.5(effect@3.19.19))(effect@3.19.19)': + dependencies: + '@effect/platform': 0.94.5(effect@3.19.19) + effect: 3.19.19 + uuid: 11.1.0 + + '@effect/platform-node-shared@0.57.1(@effect/cluster@0.56.4(@effect/platform@0.94.5(effect@3.19.19))(@effect/rpc@0.73.2(@effect/platform@0.94.5(effect@3.19.19))(effect@3.19.19))(@effect/sql@0.49.0(@effect/experimental@0.58.0(@effect/platform@0.94.5(effect@3.19.19))(effect@3.19.19))(@effect/platform@0.94.5(effect@3.19.19))(effect@3.19.19))(@effect/workflow@0.16.0(@effect/experimental@0.58.0(@effect/platform@0.94.5(effect@3.19.19))(effect@3.19.19))(@effect/platform@0.94.5(effect@3.19.19))(@effect/rpc@0.73.2(@effect/platform@0.94.5(effect@3.19.19))(effect@3.19.19))(effect@3.19.19))(effect@3.19.19))(@effect/platform@0.94.5(effect@3.19.19))(@effect/rpc@0.73.2(@effect/platform@0.94.5(effect@3.19.19))(effect@3.19.19))(@effect/sql@0.49.0(@effect/experimental@0.58.0(@effect/platform@0.94.5(effect@3.19.19))(effect@3.19.19))(@effect/platform@0.94.5(effect@3.19.19))(effect@3.19.19))(effect@3.19.19)': + dependencies: + '@effect/cluster': 0.56.4(@effect/platform@0.94.5(effect@3.19.19))(@effect/rpc@0.73.2(@effect/platform@0.94.5(effect@3.19.19))(effect@3.19.19))(@effect/sql@0.49.0(@effect/experimental@0.58.0(@effect/platform@0.94.5(effect@3.19.19))(effect@3.19.19))(@effect/platform@0.94.5(effect@3.19.19))(effect@3.19.19))(@effect/workflow@0.16.0(@effect/experimental@0.58.0(@effect/platform@0.94.5(effect@3.19.19))(effect@3.19.19))(@effect/platform@0.94.5(effect@3.19.19))(@effect/rpc@0.73.2(@effect/platform@0.94.5(effect@3.19.19))(effect@3.19.19))(effect@3.19.19))(effect@3.19.19) + '@effect/platform': 0.94.5(effect@3.19.19) + '@effect/rpc': 0.73.2(@effect/platform@0.94.5(effect@3.19.19))(effect@3.19.19) + '@effect/sql': 0.49.0(@effect/experimental@0.58.0(@effect/platform@0.94.5(effect@3.19.19))(effect@3.19.19))(@effect/platform@0.94.5(effect@3.19.19))(effect@3.19.19) + '@parcel/watcher': 2.5.6 + effect: 3.19.19 + multipasta: 0.2.7 + ws: 8.19.0 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + '@effect/platform-node@0.104.1(@effect/cluster@0.56.4(@effect/platform@0.94.5(effect@3.19.19))(@effect/rpc@0.73.2(@effect/platform@0.94.5(effect@3.19.19))(effect@3.19.19))(@effect/sql@0.49.0(@effect/experimental@0.58.0(@effect/platform@0.94.5(effect@3.19.19))(effect@3.19.19))(@effect/platform@0.94.5(effect@3.19.19))(effect@3.19.19))(@effect/workflow@0.16.0(@effect/experimental@0.58.0(@effect/platform@0.94.5(effect@3.19.19))(effect@3.19.19))(@effect/platform@0.94.5(effect@3.19.19))(@effect/rpc@0.73.2(@effect/platform@0.94.5(effect@3.19.19))(effect@3.19.19))(effect@3.19.19))(effect@3.19.19))(@effect/platform@0.94.5(effect@3.19.19))(@effect/rpc@0.73.2(@effect/platform@0.94.5(effect@3.19.19))(effect@3.19.19))(@effect/sql@0.49.0(@effect/experimental@0.58.0(@effect/platform@0.94.5(effect@3.19.19))(effect@3.19.19))(@effect/platform@0.94.5(effect@3.19.19))(effect@3.19.19))(effect@3.19.19)': + dependencies: + '@effect/cluster': 0.56.4(@effect/platform@0.94.5(effect@3.19.19))(@effect/rpc@0.73.2(@effect/platform@0.94.5(effect@3.19.19))(effect@3.19.19))(@effect/sql@0.49.0(@effect/experimental@0.58.0(@effect/platform@0.94.5(effect@3.19.19))(effect@3.19.19))(@effect/platform@0.94.5(effect@3.19.19))(effect@3.19.19))(@effect/workflow@0.16.0(@effect/experimental@0.58.0(@effect/platform@0.94.5(effect@3.19.19))(effect@3.19.19))(@effect/platform@0.94.5(effect@3.19.19))(@effect/rpc@0.73.2(@effect/platform@0.94.5(effect@3.19.19))(effect@3.19.19))(effect@3.19.19))(effect@3.19.19) + '@effect/platform': 0.94.5(effect@3.19.19) + '@effect/platform-node-shared': 0.57.1(@effect/cluster@0.56.4(@effect/platform@0.94.5(effect@3.19.19))(@effect/rpc@0.73.2(@effect/platform@0.94.5(effect@3.19.19))(effect@3.19.19))(@effect/sql@0.49.0(@effect/experimental@0.58.0(@effect/platform@0.94.5(effect@3.19.19))(effect@3.19.19))(@effect/platform@0.94.5(effect@3.19.19))(effect@3.19.19))(@effect/workflow@0.16.0(@effect/experimental@0.58.0(@effect/platform@0.94.5(effect@3.19.19))(effect@3.19.19))(@effect/platform@0.94.5(effect@3.19.19))(@effect/rpc@0.73.2(@effect/platform@0.94.5(effect@3.19.19))(effect@3.19.19))(effect@3.19.19))(effect@3.19.19))(@effect/platform@0.94.5(effect@3.19.19))(@effect/rpc@0.73.2(@effect/platform@0.94.5(effect@3.19.19))(effect@3.19.19))(@effect/sql@0.49.0(@effect/experimental@0.58.0(@effect/platform@0.94.5(effect@3.19.19))(effect@3.19.19))(@effect/platform@0.94.5(effect@3.19.19))(effect@3.19.19))(effect@3.19.19) + '@effect/rpc': 0.73.2(@effect/platform@0.94.5(effect@3.19.19))(effect@3.19.19) + '@effect/sql': 0.49.0(@effect/experimental@0.58.0(@effect/platform@0.94.5(effect@3.19.19))(effect@3.19.19))(@effect/platform@0.94.5(effect@3.19.19))(effect@3.19.19) + effect: 3.19.19 + mime: 3.0.0 + undici: 7.24.3 + ws: 8.19.0 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + '@effect/platform@0.94.5(effect@3.19.19)': + dependencies: + effect: 3.19.19 + find-my-way-ts: 0.1.6 + msgpackr: 1.11.9 + multipasta: 0.2.7 + + '@effect/printer-ansi@0.47.0(@effect/typeclass@0.38.0(effect@3.19.19))(effect@3.19.19)': + dependencies: + '@effect/printer': 0.47.0(@effect/typeclass@0.38.0(effect@3.19.19))(effect@3.19.19) + '@effect/typeclass': 0.38.0(effect@3.19.19) + effect: 3.19.19 + + '@effect/printer@0.47.0(@effect/typeclass@0.38.0(effect@3.19.19))(effect@3.19.19)': + dependencies: + '@effect/typeclass': 0.38.0(effect@3.19.19) + effect: 3.19.19 + + '@effect/rpc@0.73.2(@effect/platform@0.94.5(effect@3.19.19))(effect@3.19.19)': + dependencies: + '@effect/platform': 0.94.5(effect@3.19.19) + effect: 3.19.19 + msgpackr: 1.11.9 + + '@effect/sql@0.49.0(@effect/experimental@0.58.0(@effect/platform@0.94.5(effect@3.19.19))(effect@3.19.19))(@effect/platform@0.94.5(effect@3.19.19))(effect@3.19.19)': + dependencies: + '@effect/experimental': 0.58.0(@effect/platform@0.94.5(effect@3.19.19))(effect@3.19.19) + '@effect/platform': 0.94.5(effect@3.19.19) + effect: 3.19.19 + uuid: 11.1.0 + + '@effect/typeclass@0.38.0(effect@3.19.19)': + dependencies: + effect: 3.19.19 + + '@effect/workflow@0.16.0(@effect/experimental@0.58.0(@effect/platform@0.94.5(effect@3.19.19))(effect@3.19.19))(@effect/platform@0.94.5(effect@3.19.19))(@effect/rpc@0.73.2(@effect/platform@0.94.5(effect@3.19.19))(effect@3.19.19))(effect@3.19.19)': + dependencies: + '@effect/experimental': 0.58.0(@effect/platform@0.94.5(effect@3.19.19))(effect@3.19.19) + '@effect/platform': 0.94.5(effect@3.19.19) + '@effect/rpc': 0.73.2(@effect/platform@0.94.5(effect@3.19.19))(effect@3.19.19) + effect: 3.19.19 + '@electric-sql/pglite@0.2.17': {} + '@emisso/cli-core@0.1.0(@effect/cluster@0.56.4(@effect/platform@0.94.5(effect@3.19.19))(@effect/rpc@0.73.2(@effect/platform@0.94.5(effect@3.19.19))(effect@3.19.19))(@effect/sql@0.49.0(@effect/experimental@0.58.0(@effect/platform@0.94.5(effect@3.19.19))(effect@3.19.19))(@effect/platform@0.94.5(effect@3.19.19))(effect@3.19.19))(@effect/workflow@0.16.0(@effect/experimental@0.58.0(@effect/platform@0.94.5(effect@3.19.19))(effect@3.19.19))(@effect/platform@0.94.5(effect@3.19.19))(@effect/rpc@0.73.2(@effect/platform@0.94.5(effect@3.19.19))(effect@3.19.19))(effect@3.19.19))(effect@3.19.19))(@effect/rpc@0.73.2(@effect/platform@0.94.5(effect@3.19.19))(effect@3.19.19))(@effect/sql@0.49.0(@effect/experimental@0.58.0(@effect/platform@0.94.5(effect@3.19.19))(effect@3.19.19))(@effect/platform@0.94.5(effect@3.19.19))(effect@3.19.19))(@effect/typeclass@0.38.0(effect@3.19.19))': + dependencies: + '@effect/cli': 0.73.2(@effect/platform@0.94.5(effect@3.19.19))(@effect/printer-ansi@0.47.0(@effect/typeclass@0.38.0(effect@3.19.19))(effect@3.19.19))(@effect/printer@0.47.0(@effect/typeclass@0.38.0(effect@3.19.19))(effect@3.19.19))(effect@3.19.19) + '@effect/platform': 0.94.5(effect@3.19.19) + '@effect/platform-node': 0.104.1(@effect/cluster@0.56.4(@effect/platform@0.94.5(effect@3.19.19))(@effect/rpc@0.73.2(@effect/platform@0.94.5(effect@3.19.19))(effect@3.19.19))(@effect/sql@0.49.0(@effect/experimental@0.58.0(@effect/platform@0.94.5(effect@3.19.19))(effect@3.19.19))(@effect/platform@0.94.5(effect@3.19.19))(effect@3.19.19))(@effect/workflow@0.16.0(@effect/experimental@0.58.0(@effect/platform@0.94.5(effect@3.19.19))(effect@3.19.19))(@effect/platform@0.94.5(effect@3.19.19))(@effect/rpc@0.73.2(@effect/platform@0.94.5(effect@3.19.19))(effect@3.19.19))(effect@3.19.19))(effect@3.19.19))(@effect/platform@0.94.5(effect@3.19.19))(@effect/rpc@0.73.2(@effect/platform@0.94.5(effect@3.19.19))(effect@3.19.19))(@effect/sql@0.49.0(@effect/experimental@0.58.0(@effect/platform@0.94.5(effect@3.19.19))(effect@3.19.19))(@effect/platform@0.94.5(effect@3.19.19))(effect@3.19.19))(effect@3.19.19) + '@effect/printer': 0.47.0(@effect/typeclass@0.38.0(effect@3.19.19))(effect@3.19.19) + '@effect/printer-ansi': 0.47.0(@effect/typeclass@0.38.0(effect@3.19.19))(effect@3.19.19) + effect: 3.19.19 + transitivePeerDependencies: + - '@effect/cluster' + - '@effect/rpc' + - '@effect/sql' + - '@effect/typeclass' + - bufferutil + - utf-8-validate + '@esbuild-kit/core-utils@3.3.2': dependencies: esbuild: 0.18.20 @@ -1696,6 +2136,84 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 + '@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3': + optional: true + + '@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.3': + optional: true + + '@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.3': + optional: true + + '@msgpackr-extract/msgpackr-extract-linux-arm@3.0.3': + optional: true + + '@msgpackr-extract/msgpackr-extract-linux-x64@3.0.3': + optional: true + + '@msgpackr-extract/msgpackr-extract-win32-x64@3.0.3': + optional: true + + '@parcel/watcher-android-arm64@2.5.6': + optional: true + + '@parcel/watcher-darwin-arm64@2.5.6': + optional: true + + '@parcel/watcher-darwin-x64@2.5.6': + optional: true + + '@parcel/watcher-freebsd-x64@2.5.6': + optional: true + + '@parcel/watcher-linux-arm-glibc@2.5.6': + optional: true + + '@parcel/watcher-linux-arm-musl@2.5.6': + optional: true + + '@parcel/watcher-linux-arm64-glibc@2.5.6': + optional: true + + '@parcel/watcher-linux-arm64-musl@2.5.6': + optional: true + + '@parcel/watcher-linux-x64-glibc@2.5.6': + optional: true + + '@parcel/watcher-linux-x64-musl@2.5.6': + optional: true + + '@parcel/watcher-win32-arm64@2.5.6': + optional: true + + '@parcel/watcher-win32-ia32@2.5.6': + optional: true + + '@parcel/watcher-win32-x64@2.5.6': + optional: true + + '@parcel/watcher@2.5.6': + dependencies: + detect-libc: 2.1.2 + is-glob: 4.0.3 + node-addon-api: 7.1.1 + picomatch: 4.0.3 + optionalDependencies: + '@parcel/watcher-android-arm64': 2.5.6 + '@parcel/watcher-darwin-arm64': 2.5.6 + '@parcel/watcher-darwin-x64': 2.5.6 + '@parcel/watcher-freebsd-x64': 2.5.6 + '@parcel/watcher-linux-arm-glibc': 2.5.6 + '@parcel/watcher-linux-arm-musl': 2.5.6 + '@parcel/watcher-linux-arm64-glibc': 2.5.6 + '@parcel/watcher-linux-arm64-musl': 2.5.6 + '@parcel/watcher-linux-x64-glibc': 2.5.6 + '@parcel/watcher-linux-x64-musl': 2.5.6 + '@parcel/watcher-win32-arm64': 2.5.6 + '@parcel/watcher-win32-ia32': 2.5.6 + '@parcel/watcher-win32-x64': 2.5.6 + '@petamoriken/float16@3.9.3': {} '@rollup/rollup-android-arm-eabi@4.59.0': @@ -1862,6 +2380,8 @@ snapshots: deep-eql@5.0.2: {} + detect-libc@2.1.2: {} + drizzle-kit@0.30.6: dependencies: '@drizzle-team/brocli': 0.10.2 @@ -2025,6 +2545,8 @@ snapshots: optionalDependencies: picomatch: 4.0.3 + find-my-way-ts@0.1.6: {} + fix-dts-default-cjs-exports@1.0.1: dependencies: magic-string: 0.30.21 @@ -2049,10 +2571,20 @@ snapshots: dependencies: resolve-pkg-maps: 1.0.0 + ini@4.1.3: {} + + is-extglob@2.1.1: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + isexe@3.1.5: {} joycon@3.1.1: {} + kubernetes-types@1.30.0: {} + lilconfig@3.1.3: {} lines-and-columns@1.2.4: {} @@ -2065,6 +2597,8 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 + mime@3.0.0: {} + mlly@1.8.1: dependencies: acorn: 8.16.0 @@ -2074,6 +2608,24 @@ snapshots: ms@2.1.3: {} + msgpackr-extract@3.0.3: + dependencies: + node-gyp-build-optional-packages: 5.2.2 + optionalDependencies: + '@msgpackr-extract/msgpackr-extract-darwin-arm64': 3.0.3 + '@msgpackr-extract/msgpackr-extract-darwin-x64': 3.0.3 + '@msgpackr-extract/msgpackr-extract-linux-arm': 3.0.3 + '@msgpackr-extract/msgpackr-extract-linux-arm64': 3.0.3 + '@msgpackr-extract/msgpackr-extract-linux-x64': 3.0.3 + '@msgpackr-extract/msgpackr-extract-win32-x64': 3.0.3 + optional: true + + msgpackr@1.11.9: + optionalDependencies: + msgpackr-extract: 3.0.3 + + multipasta@0.2.7: {} + mz@2.7.0: dependencies: any-promise: 1.3.0 @@ -2082,6 +2634,13 @@ snapshots: nanoid@3.3.11: {} + node-addon-api@7.1.1: {} + + node-gyp-build-optional-packages@5.2.2: + dependencies: + detect-libc: 2.1.2 + optional: true + object-assign@4.1.1: {} path-expression-matcher@1.1.3: @@ -2105,12 +2664,13 @@ snapshots: mlly: 1.8.1 pathe: 2.0.3 - postcss-load-config@6.0.1(postcss@8.5.8)(tsx@4.21.0): + postcss-load-config@6.0.1(postcss@8.5.8)(tsx@4.21.0)(yaml@2.8.2): dependencies: lilconfig: 3.1.3 optionalDependencies: postcss: 8.5.8 tsx: 4.21.0 + yaml: 2.8.2 postcss@8.5.8: dependencies: @@ -2216,11 +2776,13 @@ snapshots: tinyspy@3.0.2: {} + toml@3.0.0: {} + tree-kill@1.2.2: {} ts-interface-checker@0.1.13: {} - tsup@8.5.1(postcss@8.5.8)(tsx@4.21.0)(typescript@5.9.3): + tsup@8.5.1(postcss@8.5.8)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2): dependencies: bundle-require: 5.1.0(esbuild@0.27.4) cac: 6.7.14 @@ -2231,7 +2793,7 @@ snapshots: fix-dts-default-cjs-exports: 1.0.1 joycon: 3.1.1 picocolors: 1.1.1 - postcss-load-config: 6.0.1(postcss@8.5.8)(tsx@4.21.0) + postcss-load-config: 6.0.1(postcss@8.5.8)(tsx@4.21.0)(yaml@2.8.2) resolve-from: 5.0.0 rollup: 4.59.0 source-map: 0.7.6 @@ -2261,6 +2823,10 @@ snapshots: undici-types@6.21.0: {} + undici@7.24.3: {} + + uuid@11.1.0: {} + vite-node@2.1.9(@types/node@22.19.15): dependencies: cac: 6.7.14 @@ -2332,4 +2898,8 @@ snapshots: siginfo: 2.0.0 stackback: 0.0.2 + ws@8.19.0: {} + + yaml@2.8.2: {} + zod@3.25.76: {}