@asteby/storefront-react 0.13.1 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -542,6 +542,8 @@ import '@asteby/storefront-react/checkout.css' // opcional: disposición, insign
542
542
 
543
543
  Cotizaciones largas (`layout="recommended"`): con 10+ servicios, la lista plana obliga a comparar todo. Con `layout="recommended"` se ven hasta tres tarifas (`recommendedCount`) con su etiqueta: la más económica, la más rápida y la de mejor equilibrio precio-tiempo («Recomendado»); el resto queda en «Ver más opciones (N)» (`<details>`, funciona sin JavaScript) agrupado por paquetería, con la marca y el nombre UNA vez por grupo (cada radio conserva su nombre completo para lectores de pantalla). Los servicios repetidos (misma paquetería y servicio cotizados por dos proveedores) se quedan con el más barato. Si la tarifa elegida está dentro, la lista se abre sola (también en el HTML del servidor). La lógica pura está en `recommendRates()` / `dedupeRates()`. `error` (+ `onRetry`) muestra un aviso amable con «Intentar de nuevo» cuando no se pudo cotizar.
544
544
 
545
+ Una sola lista (`layout="flat"`): como en la mayoría de los ecommerce. Muestra TODAS las tarifas en una lista plana, de menor a mayor precio, sin `<details>` ni grupos; cada fila lleva la marca de la paquetería, el nombre completo, el servicio, el tiempo y el precio. Los servicios repetidos (misma paquetería y servicio de dos proveedores) se quedan con el más barato, y las etiquetas «Más económico», «Más rápido» y «Recomendado» (esta última solo en cotizaciones de más de 4 servicios, `recommendedCount`) van sobre las filas que las ganan. La tarifa elegida siempre aparece (aunque sea un servicio repetido) y, sin `defaultValue`, se preselecciona la primera fila, la más barata. Son radios nativos con el mismo `name` (grupo de radio sin JavaScript); envuélvelos en tu `<fieldset><legend>`. Decisión de API: es un tercer valor de `layout`, no otra prop, para no tocar `list` ni `recommended`; la lógica pura está en `flatRates()`.
546
+
545
547
  Tarifa vencida o cambiada: si guardar la entrega falla con `isQuoteChange(err)` (códigos `quote_expired`/`rate_expired`…, un 409, o el 422 genérico de servidores anteriores), la tienda recarga las tarifas conservando la dirección, vuelve a elegir el mismo servicio con `matchRate(anterior, nuevas)`, pasa `requoted` a `DeliveryOptions` y resuelve `syncDelivery` con `'requote'`: `PaymentMethodSection` muestra un aviso (no un error) y vuelve a intentar con la nueva tarifa.
546
548
 
547
549
  ### Campos, validación en línea y resumen del pedido
package/dist/checkout.cjs CHANGED
@@ -843,6 +843,17 @@ function recommendRates(input, options = {}) {
843
843
  const more = [...groups.values()];
844
844
  return { recommended, more, moreCount: more.reduce((n, g) => n + g.rates.length, 0) };
845
845
  }
846
+ function flatRates(input, options = {}) {
847
+ const count = options.count ?? 3;
848
+ const rates = options.dedupe === false ? [...input] : dedupeRates(input);
849
+ const tags = /* @__PURE__ */ new Map();
850
+ for (const { rate, tags: t } of recommendRates(rates, { count, dedupe: false }).recommended) tags.set(rate, t);
851
+ const list = [...rates];
852
+ const kept = options.keepId ? input.find((r) => r.id === options.keepId) : void 0;
853
+ if (kept && !list.includes(kept)) list.push(kept);
854
+ const byCost = (a, b) => (costOf(a) ?? Infinity) - (costOf(b) ?? Infinity);
855
+ return list.sort(byCost).map((rate) => ({ rate, tags: tags.get(rate) ?? [] }));
856
+ }
846
857
 
847
858
  // src/checkout/delivery.ts
848
859
  var ES4 = {
@@ -1031,7 +1042,13 @@ function DeliveryOptions(props) {
1031
1042
  const m = react.useMemo(() => deliveryMessages(locale, props.messages), [locale, props.messages]);
1032
1043
  const hydrated = useHydrated();
1033
1044
  const badges = react.useMemo(() => showBadges ? rateBadges(rates) : /* @__PURE__ */ new Map(), [rates, showBadges]);
1034
- const firstValue = rates[0] ? rateValue(rates[0]) : null;
1045
+ const flat = react.useMemo(
1046
+ () => layout === "flat" ? flatRates(rates, { count: props.recommendedCount ?? 3, keepId: value ? rates.find((r) => rateValue(r) === value)?.id ?? null : null }) : null,
1047
+ // eslint-disable-next-line react-hooks/exhaustive-deps
1048
+ [layout, rates, props.recommendedCount, value]
1049
+ );
1050
+ const firstRate = flat ? flat[0]?.rate : rates[0];
1051
+ const firstValue = firstRate ? rateValue(firstRate) : null;
1035
1052
  const defaultValue = props.defaultValue === void 0 ? firstValue : props.defaultValue;
1036
1053
  const amount = (money) => Number(money.amount) === 0 ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: cx.free, "data-sf-delivery-free": true, children: m.free }) : renderAmount ? renderAmount(money) : storefront.formatMoney(money, locale);
1037
1054
  const mode = props.quoteNote ?? "always";
@@ -1097,7 +1114,7 @@ function DeliveryOptions(props) {
1097
1114
  ] }) : null,
1098
1115
  children,
1099
1116
  loading && rates.length === 0 ? [0, 1, 2].map((i) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: cx.skeleton, "data-sf-delivery-skeleton": true, "aria-hidden": "true" }, i)) : null,
1100
- plan ? plan.recommended.map(({ rate, tags }) => rateRow(rate, { tags: showBadges ? tags : [] })) : rates.map((rate) => rateRow(rate, { tags: badges.get(rate.id) ?? [] })),
1117
+ flat ? flat.map(({ rate, tags }) => rateRow(rate, { tags: showBadges ? tags : [] })) : plan ? plan.recommended.map(({ rate, tags }) => rateRow(rate, { tags: showBadges ? tags : [] })) : rates.map((rate) => rateRow(rate, { tags: badges.get(rate.id) ?? [] })),
1101
1118
  plan && plan.moreCount > 0 ? /* @__PURE__ */ jsxRuntime.jsxs("details", { className: cx.more, "data-sf-delivery-more": true, ...inMore ? { open: true } : {}, children: [
1102
1119
  /* @__PURE__ */ jsxRuntime.jsx("summary", { className: cx.moreSummary, "data-sf-delivery-more-summary": true, children: m.moreOptions.replace("{n}", String(plan.moreCount)) }),
1103
1120
  /* @__PURE__ */ jsxRuntime.jsx("div", { "data-sf-delivery-groups": true, children: plan.more.map((g) => {
@@ -1141,6 +1158,7 @@ exports.dedupeRates = dedupeRates;
1141
1158
  exports.deliveryMessages = deliveryMessages;
1142
1159
  exports.fieldMessages = fieldMessages;
1143
1160
  exports.findBrand = findBrand;
1161
+ exports.flatRates = flatRates;
1144
1162
  exports.isQuoteChange = isQuoteChange;
1145
1163
  exports.listBrands = listBrands;
1146
1164
  exports.matchRate = matchRate;