Skip to content

test(fx): pin loss-sell-no-conversion → divisa 1633/1637 = 0 (#230)#237

Merged
GeiserX merged 1 commit into
mainfrom
fix/fx-loss-sell-no-conversion-test
Jun 17, 2026
Merged

test(fx): pin loss-sell-no-conversion → divisa 1633/1637 = 0 (#230)#237
GeiserX merged 1 commit into
mainfrom
fix/fx-loss-sell-no-conversion-test

Conversation

@GeiserX

@GeiserX GeiserX commented Jun 17, 2026

Copy link
Copy Markdown
Owner

What & why

Issue #230's reporter (elmasvital) confirmed the governing principle for foreign-currency stock losses: dollars spent inside a losing position that are never converted to EUR generate ZERO divisa gain/loss — under Art. 14.2.e LIRPF the FX result is imputed only at the cobro/pago (the real FCY→EUR conversion). The €-loss lands entirely in the stock line at the sale-date rate (V2422-20). Casillas 1633/1637 must contain only amounts actually transmitted (FCY→EUR) in the year.

The carry-basis engine already implements this exactly (a loss-sell emits no FxDisposal; only conversions reach 1633/1637). This PR is test + docs only — no engine change — pinning the invariant so it can never silently regress, and correcting a misleading internal doc.

Tests added (tests/integration/fx-carry-basis-e2e.test.ts)

Both use tracked funding so the buy genuinely parks a carried basis and the sell genuinely discards the lost principal (exercising the real path, not the uncovered no-op):

  1. Loss-sell, NO conversion — fund $1000@1.20 → buy $1000 AAPL → sell $800@0.80 (a $200 USD loss), nothing converted → 0 FX disposals, 1633 = 1637 = 0.00, and the loss lands entirely in the stock line (−€160.00).
  2. Loss-sell THEN convert the survivors — same, plus convert the surviving $800@0.80 → only that real conversion hits 1633/1637 at the carried 1.20 basis (−€320.00); the $200 lost in the falling stock is discarded and never taxed (only 800 USD ever transmitted).

Doc fix (CLAUDE.md)

Re-characterized the old "loss-sell carried-principal residual (unbounded ~€250k, NOT a bug)" note. The casilla output is the correct only-real-conversions figure; the so-called "residual" is a gap versus a raw-economic benchmark the engine deliberately does not compute (forcing it would double-count FX against the already-recognized stock loss) and never reaches a declared box. The discard is also clean — it mutates only the per-position parked FIFO, never the spendable pool, so it can't shift the FIFO frontier for a later conversion.

Verification

Full suite 1611 passing (was 1607 + 4 new), typecheck + eslint clean. No source/engine files touched.

Refs #230

Summary by CodeRabbit

  • Documentation

    • Clarified FX carry-basis documentation for foreign-currency stock loss sells without EUR conversion, providing accurate guidance on tax reporting casilla values in these scenarios.
  • Tests

    • Added integration test cases for tax report generation covering foreign-currency stock loss scenarios.

A foreign-currency stock sold at a LOSS whose dollars are never converted to
EUR generates ZERO divisa gain/loss (Art. 14.2.e: imputed only on the cobro/
pago = the real FCY→EUR conversion). The €-loss lands entirely in the stock
line at the sale-date rate (V2422-20). This is issue #230's reporter's
principle, and the carry-basis engine already implements it — these e2e tests
pin it at the casilla level so it can't silently regress:
- loss-sell, NO conversion → 0 FX disposals, 1633=1637=0.00, stock=-160.00
- loss-sell THEN convert the survivors → only the surviving $800 hits
  1633/1637 at the carried basis (-320.00); the lost $200 is never taxed.

Also re-characterize the CLAUDE.md note: the 'loss-sell residual' is a
gap-vs-raw-economic that the engine deliberately does not compute (forcing it
would double-count FX against the stock loss) and NEVER reaches a casilla — not
an 'unbounded residual' that lands in a declared box.

Refs #230
@coderabbitai

coderabbitai Bot commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Caution

Review failed

Pull request was closed or merged during review

📝 Walkthrough

Walkthrough

Two E2E integration tests are added to fx-carry-basis-e2e.test.ts for Issue #230, covering FCY stock loss sells where no EUR conversion occurs in-year. CLAUDE.md is updated to clarify that the carry-basis residual is a non-casilla reconciliation gap and does not affect casillas 1633/1637.

Changes

Issue #230: Loss-sell FX behavior

Layer / File(s) Summary
CLAUDE.md carry-basis residual clarification
CLAUDE.md
Rewords the loss-sell carried-principal residual description: 1633/1637 stay zero without a real EUR conversion; the residual is a raw-economic reconciliation gap in the V2422-20 seam only.
E2E tests: no-conversion and later-conversion loss sells
tests/integration/fx-carry-basis-e2e.test.ts
Adds two Issue #230 describe blocks: first asserts fxGains.disposals is empty and all FX casilla totals are 0.00 on a pure loss sell; second asserts FX gain/loss is -320.00 and converted quantity is 800 USD when a real conversion follows, confirming the lost principal never enters FX disposals.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

  • GeiserX/DeclaRenta#178: Directly touches casillas 1633/1637 rendering; this PR adds tests asserting those same casillas must be zero in loss-sell-without-EUR-conversion scenarios.
  • GeiserX/DeclaRenta#220: Establishes the V2422-20 FCY-first FIFO gain model that the new tests exercise when asserting no FX disposals are emitted on a loss sell.
  • GeiserX/DeclaRenta#232: Introduced the carry-basis defer model and event behavior that the new E2E assertions and CLAUDE.md wording directly target.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly reflects the core change: clarifying that foreign-currency stock losses with no conversion yield zero divisa in Spanish tax forms 1633/1637, matching the PR's primary objective.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/fx-loss-sell-no-conversion-test

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@GeiserX GeiserX merged commit 12d1328 into main Jun 17, 2026
4 of 5 checks passed
@GeiserX GeiserX deleted the fix/fx-loss-sell-no-conversion-test branch June 17, 2026 10:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant