@asteby/storefront 0.12.0 → 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/dist/mock.cjs CHANGED
@@ -1,5 +1,154 @@
1
1
  'use strict';
2
2
 
3
+ // src/availability.ts
4
+ function resolveAvailability(source, options = {}) {
5
+ const a = source.availability;
6
+ if (a && typeof a === "object" && typeof a.status === "string") return a;
7
+ const qty = typeof source.quantity_available === "number" ? source.quantity_available : null;
8
+ const sellable = source.available_for_sale && (qty === null || qty > 0);
9
+ const threshold = options.lowStockThreshold;
10
+ const low = sellable && qty !== null && typeof threshold === "number" && threshold > 0 && qty <= threshold;
11
+ const status = !sellable ? "out_of_stock" : low ? "low_stock" : "in_stock";
12
+ return {
13
+ status,
14
+ quantity: sellable ? qty : null,
15
+ max_per_order: sellable ? qty : null,
16
+ restock_eta: null,
17
+ preorder_release_at: null,
18
+ // Old servers have no subscription route: never offer a form that cannot work.
19
+ notify_available: false
20
+ };
21
+ }
22
+
23
+ // src/payments.ts
24
+ function safeHttpsUrl(value) {
25
+ if (typeof value !== "string" || value.length > 2048) return null;
26
+ try {
27
+ const u = new URL(value);
28
+ if (u.protocol !== "https:" || u.username || u.password) return null;
29
+ return u.toString();
30
+ } catch {
31
+ return null;
32
+ }
33
+ }
34
+
35
+ // src/order-progress.ts
36
+ var SHIPPING_STEPS = ["paid", "preparing", "shipped", "in_transit", "delivered"];
37
+ var PICKUP_STEPS = ["paid", "preparing", "ready_for_pickup", "delivered"];
38
+ var SHIPMENT_PROBLEMS = { exception: "delivery_exception", returned: "returned", lost: "lost" };
39
+ var RANK = { pending: 0, label_created: 1, in_transit: 2, out_for_delivery: 3, delivered: 4 };
40
+ var isRecord = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
41
+ function isTestShipment(s) {
42
+ return !!s && (s.test_mode === true || s.environment === "sandbox");
43
+ }
44
+ function isTestInvoice(d) {
45
+ return !!d && (d.test_mode === true || d.environment === "sandbox");
46
+ }
47
+ function safeTrackingUrl(value) {
48
+ return safeHttpsUrl(value);
49
+ }
50
+ function time(iso) {
51
+ const t = iso ? Date.parse(iso) : NaN;
52
+ return Number.isNaN(t) ? -Infinity : t;
53
+ }
54
+ function sortTrackingEvents(events, limit = 50) {
55
+ if (!Array.isArray(events)) return [];
56
+ return events.filter(isRecord).map((e, i) => ({ e, i })).sort((a, b) => time(b.e.occurred_at) - time(a.e.occurred_at) || a.i - b.i).slice(0, Math.max(0, limit)).map((x) => x.e);
57
+ }
58
+ function orderShipments(order) {
59
+ const list = Array.isArray(order?.shipments) ? order.shipments.filter(isRecord) : [];
60
+ return [...list.filter((s) => s.kind !== "return"), ...list.filter((s) => s.kind === "return")];
61
+ }
62
+ function validProgress(p) {
63
+ return isRecord(p) && typeof p.stage === "string" && Array.isArray(p.steps);
64
+ }
65
+ var validStep = (s) => isRecord(s) && typeof s.key === "string" && typeof s.state === "string";
66
+ function cleanProgress(p) {
67
+ if (p.steps.every(validStep) && (p.problem === void 0 || p.problem === null || isRecord(p.problem) && typeof p.problem.code === "string")) return p;
68
+ const problem = isRecord(p.problem) && typeof p.problem.code === "string" ? p.problem : null;
69
+ return { stage: p.stage, problem, steps: p.steps.filter(validStep) };
70
+ }
71
+ var liveShipments = (shipments) => shipments.filter((s) => s.kind !== "return" && !isTestShipment(s));
72
+ function legacyStage(order, shipments) {
73
+ const status = typeof order.status === "string" ? order.status.toLowerCase() : "";
74
+ const pickup = order.delivery_method === "pickup";
75
+ if (status === "cancelled" || status === "canceled") return "cancelled";
76
+ if (status === "refunded") return "refunded";
77
+ if (status === "pending_payment" || order.payment_status === "pending") return "pending_payment";
78
+ if (status === "delivered" || status === "fulfilled") return "delivered";
79
+ const live = liveShipments(shipments);
80
+ const ranked = live.filter((s) => RANK[s.status] !== void 0);
81
+ const least = ranked.reduce((acc, s) => acc === null || RANK[s.status] < RANK[acc] ? s.status : acc, null);
82
+ if (least === "delivered") return "delivered";
83
+ if (least === "out_for_delivery") return "out_for_delivery";
84
+ if (least === "in_transit") return "in_transit";
85
+ if (status === "ready_for_pickup" || status === "ready" && pickup) return "ready_for_pickup";
86
+ const onlySandbox = shipments.some((s) => s.kind !== "return") && live.length === 0;
87
+ if (status === "shipped" && !onlySandbox || least === "label_created") return "shipped";
88
+ if (status === "fulfilling" || status === "accepted" || status === "ready" || status === "shipped") return "preparing";
89
+ return "paid";
90
+ }
91
+ var STAGE_TO_STEP = {
92
+ paid: "paid",
93
+ preparing: "preparing",
94
+ ready_for_pickup: "ready_for_pickup",
95
+ shipped: "shipped",
96
+ in_transit: "in_transit",
97
+ out_for_delivery: "in_transit",
98
+ delivered: "delivered"
99
+ };
100
+ function paidAt(order) {
101
+ const paid = Array.isArray(order.payments) ? order.payments.find((p) => isRecord(p) && typeof p.paid_at === "string" && p.paid_at) : void 0;
102
+ return paid?.paid_at ?? null;
103
+ }
104
+ function orderProgress(order) {
105
+ if (validProgress(order.progress)) return cleanProgress(order.progress);
106
+ const shipments = orderShipments(order);
107
+ const stage = legacyStage(order, shipments);
108
+ const keys = order.delivery_method === "pickup" ? PICKUP_STEPS : SHIPPING_STEPS;
109
+ const dead = stage === "cancelled" || stage === "refunded";
110
+ const awaiting = stage === "pending_payment";
111
+ const current = STAGE_TO_STEP[stage] ?? "paid";
112
+ const at = keys.indexOf(current);
113
+ const failed = liveShipments(shipments).find((s) => SHIPMENT_PROBLEMS[s.status]);
114
+ const problem = failed ? { code: SHIPMENT_PROBLEMS[failed.status], at: sortTrackingEvents(failed.events)[0]?.occurred_at ?? null } : order.payment_status === "failed" ? { code: "payment_failed" } : null;
115
+ const steps = dead ? [] : keys.map((key, i) => {
116
+ if (awaiting) return { key, state: "pending", at: null };
117
+ const state = i < at || stage === "delivered" ? "done" : i === at ? problem && i >= keys.indexOf("shipped") ? "problem" : "current" : "pending";
118
+ return { key, state, at: key === "paid" && state === "done" ? paidAt(order) : null };
119
+ });
120
+ return { stage, problem, steps };
121
+ }
122
+ function nextAction(order) {
123
+ if (order.next_action === null) return { kind: "none", href: null, ref: null };
124
+ if (isRecord(order.next_action) && typeof order.next_action.kind === "string") {
125
+ const href = safeTrackingUrl(order.next_action.href);
126
+ return { kind: order.next_action.kind, href, ref: typeof order.next_action.ref === "string" ? order.next_action.ref : null };
127
+ }
128
+ const progress = orderProgress(order);
129
+ if (progress.stage === "pending_payment") return { kind: "pay", href: null, ref: null };
130
+ if (progress.stage === "cancelled" || progress.stage === "refunded") return { kind: "none", href: null, ref: null };
131
+ const live = orderShipments(order).find((s) => s.kind !== "return" && !isTestShipment(s) && s.status !== "delivered" && s.status !== "cancelled");
132
+ const trackUrl = safeTrackingUrl(live?.tracking_url);
133
+ if (live && trackUrl) return { kind: "track", href: trackUrl, ref: live.id };
134
+ if (progress.stage === "ready_for_pickup") return { kind: "pickup", href: null, ref: null };
135
+ const inv = isRecord(order.invoice) ? order.invoice : void 0;
136
+ if (inv?.action === "download" || inv?.action === void 0 && inv?.stage === "issued") {
137
+ const docs = Array.isArray(order.invoices) ? order.invoices.filter((d) => isRecord(d) && d.kind === "invoice" && d.status !== "cancelled") : [];
138
+ const real = docs.find((d) => !isTestInvoice(d));
139
+ if (docs.length === 0 || real) return { kind: "download_invoice", href: null, ref: real?.id ?? null };
140
+ }
141
+ if (inv?.action === "correct_data") return { kind: "correct_invoice_data", href: null, ref: null };
142
+ if (inv?.action === "contact_store") return { kind: "contact_store", href: null, ref: null };
143
+ if (inv?.can_request === true && inv.stage !== "requested") return { kind: "request_invoice", href: null, ref: null };
144
+ return { kind: "none", href: null, ref: null };
145
+ }
146
+ function shipmentsStatus(order) {
147
+ if (order.shipments_summary && typeof order.shipments_summary.status === "string") return order.shipments_summary.status;
148
+ const live = orderShipments(order).filter((s) => s.kind !== "return" && !isTestShipment(s));
149
+ return live.length ? live[0].status : null;
150
+ }
151
+
3
152
  // src/geo.ts
