@fayz-ai/shop 0.10.0 → 0.11.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.
Files changed (63) hide show
  1. package/dist/{chunk-IL6GA4ER.js → chunk-2WACO2TG.js} +3 -3
  2. package/dist/{chunk-IL6GA4ER.js.map → chunk-2WACO2TG.js.map} +1 -1
  3. package/dist/{chunk-RGTIJQJL.js → chunk-6BO2TMZ5.js} +3 -3
  4. package/dist/{chunk-RGTIJQJL.js.map → chunk-6BO2TMZ5.js.map} +1 -1
  5. package/dist/{chunk-XOP6SHPB.js → chunk-NWW3WJ7Y.js} +4 -10
  6. package/dist/chunk-NWW3WJ7Y.js.map +1 -0
  7. package/dist/{chunk-XERLK5LD.js → chunk-STRNMFC4.js} +28 -5
  8. package/dist/chunk-STRNMFC4.js.map +1 -0
  9. package/dist/{chunk-U3O2I3RM.js → chunk-U6HV3LVP.js} +385 -23
  10. package/dist/chunk-U6HV3LVP.js.map +1 -0
  11. package/dist/data/customer-search.d.ts +3 -0
  12. package/dist/data/customer-search.d.ts.map +1 -0
  13. package/dist/data/functions.d.ts +9 -0
  14. package/dist/data/functions.d.ts.map +1 -0
  15. package/dist/data/media.d.ts +37 -0
  16. package/dist/data/media.d.ts.map +1 -0
  17. package/dist/data/mock-provider.d.ts +40 -8
  18. package/dist/data/mock-provider.d.ts.map +1 -1
  19. package/dist/data/provider.d.ts +200 -7
  20. package/dist/data/provider.d.ts.map +1 -1
  21. package/dist/data/supabase-provider.d.ts +113 -5
  22. package/dist/data/supabase-provider.d.ts.map +1 -1
  23. package/dist/data/tables.d.ts +3 -0
  24. package/dist/data/tables.d.ts.map +1 -1
  25. package/dist/entities/index.d.ts +0 -1
  26. package/dist/entities/index.d.ts.map +1 -1
  27. package/dist/entities.js +2 -2
  28. package/dist/index.d.ts +14 -5
  29. package/dist/index.d.ts.map +1 -1
  30. package/dist/index.js +891 -60
  31. package/dist/index.js.map +1 -1
  32. package/dist/inventory/index.d.ts +2 -2
  33. package/dist/inventory/index.d.ts.map +1 -1
  34. package/dist/inventory/stock-types.d.ts +60 -1
  35. package/dist/inventory/stock-types.d.ts.map +1 -1
  36. package/dist/inventory/supabase-stock-store.d.ts +1 -0
  37. package/dist/inventory/supabase-stock-store.d.ts.map +1 -1
  38. package/dist/inventory.js +1 -1
  39. package/dist/lib/commerce.d.ts.map +1 -1
  40. package/dist/lib/document.d.ts +13 -0
  41. package/dist/lib/document.d.ts.map +1 -0
  42. package/dist/lib/variant-matrix.d.ts +120 -0
  43. package/dist/lib/variant-matrix.d.ts.map +1 -0
  44. package/dist/mock.js +2 -2
  45. package/dist/providers/checkout.d.ts +129 -0
  46. package/dist/providers/checkout.d.ts.map +1 -0
  47. package/dist/providers/index.d.ts +5 -0
  48. package/dist/providers/index.d.ts.map +1 -0
  49. package/dist/providers/shipping.d.ts +43 -0
  50. package/dist/providers/shipping.d.ts.map +1 -0
  51. package/dist/providers.js +70 -0
  52. package/dist/providers.js.map +1 -0
  53. package/dist/runtime.js +3 -3
  54. package/dist/shipping/label-provider.d.ts +43 -0
  55. package/dist/shipping/label-provider.d.ts.map +1 -0
  56. package/dist/types.d.ts +457 -0
  57. package/dist/types.d.ts.map +1 -1
  58. package/package.json +8 -4
  59. package/dist/chunk-U3O2I3RM.js.map +0 -1
  60. package/dist/chunk-XERLK5LD.js.map +0 -1
  61. package/dist/chunk-XOP6SHPB.js.map +0 -1
  62. package/dist/entities/sidecar.d.ts +0 -9
  63. package/dist/entities/sidecar.d.ts.map +0 -1
