@ecomiq/storefront 0.1.1 → 1.0.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.
@@ -1,10 +1,11 @@
1
1
  import { z } from 'zod';
2
2
  import { Modal } from '@ecomiq/ui/components/modal';
3
- import { formatCurrency as formatCurrency$1, StorefrontPageDetailSchema as StorefrontPageDetailSchema$1, useCart as useCart$1, useStore as useStore$1, useStorefrontAuthClient as useStorefrontAuthClient$1, useStorefrontSession as useStorefrontSession$1, SignInForm, SignUpForm, storefrontStoreRecord as storefrontStoreRecord$1 } from '@ecomiq/storefront/react';
3
+ import { StorefrontPageDetailSchema as StorefrontPageDetailSchema$1, formatCurrency, useCart, useStore as useStore$1, useStorefrontAuthClient as useStorefrontAuthClient$1, useStorefrontSession as useStorefrontSession$1, SignInForm, SignUpForm, signInWithEcomiqCredentials } from '@ecomiq/storefront/react';
4
4
  import { toast, Toaster as Toaster$1 } from 'sonner';
5
5
  import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
6
6
  import { useTheme } from 'next-themes';
7
- import axios from 'axios';
7
+ import { StorefrontSettingsSchema, StorefrontProductsParamsSchema, StorefrontProductsResponseSchema, StorefrontProductDetailSchema, StorefrontCollectionsResponseSchema, StorefrontCollectionDetailSchema, StorefrontPageListItemSchema, StorefrontPageDetailSchema, CartCreateRequestSchema, CartResponseSchema, CartBuyerRequestSchema, CartAddressesRequestSchema, CartCouponRequestSchema, CartCheckoutRequestSchema, CartCheckoutResponseSchema, CartPaymentReceiptResponseSchema, CartCheckoutReadResponseSchema, ShippingOptionsResponseSchema, CartShippingMethodPutRequestSchema, CartItemRequestSchema } from '@ecomiq/storefront/core';
8
+ import { getCloudflareContext } from '@opennextjs/cloudflare';
8
9
  import { QueryClient, hydrate, QueryClientProvider, useMutation, useQuery, useQueryClient, useInfiniteQuery } from '@tanstack/react-query';
9
10
  import React2, { createContext, useRef, useEffect, useContext, useState, useMemo, useCallback } from 'react';
10
11
  import { create } from 'zustand';
@@ -13,6 +14,11 @@ import { createAuthMiddleware, createAuthEndpoint, formCsrfMiddleware } from 'be
13
14
  import { APIError } from 'better-auth';
14
15
  import { setSessionCookie } from 'better-auth/cookies';
15
16
  import { parseUserOutput } from 'better-auth/db';
17
+ import axios from 'axios';
18
+ import { existsSync } from 'fs';
19
+ import { createRequire } from 'module';
20
+ import { join } from 'path';
21
+ import { getRequestHeaders } from '@tanstack/react-start/server';
16
22
 
17
23
  var __defProp = Object.defineProperty;
18
24
  var __getOwnPropNames = Object.getOwnPropertyNames;
@@ -25,6 +31,12 @@ var __export = (target, all) => {
25
31
  };
26
32
 
27
33
  // src/core/client/store-api-client.ts
34
+ function setServerRequestOrigin(getter) {
35
+ serverRequestOriginGetter = getter;
36
+ }
37
+ function setServerStoreId(storeId) {
38
+ serverStoreIdGetter = storeId?.trim() || null;
39
+ }
28
40
  function setClientStoreId(storeId) {
29
41
  if (typeof window === "undefined") return;
30
42
  clientStoreIdGetter = storeId?.trim() || null;
@@ -38,11 +50,14 @@ async function getStoreApiBaseUrl() {
38
50
  }
39
51
  return STORE_API_PATH;
40
52
  }
53
+ function createStoreApiClient(baseUrl) {
54
+ return new StoreApiClient(baseUrl);
55
+ }
41
56
  async function getStoreApiClient() {
42
57
  const baseUrl = await getStoreApiBaseUrl();
43
58
  return new StoreApiClient(baseUrl);
44
59
  }
45
- var STORE_API_PATH, serverRequestOriginGetter, serverStoreIdGetter, clientStoreIdGetter, StoreApiClient;
60
+ var STORE_API_PATH, serverRequestOriginGetter, serverStoreIdGetter, clientStoreIdGetter, StoreApiClient, storeApiClient;
46
61
  var init_store_api_client = __esm({
47
62
  "src/core/client/store-api-client.ts"() {
48
63
  STORE_API_PATH = "/api/store";
@@ -142,9 +157,26 @@ var init_store_api_client = __esm({
142
157
  return this.request(path, { method: "DELETE" }, options);
143
158
  }
144
159
  };
145
- new StoreApiClient(STORE_API_PATH);
160
+ storeApiClient = new StoreApiClient(STORE_API_PATH);
146
161
  }
147
162
  });
