@ecomiq/storefront 3.0.2 → 3.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/README.md +21 -0
  2. package/dist/{account-DTahJyuQ.js → account-CZZ-ku6L.js} +4 -4
  3. package/dist/account.d.ts +9 -0
  4. package/dist/analytics/{analytics-CG1Qagwl.js → analytics-CiG_O37p.js} +4 -4
  5. package/dist/analytics/{config-C1-n2Zx-.js → config-DN5B1GOG.js} +3 -3
  6. package/dist/analytics/{consent-B13rBpBs.js → consent-D0-iQ4bg.js} +1 -1
  7. package/dist/analytics/{consent-banner-9QvRRy8v.js → consent-banner-D36fpU89.js} +1 -1
  8. package/dist/analytics/{cookies-pLozllyB.js → cookies-CWsi-zSd.js} +1 -1
  9. package/dist/analytics/{events-Dt81tIp1.js → events-CWmKY7oX.js} +2 -1
  10. package/dist/analytics/{tracker-p4yaoOyk.js → tracker-DpfDB2sY.js} +1 -1
  11. package/dist/{bank-accounts-8PuqC0GW.js → bank-accounts-MpKIxHaX.js} +1 -1
  12. package/dist/{captcha-CsBwlX74.js → captcha-CZkpghTO.js} +1 -1
  13. package/dist/{cart-actions-C58VRCAp.js → cart-actions-IKRYGqfj.js} +32 -9
  14. package/dist/{cart-form-y9P1r_EX.js → cart-form-Bq_B-Rh6.js} +6 -4
  15. package/dist/cart-form.d.ts +2 -1
  16. package/dist/{cart-summary-ur2VWKr-.js → cart-summary-B6vtRGBO.js} +6 -1
  17. package/dist/cart-summary.d.ts +2 -1
  18. package/dist/{catalog-hooks-CV6zETMZ.js → catalog-hooks-DjmpXROv.js} +1 -1
  19. package/dist/{checkout-CrDtrDrV.js → checkout-DsI1MY14.js} +6 -6
  20. package/dist/{complaint-book-CQkiQ0M-.js → complaint-book-aAQfGBvo.js} +2 -2
  21. package/dist/{culqi-authentication-Ckq_e5B7.js → culqi-authentication-DHxjPJRE.js} +1 -1
  22. package/dist/{customer-document-fields-B4RtDYe-.js → customer-document-fields-BzLdztjM.js} +1 -1
  23. package/dist/{customer-login-form-Cg1xa3UR.js → customer-login-form-k_q7_7FX.js} +2 -2
  24. package/dist/{customer-registration-form-Cy7n-Y42.js → customer-registration-form-PrBqUuI9.js} +1 -1
  25. package/dist/entries/account.js +5 -5
  26. package/dist/entries/analytics.js +4 -4
  27. package/dist/entries/cart.d.ts +2 -1
  28. package/dist/entries/cart.js +6 -5
  29. package/dist/entries/checkout.js +8 -8
  30. package/dist/entries/order.js +8 -8
  31. package/dist/{form-contracts-BNpHaTGJ.js → form-contracts-0R112spH.js} +11 -4
  32. package/dist/form-contracts.d.ts +3 -3
  33. package/dist/index.js +29 -29
  34. package/dist/{invoice-fields-BmAF1Bdv.js → invoice-fields-BzDfV1nR.js} +1 -1
  35. package/dist/{mercadopago-dialog-BzboyTG1.js → mercadopago-dialog-CpGlgKBl.js} +2 -2
  36. package/dist/{optimistic-cart-BveVOHuh.js → optimistic-cart-DcJ8-tCw.js} +1 -1
  37. package/dist/{order-display-NqS81m9p.js → order-display-CUXVESaF.js} +19 -10
  38. package/dist/{order-draft-CvtfTp4H.js → order-draft-D8sqUUNp.js} +1 -1
  39. package/dist/{order-payment-continuation-B2oNgu94.js → order-payment-continuation-CS6omD7E.js} +3 -3
  40. package/dist/{order-payment-in-review-DB6bCNsB.js → order-payment-in-review-D5Y8bjGA.js} +1 -1
  41. package/dist/{order-status-YIcJmCVH.js → order-status-QsmEO-89.js} +3 -3
  42. package/dist/{page-seo-CXj_ZxJc.js → page-seo-BKG-Ta6t.js} +3 -3
  43. package/dist/{pages-DoVOQVlP.js → pages-DyItxkdE.js} +4 -4
  44. package/dist/{payment-dialog-frame-Yqptx9Ze.js → payment-dialog-frame-xtidt_eM.js} +1 -1
  45. package/dist/personalization-BSVlktZP.js +94 -0
  46. package/dist/personalization-fields-CwBR8oGr.js +363 -0
  47. package/dist/personalization-fields.d.ts +17 -0
  48. package/dist/{preview-C3sqXLu7.js → preview-JWdMO6kX.js} +6 -4
  49. package/dist/{preview-address-BQLjWNrr.js → preview-address-bvieGEKb.js} +1 -1
  50. package/dist/{preview-cart-D1tZo7HE.js → preview-cart-COIP3Vfh.js} +3 -2
  51. package/dist/{public-pages-BH4jw2xh.js → public-pages-Byh01h-6.js} +2 -2
  52. package/dist/{registration-B99Qz9FK.js → registration-h_00PAhj.js} +3 -3
  53. package/dist/{route-text-TU3xed68.js → route-text-B6pWdevi.js} +1 -1
  54. package/dist/{section-preview-NzNw6sAL.js → section-preview-BiYfeE-d.js} +2 -2
  55. package/dist/sections/account-addresses.js +2 -2
  56. package/dist/sections/account-order.js +11 -6
  57. package/dist/sections/account-orders.js +3 -3
  58. package/dist/sections/account-profile.js +2 -2
  59. package/dist/sections/checkout.js +18 -11
  60. package/dist/sections/complaint-book.js +3 -3
  61. package/dist/sections/design-system.js +1 -1
  62. package/dist/sections/draft-invoice.js +7 -2
  63. package/dist/{sections-DyCN6unE.js → sections-CgpjubkB.js} +1 -1
  64. package/dist/{seo-CXnoXxGZ.js → seo-D9UdUMg9.js} +2 -2
  65. package/dist/{session-l0xbh3uY.js → session-DR_9-bcf.js} +6 -6
  66. package/dist/{store-CuxOWbsb.js → store-uU2gWdMY.js} +2 -2
  67. package/dist/{storefront-language-3jED0mSM.js → storefront-language-Bnt-TU2F.js} +1 -1
  68. package/dist/{storefront-text-Cdo69zlx.js → storefront-text-Co7iUnLM.js} +2 -2
  69. package/dist/{storefront-text-en-Bzm4qMCo.js → storefront-text-en-DzYmdOuY.js} +5 -1
  70. package/dist/{storefront-text-pt-BeMGHI7h.js → storefront-text-pt-CizClH0I.js} +5 -1
  71. package/dist/{stripe-dialog-C7WM3gEI.js → stripe-dialog-D88qfkUS.js} +2 -2
  72. package/dist/{templates-B4TX5AB0.js → templates-AgGupnPA.js} +2 -2
  73. package/dist/{urls-wCZ6LnmG.js → urls-D4uinJVd.js} +1 -1
  74. package/dist/{use-checkout-C7IG5Xnz.js → use-checkout-Jdk8P12A.js} +1 -1
  75. package/dist/{use-checkout-payment-t1R-GdW1.js → use-checkout-payment-BGX-BE6D.js} +1 -1
  76. package/dist/{voucher-payment-DUopOxhl.js → voucher-payment-B3o9ybg6.js} +1 -1
  77. package/dist/{worker-xF0YKH8T.js → worker-cbXmLvfR.js} +2 -2
  78. package/package.json +2 -2
  79. package/src/sections/account-order.tsx +4 -2
  80. package/src/sections/checkout.tsx +4 -1
  81. package/src/sections/draft-invoice.tsx +2 -0