@@ -1,8 +1,192 @@
1
1
  import { MOCK_PRODUCTS, MOCK_CATEGORIES, MOCK_DISCOUNTS, MOCK_VARIANTS } from './chunk-IKNGCRUM.js';
2
- import { resolveEntityStore, createMockEntityStore, validateSidecarWrite } from './chunk-XOP6SHPB.js';
2
+ import { resolveEntityStore, createMockEntityStore } from './chunk-NWW3WJ7Y.js';
3
3
  import { buildRuleContext, computePromotionActions, discountToPromotion } from './chunk-3GERSY5F.js';
4
4
  import { resolveEntityDeclaration } from '@fayz-ai/core';
5
5
 
6
+ // src/data/media.ts
7
+ var IMAGE_EXTENSIONS = /* @__PURE__ */ new Set(["png", "jpg", "jpeg", "gif", "webp", "svg", "avif", "bmp"]);
8
+ var VIDEO_EXTENSIONS = /* @__PURE__ */ new Set(["mp4", "mov", "webm", "m4v", "avi", "mkv"]);
9
+ function mediaKindOf(name, mime) {
10
+ if (mime?.startsWith("image/")) return "image";
11
+ if (mime?.startsWith("video/")) return "video";
12
+ const ext = (name.split(".").pop() ?? "").toLowerCase();
13
+ if (IMAGE_EXTENSIONS.has(ext)) return "image";
14
+ if (VIDEO_EXTENSIONS.has(ext)) return "video";
15
+ return "document";
16
+ }
17
+ function storagePathOf(url) {
18
+ const match = /\/object\/(?:public|sign|authenticated)\/[^/]+\/([^?]+)/.exec(url);
19
+ if (!match) return null;
20
+ try {
21
+ return decodeURIComponent(match[1]);
22
+ } catch {
23
+ return match[1];
24
+ }
25
+ }
26
+ function displayNameOf(path) {
27
+ const base = path.split("/").pop() ?? path;
28
+ return base.replace(/^\d{10,}-/, "");
29
+ }
30
+ function productImageToMediaFile(image, productName) {
31
+ const path = storagePathOf(image.url);
32
+ const name = displayNameOf(path ?? image.url);
33
+ return {
34
+ // Fall back to the URL as identity: an image row that points outside our
35
+ // bucket still belongs in the library, it just cannot be deleted by path.
36
+ id: path ?? image.url,
37
+ name,
38
+ url: image.url,
39
+ kind: mediaKindOf(name),
40
+ size: 0,
41
+ extension: (name.split(".").pop() ?? "").toUpperCase(),
42
+ createdAt: image.createdAt ?? "",
43
+ reference: {
44
+ type: "product",
45
+ id: image.productId,
46
+ label: productName?.trim() || "Produto sem nome",
47
+ attachmentId: image.id,
48
+ isCover: image.isPrimary
49
+ }
50
+ };
51
+ }
52
+ function mergeMediaFiles(attached, library) {
53
+ const seen = new Set(attached.map((f) => f.id));
54
+ return [...attached, ...library.filter((f) => !seen.has(f.id))].sort((a, b) => b.createdAt.localeCompare(a.createdAt));
55
+ }
56
+
57
+ // src/lib/variant-matrix.ts
58
+ var MAX_VARIANT_AXES = 3;
59
+ var MAX_VARIANT_COMBINATIONS = 100;
60
+ function variantOptionsKey(options) {
61
+ return JSON.stringify(
62
+ Object.keys(options).sort().map((key) => [key, options[key]])
63
+ );
64
+ }
65
+ function normalizeVariantAxes(axes) {
66
+ const seenNames = /* @__PURE__ */ new Set();
67
+ const result = [];
68
+ for (const axis of axes) {
69
+ const name = axis.name.trim();
70
+ if (!name || seenNames.has(name)) continue;
71
+ const values = [];
72
+ const seenValues = /* @__PURE__ */ new Set();
73
+ for (const raw of axis.values) {
74
+ const value = raw.trim();
75
+ if (!value || seenValues.has(value)) continue;
76
+ seenValues.add(value);
77
+ values.push(value);
78
+ }
79
+ if (values.length === 0) continue;
80
+ seenNames.add(name);
81
+ result.push({ name, values });
82
+ }
83
+ return result;
84
+ }
85
+ function variantCombinations(axes) {
86
+ const clean = normalizeVariantAxes(axes);
87
+ if (clean.length === 0) return [];
88
+ let rows = [{}];
89
+ for (const axis of clean) {
90
+ const next = [];
91
+ for (const row of rows) {
92
+ for (const value of axis.values) next.push({ ...row, [axis.name]: value });
93
+ }
94
+ rows = next;
95
+ }
96
+ return rows;
97
+ }
98
+ function validateVariantAxes(axes) {
99
+ const clean = normalizeVariantAxes(axes);
100
+ if (clean.length > MAX_VARIANT_AXES) {
101
+ return `Um produto aceita no m\xE1ximo ${MAX_VARIANT_AXES} op\xE7\xF5es (ex.: Tamanho, Cor, Material).`;
102
+ }
103
+ const total = clean.reduce((acc, axis) => acc * axis.values.length, 1);
104
+ if (clean.length > 0 && total > MAX_VARIANT_COMBINATIONS) {
105
+ return `Essas op\xE7\xF5es gerariam ${total} variantes, e o limite \xE9 ${MAX_VARIANT_COMBINATIONS}. Reduza os valores ou separe em dois produtos.`;
106
+ }
107
+ return null;
108
+ }
109
+ function planVariantMatrix(axes, existing) {
110
+ const wanted = variantCombinations(axes);
111
+ const wantedKeys = new Set(wanted.map(variantOptionsKey));
112
+ const byKey = /* @__PURE__ */ new Map();
113
+ let defaultVariant = null;
114
+ for (const variant of existing) {
115
+ if (variant.isDefaultVariant) {
116
+ defaultVariant = variant;
117
+ continue;
118
+ }
119
+ byKey.set(variantOptionsKey(variant.options), variant);
120
+ }
121
+ const plan = {
122
+ rows: [],
123
+ create: [],
124
+ reactivate: [],
125
+ keep: [],
126
+ archive: [],
127
+ defaultVariant
128
+ };
129
+ for (const [position, options] of wanted.entries()) {
130
+ const variant = byKey.get(variantOptionsKey(options)) ?? null;
131
+ const row = { options, position, variant };
132
+ plan.rows.push(row);
133
+ if (!variant) plan.create.push(row);
134
+ else if (variant.status === "archived") plan.reactivate.push(row);
135
+ else plan.keep.push(row);
136
+ }
137
+ for (const [key, variant] of byKey) {
138
+ if (!wantedKeys.has(key) && variant.status === "active") plan.archive.push(variant);
139
+ }
140
+ return plan;
141
+ }
142
+ function hasOnlyDefaultVariant(variants) {
143
+ const active = variants.filter((variant) => variant.status === "active");
144
+ return active.length === 1 && Boolean(active[0].isDefaultVariant);
145
+ }
146
+ function leavesNothingSellable(plan) {
147
+ const willHaveReal = plan.keep.length + plan.reactivate.length + plan.create.length;
148
+ return willHaveReal === 0 && plan.defaultVariant === null;
149
+ }
150
+ function axesFromOptions(options) {
151
+ return [...options].sort((a, b) => a.position - b.position).map((option) => ({ name: option.name, values: [...option.values] }));
152
+ }
153
+ async function applyVariantMatrix(provider, productId, axes, existing) {
154
+ const refusal = validateVariantAxes(axes);
155
+ if (refusal) throw new Error(refusal);
156
+ if (!provider.saveProductOptions || !provider.createProductVariant || !provider.updateProductVariant) {
157
+ throw new Error("Este provider n\xE3o administra variantes.");
158
+ }
159
+ const clean = normalizeVariantAxes(axes);
160
+ const plan = planVariantMatrix(clean, existing);
161
+ await provider.saveProductOptions(productId, clean);
162
+ for (const row of plan.create) {
163
+ await provider.createProductVariant(productId, { options: row.options, position: row.position });
164
+ }
165
+ for (const row of plan.reactivate) {
166
+ await provider.updateProductVariant(row.variant.id, { status: "active", position: row.position });
167
+ }
168
+ for (const row of plan.keep) {
169
+ if (row.variant.position !== row.position) {
170
+ await provider.updateProductVariant(row.variant.id, { position: row.position });
171
+ }
172
+ }
173
+ for (const variant of plan.archive) {
174
+ await (provider.archiveProductVariant?.(variant.id) ?? provider.updateProductVariant(variant.id, { status: "archived" }));
175
+ }
176
+ const real = plan.create.length + plan.reactivate.length + plan.keep.length;
177
+ if (plan.defaultVariant) {
178
+ if (real > 0 && plan.defaultVariant.status === "active") {
179
+ await provider.updateProductVariant(plan.defaultVariant.id, { status: "archived" });
180
+ } else if (real === 0 && plan.defaultVariant.status === "archived") {
181
+ await provider.updateProductVariant(plan.defaultVariant.id, { status: "active" });
182
+ }
183
+ }
184
+ return plan;
185
+ }
186
+ function formatVariantCombination(options, axes) {
187
+ const order = axes?.map((axis) => axis.name) ?? Object.keys(options).sort();
188
+ return order.filter((name) => options[name] != null).map((name) => `${name}: ${options[name]}`).join(" \xB7 ");
189
+ }
6
190
  function uid() {
7
191
  return Math.random().toString(36).slice(2);
8
192
  }
