@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
package/dist/http.js
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
export class CotizadorApiError extends Error {
|
|
2
|
+
constructor(response, body) {
|
|
3
|
+
super(body.message);
|
|
4
|
+
this.name = "CotizadorApiError";
|
|
5
|
+
this.code = body.code;
|
|
6
|
+
this.fieldErrors = body.fieldErrors;
|
|
7
|
+
this.response = response;
|
|
8
|
+
this.status = response.status;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
function trimSlashes(value) {
|
|
12
|
+
return value.replace(/\/+$/, "");
|
|
13
|
+
}
|
|
14
|
+
function normalizeBaseUrl(baseUrl) {
|
|
15
|
+
const trimmed = trimSlashes(baseUrl.trim());
|
|
16
|
+
if (!trimmed) {
|
|
17
|
+
throw new Error("Cotizador SDK baseUrl is required.");
|
|
18
|
+
}
|
|
19
|
+
return trimmed;
|
|
20
|
+
}
|
|
21
|
+
function getFetch(customFetch) {
|
|
22
|
+
const fetchImpl = customFetch ?? globalThis.fetch;
|
|
23
|
+
if (!fetchImpl) {
|
|
24
|
+
throw new Error("Cotizador SDK requires a fetch implementation in this runtime.");
|
|
25
|
+
}
|
|
26
|
+
return fetchImpl;
|
|
27
|
+
}
|
|
28
|
+
function appendLookup(params, lookup) {
|
|
29
|
+
if (lookup) {
|
|
30
|
+
params.set("lookup", lookup);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function appendInstanceSlug(params, instanceSlug) {
|
|
34
|
+
if (instanceSlug) {
|
|
35
|
+
params.set("instanceSlug", instanceSlug);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function buildQuery(args) {
|
|
39
|
+
const params = new URLSearchParams();
|
|
40
|
+
appendLookup(params, args.lookup);
|
|
41
|
+
appendInstanceSlug(params, args.instanceSlug);
|
|
42
|
+
if (args.unitIds && args.unitIds.length > 0) {
|
|
43
|
+
params.set("unitIds", args.unitIds.join(","));
|
|
44
|
+
}
|
|
45
|
+
if (args.includeShapes === false) {
|
|
46
|
+
params.set("includeShapes", "false");
|
|
47
|
+
}
|
|
48
|
+
const query = params.toString();
|
|
49
|
+
return query ? `?${query}` : "";
|
|
50
|
+
}
|
|
51
|
+
function encodePathSegment(value) {
|
|
52
|
+
return encodeURIComponent(value);
|
|
53
|
+
}
|
|
54
|
+
function credentialHeaders(credential) {
|
|
55
|
+
if (!credential) {
|
|
56
|
+
return [];
|
|
57
|
+
}
|
|
58
|
+
return credential.kind === "apiKey"
|
|
59
|
+
? [["x-cotizador-api-key", credential.value]]
|
|
60
|
+
: [["x-cotizador-session", credential.value]];
|
|
61
|
+
}
|
|
62
|
+
function buildHeaders(headers, credential) {
|
|
63
|
+
const requestHeaders = new Headers(headers);
|
|
64
|
+
requestHeaders.set("Accept", requestHeaders.get("Accept") ?? "application/json");
|
|
65
|
+
for (const [name, value] of credentialHeaders(credential)) {
|
|
66
|
+
requestHeaders.set(name, value);
|
|
67
|
+
}
|
|
68
|
+
return requestHeaders;
|
|
69
|
+
}
|
|
70
|
+
async function readJsonResponse(response) {
|
|
71
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
72
|
+
const isJson = contentType.includes("application/json");
|
|
73
|
+
const body = isJson ? await response.json() : null;
|
|
74
|
+
if (!response.ok) {
|
|
75
|
+
throw new CotizadorApiError(response, {
|
|
76
|
+
message: body && typeof body.message === "string"
|
|
77
|
+
? body.message
|
|
78
|
+
: `Cotizador API request failed with status ${response.status}.`,
|
|
79
|
+
code: body && typeof body.code === "string" ? body.code : undefined,
|
|
80
|
+
fieldErrors: body &&
|
|
81
|
+
typeof body.fieldErrors === "object" &&
|
|
82
|
+
body.fieldErrors !== null
|
|
83
|
+
? body.fieldErrors
|
|
84
|
+
: undefined,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
return body;
|
|
88
|
+
}
|
|
89
|
+
export function createCotizadorApiRequester(options) {
|
|
90
|
+
const baseUrl = normalizeBaseUrl(options.baseUrl);
|
|
91
|
+
const fetchImpl = getFetch(options.fetch);
|
|
92
|
+
async function requestJson(path, init = {}) {
|
|
93
|
+
const { credential, headers, ...requestInit } = init;
|
|
94
|
+
const response = await fetchImpl(`${baseUrl}${path}`, {
|
|
95
|
+
...requestInit,
|
|
96
|
+
headers: buildHeaders(headers, credential),
|
|
97
|
+
});
|
|
98
|
+
return await readJsonResponse(response);
|
|
99
|
+
}
|
|
100
|
+
return {
|
|
101
|
+
getAgentGuide: () => requestJson("/agent.json"),
|
|
102
|
+
getOpenApi: () => requestJson("/openapi.json"),
|
|
103
|
+
listInstances: (args) => requestJson(`/sites/${encodePathSegment(args.siteKey)}/instances${buildQuery({
|
|
104
|
+
lookup: args.lookup,
|
|
105
|
+
})}`),
|
|
106
|
+
getSnapshot: (args) => requestJson(`/sites/${encodePathSegment(args.siteKey)}/snapshot${buildQuery(args)}`),
|
|
107
|
+
getInventory: (args) => requestJson(`/sites/${encodePathSegment(args.siteKey)}/inventory${buildQuery(args)}`),
|
|
108
|
+
getSitemap: (args) => requestJson(`/sites/${encodePathSegment(args.siteKey)}/sitemap${buildQuery(args)}`),
|
|
109
|
+
getPaymentMethods: (args) => requestJson(`/sites/${encodePathSegment(args.siteKey)}/payment-methods${buildQuery(args)}`),
|
|
110
|
+
createSession: (args, credential) => requestJson("/sessions", {
|
|
111
|
+
body: JSON.stringify({
|
|
112
|
+
lookup: args.lookup ?? "host",
|
|
113
|
+
origin: args.origin,
|
|
114
|
+
siteKey: args.siteKey,
|
|
115
|
+
}),
|
|
116
|
+
credential,
|
|
117
|
+
headers: { "Content-Type": "application/json" },
|
|
118
|
+
method: "POST",
|
|
119
|
+
}),
|
|
120
|
+
createReservation: (args, credential) => requestJson(`/sites/${encodePathSegment(args.siteKey)}/reservations${buildQuery({
|
|
121
|
+
lookup: args.lookup,
|
|
122
|
+
})}`, {
|
|
123
|
+
body: JSON.stringify({
|
|
124
|
+
...args.request,
|
|
125
|
+
instanceSlug: args.request.instanceSlug ?? args.instanceSlug,
|
|
126
|
+
}),
|
|
127
|
+
credential,
|
|
128
|
+
headers: { "Content-Type": "application/json" },
|
|
129
|
+
method: "POST",
|
|
130
|
+
}),
|
|
131
|
+
getReservation: (args, credential) => requestJson(`/reservations/${encodePathSegment(args.externalReference)}`, { credential }),
|
|
132
|
+
};
|
|
133
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { CotizadorApiError } from "./http.js";
|
|
2
|
+
import type { CotizadorApiBaseOptions, CreatePublicReservationRequest, GetPublicReservationRequest } from "./types.js";
|
|
3
|
+
export { CotizadorApiError };
|
|
4
|
+
export type * from "./types.js";
|
|
5
|
+
export declare function createCotizadorClient(options: CotizadorApiBaseOptions): {
|
|
6
|
+
createReservation: (args: CreatePublicReservationRequest) => Promise<import("./types.js").PublicReservationStatus>;
|
|
7
|
+
getAgentGuide: () => Promise<Record<string, unknown>>;
|
|
8
|
+
getInventory: (args: import("./types.js").CotizadorInventoryRequest) => Promise<import("./types.js").PublicInventoryResponse>;
|
|
9
|
+
getOpenApi: () => Promise<Record<string, unknown>>;
|
|
10
|
+
getPaymentMethods: (args: import("./types.js").CotizadorSiteRequest) => Promise<import("./types.js").PublicPaymentMethodsResponse>;
|
|
11
|
+
getReservation: (args: GetPublicReservationRequest) => Promise<import("./types.js").PublicReservationStatus>;
|
|
12
|
+
getSitemap: (args: import("./types.js").CotizadorSitemapRequest) => Promise<import("./types.js").PublicSitemapResponse>;
|
|
13
|
+
getSnapshot: (args: import("./types.js").CotizadorSiteRequest) => Promise<import("./types.js").PublicSnapshotResponse>;
|
|
14
|
+
listInstances: (args: Pick<import("./types.js").CotizadorSiteRequest, "lookup" | "siteKey">) => Promise<import("./types.js").PublicInstancesResponse>;
|
|
15
|
+
};
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { CotizadorApiError, createCotizadorApiRequester } from "./http.js";
|
|
2
|
+
export { CotizadorApiError };
|
|
3
|
+
export function createCotizadorClient(options) {
|
|
4
|
+
const api = createCotizadorApiRequester(options);
|
|
5
|
+
function requireSessionToken(sessionToken) {
|
|
6
|
+
if (!sessionToken) {
|
|
7
|
+
throw new Error("A cotizador session token is required for browser write methods.");
|
|
8
|
+
}
|
|
9
|
+
return { kind: "session", value: sessionToken };
|
|
10
|
+
}
|
|
11
|
+
return {
|
|
12
|
+
createReservation: async (args) => api.createReservation(args, requireSessionToken(args.sessionToken)),
|
|
13
|
+
getAgentGuide: api.getAgentGuide,
|
|
14
|
+
getInventory: api.getInventory,
|
|
15
|
+
getOpenApi: api.getOpenApi,
|
|
16
|
+
getPaymentMethods: api.getPaymentMethods,
|
|
17
|
+
getReservation: async (args) => api.getReservation(args, requireSessionToken(args.sessionToken)),
|
|
18
|
+
getSitemap: api.getSitemap,
|
|
19
|
+
getSnapshot: api.getSnapshot,
|
|
20
|
+
listInstances: api.listInstances,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export declare const INSTALLMENT_DOWN_PAYMENT_RANGE: {
|
|
2
|
+
readonly min: 20;
|
|
3
|
+
readonly max: 100;
|
|
4
|
+
readonly step: 5;
|
|
5
|
+
};
|
|
6
|
+
export declare const INSTALLMENT_MONTHLY_BASE_RANGE: {
|
|
7
|
+
readonly min: 20;
|
|
8
|
+
readonly max: 40;
|
|
9
|
+
readonly step: 5;
|
|
10
|
+
};
|
|
11
|
+
export declare const INSTALLMENT_DEFAULT_DOWN_PAYMENT_PCT = 40;
|
|
12
|
+
export declare const INSTALLMENT_DEFAULT_MONTHLY_PCT = 20;
|
|
13
|
+
export declare const INSTALLMENT_DISCOUNT_THRESHOLD_PCT = 30;
|
|
14
|
+
export declare const INSTALLMENT_DISCOUNT_INCREMENT_PCT = 10;
|
|
15
|
+
export declare const INSTALLMENT_DISCOUNT_STEP_PCT = 1.25;
|
|
16
|
+
export declare function getInstallmentDiscountPct(downPaymentPct: number): number;
|
|
17
|
+
export declare function clampInstallmentDownPaymentPct(value: number): number;
|
|
18
|
+
export declare function getInstallmentMonthlyRange(downPaymentPct: number): {
|
|
19
|
+
min: number;
|
|
20
|
+
max: number;
|
|
21
|
+
step: 5;
|
|
22
|
+
};
|
|
23
|
+
export declare function clampInstallmentMonthlyPct(monthlyPct: number, downPaymentPct: number): number;
|
|
24
|
+
export interface InstallmentAllocation {
|
|
25
|
+
downPaymentPct: number;
|
|
26
|
+
monthlyPct: number;
|
|
27
|
+
deferredPct: number;
|
|
28
|
+
deliveryPct: number;
|
|
29
|
+
}
|
|
30
|
+
export declare function getInstallmentAllocation(downPaymentPct: number, monthlyPct: number): InstallmentAllocation;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
export const INSTALLMENT_DOWN_PAYMENT_RANGE = {
|
|
2
|
+
min: 20,
|
|
3
|
+
max: 100,
|
|
4
|
+
step: 5,
|
|
5
|
+
};
|
|
6
|
+
export const INSTALLMENT_MONTHLY_BASE_RANGE = {
|
|
7
|
+
min: 20,
|
|
8
|
+
max: 40,
|
|
9
|
+
step: 5,
|
|
10
|
+
};
|
|
11
|
+
export const INSTALLMENT_DEFAULT_DOWN_PAYMENT_PCT = 40;
|
|
12
|
+
export const INSTALLMENT_DEFAULT_MONTHLY_PCT = 20;
|
|
13
|
+
export const INSTALLMENT_DISCOUNT_THRESHOLD_PCT = 30;
|
|
14
|
+
export const INSTALLMENT_DISCOUNT_INCREMENT_PCT = 10;
|
|
15
|
+
export const INSTALLMENT_DISCOUNT_STEP_PCT = 1.25;
|
|
16
|
+
export function getInstallmentDiscountPct(downPaymentPct) {
|
|
17
|
+
if (downPaymentPct < INSTALLMENT_DISCOUNT_THRESHOLD_PCT) {
|
|
18
|
+
return 0;
|
|
19
|
+
}
|
|
20
|
+
const steps = Math.floor((downPaymentPct - INSTALLMENT_DISCOUNT_THRESHOLD_PCT) /
|
|
21
|
+
INSTALLMENT_DISCOUNT_INCREMENT_PCT);
|
|
22
|
+
return (steps + 1) * INSTALLMENT_DISCOUNT_STEP_PCT;
|
|
23
|
+
}
|
|
24
|
+
const INSTALLMENT_BASE_DOWN_PAYMENT_PCT = INSTALLMENT_DOWN_PAYMENT_RANGE.min;
|
|
25
|
+
const INSTALLMENT_BASE_MONTHLY_PLUS_DEFERRED_PCT = INSTALLMENT_MONTHLY_BASE_RANGE.max;
|
|
26
|
+
function clampToRangeBounds(value, min, max, step) {
|
|
27
|
+
const clamped = Math.min(max, Math.max(min, value));
|
|
28
|
+
return min + Math.round((clamped - min) / step) * step;
|
|
29
|
+
}
|
|
30
|
+
export function clampInstallmentDownPaymentPct(value) {
|
|
31
|
+
return clampToRangeBounds(value, INSTALLMENT_DOWN_PAYMENT_RANGE.min, INSTALLMENT_DOWN_PAYMENT_RANGE.max, INSTALLMENT_DOWN_PAYMENT_RANGE.step);
|
|
32
|
+
}
|
|
33
|
+
export function getInstallmentMonthlyRange(downPaymentPct) {
|
|
34
|
+
const downPct = clampInstallmentDownPaymentPct(downPaymentPct);
|
|
35
|
+
const totalRemaining = 100 - downPct;
|
|
36
|
+
const max = Math.min(INSTALLMENT_MONTHLY_BASE_RANGE.max, totalRemaining);
|
|
37
|
+
const min = Math.min(INSTALLMENT_MONTHLY_BASE_RANGE.min, max);
|
|
38
|
+
return { min, max, step: INSTALLMENT_MONTHLY_BASE_RANGE.step };
|
|
39
|
+
}
|
|
40
|
+
export function clampInstallmentMonthlyPct(monthlyPct, downPaymentPct) {
|
|
41
|
+
const range = getInstallmentMonthlyRange(downPaymentPct);
|
|
42
|
+
return clampToRangeBounds(monthlyPct, range.min, range.max, range.step);
|
|
43
|
+
}
|
|
44
|
+
export function getInstallmentAllocation(downPaymentPct, monthlyPct) {
|
|
45
|
+
const downPct = clampInstallmentDownPaymentPct(downPaymentPct);
|
|
46
|
+
const baseMonthly = clampInstallmentMonthlyPct(monthlyPct, downPct);
|
|
47
|
+
const baseDeferred = INSTALLMENT_BASE_MONTHLY_PLUS_DEFERRED_PCT - baseMonthly;
|
|
48
|
+
const baseDelivery = 100 -
|
|
49
|
+
INSTALLMENT_BASE_DOWN_PAYMENT_PCT -
|
|
50
|
+
INSTALLMENT_BASE_MONTHLY_PLUS_DEFERRED_PCT;
|
|
51
|
+
let extra = Math.max(0, downPct - INSTALLMENT_BASE_DOWN_PAYMENT_PCT);
|
|
52
|
+
let delivery = baseDelivery;
|
|
53
|
+
let deferred = baseDeferred;
|
|
54
|
+
let monthly = baseMonthly;
|
|
55
|
+
const fromDeferred = Math.min(deferred, extra);
|
|
56
|
+
deferred -= fromDeferred;
|
|
57
|
+
extra -= fromDeferred;
|
|
58
|
+
const fromDelivery = Math.min(delivery, extra);
|
|
59
|
+
delivery -= fromDelivery;
|
|
60
|
+
extra -= fromDelivery;
|
|
61
|
+
const fromMonthly = Math.min(monthly, extra);
|
|
62
|
+
monthly -= fromMonthly;
|
|
63
|
+
return {
|
|
64
|
+
downPaymentPct: downPct,
|
|
65
|
+
monthlyPct: monthly,
|
|
66
|
+
deferredPct: deferred,
|
|
67
|
+
deliveryPct: delivery,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { AppContent, InventoryAvailability, InventoryAvailabilityByUnitId, InventoryStatus, InventoryStatusLabels, InventoryUnit } from "./types.js";
|
|
2
|
+
type PaidShareSalesByProductTypeId = Record<string, number>;
|
|
3
|
+
export declare function buildInventoryAvailabilityByUnitId(content: AppContent, paidShareSalesByProductTypeId?: PaidShareSalesByProductTypeId): InventoryAvailabilityByUnitId;
|
|
4
|
+
export declare function getInventoryAvailability(unit: InventoryUnit | null | undefined, availabilityByUnitId: InventoryAvailabilityByUnitId): InventoryAvailability | null;
|
|
5
|
+
export declare function getResolvedInventoryStatus(unit: InventoryUnit | null | undefined, availabilityByUnitId: InventoryAvailabilityByUnitId): InventoryStatus | null;
|
|
6
|
+
export declare function canCheckoutInventoryUnit(unit: InventoryUnit | null | undefined, availabilityByUnitId: InventoryAvailabilityByUnitId): boolean;
|
|
7
|
+
export declare function canCheckoutInventoryQuantity(unit: InventoryUnit | null | undefined, availabilityByUnitId: InventoryAvailabilityByUnitId, quantity: number): boolean;
|
|
8
|
+
export declare function getResolvedInventoryStatusLabel(unit: InventoryUnit | null | undefined, availabilityByUnitId: InventoryAvailabilityByUnitId, statusLabels: InventoryStatusLabels): string;
|
|
9
|
+
export declare function getResolvedShareSummary(unit: InventoryUnit | null | undefined, availabilityByUnitId: InventoryAvailabilityByUnitId): import("./types.js").InventoryShareAvailability | null;
|
|
10
|
+
export {};
|
|
@@ -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[];
|