@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.
- package/package.json +64 -0
- package/src/category/i18n/en.ts +159 -0
- package/src/category/i18n/es.ts +159 -0
- package/src/category/i18n/fr.ts +159 -0
- package/src/category/index.ts +24 -0
- package/src/category/seo.astro +15 -0
- package/src/components/PreviewNavSidebar.astro +116 -0
- package/src/components/PreviewToolbar.astro +143 -0
- package/src/data.ts +11 -0
- package/src/env.d.ts +5 -0
- package/src/index.ts +32 -0
- package/src/layouts/PreviewLayout.astro +117 -0
- package/src/pages/[locale]/[slug].astro +146 -0
- package/src/pages/[locale].astro +251 -0
- package/src/pages/index.astro +4 -0
- package/src/tests/faq_count.test.ts +19 -0
- package/src/tests/locale_completeness.test.ts +39 -0
- package/src/tests/mocks/astro_mock.js +2 -0
- package/src/tests/no_h1_in_components.test.ts +48 -0
- package/src/tests/schemas_fulfillment.test.ts +23 -0
- package/src/tests/seo_length.test.ts +23 -0
- package/src/tests/title_quality.test.ts +55 -0
- package/src/tests/tool_validation.test.ts +17 -0
- package/src/tool/compoundInterest/bibliography.astro +14 -0
- package/src/tool/compoundInterest/component.astro +697 -0
- package/src/tool/compoundInterest/i18n/en.ts +212 -0
- package/src/tool/compoundInterest/i18n/es.ts +212 -0
- package/src/tool/compoundInterest/i18n/fr.ts +212 -0
- package/src/tool/compoundInterest/index.ts +34 -0
- package/src/tool/compoundInterest/logic.ts +40 -0
- package/src/tool/compoundInterest/seo.astro +14 -0
- package/src/tool/compoundInterest/ui.ts +19 -0
- package/src/tool/courtFeeCalculator/bibliography.astro +14 -0
- package/src/tool/courtFeeCalculator/component.astro +427 -0
- package/src/tool/courtFeeCalculator/i18n/en.ts +268 -0
- package/src/tool/courtFeeCalculator/i18n/es.ts +268 -0
- package/src/tool/courtFeeCalculator/i18n/fr.ts +268 -0
- package/src/tool/courtFeeCalculator/index.ts +33 -0
- package/src/tool/courtFeeCalculator/logic.ts +86 -0
- package/src/tool/courtFeeCalculator/seo.astro +14 -0
- package/src/tool/courtFeeCalculator/ui.ts +31 -0
- package/src/tool/debtSnowball/bibliography.astro +14 -0
- package/src/tool/debtSnowball/component.astro +1052 -0
- package/src/tool/debtSnowball/i18n/en.ts +288 -0
- package/src/tool/debtSnowball/i18n/es.ts +288 -0
- package/src/tool/debtSnowball/i18n/fr.ts +288 -0
- package/src/tool/debtSnowball/index.ts +37 -0
- package/src/tool/debtSnowball/logic.ts +156 -0
- package/src/tool/debtSnowball/seo.astro +14 -0
- package/src/tool/debtSnowball/ui.ts +49 -0
- package/src/tool/fireCalculator/bibliography.astro +14 -0
- package/src/tool/fireCalculator/component.astro +689 -0
- package/src/tool/fireCalculator/i18n/en.ts +261 -0
- package/src/tool/fireCalculator/i18n/es.ts +261 -0
- package/src/tool/fireCalculator/i18n/fr.ts +261 -0
- package/src/tool/fireCalculator/index.ts +33 -0
- package/src/tool/fireCalculator/logic.ts +108 -0
- package/src/tool/fireCalculator/seo.astro +14 -0
- package/src/tool/fireCalculator/ui.ts +33 -0
- package/src/tool/ibanBicSwiftConverter/bibliography.astro +14 -0
- package/src/tool/ibanBicSwiftConverter/component.astro +581 -0
- package/src/tool/ibanBicSwiftConverter/i18n/en.ts +167 -0
- package/src/tool/ibanBicSwiftConverter/i18n/es.ts +216 -0
- package/src/tool/ibanBicSwiftConverter/i18n/fr.ts +167 -0
- package/src/tool/ibanBicSwiftConverter/index.ts +33 -0
- package/src/tool/ibanBicSwiftConverter/logic.ts +101 -0
- package/src/tool/ibanBicSwiftConverter/seo.astro +14 -0
- package/src/tool/ibanBicSwiftConverter/ui.ts +17 -0
- package/src/tool/inflation/bibliography.astro +14 -0
- package/src/tool/inflation/component.astro +400 -0
- package/src/tool/inflation/i18n/en.ts +301 -0
- package/src/tool/inflation/i18n/es.ts +301 -0
- package/src/tool/inflation/i18n/fr.ts +301 -0
- package/src/tool/inflation/index.ts +34 -0
- package/src/tool/inflation/logic.ts +92 -0
- package/src/tool/inflation/seo.astro +14 -0
- package/src/tool/inflation/ui.ts +17 -0
- package/src/tool/lateInterest/bibliography.astro +14 -0
- package/src/tool/lateInterest/component.astro +655 -0
- package/src/tool/lateInterest/i18n/en.ts +255 -0
- package/src/tool/lateInterest/i18n/es.ts +224 -0
- package/src/tool/lateInterest/i18n/fr.ts +224 -0
- package/src/tool/lateInterest/index.ts +34 -0
- package/src/tool/lateInterest/logic.ts +89 -0
- package/src/tool/lateInterest/seo.astro +14 -0
- package/src/tool/lateInterest/ui.ts +33 -0
- package/src/tool/legalInterestRate/bibliography.astro +14 -0
- package/src/tool/legalInterestRate/component.astro +650 -0
- package/src/tool/legalInterestRate/i18n/en.ts +159 -0
- package/src/tool/legalInterestRate/i18n/es.ts +159 -0
- package/src/tool/legalInterestRate/i18n/fr.ts +159 -0
- package/src/tool/legalInterestRate/index.ts +33 -0
- package/src/tool/legalInterestRate/logic.ts +29 -0
- package/src/tool/legalInterestRate/seo.astro +14 -0
- package/src/tool/legalInterestRate/ui.ts +23 -0
- package/src/tool/lotteryOptimizer/bibliography.astro +14 -0
- package/src/tool/lotteryOptimizer/component.astro +863 -0
- package/src/tool/lotteryOptimizer/i18n/en.ts +188 -0
- package/src/tool/lotteryOptimizer/i18n/es.ts +281 -0
- package/src/tool/lotteryOptimizer/i18n/fr.ts +188 -0
- package/src/tool/lotteryOptimizer/index.ts +33 -0
- package/src/tool/lotteryOptimizer/logic.ts +133 -0
- package/src/tool/lotteryOptimizer/seo.astro +14 -0
- package/src/tool/lotteryOptimizer/ui.ts +33 -0
- package/src/tool/mortgage/bibliography.astro +14 -0
- package/src/tool/mortgage/component.astro +1030 -0
- package/src/tool/mortgage/i18n/en.ts +227 -0
- package/src/tool/mortgage/i18n/es.ts +227 -0
- package/src/tool/mortgage/i18n/fr.ts +227 -0
- package/src/tool/mortgage/index.ts +34 -0
- package/src/tool/mortgage/logic.ts +118 -0
- package/src/tool/mortgage/seo.astro +14 -0
- package/src/tool/mortgage/ui.ts +35 -0
- package/src/tool/percentageCalculator/bibliography.astro +14 -0
- package/src/tool/percentageCalculator/component.astro +640 -0
- package/src/tool/percentageCalculator/i18n/en.ts +233 -0
- package/src/tool/percentageCalculator/i18n/es.ts +233 -0
- package/src/tool/percentageCalculator/i18n/fr.ts +233 -0
- package/src/tool/percentageCalculator/index.ts +34 -0
- package/src/tool/percentageCalculator/logic.ts +48 -0
- package/src/tool/percentageCalculator/seo.astro +14 -0
- package/src/tool/percentageCalculator/ui.ts +30 -0
- package/src/tool/rentIncreaseCalculator/bibliography.astro +14 -0
- package/src/tool/rentIncreaseCalculator/component.astro +587 -0
- package/src/tool/rentIncreaseCalculator/i18n/en.ts +159 -0
- package/src/tool/rentIncreaseCalculator/i18n/es.ts +220 -0
- package/src/tool/rentIncreaseCalculator/i18n/fr.ts +159 -0
- package/src/tool/rentIncreaseCalculator/index.ts +33 -0
- package/src/tool/rentIncreaseCalculator/logic.ts +43 -0
- package/src/tool/rentIncreaseCalculator/seo.astro +14 -0
- package/src/tool/rentIncreaseCalculator/ui.ts +19 -0
- package/src/tools.ts +16 -0
- package/src/types.ts +74 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { FinanceToolEntry, ToolLocaleContent, ToolDefinition } from '../../types';
|
|
2
|
+
import MortgageComponent from './component.astro';
|
|
3
|
+
import MortgageSEO from './seo.astro';
|
|
4
|
+
import MortgageBibliography from './bibliography.astro';
|
|
5
|
+
|
|
6
|
+
import type { MortgageUI } from './ui';
|
|
7
|
+
|
|
8
|
+
export type MortgageLocaleContent = ToolLocaleContent<MortgageUI>;
|
|
9
|
+
|
|
10
|
+
import { content as es } from './i18n/es';
|
|
11
|
+
import { content as en } from './i18n/en';
|
|
12
|
+
import { content as fr } from './i18n/fr';
|
|
13
|
+
|
|
14
|
+
export const mortgage: FinanceToolEntry<MortgageUI> = {
|
|
15
|
+
id: 'hipoteca',
|
|
16
|
+
icons: {
|
|
17
|
+
bg: 'mdi:home-percent',
|
|
18
|
+
fg: 'mdi:percent',
|
|
19
|
+
},
|
|
20
|
+
i18n: {
|
|
21
|
+
es: async () => es,
|
|
22
|
+
en: async () => en,
|
|
23
|
+
fr: async () => fr,
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export { MortgageComponent, MortgageSEO, MortgageBibliography };
|
|
28
|
+
|
|
29
|
+
export const MORTGAGE_TOOL: ToolDefinition = {
|
|
30
|
+
entry: mortgage,
|
|
31
|
+
Component: MortgageComponent,
|
|
32
|
+
SEOComponent: MortgageSEO,
|
|
33
|
+
BibliographyComponent: MortgageBibliography,
|
|
34
|
+
};
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
export interface MortgageInput {
|
|
2
|
+
amount: number;
|
|
3
|
+
annualInterestRate: number;
|
|
4
|
+
years: number;
|
|
5
|
+
monthlyExtraPayment?: number;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface MortgageResult {
|
|
9
|
+
monthlyPayment: number;
|
|
10
|
+
totalInterest: number;
|
|
11
|
+
totalPaid: number;
|
|
12
|
+
amortizationTable: AmortizationRow[];
|
|
13
|
+
yearsSaved: number;
|
|
14
|
+
monthsSaved: number;
|
|
15
|
+
interestSaved: number;
|
|
16
|
+
originalTotalInterest: number;
|
|
17
|
+
actualDurationMonths: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface AmortizationRow {
|
|
21
|
+
month: number;
|
|
22
|
+
payment: number;
|
|
23
|
+
interest: number;
|
|
24
|
+
principal: number;
|
|
25
|
+
extraPayment: number;
|
|
26
|
+
remainingBalance: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export class MortgageCalculator {
|
|
30
|
+
private static calculateMonthlyPayment(amount: number, monthlyRate: number, totalMonths: number): number {
|
|
31
|
+
if (monthlyRate === 0) {
|
|
32
|
+
return amount / totalMonths;
|
|
33
|
+
}
|
|
34
|
+
const power = Math.pow(1 + monthlyRate, totalMonths);
|
|
35
|
+
return (amount * (monthlyRate * power)) / (power - 1);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
private static calculateOriginalInterest(amount: number, monthlyRate: number, totalMonths: number, monthlyPaymentBase: number): number {
|
|
39
|
+
let originalTotalInterest = 0;
|
|
40
|
+
let tempBalance = amount;
|
|
41
|
+
for (let i = 0; i < totalMonths; i++) {
|
|
42
|
+
const interest = tempBalance * monthlyRate;
|
|
43
|
+
originalTotalInterest += interest;
|
|
44
|
+
tempBalance -= monthlyPaymentBase - interest;
|
|
45
|
+
}
|
|
46
|
+
return originalTotalInterest;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
private static processMonthlyPayment(balance: number, monthlyRate: number, monthlyPaymentBase: number, monthlyExtraPayment: number): { interest: number; principal: number; extra: number; newBalance: number } {
|
|
50
|
+
const interestPayment = balance * monthlyRate;
|
|
51
|
+
let principalPayment = monthlyPaymentBase - interestPayment;
|
|
52
|
+
let currentExtra = monthlyExtraPayment;
|
|
53
|
+
|
|
54
|
+
if (principalPayment + currentExtra > balance) {
|
|
55
|
+
if (principalPayment > balance) {
|
|
56
|
+
principalPayment = balance;
|
|
57
|
+
currentExtra = 0;
|
|
58
|
+
} else {
|
|
59
|
+
currentExtra = balance - principalPayment;
|
|
60
|
+
}
|
|
61
|
+
return { interest: interestPayment, principal: principalPayment, extra: currentExtra, newBalance: 0 };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const newBalance = balance - principalPayment - currentExtra;
|
|
65
|
+
return { interest: interestPayment, principal: principalPayment, extra: currentExtra, newBalance: newBalance < 0.01 ? 0 : newBalance };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
private static buildAmortizationTable(config: { amount: number; monthlyRate: number; totalMonths: number; monthlyPaymentBase: number; monthlyExtraPayment: number }): { table: AmortizationRow[]; totalInterest: number; actualMonths: number } {
|
|
69
|
+
const { amount, monthlyRate, totalMonths, monthlyPaymentBase, monthlyExtraPayment } = config;
|
|
70
|
+
const table: AmortizationRow[] = [];
|
|
71
|
+
let remainingBalance = amount;
|
|
72
|
+
let totalInterest = 0;
|
|
73
|
+
let actualMonths = 0;
|
|
74
|
+
|
|
75
|
+
for (let month = 1; month <= totalMonths; month++) {
|
|
76
|
+
const payment = this.processMonthlyPayment(remainingBalance, monthlyRate, monthlyPaymentBase, monthlyExtraPayment);
|
|
77
|
+
|
|
78
|
+
totalInterest += payment.interest;
|
|
79
|
+
actualMonths = month;
|
|
80
|
+
remainingBalance = payment.newBalance;
|
|
81
|
+
|
|
82
|
+
table.push({
|
|
83
|
+
month,
|
|
84
|
+
payment: Number((payment.principal + payment.interest + payment.extra).toFixed(2)),
|
|
85
|
+
interest: Number(payment.interest.toFixed(2)),
|
|
86
|
+
principal: Number(payment.principal.toFixed(2)),
|
|
87
|
+
extraPayment: Number(payment.extra.toFixed(2)),
|
|
88
|
+
remainingBalance: Number(remainingBalance.toFixed(2)),
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
if (remainingBalance <= 0) break;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return { table, totalInterest, actualMonths };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
static calculate(input: MortgageInput): MortgageResult {
|
|
98
|
+
const { amount, annualInterestRate, years, monthlyExtraPayment = 0 } = input;
|
|
99
|
+
const monthlyRate = annualInterestRate / 100 / 12;
|
|
100
|
+
const totalMonths = years * 12;
|
|
101
|
+
|
|
102
|
+
const monthlyPaymentBase = this.calculateMonthlyPayment(amount, monthlyRate, totalMonths);
|
|
103
|
+
const originalTotalInterest = this.calculateOriginalInterest(amount, monthlyRate, totalMonths, monthlyPaymentBase);
|
|
104
|
+
const { table: amortizationTable, totalInterest, actualMonths } = this.buildAmortizationTable({ amount, monthlyRate, totalMonths, monthlyPaymentBase, monthlyExtraPayment });
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
monthlyPayment: Number(monthlyPaymentBase.toFixed(2)),
|
|
108
|
+
totalInterest: Number(totalInterest.toFixed(2)),
|
|
109
|
+
totalPaid: Number((amount + totalInterest).toFixed(2)),
|
|
110
|
+
amortizationTable,
|
|
111
|
+
originalTotalInterest: Number(originalTotalInterest.toFixed(2)),
|
|
112
|
+
interestSaved: Number((originalTotalInterest - totalInterest).toFixed(2)),
|
|
113
|
+
actualDurationMonths: actualMonths,
|
|
114
|
+
monthsSaved: totalMonths - actualMonths,
|
|
115
|
+
yearsSaved: Number(((totalMonths - actualMonths) / 12).toFixed(1)),
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { SEORenderer } from '@jjlmoya/utils-shared';
|
|
3
|
+
import { mortgage } from './index';
|
|
4
|
+
import type { KnownLocale } from '../../types';
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
locale?: KnownLocale;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const { locale = 'es' } = Astro.props;
|
|
11
|
+
const content = await mortgage.i18n[locale]?.();
|
|
12
|
+
if (!content) return null;
|
|
13
|
+
---
|
|
14
|
+
<SEORenderer content={{ locale: locale as string, sections: content.seo }} />
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export interface MortgageUI {
|
|
2
|
+
labelTitle: string;
|
|
3
|
+
labelLoanAmount: string;
|
|
4
|
+
labelInterestRate: string;
|
|
5
|
+
labelYears: string;
|
|
6
|
+
labelMonthlyExtra: string;
|
|
7
|
+
labelMonthlyPayment: string;
|
|
8
|
+
labelMonthCount: string;
|
|
9
|
+
labelSavingsCard: string;
|
|
10
|
+
labelSavingsInterest: string;
|
|
11
|
+
labelSavingsTime: string;
|
|
12
|
+
labelTimelineComparison: string;
|
|
13
|
+
labelTimelineOriginal: string;
|
|
14
|
+
labelTimelineOptimized: string;
|
|
15
|
+
labelCostBreakdown: string;
|
|
16
|
+
labelBorrowed: string;
|
|
17
|
+
labelTotalBorrowed: string;
|
|
18
|
+
labelTotalInterest: string;
|
|
19
|
+
labelAmortizationTable: string;
|
|
20
|
+
labelTableHeader: string;
|
|
21
|
+
labelViewAll: string;
|
|
22
|
+
labelPaid: string;
|
|
23
|
+
labelTableMonth: string;
|
|
24
|
+
labelTableInterest: string;
|
|
25
|
+
labelTablePrincipal: string;
|
|
26
|
+
labelTableExtra: string;
|
|
27
|
+
labelTableBalance: string;
|
|
28
|
+
labelMoreMonths: string;
|
|
29
|
+
labelMonth: string;
|
|
30
|
+
currencySymbol: string;
|
|
31
|
+
currencyCode: string;
|
|
32
|
+
currencyLocale: string;
|
|
33
|
+
monthPlural: string;
|
|
34
|
+
yearPlural: string;
|
|
35
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { Bibliography as SharedBibliography } from '@jjlmoya/utils-shared';
|
|
3
|
+
import { percentageCalculator } from './index';
|
|
4
|
+
import type { KnownLocale } from '../../types';
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
locale?: KnownLocale;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const { locale = 'es' } = Astro.props;
|
|
11
|
+
const content = await percentageCalculator.i18n[locale]?.();
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
{content && <SharedBibliography links={content.bibliography} />}
|