@onlistify/storefront 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/dist/index.cjs ADDED
@@ -0,0 +1,787 @@
1
+ 'use strict';
2
+
3
+ var mobx = require('mobx');
4
+ var R = require('ramda');
5
+ var lit = require('lit');
6
+ var decorators_js = require('lit/decorators.js');
7
+
8
+ var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
9
+ function _interopNamespace(e) {
10
+ if (e && e.__esModule) return e;
11
+ var n = Object.create(null);
12
+ if (e) {
13
+ Object.keys(e).forEach(function (k) {
14
+ if (k !== 'default') {
15
+ var d = Object.getOwnPropertyDescriptor(e, k);
16
+ Object.defineProperty(n, k, d.get ? d : {
17
+ enumerable: true,
18
+ get: function () { return e[k]; }
19
+ });
20
+ }
21
+ });
22
+ }
23
+ n.default = e;
24
+ return Object.freeze(n);
25
+ }
26
+
27
+ var R__namespace = /*#__PURE__*/_interopNamespace(R);
28
+
29
+ var __defProp = Object.defineProperty;
30
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
31
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
32
+ var __decorateClass = (decorators, target, key, kind) => {
33
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
34
+ for (var i = decorators.length - 1, decorator; i >= 0; i--)
35
+ if (decorator = decorators[i])
36
+ result = (kind ? decorator(target, key, result) : decorator(result)) || result;
37
+ if (kind && result) __defProp(target, key, result);
38
+ return result;
39
+ };
40
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
41
+ var FilterStore = class {
42
+ constructor() {
43
+ __publicField(this, "minPrice", null);
44
+ __publicField(this, "maxPrice", null);
45
+ __publicField(this, "transactionTypeId", null);
46
+ __publicField(this, "propertyTypeId", null);
47
+ __publicField(this, "cityId", null);
48
+ __publicField(this, "stateId", null);
49
+ __publicField(this, "neighborhoodId", null);
50
+ __publicField(this, "bedroomsMin", null);
51
+ __publicField(this, "bathroomsMin", null);
52
+ __publicField(this, "builtAreaMin", null);
53
+ __publicField(this, "builtAreaMax", null);
54
+ __publicField(this, "sort", "newest");
55
+ __publicField(this, "search", "");
56
+ __publicField(this, "page", 1);
57
+ __publicField(this, "perPage", 20);
58
+ mobx.makeAutoObservable(this);
59
+ }
60
+ get queryParams() {
61
+ const pairs = [
62
+ ["page", this.page],
63
+ ["per_page", this.perPage],
64
+ ["sort", this.sort]
65
+ ];
66
+ if (this.minPrice != null) pairs.push(["min_price", this.minPrice]);
67
+ if (this.maxPrice != null) pairs.push(["max_price", this.maxPrice]);
68
+ if (this.transactionTypeId) pairs.push(["transaction_type_id", this.transactionTypeId]);
69
+ if (this.propertyTypeId) pairs.push(["property_type_id", this.propertyTypeId]);
70
+ if (this.cityId) pairs.push(["city_id", this.cityId]);
71
+ if (this.stateId) pairs.push(["state_id", this.stateId]);
72
+ if (this.neighborhoodId) pairs.push(["neighborhood_id", this.neighborhoodId]);
73
+ if (this.bedroomsMin != null) pairs.push(["bedrooms_min", this.bedroomsMin]);
74
+ if (this.bathroomsMin != null) pairs.push(["bathrooms_min", this.bathroomsMin]);
75
+ if (this.builtAreaMin != null) pairs.push(["built_area_min", this.builtAreaMin]);
76
+ if (this.builtAreaMax != null) pairs.push(["built_area_max", this.builtAreaMax]);
77
+ if (this.search.trim()) pairs.push(["search", this.search.trim()]);
78
+ return Object.fromEntries(pairs);
79
+ }
80
+ setFilters(partial) {
81
+ if (partial.page !== void 0) this.page = partial.page;
82
+ if (partial.minPrice !== void 0) this.minPrice = partial.minPrice;
83
+ if (partial.maxPrice !== void 0) this.maxPrice = partial.maxPrice;
84
+ if (partial.transactionTypeId !== void 0) this.transactionTypeId = partial.transactionTypeId;
85
+ if (partial.propertyTypeId !== void 0) this.propertyTypeId = partial.propertyTypeId;
86
+ if (partial.cityId !== void 0) this.cityId = partial.cityId;
87
+ if (partial.stateId !== void 0) this.stateId = partial.stateId;
88
+ if (partial.neighborhoodId !== void 0) this.neighborhoodId = partial.neighborhoodId;
89
+ if (partial.bedroomsMin !== void 0) this.bedroomsMin = partial.bedroomsMin;
90
+ if (partial.bathroomsMin !== void 0) this.bathroomsMin = partial.bathroomsMin;
91
+ if (partial.builtAreaMin !== void 0) this.builtAreaMin = partial.builtAreaMin;
92
+ if (partial.builtAreaMax !== void 0) this.builtAreaMax = partial.builtAreaMax;
93
+ if (partial.sort !== void 0) this.sort = partial.sort;
94
+ if (partial.search !== void 0) this.search = partial.search;
95
+ if (partial.perPage !== void 0) this.perPage = partial.perPage;
96
+ this.page = 1;
97
+ }
98
+ setPage(n) {
99
+ this.page = n;
100
+ }
101
+ };
102
+
103
+ // ../shared/site-contract/src/index.ts
104
+ var DEFAULT_SITE_THEME = {
105
+ primaryColor: "#1a365d",
106
+ accentColor: "#2b6cb0"};
107
+ var DEFAULT_HERO_BLOCK = {
108
+ id: "hero-1",
109
+ type: "onl-hero",
110
+ props: {
111
+ title: "Bienvenido a tu inmobiliaria",
112
+ subtitle: "Encuentra tu pr\xF3ximo hogar",
113
+ ctaText: "Ver propiedades",
114
+ ctaUrl: "#",
115
+ backgroundColor: "#1a365d",
116
+ textColor: "#fff"
117
+ }
118
+ };
119
+ function isRecord(value) {
120
+ return !!value && typeof value === "object" && !Array.isArray(value);
121
+ }
122
+ function normalizeBlock(block) {
123
+ if (!isRecord(block)) {
124
+ return { id: "", type: "", props: {} };
125
+ }
126
+ return {
127
+ id: typeof block.id === "string" ? block.id : "",
128
+ type: typeof block.type === "string" ? block.type : "",
129
+ props: isRecord(block.props) ? block.props : {}
130
+ };
131
+ }
132
+ function normalizePage(page) {
133
+ if (!isRecord(page)) {
134
+ return { id: "", path: "/", blocks: [] };
135
+ }
136
+ return {
137
+ id: typeof page.id === "string" ? page.id : "",
138
+ path: typeof page.path === "string" ? page.path : "/",
139
+ blocks: Array.isArray(page.blocks) ? page.blocks.map((block) => normalizeBlock(block)) : []
140
+ };
141
+ }
142
+ function extractSiteObject(raw) {
143
+ if (!isRecord(raw)) {
144
+ return raw;
145
+ }
146
+ if (raw.data !== void 0) {
147
+ return extractSiteObject(raw.data);
148
+ }
149
+ return raw;
150
+ }
151
+ function normalizeSiteConfig(raw) {
152
+ const source = extractSiteObject(raw);
153
+ if (!isRecord(source)) {
154
+ return { version: 1, pages: [] };
155
+ }
156
+ const pages = Array.isArray(source.pages) ? source.pages.map((page) => normalizePage(page)) : [];
157
+ return {
158
+ version: typeof source.version === "number" ? source.version : 1,
159
+ theme: isRecord(source.theme) ? source.theme : void 0,
160
+ themeOverrides: isRecord(source.themeOverrides) ? source.themeOverrides : void 0,
161
+ pages
162
+ };
163
+ }
164
+ function createDefaultHeroBlock() {
165
+ return {
166
+ ...DEFAULT_HERO_BLOCK,
167
+ props: { ...DEFAULT_HERO_BLOCK.props }
168
+ };
169
+ }
170
+ function getPrimaryPage(config) {
171
+ const pages = config.pages ?? [];
172
+ return pages.find((page) => page.path === "/" || page.id === "listado") ?? pages[0];
173
+ }
174
+ function getPageBlocks(config) {
175
+ return getPrimaryPage(config)?.blocks ?? [];
176
+ }
177
+ function ensureDefaultHeroInConfig(config) {
178
+ const pages = config.pages ?? [];
179
+ if (!pages.length) {
180
+ return {
181
+ ...config,
182
+ version: config.version ?? 1,
183
+ pages: [{ id: "listado", path: "/", blocks: [createDefaultHeroBlock()] }]
184
+ };
185
+ }
186
+ const first = pages[0];
187
+ const blocks = first.blocks ?? [];
188
+ if (blocks.some((block) => block.type === "onl-hero")) {
189
+ return {
190
+ ...config,
191
+ version: config.version ?? 1,
192
+ pages
193
+ };
194
+ }
195
+ return {
196
+ ...config,
197
+ version: config.version ?? 1,
198
+ pages: [{ ...first, blocks: [createDefaultHeroBlock(), ...blocks] }, ...pages.slice(1)]
199
+ };
200
+ }
201
+ function getPropiedadCardProps(config) {
202
+ const block = getPageBlocks(config).find((item) => item.type === "propiedad-card");
203
+ return block?.props ?? {};
204
+ }
205
+ function resolveSiteTheme(config) {
206
+ const theme = config.theme ?? {};
207
+ const overrides = config.themeOverrides ?? {};
208
+ return {
209
+ primaryColor: overrides.primaryColor ?? theme.primaryColor ?? DEFAULT_SITE_THEME.primaryColor,
210
+ accentColor: overrides.accentColor ?? theme.accentColor ?? DEFAULT_SITE_THEME.accentColor,
211
+ logoUrl: theme.logoUrl,
212
+ customCssVars: theme.customCssVars ?? {}
213
+ };
214
+ }
215
+
216
+ // src/config.ts
217
+ var apiBaseUrl = "";
218
+ function setApiBase(url) {
219
+ apiBaseUrl = url;
220
+ }
221
+ function getApiBase() {
222
+ if (apiBaseUrl) return apiBaseUrl;
223
+ if (typeof ({ url: (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)) }) !== "undefined" && undefined?.VITE_PUBLIC_API_URL) {
224
+ return String(undefined.VITE_PUBLIC_API_URL);
225
+ }
226
+ return "";
227
+ }
228
+
229
+ // src/sdk/errors.ts
230
+ var StorefrontConfigError = class _StorefrontConfigError extends Error {
231
+ /** @param message Human-readable message. @param status Optional HTTP status when error comes from API. */
232
+ constructor(message, status) {
233
+ super(message);
234
+ this.status = status;
235
+ __publicField(this, "code", "STOREFRONT_CONFIG_ERROR");
236
+ this.name = "StorefrontConfigError";
237
+ Object.setPrototypeOf(this, _StorefrontConfigError.prototype);
238
+ }
239
+ };
240
+ var StorefrontNetworkError = class _StorefrontNetworkError extends Error {
241
+ /** @param message Human-readable message. @param cause Original error (e.g. fetch failure). */
242
+ constructor(message, cause) {
243
+ super(message);
244
+ this.cause = cause;
245
+ __publicField(this, "code", "STOREFRONT_NETWORK_ERROR");
246
+ this.name = "StorefrontNetworkError";
247
+ Object.setPrototypeOf(this, _StorefrontNetworkError.prototype);
248
+ }
249
+ };
250
+
251
+ // src/api.ts
252
+ async function fetchSiteConfig(slug, environment = "live") {
253
+ const base = getApiBase();
254
+ const url = `${base}/api/v1/public/tenants/${encodeURIComponent(slug)}/site-config?environment=${environment}`;
255
+ let res;
256
+ try {
257
+ res = await fetch(url);
258
+ } catch (err) {
259
+ throw new StorefrontNetworkError(`site-config: request failed`, err);
260
+ }
261
+ if (!res.ok) throw new StorefrontConfigError(`site-config: ${res.status}`, res.status);
262
+ const json = await res.json();
263
+ return { data: normalizeSiteConfig(json) };
264
+ }
265
+ async function fetchProperties(slug, params = {}) {
266
+ const base = getApiBase();
267
+ const q = new URLSearchParams();
268
+ Object.entries(params).forEach(([k, v]) => {
269
+ if (v !== void 0 && v !== null && v !== "") q.set(k, String(v));
270
+ });
271
+ let res;
272
+ try {
273
+ res = await fetch(
274
+ `${base}/api/v1/public/tenants/${encodeURIComponent(slug)}/properties?${q.toString()}`
275
+ );
276
+ } catch (err) {
277
+ throw new StorefrontNetworkError(`properties: request failed`, err);
278
+ }
279
+ if (!res.ok) throw new StorefrontConfigError(`properties: ${res.status}`, res.status);
280
+ return res.json();
281
+ }
282
+ async function fetchPlugins(slug) {
283
+ const base = getApiBase();
284
+ let res;
285
+ try {
286
+ res = await fetch(`${base}/api/v1/public/tenants/${encodeURIComponent(slug)}/plugins`);
287
+ } catch {
288
+ return { data: [] };
289
+ }
290
+ if (!res.ok) return { data: [] };
291
+ const json = await res.json();
292
+ const list = Array.isArray(json?.data) ? json.data : [];
293
+ return { data: list };
294
+ }
295
+
296
+ // src/stores/list-store.ts
297
+ var ListStore = class {
298
+ constructor(slug, filterStore) {
299
+ this.slug = slug;
300
+ this.filterStore = filterStore;
301
+ __publicField(this, "items", []);
302
+ __publicField(this, "totalCount", 0);
303
+ __publicField(this, "loading", false);
304
+ __publicField(this, "error", null);
305
+ mobx.makeAutoObservable(this);
306
+ }
307
+ setSlug(slug) {
308
+ this.slug = slug;
309
+ }
310
+ async fetchPage() {
311
+ if (!this.slug) return;
312
+ mobx.runInAction(() => {
313
+ this.loading = true;
314
+ this.error = null;
315
+ });
316
+ try {
317
+ const params = this.filterStore.queryParams;
318
+ const res = await fetchProperties(this.slug, params);
319
+ const payload = res;
320
+ let raw = [];
321
+ if (Array.isArray(payload.data)) {
322
+ raw = payload.data;
323
+ } else if (payload.data && typeof payload.data === "object" && Array.isArray(payload.data.data)) {
324
+ raw = payload.data.data;
325
+ }
326
+ const inner = payload.data && typeof payload.data === "object" && !Array.isArray(payload.data) ? payload.data : null;
327
+ const pag = payload.pagination ?? inner?.pagination ?? void 0;
328
+ const total = pag?.total ?? payload.meta?.total ?? raw.length;
329
+ const list = Array.isArray(raw) ? raw : [];
330
+ const items = R__namespace.map((p) => ({ ...p }), list);
331
+ mobx.runInAction(() => {
332
+ this.items = items;
333
+ this.totalCount = total > 0 ? total : items.length;
334
+ });
335
+ } catch (e) {
336
+ mobx.runInAction(() => {
337
+ this.error = e instanceof Error ? e.message : "Error al cargar";
338
+ this.items = [];
339
+ });
340
+ } finally {
341
+ mobx.runInAction(() => {
342
+ this.loading = false;
343
+ });
344
+ }
345
+ }
346
+ };
347
+ var PropiedadCard = class extends lit.LitElement {
348
+ get showFields() {
349
+ const def = {
350
+ title: true,
351
+ address: true,
352
+ price: true,
353
+ transactionType: true,
354
+ propertyType: true,
355
+ bedrooms: true,
356
+ bathrooms: true,
357
+ parking: true,
358
+ builtArea: true,
359
+ city: true
360
+ };
361
+ return { ...def, ...this.blockProps?.showFields };
362
+ }
363
+ get displayPrice() {
364
+ const p = this.property;
365
+ if (p.sale_price != null && p.sale_price > 0) {
366
+ return new Intl.NumberFormat("es-CO", { style: "currency", currency: p.currency || "COP", maximumFractionDigits: 0 }).format(p.sale_price);
367
+ }
368
+ if (p.rental_price != null && p.rental_price > 0) {
369
+ return new Intl.NumberFormat("es-CO", { style: "currency", currency: p.currency || "COP", maximumFractionDigits: 0 }).format(p.rental_price) + "/mes";
370
+ }
371
+ return "Consultar";
372
+ }
373
+ connectedCallback() {
374
+ super.connectedCallback();
375
+ const theme = this.blockProps?.theme;
376
+ if (theme?.primaryColor) this.style.setProperty("--prop-card-primary", theme.primaryColor);
377
+ if (theme?.accentColor) this.style.setProperty("--prop-card-accent", theme.accentColor);
378
+ if (theme?.badgeBackground) this.style.setProperty("--prop-card-badge-bg", theme.badgeBackground);
379
+ if (theme?.cardBorderRadius) this.style.setProperty("--prop-card-radius", theme.cardBorderRadius);
380
+ if (theme?.imageAspectRatio) {
381
+ const ratio = theme.imageAspectRatio === "16/9" ? "16/9" : theme.imageAspectRatio === "1/1" ? "1/1" : "4/3";
382
+ this.style.setProperty("--prop-card-aspect", ratio);
383
+ }
384
+ }
385
+ render() {
386
+ const p = this.property;
387
+ if (!p || !p.id) return lit.html`<div class="card" style="padding:1rem;color:#6b7280;">Sin datos</div>`;
388
+ const show = this.showFields;
389
+ const badges = [];
390
+ if (this.blockProps?.badges?.featured?.enabled && p.is_featured) {
391
+ badges.push(this.blockProps.badges.featured.label ?? "Destacado");
392
+ }
393
+ if (this.blockProps?.badges?.virtualTour?.enabled && p.has_virtual_tour) {
394
+ badges.push(this.blockProps.badges.virtualTour.label ?? "360\xB0");
395
+ }
396
+ return lit.html`
397
+ <a href="#/tienda/${window.__storefrontSlug ?? ""}/propiedad/${p.id}" class="card" style="text-decoration: none; color: inherit;">
398
+ <div class="img-wrap">
399
+ ${p.primary_image_url ? lit.html`<img data-src="${p.primary_image_url}" alt="${p.title}" class="lazyload" src="${p.primary_image_url}" loading="lazy" />` : lit.html`<div style="width:100%;height:100%;display:flex;align-items:center;justify-content:center;color:#9ca3af;">Sin imagen</div>`}
400
+ ${badges.length ? lit.html`<div class="badges">${badges.map((b) => lit.html`<span class="badge">${b}</span>`)}</div>` : lit.nothing}
401
+ </div>
402
+ <div class="body">
403
+ ${show.title !== false ? lit.html`<h3 class="title">${p.title}</h3>` : lit.nothing}
404
+ ${show.address !== false && p.address ? lit.html`<p class="address">${p.address}</p>` : lit.nothing}
405
+ <div class="row">
406
+ ${show.price !== false ? lit.html`<span class="price">${this.displayPrice}</span>` : lit.nothing}
407
+ ${show.transactionType !== false && p.transaction_type ? lit.html`<span class="tag">${p.transaction_type.name}</span>` : lit.nothing}
408
+ </div>
409
+ <div class="meta">
410
+ ${show.bedrooms !== false && p.bedrooms != null ? lit.html`<span>${p.bedrooms} hab.</span>` : lit.nothing}
411
+ ${show.bathrooms !== false && p.bathrooms != null ? lit.html`<span>${p.bathrooms} baños</span>` : lit.nothing}
412
+ ${show.parking !== false && p.parking_spaces > 0 ? lit.html`<span>${p.parking_spaces} est.</span>` : lit.nothing}
413
+ ${show.builtArea !== false && p.built_area > 0 ? lit.html`<span>${p.built_area} m²</span>` : lit.nothing}
414
+ ${show.city !== false && p.city ? lit.html`<span>${p.city.name}</span>` : lit.nothing}
415
+ </div>
416
+ </div>
417
+ </a>
418
+ `;
419
+ }
420
+ };
421
+ __publicField(PropiedadCard, "styles", lit.css`
422
+ :host {
423
+ display: block;
424
+ --prop-card-primary: var(--storefront-primary, #1a365d);
425
+ --prop-card-accent: var(--storefront-accent, #2b6cb0);
426
+ --prop-card-badge-bg: var(--storefront-accent, #2b6cb0);
427
+ --prop-card-radius: 8px;
428
+ --prop-card-aspect: 4 / 3;
429
+ }
430
+ .card {
431
+ border-radius: var(--prop-card-radius);
432
+ overflow: hidden;
433
+ background: #fff;
434
+ box-shadow: 0 1px 3px rgba(0,0,0,0.1);
435
+ transition: box-shadow 0.2s;
436
+ }
437
+ .card:hover {
438
+ box-shadow: 0 4px 12px rgba(0,0,0,0.15);
439
+ }
440
+ .img-wrap {
441
+ position: relative;
442
+ width: 100%;
443
+ aspect-ratio: var(--prop-card-aspect);
444
+ background: #e5e7eb;
445
+ }
446
+ .img-wrap img {
447
+ width: 100%;
448
+ height: 100%;
449
+ object-fit: cover;
450
+ }
451
+ .badges {
452
+ position: absolute;
453
+ top: 8px;
454
+ left: 8px;
455
+ right: 8px;
456
+ display: flex;
457
+ gap: 6px;
458
+ flex-wrap: wrap;
459
+ }
460
+ .badge {
461
+ padding: 4px 8px;
462
+ border-radius: 4px;
463
+ font-size: 11px;
464
+ font-weight: 600;
465
+ background: var(--prop-card-badge-bg);
466
+ color: #fff;
467
+ }
468
+ .body {
469
+ padding: 12px;
470
+ }
471
+ .title {
472
+ font-size: 1rem;
473
+ font-weight: 600;
474
+ color: var(--prop-card-primary);
475
+ margin: 0 0 4px 0;
476
+ display: -webkit-box;
477
+ -webkit-line-clamp: 2;
478
+ -webkit-box-orient: vertical;
479
+ overflow: hidden;
480
+ }
481
+ .address {
482
+ font-size: 0.8rem;
483
+ color: #6b7280;
484
+ margin: 0 0 8px 0;
485
+ display: -webkit-box;
486
+ -webkit-line-clamp: 1;
487
+ -webkit-box-orient: vertical;
488
+ overflow: hidden;
489
+ }
490
+ .row {
491
+ display: flex;
492
+ justify-content: space-between;
493
+ align-items: center;
494
+ flex-wrap: wrap;
495
+ gap: 8px;
496
+ }
497
+ .price {
498
+ font-size: 1.1rem;
499
+ font-weight: 700;
500
+ color: var(--prop-card-primary);
501
+ }
502
+ .tag {
503
+ font-size: 0.75rem;
504
+ padding: 2px 8px;
505
+ border-radius: 4px;
506
+ background: rgba(0,0,0,0.06);
507
+ color: #374151;
508
+ }
509
+ .meta {
510
+ display: flex;
511
+ gap: 12px;
512
+ margin-top: 8px;
513
+ font-size: 0.8rem;
514
+ color: #6b7280;
515
+ }
516
+ `);
517
+ __decorateClass([
518
+ decorators_js.property({ type: Object })
519
+ ], PropiedadCard.prototype, "property", 2);
520
+ __decorateClass([
521
+ decorators_js.property({ type: Object })
522
+ ], PropiedadCard.prototype, "blockProps", 2);
523
+ PropiedadCard = __decorateClass([
524
+ decorators_js.customElement("propiedad-card")
525
+ ], PropiedadCard);
526
+
527
+ // src/sdk/site-runtime.ts
528
+ var BUILTIN_PLUGIN_URLS = {
529
+ "onl-hero": { js: "/plugins/example/hero-plugin.js", css: "/plugins/example/hero-plugin.css" }
530
+ };
531
+ var loadedPlugins = /* @__PURE__ */ new Set();
532
+ function getSlugFromPath(pathname) {
533
+ const match = pathname.match(/^\/tienda\/([^/]+)/);
534
+ return match ? match[1] : "";
535
+ }
536
+ function getEnvironmentFromQuery(search) {
537
+ const env = new URLSearchParams(search).get("environment");
538
+ return env === "draft" ? "draft" : "live";
539
+ }
540
+ function getPluginUrls(slug) {
541
+ return fetchPlugins(slug).then(({ data }) => {
542
+ const merged = { ...BUILTIN_PLUGIN_URLS };
543
+ for (const plugin of data) {
544
+ const js = plugin.bundle_url?.trim();
545
+ if (!js) continue;
546
+ const css2 = plugin.css_url?.trim() || void 0;
547
+ for (const blockType of plugin.block_types || []) {
548
+ if (blockType) {
549
+ merged[blockType] = { js, css: css2 };
550
+ }
551
+ }
552
+ }
553
+ return merged;
554
+ }).catch(() => ({ ...BUILTIN_PLUGIN_URLS }));
555
+ }
556
+ function loadPlugin(type, pluginUrls) {
557
+ const urls = pluginUrls[type];
558
+ if (!urls || loadedPlugins.has(type)) return Promise.resolve();
559
+ const promises = [];
560
+ promises.push(
561
+ new Promise((resolve, reject) => {
562
+ const script = document.createElement("script");
563
+ script.src = urls.js;
564
+ script.onload = () => {
565
+ loadedPlugins.add(type);
566
+ resolve();
567
+ };
568
+ script.onerror = () => reject(new Error(`Plugin ${type} failed to load`));
569
+ document.head.appendChild(script);
570
+ })
571
+ );
572
+ if (urls.css) {
573
+ const cssHref = urls.css;
574
+ promises.push(
575
+ new Promise((resolve, reject) => {
576
+ const link = document.createElement("link");
577
+ link.rel = "stylesheet";
578
+ link.href = cssHref;
579
+ link.onload = () => resolve();
580
+ link.onerror = () => reject(new Error(`Plugin ${type} CSS failed to load`));
581
+ document.head.appendChild(link);
582
+ })
583
+ );
584
+ }
585
+ return Promise.all(promises).then(() => {
586
+ });
587
+ }
588
+ async function renderPluginBlocks(blocks, container, pluginUrls) {
589
+ for (const block of blocks) {
590
+ if (block.type === "propiedad-card") continue;
591
+ const urls = pluginUrls[block.type];
592
+ if (!urls) continue;
593
+ try {
594
+ await loadPlugin(block.type, pluginUrls);
595
+ const el = document.createElement(block.type);
596
+ el.props = block.props ?? {};
597
+ el.setAttribute("data-props", JSON.stringify(block.props ?? {}));
598
+ container.appendChild(el);
599
+ } catch {
600
+ const fallback = document.createElement("div");
601
+ fallback.className = "plugin-error";
602
+ fallback.textContent = `Bloque ${block.type} no disponible`;
603
+ container.appendChild(fallback);
604
+ }
605
+ }
606
+ }
607
+ function applyTheme(config) {
608
+ const theme = resolveSiteTheme(config);
609
+ document.documentElement.style.setProperty("--storefront-primary", theme.primaryColor);
610
+ document.documentElement.style.setProperty("--storefront-accent", theme.accentColor);
611
+ if (theme.logoUrl) {
612
+ document.documentElement.style.setProperty("--storefront-logo-url", theme.logoUrl);
613
+ }
614
+ Object.entries(theme.customCssVars).forEach(([key, value]) => {
615
+ document.documentElement.style.setProperty(`--${key}`, value);
616
+ });
617
+ }
618
+ function renderFilters(filterStore, listStore, updateList) {
619
+ const wrap = document.createElement("div");
620
+ wrap.className = "filters-bar";
621
+ wrap.innerHTML = `
622
+ <div class="filters-inner">
623
+ <input type="number" placeholder="Precio m\xEDn" id="minPrice" />
624
+ <input type="number" placeholder="Precio m\xE1x" id="maxPrice" />
625
+ <select id="sort">
626
+ <option value="newest">M\xE1s recientes</option>
627
+ <option value="price_asc">Precio menor</option>
628
+ <option value="price_desc">Precio mayor</option>
629
+ <option value="area_desc">\xC1rea mayor</option>
630
+ </select>
631
+ <input type="text" placeholder="Buscar" id="search" />
632
+ <button type="button" id="applyFilters">Filtrar</button>
633
+ </div>
634
+ `;
635
+ const minPrice = wrap.querySelector("#minPrice");
636
+ const maxPrice = wrap.querySelector("#maxPrice");
637
+ const sort = wrap.querySelector("#sort");
638
+ const search = wrap.querySelector("#search");
639
+ const apply = wrap.querySelector("#applyFilters");
640
+ minPrice.value = filterStore.minPrice != null ? String(filterStore.minPrice) : "";
641
+ maxPrice.value = filterStore.maxPrice != null ? String(filterStore.maxPrice) : "";
642
+ sort.value = filterStore.sort;
643
+ search.value = filterStore.search;
644
+ apply.addEventListener("click", () => {
645
+ filterStore.setFilters({
646
+ minPrice: minPrice.value ? Number(minPrice.value) : null,
647
+ maxPrice: maxPrice.value ? Number(maxPrice.value) : null,
648
+ sort: sort.value,
649
+ search: search.value
650
+ });
651
+ listStore.fetchPage().then(updateList);
652
+ });
653
+ return wrap;
654
+ }
655
+ function renderList(items, blockProps, slug) {
656
+ const wrap = document.createElement("div");
657
+ wrap.className = "list-grid";
658
+ window.__storefrontSlug = slug;
659
+ items.forEach((prop) => {
660
+ const card = document.createElement("propiedad-card");
661
+ card.property = prop;
662
+ card.blockProps = blockProps;
663
+ wrap.appendChild(card);
664
+ });
665
+ return wrap;
666
+ }
667
+ function renderPagination(filterStore, listStore, totalPages, updateList) {
668
+ const wrap = document.createElement("div");
669
+ wrap.className = "pagination";
670
+ const prev = document.createElement("button");
671
+ prev.textContent = "Anterior";
672
+ prev.disabled = filterStore.page <= 1;
673
+ prev.addEventListener("click", () => {
674
+ filterStore.setPage(Math.max(1, filterStore.page - 1));
675
+ listStore.fetchPage().then(updateList);
676
+ });
677
+ const next = document.createElement("button");
678
+ next.textContent = "Siguiente";
679
+ next.disabled = filterStore.page >= totalPages;
680
+ next.addEventListener("click", () => {
681
+ filterStore.setPage(Math.min(totalPages, filterStore.page + 1));
682
+ listStore.fetchPage().then(updateList);
683
+ });
684
+ const info = document.createElement("span");
685
+ info.className = "pagination-info";
686
+ info.textContent = `P\xE1gina ${filterStore.page} de ${totalPages}`;
687
+ wrap.appendChild(prev);
688
+ wrap.appendChild(info);
689
+ wrap.appendChild(next);
690
+ return wrap;
691
+ }
692
+ async function bootSiteRuntime(app, options = {}) {
693
+ if (!app) {
694
+ const err = new StorefrontConfigError("bootSiteRuntime: app element is required");
695
+ options.onError?.(err);
696
+ throw err;
697
+ }
698
+ if (options.baseUrl != null) {
699
+ setApiBase(options.baseUrl);
700
+ } else {
701
+ setApiBase(getApiBase());
702
+ }
703
+ const getSlug = options.getSlugFromPath ?? getSlugFromPath;
704
+ const slug = getSlug(typeof window !== "undefined" ? window.location.pathname : "");
705
+ if (!slug) {
706
+ app.innerHTML = "<p>URL debe ser /tienda/:slug (ej. /tienda/mi-inmobiliaria)</p>";
707
+ return;
708
+ }
709
+ app.innerHTML = "<p>Cargando...</p>";
710
+ const environment = options.environment ?? (typeof window !== "undefined" ? getEnvironmentFromQuery(window.location.search) : "live");
711
+ const filterStore = new FilterStore();
712
+ let config = { pages: [] };
713
+ try {
714
+ const res = await fetchSiteConfig(slug, environment);
715
+ config = ensureDefaultHeroInConfig(res.data ?? config);
716
+ applyTheme(config);
717
+ } catch (error) {
718
+ const err = error instanceof Error ? error : new StorefrontConfigError(String(error));
719
+ options.onError?.(err);
720
+ app.innerHTML = `<p>Error al cargar configuraci\xF3n: ${err.message}</p>`;
721
+ return;
722
+ }
723
+ const listStore = new ListStore(slug, filterStore);
724
+ const blockProps = getPropiedadCardProps(config);
725
+ const pageBlocks = getPageBlocks(config);
726
+ const pluginUrls = await getPluginUrls(slug);
727
+ const style = document.createElement("style");
728
+ style.textContent = `
729
+ .filters-bar { padding: 16px; background: #f3f4f6; margin-bottom: 16px; }
730
+ .filters-inner { display: flex; gap: 12px; flex-wrap: wrap; align-items: center; max-width: 900px; }
731
+ .filters-inner input, .filters-inner select { padding: 8px 12px; border: 1px solid #d1d5db; border-radius: 6px; }
732
+ .filters-inner button { padding: 8px 16px; background: var(--storefront-primary, #1a365d); color: #fff; border: none; border-radius: 6px; cursor: pointer; }
733
+ .list-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 20px; padding: 16px; }
734
+ .pagination { display: flex; gap: 12px; align-items: center; padding: 16px; }
735
+ .pagination button { padding: 8px 16px; cursor: pointer; }
736
+ .pagination button:disabled { opacity: 0.5; cursor: not-allowed; }
737
+ .error { padding: 16px; color: #b91c1c; }
738
+ .plugin-error { padding: 12px 16px; background: #fef2f2; color: #b91c1c; font-size: 0.875rem; }
739
+ `;
740
+ app.innerHTML = "";
741
+ app.appendChild(style);
742
+ const container = document.createElement("div");
743
+ container.className = "storefront-container";
744
+ app.appendChild(container);
745
+ const pluginBlocksEl = document.createElement("div");
746
+ pluginBlocksEl.className = "plugin-blocks";
747
+ container.appendChild(pluginBlocksEl);
748
+ await renderPluginBlocks(pageBlocks, pluginBlocksEl, pluginUrls);
749
+ const listEl = document.createElement("div");
750
+ listEl.className = "list-container";
751
+ const paginationEl = document.createElement("div");
752
+ const updateList = () => {
753
+ listEl.innerHTML = "";
754
+ if (listStore.error) {
755
+ listEl.innerHTML = `<p class="error">${listStore.error}</p>`;
756
+ paginationEl.innerHTML = "";
757
+ return;
758
+ }
759
+ if (listStore.loading) {
760
+ listEl.innerHTML = "<p>Cargando propiedades...</p>";
761
+ paginationEl.innerHTML = "";
762
+ return;
763
+ }
764
+ const totalPages = Math.max(1, Math.ceil(listStore.totalCount / filterStore.perPage));
765
+ listEl.appendChild(renderList(listStore.items, blockProps, slug));
766
+ paginationEl.innerHTML = "";
767
+ paginationEl.appendChild(renderPagination(filterStore, listStore, totalPages, updateList));
768
+ };
769
+ container.appendChild(renderFilters(filterStore, listStore, updateList));
770
+ container.appendChild(listEl);
771
+ container.appendChild(paginationEl);
772
+ try {
773
+ await listStore.fetchPage();
774
+ } catch {
775
+ }
776
+ updateList();
777
+ }
778
+
779
+ exports.StorefrontConfigError = StorefrontConfigError;
780
+ exports.StorefrontNetworkError = StorefrontNetworkError;
781
+ exports.bootSiteRuntime = bootSiteRuntime;
782
+ exports.fetchPlugins = fetchPlugins;
783
+ exports.fetchProperties = fetchProperties;
784
+ exports.fetchSiteConfig = fetchSiteConfig;
785
+ exports.setApiBase = setApiBase;
786
+ //# sourceMappingURL=index.cjs.map
787
+ //# sourceMappingURL=index.cjs.map