@grupolapa/desarrollos-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.
Files changed (50) hide show
  1. package/README.md +154 -0
  2. package/dist/bundle-payment-schedule.calc.d.ts +2 -0
  3. package/dist/bundle-payment-schedule.calc.js +160 -0
  4. package/dist/calculate-quote.d.ts +2 -0
  5. package/dist/calculate-quote.js +382 -0
  6. package/dist/corporate-actions.d.ts +95 -0
  7. package/dist/corporate-actions.js +87 -0
  8. package/dist/corporate-rent.d.ts +5 -0
  9. package/dist/corporate-rent.js +43 -0
  10. package/dist/delivery-date.d.ts +6 -0
  11. package/dist/delivery-date.js +22 -0
  12. package/dist/entrada.d.ts +111 -0
  13. package/dist/entrada.js +139 -0
  14. package/dist/format.d.ts +7 -0
  15. package/dist/format.js +37 -0
  16. package/dist/http.d.ts +29 -0
  17. package/dist/http.js +123 -0
  18. package/dist/index.d.ts +15 -0
  19. package/dist/index.js +22 -0
  20. package/dist/installment-allocation.d.ts +30 -0
  21. package/dist/installment-allocation.js +69 -0
  22. package/dist/inventory-availability.d.ts +10 -0
  23. package/dist/inventory-availability.js +105 -0
  24. package/dist/live-inventory-availability.d.ts +16 -0
  25. package/dist/live-inventory-availability.js +88 -0
  26. package/dist/payment-schemes.d.ts +22 -0
  27. package/dist/payment-schemes.js +99 -0
  28. package/dist/post-delivery-value.d.ts +3 -0
  29. package/dist/post-delivery-value.js +61 -0
  30. package/dist/product-types.d.ts +16 -0
  31. package/dist/product-types.js +82 -0
  32. package/dist/query-state.d.ts +5 -0
  33. package/dist/query-state.js +157 -0
  34. package/dist/quote-outcome-graph.d.ts +31 -0
  35. package/dist/quote-outcome-graph.js +113 -0
  36. package/dist/quote.d.ts +19 -0
  37. package/dist/quote.js +18 -0
  38. package/dist/selection-state.d.ts +34 -0
  39. package/dist/selection-state.js +156 -0
  40. package/dist/server.d.ts +17 -0
  41. package/dist/server.js +18 -0
  42. package/dist/sitemap.d.ts +22 -0
  43. package/dist/sitemap.js +45 -0
  44. package/dist/social-quote.d.ts +8 -0
  45. package/dist/social-quote.js +51 -0
  46. package/dist/tax.d.ts +5 -0
  47. package/dist/tax.js +11 -0
  48. package/dist/types.d.ts +511 -0
  49. package/dist/types.js +1 -0
  50. package/package.json +51 -0