4
153
  var RAD = Math.PI / 180;
5
154
  function distanceKm(a, b) {
@@ -24,24 +173,208 @@ function rankByDistance(items, origin, { radiusKm, limit, getPoint = (i) => i }
24
173
  return limit === void 0 ? out : out.slice(0, Math.max(0, limit));
25
174
  }
26
175
 
27
- // src/availability.ts
28
- function resolveAvailability(source, options = {}) {
29
- const a = source.availability;
30
- if (a && typeof a === "object" && typeof a.status === "string") return a;
31
- const qty = typeof source.quantity_available === "number" ? source.quantity_available : null;
32
- const sellable = source.available_for_sale && (qty === null || qty > 0);
33
- const threshold = options.lowStockThreshold;
34
- const low = sellable && qty !== null && typeof threshold === "number" && threshold > 0 && qty <= threshold;
35
- const status = !sellable ? "out_of_stock" : low ? "low_stock" : "in_stock";
36
- return {
37
- status,
38
- quantity: sellable ? qty : null,
39
- max_per_order: sellable ? qty : null,
40
- restock_eta: null,
41
- preorder_release_at: null,
42
- // Old servers have no subscription route: never offer a form that cannot work.
43
- notify_available: false
176
+ // src/mock/account-seed.ts
177
+ var money = (cents, currency) => ({ amount: (cents / 100).toFixed(2), currency_code: currency });
178
+ var line = (title, variant, quantity, unitCents, currency) => ({
179
+ title,
180
+ variant_title: variant,
181
+ sku: null,
182
+ quantity,
183
+ unit: money(unitCents, currency),
184
+ total: money(unitCents * quantity, currency),
185
+ image: null
186
+ });
187
+ var address = { first_name: "Ana", last_name: "Prueba", city: "Quer\xE9taro", province: "Qro.", zip: "76000", country_code: "MX" };
188
+ function buildAccountOrders(currency) {
189
+ const inTransit = {
190
+ id: "shp_seed_1",
191
+ number: "ENV-00001",
192
+ kind: "outbound",
193
+ status: "in_transit",
194
+ environment: "production",
195
+ test_mode: false,
196
+ carrier: "Estafeta",
197
+ carrier_logo_key: "estafeta",
198
+ service: "Terrestre",
199
+ tracking_number: "8012345678",
200
+ tracking_url: "https://rastreo.example.com/estafeta/8012345678",
201
+ shipped_at: "2026-08-12T15:00:00.000Z",
202
+ delivered_at: null,
203
+ promised_by: "2026-08-16",
204
+ label_url: null,
205
+ events: [
206
+ { id: "evt_3", status: "in_transit", description: "En tr\xE1nsito hacia tu ciudad", location: "Quer\xE9taro, Qro.", occurred_at: "2026-08-14T09:30:00.000Z", source: "carrier" },
207
+ { id: "evt_2", status: "in_transit", description: "Sali\xF3 del centro de distribuci\xF3n", location: "Ciudad de M\xE9xico, CDMX", occurred_at: "2026-08-13T07:10:00.000Z", source: "carrier" },
208
+ { id: "evt_1", status: "label_created", description: "Gu\xEDa generada", location: null, occurred_at: "2026-08-12T15:00:00.000Z", source: "store" }
209
+ ],
210
+ map: {
211
+ origin: { label: "Ciudad de M\xE9xico, CDMX", lat: 19.43, lng: -99.13, precision: "city" },
212
+ destination: { label: "Quer\xE9taro, Qro.", lat: 20.6, lng: -100.4, precision: "city" },
213
+ path: [{ label: "Quer\xE9taro, Qro.", lat: 20.59, lng: -100.39, precision: "city" }]
214
+ }
44
215
  };
216
+ const invoice = {
217
+ id: "inv_seed_1",
218
+ status: "issued",
219
+ kind: "invoice",
220
+ stage: "issued",
221
+ action: "download",
222
+ can_request: false,
223
+ folio: "8F3C1A52-6E0B-4B58-9C0D-2B7D6B4D7A11",
224
+ series: "A",
225
+ folio_number: "1042",
226
+ total: money(129900, currency),
227
+ issued_at: "2026-08-13T10:00:00.000Z",
228
+ test_mode: false,
229
+ environment: "production"
230
+ };
231
+ const creditNote = {
232
+ id: "inv_seed_1_nc",
233
+ status: "issued",
234
+ kind: "credit_note",
235
+ stage: "issued",
236
+ action: "download",
237
+ can_request: false,
238
+ folio: "1D9B7E20-4A5C-4C4B-8F11-6A0E3C9D5B02",
239
+ series: "NC",
240
+ folio_number: "7",
241
+ total: money(2e4, currency),
242
+ issued_at: "2026-08-15T10:00:00.000Z",
243
+ related_to: "inv_seed_1",
244
+ test_mode: false,
245
+ environment: "production"
246
+ };
247
+ return [
248
+ {
249
+ id: "ord_seed_shipped",
250
+ number: "0999",
251
+ status: "shipped",
252
+ created_at: "2026-08-12T12:00:00.000Z",
253
+ total: money(129900, currency),
254
+ customer_id: "cus_1",
255
+ lines: [line("Kit de frenos", "Delantero", 1, 109900, currency), line("L\xEDquido de frenos", "500 ml", 1, 2e4, currency)],
256
+ delivery_method: "shipping",
257
+ payment_status: "paid",
258
+ subtotal: money(111983, currency),
259
+ tax: money(17917, currency),
260
+ shipping: money(0, currency),
261
+ discount: money(0, currency),
262
+ tax_included: true,
263
+ shipping_method: "Est\xE1ndar",
264
+ shipping_address: address,
265
+ returns: [],
266
+ shipments: [inTransit],
267
+ invoices: [invoice, creditNote],
268
+ payments: [{ method_label: "Tarjeta Visa terminada en 4242", family: "card", status: "partially_refunded", amount: money(129900, currency), paid_at: "2026-08-12T12:01:00.000Z" }],
269
+ refunds: [{ id: "rfd_seed_1", amount: money(2e4, currency), status: "completed", method_label: "Tarjeta Visa terminada en 4242", created_at: "2026-08-15T09:00:00.000Z", completed_at: "2026-08-15T10:00:00.000Z", return_id: null }]
270
+ },
271
+ {
272
+ id: "ord_seed_preparing",
273
+ number: "0998",
274
+ status: "fulfilling",
275
+ created_at: "2026-08-11T12:00:00.000Z",
276
+ total: money(54900, currency),
277
+ customer_id: "cus_1",
278
+ lines: [line("Filtro de aceite", null, 3, 18300, currency)],
279
+ delivery_method: "shipping",
280
+ payment_status: "paid",
281
+ shipping_address: address,
282
+ shipments: [],
283
+ invoices: [],
284
+ payments: [{ method_label: "OXXO", family: "cash_voucher", status: "paid", amount: money(54900, currency), paid_at: "2026-08-11T18:00:00.000Z" }],
285
+ refunds: []
286
+ },
287
+ {
288
+ id: "ord_seed_sandbox",
289
+ number: "0997",
290
+ status: "shipped",
291
+ created_at: "2026-08-10T12:00:00.000Z",
292
+ total: money(31e3, currency),
293
+ customer_id: "cus_1",
294
+ lines: [line("Buj\xEDa iridio", null, 4, 7750, currency)],
295
+ delivery_method: "shipping",
296
+ payment_status: "paid",
297
+ shipments: [
298
+ {
299
+ id: "shp_seed_test",
300
+ kind: "outbound",
301
+ status: "label_created",
302
+ environment: "sandbox",
303
+ test_mode: true,
304
+ carrier: "Estafeta",
305
+ carrier_logo_key: "estafeta",
306
+ tracking_number: null,
307
+ tracking_url: null,
308
+ events: [],
309
+ map: null
310
+ }
311
+ ],
312
+ invoices: [
313
+ { id: "inv_seed_test", status: "issued", kind: "invoice", stage: "issued", action: "download", can_request: false, folio: "00000000-0000-4000-8000-000000000000", test_mode: true, environment: "sandbox" }
314
+ ],
315
+ payments: [],
316
+ refunds: []
317
+ },
318
+ {
319
+ id: "ord_seed_pickup",
320
+ number: "0996",
321
+ status: "ready_for_pickup",
322
+ created_at: "2026-08-09T12:00:00.000Z",
323
+ total: money(88e3, currency),
324
+ customer_id: "cus_1",
325
+ lines: [line("Bater\xEDa 12V", null, 1, 88e3, currency)],
326
+ delivery_method: "pickup",
327
+ pickup: { location_id: null, name: "Sucursal Centro", address: "Av. Reforma 100", city: "Ciudad de M\xE9xico", phone: null, ready_at: "2026-08-10T10:00:00.000Z", delivered_at: null },
328
+ payment_status: "paid",
329
+ shipments: [],
330
+ invoices: [],
331
+ payments: [],
332
+ refunds: [],
333
+ degraded: ["invoices"]
334
+ }
335
+ ];
336
+ }
337
+ function buildPendingPayments(currency, nowMs) {
338
+ return [
339
+ {
340
+ id: "chk_seed_oxxo",
341
+ number: null,
342
+ status: "pending_payment",
343
+ created_at: new Date(nowMs - 36e5).toISOString(),
344
+ total: money(45e3, currency),
345
+ expires_at: new Date(nowMs + 47 * 36e5).toISOString(),
346
+ payment: {
347
+ provider: "mercadopago",
348
+ method: "oxxo",
349
+ reference: "12345678901234",
350
+ barcode: "12345678901234",
351
+ clabe: null,
352
+ voucher_url: "https://pagos.example.com/ficha/abc",
353
+ instructions: "Paga en cualquier OXXO antes de la fecha de vencimiento.",
354
+ expires_at: new Date(nowMs + 47 * 36e5).toISOString(),
355
+ amount: money(45e3, currency)
356
+ }
357
+ },
358
+ {
359
+ id: "chk_seed_spei",
360
+ number: null,
361
+ status: "expired",
362
+ created_at: new Date(nowMs - 9 * 864e5).toISOString(),
363
+ total: money(12e4, currency),
364
+ expires_at: new Date(nowMs - 2 * 864e5).toISOString(),
365
+ payment: {
366
+ provider: "mercadopago",
367
+ method: "spei",
368
+ reference: null,
369
+ barcode: null,
370
+ clabe: "646180000000000001",
371
+ voucher_url: null,
372
+ instructions: null,
373
+ expires_at: new Date(nowMs - 2 * 864e5).toISOString(),
374
+ amount: money(12e4, currency)
375
+ }
376
+ }
377
+ ];
45
378
  }
46
379
 
47
380
  // src/mock/region-mx.seed.ts
@@ -1037,7 +1370,7 @@ var PRODUCT_SPECS = [
1037
1370
  { handle: "llanta-265-70-r16-at", title: "Llanta 265/70 R16 All Terrain", collection: "llantas", type: "Llanta", vendor: "Marca Sur", price: 3599, attributes: { ...tire(265, 70, 16), load_index: 112, speed_rating: "T", runflat: false }, tags: ["camioneta"], description: "Para camioneta, asfalto y terracer\xEDa." },
1038
1371
  { handle: "llanta-175-70-r13-city", title: "Llanta 175/70 R13 City", collection: "llantas", type: "Llanta", vendor: "Marca Centro", price: 999, attributes: { ...tire(175, 70, 13), load_index: 82, speed_rating: "T", runflat: false }, tags: ["auto"], stock: 0, description: "Econ\xF3mica y durable para ciudad." }
1039
1372
  ];
1040
- var money = (n, currency) => ({ amount: n.toFixed(2), currency_code: currency });
1373
+ var money2 = (n, currency) => ({ amount: n.toFixed(2), currency_code: currency });
1041
1374
  function cartesian(options) {
1042
1375
  let combos = [{}];
1043
1376
  for (const opt of options) {
@@ -1067,8 +1400,8 @@ function buildProducts(currency, imageUrl = MOCK_IMAGE_URL) {
1067
1400
  sku: `${spec.handle.toUpperCase().replace(/-/g, "")}-${vi + 1}`.slice(0, 32),
1068
1401
  title,
1069
1402
  selected_options: Object.entries(opts).map(([name, value]) => ({ name, value })),
1070
- price: money(price, currency),
1071
- compare_at_price: spec.compareAt ? money(spec.compareAt + delta, currency) : null,
1403
+ price: money2(price, currency),
1404
+ compare_at_price: spec.compareAt ? money2(spec.compareAt + delta, currency) : null,
1072
1405
  available_for_sale: stock > 0,
1073
1406
  quantity_available: stock,
1074
1407
  image: firstValue ? image(imageUrl, `${spec.handle}-${firstValue}`, `${spec.title} ${firstValue}`) : null,
@@ -1093,8 +1426,8 @@ function buildProducts(currency, imageUrl = MOCK_IMAGE_URL) {
1093
1426
  images,
1094
1427
  options,
1095
1428
  variants,
1096
- price_range: { min: money(Math.min(...prices), currency), max: money(Math.max(...prices), currency) },
1097
- compare_at_price_range: compares.length ? { min: money(Math.min(...compares), currency), max: money(Math.max(...compares), currency) } : null,
1429
+ price_range: { min: money2(Math.min(...prices), currency), max: money2(Math.max(...prices), currency) },
1430
+ compare_at_price_range: compares.length ? { min: money2(Math.min(...compares), currency), max: money2(Math.max(...compares), currency) } : null,
1098
1431
  available_for_sale: variants.some((v) => v.available_for_sale),
1099
1432
  seo: { title: `${spec.title} | Mi tienda`, description: spec.description },
1100
1433
  updated_at: new Date(BASE_DATE - pi * 864e5).toISOString()
@@ -1156,7 +1489,7 @@ function buildLocations(currency) {
1156
1489
  opening_hours: { weekly: weekdays("10:00", "18:00", [1, 2, 3, 4, 5, 6]) },
1157
1490
  services: ["pickup"],
1158
1491
  contacts: contacts(null),
1159
- pickup: { enabled: true, fee: money(30, currency), prep_days: 1 }
1492
+ pickup: { enabled: true, fee: money2(30, currency), prep_days: 1 }
1160
1493
  },
1161
1494
  {
1162
1495
  id: "br_occidente",
@@ -1196,7 +1529,7 @@ function buildShop(currency, imageUrl = MOCK_IMAGE_URL) {
1196
1529
  logo: { url: imageUrl("logo", 256, 256), alt: "Mi tienda", width: 256, height: 256 },
1197
1530
  contact: { email: "hola@mi-tienda.example", phone: "+52 55 0000 0000", whatsapp: "+5215500000000" },
1198
1531
  branches: buildLocations(currency),
1199
- policies: { free_shipping_min: money(999, currency), returns_days: 30 }
1532
+ policies: { free_shipping_min: money2(999, currency), returns_days: 30 }
1200
1533
  };
1201
1534
  }
1202
1535
  function buildTheme() {
@@ -1437,8 +1770,10 @@ function createMockStorefront(options = {}) {
1437
1770
  lines: [
1438
1771
  { title: "Playera b\xE1sica", variant_title: "M / Blanco", sku: null, quantity: 1, unit: fromCents(24900, currency), total: fromCents(24900, currency), image: null }
1439
1772
  ]
1440
- }
1773
+ },
1774
+ ...buildAccountOrders(currency)
1441
1775
  ],
1776
+ pendingPayments: buildPendingPayments(currency, Date.now()),
1442
1777
  otps: /* @__PURE__ */ new Map(),
1443
1778
  passwords: /* @__PURE__ */ new Map(),
1444
1779
  pendingRegistrations: /* @__PURE__ */ new Map(),
@@ -1736,6 +2071,11 @@ function createMockStorefront(options = {}) {
1736
2071
  return inv;
1737
2072
  }
1738
2073
  const orderInvoice = (order) => {
2074
+ const doc = order.invoices?.find((d) => d.kind === "invoice");
2075
+ if (doc) {
2076
+ const { id: _id, kind: _k, total: _t, issued_at: _i, cancelled_at: _c, series: _s, folio_number: _f, related_to: _r, links_expire_at: _l, test_mode: _tm, environment: _e, ...summary } = doc;
2077
+ return { ...summary, url: null, xml_url: null };
2078
+ }
1739
2079
  const entry = state.invoices.get(order.id);
1740
2080
  const paid = order.status !== "pending_payment";
1741
2081
  if (entry) {
@@ -1896,21 +2236,21 @@ function createMockStorefront(options = {}) {
1896
2236
  if (!Array.isArray(body.lines)) throw new HttpError(400, "validation", "`lines` must be an array", "lines");
1897
2237
  const updates = body.lines;
1898
2238
  updates.forEach((u, i) => {
1899
- const line = cart.lines.find((l) => l.id === u.id);
1900
- if (!line) throw new HttpError(400, "validation", `Unknown line ${String(u.id)}`, `lines[${i}].id`);
2239
+ const line2 = cart.lines.find((l) => l.id === u.id);
2240
+ if (!line2) throw new HttpError(400, "validation", `Unknown line ${String(u.id)}`, `lines[${i}].id`);
1901
2241
  if (typeof u.quantity !== "number" || !Number.isInteger(u.quantity) || u.quantity < 0) {
1902
2242
  throw new HttpError(400, "validation", "quantity must be a non-negative integer", `lines[${i}].quantity`);
1903
2243
  }
1904
2244
  });
1905
2245
  checkStock(
1906
2246
  updates.flatMap((u, i) => {
1907
- const line = cart.lines.find((l) => l.id === u.id);
1908
- return u.quantity > 0 ? [{ variant: findVariant(line.variant_id).variant, quantity: u.quantity, lineId: line.id, field: `lines[${i}].quantity` }] : [];
2247
+ const line2 = cart.lines.find((l) => l.id === u.id);
2248
+ return u.quantity > 0 ? [{ variant: findVariant(line2.variant_id).variant, quantity: u.quantity, lineId: line2.id, field: `lines[${i}].quantity` }] : [];
1909
2249
  })
1910
2250
  );
1911
2251
  for (const u of updates) {
1912
- const line = cart.lines.find((l) => l.id === u.id);
1913
- line.quantity = u.quantity;
2252
+ const line2 = cart.lines.find((l) => l.id === u.id);
2253
+ line2.quantity = u.quantity;
1914
2254
  }
1915
2255
  cart.lines = cart.lines.filter((l) => l.quantity > 0);
1916
2256
  cart.updated_at = now();
@@ -2042,7 +2382,7 @@ function createMockStorefront(options = {}) {
2042
2382
  }
2043
2383
  syncAvailability(state.products);
2044
2384
  const orderId = nextId("ord");
2045
- const number = String(1001 + state.orders.length - 1);
2385
+ const number = String(Math.max(1e3, ...state.orders.map((o) => Number(o.number) || 0)) + 1);
2046
2386
  const customerId = cart.buyer.customer_id ?? state.customers.find((c) => c.email && c.email === cart.buyer.email)?.id ?? null;
2047
2387
  state.orders.push({
2048
2388
  id: orderId,
@@ -2323,17 +2663,34 @@ function createMockStorefront(options = {}) {
2323
2663
  }
2324
2664
  return { body: publicCustomer(c), cache: "private" };
2325
2665
  });
2326
- const stripOrder = ({ customer_id: _c, ...o }) => ({ ...o, invoice: orderInvoice(o) });
2666
+ const orderView = ({ customer_id: _c, ...o }, detail) => {
2667
+ const order = { ...o, invoice: orderInvoice(o) };
2668
+ if (order.shipments) order.shipments = order.shipments.map((s) => isTestShipment(s) ? { ...s, tracking_number: null, tracking_url: null } : { ...s, events: sortTrackingEvents(s.events) });
2669
+ order.progress = orderProgress(order);
2670
+ order.next_action = nextAction(order);
2671
+ const live = orderShipments(order).filter((s) => !isTestShipment(s));
2672
+ if (order.shipments?.length) {
2673
+ order.shipments_summary = { count: order.shipments.length, status: shipmentsStatus(order) ?? "pending", carrier: live[0]?.carrier ?? null, ...live[0]?.tracking_url ? { tracking_url: live[0].tracking_url } : {} };
2674
+ }
2675
+ if (!detail) {
2676
+ delete order.shipments;
2677
+ delete order.invoices;
2678
+ delete order.payments;
2679
+ delete order.refunds;
2680
+ delete order.shipping_address;
2681
+ }
2682
+ return order;
2683
+ };
2327
2684
  route("GET", "/customer/orders", "orders:read", ({ headers, search }) => {
2328
2685
  const c = requireCustomer(headers);
2329
- const orders = state.orders.filter((o) => o.customer_id === c.id).sort((a, b) => b.created_at.localeCompare(a.created_at)).map(stripOrder);
2686
+ const orders = state.orders.filter((o) => o.customer_id === c.id).sort((a, b) => b.created_at.localeCompare(a.created_at)).map((o) => orderView(o, false));
2330
2687
  return { body: paginate(orders, search, 20), cache: "private" };
2331
2688
  });
2332
2689
  const findOrder = (headers, id) => {
2333
2690
  const c = requireCustomer(headers);
2334
2691
  return requireNode(state.orders.find((o) => o.id === id && o.customer_id === c.id), `Order ${id}`, "id");
2335
2692
  };
2336
- route("GET", "/customer/orders/:id", "orders:read", ({ headers, params }) => ({ body: stripOrder(findOrder(headers, params[0])), cache: "private" }));
2693
+ route("GET", "/customer/orders/:id", "orders:read", ({ headers, params }) => ({ body: orderView(findOrder(headers, params[0]), true), cache: "private" }));
2337
2694
  route("POST", "/customer/orders/:id/returns", "customer:write", ({ headers, params, body }) => {
2338
2695
  const order = findOrder(headers, params[0]);
2339
2696
  if (!Array.isArray(body.lines) || body.lines.length === 0) throw new HttpError(400, "validation", "lines are required", "lines");
@@ -2350,16 +2707,37 @@ function createMockStorefront(options = {}) {
2350
2707
  state.invoices.set(order.id, { stage: "queued", request });
2351
2708
  return { status: 202, body: { status: "requested", url: null, stage: "queued", invoice: orderInvoice(order) }, cache: "private" };
2352
2709
  });
2710
+ route("GET", "/customer/pending-payments", "orders:read", ({ headers, search }) => {
2711
+ const c = requireCustomer(headers);
2712
+ const nodes = c.id === "cus_1" ? state.pendingPayments : [];
2713
+ return { body: paginate(nodes, search, 20), cache: "private" };
2714
+ });
2715
+ route("GET", "/customer/orders/:id/invoices/:invoice_id/:kind", "orders:read", ({ headers, params }) => {
2716
+ const order = findOrder(headers, params[0]);
2717
+ const doc = order.invoices?.find((d) => d.id === params[1]);
2718
+ if (!doc || params[2] !== "pdf" && params[2] !== "xml") throw new HttpError(404, "not_found", "Invoice file not found", "invoice_id");
2719
+ const pdf = params[2] === "pdf";
2720
+ const text = pdf ? `%PDF-1.4
2721
+ % mock invoice ${doc.id}
2722
+ %%EOF
2723
+ ` : `<?xml version="1.0"?><cfdi:Comprobante mock="true" id="${doc.id}"/>`;
2724
+ const file = {
2725
+ filename: `${doc.kind === "credit_note" ? "nota-de-credito" : "factura"}-${doc.id}.${params[2]}`,
2726
+ content_type: pdf ? "application/pdf" : "application/xml",
2727
+ content_base64: btoa(text)
2728
+ };
2729
+ return { body: file, headers: { "Cache-Control": "no-store" } };
2730
+ });
2353
2731
  route("GET", "/customer/addresses", "customer:read", ({ headers }) => {
2354
2732
  const c = requireCustomer(headers);
2355
2733
  return { body: { nodes: state.addresses.get(c.id) ?? [] }, cache: "private" };
2356
2734
  });
2357
2735
  route("POST", "/customer/addresses", "customer:write", ({ headers, body }) => {
2358
2736
  const c = requireCustomer(headers);
2359
- const address = { ...validateAddress(body), id: nextId("addr") };
2360
- state.addresses.set(c.id, [...state.addresses.get(c.id) ?? [], address]);
2361
- if (!c.default_address_id) c.default_address_id = address.id;
2362
- return { status: 201, body: address, cache: "private" };
2737
+ const address2 = { ...validateAddress(body), id: nextId("addr") };
2738
+ state.addresses.set(c.id, [...state.addresses.get(c.id) ?? [], address2]);
2739
+ if (!c.default_address_id) c.default_address_id = address2.id;
2740
+ return { status: 201, body: address2, cache: "private" };
2363
2741
  });
2364
2742
  route("PATCH", "/customer/addresses/:id", "customer:write", ({ headers, params, body }) => {
2365
2743
  const c = requireCustomer(headers);