package/README.md CHANGED
@@ -211,6 +211,27 @@ sea de lectura si no trae el token de la sesión: los formularios llevan
211
211
  `<CustomerCsrfField />` y los `fetch` propios, la cabecera `X-Customer-Csrf`.
212
212
  `FORM_CONTRACTS` lista los `intent` y los campos que acepta cada acción.
213
213
 
214
+ ### Personalización del producto
215
+
216
+ Si el producto tiene `modifierGroups`, pinta `PersonalizationFields` dentro del
217
+ `CartForm`. El carrito manda lo que el cliente escribió o eligió y la API cobra
218
+ los extras.
219
+
220
+ ```tsx title="app/components/add-to-cart.tsx"
221
+ import { money } from "@ecomiq/storefront";
222
+ import { CartForm, PersonalizationFields } from "@ecomiq/storefront/cart";
223
+
224
+ <CartForm intent="add" variantId={variant.id}>
225
+ <PersonalizationFields groups={product.modifierGroups} formatPrice={(value) => money(value, currency, language)} />
226
+ <Button type="submit">Agregar al carrito</Button>
227
+ </CartForm>;
228
+ ```
229
+
230
+ En cada línea del carrito, `ItemPersonalization` muestra lo elegido. Para el
231
+ total de la línea usa `lineTotal(item)` de `@ecomiq/core`. Con
232
+ `hasModifierGroups`, las tarjetas de producto llevan a la ficha en vez de
233
+ agregar al carrito.
234
+
214
235
  ## Login de clientes
215
236
 
216
237
  La tienda elige el modo de acceso en Admin, en **Configuración → Compra →
@@ -1,10 +1,10 @@
1
1
  import { optional, text } from "./form-BtQcbXlJ.js";
2
2
  import { identityDocumentError, isIdentityDocumentType } from "./documents-T1mzc_j8.js";
3
- import { ADDRESS_INTENTS, ORDER_REQUEST_INTENTS, isIntent } from "./form-contracts-BNpHaTGJ.js";
3
+ import { ADDRESS_INTENTS, ORDER_REQUEST_INTENTS, isIntent } from "./form-contracts-0R112spH.js";
4
4
  import { readPlace } from "./place-CmW4hBWY.js";
5
- import { customerHeaders, requireCustomer, requireCustomerWith } from "./session-l0xbh3uY.js";
6
- import { found, readCountries, readDocumentCountries, store } from "./store-CuxOWbsb.js";
7
- import { requestUrl } from "./urls-wCZ6LnmG.js";
5
+ import { customerHeaders, requireCustomer, requireCustomerWith } from "./session-DR_9-bcf.js";
6
+ import { found, readCountries, readDocumentCountries, store } from "./store-uU2gWdMY.js";
7
+ import { requestUrl } from "./urls-D4uinJVd.js";
8
8
  import { data } from "react-router";
9
9
  import { ApiError } from "@ecomiq/core";
10
10
  // src/account.ts
package/dist/account.d.ts CHANGED
@@ -109,6 +109,15 @@ export declare function loadOrder(request: Request, context: Readonly<RouterCont
109
109
  shippedAt?: string | null | undefined;
110
110
  deliveredAt?: string | null | undefined;
111
111
  returnableQuantity: number;
112
+ modifiers: {
113
+ modifierGroupId: string;
114
+ modifierGroupName: string;
115
+ modifierOptionId: string | null;
116
+ modifierOptionName: string | null;
117
+ value: string | null;
118
+ quantity: number;
119
+ priceDelta: number;
120
+ }[];
112
121
  }[];
