@fayz-ai/plugin-shop 0.14.0 → 0.15.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 (46) hide show
  1. package/dist/{ShopPage-QFIM2HDU.js → ShopPage-5IERN4E5.js} +339 -412
  2. package/dist/ShopPage-5IERN4E5.js.map +1 -0
  3. package/dist/{chunk-2VMTPRGC.js → chunk-RPBKGW24.js} +95 -50
  4. package/dist/chunk-RPBKGW24.js.map +1 -0
  5. package/dist/index.d.ts +6 -0
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +1599 -604
  8. package/dist/index.js.map +1 -1
  9. package/dist/integrations/abacatepay/connectorDef.d.ts +1 -1
  10. package/dist/integrations/abacatepay/sync.d.ts.map +1 -1
  11. package/dist/integrations/appmax/connectorDef.d.ts +1 -1
  12. package/dist/integrations/melhor-envio/connectorDef.d.ts +1 -1
  13. package/dist/integrations/mercadopago/connectorDef.d.ts +1 -28
  14. package/dist/integrations/mercadopago/connectorDef.d.ts.map +1 -1
  15. package/dist/lib/onboarding.d.ts +1 -1
  16. package/dist/lib/onboarding.d.ts.map +1 -1
  17. package/dist/migrations/index.d.ts +6 -0
  18. package/dist/migrations/index.d.ts.map +1 -0
  19. package/dist/settings/ShippingSettings.d.ts.map +1 -1
  20. package/dist/settings/error-message.d.ts +8 -0
  21. package/dist/settings/error-message.d.ts.map +1 -1
  22. package/dist/views/DeliveryAgentSurface.d.ts +10 -0
  23. package/dist/views/DeliveryAgentSurface.d.ts.map +1 -0
  24. package/dist/views/DeliveryAreaPicker.d.ts +9 -0
  25. package/dist/views/DeliveryAreaPicker.d.ts.map +1 -0
  26. package/dist/views/DeliveryRulesDraft.d.ts +9 -0
  27. package/dist/views/DeliveryRulesDraft.d.ts.map +1 -0
  28. package/dist/views/NewOrderPage.d.ts.map +1 -1
  29. package/dist/views/OrderDetailView.d.ts.map +1 -1
  30. package/dist/views/ProductImagesTab.d.ts.map +1 -1
  31. package/dist/views/ProductPositionsTab.d.ts.map +1 -1
  32. package/dist/views/ProductVariantsTab.d.ts.map +1 -1
  33. package/dist/views/ShipOrderForm.d.ts.map +1 -1
  34. package/dist/views/ShopPage.d.ts.map +1 -1
  35. package/dist/views/delivery-agent.d.ts +14 -0
  36. package/dist/views/delivery-agent.d.ts.map +1 -0
  37. package/dist/views/delivery-draft.d.ts +58 -0
  38. package/dist/views/delivery-draft.d.ts.map +1 -0
  39. package/dist/views/productEntity.d.ts.map +1 -1
  40. package/dist/views/shippingZoneEntity.d.ts.map +1 -1
  41. package/dist/views/stock-agent.d.ts.map +1 -1
  42. package/package.json +7 -6
  43. package/dist/ShopPage-QFIM2HDU.js.map +0 -1
  44. package/dist/chunk-2VMTPRGC.js.map +0 -1
  45. package/dist/views/ChannelsPage.d.ts +0 -7
  46. package/dist/views/ChannelsPage.d.ts.map +0 -1
package/dist/index.js CHANGED
@@ -1,16 +1,17 @@
1
- import { shopCustomerLookup, buildShopCustomerEntity, resolve, invalidateProductLists, listProductsDetailed, applyQuery, errorMessage, listProductsCached, resolveTerm, candidateMeta, packageMeasureLabel, PACKAGES_REGISTRY, packagesRegistry, SHIPPING_PROFILES_REGISTRY, SHIPPING_ZONES_REGISTRY, buildShopRegistries, locationSummary, LOCATIONS_REGISTRY } from './chunk-2VMTPRGC.js';
2
- import React9, { useState, useEffect, useRef, useCallback, useMemo } from 'react';
3
- import { createConnectionStore, connectionSettings, connectionStatus, settingsStatus, connectionRuns, tl, getSupabaseClientOptional, registerMessageDefaults, connectorRuntimeToken, CONNECTOR_RUNTIME_TOKEN_HEADER, countByTenant, getActiveTenantId, emitDataChanged, resolveDataProvider } from '@fayz-ai/core';
1
+ import { shopCustomerLookup, buildShopCustomerEntity, resolve, invalidateProductLists, listProductsDetailed, applyQuery, errorMessage, listProductsCached, resolveTerm, candidateMeta, packageMeasureLabel, PACKAGES_REGISTRY, packagesRegistry, SHIPPING_PROFILES_REGISTRY, SHIPPING_ZONES_REGISTRY, buildShopRegistries, locationSummary, LOCATIONS_REGISTRY } from './chunk-RPBKGW24.js';
2
+ import * as React13 from 'react';
3
+ import React13__default, { useState, useEffect, useRef, useCallback, useMemo } from 'react';
4
+ import { createConnectionStore, connectionSettings, connectionStatus, settingsStatus, connectionRuns, tl, getSupabaseClientOptional, registerMessageDefaults, connectorRuntimeToken, CONNECTOR_RUNTIME_TOKEN_HEADER, countByTenant, errorMessage as errorMessage$1, getActiveTenantId, emitDataChanged, resolveDataProvider } from '@fayz-ai/core';
4
5
  import { getShopTenantId, setShopTenantResolver, buildShopMessageDefaults, registerShippingLabelProvider, getShopProvider, buildShopDomainEvents, axesFromOptions, planVariantMatrix, validateVariantAxes, hasOnlyDefaultVariant, MAX_VARIANT_AXES, leavesNothingSellable, productImageToMediaFile, formatVariantCombination, applyVariantMatrix } from '@fayz-ai/shop';
5
- import { fayzConnectorOAuth, useOrganizationStore, registerEntityPath, registerPersonLookup, createCrudPage, adminNavigateTo, useAgentSurface, askAI, AskAIButton, CrudFormPage, SettingsGroup, SelectRow, ToggleRow, useConnectors, useConnectorsPanelAvailable, openConnectorsPanel, MultiSelectRow, AddConnectorButton, AddConnectorPanel, ConnectorDetail, RegistryCrudView, TextRow, ConnectorRow, ConnectorMark, StateChip } from '@fayz-ai/admin';
6
- import { Package, Eye, FileEdit, PackageX, UsersRound, Users, UserX, Filter, ShoppingBag, Repeat, CircleDollarSign, Tag, TicketCheck, Layers, CreditCard, ImageOff, Link2, MapPin, Music, Camera, MessageCircle, Globe, Plus, Loader2, WandSparkles, ChevronLeft, ChevronRight, Download, RefreshCw, Trash2, Upload, ExternalLink, Check, X, AlertTriangle, Box, ChevronDown, Film, FileText, Warehouse, ArrowRight, Car, Truck, Info } from 'lucide-react';
7
- import { PageHeaderActions, PageTabs, Card, Button, Thumbnail, PageHeader, toast, ConfirmDialog, Input, Hint, Modal, ModalContent, ModalHeader, ModalTitle, ModalBody, Skeleton } from '@fayz-ai/ui';
6
+ import { fayzConnectorOAuth, describeConnectorAccount, useOrganizationStore, registerEntityPath, registerPersonLookup, createCrudPage, adminNavigateTo, DetailTabHeader, useAgentSurface, askAI, AskAIButton, CrudFormPage, SettingsGroup, SelectRow, ToggleRow, useConnectors, useConnectorsPanelAvailable, openConnectorsPanel, MultiSelectRow, AddConnectorButton, AddConnectorPanel, ConnectorDetail, RegistryCrudView, TextRow, ConnectorRow, ConnectorMark, StateChip } from '@fayz-ai/admin';
7
+ import { Package, Eye, FileEdit, PackageX, UsersRound, Users, UserX, Filter, ShoppingBag, Repeat, CircleDollarSign, Tag, TicketCheck, Layers, CreditCard, ImageOff, Plus, Loader2, WandSparkles, ChevronLeft, ChevronRight, Download, RefreshCw, Trash2, Upload, AlertTriangle, X, ExternalLink, Box, ChevronDown, Film, FileText, Warehouse, ArrowRight, MapPin, Car, Truck, Info, Check, Sparkles, Ban, ListChecks, Search, Building2 } from 'lucide-react';
8
+ import { PageHeaderActions, PageTabs, Card, Button, Modal, ModalContent, ModalHeader, ModalTitle, ModalDescription, ModalFooter, Thumbnail, PageHeader, toast, ConfirmDialog, Hint, ModalBody, Skeleton } from '@fayz-ai/ui';
8
9
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
9
10
  import { listStockReasonCodes, resolveStockStore, createSupabaseStockStore } from '@fayz-ai/shop/inventory';
10
11
  import { normalizeMovementType } from '@fayz-ai/plugin-inventory';
11
12
  import { advanceWindow, createRateLimiter, sliceWindow, paginate, reconcileMirror, serializeCursor } from '@fayz-ai/core/connector-runtime';
12
13
 