@@ -13,6 +197,8 @@ var ORDERS_KEY = "fayz.shop.mock.orders.v1";
13
197
  var CUSTOMERS_KEY = "fayz.shop.mock.customers.v1";
14
198
  var ENQUIRIES_KEY = "fayz.shop.mock.enquiries.v1";
15
199
  var CHANNELS_KEY = "fayz.shop.mock.channels.v1";
200
+ var SETTINGS_KEY = "fayz.shop.mock.settings.v1";
201
+ var SHIPPING_SETTINGS_KEY = "fayz.shop.mock.shipping-settings.v1";
16
202
  var DEFAULT_CHANNELS = [
17
203
  { id: "online-store", tenantId: "mock", kind: "online_store", label: "Loja Online", url: null, enabled: true, position: 0, createdAt: now(), updatedAt: now() }
18
204
  ];
@@ -61,7 +247,15 @@ var MockShopProvider = class {
61
247
  return stored.length ? stored : DEFAULT_CHANNELS.map((c) => ({ ...c }));
62
248
  })();
63
249
  this.entityStores = /* @__PURE__ */ new Map();
64
- this.sidecars = /* @__PURE__ */ new Map();
250
+ // ---------------------------------------------------------------------------
251
+ // Checkout sessions
252
+ //
253
+ // Mirrors the live inversion: the session holds the cart, and the ORDER is
254
+ // only written when the settle says the money is in. Here the "provider" is a
255
+ // clock — the second poll pays — which is what lets the four-step checkout be
256
+ // walked end to end without an acquirer, in demo stores and in the e2e suite.
257
+ // ---------------------------------------------------------------------------
258
+ this.checkoutSessions = {};
65
259
  this.products = (seed?.products ?? MOCK_PRODUCTS).map((p) => ({ ...p }));
