@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.
- package/README.md +154 -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 +29 -0
- package/dist/http.js +123 -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 +17 -0
- package/dist/server.js +18 -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 +511 -0
- package/dist/types.js +1 -0
- package/package.json +51 -0
package/README.md
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# Desarrollos SDK
|
|
2
|
+
|
|
3
|
+
Framework-agnostic TypeScript helpers for third-party websites that integrate
|
|
4
|
+
with Grupo LAPA developments through the public API.
|
|
5
|
+
|
|
6
|
+
## Installation
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
npm install @grupolapa/desarrollos-sdk
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
pnpm add @grupolapa/desarrollos-sdk
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
The public API base URL is:
|
|
17
|
+
|
|
18
|
+
```txt
|
|
19
|
+
https://grupolapa.com/api/public/v1/desarrollos
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Agent-readable integration instructions are available at:
|
|
23
|
+
|
|
24
|
+
```txt
|
|
25
|
+
https://grupolapa.com/api/public/v1/desarrollos/agent.json
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Browser Reads and Quotes
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
import { createDesarrollosClient } from "@grupolapa/desarrollos-sdk";
|
|
32
|
+
import {
|
|
33
|
+
calculateQuote,
|
|
34
|
+
getResolvedSchemeForUnit,
|
|
35
|
+
} from "@grupolapa/desarrollos-sdk/quote";
|
|
36
|
+
import {
|
|
37
|
+
getSelectableUnitHotspots,
|
|
38
|
+
resolveInitialSitemapMap,
|
|
39
|
+
} from "@grupolapa/desarrollos-sdk/sitemap";
|
|
40
|
+
|
|
41
|
+
const client = createDesarrollosClient({
|
|
42
|
+
baseUrl: "https://grupolapa.com/api/public/v1/desarrollos",
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const desarrolloKey = "viamare";
|
|
46
|
+
const [{ snapshot }, inventory, sitemap, paymentMethods] = await Promise.all([
|
|
47
|
+
client.getSnapshot({ desarrolloKey }),
|
|
48
|
+
client.getInventory({ desarrolloKey }),
|
|
49
|
+
client.getSitemap({ desarrolloKey }),
|
|
50
|
+
client.getPaymentMethods({ desarrolloKey }),
|
|
51
|
+
]);
|
|
52
|
+
|
|
53
|
+
const map = resolveInitialSitemapMap(sitemap);
|
|
54
|
+
const hotspots = map ? getSelectableUnitHotspots(map) : [];
|
|
55
|
+
const unit = inventory.inventory.find(
|
|
56
|
+
(item) => item.id === hotspots[0]?.unitId,
|
|
57
|
+
);
|
|
58
|
+
const scheme = unit
|
|
59
|
+
? getResolvedSchemeForUnit(unit, snapshot.content.paymentSchemes, null)
|
|
60
|
+
: null;
|
|
61
|
+
|
|
62
|
+
const quote =
|
|
63
|
+
unit && scheme
|
|
64
|
+
? calculateQuote(
|
|
65
|
+
unit,
|
|
66
|
+
{
|
|
67
|
+
unitId: unit.id,
|
|
68
|
+
scheme: scheme.id,
|
|
69
|
+
plusvaliaAnnualPct:
|
|
70
|
+
snapshot.content.financeRules.defaults.plusvaliaAnnualPct,
|
|
71
|
+
postDeliveryYears:
|
|
72
|
+
snapshot.content.financeRules.defaults.postDeliveryYears,
|
|
73
|
+
downPaymentPct: snapshot.content.financeRules.defaults.downPaymentPct,
|
|
74
|
+
includeIva: false,
|
|
75
|
+
},
|
|
76
|
+
scheme,
|
|
77
|
+
snapshot.content.financeRules,
|
|
78
|
+
)
|
|
79
|
+
: null;
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Trusted Server Flow
|
|
83
|
+
|
|
84
|
+
Keep `lapa_dev_<prefix>_<secret>` API keys on a server. Use the trusted server
|
|
85
|
+
client to list allowed developments and mint short-lived browser session tokens.
|
|
86
|
+
Browser reservation writes should use only those session tokens.
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
import { createDesarrollosServerClient } from "@grupolapa/desarrollos-sdk/server";
|
|
90
|
+
|
|
91
|
+
const serverClient = createDesarrollosServerClient({
|
|
92
|
+
baseUrl: "https://grupolapa.com/api/public/v1/desarrollos",
|
|
93
|
+
apiKey: process.env.LAPA_DESARROLLOS_API_KEY!,
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
export async function createBrowserSession(origin: string) {
|
|
97
|
+
return await serverClient.createBrowserSession({
|
|
98
|
+
desarrolloKey: "viamare",
|
|
99
|
+
origin,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
import { createDesarrollosClient } from "@grupolapa/desarrollos-sdk";
|
|
106
|
+
|
|
107
|
+
const browserClient = createDesarrollosClient({
|
|
108
|
+
baseUrl: "https://grupolapa.com/api/public/v1/desarrollos",
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
await browserClient.createReservation({
|
|
112
|
+
desarrolloKey: "viamare",
|
|
113
|
+
sessionToken,
|
|
114
|
+
request: {
|
|
115
|
+
items: [{ unitId: "unit_1" }],
|
|
116
|
+
payment: {
|
|
117
|
+
provider: "stripe",
|
|
118
|
+
returnUrl: "https://partner.example.com/return",
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
});
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## Sitemap Rendering
|
|
125
|
+
|
|
126
|
+
The API returns image and overlay asset URLs plus SVG-native shape metadata.
|
|
127
|
+
The SDK does not mutate the DOM. Use the sitemap helpers to choose maps and
|
|
128
|
+
resolve unit hotspots, then render the overlay with the frontend framework of
|
|
129
|
+
your choice.
|
|
130
|
+
|
|
131
|
+
```ts
|
|
132
|
+
import {
|
|
133
|
+
getShapeForUnit,
|
|
134
|
+
getSitemapMapAssetPlan,
|
|
135
|
+
resolveInitialSitemapMap,
|
|
136
|
+
} from "@grupolapa/desarrollos-sdk/sitemap";
|
|
137
|
+
|
|
138
|
+
const map = resolveInitialSitemapMap(sitemap);
|
|
139
|
+
if (map) {
|
|
140
|
+
const assets = getSitemapMapAssetPlan(map);
|
|
141
|
+
const shape = getShapeForUnit(map, selectedUnitId);
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Exports
|
|
146
|
+
|
|
147
|
+
- `@grupolapa/desarrollos-sdk`: browser-safe client, public types, and
|
|
148
|
+
`DesarrollosApiError`.
|
|
149
|
+
- `@grupolapa/desarrollos-sdk/server`: trusted server API-key client.
|
|
150
|
+
- `@grupolapa/desarrollos-sdk/quote`: quote, selection, payment, inventory,
|
|
151
|
+
formatting, Entrada/action, and product helpers.
|
|
152
|
+
- `@grupolapa/desarrollos-sdk/sitemap`: sitemap asset, hotspot, and shape
|
|
153
|
+
helpers.
|
|
154
|
+
- `@grupolapa/desarrollos-sdk/types`: domain and public REST TypeScript types.
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
function sumBy(items, getValue) {
|
|
2
|
+
return items.reduce((total, item) => total + getValue(item), 0);
|
|
3
|
+
}
|
|
4
|
+
function roundPct(value) {
|
|
5
|
+
return Math.round(value * 100) / 100;
|
|
6
|
+
}
|
|
7
|
+
function getTimelineRowAtOrAfterDelivery(quote, month) {
|
|
8
|
+
return (quote.timelineRows[month] ??
|
|
9
|
+
quote.timelineRows.at(-1) ??
|
|
10
|
+
quote.timelineRows[0]);
|
|
11
|
+
}
|
|
12
|
+
function joinLabels(labels) {
|
|
13
|
+
const uniqueLabels = [...new Set(labels.filter(Boolean))];
|
|
14
|
+
if (uniqueLabels.length === 0) {
|
|
15
|
+
return "—";
|
|
16
|
+
}
|
|
17
|
+
if (uniqueLabels.length === 1) {
|
|
18
|
+
return uniqueLabels[0];
|
|
19
|
+
}
|
|
20
|
+
return uniqueLabels.join(" + ");
|
|
21
|
+
}
|
|
22
|
+
function aggregateTimelineRows(quotes) {
|
|
23
|
+
const maxDeliveryMonths = Math.max(...quotes.map((quote) => quote.deliveryMonths));
|
|
24
|
+
return Array.from({ length: maxDeliveryMonths + 1 }, (_, month) => {
|
|
25
|
+
const exactRows = quotes
|
|
26
|
+
.map((quote) => quote.timelineRows[month] ?? null)
|
|
27
|
+
.filter((row) => Boolean(row));
|
|
28
|
+
const accumulatedRows = quotes.map((quote) => getTimelineRowAtOrAfterDelivery(quote, month));
|
|
29
|
+
const paymentLabels = exactRows
|
|
30
|
+
.filter((row) => row.paymentMXN > 0.005)
|
|
31
|
+
.map((row) => row.paymentLabel);
|
|
32
|
+
const milestoneLabels = exactRows
|
|
33
|
+
.filter((row) => row.isMilestone)
|
|
34
|
+
.map((row) => row.milestoneLabel);
|
|
35
|
+
return {
|
|
36
|
+
month,
|
|
37
|
+
paymentMXN: sumBy(exactRows, (row) => row.paymentMXN),
|
|
38
|
+
paymentLabel: joinLabels(paymentLabels),
|
|
39
|
+
cashbackMXN: sumBy(exactRows, (row) => row.cashbackMXN),
|
|
40
|
+
plusvaliaDeltaMXN: sumBy(exactRows, (row) => row.plusvaliaDeltaMXN),
|
|
41
|
+
accumulatedCashbackMXN: sumBy(accumulatedRows, (row) => row.accumulatedCashbackMXN),
|
|
42
|
+
accumulatedPlusvaliaMXN: sumBy(accumulatedRows, (row) => row.accumulatedPlusvaliaMXN),
|
|
43
|
+
assetValueMXN: sumBy(accumulatedRows, (row) => row.assetValueMXN),
|
|
44
|
+
isMilestone: milestoneLabels.length > 0,
|
|
45
|
+
milestoneLabel: joinLabels(milestoneLabels),
|
|
46
|
+
};
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
function aggregateProjectionPoints(quotes) {
|
|
50
|
+
const maxYearOffset = Math.max(...quotes.map((quote) => quote.projectionPoints.at(-1)?.yearOffset ?? 0));
|
|
51
|
+
return Array.from({ length: maxYearOffset + 1 }, (_, yearOffset) => {
|
|
52
|
+
const points = quotes.map((quote) => quote.projectionPoints.find((point) => point.yearOffset === yearOffset) ??
|
|
53
|
+
quote.projectionPoints.at(-1) ??
|
|
54
|
+
quote.projectionPoints[0]);
|
|
55
|
+
return {
|
|
56
|
+
yearOffset,
|
|
57
|
+
label: yearOffset === 0 ? "Entrega" : `Año +${yearOffset}`,
|
|
58
|
+
assetValueMXN: sumBy(points, (point) => point.assetValueMXN),
|
|
59
|
+
rentalAccumulatedMXN: sumBy(points, (point) => point.rentalAccumulatedMXN),
|
|
60
|
+
valueWithRentMXN: sumBy(points, (point) => point.valueWithRentMXN),
|
|
61
|
+
};
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
function aggregateBreakdownItems(items) {
|
|
65
|
+
const itemMap = new Map();
|
|
66
|
+
for (const item of items) {
|
|
67
|
+
const existing = itemMap.get(item.label);
|
|
68
|
+
if (existing) {
|
|
69
|
+
existing.valueMXN += item.valueMXN;
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
itemMap.set(item.label, { ...item });
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return [...itemMap.values()];
|
|
76
|
+
}
|
|
77
|
+
function countTimelineRows(rows, matchesLabel) {
|
|
78
|
+
return rows.filter((row) => row.paymentMXN > 0.005 && matchesLabel(row.paymentLabel)).length;
|
|
79
|
+
}
|
|
80
|
+
function sumTimelinePayments(rows, matchesLabel) {
|
|
81
|
+
return sumBy(rows.filter((row) => row.paymentMXN > 0.005 && matchesLabel(row.paymentLabel)), (row) => row.paymentMXN);
|
|
82
|
+
}
|
|
83
|
+
function labelIncludes(label, search) {
|
|
84
|
+
return label.toLocaleLowerCase("es-MX").includes(search);
|
|
85
|
+
}
|
|
86
|
+
export function aggregateQuoteResults(quotes) {
|
|
87
|
+
if (quotes.length === 0) {
|
|
88
|
+
throw new Error("At least one quote is required to aggregate a schedule.");
|
|
89
|
+
}
|
|
90
|
+
const [representativeQuote] = quotes;
|
|
91
|
+
const timelineRows = aggregateTimelineRows(quotes);
|
|
92
|
+
const projectionPoints = aggregateProjectionPoints(quotes);
|
|
93
|
+
const listPriceMXN = sumBy(quotes, (quote) => quote.listPriceMXN);
|
|
94
|
+
const downPaymentAmountMXN = sumBy(quotes, (quote) => quote.downPaymentAmountMXN);
|
|
95
|
+
const balanceAmountMXN = sumBy(quotes, (quote) => quote.balanceAmountMXN);
|
|
96
|
+
const cashbackTotalMXN = sumBy(quotes, (quote) => quote.cashbackTotalMXN);
|
|
97
|
+
const installmentDiscountMXN = sumBy(quotes, (quote) => quote.installmentDiscountMXN);
|
|
98
|
+
const netCostMXN = sumBy(quotes, (quote) => quote.netCostMXN);
|
|
99
|
+
const monthlyPaymentCount = countTimelineRows(timelineRows, (label) => labelIncludes(label, "mensualidad"));
|
|
100
|
+
const monthlyPaymentTotalMXN = sumTimelinePayments(timelineRows, (label) => labelIncludes(label, "mensualidad"));
|
|
101
|
+
const deferredPaymentsCount = countTimelineRows(timelineRows, (label) => labelIncludes(label, "anualidad"));
|
|
102
|
+
const deferredPaymentsTotalMXN = sumTimelinePayments(timelineRows, (label) => labelIncludes(label, "anualidad"));
|
|
103
|
+
const deliveryPaymentAmountMXN = sumTimelinePayments(timelineRows, (label) => labelIncludes(label, "contra entrega"));
|
|
104
|
+
const finalTimelineRow = timelineRows.at(-1);
|
|
105
|
+
const finalProjectionPoint = projectionPoints.at(-1);
|
|
106
|
+
return {
|
|
107
|
+
...representativeQuote,
|
|
108
|
+
listPriceMXN,
|
|
109
|
+
deliveryMonths: finalTimelineRow.month,
|
|
110
|
+
cashbackMonths: Math.max(...quotes.map((quote) => quote.cashbackMonths)),
|
|
111
|
+
isCashbackCapped: quotes.some((quote) => quote.isCashbackCapped),
|
|
112
|
+
downPaymentPct: listPriceMXN > 0
|
|
113
|
+
? roundPct((downPaymentAmountMXN / listPriceMXN) * 100)
|
|
114
|
+
: 0,
|
|
115
|
+
downPaymentAmountMXN,
|
|
116
|
+
balanceAmountMXN,
|
|
117
|
+
cashbackMonthlyMXN: sumBy(quotes, (quote) => quote.cashbackMonthlyMXN),
|
|
118
|
+
cashbackTotalMXN,
|
|
119
|
+
installmentDiscountPct: listPriceMXN > 0
|
|
120
|
+
? roundPct((installmentDiscountMXN / listPriceMXN) * 100)
|
|
121
|
+
: 0,
|
|
122
|
+
installmentDiscountMXN,
|
|
123
|
+
monthlyPaymentPct: listPriceMXN > 0
|
|
124
|
+
? roundPct((monthlyPaymentTotalMXN / listPriceMXN) * 100)
|
|
125
|
+
: 0,
|
|
126
|
+
monthlyPaymentCount,
|
|
127
|
+
monthlyPaymentAmountMXN: monthlyPaymentCount > 0
|
|
128
|
+
? monthlyPaymentTotalMXN / monthlyPaymentCount
|
|
129
|
+
: 0,
|
|
130
|
+
deferredPaymentPct: listPriceMXN > 0
|
|
131
|
+
? roundPct((deferredPaymentsTotalMXN / listPriceMXN) * 100)
|
|
132
|
+
: 0,
|
|
133
|
+
deferredPaymentsTotalMXN,
|
|
134
|
+
deferredPaymentsCount,
|
|
135
|
+
deferredPaymentMonths: timelineRows
|
|
136
|
+
.filter((row) => row.paymentMXN > 0.005 &&
|
|
137
|
+
labelIncludes(row.paymentLabel, "anualidad"))
|
|
138
|
+
.map((row) => row.month),
|
|
139
|
+
deferredPaymentAmountMXN: deferredPaymentsCount > 0
|
|
140
|
+
? deferredPaymentsTotalMXN / deferredPaymentsCount
|
|
141
|
+
: 0,
|
|
142
|
+
deliveryPaymentPct: listPriceMXN > 0
|
|
143
|
+
? roundPct((deliveryPaymentAmountMXN / listPriceMXN) * 100)
|
|
144
|
+
: 0,
|
|
145
|
+
deliveryPaymentAmountMXN,
|
|
146
|
+
netCostMXN,
|
|
147
|
+
plusvaliaAtDeliveryMXN: finalTimelineRow.accumulatedPlusvaliaMXN,
|
|
148
|
+
plusvaliaPostDeliveryMXN: sumBy(quotes, (quote) => quote.plusvaliaPostDeliveryMXN),
|
|
149
|
+
totalGainMXN: sumBy(quotes, (quote) => quote.totalGainMXN),
|
|
150
|
+
finalAssetValueMXN: finalTimelineRow.assetValueMXN,
|
|
151
|
+
projectedAssetValueMXN: finalProjectionPoint.assetValueMXN,
|
|
152
|
+
rentalMonthlyMXN: sumBy(quotes, (quote) => quote.rentalMonthlyMXN),
|
|
153
|
+
rentalAccumulatedMXN: finalProjectionPoint.rentalAccumulatedMXN,
|
|
154
|
+
projectionPoints,
|
|
155
|
+
timelineRows,
|
|
156
|
+
visibleTimelineRows: timelineRows.filter((row) => row.paymentMXN > 0.005 || row.isMilestone),
|
|
157
|
+
costBreakdown: aggregateBreakdownItems(quotes.flatMap((quote) => quote.costBreakdown)),
|
|
158
|
+
gainBreakdown: aggregateBreakdownItems(quotes.flatMap((quote) => quote.gainBreakdown)),
|
|
159
|
+
};
|
|
160
|
+
}
|