@jjlmoya/utils-finance 1.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 (133) hide show
  1. package/package.json +64 -0
  2. package/src/category/i18n/en.ts +159 -0
  3. package/src/category/i18n/es.ts +159 -0
  4. package/src/category/i18n/fr.ts +159 -0
  5. package/src/category/index.ts +24 -0
  6. package/src/category/seo.astro +15 -0
  7. package/src/components/PreviewNavSidebar.astro +116 -0
  8. package/src/components/PreviewToolbar.astro +143 -0
  9. package/src/data.ts +11 -0
  10. package/src/env.d.ts +5 -0
  11. package/src/index.ts +32 -0
  12. package/src/layouts/PreviewLayout.astro +117 -0
  13. package/src/pages/[locale]/[slug].astro +146 -0
  14. package/src/pages/[locale].astro +251 -0
  15. package/src/pages/index.astro +4 -0
  16. package/src/tests/faq_count.test.ts +19 -0
  17. package/src/tests/locale_completeness.test.ts +39 -0
  18. package/src/tests/mocks/astro_mock.js +2 -0
  19. package/src/tests/no_h1_in_components.test.ts +48 -0
  20. package/src/tests/schemas_fulfillment.test.ts +23 -0
  21. package/src/tests/seo_length.test.ts +23 -0
  22. package/src/tests/title_quality.test.ts +55 -0
  23. package/src/tests/tool_validation.test.ts +17 -0
  24. package/src/tool/compoundInterest/bibliography.astro +14 -0
  25. package/src/tool/compoundInterest/component.astro +697 -0
  26. package/src/tool/compoundInterest/i18n/en.ts +212 -0
  27. package/src/tool/compoundInterest/i18n/es.ts +212 -0
  28. package/src/tool/compoundInterest/i18n/fr.ts +212 -0
  29. package/src/tool/compoundInterest/index.ts +34 -0
  30. package/src/tool/compoundInterest/logic.ts +40 -0
  31. package/src/tool/compoundInterest/seo.astro +14 -0
  32. package/src/tool/compoundInterest/ui.ts +19 -0
  33. package/src/tool/courtFeeCalculator/bibliography.astro +14 -0
  34. package/src/tool/courtFeeCalculator/component.astro +427 -0
  35. package/src/tool/courtFeeCalculator/i18n/en.ts +268 -0
  36. package/src/tool/courtFeeCalculator/i18n/es.ts +268 -0
  37. package/src/tool/courtFeeCalculator/i18n/fr.ts +268 -0
  38. package/src/tool/courtFeeCalculator/index.ts +33 -0
  39. package/src/tool/courtFeeCalculator/logic.ts +86 -0
  40. package/src/tool/courtFeeCalculator/seo.astro +14 -0
  41. package/src/tool/courtFeeCalculator/ui.ts +31 -0
  42. package/src/tool/debtSnowball/bibliography.astro +14 -0
  43. package/src/tool/debtSnowball/component.astro +1052 -0
  44. package/src/tool/debtSnowball/i18n/en.ts +288 -0
  45. package/src/tool/debtSnowball/i18n/es.ts +288 -0
  46. package/src/tool/debtSnowball/i18n/fr.ts +288 -0
  47. package/src/tool/debtSnowball/index.ts +37 -0
  48. package/src/tool/debtSnowball/logic.ts +156 -0
  49. package/src/tool/debtSnowball/seo.astro +14 -0
  50. package/src/tool/debtSnowball/ui.ts +49 -0
  51. package/src/tool/fireCalculator/bibliography.astro +14 -0
  52. package/src/tool/fireCalculator/component.astro +689 -0
  53. package/src/tool/fireCalculator/i18n/en.ts +261 -0
  54. package/src/tool/fireCalculator/i18n/es.ts +261 -0
  55. package/src/tool/fireCalculator/i18n/fr.ts +261 -0
  56. package/src/tool/fireCalculator/index.ts +33 -0
  57. package/src/tool/fireCalculator/logic.ts +108 -0
  58. package/src/tool/fireCalculator/seo.astro +14 -0
  59. package/src/tool/fireCalculator/ui.ts +33 -0
  60. package/src/tool/ibanBicSwiftConverter/bibliography.astro +14 -0
  61. package/src/tool/ibanBicSwiftConverter/component.astro +581 -0
  62. package/src/tool/ibanBicSwiftConverter/i18n/en.ts +167 -0
  63. package/src/tool/ibanBicSwiftConverter/i18n/es.ts +216 -0
  64. package/src/tool/ibanBicSwiftConverter/i18n/fr.ts +167 -0
  65. package/src/tool/ibanBicSwiftConverter/index.ts +33 -0
  66. package/src/tool/ibanBicSwiftConverter/logic.ts +101 -0
  67. package/src/tool/ibanBicSwiftConverter/seo.astro +14 -0
  68. package/src/tool/ibanBicSwiftConverter/ui.ts +17 -0
  69. package/src/tool/inflation/bibliography.astro +14 -0
  70. package/src/tool/inflation/component.astro +400 -0
  71. package/src/tool/inflation/i18n/en.ts +301 -0
  72. package/src/tool/inflation/i18n/es.ts +301 -0
  73. package/src/tool/inflation/i18n/fr.ts +301 -0
  74. package/src/tool/inflation/index.ts +34 -0
  75. package/src/tool/inflation/logic.ts +92 -0
  76. package/src/tool/inflation/seo.astro +14 -0
  77. package/src/tool/inflation/ui.ts +17 -0
  78. package/src/tool/lateInterest/bibliography.astro +14 -0
  79. package/src/tool/lateInterest/component.astro +655 -0
  80. package/src/tool/lateInterest/i18n/en.ts +255 -0
  81. package/src/tool/lateInterest/i18n/es.ts +224 -0
  82. package/src/tool/lateInterest/i18n/fr.ts +224 -0
  83. package/src/tool/lateInterest/index.ts +34 -0
  84. package/src/tool/lateInterest/logic.ts +89 -0
  85. package/src/tool/lateInterest/seo.astro +14 -0
  86. package/src/tool/lateInterest/ui.ts +33 -0
  87. package/src/tool/legalInterestRate/bibliography.astro +14 -0
  88. package/src/tool/legalInterestRate/component.astro +650 -0
  89. package/src/tool/legalInterestRate/i18n/en.ts +159 -0
  90. package/src/tool/legalInterestRate/i18n/es.ts +159 -0
  91. package/src/tool/legalInterestRate/i18n/fr.ts +159 -0
  92. package/src/tool/legalInterestRate/index.ts +33 -0
  93. package/src/tool/legalInterestRate/logic.ts +29 -0
  94. package/src/tool/legalInterestRate/seo.astro +14 -0
  95. package/src/tool/legalInterestRate/ui.ts +23 -0
  96. package/src/tool/lotteryOptimizer/bibliography.astro +14 -0
  97. package/src/tool/lotteryOptimizer/component.astro +863 -0
  98. package/src/tool/lotteryOptimizer/i18n/en.ts +188 -0
  99. package/src/tool/lotteryOptimizer/i18n/es.ts +281 -0
  100. package/src/tool/lotteryOptimizer/i18n/fr.ts +188 -0
  101. package/src/tool/lotteryOptimizer/index.ts +33 -0
  102. package/src/tool/lotteryOptimizer/logic.ts +133 -0
  103. package/src/tool/lotteryOptimizer/seo.astro +14 -0
  104. package/src/tool/lotteryOptimizer/ui.ts +33 -0
  105. package/src/tool/mortgage/bibliography.astro +14 -0
  106. package/src/tool/mortgage/component.astro +1030 -0
  107. package/src/tool/mortgage/i18n/en.ts +227 -0
  108. package/src/tool/mortgage/i18n/es.ts +227 -0
  109. package/src/tool/mortgage/i18n/fr.ts +227 -0
  110. package/src/tool/mortgage/index.ts +34 -0
  111. package/src/tool/mortgage/logic.ts +118 -0
  112. package/src/tool/mortgage/seo.astro +14 -0
  113. package/src/tool/mortgage/ui.ts +35 -0
  114. package/src/tool/percentageCalculator/bibliography.astro +14 -0
  115. package/src/tool/percentageCalculator/component.astro +640 -0
  116. package/src/tool/percentageCalculator/i18n/en.ts +233 -0
  117. package/src/tool/percentageCalculator/i18n/es.ts +233 -0
  118. package/src/tool/percentageCalculator/i18n/fr.ts +233 -0
  119. package/src/tool/percentageCalculator/index.ts +34 -0
  120. package/src/tool/percentageCalculator/logic.ts +48 -0
  121. package/src/tool/percentageCalculator/seo.astro +14 -0
  122. package/src/tool/percentageCalculator/ui.ts +30 -0
  123. package/src/tool/rentIncreaseCalculator/bibliography.astro +14 -0
  124. package/src/tool/rentIncreaseCalculator/component.astro +587 -0
  125. package/src/tool/rentIncreaseCalculator/i18n/en.ts +159 -0
  126. package/src/tool/rentIncreaseCalculator/i18n/es.ts +220 -0
  127. package/src/tool/rentIncreaseCalculator/i18n/fr.ts +159 -0
  128. package/src/tool/rentIncreaseCalculator/index.ts +33 -0
  129. package/src/tool/rentIncreaseCalculator/logic.ts +43 -0
  130. package/src/tool/rentIncreaseCalculator/seo.astro +14 -0
  131. package/src/tool/rentIncreaseCalculator/ui.ts +19 -0
  132. package/src/tools.ts +16 -0
  133. package/src/types.ts +74 -0