163
+ function customerMeToProfilePatch(me) {
164
+ return {
165
+ firstName: me.firstName,
166
+ lastName: me.lastName,
167
+ phone: me.phone ?? "",
168
+ identificationNumber: me.identificationNumber,
169
+ identificationType: me.identificationType,
170
+ entityName: me.entityName,
171
+ entityIdentificationNumber: me.entityIdentificationNumber,
172
+ gender: me.gender,
173
+ role: me.role,
174
+ companyName: me.companyName,
175
+ dateOfBirth: me.dateOfBirth,
176
+ consent: me.consent,
177
+ countryIsoCode: me.countryIsoCode || "PE"
178
+ };
179
+ }
148
180
  function customerAuthOptions(accessToken) {
149
181
  const token = typeof accessToken === "string" && accessToken.trim().length > 0 ? accessToken.trim() : void 0;
150
182
  return token ? { bearerToken: token } : {};
@@ -154,12 +186,52 @@ async function registerCustomer(data) {
154
186
  const client = await getStoreApiClient();
155
187
  return client.post("customer/register", payload);
156
188
  }
189
+ async function getCustomerAddresses(accessToken) {
190
+ const client = await getStoreApiClient();
191
+ const token = typeof accessToken === "string" && accessToken.trim().length > 0 ? accessToken.trim() : void 0;
192
+ const response = await client.get("customer/addresses", {
193
+ ...token ? { bearerToken: token } : {}
194
+ });
195
+ return z.array(CustomerAddressSchema).parse(response);
196
+ }
197
+ async function createCustomerAddress(data, accessToken) {
198
+ const client = await getStoreApiClient();
199
+ const token = typeof accessToken === "string" && accessToken.trim().length > 0 ? accessToken.trim() : void 0;
200
+ const payload = CustomerAddressUpsertSchema.parse(data);
201
+ return client.post("customer/addresses", payload, {
202
+ ...token ? { bearerToken: token } : {}
203
+ });
204
+ }
205
+ async function updateCustomerAddress(addressId, data, accessToken) {
206
+ const client = await getStoreApiClient();
207
+ const token = typeof accessToken === "string" && accessToken.trim().length > 0 ? accessToken.trim() : void 0;
208
+ const payload = CustomerAddressUpsertSchema.parse(data);
209
+ const id = addressId.trim();
210
+ if (!id) throw new Error("addressId requerido");
211
+ return client.put(`customer/addresses/${encodeURIComponent(id)}`, payload, {
212
+ ...token ? { bearerToken: token } : {}
213
+ });
214
+ }
215
+ async function deleteCustomerAddress(addressId, accessToken) {
216
+ const client = await getStoreApiClient();
217
+ const token = typeof accessToken === "string" && accessToken.trim().length > 0 ? accessToken.trim() : void 0;
218
+ const id = addressId.trim();
219
+ if (!id) throw new Error("addressId requerido");
220
+ return client.delete(`customer/addresses/${encodeURIComponent(id)}`, {
221
+ ...token ? { bearerToken: token } : {}
222
+ });
223
+ }
157
224
  async function getCustomerMe(accessToken) {
158
225
  const client = await getStoreApiClient();
159
226
  const response = await client.get("customer", customerAuthOptions(accessToken));
160
227
  return CustomerMeSchema.parse(response);
161
228
  }
162
- var CustomerRegisterRequestSchema, CustomerAddressSchema, CustomerMeSchema;
229
+ async function patchCustomer(data, accessToken) {
230
+ const client = await getStoreApiClient();
231
+ const payload = CustomerProfilePatchSchema.parse(data);
232
+ await client.patch("customer", payload, customerAuthOptions(accessToken));
233
+ }
234
+ var CustomerRegisterRequestSchema, CustomerAddressSchema, CustomerAddressUpsertSchema, CustomerMeSchema, CustomerProfilePatchSchema;
163
235
  var init_store_api_customer_service = __esm({
164
236
  "src/core/client/store-api-customer.service.ts"() {
165
237
  init_store_api_client();
@@ -189,7 +261,7 @@ var init_store_api_customer_service = __esm({
189
261
  longitude: z.number().nullable(),
190
262
  isDefault: z.boolean()
191
263
  });
192
- CustomerAddressSchema.omit({ id: true });
264
+ CustomerAddressUpsertSchema = CustomerAddressSchema.omit({ id: true });
193
265
  CustomerMeSchema = z.object({
194
266
  id: z.string(),
195
267
  storeId: z.string(),
@@ -221,7 +293,7 @@ var init_store_api_customer_service = __esm({
221
293
  createdOn: z.string(),
222
294
  updatedOn: z.string().nullable()
223
295
  });
224
- z.object({
296
+ CustomerProfilePatchSchema = z.object({
225
297
  firstName: z.string().trim().min(1),
226
298
  lastName: z.string().trim().min(1),
227
299
  phone: z.string().trim(),
@@ -244,36 +316,13 @@ var init_modals_factory = __esm({
244
316
  "use client";
245
317
  init_store_api_customer_service();
246
318
  ModalsFactory = () => {
247
- const cart = useCart$1();
319
+ const cart = useCart();
248
320
  const store = useStore$1();
249
321
  const authClient = useStorefrontAuthClient$1();
250
322
  const { refetch } = useStorefrontSession$1();
251
323
  const handlerLogin = async (values) => {
252
- const settings = store.data;
253
- const record = storefrontStoreRecord$1(settings);
254
- const checkout = record?.checkoutSettings;
255
- if (!record?.id) {
256
- toast.error("Tienda no disponible");
257
- return;
258
- }
259
- const identityScope = checkout?.identityScope === "tenant" ? "tenant" : "store";
260
- const identityHeaders = {
261
- "x-Identity-Scope": identityScope,
262
- "x-Store-Id": record.id
263
- };
264
- if (record.tenantId) {
265
- identityHeaders["x-Tenant-Id"] = record.tenantId;
266
- }
267
324
  try {
268
- const response = await authClient.$fetch("/ecomiq-oauth/sign-in", {
269
- method: "POST",
270
- headers: identityHeaders,
271
- body: { email: values.email, password: values.password }
272
- });
273
- if (response.error) {
274
- toast.error(response.error.message);
275
- return;
276
- }
325
+ await signInWithEcomiqCredentials(authClient, store.data, values);
277
326
  const me = await getCustomerMe();
278
327
  const displayName = `${me.firstName} ${me.lastName}`.trim() || me.email;
279
328
  const updateUserResponse = await authClient.$fetch("/update-user", {
@@ -408,1594 +457,862 @@ var init_ecomiq_shell_ui = __esm({
408
457
  }
409
458
  });
410
459
 
411
- // src/core/constants/api-paths.ts
412
- var STOREFRONT_API_PATH_PREFIX = "v1";
460
+ // src/core/client/store-api-errors.ts
461
+ function isCartNotActiveApiError(error) {
462
+ if (!error || typeof error !== "object") return false;
463
+ const e = error;
464
+ if (e.status !== 400) return false;
465
+ const body = e.body;
466
+ const keys = body?.errors ? Object.keys(body.errors) : [];
467
+ return keys.includes("Carts.NotActive");
468
+ }
413
469
 
414
- // src/core/constants/storefront-ecomiq-oauth-cookie.ts
415
- var STOREFRONT_ECOMIQ_OAUTH_COOKIE_NAME = "ecomiq_sf_oauth_at";
416
- var STOREFRONT_ECOMIQ_OAUTH_COOKIE_PATH = "/api/store";
470
+ // src/core/client/index.ts
471
+ init_store_api_client();
417
472
 
418
- // src/core/constants/currency.ts
419
- var countryCurrency = {
420
- "PEN": "es-PE",
421
- "USD": "en-US",
422
- "COP": "es-CO",
423
- "MXN": "es-MX"
424
- };
473
+ // src/core/client/store-api-store.service.ts
474
+ init_store_api_client();
475
+ async function getStorefrontSettings(subdomain, cacheKey) {
476
+ const client = await getStoreApiClient();
477
+ const response = await client.get("settings", {
478
+ xStoreDomain: subdomain?.trim() || void 0,
479
+ cacheKey,
480
+ noStore: true
481
+ });
482
+ return StorefrontSettingsSchema.parse(response);
483
+ }
484
+ var ECOMIQ_CLIENTS_STORE_KV_BINDING = "ecomiq_clients_store";
485
+ var ECOMIQ_APP_APEX = "ecomiq.app";
486
+ var ECOMIQ_APP_SUFFIX = [`.${ECOMIQ_APP_APEX}`, `.localhost`];
487
+ function normalizeStoreHost(hostHeader) {
488
+ let h = hostHeader.trim().toLowerCase();
489
+ const colon = h.indexOf(":");
490
+ if (colon !== -1) h = h.slice(0, colon);
491
+ if (h.startsWith("www.")) h = h.slice(4);
492
+ return h;
493
+ }
494
+ function resolveStorefrontSettingsHost(hostHeader) {
495
+ const host = normalizeStoreHost(hostHeader);
496
+ if (!host) {
497
+ throw Object.assign(new Error("Host inv\xE1lido para tienda"), { status: 400 });
498
+ }
499
+ if (host === ECOMIQ_APP_APEX) {
500
+ throw Object.assign(new Error("Host apex ecomiq.app sin slug de tienda"), { status: 400 });
501
+ }
502
+ if (ECOMIQ_APP_SUFFIX.some((suffix) => host.endsWith(suffix))) {
503
+ const slug = host.slice(0, -(ECOMIQ_APP_SUFFIX.find((suffix) => host.endsWith(suffix))?.length ?? 0));
504
+ if (!slug) {
505
+ throw Object.assign(new Error("Host sin slug para ecomiq.app"), { status: 400 });
506
+ }
507
+ return slug;
508
+ }
509
+ return host;
510
+ }
511
+ function buildStorefrontKvSnapshotKeyFromStoreKey(storeKey) {
512
+ const key = storeKey.trim();
513
+ if (!key) {
514
+ throw Object.assign(new Error("Store key vac\xEDo para KV"), { status: 400 });
515
+ }
516
+ return `storefront:snapshots:store:${key}`;
517
+ }
518
+ function buildStorefrontKvSnapshotKey(hostHeader) {
519
+ return buildStorefrontKvSnapshotKeyFromStoreKey(
520
+ resolveStorefrontSettingsHost(hostHeader)
521
+ );
522
+ }
523
+ function getKvNamespace(env) {
524
+ if (!env || typeof env !== "object") return void 0;
525
+ const ns = env[ECOMIQ_CLIENTS_STORE_KV_BINDING];
526
+ if (!ns || typeof ns !== "object" || typeof ns.get !== "function") {
527
+ return void 0;
528
+ }
529
+ return ns;
530
+ }
531
+ async function getStorefrontSettingsFromKvByStoreKey(storeKey) {
532
+ const { env } = await getCloudflareContext({ async: true });
533
+ const kv = getKvNamespace(env);
534
+ if (!kv) {
535
+ const err = new Error(
536
+ `Binding KV "${ECOMIQ_CLIENTS_STORE_KV_BINDING}" no encontrado. Configura kv_namespaces en wrangler.`
537
+ );
538
+ throw Object.assign(err, { status: 500 });
539
+ }
540
+ const cacheKey = buildStorefrontKvSnapshotKeyFromStoreKey(storeKey);
541
+ const raw = await kv.get(cacheKey, { type: "json" });
542
+ if (raw == null) {
543
+ const err = new Error("Store settings not found in KV");
544
+ throw Object.assign(err, { status: 404 });
545
+ }
546
+ return StorefrontSettingsSchema.parse(raw);
547
+ }
548
+ async function getStorefrontSettingsFromKv(hostHeader) {
549
+ return getStorefrontSettingsFromKvByStoreKey(
550
+ resolveStorefrontSettingsHost(hostHeader)
551
+ );
552
+ }
425
553
 
426
- // src/core/utils/format-currency.ts
427
- function formatCurrency(amount, currency = "USD") {
428
- const locale = countryCurrency[currency] ?? "en-US";
429
- return new Intl.NumberFormat(locale, {
430
- style: "currency",
431
- currency
432
- }).format(amount);
554
+ // src/core/client/store-api-product.service.ts
555
+ init_store_api_client();
556
+ async function getStorefrontProducts(params, cacheKey) {
557
+ const parsed = StorefrontProductsParamsSchema.parse(params ?? {});
558
+ const searchParams = new URLSearchParams();
559
+ searchParams.set("page", String(parsed.page));
560
+ searchParams.set("pageSize", String(parsed.pageSize));
561
+ if (parsed.sort) searchParams.set("sort", parsed.sort);
562
+ if (parsed.sellerId) searchParams.set("sellerId", parsed.sellerId);
563
+ if (parsed.search) searchParams.set("search", parsed.search);
564
+ if (parsed.collection) searchParams.set("collection", parsed.collection);
565
+ if (parsed.category) searchParams.set("category", parsed.category);
566
+ if (parsed.brand) searchParams.set("brand", parsed.brand);
567
+ if (parsed.productType) searchParams.set("productType", parsed.productType);
568
+ if (parsed.inStock != null) searchParams.set("inStock", String(parsed.inStock));
569
+ if (parsed.minPrice != null) searchParams.set("minPrice", String(parsed.minPrice));
570
+ if (parsed.maxPrice != null) searchParams.set("maxPrice", String(parsed.maxPrice));
571
+ const path = `products?${searchParams.toString()}`;
572
+ const client = await getStoreApiClient();
573
+ const response = await client.get(path, {
574
+ cacheKey
575
+ });
576
+ return StorefrontProductsResponseSchema.parse(response);
577
+ }
578
+ async function getStorefrontProductBySlug(slug) {
579
+ const path = `products/${encodeURIComponent(slug)}`;
580
+ const client = await getStoreApiClient();
581
+ const response = await client.get(path);
582
+ return StorefrontProductDetailSchema.parse(response);
433
583
  }
434
584
 
435
- // src/core/lib/customer-order-labels.ts
436
- var ORDER_STATUS_LABELS = {
437
- New: "Pedido realizado",
438
- Confirmed: "Confirmado",
439
- Processing: "En preparaci\xF3n",
440
- InTransit: "En camino",
441
- Delivered: "Entregado",
442
- Cancelled: "Cancelado",
443
- Canceled: "Cancelado",
444
- Refunded: "Reembolsado",
445
- Pending: "Pendiente",
446
- Paid: "Pagado",
447
- Captured: "Pago confirmado"
448
- };
449
- function formatCustomerOrderStatus(status) {
450
- const trimmed = status.trim();
451
- if (!trimmed) return "Desconocido";
452
- return ORDER_STATUS_LABELS[trimmed] ?? trimmed.replace(/([A-Z])/g, " $1").trim();
453
- }
454
- function customerOrderStatusTone(status) {
455
- const s = status.toLowerCase();
456
- if (s.includes("cancel") || s.includes("refund")) return "danger";
457
- if (s.includes("deliver")) return "success";
458
- if (s.includes("transit") || s.includes("ship")) return "info";
459
- if (s.includes("confirm") || s.includes("paid") || s.includes("capture")) return "success";
460
- if (s.includes("pending") || s.includes("new") || s.includes("process")) return "warning";
461
- return "default";
462
- }
463
- function formatOrderDate(iso, locale = "es-PE") {
464
- const d = new Date(iso);
465
- if (Number.isNaN(d.getTime())) return iso;
466
- return new Intl.DateTimeFormat(locale, {
467
- day: "numeric",
468
- month: "long",
469
- year: "numeric"
470
- }).format(d);
471
- }
472
- function formatOrderDateTime(iso, locale = "es-PE") {
473
- const d = new Date(iso);
474
- if (Number.isNaN(d.getTime())) return iso;
475
- return new Intl.DateTimeFormat(locale, {
476
- day: "numeric",
477
- month: "short",
478
- year: "numeric",
479
- hour: "2-digit",
480
- minute: "2-digit"
481
- }).format(d);
585
+ // src/core/client/store-api-collection.service.ts
586
+ init_store_api_client();
587
+ async function getStorefrontCollections() {
588
+ const client = await getStoreApiClient();
589
+ const response = await client.get("collections");
590
+ return StorefrontCollectionsResponseSchema.parse(response);
591
+ }
592
+ async function getStorefrontCollectionBySlug(slug) {
593
+ const client = await getStoreApiClient();
594
+ const response = await client.get(
595
+ `collections/by-slug/${encodeURIComponent(slug)}`
596
+ );
597
+ return StorefrontCollectionDetailSchema.parse(response);
482
598
  }
483
599
 
484
- // src/core/lib/storefront-geo-inei.ts
485
- function toGeoItems(data) {
486
- if (!Array.isArray(data)) return [];
487
- return data.map((raw) => {
488
- if (!raw || typeof raw !== "object") return null;
489
- const item = raw;
490
- return {
491
- code: String(item.code ?? ""),
492
- name: String(item.name ?? "")
493
- };
494
- }).filter((x) => !!x && !!x.code && !!x.name);
495
- }
496
- var departmentsCache = null;
497
- var departmentsRequest = null;
498
- function loadStorefrontGeoDepartments(headers) {
499
- if (departmentsCache) return Promise.resolve(departmentsCache);
500
- if (departmentsRequest) return departmentsRequest;
501
- departmentsRequest = fetch(
502
- `/api/store/geo/departments?countryCode=PE&source=inei&limit=200`,
503
- { cache: "no-store", headers }
504
- ).then((r) => r.json()).then((data) => {
505
- const items = toGeoItems(data);
506
- departmentsCache = items;
507
- return items;
508
- }).catch(() => {
509
- departmentsCache = [];
510
- return [];
511
- }).finally(() => {
512
- departmentsRequest = null;
600
+ // src/core/client/store-api-pages.service.ts
601
+ init_store_api_client();
602
+ async function getStorefrontPages() {
603
+ const client = await getStoreApiClient();
604
+ const response = await client.get("pages");
605
+ return z.array(StorefrontPageListItemSchema).parse(response);
606
+ }
607
+ async function getStorefrontPageBySlug(slug) {
608
+ const path = `pages/${encodeURIComponent(slug)}`;
609
+ const client = await getStoreApiClient();
610
+ const response = await client.get(path);
611
+ return StorefrontPageDetailSchema.parse(response);
612
+ }
613
+ async function getStorefrontPagePreview(slug, token) {
614
+ const path = `pages/${encodeURIComponent(slug)}/preview?token=${encodeURIComponent(token)}`;
615
+ const client = await getStoreApiClient();
616
+ const response = await client.get(path);
617
+ return StorefrontPageDetailSchema.parse(response);
618
+ }
619
+
620
+ // src/core/client/store-api-cart.service.ts
621
+ init_store_api_client();
622
+ async function createCart(data) {
623
+ const payload = CartCreateRequestSchema.parse(data);
624
+ const client = await getStoreApiClient();
625
+ const response = await client.post("carts", payload);
626
+ return CartResponseSchema.parse(response);
627
+ }
628
+ async function getCart(cartId) {
629
+ if (!cartId) throw new Error("cartId is required");
630
+ const client = await getStoreApiClient();
631
+ const response = await client.get(`carts/${cartId}`);
632
+ return CartResponseSchema.parse(response);
633
+ }
634
+ async function addCartItem(data, cartId) {
635
+ if (!cartId) throw new Error("cartId is required");
636
+ const payload = CartItemRequestSchema.parse(data);
637
+ const variantId = Number(payload.variantId);
638
+ if (!Number.isSafeInteger(variantId) || variantId <= 0) {
639
+ throw new Error("variantId must be a valid public id");
640
+ }
641
+ const modifiers = payload.modifiers?.map((modifier) => {
642
+ const modifierGroupId = Number(modifier.modifierGroupId);
643
+ const optionIds = modifier.optionIds.map((optionId) => Number(optionId));
644
+ if (!Number.isSafeInteger(modifierGroupId) || modifierGroupId <= 0 || optionIds.some((optionId) => !Number.isSafeInteger(optionId) || optionId <= 0)) {
645
+ throw new Error("modifiers must use valid public ids");
646
+ }
647
+ return { modifierGroupId, optionIds };
513
648
  });
514
- return departmentsRequest;
515
- }
516
- async function fetchStorefrontGeoProvinces(headers, countryCode, departmentCode) {
517
- const res = await fetch(
518
- `/api/store/geo/provinces?countryCode=${encodeURIComponent(
519
- countryCode
520
- )}&departmentCode=${encodeURIComponent(departmentCode)}&source=inei&limit=200`,
521
- { headers }
649
+ const client = await getStoreApiClient();
650
+ const response = await client.post(`carts/${cartId}/items`, {
651
+ ...payload,
652
+ variantId,
653
+ ...modifiers ? { modifiers } : {}
654
+ });
655
+ return CartResponseSchema.parse(response);
656
+ }
657
+ async function patchCartBuyer(data, cartId) {
658
+ if (!cartId) throw new Error("cartId is required");
659
+ const payload = CartBuyerRequestSchema.parse(data);
660
+ const client = await getStoreApiClient();
661
+ const response = await client.patch(
662
+ `carts/${cartId}/buyer`,
663
+ payload
522
664
  );
523
- const data = await res.json();
524
- return toGeoItems(data);
525
- }
526
- async function fetchStorefrontGeoDistricts(headers, countryCode, provinceCode) {
527
- const res = await fetch(
528
- `/api/store/geo/districts?countryCode=${encodeURIComponent(
529
- countryCode
530
- )}&provinceCode=${encodeURIComponent(provinceCode)}&source=inei`,
531
- { headers }
665
+ return CartResponseSchema.parse(response);
666
+ }
667
+ async function patchCartAddresses(data, cartId) {
668
+ if (!cartId) throw new Error("cartId is required");
669
+ const payload = CartAddressesRequestSchema.parse(data);
670
+ const client = await getStoreApiClient();
671
+ const response = await client.patch(
672
+ `carts/${cartId}/addresses`,
673
+ payload
532
674
  );
533
- const data = await res.json();
534
- return toGeoItems(data);
535
- }
536
- async function resolveIneiCodesFromLabels(storeId, labels, countryCode = "PE") {
537
- const id = storeId.trim();
538
- if (!id) return null;
539
- const headers = { "X-Store-Id": id };
540
- const departments = await loadStorefrontGeoDepartments(headers);
541
- const dept = departments.find((d) => d.name.trim() === labels.department.trim());
542
- if (!dept) return null;
543
- const provinces = await fetchStorefrontGeoProvinces(headers, countryCode, dept.code);
544
- const prov = provinces.find((p) => p.name.trim() === labels.province.trim());
545
- if (!prov) {
546
- return { departmentCode: dept.code, provinceCode: "", districtCode: "" };
547
- }
548
- const districts = await fetchStorefrontGeoDistricts(headers, countryCode, prov.code);
549
- const dist = districts.find((d) => d.name.trim() === labels.district.trim());
550
- return {
551
- departmentCode: dept.code,
552
- provinceCode: prov.code,
553
- districtCode: dist?.code ?? ""
554
- };
675
+ return CartResponseSchema.parse(response);
555
676
  }
556
- var ApiClient = class {
557
- client;
558
- storeId;
559
- storeDomain;
560
- constructor(baseURL = "https://storefront.ecomiq.pe") {
561
- this.client = axios.create({
562
- baseURL,
563
- timeout: 1e4,
564
- headers: {
565
- "Content-Type": "application/json"
566
- }
567
- });
568
- this.client.interceptors.request.use(
569
- (config) => {
570
- if (config.headers) {
571
- if (this.storeId) config.headers["X-Store-Id"] = this.storeId;
572
- if (this.storeDomain) config.headers["X-Store-Domain"] = this.storeDomain;
573
- }
574
- return config;
575
- }
576
- );
577
- this.client.interceptors.response.use(
578
- (response) => response,
579
- (error) => {
580
- console.error("API Error:", error);
581
- return Promise.reject(error);
582
- }
583
- );
584
- }
585
- setStoreId(storeId) {
586
- this.storeId = storeId;
587
- }
588
- /** Subdominio para endpoints que usan X-Store-Domain (ej. GET /v1/storefront/settings) */
589
- setStoreDomain(domain) {
590
- this.storeDomain = domain;
591
- }
592
- get(url, config) {
593
- return this.client.get(url, config).then((response) => response.data);
594
- }
595
- post(url, data, config) {
596
- return this.client.post(url, data, config).then((response) => response.data);
597
- }
598
- put(url, data, config) {
599
- return this.client.put(url, data, config).then((response) => response.data);
600
- }
601
- patch(url, data, config) {
602
- return this.client.patch(url, data, config).then((response) => response.data);
603
- }
604
- delete(url, config) {
605
- return this.client.delete(url, config).then((response) => response.data);
677
+ async function addCartCoupon(cartId, data) {
678
+ if (!cartId) throw new Error("cartId is required");
679
+ const payload = CartCouponRequestSchema.parse(data);
680
+ const client = await getStoreApiClient();
681
+ const response = await client.post(
682
+ `carts/${cartId}/promotion-codes`,
683
+ payload
684
+ );
685
+ return CartResponseSchema.parse(response);
686
+ }
687
+ async function removeCartCoupon(cartId, code) {
688
+ if (!cartId) throw new Error("cartId is required");
689
+ if (!code.trim()) throw new Error("code is required");
690
+ const client = await getStoreApiClient();
691
+ const response = await client.delete(
692
+ `carts/${cartId}/promotion-codes/${encodeURIComponent(code.trim())}`
693
+ );
694
+ return CartResponseSchema.parse(response);
695
+ }
696
+ async function checkoutCart(data, cartId) {
697
+ if (!cartId) throw new Error("cartId is required");
698
+ const payload = CartCheckoutRequestSchema.parse(data);
699
+ const client = await getStoreApiClient();
700
+ const response = await client.post(
701
+ `carts/${cartId}/checkout`,
702
+ payload
703
+ );
704
+ return CartCheckoutResponseSchema.parse(response);
705
+ }
706
+ async function uploadPaymentReceipt(cartId, file) {
707
+ if (!cartId) throw new Error("cartId is required");
708
+ const baseUrl = await getStoreApiBaseUrl();
709
+ const form = new FormData();
710
+ form.set("file", file);
711
+ const response = await fetch(
712
+ `${baseUrl.replace(/\/$/, "")}/carts/${encodeURIComponent(cartId)}/payment-receipt`,
713
+ {
714
+ method: "POST",
715
+ body: form,
716
+ ...typeof window === "undefined" && { cache: "no-store" }
717
+ }
718
+ );
719
+ if (!response.ok) {
720
+ let body;
721
+ try {
722
+ const errorText = await response.text();
723
+ body = errorText ? JSON.parse(errorText) : void 0;
724
+ } catch {
725
+ body = void 0;
726
+ }
727
+ const message = typeof body === "object" && body !== null && "title" in body && typeof body.title === "string" ? body.title : response.statusText || "Store API error";
728
+ const error = new Error(message);
729
+ error.status = response.status;
730
+ error.body = body;
731
+ throw error;
606
732
  }
607
- };
608
- var apiClient = new ApiClient();
609
- var ProductVariantSchema = z.object({
610
- id: z.coerce.string(),
611
- name: z.string(),
612
- storeId: z.string(),
613
- productId: z.coerce.string(),
614
- basePrice: z.number().positive().optional().nullable(),
615
- comparePrice: z.number().positive().optional().nullable(),
616
- //TODO: remove this field
617
- originalPrice: z.number().positive().optional().nullable(),
618
- costPrice: z.number().positive().optional().nullable(),
619
- isDefault: z.boolean().optional().nullable(),
620
- reserveStock: z.number().nonnegative().optional().nullable(),
621
- weight: z.number().nonnegative().optional().nullable().default(0),
622
- width: z.number().nonnegative().optional().nullable(),
623
- height: z.number().nonnegative().optional().nullable(),
624
- unitOfMeasure: z.string().optional().nullable(),
625
- order: z.number().nonnegative().optional().nullable().default(0),
626
- sku: z.string().optional().nullable(),
627
- stock: z.number().nonnegative().optional().nullable(),
628
- leadTime: z.string().optional().nullable(),
629
- barCode: z.string().optional().nullable(),
630
- shippingWeight: z.number().nonnegative().optional().nullable(),
631
- images: z.array(z.string()).optional().nullable(),
632
- attributes: z.array(z.string()).optional().nullable(),
633
- variantStatus: z.string().optional().nullable(),
634
- status: z.string().optional().nullable(),
635
- createdOn: z.string().optional().nullable(),
636
- updatedOn: z.string().optional().nullable(),
637
- deletedOn: z.string().optional().nullable()
733
+ return CartPaymentReceiptResponseSchema.parse(await response.json());
734
+ }
735
+ async function getCartCheckout(cartId) {
736
+ if (!cartId) throw new Error("cartId is required");
737
+ const client = await getStoreApiClient();
738
+ const response = await client.get(`carts/${cartId}/checkout`);
739
+ return CartCheckoutReadResponseSchema.parse(response);
740
+ }
741
+ async function getShippingOptions(cartId, postalCode) {
742
+ if (!cartId) throw new Error("cartId is required");
743
+ if (!postalCode?.trim()) throw new Error("postalCode is required");
744
+ const client = await getStoreApiClient();
745
+ const q = new URLSearchParams({ postalCode: postalCode.trim() });
746
+ const response = await client.get(
747
+ `carts/${cartId}/shipping-options?${q.toString()}`
748
+ );
749
+ return ShippingOptionsResponseSchema.parse(response);
750
+ }
751
+ async function putCartShippingMethod(cartId, data) {
752
+ if (!cartId) throw new Error("cartId is required");
753
+ const payload = CartShippingMethodPutRequestSchema.parse(data);
754
+ const client = await getStoreApiClient();
755
+ const response = await client.put(
756
+ `carts/${cartId}/shipping-method`,
757
+ payload
758
+ );
759
+ return CartResponseSchema.parse(response);
760
+ }
761
+ async function getCartPaymentMethods(cartId) {
762
+ if (!cartId) throw new Error("cartId is required");
763
+ const { paymentMethods } = await getCartCheckout(cartId);
764
+ return paymentMethods;
765
+ }
766
+
767
+ // src/core/client/index.ts
768
+ init_store_api_customer_service();
769
+
770
+ // src/core/client/store-api-customer-order.service.ts
771
+ init_store_api_client();
772
+ var CustomerOrderShippingAddressSchema = z.object({
773
+ name: z.string().nullable().optional(),
774
+ company: z.string().nullable().optional(),
775
+ address1: z.string(),
776
+ address2: z.string().nullable().optional(),
777
+ city: z.string().nullable().optional(),
778
+ countryCode: z.string(),
779
+ province: z.string().nullable().optional(),
780
+ postalCode: z.string().nullable().optional(),
781
+ district: z.string().nullable().optional(),
782
+ phone: z.string().nullable().optional(),
783
+ latitude: z.number().nullable().optional(),
784
+ longitude: z.number().nullable().optional(),
785
+ metadata: z.record(z.unknown()).nullable().optional()
786
+ }).passthrough();
787
+ var CustomerOrderLineItemSchema = z.object({
788
+ id: z.string(),
789
+ orderId: z.coerce.string(),
790
+ productId: z.coerce.string().nullable(),
791
+ variantId: z.coerce.string().nullable(),
792
+ title: z.string(),
793
+ subtitle: z.string().nullable().optional(),
794
+ thumbnail: z.string().nullable().optional(),
795
+ variantTitle: z.string().nullable().optional(),
796
+ productHandle: z.string().nullable().optional(),
797
+ quantity: z.number(),
798
+ unitPrice: z.number(),
799
+ total: z.number(),
800
+ status: z.string()
638
801
  });
639
- var ProductSchema = z.object({
802
+ var CustomerOrderListItemSchema = z.object({
803
+ orderId: z.coerce.string(),
804
+ orderNumber: z.string(),
805
+ orderGroup: z.string(),
806
+ orderIds: z.array(z.coerce.string()),
807
+ status: z.string(),
808
+ total: z.number(),
809
+ totalPaid: z.number(),
810
+ currencyCode: z.string(),
811
+ orderDate: z.string(),
812
+ createdOn: z.string(),
813
+ updatedOn: z.string().nullable().optional(),
814
+ paymentAt: z.string().nullable().optional(),
815
+ shippingAddress: CustomerOrderShippingAddressSchema.nullable().optional(),
816
+ shippingAddressSummary: z.string().nullable().optional(),
817
+ items: z.array(CustomerOrderLineItemSchema)
818
+ });
819
+ var CustomerOrderPaymentSchema = z.object({
820
+ id: z.string(),
821
+ paymentMethod: z.string(),
822
+ paymentCode: z.string(),
823
+ amount: z.number(),
824
+ paymentStatus: z.string(),
825
+ receiptUrl: z.string().nullable().optional(),
826
+ createdOn: z.string()
827
+ });
828
+ var CustomerOrderStatusTimelineSchema = z.object({
829
+ key: z.string(),
830
+ label: z.string(),
831
+ occurredAt: z.string(),
832
+ description: z.string().nullable().optional(),
833
+ status: z.string(),
834
+ source: z.string()
835
+ });
836
+ var CustomerOrderDetailSchema = z.object({
640
837
  id: z.coerce.string(),
641
- name: z.string(),
642
- images: z.array(z.object({
643
- url: z.string().url(),
644
- name: z.string().optional(),
645
- isPrimary: z.boolean().optional()
646
- })),
647
- basePrice: z.number().positive().optional(),
648
- salePrice: z.number().positive().optional(),
649
- slug: z.string(),
650
- description: z.string().optional(),
651
- categories: z.array(z.object({
652
- id: z.coerce.string(),
653
- name: z.string(),
654
- slug: z.string(),
655
- logo: z.string().optional(),
656
- icon: z.string().optional()
657
- })).optional(),
658
- tags: z.array(z.string()).optional(),
659
- inStock: z.boolean().optional(),
660
- variants: z.array(ProductVariantSchema).optional()
661
- });
662
- var ProductsResponseSchema = z.object({
663
- items: z.array(ProductSchema),
664
- meta: z.object({
665
- total: z.number().int().positive(),
666
- page: z.number().int().positive(),
667
- pageSize: z.number().int().positive(),
668
- hasNextPage: z.boolean()
669
- })
670
- });
671
- var ProductsFiltersSchema = z.object({
672
- category: z.string().optional(),
673
- search: z.string().optional(),
674
- minPrice: z.number().positive().optional(),
675
- maxPrice: z.number().positive().optional(),
676
- inStock: z.boolean().optional(),
677
- sortBy: z.enum(["name", "price", "createdAt"]).optional(),
678
- sortOrder: z.enum(["asc", "desc"]).optional(),
679
- page: z.number().int().positive().optional(),
680
- limit: z.number().int().positive().optional()
681
- });
682
- var StorefrontProductSchema = z.object({
683
- id: z.coerce.string(),
684
- name: z.string(),
685
- slug: z.string(),
686
- productType: z.string().optional().nullable(),
687
- imageUrl: z.string().optional().nullable(),
688
- price: z.number().nonnegative(),
689
- compareAtPrice: z.number().nonnegative().optional().nullable(),
690
- variantCount: z.number().int().nonnegative().optional().default(0),
691
- isAvailable: z.boolean(),
692
- totalInventory: z.number().int().nonnegative(),
693
- bundleItemsCount: z.number().int().nonnegative().optional().default(0),
694
- brand: z.string().optional().nullable(),
695
- category: z.string().optional().nullable(),
696
- status: z.string().optional().nullable(),
697
- tags: z.array(z.string()).optional().nullable()
698
- });
699
- var StorefrontFacetSchema = z.object({
700
- value: z.string(),
701
- count: z.number().int().nonnegative()
702
- });
703
- var StorefrontProductsResponseSchema = z.object({
704
- pageIndex: z.number().int().positive(),
705
- pageSize: z.number().int().positive(),
706
- totalCount: z.number().int().nonnegative(),
707
- totalPages: z.number().int().nonnegative(),
708
- products: z.array(StorefrontProductSchema),
709
- categories: z.array(StorefrontFacetSchema).optional().default([]),
710
- brands: z.array(StorefrontFacetSchema).optional().default([]),
711
- minPrice: z.number().nonnegative().optional().nullable(),
712
- maxPrice: z.number().nonnegative().optional().nullable()
713
- });
714
- var StorefrontProductsParamsSchema = z.object({
715
- page: z.number().int().positive().optional().default(1),
716
- pageSize: z.number().int().positive().optional().default(20),
717
- sort: z.enum(["price_asc", "price_desc", "name_asc", "name_desc", "newest"]).optional().default("price_asc"),
718
- sellerId: z.string().optional(),
719
- search: z.string().optional(),
720
- collection: z.string().optional(),
721
- category: z.string().optional(),
722
- brand: z.string().optional(),
723
- productType: z.string().optional(),
724
- inStock: z.boolean().optional(),
725
- minPrice: z.number().nonnegative().optional(),
726
- maxPrice: z.number().nonnegative().optional()
727
- });
728
- var StorefrontVariantOptionSchema = z.object({
729
- key: z.string(),
730
- name: z.string(),
731
- value: z.string()
732
- });
733
- var StorefrontProductImageSchema = z.object({
734
- url: z.string(),
735
- name: z.string().optional().nullable(),
736
- order: z.number().int().nonnegative().optional()
737
- });
738
- var StorefrontVariantSchema = z.object({
739
- id: z.coerce.string().optional().default(""),
740
- sku: z.string(),
741
- status: z.string().optional().nullable(),
742
- price: z.number().nonnegative(),
743
- compareAtPrice: z.number().nonnegative().optional().nullable(),
744
- quantity: z.number().int().nonnegative(),
745
- options: z.array(StorefrontVariantOptionSchema).optional().default([]),
746
- images: z.array(StorefrontProductImageSchema).optional().default([])
838
+ orderNumber: z.string(),
839
+ orderGroup: z.string(),
840
+ status: z.string(),
841
+ total: z.number(),
842
+ totalPaid: z.number(),
843
+ currencyCode: z.string(),
844
+ orderDate: z.string(),
845
+ createdOn: z.string(),
846
+ updatedOn: z.string().nullable().optional(),
847
+ paymentAt: z.string().nullable().optional(),
848
+ subTotal: z.number(),
849
+ discount: z.number(),
850
+ shipping: z.number(),
851
+ tax: z.number(),
852
+ email: z.string(),
853
+ phone: z.string(),
854
+ firstName: z.string(),
855
+ lastName: z.string(),
856
+ shippingAddress: CustomerOrderShippingAddressSchema.nullable().optional(),
857
+ shippingAddressSummary: z.string().nullable().optional(),
858
+ billingAddress: CustomerOrderShippingAddressSchema.nullable().optional(),
859
+ items: z.array(CustomerOrderLineItemSchema),
860
+ payments: z.array(CustomerOrderPaymentSchema),
861
+ statusTimeline: z.array(CustomerOrderStatusTimelineSchema)
747
862
  });
748
- var StorefrontPriceTierSchema = z.object({
749
- variantId: z.coerce.string(),
750
- minQuantity: z.number().int().positive(),
751
- maxQuantity: z.number().int().positive().optional().nullable(),
752
- unitPrice: z.number().nonnegative()
863
+ function authOptions(accessToken) {
864
+ const token = typeof accessToken === "string" && accessToken.trim().length > 0 ? accessToken.trim() : void 0;
865
+ return token ? { bearerToken: token } : {};
866
+ }
867
+ async function getCustomerOrders(accessToken) {
868
+ const client = await getStoreApiClient();
869
+ const response = await client.get("customer/orders", authOptions(accessToken));
870
+ return z.array(CustomerOrderListItemSchema).parse(response);
871
+ }
872
+ async function getCustomerOrder(orderId, accessToken) {
873
+ const client = await getStoreApiClient();
874
+ const id = orderId.trim();
875
+ if (!id) throw new Error("orderId requerido");
876
+ const response = await client.get(
877
+ `customer/orders/${encodeURIComponent(id)}`,
878
+ authOptions(accessToken)
879
+ );
880
+ return CustomerOrderDetailSchema.parse(response);
881
+ }
882
+ var StorefrontOrderAddressSchema = z.object({
883
+ address1: z.string().optional(),
884
+ address2: z.string().optional(),
885
+ city: z.string().optional(),
886
+ province: z.string().optional(),
887
+ country: z.string().optional(),
888
+ zip: z.string().optional(),
889
+ phone: z.string().optional(),
890
+ firstName: z.string().optional(),
891
+ lastName: z.string().optional()
892
+ }).passthrough();
893
+ var OrderDraftItemSchema = z.object({
894
+ id: z.string(),
895
+ productId: z.coerce.string().nullable().optional(),
896
+ sellerId: z.string().nullable().optional(),
897
+ shippingProfileId: z.string().nullable().optional(),
898
+ variantId: z.coerce.string().nullable().optional(),
899
+ title: z.string(),
900
+ subtitle: z.string().nullable().optional(),
901
+ thumbnail: z.string().nullable().optional(),
902
+ quantity: z.number().optional().default(1),
903
+ unitPrice: z.number().optional().default(0),
904
+ subTotal: z.number().optional().default(0),
905
+ discount: z.number().optional().default(0),
906
+ shipping: z.number().optional().default(0),
907
+ tax: z.number().optional().default(0),
908
+ warranty: z.number().optional().default(0),
909
+ total: z.number().optional().default(0),
910
+ requiresShipping: z.boolean().optional().default(true),
911
+ isTaxInclusive: z.boolean().optional().default(true),
912
+ metadata: z.record(z.unknown()).optional().default({})
753
913
  });
754
- var StorefrontBundleItemSchema = z.object({
755
- productId: z.coerce.string(),
756
- variantId: z.coerce.string(),
757
- productName: z.string(),
758
- variantName: z.string().optional().nullable(),
759
- sku: z.string().optional().nullable(),
760
- brand: z.string().optional().nullable(),
761
- imageUrl: z.string().optional().nullable(),
762
- quantity: z.number().int(),
763
- availableStock: z.number().int().optional().default(0),
764
- allowBackorders: z.boolean().optional().default(false),
765
- requiresShipping: z.boolean().optional().default(true)
914
+ var OrderDraftShippingMethodSchema = z.object({
915
+ id: z.string(),
916
+ name: z.string().optional().default(""),
917
+ description: z.string().nullable().optional(),
918
+ subtotal: z.number().optional().default(0),
919
+ discountTotal: z.number().optional().default(0),
920
+ taxTotal: z.number().optional().default(0),
921
+ total: z.number().optional().default(0),
922
+ isTaxInclusive: z.boolean().optional().default(true),
923
+ sellerId: z.string().nullable().optional(),
924
+ shippingProfileId: z.string().nullable().optional(),
925
+ shippingMethodId: z.string().nullable().optional(),
926
+ connectionId: z.string().nullable().optional(),
927
+ providerName: z.string().nullable().optional(),
928
+ trackingCode: z.string().nullable().optional(),
929
+ estimatedDays: z.number().nullable().optional(),
930
+ zoneCode: z.string().nullable().optional(),
931
+ locationId: z.string().nullable().optional(),
932
+ pickupPointId: z.string().nullable().optional(),
933
+ scheduledDate: z.string().nullable().optional(),
934
+ scheduledTime: z.string().nullable().optional(),
935
+ data: z.record(z.unknown()).optional().default({})
766
936
  });
767
- var StorefrontProductDetailSchema = z.object({
768
- id: z.coerce.string(),
769
- name: z.string(),
770
- slug: z.string(),
771
- description: z.string().optional().nullable(),
772
- productType: z.string().optional().nullable(),
773
- status: z.string().optional().nullable(),
774
- imageUrl: z.string().optional().nullable(),
775
- price: z.number().nonnegative(),
776
- compareAtPrice: z.number().nonnegative().optional().nullable(),
777
- isAvailable: z.boolean(),
778
- totalInventory: z.number().int().nonnegative(),
779
- brand: z.string().optional().nullable(),
780
- category: z.string().optional().nullable(),
781
- categoryId: z.coerce.string().optional().nullable(),
782
- tags: z.array(z.string()).optional().nullable(),
783
- specifications: z.array(z.record(z.string(), z.unknown())).optional().default([]),
784
- variants: z.array(StorefrontVariantSchema).optional().default([]),
785
- images: z.array(StorefrontProductImageSchema).optional().default([]),
786
- bundleItems: z.array(StorefrontBundleItemSchema).optional().default([]),
787
- priceTiers: z.array(StorefrontPriceTierSchema).optional().default([]),
788
- createdAt: z.string().optional().nullable(),
789
- updatedAt: z.string().optional().nullable()
937
+ var OrderDraftSchema = z.object({
938
+ id: z.string(),
939
+ storeId: z.string().optional(),
940
+ customerId: z.coerce.string().nullable().optional(),
941
+ cartId: z.string().nullable().optional(),
942
+ draftNumber: z.string(),
943
+ currencyCode: z.string().optional().default("PEN"),
944
+ source: z.string().nullable().optional(),
945
+ notes: z.string().nullable().optional(),
946
+ email: z.string().nullable().optional(),
947
+ phone: z.string().nullable().optional(),
948
+ firstName: z.string().nullable().optional(),
949
+ lastName: z.string().nullable().optional(),
950
+ identificationType: z.string().nullable().optional(),
951
+ identificationNumber: z.string().nullable().optional(),
952
+ entityTaxId: z.string().nullable().optional(),
953
+ entityName: z.string().nullable().optional(),
954
+ status: z.string(),
955
+ draftDate: z.string().nullable().optional(),
956
+ sentAt: z.string().nullable().optional(),
957
+ viewedAt: z.string().nullable().optional(),
958
+ acceptedAt: z.string().nullable().optional(),
959
+ rejectedAt: z.string().nullable().optional(),
960
+ expiredAt: z.string().nullable().optional(),
961
+ cancelledAt: z.string().nullable().optional(),
962
+ convertedAt: z.string().nullable().optional(),
963
+ shareToken: z.string().nullable().optional(),
964
+ shareExpiresAt: z.string().nullable().optional(),
965
+ allowAnonymousShareAccess: z.boolean().optional().default(false),
966
+ sharedAt: z.string().nullable().optional(),
967
+ billingAddress: StorefrontOrderAddressSchema.optional().nullable(),
968
+ shippingAddress: StorefrontOrderAddressSchema.optional().nullable(),
969
+ metadata: z.record(z.unknown()).optional().default({}),
970
+ subTotal: z.number().optional().default(0),
971
+ discount: z.number().optional().default(0),
972
+ shipping: z.number().optional().default(0),
973
+ tax: z.number().optional().default(0),
974
+ warranty: z.number().optional().default(0),
975
+ total: z.number().optional().default(0),
976
+ paymentMethod: z.string().optional().default(""),
977
+ paymentCode: z.string().optional().default(""),
978
+ items: z.array(OrderDraftItemSchema).optional().default([]),
979
+ shippingMethods: z.array(OrderDraftShippingMethodSchema).optional().default([]),
980
+ createdOn: z.string().optional(),
981
+ updatedOn: z.string().nullable().optional()
790
982
  });
791
983
 
792
- // src/core/services/product.service.ts
793
- var ProductService = class _ProductService {
794
- static instance;
795
- constructor() {
796
- }
797
- static getInstance() {
798
- if (!_ProductService.instance) {
799
- _ProductService.instance = new _ProductService();
800
- }
801
- return _ProductService.instance;
802
- }
803
- /**
804
- * Obtiene todos los productos del store
805
- */
806
- async getProducts(filters) {
807
- if (filters) {
808
- ProductsFiltersSchema.parse(filters);
809
- }
810
- const params = new URLSearchParams();
811
- if (filters) {
812
- if (filters.category) params.append("category", filters.category);
813
- if (filters.search) params.append("search", filters.search);
814
- if (filters.minPrice) params.append("minPrice", filters.minPrice.toString());
815
- if (filters.maxPrice) params.append("maxPrice", filters.maxPrice.toString());
816
- if (filters.sortBy) params.append("sortBy", filters.sortBy);
817
- if (filters.sortOrder) params.append("sortOrder", filters.sortOrder);
818
- if (filters.page) params.append("page", filters.page.toString());
819
- if (filters.limit) params.append("limit", filters.limit.toString());
820
- }
821
- const queryString = params.toString();
822
- const url = `/${STOREFRONT_API_PATH_PREFIX}/products${queryString ? `?${queryString}` : ""}`;
823
- const response = await apiClient.get(url);
824
- return ProductsResponseSchema.parse(response);
825
- }
826
- /**
827
- * Obtiene un producto específico por ID
828
- */
829
- async getProduct(id) {
830
- const response = await apiClient.get(`/${STOREFRONT_API_PATH_PREFIX}/products/${id}`);
831
- return ProductSchema.parse(response);
832
- }
833
- /**
834
- * Obtiene un producto específico por slug
835
- */
836
- async getProductBySlug(slug) {
837
- const response = await apiClient.get(`/${STOREFRONT_API_PATH_PREFIX}/products/${encodeURIComponent(slug)}`);
838
- return ProductSchema.parse(response);
839
- }
840
- /**
841
- * Busca productos por término de búsqueda
842
- */
843
- async searchProducts(query, filters) {
844
- return this.getProducts({ ...filters, search: query });
845
- }
846
- /**
847
- * Obtiene productos por categoría
848
- */
849
- async getProductsByCategory(category, filters) {
850
- return this.getProducts({ ...filters, category });
851
- }
852
- /**
853
- * Obtiene productos en oferta (con descuento)
854
- */
855
- async getProductsOnSale(filters) {
856
- return this.getProducts({ ...filters, minPrice: 0 });
857
- }
858
- /**
859
- * Obtiene productos en stock
860
- */
861
- async getProductsInStock(filters) {
862
- return this.getProducts({ ...filters, inStock: true });
863
- }
864
- /**
865
- * Lista de productos del storefront (API v1/storefront/products).
866
- * Soporta paginación, orden y filtros por categoría, marca y precio.
867
- */
868
- async getStorefrontProducts(params) {
869
- const parsed = StorefrontProductsParamsSchema.parse(params ?? {});
870
- const searchParams = new URLSearchParams();
871
- searchParams.set("page", String(parsed.page));
872
- searchParams.set("pageSize", String(parsed.pageSize));
873
- if (parsed.sort) searchParams.set("sort", parsed.sort);
874
- if (parsed.sellerId) searchParams.set("sellerId", parsed.sellerId);
875
- if (parsed.collection) searchParams.set("collection", parsed.collection);
876
- if (parsed.category) searchParams.set("category", parsed.category);
877
- if (parsed.brand) searchParams.set("brand", parsed.brand);
878
- if (parsed.productType) searchParams.set("productType", parsed.productType);
879
- if (parsed.inStock != null) searchParams.set("inStock", String(parsed.inStock));
880
- if (parsed.minPrice != null) searchParams.set("minPrice", String(parsed.minPrice));
881
- if (parsed.maxPrice != null) searchParams.set("maxPrice", String(parsed.maxPrice));
882
- if (parsed.search) searchParams.set("search", parsed.search);
883
- const url = `/${STOREFRONT_API_PATH_PREFIX}/products?${searchParams.toString()}`;
884
- const response = await apiClient.get(url);
885
- return StorefrontProductsResponseSchema.parse(response);
886
- }
887
- /**
888
- * Obtiene un producto del storefront por slug (API v1/storefront/products/by-slug/:slug).
889
- */
890
- async getStorefrontProductBySlug(slug) {
891
- const response = await apiClient.get(`/${STOREFRONT_API_PATH_PREFIX}/products/by-slug/${encodeURIComponent(slug)}`);
892
- return StorefrontProductDetailSchema.parse(response);
984
+ // src/core/client/store-api-order-draft.service.ts
985
+ init_store_api_client();
986
+ async function getSharedOrderDraft(shareToken) {
987
+ const client = await getStoreApiClient();
988
+ const response = await client.get(
989
+ `order-drafts/share/${encodeURIComponent(shareToken)}`,
990
+ { noStore: true }
991
+ );
992
+ return OrderDraftSchema.parse(response);
993
+ }
994
+ var defaultOptions = {
995
+ queries: {
996
+ staleTime: 60 * 1e3
893
997
  }
894
998
  };
895
- var productService = ProductService.getInstance();
896
- var StorefrontCollectionSchema = z.object({
897
- id: z.coerce.string(),
898
- slug: z.string(),
899
- name: z.string(),
900
- description: z.string().nullable().optional(),
901
- imageUrl: z.string().nullable().optional(),
902
- type: z.string(),
903
- productCount: z.number().int().nonnegative()
904
- });
905
- var StorefrontCollectionsResponseSchema = z.array(StorefrontCollectionSchema);
906
- var StorefrontCollectionDetailSchema = StorefrontCollectionSchema;
907
-
908
- // src/core/services/collection.service.ts
909
- var CollectionService = class _CollectionService {
910
- static instance;
911
- constructor() {
912
- }
913
- static getInstance() {
914
- if (!_CollectionService.instance) {
915
- _CollectionService.instance = new _CollectionService();
916
- }
917
- return _CollectionService.instance;
918
- }
919
- /**
920
- * Lista de colecciones del storefront (GET /v1/collections).
921
- */
922
- async getStorefrontCollections() {
923
- const response = await apiClient.get(
924
- `/${STOREFRONT_API_PATH_PREFIX}/collections`
925
- );
926
- return StorefrontCollectionsResponseSchema.parse(response);
999
+ function QueryProvider({ children, dehydratedState }) {
1000
+ const queryClientRef = useRef(null);
1001
+ const hydratedRef = useRef(false);
1002
+ if (queryClientRef.current === null) {
1003
+ queryClientRef.current = new QueryClient({ defaultOptions });
927
1004
  }
928
- /**
929
- * Obtiene una colección del storefront por slug (GET /v1/collections/by-slug/:slug).
930
- */
931
- async getStorefrontCollectionBySlug(slug) {
932
- const response = await apiClient.get(
933
- `/${STOREFRONT_API_PATH_PREFIX}/collections/by-slug/${encodeURIComponent(slug)}`
1005
+ const queryClient = queryClientRef.current;
1006
+ useEffect(() => {
1007
+ if (dehydratedState && !hydratedRef.current) {
1008
+ hydrate(queryClient, dehydratedState);
1009
+ hydratedRef.current = true;
1010
+ }
1011
+ }, [queryClient, dehydratedState]);
1012
+ return /* @__PURE__ */ jsx(QueryClientProvider, { client: queryClient, children });
1013
+ }
1014
+ var StorefrontAuthContext = createContext(null);
1015
+ function StorefrontAuthProvider({
1016
+ authClient,
1017
+ children
1018
+ }) {
1019
+ return /* @__PURE__ */ jsx(StorefrontAuthContext.Provider, { value: authClient, children });
1020
+ }
1021
+ function useStorefrontAuthClient() {
1022
+ const client = useContext(StorefrontAuthContext);
1023
+ if (!client) {
1024
+ throw new Error(
1025
+ "useStorefrontAuthClient debe usarse dentro de StorefrontAuthProvider (o EcomiqProvider con authClient)."
934
1026
  );
935
- return StorefrontCollectionDetailSchema.parse(response);
936
1027
  }
937
- };
938
- var collectionService = CollectionService.getInstance();
939
- var StorefrontPageListItemSchema = z.object({
940
- id: z.string(),
941
- name: z.string(),
942
- slug: z.string(),
943
- layout: z.string()
944
- });
945
- var StorefrontPageSeoSchema = z.object({
946
- title: z.string().optional(),
947
- description: z.string().optional()
948
- }).optional();
949
- var StorefrontPageSettingsSchema = z.object({
950
- seo: StorefrontPageSeoSchema,
951
- html: z.string().optional(),
952
- title: z.string().optional()
953
- }).catchall(z.unknown());
954
- var StorefrontPageBlockSchema = z.object({
955
- id: z.string(),
956
- type: z.string(),
957
- order: z.number(),
958
- settings: z.record(z.unknown()).optional()
959
- });
960
- var StorefrontPageSectionSchema = z.object({
961
- id: z.string(),
962
- type: z.string(),
963
- order: z.number(),
964
- settings: z.record(z.unknown()).optional(),
965
- blocks: z.array(StorefrontPageBlockSchema).optional().default([])
966
- });
967
- var StorefrontPageDetailSchema = z.object({
968
- id: z.string(),
969
- name: z.string(),
970
- slug: z.string(),
971
- layout: z.string(),
972
- settings: StorefrontPageSettingsSchema.optional(),
973
- sections: z.array(StorefrontPageSectionSchema).optional()
974
- });
975
-
976
- // src/core/services/page.service.ts
977
- var PageService = class _PageService {
978
- static instance;
979
- constructor() {
980
- }
981
- static getInstance() {
982
- if (!_PageService.instance) {
983
- _PageService.instance = new _PageService();
984
- }
985
- return _PageService.instance;
986
- }
987
- /**
988
- * Lista de páginas del storefront (GET /v1/pages).
989
- */
990
- async getPages() {
991
- const response = await apiClient.get(
992
- `/${STOREFRONT_API_PATH_PREFIX}/pages`
993
- );
994
- return z.array(StorefrontPageListItemSchema).parse(response);
995
- }
996
- /**
997
- * Obtiene una página por slug (GET /v1/pages/{slug}).
998
- * sections puede no venir en la respuesta.
999
- */
1000
- async getPageBySlug(slug) {
1001
- const response = await apiClient.get(
1002
- `/${STOREFRONT_API_PATH_PREFIX}/pages/${encodeURIComponent(slug)}`
1003
- );
1004
- return StorefrontPageDetailSchema.parse(response);
1005
- }
1006
- };
1007
- var pageService = PageService.getInstance();
1008
- var StorefrontCheckoutSettingsSchema = z.object({
1009
- allowGuestCheckout: z.boolean().optional(),
1010
- identityScope: z.enum(["store", "tenant", "global"]).optional(),
1011
- signInMethods: z.array(z.string()).optional()
1012
- });
1013
- var looseRecord = z.record(z.string(), z.unknown());
1014
- var StorefrontThemeConfigSchema = z.object({
1015
- name: z.string().nullable().optional(),
1016
- mode: z.string().nullable().optional(),
1017
- colors: looseRecord.optional(),
1018
- fonts: looseRecord.optional(),
1019
- assets: looseRecord.optional()
1020
- }).passthrough();
1021
- var StorefrontFeaturesEnabledSchema = z.object({
1022
- flags: looseRecord.optional()
1023
- }).passthrough();
1024
- var StorefrontCmsBrandingSchema = z.object({
1025
- logoUrl: z.string().nullable().optional(),
1026
- logoDarkUrl: z.string().nullable().optional(),
1027
- faviconUrl: z.string().nullable().optional(),
1028
- socialImageUrl: z.string().nullable().optional()
1029
- });
1030
- var StorefrontCmsSeoSchema = z.object({
1031
- siteName: z.string().nullable().optional(),
1032
- metaTitle: z.string().nullable().optional(),
1033
- metaDescription: z.string().nullable().optional(),
1034
- titleTemplate: z.string().nullable().optional(),
1035
- robotsIndex: z.boolean().optional()
1036
- });
1037
- var StorefrontCmsSocialLinksSchema = z.object({
1038
- instagram: z.string().nullable().optional(),
1039
- facebook: z.string().nullable().optional(),
1040
- tiktok: z.string().nullable().optional(),
1041
- youtube: z.string().nullable().optional(),
1042
- x: z.string().nullable().optional()
1043
- });
1044
- var StorefrontCmsContactSchema = z.object({
1045
- supportEmail: z.string().nullable().optional(),
1046
- phone: z.string().nullable().optional(),
1047
- whatsapp: z.string().nullable().optional(),
1048
- address: z.string().nullable().optional(),
1049
- socialLinks: StorefrontCmsSocialLinksSchema.optional()
1050
- });
1051
- var StorefrontCmsTrackingSchema = z.object({
1052
- googleAnalyticsMeasurementId: z.string().nullable().optional(),
1053
- metaPixelId: z.string().nullable().optional(),
1054
- googleTagManagerContainerId: z.string().nullable().optional()
1055
- });
1056
- var StorefrontCmsCustomScriptsSchema = z.object({
1057
- enabled: z.boolean().optional(),
1058
- head: z.string().nullable().optional(),
1059
- bodyEnd: z.string().nullable().optional()
1060
- });
1061
- var StorefrontCmsSettingsSchema = z.object({
1062
- themeKey: z.string().optional(),
1063
- branding: StorefrontCmsBrandingSchema.optional(),
1064
- seo: StorefrontCmsSeoSchema.optional(),
1065
- contact: StorefrontCmsContactSchema.optional(),
1066
- tracking: StorefrontCmsTrackingSchema.optional(),
1067
- customScripts: StorefrontCmsCustomScriptsSchema.optional(),
1068
- updatedOn: z.string().nullable().optional()
1069
- });
1070
- var StorefrontCmsPageSchema = z.object({
1071
- id: z.string(),
1072
- name: z.string(),
1073
- slug: z.string(),
1074
- layout: z.string().optional(),
1075
- version: z.number().optional(),
1076
- settings: z.record(z.string(), z.unknown()).optional().nullable(),
1077
- sections: z.array(z.unknown()).optional(),
1078
- status: z.string().optional(),
1079
- hasPublishedVersion: z.boolean().optional(),
1080
- publishedOn: z.string().nullable().optional(),
1081
- publishedBy: z.string().nullable().optional(),
1082
- createdOn: z.string().nullable().optional(),
1083
- updatedOn: z.string().nullable().optional(),
1084
- createdBy: z.string().nullable().optional(),
1085
- updatedBy: z.string().nullable().optional(),
1086
- lastModifiedBy: z.string().nullable().optional()
1087
- }).passthrough();
1088
- var StorefrontSettingsLegacyFlatSchema = z.object({
1089
- id: z.coerce.string(),
1090
- tenantId: z.string().nullable().optional(),
1091
- name: z.string(),
1092
- slug: z.coerce.string().optional(),
1093
- description: z.string().nullable().optional(),
1094
- visibility: z.string().nullable().optional(),
1095
- logo: z.string().nullable().optional(),
1096
- favicon: z.string().nullable().optional(),
1097
- icon: z.string().nullable().optional(),
1098
- currency: z.string().nullable().optional(),
1099
- language: z.string().nullable().optional(),
1100
- themeConfig: z.unknown().optional().nullable(),
1101
- featuresEnabled: z.unknown().optional().nullable(),
1102
- checkoutSettings: StorefrontCheckoutSettingsSchema.optional().nullable()
1103
- }).passthrough();
1104
- function unwrapSettingsResponseBody(v) {
1105
- if (!v || typeof v !== "object" || Array.isArray(v)) return v;
1106
- const o = v;
1107
- if ("data" in o && o.data != null && typeof o.data === "object" && !Array.isArray(o.data)) {
1108
- return o.data;
1028
+ return client;
1029
+ }
1030
+ function storefrontSessionEndsSoon(expiresAt, skewMs = 12e4) {
1031
+ if (!expiresAt) return false;
1032
+ return Date.now() + skewMs >= expiresAt.getTime();
1033
+ }
1034
+ var storefrontAccessTokenNeedsRefresh = storefrontSessionEndsSoon;
1035
+ function parseStorefrontSessionExpires(at) {
1036
+ if (!at) return void 0;
1037
+ if (at instanceof Date) return Number.isNaN(at.getTime()) ? void 0 : at;
1038
+ if (typeof at === "string") {
1039
+ const d = new Date(at);
1040
+ return Number.isNaN(d.getTime()) ? void 0 : d;
1109
1041
  }
1110
- return v;
1042
+ return void 0;
1111
1043
  }
1112
- function isLegacyFlatSettingsPayload(v) {
1113
- if (!v || typeof v !== "object" || Array.isArray(v)) return false;
1114
- const o = v;
1115
- if ("store" in o || "schemaVersion" in o || "cms" in o) return false;
1116
- return (typeof o.id === "string" || typeof o.id === "number") && typeof o.name === "string";
1044
+ function mapSessionUser(raw) {
1045
+ if (!raw || typeof raw !== "object") return null;
1046
+ const u = raw;
1047
+ const explicitFirst = typeof u.firstName === "string" ? u.firstName : typeof u.given_name === "string" ? u.given_name : "";
1048
+ const explicitLast = typeof u.lastName === "string" ? u.lastName : typeof u.family_name === "string" ? u.family_name : "";
1049
+ if (explicitFirst || explicitLast) {
1050
+ return {
1051
+ id: String(u.id ?? ""),
1052
+ email: String(u.email ?? ""),
1053
+ firstName: explicitFirst,
1054
+ lastName: explicitLast,
1055
+ name: typeof u.name === "string" ? u.name : void 0,
1056
+ phone: typeof u.phone === "string" ? u.phone : void 0
1057
+ };
1058
+ }
1059
+ const emailLocal = String(u.email ?? "").split("@")[0] ?? "";
1060
+ const full = typeof u.name === "string" ? u.name.trim() : emailLocal.trim();
1061
+ const parts = full ? full.split(/\s+/) : [];
1062
+ return {
1063
+ id: String(u.id ?? ""),
1064
+ email: String(u.email ?? ""),
1065
+ firstName: parts[0] ?? "",
1066
+ lastName: parts.slice(1).join(" "),
1067
+ name: typeof u.name === "string" ? u.name : void 0,
1068
+ phone: typeof u.phone === "string" ? u.phone : void 0
1069
+ };
1117
1070
  }
1118
- function normalizeStorefrontSettingsPayload(v) {
1119
- const body = unwrapSettingsResponseBody(v);
1120
- if (!isLegacyFlatSettingsPayload(body)) return body;
1121
- const raw = body;
1122
- const parsed = StorefrontSettingsLegacyFlatSchema.safeParse(raw);
1123
- if (!parsed.success) return body;
1124
- const l = parsed.data;
1125
- const currency = l.currency?.trim() ? l.currency : "PEN";
1126
- const language = l.language?.trim() ? l.language : "es";
1127
- const slug = l.slug?.trim() ? l.slug : l.id;
1071
+ function useStorefrontSession() {
1072
+ const authClient = useStorefrontAuthClient();
1073
+ const session = authClient.useSession();
1074
+ const user = mapSessionUser(session.data?.user);
1075
+ const storefrontSession = session.data?.session;
1076
+ const sessionExpiresAt = parseStorefrontSessionExpires(storefrontSession?.expiresAt);
1128
1077
  return {
1129
- schemaVersion: 0,
1130
- generatedAt: (/* @__PURE__ */ new Date(0)).toISOString(),
1131
- store: {
1132
- id: l.id,
1133
- tenantId: l.tenantId ?? void 0,
1134
- name: l.name,
1135
- slug,
1136
- description: l.description ?? null,
1137
- logo: l.logo ?? null,
1138
- icon: l.icon ?? null,
1139
- visibility: l.visibility ?? void 0,
1140
- currency,
1141
- language,
1142
- themeConfig: l.themeConfig ?? void 0,
1143
- featuresEnabled: l.featuresEnabled ?? void 0,
1144
- checkoutSettings: l.checkoutSettings ?? void 0
1145
- },
1146
- cms: {
1147
- settings: {
1148
- branding: {
1149
- faviconUrl: l.favicon ?? null,
1150
- logoUrl: l.logo ?? null,
1151
- logoDarkUrl: null,
1152
- socialImageUrl: null
1153
- }
1154
- },
1155
- pages: []
1156
- }
1078
+ session,
1079
+ user,
1080
+ sessionExpiresAt,
1081
+ isAuthenticated: Boolean(session.data?.user),
1082
+ isPending: session.isPending,
1083
+ error: session.error,
1084
+ refetch: session.refetch,
1085
+ signOut: () => authClient.signOut()
1157
1086
  };
1158
1087
  }
1159
- var StorefrontStoreSchema = z.object({
1160
- id: z.string(),
1161
- tenantId: z.string().optional(),
1162
- sellerId: z.string().optional(),
1163
- plan: z.string().optional(),
1164
- storageLimitBytes: z.number().optional(),
1165
- monthlyTransferLimitBytes: z.number().optional(),
1166
- maxUploadSizeBytes: z.number().optional(),
1167
- maxStores: z.number().optional(),
1168
- maxMembers: z.number().optional(),
1169
- maxProducts: z.number().optional(),
1170
- maxCategories: z.number().optional(),
1171
- maxBrands: z.number().optional(),
1172
- maxAttributes: z.number().optional(),
1173
- maxCollections: z.number().optional(),
1174
- enableApi: z.boolean().optional(),
1175
- isActive: z.boolean().optional(),
1176
- name: z.string(),
1177
- description: z.string().nullable().optional(),
1178
- avatarUrl: z.string().nullable().optional(),
1179
- logo: z.string().nullable().optional(),
1180
- icon: z.string().nullable().optional(),
1181
- slug: z.string(),
1182
- domain: z.string().nullable().optional(),
1183
- domains: z.array(z.string()).optional(),
1184
- timeZone: z.string().nullable().optional(),
1185
- currency: z.string(),
1186
- language: z.string(),
1187
- visibility: z.string().optional(),
1188
- privateAccessHash: z.string().nullable().optional(),
1189
- privateAccessVersion: z.number().optional(),
1190
- themeConfig: StorefrontThemeConfigSchema.optional(),
1191
- featuresEnabled: StorefrontFeaturesEnabledSchema.optional(),
1192
- checkoutSettings: StorefrontCheckoutSettingsSchema.optional().nullable()
1193
- }).passthrough();
1194
- var StorefrontSettingsEnvelopeSchema = z.object({
1195
- schemaVersion: z.number(),
1196
- generatedAt: z.string(),
1197
- store: StorefrontStoreSchema,
1198
- cms: z.object({
1199
- settings: StorefrontCmsSettingsSchema,
1200
- pages: z.array(StorefrontCmsPageSchema)
1201
- })
1202
- }).passthrough();
1203
- var StorefrontSettingsSchema = z.preprocess(
1204
- normalizeStorefrontSettingsPayload,
1205
- StorefrontSettingsEnvelopeSchema
1206
- );
1207
- function storefrontStoreRecord(settings) {
1208
- return settings?.store ?? null;
1088
+ function useStoreProducts() {
1089
+ const useStorefrontProductsList = (params) => {
1090
+ return useQuery({
1091
+ queryKey: ["storefront", "products", params],
1092
+ queryFn: () => getStorefrontProducts(params)
1093
+ });
1094
+ };
1095
+ const useStorefrontInfiniteProducts = (params) => {
1096
+ return useInfiniteQuery({
1097
+ queryKey: ["storefront", "products", "infinite", params],
1098
+ queryFn: ({ pageParam = 1 }) => getStorefrontProducts({ ...params, page: pageParam }),
1099
+ getNextPageParam: (lastPage) => lastPage.pageIndex < lastPage.totalPages ? lastPage.pageIndex + 1 : void 0,
1100
+ initialPageParam: 1
1101
+ });
1102
+ };
1103
+ const useProductBySlug = (slug) => {
1104
+ return useQuery({
1105
+ queryKey: ["storefront", "product", "slug", slug],
1106
+ queryFn: () => getStorefrontProductBySlug(slug),
1107
+ enabled: !!slug
1108
+ });
1109
+ };
1110
+ const useRefreshStorefrontProducts = () => {
1111
+ const queryClient = useQueryClient();
1112
+ return useMutation({
1113
+ mutationFn: (params) => getStorefrontProducts(params),
1114
+ onSuccess: () => {
1115
+ queryClient.invalidateQueries({ queryKey: ["storefront", "products"] });
1116
+ }
1117
+ });
1118
+ };
1119
+ return {
1120
+ useStorefrontProductsList,
1121
+ useStorefrontInfiniteProducts,
1122
+ useProductBySlug,
1123
+ useRefreshStorefrontProducts
1124
+ };
1209
1125
  }
1210
- function storefrontFaviconUrl(settings) {
1211
- const b = settings?.cms?.settings?.branding;
1212
- return b?.faviconUrl ?? settings?.store?.logo ?? settings?.store?.icon ?? null;
1126
+ function useStoreCollections() {
1127
+ const useStorefrontCollectionsList = () => {
1128
+ return useQuery({
1129
+ queryKey: ["storefront", "collections"],
1130
+ queryFn: () => getStorefrontCollections()
1131
+ });
1132
+ };
1133
+ const useCollectionBySlug = (slug) => {
1134
+ return useQuery({
1135
+ queryKey: ["storefront", "collection", "slug", slug],
1136
+ queryFn: () => getStorefrontCollectionBySlug(slug),
1137
+ enabled: !!slug
1138
+ });
1139
+ };
1140
+ const useRefreshStorefrontCollections = () => {
1141
+ const queryClient = useQueryClient();
1142
+ return useMutation({
1143
+ mutationFn: () => getStorefrontCollections(),
1144
+ onSuccess: () => {
1145
+ queryClient.invalidateQueries({ queryKey: ["storefront", "collections"] });
1146
+ }
1147
+ });
1148
+ };
1149
+ return {
1150
+ useStorefrontCollectionsList,
1151
+ useCollectionBySlug,
1152
+ useRefreshStorefrontCollections
1153
+ };
1213
1154
  }
1214
- function storefrontOgImageUrl(settings) {
1215
- const b = settings?.cms?.settings?.branding;
1216
- return b?.socialImageUrl ?? settings?.store?.logo ?? null;
1155
+ function useStorePages() {
1156
+ const useStorefrontPagesList = () => {
1157
+ return useQuery({
1158
+ queryKey: ["storefront", "pages"],
1159
+ queryFn: () => getStorefrontPages()
1160
+ });
1161
+ };
1162
+ const usePageBySlug = (slug) => {
1163
+ return useQuery({
1164
+ queryKey: ["storefront", "page", "slug", slug],
1165
+ queryFn: () => getStorefrontPageBySlug(slug),
1166
+ enabled: !!slug
1167
+ });
1168
+ };
1169
+ const useRefreshStorefrontPages = () => {
1170
+ const queryClient = useQueryClient();
1171
+ return useMutation({
1172
+ mutationFn: () => getStorefrontPages(),
1173
+ onSuccess: () => {
1174
+ queryClient.invalidateQueries({ queryKey: ["storefront", "pages"] });
1175
+ }
1176
+ });
1177
+ };
1178
+ return {
1179
+ useStorefrontPagesList,
1180
+ usePageBySlug,
1181
+ useRefreshStorefrontPages
1182
+ };
1217
1183
  }
1218
-
1219
- // src/core/services/store.service.ts
1220
- var StoreService = class _StoreService {
1221
- static instance;
1222
- constructor() {
1223
- }
1224
- static getInstance() {
1225
- if (!_StoreService.instance) {
1226
- _StoreService.instance = new _StoreService();
1227
- }
1228
- return _StoreService.instance;
1229
- }
1230
- /**
1231
- * Obtiene la configuración de la tienda por subdominio (GET /v1/settings).
1232
- * Usa el header X-Store-Domain. Si se pasa domain, se configura antes de la petición.
1233
- */
1234
- async getStorefrontSettings(domain) {
1235
- if (domain) {
1236
- apiClient.setStoreDomain(domain);
1237
- }
1238
- const response = await apiClient.get(`/${STOREFRONT_API_PATH_PREFIX}/settings`);
1239
- return StorefrontSettingsSchema.parse(response);
1240
- }
1241
- };
1242
- var storeService = StoreService.getInstance();
1243
- var CartBuyerRequestSchema = z.object({
1244
- email: z.string().email(),
1245
- firstName: z.string().min(1),
1246
- lastName: z.string().min(1),
1247
- phone: z.string().min(1)
1248
- });
1249
- var CartCreateRequestSchema = z.object({
1250
- currencyCode: z.string(),
1251
- salesChannelId: z.string(),
1252
- email: z.string().email()
1253
- });
1254
- var CartAddressSchema = z.object({
1255
- name: z.string().min(1),
1256
- address1: z.string().min(1),
1257
- city: z.string().min(1),
1258
- countryCode: z.string().min(2),
1259
- company: z.string().nullable().optional(),
1260
- address2: z.string().nullable().optional(),
1261
- province: z.string().nullable().optional(),
1262
- postalCode: z.string().nullable().optional(),
1263
- district: z.string().nullable().optional(),
1264
- phone: z.string().nullable().optional(),
1265
- latitude: z.number().nullable().optional(),
1266
- longitude: z.number().nullable().optional(),
1267
- geo: z.object({
1268
- departmentCode: z.string().nullable().optional(),
1269
- provinceCode: z.string().nullable().optional(),
1270
- districtCode: z.string().nullable().optional()
1271
- }).nullable().optional()
1272
- });
1273
- var CartAddressesRequestSchema = z.object({
1274
- shippingAddress: CartAddressSchema,
1275
- billingAddress: CartAddressSchema
1276
- });
1277
- var CartItemModifierRequestSchema = z.object({
1278
- modifierGroupId: z.coerce.string().min(1),
1279
- optionIds: z.array(z.coerce.string().min(1)).optional().default([])
1280
- });
1281
- var CartItemRequestSchema = z.object({
1282
- variantId: z.coerce.string().min(1),
1283
- quantity: z.number().int().positive(),
1284
- modifiers: z.array(CartItemModifierRequestSchema).optional()
1285
- });
1286
- var CartCouponRequestSchema = z.object({
1287
- code: z.string().min(1)
1288
- });
1289
- z.object({
1290
- subtotal: z.number(),
1291
- discount: z.number(),
1292
- tax: z.number(),
1293
- warranty: z.number().optional().default(0),
1294
- total: z.number()
1295
- });
1296
- z.object({
1297
- name: z.string().nullable().optional(),
1298
- address1: z.string(),
1299
- city: z.string(),
1300
- countryCode: z.string(),
1301
- company: z.string().nullable().optional(),
1302
- address2: z.string().nullable().optional(),
1303
- province: z.string().nullable().optional(),
1304
- postalCode: z.string().nullable().optional(),
1305
- district: z.string().nullable().optional(),
1306
- phone: z.string().nullable().optional(),
1307
- latitude: z.number().nullable().optional(),
1308
- longitude: z.number().nullable().optional(),
1309
- geo: z.object({
1310
- departmentCode: z.string().nullable().optional(),
1311
- provinceCode: z.string().nullable().optional(),
1312
- districtCode: z.string().nullable().optional()
1313
- }).nullable().optional()
1314
- });
1315
- var CartResponseBundleItemSchema = z.object({
1316
- productId: z.coerce.string(),
1317
- variantId: z.coerce.string(),
1318
- productName: z.string(),
1319
- variantName: z.string().nullable().optional(),
1320
- sku: z.string().nullable().optional(),
1321
- brand: z.string().nullable().optional(),
1322
- imageUrl: z.string().nullable().optional(),
1323
- quantity: z.number().int()
1324
- });
1325
- var CartResponseItemSchema = z.object({
1326
- id: z.string(),
1327
- title: z.string(),
1328
- variantLabel: z.string().nullable().optional(),
1329
- thumbnail: z.string().nullable().optional(),
1330
- quantity: z.number().int(),
1331
- variantId: z.coerce.string(),
1332
- variantSku: z.string().nullable().optional(),
1333
- unitPrice: z.number(),
1334
- bundleItems: z.array(CartResponseBundleItemSchema).optional().default([])
1335
- });
1336
- z.object({
1337
- id: z.string(),
1338
- name: z.string(),
1339
- description: z.string().nullable().optional(),
1340
- total: z.number(),
1341
- estimatedDays: z.number().int().nullable().optional(),
1342
- pickupPointId: z.string().nullable().optional(),
1343
- scheduledDate: z.string().nullable().optional(),
1344
- scheduledTime: z.string().nullable().optional()
1345
- });
1346
- var CartResponseSchema = z.object({
1347
- id: z.string(),
1348
- regionId: z.string().nullable().optional(),
1349
- salesChannelId: z.string().nullable().optional(),
1350
- email: z.string().email().nullable().optional(),
1351
- phone: z.string().nullable().optional(),
1352
- firstName: z.string().nullable().optional(),
1353
- lastName: z.string().nullable().optional(),
1354
- currencyCode: z.string(),
1355
- completedAt: z.string().nullable().optional(),
1356
- shippingAddress: z.unknown().nullable().optional(),
1357
- billingAddress: z.unknown().nullable().optional(),
1358
- metadata: z.record(z.string(), z.unknown()).optional().default({}),
1359
- promotionCodes: z.array(z.string()).optional().default([]),
1360
- items: z.array(CartResponseItemSchema).optional().default([]),
1361
- shippingMethods: z.array(z.unknown()).optional().default([]),
1362
- totals: z.object({
1363
- discountTotal: z.number(),
1364
- shippingTotal: z.number(),
1365
- subtotal: z.number(),
1366
- taxTotal: z.number(),
1367
- total: z.number(),
1368
- warrantyTotal: z.number()
1369
- })
1370
- });
1371
- var CartCheckoutRequestSchema = z.object({
1372
- paymentMethodCode: z.string().min(1),
1373
- externalRef: z.string().min(1),
1374
- receiptUrl: z.string().min(1).optional()
1375
- });
1376
- var CartCheckoutPaymentActionSchema = z.object({
1377
- publicConfiguration: z.object({
1378
- publishableKey: z.string().nullable().optional(),
1379
- checkoutMode: z.string().nullable().optional(),
1380
- phoneNumber: z.string().nullable().optional(),
1381
- accountHolder: z.string().nullable().optional(),
1382
- qrImageUrl: z.string().nullable().optional()
1383
- }).passthrough().nullable().optional(),
1384
- url: z.string().nullable().optional(),
1385
- html: z.string().nullable().optional(),
1386
- clientSecret: z.string().nullable().optional(),
1387
- qrCode: z.string().nullable().optional(),
1388
- expiresAt: z.string().nullable().optional()
1389
- }).passthrough();
1390
- var CartCheckoutPaymentSchema = z.object({
1391
- transactionId: z.string(),
1392
- paymentMethodCode: z.string(),
1393
- providerId: z.string().nullable().optional(),
1394
- status: z.string(),
1395
- amount: z.number(),
1396
- reference: z.string(),
1397
- actionType: z.string(),
1398
- receiptUrl: z.string().nullable().optional(),
1399
- action: CartCheckoutPaymentActionSchema.nullable().optional()
1400
- }).passthrough();
1401
- var CartCheckoutResponseSchema = z.object({
1402
- publicKey: z.string().optional(),
1403
- publishableKey: z.string().optional(),
1404
- stripePublicKey: z.string().optional(),
1405
- clientSecret: z.string().optional(),
1406
- intentClientSecret: z.string().optional(),
1407
- orderId: z.coerce.string().optional(),
1408
- orderGroup: z.string().optional(),
1409
- orderIds: z.array(z.coerce.string()).optional(),
1410
- /** Payload de pago (embedded, redirect, etc.) */
1411
- payment: CartCheckoutPaymentSchema.optional(),
1412
- cart: z.unknown().optional()
1413
- }).passthrough();
1414
- var CartPaymentReceiptResponseSchema = z.object({
1415
- id: z.string(),
1416
- url: z.string()
1417
- });
1418
- var CartPaymentMethodSchema = z.object({
1419
- code: z.string(),
1420
- name: z.string(),
1421
- description: z.string().nullable().optional(),
1422
- kind: z.string(),
1423
- isActive: z.boolean(),
1424
- isDefault: z.boolean(),
1425
- isManual: z.boolean(),
1426
- sortOrder: z.number(),
1427
- phoneNumber: z.string().nullable().optional(),
1428
- accountHolder: z.string().nullable().optional(),
1429
- qrImageUrl: z.string().nullable().optional()
1430
- });
1431
- var CartPaymentMethodsResponseSchema = z.object({
1432
- methods: z.array(CartPaymentMethodSchema)
1433
- });
1434
- var CartCheckoutReadResponseSchema = z.object({
1435
- cart: CartResponseSchema,
1436
- paymentMethods: CartPaymentMethodsResponseSchema
1437
- });
1438
- var ShippingRateSchema = z.object({
1439
- shippingMethodId: z.string(),
1440
- name: z.string(),
1441
- displayName: z.string().optional(),
1442
- type: z.string().optional(),
1443
- price: z.number(),
1444
- currency: z.string().optional(),
1445
- sortOrder: z.number().optional(),
1446
- rateId: z.string(),
1447
- description: z.string().nullable().optional(),
1448
- deliveryLocationId: z.string().nullable().optional(),
1449
- deliveryLocationName: z.string().nullable().optional(),
1450
- estimatedDeliveryDays: z.number().nullable().optional(),
1451
- serviceCode: z.string().nullable().optional(),
1452
- metadata: z.record(z.string(), z.unknown()).optional()
1453
- }).passthrough();
1454
- var ShippingOptionGroupSchema = z.object({
1455
- sellerId: z.string(),
1456
- shippingProfileId: z.string().nullable().optional(),
1457
- orderAmount: z.number().optional(),
1458
- totalWeight: z.number().optional(),
1459
- itemIds: z.array(z.string()).optional(),
1460
- rates: z.array(ShippingRateSchema)
1461
- }).passthrough();
1462
- var ShippingOptionsResponseSchema = z.object({
1463
- groups: z.array(ShippingOptionGroupSchema)
1464
- });
1465
- var CartShippingMethodPutRequestSchema = z.object({
1466
- rateId: z.string().min(1),
1467
- shippingMethodId: z.string().min(1),
1468
- serviceCode: z.string().nullable().optional(),
1469
- sellerId: z.string().nullable().optional(),
1470
- shippingProfileId: z.string().nullable().optional(),
1471
- locationId: z.string().nullable().optional(),
1472
- pickupPointId: z.string().nullable().optional(),
1473
- scheduledDate: z.string().nullable().optional(),
1474
- scheduledTime: z.string().nullable().optional()
1475
- });
1476
-
1477
- // src/core/services/cart.service.ts
1478
- var CartService = class _CartService {
1479
- static instance;
1480
- constructor() {
1481
- }
1482
- static getInstance() {
1483
- if (!_CartService.instance) {
1484
- _CartService.instance = new _CartService();
1485
- }
1486
- return _CartService.instance;
1487
- }
1488
- /**
1489
- * Obtiene el carrito actual (GET /v1/carts).
1490
- */
1491
- async getCart() {
1492
- const response = await apiClient.get(`/${STOREFRONT_API_PATH_PREFIX}/carts`);
1493
- return CartResponseSchema.parse(response);
1494
- }
1495
- /**
1496
- * Crea un carrito nuevo (POST /v1/carts).
1497
- */
1498
- async createCart(data) {
1499
- const payload = CartCreateRequestSchema.parse(data);
1500
- const response = await apiClient.post(`/${STOREFRONT_API_PATH_PREFIX}/carts`, payload);
1501
- return CartResponseSchema.parse(response);
1502
- }
1503
- /**
1504
- * Actualiza los datos del comprador del carrito actual (PATCH /v1/carts/buyer).
1505
- * El carrito se resuelve en backend vía contexto (token/guest), por eso no requiere cartId.
1506
- */
1507
- async updateBuyer(data) {
1508
- const payload = CartBuyerRequestSchema.parse(data);
1509
- const response = await apiClient.patch(`/${STOREFRONT_API_PATH_PREFIX}/carts/buyer`, payload);
1510
- return CartResponseSchema.parse(response);
1511
- }
1512
- /**
1513
- * Actualiza direcciones de envío y facturación del carrito (PATCH /v1/carts/addresses).
1514
- */
1515
- async updateAddresses(data) {
1516
- const payload = CartAddressesRequestSchema.parse(data);
1517
- const response = await apiClient.patch(`/${STOREFRONT_API_PATH_PREFIX}/carts/addresses`, payload);
1518
- return CartResponseSchema.parse(response);
1519
- }
1520
- /**
1521
- * Inicializa/obtiene detalles del método de pago para el checkout (POST /v1/carts/checkout).
1522
- * Usado para providers como Stripe (public key, client secret, etc).
1523
- */
1524
- async checkout(data) {
1525
- const payload = CartCheckoutRequestSchema.parse(data);
1526
- const response = await apiClient.post(`/${STOREFRONT_API_PATH_PREFIX}/carts/checkout`, payload);
1527
- return CartCheckoutResponseSchema.parse(response);
1528
- }
1529
- };
1530
- var cartService = CartService.getInstance();
1531
- var defaultOptions = {
1532
- queries: {
1533
- staleTime: 60 * 1e3
1534
- }
1535
- };
1536
- function QueryProvider({ children, dehydratedState }) {
1537
- const queryClientRef = useRef(null);
1538
- const hydratedRef = useRef(false);
1539
- if (queryClientRef.current === null) {
1540
- queryClientRef.current = new QueryClient({ defaultOptions });
1541
- }
1542
- const queryClient = queryClientRef.current;
1543
- useEffect(() => {
1544
- if (dehydratedState && !hydratedRef.current) {
1545
- hydrate(queryClient, dehydratedState);
1546
- hydratedRef.current = true;
1547
- }
1548
- }, [queryClient, dehydratedState]);
1549
- return /* @__PURE__ */ jsx(QueryClientProvider, { client: queryClient, children });
1550
- }
1551
- var StorefrontAuthContext = createContext(null);
1552
- function StorefrontAuthProvider({
1553
- authClient,
1554
- children
1555
- }) {
1556
- return /* @__PURE__ */ jsx(StorefrontAuthContext.Provider, { value: authClient, children });
1557
- }
1558
- function useStorefrontAuthClient() {
1559
- const client = useContext(StorefrontAuthContext);
1560
- if (!client) {
1561
- throw new Error(
1562
- "useStorefrontAuthClient debe usarse dentro de StorefrontAuthProvider (o EcomiqProvider con authClient)."
1563
- );
1564
- }
1565
- return client;
1566
- }
1567
- function storefrontSessionEndsSoon(expiresAt, skewMs = 12e4) {
1568
- if (!expiresAt) return false;
1569
- return Date.now() + skewMs >= expiresAt.getTime();
1570
- }
1571
- var storefrontAccessTokenNeedsRefresh = storefrontSessionEndsSoon;
1572
- function parseStorefrontSessionExpires(at) {
1573
- if (!at) return void 0;
1574
- if (at instanceof Date) return Number.isNaN(at.getTime()) ? void 0 : at;
1575
- if (typeof at === "string") {
1576
- const d = new Date(at);
1577
- return Number.isNaN(d.getTime()) ? void 0 : d;
1578
- }
1579
- return void 0;
1580
- }
1581
- function mapSessionUser(raw) {
1582
- if (!raw || typeof raw !== "object") return null;
1583
- const u = raw;
1584
- const explicitFirst = typeof u.firstName === "string" ? u.firstName : typeof u.given_name === "string" ? u.given_name : "";
1585
- const explicitLast = typeof u.lastName === "string" ? u.lastName : typeof u.family_name === "string" ? u.family_name : "";
1586
- if (explicitFirst || explicitLast) {
1587
- return {
1588
- id: String(u.id ?? ""),
1589
- email: String(u.email ?? ""),
1590
- firstName: explicitFirst,
1591
- lastName: explicitLast,
1592
- name: typeof u.name === "string" ? u.name : void 0,
1593
- phone: typeof u.phone === "string" ? u.phone : void 0
1594
- };
1595
- }
1596
- const emailLocal = String(u.email ?? "").split("@")[0] ?? "";
1597
- const full = typeof u.name === "string" ? u.name.trim() : emailLocal.trim();
1598
- const parts = full ? full.split(/\s+/) : [];
1599
- return {
1600
- id: String(u.id ?? ""),
1601
- email: String(u.email ?? ""),
1602
- firstName: parts[0] ?? "",
1603
- lastName: parts.slice(1).join(" "),
1604
- name: typeof u.name === "string" ? u.name : void 0,
1605
- phone: typeof u.phone === "string" ? u.phone : void 0
1184
+ function useStoreCart() {
1185
+ const useGetCart = (cartId) => useQuery({
1186
+ queryKey: ["storefront", "cart", cartId ?? null],
1187
+ queryFn: () => getCart(cartId ?? void 0),
1188
+ // Si devuelve 404 no reintentar; para otros errores solo un intento adicional como máximo.
1189
+ retry: (failureCount, error) => {
1190
+ const status = error.status;
1191
+ if (status === 404) return false;
1192
+ return failureCount < 1;
1193
+ },
1194
+ // Evitar refetch automático al enfocar la ventana; el carrito se carga una sola vez aquí.
1195
+ refetchOnWindowFocus: false,
1196
+ enabled: !!cartId
1197
+ });
1198
+ const useCreateCart = () => useMutation({
1199
+ mutationFn: (data) => createCart(data)
1200
+ });
1201
+ const useUpdateCartBuyer = () => useMutation({
1202
+ mutationFn: ({ data, cartId }) => patchCartBuyer(data, cartId)
1203
+ });
1204
+ const useUpdateCartAddresses = () => useMutation({
1205
+ mutationFn: ({ data, cartId }) => patchCartAddresses(data, cartId)
1206
+ });
1207
+ const useAddCartItem = () => useMutation({
1208
+ mutationFn: ({ cartId, ...data }) => addCartItem(data, cartId)
1209
+ });
1210
+ const useCheckoutCart = () => useMutation({
1211
+ mutationFn: ({ data, cartId }) => checkoutCart(data, cartId)
1212
+ });
1213
+ const useUploadPaymentReceipt = () => useMutation({
1214
+ mutationFn: ({ cartId, file }) => uploadPaymentReceipt(cartId, file)
1215
+ });
1216
+ const useGetCartCheckout = (cartId) => {
1217
+ const inactiveHandledRef = useRef(null);
1218
+ const query = useQuery({
1219
+ queryKey: ["storefront", "cart", cartId ?? null, "checkout"],
1220
+ queryFn: () => getCartCheckout(cartId),
1221
+ retry: (failureCount, error) => {
1222
+ const status = error.status;
1223
+ if (status === 404) return false;
1224
+ if (status === 400 && isCartNotActiveApiError(error)) return false;
1225
+ return failureCount < 1;
1226
+ },
1227
+ /** Evita GET repetidos al cambiar de paso / re-render; `refetch()` sigue forzando actualización tras mutaciones. */
1228
+ staleTime: 6e4,
1229
+ refetchOnMount: false,
1230
+ refetchOnReconnect: false,
1231
+ refetchOnWindowFocus: false,
1232
+ enabled: !!cartId
1233
+ });
1234
+ useEffect(() => {
1235
+ inactiveHandledRef.current = null;
1236
+ }, [cartId]);
1237
+ useEffect(() => {
1238
+ if (!cartId) return;
1239
+ if (!query.isError || !query.error) return;
1240
+ if (!isCartNotActiveApiError(query.error)) return;
1241
+ if (inactiveHandledRef.current === cartId) return;
1242
+ inactiveHandledRef.current = cartId;
1243
+ toast.error(
1244
+ "Este carrito ya no est\xE1 disponible. Se ha vaciado tu bolsa para que puedas empezar de nuevo."
1245
+ );
1246
+ useCart.getState().clearCartSession();
1247
+ }, [cartId, query.isError, query.error]);
1248
+ return query;
1606
1249
  };
1607
- }
1608
- function useStorefrontSession() {
1609
- const authClient = useStorefrontAuthClient();
1610
- const session = authClient.useSession();
1611
- const user = mapSessionUser(session.data?.user);
1612
- const storefrontSession = session.data?.session;
1613
- const sessionExpiresAt = parseStorefrontSessionExpires(storefrontSession?.expiresAt);
1250
+ const useGetCartPaymentMethods = (cartId) => useQuery({
1251
+ queryKey: ["storefront", "cart", cartId ?? null, "payment-methods"],
1252
+ queryFn: () => getCartPaymentMethods(cartId ?? void 0),
1253
+ retry: (failureCount, error) => {
1254
+ const status = error.status;
1255
+ if (status === 404) return false;
1256
+ return failureCount < 1;
1257
+ },
1258
+ refetchOnWindowFocus: false,
1259
+ enabled: !!cartId
1260
+ });
1261
+ const useGetShippingOptions = (cartId, postalCode, enabled) => useQuery({
1262
+ queryKey: [
1263
+ "storefront",
1264
+ "cart",
1265
+ cartId ?? null,
1266
+ "shipping-options",
1267
+ postalCode ?? ""
1268
+ ],
1269
+ queryFn: () => getShippingOptions(cartId, postalCode),
1270
+ retry: (failureCount, error) => {
1271
+ const status = error.status;
1272
+ if (status === 404) return false;
1273
+ return failureCount < 1;
1274
+ },
1275
+ refetchOnWindowFocus: false,
1276
+ enabled: !!cartId && !!postalCode?.trim() && enabled
1277
+ });
1278
+ const usePutCartShippingMethod = () => useMutation({
1279
+ mutationFn: ({ cartId, data }) => putCartShippingMethod(cartId, data)
1280
+ });
1281
+ const useAddCartCoupon = () => useMutation({
1282
+ mutationFn: ({ cartId, data }) => addCartCoupon(cartId, data)
1283
+ });
1284
+ const useRemoveCartCoupon = () => useMutation({
1285
+ mutationFn: ({ cartId, code }) => removeCartCoupon(cartId, code)
1286
+ });
1614
1287
  return {
1615
- session,
1616
- user,
1617
- sessionExpiresAt,
1618
- isAuthenticated: Boolean(session.data?.user),
1619
- isPending: session.isPending,
1620
- error: session.error,
1621
- refetch: session.refetch,
1622
- signOut: () => authClient.signOut()
1288
+ useCreateCart,
1289
+ useGetCart,
1290
+ useGetCartCheckout,
1291
+ useUpdateCartBuyer,
1292
+ useUpdateCartAddresses,
1293
+ useAddCartItem,
1294
+ useCheckoutCart,
1295
+ useUploadPaymentReceipt,
1296
+ useGetCartPaymentMethods,
1297
+ useGetShippingOptions,
1298
+ usePutCartShippingMethod,
1299
+ useAddCartCoupon,
1300
+ useRemoveCartCoupon
1623
1301
  };
1624
1302
  }
1625
-
1626
- // src/react/hooks/use-user.ts
1627
- function useUser() {
1628
- return {};
1629
- }
1630
-
1631
- // src/react/hooks/use-config.ts
1632
- function useConfig() {
1633
- return {};
1634
- }
1635
- var useProducts = (storeId) => {
1636
- if (storeId) {
1637
- apiClient.setStoreId(storeId);
1638
- }
1639
- const useProductsList = (filters) => {
1640
- return useQuery({
1641
- queryKey: ["products", filters],
1642
- queryFn: () => productService.getProducts(filters),
1643
- enabled: !!storeId
1644
- });
1645
- };
1646
- const useInfiniteProducts = (filters) => {
1647
- return useInfiniteQuery({
1648
- queryKey: ["products", "infinite", filters],
1649
- queryFn: ({ pageParam = 1 }) => productService.getProducts({ ...filters, page: pageParam }),
1650
- getNextPageParam: (lastPage) => lastPage.meta.hasNextPage ? lastPage.meta.page + 1 : void 0,
1651
- initialPageParam: 1,
1652
- enabled: !!storeId
1653
- });
1654
- };
1655
- const useProduct = (id) => {
1656
- return useQuery({
1657
- queryKey: ["product", id],
1658
- queryFn: () => productService.getProduct(id),
1659
- enabled: !!id && !!storeId
1660
- });
1661
- };
1662
- const useProductBySlug = (slug) => {
1663
- return useQuery({
1664
- queryKey: ["product", "slug", slug],
1665
- queryFn: () => productService.getProductBySlug(slug),
1666
- enabled: !!slug && !!storeId
1667
- });
1668
- };
1669
- const useSearchProducts = (query, filters) => {
1670
- return useQuery({
1671
- queryKey: ["products", "search", query, filters],
1672
- queryFn: () => productService.searchProducts(query, filters),
1673
- enabled: !!query && !!storeId
1674
- });
1675
- };
1676
- const useProductsByCategory = (category, filters) => {
1677
- return useQuery({
1678
- queryKey: ["products", "category", category, filters],
1679
- queryFn: () => productService.getProductsByCategory(category, filters),
1680
- enabled: !!category && !!storeId
1681
- });
1682
- };
1683
- const useProductsOnSale = (filters) => {
1684
- return useQuery({
1685
- queryKey: ["products", "sale", filters],
1686
- queryFn: () => productService.getProductsOnSale(filters),
1687
- enabled: !!storeId
1688
- });
1689
- };
1690
- const useProductsInStock = (filters) => {
1691
- return useQuery({
1692
- queryKey: ["products", "stock", filters],
1693
- queryFn: () => productService.getProductsInStock(filters),
1694
- enabled: !!storeId
1695
- });
1696
- };
1697
- const useRefreshProducts = () => {
1698
- const queryClient = useQueryClient();
1699
- return useMutation({
1700
- mutationFn: () => productService.getProducts(),
1701
- onSuccess: (data) => {
1702
- queryClient.setQueryData(["products"], data);
1703
- }
1704
- });
1705
- };
1706
- const useStorefrontProductsList = (params) => {
1707
- return useQuery({
1708
- queryKey: ["storefront", "products", params],
1709
- queryFn: () => productService.getStorefrontProducts(params),
1710
- enabled: !!storeId
1711
- });
1712
- };
1713
- const useStorefrontInfiniteProducts = (params) => {
1714
- return useInfiniteQuery({
1715
- queryKey: ["storefront", "products", "infinite", params],
1716
- queryFn: ({ pageParam = 1 }) => productService.getStorefrontProducts({ ...params, page: pageParam }),
1717
- getNextPageParam: (lastPage) => lastPage.pageIndex < lastPage.totalPages ? lastPage.pageIndex + 1 : void 0,
1718
- initialPageParam: 1,
1719
- enabled: !!storeId
1720
- });
1721
- };
1722
- const useRefreshStorefrontProducts = () => {
1723
- const queryClient = useQueryClient();
1724
- return useMutation({
1725
- mutationFn: (params) => productService.getStorefrontProducts(params),
1726
- onSuccess: () => {
1727
- queryClient.invalidateQueries({ queryKey: ["storefront", "products"] });
1728
- }
1729
- });
1730
- };
1731
- return {
1732
- useProductsList,
1733
- useInfiniteProducts,
1734
- useProduct,
1735
- useProductBySlug,
1736
- useSearchProducts,
1737
- useProductsByCategory,
1738
- useProductsOnSale,
1739
- useProductsInStock,
1740
- useRefreshProducts,
1741
- useStorefrontProductsList,
1742
- useStorefrontInfiniteProducts,
1743
- useRefreshStorefrontProducts
1744
- };
1745
- };
1746
- var useCollections = (storeId) => {
1747
- if (storeId) {
1748
- apiClient.setStoreId(storeId);
1749
- }
1750
- const useStorefrontCollectionsList = () => {
1751
- return useQuery({
1752
- queryKey: ["storefront", "collections"],
1753
- queryFn: () => collectionService.getStorefrontCollections(),
1754
- enabled: !!storeId
1755
- });
1756
- };
1757
- const useStorefrontCollectionBySlug = (slug) => {
1758
- return useQuery({
1759
- queryKey: ["storefront", "collection", "slug", slug],
1760
- queryFn: () => collectionService.getStorefrontCollectionBySlug(slug),
1761
- enabled: !!slug && !!storeId
1762
- });
1763
- };
1764
- const useRefreshStorefrontCollections = () => {
1765
- const queryClient = useQueryClient();
1766
- return useMutation({
1767
- mutationFn: () => collectionService.getStorefrontCollections(),
1768
- onSuccess: () => {
1769
- queryClient.invalidateQueries({ queryKey: ["storefront", "collections"] });
1770
- }
1771
- });
1772
- };
1773
- return {
1774
- useStorefrontCollectionsList,
1775
- useStorefrontCollectionBySlug,
1776
- useRefreshStorefrontCollections
1777
- };
1778
- };
1779
- var usePages = (storeId) => {
1780
- if (storeId) {
1781
- apiClient.setStoreId(storeId);
1782
- }
1783
- const usePagesList = () => {
1784
- return useQuery({
1785
- queryKey: ["storefront", "pages"],
1786
- queryFn: () => pageService.getPages(),
1787
- enabled: !!storeId
1788
- });
1789
- };
1790
- const usePageBySlug = (slug) => {
1791
- return useQuery({
1792
- queryKey: ["storefront", "page", "slug", slug],
1793
- queryFn: () => pageService.getPageBySlug(slug),
1794
- enabled: !!slug && !!storeId
1795
- });
1796
- };
1797
- const useRefreshPages = () => {
1798
- const queryClient = useQueryClient();
1799
- return useMutation({
1800
- mutationFn: () => pageService.getPages(),
1801
- onSuccess: () => {
1802
- queryClient.invalidateQueries({ queryKey: ["storefront", "pages"] });
1803
- }
1804
- });
1805
- };
1806
- return {
1807
- usePagesList,
1808
- usePageBySlug,
1809
- useRefreshPages
1810
- };
1811
- };
1812
- var useCartAddresses = () => {
1813
- return useMutation({
1814
- mutationFn: (data) => cartService.updateAddresses(data)
1815
- });
1816
- };
1817
- var useCartGet = () => {
1818
- return useQuery({
1819
- queryKey: ["cart"],
1820
- queryFn: () => cartService.getCart()
1821
- });
1822
- };
1823
- var useCartCreate = () => {
1824
- return useMutation({
1825
- mutationFn: (data) => cartService.createCart(data)
1826
- });
1827
- };
1828
- function useStorefrontGeoInei(storeId, form) {
1829
- const [departments, setDepartments] = useState([]);
1830
- const [provinces, setProvinces] = useState([]);
1831
- const [districts, setDistricts] = useState([]);
1832
- const [loadingDepartments, setLoadingDepartments] = useState(false);
1833
- const [loadingProvinces, setLoadingProvinces] = useState(false);
1834
- const [loadingDistricts, setLoadingDistricts] = useState(false);
1835
- const prevDepartmentCode = useRef(void 0);
1836
- const prevProvinceCode = useRef(void 0);
1837
- const countryCode = (form.watch("countryCode") || "PE").toUpperCase();
1838
- const departmentCode = form.watch("departmentCode");
1839
- const provinceCode = form.watch("provinceCode");
1840
- const districtCode = form.watch("districtCode");
1841
- const storeRequestHeaders = useMemo(() => {
1842
- const id = storeId.trim();
1843
- if (!id) return null;
1844
- return { "X-Store-Id": id };
1845
- }, [storeId]);
1846
- const selectedDepartment = useMemo(
1847
- () => departments.find((d) => d.code === departmentCode),
1848
- [departments, departmentCode]
1849
- );
1850
- const selectedProvince = useMemo(
1851
- () => provinces.find((p) => p.code === provinceCode),
1852
- [provinces, provinceCode]
1853
- );
1854
- const selectedDistrict = useMemo(
1855
- () => districts.find((d) => d.code === districtCode),
1856
- [districts, districtCode]
1857
- );
1858
- useEffect(() => {
1859
- let active = true;
1860
- if (!storeRequestHeaders) {
1861
- setDepartments([]);
1862
- setLoadingDepartments(false);
1863
- return () => {
1864
- active = false;
1865
- };
1866
- }
1867
- setLoadingDepartments(true);
1868
- loadStorefrontGeoDepartments(storeRequestHeaders).then((items) => {
1869
- if (!active) return;
1870
- setDepartments(items);
1871
- }).finally(() => {
1872
- if (!active) return;
1873
- setLoadingDepartments(false);
1874
- });
1875
- return () => {
1876
- active = false;
1877
- };
1878
- }, [storeRequestHeaders]);
1879
- useEffect(() => {
1880
- const prev = prevDepartmentCode.current;
1881
- const dept = departmentCode;
1882
- const departmentChanged = prev !== void 0 && prev !== dept;
1883
- prevDepartmentCode.current = dept;
1884
- if (!dept) {
1885
- setProvinces([]);
1886
- setDistricts([]);
1887
- prevProvinceCode.current = "";
1888
- prevDepartmentCode.current = "";
1889
- return;
1890
- }
1891
- if (!storeRequestHeaders) {
1892
- setProvinces([]);
1893
- return;
1894
- }
1895
- if (departmentChanged) {
1896
- form.setValue("provinceCode", "", { shouldValidate: true });
1897
- form.setValue("provinceName", "", { shouldValidate: true });
1898
- form.setValue("districtCode", "", { shouldValidate: true });
1899
- form.setValue("districtName", "", { shouldValidate: true });
1900
- setDistricts([]);
1901
- prevProvinceCode.current = "";
1902
- }
1903
- if (selectedDepartment?.name) {
1904
- form.setValue("departmentName", selectedDepartment.name, {
1905
- shouldValidate: true
1906
- });
1907
- }
1908
- let active = true;
1909
- setLoadingProvinces(true);
1910
- fetchStorefrontGeoProvinces(storeRequestHeaders, countryCode, dept).then((items) => {
1911
- if (!active) return;
1912
- setProvinces(items);
1913
- }).catch(() => {
1914
- if (!active) return;
1915
- setProvinces([]);
1916
- }).finally(() => {
1917
- if (!active) return;
1918
- setLoadingProvinces(false);
1919
- });
1920
- return () => {
1921
- active = false;
1922
- };
1923
- }, [departmentCode, countryCode, selectedDepartment?.name, form, storeRequestHeaders]);
1924
- useEffect(() => {
1925
- const prev = prevProvinceCode.current;
1926
- const prov = provinceCode;
1927
- const provinceChanged = prev !== void 0 && prev !== prov;
1928
- prevProvinceCode.current = prov;
1929
- if (!prov) {
1930
- setDistricts([]);
1931
- prevProvinceCode.current = "";
1932
- return;
1933
- }
1934
- if (!storeRequestHeaders) {
1935
- setDistricts([]);
1936
- return;
1937
- }
1938
- if (provinceChanged) {
1939
- form.setValue("districtCode", "", { shouldValidate: true });
1940
- form.setValue("districtName", "", { shouldValidate: true });
1941
- }
1942
- if (selectedProvince?.name) {
1943
- form.setValue("provinceName", selectedProvince.name, {
1944
- shouldValidate: true
1945
- });
1946
- }
1947
- let active = true;
1948
- setLoadingDistricts(true);
1949
- fetchStorefrontGeoDistricts(storeRequestHeaders, countryCode, prov).then((items) => {
1950
- if (!active) return;
1951
- setDistricts(items);
1952
- }).catch(() => {
1953
- if (!active) return;
1954
- setDistricts([]);
1955
- }).finally(() => {
1956
- if (!active) return;
1957
- setLoadingDistricts(false);
1958
- });
1959
- return () => {
1960
- active = false;
1961
- };
1962
- }, [provinceCode, countryCode, selectedProvince?.name, form, storeRequestHeaders]);
1963
- useEffect(() => {
1964
- if (selectedDistrict?.name) {
1965
- form.setValue("districtName", selectedDistrict.name, {
1966
- shouldValidate: true
1967
- });
1968
- }
1969
- }, [selectedDistrict?.name, form]);
1970
- return {
1971
- departments,
1972
- provinces,
1973
- districts,
1974
- loadingDepartments,
1975
- loadingProvinces,
1976
- loadingDistricts,
1977
- departmentCode,
1978
- provinceCode,
1979
- districtCode,
1980
- selectedDepartment,
1981
- selectedProvince,
1982
- selectedDistrict,
1983
- storeRequestHeaders
1984
- };
1985
- }
1986
- var useStore = create()(
1987
- (set) => ({
1988
- data: null,
1989
- drawers: {
1990
- cart: false,
1991
- search: false,
1992
- menu: false,
1993
- filter: false
1994
- },
1995
- openDrawer: (drawer) => set((state) => ({
1996
- drawers: {
1997
- ...state.drawers,
1998
- [drawer]: true
1303
+ var useStore = create()(
1304
+ (set) => ({
1305
+ data: null,
1306
+ drawers: {
1307
+ cart: false,
1308
+ search: false,
1309
+ menu: false,
1310
+ filter: false
1311
+ },
1312
+ openDrawer: (drawer) => set((state) => ({
1313
+ drawers: {
1314
+ ...state.drawers,
1315
+ [drawer]: true
1999
1316
  }
2000
1317
  })),
2001
1318
  closeDrawer: (drawer) => set((state) => ({
@@ -2050,7 +1367,7 @@ var useStore = create()(
2050
1367
  }))
2051
1368
  })
2052
1369
  );
2053
- var useCart = create()(
1370
+ var useCart2 = create()(
2054
1371
  persist(
2055
1372
  (set) => ({
2056
1373
  count: 0,
@@ -2129,10 +1446,12 @@ function EcomiqProvider({
2129
1446
  return () => setClientStoreId(null);
2130
1447
  }, [store]);
2131
1448
  const [queryClient] = useState(() => new QueryClient());
2132
- return /* @__PURE__ */ jsx(StorefrontAuthProvider, { authClient, children: /* @__PURE__ */ jsxs(QueryClientProvider, { client: queryClient, children: [
1449
+ const tree = /* @__PURE__ */ jsxs(QueryClientProvider, { client: queryClient, children: [
2133
1450
  children,
2134
1451
  withShellUi ? /* @__PURE__ */ jsx(EcomiqShellUi2, {}) : null
2135
- ] }) });
1452
+ ] });
1453
+ if (!authClient) return tree;
1454
+ return /* @__PURE__ */ jsx(StorefrontAuthProvider, { authClient, children: tree });
2136
1455
  }
2137
1456
  function EcomiqShellUi2() {
2138
1457
  const [ui, setUi] = useState(null);
@@ -2142,6 +1461,10 @@ function EcomiqShellUi2() {
2142
1461
  return ui ? React2.createElement(ui) : null;
2143
1462
  }
2144
1463
 
1464
+ // src/core/constants/storefront-ecomiq-oauth-cookie.ts
1465
+ var STOREFRONT_ECOMIQ_OAUTH_COOKIE_NAME = "ecomiq_sf_oauth_at";
1466
+ var STOREFRONT_ECOMIQ_OAUTH_COOKIE_PATH = "/api/store";
1467
+
2145
1468
  // src/react/auth/ecomiq-oauth-password-constants.ts
2146
1469
  var PLUGIN_ID = "ecomiq-oauth-password";
2147
1470
  var ECOMIQ_OAUTH_TOKEN_URL = "https://auth.ecomiq.pe/connect/token";
@@ -2152,415 +1475,1490 @@ var tokenResponseSchema = z.object({
2152
1475
  /** Segundos hasta expiración (Ecomiq envía ej. `86399`). Sincroniza la sesión Better Auth con el bearer. */
2153
1476
  expires_in: z.coerce.number().int().positive().optional()
2154
1477
  });
2155
- var userInfoSchema = z.object({
2156
- sub: z.string(),
2157
- email: z.string().email().optional(),
2158
- family_name: z.string().optional(),
2159
- given_name: z.string().optional(),
2160
- iss: z.string().optional(),
2161
- aud: z.union([z.string(), z.array(z.string())]).optional()
1478
+ var userInfoSchema = z.object({
1479
+ sub: z.string(),
1480
+ email: z.string().email().optional(),
1481
+ family_name: z.string().optional(),
1482
+ given_name: z.string().optional(),
1483
+ iss: z.string().optional(),
1484
+ aud: z.union([z.string(), z.array(z.string())]).optional()
1485
+ });
1486
+ var signInBody = z.object({
1487
+ email: z.string().email(),
1488
+ password: z.string().min(1),
1489
+ callbackURL: z.string().optional(),
1490
+ rememberMe: z.boolean().optional().default(true)
1491
+ });
1492
+
1493
+ // src/react/auth/ecomiq-oauth-password-plugin.ts
1494
+ function ecomiqOAuthPassword() {
1495
+ const secureCookie = true;
1496
+ const clearStorefrontOAuthCookieMiddleware = createAuthMiddleware(async (ctx) => {
1497
+ ctx.setCookie(STOREFRONT_ECOMIQ_OAUTH_COOKIE_NAME, "", {
1498
+ path: STOREFRONT_ECOMIQ_OAUTH_COOKIE_PATH,
1499
+ httpOnly: true,
1500
+ sameSite: "lax",
1501
+ secure: secureCookie,
1502
+ maxAge: 0
1503
+ });
1504
+ });
1505
+ return {
1506
+ id: PLUGIN_ID,
1507
+ schema: {
1508
+ user: {
1509
+ fields: {
1510
+ firstName: { type: "string", required: false },
1511
+ lastName: { type: "string", required: false },
1512
+ phone: { type: "string", required: false }
1513
+ }
1514
+ }
1515
+ },
1516
+ hooks: {
1517
+ after: [
1518
+ {
1519
+ matcher: (hookCtx) => hookCtx.path === "/sign-out",
1520
+ handler: clearStorefrontOAuthCookieMiddleware
1521
+ }
1522
+ ]
1523
+ },
1524
+ endpoints: {
1525
+ signIn: createAuthEndpoint(
1526
+ "/ecomiq-oauth/sign-in",
1527
+ {
1528
+ method: "POST",
1529
+ use: [formCsrfMiddleware],
1530
+ body: signInBody
1531
+ },
1532
+ async (ctx) => {
1533
+ const { email: loginEmail, password, callbackURL, rememberMe } = ctx.body;
1534
+ const tokens = await exchangePasswordGrant(loginEmail, password, {
1535
+ identityScope: ctx.getHeader("x-Identity-Scope"),
1536
+ tenantId: ctx.getHeader("x-Tenant-Id"),
1537
+ storeId: ctx.getHeader("x-Store-Id")
1538
+ });
1539
+ const profile = await fetchUserInfo(tokens.access_token);
1540
+ const profileEmail = profile.email?.toLowerCase().trim();
1541
+ const canonicalEmail = profileEmail ?? loginEmail.toLowerCase().trim();
1542
+ if (profileEmail && profileEmail !== loginEmail.toLowerCase().trim()) {
1543
+ throw APIError.from("UNAUTHORIZED", {
1544
+ code: "ECOMIQ_EMAIL_MISMATCH",
1545
+ message: "El correo del perfil OAuth no coincide con el inicio de sesi\xF3n"
1546
+ });
1547
+ }
1548
+ const displayName = displayNameFromUserInfo(profile, canonicalEmail);
1549
+ const existing = await ctx.context.internalAdapter.findUserByEmail(canonicalEmail);
1550
+ let user = existing?.user;
1551
+ if (!user) {
1552
+ user = await ctx.context.internalAdapter.createUser({
1553
+ email: canonicalEmail,
1554
+ name: displayName,
1555
+ emailVerified: true
1556
+ });
1557
+ } else if (user.name !== displayName) {
1558
+ await ctx.context.internalAdapter.updateUser(user.id, { name: displayName });
1559
+ user = { ...user, name: displayName };
1560
+ }
1561
+ const oauthSessionExpires = tokens.expires_in !== void 0 && tokens.expires_in > 0 ? new Date(Date.now() + tokens.expires_in * 1e3) : void 0;
1562
+ const session = await ctx.context.internalAdapter.createSession(
1563
+ user.id,
1564
+ rememberMe === false,
1565
+ oauthSessionExpires ? { expiresAt: oauthSessionExpires } : {},
1566
+ Boolean(oauthSessionExpires)
1567
+ );
1568
+ if (!session) {
1569
+ throw APIError.from("INTERNAL_SERVER_ERROR", {
1570
+ code: "ECOMIQ_SESSION_FAILED",
1571
+ message: "No se pudo crear la sesi\xF3n"
1572
+ });
1573
+ }
1574
+ await setSessionCookie(ctx, { session, user }, rememberMe === false);
1575
+ const oauthMaxAgeSec = oauthSessionExpires !== void 0 ? Math.max(60, Math.ceil((oauthSessionExpires.getTime() - Date.now()) / 1e3)) : tokens.expires_in !== void 0 && tokens.expires_in > 0 ? tokens.expires_in : 60 * 60 * 24;
1576
+ await ctx.setCookie(STOREFRONT_ECOMIQ_OAUTH_COOKIE_NAME, tokens.access_token, {
1577
+ path: STOREFRONT_ECOMIQ_OAUTH_COOKIE_PATH,
1578
+ httpOnly: true,
1579
+ sameSite: "lax",
1580
+ secure: secureCookie,
1581
+ maxAge: oauthMaxAgeSec
1582
+ });
1583
+ if (callbackURL) ctx.setHeader("Location", callbackURL);
1584
+ return ctx.json({
1585
+ redirect: !!callbackURL,
1586
+ token: session.token,
1587
+ url: callbackURL,
1588
+ user: parseUserOutput(ctx.context.options, user)
1589
+ });
1590
+ }
1591
+ )
1592
+ }
1593
+ };
1594
+ }
1595
+ function displayNameFromUserInfo(profile, fallbackEmail) {
1596
+ const parts = [profile.given_name?.trim(), profile.family_name?.trim()].filter(Boolean);
1597
+ const joined = parts.join(" ").trim();
1598
+ if (joined) return joined;
1599
+ return fallbackEmail.split("@")[0] || "Cliente";
1600
+ }
1601
+ async function fetchUserInfo(accessToken) {
1602
+ const res = await fetch(ECOMIQ_OAUTH_USERINFO_URL, {
1603
+ method: "GET",
1604
+ headers: {
1605
+ Authorization: `Bearer ${accessToken}`,
1606
+ accept: "application/json"
1607
+ }
1608
+ });
1609
+ const text = await res.text();
1610
+ let json;
1611
+ try {
1612
+ json = text ? JSON.parse(text) : {};
1613
+ } catch {
1614
+ throw APIError.from("UNAUTHORIZED", {
1615
+ code: "ECOMIQ_USERINFO_INVALID_BODY",
1616
+ message: "Respuesta inv\xE1lida del endpoint userinfo"
1617
+ });
1618
+ }
1619
+ if (!res.ok) {
1620
+ const err = json;
1621
+ throw APIError.from("UNAUTHORIZED", {
1622
+ code: "ECOMIQ_USERINFO_REJECTED",
1623
+ message: err.error_description ?? err.error ?? "No se pudo obtener el perfil del usuario"
1624
+ });
1625
+ }
1626
+ const parsed = userInfoSchema.safeParse(json);
1627
+ if (!parsed.success) {
1628
+ throw APIError.from("UNAUTHORIZED", {
1629
+ code: "ECOMIQ_USERINFO_SHAPE",
1630
+ message: "Perfil OAuth con formato inv\xE1lido"
1631
+ });
1632
+ }
1633
+ return parsed.data;
1634
+ }
1635
+ async function exchangePasswordGrant(username, password, context) {
1636
+ const clientId = process.env.ECOMIQ_AUTH_CLIENT_ID ?? "";
1637
+ const clientSecret = process.env.ECOMIQ_AUTH_CLIENT_SECRET ?? "";
1638
+ const scope = process.env.ECOMIQ_AUTH_SCOPE ?? "";
1639
+ if (!clientId || !clientSecret) {
1640
+ throw APIError.from("INTERNAL_SERVER_ERROR", {
1641
+ code: "ECOMIQ_AUTH_NOT_CONFIGURED",
1642
+ message: "OAuth Ecomiq no configurado: ECOMIQ_AUTH_CLIENT_ID y ECOMIQ_AUTH_CLIENT_SECRET."
1643
+ });
1644
+ }
1645
+ const body = new URLSearchParams({
1646
+ grant_type: "password",
1647
+ username,
1648
+ password,
1649
+ scope,
1650
+ client_id: clientId,
1651
+ client_secret: clientSecret
1652
+ });
1653
+ const identityScope = context.identityScope;
1654
+ const tenantId = context.tenantId;
1655
+ const storeId = context.storeId;
1656
+ const headers = new Headers({
1657
+ "content-type": "application/x-www-form-urlencoded",
1658
+ accept: "application/json"
1659
+ });
1660
+ if (identityScope?.trim()) {
1661
+ headers.set("x-Identity-Scope", identityScope.trim());
1662
+ }
1663
+ if (tenantId?.trim()) {
1664
+ headers.set("x-Tenant-Id", tenantId.trim());
1665
+ }
1666
+ if (storeId?.trim()) {
1667
+ headers.set("x-Store-Id", storeId.trim());
1668
+ }
1669
+ let res;
1670
+ let text;
1671
+ try {
1672
+ res = await fetch(ECOMIQ_OAUTH_TOKEN_URL, {
1673
+ method: "POST",
1674
+ headers,
1675
+ body
1676
+ });
1677
+ text = await res.text();
1678
+ } catch (e) {
1679
+ throw mapTokenEndpointFetchFailure(e);
1680
+ }
1681
+ const json = safeParseJson(text);
1682
+ if (!res.ok) {
1683
+ throwBadRequestFromOAuth(json, res.statusText || text || `HTTP ${res.status}`);
1684
+ }
1685
+ const parsed = tokenResponseSchema.safeParse(json);
1686
+ if (!parsed.success) {
1687
+ throwBadRequestFromOAuth(json, "Token de acceso no recibido");
1688
+ }
1689
+ return parsed.data;
1690
+ }
1691
+ function safeParseJson(text) {
1692
+ try {
1693
+ return text ? JSON.parse(text) : {};
1694
+ } catch {
1695
+ return {};
1696
+ }
1697
+ }
1698
+ function throwBadRequestFromOAuth(payload, fallbackMessage) {
1699
+ const err = payload;
1700
+ const errorCode = typeof err.error === "string" && err.error.trim() ? err.error.trim() : "invalid_grant";
1701
+ const errorMessage = typeof err.error_description === "string" && err.error_description.trim() ? err.error_description.trim() : typeof err.error === "string" && err.error.trim() ? err.error.trim() : fallbackMessage.trim() || "Credenciales invalidas.";
1702
+ throw APIError.from("BAD_REQUEST", {
1703
+ code: errorCode,
1704
+ message: errorMessage.slice(0, 500)
1705
+ });
1706
+ }
1707
+ function mapTokenEndpointFetchFailure(e) {
1708
+ const codes = collectNetworkErrorCodes(e);
1709
+ const msg = e instanceof Error ? e.message : String(e);
1710
+ const isDnsError = codes.some((c) => c === "ENOTFOUND" || c === "EAI_AGAIN");
1711
+ if (isDnsError) {
1712
+ throw APIError.from("BAD_GATEWAY", {
1713
+ code: "ECOMIQ_AUTH_DNS",
1714
+ message: "No se pudo resolver el servidor de autenticacion."
1715
+ });
1716
+ }
1717
+ const isConnectionRefused = codes.includes("ECONNREFUSED");
1718
+ if (isConnectionRefused) {
1719
+ throw APIError.from("BAD_GATEWAY", {
1720
+ code: "ECOMIQ_AUTH_REFUSED",
1721
+ message: "Conexion rechazada al servidor de autenticacion."
1722
+ });
1723
+ }
1724
+ const looksLikeAbortOrSocketClose = codes.some(
1725
+ (c) => ["UND_ERR_SOCKET", "ECONNRESET", "EPIPE", "ECONNABORTED"].includes(c)
1726
+ ) || /terminated|abort|closed|FIN|socket/i.test(msg);
1727
+ if (looksLikeAbortOrSocketClose) {
1728
+ throwBadRequestFromOAuth({}, "Credenciales invalidas o conexion cerrada por el servidor.");
1729
+ }
1730
+ throw APIError.from("INTERNAL_SERVER_ERROR", {
1731
+ code: "ECOMIQ_AUTH_REQUEST_FAILED",
1732
+ message: msg.slice(0, 280) || "Error al contactar el servidor de autenticacion."
1733
+ });
1734
+ }
1735
+ function collectNetworkErrorCodes(e) {
1736
+ const out = [];
1737
+ const visit = (x) => {
1738
+ if (!x || typeof x !== "object") return;
1739
+ const code = "code" in x && typeof x.code === "string" ? x.code : void 0;
1740
+ if (code) out.push(code);
1741
+ if ("cause" in x && x.cause) {
1742
+ visit(x.cause);
1743
+ }
1744
+ };
1745
+ visit(e);
1746
+ return out;
1747
+ }
1748
+
1749
+ // src/react/auth/ecomiq-oauth-password-client.ts
1750
+ function ecomiqOAuthPasswordClient() {
1751
+ return {
1752
+ id: PLUGIN_ID,
1753
+ pathMethods: {
1754
+ "/ecomiq-oauth/sign-in": "POST"
1755
+ },
1756
+ atomListeners: [
1757
+ {
1758
+ matcher: (path) => path === "/ecomiq-oauth/sign-in",
1759
+ signal: "$sessionSignal"
1760
+ }
1761
+ ]
1762
+ };
1763
+ }
1764
+ var StorefrontCheckoutSettingsSchema = z.object({
1765
+ allowGuestCheckout: z.boolean().optional(),
1766
+ identityScope: z.enum(["store", "tenant", "global"]).optional(),
1767
+ signInMethods: z.array(z.string()).optional()
1768
+ });
1769
+ var looseRecord = z.record(z.string(), z.unknown());
1770
+ var StorefrontThemeConfigSchema = z.object({
1771
+ name: z.string().nullable().optional(),
1772
+ mode: z.string().nullable().optional(),
1773
+ colors: looseRecord.optional(),
1774
+ fonts: looseRecord.optional(),
1775
+ assets: looseRecord.optional()
1776
+ }).passthrough();
1777
+ var StorefrontFeaturesEnabledSchema = z.object({
1778
+ flags: looseRecord.optional()
1779
+ }).passthrough();
1780
+ var StorefrontCmsBrandingSchema = z.object({
1781
+ logoUrl: z.string().nullable().optional(),
1782
+ logoDarkUrl: z.string().nullable().optional(),
1783
+ faviconUrl: z.string().nullable().optional(),
1784
+ socialImageUrl: z.string().nullable().optional()
1785
+ });
1786
+ var StorefrontCmsSeoSchema = z.object({
1787
+ siteName: z.string().nullable().optional(),
1788
+ metaTitle: z.string().nullable().optional(),
1789
+ metaDescription: z.string().nullable().optional(),
1790
+ titleTemplate: z.string().nullable().optional(),
1791
+ robotsIndex: z.boolean().optional()
1792
+ });
1793
+ var StorefrontCmsSocialLinksSchema = z.object({
1794
+ instagram: z.string().nullable().optional(),
1795
+ facebook: z.string().nullable().optional(),
1796
+ tiktok: z.string().nullable().optional(),
1797
+ youtube: z.string().nullable().optional(),
1798
+ x: z.string().nullable().optional()
1799
+ });
1800
+ var StorefrontCmsContactSchema = z.object({
1801
+ supportEmail: z.string().nullable().optional(),
1802
+ phone: z.string().nullable().optional(),
1803
+ whatsapp: z.string().nullable().optional(),
1804
+ address: z.string().nullable().optional(),
1805
+ socialLinks: StorefrontCmsSocialLinksSchema.optional()
1806
+ });
1807
+ var StorefrontCmsTrackingSchema = z.object({
1808
+ googleAnalyticsMeasurementId: z.string().nullable().optional(),
1809
+ metaPixelId: z.string().nullable().optional(),
1810
+ googleTagManagerContainerId: z.string().nullable().optional()
1811
+ });
1812
+ var StorefrontCmsCustomScriptsSchema = z.object({
1813
+ enabled: z.boolean().optional(),
1814
+ head: z.string().nullable().optional(),
1815
+ bodyEnd: z.string().nullable().optional()
1816
+ });
1817
+ var StorefrontCmsSettingsSchema = z.object({
1818
+ themeKey: z.string().optional(),
1819
+ branding: StorefrontCmsBrandingSchema.optional(),
1820
+ seo: StorefrontCmsSeoSchema.optional(),
1821
+ contact: StorefrontCmsContactSchema.optional(),
1822
+ tracking: StorefrontCmsTrackingSchema.optional(),
1823
+ customScripts: StorefrontCmsCustomScriptsSchema.optional(),
1824
+ updatedOn: z.string().nullable().optional()
1825
+ });
1826
+ var StorefrontCmsPageSchema = z.object({
1827
+ id: z.string(),
1828
+ name: z.string(),
1829
+ slug: z.string(),
1830
+ layout: z.string().optional(),
1831
+ version: z.number().optional(),
1832
+ settings: z.record(z.string(), z.unknown()).optional().nullable(),
1833
+ sections: z.array(z.unknown()).optional(),
1834
+ status: z.string().optional(),
1835
+ hasPublishedVersion: z.boolean().optional(),
1836
+ publishedOn: z.string().nullable().optional(),
1837
+ publishedBy: z.string().nullable().optional(),
1838
+ createdOn: z.string().nullable().optional(),
1839
+ updatedOn: z.string().nullable().optional(),
1840
+ createdBy: z.string().nullable().optional(),
1841
+ updatedBy: z.string().nullable().optional(),
1842
+ lastModifiedBy: z.string().nullable().optional()
1843
+ }).passthrough();
1844
+ var StorefrontSettingsLegacyFlatSchema = z.object({
1845
+ id: z.coerce.string(),
1846
+ tenantId: z.string().nullable().optional(),
1847
+ name: z.string(),
1848
+ slug: z.coerce.string().optional(),
1849
+ description: z.string().nullable().optional(),
1850
+ visibility: z.string().nullable().optional(),
1851
+ logo: z.string().nullable().optional(),
1852
+ favicon: z.string().nullable().optional(),
1853
+ icon: z.string().nullable().optional(),
1854
+ currency: z.string().nullable().optional(),
1855
+ language: z.string().nullable().optional(),
1856
+ themeConfig: z.unknown().optional().nullable(),
1857
+ featuresEnabled: z.unknown().optional().nullable(),
1858
+ checkoutSettings: StorefrontCheckoutSettingsSchema.optional().nullable()
1859
+ }).passthrough();
1860
+ function unwrapSettingsResponseBody(v) {
1861
+ if (!v || typeof v !== "object" || Array.isArray(v)) return v;
1862
+ const o = v;
1863
+ if ("data" in o && o.data != null && typeof o.data === "object" && !Array.isArray(o.data)) {
1864
+ return o.data;
1865
+ }
1866
+ return v;
1867
+ }
1868
+ function isLegacyFlatSettingsPayload(v) {
1869
+ if (!v || typeof v !== "object" || Array.isArray(v)) return false;
1870
+ const o = v;
1871
+ if ("store" in o || "schemaVersion" in o || "cms" in o) return false;
1872
+ return (typeof o.id === "string" || typeof o.id === "number") && typeof o.name === "string";
1873
+ }
1874
+ function normalizeStorefrontSettingsPayload(v) {
1875
+ const body = unwrapSettingsResponseBody(v);
1876
+ if (!isLegacyFlatSettingsPayload(body)) return body;
1877
+ const raw = body;
1878
+ const parsed = StorefrontSettingsLegacyFlatSchema.safeParse(raw);
1879
+ if (!parsed.success) return body;
1880
+ const l = parsed.data;
1881
+ const currency = l.currency?.trim() ? l.currency : "PEN";
1882
+ const language = l.language?.trim() ? l.language : "es";
1883
+ const slug = l.slug?.trim() ? l.slug : l.id;
1884
+ return {
1885
+ schemaVersion: 0,
1886
+ generatedAt: (/* @__PURE__ */ new Date(0)).toISOString(),
1887
+ store: {
1888
+ id: l.id,
1889
+ tenantId: l.tenantId ?? void 0,
1890
+ name: l.name,
1891
+ slug,
1892
+ description: l.description ?? null,
1893
+ logo: l.logo ?? null,
1894
+ icon: l.icon ?? null,
1895
+ visibility: l.visibility ?? void 0,
1896
+ currency,
1897
+ language,
1898
+ themeConfig: l.themeConfig ?? void 0,
1899
+ featuresEnabled: l.featuresEnabled ?? void 0,
1900
+ checkoutSettings: l.checkoutSettings ?? void 0
1901
+ },
1902
+ cms: {
1903
+ settings: {
1904
+ branding: {
1905
+ faviconUrl: l.favicon ?? null,
1906
+ logoUrl: l.logo ?? null,
1907
+ logoDarkUrl: null,
1908
+ socialImageUrl: null
1909
+ }
1910
+ },
1911
+ pages: []
1912
+ }
1913
+ };
1914
+ }
1915
+ var StorefrontStoreSchema = z.object({
1916
+ id: z.string(),
1917
+ tenantId: z.string().optional(),
1918
+ sellerId: z.string().optional(),
1919
+ plan: z.string().optional(),
1920
+ storageLimitBytes: z.number().optional(),
1921
+ monthlyTransferLimitBytes: z.number().optional(),
1922
+ maxUploadSizeBytes: z.number().optional(),
1923
+ maxStores: z.number().optional(),
1924
+ maxMembers: z.number().optional(),
1925
+ maxProducts: z.number().optional(),
1926
+ maxCategories: z.number().optional(),
1927
+ maxBrands: z.number().optional(),
1928
+ maxAttributes: z.number().optional(),
1929
+ maxCollections: z.number().optional(),
1930
+ enableApi: z.boolean().optional(),
1931
+ isActive: z.boolean().optional(),
1932
+ name: z.string(),
1933
+ description: z.string().nullable().optional(),
1934
+ avatarUrl: z.string().nullable().optional(),
1935
+ logo: z.string().nullable().optional(),
1936
+ icon: z.string().nullable().optional(),
1937
+ slug: z.string(),
1938
+ domain: z.string().nullable().optional(),
1939
+ domains: z.array(z.string()).optional(),
1940
+ timeZone: z.string().nullable().optional(),
1941
+ currency: z.string(),
1942
+ language: z.string(),
1943
+ visibility: z.string().optional(),
1944
+ privateAccessHash: z.string().nullable().optional(),
1945
+ privateAccessVersion: z.number().optional(),
1946
+ themeConfig: StorefrontThemeConfigSchema.optional(),
1947
+ featuresEnabled: StorefrontFeaturesEnabledSchema.optional(),
1948
+ checkoutSettings: StorefrontCheckoutSettingsSchema.optional().nullable()
1949
+ }).passthrough();
1950
+ var StorefrontSettingsEnvelopeSchema = z.object({
1951
+ schemaVersion: z.number(),
1952
+ generatedAt: z.string(),
1953
+ store: StorefrontStoreSchema,
1954
+ cms: z.object({
1955
+ settings: StorefrontCmsSettingsSchema,
1956
+ pages: z.array(StorefrontCmsPageSchema)
1957
+ })
1958
+ }).passthrough();
1959
+ var StorefrontSettingsSchema3 = z.preprocess(
1960
+ normalizeStorefrontSettingsPayload,
1961
+ StorefrontSettingsEnvelopeSchema
1962
+ );
1963
+ function storefrontStoreRecord(settings) {
1964
+ return settings?.store ?? null;
1965
+ }
1966
+
1967
+ // src/core/lib/ecomiq-oauth-identity.ts
1968
+ function buildEcomiqOAuthIdentityHeaders(settings) {
1969
+ const record = storefrontStoreRecord(settings);
1970
+ if (!record?.id) {
1971
+ throw new Error("Tienda no disponible para iniciar sesi\xF3n");
1972
+ }
1973
+ const checkout = record.checkoutSettings;
1974
+ const identityScope = checkout?.identityScope === "tenant" ? "tenant" : "store";
1975
+ const headers = {
1976
+ "x-Identity-Scope": identityScope,
1977
+ "x-Store-Id": record.id
1978
+ };
1979
+ if (record.tenantId) headers["x-Tenant-Id"] = record.tenantId;
1980
+ return headers;
1981
+ }
1982
+
1983
+ // src/react/auth/ecomiq-storefront-sign-in.ts
1984
+ async function signInWithEcomiqCredentials2(authClient, settings, credentials) {
1985
+ const response = await authClient.$fetch("/ecomiq-oauth/sign-in", {
1986
+ method: "POST",
1987
+ headers: buildEcomiqOAuthIdentityHeaders(settings),
1988
+ body: credentials
1989
+ });
1990
+ if (response.error) {
1991
+ throw new Error(response.error.message ?? "Error al iniciar sesi\xF3n");
1992
+ }
1993
+ return response;
1994
+ }
1995
+ function isRenderableCmsPage(p) {
1996
+ if (!p.status) return true;
1997
+ return p.status === "published";
1998
+ }
1999
+ function findStorefrontCmsPageBySlug(settings, slug) {
2000
+ const pages = settings?.cms?.pages;
2001
+ if (!pages?.length) return null;
2002
+ const raw = pages.find(
2003
+ (p) => p.slug === slug && isRenderableCmsPage(p)
2004
+ );
2005
+ if (!raw) return null;
2006
+ const parsed = StorefrontPageDetailSchema$1.safeParse(raw);
2007
+ return parsed.success ? parsed.data : null;
2008
+ }
2009
+ function buildOptionGroups(variants) {
2010
+ const map = /* @__PURE__ */ new Map();
2011
+ for (const v of variants) {
2012
+ for (const opt of v.options ?? []) {
2013
+ const key = opt.key;
2014
+ const name = opt.name ?? key;
2015
+ if (!map.has(key)) map.set(key, /* @__PURE__ */ new Map());
2016
+ map.get(key).set(opt.value, name);
2017
+ }
2018
+ }
2019
+ return Array.from(map.entries()).map(([key, valueMap]) => ({
2020
+ key,
2021
+ name: Array.from(valueMap.values())[0] ?? key,
2022
+ values: Array.from(valueMap.keys())
2023
+ }));
2024
+ }
2025
+ function findVariant(variants, selected, hasConfigurableOptions) {
2026
+ if (!hasConfigurableOptions && variants.length > 0) {
2027
+ return variants[0] ?? null;
2028
+ }
2029
+ if (Object.keys(selected).length === 0) return null;
2030
+ for (const v of variants) {
2031
+ const opts = v.options ?? [];
2032
+ const match = opts.length > 0 && opts.every((o) => selected[o.key] === o.value);
2033
+ if (match) return v;
2034
+ }
2035
+ return null;
2036
+ }
2037
+ function getInitialOptions(variants, optionGroups) {
2038
+ const first = variants[0];
2039
+ if (!first) return {};
2040
+ const opts = first.options ?? [];
2041
+ const selected = {};
2042
+ for (const g of optionGroups) {
2043
+ const o = opts.find((x) => x.key === g.key);
2044
+ if (o) selected[g.key] = o.value;
2045
+ }
2046
+ return selected;
2047
+ }
2048
+ function ProductVariantSelector({
2049
+ product,
2050
+ currency = "USD",
2051
+ features = [],
2052
+ onVariantChange,
2053
+ children
2054
+ }) {
2055
+ const variants = useMemo(() => product.variants ?? [], [product.variants]);
2056
+ const optionGroups = useMemo(() => buildOptionGroups(variants), [variants]);
2057
+ const hasConfigurableOptions = optionGroups.length > 0;
2058
+ const [selectedOptions, setSelectedOptions] = useState(
2059
+ () => getInitialOptions(variants, optionGroups)
2060
+ );
2061
+ const selectedVariant = useMemo(
2062
+ () => findVariant(variants, selectedOptions, hasConfigurableOptions),
2063
+ [variants, selectedOptions, hasConfigurableOptions]
2064
+ );
2065
+ const displayPrice = selectedVariant?.price ?? product.price ?? 0;
2066
+ const compareAtPrice = selectedVariant?.compareAtPrice ?? product.compareAtPrice;
2067
+ const isAvailable = (selectedVariant ? (selectedVariant.quantity ?? 0) > 0 : product.isAvailable) ?? true;
2068
+ useEffect(() => {
2069
+ onVariantChange?.(selectedVariant, displayPrice);
2070
+ }, [selectedVariant, displayPrice, onVariantChange]);
2071
+ const setOption = useCallback((key, value) => {
2072
+ setSelectedOptions((prev) => ({ ...prev, [key]: value }));
2073
+ }, []);
2074
+ const hasVariants = hasConfigurableOptions;
2075
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-5", children: [
2076
+ /* @__PURE__ */ jsxs("div", { className: "flex items-baseline gap-2 flex-wrap", children: [
2077
+ /* @__PURE__ */ jsx(
2078
+ "span",
2079
+ {
2080
+ className: "inline-flex items-center rounded-md bg-primary/10 px-2.5 py-0.5 text-base font-semibold text-primary",
2081
+ "data-testid": "product-display-price",
2082
+ children: formatCurrency(displayPrice, currency)
2083
+ }
2084
+ ),
2085
+ compareAtPrice != null && compareAtPrice > displayPrice && /* @__PURE__ */ jsx("span", { className: "text-sm text-muted-foreground line-through", children: formatCurrency(compareAtPrice, currency) })
2086
+ ] }),
2087
+ hasVariants && optionGroups.map((group) => /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
2088
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-semibold uppercase tracking-wider text-muted-foreground", children: group.name }),
2089
+ /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-2", children: group.values.map((value) => {
2090
+ const isSelected = selectedOptions[group.key] === value;
2091
+ const isColorLike = group.key.toLowerCase().includes("color") || group.key.toLowerCase().includes("colour");
2092
+ return /* @__PURE__ */ jsx(
2093
+ "button",
2094
+ {
2095
+ type: "button",
2096
+ onClick: () => setOption(group.key, value),
2097
+ className: isColorLike ? `min-w-[72px] rounded-md border-2 px-3 py-2 text-sm font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2 ${isSelected ? "border-primary bg-primary text-foreground" : "border-border bg-muted/30 text-foreground hover:border-muted-foreground/50"}` : `rounded-md border px-3 py-2 text-sm font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2 ${isSelected ? "border-primary bg-muted ring-1 ring-primary" : "border-border bg-transparent text-muted-foreground hover:border-muted-foreground/50 hover:text-foreground"}`,
2098
+ style: isColorLike && isSelected && value.toLowerCase() === "black" ? { backgroundColor: "#1a1a1a", borderColor: "#1a1a1a", color: "#fff" } : isColorLike && value.toLowerCase() === "white" ? { backgroundColor: "#fafafa", color: "#1a1a1a", borderColor: "#e5e5e5" } : void 0,
2099
+ "data-testid": `variant-option-${group.key}-${value}`,
2100
+ children: value
2101
+ },
2102
+ value
2103
+ );
2104
+ }) })
2105
+ ] }, group.key)),
2106
+ features.length > 0 && /* @__PURE__ */ jsx("ul", { className: "list-disc list-inside space-y-1 text-sm text-muted-foreground", children: features.map((f, i) => /* @__PURE__ */ jsx("li", { children: f }, i)) }),
2107
+ children?.({
2108
+ product,
2109
+ selectedVariant,
2110
+ displayPrice,
2111
+ compareAtPrice,
2112
+ isAvailable,
2113
+ selectedOptions
2114
+ })
2115
+ ] });
2116
+ }
2117
+
2118
+ // src/core/constants/api-paths.ts
2119
+ var STOREFRONT_API_PATH_PREFIX = "v1";
2120
+
2121
+ // src/core/constants/currency.ts
2122
+ var countryCurrency = {
2123
+ "PEN": "es-PE",
2124
+ "USD": "en-US",
2125
+ "COP": "es-CO",
2126
+ "MXN": "es-MX"
2127
+ };
2128
+
2129
+ // src/core/utils/format-currency.ts
2130
+ function formatCurrency2(amount, currency = "USD") {
2131
+ const locale = countryCurrency[currency] ?? "en-US";
2132
+ return new Intl.NumberFormat(locale, {
2133
+ style: "currency",
2134
+ currency
2135
+ }).format(amount);
2136
+ }
2137
+ var ApiClient = class {
2138
+ client;
2139
+ storeId;
2140
+ storeDomain;
2141
+ constructor(baseURL = "https://storefront.ecomiq.pe") {
2142
+ this.client = axios.create({
2143
+ baseURL,
2144
+ timeout: 1e4,
2145
+ headers: {
2146
+ "Content-Type": "application/json"
2147
+ }
2148
+ });
2149
+ this.client.interceptors.request.use(
2150
+ (config) => {
2151
+ if (config.headers) {
2152
+ if (this.storeId) config.headers["X-Store-Id"] = this.storeId;
2153
+ if (this.storeDomain) config.headers["X-Store-Domain"] = this.storeDomain;
2154
+ }
2155
+ return config;
2156
+ }
2157
+ );
2158
+ this.client.interceptors.response.use(
2159
+ (response) => response,
2160
+ (error) => {
2161
+ console.error("API Error:", error);
2162
+ return Promise.reject(error);
2163
+ }
2164
+ );
2165
+ }
2166
+ setStoreId(storeId) {
2167
+ this.storeId = storeId;
2168
+ }
2169
+ /** Subdominio para endpoints que usan X-Store-Domain (ej. GET /v1/storefront/settings) */
2170
+ setStoreDomain(domain) {
2171
+ this.storeDomain = domain;
2172
+ }
2173
+ get(url, config) {
2174
+ return this.client.get(url, config).then((response) => response.data);
2175
+ }
2176
+ post(url, data, config) {
2177
+ return this.client.post(url, data, config).then((response) => response.data);
2178
+ }
2179
+ put(url, data, config) {
2180
+ return this.client.put(url, data, config).then((response) => response.data);
2181
+ }
2182
+ patch(url, data, config) {
2183
+ return this.client.patch(url, data, config).then((response) => response.data);
2184
+ }
2185
+ delete(url, config) {
2186
+ return this.client.delete(url, config).then((response) => response.data);
2187
+ }
2188
+ };
2189
+ new ApiClient();
2190
+ var ProductVariantSchema = z.object({
2191
+ id: z.coerce.string(),
2192
+ name: z.string(),
2193
+ storeId: z.string(),
2194
+ productId: z.coerce.string(),
2195
+ basePrice: z.number().positive().optional().nullable(),
2196
+ comparePrice: z.number().positive().optional().nullable(),
2197
+ //TODO: remove this field
2198
+ originalPrice: z.number().positive().optional().nullable(),
2199
+ costPrice: z.number().positive().optional().nullable(),
2200
+ isDefault: z.boolean().optional().nullable(),
2201
+ reserveStock: z.number().nonnegative().optional().nullable(),
2202
+ weight: z.number().nonnegative().optional().nullable().default(0),
2203
+ width: z.number().nonnegative().optional().nullable(),
2204
+ height: z.number().nonnegative().optional().nullable(),
2205
+ unitOfMeasure: z.string().optional().nullable(),
2206
+ order: z.number().nonnegative().optional().nullable().default(0),
2207
+ sku: z.string().optional().nullable(),
2208
+ stock: z.number().nonnegative().optional().nullable(),
2209
+ leadTime: z.string().optional().nullable(),
2210
+ barCode: z.string().optional().nullable(),
2211
+ shippingWeight: z.number().nonnegative().optional().nullable(),
2212
+ images: z.array(z.string()).optional().nullable(),
2213
+ attributes: z.array(z.string()).optional().nullable(),
2214
+ variantStatus: z.string().optional().nullable(),
2215
+ status: z.string().optional().nullable(),
2216
+ createdOn: z.string().optional().nullable(),
2217
+ updatedOn: z.string().optional().nullable(),
2218
+ deletedOn: z.string().optional().nullable()
2219
+ });
2220
+ var ProductSchema = z.object({
2221
+ id: z.coerce.string(),
2222
+ name: z.string(),
2223
+ images: z.array(z.object({
2224
+ url: z.string().url(),
2225
+ name: z.string().optional(),
2226
+ isPrimary: z.boolean().optional()
2227
+ })),
2228
+ basePrice: z.number().positive().optional(),
2229
+ salePrice: z.number().positive().optional(),
2230
+ slug: z.string(),
2231
+ description: z.string().optional(),
2232
+ categories: z.array(z.object({
2233
+ id: z.coerce.string(),
2234
+ name: z.string(),
2235
+ slug: z.string(),
2236
+ logo: z.string().optional(),
2237
+ icon: z.string().optional()
2238
+ })).optional(),
2239
+ tags: z.array(z.string()).optional(),
2240
+ inStock: z.boolean().optional(),
2241
+ variants: z.array(ProductVariantSchema).optional()
2242
+ });
2243
+ z.object({
2244
+ items: z.array(ProductSchema),
2245
+ meta: z.object({
2246
+ total: z.number().int().positive(),
2247
+ page: z.number().int().positive(),
2248
+ pageSize: z.number().int().positive(),
2249
+ hasNextPage: z.boolean()
2250
+ })
2251
+ });
2252
+ z.object({
2253
+ category: z.string().optional(),
2254
+ search: z.string().optional(),
2255
+ minPrice: z.number().positive().optional(),
2256
+ maxPrice: z.number().positive().optional(),
2257
+ inStock: z.boolean().optional(),
2258
+ sortBy: z.enum(["name", "price", "createdAt"]).optional(),
2259
+ sortOrder: z.enum(["asc", "desc"]).optional(),
2260
+ page: z.number().int().positive().optional(),
2261
+ limit: z.number().int().positive().optional()
2262
+ });
2263
+ var StorefrontProductSchema = z.object({
2264
+ id: z.coerce.string(),
2265
+ name: z.string(),
2266
+ slug: z.string(),
2267
+ productType: z.string().optional().nullable(),
2268
+ imageUrl: z.string().optional().nullable(),
2269
+ price: z.number().nonnegative(),
2270
+ compareAtPrice: z.number().nonnegative().optional().nullable(),
2271
+ variantCount: z.number().int().nonnegative().optional().default(0),
2272
+ isAvailable: z.boolean(),
2273
+ totalInventory: z.number().int().nonnegative(),
2274
+ bundleItemsCount: z.number().int().nonnegative().optional().default(0),
2275
+ brand: z.string().optional().nullable(),
2276
+ category: z.string().optional().nullable(),
2277
+ status: z.string().optional().nullable(),
2278
+ tags: z.array(z.string()).optional().nullable()
2279
+ });
2280
+ var StorefrontFacetSchema = z.object({
2281
+ value: z.string(),
2282
+ count: z.number().int().nonnegative()
2283
+ });
2284
+ var StorefrontProductsResponseSchema2 = z.object({
2285
+ pageIndex: z.number().int().positive(),
2286
+ pageSize: z.number().int().positive(),
2287
+ totalCount: z.number().int().nonnegative(),
2288
+ totalPages: z.number().int().nonnegative(),
2289
+ products: z.array(StorefrontProductSchema),
2290
+ categories: z.array(StorefrontFacetSchema).optional().default([]),
2291
+ brands: z.array(StorefrontFacetSchema).optional().default([]),
2292
+ minPrice: z.number().nonnegative().optional().nullable(),
2293
+ maxPrice: z.number().nonnegative().optional().nullable()
2294
+ });
2295
+ z.object({
2296
+ page: z.number().int().positive().optional().default(1),
2297
+ pageSize: z.number().int().positive().optional().default(20),
2298
+ sort: z.enum(["price_asc", "price_desc", "name_asc", "name_desc", "newest"]).optional().default("price_asc"),
2299
+ sellerId: z.string().optional(),
2300
+ search: z.string().optional(),
2301
+ collection: z.string().optional(),
2302
+ category: z.string().optional(),
2303
+ brand: z.string().optional(),
2304
+ productType: z.string().optional(),
2305
+ inStock: z.boolean().optional(),
2306
+ minPrice: z.number().nonnegative().optional(),
2307
+ maxPrice: z.number().nonnegative().optional()
2308
+ });
2309
+ var StorefrontVariantOptionSchema = z.object({
2310
+ key: z.string(),
2311
+ name: z.string(),
2312
+ value: z.string()
2313
+ });
2314
+ var StorefrontProductImageSchema = z.object({
2315
+ url: z.string(),
2316
+ name: z.string().optional().nullable(),
2317
+ order: z.number().int().nonnegative().optional()
2318
+ });
2319
+ var StorefrontVariantSchema = z.object({
2320
+ id: z.coerce.string().optional().default(""),
2321
+ sku: z.string(),
2322
+ status: z.string().optional().nullable(),
2323
+ price: z.number().nonnegative(),
2324
+ compareAtPrice: z.number().nonnegative().optional().nullable(),
2325
+ quantity: z.number().int().nonnegative(),
2326
+ options: z.array(StorefrontVariantOptionSchema).optional().default([]),
2327
+ images: z.array(StorefrontProductImageSchema).optional().default([])
2328
+ });
2329
+ var StorefrontPriceTierSchema = z.object({
2330
+ variantId: z.coerce.string(),
2331
+ minQuantity: z.number().int().positive(),
2332
+ maxQuantity: z.number().int().positive().optional().nullable(),
2333
+ unitPrice: z.number().nonnegative()
2334
+ });
2335
+ var StorefrontBundleItemSchema = z.object({
2336
+ productId: z.coerce.string(),
2337
+ variantId: z.coerce.string(),
2338
+ productName: z.string(),
2339
+ variantName: z.string().optional().nullable(),
2340
+ sku: z.string().optional().nullable(),
2341
+ brand: z.string().optional().nullable(),
2342
+ imageUrl: z.string().optional().nullable(),
2343
+ quantity: z.number().int(),
2344
+ availableStock: z.number().int().optional().default(0),
2345
+ allowBackorders: z.boolean().optional().default(false),
2346
+ requiresShipping: z.boolean().optional().default(true)
2347
+ });
2348
+ z.object({
2349
+ id: z.coerce.string(),
2350
+ name: z.string(),
2351
+ slug: z.string(),
2352
+ description: z.string().optional().nullable(),
2353
+ productType: z.string().optional().nullable(),
2354
+ status: z.string().optional().nullable(),
2355
+ imageUrl: z.string().optional().nullable(),
2356
+ price: z.number().nonnegative(),
2357
+ compareAtPrice: z.number().nonnegative().optional().nullable(),
2358
+ isAvailable: z.boolean(),
2359
+ totalInventory: z.number().int().nonnegative(),
2360
+ brand: z.string().optional().nullable(),
2361
+ category: z.string().optional().nullable(),
2362
+ categoryId: z.coerce.string().optional().nullable(),
2363
+ tags: z.array(z.string()).optional().nullable(),
2364
+ specifications: z.array(z.record(z.string(), z.unknown())).optional().default([]),
2365
+ variants: z.array(StorefrontVariantSchema).optional().default([]),
2366
+ images: z.array(StorefrontProductImageSchema).optional().default([]),
2367
+ bundleItems: z.array(StorefrontBundleItemSchema).optional().default([]),
2368
+ priceTiers: z.array(StorefrontPriceTierSchema).optional().default([]),
2369
+ createdAt: z.string().optional().nullable(),
2370
+ updatedAt: z.string().optional().nullable()
2371
+ });
2372
+ var StorefrontCollectionSchema = z.object({
2373
+ id: z.coerce.string(),
2374
+ slug: z.string(),
2375
+ name: z.string(),
2376
+ description: z.string().nullable().optional(),
2377
+ imageUrl: z.string().nullable().optional(),
2378
+ type: z.string(),
2379
+ productCount: z.number().int().nonnegative()
2380
+ });
2381
+ z.array(StorefrontCollectionSchema);
2382
+ z.object({
2383
+ id: z.string(),
2384
+ name: z.string(),
2385
+ slug: z.string(),
2386
+ layout: z.string()
2387
+ });
2388
+ var StorefrontPageSeoSchema = z.object({
2389
+ title: z.string().optional(),
2390
+ description: z.string().optional()
2391
+ }).optional();
2392
+ var StorefrontPageSettingsSchema = z.object({
2393
+ seo: StorefrontPageSeoSchema,
2394
+ html: z.string().optional(),
2395
+ title: z.string().optional()
2396
+ }).catchall(z.unknown());
2397
+ var StorefrontPageBlockSchema = z.object({
2398
+ id: z.string(),
2399
+ type: z.string(),
2400
+ order: z.number(),
2401
+ settings: z.record(z.unknown()).optional()
2402
+ });
2403
+ var StorefrontPageSectionSchema = z.object({
2404
+ id: z.string(),
2405
+ type: z.string(),
2406
+ order: z.number(),
2407
+ settings: z.record(z.unknown()).optional(),
2408
+ blocks: z.array(StorefrontPageBlockSchema).optional().default([])
2409
+ });
2410
+ z.object({
2411
+ id: z.string(),
2412
+ name: z.string(),
2413
+ slug: z.string(),
2414
+ layout: z.string(),
2415
+ settings: StorefrontPageSettingsSchema.optional(),
2416
+ sections: z.array(StorefrontPageSectionSchema).optional()
2417
+ });
2418
+ z.object({
2419
+ email: z.string().email(),
2420
+ firstName: z.string().min(1),
2421
+ lastName: z.string().min(1),
2422
+ phone: z.string().min(1)
2423
+ });
2424
+ z.object({
2425
+ currencyCode: z.string(),
2426
+ salesChannelId: z.string(),
2427
+ email: z.string().email()
2428
+ });
2429
+ var CartAddressSchema = z.object({
2430
+ name: z.string().min(1),
2431
+ address1: z.string().min(1),
2432
+ city: z.string().min(1),
2433
+ countryCode: z.string().min(2),
2434
+ company: z.string().nullable().optional(),
2435
+ address2: z.string().nullable().optional(),
2436
+ province: z.string().nullable().optional(),
2437
+ postalCode: z.string().nullable().optional(),
2438
+ district: z.string().nullable().optional(),
2439
+ phone: z.string().nullable().optional(),
2440
+ latitude: z.number().nullable().optional(),
2441
+ longitude: z.number().nullable().optional(),
2442
+ geo: z.object({
2443
+ departmentCode: z.string().nullable().optional(),
2444
+ provinceCode: z.string().nullable().optional(),
2445
+ districtCode: z.string().nullable().optional()
2446
+ }).nullable().optional()
2447
+ });
2448
+ z.object({
2449
+ shippingAddress: CartAddressSchema,
2450
+ billingAddress: CartAddressSchema
2451
+ });
2452
+ var CartItemModifierRequestSchema = z.object({
2453
+ modifierGroupId: z.coerce.string().min(1),
2454
+ optionIds: z.array(z.coerce.string().min(1)).optional().default([])
2455
+ });
2456
+ z.object({
2457
+ variantId: z.coerce.string().min(1),
2458
+ quantity: z.number().int().positive(),
2459
+ modifiers: z.array(CartItemModifierRequestSchema).optional()
2460
+ });
2461
+ z.object({
2462
+ code: z.string().min(1)
2463
+ });
2464
+ z.object({
2465
+ subtotal: z.number(),
2466
+ discount: z.number(),
2467
+ tax: z.number(),
2468
+ warranty: z.number().optional().default(0),
2469
+ total: z.number()
2470
+ });
2471
+ z.object({
2472
+ name: z.string().nullable().optional(),
2473
+ address1: z.string(),
2474
+ city: z.string(),
2475
+ countryCode: z.string(),
2476
+ company: z.string().nullable().optional(),
2477
+ address2: z.string().nullable().optional(),
2478
+ province: z.string().nullable().optional(),
2479
+ postalCode: z.string().nullable().optional(),
2480
+ district: z.string().nullable().optional(),
2481
+ phone: z.string().nullable().optional(),
2482
+ latitude: z.number().nullable().optional(),
2483
+ longitude: z.number().nullable().optional(),
2484
+ geo: z.object({
2485
+ departmentCode: z.string().nullable().optional(),
2486
+ provinceCode: z.string().nullable().optional(),
2487
+ districtCode: z.string().nullable().optional()
2488
+ }).nullable().optional()
2489
+ });
2490
+ var CartResponseBundleItemSchema = z.object({
2491
+ productId: z.coerce.string(),
2492
+ variantId: z.coerce.string(),
2493
+ productName: z.string(),
2494
+ variantName: z.string().nullable().optional(),
2495
+ sku: z.string().nullable().optional(),
2496
+ brand: z.string().nullable().optional(),
2497
+ imageUrl: z.string().nullable().optional(),
2498
+ quantity: z.number().int()
2499
+ });
2500
+ var CartResponseItemSchema = z.object({
2501
+ id: z.string(),
2502
+ title: z.string(),
2503
+ variantLabel: z.string().nullable().optional(),
2504
+ thumbnail: z.string().nullable().optional(),
2505
+ quantity: z.number().int(),
2506
+ variantId: z.coerce.string(),
2507
+ variantSku: z.string().nullable().optional(),
2508
+ unitPrice: z.number(),
2509
+ bundleItems: z.array(CartResponseBundleItemSchema).optional().default([])
2510
+ });
2511
+ z.object({
2512
+ id: z.string(),
2513
+ name: z.string(),
2514
+ description: z.string().nullable().optional(),
2515
+ total: z.number(),
2516
+ estimatedDays: z.number().int().nullable().optional(),
2517
+ pickupPointId: z.string().nullable().optional(),
2518
+ scheduledDate: z.string().nullable().optional(),
2519
+ scheduledTime: z.string().nullable().optional()
2520
+ });
2521
+ var CartResponseSchema2 = z.object({
2522
+ id: z.string(),
2523
+ regionId: z.string().nullable().optional(),
2524
+ salesChannelId: z.string().nullable().optional(),
2525
+ email: z.string().email().nullable().optional(),
2526
+ phone: z.string().nullable().optional(),
2527
+ firstName: z.string().nullable().optional(),
2528
+ lastName: z.string().nullable().optional(),
2529
+ currencyCode: z.string(),
2530
+ completedAt: z.string().nullable().optional(),
2531
+ shippingAddress: z.unknown().nullable().optional(),
2532
+ billingAddress: z.unknown().nullable().optional(),
2533
+ metadata: z.record(z.string(), z.unknown()).optional().default({}),
2534
+ promotionCodes: z.array(z.string()).optional().default([]),
2535
+ items: z.array(CartResponseItemSchema).optional().default([]),
2536
+ shippingMethods: z.array(z.unknown()).optional().default([]),
2537
+ totals: z.object({
2538
+ discountTotal: z.number(),
2539
+ shippingTotal: z.number(),
2540
+ subtotal: z.number(),
2541
+ taxTotal: z.number(),
2542
+ total: z.number(),
2543
+ warrantyTotal: z.number()
2544
+ })
2545
+ });
2546
+ z.object({
2547
+ paymentMethodCode: z.string().min(1),
2548
+ externalRef: z.string().min(1),
2549
+ receiptUrl: z.string().min(1).optional()
2550
+ });
2551
+ var CartCheckoutPaymentActionSchema = z.object({
2552
+ publicConfiguration: z.object({
2553
+ publishableKey: z.string().nullable().optional(),
2554
+ checkoutMode: z.string().nullable().optional(),
2555
+ phoneNumber: z.string().nullable().optional(),
2556
+ accountHolder: z.string().nullable().optional(),
2557
+ qrImageUrl: z.string().nullable().optional()
2558
+ }).passthrough().nullable().optional(),
2559
+ url: z.string().nullable().optional(),
2560
+ html: z.string().nullable().optional(),
2561
+ clientSecret: z.string().nullable().optional(),
2562
+ qrCode: z.string().nullable().optional(),
2563
+ expiresAt: z.string().nullable().optional()
2564
+ }).passthrough();
2565
+ var CartCheckoutPaymentSchema = z.object({
2566
+ transactionId: z.string(),
2567
+ paymentMethodCode: z.string(),
2568
+ providerId: z.string().nullable().optional(),
2569
+ status: z.string(),
2570
+ amount: z.number(),
2571
+ reference: z.string(),
2572
+ actionType: z.string(),
2573
+ receiptUrl: z.string().nullable().optional(),
2574
+ action: CartCheckoutPaymentActionSchema.nullable().optional()
2575
+ }).passthrough();
2576
+ z.object({
2577
+ publicKey: z.string().optional(),
2578
+ publishableKey: z.string().optional(),
2579
+ stripePublicKey: z.string().optional(),
2580
+ clientSecret: z.string().optional(),
2581
+ intentClientSecret: z.string().optional(),
2582
+ orderId: z.coerce.string().optional(),
2583
+ orderGroup: z.string().optional(),
2584
+ orderIds: z.array(z.coerce.string()).optional(),
2585
+ /** Payload de pago (embedded, redirect, etc.) */
2586
+ payment: CartCheckoutPaymentSchema.optional(),
2587
+ cart: z.unknown().optional()
2588
+ }).passthrough();
2589
+ z.object({
2590
+ id: z.string(),
2591
+ url: z.string()
2592
+ });
2593
+ var CartPaymentMethodSchema = z.object({
2594
+ code: z.string(),
2595
+ name: z.string(),
2596
+ description: z.string().nullable().optional(),
2597
+ kind: z.string(),
2598
+ isActive: z.boolean(),
2599
+ isDefault: z.boolean(),
2600
+ isManual: z.boolean(),
2601
+ sortOrder: z.number(),
2602
+ phoneNumber: z.string().nullable().optional(),
2603
+ accountHolder: z.string().nullable().optional(),
2604
+ qrImageUrl: z.string().nullable().optional()
2162
2605
  });
2163
- var signInBody = z.object({
2164
- email: z.string().email(),
2165
- password: z.string().min(1),
2166
- callbackURL: z.string().optional(),
2167
- rememberMe: z.boolean().optional().default(true)
2606
+ var CartPaymentMethodsResponseSchema = z.object({
2607
+ methods: z.array(CartPaymentMethodSchema)
2168
2608
  });
2169
-
2170
- // src/react/auth/ecomiq-oauth-password-plugin.ts
2171
- function ecomiqOAuthPassword() {
2172
- const secureCookie = true;
2173
- const clearStorefrontOAuthCookieMiddleware = createAuthMiddleware(async (ctx) => {
2174
- ctx.setCookie(STOREFRONT_ECOMIQ_OAUTH_COOKIE_NAME, "", {
2175
- path: STOREFRONT_ECOMIQ_OAUTH_COOKIE_PATH,
2176
- httpOnly: true,
2177
- sameSite: "lax",
2178
- secure: secureCookie,
2179
- maxAge: 0
2180
- });
2181
- });
2182
- return {
2183
- id: PLUGIN_ID,
2184
- schema: {
2185
- user: {
2186
- fields: {
2187
- firstName: { type: "string", required: false },
2188
- lastName: { type: "string", required: false },
2189
- phone: { type: "string", required: false }
2190
- }
2191
- }
2192
- },
2193
- hooks: {
2194
- after: [
2195
- {
2196
- matcher: (hookCtx) => hookCtx.path === "/sign-out",
2197
- handler: clearStorefrontOAuthCookieMiddleware
2198
- }
2199
- ]
2200
- },
2201
- endpoints: {
2202
- signIn: createAuthEndpoint(
2203
- "/ecomiq-oauth/sign-in",
2204
- {
2205
- method: "POST",
2206
- use: [formCsrfMiddleware],
2207
- body: signInBody
2208
- },
2209
- async (ctx) => {
2210
- const { email: loginEmail, password, callbackURL, rememberMe } = ctx.body;
2211
- const tokens = await exchangePasswordGrant(loginEmail, password, {
2212
- identityScope: ctx.getHeader("x-Identity-Scope"),
2213
- tenantId: ctx.getHeader("x-Tenant-Id"),
2214
- storeId: ctx.getHeader("x-Store-Id")
2215
- });
2216
- const profile = await fetchUserInfo(tokens.access_token);
2217
- const profileEmail = profile.email?.toLowerCase().trim();
2218
- const canonicalEmail = profileEmail ?? loginEmail.toLowerCase().trim();
2219
- if (profileEmail && profileEmail !== loginEmail.toLowerCase().trim()) {
2220
- throw APIError.from("UNAUTHORIZED", {
2221
- code: "ECOMIQ_EMAIL_MISMATCH",
2222
- message: "El correo del perfil OAuth no coincide con el inicio de sesi\xF3n"
2223
- });
2224
- }
2225
- const displayName = displayNameFromUserInfo(profile, canonicalEmail);
2226
- const existing = await ctx.context.internalAdapter.findUserByEmail(canonicalEmail);
2227
- let user = existing?.user;
2228
- if (!user) {
2229
- user = await ctx.context.internalAdapter.createUser({
2230
- email: canonicalEmail,
2231
- name: displayName,
2232
- emailVerified: true
2233
- });
2234
- } else if (user.name !== displayName) {
2235
- await ctx.context.internalAdapter.updateUser(user.id, { name: displayName });
2236
- user = { ...user, name: displayName };
2237
- }
2238
- const oauthSessionExpires = tokens.expires_in !== void 0 && tokens.expires_in > 0 ? new Date(Date.now() + tokens.expires_in * 1e3) : void 0;
2239
- const session = await ctx.context.internalAdapter.createSession(
2240
- user.id,
2241
- rememberMe === false,
2242
- oauthSessionExpires ? { expiresAt: oauthSessionExpires } : {},
2243
- Boolean(oauthSessionExpires)
2244
- );
2245
- if (!session) {
2246
- throw APIError.from("INTERNAL_SERVER_ERROR", {
2247
- code: "ECOMIQ_SESSION_FAILED",
2248
- message: "No se pudo crear la sesi\xF3n"
2249
- });
2250
- }
2251
- await setSessionCookie(ctx, { session, user }, rememberMe === false);
2252
- const oauthMaxAgeSec = oauthSessionExpires !== void 0 ? Math.max(60, Math.ceil((oauthSessionExpires.getTime() - Date.now()) / 1e3)) : tokens.expires_in !== void 0 && tokens.expires_in > 0 ? tokens.expires_in : 60 * 60 * 24;
2253
- await ctx.setCookie(STOREFRONT_ECOMIQ_OAUTH_COOKIE_NAME, tokens.access_token, {
2254
- path: STOREFRONT_ECOMIQ_OAUTH_COOKIE_PATH,
2255
- httpOnly: true,
2256
- sameSite: "lax",
2257
- secure: secureCookie,
2258
- maxAge: oauthMaxAgeSec
2259
- });
2260
- if (callbackURL) ctx.setHeader("Location", callbackURL);
2261
- return ctx.json({
2262
- redirect: !!callbackURL,
2263
- token: session.token,
2264
- url: callbackURL,
2265
- user: parseUserOutput(ctx.context.options, user)
2266
- });
2267
- }
2268
- )
2269
- }
2270
- };
2271
- }
2272
- function displayNameFromUserInfo(profile, fallbackEmail) {
2273
- const parts = [profile.given_name?.trim(), profile.family_name?.trim()].filter(Boolean);
2274
- const joined = parts.join(" ").trim();
2275
- if (joined) return joined;
2276
- return fallbackEmail.split("@")[0] || "Cliente";
2277
- }
2278
- async function fetchUserInfo(accessToken) {
2279
- const res = await fetch(ECOMIQ_OAUTH_USERINFO_URL, {
2280
- method: "GET",
2281
- headers: {
2282
- Authorization: `Bearer ${accessToken}`,
2283
- accept: "application/json"
2284
- }
2285
- });
2286
- const text = await res.text();
2287
- let json;
2288
- try {
2289
- json = text ? JSON.parse(text) : {};
2290
- } catch {
2291
- throw APIError.from("UNAUTHORIZED", {
2292
- code: "ECOMIQ_USERINFO_INVALID_BODY",
2293
- message: "Respuesta inv\xE1lida del endpoint userinfo"
2294
- });
2295
- }
2296
- if (!res.ok) {
2297
- const err = json;
2298
- throw APIError.from("UNAUTHORIZED", {
2299
- code: "ECOMIQ_USERINFO_REJECTED",
2300
- message: err.error_description ?? err.error ?? "No se pudo obtener el perfil del usuario"
2301
- });
2302
- }
2303
- const parsed = userInfoSchema.safeParse(json);
2304
- if (!parsed.success) {
2305
- throw APIError.from("UNAUTHORIZED", {
2306
- code: "ECOMIQ_USERINFO_SHAPE",
2307
- message: "Perfil OAuth con formato inv\xE1lido"
2308
- });
2309
- }
2310
- return parsed.data;
2311
- }
2312
- async function exchangePasswordGrant(username, password, context) {
2313
- const clientId = process.env.ECOMIQ_AUTH_CLIENT_ID ?? "";
2314
- const clientSecret = process.env.ECOMIQ_AUTH_CLIENT_SECRET ?? "";
2315
- const scope = process.env.ECOMIQ_AUTH_SCOPE ?? "";
2316
- if (!clientId || !clientSecret) {
2317
- throw APIError.from("INTERNAL_SERVER_ERROR", {
2318
- code: "ECOMIQ_AUTH_NOT_CONFIGURED",
2319
- message: "OAuth Ecomiq no configurado: ECOMIQ_AUTH_CLIENT_ID y ECOMIQ_AUTH_CLIENT_SECRET."
2320
- });
2321
- }
2322
- const body = new URLSearchParams({
2323
- grant_type: "password",
2324
- username,
2325
- password,
2326
- scope,
2327
- client_id: clientId,
2328
- client_secret: clientSecret
2329
- });
2330
- const identityScope = context.identityScope;
2331
- const tenantId = context.tenantId;
2332
- const storeId = context.storeId;
2333
- const headers = new Headers({
2334
- "content-type": "application/x-www-form-urlencoded",
2335
- accept: "application/json"
2336
- });
2337
- if (identityScope?.trim()) {
2338
- headers.set("x-Identity-Scope", identityScope.trim());
2609
+ z.object({
2610
+ cart: CartResponseSchema2,
2611
+ paymentMethods: CartPaymentMethodsResponseSchema
2612
+ });
2613
+ var ShippingRateSchema = z.object({
2614
+ shippingMethodId: z.string(),
2615
+ name: z.string(),
2616
+ displayName: z.string().optional(),
2617
+ type: z.string().optional(),
2618
+ price: z.number(),
2619
+ currency: z.string().optional(),
2620
+ sortOrder: z.number().optional(),
2621
+ rateId: z.string(),
2622
+ description: z.string().nullable().optional(),
2623
+ deliveryLocationId: z.string().nullable().optional(),
2624
+ deliveryLocationName: z.string().nullable().optional(),
2625
+ estimatedDeliveryDays: z.number().nullable().optional(),
2626
+ serviceCode: z.string().nullable().optional(),
2627
+ metadata: z.record(z.string(), z.unknown()).optional()
2628
+ }).passthrough();
2629
+ var ShippingOptionGroupSchema = z.object({
2630
+ sellerId: z.string(),
2631
+ shippingProfileId: z.string().nullable().optional(),
2632
+ orderAmount: z.number().optional(),
2633
+ totalWeight: z.number().optional(),
2634
+ itemIds: z.array(z.string()).optional(),
2635
+ rates: z.array(ShippingRateSchema)
2636
+ }).passthrough();
2637
+ z.object({
2638
+ groups: z.array(ShippingOptionGroupSchema)
2639
+ });
2640
+ z.object({
2641
+ rateId: z.string().min(1),
2642
+ shippingMethodId: z.string().min(1),
2643
+ serviceCode: z.string().nullable().optional(),
2644
+ sellerId: z.string().nullable().optional(),
2645
+ shippingProfileId: z.string().nullable().optional(),
2646
+ locationId: z.string().nullable().optional(),
2647
+ pickupPointId: z.string().nullable().optional(),
2648
+ scheduledDate: z.string().nullable().optional(),
2649
+ scheduledTime: z.string().nullable().optional()
2650
+ });
2651
+ var CONFIG_FILENAMES = [
2652
+ "ecomiq.config.js",
2653
+ "ecomiq.config.cjs",
2654
+ "ecomiq.config.mjs"
2655
+ ];
2656
+ var EcomiqConfigSchema = z.object({
2657
+ /**
2658
+ * Slug de tienda en Ecomiq (p. ej. `mi-tienda` para `mi-tienda.ecomiq.app`).
2659
+ * Si está definido, tiene prioridad sobre el `Host` de la request (útil en `localhost`).
2660
+ */
2661
+ slugStore: z.string().min(1).optional()
2662
+ });
2663
+ var cachedConfig;
2664
+ function parseExportedConfig(raw) {
2665
+ const candidate = raw && typeof raw === "object" && "default" in raw ? raw.default : raw;
2666
+ const parsed = EcomiqConfigSchema.safeParse(candidate);
2667
+ if (!parsed.success) {
2668
+ console.warn(
2669
+ "[ecomiq] ecomiq.config.* inv\xE1lido:",
2670
+ parsed.error.flatten().fieldErrors
2671
+ );
2672
+ return null;
2339
2673
  }
2340
- if (tenantId?.trim()) {
2341
- headers.set("x-Tenant-Id", tenantId.trim());
2674
+ const slugStore = parsed.data.slugStore?.trim();
2675
+ return slugStore ? { slugStore } : {};
2676
+ }
2677
+ function loadEcomiqConfig(cwd = process.cwd()) {
2678
+ if (cachedConfig !== void 0) return cachedConfig;
2679
+ for (const filename of CONFIG_FILENAMES) {
2680
+ const configPath = join(cwd, filename);
2681
+ if (!existsSync(configPath)) continue;
2682
+ try {
2683
+ const requireFromApp = createRequire(join(cwd, "package.json"));
2684
+ const loaded = requireFromApp(configPath);
2685
+ const parsed = parseExportedConfig(loaded);
2686
+ cachedConfig = parsed;
2687
+ return cachedConfig;
2688
+ } catch (error) {
2689
+ console.warn(`[ecomiq] No se pudo cargar ${configPath}:`, error);
2690
+ }
2342
2691
  }
2343
- if (storeId?.trim()) {
2344
- headers.set("x-Store-Id", storeId.trim());
2692
+ cachedConfig = null;
2693
+ return null;
2694
+ }
2695
+ function resolvePublicStoreKey(hostHeader, cwd) {
2696
+ const slugFromConfig = loadEcomiqConfig(cwd)?.slugStore?.trim();
2697
+ if (slugFromConfig) return slugFromConfig;
2698
+ if (!hostHeader?.trim()) {
2699
+ throw Object.assign(
2700
+ new Error(
2701
+ "Host is required, o define slugStore en ecomiq.config.js en la ra\xEDz del proyecto"
2702
+ ),
2703
+ { status: 400 }
2704
+ );
2345
2705
  }
2346
- let res;
2347
- let text;
2706
+ return resolveStorefrontSettingsHost(hostHeader);
2707
+ }
2708
+
2709
+ // src/core/server/request-origin.ts
2710
+ init_store_api_client();
2711
+ function applyServerRequestOriginFromHeaders(headerList) {
2712
+ const host = headerList.get("host")?.trim();
2713
+ if (!host) return;
2714
+ const proto = headerList.get("x-forwarded-proto") ?? headerList.get("x-forwarded-ssl") ?? "http";
2715
+ const secure = proto === "https" || (headerList.get("x-forwarded-ssl") ?? "") === "on";
2716
+ setServerRequestOrigin(`${secure ? "https" : "http"}://${host}`);
2717
+ }
2718
+ function normalizeRequestHostname(headerList) {
2719
+ const host = headerList.get("host");
2720
+ return host?.replace(/^www\./, "") ?? null;
2721
+ }
2722
+
2723
+ // src/core/server/fetch-public-store.ts
2724
+ init_store_api_client();
2725
+
2726
+ // src/react/shell/lib/public-store-settings-source.ts
2727
+ var ENV_PUBLIC_STORE_SETTINGS_SOURCE = "PUBLIC_STORE_SETTINGS_SOURCE";
2728
+ function resolvePublicStoreSettingsSource(envValue = process.env[ENV_PUBLIC_STORE_SETTINGS_SOURCE]) {
2729
+ const v = (envValue ?? "api").trim().toLowerCase();
2730
+ return v === "kv" ? "kv" : "api";
2731
+ }
2732
+
2733
+ // src/core/server/fetch-public-store.ts
2734
+ async function fetchPublicStoreSettings(options) {
2735
+ let storeKey;
2348
2736
  try {
2349
- res = await fetch(ECOMIQ_OAUTH_TOKEN_URL, {
2350
- method: "POST",
2351
- headers,
2352
- body
2353
- });
2354
- text = await res.text();
2355
- } catch (e) {
2356
- throw mapTokenEndpointFetchFailure(e);
2357
- }
2358
- const json = safeParseJson(text);
2359
- if (!res.ok) {
2360
- throwBadRequestFromOAuth(json, res.statusText || text || `HTTP ${res.status}`);
2361
- }
2362
- const parsed = tokenResponseSchema.safeParse(json);
2363
- if (!parsed.success) {
2364
- throwBadRequestFromOAuth(json, "Token de acceso no recibido");
2737
+ storeKey = resolvePublicStoreKey(options.host);
2738
+ } catch (error) {
2739
+ const err = error;
2740
+ return {
2741
+ data: null,
2742
+ error: {
2743
+ message: err.message ?? "Host is required",
2744
+ statusCode: err.status ?? 400,
2745
+ isNotFound: true,
2746
+ subdomain: void 0
2747
+ }
2748
+ };
2365
2749
  }
2366
- return parsed.data;
2367
- }
2368
- function safeParseJson(text) {
2750
+ const source = options.source ?? resolvePublicStoreSettingsSource();
2369
2751
  try {
2370
- return text ? JSON.parse(text) : {};
2371
- } catch {
2372
- return {};
2752
+ setServerStoreId(null);
2753
+ const data = source === "kv" ? await getStorefrontSettingsFromKvByStoreKey(storeKey) : await getStorefrontSettings(
2754
+ storeKey,
2755
+ `storefront-settings-${storeKey}`
2756
+ );
2757
+ setServerStoreId(data.store.id);
2758
+ return { data, error: null };
2759
+ } catch (error) {
2760
+ const err = error;
2761
+ const statusCode = err.status ?? 500;
2762
+ return {
2763
+ data: null,
2764
+ error: {
2765
+ message: err.message ?? "Error fetching store settings",
2766
+ statusCode,
2767
+ isNotFound: statusCode === 404,
2768
+ subdomain: storeKey
2769
+ }
2770
+ };
2373
2771
  }
2374
2772
  }
2375
- function throwBadRequestFromOAuth(payload, fallbackMessage) {
2376
- const err = payload;
2377
- const errorCode = typeof err.error === "string" && err.error.trim() ? err.error.trim() : "invalid_grant";
2378
- const errorMessage = typeof err.error_description === "string" && err.error_description.trim() ? err.error_description.trim() : typeof err.error === "string" && err.error.trim() ? err.error.trim() : fallbackMessage.trim() || "Credenciales invalidas.";
2379
- throw APIError.from("BAD_REQUEST", {
2380
- code: errorCode,
2381
- message: errorMessage.slice(0, 500)
2382
- });
2773
+
2774
+ // src/core/server/store-proxy-handler.ts
2775
+ var DEFAULT_API_BASE_URL = "https://storefront.ecomiq.pe";
2776
+ function jsonError(status, message, error) {
2777
+ return Response.json({ error, message, status }, { status });
2383
2778
  }
2384
- function mapTokenEndpointFetchFailure(e) {
2385
- const codes = collectNetworkErrorCodes(e);
2386
- const msg = e instanceof Error ? e.message : String(e);
2387
- const isDnsError = codes.some((c) => c === "ENOTFOUND" || c === "EAI_AGAIN");
2388
- if (isDnsError) {
2389
- throw APIError.from("BAD_GATEWAY", {
2390
- code: "ECOMIQ_AUTH_DNS",
2391
- message: "No se pudo resolver el servidor de autenticacion."
2392
- });
2393
- }
2394
- const isConnectionRefused = codes.includes("ECONNREFUSED");
2395
- if (isConnectionRefused) {
2396
- throw APIError.from("BAD_GATEWAY", {
2397
- code: "ECOMIQ_AUTH_REFUSED",
2398
- message: "Conexion rechazada al servidor de autenticacion."
2399
- });
2779
+ async function ensureStoreContext(request) {
2780
+ const xStoreId = request.headers.get("x-store-id")?.trim();
2781
+ const xStoreDomain = request.headers.get("x-store-domain")?.trim();
2782
+ if (xStoreDomain) {
2783
+ return {
2784
+ ok: true,
2785
+ storeId: xStoreId || "",
2786
+ storeDomain: xStoreDomain || null
2787
+ };
2400
2788
  }
2401
- const looksLikeAbortOrSocketClose = codes.some(
2402
- (c) => ["UND_ERR_SOCKET", "ECONNRESET", "EPIPE", "ECONNABORTED"].includes(c)
2403
- ) || /terminated|abort|closed|FIN|socket/i.test(msg);
2404
- if (looksLikeAbortOrSocketClose) {
2405
- throwBadRequestFromOAuth({}, "Credenciales invalidas o conexion cerrada por el servidor.");
2789
+ if (!xStoreId) {
2790
+ return {
2791
+ ok: false,
2792
+ response: jsonError(
2793
+ 400,
2794
+ "X-Store-Id is required",
2795
+ "Store context required"
2796
+ )
2797
+ };
2406
2798
  }
2407
- throw APIError.from("INTERNAL_SERVER_ERROR", {
2408
- code: "ECOMIQ_AUTH_REQUEST_FAILED",
2409
- message: msg.slice(0, 280) || "Error al contactar el servidor de autenticacion."
2410
- });
2411
- }
2412
- function collectNetworkErrorCodes(e) {
2413
- const out = [];
2414
- const visit = (x) => {
2415
- if (!x || typeof x !== "object") return;
2416
- const code = "code" in x && typeof x.code === "string" ? x.code : void 0;
2417
- if (code) out.push(code);
2418
- if ("cause" in x && x.cause) {
2419
- visit(x.cause);
2420
- }
2421
- };
2422
- visit(e);
2423
- return out;
2424
- }
2425
-
2426
- // src/react/auth/ecomiq-oauth-password-client.ts
2427
- function ecomiqOAuthPasswordClient() {
2428
2799
  return {
2429
- id: PLUGIN_ID,
2430
- pathMethods: {
2431
- "/ecomiq-oauth/sign-in": "POST"
2432
- },
2433
- atomListeners: [
2434
- {
2435
- matcher: (path) => path === "/ecomiq-oauth/sign-in",
2436
- signal: "$sessionSignal"
2437
- }
2438
- ]
2800
+ ok: true,
2801
+ storeId: xStoreId || "",
2802
+ storeDomain: xStoreDomain || null
2439
2803
  };
2440
2804
  }
2441
- function buildOptionGroups(variants) {
2442
- const map = /* @__PURE__ */ new Map();
2443
- for (const v of variants) {
2444
- for (const opt of v.options ?? []) {
2445
- const key = opt.key;
2446
- const name = opt.name ?? key;
2447
- if (!map.has(key)) map.set(key, /* @__PURE__ */ new Map());
2448
- map.get(key).set(opt.value, name);
2449
- }
2805
+ var SKIP_FORWARD_HEADERS = /* @__PURE__ */ new Set([
2806
+ "host",
2807
+ "connection",
2808
+ "content-length",
2809
+ "transfer-encoding",
2810
+ "x-store-id",
2811
+ "x-store-domain",
2812
+ "authorization",
2813
+ "cookie",
2814
+ "cookie2",
2815
+ "set-cookie",
2816
+ "x-forwarded-for",
2817
+ "x-forwarded-proto",
2818
+ "x-forwarded-host",
2819
+ "x-real-ip",
2820
+ "forwarded",
2821
+ "cf-connecting-ip",
2822
+ "cf-connecting-ipv6",
2823
+ "true-client-ip",
2824
+ "cf-ipcountry",
2825
+ "cf-ray",
2826
+ "cf-visitor"
2827
+ ]);
2828
+ function getClientIp(request) {
2829
+ const candidates = [
2830
+ request.headers.get("cf-connecting-ip"),
2831
+ request.headers.get("true-client-ip"),
2832
+ request.headers.get("x-real-ip")
2833
+ ];
2834
+ for (const value of candidates) {
2835
+ const trimmed = value?.trim();
2836
+ if (trimmed) return trimmed;
2837
+ }
2838
+ const xff = request.headers.get("x-forwarded-for");
2839
+ if (xff?.trim()) {
2840
+ const first = xff.split(",")[0]?.trim();
2841
+ if (first) return first;
2450
2842
  }
2451
- return Array.from(map.entries()).map(([key, valueMap]) => ({
2452
- key,
2453
- name: Array.from(valueMap.values())[0] ?? key,
2454
- values: Array.from(valueMap.keys())
2455
- }));
2843
+ return null;
2456
2844
  }
2457
- function findVariant(variants, selected, hasConfigurableOptions) {
2458
- if (!hasConfigurableOptions && variants.length > 0) {
2459
- return variants[0] ?? null;
2460
- }
2461
- if (Object.keys(selected).length === 0) return null;
2462
- for (const v of variants) {
2463
- const opts = v.options ?? [];
2464
- const match = opts.length > 0 && opts.every((o) => selected[o.key] === o.value);
2465
- if (match) return v;
2845
+ function getCookieValue(request, name) {
2846
+ const cookieHeader = request.headers.get("cookie");
2847
+ if (!cookieHeader) return void 0;
2848
+ for (const part of cookieHeader.split(";")) {
2849
+ const [rawKey, ...rest] = part.trim().split("=");
2850
+ if (rawKey?.trim() === name) {
2851
+ return decodeURIComponent(rest.join("="));
2852
+ }
2466
2853
  }
2467
- return null;
2854
+ return void 0;
2468
2855
  }
2469
- function getInitialOptions(variants, optionGroups) {
2470
- const first = variants[0];
2471
- if (!first) return {};
2472
- const opts = first.options ?? [];
2473
- const selected = {};
2474
- for (const g of optionGroups) {
2475
- const o = opts.find((x) => x.key === g.key);
2476
- if (o) selected[g.key] = o.value;
2856
+ function buildUpstreamRequest(request, pathSegments, config, storeId, storeDomain) {
2857
+ const requestUrl = new URL(request.url);
2858
+ const base = (config.apiBaseUrl ?? DEFAULT_API_BASE_URL).replace(/\/$/, "");
2859
+ const userPath = pathSegments.length ? pathSegments.join("/") : "";
2860
+ const path = userPath ? `${STOREFRONT_API_PATH_PREFIX}/${userPath}` : STOREFRONT_API_PATH_PREFIX;
2861
+ const search = requestUrl.searchParams.toString();
2862
+ const url = search ? `${base}/${path}?${search}` : `${base}/${path}`;
2863
+ const headers = new Headers();
2864
+ request.headers.forEach((value, key) => {
2865
+ const lower = key.toLowerCase();
2866
+ if (!SKIP_FORWARD_HEADERS.has(lower)) {
2867
+ headers.set(key, value);
2868
+ }
2869
+ });
2870
+ headers.set("X-Store-Id", storeId);
2871
+ if (storeDomain) {
2872
+ headers.set("X-Store-domain", storeDomain);
2873
+ }
2874
+ const clientIp = getClientIp(request);
2875
+ if (clientIp) {
2876
+ headers.set("X-Forwarded-For", clientIp);
2877
+ }
2878
+ const forwardedProto = request.headers.get("cf-visitor")?.match(/"scheme":"(https?)"/)?.[1] ?? requestUrl.protocol.replace(":", "") ?? "https";
2879
+ headers.set("X-Forwarded-Proto", forwardedProto);
2880
+ const forwardedHost = request.headers.get("host") ?? requestUrl.host;
2881
+ if (forwardedHost) {
2882
+ headers.set("X-Forwarded-Host", forwardedHost);
2883
+ }
2884
+ const incomingAuth = request.headers.get("authorization")?.trim();
2885
+ const storefrontOAuth = getCookieValue(request, STOREFRONT_ECOMIQ_OAUTH_COOKIE_NAME)?.trim();
2886
+ if (incomingAuth && /^Bearer\s+\S+/i.test(incomingAuth)) {
2887
+ headers.set("Authorization", incomingAuth);
2888
+ } else if (storefrontOAuth) {
2889
+ headers.set("Authorization", `Bearer ${storefrontOAuth}`);
2890
+ }
2891
+ headers.set("Accept-Encoding", "identity");
2892
+ const init = {
2893
+ method: request.method,
2894
+ headers
2895
+ };
2896
+ const bodyMethods = ["POST", "PUT", "PATCH"];
2897
+ if (bodyMethods.includes(request.method) && request.body) {
2898
+ init.body = request.body;
2899
+ init.duplex = "half";
2477
2900
  }
2478
- return selected;
2901
+ return { url, init };
2479
2902
  }
2480
- function ProductVariantSelector({
2481
- product,
2482
- currency = "USD",
2483
- features = [],
2484
- onVariantChange,
2485
- children
2486
- }) {
2487
- const variants = useMemo(() => product.variants ?? [], [product.variants]);
2488
- const optionGroups = useMemo(() => buildOptionGroups(variants), [variants]);
2489
- const hasConfigurableOptions = optionGroups.length > 0;
2490
- const [selectedOptions, setSelectedOptions] = useState(
2491
- () => getInitialOptions(variants, optionGroups)
2492
- );
2493
- const selectedVariant = useMemo(
2494
- () => findVariant(variants, selectedOptions, hasConfigurableOptions),
2495
- [variants, selectedOptions, hasConfigurableOptions]
2903
+ function isResponseBodyForbidden(status) {
2904
+ return status === 204 || status === 205 || status === 304;
2905
+ }
2906
+ async function handleStoreProxyRequest(request, pathSegments, config = {}) {
2907
+ const storeContext = await ensureStoreContext(request);
2908
+ if (!storeContext.ok) {
2909
+ return storeContext.response;
2910
+ }
2911
+ const { storeId, storeDomain } = storeContext;
2912
+ const { url, init } = buildUpstreamRequest(
2913
+ request,
2914
+ pathSegments,
2915
+ config,
2916
+ storeId,
2917
+ storeDomain
2496
2918
  );
2497
- const displayPrice = selectedVariant?.price ?? product.price ?? 0;
2498
- const compareAtPrice = selectedVariant?.compareAtPrice ?? product.compareAtPrice;
2499
- const isAvailable = (selectedVariant ? (selectedVariant.quantity ?? 0) > 0 : product.isAvailable) ?? true;
2500
- useEffect(() => {
2501
- onVariantChange?.(selectedVariant, displayPrice);
2502
- }, [selectedVariant, displayPrice, onVariantChange]);
2503
- const setOption = useCallback((key, value) => {
2504
- setSelectedOptions((prev) => ({ ...prev, [key]: value }));
2505
- }, []);
2506
- const hasVariants = hasConfigurableOptions;
2507
- return /* @__PURE__ */ jsxs("div", { className: "space-y-5", children: [
2508
- /* @__PURE__ */ jsxs("div", { className: "flex items-baseline gap-2 flex-wrap", children: [
2509
- /* @__PURE__ */ jsx(
2510
- "span",
2511
- {
2512
- className: "inline-flex items-center rounded-md bg-primary/10 px-2.5 py-0.5 text-base font-semibold text-primary",
2513
- "data-testid": "product-display-price",
2514
- children: formatCurrency$1(displayPrice, currency)
2515
- }
2516
- ),
2517
- compareAtPrice != null && compareAtPrice > displayPrice && /* @__PURE__ */ jsx("span", { className: "text-sm text-muted-foreground line-through", children: formatCurrency$1(compareAtPrice, currency) })
2518
- ] }),
2519
- hasVariants && optionGroups.map((group) => /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
2520
- /* @__PURE__ */ jsx("span", { className: "text-xs font-semibold uppercase tracking-wider text-muted-foreground", children: group.name }),
2521
- /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-2", children: group.values.map((value) => {
2522
- const isSelected = selectedOptions[group.key] === value;
2523
- const isColorLike = group.key.toLowerCase().includes("color") || group.key.toLowerCase().includes("colour");
2524
- return /* @__PURE__ */ jsx(
2525
- "button",
2526
- {
2527
- type: "button",
2528
- onClick: () => setOption(group.key, value),
2529
- className: isColorLike ? `min-w-[72px] rounded-md border-2 px-3 py-2 text-sm font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2 ${isSelected ? "border-primary bg-primary text-foreground" : "border-border bg-muted/30 text-foreground hover:border-muted-foreground/50"}` : `rounded-md border px-3 py-2 text-sm font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2 ${isSelected ? "border-primary bg-muted ring-1 ring-primary" : "border-border bg-transparent text-muted-foreground hover:border-muted-foreground/50 hover:text-foreground"}`,
2530
- style: isColorLike && isSelected && value.toLowerCase() === "black" ? { backgroundColor: "#1a1a1a", borderColor: "#1a1a1a", color: "#fff" } : isColorLike && value.toLowerCase() === "white" ? { backgroundColor: "#fafafa", color: "#1a1a1a", borderColor: "#e5e5e5" } : void 0,
2531
- "data-testid": `variant-option-${group.key}-${value}`,
2532
- children: value
2533
- },
2534
- value
2535
- );
2536
- }) })
2537
- ] }, group.key)),
2538
- features.length > 0 && /* @__PURE__ */ jsx("ul", { className: "list-disc list-inside space-y-1 text-sm text-muted-foreground", children: features.map((f, i) => /* @__PURE__ */ jsx("li", { children: f }, i)) }),
2539
- children?.({
2540
- product,
2541
- selectedVariant,
2542
- displayPrice,
2543
- compareAtPrice,
2544
- isAvailable,
2545
- selectedOptions
2546
- })
2547
- ] });
2919
+ const upstream = await fetch(url, init);
2920
+ const responseHeaders = new Headers(upstream.headers);
2921
+ const status = upstream.status;
2922
+ if (isResponseBodyForbidden(status)) {
2923
+ responseHeaders.delete("content-length");
2924
+ responseHeaders.delete("content-type");
2925
+ return new Response(null, {
2926
+ status,
2927
+ statusText: upstream.statusText,
2928
+ headers: responseHeaders
2929
+ });
2930
+ }
2931
+ const body = await upstream.text();
2932
+ return new Response(body, {
2933
+ status,
2934
+ statusText: upstream.statusText,
2935
+ headers: responseHeaders
2936
+ });
2548
2937
  }
2549
- function isRenderableCmsPage(p) {
2550
- if (!p.status) return true;
2551
- return p.status === "published";
2938
+
2939
+ // src/react/frameworks/index.ts
2940
+ var usePublicStore = () => useStore.getState().data;
2941
+ async function getHostname() {
2942
+ const headerList = getRequestHeaders();
2943
+ applyServerRequestOriginFromHeaders(headerList);
2944
+ return normalizeRequestHostname(headerList);
2552
2945
  }
2553
- function findStorefrontCmsPageBySlug(settings, slug) {
2554
- const pages = settings?.cms?.pages;
2555
- if (!pages?.length) return null;
2556
- const raw = pages.find(
2557
- (p) => p.slug === slug && isRenderableCmsPage(p)
2558
- );
2559
- if (!raw) return null;
2560
- const parsed = StorefrontPageDetailSchema$1.safeParse(raw);
2561
- return parsed.success ? parsed.data : null;
2946
+ async function getPublicStore(options) {
2947
+ const headerList = getRequestHeaders();
2948
+ applyServerRequestOriginFromHeaders(headerList);
2949
+ return fetchPublicStoreSettings({
2950
+ host: headerList.get("host"),
2951
+ source: options?.source
2952
+ });
2953
+ }
2954
+ async function getPublicStoreFromHeaders(headerList, options) {
2955
+ applyServerRequestOriginFromHeaders(headerList);
2956
+ return fetchPublicStoreSettings({
2957
+ host: headerList.get("host"),
2958
+ ...options
2959
+ });
2562
2960
  }
2563
2961
 
2564
- export { CartAddressesRequestSchema, CartBuyerRequestSchema, CartCheckoutPaymentActionSchema, CartCheckoutPaymentSchema, CartCheckoutReadResponseSchema, CartCheckoutRequestSchema, CartCheckoutResponseSchema, CartCouponRequestSchema, CartCreateRequestSchema, CartItemModifierRequestSchema, CartItemRequestSchema, CartPaymentMethodSchema, CartPaymentMethodsResponseSchema, CartPaymentReceiptResponseSchema, CartResponseBundleItemSchema, CartResponseItemSchema, CartResponseSchema, CartShippingMethodPutRequestSchema, EcomiqProvider, ProductSchema, ProductVariantSchema, ProductVariantSelector, ProductsFiltersSchema, ProductsResponseSchema, QueryProvider, STOREFRONT_API_PATH_PREFIX, STOREFRONT_ECOMIQ_OAUTH_COOKIE_NAME, STOREFRONT_ECOMIQ_OAUTH_COOKIE_PATH, ShippingOptionGroupSchema, ShippingOptionsResponseSchema, ShippingRateSchema, StorefrontAuthProvider, StorefrontBundleItemSchema, StorefrontCheckoutSettingsSchema, StorefrontCollectionDetailSchema, StorefrontCollectionSchema, StorefrontCollectionsResponseSchema, StorefrontPageDetailSchema, StorefrontPageListItemSchema, StorefrontPriceTierSchema, StorefrontProductDetailSchema, StorefrontProductSchema, StorefrontProductsParamsSchema, StorefrontProductsResponseSchema, StorefrontSettingsLegacyFlatSchema, StorefrontSettingsSchema, StorefrontStoreSchema, apiClient, cartService, collectionService, countryCurrency, customerOrderStatusTone, ecomiqOAuthPassword, ecomiqOAuthPasswordClient, fetchStorefrontGeoDistricts, fetchStorefrontGeoProvinces, findStorefrontCmsPageBySlug, formatCurrency, formatCustomerOrderStatus, formatOrderDate, formatOrderDateTime, loadStorefrontGeoDepartments, normalizeStorefrontSettingsPayload, pageService, productService, resolveIneiCodesFromLabels, storeService, storefrontAccessTokenNeedsRefresh, storefrontFaviconUrl, storefrontOgImageUrl, storefrontSessionEndsSoon, storefrontStoreRecord, toGeoItems, useCart, useCartAddresses, useCartCreate, useCartGet, useCollections, useConfig, usePages, useProducts, useStore, useStorefrontAuthClient, useStorefrontGeoInei, useStorefrontSession, useUser };
2565
- //# sourceMappingURL=react.js.map
2566
- //# sourceMappingURL=react.js.map
2962
+ export { CustomerAddressSchema, CustomerAddressUpsertSchema, CustomerMeSchema, CustomerOrderDetailSchema, CustomerOrderListItemSchema, CustomerProfilePatchSchema, CustomerRegisterRequestSchema, ECOMIQ_CLIENTS_STORE_KV_BINDING, EcomiqProvider, ProductVariantSelector, QueryProvider, StoreApiClient, StorefrontAuthProvider, StorefrontProductSchema, StorefrontProductsResponseSchema2 as StorefrontProductsResponseSchema, StorefrontSettingsSchema3 as StorefrontSettingsSchema, addCartCoupon, applyServerRequestOriginFromHeaders, buildStorefrontKvSnapshotKey, buildStorefrontKvSnapshotKeyFromStoreKey, checkoutCart, createCart, createCustomerAddress, createStoreApiClient, customerMeToProfilePatch, deleteCustomerAddress, ecomiqOAuthPassword, ecomiqOAuthPasswordClient, fetchPublicStoreSettings, findStorefrontCmsPageBySlug, formatCurrency2 as formatCurrency, getCart, getCartCheckout, getCartPaymentMethods, getCustomerAddresses, getCustomerMe, getCustomerOrder, getCustomerOrders, getHostname, getPublicStore, getPublicStoreFromHeaders, getSharedOrderDraft, getShippingOptions, getStoreApiBaseUrl, getStoreApiClient, getStorefrontCollectionBySlug, getStorefrontCollections, getStorefrontPageBySlug, getStorefrontPagePreview, getStorefrontPages, getStorefrontProductBySlug, getStorefrontProducts, getStorefrontSettings, getStorefrontSettingsFromKv, getStorefrontSettingsFromKvByStoreKey, handleStoreProxyRequest, isCartNotActiveApiError, normalizeStoreHost, patchCartAddresses, patchCartBuyer, patchCustomer, putCartShippingMethod, registerCustomer, removeCartCoupon, resolvePublicStoreSettingsSource, resolveStorefrontSettingsHost, setClientStoreId, setServerRequestOrigin, setServerStoreId, signInWithEcomiqCredentials2 as signInWithEcomiqCredentials, storeApiClient, storefrontAccessTokenNeedsRefresh, storefrontSessionEndsSoon, updateCustomerAddress, uploadPaymentReceipt, useCart2 as useCart, usePublicStore, useStore, useStoreCart, useStoreCollections, useStorePages, useStoreProducts, useStorefrontAuthClient, useStorefrontSession };
2963
+ //# sourceMappingURL=tanstack.js.map
2964
+ //# sourceMappingURL=tanstack.js.map