@grupolapa/cotizador-sdk 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.
- package/README.md +125 -0
- package/dist/bundle-payment-schedule.calc.d.ts +2 -0
- package/dist/bundle-payment-schedule.calc.js +160 -0
- package/dist/calculate-quote.d.ts +2 -0
- package/dist/calculate-quote.js +382 -0
- package/dist/corporate-actions.d.ts +95 -0
- package/dist/corporate-actions.js +87 -0
- package/dist/corporate-rent.d.ts +5 -0
- package/dist/corporate-rent.js +43 -0
- package/dist/delivery-date.d.ts +6 -0
- package/dist/delivery-date.js +22 -0
- package/dist/entrada.d.ts +111 -0
- package/dist/entrada.js +139 -0
- package/dist/format.d.ts +7 -0
- package/dist/format.js +37 -0
- package/dist/http.d.ts +28 -0
- package/dist/http.js +133 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +22 -0
- package/dist/installment-allocation.d.ts +30 -0
- package/dist/installment-allocation.js +69 -0
- package/dist/inventory-availability.d.ts +10 -0
- package/dist/inventory-availability.js +105 -0
- package/dist/live-inventory-availability.d.ts +16 -0
- package/dist/live-inventory-availability.js +88 -0
- package/dist/payment-schemes.d.ts +22 -0
- package/dist/payment-schemes.js +99 -0
- package/dist/post-delivery-value.d.ts +3 -0
- package/dist/post-delivery-value.js +61 -0
- package/dist/product-types.d.ts +16 -0
- package/dist/product-types.js +82 -0
- package/dist/query-state.d.ts +5 -0
- package/dist/query-state.js +157 -0
- package/dist/quote-outcome-graph.d.ts +31 -0
- package/dist/quote-outcome-graph.js +113 -0
- package/dist/quote.d.ts +19 -0
- package/dist/quote.js +18 -0
- package/dist/selection-state.d.ts +34 -0
- package/dist/selection-state.js +156 -0
- package/dist/server.d.ts +16 -0
- package/dist/server.js +17 -0
- package/dist/sitemap.d.ts +22 -0
- package/dist/sitemap.js +45 -0
- package/dist/social-quote.d.ts +8 -0
- package/dist/social-quote.js +51 -0
- package/dist/tax.d.ts +5 -0
- package/dist/tax.js +11 -0
- package/dist/types.d.ts +502 -0
- package/dist/types.js +1 -0
- package/package.json +51 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
function createRow(periodIndex, label, previousPoint, currentPoint) {
|
|
2
|
+
const plusvaliaDeltaMXN = currentPoint.assetValueMXN - previousPoint.assetValueMXN;
|
|
3
|
+
const rentalDeltaMXN = currentPoint.rentalAccumulatedMXN - previousPoint.rentalAccumulatedMXN;
|
|
4
|
+
return {
|
|
5
|
+
periodIndex,
|
|
6
|
+
label,
|
|
7
|
+
plusvaliaDeltaMXN,
|
|
8
|
+
rentalDeltaMXN,
|
|
9
|
+
valueAddedMXN: plusvaliaDeltaMXN + rentalDeltaMXN,
|
|
10
|
+
assetValueMXN: currentPoint.assetValueMXN,
|
|
11
|
+
rentalAccumulatedMXN: currentPoint.rentalAccumulatedMXN,
|
|
12
|
+
ownerValueMXN: currentPoint.valueWithRentMXN,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
function getInterpolatedPoint(previousYearPoint, nextYearPoint, progress) {
|
|
16
|
+
const assetGrowthRatio = previousYearPoint.assetValueMXN > 0
|
|
17
|
+
? nextYearPoint.assetValueMXN / previousYearPoint.assetValueMXN
|
|
18
|
+
: 1;
|
|
19
|
+
const assetValueMXN = previousYearPoint.assetValueMXN * Math.pow(assetGrowthRatio, progress);
|
|
20
|
+
const rentalAccumulatedMXN = previousYearPoint.rentalAccumulatedMXN +
|
|
21
|
+
(nextYearPoint.rentalAccumulatedMXN -
|
|
22
|
+
previousYearPoint.rentalAccumulatedMXN) *
|
|
23
|
+
progress;
|
|
24
|
+
return {
|
|
25
|
+
yearOffset: nextYearPoint.yearOffset,
|
|
26
|
+
label: nextYearPoint.label,
|
|
27
|
+
assetValueMXN,
|
|
28
|
+
rentalAccumulatedMXN,
|
|
29
|
+
valueWithRentMXN: assetValueMXN + rentalAccumulatedMXN,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
export function buildAnnualPostDeliveryValueRows(projectionPoints) {
|
|
33
|
+
const deliveryPoint = projectionPoints[0];
|
|
34
|
+
if (!deliveryPoint) {
|
|
35
|
+
return [];
|
|
36
|
+
}
|
|
37
|
+
return [
|
|
38
|
+
createRow(0, "Año 0", deliveryPoint, deliveryPoint),
|
|
39
|
+
...projectionPoints
|
|
40
|
+
.slice(1)
|
|
41
|
+
.map((point, index) => createRow(index + 1, `Año ${point.yearOffset}`, projectionPoints[index], point)),
|
|
42
|
+
];
|
|
43
|
+
}
|
|
44
|
+
export function buildQuarterlyPostDeliveryValueRows(projectionPoints) {
|
|
45
|
+
const rows = [];
|
|
46
|
+
let previousQuarterPoint = projectionPoints[0];
|
|
47
|
+
if (!previousQuarterPoint) {
|
|
48
|
+
return rows;
|
|
49
|
+
}
|
|
50
|
+
for (let yearIndex = 1; yearIndex < projectionPoints.length; yearIndex += 1) {
|
|
51
|
+
const previousYearPoint = projectionPoints[yearIndex - 1];
|
|
52
|
+
const nextYearPoint = projectionPoints[yearIndex];
|
|
53
|
+
for (let quarter = 1; quarter <= 4; quarter += 1) {
|
|
54
|
+
const periodIndex = (yearIndex - 1) * 4 + quarter;
|
|
55
|
+
const currentQuarterPoint = getInterpolatedPoint(previousYearPoint, nextYearPoint, quarter / 4);
|
|
56
|
+
rows.push(createRow(periodIndex, `Año ${nextYearPoint.yearOffset} · T${quarter}`, previousQuarterPoint, currentQuarterPoint));
|
|
57
|
+
previousQuarterPoint = currentQuarterPoint;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return rows;
|
|
61
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { InventoryCategory, InventoryUnit, ProductType } from "./types.js";
|
|
2
|
+
type InventoryUnitSortTarget = {
|
|
3
|
+
id: string;
|
|
4
|
+
unitNumber: string;
|
|
5
|
+
name?: string;
|
|
6
|
+
};
|
|
7
|
+
export declare function buildInventoryUnitName(productTypeName: string, unitNumber: string): string;
|
|
8
|
+
export declare function flattenProductTypes(productTypes: ProductType[]): InventoryUnit[];
|
|
9
|
+
export declare function compareInventoryUnitsByName(left: InventoryUnitSortTarget, right: InventoryUnitSortTarget): number;
|
|
10
|
+
export declare function sortInventoryUnitsByName<T extends InventoryUnitSortTarget>(units: readonly T[]): T[];
|
|
11
|
+
export declare function getCategoryProductTypes(productTypes: ProductType[], category: InventoryCategory): ProductType[];
|
|
12
|
+
export declare function getInventoryUnitsForCategory(productTypes: ProductType[], category: InventoryCategory): InventoryUnit[];
|
|
13
|
+
export declare function getInventoryUnitShortLabel(unit: Pick<InventoryUnit, "unitNumber">): string;
|
|
14
|
+
export declare function getInventoryUnitDetailLabel(unit: Pick<InventoryUnit, "productTypeName" | "unitNumber">): string;
|
|
15
|
+
export declare function getInventoryShapeSuggestionLabels(unit: Pick<InventoryUnit, "unitNumber" | "name" | "productTypeName">): string[];
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
const inventoryUnitLabelCollator = new Intl.Collator("es-MX", {
|
|
2
|
+
numeric: true,
|
|
3
|
+
sensitivity: "base",
|
|
4
|
+
});
|
|
5
|
+
function normalizeComparableLabel(value) {
|
|
6
|
+
return value
|
|
7
|
+
.normalize("NFD")
|
|
8
|
+
.replace(/[\u0300-\u036f]/g, "")
|
|
9
|
+
.toLowerCase()
|
|
10
|
+
.trim()
|
|
11
|
+
.replace(/\s+/g, " ");
|
|
12
|
+
}
|
|
13
|
+
export function buildInventoryUnitName(productTypeName, unitNumber) {
|
|
14
|
+
const normalizedProductTypeName = normalizeComparableLabel(productTypeName);
|
|
15
|
+
const normalizedUnitNumber = normalizeComparableLabel(unitNumber);
|
|
16
|
+
if (normalizedUnitNumber.length === 0 ||
|
|
17
|
+
normalizedUnitNumber === normalizedProductTypeName ||
|
|
18
|
+
normalizedUnitNumber.includes(normalizedProductTypeName) ||
|
|
19
|
+
normalizedProductTypeName.includes(normalizedUnitNumber)) {
|
|
20
|
+
return unitNumber;
|
|
21
|
+
}
|
|
22
|
+
return `${unitNumber} · ${productTypeName}`;
|
|
23
|
+
}
|
|
24
|
+
export function flattenProductTypes(productTypes) {
|
|
25
|
+
return productTypes.flatMap((productType) => productType.inventory.map((unit) => ({
|
|
26
|
+
id: unit.id,
|
|
27
|
+
unitNumber: unit.unitNumber,
|
|
28
|
+
name: buildInventoryUnitName(productType.name, unit.unitNumber),
|
|
29
|
+
productTypeId: productType.id,
|
|
30
|
+
productTypeName: productType.name,
|
|
31
|
+
category: productType.category,
|
|
32
|
+
areaM2: unit.areaM2,
|
|
33
|
+
pricePerM2MXN: unit.pricePerM2MXN,
|
|
34
|
+
deliveryDate: productType.deliveryDate,
|
|
35
|
+
allowedSchemeIds: productType.allowedSchemeIds,
|
|
36
|
+
defaultSchemeId: productType.defaultSchemeId,
|
|
37
|
+
inventoryMode: productType.inventoryMode,
|
|
38
|
+
status: unit.status,
|
|
39
|
+
...(unit.svgElementIds && unit.svgElementIds.length > 0
|
|
40
|
+
? { svgElementIds: unit.svgElementIds }
|
|
41
|
+
: {}),
|
|
42
|
+
...(unit.svgPath ? { svgPath: unit.svgPath } : {}),
|
|
43
|
+
})));
|
|
44
|
+
}
|
|
45
|
+
function getInventoryUnitSortLabel(unit) {
|
|
46
|
+
const unitNumber = unit.unitNumber.trim();
|
|
47
|
+
if (unitNumber.length > 0) {
|
|
48
|
+
return unitNumber;
|
|
49
|
+
}
|
|
50
|
+
return unit.name?.trim() ?? "";
|
|
51
|
+
}
|
|
52
|
+
export function compareInventoryUnitsByName(left, right) {
|
|
53
|
+
const labelComparison = inventoryUnitLabelCollator.compare(getInventoryUnitSortLabel(left), getInventoryUnitSortLabel(right));
|
|
54
|
+
if (labelComparison !== 0) {
|
|
55
|
+
return labelComparison;
|
|
56
|
+
}
|
|
57
|
+
const leftName = left.name?.trim() ?? "";
|
|
58
|
+
const rightName = right.name?.trim() ?? "";
|
|
59
|
+
const nameComparison = inventoryUnitLabelCollator.compare(leftName, rightName);
|
|
60
|
+
if (nameComparison !== 0) {
|
|
61
|
+
return nameComparison;
|
|
62
|
+
}
|
|
63
|
+
return inventoryUnitLabelCollator.compare(left.id, right.id);
|
|
64
|
+
}
|
|
65
|
+
export function sortInventoryUnitsByName(units) {
|
|
66
|
+
return units.toSorted(compareInventoryUnitsByName);
|
|
67
|
+
}
|
|
68
|
+
export function getCategoryProductTypes(productTypes, category) {
|
|
69
|
+
return productTypes.filter((productType) => productType.category === category);
|
|
70
|
+
}
|
|
71
|
+
export function getInventoryUnitsForCategory(productTypes, category) {
|
|
72
|
+
return sortInventoryUnitsByName(flattenProductTypes(getCategoryProductTypes(productTypes, category)));
|
|
73
|
+
}
|
|
74
|
+
export function getInventoryUnitShortLabel(unit) {
|
|
75
|
+
return unit.unitNumber;
|
|
76
|
+
}
|
|
77
|
+
export function getInventoryUnitDetailLabel(unit) {
|
|
78
|
+
return buildInventoryUnitName(unit.productTypeName, unit.unitNumber);
|
|
79
|
+
}
|
|
80
|
+
export function getInventoryShapeSuggestionLabels(unit) {
|
|
81
|
+
return Array.from(new Set([unit.unitNumber, unit.name, unit.productTypeName].filter(Boolean)));
|
|
82
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { FinanceRules, InventoryUnit, PaymentSchemeDefinition, QuoteUrlState } from "./types.js";
|
|
2
|
+
export declare function normalizeQuoteUrlState(search: Record<string, unknown>, units: InventoryUnit[], rules: FinanceRules, paymentSchemes: PaymentSchemeDefinition[]): QuoteUrlState;
|
|
3
|
+
export declare function normalizeSocialQuoteUrlState(search: Record<string, unknown>, units: InventoryUnit[], rules: FinanceRules, paymentSchemes: PaymentSchemeDefinition[]): QuoteUrlState;
|
|
4
|
+
export declare function normalizeUondrQuoteUrlState(search: Record<string, unknown>, units: InventoryUnit[], rules: FinanceRules, paymentSchemes: PaymentSchemeDefinition[]): QuoteUrlState;
|
|
5
|
+
export declare function buildQuoteSearch(state: QuoteUrlState): string;
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { clampToRange, getDefaultDownPaymentPctForScheme, getResolvedSchemeForUnit, isInstallmentScheme, } from "./payment-schemes.js";
|
|
2
|
+
import { clampUondrCashbackDownPaymentPct } from "./social-quote.js";
|
|
3
|
+
import { clampInstallmentDownPaymentPct, clampInstallmentMonthlyPct, INSTALLMENT_DEFAULT_DOWN_PAYMENT_PCT, INSTALLMENT_DEFAULT_MONTHLY_PCT, } from "./installment-allocation.js";
|
|
4
|
+
function parseNumberParam(value, fallback) {
|
|
5
|
+
if (value === null) {
|
|
6
|
+
return fallback;
|
|
7
|
+
}
|
|
8
|
+
const parsed = Number(value);
|
|
9
|
+
return Number.isNaN(parsed) ? fallback : parsed;
|
|
10
|
+
}
|
|
11
|
+
function parseBooleanParam(value, fallback) {
|
|
12
|
+
if (typeof value === "boolean") {
|
|
13
|
+
return value;
|
|
14
|
+
}
|
|
15
|
+
if (typeof value === "number") {
|
|
16
|
+
if (value === 1) {
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
if (value === 0) {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
if (typeof value !== "string") {
|
|
24
|
+
return fallback;
|
|
25
|
+
}
|
|
26
|
+
const normalized = value.trim().toLowerCase();
|
|
27
|
+
if (["1", "true", "yes", "si", "con"].includes(normalized)) {
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
if (["0", "false", "no", "sin"].includes(normalized)) {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
return fallback;
|
|
34
|
+
}
|
|
35
|
+
function getStringParam(value) {
|
|
36
|
+
return typeof value === "string" ? value : null;
|
|
37
|
+
}
|
|
38
|
+
function getNumericParam(value) {
|
|
39
|
+
if (typeof value === "string") {
|
|
40
|
+
return value;
|
|
41
|
+
}
|
|
42
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
43
|
+
return String(value);
|
|
44
|
+
}
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
function pickStringParam(...values) {
|
|
48
|
+
for (const value of values) {
|
|
49
|
+
const result = getStringParam(value);
|
|
50
|
+
if (result !== null) {
|
|
51
|
+
return result;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
function pickNumericParam(...values) {
|
|
57
|
+
for (const value of values) {
|
|
58
|
+
const result = getNumericParam(value);
|
|
59
|
+
if (result !== null) {
|
|
60
|
+
return result;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
function isCategory(value) {
|
|
66
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
67
|
+
}
|
|
68
|
+
export function normalizeQuoteUrlState(search, units, rules, paymentSchemes) {
|
|
69
|
+
const requestedUnitId = pickStringParam(search.unitId, search.unit);
|
|
70
|
+
const requestedCategoryParam = getStringParam(search.category);
|
|
71
|
+
const requestedScheme = getStringParam(search.scheme);
|
|
72
|
+
const defaultUnit = units.find((unit) => unit.id === rules.defaults.unitId) ??
|
|
73
|
+
units.find((unit) => unit.category === rules.defaults.category) ??
|
|
74
|
+
units[0];
|
|
75
|
+
const requestedUnit = units.find((unit) => unit.id === requestedUnitId) ?? null;
|
|
76
|
+
const requestedCategory = isCategory(requestedCategoryParam)
|
|
77
|
+
? requestedCategoryParam
|
|
78
|
+
: (requestedUnit?.category ??
|
|
79
|
+
(isCategory(rules.defaults.category)
|
|
80
|
+
? rules.defaults.category
|
|
81
|
+
: (defaultUnit?.category ?? "")));
|
|
82
|
+
const resolvedCategory = units.some((unit) => unit.category === requestedCategory)
|
|
83
|
+
? requestedCategory
|
|
84
|
+
: (defaultUnit?.category ?? "");
|
|
85
|
+
const selectedUnit = requestedUnit && requestedUnit.category === resolvedCategory
|
|
86
|
+
? requestedUnit
|
|
87
|
+
: null;
|
|
88
|
+
const schemeSourceUnit = selectedUnit ??
|
|
89
|
+
(defaultUnit?.category === resolvedCategory ? defaultUnit : null);
|
|
90
|
+
const resolvedScheme = schemeSourceUnit
|
|
91
|
+
? getResolvedSchemeForUnit(schemeSourceUnit, paymentSchemes, requestedScheme)
|
|
92
|
+
: (paymentSchemes.find((scheme) => scheme.id === requestedScheme && scheme.active) ??
|
|
93
|
+
paymentSchemes.find((scheme) => scheme.id === rules.defaults.schemeId && scheme.active) ??
|
|
94
|
+
paymentSchemes.find((scheme) => scheme.active) ??
|
|
95
|
+
null);
|
|
96
|
+
const defaultDownPaymentPct = getDefaultDownPaymentPctForScheme(resolvedScheme, rules);
|
|
97
|
+
return {
|
|
98
|
+
category: resolvedCategory,
|
|
99
|
+
unitId: selectedUnit?.id ?? "",
|
|
100
|
+
scheme: resolvedScheme?.id ?? rules.defaults.schemeId,
|
|
101
|
+
postDeliveryYears: clampToRange(parseNumberParam(pickNumericParam(search.postDeliveryYears, search.years), rules.defaults.postDeliveryYears), rules.ranges.postDeliveryYears),
|
|
102
|
+
downPaymentPct: resolvedScheme?.type === "cashback"
|
|
103
|
+
? clampToRange(parseNumberParam(pickNumericParam(search.downPaymentPct, search.enganche), defaultDownPaymentPct), resolvedScheme.downPaymentRange)
|
|
104
|
+
: defaultDownPaymentPct,
|
|
105
|
+
installmentDownPaymentPct: (() => {
|
|
106
|
+
const value = clampInstallmentDownPaymentPct(parseNumberParam(pickNumericParam(search.installmentDownPaymentPct, search.iEng), INSTALLMENT_DEFAULT_DOWN_PAYMENT_PCT));
|
|
107
|
+
return value;
|
|
108
|
+
})(),
|
|
109
|
+
installmentMonthlyPct: (() => {
|
|
110
|
+
const downValue = clampInstallmentDownPaymentPct(parseNumberParam(pickNumericParam(search.installmentDownPaymentPct, search.iEng), INSTALLMENT_DEFAULT_DOWN_PAYMENT_PCT));
|
|
111
|
+
return clampInstallmentMonthlyPct(parseNumberParam(pickNumericParam(search.installmentMonthlyPct, search.iMens), INSTALLMENT_DEFAULT_MONTHLY_PCT), downValue);
|
|
112
|
+
})(),
|
|
113
|
+
includeIva: parseBooleanParam(search.includeIva ?? search.iva, false),
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
export function normalizeSocialQuoteUrlState(search, units, rules, paymentSchemes) {
|
|
117
|
+
const state = normalizeQuoteUrlState(search, units, rules, paymentSchemes);
|
|
118
|
+
const selectedUnit = units.find((unit) => unit.id === state.unitId) ?? null;
|
|
119
|
+
const resolvedScheme = selectedUnit
|
|
120
|
+
? getResolvedSchemeForUnit(selectedUnit, paymentSchemes, state.scheme)
|
|
121
|
+
: (paymentSchemes.find((scheme) => scheme.id === state.scheme && scheme.active) ?? null);
|
|
122
|
+
return {
|
|
123
|
+
...state,
|
|
124
|
+
postDeliveryYears: rules.defaults.postDeliveryYears,
|
|
125
|
+
downPaymentPct: resolvedScheme && isInstallmentScheme(resolvedScheme)
|
|
126
|
+
? resolvedScheme.downPaymentPct
|
|
127
|
+
: getDefaultDownPaymentPctForScheme(resolvedScheme, rules),
|
|
128
|
+
includeIva: false,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
export function normalizeUondrQuoteUrlState(search, units, rules, paymentSchemes) {
|
|
132
|
+
const state = normalizeQuoteUrlState(search, units, rules, paymentSchemes);
|
|
133
|
+
const selectedUnit = units.find((unit) => unit.id === state.unitId) ?? null;
|
|
134
|
+
const resolvedScheme = selectedUnit
|
|
135
|
+
? getResolvedSchemeForUnit(selectedUnit, paymentSchemes, state.scheme)
|
|
136
|
+
: (paymentSchemes.find((scheme) => scheme.id === state.scheme && scheme.active) ?? null);
|
|
137
|
+
return {
|
|
138
|
+
...state,
|
|
139
|
+
postDeliveryYears: rules.defaults.postDeliveryYears,
|
|
140
|
+
downPaymentPct: clampUondrCashbackDownPaymentPct(resolvedScheme, state.downPaymentPct),
|
|
141
|
+
includeIva: false,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
export function buildQuoteSearch(state) {
|
|
145
|
+
const params = new URLSearchParams();
|
|
146
|
+
params.set("category", state.category);
|
|
147
|
+
if (state.unitId) {
|
|
148
|
+
params.set("unit", state.unitId);
|
|
149
|
+
}
|
|
150
|
+
params.set("scheme", state.scheme);
|
|
151
|
+
params.set("years", String(state.postDeliveryYears));
|
|
152
|
+
params.set("enganche", String(state.downPaymentPct));
|
|
153
|
+
params.set("iEng", String(state.installmentDownPaymentPct));
|
|
154
|
+
params.set("iMens", String(state.installmentMonthlyPct));
|
|
155
|
+
params.set("iva", state.includeIva ? "1" : "0");
|
|
156
|
+
return params.toString();
|
|
157
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { QuoteResult } from "./types.js";
|
|
2
|
+
export interface QuoteOutcomeGraphSegment {
|
|
3
|
+
key: "base" | "discount" | "plusvalia" | "cashback" | "rent";
|
|
4
|
+
label: string;
|
|
5
|
+
shortLabel: string;
|
|
6
|
+
valueMXN: number;
|
|
7
|
+
}
|
|
8
|
+
export interface QuoteOutcomeGraphBar {
|
|
9
|
+
key: "paid" | "delivery" | "horizon";
|
|
10
|
+
label: string;
|
|
11
|
+
sublabel?: string;
|
|
12
|
+
valueMXN: number;
|
|
13
|
+
segments: QuoteOutcomeGraphSegment[];
|
|
14
|
+
}
|
|
15
|
+
export interface QuoteOutcomeGraphDelta {
|
|
16
|
+
key: "delivery" | "rent" | "total";
|
|
17
|
+
label: string;
|
|
18
|
+
labelLines: string[];
|
|
19
|
+
valueMXN: number;
|
|
20
|
+
}
|
|
21
|
+
export interface QuoteOutcomeGraphModel {
|
|
22
|
+
title: string;
|
|
23
|
+
description: string;
|
|
24
|
+
ariaLabel: string;
|
|
25
|
+
bars: [QuoteOutcomeGraphBar, QuoteOutcomeGraphBar, QuoteOutcomeGraphBar];
|
|
26
|
+
deliveryDelta: QuoteOutcomeGraphDelta;
|
|
27
|
+
rentDelta: QuoteOutcomeGraphDelta;
|
|
28
|
+
totalDelta: QuoteOutcomeGraphDelta;
|
|
29
|
+
chartMaxValueMXN: number;
|
|
30
|
+
}
|
|
31
|
+
export declare function buildQuoteOutcomeGraphModel(quote: QuoteResult): QuoteOutcomeGraphModel;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { formatCompactCurrency, formatYears } from "./format.js";
|
|
2
|
+
function createBaseSegments(quote) {
|
|
3
|
+
return [
|
|
4
|
+
{
|
|
5
|
+
key: "base",
|
|
6
|
+
label: "Costo neto",
|
|
7
|
+
shortLabel: "Costo",
|
|
8
|
+
valueMXN: quote.netCostMXN,
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
key: "discount",
|
|
12
|
+
label: "Descuento por enganche",
|
|
13
|
+
shortLabel: "Descuento",
|
|
14
|
+
valueMXN: quote.installmentDiscountMXN,
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
key: "plusvalia",
|
|
18
|
+
label: "Plusvalía",
|
|
19
|
+
shortLabel: "Plusvalía",
|
|
20
|
+
valueMXN: quote.plusvaliaAtDeliveryMXN,
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
key: "cashback",
|
|
24
|
+
label: "Cashback",
|
|
25
|
+
shortLabel: "Cashback",
|
|
26
|
+
valueMXN: quote.cashbackTotalMXN,
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
key: "rent",
|
|
30
|
+
label: "Rentas",
|
|
31
|
+
shortLabel: "Rentas",
|
|
32
|
+
valueMXN: quote.rentalAccumulatedMXN,
|
|
33
|
+
},
|
|
34
|
+
];
|
|
35
|
+
}
|
|
36
|
+
export function buildQuoteOutcomeGraphModel(quote) {
|
|
37
|
+
const horizonLabel = formatYears(quote.input.postDeliveryYears);
|
|
38
|
+
const valueWithRentMXN = quote.projectedAssetValueMXN + quote.rentalAccumulatedMXN;
|
|
39
|
+
const deliveryDeltaValueMXN = quote.finalAssetValueMXN - quote.netCostMXN;
|
|
40
|
+
const postDeliveryDeltaValueMXN = quote.plusvaliaPostDeliveryMXN + quote.rentalAccumulatedMXN;
|
|
41
|
+
const [baseSegment, discountSegment, plusvaliaSegment, cashbackSegment, rentSegment,] = createBaseSegments(quote);
|
|
42
|
+
const chartMaxValueMXN = Math.max(quote.netCostMXN, quote.finalAssetValueMXN, valueWithRentMXN, 1) *
|
|
43
|
+
1.08;
|
|
44
|
+
const deliveryDeltaLabel = quote.isCashback
|
|
45
|
+
? "Plusvalía + cashback a escritura"
|
|
46
|
+
: quote.installmentDiscountMXN > 0
|
|
47
|
+
? "Plusvalía + descuento a escritura"
|
|
48
|
+
: "Plusvalía en entrega";
|
|
49
|
+
const deliveryDeltaLabelLines = quote.isCashback
|
|
50
|
+
? ["Plusvalía + cashback", "a escritura"]
|
|
51
|
+
: quote.installmentDiscountMXN > 0
|
|
52
|
+
? ["Plusvalía + descuento", "a escritura"]
|
|
53
|
+
: ["Plusvalía", "en entrega"];
|
|
54
|
+
return {
|
|
55
|
+
title: "Valor recibido",
|
|
56
|
+
description: `Compara costo neto, valor a escritura y valor + rentas en ${horizonLabel}.`,
|
|
57
|
+
ariaLabel: `Comparación de valor entre ${formatCompactCurrency(quote.netCostMXN)} pagados, ${formatCompactCurrency(quote.finalAssetValueMXN)} al escriturar y ${formatCompactCurrency(valueWithRentMXN)} con rentas acumuladas en ${horizonLabel}.`,
|
|
58
|
+
bars: [
|
|
59
|
+
{
|
|
60
|
+
key: "paid",
|
|
61
|
+
label: "Compra",
|
|
62
|
+
valueMXN: quote.netCostMXN,
|
|
63
|
+
segments: [baseSegment],
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
key: "delivery",
|
|
67
|
+
label: "Valor a escritura",
|
|
68
|
+
valueMXN: quote.finalAssetValueMXN,
|
|
69
|
+
segments: [
|
|
70
|
+
baseSegment,
|
|
71
|
+
discountSegment,
|
|
72
|
+
plusvaliaSegment,
|
|
73
|
+
cashbackSegment,
|
|
74
|
+
].filter((segment) => segment.valueMXN > 0),
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
key: "horizon",
|
|
78
|
+
label: "Valor + rentas",
|
|
79
|
+
sublabel: `en ${horizonLabel}`,
|
|
80
|
+
valueMXN: valueWithRentMXN,
|
|
81
|
+
segments: [
|
|
82
|
+
baseSegment,
|
|
83
|
+
discountSegment,
|
|
84
|
+
{
|
|
85
|
+
...plusvaliaSegment,
|
|
86
|
+
valueMXN: quote.plusvaliaAtDeliveryMXN + quote.plusvaliaPostDeliveryMXN,
|
|
87
|
+
},
|
|
88
|
+
cashbackSegment,
|
|
89
|
+
rentSegment,
|
|
90
|
+
].filter((segment) => segment.valueMXN > 0),
|
|
91
|
+
},
|
|
92
|
+
],
|
|
93
|
+
deliveryDelta: {
|
|
94
|
+
key: "delivery",
|
|
95
|
+
label: deliveryDeltaLabel,
|
|
96
|
+
labelLines: deliveryDeltaLabelLines,
|
|
97
|
+
valueMXN: deliveryDeltaValueMXN,
|
|
98
|
+
},
|
|
99
|
+
rentDelta: {
|
|
100
|
+
key: "rent",
|
|
101
|
+
label: `Plusvalía + rentas por ${horizonLabel}`,
|
|
102
|
+
labelLines: ["Plusvalía + rentas", horizonLabel],
|
|
103
|
+
valueMXN: postDeliveryDeltaValueMXN,
|
|
104
|
+
},
|
|
105
|
+
totalDelta: {
|
|
106
|
+
key: "total",
|
|
107
|
+
label: "Ganancia total",
|
|
108
|
+
labelLines: ["Ganancia", "total"],
|
|
109
|
+
valueMXN: valueWithRentMXN - quote.netCostMXN,
|
|
110
|
+
},
|
|
111
|
+
chartMaxValueMXN,
|
|
112
|
+
};
|
|
113
|
+
}
|
package/dist/quote.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export * from "./bundle-payment-schedule.calc.js";
|
|
2
|
+
export * from "./calculate-quote.js";
|
|
3
|
+
export * from "./corporate-actions.js";
|
|
4
|
+
export * from "./corporate-rent.js";
|
|
5
|
+
export * from "./delivery-date.js";
|
|
6
|
+
export * from "./entrada.js";
|
|
7
|
+
export * from "./format.js";
|
|
8
|
+
export * from "./installment-allocation.js";
|
|
9
|
+
export * from "./inventory-availability.js";
|
|
10
|
+
export * from "./live-inventory-availability.js";
|
|
11
|
+
export * from "./payment-schemes.js";
|
|
12
|
+
export * from "./post-delivery-value.js";
|
|
13
|
+
export * from "./product-types.js";
|
|
14
|
+
export * from "./query-state.js";
|
|
15
|
+
export * from "./quote-outcome-graph.js";
|
|
16
|
+
export * from "./selection-state.js";
|
|
17
|
+
export * from "./social-quote.js";
|
|
18
|
+
export * from "./tax.js";
|
|
19
|
+
export type * from "./types.js";
|
package/dist/quote.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export * from "./bundle-payment-schedule.calc.js";
|
|
2
|
+
export * from "./calculate-quote.js";
|
|
3
|
+
export * from "./corporate-actions.js";
|
|
4
|
+
export * from "./corporate-rent.js";
|
|
5
|
+
export * from "./delivery-date.js";
|
|
6
|
+
export * from "./entrada.js";
|
|
7
|
+
export * from "./format.js";
|
|
8
|
+
export * from "./installment-allocation.js";
|
|
9
|
+
export * from "./inventory-availability.js";
|
|
10
|
+
export * from "./live-inventory-availability.js";
|
|
11
|
+
export * from "./payment-schemes.js";
|
|
12
|
+
export * from "./post-delivery-value.js";
|
|
13
|
+
export * from "./product-types.js";
|
|
14
|
+
export * from "./query-state.js";
|
|
15
|
+
export * from "./quote-outcome-graph.js";
|
|
16
|
+
export * from "./selection-state.js";
|
|
17
|
+
export * from "./social-quote.js";
|
|
18
|
+
export * from "./tax.js";
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { AppContent, InventoryCategory, QuoteUrlState } from "./types.js";
|
|
2
|
+
export type CotizadorSurface = "default" | "social" | "uondr" | "a-uondr" | "ud" | "entrada";
|
|
3
|
+
export type CotizadorSelectionState = QuoteUrlState;
|
|
4
|
+
export type CotizadorSelectionAction = {
|
|
5
|
+
type: "reset";
|
|
6
|
+
state: CotizadorSelectionState;
|
|
7
|
+
} | {
|
|
8
|
+
type: "selectCategory";
|
|
9
|
+
category: InventoryCategory;
|
|
10
|
+
} | {
|
|
11
|
+
type: "selectUnit";
|
|
12
|
+
unitId: string;
|
|
13
|
+
} | {
|
|
14
|
+
type: "selectScheme";
|
|
15
|
+
schemeId: string;
|
|
16
|
+
} | {
|
|
17
|
+
type: "setPostDeliveryYears";
|
|
18
|
+
value: number;
|
|
19
|
+
} | {
|
|
20
|
+
type: "setDownPaymentPct";
|
|
21
|
+
value: number;
|
|
22
|
+
} | {
|
|
23
|
+
type: "setInstallmentDownPaymentPct";
|
|
24
|
+
value: number;
|
|
25
|
+
} | {
|
|
26
|
+
type: "setInstallmentMonthlyPct";
|
|
27
|
+
value: number;
|
|
28
|
+
} | {
|
|
29
|
+
type: "setIncludeIva";
|
|
30
|
+
value: boolean;
|
|
31
|
+
};
|
|
32
|
+
export declare function normalizeSelectionState(requestedState: CotizadorSelectionState, content: AppContent, surface: CotizadorSurface): CotizadorSelectionState;
|
|
33
|
+
export declare function createInitialSelectionState(content: AppContent, surface: CotizadorSurface): QuoteUrlState;
|
|
34
|
+
export declare function reduceSelectionState(state: CotizadorSelectionState, action: CotizadorSelectionAction, content: AppContent, surface: CotizadorSurface): QuoteUrlState;
|