@@ -0,0 +1,655 @@
1
+ ---
2
+ import type { KnownLocale } from '../../types';
3
+
4
+ interface Props {
5
+ locale?: KnownLocale;
6
+ ui?: Record<string, unknown>;
7
+ }
8
+
9
+ const { ui } = Astro.props;
10
+ ---
11
+
12
+ <div
13
+ class="li-container"
14
+ data-currency-symbol={ui.currencySymbol}
15
+ data-currency-code={ui.currencyCode || 'EUR'}
16
+ data-currency-locale={ui.currencyLocale || 'es-ES'}
17
+ data-report-title={ui.reportTitle}
18
+ data-report-label-principal={ui.reportLabelPrincipal}
19
+ data-report-label-type={ui.reportLabelType}
20
+ data-report-label-period={ui.reportLabelPeriod}
21
+ data-report-label-days={ui.reportLabelDays}
22
+ data-report-label-interest={ui.reportLabelInterest}
23
+ data-report-label-total={ui.reportLabelTotal}
24
+ data-report-days-suffix={ui.reportDaysSuffix}
25
+ >
26
+ <div class="li-card fade-in">
27
+ <div class="li-input-grid">
28
+ <div class="li-input-group">
29
+ <label for="principal-amount">{ui.labelPrincipal}</label>
30
+ <div class="li-input-wrapper">
31
+ <input
32
+ type="number"
33
+ id="principal-amount"
34
+ placeholder="Ej. 1000"
35
+ min="0"
36
+ value="1000"
37
+ step="0.01"
38
+ />
39
+ <span class="li-input-unit">{ui.currencySymbol}</span>
40
+ </div>
41
+ </div>
42
+
43
+ <div class="li-input-group">
44
+ <label for="interest-type">{ui.labelAnnualRate}</label>
45
+ <div class="li-input-wrapper">
46
+ <select id="interest-type">
47
+ <option value="legal">{ui.labelSimpleTitle}</option>
48
+ <option value="demora" selected>{ui.labelCompoundTitle}</option>
49
+ </select>
50
+ </div>
51
+ </div>
52
+
53
+ <div class="li-input-group">
54
+ <label for="start-date">{ui.labelDays}</label>
55
+ <div class="li-input-wrapper">
56
+ <input type="date" id="start-date" value="2024-01-01" />
57
+ </div>
58
+ </div>
59
+
60
+ <div class="li-input-group">
61
+ <label for="end-date">{ui.labelMonths}</label>
62
+ <div class="li-input-wrapper">
63
+ <input type="date" id="end-date" value="2025-01-01" />
64
+ </div>
65
+ </div>
66
+ </div>
67
+
68
+ <div class="li-results-display" id="interest-results">
69
+ <div class="li-total-interest-box">
70
+ <span class="li-total-label">{ui.labelTotalInterest}</span>
71
+ <span class="li-total-value" id="val-total-interest">0,00 {ui.currencySymbol}</span>
72
+ </div>
73
+
74
+ <div class="li-breakdown-grid">
75
+ <div class="li-sub-result-card">
76
+ <span class="li-sub-label">{ui.labelDailyInterest}</span>
77
+ <span class="li-sub-value" id="val-days">0 días</span>
78
+ </div>
79
+ <div class="li-sub-result-card">
80
+ <span class="li-sub-label">{ui.labelEffectiveRate}</span>
81
+ <span class="li-sub-value" id="val-rate">0.00{ui.percentSymbol}</span>
82
+ </div>
83
+ <div class="li-sub-result-card">
84
+ <span class="li-sub-label">{ui.labelTotalAmount}</span>
85
+ <span class="li-sub-value" id="val-final-total">0,00 {ui.currencySymbol}</span>
86
+ </div>
87
+ </div>
88
+
89
+ <div class="li-info-banner">
90
+ <p>
91
+ <strong>{ui.labelFormula}:</strong> {ui.formulaDescription}
92
+ </p>
93
+ </div>
94
+ </div>
95
+
96
+ <div class="action-row">
97
+ <button class="li-btn-primary" id="copy-report">
98
+ {ui.labelCopyTooltip}
99
+ </button>
100
+ </div>
101
+ </div>
102
+ </div>
103
+
104
+ <style>
105
+ :root {
106
+ --li-bg-card: #fff;
107
+ --li-bg-card-hover: #f9fafb;
108
+ --li-border-card: #e5e7eb;
109
+ --li-text-primary: #1f2937;
110
+ --li-text-secondary: #6b7280;
111
+ --li-text-input: #1f2937;
112
+ --li-bg-input: #fff;
113
+ --li-border-input: #d1d5db;
114
+ --li-focus-ring: rgba(59, 130, 246, 0.1);
115
+ --li-focus-border: #3b82f6;
116
+ --li-unit-color: #3b82f6;
117
+ --li-bg-result: #f3f4f6;
118
+ --li-border-result: #d1d5db;
119
+ --li-label-result: #6b7280;
120
+ --li-value-result: #1f2937;
121
+ --li-bg-total-gradient-1: #dbeafe;
122
+ --li-bg-total-gradient-2: #bfdbfe;
123
+ --li-border-total: #3b82f6;
124
+ --li-label-total: #1e40af;
125
+ --li-value-total: #1e3a8a;
126
+ --li-bg-banner: #fef3c7;
127
+ --li-border-banner: #f59e0b;
128
+ --li-text-banner: #92400e;
129
+ --li-bg-button: #3b82f6;
130
+ --li-bg-button-hover: #2563eb;
131
+ --li-text-button: #fff;
132
+ }
133
+
134
+ :global(.theme-dark) {
135
+ --li-bg-card: #1f2937;
136
+ --li-border-card: #374151;
137
+ --li-text-primary: #f3f4f6;
138
+ --li-text-secondary: #d1d5db;
139
+ --li-text-input: #f3f4f6;
140
+ --li-bg-input: #111827;
141
+ --li-border-input: #4b5563;
142
+ --li-focus-ring: rgba(59, 130, 246, 0.1);
143
+ --li-focus-border: #3b82f6;
144
+ --li-unit-color: #60a5fa;
145
+ --li-bg-result: #111827;
146
+ --li-border-result: #374151;
147
+ --li-label-result: #d1d5db;
148
+ --li-value-result: #f3f4f6;
149
+ --li-bg-total-gradient-1: #1e3a8a;
150
+ --li-bg-total-gradient-2: #1e40af;
151
+ --li-border-total: #3b82f6;
152
+ --li-label-total: #93c5fd;
153
+ --li-value-total: #dbeafe;
154
+ --li-bg-banner: #78350f;
155
+ --li-border-banner: #f59e0b;
156
+ --li-text-banner: #fef3c7;
157
+ --li-bg-button: #3b82f6;
158
+ --li-bg-button-hover: #1e40af;
159
+ --li-text-button: #fff;
160
+ }
161
+
162
+ :global([data-theme='dark']) {
163
+ --li-bg-card: #1f2937;
164
+ --li-border-card: #374151;
165
+ --li-text-primary: #f3f4f6;
166
+ --li-text-secondary: #d1d5db;
167
+ --li-text-input: #f3f4f6;
168
+ --li-bg-input: #111827;
169
+ --li-border-input: #4b5563;
170
+ --li-focus-ring: rgba(59, 130, 246, 0.1);
171
+ --li-focus-border: #3b82f6;
172
+ --li-unit-color: #60a5fa;
173
+ --li-bg-result: #111827;
174
+ --li-border-result: #374151;
175
+ --li-label-result: #d1d5db;
176
+ --li-value-result: #f3f4f6;
177
+ --li-bg-total-gradient-1: #1e3a8a;
178
+ --li-bg-total-gradient-2: #1e40af;
179
+ --li-border-total: #3b82f6;
180
+ --li-label-total: #93c5fd;
181
+ --li-value-total: #dbeafe;
182
+ --li-bg-banner: #78350f;
183
+ --li-border-banner: #f59e0b;
184
+ --li-text-banner: #fef3c7;
185
+ --li-bg-button: #3b82f6;
186
+ --li-bg-button-hover: #1e40af;
187
+ --li-text-button: #fff;
188
+ }
189
+
190
+ @media (prefers-color-scheme: dark) {
191
+ :root {
192
+ --li-bg-card: #1f2937;
193
+ --li-border-card: #374151;
194
+ --li-text-primary: #f3f4f6;
195
+ --li-text-secondary: #d1d5db;
196
+ --li-text-input: #f3f4f6;
197
+ --li-bg-input: #111827;
198
+ --li-border-input: #4b5563;
199
+ --li-focus-ring: rgba(59, 130, 246, 0.1);
200
+ --li-focus-border: #3b82f6;
201
+ --li-unit-color: #60a5fa;
202
+ --li-bg-result: #111827;
203
+ --li-border-result: #374151;
204
+ --li-label-result: #d1d5db;
205
+ --li-value-result: #f3f4f6;
206
+ --li-bg-total-gradient-1: #1e3a8a;
207
+ --li-bg-total-gradient-2: #1e40af;
208
+ --li-border-total: #3b82f6;
209
+ --li-label-total: #93c5fd;
210
+ --li-value-total: #dbeafe;
211
+ --li-bg-banner: #78350f;
212
+ --li-border-banner: #f59e0b;
213
+ --li-text-banner: #fef3c7;
214
+ --li-bg-button: #3b82f6;
215
+ --li-bg-button-hover: #1e40af;
216
+ --li-text-button: #fff;
217
+ }
218
+ }
219
+
220
+ .li-container {
221
+ display: flex;
222
+ justify-content: center;
223
+ width: 100%;
224
+ padding: 2rem 1rem;
225
+ }
226
+
227
+ .li-card {
228
+ background: var(--li-bg-card);
229
+ border: 1px solid var(--li-border-card);
230
+ border-radius: 12px;
231
+ padding: 2rem;
232
+ width: 100%;
233
+ max-width: 600px;
234
+ color: var(--li-text-primary);
235
+ }
236
+
237
+ .li-input-grid {
238
+ display: grid;
239
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
240
+ gap: 1.5rem;
241
+ margin-bottom: 2rem;
242
+ }
243
+
244
+ .li-input-group {
245
+ display: flex;
246
+ flex-direction: column;
247
+ }
248
+
249
+ .li-input-group label {
250
+ font-weight: 600;
251
+ margin-bottom: 0.5rem;
252
+ font-size: 0.9rem;
253
+ color: var(--li-text-primary);
254
+ }
255
+
256
+ .li-input-wrapper {
257
+ position: relative;
258
+ display: flex;
259
+ align-items: center;
260
+ }
261
+
262
+ .li-input-wrapper input,
263
+ .li-input-wrapper select {
264
+ width: 100%;
265
+ padding: 0.75rem 1rem;
266
+ border: 1px solid var(--li-border-input);
267
+ border-radius: 8px;
268
+ font-size: 1rem;
269
+ background: var(--li-bg-input);
270
+ color: var(--li-text-input);
271
+ color-scheme: light;
272
+ }
273
+
274
+ :global(.theme-dark) .li-input-wrapper input,
275
+ :global(.theme-dark) .li-input-wrapper select,
276
+ :global([data-theme='dark']) .li-input-wrapper input,
277
+ :global([data-theme='dark']) .li-input-wrapper select {
278
+ color-scheme: dark;
279
+ }
280
+
281
+ .li-input-wrapper select {
282
+ padding-right: 2.5rem;
283
+ appearance: none;
284
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath fill='%233b82f6' d='M1 1l5 5 5-5'/%3E%3C/svg%3E");
285
+ background-repeat: no-repeat;
286
+ background-position: right 0.75rem center;
287
+ background-size: 12px 8px;
288
+ }
289
+
290
+ :global(.theme-dark) .li-input-wrapper select,
291
+ :global([data-theme='dark']) .li-input-wrapper select {
292
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath fill='%2360a5fa' d='M1 1l5 5 5-5'/%3E%3C/svg%3E");
293
+ }
294
+
295
+ .li-input-wrapper input:focus,
296
+ .li-input-wrapper select:focus {
297
+ outline: none;
298
+ border-color: var(--li-focus-border);
299
+ box-shadow: 0 0 0 3px var(--li-focus-ring);
300
+ }
301
+
302
+ .li-input-unit {
303
+ position: absolute;
304
+ right: 1rem;
305
+ font-weight: 600;
306
+ color: var(--li-unit-color);
307
+ pointer-events: none;
308
+ }
309
+
310
+ .li-results-display {
311
+ margin-bottom: 2rem;
312
+ }
313
+
314
+ .li-total-interest-box {
315
+ background: linear-gradient(135deg, var(--li-bg-total-gradient-1), var(--li-bg-total-gradient-2));
316
+ border: 2px solid var(--li-border-total);
317
+ border-radius: 12px;
318
+ padding: 1.5rem;
319
+ text-align: center;
320
+ margin-bottom: 1.5rem;
321
+ }
322
+
323
+ .li-total-label {
324
+ display: block;
325
+ font-size: 0.9rem;
326
+ font-weight: 600;
327
+ color: var(--li-label-total);
328
+ margin-bottom: 0.5rem;
329
+ }
330
+
331
+ .li-total-value {
332
+ display: block;
333
+ font-size: 2rem;
334
+ font-weight: 800;
335
+ color: var(--li-value-total);
336
+ }
337
+
338
+ .li-breakdown-grid {
339
+ display: grid;
340
+ grid-template-columns: repeat(3, 1fr);
341
+ gap: 1rem;
342
+ margin-bottom: 1.5rem;
343
+ }
344
+
345
+ .li-sub-result-card {
346
+ background: var(--li-bg-result);
347
+ border: 1px solid var(--li-border-result);
348
+ border-radius: 8px;
349
+ padding: 1rem;
350
+ text-align: center;
351
+ }
352
+
353
+ .li-sub-label {
354
+ display: block;
355
+ font-size: 0.8rem;
356
+ font-weight: 600;
357
+ color: var(--li-label-result);
358
+ margin-bottom: 0.5rem;
359
+ }
360
+
361
+ .li-sub-value {
362
+ display: block;
363
+ font-size: 1.3rem;
364
+ font-weight: 700;
365
+ color: var(--li-value-result);
366
+ }
367
+
368
+ .li-info-banner {
369
+ background: var(--li-bg-banner);
370
+ border-left: 4px solid var(--li-border-banner);
371
+ border-radius: 6px;
372
+ padding: 1rem;
373
+ margin-bottom: 1.5rem;
374
+ }
375
+
376
+ .li-info-banner p {
377
+ margin: 0;
378
+ font-size: 0.9rem;
379
+ color: var(--li-text-banner);
380
+ }
381
+
382
+ .action-row {
383
+ display: flex;
384
+ gap: 1rem;
385
+ }
386
+
387
+ .li-btn-primary {
388
+ flex: 1;
389
+ padding: 0.75rem 1.5rem;
390
+ background: var(--li-bg-button);
391
+ color: var(--li-text-button);
392
+ border: none;
393
+ border-radius: 8px;
394
+ font-weight: 600;
395
+ cursor: pointer;
396
+ transition: all 0.2s;
397
+ }
398
+
399
+ .li-btn-primary:hover {
400
+ background: var(--li-bg-button-hover);
401
+ transform: translateY(-2px);
402
+ }
403
+
404
+ .li-btn-primary:active {
405
+ transform: translateY(0);
406
+ }
407
+
408
+ .fade-in {
409
+ animation: fade-in-up 0.4s ease-out;
410
+ }
411
+
412
+ @keyframes fade-in-up {
413
+ from {
414
+ opacity: 0;
415
+ transform: translateY(20px);
416
+ }
417
+ to {
418
+ opacity: 1;
419
+ transform: translateY(0);
420
+ }
421
+ }
422
+
423
+ @media (prefers-color-scheme: dark) {
424
+ .li-card {
425
+ background: #1f2937;
426
+ border-color: #374151;
427
+ color: #f3f4f6;
428
+ }
429
+
430
+ .li-input-group label {
431
+ color: #d1d5db;
432
+ }
433
+
434
+ .li-input-wrapper input,
435
+ .li-input-wrapper select {
436
+ background: #111827;
437
+ border-color: #4b5563;
438
+ color: #f3f4f6;
439
+ }
440
+
441
+ .li-input-unit {
442
+ color: #60a5fa;
443
+ }
444
+
445
+ .li-sub-result-card {
446
+ background: #111827;
447
+ border-color: #374151;
448
+ }
449
+
450
+ .li-sub-label {
451
+ color: #d1d5db;
452
+ }
453
+
454
+ .li-sub-value {
455
+ color: #f3f4f6;
456
+ }
457
+
458
+ .li-info-banner {
459
+ background: #78350f;
460
+ color: #fef3c7;
461
+ }
462
+
463
+ .li-info-banner p {
464
+ color: #fef3c7;
465
+ }
466
+
467
+ .li-total-interest-box {
468
+ background: linear-gradient(135deg, #1e3a8a, #1e40af);
469
+ border-color: #3b82f6;
470
+ }
471
+
472
+ .li-total-label {
473
+ color: #93c5fd;
474
+ }
475
+
476
+ .li-total-value {
477
+ color: #dbeafe;
478
+ }
479
+
480
+ .li-btn-primary {
481
+ background: #3b82f6;
482
+ color: #fff;
483
+ }
484
+
485
+ .li-btn-primary:hover {
486
+ background: #1e40af;
487
+ }
488
+ }
489
+
490
+ @media (max-width: 640px) {
491
+ .li-input-grid {
492
+ grid-template-columns: 1fr;
493
+ }
494
+
495
+ .li-breakdown-grid {
496
+ grid-template-columns: 1fr;
497
+ }
498
+
499
+ .li-total-value {
500
+ font-size: 1.5rem;
501
+ }
502
+ }
503
+ </style>
504
+
505
+ <script>
506
+ const principalInput = document.getElementById("principal-amount") as HTMLInputElement | null;
507
+ const typeSelect = document.getElementById("interest-type") as HTMLSelectElement | null;
508
+ const startInput = document.getElementById("start-date") as HTMLInputElement | null;
509
+ const endInput = document.getElementById("end-date") as HTMLInputElement | null;
510
+
511
+ const totalInterestDisplay = document.getElementById("val-total-interest");
512
+ const daysDisplay = document.getElementById("val-days");
513
+ const rateDisplay = document.getElementById("val-rate");
514
+ const finalTotalDisplay = document.getElementById("val-final-total");
515
+ const copyBtn = document.getElementById("copy-report");
516
+ const container = document.querySelector(".li-container");
517
+
518
+ const RATE_LEGAL = 3.25;
519
+ const RATE_DEMORA = 4.0625;
520
+
521
+ function getCurrencyLocale(): string {
522
+ return container?.getAttribute("data-currency-locale") || "es-ES";
523
+ }
524
+
525
+ function getCurrencyCode(): string {
526
+ return container?.getAttribute("data-currency-code") || "EUR";
527
+ }
528
+
529
+ function formatCurrency(val: number): string {
530
+ return new Intl.NumberFormat(getCurrencyLocale(), {
531
+ style: "currency",
532
+ currency: getCurrencyCode(),
533
+ }).format(val);
534
+ }
535
+
536
+ function getReportLabel(key: string): string {
537
+ return container?.getAttribute(`data-report-${key}`) || "";
538
+ }
539
+
540
+ function getCurrencySymbol(): string {
541
+ return container?.getAttribute("data-currency-symbol") || "€";
542
+ }
543
+
544
+ function getReportLabels() {
545
+ return {
546
+ title: getReportLabel("title"),
547
+ principal: getReportLabel("label-principal"),
548
+ type: getReportLabel("label-type"),
549
+ period: getReportLabel("label-period"),
550
+ days: getReportLabel("label-days"),
551
+ interest: getReportLabel("label-interest"),
552
+ total: getReportLabel("label-total"),
553
+ };
554
+ }
555
+
556
+ function updateElement(el: Element | null, text: string): void {
557
+ if (el) el.textContent = text;
558
+ }
559
+
560
+ function displayInvalidDates(principal: number): void {
561
+ const symbol = getCurrencySymbol();
562
+ updateElement(totalInterestDisplay, `0,00 ${symbol}`);
563
+ updateElement(daysDisplay, "0 días");
564
+ updateElement(finalTotalDisplay, formatCurrency(principal));
565
+ }
566
+
567
+ function displayResults(interest: number, diffDays: number, rate: number, principal: number): void {
568
+ const suffix = getReportLabel("days-suffix");
569
+ updateElement(totalInterestDisplay, formatCurrency(interest));
570
+ updateElement(daysDisplay, `${diffDays} ${suffix}`);
571
+ updateElement(rateDisplay, `${rate}%`);
572
+ updateElement(finalTotalDisplay, formatCurrency(principal + interest));
573
+ }
574
+
575
+ function areInputsValid(): boolean {
576
+ return !!(principalInput && typeSelect && startInput && endInput);
577
+ }
578
+
579
+ function areDatesValid(start: Date, end: Date): boolean {
580
+ return !isNaN(start.getTime()) && !isNaN(end.getTime()) && end >= start;
581
+ }
582
+
583
+ function calculate() {
584
+ if (!areInputsValid()) return;
585
+
586
+ const principal = parseFloat(principalInput!.value) || 0;
587
+ const type = typeSelect!.value;
588
+ const rate = type === "legal" ? RATE_LEGAL : RATE_DEMORA;
589
+
590
+ const start = new Date(startInput!.value);
591
+ const end = new Date(endInput!.value);
592
+
593
+ if (!areDatesValid(start, end)) {
594
+ displayInvalidDates(principal);
595
+ return;
596
+ }
597
+
598
+ const diffTime = Math.abs(end.getTime() - start.getTime());
599
+ const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
600
+ const interest = (principal * diffDays * (rate / 100)) / 365;
601
+
602
+ displayResults(interest, diffDays, rate, principal);
603
+ }
604
+
605
+ function getPrincipalForReport(): string {
606
+ return formatCurrency(parseFloat(principalInput?.value || "0"));
607
+ }
608
+
609
+ function getTypeForReport(): string {
610
+ return typeSelect?.options[typeSelect.selectedIndex].text || "";
611
+ }
612
+
613
+ function getPeriodForReport(): string {
614
+ return `${startInput?.value} hasta ${endInput?.value}`;
615
+ }
616
+
617
+ function buildReport(): string {
618
+ const labels = getReportLabels();
619
+ const separator = "-".repeat(labels.title.length);
620
+
621
+ return `
622
+ ${labels.title}
623
+ ${separator}
624
+ ${labels.principal}: ${getPrincipalForReport()}
625
+ ${labels.type}: ${getTypeForReport()}
626
+ ${labels.period}: ${getPeriodForReport()}
627
+ ${labels.days}: ${daysDisplay?.textContent}
628
+ ${separator}
629
+ ${labels.interest}: ${totalInterestDisplay?.textContent}
630
+ ${labels.total}: ${finalTotalDisplay?.textContent}
631
+ `.trim();
632
+ }
633
+
634
+ function handleCopyClick(): void {
635
+ const report = buildReport();
636
+ navigator.clipboard.writeText(report);
637
+
638
+ if (copyBtn) {
639
+ const originalHtml = copyBtn.innerHTML;
640
+ copyBtn.innerHTML = '¡Copiado!';
641
+ setTimeout(() => {
642
+ copyBtn.innerHTML = originalHtml;
643
+ }, 2000);
644
+ }
645
+ }
646
+
647
+ [principalInput, typeSelect, startInput, endInput].forEach((el) => {
648
+ el?.addEventListener("input", calculate);
649
+ el?.addEventListener("change", calculate);
650
+ });
651
+
652
+ copyBtn?.addEventListener("click", handleCopyClick);
653
+
654
+ calculate();
655
+ </script>