113
122
  payments: {
114
123
  id: string;
@@ -1,7 +1,7 @@
1
- import { currentConsent } from "./consent-B13rBpBs.js";
2
- import { expiredPurchaseCookie, readPurchase } from "./cookies-pLozllyB.js";
3
- import { itemsValue } from "./events-Dt81tIp1.js";
4
- import { configureTracker, startTracker, track } from "./tracker-p4yaoOyk.js";
1
+ import { currentConsent } from "./consent-D0-iQ4bg.js";
2
+ import { expiredPurchaseCookie, readPurchase } from "./cookies-CWsi-zSd.js";
3
+ import { itemsValue } from "./events-CWmKY7oX.js";
4
+ import { configureTracker, startTracker, track } from "./tracker-DpfDB2sY.js";
5
5
  import { useLocation, useMatches, useRouteLoaderData } from "react-router";
6
6
  import { useEffect, useRef } from "react";
7
7
  // src/analytics/analytics.tsx
@@ -1,6 +1,6 @@
1
- import { worker } from "../worker-xF0YKH8T.js";
2
- import { readConsent } from "./cookies-pLozllyB.js";
3
- import { cartItem } from "./events-Dt81tIp1.js";
1
+ import { worker } from "../worker-cbXmLvfR.js";
2
+ import { readConsent } from "./cookies-CWsi-zSd.js";
3
+ import { cartItem } from "./events-CWmKY7oX.js";
4
4
  // src/analytics/config.ts
5
5
  const PRIVACY_PAGE = "privacidad";
6
6
  const NOTICE_VERSION = 2;
@@ -1,4 +1,4 @@
1
- import { consentCookie, cookieDomains, readConsent, revokedCookies } from "./cookies-pLozllyB.js";
1
+ import { consentCookie, cookieDomains, readConsent, revokedCookies } from "./cookies-CWsi-zSd.js";
2
2
  // src/analytics/consent.ts
3
3
  const CONSENT_EVENT = "ecomiq:consent";
4
4
  const CONSENT_OPEN_EVENT = "ecomiq:consent-open";
@@ -1,4 +1,4 @@
1
- import { consentPurposes, installConsent, onConsent, onConsentOpen, openConsent } from "./consent-B13rBpBs.js";
1
+ import { consentPurposes, installConsent, onConsent, onConsentOpen, openConsent } from "./consent-D0-iQ4bg.js";
2
2
  import { Link, useRouteLoaderData } from "react-router";
3
3
  import { useEffect, useId, useRef, useState } from "react";
4
4
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
@@ -1,4 +1,4 @@
1
- import { cartItem } from "./events-Dt81tIp1.js";
1
+ import { cartItem } from "./events-CWmKY7oX.js";
2
2
  // src/analytics/cookies.ts
3
3
  const CONSENT_COOKIE = "__Host-ecomiq-consent";
4
4
  const PURCHASE_COOKIE = "__Host-ecomiq-purchase";
@@ -1,3 +1,4 @@
1
+ import { lineUnitPrice } from "@ecomiq/core";
1
2
  // src/analytics/events.ts
2
3
  const LIST_LIMIT = 24;
3
4
  const SEARCH_LIST = {
@@ -31,7 +32,7 @@ function cartItem(item) {
31
32
  item_id: item.productId ?? item.variantId ?? item.id,
32
33
  item_name: item.title,
33
34
  item_variant: filled(item.variantLabel),
34
- price: item.unitPrice,
35
+ price: lineUnitPrice(item),
35
36
  quantity: item.quantity
36
37
  };
37
38
  }
@@ -1,4 +1,4 @@
1
- import { currentConsent, installConsent, onConsent } from "./consent-B13rBpBs.js";
1
+ import { currentConsent, installConsent, onConsent } from "./consent-D0-iQ4bg.js";
2
2
  // src/analytics/tracker.ts
3
3
  const QUEUE_LIMIT = 50;
4
4
  let config = null;
@@ -1,4 +1,4 @@
1
- import { useStorefrontText } from "./storefront-language-3jED0mSM.js";
1
+ import { useStorefrontText } from "./storefront-language-Bnt-TU2F.js";
2
2
  import { useState } from "react";
3
3
  import { jsx, jsxs } from "react/jsx-runtime";
4
4
  // src/bank-accounts.tsx
@@ -1,4 +1,4 @@
1
- import { useStorefrontText } from "./storefront-language-3jED0mSM.js";
1
+ import { useStorefrontText } from "./storefront-language-Bnt-TU2F.js";
2
2
  import { text } from "./form-BtQcbXlJ.js";
3
3
  import { useEffect, useRef, useState } from "react";
4
4
  import { jsx, jsxs } from "react/jsx-runtime";
@@ -1,9 +1,11 @@
1
1
  import { cartCookie, cartToken, readCart } from "./cart-D7DxZ2lD.js";
2
2
  import { text } from "./form-BtQcbXlJ.js";
3
- import { CART_INTENTS, isIntent } from "./form-contracts-BNpHaTGJ.js";
4
- import { store } from "./store-CuxOWbsb.js";
3
+ import { CART_INTENTS, isIntent } from "./form-contracts-0R112spH.js";
4
+ import { readPersonalization } from "./personalization-BSVlktZP.js";
5
+ import { store } from "./store-uU2gWdMY.js";
5
6
  import { data, redirect } from "react-router";
6
- import { ApiError } from "@ecomiq/core";
7
+ import { ApiError, CartItemModifierSchema } from "@ecomiq/core";
8
+ import { z } from "zod";
7
9
  // src/cart-actions.ts
8
10
  const MAX_CART_QUANTITY = 999;
9
11
  const MAX_REORDER_LINES = 20;
@@ -13,7 +15,13 @@ const ERRORS = {
13
15
  "Catalog.VariantNotFound": "Este producto ya no está disponible.",
14
16
  "Carts.QuantityOutOfRange": "Esa cantidad supera el máximo por pedido.",
15
17
  "Carts.ItemNotFound": "Ese producto ya no está en tu carrito.",
16
- "Checkout.InProgress": "Tu compra se está pagando. Termina el pago antes de cambiar el carrito."
18
+ "Checkout.InProgress": "Tu compra se está pagando. Termina el pago antes de cambiar el carrito.",
19
+ "Modifiers.ValueRequired": "Completa la personalización.",
20
+ "Modifiers.ValueTooLong": "El texto de la personalización es muy largo.",
21
+ "Modifiers.InvalidValue": "El texto de la personalización va en una sola línea.",
22
+ "Modifiers.MinSelection": "Elige las opciones que faltan.",
23
+ "Modifiers.MaxSelection": "Elegiste más opciones de las permitidas.",
24
+ "Modifiers.OptionQuantity": "Elegiste más unidades de una opción de las permitidas."
17
25
  };
18
26
  const CLOSED = /* @__PURE__ */ new Set(["Carts.NotActive", "Carts.NotFound"]);
19
27
  function failure(message, headers) {
@@ -27,21 +35,33 @@ function failure(message, headers) {
27
35
  }
28
36
  async function apiFailure(error) {
29
37
  if (error.code !== void 0 && CLOSED.has(error.code)) return failure("Tu carrito anterior ya se cerró. Vuelve a agregar el producto.", { "Set-Cookie": await cartCookie.serialize("", { maxAge: 0 }) });
30
- if (error.code?.startsWith("Modifiers.")) return failure("Revisa las opciones elegidas.");
31
- return failure((error.code === void 0 ? void 0 : ERRORS[error.code]) ?? "No pudimos actualizar el carrito. Intenta de nuevo.");
38
+ const known = error.code === void 0 ? void 0 : ERRORS[error.code];
39
+ if (known === void 0 && error.code?.startsWith("Modifiers.")) return failure("Revisa la personalización.");
40
+ return failure(known ?? "No pudimos actualizar el carrito. Intenta de nuevo.");
32
41
  }
33
42
  function quantity(form, minimum) {
34
43
  const value = Math.floor(Number(text(form, "quantity")));
35
44
  if (!Number.isFinite(value) || value < minimum) return minimum;
36
45
  return Math.min(999, value);
37
46
  }
47
+ const ReorderModifiersSchema = z.array(CartItemModifierSchema).max(50);
48
+ function reorderModifiers(value) {
49
+ if (typeof value !== "string" || value === "") return [];
50
+ try {
51
+ return ReorderModifiersSchema.safeParse(JSON.parse(value)).data ?? [];
52
+ } catch {
53
+ return [];
54
+ }
55
+ }
38
56
  function reorderLines(form) {
39
57
  const quantities = form.getAll("quantity");
58
+ const modifiers = form.getAll("modifiers");
40
59
  return form.getAll("variantId").map((value, index) => {
41
60
  const amount = Math.floor(Number(quantities[index]));
42
61
  return {
43
62
  variantId: typeof value === "string" ? value.trim() : "",
44
- quantity: Number.isFinite(amount) && amount > 0 ? Math.min(999, amount) : 1
63
+ quantity: Number.isFinite(amount) && amount > 0 ? Math.min(999, amount) : 1,
64
+ modifiers: reorderModifiers(modifiers[index])
45
65
  };
46
66
  }).filter((line) => line.variantId !== "").slice(0, MAX_REORDER_LINES);
47
67
  }
@@ -73,10 +93,12 @@ async function cartAction(request, context) {
73
93
  const variantId = text(form, "variantId") || await availableVariant(client, text(form, "product"));
74
94
  if (!variantId) return failure("Este producto no está disponible.");
75
95
  const added = intent === "quick" ? 1 : quantity(form, 1);
96
+ const modifiers = intent === "quick" ? [] : readPersonalization(form);
76
97
  cart = await client.addCartItem({
77
98
  cartToken: cart.token,
78
99
  variantId,
79
- quantity: added
100
+ quantity: added,
101
+ ...modifiers.length > 0 ? { modifiers } : {}
80
102
  });
81
103
  } else if (intent === "reorder") {
82
104
  let added = 0;
@@ -84,7 +106,8 @@ async function cartAction(request, context) {
84
106
  cart = await client.addCartItem({
85
107
  cartToken: cart.token,
86
108
  variantId: line.variantId,
87
- quantity: line.quantity
109
+ quantity: line.quantity,
110
+ ...line.modifiers.length > 0 ? { modifiers: line.modifiers } : {}
88
111
  });
89
112
  added += 1;
90
113
  } catch (error) {
@@ -1,5 +1,6 @@
1
1
  import { CustomerCsrfField, useCustomerCsrfToken } from "./customer-csrf-C1oDX-Py.js";
2
- import { CUSTOMER_CSRF_FIELD } from "./form-contracts-BNpHaTGJ.js";
2
+ import { CUSTOMER_CSRF_FIELD } from "./form-contracts-0R112spH.js";
3
+ import { personalizationEntries } from "./personalization-BSVlktZP.js";
3
4
  import { Form, useFetcher } from "react-router";
4
5
  import { jsx, jsxs } from "react/jsx-runtime";
5
6
  // src/cart-form.tsx
@@ -51,10 +52,11 @@ function CartForm({ intent, itemId, variantId, product, quantity, next, fetcher,
51
52
  function useCart() {
52
53
  const fetcher = useFetcher();
53
54
  const csrf = useCustomerCsrfToken();
54
- function submit(intent, fields) {
55
+ function submit(intent, fields, extra = []) {
55
56
  const form = new FormData();
56
57
  form.set("intent", intent);
57
58
  for (const [name, value] of Object.entries(fields)) form.set(name, value);
59
+ for (const [name, value] of extra) form.append(name, value);
58
60
  if (csrf) form.set(CUSTOMER_CSRF_FIELD, csrf);
59
61
  fetcher.submit(form, {
60
62
  method: "post",
@@ -67,10 +69,10 @@ function useCart() {
67
69
  fetcher,
68
70
  busy: fetcher.state !== "idle",
69
71
  error,
70
- add: (variantId, quantity = 1) => submit("add", {
72
+ add: (variantId, quantity = 1, modifiers = []) => submit("add", {
71
73
  variantId,
72
74
  quantity: String(Math.max(1, quantity))
73
- }),
75
+ }, personalizationEntries(modifiers)),
74
76
  update: (itemId, quantity) => submit("update", {
75
77
  itemId,
76
78
  quantity: String(Math.max(0, quantity))
@@ -1,6 +1,7 @@
1
1
  import { CartActionData } from "./cart-actions.js";
2
2
  import { CartIntent } from "./form-contracts.js";
3
3
  import { FetcherFormProps, FetcherWithComponents } from "react-router";
4
+ import { CartItemModifierInput } from "@ecomiq/core";
4
5
  import { Ref } from "react";
5
6
  export type CartFormProps = Omit<FetcherFormProps, "action" | "method" | "encType"> & {
6
7
  ref?: Ref<HTMLFormElement>;
@@ -16,7 +17,7 @@ export type CartControls = {
16
17
  fetcher: FetcherWithComponents<CartActionData>;
17
18
  busy: boolean;
18
19
  error: string | null;
19
- add: (variantId: string, quantity?: number) => void;
20
+ add: (variantId: string, quantity?: number, modifiers?: readonly CartItemModifierInput[]) => void;
20
21
  update: (itemId: string, quantity: number) => void;
21
22
  remove: (itemId: string) => void;
22
23
  };
@@ -1,3 +1,4 @@
1
+ import { lineTotal } from "@ecomiq/core";
1
2
  // src/cart-summary.ts
2
3
  const LABELS = {
3
4
  subtotal: "Subtotal",
@@ -9,7 +10,11 @@ const LABELS = {
9
10
  pending: "Por calcular"
10
11
  };
11
12
  function lineSubtotal(items) {
12
- return items.reduce((sum, item) => sum + item.unitPrice * item.quantity, 0);
13
+ return items.reduce((sum, item) => sum + lineTotal({
14
+ unitPrice: item.unitPrice,
15
+ quantity: item.quantity,
16
+ modifiers: item.modifiers ?? []
17
+ }), 0);
13
18
  }
14
19
  function groupBySeller(items, marketplace) {
15
20
  if (!marketplace) return [{
@@ -1,8 +1,9 @@
1
- import { StorefrontCart } from "@ecomiq/core";
1
+ import { ItemModifier, StorefrontCart } from "@ecomiq/core";
2
2
  export type SellerLine = {
3
3
  sellerName: string | null;
4
4
  unitPrice: number;
5
5
  quantity: number;
6
+ modifiers?: readonly Pick<ItemModifier, "priceDelta" | "quantity">[];
6
7
  };
7
8
  export type SellerGroup<T extends SellerLine> = {
8
9
  seller: string | null;
@@ -1,4 +1,4 @@
1
- import { useStorefrontText } from "./storefront-language-3jED0mSM.js";
1
+ import { useStorefrontText } from "./storefront-language-Bnt-TU2F.js";
2
2
  import { useFetcher, useLocation, useNavigation, useSearchParams, useSubmit } from "react-router";
3
3
  import { useEffect, useRef, useState } from "react";
4
4
  // src/catalog-hooks.ts
@@ -1,14 +1,14 @@
1
1
  import { addressDefaults } from "./address-defaults-BqjRhS82.js";
2
- import { trackingConsented } from "./analytics/config-C1-n2Zx-.js";
3
- import { purchaseCookie } from "./analytics/cookies-pLozllyB.js";
4
- import { checkoutAnalytics } from "./analytics/events-Dt81tIp1.js";
2
+ import { trackingConsented } from "./analytics/config-DN5B1GOG.js";
3
+ import { purchaseCookie } from "./analytics/cookies-CWsi-zSd.js";
4
+ import { checkoutAnalytics } from "./analytics/events-CWmKY7oX.js";
5
5
  import { cartCookie, cartToken } from "./cart-D7DxZ2lD.js";
6
6
  import { optional, text } from "./form-BtQcbXlJ.js";
7
7
  import { identityDocumentError, isIdentityDocumentType } from "./documents-T1mzc_j8.js";
8
- import { CHECKOUT_INTENTS, isIntent } from "./form-contracts-BNpHaTGJ.js";
8
+ import { CHECKOUT_INTENTS, isIntent } from "./form-contracts-0R112spH.js";
9
9
  import { readPlace } from "./place-CmW4hBWY.js";
10
- import { customer, customerHeaders, requireCustomerRegistration } from "./session-l0xbh3uY.js";
11
- import { readCountries, readDocumentCountries, store } from "./store-CuxOWbsb.js";
10
+ import { customer, customerHeaders, requireCustomerRegistration } from "./session-DR_9-bcf.js";
11
+ import { readCountries, readDocumentCountries, store } from "./store-uU2gWdMY.js";
12
12
  import { data, redirect, replace } from "react-router";
13
13
  import { ApiError, OrderPaymentStartSchema, StorefrontInvoiceTypeSchema } from "@ecomiq/core";
14
14
  import { z } from "zod";
@@ -1,8 +1,8 @@
1
- import { captchaResponse } from "./captcha-CsBwlX74.js";
1
+ import { captchaResponse } from "./captcha-CZkpghTO.js";
2
2
  import { COMPLAINT_DOCUMENT_TYPES, identityDocumentError } from "./documents-T1mzc_j8.js";
3
3
  import { optional, text } from "./form-BtQcbXlJ.js";
4
4
  import { rejectCrossOriginAction } from "./request-security-CRsRMgwj.js";
5
- import { found, readDocumentCountries, store } from "./store-CuxOWbsb.js";
5
+ import { found, readDocumentCountries, store } from "./store-uU2gWdMY.js";
6
6
  import { data } from "react-router";
7
7
  import { ApiError, COMPLAINT_BOOK_ATTACHMENT_TYPES, COMPLAINT_BOOK_MAX_ATTACHMENTS, COMPLAINT_BOOK_MAX_ATTACHMENT_BYTES, ComplaintBookEntryInputSchema } from "@ecomiq/core";
8
8
  import { z } from "zod";
@@ -1,4 +1,4 @@
1
- import { useStorefrontLocale, useStorefrontText } from "./storefront-language-3jED0mSM.js";
1
+ import { useStorefrontLocale, useStorefrontText } from "./storefront-language-Bnt-TU2F.js";
2
2
  import { useCustomerCsrfToken } from "./customer-csrf-C1oDX-Py.js";
3
3
  import { CulqiPaymentSourceSchema, culqiSourceKey, loadCulqiThreeDS } from "./culqi-three-ds-HP3mfrvV.js";
4
4
  import { useNavigate, useSubmit } from "react-router";
@@ -1,5 +1,5 @@
1
1
  import { IDENTITY_DOCUMENT_TYPES, identityDocumentRule, isIdentityDocumentType } from "./documents-T1mzc_j8.js";
2
- import { useStorefrontText } from "./storefront-language-3jED0mSM.js";
2
+ import { useStorefrontText } from "./storefront-language-Bnt-TU2F.js";
3
3
  import { useState } from "react";
4
4
  import { jsx, jsxs } from "react/jsx-runtime";
5
5
  // src/customer-document-fields.tsx
@@ -1,5 +1,5 @@
1
- import { useStorefrontText } from "./storefront-language-3jED0mSM.js";
2
- import { Captcha } from "./captcha-CsBwlX74.js";
1
+ import { useStorefrontText } from "./storefront-language-Bnt-TU2F.js";
2
+ import { Captcha } from "./captcha-CZkpghTO.js";
3
3
  import { Form, useNavigation } from "react-router";
4
4
  import { useEffect, useState } from "react";
5
5
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
@@ -1,4 +1,4 @@
1
- import { useStorefrontText } from "./storefront-language-3jED0mSM.js";
1
+ import { useStorefrontText } from "./storefront-language-Bnt-TU2F.js";
2
2
  import { CustomerCsrfField } from "./customer-csrf-C1oDX-Py.js";
3
3
  import { Form, useNavigation } from "react-router";
4
4
  import { jsx, jsxs } from "react/jsx-runtime";
@@ -1,10 +1,10 @@
1
- import { addressAction, loadAccount, loadAddresses, loadOrder, loadOrders, loadProfile, orderFiltersQuery, orderRequestAction, profileAction, readOrderFilters } from "../account-DTahJyuQ.js";
1
+ import { addressAction, loadAccount, loadAddresses, loadOrder, loadOrders, loadProfile, orderFiltersQuery, orderRequestAction, profileAction, readOrderFilters } from "../account-CZZ-ku6L.js";
2
2
  import { mainPayment, orderStepLabel, orderStepOf, paymentLabel, paymentStatusLabel } from "../order-view-9IlBg5AF.js";
3
3
  import { useOrderPages } from "../order-pages-Dm6l5m0m.js";
4
- import { PayButton, PaymentPreview, ReorderButton, useOrderHeadline, useOrderStepDetail } from "../order-display-NqS81m9p.js";
4
+ import { PayButton, PaymentPreview, ReorderButton, useOrderHeadline, useOrderStepDetail } from "../order-display-CUXVESaF.js";
5
5
  import { AddressForm, ProfileForm } from "../account-forms-AVk71ko7.js";
6
6
  import { addressDefaults } from "../address-defaults-BqjRhS82.js";
7
- import { placeValue, useGeoCascade } from "../catalog-hooks-CV6zETMZ.js";
8
- import { orderSummaryLines } from "../cart-summary-ur2VWKr-.js";
9
- import { CustomerDocumentFields } from "../customer-document-fields-B4RtDYe-.js";
7
+ import { placeValue, useGeoCascade } from "../catalog-hooks-DjmpXROv.js";
8
+ import { orderSummaryLines } from "../cart-summary-B6vtRGBO.js";
9
+ import { CustomerDocumentFields } from "../customer-document-fields-BzLdztjM.js";
10
10
  export { AddressForm, CustomerDocumentFields, PayButton, PaymentPreview, ProfileForm, ReorderButton, addressAction, addressDefaults, loadAccount, loadAddresses, loadOrder, loadOrders, loadProfile, mainPayment, orderFiltersQuery, orderRequestAction, orderStepLabel, orderStepOf, orderSummaryLines, paymentLabel, paymentStatusLabel, placeValue, profileAction, readOrderFilters, useGeoCascade, useOrderHeadline, useOrderPages, useOrderStepDetail };
@@ -1,5 +1,5 @@
1
- import { Analytics } from "../analytics/analytics-CG1Qagwl.js";
2
- import { readAnalytics } from "../analytics/config-C1-n2Zx-.js";
3
- import { ConsentBanner, ConsentLink } from "../analytics/consent-banner-9QvRRy8v.js";
4
- import { catalogAnalytics } from "../analytics/events-Dt81tIp1.js";
1
+ import { Analytics } from "../analytics/analytics-CiG_O37p.js";
2
+ import { readAnalytics } from "../analytics/config-DN5B1GOG.js";
3
+ import { ConsentBanner, ConsentLink } from "../analytics/consent-banner-D36fpU89.js";
4
+ import { catalogAnalytics } from "../analytics/events-CWmKY7oX.js";
5
5
  export { Analytics, ConsentBanner, ConsentLink, catalogAnalytics, readAnalytics };
@@ -4,5 +4,6 @@ import { CartControls, CartForm, CartFormProps, useCart } from "../cart-form.js"
4
4
  import { CartLineLike, OptimisticCart, OptimisticCartLine, useOptimisticCart } from "../optimistic-cart.js";
5
5
  import { CartDrawer, CartDrawerProvider, useCartDrawer } from "../cart-drawer.js";
6
6
  import { SellerGroup, SellerLine, SummaryCart, SummaryLabels, SummaryLine, SummaryLineKey, groupBySeller, summaryLines } from "../cart-summary.js";
7
+ import { ItemPersonalization, PersonalizationFields, PersonalizationFieldsProps } from "../personalization-fields.js";
7
8
  import { CartIntent } from "../form-contracts.js";
8
- export { type CartActionData, type CartControls, type CartData, type CartDrawer, CartDrawerProvider, CartForm, type CartFormProps, type CartIntent, type CartLineLike, type OptimisticCart, type OptimisticCartLine, type SellerGroup, type SellerLine, type SummaryCart, type SummaryLabels, type SummaryLine, type SummaryLineKey, cartAction, groupBySeller, loadCart, readCart, summaryLines, useCart, useCartDrawer, useOptimisticCart };
9
+ export { type CartActionData, type CartControls, type CartData, type CartDrawer, CartDrawerProvider, CartForm, type CartFormProps, type CartIntent, type CartLineLike, ItemPersonalization, type OptimisticCart, type OptimisticCartLine, PersonalizationFields, type PersonalizationFieldsProps, type SellerGroup, type SellerLine, type SummaryCart, type SummaryLabels, type SummaryLine, type SummaryLineKey, cartAction, groupBySeller, loadCart, readCart, summaryLines, useCart, useCartDrawer, useOptimisticCart };
@@ -1,7 +1,8 @@
1
1
  import { readCart } from "../cart-D7DxZ2lD.js";
2
- import { cartAction, loadCart } from "../cart-actions-C58VRCAp.js";
3
- import { CartForm, useCart } from "../cart-form-y9P1r_EX.js";
4
- import { useOptimisticCart } from "../optimistic-cart-BveVOHuh.js";
2
+ import { cartAction, loadCart } from "../cart-actions-IKRYGqfj.js";
3
+ import { CartForm, useCart } from "../cart-form-Bq_B-Rh6.js";
4
+ import { useOptimisticCart } from "../optimistic-cart-DcJ8-tCw.js";
5
5
  import { CartDrawerProvider, useCartDrawer } from "../cart-drawer-B7PPsR69.js";
6
- import { groupBySeller, summaryLines } from "../cart-summary-ur2VWKr-.js";
7
- export { CartDrawerProvider, CartForm, cartAction, groupBySeller, loadCart, readCart, summaryLines, useCart, useCartDrawer, useOptimisticCart };
6
+ import { groupBySeller, summaryLines } from "../cart-summary-B6vtRGBO.js";
7
+ import { ItemPersonalization, PersonalizationFields } from "../personalization-fields-CwBR8oGr.js";
8
+ export { CartDrawerProvider, CartForm, ItemPersonalization, PersonalizationFields, cartAction, groupBySeller, loadCart, readCart, summaryLines, useCart, useCartDrawer, useOptimisticCart };
@@ -1,11 +1,11 @@
1
- import { checkoutAction, loadCheckout, shouldRevalidateCheckout } from "../checkout-CrDtrDrV.js";
2
- import { useCheckout } from "../use-checkout-C7IG5Xnz.js";
3
- import { useCheckoutPayment } from "../use-checkout-payment-t1R-GdW1.js";
4
- import { MercadoPagoDialog } from "../mercadopago-dialog-BzboyTG1.js";
5
- import { StripeAuthentication, StripeDialog, findStripeWallet, useAvailableStripeWallets } from "../stripe-dialog-C7WM3gEI.js";
1
+ import { checkoutAction, loadCheckout, shouldRevalidateCheckout } from "../checkout-DsI1MY14.js";
2
+ import { useCheckout } from "../use-checkout-Jdk8P12A.js";
3
+ import { useCheckoutPayment } from "../use-checkout-payment-BGX-BE6D.js";
4
+ import { MercadoPagoDialog } from "../mercadopago-dialog-CpGlgKBl.js";
5
+ import { StripeAuthentication, StripeDialog, findStripeWallet, useAvailableStripeWallets } from "../stripe-dialog-D88qfkUS.js";
6
6
  import { CheckoutForm, OrderBumpForm, PromoCodeForm } from "../checkout-form-CbOIdENC.js";
7
- import { InvoiceFields } from "../invoice-fields-BmAF1Bdv.js";
7
+ import { InvoiceFields } from "../invoice-fields-BzDfV1nR.js";
8
8
  import { PaymentMethods } from "../payment-methods-B8ocTgFZ.js";
9
- import { summaryLines } from "../cart-summary-ur2VWKr-.js";
10
- import { CulqiAuthentication } from "../culqi-authentication-Ckq_e5B7.js";
9
+ import { summaryLines } from "../cart-summary-B6vtRGBO.js";
10
+ import { CulqiAuthentication } from "../culqi-authentication-DHxjPJRE.js";
11
11
  export { CheckoutForm, CulqiAuthentication, InvoiceFields, MercadoPagoDialog, OrderBumpForm, PaymentMethods, PromoCodeForm, StripeAuthentication, StripeDialog, checkoutAction, findStripeWallet, loadCheckout, shouldRevalidateCheckout, summaryLines, useAvailableStripeWallets, useCheckout, useCheckoutPayment };
@@ -1,11 +1,11 @@
1
- import { hasOrderPaymentContinuation, loadOrderStatus, orderPaymentAction, orderProgress, orderStepTitle } from "../order-status-YIcJmCVH.js";
2
- import { loadOrderDraft, orderDraftAction } from "../order-draft-CvtfTp4H.js";
1
+ import { hasOrderPaymentContinuation, loadOrderStatus, orderPaymentAction, orderProgress, orderStepTitle } from "../order-status-QsmEO-89.js";
2
+ import { loadOrderDraft, orderDraftAction } from "../order-draft-D8sqUUNp.js";
3
3
  import { useOrderPayment } from "../use-order-payment-BKIz_IO0.js";
4
- import { OrderPaymentContinuation } from "../order-payment-continuation-B2oNgu94.js";
5
- import { OrderPaymentInReview } from "../order-payment-in-review-DB6bCNsB.js";
6
- import { BankAccountList } from "../bank-accounts-8PuqC0GW.js";
4
+ import { OrderPaymentContinuation } from "../order-payment-continuation-CS6omD7E.js";
5
+ import { OrderPaymentInReview } from "../order-payment-in-review-D5Y8bjGA.js";
6
+ import { BankAccountList } from "../bank-accounts-MpKIxHaX.js";
7
7
  import { PaymentMethods } from "../payment-methods-B8ocTgFZ.js";
8
- import { orderSummaryLines } from "../cart-summary-ur2VWKr-.js";
9
- import { MercadoPagoDialog } from "../mercadopago-dialog-BzboyTG1.js";
10
- import { useOrderAuthentication } from "../culqi-authentication-Ckq_e5B7.js";
8
+ import { orderSummaryLines } from "../cart-summary-B6vtRGBO.js";
9
+ import { MercadoPagoDialog } from "../mercadopago-dialog-CpGlgKBl.js";
10
+ import { useOrderAuthentication } from "../culqi-authentication-DHxjPJRE.js";
11
11
  export { BankAccountList, MercadoPagoDialog, OrderPaymentContinuation, OrderPaymentInReview, PaymentMethods, hasOrderPaymentContinuation, loadOrderDraft, loadOrderStatus, orderDraftAction, orderPaymentAction, orderProgress, orderStepTitle, orderSummaryLines, useOrderAuthentication, useOrderPayment };
@@ -1,17 +1,20 @@
1
1
  // src/form-contracts.ts
2
2
  const CUSTOMER_CSRF_FIELD = "__customer_csrf";
3
+ const MODIFIER_FIELD_PREFIX = "modifier.";
3
4
  const CART_INTENTS = {
4
5
  add: [
5
6
  "variantId",
6
7
  "product",
7
8
  "quantity",
8
- "next"
9
+ "next",
10
+ `${MODIFIER_FIELD_PREFIX}*`
9
11
  ],
10
12
  buy: [
11
13
  "variantId",
12
14
  "product",
13
15
  "quantity",
14
- "next"
16
+ "next",
17
+ `${MODIFIER_FIELD_PREFIX}*`
15
18
  ],
16
19
  quick: [
17
20
  "variantId",
@@ -20,7 +23,11 @@ const CART_INTENTS = {
20
23
  ],
21
24
  update: ["itemId", "quantity"],
22
25
  remove: ["itemId"],
23
- reorder: ["variantId", "quantity"]
26
+ reorder: [
27
+ "variantId",
28
+ "quantity",
29
+ "modifiers"
30
+ ]
24
31
  };
25
32
  const SHIPPING_RATE_FIELD_PREFIX = "rate-";
26
33
  const CHECKOUT_INTENTS = {
@@ -163,4 +170,4 @@ const FORM_CONTRACTS = [
163
170
  function isIntent(intents, value) {
164
171
  return Object.hasOwn(intents, value);
165
172
  }
166
- export { ADDRESS_INTENTS, CART_INTENTS, CHECKOUT_INTENTS, CUSTOMER_CSRF_FIELD, FORM_CONTRACTS, ORDER_PAYMENT_FIELDS, ORDER_REQUEST_INTENTS, PROFILE_FIELDS, SHIPPING_RATE_FIELD_PREFIX, isIntent };
173
+ export { ADDRESS_INTENTS, CART_INTENTS, CHECKOUT_INTENTS, CUSTOMER_CSRF_FIELD, FORM_CONTRACTS, MODIFIER_FIELD_PREFIX, ORDER_PAYMENT_FIELDS, ORDER_REQUEST_INTENTS, PROFILE_FIELDS, SHIPPING_RATE_FIELD_PREFIX, isIntent };
@@ -8,12 +8,12 @@ export type FormContract = {
8
8
  };
9
9
  export declare const CUSTOMER_CSRF_FIELD = "__customer_csrf";
10
10
  export declare const CART_INTENTS: {
11
- readonly add: readonly ["variantId", "product", "quantity", "next"];
12
- readonly buy: readonly ["variantId", "product", "quantity", "next"];
11
+ readonly add: readonly ["variantId", "product", "quantity", "next", "modifier.*"];
12
+ readonly buy: readonly ["variantId", "product", "quantity", "next", "modifier.*"];
13
13
  readonly quick: readonly ["variantId", "product", "next"];
14
14
  readonly update: readonly ["itemId", "quantity"];
15
15
  readonly remove: readonly ["itemId"];
16
- readonly reorder: readonly ["variantId", "quantity"];
16
+ readonly reorder: readonly ["variantId", "quantity", "modifiers"];
17
17
  };
18
18
  export declare const CHECKOUT_INTENTS: {
19
19
  readonly contact: readonly ["email", "firstName", "lastName", "phone", "identificationType", "identificationNumber", "identificationCountryCode", "invoiceType", "entityTaxId", "entityName"];