@@ -0,0 +1,105 @@
1
+ function buildBaseAvailability(unit, inventoryMode) {
2
+ return {
3
+ unitId: unit.id,
4
+ productTypeId: unit.productTypeId,
5
+ inventoryMode,
6
+ status: unit.status,
7
+ canCheckout: unit.status === "available",
8
+ };
9
+ }
10
+ function blocksCheckout(status) {
11
+ return status !== "available";
12
+ }
13
+ function getProductTypeById(content) {
14
+ return new Map(content.productTypes.map((productType) => [productType.id, productType]));
15
+ }
16
+ function getShareModeAvailability(unit, productType, soldPaid) {
17
+ const total = productType.shareInventory?.totalShares ?? 1;
18
+ const soldBaseline = productType.shareInventory?.soldSharesBaseline ?? 0;
19
+ const soldTotal = soldBaseline + soldPaid;
20
+ const remaining = Math.max(total - soldTotal, 0);
21
+ if (blocksCheckout(unit.status)) {
22
+ return {
23
+ ...buildBaseAvailability(unit, "shares"),
24
+ canCheckout: false,
25
+ share: {
26
+ total,
27
+ soldBaseline,
28
+ soldPaid,
29
+ soldTotal,
30
+ remaining,
31
+ },
32
+ };
33
+ }
34
+ return {
35
+ ...buildBaseAvailability(unit, "shares"),
36
+ status: remaining > 0 ? "available" : "sold",
37
+ canCheckout: remaining > 0,
38
+ isSoldOutByShares: remaining <= 0,
39
+ share: {
40
+ total,
41
+ soldBaseline,
42
+ soldPaid,
43
+ soldTotal,
44
+ remaining,
45
+ },
46
+ };
47
+ }
48
+ export function buildInventoryAvailabilityByUnitId(content, paidShareSalesByProductTypeId = {}) {
49
+ const productTypeById = getProductTypeById(content);
50
+ return Object.fromEntries(content.inventory.map((unit) => {
51
+ const productType = productTypeById.get(unit.productTypeId);
52
+ const availability = productType?.inventoryMode === "shares"
53
+ ? getShareModeAvailability(unit, productType, paidShareSalesByProductTypeId[productType.id] ?? 0)
54
+ : buildBaseAvailability(unit, productType?.inventoryMode ?? unit.inventoryMode ?? "units");
55
+ return [unit.id, availability];
56
+ }));
57
+ }
58
+ export function getInventoryAvailability(unit, availabilityByUnitId) {
59
+ if (!unit) {
60
+ return null;
61
+ }
62
+ return availabilityByUnitId[unit.id] ?? null;
63
+ }
64
+ export function getResolvedInventoryStatus(unit, availabilityByUnitId) {
65
+ if (!unit) {
66
+ return null;
67
+ }
68
+ return (getInventoryAvailability(unit, availabilityByUnitId)?.status ?? unit.status);
69
+ }
70
+ export function canCheckoutInventoryUnit(unit, availabilityByUnitId) {
71
+ return canCheckoutInventoryQuantity(unit, availabilityByUnitId, 1);
72
+ }
73
+ export function canCheckoutInventoryQuantity(unit, availabilityByUnitId, quantity) {
74
+ if (!unit) {
75
+ return false;
76
+ }
77
+ const availability = getInventoryAvailability(unit, availabilityByUnitId);
78
+ if (!availability?.canCheckout) {
79
+ return false;
80
+ }
81
+ if (availability.inventoryMode !== "shares") {
82
+ return quantity <= 1;
83
+ }
84
+ if (!Number.isFinite(quantity) || quantity <= 0) {
85
+ return false;
86
+ }
87
+ return (availability.share?.remaining ?? 0) >= quantity;
88
+ }
89
+ export function getResolvedInventoryStatusLabel(unit, availabilityByUnitId, statusLabels) {
90
+ if (!unit) {
91
+ return "";
92
+ }
93
+ const availability = getInventoryAvailability(unit, availabilityByUnitId);
94
+ if (availability?.isSoldOutByShares) {
95
+ return "Agotado";
96
+ }
97
+ const resolvedStatus = availability?.status ?? unit.status;
98
+ if (resolvedStatus === "unavailable") {
99
+ return statusLabels.unavailable ?? "No disponible";
100
+ }
101
+ return statusLabels[resolvedStatus] ?? resolvedStatus;
102
+ }
103
+ export function getResolvedShareSummary(unit, availabilityByUnitId) {
104
+ return getInventoryAvailability(unit, availabilityByUnitId)?.share ?? null;
105
+ }
@@ -0,0 +1,16 @@
1
+ import type { AppContent, InventoryAvailabilityByUnitId, InventoryShareAvailability, InventoryStatus } from "./types.js";
2
+ export interface LiveInventoryAvailability {
3
+ status: InventoryStatus;
4
+ canCheckout: boolean;
5
+ isSoldOutByShares?: boolean;
6
+ share?: InventoryShareAvailability;
7
+ }
8
+ export type LiveInventoryAvailabilityByUnitId = Record<string, LiveInventoryAvailability>;
9
+ export declare function toInventoryStatus(value: string | undefined): InventoryStatus;
10
+ export declare function buildLiveInventoryAvailabilityFromLot(lot: {
11
+ disponibilidad?: string;
12
+ shares?: number;
13
+ sharesSold?: number;
14
+ }): LiveInventoryAvailability;
15
+ export declare function getLiveInventoryUnitIds(content: AppContent): string[];
16
+ export declare function mergeLiveInventoryAvailabilityByUnitId(base: InventoryAvailabilityByUnitId, live: LiveInventoryAvailabilityByUnitId | null | undefined): InventoryAvailabilityByUnitId;
@@ -0,0 +1,88 @@
1
+ export function toInventoryStatus(value) {
2
+ if (value === "Apartado")
3
+ return "reserved";
4
+ if (value === "Vendido")
5
+ return "sold";
6
+ if (value === "No Disponible")
7
+ return "unavailable";
8
+ return "available";
9
+ }
10
+ export function buildLiveInventoryAvailabilityFromLot(lot) {
11
+ const status = toInventoryStatus(lot.disponibilidad);
12
+ if (!lot.shares || lot.shares <= 0) {
13
+ return {
14
+ status,
15
+ canCheckout: status === "available",
16
+ };
17
+ }
18
+ const total = lot.shares ?? 1;
19
+ const soldBaseline = Math.min(lot.sharesSold ?? 0, lot.shares ?? lot.sharesSold ?? 0);
20
+ const soldTotal = soldBaseline;
21
+ const remaining = Math.max(total - soldTotal, 0);
22
+ const share = {
23
+ total,
24
+ soldBaseline,
25
+ soldPaid: 0,
26
+ soldTotal,
27
+ remaining,
28
+ };
29
+ if (status !== "available") {
30
+ return {
31
+ status,
32
+ canCheckout: false,
33
+ isSoldOutByShares: remaining <= 0,
34
+ share,
35
+ };
36
+ }
37
+ return {
38
+ status: remaining > 0 ? "available" : "sold",
39
+ canCheckout: remaining > 0,
40
+ isSoldOutByShares: remaining <= 0,
41
+ share,
42
+ };
43
+ }
44
+ export function getLiveInventoryUnitIds(content) {
45
+ return Array.from(new Set(content.inventory.map((unit) => unit.id.trim()).filter(Boolean))).toSorted();
46
+ }
47
+ function sharesEqual(left, right) {
48
+ if (!left && !right)
49
+ return true;
50
+ if (!left || !right)
51
+ return false;
52
+ return (left.total === right.total &&
53
+ left.soldBaseline === right.soldBaseline &&
54
+ left.soldPaid === right.soldPaid &&
55
+ left.soldTotal === right.soldTotal &&
56
+ left.remaining === right.remaining);
57
+ }
58
+ export function mergeLiveInventoryAvailabilityByUnitId(base, live) {
59
+ if (!live) {
60
+ return base;
61
+ }
62
+ let changed = false;
63
+ const next = { ...base };
64
+ for (const [unitId, liveAvailability] of Object.entries(live)) {
65
+ const current = base[unitId];
66
+ if (!current)
67
+ continue;
68
+ const merged = {
69
+ ...current,
70
+ status: liveAvailability.status,
71
+ canCheckout: liveAvailability.canCheckout,
72
+ ...(liveAvailability.isSoldOutByShares !== undefined
73
+ ? { isSoldOutByShares: liveAvailability.isSoldOutByShares }
74
+ : {}),
75
+ ...(liveAvailability.share !== undefined
76
+ ? { share: liveAvailability.share }
77
+ : {}),
78
+ };
79
+ if (current.status !== merged.status ||
80
+ current.canCheckout !== merged.canCheckout ||
81
+ current.isSoldOutByShares !== merged.isSoldOutByShares ||
82
+ !sharesEqual(current.share, merged.share)) {
83
+ next[unitId] = merged;
84
+ changed = true;
85
+ }
86
+ }
87
+ return changed ? next : base;
88
+ }
@@ -0,0 +1,22 @@
1
+ import type { CashbackPaymentScheme, FinanceRules, InstallmentPaymentScheme, InventoryUnit, PaymentSchemeDefinition, RangeRule } from "./types.js";
2
+ export declare const MAX_CASHBACK_MONTHS = 30;
3
+ export declare function getUnitListPriceMXN(unit: InventoryUnit): number;
4
+ export declare function getUnitEstimatedRentalAnnualMXN(unit: InventoryUnit, rentalRate: number, includeIva: boolean): number;
5
+ export declare function getUnitEstimatedRentalMonthlyMXN(unit: InventoryUnit, rentalRate: number, includeIva: boolean): number;
6
+ export declare function getFinanceRulesRentalRateForUnit(unit: InventoryUnit, financeRules: FinanceRules): number;
7
+ export declare function getUnitDeliveryDateParts(unit: InventoryUnit): {
8
+ year: number;
9
+ month: number;
10
+ } | null;
11
+ export declare function getUnitDeliveryLabel(unit: InventoryUnit): string;
12
+ export declare function getUnitDeliveryMonths(unit: InventoryUnit, referenceDate?: Date): number;
13
+ export declare function isCashbackScheme(scheme: PaymentSchemeDefinition): scheme is CashbackPaymentScheme;
14
+ export declare function isInstallmentScheme(scheme: PaymentSchemeDefinition): scheme is InstallmentPaymentScheme;
15
+ export declare function getPaymentSchemeById(paymentSchemes: PaymentSchemeDefinition[], schemeId: string): PaymentSchemeDefinition | null;
16
+ export declare function getActivePaymentSchemes(paymentSchemes: PaymentSchemeDefinition[]): PaymentSchemeDefinition[];
17
+ export declare function getAllowedPaymentSchemes(unit: InventoryUnit, paymentSchemes: PaymentSchemeDefinition[]): PaymentSchemeDefinition[];
18
+ export declare function getDefaultSchemeForUnit(unit: InventoryUnit, paymentSchemes: PaymentSchemeDefinition[]): PaymentSchemeDefinition;
19
+ export declare function getResolvedSchemeForUnit(unit: InventoryUnit, paymentSchemes: PaymentSchemeDefinition[], requestedSchemeId: string | null | undefined): PaymentSchemeDefinition;
20
+ export declare function clampToRange(value: number, range: RangeRule): number;
21
+ export declare function getSchemeDownPaymentRange(scheme: PaymentSchemeDefinition | null): RangeRule | null;
22
+ export declare function getDefaultDownPaymentPctForScheme(scheme: PaymentSchemeDefinition | null, financeRules: FinanceRules): number;
@@ -0,0 +1,99 @@
1
+ import { parseDeliveryDateParts } from "./delivery-date.js";
2
+ import { isCorporateRentalUnit } from "./corporate-rent.js";
3
+ import { applyIvaModeMXN } from "./tax.js";
4
+ function roundCurrency(value) {
5
+ return Math.round(value * 100) / 100;
6
+ }
7
+ const MONTH_LABELS = [
8
+ "Enero",
9
+ "Febrero",
10
+ "Marzo",
11
+ "Abril",
12
+ "Mayo",
13
+ "Junio",
14
+ "Julio",
15
+ "Agosto",
16
+ "Septiembre",
17
+ "Octubre",
18
+ "Noviembre",
19
+ "Diciembre",
20
+ ];
21
+ export const MAX_CASHBACK_MONTHS = 30;
22
+ export function getUnitListPriceMXN(unit) {
23
+ return roundCurrency(unit.areaM2 * unit.pricePerM2MXN);
24
+ }
25
+ export function getUnitEstimatedRentalAnnualMXN(unit, rentalRate, includeIva) {
26
+ return applyIvaModeMXN(getUnitListPriceMXN(unit), includeIva) * rentalRate;
27
+ }
28
+ export function getUnitEstimatedRentalMonthlyMXN(unit, rentalRate, includeIva) {
29
+ return getUnitEstimatedRentalAnnualMXN(unit, rentalRate, includeIva) / 12;
30
+ }
31
+ export function getFinanceRulesRentalRateForUnit(unit, financeRules) {
32
+ return isCorporateRentalUnit(unit)
33
+ ? financeRules.accionesRentalRate
34
+ : financeRules.rentalRate;
35
+ }
36
+ export function getUnitDeliveryDateParts(unit) {
37
+ return parseDeliveryDateParts(unit.deliveryDate);
38
+ }
39
+ export function getUnitDeliveryLabel(unit) {
40
+ const parts = getUnitDeliveryDateParts(unit);
41
+ if (!parts) {
42
+ return "Por definir";
43
+ }
44
+ return `${MONTH_LABELS[parts.month - 1]} ${parts.year}`;
45
+ }
46
+ export function getUnitDeliveryMonths(unit, referenceDate = new Date()) {
47
+ const parts = getUnitDeliveryDateParts(unit);
48
+ if (!parts) {
49
+ return 1;
50
+ }
51
+ const currentYear = referenceDate.getFullYear();
52
+ const currentMonth = referenceDate.getMonth() + 1;
53
+ const diff = (parts.year - currentYear) * 12 + (parts.month - currentMonth);
54
+ return Math.max(diff, 1);
55
+ }
56
+ export function isCashbackScheme(scheme) {
57
+ return scheme.type === "cashback";
58
+ }
59
+ export function isInstallmentScheme(scheme) {
60
+ return scheme.type === "installment";
61
+ }
62
+ export function getPaymentSchemeById(paymentSchemes, schemeId) {
63
+ return paymentSchemes.find((scheme) => scheme.id === schemeId) ?? null;
64
+ }
65
+ export function getActivePaymentSchemes(paymentSchemes) {
66
+ return paymentSchemes.filter((scheme) => scheme.active);
67
+ }
68
+ export function getAllowedPaymentSchemes(unit, paymentSchemes) {
69
+ return unit.allowedSchemeIds
70
+ .map((schemeId) => getPaymentSchemeById(paymentSchemes, schemeId))
71
+ .filter((scheme) => Boolean(scheme?.active));
72
+ }
73
+ export function getDefaultSchemeForUnit(unit, paymentSchemes) {
74
+ const allowedSchemes = getAllowedPaymentSchemes(unit, paymentSchemes);
75
+ return (allowedSchemes.find((scheme) => scheme.id === unit.defaultSchemeId) ??
76
+ allowedSchemes[0] ??
77
+ getActivePaymentSchemes(paymentSchemes)[0] ??
78
+ null);
79
+ }
80
+ export function getResolvedSchemeForUnit(unit, paymentSchemes, requestedSchemeId) {
81
+ const allowedSchemes = getAllowedPaymentSchemes(unit, paymentSchemes);
82
+ return (allowedSchemes.find((scheme) => scheme.id === requestedSchemeId) ??
83
+ getDefaultSchemeForUnit(unit, paymentSchemes));
84
+ }
85
+ export function clampToRange(value, range) {
86
+ const clamped = Math.min(range.max, Math.max(range.min, value));
87
+ const steps = Math.round((clamped - range.min) / range.step);
88
+ return range.min + steps * range.step;
89
+ }
90
+ export function getSchemeDownPaymentRange(scheme) {
91
+ return scheme && isCashbackScheme(scheme) ? scheme.downPaymentRange : null;
92
+ }
93
+ export function getDefaultDownPaymentPctForScheme(scheme, financeRules) {
94
+ const range = getSchemeDownPaymentRange(scheme);
95
+ if (!range) {
96
+ return financeRules.defaults.downPaymentPct;
97
+ }
98
+ return clampToRange(financeRules.defaults.downPaymentPct, range);
99
+ }
@@ -0,0 +1,3 @@
1
+ import type { PostDeliveryValueRow, ProjectionPoint } from "./types.js";
2
+ export declare function buildAnnualPostDeliveryValueRows(projectionPoints: ProjectionPoint[]): PostDeliveryValueRow[];
3
+ export declare function buildQuarterlyPostDeliveryValueRows(projectionPoints: ProjectionPoint[]): PostDeliveryValueRow[];
@@ -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
+ }