fiscal-es 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +93 -2
  3. package/cases/SCHEMA.md +68 -0
  4. package/cases/irpf/ahorro/irpf-ahorro-2026-001.yaml +20 -0
  5. package/cases/irpf/ahorro/irpf-ahorro-2026-002.yaml +19 -0
  6. package/cases/irpf/ahorro/irpf-ahorro-2026-003.yaml +21 -0
  7. package/cases/irpf/ahorro/irpf-ahorro-2026-004.yaml +21 -0
  8. package/cases/irpf/ahorro/irpf-ahorro-2026-005.yaml +20 -0
  9. package/cases/irpf/ahorro/irpf-ahorro-2026-006.yaml +22 -0
  10. package/cases/irpf/ahorro/irpf-ahorro-2026-007.yaml +22 -0
  11. package/cases/irpf/ahorro/irpf-ahorro-2026-008.yaml +23 -0
  12. package/cases/irpf/ahorro/irpf-ahorro-2026-009.yaml +22 -0
  13. package/cases/irpf/ahorro/irpf-ahorro-2026-010.yaml +20 -0
  14. package/cases/irpf/ahorro/irpf-ahorro-2026-011.yaml +22 -0
  15. package/cases/irpf/ahorro/irpf-ahorro-2026-012.yaml +22 -0
  16. package/cases/irpf/ahorro/irpf-ahorro-2026-013.yaml +23 -0
  17. package/cases/irpf/ahorro/irpf-ahorro-2026-014.yaml +23 -0
  18. package/cases/irpf/ahorro/irpf-ahorro-2026-015.yaml +22 -0
  19. package/cases/irpf/ahorro/irpf-ahorro-2026-016.yaml +22 -0
  20. package/cases/irpf/ahorro/irpf-ahorro-2026-017.yaml +23 -0
  21. package/cases/irpf/ahorro/irpf-ahorro-2026-018.yaml +20 -0
  22. package/cases/irpf/ahorro/irpf-ahorro-2026-019.yaml +21 -0
  23. package/cases/irpf/ahorro/irpf-ahorro-2026-020.yaml +21 -0
  24. package/cases/irpf/ahorro/irpf-ahorro-2026-021.yaml +21 -0
  25. package/cases/irpf/ahorro/irpf-ahorro-2026-022.yaml +21 -0
  26. package/cases/irpf/ahorro/irpf-ahorro-2026-023.yaml +23 -0
  27. package/cases/irpf/ahorro/irpf-ahorro-2026-024.yaml +21 -0
  28. package/cases/irpf/ahorro/irpf-ahorro-2026-025.yaml +22 -0
  29. package/cases/irpf/ahorro/irpf-ahorro-2026-026.yaml +22 -0
  30. package/cases/irpf/compensacion/irpf-compensacion-2026-001.yaml +21 -0
  31. package/cases/irpf/compensacion/irpf-compensacion-2026-002.yaml +22 -0
  32. package/cases/irpf/compensacion/irpf-compensacion-2026-003.yaml +22 -0
  33. package/cases/irpf/compensacion/irpf-compensacion-2026-004.yaml +25 -0
  34. package/cases/irpf/compensacion/irpf-compensacion-2026-005.yaml +25 -0
  35. package/cases/irpf/compensacion/irpf-compensacion-2026-006.yaml +21 -0
  36. package/cases/irpf/compensacion/irpf-compensacion-2026-007.yaml +23 -0
  37. package/cases/irpf/compensacion/irpf-compensacion-2026-008.yaml +20 -0
  38. package/cases/irpf/compensacion/irpf-compensacion-2026-009.yaml +21 -0
  39. package/cases/irpf/compensacion/irpf-compensacion-2026-010.yaml +21 -0
  40. package/dist/dinero.d.ts +31 -0
  41. package/dist/dinero.js +64 -0
  42. package/dist/errores.d.ts +11 -0
  43. package/dist/errores.js +18 -0
  44. package/dist/index.d.ts +7 -0
  45. package/dist/index.js +4 -0
  46. package/dist/irpf/ahorro.d.ts +3 -0
  47. package/dist/irpf/ahorro.js +41 -0
  48. package/dist/irpf/compensacion.d.ts +10 -0
  49. package/dist/irpf/compensacion.js +13 -0
  50. package/dist/irpf/escala.d.ts +7 -0
  51. package/dist/irpf/escala.js +18 -0
  52. package/dist/irpf/tipos.d.ts +13 -0
  53. package/dist/irpf/tipos.js +1 -0
  54. package/dist/tablas/2026.d.ts +28 -0
  55. package/dist/tablas/2026.js +16 -0
  56. package/dist/tablas/index.d.ts +5 -0
  57. package/dist/tablas/index.js +13 -0
  58. package/dist/tablas/tipos.d.ts +16 -0
  59. package/dist/tablas/tipos.js +1 -0
  60. package/package.json +46 -4
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Miguel Vidal Bellido
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,94 @@
1
- # Temporary Holding Version
1
+ # fiscal-es
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Cuota del IRPF sobre la base del ahorro (España, territorio común): integra ganancias, pérdidas y rendimientos
4
+ del capital mobiliario de un ejercicio y aplica la escala estatal y la autonómica por separado.
5
+
6
+ TypeScript, ESM, sin dependencias de runtime, funciones puras. Importes en céntimos enteros (`Centimos`) y tipos
7
+ en puntos básicos enteros (`Bp`); nunca coma flotante.
8
+
9
+ ## Instalación
10
+
11
+ ```sh
12
+ npm install fiscal-es
13
+ ```
14
+
15
+ Solo ESM (`import`); no hay build CommonJS.
16
+
17
+ ## Uso
18
+
19
+ ### Calcular la cuota del ahorro
20
+
21
+ ```js
22
+ import { calcularAhorro, formatEuros, parseEuros } from "fiscal-es";
23
+
24
+ const r = calcularAhorro({
25
+ ejercicio: 2026,
26
+ gananciasPatrimoniales: parseEuros("10000.00"),
27
+ perdidasPatrimoniales: parseEuros("3000.00"),
28
+ rendimientosCapitalMobiliario: parseEuros("0.00"),
29
+ });
30
+ // r = { baseLiquidableAhorro: 700000, cuotaEstatal: 67500, cuotaAutonomica: 67500, cuotaAhorro: 135000 }
31
+ formatEuros(r.cuotaAhorro); // "1350.00"
32
+ ```
33
+
34
+ `parseEuros` solo acepta el formato `"-1234.56"` (punto decimal, dos decimales); cualquier otro lanza
35
+ `SyntaxError`. Una entrada sin sentido (importes no enteros, ganancias o pérdidas negativas) lanza
36
+ `EntradaInvalida`; un ejercicio sin tabla, `EjercicioSinTabla`.
37
+
38
+ ### Ejercicios disponibles
39
+
40
+ ```js
41
+ import { ejerciciosDisponibles, getTabla } from "fiscal-es";
42
+
43
+ for (const ejercicio of ejerciciosDisponibles()) {
44
+ console.log(ejercicio, getTabla(ejercicio).fuente.verificado); // 2026 "2026-10-25"
45
+ }
46
+ ```
47
+
48
+ ## Rango y errores
49
+
50
+ El cálculo es exacto para importes de hasta ≈ 9 000 millones de euros por operación: con tipos de hasta
51
+ 10 000 bp, el producto intermedio céntimos × puntos básicos cabe en `Number.MAX_SAFE_INTEGER`. Por encima, una
52
+ operación cuyo resultado o producto intermedio salga del rango entero seguro lanza `RangeError`.
53
+
54
+ - `SyntaxError`: `parseEuros` recibe un formato distinto de `"-1234.56"` (ver [Uso](#uso)).
55
+ - `EntradaInvalida`: `calcularAhorro` recibe un importe que no es un entero de céntimos (`NaN`, fracción de
56
+ céntimo), ganancias o pérdidas negativas, o un ejercicio no entero.
57
+ - `EjercicioSinTabla`: `calcularAhorro` recibe un ejercicio sin tabla.
58
+ - `RangeError`: `parseEuros`, `sumar`, `restar`, `porBp` o `redondearCentimos` reciben o producen un valor fuera
59
+ del rango entero seguro.
60
+
61
+ ## Ejercicios soportados
62
+
63
+ | Ejercicio | Escala verificada |
64
+ | --------- | ----------------- |
65
+ | 2026 | 2026-10-25 |
66
+
67
+ ## `cases/`: la spec ejecutable
68
+
69
+ Cada fichero YAML de `cases/` es un caso con su entrada, la salida esperada al céntimo, la referencia normativa
70
+ y la fecha en que una persona lo verificó. La suite del paquete los ejecuta todos contra el motor; un cambio en
71
+ el motor que altere la salida de un caso verificado es un cambio de versión mayor. El formato está en
72
+ `cases/SCHEMA.md`. `cases/_propuestos/` contiene casos aún no verificados y no forma parte de la spec.
73
+
74
+ ## Límites conocidos
75
+
76
+ - Sin compensación cruzada: un saldo negativo de ganancias y pérdidas no compensa rendimientos del capital
77
+ mobiliario positivos, ni al revés (art. 49.1 LIRPF). El saldo negativo simplemente no tributa.
78
+ - Sin arrastre de saldos negativos a los cuatro ejercicios siguientes (art. 49.2 LIRPF).
79
+ - Sin regímenes forales (Navarra, País Vasco).
80
+ - Solo la base del ahorro: no calcula la base general, mínimos personales ni deducciones.
81
+
82
+ ## Desarrollo en el monorepo
83
+
84
+ `exports` tiene una condición propia, `fiscal-es-src`, que apunta a `src/` para que la app del monorepo use el
85
+ motor sin construirlo; los consumidores externos no la activan y reciben `dist/`.
86
+
87
+ ## Aviso
88
+
89
+ Herramienta de cálculo. No es asesoramiento fiscal ni financiero. Comprueba tu caso con la normativa vigente o
90
+ con un profesional.
91
+
92
+ ## Licencia
93
+
94
+ MIT
@@ -0,0 +1,68 @@
1
+ # Casos fiscales — esquema y reglas
2
+
3
+ Los ficheros `cases/**/*.yaml` son **la spec del motor**. A la vez: test, documentación y contrato público.
4
+ Un único test genérico (`tests/cases.test.ts`) los carga todos y ejecuta cada uno contra el motor.
5
+ Comparación exacta en céntimos. No hay tolerancias.
6
+
7
+ ## Formato
8
+
9
+ ```yaml
10
+ id: irpf-ahorro-2026-002 # único; <área>-<tema>-<ejercicio>-<nnn>
11
+ title: Ganancia que cruza el primer tramo de la base del ahorro
12
+ ejercicio: 2026
13
+ estado: rojo # rojo | verde. Solo pasa a verde tras revisión humana del resultado.
14
+ normativa:
15
+ - ref: "Ley 35/2006 IRPF, art. 66.1 (gravamen estatal base del ahorro)"
16
+ - ref: "Ley 35/2006 IRPF, art. 76 (gravamen autonómico base del ahorro)"
17
+ verificado: null # fecha ISO cuando una persona ha contrastado expected con la fuente; null si no
18
+ input:
19
+ ganancias_patrimoniales: "8000.00"
20
+ perdidas_patrimoniales: "0.00"
21
+ rendimientos_capital_mobiliario: "0.00"
22
+ expected:
23
+ base_liquidable_ahorro: "8000.00"
24
+ cuota_estatal: "780.00" # 6000·9,5 % + 2000·10,5 %
25
+ cuota_autonomica: "780.00"
26
+ cuota_ahorro: "1560.00"
27
+ notas: >
28
+ Texto libre opcional: cómo se ha calculado a mano, dudas, referencias al manual de Renta.
29
+ ```
30
+
31
+ ## Reglas de validación (el loader las aplica; un caso inválido hace fallar la suite)
32
+
33
+ 1. Todo importe (`input.*` y `expected.*`) es un **string** decimal con exactamente dos decimales y punto.
34
+ Un número YAML (`8000`, `8000.0`) para dinero es un error. Motivo: ADR-002.
35
+ 2. `id` único en todo `cases/`. El nombre del fichero es `<id>.yaml`.
36
+ 3. `ejercicio` tiene que tener tabla en `src/tablas/`. Si no, el caso falla con un mensaje que lo diga.
37
+ 4. `normativa` tiene al menos una entrada. Sin cita, no hay caso.
38
+ 5. `estado: verde` exige `verificado` no nulo. El test lo comprueba: un caso verde sin verificación es un fallo.
39
+ 6. Un cambio en `expected` de un caso existente exige cambiar `verificado` a `null` o a una fecha nueva.
40
+ (Esto lo comprueba la revisión humana y `fiscal-reviewer`, no el test.)
41
+
42
+ ## Cómo se calcula (para quien escribe casos a mano)
43
+
44
+ - La base liquidable del ahorro = saldo de rendimientos del capital mobiliario + saldo de (ganancias − pérdidas).
45
+ En v1 cada saldo se integra por separado y **un saldo negativo se trata como cero**: ni compensa al otro saldo
46
+ (el 25 % cruzado del art. 49.1) ni se arrastra a ejercicios siguientes. Es una simplificación deliberada
47
+ (ADR-005); cuando se implemente la compensación cruzada se añadirán casos nuevos, no se cambiarán estos.
48
+ Consecuencias: pérdidas mayores que ganancias → saldo cero; rendimientos negativos → saldo cero.
49
+ - Cuota estatal y cuota autonómica se calculan **por separado**, cada una aplicando su escala por tramos a la
50
+ misma base, y cada una se redondea al céntimo. `cuota_ahorro` es la suma de las dos ya redondeadas.
51
+ Esto importa: 1.234,56 € × 9,5 % = 117,2832 → 117,28; dos veces = 234,56, mientras que 1.234,56 × 19 %
52
+ = 234,5664 → 234,57. El motor replica el cálculo de Renta, no el atajo.
53
+
54
+ ## Qué NO va aquí
55
+
56
+ - Casos de UI, de formato o de URL. Eso es Vitest normal en `apps/web`.
57
+ - Simulaciones completas de N años. Eso es `packages/fiscal-es/tests/simulacion.test.ts` cuando exista
58
+ (y usa el motor caso a caso).
59
+
60
+ ## Convención de carpetas
61
+
62
+ ```
63
+ cases/
64
+ irpf/
65
+ ahorro/ # escala de la base del ahorro
66
+ compensacion/
67
+ README-por-area.md (opcional)
68
+ ```
@@ -0,0 +1,20 @@
1
+ id: irpf-ahorro-2026-001
2
+ title: Ganancia dentro del primer tramo de la base del ahorro
3
+ ejercicio: 2026
4
+ estado: verde
5
+ normativa:
6
+ - ref: "Ley 35/2006 IRPF, art. 66.1 (gravamen estatal base del ahorro)"
7
+ - ref: "Ley 35/2006 IRPF, art. 76 (gravamen autonómico base del ahorro)"
8
+ verificado: "2026-10-25"
9
+ input:
10
+ ganancias_patrimoniales: "5000.00"
11
+ perdidas_patrimoniales: "0.00"
12
+ rendimientos_capital_mobiliario: "0.00"
13
+ expected:
14
+ base_liquidable_ahorro: "5000.00"
15
+ cuota_estatal: "475.00"
16
+ cuota_autonomica: "475.00"
17
+ cuota_ahorro: "950.00"
18
+ notas: >
19
+ 5.000 × 9,5 % = 475,00 estatal; igual autonómico. Total 19 %.
20
+ Pendiente de verificar la escala vigente del ejercicio 2026 contra el manual de Renta.
@@ -0,0 +1,19 @@
1
+ id: irpf-ahorro-2026-002
2
+ title: Ganancia que cruza el primer tramo de la base del ahorro
3
+ ejercicio: 2026
4
+ estado: verde
5
+ normativa:
6
+ - ref: "Ley 35/2006 IRPF, art. 66.1 (gravamen estatal base del ahorro)"
7
+ - ref: "Ley 35/2006 IRPF, art. 76 (gravamen autonómico base del ahorro)"
8
+ verificado: "2026-10-25"
9
+ input:
10
+ ganancias_patrimoniales: "8000.00"
11
+ perdidas_patrimoniales: "0.00"
12
+ rendimientos_capital_mobiliario: "0.00"
13
+ expected:
14
+ base_liquidable_ahorro: "8000.00"
15
+ cuota_estatal: "780.00"
16
+ cuota_autonomica: "780.00"
17
+ cuota_ahorro: "1560.00"
18
+ notas: >
19
+ Estatal: 6.000 × 9,5 % = 570,00 + 2.000 × 10,5 % = 210,00 → 780,00. Autonómico idéntico.
@@ -0,0 +1,21 @@
1
+ id: irpf-ahorro-2026-003
2
+ title: Base que recorre todos los tramos de la escala del ahorro
3
+ ejercicio: 2026
4
+ estado: verde
5
+ normativa:
6
+ - ref: "Ley 35/2006 IRPF, art. 66.1 (gravamen estatal base del ahorro)"
7
+ - ref: "Ley 35/2006 IRPF, art. 76 (gravamen autonómico base del ahorro)"
8
+ verificado: "2026-10-25"
9
+ input:
10
+ ganancias_patrimoniales: "350000.00"
11
+ perdidas_patrimoniales: "0.00"
12
+ rendimientos_capital_mobiliario: "0.00"
13
+ expected:
14
+ base_liquidable_ahorro: "350000.00"
15
+ cuota_estatal: "43440.00"
16
+ cuota_autonomica: "43440.00"
17
+ cuota_ahorro: "86880.00"
18
+ notas: >
19
+ Tipos combinados: 19 % hasta 6.000; 21 % hasta 50.000; 23 % hasta 200.000; 27 % hasta 300.000; 30 % resto.
20
+ Combinado: 1.140 + 9.240 + 34.500 + 27.000 + 15.000 = 86.880,00. Mitad estatal, mitad autonómico.
21
+ Este caso existe para pillar errores de umbral (¿el tramo cierra en 50.000,00 o en 50.000,01?).
@@ -0,0 +1,21 @@
1
+ id: irpf-ahorro-2026-004
2
+ title: Redondeo por separado de cuota estatal y autonómica (intereses de cuenta remunerada)
3
+ ejercicio: 2026
4
+ estado: verde
5
+ normativa:
6
+ - ref: "Ley 35/2006 IRPF, art. 25.2 (intereses como rendimiento del capital mobiliario)"
7
+ - ref: "Ley 35/2006 IRPF, art. 66.1 y art. 76"
8
+ verificado: "2026-10-25"
9
+ input:
10
+ ganancias_patrimoniales: "0.00"
11
+ perdidas_patrimoniales: "0.00"
12
+ rendimientos_capital_mobiliario: "1234.56"
13
+ expected:
14
+ base_liquidable_ahorro: "1234.56"
15
+ cuota_estatal: "117.28"
16
+ cuota_autonomica: "117.28"
17
+ cuota_ahorro: "234.56"
18
+ notas: >
19
+ 1.234,56 × 9,5 % = 117,2832 → 117,28 (cada cuota por separado). Suma 234,56.
20
+ El atajo 1.234,56 × 19 % = 234,5664 → 234,57 es INCORRECTO. Este caso existe para detectar
21
+ implementaciones que calculan con el tipo combinado. Verificar la regla de redondeo con el manual de Renta.
@@ -0,0 +1,20 @@
1
+ id: irpf-ahorro-2026-005
2
+ title: Base del ahorro cero
3
+ ejercicio: 2026
4
+ estado: verde
5
+ normativa:
6
+ - ref: "Ley 35/2006 IRPF, art. 66.1 (gravamen estatal base del ahorro)"
7
+ - ref: "Ley 35/2006 IRPF, art. 76 (gravamen autonómico base del ahorro)"
8
+ verificado: "2026-10-25"
9
+ input:
10
+ ganancias_patrimoniales: "0.00"
11
+ perdidas_patrimoniales: "0.00"
12
+ rendimientos_capital_mobiliario: "0.00"
13
+ expected:
14
+ base_liquidable_ahorro: "0.00"
15
+ cuota_estatal: "0.00"
16
+ cuota_autonomica: "0.00"
17
+ cuota_ahorro: "0.00"
18
+ notas: >
19
+ Sin rentas del ahorro. Base 0 → cuotas 0,00. Detecta implementaciones que fallan con base vacía
20
+ (bucle de tramos que no arranca, división por cero, "-0.00" en la salida).
@@ -0,0 +1,22 @@
1
+ id: irpf-ahorro-2026-006
2
+ title: Base exactamente en el umbral superior del primer tramo (6.000,00)
3
+ ejercicio: 2026
4
+ estado: verde
5
+ normativa:
6
+ - ref: "Ley 35/2006 IRPF, art. 66.1 (gravamen estatal base del ahorro)"
7
+ - ref: "Ley 35/2006 IRPF, art. 76 (gravamen autonómico base del ahorro)"
8
+ verificado: "2026-10-25"
9
+ input:
10
+ ganancias_patrimoniales: "6000.00"
11
+ perdidas_patrimoniales: "0.00"
12
+ rendimientos_capital_mobiliario: "0.00"
13
+ expected:
14
+ base_liquidable_ahorro: "6000.00"
15
+ cuota_estatal: "570.00"
16
+ cuota_autonomica: "570.00"
17
+ cuota_ahorro: "1140.00"
18
+ notas: >
19
+ Estatal: 6.000,00 × 9,5 % = 570,00. Autonómico idéntico. Total 1.140,00.
20
+ Todo dentro del primer tramo: ni un céntimo puede ir al 10,5 %. Si el motor trata `hasta` como
21
+ exclusivo y cruza al segundo tramo, la diferencia aquí es 0 céntimos (ver 007); el caso sirve sobre
22
+ todo para detectar que se aplique el tipo marginal a toda la base (6.000 × 10,5 % = 630,00).
@@ -0,0 +1,22 @@
1
+ id: irpf-ahorro-2026-007
2
+ title: Base un céntimo por encima del primer umbral (6.000,01)
3
+ ejercicio: 2026
4
+ estado: verde
5
+ normativa:
6
+ - ref: "Ley 35/2006 IRPF, art. 66.1 (gravamen estatal base del ahorro)"
7
+ - ref: "Ley 35/2006 IRPF, art. 76 (gravamen autonómico base del ahorro)"
8
+ verificado: "2026-10-25"
9
+ input:
10
+ ganancias_patrimoniales: "6000.01"
11
+ perdidas_patrimoniales: "0.00"
12
+ rendimientos_capital_mobiliario: "0.00"
13
+ expected:
14
+ base_liquidable_ahorro: "6000.01"
15
+ cuota_estatal: "570.00"
16
+ cuota_autonomica: "570.00"
17
+ cuota_ahorro: "1140.00"
18
+ notas: >
19
+ Estatal en céntimos·bp: 600.000 × 950 = 570.000.000 + 1 × 1.050 = 1.050 → 570.001.050 / 10.000
20
+ = 57.000,105 c → 57.000 c = 570,00. Autonómico idéntico. Total 1.140,00.
21
+ Errores que detecta: el tipo marginal sobre toda la base (6.000,01 × 10,5 % = 630,00), y el redondeo
22
+ hacia arriba o por exceso del tramo de 0,01 € (daría 570,01).
@@ -0,0 +1,23 @@
1
+ id: irpf-ahorro-2026-008
2
+ title: Medio céntimo exacto en cada cuota (redondeo mitad alejándose de cero, no bancario ni truncado)
3
+ ejercicio: 2026
4
+ estado: verde
5
+ normativa:
6
+ - ref: "Ley 35/2006 IRPF, art. 25.2 (intereses como rendimiento del capital mobiliario)"
7
+ - ref: "Ley 35/2006 IRPF, art. 66.1 y art. 76"
8
+ verificado: "2026-10-25"
9
+ input:
10
+ ganancias_patrimoniales: "0.00"
11
+ perdidas_patrimoniales: "0.00"
12
+ rendimientos_capital_mobiliario: "3.00"
13
+ expected:
14
+ base_liquidable_ahorro: "3.00"
15
+ cuota_estatal: "0.29"
16
+ cuota_autonomica: "0.29"
17
+ cuota_ahorro: "0.58"
18
+ notas: >
19
+ Estatal: 300 c × 950 bp = 285.000 → 28,5 c → 29 c = 0,29 (mitad hacia arriba). Autonómico idéntico. Total 0,58.
20
+ Resultados erróneos que distingue: truncado → 0,28 + 0,28 = 0,56; redondeo bancario (half-even, 28,5 → 28)
21
+ → 0,56; tipo combinado 3,00 × 19 % = 0,57.
22
+ Duda normativa: confirmar con el manual de Renta o con el programa Renta Web que la AEAT redondea cada
23
+ cuota al céntimo con la mitad hacia arriba (ADR-002 lo asume).
@@ -0,0 +1,22 @@
1
+ id: irpf-ahorro-2026-009
2
+ title: Base con céntimos en el segundo tramo; el redondeo por cuota separada difiere del combinado
3
+ ejercicio: 2026
4
+ estado: verde
5
+ normativa:
6
+ - ref: "Ley 35/2006 IRPF, art. 66.1 (gravamen estatal base del ahorro)"
7
+ - ref: "Ley 35/2006 IRPF, art. 76 (gravamen autonómico base del ahorro)"
8
+ verificado: "2026-10-25"
9
+ input:
10
+ ganancias_patrimoniales: "7777.77"
11
+ perdidas_patrimoniales: "0.00"
12
+ rendimientos_capital_mobiliario: "0.00"
13
+ expected:
14
+ base_liquidable_ahorro: "7777.77"
15
+ cuota_estatal: "756.67"
16
+ cuota_autonomica: "756.67"
17
+ cuota_ahorro: "1513.34"
18
+ notas: >
19
+ Estatal en céntimos·bp: 600.000 × 950 = 570.000.000; 177.777 × 1.050 = 186.665.850; suma 756.665.850
20
+ / 10.000 = 75.666,585 c → 75.667 c = 756,67. Autonómico idéntico. Total 1.513,34.
21
+ Atajo combinado: 600.000 × 1.900 + 177.777 × 2.100 = 1.513.331.700 → 151.333,17 c → 1.513,33 (incorrecto).
22
+ Este caso es como el 004, pero cruza un tramo y lleva céntimos en la base.
@@ -0,0 +1,20 @@
1
+ id: irpf-ahorro-2026-010
2
+ title: Base de un céntimo; la cuota redondea a cero
3
+ ejercicio: 2026
4
+ estado: verde
5
+ normativa:
6
+ - ref: "Ley 35/2006 IRPF, art. 25.2 (intereses como rendimiento del capital mobiliario)"
7
+ - ref: "Ley 35/2006 IRPF, art. 66.1 y art. 76"
8
+ verificado: "2026-10-25"
9
+ input:
10
+ ganancias_patrimoniales: "0.00"
11
+ perdidas_patrimoniales: "0.00"
12
+ rendimientos_capital_mobiliario: "0.01"
13
+ expected:
14
+ base_liquidable_ahorro: "0.01"
15
+ cuota_estatal: "0.00"
16
+ cuota_autonomica: "0.00"
17
+ cuota_ahorro: "0.00"
18
+ notas: >
19
+ 1 c × 950 bp = 950 → 0,095 c → 0 c. Autonómico idéntico. Total 0,00.
20
+ Detecta el redondeo hacia arriba (ceil), que daría 0,01 + 0,01.
@@ -0,0 +1,22 @@
1
+ id: irpf-ahorro-2026-011
2
+ title: Base un céntimo por encima del segundo umbral (50.000,01)
3
+ ejercicio: 2026
4
+ estado: verde
5
+ normativa:
6
+ - ref: "Ley 35/2006 IRPF, art. 66.1 (gravamen estatal base del ahorro)"
7
+ - ref: "Ley 35/2006 IRPF, art. 76 (gravamen autonómico base del ahorro)"
8
+ verificado: "2026-10-25"
9
+ input:
10
+ ganancias_patrimoniales: "50000.01"
11
+ perdidas_patrimoniales: "0.00"
12
+ rendimientos_capital_mobiliario: "0.00"
13
+ expected:
14
+ base_liquidable_ahorro: "50000.01"
15
+ cuota_estatal: "5190.00"
16
+ cuota_autonomica: "5190.00"
17
+ cuota_ahorro: "10380.00"
18
+ notas: >
19
+ Estatal en céntimos·bp: 600.000 × 950 = 570.000.000; 4.400.000 × 1.050 = 4.620.000.000; 1 × 1.150 = 1.150;
20
+ suma 5.190.001.150 / 10.000 = 519.000,115 c → 519.000 c = 5.190,00. Autonómico idéntico. Total 10.380,00.
21
+ Detecta `hasta` tratado como exclusivo en el segundo tramo y el marginal aplicado a toda la base
22
+ (50.000,01 × 11,5 % = 5.750,00).
@@ -0,0 +1,22 @@
1
+ id: irpf-ahorro-2026-012
2
+ title: Base un céntimo por encima del último umbral (300.000,01), entra en el tramo sin límite
3
+ ejercicio: 2026
4
+ estado: verde
5
+ normativa:
6
+ - ref: "Ley 35/2006 IRPF, art. 66.1 (gravamen estatal base del ahorro)"
7
+ - ref: "Ley 35/2006 IRPF, art. 76 (gravamen autonómico base del ahorro)"
8
+ verificado: "2026-10-25"
9
+ input:
10
+ ganancias_patrimoniales: "300000.01"
11
+ perdidas_patrimoniales: "0.00"
12
+ rendimientos_capital_mobiliario: "0.00"
13
+ expected:
14
+ base_liquidable_ahorro: "300000.01"
15
+ cuota_estatal: "35940.00"
16
+ cuota_autonomica: "35940.00"
17
+ cuota_ahorro: "71880.00"
18
+ notas: >
19
+ Estatal en céntimos·bp: 600.000 × 950 = 570.000.000; 4.400.000 × 1.050 = 4.620.000.000;
20
+ 15.000.000 × 1.150 = 17.250.000.000; 10.000.000 × 1.350 = 13.500.000.000; 1 × 1.500 = 1.500;
21
+ suma 35.940.001.500 / 10.000 = 3.594.000,15 c → 3.594.000 c = 35.940,00. Autonómico idéntico. Total 71.880,00.
22
+ Detecta un tramo `hasta: null` que no se aplica, o que se aplica desde 0, y el `hasta` exclusivo en el último umbral.
@@ -0,0 +1,23 @@
1
+ id: irpf-ahorro-2026-013
2
+ title: Medio céntimo exacto en el segundo tramo (redondeo mitad hacia arriba y cuotas separadas)
3
+ ejercicio: 2026
4
+ estado: verde
5
+ normativa:
6
+ - ref: "Ley 35/2006 IRPF, art. 66.1 (gravamen estatal base del ahorro)"
7
+ - ref: "Ley 35/2006 IRPF, art. 76 (gravamen autonómico base del ahorro)"
8
+ verificado: "2026-10-25"
9
+ input:
10
+ ganancias_patrimoniales: "6001.00"
11
+ perdidas_patrimoniales: "0.00"
12
+ rendimientos_capital_mobiliario: "0.00"
13
+ expected:
14
+ base_liquidable_ahorro: "6001.00"
15
+ cuota_estatal: "570.11"
16
+ cuota_autonomica: "570.11"
17
+ cuota_ahorro: "1140.22"
18
+ notas: >
19
+ Estatal en céntimos·bp: 600.000 × 950 = 570.000.000 + 100 × 1.050 = 105.000 → 570.105.000 / 10.000
20
+ = 57.010,5 c → 57.011 c = 570,11 (mitad hacia arriba). Autonómico idéntico. Total 1.140,22.
21
+ Errores que detecta: half-even o truncado (57.010 → 570,10; total 1.140,20); tipo combinado
22
+ (600.000 × 1.900 + 100 × 2.100 = 1.140.210.000 → 1.140,21). El caso 008 cubre el medio céntimo solo
23
+ en el primer tramo; este lo cubre en un tramo que no es el primero.
@@ -0,0 +1,23 @@
1
+ id: irpf-ahorro-2026-014
2
+ title: Medio céntimo exacto en el último tramo (sin límite superior)
3
+ ejercicio: 2026
4
+ estado: verde
5
+ normativa:
6
+ - ref: "Ley 35/2006 IRPF, art. 66.1 (gravamen estatal base del ahorro)"
7
+ - ref: "Ley 35/2006 IRPF, art. 76 (gravamen autonómico base del ahorro)"
8
+ verificado: "2026-10-25"
9
+ input:
10
+ ganancias_patrimoniales: "300000.30"
11
+ perdidas_patrimoniales: "0.00"
12
+ rendimientos_capital_mobiliario: "0.00"
13
+ expected:
14
+ base_liquidable_ahorro: "300000.30"
15
+ cuota_estatal: "35940.05"
16
+ cuota_autonomica: "35940.05"
17
+ cuota_ahorro: "71880.10"
18
+ notas: >
19
+ Estatal en céntimos·bp: tramos completos 570.000.000 + 4.620.000.000 + 17.250.000.000 + 13.500.000.000
20
+ = 35.940.000.000; + 30 × 1.500 = 45.000 → 35.940.045.000 / 10.000 = 3.594.004,5 c → 3.594.005 c
21
+ = 35.940,05. Autonómico idéntico. Total 71.880,10.
22
+ Errores que detecta: half-even o truncado (3.594.004 → 71.880,08); tipo combinado
23
+ (7.188.000.000... + 30 × 3.000 = 90.000 → 71.880,09).
@@ -0,0 +1,22 @@
1
+ id: irpf-ahorro-2026-015
2
+ title: Base exactamente en el umbral de 200.000,00
3
+ ejercicio: 2026
4
+ estado: verde
5
+ normativa:
6
+ - ref: "Ley 35/2006 IRPF, art. 66.1 (gravamen estatal base del ahorro)"
7
+ - ref: "Ley 35/2006 IRPF, art. 76 (gravamen autonómico base del ahorro)"
8
+ verificado: "2026-10-25"
9
+ input:
10
+ ganancias_patrimoniales: "200000.00"
11
+ perdidas_patrimoniales: "0.00"
12
+ rendimientos_capital_mobiliario: "0.00"
13
+ expected:
14
+ base_liquidable_ahorro: "200000.00"
15
+ cuota_estatal: "22440.00"
16
+ cuota_autonomica: "22440.00"
17
+ cuota_ahorro: "44880.00"
18
+ notas: >
19
+ Estatal: 600.000 × 950 + 4.400.000 × 1.050 + 15.000.000 × 1.150 = 22.440.000.000 / 10.000
20
+ = 2.244.000 c = 22.440,00. Autonómico idéntico. Total 44.880,00 (1.140 + 9.240 + 34.500).
21
+ Ningún caso existente toca el umbral de 200.000 (el 003 lo cruza de largo). Detecta el tipo marginal
22
+ aplicado a toda la base (200.000 × 11,5 % = 23.000,00) y un tercer tramo mal cerrado.
@@ -0,0 +1,22 @@
1
+ id: irpf-ahorro-2026-016
2
+ title: Un céntimo por encima del umbral de 200.000,00
3
+ ejercicio: 2026
4
+ estado: verde
5
+ normativa:
6
+ - ref: "Ley 35/2006 IRPF, art. 66.1 (gravamen estatal base del ahorro)"
7
+ - ref: "Ley 35/2006 IRPF, art. 76 (gravamen autonómico base del ahorro)"
8
+ verificado: "2026-10-25"
9
+ input:
10
+ ganancias_patrimoniales: "200000.01"
11
+ perdidas_patrimoniales: "0.00"
12
+ rendimientos_capital_mobiliario: "0.00"
13
+ expected:
14
+ base_liquidable_ahorro: "200000.01"
15
+ cuota_estatal: "22440.00"
16
+ cuota_autonomica: "22440.00"
17
+ cuota_ahorro: "44880.00"
18
+ notas: >
19
+ Estatal: 22.440.000.000 + 1 × 1.350 = 22.440.001.350 / 10.000 = 2.244.000,135 c → 2.244.000 c = 22.440,00.
20
+ Autonómico idéntico. Total 44.880,00.
21
+ Detecta el marginal sobre toda la base (200.000,01 × 13,5 % = 27.000,00) y el redondeo hacia arriba
22
+ del céntimo que entra en el cuarto tramo (daría 22.440,01).
@@ -0,0 +1,23 @@
1
+ id: irpf-ahorro-2026-017
2
+ title: Base muy grande dentro del rango seguro (1.000 millones de euros)
3
+ ejercicio: 2026
4
+ estado: verde
5
+ normativa:
6
+ - ref: "Ley 35/2006 IRPF, art. 66.1 (gravamen estatal base del ahorro)"
7
+ - ref: "Ley 35/2006 IRPF, art. 76 (gravamen autonómico base del ahorro)"
8
+ verificado: "2026-10-25"
9
+ input:
10
+ ganancias_patrimoniales: "1000000000.00"
11
+ perdidas_patrimoniales: "0.00"
12
+ rendimientos_capital_mobiliario: "0.00"
13
+ expected:
14
+ base_liquidable_ahorro: "1000000000.00"
15
+ cuota_estatal: "149990940.00"
16
+ cuota_autonomica: "149990940.00"
17
+ cuota_ahorro: "299981880.00"
18
+ notas: >
19
+ Estatal en céntimos·bp: tramos completos 35.940.000.000 + (100.000.000.000 − 30.000.000) × 1.500
20
+ = 149.955.000.000.000 → suma 149.990.940.000.000 (≈ 1,5 × 10¹⁴, dentro de MAX_SAFE_INTEGER)
21
+ / 10.000 = 14.999.094.000 c = 149.990.940,00. Autonómico idéntico. Total 299.981.880,00
22
+ (= 71.880 + 999.700.000 × 30 %). Comprueba que el acumulado de toda la escala no pierde precisión
23
+ ni lanza un falso RangeError por debajo del límite de ADR-002.
@@ -0,0 +1,20 @@
1
+ id: irpf-ahorro-2026-018
2
+ title: Base exactamente en el límite superior del segundo tramo (50.000,00)
3
+ ejercicio: 2026
4
+ estado: verde
5
+ normativa:
6
+ - ref: "Ley 35/2006 IRPF, art. 66.1 (gravamen estatal base del ahorro)"
7
+ - ref: "Ley 35/2006 IRPF, art. 76 (gravamen autonómico base del ahorro)"
8
+ verificado: "2026-10-25"
9
+ input:
10
+ ganancias_patrimoniales: "50000.00"
11
+ perdidas_patrimoniales: "0.00"
12
+ rendimientos_capital_mobiliario: "0.00"
13
+ expected:
14
+ base_liquidable_ahorro: "50000.00"
15
+ cuota_estatal: "5190.00"
16
+ cuota_autonomica: "5190.00"
17
+ cuota_ahorro: "10380.00"
18
+ notas: >
19
+ Límite inclusivo del tramo 2 (existe 50.000,01 en el caso 011, pero no el valor exacto). 6.000 × 9,5 % = 570,00;
20
+ 44.000 × 10,5 % = 4.620,00; estatal 5.190,00. Autonómico idéntico. Total 10.380,00.
@@ -0,0 +1,21 @@
1
+ id: irpf-ahorro-2026-019
2
+ title: Base exactamente en el límite superior del cuarto tramo (300.000,00)
3
+ ejercicio: 2026
4
+ estado: verde
5
+ normativa:
6
+ - ref: "Ley 35/2006 IRPF, art. 66.1 (gravamen estatal base del ahorro)"
7
+ - ref: "Ley 35/2006 IRPF, art. 76 (gravamen autonómico base del ahorro)"
8
+ verificado: "2026-10-25"
9
+ input:
10
+ ganancias_patrimoniales: "300000.00"
11
+ perdidas_patrimoniales: "0.00"
12
+ rendimientos_capital_mobiliario: "0.00"
13
+ expected:
14
+ base_liquidable_ahorro: "300000.00"
15
+ cuota_estatal: "35940.00"
16
+ cuota_autonomica: "35940.00"
17
+ cuota_ahorro: "71880.00"
18
+ notas: >
19
+ Límite inclusivo del tramo 4 (existe 300.000,01 en el caso 012, pero no el valor exacto). 570,00 + 4.620,00 +
20
+ 150.000 × 11,5 % = 17.250,00 + 100.000 × 13,5 % = 13.500,00 → estatal 35.940,00. Autonómico idéntico.
21
+ Total 71.880,00.
@@ -0,0 +1,21 @@
1
+ id: irpf-ahorro-2026-020
2
+ title: Ganancia patrimonial de 6.341,25 que cruza el primer tramo (fixture del vehículo de acumulación)
3
+ ejercicio: 2026
4
+ estado: verde
5
+ normativa:
6
+ - ref: "Ley 35/2006 IRPF, art. 66.1 (gravamen estatal base del ahorro)"
7
+ - ref: "Ley 35/2006 IRPF, art. 76 (gravamen autonómico base del ahorro)"
8
+ verificado: "2026-10-25"
9
+ input:
10
+ ganancias_patrimoniales: "6341.25"
11
+ perdidas_patrimoniales: "0.00"
12
+ rendimientos_capital_mobiliario: "0.00"
13
+ expected:
14
+ base_liquidable_ahorro: "6341.25"
15
+ cuota_estatal: "605.83"
16
+ cuota_autonomica: "605.83"
17
+ cuota_ahorro: "1211.66"
18
+ notas: >
19
+ Estatal en céntimos·bp: 600.000 × 950 = 570.000.000; 34.125 × 1.050 = 35.831.250; suma 605.831.250
20
+ / 10.000 = 60.583,125 c → 60.583 c = 605,83. Autonómico idéntico. Total 1.211,66.
21
+ Es la ganancia de 50.000,00 a 12 meses al 1 % mensual con redondeo mensual (apps/web acumulacion.test.ts).