13
- function buildShopOnboarding() {
14
+ function buildShopOnboarding(siteRoute = "/site") {
14
15
  return {
15
16
  id: "shop",
16
17
  title: "Sua loja no ar",
@@ -86,22 +87,22 @@ function buildShopOnboarding() {
86
87
  {
87
88
  key: "storefront",
88
89
  title: "Configure sua loja online",
89
- description: "O endere\xE7o que voc\xEA manda para o cliente \u2014 e os canais que apontam para ela.",
90
+ description: "O endere\xE7o que voc\xEA manda para o cliente \u2014 e como a vitrine est\xE1 hoje.",
90
91
  icon: "Store",
91
- route: "/store",
92
+ route: siteRoute,
92
93
  priority: "high",
93
94
  // A plataforma chama de "publique seu site"; numa loja é a vitrine, e
94
95
  // quem sabe configurá-la é este módulo.
95
96
  replaces: "publish",
96
- agentHint: "Confira o endere\xE7o da vitrine e os canais ativos. Publicar sem frete configurado gera pedido que ningu\xE9m consegue entregar.",
97
- howTo: 'Canais de venda (o item "Sua loja" na barra lateral). A Loja Online \xE9 a sua vitrine: confira o endere\xE7o, veja como est\xE1 e publique. Esse link \xE9 o que vai para a bio do Instagram e para o WhatsApp.',
97
+ agentHint: "Confira como a vitrine est\xE1 e o endere\xE7o dela. Publicar sem frete configurado gera pedido que ningu\xE9m consegue entregar.",
98
+ howTo: 'O construtor do site (o "Editar" no item "Sua loja", na barra lateral). Veja como a vitrine est\xE1, ajuste o que precisar conversando com o assistente e publique. Esse link \xE9 o que vai para a bio do Instagram e para o WhatsApp.',
98
99
  celebrate: "Vitrine pronta \u2728",
99
100
  script: [
100
101
  { kind: "say", text: "Agora a vitrine \u2014 o endere\xE7o que voc\xEA manda para o cliente." },
101
- { kind: "goto", route: "/store" },
102
+ { kind: "goto", route: siteRoute },
102
103
  {
103
104
  kind: "ask",
104
- text: "Estou na tela de canais de venda. Confira comigo o endere\xE7o da Loja Online e me diga o que falta para publicar."
105
+ text: "Estou vendo o site da loja. Confira comigo como ele est\xE1 e me diga o que falta para publicar."
105
106
  }
106
107
  ]
107
108
  },
@@ -174,7 +175,7 @@ function buildShopOnboarding() {
174
175
  waitForUser: true
175
176
  }
176
177
  ],
177
- check: async () => await countByTenant("plg_shop_orders") > 0,
178
+ check: async () => await countByTenant("v_shop_orders") > 0,
178
179
  tour: {
179
180
  id: "shop.orders",
180
181
  stops: [
@@ -193,17 +194,17 @@ function buildShopOnboarding() {
193
194
 
194
195
  // src/integrations/melhor-envio/labelProvider.ts
195
196
  var MELHOR_ENVIO_CARRIER = "melhor-envio";
196
- function createMelhorEnvioLabelProvider(resolve4) {
197
+ function createMelhorEnvioLabelProvider(resolve3) {
197
198
  const unsupported = () => {
198
199
  throw new Error("Este painel n\xE3o sabe comprar etiquetas nesta loja.");
199
200
  };
200
201
  return {
201
202
  id: MELHOR_ENVIO_CARRIER,
202
203
  label: "Melhor Envio",
203
- quote: async (orderId) => await resolve4().quoteShippingLabel?.(orderId, MELHOR_ENVIO_CARRIER) ?? unsupported(),
204
- buy: async (orderId, serviceId) => await resolve4().buyShippingLabel?.(orderId, serviceId, MELHOR_ENVIO_CARRIER) ?? unsupported(),
205
- print: async (orderId) => await resolve4().printShippingLabel?.(orderId, MELHOR_ENVIO_CARRIER) ?? null,
206
- track: async (orderId) => await resolve4().trackShippingLabel?.(orderId, MELHOR_ENVIO_CARRIER) ?? null
204
+ quote: async (orderId) => await resolve3().quoteShippingLabel?.(orderId, MELHOR_ENVIO_CARRIER) ?? unsupported(),
205
+ buy: async (orderId, serviceId) => await resolve3().buyShippingLabel?.(orderId, serviceId, MELHOR_ENVIO_CARRIER) ?? unsupported(),
206
+ print: async (orderId) => await resolve3().printShippingLabel?.(orderId, MELHOR_ENVIO_CARRIER) ?? null,
207
+ track: async (orderId) => await resolve3().trackShippingLabel?.(orderId, MELHOR_ENVIO_CARRIER) ?? null
207
208
  };
208
209
  }
209
210
  function ProductImagesTab({ item, getProvider }) {
@@ -255,27 +256,34 @@ function ProductImagesTab({ item, getProvider }) {
255
256
  }
256
257
  const update = getProvider().updateProductImage;
257
258
  return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
258
- /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center justify-between gap-3", children: [
259
- /* @__PURE__ */ jsxs("div", { children: [
260
- /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: "Fotos do produto" }),
261
- /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "A primeira foto \xE9 a capa \u2014 aparece no cat\xE1logo, no carrinho e no pedido. As demais viram o carrossel na p\xE1gina do produto." })
262
- ] }),
263
- /* @__PURE__ */ jsxs("label", { className: "inline-flex cursor-pointer items-center rounded-md bg-primary px-3 py-1.5 text-sm font-medium text-primary-foreground transition hover:opacity-90", children: [
264
- busy ? "Enviando\u2026" : "Adicionar fotos",
265
- /* @__PURE__ */ jsx(
266
- "input",
267
- {
268
- ref: fileRef,
269
- type: "file",
270
- accept: "image/*",
271
- multiple: true,
272
- disabled: busy,
273
- className: "hidden",
274
- onChange: (event) => void upload(event.target.files)
275
- }
259
+ /* @__PURE__ */ jsx(
260
+ DetailTabHeader,
261
+ {
262
+ title: "Fotos do produto",
263
+ description: "A primeira foto \xE9 a capa \u2014 aparece no cat\xE1logo, no carrinho e no pedido. As demais viram o carrossel na p\xE1gina do produto.",
264
+ actions: (
265
+ // The same Button as every other tab. It used to be a <label> hand-
266
+ // painted to look like one, which drifted the moment the real button
267
+ // changed — different height, no icon, no disabled state.
268
+ /* @__PURE__ */ jsxs(Button, { disabled: busy, onClick: () => fileRef.current?.click(), children: [
269
+ /* @__PURE__ */ jsx(Plus, { className: "mr-1.5 h-4 w-4" }),
270
+ busy ? "Enviando\u2026" : "Adicionar fotos"
271
+ ] })
276
272
  )
277
- ] })
278
- ] }),
273
+ }
274
+ ),
275
+ /* @__PURE__ */ jsx(
276
+ "input",
277
+ {
278
+ ref: fileRef,
279
+ type: "file",
280
+ accept: "image/*",
281
+ multiple: true,
282
+ disabled: busy,
283
+ className: "hidden",
284
+ onChange: (event) => void upload(event.target.files)
285
+ }
286
+ ),
279
287
  error && /* @__PURE__ */ jsx("p", { className: "rounded-md border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-800", children: error }),
280
288
  images.length === 0 ? /* @__PURE__ */ jsx("p", { className: "rounded-lg border border-dashed px-4 py-10 text-center text-sm text-muted-foreground", children: "Nenhuma foto ainda. Produtos com mais de uma foto vendem melhor \u2014 mostre a embalagem, o produto pronto e uma refer\xEAncia de tamanho." }) : /* @__PURE__ */ jsx("ul", { className: "fz-cards gap-3", children: images.map((image, index) => /* @__PURE__ */ jsxs("li", { className: "overflow-hidden rounded-lg border", children: [
281
289
  /* @__PURE__ */ jsxs("div", { className: "relative aspect-square bg-muted", children: [
@@ -353,20 +361,21 @@ function ProductVariantsTab({ item, getProvider }) {
353
361
  const product = item;
354
362
  const shop = getProvider();
355
363
  const supported = typeof shop.getProductVariants === "function" && typeof shop.saveProductOptions === "function" && typeof shop.createProductVariant === "function" && typeof shop.updateProductVariant === "function";
356
- const [axes, setAxes] = React9.useState([]);
357
- const [variants, setVariants] = React9.useState([]);
358
- const [busy, setBusy] = React9.useState(false);
359
- const [error, setError] = React9.useState(null);
360
- const [selected, setSelected] = React9.useState(/* @__PURE__ */ new Set());
361
- const [dirty, setDirty] = React9.useState(false);
362
- const reload = React9.useCallback(async () => {
364
+ const [axes, setAxes] = React13__default.useState([]);
365
+ const [variants, setVariants] = React13__default.useState([]);
366
+ const [busy, setBusy] = React13__default.useState(false);
367
+ const [adding, setAdding] = React13__default.useState(false);
368
+ const [error, setError] = React13__default.useState(null);
369
+ const [selected, setSelected] = React13__default.useState(/* @__PURE__ */ new Set());
370
+ const [dirty, setDirty] = React13__default.useState(false);
371
+ const reload = React13__default.useCallback(async () => {
363
372
  const set = await getProvider().getProductVariants(product.id, { includeArchived: true });
364
373
  setAxes(axesFromOptions(set.options));
365
374
  setVariants(set.variants);
366
375
  setSelected(/* @__PURE__ */ new Set());
367
376
  setDirty(false);
368
377
  }, [getProvider, product.id]);
369
- React9.useEffect(() => {
378
+ React13__default.useEffect(() => {
370
379
  if (!supported) return;
371
380
  void reload().catch((e) => setError(message(e)));
372
381
  }, [reload, supported]);
@@ -382,7 +391,7 @@ function ProductVariantsTab({ item, getProvider }) {
382
391
  setBusy(false);
383
392
  }
384
393
  }
385
- const plan = React9.useMemo(() => planVariantMatrix(axes, variants), [axes, variants]);
394
+ const plan = React13__default.useMemo(() => planVariantMatrix(axes, variants), [axes, variants]);
386
395
  const axisError = validateVariantAxes(axes);
387
396
  const onlyDefault = hasOnlyDefaultVariant(variants);
388
397
  const rows = plan.rows;
@@ -396,25 +405,40 @@ function ProductVariantsTab({ item, getProvider }) {
396
405
  }
397
406
  return /* @__PURE__ */ jsxs("div", { className: "space-y-6", children: [
398
407
  /* @__PURE__ */ jsxs("section", { className: "space-y-3", children: [
399
- /* @__PURE__ */ jsxs("header", { className: "flex flex-wrap items-start justify-between gap-3", children: [
400
- /* @__PURE__ */ jsxs("div", { children: [
401
- /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: "Op\xE7\xF5es" }),
402
- /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Tamanho, cor, sabor. Cada combina\xE7\xE3o vira uma variante com pre\xE7o, SKU e estoque pr\xF3prios \u2014 \xE9 assim que P e GG deixam de dividir o mesmo contador." })
403
- ] }),
404
- /* @__PURE__ */ jsxs(
405
- "button",
406
- {
407
- type: "button",
408
- disabled: busy || axes.length >= MAX_VARIANT_AXES,
409
- onClick: () => patchAxes([...axes, { name: "", values: [] }]),
410
- className: "inline-flex items-center gap-1.5 rounded-md border px-2.5 py-1.5 text-xs font-medium transition hover:bg-muted disabled:opacity-40",
411
- children: [
412
- /* @__PURE__ */ jsx(Plus, { className: "h-3.5 w-3.5" }),
413
- " Adicionar op\xE7\xE3o"
414
- ]
408
+ /* @__PURE__ */ jsx(
409
+ DetailTabHeader,
410
+ {
411
+ title: "Op\xE7\xF5es",
412
+ description: "Tamanho, cor, sabor. Cada combina\xE7\xE3o vira uma variante com pre\xE7o, SKU e estoque pr\xF3prios \u2014 \xE9 assim que P e GG deixam de dividir o mesmo contador.",
413
+ actions: (
414
+ // Same Button as the other two tabs. It used to be a bare <button>
415
+ // at text-xs with its own border, which read as a lesser control
416
+ // than "Dar entrada" one tab over, for no reason anybody chose.
417
+ /* @__PURE__ */ jsxs(
418
+ Button,
419
+ {
420
+ disabled: busy || axes.length >= MAX_VARIANT_AXES,
421
+ onClick: () => setAdding(true),
422
+ children: [
423
+ /* @__PURE__ */ jsx(Plus, { className: "mr-1.5 h-4 w-4" }),
424
+ " Adicionar op\xE7\xE3o"
425
+ ]
426
+ }
427
+ )
428
+ )
429
+ }
430
+ ),
431
+ /* @__PURE__ */ jsx(
432
+ NewAxisModal,
433
+ {
434
+ open: adding,
435
+ onClose: () => setAdding(false),
436
+ onCreate: (axis) => {
437
+ patchAxes([...axes, axis]);
438
+ setAdding(false);
415
439
  }
416
- )
417
- ] }),
440
+ }
441
+ ),
418
442
  axes.length === 0 ? /* @__PURE__ */ jsx("p", { className: "rounded-lg border border-dashed px-4 py-8 text-center text-sm text-muted-foreground", children: "Este produto \xE9 vendido de um jeito s\xF3. Adicione uma op\xE7\xE3o se ele tem tamanhos, cores ou sabores." }) : /* @__PURE__ */ jsx("ul", { className: "space-y-3", children: axes.map((axis, index) => /* @__PURE__ */ jsx(
419
443
  AxisEditor,
420
444
  {
@@ -483,7 +507,7 @@ function ProductVariantsTab({ item, getProvider }) {
483
507
  ] });
484
508
  }
485
509
  function AxisEditor({ axis, index, count, disabled, onChange, onMove, onRemove }) {
486
- const [draft, setDraft] = React9.useState("");
510
+ const [draft2, setDraft] = React13__default.useState("");
487
511
  function commit(raw) {
488
512
  const values = raw.split(",").map((v) => v.trim()).filter(Boolean);
489
513
  if (values.length === 0) return;
@@ -538,16 +562,16 @@ function AxisEditor({ axis, index, count, disabled, onChange, onMove, onRemove }
538
562
  /* @__PURE__ */ jsx(
539
563
  "input",
540
564
  {
541
- value: draft,
565
+ value: draft2,
542
566
  disabled,
543
567
  onChange: (e) => setDraft(e.target.value),
544
568
  onKeyDown: (e) => {
545
569
  if (e.key === "Enter") {
546
570
  e.preventDefault();
547
- commit(draft);
571
+ commit(draft2);
548
572
  }
549
573
  },
550
- onBlur: () => commit(draft),
574
+ onBlur: () => commit(draft2),
551
575
  placeholder: axis.values.length === 0 ? "P, M, G" : "adicionar",
552
576
  "aria-label": `Valores da op\xE7\xE3o ${index + 1}`,
553
577
  className: "h-7 min-w-28 flex-1 rounded-input border border-dashed border-input bg-transparent px-2 text-xs"
@@ -567,14 +591,17 @@ function VariantGrid({ rows, axes, product, busy, dirty, selected, onSelect, onS
567
591
  onSelect(next);
568
592
  }
569
593
  return /* @__PURE__ */ jsxs("section", { className: "space-y-3", children: [
570
- /* @__PURE__ */ jsxs("header", { children: [
571
- /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: "Variantes" }),
572
- /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground", children: [
573
- "Pre\xE7o vazio herda o pre\xE7o do produto \u2014 o cat\xE1logo em que todo tamanho custa igual n\xE3o precisa repetir o n\xFAmero ",
574
- rows.length,
575
- " vezes. Medida vazia herda a do produto, do mesmo jeito."
576
- ] })
577
- ] }),
594
+ /* @__PURE__ */ jsx(
595
+ DetailTabHeader,
596
+ {
597
+ title: "Variantes",
598
+ description: /* @__PURE__ */ jsxs(Fragment, { children: [
599
+ "Pre\xE7o vazio herda o pre\xE7o do produto \u2014 o cat\xE1logo em que todo tamanho custa igual n\xE3o precisa repetir o n\xFAmero ",
600
+ rows.length,
601
+ " vezes. Medida vazia herda a do produto, do mesmo jeito."
602
+ ] })
603
+ }
604
+ ),
578
605
  dirty && /* @__PURE__ */ jsx(Notice, { children: "As linhas abaixo ainda refletem o que est\xE1 salvo. Salve as op\xE7\xF5es para gerar as novas combina\xE7\xF5es." }),
579
606
  selected.size > 0 && /* @__PURE__ */ jsx(
580
607
  BulkBar,
@@ -756,9 +783,9 @@ function VariantRow({ row, axes, product, busy, checked, onToggle, onSave, onSto
756
783
  ] });
757
784
  }
758
785
  function BulkBar({ count, disabled, onApply, onClear }) {
759
- const [price, setPrice] = React9.useState("");
760
- const [weight, setWeight] = React9.useState("");
761
- const [unit, setUnit] = React9.useState("");
786
+ const [price, setPrice] = React13__default.useState("");
787
+ const [weight, setWeight] = React13__default.useState("");
788
+ const [unit, setUnit] = React13__default.useState("");
762
789
  return /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2 rounded-lg border bg-muted/30 px-3 py-2 text-xs", children: [
763
790
  /* @__PURE__ */ jsxs("span", { className: "font-medium", children: [
764
791
  count,
@@ -837,19 +864,19 @@ function parseNumber(raw) {
837
864
  return Number.isFinite(n) && n >= 0 ? n : null;
838
865
  }
839
866
  function NumberCell({ value, disabled, placeholder, onCommit, width = "w-20", integer }) {
840
- const [draft, setDraft] = React9.useState(value == null ? "" : String(value));
841
- React9.useEffect(() => {
867
+ const [draft2, setDraft] = React13__default.useState(value == null ? "" : String(value));
868
+ React13__default.useEffect(() => {
842
869
  setDraft(value == null ? "" : String(value));
843
870
  }, [value]);
844
871
  return /* @__PURE__ */ jsx(
845
872
  "input",
846
873
  {
847
- value: draft,
874
+ value: draft2,
848
875
  disabled,
849
876
  placeholder,
850
877
  onChange: (e) => setDraft(e.target.value),
851
878
  onBlur: () => {
852
- const parsed = parseNumber(draft);
879
+ const parsed = parseNumber(draft2);
853
880
  onCommit(parsed == null ? null : integer ? Math.trunc(parsed) : parsed);
854
881
  },
855
882
  className: `h-7 ${width} rounded-input border border-input bg-background px-2 text-xs tabular-nums`
@@ -857,18 +884,18 @@ function NumberCell({ value, disabled, placeholder, onCommit, width = "w-20", in
857
884
  );
858
885
  }
859
886
  function TextCell({ value, disabled, placeholder, onCommit }) {
860
- const [draft, setDraft] = React9.useState(value);
861
- React9.useEffect(() => {
887
+ const [draft2, setDraft] = React13__default.useState(value);
888
+ React13__default.useEffect(() => {
862
889
  setDraft(value);
863
890
  }, [value]);
864
891
  return /* @__PURE__ */ jsx(
865
892
  "input",
866
893
  {
867
- value: draft,
894
+ value: draft2,
868
895
  disabled,
869
896
  placeholder,
870
897
  onChange: (e) => setDraft(e.target.value),
871
- onBlur: () => onCommit(draft.trim()),
898
+ onBlur: () => onCommit(draft2.trim()),
872
899
  className: "h-7 w-28 rounded-input border border-input bg-background px-2 text-xs"
873
900
  }
874
901
  );
@@ -903,7 +930,96 @@ function describePlan(plan) {
903
930
  return `${parts.join(", ")}. Combina\xE7\xF5es removidas s\xE3o arquivadas, nunca exclu\xEDdas \u2014 o estoque delas continua no hist\xF3rico.`;
904
931
  }
905
932
  function message(error) {
906
- return error instanceof Error ? error.message : String(error);
933
+ return errorMessage$1(error);
934
+ }
935
+ function NewAxisModal({ open, onClose, onCreate }) {
936
+ const [name, setName] = React13__default.useState("");
937
+ const [values, setValues] = React13__default.useState([""]);
938
+ React13__default.useEffect(() => {
939
+ if (open) {
940
+ setName("");
941
+ setValues([""]);
942
+ }
943
+ }, [open]);
944
+ function setValue(index, next) {
945
+ setValues((prev) => {
946
+ const out = [...prev];
947
+ out[index] = next;
948
+ if (index === out.length - 1 && next.trim()) out.push("");
949
+ return out;
950
+ });
951
+ }
952
+ function removeValue(index) {
953
+ setValues((prev) => {
954
+ const out = prev.filter((_, i) => i !== index);
955
+ return out.length === 0 || out[out.length - 1].trim() ? [...out, ""] : out;
956
+ });
957
+ }
958
+ const filled = values.map((v) => v.trim()).filter(Boolean);
959
+ const unique = [...new Set(filled)];
960
+ const hasDuplicate = unique.length !== filled.length;
961
+ const ready = name.trim().length > 0 && unique.length > 0 && !hasDuplicate;
962
+ return /* @__PURE__ */ jsx(Modal, { open, onOpenChange: (o) => {
963
+ if (!o) onClose();
964
+ }, children: /* @__PURE__ */ jsxs(ModalContent, { className: "sm:max-w-md", children: [
965
+ /* @__PURE__ */ jsxs(ModalHeader, { children: [
966
+ /* @__PURE__ */ jsx(ModalTitle, { children: "Nova op\xE7\xE3o" }),
967
+ /* @__PURE__ */ jsx(ModalDescription, { children: "Cada combina\xE7\xE3o de op\xE7\xF5es vira uma variante com pre\xE7o, SKU e estoque pr\xF3prios." })
968
+ ] }),
969
+ /* @__PURE__ */ jsxs("div", { className: "space-y-3 py-2", children: [
970
+ /* @__PURE__ */ jsxs("label", { className: "block", children: [
971
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground", children: "Nome" }),
972
+ /* @__PURE__ */ jsx(
973
+ "input",
974
+ {
975
+ autoFocus: true,
976
+ value: name,
977
+ onChange: (e) => setName(e.target.value),
978
+ placeholder: "Tamanho",
979
+ className: "mt-1 h-9 w-full rounded-md border bg-background px-2.5 text-sm outline-none focus:border-primary"
980
+ }
981
+ )
982
+ ] }),
983
+ /* @__PURE__ */ jsxs("div", { children: [
984
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground", children: "Valores" }),
985
+ /* @__PURE__ */ jsx("ul", { className: "mt-1 space-y-1.5", children: values.map((value, index) => /* @__PURE__ */ jsxs("li", { className: "flex items-center gap-1.5", children: [
986
+ /* @__PURE__ */ jsx(
987
+ "input",
988
+ {
989
+ value,
990
+ onChange: (e) => setValue(index, e.target.value),
991
+ onKeyDown: (e) => {
992
+ if (e.key === "Enter") {
993
+ e.preventDefault();
994
+ if (index === values.length - 1 && value.trim()) setValue(index, value);
995
+ const rows = e.currentTarget.closest("ul")?.querySelectorAll("input");
996
+ window.setTimeout(() => rows?.[index + 1]?.focus(), 0);
997
+ }
998
+ },
999
+ placeholder: index === values.length - 1 ? "Adicionar valor" : "",
1000
+ "aria-label": `Valor ${index + 1}`,
1001
+ className: "h-9 w-full rounded-md border bg-background px-2.5 text-sm outline-none focus:border-primary"
1002
+ }
1003
+ ),
1004
+ index < values.length - 1 && /* @__PURE__ */ jsx(
1005
+ "button",
1006
+ {
1007
+ type: "button",
1008
+ onClick: () => removeValue(index),
1009
+ "aria-label": `Remover valor ${index + 1}`,
1010
+ className: "fz-tap shrink-0 rounded-md p-1.5 text-muted-foreground transition hover:bg-muted hover:text-destructive",
1011
+ children: /* @__PURE__ */ jsx(X, { className: "h-4 w-4" })
1012
+ }
1013
+ )
1014
+ ] }, index)) }),
1015
+ hasDuplicate && /* @__PURE__ */ jsx("span", { className: "mt-1 block text-[11px] text-destructive", children: "H\xE1 valores repetidos \u2014 cada um vira uma variante, ent\xE3o dois iguais viram uma s\xF3." })
1016
+ ] })
1017
+ ] }),
1018
+ /* @__PURE__ */ jsxs(ModalFooter, { children: [
1019
+ /* @__PURE__ */ jsx(Button, { variant: "ghost", onClick: onClose, children: "Cancelar" }),
1020
+ /* @__PURE__ */ jsx(Button, { disabled: !ready, onClick: () => onCreate({ name: name.trim(), values: unique }), children: "Adicionar" })
1021
+ ] })
1022
+ ] }) });
907
1023
  }
908
1024
  var KIND_OF = {
909
1025
  // `sign: 0` = o razão aceita os dois lados (ajuste corrige para cima e para baixo).
@@ -1017,8 +1133,14 @@ async function commitMovement(args) {
1017
1133
  // a Lista, e é a que o usuário estava vendo
1018
1134
  "plg_shop_product_variants",
1019
1135
  // a matriz de variantes
1020
- "plg_shop_stock_balances",
1136
+ // O saldo mudou de livro: quem move o número agora é o razão do
1137
+ // plugin-inventory, e o saldo compartilhado é `public.stock_balances`.
1138
+ // Invalidar a tabela antiga deixava as Posições mostrando o valor anterior
1139
+ // até alguém recarregar — o cache apontando para onde ninguém mais escreve.
1140
+ "stock_balances",
1021
1141
  // as Posições
1142
+ "plg_inventory_stock_movements",
1143
+ // o histórico de movimentos
1022
1144
  "products"
1023
1145
  // o arquétipo, que as telas de Estoque leem
1024
1146
  ]) {
@@ -1188,7 +1310,7 @@ function ProductPositionsTab({ item, getProvider }) {
1188
1310
  const [noteOpen, setNoteOpen] = useState(false);
1189
1311
  const [occurredOn, setOccurredOn] = useState(() => todayISO());
1190
1312
  const [saving, setSaving] = useState(false);
1191
- const quantityRef = React9.useRef(null);
1313
+ const quantityRef = React13__default.useRef(null);
1192
1314
  const load = useCallback(async () => {
1193
1315
  setLoading(true);
1194
1316
  try {
@@ -1267,7 +1389,7 @@ function ProductPositionsTab({ item, getProvider }) {
1267
1389
  const amount = Math.abs(Number(quantity) || 0);
1268
1390
  const outgoing = type === "saida" || type === "perda";
1269
1391
  const nextBalance = amount > 0 ? outgoing ? total.available - amount : total.available + amount : null;
1270
- const live = React9.useRef({ product, variantId, total, getProvider, load });
1392
+ const live = React13__default.useRef({ product, variantId, total, getProvider, load });
1271
1393
  live.current = { product, variantId, total, getProvider, load };
1272
1394
  const contribution = useMemo(() => ({
1273
1395
  id: "shop.product.positions",
@@ -1348,10 +1470,11 @@ function ProductPositionsTab({ item, getProvider }) {
1348
1470
  return /* @__PURE__ */ jsx("p", { className: missing ? "rounded-lg border border-dashed px-4 py-8 text-center text-sm text-muted-foreground" : "rounded-lg border border-destructive/40 bg-destructive/5 p-4 text-sm text-destructive", children: error });
1349
1471
  }
1350
1472
  return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
1351
- /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center justify-between gap-3", children: [
1352
- /* @__PURE__ */ jsxs("div", { children: [
1353
- /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: "Estoque do produto" }),
1354
- /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground", children: [
1473
+ /* @__PURE__ */ jsx(
1474
+ DetailTabHeader,
1475
+ {
1476
+ title: "Estoque do produto",
1477
+ description: /* @__PURE__ */ jsxs(Fragment, { children: [
1355
1478
  /* @__PURE__ */ jsx("span", { className: "tabular-nums", children: total.onHand }),
1356
1479
  " em m\xE3os \xB7",
1357
1480
  " ",
@@ -1360,31 +1483,31 @@ function ProductPositionsTab({ item, getProvider }) {
1360
1483
  " ",
1361
1484
  /* @__PURE__ */ jsx("span", { className: "font-medium tabular-nums text-foreground", children: total.available }),
1362
1485
  " dispon\xEDvel. O raz\xE3o n\xE3o se edita: um erro se corrige com o movimento contr\xE1rio."
1486
+ ] }),
1487
+ actions: variantId && /* @__PURE__ */ jsxs(Fragment, { children: [
1488
+ /* @__PURE__ */ jsxs(
1489
+ Button,
1490
+ {
1491
+ variant: "ghost",
1492
+ size: "sm",
1493
+ onClick: () => askAI({
1494
+ intro: STOCK_INTRO,
1495
+ hint: "O que entrou ou saiu\u2026",
1496
+ draft: `Entrada de unidades de ${product.name}`
1497
+ }),
1498
+ children: [
1499
+ /* @__PURE__ */ jsx(WandSparkles, { className: "mr-1.5 h-3.5 w-3.5" }),
1500
+ "Com IA"
1501
+ ]
1502
+ }
1503
+ ),
1504
+ /* @__PURE__ */ jsxs(Button, { onClick: () => open("entrada"), children: [
1505
+ /* @__PURE__ */ jsx(Plus, { className: "mr-1.5 h-4 w-4" }),
1506
+ "Dar entrada"
1507
+ ] })
1363
1508
  ] })
1364
- ] }),
1365
- variantId && type === null && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
1366
- /* @__PURE__ */ jsxs(
1367
- Button,
1368
- {
1369
- variant: "ghost",
1370
- size: "sm",
1371
- onClick: () => askAI({
1372
- intro: STOCK_INTRO,
1373
- hint: "O que entrou ou saiu\u2026",
1374
- draft: `Entrada de unidades de ${product.name}`
1375
- }),
1376
- children: [
1377
- /* @__PURE__ */ jsx(WandSparkles, { className: "mr-1.5 h-3.5 w-3.5" }),
1378
- "Com IA"
1379
- ]
1380
- }
1381
- ),
1382
- /* @__PURE__ */ jsxs(Button, { onClick: () => open("entrada"), children: [
1383
- /* @__PURE__ */ jsx(Plus, { className: "mr-1.5 h-4 w-4" }),
1384
- "Dar entrada"
1385
- ] })
1386
- ] })
1387
- ] }),
1509
+ }
1510
+ ),
1388
1511
  !variantId && // Nunca desabilitar um botão sem dizer por quê. Sem variante o razão não
1389
1512
  // tem onde postar, e um "Lançar" apagado sem explicação faz o lojista
1390
1513
  // clicar três vezes e concluir que a tela está quebrada.
@@ -1397,103 +1520,110 @@ function ProductPositionsTab({ item, getProvider }) {
1397
1520
  /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground", children: "Variante" }),
1398
1521
  /* @__PURE__ */ jsx("select", { value: variantId, onChange: (e) => setVariantId(e.target.value), className: field, children: variants.map((v) => /* @__PURE__ */ jsx("option", { value: v.id, children: Object.values(v.options ?? {}).join(" \xB7 ") || v.sku || v.id.slice(0, 8) }, v.id)) })
1399
1522
  ] }),
1400
- type !== null && /* @__PURE__ */ jsxs("div", { className: "rounded-card border bg-card p-4 motion-safe:animate-zoom-in", children: [
1401
- /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1", children: MOVEMENT_TYPES.map((t) => /* @__PURE__ */ jsx(
1402
- "button",
1403
- {
1404
- type: "button",
1405
- onClick: () => setType(t.value),
1406
- className: `fz-tap rounded-button px-3 py-1.5 text-xs font-medium transition-colors ${type === t.value ? "bg-primary text-primary-foreground" : "text-muted-foreground hover:bg-muted hover:text-foreground"}`,
1407
- children: t.label
1408
- },
1409
- t.value
1410
- )) }),
1411
- /* @__PURE__ */ jsxs("div", { className: "mt-3 flex flex-wrap items-end gap-4", children: [
1412
- /* @__PURE__ */ jsxs("label", { className: "block", children: [
1413
- /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground", children: "Quantas?" }),
1414
- /* @__PURE__ */ jsx(
1415
- "input",
1416
- {
1417
- ref: quantityRef,
1418
- type: "number",
1419
- inputMode: "numeric",
1420
- min: 1,
1421
- value: quantity,
1422
- onChange: (e) => setQuantity(e.target.value),
1423
- onKeyDown: (e) => {
1424
- if (e.key === "Enter") {
1425
- e.preventDefault();
1426
- void submit();
1427
- }
1428
- if (e.key === "Escape") {
1429
- e.preventDefault();
1430
- close();
1431
- }
1432
- },
1433
- className: "mt-1 h-12 w-28 rounded-md border bg-background px-3 text-2xl font-semibold tabular-nums outline-none focus:border-primary"
1434
- }
1435
- )
1436
- ] }),
1437
- /* @__PURE__ */ jsx("p", { "aria-live": "polite", className: "pb-2 text-sm text-muted-foreground", children: nextBalance === null ? /* @__PURE__ */ jsxs(Fragment, { children: [
1438
- "Fica em ",
1439
- /* @__PURE__ */ jsx("span", { className: "font-medium tabular-nums text-foreground", children: total.available })
1440
- ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
1441
- /* @__PURE__ */ jsx("span", { className: "tabular-nums", children: total.available }),
1442
- /* @__PURE__ */ jsx("span", { className: "mx-1.5", children: "\u2192" }),
1443
- /* @__PURE__ */ jsx("span", { className: `text-base font-semibold tabular-nums ${nextBalance < 0 ? "text-destructive" : "text-foreground"}`, children: nextBalance }),
1444
- nextBalance < 0 && /* @__PURE__ */ jsx("span", { className: "ml-2 text-destructive", children: "n\xE3o h\xE1 tudo isso" })
1445
- ] }) }),
1446
- /* @__PURE__ */ jsx("span", { className: "flex-1" }),
1447
- /* @__PURE__ */ jsxs("label", { className: "block", children: [
1448
- /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground", children: "Motivo" }),
1449
- /* @__PURE__ */ jsxs("select", { value: reasonCode, onChange: (e) => setReasonCode(e.target.value), className: field, children: [
1450
- /* @__PURE__ */ jsx("option", { value: "", children: "Padr\xE3o" }),
1451
- reasons.map((r) => /* @__PURE__ */ jsx("option", { value: r.key, children: r.label }, r.key))
1523
+ /* @__PURE__ */ jsx(Modal, { open: type !== null, onOpenChange: (o) => {
1524
+ if (!o) close();
1525
+ }, children: /* @__PURE__ */ jsxs(ModalContent, { className: "sm:max-w-lg", children: [
1526
+ /* @__PURE__ */ jsxs(ModalHeader, { children: [
1527
+ /* @__PURE__ */ jsx(ModalTitle, { children: "Lan\xE7ar movimento" }),
1528
+ /* @__PURE__ */ jsx(ModalDescription, { children: "O raz\xE3o n\xE3o se edita: um erro se corrige com o movimento contr\xE1rio." })
1529
+ ] }),
1530
+ /* @__PURE__ */ jsxs("div", { className: "p-1", children: [
1531
+ /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1", children: MOVEMENT_TYPES.map((t) => /* @__PURE__ */ jsx(
1532
+ "button",
1533
+ {
1534
+ type: "button",
1535
+ onClick: () => setType(t.value),
1536
+ className: `fz-tap rounded-button px-3 py-1.5 text-xs font-medium transition-colors ${type === t.value ? "bg-primary text-primary-foreground" : "text-muted-foreground hover:bg-muted hover:text-foreground"}`,
1537
+ children: t.label
1538
+ },
1539
+ t.value
1540
+ )) }),
1541
+ /* @__PURE__ */ jsxs("div", { className: "mt-3 space-y-3", children: [
1542
+ /* @__PURE__ */ jsxs("label", { className: "block", children: [
1543
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground", children: "Quantidade" }),
1544
+ /* @__PURE__ */ jsx(
1545
+ "input",
1546
+ {
1547
+ ref: quantityRef,
1548
+ type: "number",
1549
+ inputMode: "numeric",
1550
+ min: 1,
1551
+ value: quantity,
1552
+ onChange: (e) => setQuantity(e.target.value),
1553
+ onKeyDown: (e) => {
1554
+ if (e.key === "Enter") {
1555
+ e.preventDefault();
1556
+ void submit();
1557
+ }
1558
+ if (e.key === "Escape") {
1559
+ e.preventDefault();
1560
+ close();
1561
+ }
1562
+ },
1563
+ placeholder: "0",
1564
+ className: `${field} font-semibold tabular-nums`
1565
+ }
1566
+ ),
1567
+ /* @__PURE__ */ jsx("span", { "aria-live": "polite", className: "mt-1 block text-xs text-muted-foreground", children: nextBalance === null ? /* @__PURE__ */ jsxs(Fragment, { children: [
1568
+ "Fica em ",
1569
+ /* @__PURE__ */ jsx("span", { className: "font-medium tabular-nums text-foreground", children: total.available })
1570
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
1571
+ /* @__PURE__ */ jsx("span", { className: "tabular-nums", children: total.available }),
1572
+ /* @__PURE__ */ jsx("span", { className: "mx-1.5", children: "\u2192" }),
1573
+ /* @__PURE__ */ jsx("span", { className: `text-base font-semibold tabular-nums ${nextBalance < 0 ? "text-destructive" : "text-foreground"}`, children: nextBalance }),
1574
+ nextBalance < 0 && /* @__PURE__ */ jsx("span", { className: "ml-2 text-destructive", children: "n\xE3o h\xE1 tudo isso" })
1575
+ ] }) })
1576
+ ] }),
1577
+ /* @__PURE__ */ jsxs("label", { className: "block", children: [
1578
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground", children: "Motivo" }),
1579
+ /* @__PURE__ */ jsxs("select", { value: reasonCode, onChange: (e) => setReasonCode(e.target.value), className: field, children: [
1580
+ /* @__PURE__ */ jsx("option", { value: "", children: "Padr\xE3o" }),
1581
+ reasons.map((r) => /* @__PURE__ */ jsx("option", { value: r.key, children: r.label }, r.key))
1582
+ ] })
1583
+ ] }),
1584
+ /* @__PURE__ */ jsxs("label", { className: "block", children: [
1585
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground", children: type === "entrada" ? "Chegou em" : "Quando" }),
1586
+ /* @__PURE__ */ jsx(
1587
+ "input",
1588
+ {
1589
+ type: "date",
1590
+ value: occurredOn,
1591
+ max: todayISO(),
1592
+ onChange: (e) => setOccurredOn(e.target.value || todayISO()),
1593
+ className: field
1594
+ }
1595
+ )
1452
1596
  ] })
1453
1597
  ] }),
1454
- /* @__PURE__ */ jsxs("label", { className: "block", children: [
1455
- /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground", children: type === "entrada" ? "Chegou em" : "Quando" }),
1598
+ noteOpen ? /* @__PURE__ */ jsxs("label", { className: "mt-3 block", children: [
1599
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground", children: "Observa\xE7\xE3o" }),
1456
1600
  /* @__PURE__ */ jsx(
1457
1601
  "input",
1458
1602
  {
1459
- type: "date",
1460
- value: occurredOn,
1461
- max: todayISO(),
1462
- onChange: (e) => setOccurredOn(e.target.value || todayISO()),
1463
- className: field
1603
+ autoFocus: true,
1604
+ value: note,
1605
+ onChange: (e) => setNote(e.target.value),
1606
+ className: field,
1607
+ placeholder: "Nota fiscal, quem entregou\u2026"
1464
1608
  }
1465
1609
  )
1466
- ] })
1467
- ] }),
1468
- noteOpen ? /* @__PURE__ */ jsxs("label", { className: "mt-3 block", children: [
1469
- /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground", children: "Observa\xE7\xE3o" }),
1470
- /* @__PURE__ */ jsx(
1471
- "input",
1610
+ ] }) : /* @__PURE__ */ jsx(
1611
+ "button",
1472
1612
  {
1473
- autoFocus: true,
1474
- value: note,
1475
- onChange: (e) => setNote(e.target.value),
1476
- className: field,
1477
- placeholder: "Nota fiscal, quem entregou\u2026"
1613
+ type: "button",
1614
+ onClick: () => setNoteOpen(true),
1615
+ className: "mt-2 text-xs font-medium text-muted-foreground underline-offset-2 hover:text-foreground hover:underline",
1616
+ children: "+ observa\xE7\xE3o"
1478
1617
  }
1479
1618
  )
1480
- ] }) : /* @__PURE__ */ jsx(
1481
- "button",
1482
- {
1483
- type: "button",
1484
- onClick: () => setNoteOpen(true),
1485
- className: "mt-2 text-xs font-medium text-muted-foreground underline-offset-2 hover:text-foreground hover:underline",
1486
- children: "+ observa\xE7\xE3o"
1487
- }
1488
- ),
1489
- /* @__PURE__ */ jsxs("div", { className: "mt-4 flex items-center gap-2", children: [
1490
- /* @__PURE__ */ jsx(Button, { onClick: () => void submit(), disabled: saving || amount === 0, children: saving ? "Lan\xE7ando\u2026" : "Lan\xE7ar" }),
1491
- /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", onClick: close, disabled: saving, children: "Cancelar" }),
1492
- /* @__PURE__ */ jsx("span", { className: "flex-1" }),
1619
+ ] }),
1620
+ /* @__PURE__ */ jsxs(ModalFooter, { children: [
1493
1621
  amount === 0 && // O motivo de o botão estar apagado, ao lado dele.
1494
- /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "Diga quantas unidades" })
1622
+ /* @__PURE__ */ jsx("span", { className: "mr-auto text-xs text-muted-foreground", children: "Diga quantas unidades" }),
1623
+ /* @__PURE__ */ jsx(Button, { variant: "ghost", onClick: close, disabled: saving, children: "Cancelar" }),
1624
+ /* @__PURE__ */ jsx(Button, { onClick: () => void submit(), disabled: saving || amount === 0, children: saving ? "Lan\xE7ando\u2026" : "Lan\xE7ar" })
1495
1625
  ] })
1496
- ] }),
1626
+ ] }) }),
1497
1627
  variantId && (rows.length === 0 ? /* @__PURE__ */ jsx("p", { className: "rounded-lg border border-dashed px-4 py-8 text-center text-sm text-muted-foreground", children: "Nenhuma posi\xE7\xE3o ainda \u2014 a primeira entrada abre o saldo." }) : /* @__PURE__ */ jsx("ul", { className: "space-y-2", children: rows.map((r) => /* @__PURE__ */ jsxs("li", { className: "flex items-center gap-3 rounded-card border bg-card p-3", children: [
1498
1628
  /* @__PURE__ */ jsx("span", { className: "flex-1 text-sm font-medium", children: r.location }),
1499
1629
  /* @__PURE__ */ jsxs("span", { className: "text-xs text-muted-foreground", children: [
@@ -1533,20 +1663,20 @@ function shippingRegistryIdFromHash(hash) {
1533
1663
  }
1534
1664
  var PACKAGES_ROUTE = shippingRegistryPath(PACKAGES_REGISTRY);
1535
1665
  function PackageSelect({ value, onChange, getProvider }) {
1536
- const shop = React9.useMemo(() => getProvider?.() ?? getShopProvider(), [getProvider]);
1537
- const [rows, setRows] = React9.useState(null);
1538
- const [open, setOpen] = React9.useState(false);
1539
- const [creating, setCreating] = React9.useState(false);
1540
- const ref = React9.useRef(null);
1541
- const load = React9.useCallback(() => {
1666
+ const shop = React13__default.useMemo(() => getProvider?.() ?? getShopProvider(), [getProvider]);
1667
+ const [rows, setRows] = React13__default.useState(null);
1668
+ const [open, setOpen] = React13__default.useState(false);
1669
+ const [creating, setCreating] = React13__default.useState(false);
1670
+ const ref = React13__default.useRef(null);
1671
+ const load = React13__default.useCallback(() => {
1542
1672
  if (typeof shop.listShippingPackages !== "function") {
1543
1673
  setRows([]);
1544
1674
  return;
1545
1675
  }
1546
1676
  void shop.listShippingPackages().then(setRows).catch(() => setRows([]));
1547
1677
  }, [shop]);
1548
- React9.useEffect(load, [load]);
1549
- React9.useEffect(() => {
1678
+ React13__default.useEffect(load, [load]);
1679
+ React13__default.useEffect(() => {
1550
1680
  if (!open) return;
1551
1681
  const away = (event) => {
1552
1682
  if (ref.current && !ref.current.contains(event.target)) setOpen(false);
@@ -1641,8 +1771,8 @@ function PackageSelect({ value, onChange, getProvider }) {
1641
1771
  ] });
1642
1772
  }
1643
1773
  function NewPackageModal({ onClose, onCreated }) {
1644
- const [error, setError] = React9.useState(null);
1645
- const provider = React9.useMemo(
1774
+ const [error, setError] = React13__default.useState(null);
1775
+ const provider = React13__default.useMemo(
1646
1776
  () => resolveDataProvider(packagesRegistry.entity),
1647
1777
  []
1648
1778
  );
@@ -1688,9 +1818,9 @@ function Option({ label, hint, active, onClick }) {
1688
1818
  );
1689
1819
  }
1690
1820
  function ShippingProfileSelect({ value, onChange, getProvider }) {
1691
- const shop = React9.useMemo(() => getProvider?.() ?? getShopProvider(), [getProvider]);
1692
- const [rows, setRows] = React9.useState(null);
1693
- React9.useEffect(() => {
1821
+ const shop = React13__default.useMemo(() => getProvider?.() ?? getShopProvider(), [getProvider]);
1822
+ const [rows, setRows] = React13__default.useState(null);
1823
+ React13__default.useEffect(() => {
1694
1824
  let cancelled = false;
1695
1825
  if (typeof shop.listShippingProfiles !== "function") {
1696
1826
  setRows([]);
@@ -1924,6 +2054,30 @@ function buildShopProductEntity(currencyCode = "BRL", getProvider) {
1924
2054
  hint: "Menor aparece primeiro.",
1925
2055
  showInTable: false
1926
2056
  },
2057
+ // — Busca (SEO) —
2058
+ // A coleção já tinha estes dois; o produto — que é a página onde alguém
2059
+ // CAI vindo do Google — não tinha nenhum, então cada página herdava o que
2060
+ // a vitrine inventasse a partir do nome.
2061
+ {
2062
+ key: "seoTitle",
2063
+ label: "T\xEDtulo da p\xE1gina",
2064
+ type: "text",
2065
+ group: "seo",
2066
+ span: 2,
2067
+ placeholder: "T\xEDtulo exibido nos resultados de busca",
2068
+ hint: "Vazio usa o nome do produto.",
2069
+ showInTable: false
2070
+ },
2071
+ {
2072
+ key: "seoDescription",
2073
+ label: "Meta descri\xE7\xE3o",
2074
+ type: "textarea",
2075
+ group: "seo",
2076
+ span: 2,
2077
+ placeholder: "O trecho que aparece embaixo do t\xEDtulo no Google",
2078
+ hint: "Vazio usa a descri\xE7\xE3o do produto.",
2079
+ showInTable: false
2080
+ },
1927
2081
  // — Envio —
1928
2082
  // O desenho é o do Shopify e a ordem importa: o interruptor primeiro, e o
1929
2083
  // resto da seção só existe depois dele. Perguntar altura de um e-book não é
@@ -2059,6 +2213,7 @@ function buildShopProductEntity(currencyCode = "BRL", getProvider) {
2059
2213
  { id: "pricing", label: "Pre\xE7o", description: 'O pre\xE7o comparativo vira o "de" riscado na vitrine.', columns: 3 },
2060
2214
  { id: "inventory", label: "Estoque", columns: 2 },
2061
2215
  { id: "publishing", label: "Publica\xE7\xE3o", description: "Controla se o produto aparece na loja.", columns: 2 },
2216
+ { id: "seo", label: "Busca (SEO)", description: "Como este produto aparece nos resultados de busca.", columns: 2 },
2062
2217
  {
2063
2218
  id: "shipping",
2064
2219
  label: "Envio",
@@ -2478,7 +2633,7 @@ var STATUS_LABEL2 = {
2478
2633
  disabled: "Desativado"
2479
2634
  };
2480
2635
  function buildShopGiftCardEntity(currencyCode = "BRL") {
2481
- const money2 = (v) => new Intl.NumberFormat("pt-BR", { style: "currency", currency: currencyCode }).format(Number(v) || 0);
2636
+ const money3 = (v) => new Intl.NumberFormat("pt-BR", { style: "currency", currency: currencyCode }).format(Number(v) || 0);
2482
2637
  const fields = [
2483
2638
  {
2484
2639
  key: "code",
@@ -2506,7 +2661,7 @@ function buildShopGiftCardEntity(currencyCode = "BRL") {
2506
2661
  hint: "Valor de face do cart\xE3o. N\xE3o muda depois de emitido.",
2507
2662
  showInTable: true,
2508
2663
  sortable: true,
2509
- renderCell: (v) => /* @__PURE__ */ jsx("span", { className: "block text-right text-muted-foreground", children: money2(v) })
2664
+ renderCell: (v) => /* @__PURE__ */ jsx("span", { className: "block text-right text-muted-foreground", children: money3(v) })
2510
2665
  },
2511
2666
  {
2512
2667
  key: "balance",
@@ -2520,7 +2675,7 @@ function buildShopGiftCardEntity(currencyCode = "BRL") {
2520
2675
  hint: "O que ainda pode ser gasto. Cai a cada resgate.",
2521
2676
  showInTable: true,
2522
2677
  sortable: true,
2523
- renderCell: (v) => /* @__PURE__ */ jsx("span", { className: "block text-right font-medium", children: money2(v) })
2678
+ renderCell: (v) => /* @__PURE__ */ jsx("span", { className: "block text-right font-medium", children: money3(v) })
2524
2679
  },
2525
2680
  {
2526
2681
  key: "status",
@@ -2655,20 +2810,20 @@ function buildShopCustomerGroupEntity() {
2655
2810
  fieldGroups: [{ id: "basic", label: "Grupo", columns: 2 }]
2656
2811
  };
2657
2812
  }
2658
- function describeSegment(s, money2) {
2813
+ function describeSegment(s, money3) {
2659
2814
  const parts = [];
2660
2815
  if (s.minOrders != null && s.maxOrders != null) parts.push(`${s.minOrders}\u2013${s.maxOrders} pedidos`);
2661
2816
  else if (s.minOrders != null) parts.push(`${s.minOrders}+ pedidos`);
2662
2817
  else if (s.maxOrders != null) parts.push(`at\xE9 ${s.maxOrders} pedidos`);
2663
- if (s.minSpent != null && s.maxSpent != null) parts.push(`gastou ${money2(s.minSpent)}\u2013${money2(s.maxSpent)}`);
2664
- else if (s.minSpent != null) parts.push(`gastou ${money2(s.minSpent)}+`);
2665
- else if (s.maxSpent != null) parts.push(`gastou at\xE9 ${money2(s.maxSpent)}`);
2818
+ if (s.minSpent != null && s.maxSpent != null) parts.push(`gastou ${money3(s.minSpent)}\u2013${money3(s.maxSpent)}`);
2819
+ else if (s.minSpent != null) parts.push(`gastou ${money3(s.minSpent)}+`);
2820
+ else if (s.maxSpent != null) parts.push(`gastou at\xE9 ${money3(s.maxSpent)}`);
2666
2821
  if (s.joinedAfter) parts.push(`entrou ap\xF3s ${new Date(s.joinedAfter).toLocaleDateString("pt-BR")}`);
2667
2822
  if (s.joinedBefore) parts.push(`entrou antes de ${new Date(s.joinedBefore).toLocaleDateString("pt-BR")}`);
2668
2823
  return parts.length ? parts.join(" \xB7 ") : "Todos os clientes";
2669
2824
  }
2670
2825
  function buildShopCustomerSegmentEntity(currencyCode = "BRL") {
2671
- const money2 = (v) => new Intl.NumberFormat("pt-BR", { style: "currency", currency: currencyCode }).format(v);
2826
+ const money3 = (v) => new Intl.NumberFormat("pt-BR", { style: "currency", currency: currencyCode }).format(v);
2672
2827
  const fields = [
2673
2828
  {
2674
2829
  key: "name",
@@ -2745,7 +2900,7 @@ function buildShopCustomerSegmentEntity(currencyCode = "BRL") {
2745
2900
  showInForm: false,
2746
2901
  showInTable: true,
2747
2902
  sortable: false,
2748
- renderCell: (_v, row) => /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: describeSegment(row, money2) })
2903
+ renderCell: (_v, row) => /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: describeSegment(row, money3) })
2749
2904
  },
2750
2905
  {
2751
2906
  key: "matchCount",
@@ -3075,17 +3230,17 @@ function createShopCustomerSegmentDataProvider(provider) {
3075
3230
  }
3076
3231
  var DEBOUNCE_MS = 400;
3077
3232
  function useStoreSettings(provider, defaults = {}) {
3078
- const shop = React9.useMemo(
3233
+ const shop = React13__default.useMemo(
3079
3234
  () => provider ? resolve(provider) : getShopProvider(),
3080
3235
  [provider]
3081
3236
  );
3082
3237
  const supported = typeof shop.getStoreSettings === "function" && typeof shop.updateStoreSettings === "function";
3083
- const [stored, setStored] = React9.useState(null);
3084
- const [loading, setLoading] = React9.useState(supported);
3085
- const [saving, setSaving] = React9.useState(false);
3086
- const [error, setError] = React9.useState(null);
3087
- const [loadError, setLoadError] = React9.useState(null);
3088
- React9.useEffect(() => {
3238
+ const [stored, setStored] = React13__default.useState(null);
3239
+ const [loading, setLoading] = React13__default.useState(supported);
3240
+ const [saving, setSaving] = React13__default.useState(false);
3241
+ const [error, setError] = React13__default.useState(null);
3242
+ const [loadError, setLoadError] = React13__default.useState(null);
3243
+ React13__default.useEffect(() => {
3089
3244
  if (!supported) return;
3090
3245
  let cancelled = false;
3091
3246
  setLoading(true);
@@ -3101,12 +3256,12 @@ function useStoreSettings(provider, defaults = {}) {
3101
3256
  cancelled = true;
3102
3257
  };
3103
3258
  }, [shop, supported]);
3104
- const pendingRef = React9.useRef({});
3105
- const timerRef = React9.useRef(null);
3106
- React9.useEffect(() => () => {
3259
+ const pendingRef = React13__default.useRef({});
3260
+ const timerRef = React13__default.useRef(null);
3261
+ React13__default.useEffect(() => () => {
3107
3262
  if (timerRef.current) clearTimeout(timerRef.current);
3108
3263
  }, []);
3109
- const set = React9.useCallback(
3264
+ const set = React13__default.useCallback(
3110
3265
  (patch) => {
3111
3266
  setStored((current) => ({ ...current ?? {}, ...patch }));
3112
3267
  if (!supported) return;
@@ -3122,7 +3277,7 @@ function useStoreSettings(provider, defaults = {}) {
3122
3277
  },
3123
3278
  [shop, supported]
3124
3279
  );
3125
- const value = React9.useMemo(() => ({ ...defaults, ...stored ?? {} }), [defaults, stored]);
3280
+ const value = React13__default.useMemo(() => ({ ...defaults, ...stored ?? {} }), [defaults, stored]);
3126
3281
  return { value, stored, set, loading, saving, error, loadError, supported };
3127
3282
  }
3128
3283
  function SettingsState({
@@ -3351,25 +3506,25 @@ function PaymentsSettings({ provider }) {
3351
3506
  const settings = useStoreSettings(provider);
3352
3507
  const { value, set } = settings;
3353
3508
  const { connectors } = useConnectors({ hostPluginId: "shop", includeGrants: false });
3354
- const [adding, setAdding] = React9.useState(false);
3355
- const [selected, setSelected] = React9.useState(null);
3509
+ const [adding, setAdding] = React13__default.useState(false);
3510
+ const [selected, setSelected] = React13__default.useState(null);
3356
3511
  const panelAvailable = useConnectorsPanelAvailable();
3357
- const openJourney = React9.useCallback(
3512
+ const openJourney = React13__default.useCallback(
3358
3513
  (connector) => {
3359
3514
  if (panelAvailable) openConnectorsPanel(connector);
3360
3515
  else setSelected(connector);
3361
3516
  },
3362
3517
  [panelAvailable]
3363
3518
  );
3364
- const payment = React9.useMemo(
3519
+ const payment = React13__default.useMemo(
3365
3520
  () => (connectors ?? []).filter((c) => c.category && PAYMENT_CATEGORIES.includes(c.category)),
3366
3521
  [connectors]
3367
3522
  );
3368
- const live = React9.useMemo(() => payment.filter((c) => c.state === "connected"), [payment]);
3523
+ const live = React13__default.useMemo(() => payment.filter((c) => c.state === "connected"), [payment]);
3369
3524
  const chosen = value.paymentMethods ?? [];
3370
3525
  const staleProvider = !!value.paymentProvider && !live.some((c) => c.slug === value.paymentProvider) && live.length === 0;
3371
- const movedFrom = React9.useRef(null);
3372
- React9.useEffect(() => {
3526
+ const movedFrom = React13__default.useRef(null);
3527
+ React13__default.useEffect(() => {
3373
3528
  if (!settings.supported || settings.loading) return;
3374
3529
  const next = resolvePaymentProvider(value.paymentProvider, live.map((c) => c.slug));
3375
3530
  if (!next) return;
@@ -3601,11 +3756,11 @@ function PackagesSettings({ basePath = shippingRegistryPath(PACKAGES_REGISTRY) }
3601
3756
  ] });
3602
3757
  }
3603
3758
  function HandlingSettings({ settings, provider }) {
3604
- const shop = React9.useMemo(() => provider ? resolve(provider) : getShopProvider(), [provider]);
3759
+ const shop = React13__default.useMemo(() => provider ? resolve(provider) : getShopProvider(), [provider]);
3605
3760
  const { value, set, loading, supported } = settings;
3606
- const [noteDraft, setNoteDraft] = React9.useState(null);
3607
- const [profiles, setProfiles] = React9.useState(null);
3608
- React9.useEffect(() => {
3761
+ const [noteDraft, setNoteDraft] = React13__default.useState(null);
3762
+ const [profiles, setProfiles] = React13__default.useState(null);
3763
+ React13__default.useEffect(() => {
3609
3764
  let cancelled = false;
3610
3765
  void shop.listShippingProfiles?.().then((list) => {
3611
3766
  if (!cancelled) setProfiles(list);
@@ -3713,68 +3868,1046 @@ function ChoiceCards({
3713
3868
  );
3714
3869
  }) });
3715
3870
  }
3716
- function parseMoney(raw) {
3717
- const cleaned = raw.replace(/[^\d,.-]/g, "").replace(",", ".");
3718
- if (!cleaned.trim()) return null;
3719
- const n = Number(cleaned);
3720
- return Number.isFinite(n) && n >= 0 ? Math.round(n * 100) / 100 : null;
3871
+ var EMPTY = { rules: [], gaps: [] };
3872
+ var draft = EMPTY;
3873
+ var listeners = /* @__PURE__ */ new Set();
3874
+ var seq = 0;
3875
+ var nextId = () => `rule-${++seq}`;
3876
+ var identity = (rule) => [rule.matchKind, rule.cityCode ?? "", rule.districtKey ?? "", rule.postalFrom ?? "", rule.postalTo ?? ""].join("|");
3877
+ function emit() {
3878
+ for (const listener of listeners) listener();
3879
+ }
3880
+ function getDeliveryDraft() {
3881
+ return draft;
3882
+ }
3883
+ function setDeliveryDraft(next) {
3884
+ draft = { ...next, rules: next.rules.map((rule) => ({ ...rule, id: rule.id || nextId() })) };
3885
+ emit();
3886
+ }
3887
+ function addDeliveryDraftRules(incoming) {
3888
+ const seen = new Set(draft.rules.map(identity));
3889
+ const added = [];
3890
+ for (const rule of incoming) {
3891
+ const key = identity(rule);
3892
+ if (seen.has(key)) continue;
3893
+ seen.add(key);
3894
+ added.push({ ...rule, id: nextId() });
3895
+ }
3896
+ if (added.length === 0) return 0;
3897
+ draft = { ...draft, rules: [...draft.rules, ...added] };
3898
+ emit();
3899
+ return added.length;
3900
+ }
3901
+ function isInDeliveryDraft(rule) {
3902
+ const key = identity(rule);
3903
+ return draft.rules.some((existing) => identity(existing) === key);
3904
+ }
3905
+ function setAllDeliveryDraftRates(patch) {
3906
+ let touched = 0;
3907
+ const rules = draft.rules.map((rule) => {
3908
+ if (rule.outcome === "refuse") return rule;
3909
+ touched++;
3910
+ return { ...rule, ...patch };
3911
+ });
3912
+ if (touched === 0) return 0;
3913
+ draft = { ...draft, rules };
3914
+ emit();
3915
+ return touched;
3916
+ }
3917
+ function updateDeliveryDraftRule(id, patch) {
3918
+ const index = draft.rules.findIndex((rule) => rule.id === id);
3919
+ if (index < 0) return false;
3920
+ const rules = draft.rules.slice();
3921
+ rules[index] = { ...rules[index], ...patch, id };
3922
+ draft = { ...draft, rules };
3923
+ emit();
3924
+ return true;
3721
3925
  }
3722
- var formatMoney = (v) => v == null ? "" : String(v).replace(".", ",");
3723
- var FREIGHT_SOURCE = [
3724
- {
3725
- value: "own",
3726
- label: "Eu mesmo calculo e despacho",
3727
- description: "O frete sai do pre\xE7o base e das suas faixas de CEP, e voc\xEA despacha como quiser \u2014 carro pr\xF3prio, motoboy, ag\xEAncia. Nenhum gateway \xE9 consultado, mesmo que exista um conectado.",
3728
- Icon: Car
3729
- },
3730
- {
3731
- value: "gateway",
3732
- label: "Perguntar a um gateway de frete",
3733
- description: "O pre\xE7o e o prazo v\xEAm da transportadora, calculados pelo CEP do comprador. Quando o gateway n\xE3o responde a tempo, o checkout usa suas faixas \u2014 a compra nunca fica sem pre\xE7o.",
3734
- Icon: Truck
3926
+ function removeDeliveryDraftRule(id) {
3927
+ const rules = draft.rules.filter((rule) => rule.id !== id);
3928
+ if (rules.length === draft.rules.length) return false;
3929
+ draft = { ...draft, rules };
3930
+ emit();
3931
+ return true;
3932
+ }
3933
+ function clearDeliveryDraft() {
3934
+ draft = EMPTY;
3935
+ emit();
3936
+ }
3937
+ function subscribeDeliveryDraft(listener) {
3938
+ listeners.add(listener);
3939
+ return () => {
3940
+ listeners.delete(listener);
3941
+ };
3942
+ }
3943
+ function useDeliveryDraft() {
3944
+ return React13.useSyncExternalStore(subscribeDeliveryDraft, getDeliveryDraft, getDeliveryDraft);
3945
+ }
3946
+
3947
+ // src/views/delivery-agent.ts
3948
+ var DELIVERY_AREAS_INSTRUCTIONS = `
3949
+ Esta \xE9 a tela de Entrega da loja. Aqui voc\xEA configura ONDE a loja entrega, ONDE ela recusa e onde precisa confirmar antes.
3950
+
3951
+ Uma regra tem duas partes: a \xC1REA (um munic\xEDpio ou um bairro) e o DESFECHO:
3952
+ \xB7 entrego (deliver) \xB7 confirmo antes (review) \xB7 n\xE3o entrego (refuse)
3953
+ Entre as regras que casam com um endere\xE7o, vence a mais estrita. Ent\xE3o "Zona Norte: entrego" + "Mar\xE9: n\xE3o entrego" s\xE3o DUAS regras, e a segunda ganha na Mar\xE9. Nunca tente recortar uma \xE1rea grande em peda\xE7os para abrir exce\xE7\xE3o \u2014 declare a exce\xE7\xE3o.
3954
+
3955
+ PREFIRA A \xC1REA MAIOR MENOS AS EXCE\xC7\xD5ES. \xC9 assim que o lojista fala, e \xE9 o que a regra mais estrita permite. Quando ele lista v\xE1rias zonas de uma mesma cidade \u2014 "zona norte, zona sul, zona oeste, zona sudoeste" \u2014 isso \xC9 a cidade inteira: proponha UMA regra de munic\xEDpio e as exce\xE7\xF5es, n\xE3o oitenta bairros. Uma whitelist de bairros \xE9 pior de duas formas: ela envelhece (bairro que faltar na lista vira venda recusada em sil\xEAncio) e ningu\xE9m confere oitenta linhas.
3956
+
3957
+ Quando a lojista colar uma mensagem descrevendo a opera\xE7\xE3o dela:
3958
+ 1. Separe cada lugar citado e se ele \xE9 entrega, recusa ou d\xFAvida. "Evitar X", "exceto X" e "n\xE3o entregamos X" s\xE3o recusa.
3959
+ 2. Chame \`findDeliveryAreas\` UMA vez com todos os nomes juntos.
3960
+ \xB7 \`resolved\` est\xE1 pronto para virar regra.
3961
+ \xB7 \`options\` \xE9 amb\xEDguo: pergunte qual, n\xE3o escolha.
3962
+ \xB7 \`notFound\` \xE9 o caso importante. Muita comunidade n\xE3o tem bairro nem CEP pr\xF3prio \u2014 divide o do bairro vizinho. N\xC3O invente faixa de CEP para ela. Diga isso em uma linha e ofere\xE7a o que existe: marcar o bairro que a cont\xE9m como "confirmo antes".
3963
+ \xB7 \`group\` (Zona Sul, Regi\xE3o dos Lagos) traz \`members\`. Use os membros como regras individuais S\xD3 quando ele citou uma parte da cidade. Se ele citou zonas que somam a cidade toda, jogue os grupos fora e use o munic\xEDpio.
3964
+ 3. Chame \`proposeDeliveryRules\` com o que resolveu. Ele N\xC3O grava \u2014 desenha a tabela na tela dela.
3965
+ 4. Pre\xE7o e prazo: s\xF3 preencha o que a mensagem disse. Se ela n\xE3o disse, deixe vazio e pergunte UMA vez, junto, no fim: "quanto voc\xEA cobra em cada regi\xE3o, e tem frete gr\xE1tis acima de algum valor?".
3966
+ 5. Quando ela confirmar, chame \`commitDeliveryRules\`. Depois teste com \`checkDeliveryFor\` dois ou tr\xEAs CEPs (um que entrega, um que recusa) e relate o resultado em duas linhas.
3967
+
3968
+ Regras:
3969
+ \xB7 Voc\xEA NUNCA escreve um CEP ou um c\xF3digo de munic\xEDpio que n\xE3o tenha vindo de \`findDeliveryAreas\`.
3970
+ \xB7 N\xC3O proponha faixa de CEP. Faixa \xE9 como transportadora precifica, n\xE3o como uma pessoa descreve onde entrega, e ningu\xE9m no mercado pede isso ao lojista. Ela existe s\xF3 para quando a lojista estiver COPIANDO uma tabela de transportadora e disser os n\xFAmeros ela mesma. Se voc\xEA n\xE3o achar a \xE1rea pelo nome, pergunte \u2014 n\xE3o caia na faixa.
3971
+ \xB7 Comunidade que n\xE3o resolve pelo nome (Chapad\xE3o \xE9 o caso) n\xE3o vira recusa inventada: ofere\xE7a marcar o bairro que a cont\xE9m como "confirmo antes", que aceita o pedido e deixa a decis\xE3o com ele.
3972
+ \xB7 N\xE3o imprima a tabela inteira no texto: ela est\xE1 na tela. Diga quantas regras entraram, quantas s\xE3o recusa, e o que ficou em aberto.
3973
+ \xB7 Portugu\xEAs do Brasil, curto.
3974
+ `.trim();
3975
+ var FIND_AREAS = {
3976
+ id: "shop.delivery.find-areas",
3977
+ name: "findDeliveryAreas",
3978
+ description: 'Resolves place names the merchant wrote ("Angra", "Zona Sul", "Barra da Tijuca", "Itacuru\xE7\xE1") into real municipalities (IBGE code), neighbourhoods, or curated groups that expand into several of them. Send EVERY name of the message in one call. This is the ONLY source of a municipality code or a neighbourhood key \u2014 never write one that did not come from here. A name that answers `notFound` is a question for the merchant, not a rule to invent.',
3979
+ icon: "MapPin",
3980
+ mode: "read",
3981
+ execution: { plane: "client" },
3982
+ category: "Shop",
3983
+ role: "operations",
3984
+ parameters: {
3985
+ type: "object",
3986
+ properties: {
3987
+ queries: {
3988
+ type: "array",
3989
+ description: 'The place names as the merchant wrote them, without "n\xE3o entrego" / "exceto".',
3990
+ items: { type: "string" }
3991
+ }
3992
+ },
3993
+ required: ["queries"]
3735
3994
  }
3736
- ];
3737
- function ShippingSettings({ provider }) {
3738
- const [registryId, setRegistryId] = React9.useState(shippingRegistryIdFromHash);
3739
- React9.useEffect(() => {
3740
- const handler = () => setRegistryId(shippingRegistryIdFromHash());
3741
- window.addEventListener("hashchange", handler);
3742
- return () => window.removeEventListener("hashchange", handler);
3743
- }, []);
3744
- const shop = React9.useMemo(() => provider ? resolve(provider) : getShopProvider(), [provider]);
3745
- const registries = React9.useMemo(
3746
- () => buildShopRegistries(provider ?? getShopProvider),
3747
- [provider]
3748
- );
3749
- const storeSettings = useStoreSettings(provider);
3750
- const supported = typeof shop.getShippingSettings === "function" && typeof shop.updateShippingSettings === "function";
3751
- const [row, setRow] = React9.useState(null);
3752
- const [zones, setZones] = React9.useState(null);
3753
- const [locations, setLocations] = React9.useState(null);
3754
- const [loading, setLoading] = React9.useState(supported);
3755
- const [saving, setSaving] = React9.useState(false);
3756
- const [error, setError] = React9.useState(null);
3757
- const [loadError, setLoadError] = React9.useState(null);
3758
- const [flatDraft, setFlatDraft] = React9.useState("");
3759
- const [freeDraft, setFreeDraft] = React9.useState("");
3760
- const [flatBad, setFlatBad] = React9.useState(false);
3761
- const [freeBad, setFreeBad] = React9.useState(false);
3762
- const { connectors } = useConnectors({ hostPluginId: "shop", includeGrants: false });
3763
- const [selected, setSelected] = React9.useState(null);
3764
- const panelAvailable = useConnectorsPanelAvailable();
3765
- const openJourney = React9.useCallback(
3766
- (connector) => {
3767
- if (panelAvailable) openConnectorsPanel(connector);
3768
- else setSelected(connector);
3995
+ };
3996
+ var PROPOSE_RULES = {
3997
+ id: "shop.delivery.propose-rules",
3998
+ name: "proposeDeliveryRules",
3999
+ description: "Draws the delivery rules the message describes as an EDITABLE TABLE on the merchant's screen. Writes nothing. Every area must carry the ids findDeliveryAreas answered. A group is expanded into one rule per member here, so the merchant sees and trims what it covers. Leave rate and ETA out when the message did not state them.",
4000
+ icon: "ListChecks",
4001
+ mode: "read",
4002
+ execution: { plane: "client" },
4003
+ category: "Shop",
4004
+ role: "operations",
4005
+ parameters: {
4006
+ type: "object",
4007
+ properties: {
4008
+ rules: {
4009
+ type: "array",
4010
+ description: "One entry per area named in the message.",
4011
+ items: {
4012
+ type: "object",
4013
+ properties: {
4014
+ kind: {
4015
+ type: "string",
4016
+ enum: ["city", "district", "group", "postal_range"],
4017
+ description: "The `kind` findDeliveryAreas answered. Use postal_range ONLY when the merchant is copying a carrier freight table and dictated the numbers \u2014 never to describe where they deliver."
4018
+ },
4019
+ label: { type: "string", description: "The place name, as it should read on the table." },
4020
+ cityCode: { type: "string", description: "IBGE code from findDeliveryAreas." },
4021
+ districtKey: { type: "string", description: "Neighbourhood key from findDeliveryAreas." },
4022
+ postalFrom: { type: "string", description: "Only for postal_range." },
4023
+ postalTo: { type: "string", description: "Only for postal_range." },
4024
+ outcome: {
4025
+ type: "string",
4026
+ enum: ["deliver", "review", "refuse"],
4027
+ description: "deliver | review (confirm before dispatch) | refuse."
4028
+ },
4029
+ rate: { type: "number", description: "Only if the message stated a price. Otherwise omit." },
4030
+ freeAbove: { type: "number", description: "Only if the message stated a threshold." },
4031
+ etaMinDays: { type: "number" },
4032
+ etaMaxDays: { type: "number" },
4033
+ source: { type: "string", description: "The words of the message this rule came from." }
4034
+ },
4035
+ required: ["kind", "label", "outcome"]
4036
+ }
4037
+ },
4038
+ gaps: {
4039
+ type: "array",
4040
+ description: "What the message named and you could NOT resolve, so the merchant sees it too.",
4041
+ items: {
4042
+ type: "object",
4043
+ properties: {
4044
+ query: { type: "string" },
4045
+ reason: { type: "string", description: "Plain Portuguese, one line." }
4046
+ },
4047
+ required: ["query", "reason"]
4048
+ }
4049
+ }
3769
4050
  },
3770
- [panelAvailable]
3771
- );
3772
- const carriers = React9.useMemo(
3773
- () => (connectors ?? []).filter((c) => c.category === "logistics" || c.category === "shipping"),
3774
- [connectors]
3775
- );
4051
+ required: ["rules"]
4052
+ }
4053
+ };
4054
+ var SET_RULE = {
4055
+ id: "shop.delivery.set-rule",
4056
+ name: "setDeliveryRule",
4057
+ description: "Corrects one line of the draft table already on screen \u2014 its outcome, price, free-above or ETA. Use when the merchant answers a question about the rules you proposed, instead of rebuilding the whole draft.",
4058
+ icon: "Pencil",
4059
+ mode: "read",
4060
+ execution: { plane: "client" },
4061
+ category: "Shop",
4062
+ role: "operations",
4063
+ parameters: {
4064
+ type: "object",
4065
+ properties: {
4066
+ id: { type: "string", description: "The rule id proposeDeliveryRules answered." },
4067
+ outcome: { type: "string", enum: ["deliver", "review", "refuse"] },
4068
+ rate: { type: "number" },
4069
+ freeAbove: { type: "number" },
4070
+ etaMinDays: { type: "number" },
4071
+ etaMaxDays: { type: "number" }
4072
+ },
4073
+ required: ["id"]
4074
+ }
4075
+ };
4076
+ var COMMIT_RULES = {
4077
+ id: "shop.delivery.commit-rules",
4078
+ name: "commitDeliveryRules",
4079
+ description: "Saves the draft table as the store's delivery rules. Call ONLY after the merchant said the table is right. It changes who can buy: an address outside every rule cannot check out, and a refused one is blocked server-side. Saves the draft as it stands on screen \u2014 the merchant may have edited it, and those edits win.",
4080
+ icon: "Save",
4081
+ mode: "persist",
4082
+ execution: { plane: "client" },
4083
+ category: "Shop",
4084
+ role: "operations",
4085
+ permission: { feature: "shop", action: "update" },
4086
+ parameters: { type: "object", properties: {} }
4087
+ };
4088
+ var CHECK_DELIVERY = {
4089
+ id: "shop.delivery.check",
4090
+ name: "checkDeliveryFor",
4091
+ description: 'Answers what the store does with a given postcode right now: deliver, confirm first, refuse, or outside every rule. Use it to CHECK YOUR OWN WORK after saving, and to answer "why could this customer not buy?".',
4092
+ icon: "Search",
4093
+ mode: "read",
4094
+ execution: { plane: "client" },
4095
+ category: "Shop",
4096
+ role: "operations",
4097
+ parameters: {
4098
+ type: "object",
4099
+ properties: {
4100
+ postalCodes: {
4101
+ type: "array",
4102
+ description: "One or more CEPs, with or without punctuation.",
4103
+ items: { type: "string" }
4104
+ }
4105
+ },
4106
+ required: ["postalCodes"]
4107
+ }
4108
+ };
4109
+ [
4110
+ FIND_AREAS.id,
4111
+ PROPOSE_RULES.id,
4112
+ SET_RULE.id,
4113
+ COMMIT_RULES.id,
4114
+ CHECK_DELIVERY.id
4115
+ ];
4116
+ var text2 = (value) => typeof value === "string" ? value.trim() : "";
4117
+ var number = (value) => typeof value === "number" && Number.isFinite(value) ? value : null;
4118
+ var OUTCOMES = ["deliver", "review", "refuse"];
4119
+ var outcomeOf = (value) => OUTCOMES.includes(value) ? value : "deliver";
4120
+ function candidateOut(area) {
4121
+ return {
4122
+ kind: area.kind,
4123
+ label: area.label,
4124
+ cityCode: area.cityCode ?? void 0,
4125
+ cityName: area.cityName ?? void 0,
4126
+ state: area.state ?? void 0,
4127
+ districtKey: area.districtKey ?? void 0,
4128
+ knownPostalCodes: area.knownPostalCodes || void 0,
4129
+ members: area.members?.map((m) => ({
4130
+ kind: m.kind,
4131
+ label: m.label,
4132
+ cityCode: m.cityCode ?? void 0,
4133
+ districtKey: m.districtKey ?? void 0
4134
+ }))
4135
+ };
4136
+ }
4137
+ function pickCandidate(query, areas) {
4138
+ if (areas.length === 0) return {};
4139
+ if (areas.length === 1) return { resolved: areas[0] };
4140
+ const key = query.trim().toLowerCase();
4141
+ const exact = areas.filter((a) => a.label.trim().toLowerCase() === key);
4142
+ if (exact.length === 1) return { resolved: exact[0] };
4143
+ return { options: areas.slice(0, 6) };
4144
+ }
4145
+ function ruleFromArgs(raw, index) {
4146
+ const kind = text2(raw.kind);
4147
+ const label = text2(raw.label);
4148
+ if (!label) return null;
4149
+ const base = {
4150
+ id: `draft-${index}`,
4151
+ label,
4152
+ outcome: outcomeOf(raw.outcome),
4153
+ rate: number(raw.rate),
4154
+ freeAbove: number(raw.freeAbove),
4155
+ etaMinDays: number(raw.etaMinDays),
4156
+ etaMaxDays: number(raw.etaMaxDays),
4157
+ source: text2(raw.source) || void 0
4158
+ };
4159
+ if (kind === "city") {
4160
+ const cityCode = text2(raw.cityCode);
4161
+ if (!cityCode) return null;
4162
+ return { ...base, matchKind: "city", cityCode };
4163
+ }
4164
+ if (kind === "district") {
4165
+ const districtKey = text2(raw.districtKey);
4166
+ if (!districtKey) return null;
4167
+ return {
4168
+ ...base,
4169
+ matchKind: "district",
4170
+ districtKey,
4171
+ cityCode: text2(raw.cityCode) || null
4172
+ };
4173
+ }
4174
+ if (kind === "postal_range") {
4175
+ const from = text2(raw.postalFrom).replace(/\D/g, "");
4176
+ const to = text2(raw.postalTo).replace(/\D/g, "");
4177
+ if (from.length !== 8 || to.length !== 8) return null;
4178
+ return { ...base, matchKind: "postal_range", postalFrom: from, postalTo: to };
4179
+ }
4180
+ return null;
4181
+ }
4182
+ function zoneInputFrom(rule) {
4183
+ return {
4184
+ name: rule.label,
4185
+ carrier: null,
4186
+ matchKind: rule.matchKind,
4187
+ cityCode: rule.cityCode ?? null,
4188
+ districtKey: rule.districtKey ?? null,
4189
+ outcome: rule.outcome,
4190
+ postalFrom: rule.postalFrom ?? null,
4191
+ postalTo: rule.postalTo ?? null,
4192
+ // Uma regra de recusa não cobra nada: o preço dela nunca é lido.
4193
+ rate: rule.outcome === "refuse" ? 0 : rule.rate ?? 0,
4194
+ freeAbove: rule.freeAbove ?? null,
4195
+ etaMinDays: rule.etaMinDays ?? null,
4196
+ etaMaxDays: rule.etaMaxDays ?? null,
4197
+ active: true,
4198
+ sortOrder: 0,
4199
+ excluded: rule.outcome === "refuse"
4200
+ };
4201
+ }
4202
+ function createDeliveryAreaTools({ getProvider, onSaved }) {
4203
+ const failed = (error) => ({ ok: false, error: errorMessage(error) });
4204
+ return [
4205
+ {
4206
+ tool: FIND_AREAS,
4207
+ execute: async (args) => {
4208
+ const queries = (Array.isArray(args.queries) ? args.queries : []).map(text2).filter(Boolean).slice(0, 30);
4209
+ if (queries.length === 0) return { ok: false, error: "no_queries" };
4210
+ try {
4211
+ const shop = getProvider();
4212
+ if (typeof shop.findAreas !== "function") {
4213
+ return { ok: false, error: "area_lookup_unavailable" };
4214
+ }
4215
+ const results = await Promise.all(
4216
+ queries.map(async (query) => {
4217
+ const areas = await shop.findAreas(query, 8).catch(() => []);
4218
+ const { resolved, options } = pickCandidate(query, areas);
4219
+ if (resolved) return { query, resolved: candidateOut(resolved) };
4220
+ if (options) return { query, options: options.map(candidateOut) };
4221
+ return {
4222
+ query,
4223
+ notFound: true,
4224
+ // Dito aqui e não deixado para o modelo deduzir: é a frase que
4225
+ // separa "não existe" de "existe mas não dá para separar por CEP".
4226
+ hint: "Sem munic\xEDpio, bairro ou grupo com esse nome. Comunidade normalmente n\xE3o tem CEP pr\xF3prio \u2014 divide o do bairro vizinho. Pergunte \xE0 lojista qual bairro a cont\xE9m."
4227
+ };
4228
+ })
4229
+ );
4230
+ return { ok: true, results };
4231
+ } catch (error) {
4232
+ return failed(error);
4233
+ }
4234
+ }
4235
+ },
4236
+ {
4237
+ tool: PROPOSE_RULES,
4238
+ execute: async (args) => {
4239
+ const raw = Array.isArray(args.rules) ? args.rules : [];
4240
+ const rules = [];
4241
+ const gaps = (Array.isArray(args.gaps) ? args.gaps : []).map((g) => ({ query: text2(g?.query), reason: text2(g?.reason) })).filter((g) => g.query);
4242
+ raw.forEach((entry, index) => {
4243
+ const rule = ruleFromArgs(entry ?? {}, rules.length);
4244
+ if (rule) {
4245
+ rules.push(rule);
4246
+ return;
4247
+ }
4248
+ gaps.push({
4249
+ query: text2(entry?.label) || `regra ${index + 1}`,
4250
+ reason: "Faltou o identificador da \xE1rea. Chame findDeliveryAreas para este nome e use o que ele responder."
4251
+ });
4252
+ });
4253
+ if (rules.length === 0 && gaps.length === 0) return { ok: false, error: "empty_draft" };
4254
+ setDeliveryDraft({ rules, gaps });
4255
+ return {
4256
+ ok: true,
4257
+ drafted: rules.length,
4258
+ refusing: rules.filter((r) => r.outcome === "refuse").length,
4259
+ reviewing: rules.filter((r) => r.outcome === "review").length,
4260
+ missingPrice: rules.filter((r) => r.outcome !== "refuse" && r.rate == null).length,
4261
+ gaps,
4262
+ rules: rules.map((r) => ({ id: r.id, label: r.label, outcome: r.outcome })),
4263
+ nextStep: "commitDeliveryRules"
4264
+ };
4265
+ }
4266
+ },
4267
+ {
4268
+ tool: SET_RULE,
4269
+ execute: async (args) => {
4270
+ const id = text2(args.id);
4271
+ if (!id) return { ok: false, error: "no_id" };
4272
+ const patch = {};
4273
+ if (args.outcome !== void 0) patch.outcome = outcomeOf(args.outcome);
4274
+ if (args.rate !== void 0) patch.rate = number(args.rate);
4275
+ if (args.freeAbove !== void 0) patch.freeAbove = number(args.freeAbove);
4276
+ if (args.etaMinDays !== void 0) patch.etaMinDays = number(args.etaMinDays);
4277
+ if (args.etaMaxDays !== void 0) patch.etaMaxDays = number(args.etaMaxDays);
4278
+ if (Object.keys(patch).length === 0) return { ok: false, error: "nothing_to_change" };
4279
+ if (!updateDeliveryDraftRule(id, patch)) return { ok: false, error: "rule_not_in_draft" };
4280
+ return { ok: true, id, ...patch };
4281
+ }
4282
+ },
4283
+ {
4284
+ tool: COMMIT_RULES,
4285
+ execute: async () => {
4286
+ const { rules } = getDeliveryDraft();
4287
+ if (rules.length === 0) return { ok: false, error: "nothing_to_save" };
4288
+ try {
4289
+ const shop = getProvider();
4290
+ if (typeof shop.createShippingZone !== "function") {
4291
+ return { ok: false, error: "zones_unavailable" };
4292
+ }
4293
+ const saved = [];
4294
+ const rejected = [];
4295
+ for (const rule of rules) {
4296
+ try {
4297
+ await shop.createShippingZone(zoneInputFrom(rule));
4298
+ saved.push(rule.label);
4299
+ } catch (error) {
4300
+ rejected.push({ label: rule.label, error: errorMessage(error) });
4301
+ }
4302
+ }
4303
+ if (saved.length > 0) {
4304
+ clearDeliveryDraft();
4305
+ onSaved();
4306
+ }
4307
+ return { ok: rejected.length === 0, saved: saved.length, rejected };
4308
+ } catch (error) {
4309
+ return failed(error);
4310
+ }
4311
+ }
4312
+ },
4313
+ {
4314
+ tool: CHECK_DELIVERY,
4315
+ execute: async (args) => {
4316
+ const codes = (Array.isArray(args.postalCodes) ? args.postalCodes : []).map((c) => text2(c).replace(/\D/g, "")).filter((c) => c.length === 8).slice(0, 10);
4317
+ if (codes.length === 0) return { ok: false, error: "no_postal_codes" };
4318
+ try {
4319
+ const shop = getProvider();
4320
+ if (typeof shop.checkDelivery !== "function") {
4321
+ return { ok: false, error: "delivery_check_unavailable" };
4322
+ }
4323
+ const results = await Promise.all(
4324
+ codes.map(async (code) => {
4325
+ const check = await shop.checkDelivery(code);
4326
+ return {
4327
+ postalCode: code,
4328
+ // null = nenhuma regra casou: fora de área, que não é o mesmo
4329
+ // que recusado e o texto para a lojista é outro.
4330
+ outcome: check.outcome ?? null,
4331
+ rule: check.zoneName ?? check.refusedZone ?? null,
4332
+ outsideEveryRule: !check.refused && !check.ownZone
4333
+ };
4334
+ })
4335
+ );
4336
+ return { ok: true, results };
4337
+ } catch (error) {
4338
+ return failed(error);
4339
+ }
4340
+ }
4341
+ }
4342
+ ];
4343
+ }
4344
+
4345
+ // src/views/DeliveryAgentSurface.tsx
4346
+ function DeliveryAgentSurface({ provider, zones, onSaved }) {
4347
+ const live = React13.useRef({ provider, zones, onSaved });
4348
+ live.current = { provider, zones, onSaved };
4349
+ const contribution = React13.useMemo(() => ({
4350
+ id: "shop.delivery.settings",
4351
+ pluginId: "shop",
4352
+ role: "operations",
4353
+ instructions: DELIVERY_AREAS_INSTRUCTIONS,
4354
+ describe: () => {
4355
+ const { zones: rows } = live.current;
4356
+ const draft2 = getDeliveryDraft();
4357
+ const byOutcome = (outcome) => rows.filter((z) => z.active && (z.outcome ?? (z.excluded ? "refuse" : "deliver")) === outcome);
4358
+ return {
4359
+ title: "Configura\xE7\xE3o \u2014 Entrega",
4360
+ state: {
4361
+ screen: "/settings/shop-shipping",
4362
+ savedRules: {
4363
+ delivering: byOutcome("deliver").length,
4364
+ reviewing: byOutcome("review").length,
4365
+ refusing: byOutcome("refuse").length,
4366
+ // Os nomes, não as faixas: o agente precisa saber o que JÁ existe
4367
+ // para não propor a mesma região duas vezes.
4368
+ names: rows.filter((z) => z.active).slice(0, 40).map((z) => z.name)
4369
+ },
4370
+ draft: draft2.rules.length === 0 && draft2.gaps.length === 0 ? null : {
4371
+ rules: draft2.rules.map((r) => ({
4372
+ id: r.id,
4373
+ label: r.label,
4374
+ outcome: r.outcome,
4375
+ rate: r.rate ?? null,
4376
+ freeAbove: r.freeAbove ?? null
4377
+ })),
4378
+ gaps: draft2.gaps
4379
+ }
4380
+ }
4381
+ };
4382
+ },
4383
+ tools: createDeliveryAreaTools({
4384
+ getProvider: () => live.current.provider(),
4385
+ onSaved: () => live.current.onSaved()
4386
+ })
4387
+ }), []);
4388
+ useAgentSurface(contribution);
4389
+ return null;
4390
+ }
4391
+ var MIN_QUERY = 2;
4392
+ var DEBOUNCE_MS2 = 250;
4393
+ function ruleFor(area, outcome) {
4394
+ if (area.kind === "city" && area.cityCode) {
4395
+ return { matchKind: "city", label: area.label, cityCode: area.cityCode, outcome };
4396
+ }
4397
+ if (area.kind === "district" && area.districtKey) {
4398
+ return {
4399
+ matchKind: "district",
4400
+ label: area.label,
4401
+ districtKey: area.districtKey,
4402
+ cityCode: area.cityCode ?? null,
4403
+ outcome
4404
+ };
4405
+ }
4406
+ return null;
4407
+ }
4408
+ function rulesFor(area, outcome) {
4409
+ if (area.kind !== "group") {
4410
+ const single = ruleFor(area, outcome);
4411
+ return single ? [single] : [];
4412
+ }
4413
+ return (area.members ?? []).flatMap((member) => {
4414
+ const rule = ruleFor(
4415
+ { kind: member.kind, label: member.label, cityCode: member.cityCode, districtKey: member.districtKey },
4416
+ outcome
4417
+ );
4418
+ return rule ? [rule] : [];
4419
+ });
4420
+ }
4421
+ function areaMeta(area) {
4422
+ if (area.matchedAs) return area.matchedAs;
4423
+ if (area.kind === "group") {
4424
+ const n = area.members?.length ?? 0;
4425
+ return `${n} ${n === 1 ? "regi\xE3o" : "regi\xF5es"}${area.cityName ? ` \xB7 ${area.cityName}` : ""}`;
4426
+ }
4427
+ if (area.kind === "city") return `Cidade inteira${area.state ? ` \xB7 ${area.state}` : ""}`;
4428
+ return `Bairro${area.cityName ? ` \xB7 ${area.cityName}` : ""}`;
4429
+ }
4430
+ var areaKeyOf = (area) => `${area.kind}:${area.districtKey ?? area.cityCode ?? area.label}`;
4431
+ function AreaSearch({ provider, outcome, placeholder, onlyCityCodes, emptyHint }) {
4432
+ const [query, setQuery] = React13__default.useState("");
4433
+ const [results, setResults] = React13__default.useState(null);
4434
+ const [loading, setLoading] = React13__default.useState(false);
4435
+ const [unavailable, setUnavailable] = React13__default.useState(false);
4436
+ const [justAdded, setJustAdded] = React13__default.useState(null);
4437
+ React13__default.useEffect(() => {
4438
+ const text3 = query.trim();
4439
+ if (text3.length < MIN_QUERY) {
4440
+ setResults(null);
4441
+ return void 0;
4442
+ }
4443
+ let cancelled = false;
4444
+ setLoading(true);
4445
+ const timer = setTimeout(() => {
4446
+ const shop = provider ? resolve(provider) : getShopProvider();
4447
+ if (typeof shop.findAreas !== "function") {
4448
+ if (!cancelled) {
4449
+ setUnavailable(true);
4450
+ setLoading(false);
4451
+ }
4452
+ return;
4453
+ }
4454
+ void shop.findAreas(text3, 10).then((list) => {
4455
+ if (!cancelled) {
4456
+ setResults(list);
4457
+ setUnavailable(false);
4458
+ }
4459
+ }).catch(() => {
4460
+ if (!cancelled) {
4461
+ setResults([]);
4462
+ setUnavailable(true);
4463
+ }
4464
+ }).finally(() => {
4465
+ if (!cancelled) setLoading(false);
4466
+ });
4467
+ }, DEBOUNCE_MS2);
4468
+ return () => {
4469
+ cancelled = true;
4470
+ clearTimeout(timer);
4471
+ };
4472
+ }, [query, provider]);
4473
+ const add = (area) => {
4474
+ const rules = rulesFor(area, outcome);
4475
+ if (rules.length === 0) return;
4476
+ const added = addDeliveryDraftRules(rules);
4477
+ setJustAdded(areaKeyOf(area));
4478
+ setTimeout(() => setJustAdded(null), 1400);
4479
+ if (added > 0) setQuery("");
4480
+ };
4481
+ const visible = (results ?? []).filter((area) => {
4482
+ if (!onlyCityCodes || onlyCityCodes.length === 0) return true;
4483
+ if (area.kind === "city") return true;
4484
+ return !area.cityCode || onlyCityCodes.includes(area.cityCode);
4485
+ });
4486
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
4487
+ /* @__PURE__ */ jsxs("div", { className: "relative", children: [
4488
+ /* @__PURE__ */ jsx(Search, { className: "pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" }),
4489
+ /* @__PURE__ */ jsx(
4490
+ "input",
4491
+ {
4492
+ value: query,
4493
+ onChange: (e) => setQuery(e.target.value),
4494
+ placeholder,
4495
+ className: "w-full rounded-lg border border-border bg-background py-2 pl-9 pr-9 text-sm"
4496
+ }
4497
+ ),
4498
+ loading && /* @__PURE__ */ jsx(Loader2, { className: "absolute right-3 top-1/2 h-4 w-4 -translate-y-1/2 animate-spin text-muted-foreground" })
4499
+ ] }),
4500
+ results !== null && /* @__PURE__ */ jsxs("ul", { className: "mt-2 max-h-72 divide-y divide-border overflow-y-auto rounded-lg border border-border", children: [
4501
+ visible.length === 0 && /* @__PURE__ */ jsx("li", { className: "px-3 py-6 text-center text-xs text-muted-foreground", children: unavailable ? "A busca de regi\xF5es ainda n\xE3o est\xE1 dispon\xEDvel neste banco." : emptyHint }),
4502
+ visible.map((area) => {
4503
+ const key = areaKeyOf(area);
4504
+ const already = area.kind !== "group" && isInDeliveryDraft({
4505
+ matchKind: area.kind === "city" ? "city" : "district",
4506
+ cityCode: area.cityCode ?? null,
4507
+ districtKey: area.districtKey ?? null
4508
+ });
4509
+ return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(
4510
+ "button",
4511
+ {
4512
+ type: "button",
4513
+ onClick: () => add(area),
4514
+ disabled: already,
4515
+ className: "flex w-full items-center gap-3 px-3 py-2.5 text-left transition hover:bg-muted/50 disabled:opacity-50",
4516
+ children: [
4517
+ area.kind === "city" ? /* @__PURE__ */ jsx(Building2, { className: "h-4 w-4 shrink-0 text-muted-foreground" }) : /* @__PURE__ */ jsx(MapPin, { className: "h-4 w-4 shrink-0 text-muted-foreground" }),
4518
+ /* @__PURE__ */ jsxs("span", { className: "min-w-0 flex-1", children: [
4519
+ /* @__PURE__ */ jsx("span", { className: "block truncate text-sm font-medium", children: area.label }),
4520
+ /* @__PURE__ */ jsxs("span", { className: "block text-xs text-muted-foreground", children: [
4521
+ areaMeta(area),
4522
+ area.kind === "district" && !area.knownPostalCodes && " \xB7 ainda sem CEP visto"
4523
+ ] })
4524
+ ] }),
4525
+ justAdded === key ? /* @__PURE__ */ jsx(Check, { className: "h-4 w-4 shrink-0 text-emerald-600" }) : already ? /* @__PURE__ */ jsx("span", { className: "shrink-0 text-xs text-muted-foreground", children: "j\xE1 escolhido" }) : /* @__PURE__ */ jsx(Plus, { className: "h-4 w-4 shrink-0 text-muted-foreground" })
4526
+ ]
4527
+ }
4528
+ ) }, key);
4529
+ })
4530
+ ] })
4531
+ ] });
4532
+ }
4533
+ function DeliveryAreaPicker({ provider, onAskAssistant }) {
4534
+ const draft2 = useDeliveryDraft();
4535
+ const covered = draft2.rules.filter((r) => r.outcome !== "refuse");
4536
+ const coveredCities = covered.filter((r) => r.matchKind === "city");
4537
+ const exceptions = draft2.rules.filter((r) => r.outcome === "refuse");
4538
+ const cityCodes = covered.map((r) => r.cityCode).filter(Boolean);
4539
+ return /* @__PURE__ */ jsxs("div", { className: "rounded-card border bg-card px-5 py-4 shadow-sm", children: [
4540
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-start justify-between gap-3", children: [
4541
+ /* @__PURE__ */ jsxs("div", { children: [
4542
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: "Onde voc\xEA entrega?" }),
4543
+ /* @__PURE__ */ jsx("p", { className: "mt-0.5 text-xs text-muted-foreground", children: 'Digite uma cidade, um bairro ou uma regi\xE3o \u2014 "Rio de Janeiro", "Ipanema", "Zona Sul". Se voc\xEA atende a cidade toda, uma linha s\xF3 resolve: escolha a cidade.' })
4544
+ ] }),
4545
+ onAskAssistant && /* @__PURE__ */ jsxs(
4546
+ "button",
4547
+ {
4548
+ type: "button",
4549
+ onClick: onAskAssistant,
4550
+ className: "inline-flex items-center gap-1.5 rounded-md border border-border px-3 py-1.5 text-xs font-medium hover:bg-muted",
4551
+ children: [
4552
+ /* @__PURE__ */ jsx(Sparkles, { className: "h-3.5 w-3.5 text-primary" }),
4553
+ "Prefiro contar por escrito"
4554
+ ]
4555
+ }
4556
+ )
4557
+ ] }),
4558
+ /* @__PURE__ */ jsx("div", { className: "mt-3", children: /* @__PURE__ */ jsx(
4559
+ AreaSearch,
4560
+ {
4561
+ provider,
4562
+ outcome: "deliver",
4563
+ placeholder: "Cidade, bairro ou regi\xE3o\u2026",
4564
+ emptyHint: "Nada com esse nome. Tente a cidade, ou conte por escrito ao assistente."
4565
+ }
4566
+ ) }),
4567
+ covered.length > 0 && /* @__PURE__ */ jsxs("div", { className: "mt-5 border-t border-border pt-4", children: [
4568
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
4569
+ /* @__PURE__ */ jsx(Ban, { className: "h-4 w-4 text-destructive" }),
4570
+ /* @__PURE__ */ jsx("h4", { className: "text-sm font-semibold", children: "E onde voc\xEA n\xE3o entrega?" })
4571
+ ] }),
4572
+ /* @__PURE__ */ jsx("p", { className: "mt-0.5 text-xs text-muted-foreground", children: coveredCities.length > 0 ? `Recortes dentro do que voc\xEA j\xE1 cobre \u2014 comunidade, bairro de risco, regi\xE3o distante. A exce\xE7\xE3o vence a \xE1rea maior, ent\xE3o ${coveredCities[0].label} continua valendo em todo o resto.` : "Regi\xF5es que voc\xEA quer recusar mesmo estando perto do que j\xE1 cobre. A exce\xE7\xE3o vence a \xE1rea maior." }),
4573
+ /* @__PURE__ */ jsx("div", { className: "mt-3", children: /* @__PURE__ */ jsx(
4574
+ AreaSearch,
4575
+ {
4576
+ provider,
4577
+ outcome: "refuse",
4578
+ placeholder: "Bairro ou regi\xE3o que voc\xEA recusa\u2026",
4579
+ onlyCityCodes: cityCodes,
4580
+ emptyHint: /* @__PURE__ */ jsx(Fragment, { children: "Nada com esse nome. Comunidade normalmente n\xE3o tem CEP pr\xF3prio \u2014 divide o do bairro vizinho. Escolha o bairro que a cont\xE9m e marque como \u201Cconfirmo antes\u201D na tabela." })
4581
+ }
4582
+ ) })
4583
+ ] }),
4584
+ draft2.rules.length > 0 && /* @__PURE__ */ jsxs("p", { className: "mt-3 text-xs text-muted-foreground", children: [
4585
+ covered.length,
4586
+ " ",
4587
+ covered.length === 1 ? "regi\xE3o" : "regi\xF5es",
4588
+ " onde voc\xEA entrega",
4589
+ exceptions.length > 0 && `, ${exceptions.length} ${exceptions.length === 1 ? "exce\xE7\xE3o" : "exce\xE7\xF5es"}`,
4590
+ " ",
4591
+ "\u2014 confira o pre\xE7o na tabela abaixo e salve."
4592
+ ] })
4593
+ ] });
4594
+ }
4595
+ var OUTCOME_LABEL = {
4596
+ deliver: "Entrego",
4597
+ review: "Confirmo antes",
4598
+ refuse: "N\xE3o entrego"
4599
+ };
4600
+ var OUTCOME_TONE = {
4601
+ deliver: "text-foreground",
4602
+ review: "text-amber-600 dark:text-amber-500",
4603
+ refuse: "text-destructive"
4604
+ };
4605
+ function parseMoney(raw) {
4606
+ const cleaned = raw.replace(/[^\d,.-]/g, "").replace(",", ".");
4607
+ if (!cleaned.trim()) return null;
4608
+ const n = Number(cleaned);
4609
+ return Number.isFinite(n) && n >= 0 ? Math.round(n * 100) / 100 : null;
4610
+ }
4611
+ var money2 = (v) => v == null ? "" : String(v).replace(".", ",");
4612
+ function ruleArea(rule) {
4613
+ if (rule.matchKind === "postal_range") {
4614
+ const fmt = (c) => c ? `${c.slice(0, 5)}-${c.slice(5)}` : "?";
4615
+ return `${fmt(rule.postalFrom)} \u2192 ${fmt(rule.postalTo)}`;
4616
+ }
4617
+ return rule.matchKind === "city" ? "Munic\xEDpio" : "Bairro";
4618
+ }
4619
+ function DeliveryRulesDraft({ provider, onSaved }) {
4620
+ const draft2 = useDeliveryDraft();
4621
+ const [saving, setSaving] = React13__default.useState(false);
4622
+ const [error, setError] = React13__default.useState(null);
4623
+ const [bulkRate, setBulkRate] = React13__default.useState("");
4624
+ const [bulkFree, setBulkFree] = React13__default.useState("");
4625
+ if (draft2.rules.length === 0 && draft2.gaps.length === 0) return null;
4626
+ const refusing = draft2.rules.filter((r) => r.outcome === "refuse").length;
4627
+ const reviewing = draft2.rules.filter((r) => r.outcome === "review").length;
4628
+ const delivering = draft2.rules.length - refusing - reviewing;
4629
+ const save = async () => {
4630
+ setSaving(true);
4631
+ setError(null);
4632
+ try {
4633
+ const shop = provider ? resolve(provider) : getShopProvider();
4634
+ if (typeof shop.createShippingZone !== "function") {
4635
+ throw new Error("Este banco ainda n\xE3o tem as regras de entrega.");
4636
+ }
4637
+ for (const rule of draft2.rules) {
4638
+ await shop.createShippingZone(zoneInputFrom(rule));
4639
+ }
4640
+ clearDeliveryDraft();
4641
+ onSaved();
4642
+ } catch (err) {
4643
+ setError(errorMessage(err));
4644
+ } finally {
4645
+ setSaving(false);
4646
+ }
4647
+ };
4648
+ return /* @__PURE__ */ jsxs("div", { className: "rounded-card border border-primary/40 bg-primary/5 shadow-sm", children: [
4649
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-3 border-b border-primary/20 px-5 py-4", children: [
4650
+ /* @__PURE__ */ jsx(ListChecks, { className: "h-4 w-4 text-primary" }),
4651
+ /* @__PURE__ */ jsxs("div", { className: "flex-1", children: [
4652
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: "Regras a salvar" }),
4653
+ /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground", children: [
4654
+ delivering,
4655
+ " entrega",
4656
+ delivering === 1 ? "" : "s",
4657
+ reviewing > 0 && `, ${reviewing} a confirmar`,
4658
+ refusing > 0 && `, ${refusing} recusa${refusing === 1 ? "" : "s"}`,
4659
+ " ",
4660
+ "\u2014 confira e corrija antes de salvar. Nada foi gravado ainda."
4661
+ ] })
4662
+ ] }),
4663
+ /* @__PURE__ */ jsx(
4664
+ "button",
4665
+ {
4666
+ type: "button",
4667
+ onClick: clearDeliveryDraft,
4668
+ disabled: saving,
4669
+ className: "rounded-md px-3 py-2 text-xs text-muted-foreground hover:bg-muted",
4670
+ children: "Descartar"
4671
+ }
4672
+ ),
4673
+ /* @__PURE__ */ jsxs(
4674
+ "button",
4675
+ {
4676
+ type: "button",
4677
+ onClick: save,
4678
+ disabled: saving || draft2.rules.length === 0,
4679
+ className: "inline-flex items-center gap-1.5 rounded-md bg-primary px-3.5 py-2 text-xs font-medium text-primary-foreground transition hover:opacity-90 disabled:opacity-50",
4680
+ children: [
4681
+ saving ? /* @__PURE__ */ jsx(Loader2, { className: "h-3.5 w-3.5 animate-spin" }) : /* @__PURE__ */ jsx(Check, { className: "h-3.5 w-3.5" }),
4682
+ "Salvar ",
4683
+ draft2.rules.length,
4684
+ " regra",
4685
+ draft2.rules.length === 1 ? "" : "s"
4686
+ ]
4687
+ }
4688
+ )
4689
+ ] }),
4690
+ error && /* @__PURE__ */ jsx("p", { className: "px-5 pt-3 text-xs text-destructive", children: error }),
4691
+ delivering + reviewing > 1 && /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2 border-b border-primary/10 px-5 py-3", children: [
4692
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "Mesmo pre\xE7o em todas:" }),
4693
+ /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1", children: [
4694
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "R$" }),
4695
+ /* @__PURE__ */ jsx(
4696
+ "input",
4697
+ {
4698
+ value: bulkRate,
4699
+ onChange: (e) => setBulkRate(e.target.value),
4700
+ placeholder: "15,00",
4701
+ "aria-label": "Frete para todas as regi\xF5es",
4702
+ className: "w-20 rounded-md border border-border bg-background px-2 py-1 text-xs"
4703
+ }
4704
+ )
4705
+ ] }),
4706
+ /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1", children: [
4707
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "gr\xE1tis acima de R$" }),
4708
+ /* @__PURE__ */ jsx(
4709
+ "input",
4710
+ {
4711
+ value: bulkFree,
4712
+ onChange: (e) => setBulkFree(e.target.value),
4713
+ placeholder: "opcional",
4714
+ "aria-label": "Frete gr\xE1tis acima de, para todas as regi\xF5es",
4715
+ className: "w-24 rounded-md border border-border bg-background px-2 py-1 text-xs"
4716
+ }
4717
+ )
4718
+ ] }),
4719
+ /* @__PURE__ */ jsx(
4720
+ "button",
4721
+ {
4722
+ type: "button",
4723
+ onClick: () => {
4724
+ setAllDeliveryDraftRates({
4725
+ rate: parseMoney(bulkRate),
4726
+ freeAbove: parseMoney(bulkFree)
4727
+ });
4728
+ setBulkRate("");
4729
+ setBulkFree("");
4730
+ },
4731
+ disabled: !bulkRate.trim() && !bulkFree.trim(),
4732
+ className: "rounded-md border border-border px-2.5 py-1 text-xs font-medium hover:bg-muted disabled:opacity-40",
4733
+ children: "Aplicar"
4734
+ }
4735
+ )
4736
+ ] }),
4737
+ /* @__PURE__ */ jsx("div", { className: "overflow-x-auto", children: /* @__PURE__ */ jsxs("table", { className: "w-full text-sm", children: [
4738
+ /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { className: "text-left text-xs text-muted-foreground", children: [
4739
+ /* @__PURE__ */ jsx("th", { className: "px-5 py-2.5 font-medium", children: "Regi\xE3o" }),
4740
+ /* @__PURE__ */ jsx("th", { className: "px-2 py-2.5 font-medium", children: "O que fa\xE7o" }),
4741
+ /* @__PURE__ */ jsx("th", { className: "px-2 py-2.5 font-medium", children: "Frete" }),
4742
+ /* @__PURE__ */ jsx("th", { className: "px-2 py-2.5 font-medium", children: "Gr\xE1tis acima de" }),
4743
+ /* @__PURE__ */ jsx("th", { className: "px-2 py-2.5 font-medium", children: "Prazo (dias)" }),
4744
+ /* @__PURE__ */ jsx("th", { className: "px-2 py-2" })
4745
+ ] }) }),
4746
+ /* @__PURE__ */ jsx("tbody", { children: draft2.rules.map((rule) => /* @__PURE__ */ jsxs("tr", { className: "border-t border-primary/10", children: [
4747
+ /* @__PURE__ */ jsxs("td", { className: "px-5 py-2.5", children: [
4748
+ /* @__PURE__ */ jsx("span", { className: "font-medium", children: rule.label }),
4749
+ /* @__PURE__ */ jsx("span", { className: "ml-2 text-xs text-muted-foreground", children: ruleArea(rule) }),
4750
+ rule.source && /* @__PURE__ */ jsxs("p", { className: "text-xs italic text-muted-foreground", children: [
4751
+ "\u201C",
4752
+ rule.source,
4753
+ "\u201D"
4754
+ ] })
4755
+ ] }),
4756
+ /* @__PURE__ */ jsx("td", { className: "px-2 py-2.5", children: /* @__PURE__ */ jsx(
4757
+ "select",
4758
+ {
4759
+ value: rule.outcome,
4760
+ onChange: (e) => updateDeliveryDraftRule(rule.id, { outcome: e.target.value }),
4761
+ className: `rounded-md border border-border bg-background px-2 py-1.5 text-xs ${OUTCOME_TONE[rule.outcome]}`,
4762
+ children: Object.keys(OUTCOME_LABEL).map((value) => /* @__PURE__ */ jsx("option", { value, children: OUTCOME_LABEL[value] }, value))
4763
+ }
4764
+ ) }),
4765
+ rule.outcome === "refuse" ? /* @__PURE__ */ jsx("td", { className: "px-2 py-2.5 text-xs text-muted-foreground", colSpan: 3, children: "n\xE3o se cobra" }) : /* @__PURE__ */ jsxs(Fragment, { children: [
4766
+ /* @__PURE__ */ jsx("td", { className: "px-2 py-2.5", children: /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center rounded-md border border-border bg-background focus-within:ring-1 focus-within:ring-ring", children: [
4767
+ /* @__PURE__ */ jsx("span", { className: "pl-2 text-xs text-muted-foreground", children: "R$" }),
4768
+ /* @__PURE__ */ jsx(
4769
+ "input",
4770
+ {
4771
+ value: money2(rule.rate),
4772
+ placeholder: "0,00",
4773
+ "aria-label": `Frete para ${rule.label}`,
4774
+ onChange: (e) => updateDeliveryDraftRule(rule.id, { rate: parseMoney(e.target.value) }),
4775
+ className: "w-16 bg-transparent px-1.5 py-1.5 text-xs outline-none"
4776
+ }
4777
+ )
4778
+ ] }) }),
4779
+ /* @__PURE__ */ jsx("td", { className: "px-2 py-2.5", children: /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center rounded-md border border-border bg-background focus-within:ring-1 focus-within:ring-ring", children: [
4780
+ /* @__PURE__ */ jsx("span", { className: "pl-2 text-xs text-muted-foreground", children: "R$" }),
4781
+ /* @__PURE__ */ jsx(
4782
+ "input",
4783
+ {
4784
+ value: money2(rule.freeAbove),
4785
+ placeholder: "nunca",
4786
+ "aria-label": `Frete gr\xE1tis acima de, em ${rule.label}`,
4787
+ onChange: (e) => updateDeliveryDraftRule(rule.id, { freeAbove: parseMoney(e.target.value) }),
4788
+ className: "w-20 bg-transparent px-1.5 py-1.5 text-xs outline-none"
4789
+ }
4790
+ )
4791
+ ] }) }),
4792
+ /* @__PURE__ */ jsx("td", { className: "px-2 py-2.5", children: /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center rounded-md border border-border bg-background focus-within:ring-1 focus-within:ring-ring", children: [
4793
+ /* @__PURE__ */ jsx(
4794
+ "input",
4795
+ {
4796
+ value: rule.etaMinDays ?? "",
4797
+ placeholder: "0",
4798
+ inputMode: "numeric",
4799
+ "aria-label": `Prazo m\xEDnimo em ${rule.label}`,
4800
+ onChange: (e) => updateDeliveryDraftRule(rule.id, {
4801
+ etaMinDays: e.target.value ? Number(e.target.value) : null
4802
+ }),
4803
+ className: "w-10 bg-transparent py-1.5 pl-2 text-center text-xs outline-none"
4804
+ }
4805
+ ),
4806
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "a" }),
4807
+ /* @__PURE__ */ jsx(
4808
+ "input",
4809
+ {
4810
+ value: rule.etaMaxDays ?? "",
4811
+ placeholder: "2",
4812
+ inputMode: "numeric",
4813
+ "aria-label": `Prazo m\xE1ximo em ${rule.label}`,
4814
+ onChange: (e) => updateDeliveryDraftRule(rule.id, {
4815
+ etaMaxDays: e.target.value ? Number(e.target.value) : null
4816
+ }),
4817
+ className: "w-10 bg-transparent py-1.5 pr-2 text-center text-xs outline-none"
4818
+ }
4819
+ )
4820
+ ] }) })
4821
+ ] }),
4822
+ /* @__PURE__ */ jsx("td", { className: "px-5 py-2.5 text-right", children: /* @__PURE__ */ jsx(
4823
+ "button",
4824
+ {
4825
+ type: "button",
4826
+ onClick: () => removeDeliveryDraftRule(rule.id),
4827
+ "aria-label": `Remover ${rule.label}`,
4828
+ className: "rounded-md p-1 text-muted-foreground hover:bg-muted hover:text-destructive",
4829
+ children: /* @__PURE__ */ jsx(Trash2, { className: "h-3.5 w-3.5" })
4830
+ }
4831
+ ) })
4832
+ ] }, rule.id)) })
4833
+ ] }) }),
4834
+ draft2.gaps.length > 0 && /* @__PURE__ */ jsxs("div", { className: "border-t border-primary/20 px-5 py-3.5", children: [
4835
+ /* @__PURE__ */ jsx("p", { className: "text-xs font-medium text-muted-foreground", children: "O que ficou de fora \u2014 e por qu\xEA" }),
4836
+ /* @__PURE__ */ jsx("ul", { className: "mt-1 space-y-1", children: draft2.gaps.map((gap) => /* @__PURE__ */ jsxs("li", { className: "flex items-start gap-2 text-xs text-muted-foreground", children: [
4837
+ /* @__PURE__ */ jsx(X, { className: "mt-0.5 h-3 w-3 shrink-0 text-destructive" }),
4838
+ /* @__PURE__ */ jsxs("span", { children: [
4839
+ /* @__PURE__ */ jsx("span", { className: "font-medium text-foreground", children: gap.query }),
4840
+ " \u2014 ",
4841
+ gap.reason
4842
+ ] })
4843
+ ] }, gap.query)) })
4844
+ ] })
4845
+ ] });
4846
+ }
4847
+ function parseMoney2(raw) {
4848
+ const cleaned = raw.replace(/[^\d,.-]/g, "").replace(",", ".");
4849
+ if (!cleaned.trim()) return null;
4850
+ const n = Number(cleaned);
4851
+ return Number.isFinite(n) && n >= 0 ? Math.round(n * 100) / 100 : null;
4852
+ }
4853
+ var formatMoney = (v) => v == null ? "" : String(v).replace(".", ",");
4854
+ var FREIGHT_SOURCE = [
4855
+ {
4856
+ value: "own",
4857
+ label: "Eu mesmo calculo e despacho",
4858
+ description: "O frete sai do pre\xE7o base e das suas faixas de CEP, e voc\xEA despacha como quiser \u2014 carro pr\xF3prio, motoboy, ag\xEAncia. Nenhum gateway \xE9 consultado, mesmo que exista um conectado.",
4859
+ Icon: Car
4860
+ },
4861
+ {
4862
+ value: "gateway",
4863
+ label: "Perguntar a um gateway de frete",
4864
+ description: "O pre\xE7o e o prazo v\xEAm da transportadora, calculados pelo CEP do comprador. Quando o gateway n\xE3o responde a tempo, o checkout usa suas faixas \u2014 a compra nunca fica sem pre\xE7o.",
4865
+ Icon: Truck
4866
+ }
4867
+ ];
4868
+ function ShippingSettings({ provider }) {
4869
+ const [registryId, setRegistryId] = React13__default.useState(shippingRegistryIdFromHash);
4870
+ React13__default.useEffect(() => {
4871
+ const handler = () => setRegistryId(shippingRegistryIdFromHash());
4872
+ window.addEventListener("hashchange", handler);
4873
+ return () => window.removeEventListener("hashchange", handler);
4874
+ }, []);
4875
+ const shop = React13__default.useMemo(() => provider ? resolve(provider) : getShopProvider(), [provider]);
4876
+ const registries = React13__default.useMemo(
4877
+ () => buildShopRegistries(provider ?? getShopProvider),
4878
+ [provider]
4879
+ );
4880
+ const storeSettings = useStoreSettings(provider);
4881
+ const supported = typeof shop.getShippingSettings === "function" && typeof shop.updateShippingSettings === "function";
4882
+ const [row, setRow] = React13__default.useState(null);
4883
+ const [zones, setZones] = React13__default.useState(null);
4884
+ const [locations, setLocations] = React13__default.useState(null);
4885
+ const [loading, setLoading] = React13__default.useState(supported);
4886
+ const [saving, setSaving] = React13__default.useState(false);
4887
+ const [error, setError] = React13__default.useState(null);
4888
+ const [loadError, setLoadError] = React13__default.useState(null);
4889
+ const [flatDraft, setFlatDraft] = React13__default.useState("");
4890
+ const [freeDraft, setFreeDraft] = React13__default.useState("");
4891
+ const [flatBad, setFlatBad] = React13__default.useState(false);
4892
+ const [freeBad, setFreeBad] = React13__default.useState(false);
4893
+ const { connectors } = useConnectors({ hostPluginId: "shop", includeGrants: false });
4894
+ const [selected, setSelected] = React13__default.useState(null);
4895
+ const panelAvailable = useConnectorsPanelAvailable();
4896
+ const openJourney = React13__default.useCallback(
4897
+ (connector) => {
4898
+ if (panelAvailable) openConnectorsPanel(connector);
4899
+ else setSelected(connector);
4900
+ },
4901
+ [panelAvailable]
4902
+ );
4903
+ const carriers = React13__default.useMemo(
4904
+ () => (connectors ?? []).filter((c) => c.category === "logistics" || c.category === "shipping"),
4905
+ [connectors]
4906
+ );
3776
4907
  const carriersConnected = carriers.some((c) => c.state !== "ready");
3777
- React9.useEffect(() => {
4908
+ const [zonesEpoch, setZonesEpoch] = React13__default.useState(0);
4909
+ const reloadZones = React13__default.useCallback(() => setZonesEpoch((n) => n + 1), []);
4910
+ React13__default.useEffect(() => {
3778
4911
  let cancelled = false;
3779
4912
  if (supported) {
3780
4913
  setLoading(true);
@@ -3802,8 +4935,8 @@ function ShippingSettings({ provider }) {
3802
4935
  return () => {
3803
4936
  cancelled = true;
3804
4937
  };
3805
- }, [shop, supported]);
3806
- const save = React9.useCallback(
4938
+ }, [shop, supported, zonesEpoch]);
4939
+ const save = React13__default.useCallback(
3807
4940
  (patch) => {
3808
4941
  if (!supported) return;
3809
4942
  setSaving(true);
@@ -3813,7 +4946,7 @@ function ShippingSettings({ provider }) {
3813
4946
  [shop, supported]
3814
4947
  );
3815
4948
  const commitFlat = () => {
3816
- const parsed = parseMoney(flatDraft);
4949
+ const parsed = parseMoney2(flatDraft);
3817
4950
  const next = flatDraft.trim() === "" ? 0 : parsed;
3818
4951
  if (next == null) {
3819
4952
  setFlatBad(true);
@@ -3829,7 +4962,7 @@ function ShippingSettings({ provider }) {
3829
4962
  if (row?.freeAbove != null) save({ freeAbove: null });
3830
4963
  return;
3831
4964
  }
3832
- const parsed = parseMoney(freeDraft);
4965
+ const parsed = parseMoney2(freeDraft);
3833
4966
  if (parsed == null) {
3834
4967
  setFreeBad(true);
3835
4968
  return;
@@ -3838,8 +4971,10 @@ function ShippingSettings({ provider }) {
3838
4971
  setFreeDraft(formatMoney(parsed));
3839
4972
  if (parsed !== row?.freeAbove) save({ freeAbove: parsed });
3840
4973
  };
3841
- const activeZones = (zones ?? []).filter((z) => z.active && !z.excluded);
3842
- const excludedZones = (zones ?? []).filter((z) => z.active && z.excluded);
4974
+ const outcomeOf2 = (z) => z.outcome ?? (z.excluded ? "refuse" : "deliver");
4975
+ const activeZones = (zones ?? []).filter((z) => z.active && outcomeOf2(z) === "deliver");
4976
+ const reviewZones = (zones ?? []).filter((z) => z.active && outcomeOf2(z) === "review");
4977
+ const excludedZones = (zones ?? []).filter((z) => z.active && outcomeOf2(z) === "refuse");
3843
4978
  const usesGateway = storeSettings.value.carrierQuotes !== false;
3844
4979
  const dispatching = (locations ?? []).filter((l) => l.status === "active" && l.shipsOnline !== false);
3845
4980
  const originCep = dispatching.find((l) => l.postalCode)?.postalCode ?? null;
@@ -3937,11 +5072,21 @@ function ShippingSettings({ provider }) {
3937
5072
  /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "Nenhum local tem CEP de origem. Sem ele a transportadora n\xE3o cota, e o checkout cai nas suas zonas \u2014 preencha o CEP do local padr\xE3o." })
3938
5073
  ] })
3939
5074
  ] }),
3940
- /* @__PURE__ */ jsxs("div", { children: [
5075
+ /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
3941
5076
  /* @__PURE__ */ jsxs("div", { className: "px-1 pb-1 pt-4", children: [
3942
- /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: "Zonas de entrega" }),
3943
- /* @__PURE__ */ jsx("p", { className: "mt-0.5 text-[10px] text-muted-foreground", children: "Faixas de CEP com pre\xE7o, prazo e frete gr\xE1tis pr\xF3prios. Um CEP fora de todas as zonas cai no pre\xE7o base acima; se a loja n\xE3o tiver pre\xE7o base nem zona que o cubra, o checkout diz que n\xE3o entrega ali. Uma faixa tamb\xE9m pode ser uma recusa \u2014 \xABn\xE3o entrego aqui\xBB \u2014 e ela vence qualquer zona e qualquer gateway." })
5077
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: "Onde voc\xEA entrega" }),
5078
+ /* @__PURE__ */ jsx("p", { className: "mt-0.5 text-[10px] text-muted-foreground", children: "As regi\xF5es onde a loja entrega, com pre\xE7o, prazo e frete gr\xE1tis pr\xF3prios. Um endere\xE7o fora de todas elas cai no pre\xE7o base acima; se a loja n\xE3o tiver pre\xE7o base nem regi\xE3o que o cubra, o checkout diz que n\xE3o entrega ali. Uma regi\xE3o tamb\xE9m pode ser uma recusa \u2014 \xABn\xE3o entrego aqui\xBB \u2014 e ela vence qualquer outra e qualquer gateway." })
3944
5079
  ] }),
5080
+ /* @__PURE__ */ jsx(
5081
+ DeliveryAgentSurface,
5082
+ {
5083
+ provider: () => shop,
5084
+ zones: zones ?? [],
5085
+ onSaved: reloadZones
5086
+ }
5087
+ ),
5088
+ /* @__PURE__ */ jsx(DeliveryAreaPicker, { provider }),
5089
+ /* @__PURE__ */ jsx(DeliveryRulesDraft, { provider, onSaved: reloadZones }),
3945
5090
  /* @__PURE__ */ jsx("div", { className: "rounded-card border bg-card px-5 shadow-sm", children: /* @__PURE__ */ jsxs(
3946
5091
  "button",
3947
5092
  {
@@ -3953,10 +5098,15 @@ function ShippingSettings({ provider }) {
3953
5098
  children: [
3954
5099
  /* @__PURE__ */ jsx(MapPin, { className: "h-4 w-4 shrink-0 text-muted-foreground" }),
3955
5100
  /* @__PURE__ */ jsxs("span", { className: "min-w-0 flex-1", children: [
3956
- /* @__PURE__ */ jsx("span", { className: "block text-sm font-medium", children: zones == null ? "Carregando zonas\u2026" : activeZones.length === 0 ? "Nenhuma zona ainda" : `${activeZones.length} ${activeZones.length === 1 ? "zona ativa" : "zonas ativas"}` }),
5101
+ /* @__PURE__ */ jsx("span", { className: "block text-sm font-medium", children: zones == null ? "Carregando regi\xF5es\u2026" : activeZones.length === 0 ? "Nenhuma regi\xE3o salva ainda" : `${activeZones.length} ${activeZones.length === 1 ? "regi\xE3o onde entrego" : "regi\xF5es onde entrego"}` }),
3957
5102
  /* @__PURE__ */ jsxs("span", { className: "mt-0.5 block text-xs text-muted-foreground", children: [
3958
- activeZones.length === 0 ? "Sem zonas, toda entrega usa o pre\xE7o base." : activeZones.slice(0, 3).map((z) => z.name).join(" \xB7 "),
5103
+ activeZones.length === 0 ? "Sem regi\xE3o salva, toda entrega usa o pre\xE7o base. Escolha as suas na busca acima." : activeZones.slice(0, 3).map((z) => z.name).join(" \xB7 "),
3959
5104
  activeZones.length > 3 && ` e ${activeZones.length - 3} mais`,
5105
+ reviewZones.length > 0 && /* @__PURE__ */ jsxs("span", { className: "mt-0.5 block text-amber-700 dark:text-amber-500", children: [
5106
+ reviewZones.length === 1 ? "1 regi\xE3o a confirmar antes de sair" : `${reviewZones.length} regi\xF5es a confirmar antes de sair`,
5107
+ " \u2014 ",
5108
+ reviewZones.slice(0, 2).map((z) => z.name).join(" \xB7 ")
5109
+ ] }),
3960
5110
  excludedZones.length > 0 && /* @__PURE__ */ jsxs("span", { className: "mt-0.5 block text-red-700 dark:text-red-400", children: [
3961
5111
  excludedZones.length === 1 ? "1 regi\xE3o recusada" : `${excludedZones.length} regi\xF5es recusadas`,
3962
5112
  " \u2014 ",
@@ -4120,7 +5270,7 @@ var melhorEnvioConnectorDef = {
4120
5270
  direction: "outbound",
4121
5271
  docsUrl: "https://docs.melhorenvio.com.br",
4122
5272
  // A quote WRITES NOTHING — `createShippingQuoter` says so in as many words,
4123
- // and 029_connections.sql is why: one row per quote would make plg_sync_runs
5273
+ // and 029_connections.sql is why: one row per quote would make sync_runs
4124
5274
  // the largest table in the pool within weeks. So `order` is read-only here,
4125
5275
  // and the only write is the label, which is an effect with an idempotency key
4126
5276
  // (`createLabelBuyer`) because a label bought twice is money spent twice.
@@ -4191,7 +5341,7 @@ var melhorEnvioConnectorDef = {
4191
5341
  ),
4192
5342
  ...connectionSettings(store),
4193
5343
  // This used to say the platform's redirect route writes `active = true`. It
4194
- // does not, and says so itself: "It does not touch `plg_connections` in the
5344
+ // does not, and says so itself: "It does not touch `connections` in the
4195
5345
  // tenant's pool. Per FAY-1388 ... the app writes its own." Bling shipped with
4196
5346
  // the same belief and looped — authorize, come back, still "not connected",
4197
5347
  // click again, forever.
@@ -4342,7 +5492,7 @@ var appmaxConnectorDef = {
4342
5492
  }
4343
5493
  ],
4344
5494
  // Read from the row the list already batched, so a screen with N connectors
4345
- // costs one plg_connections request rather than N.
5495
+ // costs one connections request rather than N.
4346
5496
  ...connectionStatus(
4347
5497
  APPMAX_CONNECTOR_ID,
4348
5498
  (connection) => ({
@@ -4618,7 +5768,7 @@ var abacatePayConnectorDef = {
4618
5768
  }
4619
5769
  ],
4620
5770
  // Read from the row the list already batched, so a screen with N connectors
4621
- // costs one plg_connections request rather than N.
5771
+ // costs one connections request rather than N.
4622
5772
  ...connectionStatus(
4623
5773
  ABACATEPAY_CONNECTOR_ID,
4624
5774
  (connection) => ({
@@ -4763,7 +5913,7 @@ var stripeConnectConnectorDef = {
4763
5913
  // Reached with no values when the browser returns marked `connected`. It is
4764
5914
  // required even though nothing is collected here: the platform callback stores
4765
5915
  // the credential but deliberately never touches the pool, so this is the only
4766
- // thing that writes the tenant's `plg_connections` row.
5916
+ // thing that writes the tenant's `connections` row.
4767
5917
  saveConnection: async () => {
4768
5918
  await store4.save({ active: true });
4769
5919
  },
@@ -5037,7 +6187,7 @@ var vindiConnectorDef = {
5037
6187
  }
5038
6188
  ],
5039
6189
  // Read from the row the list already batched, so a screen with N connectors
5040
- // costs one plg_connections request rather than N.
6190
+ // costs one connections request rather than N.
5041
6191
  ...connectionStatus(
5042
6192
  VINDI_CONNECTOR_ID,
5043
6193
  (connection) => ({
@@ -5068,39 +6218,24 @@ var vindiConnectorDef = {
5068
6218
  };
5069
6219
  var MERCADOPAGO_CONNECTOR_ID = "mercadopago";
5070
6220
  var MERCADOPAGO_LATENCY_BUDGET_MS = 6e3;
5071
- var MERCADOPAGO_FIELDS = [
5072
- {
5073
- key: "accessToken",
5074
- label: "Access token",
5075
- type: "password",
5076
- secret: true,
5077
- placeholder: "APP_USR-\u2026 (Suas integra\xE7\xF5es \u2192 sua aplica\xE7\xE3o \u2192 Credenciais)"
5078
- },
5079
- {
5080
- key: "publicKey",
5081
- label: "Public key",
5082
- // Not masked: it travels to the browser anyway, and a merchant who cannot
5083
- // see it cannot tell the test key from the production one at a glance.
5084
- type: "text",
5085
- secret: true,
5086
- placeholder: "APP_USR-\u2026"
5087
- }
5088
- ];
5089
- var store6 = createConnectionStore(MERCADOPAGO_CONNECTOR_ID, {
5090
- fields: MERCADOPAGO_FIELDS
5091
- });
5092
- function connectionDetail2(settings) {
5093
- const key = String(settings?.publicKey ?? "").trim();
5094
- if (!key) return "Pix e cart\xE3o";
5095
- return /^TEST-/i.test(key) ? "Sandbox \xB7 Pix e cart\xE3o" : "Produ\xE7\xE3o \xB7 Pix e cart\xE3o";
6221
+ var store6 = createConnectionStore(MERCADOPAGO_CONNECTOR_ID);
6222
+ function connectionDetail2(connection) {
6223
+ const account = String(connection?.displayName ?? "").trim();
6224
+ return account ? `${account} \xB7 Pix e cart\xE3o` : "Pix e cart\xE3o";
5096
6225
  }
5097
6226
  function MercadoPagoExtraPanel() {
5098
6227
  const [connected, setConnected] = useState(false);
6228
+ const [account, setAccount] = useState({ name: null, id: null });
5099
6229
  const [loaded, setLoaded] = useState(false);
5100
6230
  useEffect(() => {
5101
6231
  let cancelled = false;
5102
6232
  void store6.get().then((c) => {
5103
- if (!cancelled) setConnected(!!c?.active);
6233
+ if (cancelled) return;
6234
+ setConnected(!!c?.active);
6235
+ setAccount({
6236
+ name: String(c?.displayName ?? "").trim() || null,
6237
+ id: String(c?.providerAccountKey ?? "").trim() || null
6238
+ });
5104
6239
  }).catch(() => {
5105
6240
  }).finally(() => {
5106
6241
  if (!cancelled) setLoaded(true);
@@ -5111,6 +6246,29 @@ function MercadoPagoExtraPanel() {
5111
6246
  }, []);
5112
6247
  if (!loaded) return null;
5113
6248
  return /* @__PURE__ */ jsxs("div", { className: "space-y-2 border-t pt-4 text-xs text-muted-foreground", children: [
6249
+ connected && (account.name || account.id) && /* Named before anything else is explained. "Conectado" is not an answer
6250
+ to "connected to WHAT" — and a store whose money is going to the
6251
+ wrong Mercado Pago account looks identical to one whose money is not. */
6252
+ /* @__PURE__ */ jsxs("div", { className: "rounded-md border bg-muted/40 p-3", children: [
6253
+ /* @__PURE__ */ jsx("p", { className: "font-medium text-foreground", children: "Conta conectada" }),
6254
+ account.name && /* @__PURE__ */ jsx("p", { className: "mt-1 break-words", children: account.name }),
6255
+ account.id && /* @__PURE__ */ jsxs("p", { className: "mt-0.5", children: [
6256
+ "ID no Mercado Pago: ",
6257
+ /* @__PURE__ */ jsx("code", { className: "rounded bg-muted px-1 py-0.5", children: account.id })
6258
+ ] }),
6259
+ /* @__PURE__ */ jsxs("p", { className: "mt-1", children: [
6260
+ "N\xE3o \xE9 esta a conta? Clique em ",
6261
+ /* @__PURE__ */ jsx("strong", { children: "Desconectar" }),
6262
+ " e conecte de novo, logado na conta certa do Mercado Pago."
6263
+ ] })
6264
+ ] }),
6265
+ connected && !account.name && !account.id && /* An older connection, made before the account was recorded. Saying so
6266
+ beats showing nothing and letting it read as "no account". */
6267
+ /* @__PURE__ */ jsxs("p", { className: "rounded-md border border-dashed p-3", children: [
6268
+ "Esta conex\xE3o foi feita antes de a loja passar a guardar qual conta foi autorizada. Para ver o nome da conta aqui, clique em ",
6269
+ /* @__PURE__ */ jsx("strong", { children: "Desconectar" }),
6270
+ " e conecte novamente."
6271
+ ] }),
5114
6272
  connected ? /* @__PURE__ */ jsxs(Fragment, { children: [
5115
6273
  /* @__PURE__ */ jsxs("p", { children: [
5116
6274
  "Pix e cart\xE3o acontecem ",
@@ -5120,32 +6278,35 @@ function MercadoPagoExtraPanel() {
5120
6278
  /* @__PURE__ */ jsx("p", { children: "O n\xFAmero do cart\xE3o n\xE3o passa pelo seu site: ele \xE9 digitado dentro de um formul\xE1rio do pr\xF3prio Mercado Pago e vira um c\xF3digo de uso \xFAnico antes de chegar at\xE9 n\xF3s. \xC9 por isso que d\xE1 para receber cart\xE3o na sua p\xE1gina sem a auditoria que isso normalmente exige." }),
5121
6279
  /* @__PURE__ */ jsx("p", { children: 'O valor cobrado \xE9 sempre recalculado aqui no servidor a partir do pedido. Pre\xE7o alterado no navegador n\xE3o \xE9 aceito, e clicar duas vezes em "pagar" gera uma cobran\xE7a s\xF3.' }),
5122
6280
  /* @__PURE__ */ jsx("p", { children: "O Pix vale por 30 minutos. Se o cliente fechar a aba depois de pagar, o pedido aparece aqui do mesmo jeito \u2014 o Mercado Pago avisa a loja assim que o dinheiro cai." })
5123
- ] }) : /* @__PURE__ */ jsxs("p", { children: [
5124
- "As duas chaves ficam no painel do Mercado Pago, em ",
5125
- /* @__PURE__ */ jsx("strong", { children: "Suas integra\xE7\xF5es" }),
5126
- " \u2192 sua aplica\xE7\xE3o \u2192 ",
5127
- /* @__PURE__ */ jsx("strong", { children: "Credenciais de produ\xE7\xE3o" }),
5128
- ". Copie o ",
5129
- /* @__PURE__ */ jsx("em", { children: "access token" }),
5130
- " e a",
5131
- " ",
5132
- /* @__PURE__ */ jsx("em", { children: "public key" }),
5133
- " da ",
5134
- /* @__PURE__ */ jsx("strong", { children: "mesma" }),
5135
- " aplica\xE7\xE3o: elas s\xE3o um par, e uma sem a outra n\xE3o fecha uma venda."
6281
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
6282
+ /* @__PURE__ */ jsxs("p", { children: [
6283
+ "Clique em ",
6284
+ /* @__PURE__ */ jsx("strong", { children: "Conectar" }),
6285
+ " e voc\xEA vai para o Mercado Pago autorizar a Fayz. \xC9 na tela deles, com o seu login \u2014 nenhuma senha e nenhuma chave passam por aqui."
6286
+ ] }),
6287
+ /* @__PURE__ */ jsxs("p", { children: [
6288
+ "O dinheiro continua caindo na ",
6289
+ /* @__PURE__ */ jsx("strong", { children: "sua" }),
6290
+ " conta do Mercado Pago. A autoriza\xE7\xE3o s\xF3 permite abrir cobran\xE7as em nome da sua loja, e voc\xEA pode cancel\xE1-la quando quiser no painel do Mercado Pago."
6291
+ ] }),
6292
+ /* @__PURE__ */ jsxs("p", { children: [
6293
+ "Use uma conta ",
6294
+ /* @__PURE__ */ jsx("strong", { children: "PJ" }),
6295
+ ", com o CNPJ da loja. Conta pessoa f\xEDsica costuma vir com bandeiras de cart\xE3o bloqueadas at\xE9 a verifica\xE7\xE3o terminar \u2014 e o bloqueio aparece como uma compra recusada, n\xE3o como um aviso."
6296
+ ] })
5136
6297
  ] }),
5137
6298
  /* @__PURE__ */ jsxs("div", { className: "rounded-md border border-dashed p-3", children: [
5138
6299
  /* @__PURE__ */ jsx("p", { className: "font-medium text-foreground", children: "Para testar sem cobrar de verdade" }),
5139
6300
  /* @__PURE__ */ jsxs("p", { className: "mt-1", children: [
5140
- "As chaves que come\xE7am com ",
6301
+ "O teste \xE9 feito com ",
6302
+ /* @__PURE__ */ jsx("strong", { children: "usu\xE1rios de teste" }),
6303
+ ": crie um vendedor e um comprador em Suas integra\xE7\xF5es \u2192 Contas de teste, e autorize aqui logado como o vendedor de teste. N\xE3o \xE9 dinheiro real, e \xE9 assim que o pr\xF3prio Mercado Pago recomenda. Credenciais que come\xE7am com",
6304
+ " ",
5141
6305
  /* @__PURE__ */ jsx("code", { className: "rounded bg-muted px-1 py-0.5", children: "TEST-" }),
5142
6306
  " ",
5143
6307
  /* @__PURE__ */ jsx("strong", { children: "n\xE3o funcionam" }),
5144
- " aqui \u2014 o Mercado Pago as recusa nesta API. O teste \xE9 feito com ",
5145
- /* @__PURE__ */ jsx("strong", { children: "usu\xE1rios de teste" }),
5146
- ": crie um vendedor e um comprador em Suas integra\xE7\xF5es \u2192 Contas de teste, e use as chaves de ",
5147
- /* @__PURE__ */ jsx("em", { children: "produ\xE7\xE3o" }),
5148
- " do vendedor de teste. N\xE3o \xE9 dinheiro real, e \xE9 assim que o pr\xF3prio Mercado Pago recomenda."
6308
+ " ",
6309
+ "nesta API \u2014 o usu\xE1rio de teste usa as chaves de produ\xE7\xE3o dele."
5149
6310
  ] })
5150
6311
  ] })
5151
6312
  ] });
@@ -5163,15 +6324,25 @@ var mercadoPagoConnectorDef = {
5163
6324
  // its own domain, so a path would not.
5164
6325
  logoUrl: "https://assets.fayz.ai/connectors/mercadopago.png",
5165
6326
  category: "payments",
5166
- // Pasted keys today. Unlike Vindi — whose consent screen only LOOKS like OAuth
5167
- // — Mercado Pago has a real merchant OAuth, and it is the right end state: one
5168
- // platform application, each seller authorizing it, `marketplace_fee` taken on
5169
- // the charge instead of invoiced afterwards. What it needs is the redirect URI
5170
- // registered against the platform broker, which is a change in ymaia and not
5171
- // here. The same slug is already declared by plugin-financial with
5172
- // `authKind: 'oauth'`, and because a credential is keyed by slug + tenant, a
5173
- // store that authorizes there is redeemable from here the day that lands.
5174
- authKind: "api-key",
6327
+ // A real consent screen, unlike Vindi's — whose flow only LOOKS like OAuth and
6328
+ // is a CODE copied out of an address bar. The seller authorizes Fayz once at
6329
+ // auth.mercadopago.com, the money keeps landing in their own account, and
6330
+ // `marketplace_fee` takes our cut on the way past.
6331
+ //
6332
+ // That fee is the reason this is not pasted keys: Mercado Pago's docs are
6333
+ // explicit that it is "exclusivo para integrações com OAuth", so a connection
6334
+ // made by copying credentials out of a panel can never earn. The pasted-key
6335
+ // version shipped first because it could be tested the same afternoon; this
6336
+ // is the one that can be a product.
6337
+ //
6338
+ // A store connected the old way keeps working and does not have to be
6339
+ // migrated by hand: the vault is keyed by slug + tenant, and the runtime reads
6340
+ // `accessToken` and `access_token` alike. Reconnecting through the button
6341
+ // upgrades it in place — same slug, same row, now with a refresh token.
6342
+ authKind: "oauth",
6343
+ // The whole of it. `provider` defaults to the connector id, and both are
6344
+ // `mercadopago` — the platform registry keys on that word.
6345
+ startOAuth: fayzConnectorOAuth(MERCADOPAGO_CONNECTOR_ID),
5175
6346
  // The charge is opened in the buyer's request path; the notification and the
5176
6347
  // refund are outbound sync work.
5177
6348
  planes: ["request", "sync"],
@@ -5203,7 +6374,6 @@ var mercadoPagoConnectorDef = {
5203
6374
  // confirmed without a payment is worse than a refused checkout. Same verdict
5204
6375
  // as the other four gateways, opposite to the carrier next door.
5205
6376
  onFailure: "block",
5206
- fields: MERCADOPAGO_FIELDS,
5207
6377
  invokedAt: [
5208
6378
  {
5209
6379
  key: "shop.checkout.session_open",
@@ -5219,7 +6389,7 @@ var mercadoPagoConnectorDef = {
5219
6389
  }
5220
6390
  ],
5221
6391
  // Read from the row the list already batched, so a screen with N connectors
5222
- // costs one plg_connections request rather than N.
6392
+ // costs one connections request rather than N.
5223
6393
  //
5224
6394
  // No `settingsStatus`: this connector has nothing to configure, so a
5225
6395
  // connection either has its credential pair or does not. "Conectado, falta
@@ -5228,7 +6398,7 @@ var mercadoPagoConnectorDef = {
5228
6398
  MERCADOPAGO_CONNECTOR_ID,
5229
6399
  (connection) => ({
5230
6400
  connected: !!connection?.active,
5231
- detail: connectionDetail2(connection?.settings),
6401
+ detail: connectionDetail2(connection),
5232
6402
  plane: "request"
5233
6403
  }),
5234
6404
  { store: () => store6 }
@@ -5243,190 +6413,16 @@ var mercadoPagoConnectorDef = {
5243
6413
  // key alone cannot open an order. Testing belongs to the edge function that
5244
6414
  // holds the credential.
5245
6415
  saveConnection: async () => {
5246
- await store6.save({ active: true });
6416
+ const account = await describeConnectorAccount({ connectorId: MERCADOPAGO_CONNECTOR_ID });
6417
+ await store6.save({
6418
+ active: true,
6419
+ ...account?.displayName ? { displayName: account.displayName } : {},
6420
+ ...account?.providerAccountKey ? { providerAccountKey: account.providerAccountKey } : {}
6421
+ });
5247
6422
  },
5248
6423
  disconnect: () => store6.disconnect(),
5249
6424
  ExtraPanel: MercadoPagoExtraPanel
5250
6425
  };
5251
- var KIND_META = {
5252
- online_store: { label: "Loja Online", icon: Globe, hint: "Seu storefront fayz \u2014 a vitrine p\xFAblica da loja.", placeholder: "https://minhaloja.com.br/" },
5253
- whatsapp: { label: "WhatsApp", icon: MessageCircle, hint: "Link direto para o WhatsApp Business.", placeholder: "https://wa.me/5521999999999" },
5254
- instagram: { label: "Instagram", icon: Camera, hint: "Perfil ou loja no Instagram.", placeholder: "https://instagram.com/sualoja" },
5255
- tiktok_shop: { label: "TikTok Shop", icon: Music, hint: "Sua vitrine no TikTok Shop.", placeholder: "https://shop.tiktok.com/@sualoja" },
5256
- marketplace: { label: "Marketplace", icon: ShoppingBag, hint: "Mercado Livre, Amazon, Shopee\u2026", placeholder: "https://\u2026" },
5257
- physical: { label: "Loja F\xEDsica", icon: MapPin, hint: "Endere\xE7o ou ponto de venda f\xEDsico.", placeholder: "https://maps.google.com/\u2026" },
5258
- custom: { label: "Outro canal", icon: Link2, hint: "Qualquer outro ponto de venda com um link.", placeholder: "https://\u2026" }
5259
- };
5260
- var ADDABLE_KINDS = ["whatsapp", "instagram", "tiktok_shop", "marketplace", "physical", "custom"];
5261
- function resolve2(resolver) {
5262
- return typeof resolver === "function" ? resolver() : resolver ?? getShopProvider();
5263
- }
5264
- function notifyChannelsChanged() {
5265
- if (typeof window !== "undefined") window.dispatchEvent(new Event("fayz:channels-changed"));
5266
- }
5267
- function ChannelsPage({ resolveProvider }) {
5268
- const provider = React9.useMemo(() => resolve2(resolveProvider), [resolveProvider]);
5269
- const supported = typeof provider.listSalesChannels === "function";
5270
- const [channels, setChannels] = React9.useState([]);
5271
- const [loading, setLoading] = React9.useState(true);
5272
- const [adding, setAdding] = React9.useState(null);
5273
- const reload = React9.useCallback(async () => {
5274
- if (!provider.listSalesChannels) {
5275
- setLoading(false);
5276
- return;
5277
- }
5278
- setLoading(true);
5279
- try {
5280
- const rows = await provider.listSalesChannels();
5281
- if (!rows.some((c) => c.kind === "online_store") && provider.createSalesChannel) {
5282
- await provider.createSalesChannel({ kind: "online_store", label: "Loja Online", enabled: true, position: 0 });
5283
- setChannels(await provider.listSalesChannels());
5284
- } else {
5285
- setChannels(rows);
5286
- }
5287
- } finally {
5288
- setLoading(false);
5289
- }
5290
- }, [provider]);
5291
- React9.useEffect(() => {
5292
- void reload();
5293
- }, [reload]);
5294
- const patch = async (id, input) => {
5295
- if (!provider.updateSalesChannel) return;
5296
- const updated = await provider.updateSalesChannel(id, input);
5297
- setChannels((prev) => prev.map((c) => c.id === id ? updated : c));
5298
- notifyChannelsChanged();
5299
- };
5300
- const remove = async (id) => {
5301
- if (!provider.deleteSalesChannel) return;
5302
- await provider.deleteSalesChannel(id);
5303
- setChannels((prev) => prev.filter((c) => c.id !== id));
5304
- notifyChannelsChanged();
5305
- };
5306
- const add = async (kind, label, url) => {
5307
- if (!provider.createSalesChannel) return;
5308
- const created = await provider.createSalesChannel({ kind, label: label || KIND_META[kind].label, url: url || null, enabled: true, position: channels.length });
5309
- setChannels((prev) => [...prev, created]);
5310
- setAdding(null);
5311
- notifyChannelsChanged();
5312
- };
5313
- if (!supported) {
5314
- return /* @__PURE__ */ jsxs("div", { className: "mx-auto max-w-4xl p-6", children: [
5315
- /* @__PURE__ */ jsx("h1", { className: "text-xl font-semibold text-foreground", children: "Canais de venda" }),
5316
- /* @__PURE__ */ jsx("p", { className: "mt-2 text-sm text-muted-foreground", children: "Este provedor de loja ainda n\xE3o exp\xF5e canais de venda." })
5317
- ] });
5318
- }
5319
- const online = channels.find((c) => c.kind === "online_store");
5320
- const others = channels.filter((c) => c.kind !== "online_store");
5321
- return /* @__PURE__ */ jsxs("div", { className: "mx-auto max-w-4xl p-6", children: [
5322
- /* @__PURE__ */ jsxs("div", { className: "mb-6", children: [
5323
- /* @__PURE__ */ jsx("h1", { className: "text-xl font-semibold text-foreground", children: "Canais de venda" }),
5324
- /* @__PURE__ */ jsx("p", { className: "mt-1 text-sm text-muted-foreground", children: "Onde sua loja vende. A Loja Online \xE9 o seu storefront fayz; os demais s\xE3o links que aparecem no menu lateral." })
5325
- ] }),
5326
- loading ? /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: "Carregando\u2026" }) : /* @__PURE__ */ jsxs("div", { className: "space-y-6", children: [
5327
- online && /* @__PURE__ */ jsx(OnlineStoreCard, { channel: online, onSave: (url) => patch(online.id, { url }), onToggle: (enabled) => patch(online.id, { enabled }) }),
5328
- /* @__PURE__ */ jsxs("div", { children: [
5329
- /* @__PURE__ */ jsx("div", { className: "mb-2 flex items-center justify-between", children: /* @__PURE__ */ jsx("h2", { className: "text-sm font-semibold text-foreground", children: "Outros canais" }) }),
5330
- /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
5331
- others.map((c) => /* @__PURE__ */ jsx(ChannelRow, { channel: c, onSave: (url) => patch(c.id, { url }), onToggle: (enabled) => patch(c.id, { enabled }), onRemove: () => remove(c.id) }, c.id)),
5332
- others.length === 0 && /* @__PURE__ */ jsx("p", { className: "rounded-card border border-dashed border-border p-4 text-sm text-muted-foreground", children: "Nenhum canal adicional ainda. Adicione WhatsApp, Instagram, TikTok Shop e outros pontos de venda." })
5333
- ] }),
5334
- adding ? /* @__PURE__ */ jsx(AddChannelForm, { kind: adding, onCancel: () => setAdding(null), onAdd: (label, url) => add(adding, label, url), onKind: setAdding }) : /* @__PURE__ */ jsx("div", { className: "mt-3 flex flex-wrap gap-2", children: ADDABLE_KINDS.map((k) => {
5335
- const Icon = KIND_META[k].icon;
5336
- return /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", onClick: () => setAdding(k), children: [
5337
- /* @__PURE__ */ jsx(Icon, { className: "mr-1.5 h-3.5 w-3.5" }),
5338
- " ",
5339
- KIND_META[k].label
5340
- ] }, k);
5341
- }) })
5342
- ] })
5343
- ] })
5344
- ] });
5345
- }
5346
- function OnlineStoreCard({ channel, onSave, onToggle }) {
5347
- const [url, setUrl] = React9.useState(channel.url ?? "");
5348
- const dirty = (channel.url ?? "") !== url;
5349
- const meta = KIND_META.online_store;
5350
- return /* @__PURE__ */ jsx("div", { className: "rounded-card border border-border bg-card p-5", children: /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3", children: [
5351
- /* @__PURE__ */ jsx("div", { className: "flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-primary/10", children: /* @__PURE__ */ jsx(meta.icon, { className: "h-5 w-5 text-primary" }) }),
5352
- /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
5353
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
5354
- /* @__PURE__ */ jsx("span", { className: "font-semibold text-foreground", children: "Loja Online" }),
5355
- /* @__PURE__ */ jsx("span", { className: `rounded-full px-2 py-0.5 text-xs font-medium ${channel.enabled ? "bg-emerald-100 text-emerald-800" : "bg-muted text-muted-foreground"}`, children: channel.enabled ? "Ativa" : "Inativa" })
5356
- ] }),
5357
- /* @__PURE__ */ jsx("p", { className: "mt-0.5 text-sm text-muted-foreground", children: meta.hint }),
5358
- /* @__PURE__ */ jsxs("div", { className: "mt-3 flex flex-col gap-2 sm:flex-row sm:items-center", children: [
5359
- /* @__PURE__ */ jsx(Input, { value: url, placeholder: meta.placeholder, onChange: (e) => setUrl(e.target.value), className: "flex-1" }),
5360
- /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
5361
- /* @__PURE__ */ jsx(Button, { size: "sm", disabled: !dirty, onClick: () => onSave(url.trim() || null), children: "Salvar" }),
5362
- /* @__PURE__ */ jsxs(Button, { size: "sm", variant: "outline", disabled: !channel.url, onClick: () => channel.url && window.open(channel.url, "_blank", "noopener,noreferrer"), children: [
5363
- /* @__PURE__ */ jsx(ExternalLink, { className: "mr-1.5 h-3.5 w-3.5" }),
5364
- " Ver loja"
5365
- ] })
5366
- ] })
5367
- ] }),
5368
- /* @__PURE__ */ jsx("div", { className: "mt-3", children: /* @__PURE__ */ jsx("button", { type: "button", onClick: () => onToggle(!channel.enabled), className: "text-xs font-medium text-muted-foreground hover:text-foreground", children: channel.enabled ? "Desativar canal" : "Ativar canal" }) })
5369
- ] })
5370
- ] }) });
5371
- }
5372
- function ChannelRow({ channel, onSave, onToggle, onRemove }) {
5373
- const [url, setUrl] = React9.useState(channel.url ?? "");
5374
- const [editing, setEditing] = React9.useState(false);
5375
- const meta = KIND_META[channel.kind] ?? KIND_META.custom;
5376
- const Icon = meta.icon;
5377
- return /* @__PURE__ */ jsx("div", { className: "rounded-card border border-border bg-card p-3", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
5378
- /* @__PURE__ */ jsx("div", { className: `flex h-9 w-9 shrink-0 items-center justify-center rounded-full ${channel.enabled ? "bg-primary/10" : "bg-muted"}`, children: /* @__PURE__ */ jsx(Icon, { className: `h-4 w-4 ${channel.enabled ? "text-primary" : "text-muted-foreground"}` }) }),
5379
- /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
5380
- /* @__PURE__ */ jsx("p", { className: "truncate text-sm font-medium text-foreground", children: channel.label }),
5381
- editing ? /* @__PURE__ */ jsx(Input, { value: url, placeholder: meta.placeholder, onChange: (e) => setUrl(e.target.value), className: "mt-1 h-8" }) : /* @__PURE__ */ jsx("p", { className: "truncate text-xs text-muted-foreground", children: channel.url || "Sem link configurado" })
5382
- ] }),
5383
- /* @__PURE__ */ jsx("div", { className: "flex shrink-0 items-center gap-1", children: editing ? /* @__PURE__ */ jsxs(Fragment, { children: [
5384
- /* @__PURE__ */ jsx(Button, { size: "sm", variant: "ghost", onClick: () => {
5385
- onSave(url.trim() || null);
5386
- setEditing(false);
5387
- }, children: /* @__PURE__ */ jsx(Check, { className: "h-4 w-4" }) }),
5388
- /* @__PURE__ */ jsx(Button, { size: "sm", variant: "ghost", onClick: () => {
5389
- setUrl(channel.url ?? "");
5390
- setEditing(false);
5391
- }, children: /* @__PURE__ */ jsx(X, { className: "h-4 w-4" }) })
5392
- ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
5393
- channel.url && /* @__PURE__ */ jsx(Button, { size: "sm", variant: "ghost", onClick: () => window.open(channel.url, "_blank", "noopener,noreferrer"), children: /* @__PURE__ */ jsx(ExternalLink, { className: "h-4 w-4" }) }),
5394
- /* @__PURE__ */ jsx("button", { type: "button", onClick: () => onToggle(!channel.enabled), title: channel.enabled ? "Desativar" : "Ativar", className: `rounded-full px-2 py-0.5 text-xs font-medium ${channel.enabled ? "bg-emerald-100 text-emerald-800" : "bg-muted text-muted-foreground"}`, children: channel.enabled ? "Ativo" : "Inativo" }),
5395
- /* @__PURE__ */ jsx(Button, { size: "sm", variant: "ghost", onClick: () => setEditing(true), children: "Editar" }),
5396
- /* @__PURE__ */ jsx(Button, { size: "sm", variant: "ghost", onClick: onRemove, children: /* @__PURE__ */ jsx(Trash2, { className: "h-4 w-4 text-red-500" }) })
5397
- ] }) })
5398
- ] }) });
5399
- }
5400
- function AddChannelForm({ kind, onKind, onCancel, onAdd }) {
5401
- const meta = KIND_META[kind];
5402
- const [label, setLabel] = React9.useState(meta.label);
5403
- const [url, setUrl] = React9.useState("");
5404
- React9.useEffect(() => {
5405
- setLabel(KIND_META[kind].label);
5406
- }, [kind]);
5407
- return /* @__PURE__ */ jsxs("div", { className: "mt-3 rounded-card border border-border bg-card p-4", children: [
5408
- /* @__PURE__ */ jsx("div", { className: "mb-3 flex flex-wrap gap-2", children: ADDABLE_KINDS.map((k) => {
5409
- const Icon = KIND_META[k].icon;
5410
- return /* @__PURE__ */ jsxs("button", { type: "button", onClick: () => onKind(k), className: `flex items-center gap-1.5 rounded-full px-3 py-1 text-xs font-medium ${k === kind ? "bg-primary text-primary-foreground" : "bg-muted text-muted-foreground hover:text-foreground"}`, children: [
5411
- /* @__PURE__ */ jsx(Icon, { className: "h-3.5 w-3.5" }),
5412
- " ",
5413
- KIND_META[k].label
5414
- ] }, k);
5415
- }) }),
5416
- /* @__PURE__ */ jsx("p", { className: "mb-2 text-xs text-muted-foreground", children: meta.hint }),
5417
- /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2 sm:flex-row", children: [
5418
- /* @__PURE__ */ jsx(Input, { value: label, placeholder: "Nome do canal", onChange: (e) => setLabel(e.target.value), className: "sm:w-48" }),
5419
- /* @__PURE__ */ jsx(Input, { value: url, placeholder: meta.placeholder, onChange: (e) => setUrl(e.target.value), className: "flex-1" })
5420
- ] }),
5421
- /* @__PURE__ */ jsxs("div", { className: "mt-3 flex gap-2", children: [
5422
- /* @__PURE__ */ jsxs(Button, { size: "sm", onClick: () => onAdd(label, url), children: [
5423
- /* @__PURE__ */ jsx(Plus, { className: "mr-1.5 h-3.5 w-3.5" }),
5424
- " Adicionar"
5425
- ] }),
5426
- /* @__PURE__ */ jsx(Button, { size: "sm", variant: "outline", onClick: onCancel, children: "Cancelar" })
5427
- ] })
5428
- ] });
5429
- }
5430
6426
 
5431
6427
  // src/views/media-links.ts
5432
6428
  function recordRoute(reference) {
@@ -5443,7 +6439,7 @@ var TABS = [
5443
6439
  { value: "video", label: "V\xEDdeos" }
5444
6440
  ];
5445
6441
  var LOW_RESOLUTION_PX = 800;
5446
- function resolve3(resolver) {
6442
+ function resolve2(resolver) {
5447
6443
  return typeof resolver === "function" ? resolver() : resolver ?? getShopProvider();
5448
6444
  }
5449
6445
  function formatSize(bytes) {
@@ -5453,17 +6449,17 @@ function formatSize(bytes) {
5453
6449
  return `${(bytes / 1024 ** i).toFixed(i === 0 ? 0 : 1)} ${units[i]}`;
5454
6450
  }
5455
6451
  function ContentPage({ resolveProvider }) {
5456
- const provider = React9.useMemo(() => resolve3(resolveProvider), [resolveProvider]);
6452
+ const provider = React13__default.useMemo(() => resolve2(resolveProvider), [resolveProvider]);
5457
6453
  const supported = typeof provider.listMediaFiles === "function";
5458
- const [files, setFiles] = React9.useState(null);
5459
- const [tab, setTab] = React9.useState("all");
5460
- const [uploading, setUploading] = React9.useState(false);
5461
- const [pendingDelete, setPendingDelete] = React9.useState(null);
5462
- const [deleting, setDeleting] = React9.useState(false);
5463
- const [replacing, setReplacing] = React9.useState(false);
5464
- const [openId, setOpenId] = React9.useState(null);
5465
- const [dimensions, setDimensions] = React9.useState({});
5466
- const reload = React9.useCallback(async () => {
6454
+ const [files, setFiles] = React13__default.useState(null);
6455
+ const [tab, setTab] = React13__default.useState("all");
6456
+ const [uploading, setUploading] = React13__default.useState(false);
6457
+ const [pendingDelete, setPendingDelete] = React13__default.useState(null);
6458
+ const [deleting, setDeleting] = React13__default.useState(false);
6459
+ const [replacing, setReplacing] = React13__default.useState(false);
6460
+ const [openId, setOpenId] = React13__default.useState(null);
6461
+ const [dimensions, setDimensions] = React13__default.useState({});
6462
+ const reload = React13__default.useCallback(async () => {
5467
6463
  if (!provider.listMediaFiles) {
5468
6464
  setFiles([]);
5469
6465
  return;
@@ -5475,10 +6471,10 @@ function ContentPage({ resolveProvider }) {
5475
6471
  setFiles([]);
5476
6472
  }
5477
6473
  }, [provider]);
5478
- React9.useEffect(() => {
6474
+ React13__default.useEffect(() => {
5479
6475
  void reload();
5480
6476
  }, [reload]);
5481
- const measure = React9.useCallback((id, d) => {
6477
+ const measure = React13__default.useCallback((id, d) => {
5482
6478
  setDimensions((prev) => prev[id] ? prev : { ...prev, [id]: d });
5483
6479
  }, []);
5484
6480
  const handleUpload = async (picked) => {
@@ -5500,19 +6496,19 @@ function ContentPage({ resolveProvider }) {
5500
6496
  const handleReplace = async (file, picked) => {
5501
6497
  setReplacing(true);
5502
6498
  try {
5503
- let nextId;
6499
+ let nextId2;
5504
6500
  if (file.reference) {
5505
6501
  if (!provider.replaceProductImage) throw new Error("unsupported");
5506
6502
  const image = await provider.replaceProductImage(file.reference.attachmentId, picked);
5507
- nextId = productImageToMediaFile(image, file.reference.label).id;
6503
+ nextId2 = productImageToMediaFile(image, file.reference.label).id;
5508
6504
  } else {
5509
6505
  if (!provider.uploadMediaFile) throw new Error("unsupported");
5510
6506
  const uploaded = await provider.uploadMediaFile(picked);
5511
6507
  if (provider.deleteMediaFile) await provider.deleteMediaFile(file.id);
5512
- nextId = uploaded.id;
6508
+ nextId2 = uploaded.id;
5513
6509
  }
5514
6510
  await reload();
5515
- setOpenId(nextId);
6511
+ setOpenId(nextId2);
5516
6512
  toast.success("Arquivo trocado");
5517
6513
  } catch (err) {
5518
6514
  console.warn("[content] replace failed", err);
@@ -5668,7 +6664,7 @@ function SectionTitle({ title, count }) {
5668
6664
  ] });
5669
6665
  }
5670
6666
  function UploadButton({ uploading, onPick }) {
5671
- const inputRef = React9.useRef(null);
6667
+ const inputRef = React13__default.useRef(null);
5672
6668
  return /* @__PURE__ */ jsxs(Fragment, { children: [
5673
6669
  /* @__PURE__ */ jsx(
5674
6670
  "input",
@@ -5748,8 +6744,8 @@ function MediaDetail({
5748
6744
  onDelete,
5749
6745
  replacing
5750
6746
  }) {
5751
- const replaceRef = React9.useRef(null);
5752
- React9.useEffect(() => {
6747
+ const replaceRef = React13__default.useRef(null);
6748
+ React13__default.useEffect(() => {
5753
6749
  function onKey(e) {
5754
6750
  if (e.key === "ArrowLeft") onNavigate(-1);
5755
6751
  if (e.key === "ArrowRight") onNavigate(1);
@@ -6210,7 +7206,7 @@ var blingConnectorDef = {
6210
7206
  // where the question is actually asked. It is the only thing in that panel
6211
7207
  // that was neither status, nor history, nor an act.
6212
7208
  disconnectNote: "Desconectar congela o espelho: nenhum produto \xE9 apagado e o estoque para de ser atualizado.",
6213
- // No panel of its own. The history is `plg_sync_runs` read through the shared
7209
+ // No panel of its own. The history is `sync_runs` read through the shared
6214
7210
  // store, and the shared surface renders it — including the "first load brings
6215
7211
  // a sample" sentence, which it derives from `firstLoad` above rather than
6216
7212
  // repeating the number in prose.
@@ -6781,7 +7777,7 @@ var ORDERS = "plg_shop_rep_orders";
6781
7777
  var ITEMS = "plg_shop_rep_order_items";
6782
7778
  var CUSTOMERS = "plg_shop_rep_customers";
6783
7779
  var PRODUCTS = "plg_shop_rep_products";
6784
- var CANONICAL = "public.plg_shop_orders";
7780
+ var CANONICAL = "public.v_shop_orders";
6785
7781
  var CAT = {
6786
7782
  sales: tl("Sales", "Vendas"),
6787
7783
  orders: tl("Orders", "Pedidos"),
@@ -7604,7 +8600,7 @@ function buildShopAnalyticsCards() {
7604
8600
  }
7605
8601
 
7606
8602
  // src/index.ts
7607
- var ShopPage = React9.lazy(() => import('./ShopPage-QFIM2HDU.js').then((m) => ({ default: m.ShopPage })));
8603
+ var ShopPage = React13__default.lazy(() => import('./ShopPage-5IERN4E5.js').then((m) => ({ default: m.ShopPage })));
7608
8604
  function createShopPlugin(options) {
7609
8605
  const curr = { code: "BRL", locale: "pt-BR", symbol: "R$", ...options?.currency };
7610
8606
  setShopTenantResolver(() => useOrganizationStore.getState().currentOrg?.id);
@@ -7613,10 +8609,10 @@ function createShopPlugin(options) {
7613
8609
  registerEntityPath("shop_customer", "/orders/customers");
7614
8610
  registerPersonLookup("shop_customer", shopCustomerLookup(() => resolveProvider()));
7615
8611
  const pageFor = (section) => {
7616
- const Wrapped = () => React9.createElement(
7617
- React9.Suspense,
8612
+ const Wrapped = () => React13__default.createElement(
8613
+ React13__default.Suspense,
7618
8614
  { fallback: null },
7619
- React9.createElement(
8615
+ React13__default.createElement(
7620
8616
  ShopPage,
7621
8617
  { provider: options?.provider, section }
7622
8618
  )
@@ -7795,7 +8791,7 @@ function createShopPlugin(options) {
7795
8791
  dataProvider: createShopGiftCardDataProvider(resolveProvider),
7796
8792
  feature: "shop",
7797
8793
  stats: (items, total) => {
7798
- const money2 = (v) => new Intl.NumberFormat(curr.locale, { style: "currency", currency: curr.code }).format(v);
8794
+ const money3 = (v) => new Intl.NumberFormat(curr.locale, { style: "currency", currency: curr.code }).format(v);
7799
8795
  const active = items?.filter((g) => g.status === "active") ?? [];
7800
8796
  return [
7801
8797
  { id: "total", label: "Cart\xF5es emitidos", value: total, icon: CreditCard },
@@ -7806,23 +8802,22 @@ function createShopPlugin(options) {
7806
8802
  id: "outstanding",
7807
8803
  label: "Saldo em aberto",
7808
8804
  icon: CircleDollarSign,
7809
- value: money2(active.reduce((sum, g) => sum + (g.balance ?? 0), 0))
8805
+ value: money3(active.reduce((sum, g) => sum + (g.balance ?? 0), 0))
7810
8806
  },
7811
8807
  {
7812
8808
  id: "issued",
7813
8809
  label: "Valor emitido",
7814
8810
  icon: TicketCheck,
7815
- value: money2(items?.reduce((sum, g) => sum + (g.initialBalance ?? 0), 0) ?? 0)
8811
+ value: money3(items?.reduce((sum, g) => sum + (g.initialBalance ?? 0), 0) ?? 0)
7816
8812
  }
7817
8813
  ];
7818
8814
  }
7819
8815
  });
7820
- const ContentPageWrapped = () => React9.createElement(ContentPage, { resolveProvider: options?.provider });
8816
+ const ContentPageWrapped = () => React13__default.createElement(ContentPage, { resolveProvider: options?.provider });
7821
8817
  ContentPageWrapped.displayName = "ContentPage";
7822
- const ChannelsPageWrapped = () => React9.createElement(ChannelsPage, { resolveProvider: options?.provider });
7823
- ChannelsPageWrapped.displayName = "ChannelsPage";
7824
8818
  const navSection = options?.navSection ?? "main";
7825
8819
  const navPosition = options?.navPosition ?? 6;
8820
+ const siteRoute = options?.siteRoute ?? "/site";
7826
8821
  const NAV = [
7827
8822
  // Pedidos ▸ Pedidos · Clientes
7828
8823
  {
@@ -7887,7 +8882,6 @@ function createShopPlugin(options) {
7887
8882
  }
7888
8883
  ];
7889
8884
  const HIDDEN_ROUTES = [
7890
- { path: "/store", component: ChannelsPageWrapped, title: label("store", "Canais de venda") },
7891
8885
  // Reached from the Clientes tab strip, deliberately not from the menu:
7892
8886
  // three sidebar entries for one destination is the duplication the tab
7893
8887
  // strip exists to avoid. Nesting under /orders/customers also keeps
@@ -7909,7 +8903,7 @@ function createShopPlugin(options) {
7909
8903
  ];
7910
8904
  const redirectTo = (target) => {
7911
8905
  const Redirect = () => {
7912
- React9.useEffect(() => {
8906
+ React13__default.useEffect(() => {
7913
8907
  adminNavigateTo(target);
7914
8908
  }, []);
7915
8909
  return null;
@@ -7930,7 +8924,8 @@ function createShopPlugin(options) {
7930
8924
  // rota vem do mesmo lugar que a tela usa.
7931
8925
  { path: "/shop/shipping", component: redirectTo(shippingRegistryPath(SHIPPING_ZONES_REGISTRY)) },
7932
8926
  { path: "/store/shipping", component: redirectTo(shippingRegistryPath(SHIPPING_ZONES_REGISTRY)) },
7933
- { path: "/shop/channels", component: redirectTo("/store") },
8927
+ { path: "/store", component: redirectTo(siteRoute) },
8928
+ { path: "/shop/channels", component: redirectTo(siteRoute) },
7934
8929
  { path: "/customers", component: redirectTo("/orders/customers") },
7935
8930
  // A mensageria da loja virou parte de Automações — mesmos eventos, agora ao
7936
8931
  // lado dos do CRM e da agenda, e com WhatsApp além do e-mail.
@@ -7944,7 +8939,7 @@ function createShopPlugin(options) {
7944
8939
  // O que a loja pede de um tenant novo, na ordem em que faz diferença.
7945
8940
  // Vira linha de tarefa, aparece no checklist da trilha e é o roteiro que o
7946
8941
  // assistente segue — ver ./lib/onboarding.ts.
7947
- onboarding: { program: buildShopOnboarding() },
8942
+ onboarding: { program: buildShopOnboarding(siteRoute) },
7948
8943
  name: options?.navLabel ?? "Shop",
7949
8944
  icon: "ShoppingBag",
7950
8945
  version: "1.0.0",
@@ -8037,7 +9032,7 @@ function createShopPlugin(options) {
8037
9032
  label: label("store", "Loja"),
8038
9033
  icon: "ShoppingBag",
8039
9034
  group: COMMERCE_GROUP,
8040
- component: settingsTab("StoreGeneralSettings", () => React9.createElement(StoreGeneralSettings, { currency: curr, provider: options?.provider })),
9035
+ component: settingsTab("StoreGeneralSettings", () => React13__default.createElement(StoreGeneralSettings, { currency: curr, provider: options?.provider })),
8041
9036
  order: 30,
8042
9037
  permission: READ
8043
9038
  },
@@ -8046,7 +9041,7 @@ function createShopPlugin(options) {
8046
9041
  label: "Pagamentos",
8047
9042
  icon: "CreditCard",
8048
9043
  group: COMMERCE_GROUP,
8049
- component: settingsTab("PaymentsSettings", () => React9.createElement(PaymentsSettings, { provider: options?.provider })),
9044
+ component: settingsTab("PaymentsSettings", () => React13__default.createElement(PaymentsSettings, { provider: options?.provider })),
8050
9045
  order: 31,
8051
9046
  permission: READ
8052
9047
  },
@@ -8055,7 +9050,7 @@ function createShopPlugin(options) {
8055
9050
  label: "Checkout",
8056
9051
  icon: "ShoppingCart",
8057
9052
  group: COMMERCE_GROUP,
8058
- component: settingsTab("CheckoutSettings", () => React9.createElement(CheckoutSettings, { provider: options?.provider })),
9053
+ component: settingsTab("CheckoutSettings", () => React13__default.createElement(CheckoutSettings, { provider: options?.provider })),
8059
9054
  order: 32,
8060
9055
  permission: READ
8061
9056
  },
@@ -8064,7 +9059,7 @@ function createShopPlugin(options) {
8064
9059
  label: label("shipping", "Entrega"),
8065
9060
  icon: "Truck",
8066
9061
  group: COMMERCE_GROUP,
8067
- component: settingsTab("ShippingSettings", () => React9.createElement(ShippingSettings, { provider: options?.provider })),
9062
+ component: settingsTab("ShippingSettings", () => React13__default.createElement(ShippingSettings, { provider: options?.provider })),
8068
9063
  order: 33,
8069
9064
  permission: READ
8070
9065
  },
@@ -8073,7 +9068,7 @@ function createShopPlugin(options) {
8073
9068
  label: "Fulfillment",
8074
9069
  icon: "PackageCheck",
8075
9070
  group: COMMERCE_GROUP,
8076
- component: settingsTab("FulfillmentSettings", () => React9.createElement(FulfillmentSettings, { provider: options?.provider })),
9071
+ component: settingsTab("FulfillmentSettings", () => React13__default.createElement(FulfillmentSettings, { provider: options?.provider })),
8077
9072
  order: 34,
8078
9073
  permission: READ
8079
9074
  }