66
260
  this.categories = (seed?.categories ?? MOCK_CATEGORIES).map((c) => ({ ...c }));
67
261
  this.discounts = (seed?.discounts ?? MOCK_DISCOUNTS).map((d) => ({ ...d }));
@@ -73,9 +267,6 @@ var MockShopProvider = class {
73
267
  );
74
268
  this.promotions = (seed?.promotions ?? []).map((p) => ({ ...p }));
75
269
  this.entitySeed = seed?.entities ?? {};
76
- for (const [key, fields] of Object.entries(seed?.entityFields ?? {})) {
77
- this.sidecars.set(key, { ...fields });
78
- }
79
270
  }
80
271
  /** The I5 indirection: a promoted (registered) store wins; otherwise the
81
272
  * entity gets a lazily-built in-memory store seeded from the mock seed.
@@ -162,16 +353,107 @@ var MockShopProvider = class {
162
353
  }
163
354
  /** Option axes + variants (spine M5) — active variants only, like the 0028
164
355
  * storefront RLS. Answers empty sets for a variantless product. */
165
- async getProductVariants(productId) {
356
+ async getProductVariants(productId, opts) {
166
357
  const set = this.variantSets[productId];
167
358
  if (!set) return { options: [], variants: [] };
168
359
  return {
169
360
  options: set.options.map((o) => ({ ...o, values: [...o.values] })).sort((a, b) => a.position - b.position),
170
- variants: set.variants.filter((v) => v.status === "active").map((v) => ({ ...v, options: { ...v.options } })).sort((a, b) => a.position - b.position)
361
+ variants: set.variants.filter((v) => opts?.includeArchived || v.status === "active").map((v) => ({ ...v, options: { ...v.options } })).sort((a, b) => a.position - b.position)
171
362
  };
172
363
  }
364
+ // --- The variant admin ------------------------------------------------------
365
+ // Modelled on the SQL rather than on convenience: 0028's UNIQUE
366
+ // (product_id, options), 0037's default-variant triggers and 0036's mirror all
367
+ // have a counterpart here, so a test that passes against this mock is
368
+ // asserting the real semantics and not a friendlier imitation of them.
369
+ variantSet(productId) {
370
+ var _a;
371
+ (_a = this.variantSets)[productId] ?? (_a[productId] = { options: [], variants: [] });
372
+ return this.variantSets[productId];
373
+ }
374
+ async saveProductOptions(productId, input) {
375
+ const set = this.variantSet(productId);
376
+ const existing = new Map(set.options.map((o) => [o.name, o]));
377
+ set.options = input.map((axis, position) => ({
378
+ id: existing.get(axis.name)?.id ?? uid(),
379
+ name: axis.name,
380
+ values: [...axis.values],
381
+ position
382
+ }));
383
+ return set.options.map((o) => ({ ...o, values: [...o.values] }));
384
+ }
385
+ async createProductVariant(productId, input) {
386
+ const set = this.variantSet(productId);
387
+ const key = variantOptionsKey(input.options);
388
+ const existing = set.variants.find((v) => variantOptionsKey(v.options) === key);
389
+ if (existing) return this.updateProductVariant(existing.id, { ...input, status: input.status ?? "active" });
390
+ const variant = {
391
+ id: uid(),
392
+ productId,
393
+ options: { ...input.options },
394
+ sku: input.sku ?? null,
395
+ price: input.price ?? null,
396
+ compareAtPrice: input.compareAtPrice ?? null,
397
+ inventoryCount: 0,
398
+ position: input.position ?? set.variants.length,
399
+ status: input.status ?? "active",
400
+ imageId: input.imageId ?? null,
401
+ isDefaultVariant: false,
402
+ weight: input.weight ?? null,
403
+ weightUnit: input.weightUnit ?? null,
404
+ heightCm: input.heightCm ?? null,
405
+ widthCm: input.widthCm ?? null,
406
+ lengthCm: input.lengthCm ?? null
407
+ };
408
+ set.variants.push(variant);
409
+ for (const other of set.variants) {
410
+ if (other.isDefaultVariant && other.status === "active") other.status = "archived";
411
+ }
412
+ this.syncProductFromVariants(productId);
413
+ return { ...variant, options: { ...variant.options } };
414
+ }
415
+ async updateProductVariant(variantId, input) {
416
+ for (const [productId, set] of Object.entries(this.variantSets)) {
417
+ const variant = set.variants.find((v) => v.id === variantId);
418
+ if (!variant) continue;
419
+ for (const [key, value] of Object.entries(input)) {
420
+ if (value !== void 0) variant[key] = value;
421
+ }
422
+ this.syncProductFromVariants(productId);
423
+ return { ...variant, options: { ...variant.options } };
424
+ }
425
+ throw new Error("Variant not found");
426
+ }
427
+ async archiveProductVariant(variantId) {
428
+ await this.updateProductVariant(variantId, { status: "archived" });
429
+ }
430
+ /**
431
+ * The 0034→0036 path, compressed: the merchant states a count, the difference
432
+ * is what gets recorded, and inventory_count is the mirror that follows. The
433
+ * ledger itself is MockStockStore's business — what matters here is that no
434
+ * caller ever assigns to inventoryCount.
435
+ */
436
+ async setVariantStock(variantId, count) {
437
+ const target = Math.max(0, Math.trunc(count));
438
+ for (const [productId, set] of Object.entries(this.variantSets)) {
439
+ const variant = set.variants.find((v) => v.id === variantId);
440
+ if (!variant) continue;
441
+ variant.inventoryCount = target;
442
+ this.syncProductFromVariants(productId);
443
+ return target;
444
+ }
445
+ throw new Error("Variant not found");
446
+ }
447
+ /** 0028's sync trigger: the product row is the SUM of active variant stock. */
448
+ syncProductFromVariants(productId) {
449
+ const product = this.products.find((p) => p.id === productId);
450
+ const set = this.variantSets[productId];
451
+ if (!product || !set) return;
452
+ const active = set.variants.filter((v) => v.status === "active");
453
+ product.inventoryCount = active.reduce((sum, v) => sum + v.inventoryCount, 0);
454
+ }
173
455
  async createProduct(input) {
174
- const p = { id: uid(), tenantId: "mock", name: input.name, slug: input.slug ?? input.name.toLowerCase().replace(/\s+/g, "-"), description: input.description ?? null, price: input.price, compareAtPrice: input.compareAtPrice ?? null, currency: input.currency ?? "BRL", status: input.status ?? "active", inventoryCount: input.inventoryCount ?? 0, sku: input.sku ?? null, sortOrder: input.sortOrder ?? 0, metadata: input.metadata ?? {}, images: [], categoryId: input.categoryId ?? null, categoryName: null, isPhysical: input.isPhysical ?? true, weight: input.weight ?? null, weightUnit: input.weightUnit ?? "kg", createdAt: now(), updatedAt: now() };
456
+ const p = { id: uid(), tenantId: "mock", name: input.name, slug: input.slug ?? input.name.toLowerCase().replace(/\s+/g, "-"), description: input.description ?? null, price: input.price, compareAtPrice: input.compareAtPrice ?? null, currency: input.currency ?? "BRL", status: input.status ?? "active", inventoryCount: input.inventoryCount ?? 0, sku: input.sku ?? null, sortOrder: input.sortOrder ?? 0, metadata: input.metadata ?? {}, images: [], categoryId: input.categoryId ?? null, categoryName: null, isPhysical: input.isPhysical ?? true, weight: input.weight ?? null, weightUnit: input.weightUnit ?? "kg", handling: input.handling ?? [], createdAt: now(), updatedAt: now() };
175
457
  this.products.push(p);
176
458
  return p;
177
459
  }
@@ -187,7 +469,15 @@ var MockShopProvider = class {
187
469
  async uploadProductImage(_productId, _file) {
188
470
  throw new Error("Image upload not available in mock mode");
189
471
  }
190
- async deleteProductImage(_imageId) {
472
+ async deleteProductImage(imageId) {
473
+ this.products = this.products.map((p) => p.images.some((i) => i.id === imageId) ? { ...p, images: p.images.filter((i) => i.id !== imageId) } : p);
474
+ }
475
+ /** The media library, mock-side: no bucket to walk, so the catalogue's own
476
+ * photos ARE the library. Uploading has nowhere to go, which is why
477
+ * uploadMediaFile stays unimplemented and the screen hides its button. */
478
+ async listMediaFiles() {
479
+ const attached = this.products.flatMap((p) => p.images.map((image) => productImageToMediaFile(image, p.name)));
480
+ return mergeMediaFiles(attached, []);
191
481
  }
192
482
  async createProductEnquiry(input) {
193
483
  this.syncEnquiries();
@@ -458,6 +748,45 @@ var MockShopProvider = class {
458
748
  }
459
749
  return order;
460
750
  }
751
+ async openCheckoutSession(input) {
752
+ const sessionId = uid();
753
+ this.checkoutSessions[sessionId] = { input, polls: 0 };
754
+ let total = 0;
755
+ for (const line of input.items) {
756
+ const product = this.products.find((p) => p.id === line.productId);
757
+ if (!product) continue;
758
+ const variant = line.variantId ? this.variantSets[line.productId]?.variants.find((v) => v.id === line.variantId) : void 0;
759
+ total += (variant?.price ?? product.price) * line.quantity;
760
+ }
761
+ return {
762
+ sessionId,
763
+ total: Math.round(total * 100) / 100,
764
+ state: "open",
765
+ charge: {
766
+ // Shaped like a real EMV payload so the copy button and the layout are
767
+ // exercised, not a placeholder that fits in one line.
768
+ emv: `00020126580014BR.GOV.BCB.PIX0136${sessionId}520400005303986540${total.toFixed(2)}5802BR6304MOCK`,
769
+ qrcodeBase64: null,
770
+ expiresAt: new Date(Date.now() + 30 * 6e4).toISOString()
771
+ }
772
+ };
773
+ }
774
+ async settleCheckoutSession(sessionId) {
775
+ const session = this.checkoutSessions[sessionId];
776
+ if (!session) return { state: "expired", reason: "Sess\xE3o de checkout n\xE3o encontrada." };
777
+ if (session.orderId) return { state: "paid", orderId: session.orderId };
778
+ session.polls += 1;
779
+ if (session.polls < 2) return { state: "open" };
780
+ const order = await this.placeOrder({
781
+ items: session.input.items,
782
+ customer: session.input.customer,
783
+ discountCode: session.input.discountCode ?? void 0,
784
+ shippingAddress: session.input.shippingAddress
785
+ });
786
+ await this.updateOrder(order.id, { financialStatus: "paid" });
787
+ session.orderId = order.id;
788
+ return { state: "paid", orderId: order.id };
789
+ }
461
790
  // ---------------------------------------------------------------------------
462
791
  // Customers
463
792
  // ---------------------------------------------------------------------------
@@ -474,7 +803,7 @@ var MockShopProvider = class {
474
803
  }
475
804
  async createCustomer(input) {
476
805
  this.syncCustomers();
477
- const c = { id: uid(), tenantId: "mock", firstName: input.firstName, lastName: input.lastName ?? "", email: input.email ?? null, phone: input.phone ?? null, notes: input.notes ?? null, ordersCount: 0, totalSpent: 0, createdAt: now(), updatedAt: now() };
806
+ const c = { id: uid(), tenantId: "mock", firstName: input.firstName, lastName: input.lastName ?? "", email: input.email ?? null, phone: input.phone ?? null, document: input.document ?? null, notes: input.notes ?? null, ordersCount: 0, totalSpent: 0, createdAt: now(), updatedAt: now() };
478
807
  this.customers.push(c);
479
808
  this.persistCustomers();
480
809
  return c;
@@ -570,6 +899,49 @@ var MockShopProvider = class {
570
899
  this.persistChannels();
571
900
  }
572
901
  // ---------------------------------------------------------------------------
902
+ // Store policy + delivery fallback
903
+ //
904
+ // A single object behind a list-shaped store helper, so the same loadStored /
905
+ // store pair serves it. Absent = null, NOT an object of defaults: "nothing
906
+ // decided" is a state the callers are written to handle, and manufacturing
907
+ // defaults here would hide the one case the live provider really has.
908
+ // ---------------------------------------------------------------------------
909
+ async getStoreSettings() {
910
+ return loadStored(SETTINGS_KEY)[0] ?? null;
911
+ }
912
+ async updateStoreSettings(input) {
913
+ const next = { ...await this.getStoreSettings() ?? {}, ...input };
914
+ store(SETTINGS_KEY, [next]);
915
+ return next;
916
+ }
917
+ /** In mock mode there is no publication gate to fail, so the shopper's read
918
+ * and the merchant's are the same row. */
919
+ async getPublicStoreSettings() {
920
+ return this.getStoreSettings();
921
+ }
922
+ /**
923
+ * Mock mode has no delivery zones at all, so nothing is refused and no zone is
924
+ * the store's own. Answered rather than omitted so a screen developed against
925
+ * the mock sees the same shape it will see live — an absent method would make
926
+ * the caller take its "provider cannot check" branch and never exercise this.
927
+ */
928
+ async checkDelivery() {
929
+ return { refused: false, ownZone: false };
930
+ }
931
+ async getShippingSettings() {
932
+ return loadStored(SHIPPING_SETTINGS_KEY)[0] ?? null;
933
+ }
934
+ async updateShippingSettings(input) {
935
+ const current = await this.getShippingSettings();
936
+ const next = {
937
+ flatRate: input.flatRate ?? current?.flatRate ?? 0,
938
+ freeAbove: input.freeAbove !== void 0 ? input.freeAbove : current?.freeAbove ?? null,
939
+ currency: input.currency ?? current?.currency ?? "BRL"
940
+ };
941
+ store(SHIPPING_SETTINGS_KEY, [next]);
942
+ return next;
943
+ }
944
+ // ---------------------------------------------------------------------------
573
945
  // Custom entities (spine M4) — same EntityStore port as the live provider.
574
946
  // ---------------------------------------------------------------------------
575
947
  async listEntityRecords(entity, options) {
@@ -590,16 +962,6 @@ var MockShopProvider = class {
590
962
  async deleteEntityRecord(entity, id) {
591
963
  return this.entityStore(entity).delete(id);
592
964
  }
593
- // Sidecar fields on core records — validated against defineSidecarFields.
594
- async getEntityFields(reference, referenceId) {
595
- return this.sidecars.get(`${reference}:${referenceId}`) ?? null;
596
- }
597
- async setEntityFields(reference, referenceId, fields) {
598
- validateSidecarWrite(reference, fields);
599
- const stored = { ...fields };
600
- this.sidecars.set(`${reference}:${referenceId}`, stored);
601
- return stored;
602
- }
603
965
  // Coleções, grupos/segmentos, vale-presente e e-mails — paridade com o
604
966
  // provider Supabase. Mídia não entra: é storage, não tem mock honesto.
605
967
  async listCollections() {
@@ -803,6 +1165,6 @@ function createMockShopProvider(seed) {
803
1165
  return new MockShopProvider(seed);
804
1166
  }
805
1167
 
806
- export { MockShopProvider, createMockShopProvider };
807
- //# sourceMappingURL=chunk-U3O2I3RM.js.map
808
- //# sourceMappingURL=chunk-U3O2I3RM.js.map
1168
+ export { MAX_VARIANT_AXES, MAX_VARIANT_COMBINATIONS, MockShopProvider, applyVariantMatrix, axesFromOptions, createMockShopProvider, displayNameOf, formatVariantCombination, hasOnlyDefaultVariant, leavesNothingSellable, mediaKindOf, mergeMediaFiles, normalizeVariantAxes, planVariantMatrix, productImageToMediaFile, storagePathOf, validateVariantAxes, variantCombinations, variantOptionsKey };
1169
+ //# sourceMappingURL=chunk-U6HV3LVP.js.map
1170
+ //# sourceMappingURL=chunk-U6HV3LVP.js.map