@fayz-ai/plugin-shop 0.12.0 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/dist/{ShopPage-VVLOH3RF.js → ShopPage-QFIM2HDU.js} +1494 -423
  2. package/dist/ShopPage-QFIM2HDU.js.map +1 -0
  3. package/dist/{chunk-HAKXHHAG.js → chunk-2VMTPRGC.js} +202 -11
  4. package/dist/chunk-2VMTPRGC.js.map +1 -0
  5. package/dist/index.d.ts +1 -0
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +1312 -298
  8. package/dist/index.js.map +1 -1
  9. package/dist/integrations/bling/data.d.ts.map +1 -1
  10. package/dist/integrations/mercadopago/connectorDef.d.ts +41 -0
  11. package/dist/integrations/mercadopago/connectorDef.d.ts.map +1 -0
  12. package/dist/integrations/neochef/data.d.ts.map +1 -1
  13. package/dist/integrations/vindi/connectorDef.d.ts +42 -29
  14. package/dist/integrations/vindi/connectorDef.d.ts.map +1 -1
  15. package/dist/lib/onboarding.d.ts +17 -0
  16. package/dist/lib/onboarding.d.ts.map +1 -0
  17. package/dist/views/CustomerModal.d.ts +33 -0
  18. package/dist/views/CustomerModal.d.ts.map +1 -0
  19. package/dist/views/NewOrderCharge.d.ts +26 -2
  20. package/dist/views/NewOrderCharge.d.ts.map +1 -1
  21. package/dist/views/NewOrderPage.d.ts +10 -1
  22. package/dist/views/NewOrderPage.d.ts.map +1 -1
  23. package/dist/views/OrderDetailView.d.ts.map +1 -1
  24. package/dist/views/OrdersAgentSurface.d.ts +10 -0
  25. package/dist/views/OrdersAgentSurface.d.ts.map +1 -0
  26. package/dist/views/ProductPositionsTab.d.ts +7 -0
  27. package/dist/views/ProductPositionsTab.d.ts.map +1 -0
  28. package/dist/views/ProductStockTab.d.ts +84 -0
  29. package/dist/views/ProductStockTab.d.ts.map +1 -0
  30. package/dist/views/ShopPage.d.ts.map +1 -1
  31. package/dist/views/StockEntryModal.d.ts +32 -0
  32. package/dist/views/StockEntryModal.d.ts.map +1 -0
  33. package/dist/views/customerEntity.d.ts.map +1 -1
  34. package/dist/views/discount-agent.d.ts +2 -0
  35. package/dist/views/discount-agent.d.ts.map +1 -0
  36. package/dist/views/new-order-rules.d.ts +13 -0
  37. package/dist/views/new-order-rules.d.ts.map +1 -1
  38. package/dist/views/orders-agent.d.ts +11 -0
  39. package/dist/views/orders-agent.d.ts.map +1 -0
  40. package/dist/views/product-matching.d.ts +38 -0
  41. package/dist/views/product-matching.d.ts.map +1 -0
  42. package/dist/views/productEntity.d.ts.map +1 -1
  43. package/dist/views/stock-agent.d.ts +82 -0
  44. package/dist/views/stock-agent.d.ts.map +1 -0
  45. package/package.json +8 -6
  46. package/dist/ShopPage-VVLOH3RF.js.map +0 -1
  47. package/dist/chunk-HAKXHHAG.js.map +0 -1
package/dist/index.js CHANGED
@@ -1,13 +1,196 @@
1
- import { shopCustomerLookup, resolve, invalidateProductLists, listProductsDetailed, applyQuery, errorMessage, packageMeasureLabel, PACKAGES_REGISTRY, packagesRegistry, SHIPPING_PROFILES_REGISTRY, SHIPPING_ZONES_REGISTRY, buildShopRegistries, locationSummary, LOCATIONS_REGISTRY } from './chunk-HAKXHHAG.js';
2
- import React8, { useState, useEffect, useRef } from 'react';
3
- import { createConnectionStore, connectionSettings, connectionStatus, settingsStatus, connectionRuns, tl, getSupabaseClientOptional, registerMessageDefaults, connectorRuntimeToken, CONNECTOR_RUNTIME_TOKEN_HEADER, 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-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';
4
4
  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, ClientOrdersTab, createClientOrdersProvider, 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, ChevronLeft, ChevronRight, Download, RefreshCw, Trash2, Upload, ExternalLink, Check, X, AlertTriangle, Box, ChevronDown, Film, FileText, Warehouse, ArrowRight, Car, Truck, Info } from 'lucide-react';
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
7
  import { PageHeaderActions, PageTabs, Card, Button, Thumbnail, PageHeader, toast, ConfirmDialog, Input, Hint, Modal, ModalContent, ModalHeader, ModalTitle, ModalBody, Skeleton } from '@fayz-ai/ui';
8
8
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
9
+ import { listStockReasonCodes, resolveStockStore, createSupabaseStockStore } from '@fayz-ai/shop/inventory';
10
+ import { normalizeMovementType } from '@fayz-ai/plugin-inventory';
9
11
  import { advanceWindow, createRateLimiter, sliceWindow, paginate, reconcileMirror, serializeCursor } from '@fayz-ai/core/connector-runtime';
10
12
 
13
+ function buildShopOnboarding() {
14
+ return {
15
+ id: "shop",
16
+ title: "Sua loja no ar",
17
+ order: 10,
18
+ role: "operations",
19
+ instructions: `Voc\xEA est\xE1 implantando uma LOJA. A pessoa n\xE3o quer aprender um sistema: ela quer vender.
20
+
21
+ - A ordem importa e \xE9 esta: ter o que vender \u2192 onde vender \u2192 como receber \u2192 como entregar \u2192 um motivo para comprar \u2192 a primeira venda. N\xE3o pule para "divulgue" antes de a loja aceitar um pedido.
22
+ - Importar cat\xE1logo \xE9 o passo que trava mais gente. Ofere\xE7a planilha, integra\xE7\xE3o (Bling, Nuvemshop) ou cadastrar um produto \xE0 m\xE3o para destravar \u2014 um produto j\xE1 \xE9 o suficiente para seguir.
23
+ - Em pagamento, pergunte COMO ela recebe hoje (Pix, cart\xE3o, maquininha) antes de falar de gateway. O nome do provedor \xE9 detalhe nosso, n\xE3o dela.
24
+ - Antes de dizer que a loja est\xE1 pronta, confira: tem produto ativo, tem forma de pagamento, e a vitrine est\xE1 publicada. Se faltar uma, diga qual falta.
25
+ - Quando estiver pronta para receber pedidos, chame o colega de growth para a primeira campanha \u2014 n\xE3o invente plano de m\xEDdia por conta pr\xF3pria.`,
26
+ steps: [
27
+ {
28
+ key: "catalog",
29
+ title: "Cadastre seu primeiro produto",
30
+ description: "Seu cat\xE1logo \xE9 o que a loja vende. Um produto j\xE1 destrava o resto.",
31
+ icon: "Package",
32
+ route: "/products",
33
+ priority: "high",
34
+ // O passo genérico da plataforma ("cadastre seu primeiro produto") e
35
+ // este são o mesmo recado — a loja é dona dele.
36
+ replaces: "catalog",
37
+ agentHint: "Pergunte onde o cat\xE1logo est\xE1 hoje (planilha, ERP, Instagram). Ofere\xE7a importar; se travar, um produto \xE0 m\xE3o j\xE1 destrava.",
38
+ howTo: "Produtos \u203A Adicionar. Nome, pre\xE7o e uma foto j\xE1 bastam para o produto aparecer na loja \u2014 estoque e varia\xE7\xF5es d\xE1 para completar depois. Se voc\xEA j\xE1 tem uma planilha, use Adicionar \u203A Importar: colunas de nome, pre\xE7o e estoque viram o cat\xE1logo inteiro de uma vez.",
39
+ celebrate: "Produto no ar \u{1F389}",
40
+ // A coreografia, como dado: nenhuma dessas quatro falas varia, e cada
41
+ // uma delas custava um turno de modelo para ser escrita de novo.
42
+ script: [
43
+ { kind: "say", text: "Vamos come\xE7ar pelo que a loja vende. Te levo at\xE9 os Produtos." },
44
+ { kind: "goto", route: "/products" },
45
+ {
46
+ kind: "say",
47
+ text: "Esta \xE9 a sua lista de produtos \u2014 hoje vazia. Tudo que entrar aqui aparece na vitrine."
48
+ },
49
+ {
50
+ // Três caminhos, e "cadastre um produto" assumia que só existia um.
51
+ // A pergunta é o passo: quem tem planilha não quer digitar, e quem
52
+ // não tem nada quer que alguém comece por ele.
53
+ kind: "choose",
54
+ id: "shop.catalog.how",
55
+ question: "Como voc\xEA prefere trazer seus produtos?",
56
+ options: [
57
+ { id: "import", label: "Tenho uma planilha", meta: "Importo e mapeio as colunas com voc\xEA" },
58
+ { id: "manual", label: "Vou cadastrar \xE0 m\xE3o", meta: "Te mostro onde, come\xE7ando por um" },
59
+ {
60
+ id: "suggest",
61
+ label: "Me sugira o cat\xE1logo",
62
+ meta: "Monto uma lista a partir do que j\xE1 sei do seu neg\xF3cio"
63
+ }
64
+ ]
65
+ }
66
+ ],
67
+ check: async () => await countByTenant("plg_shop_products") > 0,
68
+ tour: {
69
+ id: "shop.catalog",
70
+ stops: [
71
+ {
72
+ anchor: "action.new",
73
+ title: "\xC9 aqui que nasce um produto",
74
+ body: "Nome, pre\xE7o e uma foto j\xE1 bastam. Pode cadastrar agora \u2014 eu vejo quando ele entrar.",
75
+ route: "/products",
76
+ waitForUser: true
77
+ },
78
+ {
79
+ anchor: "action.import",
80
+ title: "Ou traga tudo de uma vez",
81
+ body: "Uma planilha com nome, pre\xE7o e estoque vira o cat\xE1logo inteiro."
82
+ }
83
+ ]
84
+ }
85
+ },
86
+ {
87
+ key: "storefront",
88
+ 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
+ icon: "Store",
91
+ route: "/store",
92
+ priority: "high",
93
+ // A plataforma chama de "publique seu site"; numa loja é a vitrine, e
94
+ // quem sabe configurá-la é este módulo.
95
+ 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.',
98
+ celebrate: "Vitrine pronta \u2728",
99
+ script: [
100
+ { kind: "say", text: "Agora a vitrine \u2014 o endere\xE7o que voc\xEA manda para o cliente." },
101
+ { kind: "goto", route: "/store" },
102
+ {
103
+ 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
+ }
106
+ ]
107
+ },
108
+ {
109
+ key: "payments",
110
+ title: "Decida como vai receber",
111
+ description: "Pix, cart\xE3o ou boleto \u2014 o dinheiro precisa de um caminho.",
112
+ icon: "CreditCard",
113
+ route: "/settings/shop-payments",
114
+ priority: "high",
115
+ replaces: "payments",
116
+ agentHint: "Comece por como ela j\xE1 recebe hoje. Pix costuma ser o caminho mais curto para a primeira venda; cart\xE3o vem depois.",
117
+ howTo: "Configura\xE7\xF5es \u203A Pagamentos. Ligue as formas que voc\xEA aceita e conecte o provedor de cada uma. Para Pix voc\xEA precisa da chave ou das credenciais do provedor; sem isso o pedido entra, mas o pagamento n\xE3o \xE9 confirmado sozinho.",
118
+ celebrate: "Recebimento no lugar \u{1F4B3}",
119
+ script: [
120
+ { kind: "goto", route: "/settings/shop-payments" },
121
+ {
122
+ kind: "ask",
123
+ text: "Estamos em Pagamentos. Pergunte como a pessoa recebe hoje (Pix, cart\xE3o, maquininha) antes de falar de provedor, e ligue a forma que ela usar."
124
+ }
125
+ ]
126
+ },
127
+ {
128
+ key: "shipping",
129
+ title: "Configure a entrega",
130
+ description: "Zonas, prazos e quanto custa o frete.",
131
+ icon: "Truck",
132
+ route: "/settings/shop-shipping",
133
+ agentHint: "Pergunte at\xE9 onde entregam e como (carro pr\xF3prio, transportadora, retirada). Frete errado \xE9 o motivo n\xBA 1 de carrinho abandonado.",
134
+ howTo: "Configura\xE7\xF5es \u203A Entrega. Crie uma zona (por CEP ou por cidade), diga o prazo e o pre\xE7o \u2014 pode ser valor fixo para come\xE7ar. Retirada no local \xE9 uma zona com frete zero.",
135
+ celebrate: "Entrega configurada \u{1F69A}",
136
+ script: [
137
+ { kind: "goto", route: "/settings/shop-shipping" },
138
+ {
139
+ kind: "ask",
140
+ text: "Estamos em Entrega. Pergunte at\xE9 onde a pessoa entrega e como, e monte a primeira zona junto com ela."
141
+ }
142
+ ]
143
+ },
144
+ {
145
+ key: "discount",
146
+ title: "Crie um cupom de desconto",
147
+ description: "D\xEA um motivo para o cliente fechar a primeira compra.",
148
+ icon: "Tag",
149
+ route: "/discounts",
150
+ priority: "low",
151
+ agentHint: "Este \xE9 passo de growth: sugira um cupom de primeira compra e ajude a decidir o valor. Se houver colega de growth, passe a bola.",
152
+ howTo: "Descontos \u203A Adicionar. Escolha o c\xF3digo (algo curto, tipo BEMVINDO10), o tipo (percentual ou valor) e at\xE9 quando vale. O cliente digita esse c\xF3digo no checkout."
153
+ },
154
+ {
155
+ key: "first-order",
156
+ title: "Receba seu primeiro pedido",
157
+ description: "Fa\xE7a um pedido de teste e veja ele cair aqui dentro.",
158
+ icon: "ShoppingBag",
159
+ route: "/orders",
160
+ agentHint: 'Um pedido de teste \xE9 o que transforma "acho que est\xE1 no ar" em "est\xE1 no ar". Acompanhe at\xE9 ele aparecer na lista.',
161
+ howTo: "Abra sua vitrine como se fosse um cliente, escolha um produto e finalize a compra. O pedido aparece em Pedidos em segundos \u2014 e \xE9 assim que voc\xEA confere que pre\xE7o, frete e pagamento est\xE3o certos antes do primeiro cliente de verdade.",
162
+ celebrate: "Primeiro pedido na casa \u{1F6CD}\uFE0F",
163
+ script: [
164
+ {
165
+ kind: "say",
166
+ text: "\xDAltimo: um pedido de teste. Abra sua vitrine como se fosse um cliente e finalize uma compra \u2014 eu aviso aqui quando ele cair."
167
+ },
168
+ { kind: "goto", route: "/orders" },
169
+ {
170
+ kind: "point",
171
+ anchor: "nav./orders",
172
+ title: "Todo pedido cai aqui",
173
+ body: "Pago, em separa\xE7\xE3o, enviado \u2014 o estado de cada venda vive nesta lista.",
174
+ waitForUser: true
175
+ }
176
+ ],
177
+ check: async () => await countByTenant("plg_shop_orders") > 0,
178
+ tour: {
179
+ id: "shop.orders",
180
+ stops: [
181
+ {
182
+ anchor: "nav./orders",
183
+ title: "Todo pedido cai aqui",
184
+ body: "Pago, em separa\xE7\xE3o, enviado \u2014 o estado de cada venda vive nesta lista.",
185
+ route: "/orders"
186
+ }
187
+ ]
188
+ }
189
+ }
190
+ ]
191
+ };
192
+ }
193
+
11
194
  // src/integrations/melhor-envio/labelProvider.ts
12
195
  var MELHOR_ENVIO_CARRIER = "melhor-envio";
13
196
  function createMelhorEnvioLabelProvider(resolve4) {
@@ -170,20 +353,20 @@ function ProductVariantsTab({ item, getProvider }) {
170
353
  const product = item;
171
354
  const shop = getProvider();
172
355
  const supported = typeof shop.getProductVariants === "function" && typeof shop.saveProductOptions === "function" && typeof shop.createProductVariant === "function" && typeof shop.updateProductVariant === "function";
173
- const [axes, setAxes] = React8.useState([]);
174
- const [variants, setVariants] = React8.useState([]);
175
- const [busy, setBusy] = React8.useState(false);
176
- const [error, setError] = React8.useState(null);
177
- const [selected, setSelected] = React8.useState(/* @__PURE__ */ new Set());
178
- const [dirty, setDirty] = React8.useState(false);
179
- const reload = React8.useCallback(async () => {
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 () => {
180
363
  const set = await getProvider().getProductVariants(product.id, { includeArchived: true });
181
364
  setAxes(axesFromOptions(set.options));
182
365
  setVariants(set.variants);
183
366
  setSelected(/* @__PURE__ */ new Set());
184
367
  setDirty(false);
185
368
  }, [getProvider, product.id]);
186
- React8.useEffect(() => {
369
+ React9.useEffect(() => {
187
370
  if (!supported) return;
188
371
  void reload().catch((e) => setError(message(e)));
189
372
  }, [reload, supported]);
@@ -199,7 +382,7 @@ function ProductVariantsTab({ item, getProvider }) {
199
382
  setBusy(false);
200
383
  }
201
384
  }
202
- const plan = React8.useMemo(() => planVariantMatrix(axes, variants), [axes, variants]);
385
+ const plan = React9.useMemo(() => planVariantMatrix(axes, variants), [axes, variants]);
203
386
  const axisError = validateVariantAxes(axes);
204
387
  const onlyDefault = hasOnlyDefaultVariant(variants);
205
388
  const rows = plan.rows;
@@ -300,7 +483,7 @@ function ProductVariantsTab({ item, getProvider }) {
300
483
  ] });
301
484
  }
302
485
  function AxisEditor({ axis, index, count, disabled, onChange, onMove, onRemove }) {
303
- const [draft, setDraft] = React8.useState("");
486
+ const [draft, setDraft] = React9.useState("");
304
487
  function commit(raw) {
305
488
  const values = raw.split(",").map((v) => v.trim()).filter(Boolean);
306
489
  if (values.length === 0) return;
@@ -447,9 +630,9 @@ function VariantRow({ row, axes, product, busy, checked, onToggle, onSave, onSto
447
630
  const variant = row.variant;
448
631
  const pending = !variant;
449
632
  const label = formatVariantCombination(row.options, axes);
450
- const commit = (field, value, previous) => {
633
+ const commit = (field2, value, previous) => {
451
634
  if (!variant || value === previous) return;
452
- onSave(variant.id, { [field]: value });
635
+ onSave(variant.id, { [field2]: value });
453
636
  };
454
637
  return /* @__PURE__ */ jsxs("tr", { className: pending ? "bg-muted/20 text-muted-foreground" : void 0, children: [
455
638
  /* @__PURE__ */ jsx("td", { className: "px-2 py-1.5", children: /* @__PURE__ */ jsx(
@@ -573,9 +756,9 @@ function VariantRow({ row, axes, product, busy, checked, onToggle, onSave, onSto
573
756
  ] });
574
757
  }
575
758
  function BulkBar({ count, disabled, onApply, onClear }) {
576
- const [price, setPrice] = React8.useState("");
577
- const [weight, setWeight] = React8.useState("");
578
- const [unit, setUnit] = React8.useState("");
759
+ const [price, setPrice] = React9.useState("");
760
+ const [weight, setWeight] = React9.useState("");
761
+ const [unit, setUnit] = React9.useState("");
579
762
  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: [
580
763
  /* @__PURE__ */ jsxs("span", { className: "font-medium", children: [
581
764
  count,
@@ -654,8 +837,8 @@ function parseNumber(raw) {
654
837
  return Number.isFinite(n) && n >= 0 ? n : null;
655
838
  }
656
839
  function NumberCell({ value, disabled, placeholder, onCommit, width = "w-20", integer }) {
657
- const [draft, setDraft] = React8.useState(value == null ? "" : String(value));
658
- React8.useEffect(() => {
840
+ const [draft, setDraft] = React9.useState(value == null ? "" : String(value));
841
+ React9.useEffect(() => {
659
842
  setDraft(value == null ? "" : String(value));
660
843
  }, [value]);
661
844
  return /* @__PURE__ */ jsx(
@@ -674,8 +857,8 @@ function NumberCell({ value, disabled, placeholder, onCommit, width = "w-20", in
674
857
  );
675
858
  }
676
859
  function TextCell({ value, disabled, placeholder, onCommit }) {
677
- const [draft, setDraft] = React8.useState(value);
678
- React8.useEffect(() => {
860
+ const [draft, setDraft] = React9.useState(value);
861
+ React9.useEffect(() => {
679
862
  setDraft(value);
680
863
  }, [value]);
681
864
  return /* @__PURE__ */ jsx(
@@ -722,6 +905,613 @@ function describePlan(plan) {
722
905
  function message(error) {
723
906
  return error instanceof Error ? error.message : String(error);
724
907
  }
908
+ var KIND_OF = {
909
+ // `sign: 0` = o razão aceita os dois lados (ajuste corrige para cima e para baixo).
910
+ entry: { kind: "receipt", sign: 1, reason: "purchase" },
911
+ exit: { kind: "adjustment", sign: -1, reason: "correction" },
912
+ adjustment: { kind: "adjustment", sign: 0, reason: "correction" },
913
+ loss: { kind: "loss", sign: -1, reason: "breakage" },
914
+ // Transferência é DOIS movimentos amarrados, com origem e destino. Metade de
915
+ // uma transferência é estoque sumindo de um lugar e não aparecendo no outro.
916
+ transfer: { kind: "transfer_out", sign: -1, reason: "transfer" }
917
+ };
918
+ function planMovement(rawType, quantity, reasonCode) {
919
+ const type = normalizeMovementType(rawType);
920
+ if (!type) {
921
+ return { error: "unknown_movement_type", message: "Use entrada, sa\xEDda, ajuste ou perda." };
922
+ }
923
+ if (type === "transfer") {
924
+ return {
925
+ error: "transfer_unsupported",
926
+ message: "Transfer\xEAncia entre locais precisa de origem e destino \u2014 ainda n\xE3o fa\xE7o por aqui."
927
+ };
928
+ }
929
+ const magnitude = Math.abs(Number(quantity) || 0);
930
+ if (magnitude === 0) return { error: "zero_quantity", message: "Movimento de zero n\xE3o \xE9 movimento." };
931
+ const fallback = KIND_OF[type];
932
+ const declared = reasonCode ? listStockReasonCodes().find((r) => r.key === reasonCode) : void 0;
933
+ const kind = declared?.movementKind ?? fallback.kind;
934
+ const sign = fallback.sign === 0 ? Number(quantity) < 0 ? -1 : 1 : fallback.sign;
935
+ return { kind, quantity: magnitude * sign, reasonCode: declared?.key ?? fallback.reason };
936
+ }
937
+ var WHERE_IS = {
938
+ id: "shop.stock.position",
939
+ name: "getStockPosition",
940
+ description: 'Answers "where is my X" for a product the merchant names in words: how many are on hand, how many are held for orders, and how that splits across locations and lots. Resolves the words itself \u2014 send the term as the merchant said it. When two products fit equally it asks with a card instead of guessing.',
941
+ icon: "Boxes",
942
+ mode: "read",
943
+ execution: { plane: "client" },
944
+ category: "Shop",
945
+ role: "operations",
946
+ parameters: {
947
+ type: "object",
948
+ properties: { term: { type: "string", description: "The product, in the merchant's words." } },
949
+ required: ["term"]
950
+ }
951
+ };
952
+ var RECORD = {
953
+ id: "shop.stock.record",
954
+ name: "recordStockMovement",
955
+ description: "Writes one line in the stock ledger: entrada, sa\xEDda, ajuste or perda. Takes the `variantId` from getStockPosition \u2014 never a name. Send the quantity as a plain positive number and say which movement it is; the sign belongs to the ledger, not to you. The ledger is append-only: a mistake is corrected by an opposing movement, never by editing. `occurredAt` is WHEN the goods moved, which is not when they were typed \u2014 Friday's delivery entered on Monday.",
956
+ icon: "ArrowDownToLine",
957
+ mode: "persist",
958
+ execution: { plane: "client" },
959
+ category: "Shop",
960
+ role: "operations",
961
+ permission: { feature: "shop", action: "update" },
962
+ parameters: {
963
+ type: "object",
964
+ properties: {
965
+ variantId: { type: "string", description: "From getStockPosition." },
966
+ productName: { type: "string", description: "So the merchant reads the card without ids." },
967
+ movementType: {
968
+ type: "string",
969
+ description: "entrada | sa\xEDda | ajuste | perda",
970
+ enum: ["entrada", "saida", "ajuste", "perda"]
971
+ },
972
+ quantity: { type: "number", description: "How many, as a positive number." },
973
+ reasonCode: { type: "string", description: "Reason key when the merchant gave one (purchase, breakage, expiry, theft\u2026)." },
974
+ locationId: { type: "string", description: "Which location. Omitted means the store default." },
975
+ note: { type: "string", description: "Anything the merchant said that is not a field \u2014 the invoice number, who delivered." },
976
+ occurredAt: { type: "string", description: "ISO date when it actually moved. Omitted means now." }
977
+ },
978
+ required: ["variantId", "productName", "movementType", "quantity"]
979
+ }
980
+ };
981
+ function isLedgerMissing(error) {
982
+ const raw = error instanceof Error ? error.message : String(error?.message ?? error);
983
+ return /plg_shop_stock_|\btracking\b.*does not exist/i.test(raw);
984
+ }
985
+ var LEDGER_MISSING_MESSAGE = "O controle de estoque ainda n\xE3o foi aplicado nesta loja (migration 0034). Enquanto isso o saldo vive s\xF3 no n\xFAmero do produto.";
986
+ function occurredAtFor(chosenDay, today) {
987
+ if (!chosenDay || chosenDay === today) return void 0;
988
+ const parsed = /* @__PURE__ */ new Date(`${chosenDay}T12:00:00`);
989
+ return Number.isNaN(parsed.getTime()) ? void 0 : parsed.toISOString();
990
+ }
991
+ async function commitMovement(args) {
992
+ const plan = planMovement(args.movementType, args.quantity, args.reasonCode);
993
+ if ("error" in plan) return { ok: false, ...plan };
994
+ const store7 = stockStore();
995
+ if (plan.quantity < 0) {
996
+ const available = await store7.getAvailable(args.variantId);
997
+ if (Math.abs(plan.quantity) > available) {
998
+ return {
999
+ ok: false,
1000
+ error: "insufficient_stock",
1001
+ available,
1002
+ message: `S\xF3 h\xE1 ${available} em estoque \u2014 a sa\xEDda de ${Math.abs(plan.quantity)} deixaria negativo.`
1003
+ };
1004
+ }
1005
+ }
1006
+ const movement = await store7.recordMovement({
1007
+ variantId: args.variantId,
1008
+ kind: plan.kind,
1009
+ quantity: plan.quantity,
1010
+ reasonCode: plan.reasonCode,
1011
+ ...args.locationId ? { locationId: args.locationId } : {},
1012
+ ...args.note ? { note: args.note } : {},
1013
+ ...args.occurredAt ? { occurredAt: args.occurredAt } : {}
1014
+ });
1015
+ for (const table of [
1016
+ "plg_shop_products",
1017
+ // a Lista, e é a que o usuário estava vendo
1018
+ "plg_shop_product_variants",
1019
+ // a matriz de variantes
1020
+ "plg_shop_stock_balances",
1021
+ // as Posições
1022
+ "products"
1023
+ // o arquétipo, que as telas de Estoque leem
1024
+ ]) {
1025
+ emitDataChanged({ table, op: "update" });
1026
+ }
1027
+ return {
1028
+ ok: true,
1029
+ movementId: movement.id,
1030
+ kind: plan.kind,
1031
+ quantity: plan.quantity,
1032
+ reasonCode: plan.reasonCode,
1033
+ available: await store7.getAvailable(args.variantId)
1034
+ };
1035
+ }
1036
+ var text = (value) => typeof value === "string" ? value.trim() : "";
1037
+ var stockStore = () => resolveStockStore(getActiveTenantId() ?? "", () => createSupabaseStockStore());
1038
+ function summarize(balances, locations) {
1039
+ const nameOf = (id) => locations.find((l) => l.id === id)?.name ?? id;
1040
+ const total = balances.reduce(
1041
+ (acc, b) => ({
1042
+ onHand: acc.onHand + b.onHand,
1043
+ reserved: acc.reserved + b.reserved,
1044
+ available: acc.available + b.available
1045
+ }),
1046
+ { onHand: 0, reserved: 0, available: 0 }
1047
+ );
1048
+ const byLocation = /* @__PURE__ */ new Map();
1049
+ for (const b of balances) {
1050
+ const row = byLocation.get(b.locationId) ?? { location: nameOf(b.locationId), onHand: 0, available: 0 };
1051
+ row.onHand += b.onHand;
1052
+ row.available += b.available;
1053
+ byLocation.set(b.locationId, row);
1054
+ }
1055
+ return { total, byLocation: [...byLocation.values()] };
1056
+ }
1057
+ function createStockAgentTools({ getProvider, onChanged }) {
1058
+ const failed = (error) => isLedgerMissing(error) ? { ok: false, error: "ledger_not_installed", message: LEDGER_MISSING_MESSAGE } : { ok: false, error: errorMessage(error) };
1059
+ const variantsOf = async (product) => {
1060
+ try {
1061
+ const set = await getProvider().getProductVariants?.(product.id);
1062
+ const ids = (set?.variants ?? []).map((v) => v.id);
1063
+ if (ids.length > 0) return ids;
1064
+ } catch {
1065
+ }
1066
+ return [];
1067
+ };
1068
+ return [
1069
+ {
1070
+ tool: WHERE_IS,
1071
+ execute: async (args) => {
1072
+ const term = text(args.term);
1073
+ if (!term) return { ok: false, error: "no_term" };
1074
+ try {
1075
+ const catalog = await listProductsCached(getProvider, { status: "active", limit: 500 });
1076
+ const answer = resolveTerm(term, catalog);
1077
+ if (!answer.resolved) {
1078
+ if (!answer.options?.length) return { ok: true, term, notFound: true };
1079
+ return {
1080
+ ok: true,
1081
+ kind: "record_choice",
1082
+ choices: [{
1083
+ id: "stock-product",
1084
+ question: `Qual "${term}"?`,
1085
+ options: answer.options.map((c) => ({ id: c.productId, label: c.name, meta: candidateMeta(c) }))
1086
+ }],
1087
+ term,
1088
+ needsChoice: true
1089
+ };
1090
+ }
1091
+ const product = catalog.find((p) => p.id === answer.resolved.productId);
1092
+ const variantIds = await variantsOf(product);
1093
+ const store7 = stockStore();
1094
+ const [balances, locations] = await Promise.all([
1095
+ store7.listBalances(variantIds.length > 0 ? { variantIds } : {}),
1096
+ store7.listLocations()
1097
+ ]);
1098
+ const mine = variantIds.length > 0 ? balances : balances.filter(() => false);
1099
+ const { total, byLocation } = summarize(mine, locations);
1100
+ return {
1101
+ ok: true,
1102
+ product: { productId: product.id, name: product.name, sku: product.sku },
1103
+ // A variante é o que o lançamento precisa; sem ela o razão não tem
1104
+ // onde postar, e é melhor dizer isso do que gravar no lugar errado.
1105
+ variantId: variantIds[0] ?? null,
1106
+ variants: variantIds,
1107
+ total,
1108
+ byLocation,
1109
+ locations: locations.map((l) => ({ locationId: l.id, name: l.name, isDefault: l.isDefault }))
1110
+ };
1111
+ } catch (error) {
1112
+ return failed(error);
1113
+ }
1114
+ }
1115
+ },
1116
+ {
1117
+ tool: RECORD,
1118
+ execute: async (args) => {
1119
+ const variantId = text(args.variantId);
1120
+ if (!variantId) return { ok: false, error: "no_variant", message: "Preciso da variante \u2014 pergunte a posi\xE7\xE3o primeiro." };
1121
+ try {
1122
+ const answer = await commitMovement({
1123
+ variantId,
1124
+ movementType: text(args.movementType),
1125
+ quantity: Number(args.quantity),
1126
+ reasonCode: text(args.reasonCode) || void 0,
1127
+ locationId: text(args.locationId) || void 0,
1128
+ note: text(args.note) || void 0,
1129
+ occurredAt: text(args.occurredAt) || void 0
1130
+ });
1131
+ if (answer.ok) onChanged?.();
1132
+ return answer;
1133
+ } catch (error) {
1134
+ return failed(error);
1135
+ }
1136
+ }
1137
+ }
1138
+ ];
1139
+ }
1140
+ var STOCK_INSTRUCTIONS = `
1141
+ Esta tela \xE9 o estoque da loja. O lojista lan\xE7a o que entrou e o que saiu, e pergunta onde est\xE3o as coisas.
1142
+
1143
+ \xB7 "Cad\xEA minha costela", "quanto tem de X": \`getStockPosition\` com as palavras dele. Ela resolve o produto sozinha e devolve o saldo por local. Responda com o n\xFAmero e o local, uma linha.
1144
+ \xB7 Lan\xE7ar entrada ou sa\xEDda: pegue o \`variantId\` na posi\xE7\xE3o e chame \`recordStockMovement\`. Quantidade sempre positiva \u2014 o sinal \xE9 do raz\xE3o, n\xE3o seu.
1145
+ \xB7 A esp\xE9cie vem do que ele disse: "chegaram 20" \xE9 entrada, "quebrou 3" \xE9 perda com raz\xE3o \`breakage\`, "vencerem 2" \xE9 perda com \`expiry\`, "contei e tem 18" \xE9 ajuste.
1146
+ \xB7 A nota de sexta lan\xE7ada na segunda: se ele disser QUANDO chegou, mande em \`occurredAt\`. Sem isso o raz\xE3o registra hoje, e o custo cai no m\xEAs errado.
1147
+ \xB7 O raz\xE3o n\xE3o se edita. Errou, lan\xE7a o movimento contr\xE1rio \u2014 nunca prometa "corrigir" o lan\xE7amento.
1148
+ \xB7 Nunca invente produto nem variante. Termo amb\xEDguo j\xE1 virou pergunta com bot\xF5es: n\xE3o repita as op\xE7\xF5es no texto.
1149
+ `.trim();
1150
+ var stockStore2 = () => resolveStockStore(getActiveTenantId() ?? "", () => createSupabaseStockStore());
1151
+ var MOVEMENT_TYPES = [
1152
+ { value: "entrada", label: "Entrada" },
1153
+ { value: "saida", label: "Sa\xEDda" },
1154
+ { value: "ajuste", label: "Ajuste" },
1155
+ { value: "perda", label: "Perda" }
1156
+ ];
1157
+ var KIND_FOR = {
1158
+ entrada: "receipt",
1159
+ saida: "adjustment",
1160
+ ajuste: "adjustment",
1161
+ perda: "loss"
1162
+ };
1163
+ var todayISO = () => (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
1164
+ var field = "mt-1 w-full rounded-md border bg-background px-2.5 py-1.5 text-sm outline-none focus:border-primary";
1165
+ var STOCK_INTRO = {
1166
+ role: "operations",
1167
+ persona: "Estoquista",
1168
+ pitch: "Diga o que entrou ou saiu que eu lan\xE7o.",
1169
+ description: "Lan\xE7o entrada, sa\xEDda, ajuste ou perda no raz\xE3o deste produto, e respondo quanto tem e onde. Voc\xEA confirma antes de gravar.",
1170
+ suggestions: [
1171
+ { label: "Chegaram 20 unidades" },
1172
+ { label: "Quebraram 2" }
1173
+ ]
1174
+ };
1175
+ function ProductPositionsTab({ item, getProvider }) {
1176
+ const product = item;
1177
+ const [variants, setVariants] = useState([]);
1178
+ const [balances, setBalances] = useState([]);
1179
+ const [locations, setLocations] = useState([]);
1180
+ const [movements, setMovements] = useState([]);
1181
+ const [loading, setLoading] = useState(true);
1182
+ const [error, setError] = useState(null);
1183
+ const [variantId, setVariantId] = useState("");
1184
+ const [type, setType] = useState(null);
1185
+ const [quantity, setQuantity] = useState("");
1186
+ const [reasonCode, setReasonCode] = useState("");
1187
+ const [note, setNote] = useState("");
1188
+ const [noteOpen, setNoteOpen] = useState(false);
1189
+ const [occurredOn, setOccurredOn] = useState(() => todayISO());
1190
+ const [saving, setSaving] = useState(false);
1191
+ const quantityRef = React9.useRef(null);
1192
+ const load = useCallback(async () => {
1193
+ setLoading(true);
1194
+ try {
1195
+ const set = await getProvider().getProductVariants?.(product.id);
1196
+ const list = set?.variants ?? [];
1197
+ setVariants(list);
1198
+ const ids = list.map((v) => v.id);
1199
+ setVariantId((current) => current || ids[0] || "");
1200
+ const store7 = stockStore2();
1201
+ const [b, l] = await Promise.all([
1202
+ ids.length > 0 ? store7.listBalances({ variantIds: ids }) : Promise.resolve([]),
1203
+ store7.listLocations()
1204
+ ]);
1205
+ setBalances(b);
1206
+ setLocations(l);
1207
+ setMovements(ids[0] ? await store7.listMovements({ variantId: ids[0], limit: 10 }) : []);
1208
+ setError(null);
1209
+ } catch (err) {
1210
+ setError(isLedgerMissing(err) ? LEDGER_MISSING_MESSAGE : errorMessage(err));
1211
+ } finally {
1212
+ setLoading(false);
1213
+ }
1214
+ }, [getProvider, product.id]);
1215
+ useEffect(() => {
1216
+ void load();
1217
+ }, [load]);
1218
+ const nameOf = useCallback(
1219
+ (id) => locations.find((l) => l.id === id)?.name ?? id,
1220
+ [locations]
1221
+ );
1222
+ const rows = useMemo(() => {
1223
+ const mine = variantId ? balances.filter((b) => b.variantId === variantId) : balances;
1224
+ return mine.map((b) => ({
1225
+ key: b.id,
1226
+ location: nameOf(b.locationId),
1227
+ onHand: b.onHand,
1228
+ reserved: b.reserved,
1229
+ available: b.available
1230
+ }));
1231
+ }, [balances, variantId, nameOf]);
1232
+ const total = useMemo(
1233
+ () => rows.reduce(
1234
+ (acc, r) => ({
1235
+ onHand: acc.onHand + r.onHand,
1236
+ reserved: acc.reserved + r.reserved,
1237
+ available: acc.available + r.available
1238
+ }),
1239
+ { onHand: 0, reserved: 0, available: 0 }
1240
+ ),
1241
+ [rows]
1242
+ );
1243
+ const reasons = useMemo(
1244
+ () => listStockReasonCodes().filter(
1245
+ (r) => !r.movementKind || r.movementKind === KIND_FOR[type ?? "entrada"]
1246
+ ),
1247
+ [type]
1248
+ );
1249
+ useEffect(() => {
1250
+ setReasonCode("");
1251
+ }, [type]);
1252
+ const open = (next) => {
1253
+ setType(next);
1254
+ setQuantity("");
1255
+ setNote("");
1256
+ setNoteOpen(false);
1257
+ setOccurredOn(todayISO());
1258
+ window.setTimeout(() => quantityRef.current?.focus(), 0);
1259
+ };
1260
+ const close = () => {
1261
+ setType(null);
1262
+ setQuantity("");
1263
+ setNote("");
1264
+ setNoteOpen(false);
1265
+ setOccurredOn(todayISO());
1266
+ };
1267
+ const amount = Math.abs(Number(quantity) || 0);
1268
+ const outgoing = type === "saida" || type === "perda";
1269
+ const nextBalance = amount > 0 ? outgoing ? total.available - amount : total.available + amount : null;
1270
+ const live = React9.useRef({ product, variantId, total, getProvider, load });
1271
+ live.current = { product, variantId, total, getProvider, load };
1272
+ const contribution = useMemo(() => ({
1273
+ id: "shop.product.positions",
1274
+ pluginId: "shop",
1275
+ role: "operations",
1276
+ instructions: STOCK_INSTRUCTIONS,
1277
+ describe: () => {
1278
+ const o = live.current;
1279
+ return {
1280
+ title: `Estoque \u2014 ${o.product.name}`,
1281
+ record: { entity: "shop_product", id: o.product.id, label: o.product.name },
1282
+ state: {
1283
+ screen: "/products/:id#posicoes",
1284
+ productId: o.product.id,
1285
+ productName: o.product.name,
1286
+ variantId: o.variantId || null,
1287
+ onHand: o.total.onHand,
1288
+ reserved: o.total.reserved,
1289
+ available: o.total.available
1290
+ }
1291
+ };
1292
+ },
1293
+ tools: createStockAgentTools({
1294
+ getProvider: () => live.current.getProvider(),
1295
+ onChanged: () => {
1296
+ void live.current.load();
1297
+ }
1298
+ })
1299
+ }), []);
1300
+ useAgentSurface(contribution);
1301
+ const submit = async () => {
1302
+ if (!variantId || !type) return;
1303
+ const amount2 = Number(quantity);
1304
+ if (!amount2) {
1305
+ toast.error("Diga quantas unidades.");
1306
+ return;
1307
+ }
1308
+ setSaving(true);
1309
+ try {
1310
+ const answer = await commitMovement({
1311
+ variantId,
1312
+ movementType: type,
1313
+ quantity: amount2,
1314
+ reasonCode: reasonCode || void 0,
1315
+ note: note.trim() || void 0,
1316
+ occurredAt: occurredAtFor(occurredOn, todayISO())
1317
+ });
1318
+ if (!answer.ok) {
1319
+ toast.error(answer.message ?? answer.error);
1320
+ return;
1321
+ }
1322
+ toast.success(`Lan\xE7ado. Saldo agora: ${answer.available}.`);
1323
+ close();
1324
+ await load();
1325
+ } catch (err) {
1326
+ toast.error(errorMessage(err));
1327
+ } finally {
1328
+ setSaving(false);
1329
+ }
1330
+ };
1331
+ if (loading) {
1332
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
1333
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-3", children: [
1334
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
1335
+ /* @__PURE__ */ jsx("div", { className: "h-4 w-24 animate-pulse rounded bg-muted" }),
1336
+ /* @__PURE__ */ jsx("div", { className: "h-3 w-64 animate-pulse rounded bg-muted" })
1337
+ ] }),
1338
+ /* @__PURE__ */ jsx("div", { className: "h-9 w-32 animate-pulse rounded-md bg-muted" })
1339
+ ] }),
1340
+ /* @__PURE__ */ jsx("ul", { className: "space-y-2", children: [0, 1].map((i) => /* @__PURE__ */ jsxs("li", { className: "flex items-center gap-3 rounded-card border bg-card p-3", children: [
1341
+ /* @__PURE__ */ jsx("div", { className: "h-4 flex-1 animate-pulse rounded bg-muted" }),
1342
+ /* @__PURE__ */ jsx("div", { className: "h-4 w-12 animate-pulse rounded bg-muted" })
1343
+ ] }, i)) })
1344
+ ] });
1345
+ }
1346
+ if (error) {
1347
+ const missing = error === LEDGER_MISSING_MESSAGE;
1348
+ 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
+ }
1350
+ 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: [
1355
+ /* @__PURE__ */ jsx("span", { className: "tabular-nums", children: total.onHand }),
1356
+ " em m\xE3os \xB7",
1357
+ " ",
1358
+ /* @__PURE__ */ jsx("span", { className: "tabular-nums", children: total.reserved }),
1359
+ " reservado para pedidos \xB7",
1360
+ " ",
1361
+ /* @__PURE__ */ jsx("span", { className: "font-medium tabular-nums text-foreground", children: total.available }),
1362
+ " dispon\xEDvel. O raz\xE3o n\xE3o se edita: um erro se corrige com o movimento contr\xE1rio."
1363
+ ] })
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
+ ] }),
1388
+ !variantId && // Nunca desabilitar um botão sem dizer por quê. Sem variante o razão não
1389
+ // tem onde postar, e um "Lançar" apagado sem explicação faz o lojista
1390
+ // clicar três vezes e concluir que a tela está quebrada.
1391
+ /* @__PURE__ */ jsxs("p", { className: "rounded-lg border border-dashed px-4 py-8 text-center text-sm text-muted-foreground", children: [
1392
+ "Este produto ainda n\xE3o tem uma variante para o estoque apontar. Crie uma em ",
1393
+ /* @__PURE__ */ jsx("span", { className: "font-medium text-foreground", children: "Variantes" }),
1394
+ " e volte aqui."
1395
+ ] }),
1396
+ variants.length > 1 && /* @__PURE__ */ jsxs("label", { className: "block max-w-xs", children: [
1397
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground", children: "Variante" }),
1398
+ /* @__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
+ ] }),
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))
1452
+ ] })
1453
+ ] }),
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" }),
1456
+ /* @__PURE__ */ jsx(
1457
+ "input",
1458
+ {
1459
+ type: "date",
1460
+ value: occurredOn,
1461
+ max: todayISO(),
1462
+ onChange: (e) => setOccurredOn(e.target.value || todayISO()),
1463
+ className: field
1464
+ }
1465
+ )
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",
1472
+ {
1473
+ autoFocus: true,
1474
+ value: note,
1475
+ onChange: (e) => setNote(e.target.value),
1476
+ className: field,
1477
+ placeholder: "Nota fiscal, quem entregou\u2026"
1478
+ }
1479
+ )
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" }),
1493
+ 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" })
1495
+ ] })
1496
+ ] }),
1497
+ 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
+ /* @__PURE__ */ jsx("span", { className: "flex-1 text-sm font-medium", children: r.location }),
1499
+ /* @__PURE__ */ jsxs("span", { className: "text-xs text-muted-foreground", children: [
1500
+ r.reserved,
1501
+ " reservado"
1502
+ ] }),
1503
+ /* @__PURE__ */ jsx("span", { className: "w-16 text-right text-sm font-semibold tabular-nums", children: r.available })
1504
+ ] }, r.key)) })),
1505
+ movements.length > 0 && /* @__PURE__ */ jsxs("section", { className: "space-y-2 pt-2", children: [
1506
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: "\xDAltimos movimentos" }),
1507
+ /* @__PURE__ */ jsx("ul", { className: "space-y-2", children: movements.map((m) => /* @__PURE__ */ jsxs("li", { className: "flex items-center gap-3 rounded-card border bg-card px-3 py-2", children: [
1508
+ /* @__PURE__ */ jsx("span", { className: `w-14 text-sm font-semibold tabular-nums ${m.quantity < 0 ? "text-destructive" : "text-emerald-700"}`, children: m.quantity > 0 ? `+${m.quantity}` : m.quantity }),
1509
+ /* @__PURE__ */ jsx("span", { className: "flex-1 text-sm", children: m.reasonCode ?? m.kind }),
1510
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: new Date(m.occurredAt ?? m.createdAt).toLocaleDateString("pt-BR") })
1511
+ ] }, m.id)) })
1512
+ ] })
1513
+ ] });
1514
+ }
725
1515
 
726
1516
  // src/data/registry-routes.ts
727
1517
  var SHIPPING_SETTINGS_TAB = "shop-shipping";
@@ -743,20 +1533,20 @@ function shippingRegistryIdFromHash(hash) {
743
1533
  }
744
1534
  var PACKAGES_ROUTE = shippingRegistryPath(PACKAGES_REGISTRY);
745
1535
  function PackageSelect({ value, onChange, getProvider }) {
746
- const shop = React8.useMemo(() => getProvider?.() ?? getShopProvider(), [getProvider]);
747
- const [rows, setRows] = React8.useState(null);
748
- const [open, setOpen] = React8.useState(false);
749
- const [creating, setCreating] = React8.useState(false);
750
- const ref = React8.useRef(null);
751
- const load = React8.useCallback(() => {
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(() => {
752
1542
  if (typeof shop.listShippingPackages !== "function") {
753
1543
  setRows([]);
754
1544
  return;
755
1545
  }
756
1546
  void shop.listShippingPackages().then(setRows).catch(() => setRows([]));
757
1547
  }, [shop]);
758
- React8.useEffect(load, [load]);
759
- React8.useEffect(() => {
1548
+ React9.useEffect(load, [load]);
1549
+ React9.useEffect(() => {
760
1550
  if (!open) return;
761
1551
  const away = (event) => {
762
1552
  if (ref.current && !ref.current.contains(event.target)) setOpen(false);
@@ -851,8 +1641,8 @@ function PackageSelect({ value, onChange, getProvider }) {
851
1641
  ] });
852
1642
  }
853
1643
  function NewPackageModal({ onClose, onCreated }) {
854
- const [error, setError] = React8.useState(null);
855
- const provider = React8.useMemo(
1644
+ const [error, setError] = React9.useState(null);
1645
+ const provider = React9.useMemo(
856
1646
  () => resolveDataProvider(packagesRegistry.entity),
857
1647
  []
858
1648
  );
@@ -898,9 +1688,9 @@ function Option({ label, hint, active, onClick }) {
898
1688
  );
899
1689
  }
900
1690
  function ShippingProfileSelect({ value, onChange, getProvider }) {
901
- const shop = React8.useMemo(() => getProvider?.() ?? getShopProvider(), [getProvider]);
902
- const [rows, setRows] = React8.useState(null);
903
- React8.useEffect(() => {
1691
+ const shop = React9.useMemo(() => getProvider?.() ?? getShopProvider(), [getProvider]);
1692
+ const [rows, setRows] = React9.useState(null);
1693
+ React9.useEffect(() => {
904
1694
  let cancelled = false;
905
1695
  if (typeof shop.listShippingProfiles !== "function") {
906
1696
  setRows([]);
@@ -1077,14 +1867,27 @@ function buildShopProductEntity(currencyCode = "BRL", getProvider) {
1077
1867
  searchable: true,
1078
1868
  showInTable: false
1079
1869
  },
1870
+ // Não se DIGITA saldo.
1871
+ //
1872
+ // Este campo era um número editável, e um número editável convida a
1873
+ // resposta errada: alguém conta a prateleira, acha 18, escreve 18, e o
1874
+ // razão perde POR QUE mudou — se chegaram seis ou se sumiram quatro. É
1875
+ // exatamente o que um razão existe para impedir, e a 0036 já tratava a
1876
+ // escrita direta aqui como um ajuste às cegas para não perder o movimento.
1877
+ //
1878
+ // Vira reflexo: mostra o saldo, e o ato acontece em Posições, onde ele tem
1879
+ // espécie, motivo e data.
1080
1880
  {
1081
1881
  key: "inventoryCount",
1082
1882
  label: "Estoque",
1083
1883
  type: "number",
1084
1884
  group: "inventory",
1085
1885
  min: 0,
1086
- placeholder: "0",
1087
- hint: "Chega a zero e o produto aparece como esgotado na loja.",
1886
+ // `showInForm: false` e não um campo desabilitado: some do formulário de
1887
+ // edição, continua na tabela e na ficha. Mostra-se o saldo; digita-se o
1888
+ // movimento.
1889
+ showInForm: false,
1890
+ hint: "Saldo do raz\xE3o. Entrada, sa\xEDda e perda se lan\xE7am na aba Posi\xE7\xF5es.",
1088
1891
  showInTable: true,
1089
1892
  sortable: true,
1090
1893
  renderCell: (v) => {
@@ -1182,7 +1985,7 @@ function buildShopProductEntity(currencyCode = "BRL", getProvider) {
1182
1985
  group: "shipping",
1183
1986
  min: 0,
1184
1987
  placeholder: "0,4",
1185
- hint: "Do produto embalado. A caixa vem do cadastro acima.",
1988
+ hint: "Do produto, j\xE1 embalado. As medidas v\xEAm da embalagem escolhida acima.",
1186
1989
  showInTable: false,
1187
1990
  visibleWhen: (values) => values.isPhysical !== false
1188
1991
  },
@@ -1199,9 +2002,19 @@ function buildShopProductEntity(currencyCode = "BRL", getProvider) {
1199
2002
  // Em centímetros, sem seletor de unidade: a coluna é `*_cm` e a caixa da
1200
2003
  // loja também. Duas unidades para a mesma medida é como o peso passou
1201
2004
  // quatro anos sendo lido errado.
1202
- { key: "heightCm", label: "Altura (cm)", type: "number", group: "shipping", min: 0, placeholder: "10", showInTable: false, visibleWhen: physical },
1203
- { key: "widthCm", label: "Largura (cm)", type: "number", group: "shipping", min: 0, placeholder: "20", showInTable: false, visibleWhen: physical },
1204
- { key: "lengthCm", label: "Comprimento (cm)", type: "number", group: "shipping", min: 0, placeholder: "30", showInTable: false, visibleWhen: physical },
2005
+ // A medida é da EMBALAGEM, não do produto.
2006
+ //
2007
+ // O seletor logo acima já associa o produto a uma embalagem cadastrada da
2008
+ // loja (0047) — e é lá que altura, largura e comprimento vivem. Repetir os
2009
+ // três aqui pedia a mesma informação duas vezes por caminhos diferentes, e
2010
+ // as duas divergiam no dia em que alguém trocasse a caixa: a cotação lê a
2011
+ // embalagem, o formulário mostrava outro número.
2012
+ //
2013
+ // As colunas continuam existindo — um catálogo anterior à 0047 tem produto
2014
+ // medido e sem embalagem, e a cotação ainda os lê. O que sai é a pergunta.
2015
+ { key: "heightCm", label: "Altura (cm)", type: "number", group: "shipping", min: 0, showInForm: false, showInTable: false },
2016
+ { key: "widthCm", label: "Largura (cm)", type: "number", group: "shipping", min: 0, showInForm: false, showInTable: false },
2017
+ { key: "lengthCm", label: "Comprimento (cm)", type: "number", group: "shipping", min: 0, showInForm: false, showInTable: false },
1205
2018
  {
1206
2019
  // Declarado aqui para poder viajar — para a transportadora, para o motoboy
1207
2020
  // ou para quem embala. O que a loja FAZ com um carrinho refrigerado é uma
@@ -1228,6 +2041,17 @@ function buildShopProductEntity(currencyCode = "BRL", getProvider) {
1228
2041
  namePlural: "Produtos",
1229
2042
  icon: "Package",
1230
2043
  displayField: "name",
2044
+ // Qual tabela esta tela É. Sem isto, `deriveEntityKey` cai no rótulo — e a
2045
+ // chave da entidade vira `produto`, que muda com uma tradução. Quem avisa
2046
+ // "escrevi aqui" nomeia a TABELA (é o que emitDataChanged invalida no cache),
2047
+ // então sem esta linha o aviso não encontra a lista: o barramento casa
2048
+ // tabela-com-tabela, e o lado do assinante estava vazio.
2049
+ //
2050
+ // Não muda o caminho do dado: o índice do plugin passa um
2051
+ // `dataProvider: createShopProductDataProvider(...)` explícito, que vence o
2052
+ // provider genérico que `data.table` acionaria — mesma forma da entidade de
2053
+ // cliente logo ao lado.
2054
+ data: { table: "plg_shop_products" },
1231
2055
  fields,
1232
2056
  fieldGroups: [
1233
2057
  // imageSlot renders the scaffolding's image uploader alongside the group.
@@ -1238,7 +2062,7 @@ function buildShopProductEntity(currencyCode = "BRL", getProvider) {
1238
2062
  {
1239
2063
  id: "shipping",
1240
2064
  label: "Envio",
1241
- description: "Peso e medidas do produto embalado. Sem eles a transportadora \xE9 consultada com a caixa padr\xE3o da loja, e o frete cotado pode n\xE3o ser o frete pago.",
2065
+ description: "Em que caixa este produto sai e quanto ele pesa. A medida \xE9 da embalagem; o peso \xE9 do produto. Sem embalagem escolhida a transportadora \xE9 consultada com a caixa padr\xE3o da loja, e o frete cotado pode n\xE3o ser o frete pago.",
1242
2066
  columns: 3
1243
2067
  }
1244
2068
  ],
@@ -1262,139 +2086,21 @@ function buildShopProductEntity(currencyCode = "BRL", getProvider) {
1262
2086
  icon: "Layers",
1263
2087
  component: ProductVariantsTab,
1264
2088
  props: { getProvider }
2089
+ },
2090
+ // Separada de Variantes de propósito: aquela edita O QUE se vende,
2091
+ // esta responde ONDE estão as unidades — e a ficha só sabia dizer um
2092
+ // número só, que não distingue a loja do depósito nem o que já está
2093
+ // prometido a um pedido.
2094
+ {
2095
+ id: "positions",
2096
+ label: "Posi\xE7\xF5es",
2097
+ icon: "Boxes",
2098
+ component: ProductPositionsTab,
2099
+ props: { getProvider }
1265
2100
  }
1266
2101
  ] : void 0
1267
2102
  };
1268
2103
  }
1269
- function money2(value) {
1270
- const n = Number(value);
1271
- if (!Number.isFinite(n)) return "R$ 0,00";
1272
- return new Intl.NumberFormat("pt-BR", { style: "currency", currency: "BRL" }).format(n);
1273
- }
1274
- function buildShopCustomerEntity() {
1275
- const fields = [
1276
- // — Contato —
1277
- {
1278
- key: "firstName",
1279
- label: "Nome",
1280
- type: "text",
1281
- required: true,
1282
- group: "contact",
1283
- placeholder: "Maria",
1284
- searchable: true,
1285
- showInTable: true,
1286
- sortable: true,
1287
- renderCell: (_v, row) => /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
1288
- /* @__PURE__ */ jsx("p", { className: "truncate font-medium", children: [row.firstName, row.lastName].filter(Boolean).join(" ") }),
1289
- row.email && /* @__PURE__ */ jsx("p", { className: "truncate text-xs text-muted-foreground", children: row.email })
1290
- ] })
1291
- },
1292
- { key: "lastName", label: "Sobrenome", type: "text", group: "contact", placeholder: "Silva", searchable: true, showInTable: false },
1293
- {
1294
- key: "email",
1295
- label: "E-mail",
1296
- type: "text",
1297
- group: "contact",
1298
- placeholder: "maria@exemplo.com",
1299
- searchable: true,
1300
- hint: "\xC9 por ele que o pedido \xE9 vinculado ao cliente no checkout.",
1301
- showInTable: false
1302
- },
1303
- {
1304
- key: "phone",
1305
- label: "Telefone",
1306
- type: "text",
1307
- group: "contact",
1308
- placeholder: "(21) 99999-0000",
1309
- searchable: true,
1310
- showInTable: true,
1311
- sortable: false,
1312
- renderCell: (v) => /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: v || "\u2014" })
1313
- },
1314
- // The column, the type and the provider all carried `document` already; the
1315
- // form was the only place it could not be typed. Which meant a store could
1316
- // see "cliente sem CPF/CNPJ" on every Bling run and have nowhere to fix it.
1317
- {
1318
- key: "document",
1319
- label: "CPF / CNPJ",
1320
- type: "text",
1321
- group: "contact",
1322
- placeholder: "000.000.000-00",
1323
- searchable: true,
1324
- hint: "Necess\xE1rio para emitir nota \u2014 sem ele o cliente n\xE3o sobe para o ERP.",
1325
- showInTable: false
1326
- },
1327
- // — Histórico (somente leitura) —
1328
- {
1329
- key: "ordersCount",
1330
- label: "Pedidos",
1331
- type: "number",
1332
- showInForm: false,
1333
- showInTable: true,
1334
- sortable: true,
1335
- renderCell: (v) => /* @__PURE__ */ jsx("span", { className: "block text-right", children: Number(v) || 0 })
1336
- },
1337
- {
1338
- key: "totalSpent",
1339
- label: "Total gasto",
1340
- type: "number",
1341
- showInForm: false,
1342
- showInTable: true,
1343
- sortable: true,
1344
- renderCell: (v) => /* @__PURE__ */ jsx("span", { className: "block text-right font-medium", children: money2(v) })
1345
- },
1346
- // — Interno —
1347
- {
1348
- key: "notes",
1349
- label: "Observa\xE7\xF5es",
1350
- type: "textarea",
1351
- group: "internal",
1352
- span: 2,
1353
- placeholder: "Prefer\xEAncias, restri\xE7\xF5es, combinados de entrega\u2026",
1354
- hint: "Vis\xEDvel s\xF3 no admin. O cliente nunca v\xEA.",
1355
- showInTable: false
1356
- }
1357
- ];
1358
- return {
1359
- name: "Cliente",
1360
- namePlural: "Clientes",
1361
- icon: "Users",
1362
- displayField: "firstName",
1363
- // `person` layout brings the shared archetype tabs — including Endereços,
1364
- // which reads the core addresses table and therefore works in every app.
1365
- layout: "person",
1366
- // Without archetypeKind the shell shows every person tab, including the
1367
- // staff-only Agenda (its visibleFor filter only runs when a kind is set).
1368
- data: { table: "plg_shop_customers", archetypeKind: "customer" },
1369
- fields,
1370
- // The SAME tab and the SAME provider beauty-saas uses. Since the shop's
1371
- // orders now live in public.orders with party_id set, the canonical provider
1372
- // already returns them — a shop-specific adapter would be a second
1373
- // implementation of "list this person's orders", which is how two apps end
1374
- // up with two different answers to the same question.
1375
- detailTabs: [{
1376
- id: "orders",
1377
- label: "Pedidos",
1378
- icon: "ShoppingBag",
1379
- aliases: ["appointments", "quotes"],
1380
- component: ClientOrdersTab,
1381
- props: {
1382
- provider: createClientOrdersProvider(),
1383
- currency: { code: "BRL", locale: "pt-BR" },
1384
- // The one part that legitimately differs per app: where a document
1385
- // leads. Beauty sends a booking to the agenda and an invoice to
1386
- // receivables; a shop order goes to the order screen.
1387
- navigator: {
1388
- onNavigate: (doc) => adminNavigateTo(`/shop/orders/${doc.id}`)
1389
- }
1390
- }
1391
- }],
1392
- fieldGroups: [
1393
- { id: "contact", label: "Contato", columns: 2 },
1394
- { id: "internal", label: "Notas internas", columns: 1 }
1395
- ]
1396
- };
1397
- }
1398
2104
  var STATUS_TONE2 = {
1399
2105
  active: "bg-emerald-100 text-emerald-800",
1400
2106
  draft: "bg-gray-100 text-gray-700",
@@ -1556,6 +2262,34 @@ function buildShopDiscountEntity() {
1556
2262
  facets: [{ field: "status", allLabel: "Todos" }]
1557
2263
  };
1558
2264
  }
2265
+
2266
+ // src/views/discount-agent.ts
2267
+ var DISCOUNT_CREATE_INSTRUCTIONS = `
2268
+ Esta lista \xE9 a de Descontos da loja. Um registro aqui \xE9 um CUPOM que o comprador digita no carrinho.
2269
+
2270
+ O c\xF3digo \xE9 seu para escrever:
2271
+ \xB7 Se o lojista n\xE3o ditar um, invente a partir da campanha: MAI\xDASCULAS, s\xF3 letras A-Z e n\xFAmeros, de 4 a 16 caracteres, sem espa\xE7o, acento, h\xEDfen ou pontua\xE7\xE3o.
2272
+ \xB7 Curto e dit\xE1vel ao telefone. Evite 0/O e 1/I juntos, evite palavra que o comprador precise soletrar.
2273
+ \xB7 Nunca repita um c\xF3digo que j\xE1 aparece em \`taken.code\`. Se a cria\xE7\xE3o voltar \`duplicate_value\`, escolha outro e chame de novo \u2014 sem perguntar nada ao lojista.
2274
+ \xB7 \`title\` \xE9 o nome interno, s\xF3 o lojista v\xEA: escreva a campanha em portugu\xEAs ("Frete gr\xE1tis fim de semana"), n\xE3o repita o c\xF3digo.
2275
+
2276
+ A regra:
2277
+ \xB7 "frete gr\xE1tis" \u2192 \`type: free_shipping\` e SEM \`value\` \u2014 frete gr\xE1tis n\xE3o desconta o subtotal.
2278
+ \xB7 "10%", "10 por cento" \u2192 \`type: percentage\`, \`value: 10\`.
2279
+ \xB7 "R$ 50", "50 reais de desconto" \u2192 \`type: fixed_amount\`, \`value: 50\`.
2280
+ \xB7 "acima de R$ 200", "a partir de 200" \u2192 \`minSubtotal: 200\`.
2281
+ \xB7 "s\xF3 a primeira compra", "uma vez por cliente" \u2192 \`oncePerCustomer: true\`.
2282
+ \xB7 "os 100 primeiros" \u2192 \`usageLimit: 100\`.
2283
+ \xB7 Prazo vira data em \`endsAt\`: "at\xE9 o fim dessa semana" \xE9 o domingo desta semana, "at\xE9 o fim do m\xEAs" \xE9 o \xFAltimo dia do m\xEAs. Sem prazo dito, deixe vazio \u2014 cupom sem fim \xE9 uma escolha, n\xE3o um esquecimento.
2284
+
2285
+ Status:
2286
+ \xB7 \`active\` por padr\xE3o. O lojista est\xE1 pedindo uma promo\xE7\xE3o para divulgar hoje; um cupom que nasce dormindo \xE9 um cupom que o cliente tenta usar e n\xE3o funciona.
2287
+ \xB7 \`draft\` s\xF3 quando ele disser que \xE9 para deixar pronto para depois.
2288
+
2289
+ O que voc\xEA N\xC3O inventa: o tamanho do desconto. Se ele disser "desconto para clientes VIP" sem dizer quanto, essa \xE9 a \xFAnica pergunta a fazer \u2014 e fa\xE7a s\xF3 ela.
2290
+
2291
+ Depois de criar, diga em uma linha o c\xF3digo, o efeito e at\xE9 quando vale, para ele j\xE1 poder divulgar.
2292
+ `.trim();
1559
2293
  function buildShopCategoryEntity() {
1560
2294
  const fields = [
1561
2295
  {
@@ -1744,7 +2478,7 @@ var STATUS_LABEL2 = {
1744
2478
  disabled: "Desativado"
1745
2479
  };
1746
2480
  function buildShopGiftCardEntity(currencyCode = "BRL") {
1747
- const money3 = (v) => new Intl.NumberFormat("pt-BR", { style: "currency", currency: currencyCode }).format(Number(v) || 0);
2481
+ const money2 = (v) => new Intl.NumberFormat("pt-BR", { style: "currency", currency: currencyCode }).format(Number(v) || 0);
1748
2482
  const fields = [
1749
2483
  {
1750
2484
  key: "code",
@@ -1772,7 +2506,7 @@ function buildShopGiftCardEntity(currencyCode = "BRL") {
1772
2506
  hint: "Valor de face do cart\xE3o. N\xE3o muda depois de emitido.",
1773
2507
  showInTable: true,
1774
2508
  sortable: true,
1775
- renderCell: (v) => /* @__PURE__ */ jsx("span", { className: "block text-right text-muted-foreground", children: money3(v) })
2509
+ renderCell: (v) => /* @__PURE__ */ jsx("span", { className: "block text-right text-muted-foreground", children: money2(v) })
1776
2510
  },
1777
2511
  {
1778
2512
  key: "balance",
@@ -1786,7 +2520,7 @@ function buildShopGiftCardEntity(currencyCode = "BRL") {
1786
2520
  hint: "O que ainda pode ser gasto. Cai a cada resgate.",
1787
2521
  showInTable: true,
1788
2522
  sortable: true,
1789
- renderCell: (v) => /* @__PURE__ */ jsx("span", { className: "block text-right font-medium", children: money3(v) })
2523
+ renderCell: (v) => /* @__PURE__ */ jsx("span", { className: "block text-right font-medium", children: money2(v) })
1790
2524
  },
1791
2525
  {
1792
2526
  key: "status",
@@ -1921,20 +2655,20 @@ function buildShopCustomerGroupEntity() {
1921
2655
  fieldGroups: [{ id: "basic", label: "Grupo", columns: 2 }]
1922
2656
  };
1923
2657
  }
1924
- function describeSegment(s, money3) {
2658
+ function describeSegment(s, money2) {
1925
2659
  const parts = [];
1926
2660
  if (s.minOrders != null && s.maxOrders != null) parts.push(`${s.minOrders}\u2013${s.maxOrders} pedidos`);
1927
2661
  else if (s.minOrders != null) parts.push(`${s.minOrders}+ pedidos`);
1928
2662
  else if (s.maxOrders != null) parts.push(`at\xE9 ${s.maxOrders} pedidos`);
1929
- if (s.minSpent != null && s.maxSpent != null) parts.push(`gastou ${money3(s.minSpent)}\u2013${money3(s.maxSpent)}`);
1930
- else if (s.minSpent != null) parts.push(`gastou ${money3(s.minSpent)}+`);
1931
- else if (s.maxSpent != null) parts.push(`gastou at\xE9 ${money3(s.maxSpent)}`);
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)}`);
1932
2666
  if (s.joinedAfter) parts.push(`entrou ap\xF3s ${new Date(s.joinedAfter).toLocaleDateString("pt-BR")}`);
1933
2667
  if (s.joinedBefore) parts.push(`entrou antes de ${new Date(s.joinedBefore).toLocaleDateString("pt-BR")}`);
1934
2668
  return parts.length ? parts.join(" \xB7 ") : "Todos os clientes";
1935
2669
  }
1936
2670
  function buildShopCustomerSegmentEntity(currencyCode = "BRL") {
1937
- const money3 = (v) => new Intl.NumberFormat("pt-BR", { style: "currency", currency: currencyCode }).format(v);
2671
+ const money2 = (v) => new Intl.NumberFormat("pt-BR", { style: "currency", currency: currencyCode }).format(v);
1938
2672
  const fields = [
1939
2673
  {
1940
2674
  key: "name",
@@ -2011,7 +2745,7 @@ function buildShopCustomerSegmentEntity(currencyCode = "BRL") {
2011
2745
  showInForm: false,
2012
2746
  showInTable: true,
2013
2747
  sortable: false,
2014
- renderCell: (_v, row) => /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: describeSegment(row, money3) })
2748
+ renderCell: (_v, row) => /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: describeSegment(row, money2) })
2015
2749
  },
2016
2750
  {
2017
2751
  key: "matchCount",
@@ -2341,17 +3075,17 @@ function createShopCustomerSegmentDataProvider(provider) {
2341
3075
  }
2342
3076
  var DEBOUNCE_MS = 400;
2343
3077
  function useStoreSettings(provider, defaults = {}) {
2344
- const shop = React8.useMemo(
3078
+ const shop = React9.useMemo(
2345
3079
  () => provider ? resolve(provider) : getShopProvider(),
2346
3080
  [provider]
2347
3081
  );
2348
3082
  const supported = typeof shop.getStoreSettings === "function" && typeof shop.updateStoreSettings === "function";
2349
- const [stored, setStored] = React8.useState(null);
2350
- const [loading, setLoading] = React8.useState(supported);
2351
- const [saving, setSaving] = React8.useState(false);
2352
- const [error, setError] = React8.useState(null);
2353
- const [loadError, setLoadError] = React8.useState(null);
2354
- React8.useEffect(() => {
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(() => {
2355
3089
  if (!supported) return;
2356
3090
  let cancelled = false;
2357
3091
  setLoading(true);
@@ -2367,12 +3101,12 @@ function useStoreSettings(provider, defaults = {}) {
2367
3101
  cancelled = true;
2368
3102
  };
2369
3103
  }, [shop, supported]);
2370
- const pendingRef = React8.useRef({});
2371
- const timerRef = React8.useRef(null);
2372
- React8.useEffect(() => () => {
3104
+ const pendingRef = React9.useRef({});
3105
+ const timerRef = React9.useRef(null);
3106
+ React9.useEffect(() => () => {
2373
3107
  if (timerRef.current) clearTimeout(timerRef.current);
2374
3108
  }, []);
2375
- const set = React8.useCallback(
3109
+ const set = React9.useCallback(
2376
3110
  (patch) => {
2377
3111
  setStored((current) => ({ ...current ?? {}, ...patch }));
2378
3112
  if (!supported) return;
@@ -2388,7 +3122,7 @@ function useStoreSettings(provider, defaults = {}) {
2388
3122
  },
2389
3123
  [shop, supported]
2390
3124
  );
2391
- const value = React8.useMemo(() => ({ ...defaults, ...stored ?? {} }), [defaults, stored]);
3125
+ const value = React9.useMemo(() => ({ ...defaults, ...stored ?? {} }), [defaults, stored]);
2392
3126
  return { value, stored, set, loading, saving, error, loadError, supported };
2393
3127
  }
2394
3128
  function SettingsState({
@@ -2617,25 +3351,25 @@ function PaymentsSettings({ provider }) {
2617
3351
  const settings = useStoreSettings(provider);
2618
3352
  const { value, set } = settings;
2619
3353
  const { connectors } = useConnectors({ hostPluginId: "shop", includeGrants: false });
2620
- const [adding, setAdding] = React8.useState(false);
2621
- const [selected, setSelected] = React8.useState(null);
3354
+ const [adding, setAdding] = React9.useState(false);
3355
+ const [selected, setSelected] = React9.useState(null);
2622
3356
  const panelAvailable = useConnectorsPanelAvailable();
2623
- const openJourney = React8.useCallback(
3357
+ const openJourney = React9.useCallback(
2624
3358
  (connector) => {
2625
3359
  if (panelAvailable) openConnectorsPanel(connector);
2626
3360
  else setSelected(connector);
2627
3361
  },
2628
3362
  [panelAvailable]
2629
3363
  );
2630
- const payment = React8.useMemo(
3364
+ const payment = React9.useMemo(
2631
3365
  () => (connectors ?? []).filter((c) => c.category && PAYMENT_CATEGORIES.includes(c.category)),
2632
3366
  [connectors]
2633
3367
  );
2634
- const live = React8.useMemo(() => payment.filter((c) => c.state === "connected"), [payment]);
3368
+ const live = React9.useMemo(() => payment.filter((c) => c.state === "connected"), [payment]);
2635
3369
  const chosen = value.paymentMethods ?? [];
2636
3370
  const staleProvider = !!value.paymentProvider && !live.some((c) => c.slug === value.paymentProvider) && live.length === 0;
2637
- const movedFrom = React8.useRef(null);
2638
- React8.useEffect(() => {
3371
+ const movedFrom = React9.useRef(null);
3372
+ React9.useEffect(() => {
2639
3373
  if (!settings.supported || settings.loading) return;
2640
3374
  const next = resolvePaymentProvider(value.paymentProvider, live.map((c) => c.slug));
2641
3375
  if (!next) return;
@@ -2841,17 +3575,17 @@ function CheckoutSettings({ provider }) {
2841
3575
  {
2842
3576
  title: "Campos do cliente",
2843
3577
  description: "O que o comprador v\xEA e o que ele precisa preencher. Campos travados s\xE3o os que a entrega n\xE3o dispensa.",
2844
- children: FIELDS.map((field) => /* @__PURE__ */ jsx(
3578
+ children: FIELDS.map((field2) => /* @__PURE__ */ jsx(
2845
3579
  SelectRow,
2846
3580
  {
2847
- label: field.label,
2848
- description: field.hint,
2849
- value: toRequirement(fields[field.key], !!field.locked),
2850
- options: field.locked ? [{ value: "required", label: "Pedir (obrigat\xF3rio)" }] : REQUIREMENT,
2851
- onChange: (choice) => setField(field.key, choice),
2852
- disabled: field.locked
3581
+ label: field2.label,
3582
+ description: field2.hint,
3583
+ value: toRequirement(fields[field2.key], !!field2.locked),
3584
+ options: field2.locked ? [{ value: "required", label: "Pedir (obrigat\xF3rio)" }] : REQUIREMENT,
3585
+ onChange: (choice) => setField(field2.key, choice),
3586
+ disabled: field2.locked
2853
3587
  },
2854
- field.key
3588
+ field2.key
2855
3589
  ))
2856
3590
  }
2857
3591
  )
@@ -2867,11 +3601,11 @@ function PackagesSettings({ basePath = shippingRegistryPath(PACKAGES_REGISTRY) }
2867
3601
  ] });
2868
3602
  }
2869
3603
  function HandlingSettings({ settings, provider }) {
2870
- const shop = React8.useMemo(() => provider ? resolve(provider) : getShopProvider(), [provider]);
3604
+ const shop = React9.useMemo(() => provider ? resolve(provider) : getShopProvider(), [provider]);
2871
3605
  const { value, set, loading, supported } = settings;
2872
- const [noteDraft, setNoteDraft] = React8.useState(null);
2873
- const [profiles, setProfiles] = React8.useState(null);
2874
- React8.useEffect(() => {
3606
+ const [noteDraft, setNoteDraft] = React9.useState(null);
3607
+ const [profiles, setProfiles] = React9.useState(null);
3608
+ React9.useEffect(() => {
2875
3609
  let cancelled = false;
2876
3610
  void shop.listShippingProfiles?.().then((list) => {
2877
3611
  if (!cancelled) setProfiles(list);
@@ -3001,46 +3735,46 @@ var FREIGHT_SOURCE = [
3001
3735
  }
3002
3736
  ];
3003
3737
  function ShippingSettings({ provider }) {
3004
- const [registryId, setRegistryId] = React8.useState(shippingRegistryIdFromHash);
3005
- React8.useEffect(() => {
3738
+ const [registryId, setRegistryId] = React9.useState(shippingRegistryIdFromHash);
3739
+ React9.useEffect(() => {
3006
3740
  const handler = () => setRegistryId(shippingRegistryIdFromHash());
3007
3741
  window.addEventListener("hashchange", handler);
3008
3742
  return () => window.removeEventListener("hashchange", handler);
3009
3743
  }, []);
3010
- const shop = React8.useMemo(() => provider ? resolve(provider) : getShopProvider(), [provider]);
3011
- const registries = React8.useMemo(
3744
+ const shop = React9.useMemo(() => provider ? resolve(provider) : getShopProvider(), [provider]);
3745
+ const registries = React9.useMemo(
3012
3746
  () => buildShopRegistries(provider ?? getShopProvider),
3013
3747
  [provider]
3014
3748
  );
3015
3749
  const storeSettings = useStoreSettings(provider);
3016
3750
  const supported = typeof shop.getShippingSettings === "function" && typeof shop.updateShippingSettings === "function";
3017
- const [row, setRow] = React8.useState(null);
3018
- const [zones, setZones] = React8.useState(null);
3019
- const [locations, setLocations] = React8.useState(null);
3020
- const [loading, setLoading] = React8.useState(supported);
3021
- const [saving, setSaving] = React8.useState(false);
3022
- const [error, setError] = React8.useState(null);
3023
- const [loadError, setLoadError] = React8.useState(null);
3024
- const [flatDraft, setFlatDraft] = React8.useState("");
3025
- const [freeDraft, setFreeDraft] = React8.useState("");
3026
- const [flatBad, setFlatBad] = React8.useState(false);
3027
- const [freeBad, setFreeBad] = React8.useState(false);
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);
3028
3762
  const { connectors } = useConnectors({ hostPluginId: "shop", includeGrants: false });
3029
- const [selected, setSelected] = React8.useState(null);
3763
+ const [selected, setSelected] = React9.useState(null);
3030
3764
  const panelAvailable = useConnectorsPanelAvailable();
3031
- const openJourney = React8.useCallback(
3765
+ const openJourney = React9.useCallback(
3032
3766
  (connector) => {
3033
3767
  if (panelAvailable) openConnectorsPanel(connector);
3034
3768
  else setSelected(connector);
3035
3769
  },
3036
3770
  [panelAvailable]
3037
3771
  );
3038
- const carriers = React8.useMemo(
3772
+ const carriers = React9.useMemo(
3039
3773
  () => (connectors ?? []).filter((c) => c.category === "logistics" || c.category === "shipping"),
3040
3774
  [connectors]
3041
3775
  );
3042
3776
  const carriersConnected = carriers.some((c) => c.state !== "ready");
3043
- React8.useEffect(() => {
3777
+ React9.useEffect(() => {
3044
3778
  let cancelled = false;
3045
3779
  if (supported) {
3046
3780
  setLoading(true);
@@ -3069,7 +3803,7 @@ function ShippingSettings({ provider }) {
3069
3803
  cancelled = true;
3070
3804
  };
3071
3805
  }, [shop, supported]);
3072
- const save = React8.useCallback(
3806
+ const save = React9.useCallback(
3073
3807
  (patch) => {
3074
3808
  if (!supported) return;
3075
3809
  setSaving(true);
@@ -4040,6 +4774,10 @@ var stripeConnectConnectorDef = {
4040
4774
  };
4041
4775
  var VINDI_CONNECTOR_ID = "vindi";
4042
4776
  var VINDI_LATENCY_BUDGET_MS = 6e3;
4777
+ var VINDI_AUTHENTICATION_URLS = {
4778
+ sandbox: "https://tc-intermediador-sandbox.yapay.com.br/authentication?consumer_key=",
4779
+ production: "https://tc.intermediador.yapay.com.br/authentication?consumer_key="
4780
+ };
4043
4781
  var VINDI_FIELDS = [
4044
4782
  {
4045
4783
  key: "tokenAccount",
@@ -4047,6 +4785,27 @@ var VINDI_FIELDS = [
4047
4785
  type: "password",
4048
4786
  secret: true,
4049
4787
  placeholder: "Vindi Pagamentos \u2192 Minha Conta \u2192 Token"
4788
+ },
4789
+ {
4790
+ key: "consumerKey",
4791
+ label: "Chave de consumidor (para links de pagamento)",
4792
+ type: "password",
4793
+ secret: true,
4794
+ placeholder: "consumer_key"
4795
+ },
4796
+ {
4797
+ key: "consumerSecret",
4798
+ label: "Segredo de consumidor",
4799
+ type: "password",
4800
+ secret: true,
4801
+ placeholder: "consumer_secret"
4802
+ },
4803
+ {
4804
+ key: "authorizationCode",
4805
+ label: "CODE de autoriza\xE7\xE3o",
4806
+ type: "password",
4807
+ secret: true,
4808
+ placeholder: "copiado da barra de endere\xE7o, uma vez por ambiente"
4050
4809
  }
4051
4810
  ];
4052
4811
  var VINDI_ENVIRONMENT_SETTING = {
@@ -4145,11 +4904,14 @@ function connectionDetail(settings) {
4145
4904
  }
4146
4905
  function VindiExtraPanel() {
4147
4906
  const [connected, setConnected] = useState(false);
4907
+ const [environment, setEnvironment] = useState("");
4148
4908
  const [loaded, setLoaded] = useState(false);
4149
4909
  useEffect(() => {
4150
4910
  let cancelled = false;
4151
4911
  void store5.get().then((c) => {
4152
- if (!cancelled) setConnected(!!c?.active);
4912
+ if (cancelled) return;
4913
+ setConnected(!!c?.active);
4914
+ setEnvironment(String(c?.settings?.environment ?? ""));
4153
4915
  }).catch(() => {
4154
4916
  }).finally(() => {
4155
4917
  if (!cancelled) setLoaded(true);
@@ -4159,16 +4921,53 @@ function VindiExtraPanel() {
4159
4921
  };
4160
4922
  }, []);
4161
4923
  if (!loaded) return null;
4162
- return /* @__PURE__ */ jsx("div", { className: "space-y-2 border-t pt-4 text-xs text-muted-foreground", children: connected ? /* @__PURE__ */ jsxs(Fragment, { children: [
4163
- /* @__PURE__ */ jsx("p", { children: "Pix e boleto acontecem dentro da sua loja: o cliente v\xEA o QR Code ou a linha digit\xE1vel sem sair da p\xE1gina. O cart\xE3o \xE9 diferente \u2014 ele \xE9 digitado na p\xE1gina segura da Vindi, e o cliente volta para a loja em seguida. \xC9 assim de prop\xF3sito: nenhum n\xFAmero de cart\xE3o passa pelo seu site." }),
4164
- /* @__PURE__ */ jsxs("p", { children: [
4165
- "Voc\xEA tamb\xE9m pode gerar um ",
4166
- /* @__PURE__ */ jsx("strong", { children: "link de pagamento" }),
4167
- " em Pedidos \u2192 Novo e mandar no WhatsApp. O cliente escolhe como pagar na p\xE1gina da Vindi, e o pedido aparece aqui sozinho quando o dinheiro cair."
4168
- ] }),
4169
- /* @__PURE__ */ jsx("p", { children: "Sandbox e produ\xE7\xE3o s\xE3o contas diferentes, com tokens diferentes. Se voc\xEA trocar o ambiente aqui, troque tamb\xE9m as credenciais \u2014 as de teste n\xE3o cobram em produ\xE7\xE3o e as de produ\xE7\xE3o n\xE3o funcionam no sandbox." }),
4170
- /* @__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.' })
4171
- ] }) : /* @__PURE__ */ jsx("p", { children: "O token da conta voc\xEA pega no painel da Vindi Pagamentos, em Minha Conta. Confira se ele \xE9 o do mesmo ambiente que voc\xEA vai escolher aqui \u2014 o token de sandbox n\xE3o cobra em produ\xE7\xE3o." }) });
4924
+ const authenticationUrl = VINDI_AUTHENTICATION_URLS[environment];
4925
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-2 border-t pt-4 text-xs text-muted-foreground", children: [
4926
+ connected ? /* @__PURE__ */ jsxs(Fragment, { children: [
4927
+ /* @__PURE__ */ jsx("p", { children: "Pix e boleto acontecem dentro da sua loja: o cliente v\xEA o QR Code ou a linha digit\xE1vel sem sair da p\xE1gina. O cart\xE3o \xE9 diferente \u2014 ele \xE9 digitado na p\xE1gina segura da Vindi, e o cliente volta para a loja em seguida. \xC9 assim de prop\xF3sito: nenhum n\xFAmero de cart\xE3o passa pelo seu site." }),
4928
+ /* @__PURE__ */ jsxs("p", { children: [
4929
+ "Voc\xEA tamb\xE9m pode gerar um ",
4930
+ /* @__PURE__ */ jsx("strong", { children: "link de pagamento" }),
4931
+ " em Pedidos \u2192 Novo e mandar no WhatsApp. O cliente escolhe como pagar na p\xE1gina da Vindi, e o pedido aparece aqui sozinho quando o dinheiro cair."
4932
+ ] }),
4933
+ /* @__PURE__ */ jsx("p", { children: "Sandbox e produ\xE7\xE3o s\xE3o contas diferentes, com tokens diferentes. Se voc\xEA trocar o ambiente aqui, troque tamb\xE9m as credenciais \u2014 as de teste n\xE3o cobram em produ\xE7\xE3o e as de produ\xE7\xE3o n\xE3o funcionam no sandbox." }),
4934
+ /* @__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.' })
4935
+ ] }) : /* @__PURE__ */ jsx("p", { children: "O token da conta voc\xEA pega no painel da Vindi Pagamentos, em Minha Conta. Confira se ele \xE9 o do mesmo ambiente que voc\xEA vai escolher aqui \u2014 o token de sandbox n\xE3o cobra em produ\xE7\xE3o." }),
4936
+ /* @__PURE__ */ jsxs("div", { className: "rounded-md border border-dashed p-3", children: [
4937
+ /* @__PURE__ */ jsx("p", { className: "font-medium text-foreground", children: "Para gerar links de pagamento" }),
4938
+ /* @__PURE__ */ jsxs("p", { className: "mt-1", children: [
4939
+ "O token da conta n\xE3o emite link \u2014 a Vindi exige uma autoriza\xE7\xE3o \xE0 parte. Pe\xE7a a",
4940
+ " ",
4941
+ /* @__PURE__ */ jsx("strong", { children: "chave" }),
4942
+ " e o ",
4943
+ /* @__PURE__ */ jsx("strong", { children: "segredo de consumidor" }),
4944
+ " no painel da Vindi e preencha os campos acima. Falta ainda o ",
4945
+ /* @__PURE__ */ jsx("strong", { children: "CODE" }),
4946
+ ":"
4947
+ ] }),
4948
+ /* @__PURE__ */ jsxs("ol", { className: "mt-2 list-decimal space-y-1 pl-4", children: [
4949
+ /* @__PURE__ */ jsx("li", { children: authenticationUrl ? /* @__PURE__ */ jsxs(Fragment, { children: [
4950
+ "Abra no navegador",
4951
+ " ",
4952
+ /* @__PURE__ */ jsxs("code", { className: "break-all rounded bg-muted px-1 py-0.5", children: [
4953
+ authenticationUrl,
4954
+ "SUA_CHAVE"
4955
+ ] }),
4956
+ " ",
4957
+ "trocando ",
4958
+ /* @__PURE__ */ jsx("code", { children: "SUA_CHAVE" }),
4959
+ " pela sua chave de consumidor."
4960
+ ] }) : /* @__PURE__ */ jsx(Fragment, { children: "Escolha o ambiente acima e o endere\xE7o para buscar o CODE aparece aqui." }) }),
4961
+ /* @__PURE__ */ jsx("li", { children: "A Vindi redireciona. O CODE fica na barra de endere\xE7o \u2014 copie de l\xE1." }),
4962
+ /* @__PURE__ */ jsxs("li", { children: [
4963
+ "Cole no campo ",
4964
+ /* @__PURE__ */ jsx("strong", { children: "CODE de autoriza\xE7\xE3o" }),
4965
+ " e salve."
4966
+ ] })
4967
+ ] }),
4968
+ /* @__PURE__ */ jsx("p", { className: "mt-2", children: "\xC9 uma vez por ambiente. A partir da\xED a renova\xE7\xE3o di\xE1ria acontece sozinha." })
4969
+ ] })
4970
+ ] });
4172
4971
  }
4173
4972
  var vindiConnectorDef = {
4174
4973
  id: VINDI_CONNECTOR_ID,
@@ -4267,6 +5066,188 @@ var vindiConnectorDef = {
4267
5066
  disconnect: () => store5.disconnect(),
4268
5067
  ExtraPanel: VindiExtraPanel
4269
5068
  };
5069
+ var MERCADOPAGO_CONNECTOR_ID = "mercadopago";
5070
+ 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";
5096
+ }
5097
+ function MercadoPagoExtraPanel() {
5098
+ const [connected, setConnected] = useState(false);
5099
+ const [loaded, setLoaded] = useState(false);
5100
+ useEffect(() => {
5101
+ let cancelled = false;
5102
+ void store6.get().then((c) => {
5103
+ if (!cancelled) setConnected(!!c?.active);
5104
+ }).catch(() => {
5105
+ }).finally(() => {
5106
+ if (!cancelled) setLoaded(true);
5107
+ });
5108
+ return () => {
5109
+ cancelled = true;
5110
+ };
5111
+ }, []);
5112
+ if (!loaded) return null;
5113
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-2 border-t pt-4 text-xs text-muted-foreground", children: [
5114
+ connected ? /* @__PURE__ */ jsxs(Fragment, { children: [
5115
+ /* @__PURE__ */ jsxs("p", { children: [
5116
+ "Pix e cart\xE3o acontecem ",
5117
+ /* @__PURE__ */ jsx("strong", { children: "dentro da sua loja" }),
5118
+ ". O cliente v\xEA o QR Code ou digita o cart\xE3o sem sair da p\xE1gina, e volta para a confirma\xE7\xE3o no mesmo lugar. Diferente das outras adquirentes, aqui o cart\xE3o n\xE3o redireciona."
5119
+ ] }),
5120
+ /* @__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
+ /* @__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
+ /* @__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."
5136
+ ] }),
5137
+ /* @__PURE__ */ jsxs("div", { className: "rounded-md border border-dashed p-3", children: [
5138
+ /* @__PURE__ */ jsx("p", { className: "font-medium text-foreground", children: "Para testar sem cobrar de verdade" }),
5139
+ /* @__PURE__ */ jsxs("p", { className: "mt-1", children: [
5140
+ "As chaves que come\xE7am com ",
5141
+ /* @__PURE__ */ jsx("code", { className: "rounded bg-muted px-1 py-0.5", children: "TEST-" }),
5142
+ " ",
5143
+ /* @__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."
5149
+ ] })
5150
+ ] })
5151
+ ] });
5152
+ }
5153
+ var mercadoPagoConnectorDef = {
5154
+ id: MERCADOPAGO_CONNECTOR_ID,
5155
+ hostPluginId: "shop",
5156
+ name: "Mercado Pago",
5157
+ description: "Pix e cart\xE3o pelo Mercado Pago, os dois pagos dentro da loja \u2014 o cart\xE3o \xE9 digitado sem o cliente sair da p\xE1gina.",
5158
+ icon: "CreditCard",
5159
+ // Declared rather than borrowed from the catalogue, for the same reason Vindi
5160
+ // declares its own: the catalogue row is a change in another repo that has to
5161
+ // be seeded before it exists anywhere, and until then the shelf renders a grey
5162
+ // plug. The absolute URL answers from any origin — a tenant app is served from
5163
+ // its own domain, so a path would not.
5164
+ logoUrl: "https://assets.fayz.ai/connectors/mercadopago.png",
5165
+ 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",
5175
+ // The charge is opened in the buyer's request path; the notification and the
5176
+ // refund are outbound sync work.
5177
+ planes: ["request", "sync"],
5178
+ direction: "outbound",
5179
+ docsUrl: "https://www.mercadopago.com.br/developers/pt/docs/checkout-api/landing",
5180
+ providerInstall: { url: "https://www.mercadopago.com.br/developers/panel/app" },
5181
+ // Grounded in what the request plane does, and padded with nothing: the total
5182
+ // is recomputed server-side from the order before the charge is opened, which
5183
+ // is a read of the order and never a write of it; the charge and its
5184
+ // confirmation are the write.
5185
+ permissions: [
5186
+ {
5187
+ kind: "data",
5188
+ resource: "order",
5189
+ actions: ["read"],
5190
+ reason: "Ler o pedido para recalcular o valor no servidor antes de abrir a cobran\xE7a \u2014 o total nunca vem do navegador do cliente.",
5191
+ sensitive: true
5192
+ },
5193
+ {
5194
+ kind: "data",
5195
+ resource: "payment",
5196
+ actions: ["write"],
5197
+ reason: "Registrar aqui a cobran\xE7a aberta e a confirma\xE7\xE3o do pagamento. O n\xFAmero do cart\xE3o n\xE3o passa por este app \u2014 ele \xE9 digitado num formul\xE1rio do pr\xF3prio Mercado Pago.",
5198
+ sensitive: true
5199
+ }
5200
+ ],
5201
+ latencyBudgetMs: MERCADOPAGO_LATENCY_BUDGET_MS,
5202
+ // A gateway that does not answer must stop the sale: an order that reads as
5203
+ // confirmed without a payment is worse than a refused checkout. Same verdict
5204
+ // as the other four gateways, opposite to the carrier next door.
5205
+ onFailure: "block",
5206
+ fields: MERCADOPAGO_FIELDS,
5207
+ invokedAt: [
5208
+ {
5209
+ key: "shop.checkout.session_open",
5210
+ label: "O cliente finaliza a compra e a cobran\xE7a \xE9 aberta no Mercado Pago (QR do Pix ou cart\xE3o digitado na pr\xF3pria loja)",
5211
+ touches: ["order", "payment"]
5212
+ },
5213
+ {
5214
+ key: "shop.payment.capture",
5215
+ label: "O Mercado Pago avisa que o pagamento caiu, a cobran\xE7a \xE9 reconsultada e o pedido \xE9 liberado",
5216
+ plane: "sync",
5217
+ trigger: "webhook",
5218
+ touches: ["payment"]
5219
+ }
5220
+ ],
5221
+ // Read from the row the list already batched, so a screen with N connectors
5222
+ // costs one plg_connections request rather than N.
5223
+ //
5224
+ // No `settingsStatus`: this connector has nothing to configure, so a
5225
+ // connection either has its credential pair or does not. "Conectado, falta
5226
+ // configurar" would be a state that can never be true here.
5227
+ ...connectionStatus(
5228
+ MERCADOPAGO_CONNECTOR_ID,
5229
+ (connection) => ({
5230
+ connected: !!connection?.active,
5231
+ detail: connectionDetail2(connection?.settings),
5232
+ plane: "request"
5233
+ }),
5234
+ { store: () => store6 }
5235
+ ),
5236
+ ...connectionSettings(store6),
5237
+ // Reached only AFTER the credential sink accepted the pair — that ordering is
5238
+ // what makes `active = true` mean "credentials were placed for this store"
5239
+ // rather than "somebody pressed a button". The secret never arrives here: the
5240
+ // hub split it out before calling this.
5241
+ //
5242
+ // No `testConnection`: the probe is handed the non-secret half, and a public
5243
+ // key alone cannot open an order. Testing belongs to the edge function that
5244
+ // holds the credential.
5245
+ saveConnection: async () => {
5246
+ await store6.save({ active: true });
5247
+ },
5248
+ disconnect: () => store6.disconnect(),
5249
+ ExtraPanel: MercadoPagoExtraPanel
5250
+ };
4270
5251
  var KIND_META = {
4271
5252
  online_store: { label: "Loja Online", icon: Globe, hint: "Seu storefront fayz \u2014 a vitrine p\xFAblica da loja.", placeholder: "https://minhaloja.com.br/" },
4272
5253
  whatsapp: { label: "WhatsApp", icon: MessageCircle, hint: "Link direto para o WhatsApp Business.", placeholder: "https://wa.me/5521999999999" },
@@ -4284,12 +5265,12 @@ function notifyChannelsChanged() {
4284
5265
  if (typeof window !== "undefined") window.dispatchEvent(new Event("fayz:channels-changed"));
4285
5266
  }
4286
5267
  function ChannelsPage({ resolveProvider }) {
4287
- const provider = React8.useMemo(() => resolve2(resolveProvider), [resolveProvider]);
5268
+ const provider = React9.useMemo(() => resolve2(resolveProvider), [resolveProvider]);
4288
5269
  const supported = typeof provider.listSalesChannels === "function";
4289
- const [channels, setChannels] = React8.useState([]);
4290
- const [loading, setLoading] = React8.useState(true);
4291
- const [adding, setAdding] = React8.useState(null);
4292
- const reload = React8.useCallback(async () => {
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 () => {
4293
5274
  if (!provider.listSalesChannels) {
4294
5275
  setLoading(false);
4295
5276
  return;
@@ -4307,7 +5288,7 @@ function ChannelsPage({ resolveProvider }) {
4307
5288
  setLoading(false);
4308
5289
  }
4309
5290
  }, [provider]);
4310
- React8.useEffect(() => {
5291
+ React9.useEffect(() => {
4311
5292
  void reload();
4312
5293
  }, [reload]);
4313
5294
  const patch = async (id, input) => {
@@ -4363,7 +5344,7 @@ function ChannelsPage({ resolveProvider }) {
4363
5344
  ] });
4364
5345
  }
4365
5346
  function OnlineStoreCard({ channel, onSave, onToggle }) {
4366
- const [url, setUrl] = React8.useState(channel.url ?? "");
5347
+ const [url, setUrl] = React9.useState(channel.url ?? "");
4367
5348
  const dirty = (channel.url ?? "") !== url;
4368
5349
  const meta = KIND_META.online_store;
4369
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: [
@@ -4389,8 +5370,8 @@ function OnlineStoreCard({ channel, onSave, onToggle }) {
4389
5370
  ] }) });
4390
5371
  }
4391
5372
  function ChannelRow({ channel, onSave, onToggle, onRemove }) {
4392
- const [url, setUrl] = React8.useState(channel.url ?? "");
4393
- const [editing, setEditing] = React8.useState(false);
5373
+ const [url, setUrl] = React9.useState(channel.url ?? "");
5374
+ const [editing, setEditing] = React9.useState(false);
4394
5375
  const meta = KIND_META[channel.kind] ?? KIND_META.custom;
4395
5376
  const Icon = meta.icon;
4396
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: [
@@ -4418,9 +5399,9 @@ function ChannelRow({ channel, onSave, onToggle, onRemove }) {
4418
5399
  }
4419
5400
  function AddChannelForm({ kind, onKind, onCancel, onAdd }) {
4420
5401
  const meta = KIND_META[kind];
4421
- const [label, setLabel] = React8.useState(meta.label);
4422
- const [url, setUrl] = React8.useState("");
4423
- React8.useEffect(() => {
5402
+ const [label, setLabel] = React9.useState(meta.label);
5403
+ const [url, setUrl] = React9.useState("");
5404
+ React9.useEffect(() => {
4424
5405
  setLabel(KIND_META[kind].label);
4425
5406
  }, [kind]);
4426
5407
  return /* @__PURE__ */ jsxs("div", { className: "mt-3 rounded-card border border-border bg-card p-4", children: [
@@ -4472,17 +5453,17 @@ function formatSize(bytes) {
4472
5453
  return `${(bytes / 1024 ** i).toFixed(i === 0 ? 0 : 1)} ${units[i]}`;
4473
5454
  }
4474
5455
  function ContentPage({ resolveProvider }) {
4475
- const provider = React8.useMemo(() => resolve3(resolveProvider), [resolveProvider]);
5456
+ const provider = React9.useMemo(() => resolve3(resolveProvider), [resolveProvider]);
4476
5457
  const supported = typeof provider.listMediaFiles === "function";
4477
- const [files, setFiles] = React8.useState(null);
4478
- const [tab, setTab] = React8.useState("all");
4479
- const [uploading, setUploading] = React8.useState(false);
4480
- const [pendingDelete, setPendingDelete] = React8.useState(null);
4481
- const [deleting, setDeleting] = React8.useState(false);
4482
- const [replacing, setReplacing] = React8.useState(false);
4483
- const [openId, setOpenId] = React8.useState(null);
4484
- const [dimensions, setDimensions] = React8.useState({});
4485
- const reload = React8.useCallback(async () => {
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 () => {
4486
5467
  if (!provider.listMediaFiles) {
4487
5468
  setFiles([]);
4488
5469
  return;
@@ -4494,10 +5475,10 @@ function ContentPage({ resolveProvider }) {
4494
5475
  setFiles([]);
4495
5476
  }
4496
5477
  }, [provider]);
4497
- React8.useEffect(() => {
5478
+ React9.useEffect(() => {
4498
5479
  void reload();
4499
5480
  }, [reload]);
4500
- const measure = React8.useCallback((id, d) => {
5481
+ const measure = React9.useCallback((id, d) => {
4501
5482
  setDimensions((prev) => prev[id] ? prev : { ...prev, [id]: d });
4502
5483
  }, []);
4503
5484
  const handleUpload = async (picked) => {
@@ -4687,7 +5668,7 @@ function SectionTitle({ title, count }) {
4687
5668
  ] });
4688
5669
  }
4689
5670
  function UploadButton({ uploading, onPick }) {
4690
- const inputRef = React8.useRef(null);
5671
+ const inputRef = React9.useRef(null);
4691
5672
  return /* @__PURE__ */ jsxs(Fragment, { children: [
4692
5673
  /* @__PURE__ */ jsx(
4693
5674
  "input",
@@ -4767,8 +5748,8 @@ function MediaDetail({
4767
5748
  onDelete,
4768
5749
  replacing
4769
5750
  }) {
4770
- const replaceRef = React8.useRef(null);
4771
- React8.useEffect(() => {
5751
+ const replaceRef = React9.useRef(null);
5752
+ React9.useEffect(() => {
4772
5753
  function onKey(e) {
4773
5754
  if (e.key === "ArrowLeft") onNavigate(-1);
4774
5755
  if (e.key === "ArrowRight") onNavigate(1);
@@ -4995,7 +5976,7 @@ async function pendingCustomers() {
4995
5976
  if (!tenantId || !supabase) return null;
4996
5977
  const { data: linked } = await supabase.from("plg_shop_customer_links").select("customer_id").eq("tenant_id", tenantId).eq("source", "bling");
4997
5978
  const already = new Set((linked ?? []).map((row) => row.customer_id));
4998
- const { data, error } = await supabase.from("plg_shop_customers").select("id").eq("tenant_id", tenantId).gt("orders_count", 0);
5979
+ const { data, error } = await supabase.from("v_shop_customers").select("id").eq("tenant_id", tenantId).gt("orders_count", 0);
4999
5980
  if (error) return null;
5000
5981
  return (data ?? []).filter((row) => !already.has(row.id)).length;
5001
5982
  }
@@ -5005,7 +5986,7 @@ async function pendingOrders() {
5005
5986
  if (!tenantId || !supabase) return null;
5006
5987
  const { data: linked } = await supabase.from("plg_shop_order_links").select("order_id").eq("tenant_id", tenantId).eq("source", "bling").eq("kind", "sales_order");
5007
5988
  const already = new Set((linked ?? []).map((row) => row.order_id));
5008
- const { data, error } = await supabase.from("plg_shop_orders").select("id").eq("tenant_id", tenantId).neq("status", "cancelled");
5989
+ const { data, error } = await supabase.from("v_shop_orders").select("id").eq("tenant_id", tenantId).neq("status", "cancelled");
5009
5990
  if (error) return null;
5010
5991
  return (data ?? []).filter((row) => !already.has(row.id)).length;
5011
5992
  }
@@ -5013,7 +5994,7 @@ async function listOrderDeliveries(limit = 10) {
5013
5994
  const tenantId = getShopTenantId();
5014
5995
  const supabase = getSupabaseClientOptional();
5015
5996
  if (!tenantId || !supabase) return [];
5016
- const { data: orders } = await supabase.from("plg_shop_orders").select("id,order_number,customer_name,customer_document,paid_at,created_at").eq("tenant_id", tenantId).neq("status", "cancelled").order("created_at", { ascending: false }).limit(limit);
5997
+ const { data: orders } = await supabase.from("v_shop_orders").select("id,order_number,customer_name,customer_document,paid_at,created_at").eq("tenant_id", tenantId).neq("status", "cancelled").order("created_at", { ascending: false }).limit(limit);
5017
5998
  const rows = orders ?? [];
5018
5999
  if (rows.length === 0) return [];
5019
6000
  const { data: filed } = await supabase.from("plg_shop_order_links").select("order_id,external_id,external_number,synced_at").eq("tenant_id", tenantId).eq("source", "bling").eq("kind", "sales_order").in("order_id", rows.map((row) => row.id));
@@ -5537,7 +6518,7 @@ async function pendingOrders2() {
5537
6518
  if (!tenantId || !supabase) return null;
5538
6519
  const { data: linked } = await supabase.from("plg_shop_order_links").select("order_id").eq("tenant_id", tenantId).eq("source", "neochef").eq("kind", "kitchen_order");
5539
6520
  const already = new Set((linked ?? []).map((row) => row.order_id));
5540
- const { data, error } = await supabase.from("plg_shop_orders").select("id").eq("tenant_id", tenantId).neq("status", "cancelled");
6521
+ const { data, error } = await supabase.from("v_shop_orders").select("id").eq("tenant_id", tenantId).neq("status", "cancelled");
5541
6522
  if (error) return null;
5542
6523
  return (data ?? []).filter((row) => !already.has(row.id)).length;
5543
6524
  }
@@ -5545,7 +6526,7 @@ async function listOrderDeliveries2(limit = 10) {
5545
6526
  const tenantId = getShopTenantId();
5546
6527
  const supabase = getSupabaseClientOptional();
5547
6528
  if (!tenantId || !supabase) return [];
5548
- const { data: orders } = await supabase.from("plg_shop_orders").select("id,order_number,paid_at,created_at,customer_name,status").eq("tenant_id", tenantId).order("created_at", { ascending: false }).limit(limit);
6529
+ const { data: orders } = await supabase.from("v_shop_orders").select("id,order_number,paid_at,created_at,customer_name,status").eq("tenant_id", tenantId).order("created_at", { ascending: false }).limit(limit);
5549
6530
  const rows = orders ?? [];
5550
6531
  if (rows.length === 0) return [];
5551
6532
  const { data: filed } = await supabase.from("plg_shop_order_links").select("order_id,external_id,external_number,synced_at").eq("tenant_id", tenantId).eq("source", "neochef").eq("kind", "kitchen_order").in("order_id", rows.map((row) => row.id));
@@ -6623,7 +7604,7 @@ function buildShopAnalyticsCards() {
6623
7604
  }
6624
7605
 
6625
7606
  // src/index.ts
6626
- var ShopPage = React8.lazy(() => import('./ShopPage-VVLOH3RF.js').then((m) => ({ default: m.ShopPage })));
7607
+ var ShopPage = React9.lazy(() => import('./ShopPage-QFIM2HDU.js').then((m) => ({ default: m.ShopPage })));
6627
7608
  function createShopPlugin(options) {
6628
7609
  const curr = { code: "BRL", locale: "pt-BR", symbol: "R$", ...options?.currency };
6629
7610
  setShopTenantResolver(() => useOrganizationStore.getState().currentOrg?.id);
@@ -6632,10 +7613,10 @@ function createShopPlugin(options) {
6632
7613
  registerEntityPath("shop_customer", "/orders/customers");
6633
7614
  registerPersonLookup("shop_customer", shopCustomerLookup(() => resolveProvider()));
6634
7615
  const pageFor = (section) => {
6635
- const Wrapped = () => React8.createElement(
6636
- React8.Suspense,
7616
+ const Wrapped = () => React9.createElement(
7617
+ React9.Suspense,
6637
7618
  { fallback: null },
6638
- React8.createElement(
7619
+ React9.createElement(
6639
7620
  ShopPage,
6640
7621
  { provider: options?.provider, section }
6641
7622
  )
@@ -6761,6 +7742,29 @@ function createShopPlugin(options) {
6761
7742
  const DiscountsCrudPage = createCrudPage(buildShopDiscountEntity(), {
6762
7743
  dataProvider: createShopDiscountDataProvider(resolveProvider),
6763
7744
  feature: "shop",
7745
+ // O cupom vive atrás do provider de commerce, sem tabela que as primitivas
7746
+ // de dado do agente alcancem — então a lista empresta o próprio store, como
7747
+ // a de produtos faz (ver CrudAgentSurface).
7748
+ aiCreate: {
7749
+ hint: "Descreva o cupom\u2026",
7750
+ description: "Descreva a promo\xE7\xE3o e eu crio o cupom.",
7751
+ // O código não se pergunta: ver DISCOUNT_CREATE_INSTRUCTIONS. `code` é
7752
+ // único no banco (0001:208), então a lista publica os que já existem e a
7753
+ // recusa do índice volta legível se dois turnos escolherem o mesmo.
7754
+ instructions: DISCOUNT_CREATE_INSTRUCTIONS,
7755
+ uniqueFields: ["code"],
7756
+ intro: {
7757
+ role: "operations",
7758
+ persona: "Cupons e promo\xE7\xF5es",
7759
+ pitch: "Diga a promo\xE7\xE3o que voc\xEA quer fazer.",
7760
+ description: "Frete gr\xE1tis, percentual ou valor fixo \u2014 diga a regra do jeito que voc\xEA falaria com um cliente, e eu escrevo o c\xF3digo, o tipo, o m\xEDnimo e a validade. S\xF3 pergunto o tamanho do desconto quando voc\xEA n\xE3o disser.",
7761
+ suggestions: [
7762
+ { label: "Frete gr\xE1tis at\xE9 o fim da semana" },
7763
+ { label: "10% na primeira compra, uma vez por cliente" },
7764
+ { label: "R$ 50 off acima de R$ 300, limite de 100 usos" }
7765
+ ]
7766
+ }
7767
+ },
6764
7768
  stats: (items, total) => [
6765
7769
  { id: "total", label: "Total de cupons", value: total, icon: Tag },
6766
7770
  { id: "active", label: "Ativos agora", value: items?.filter((d) => d.status === "active").length ?? 0, icon: Eye },
@@ -6791,7 +7795,7 @@ function createShopPlugin(options) {
6791
7795
  dataProvider: createShopGiftCardDataProvider(resolveProvider),
6792
7796
  feature: "shop",
6793
7797
  stats: (items, total) => {
6794
- const money3 = (v) => new Intl.NumberFormat(curr.locale, { style: "currency", currency: curr.code }).format(v);
7798
+ const money2 = (v) => new Intl.NumberFormat(curr.locale, { style: "currency", currency: curr.code }).format(v);
6795
7799
  const active = items?.filter((g) => g.status === "active") ?? [];
6796
7800
  return [
6797
7801
  { id: "total", label: "Cart\xF5es emitidos", value: total, icon: CreditCard },
@@ -6802,20 +7806,20 @@ function createShopPlugin(options) {
6802
7806
  id: "outstanding",
6803
7807
  label: "Saldo em aberto",
6804
7808
  icon: CircleDollarSign,
6805
- value: money3(active.reduce((sum, g) => sum + (g.balance ?? 0), 0))
7809
+ value: money2(active.reduce((sum, g) => sum + (g.balance ?? 0), 0))
6806
7810
  },
6807
7811
  {
6808
7812
  id: "issued",
6809
7813
  label: "Valor emitido",
6810
7814
  icon: TicketCheck,
6811
- value: money3(items?.reduce((sum, g) => sum + (g.initialBalance ?? 0), 0) ?? 0)
7815
+ value: money2(items?.reduce((sum, g) => sum + (g.initialBalance ?? 0), 0) ?? 0)
6812
7816
  }
6813
7817
  ];
6814
7818
  }
6815
7819
  });
6816
- const ContentPageWrapped = () => React8.createElement(ContentPage, { resolveProvider: options?.provider });
7820
+ const ContentPageWrapped = () => React9.createElement(ContentPage, { resolveProvider: options?.provider });
6817
7821
  ContentPageWrapped.displayName = "ContentPage";
6818
- const ChannelsPageWrapped = () => React8.createElement(ChannelsPage, { resolveProvider: options?.provider });
7822
+ const ChannelsPageWrapped = () => React9.createElement(ChannelsPage, { resolveProvider: options?.provider });
6819
7823
  ChannelsPageWrapped.displayName = "ChannelsPage";
6820
7824
  const navSection = options?.navSection ?? "main";
6821
7825
  const navPosition = options?.navPosition ?? 6;
@@ -6905,7 +7909,7 @@ function createShopPlugin(options) {
6905
7909
  ];
6906
7910
  const redirectTo = (target) => {
6907
7911
  const Redirect = () => {
6908
- React8.useEffect(() => {
7912
+ React9.useEffect(() => {
6909
7913
  adminNavigateTo(target);
6910
7914
  }, []);
6911
7915
  return null;
@@ -6937,6 +7941,10 @@ function createShopPlugin(options) {
6937
7941
  return {
6938
7942
  id: "shop",
6939
7943
  defaultAgentRole: "operations",
7944
+ // O que a loja pede de um tenant novo, na ordem em que faz diferença.
7945
+ // Vira linha de tarefa, aparece no checklist da trilha e é o roteiro que o
7946
+ // assistente segue — ver ./lib/onboarding.ts.
7947
+ onboarding: { program: buildShopOnboarding() },
6940
7948
  name: options?.navLabel ?? "Shop",
6941
7949
  icon: "ShoppingBag",
6942
7950
  version: "1.0.0",
@@ -7029,7 +8037,7 @@ function createShopPlugin(options) {
7029
8037
  label: label("store", "Loja"),
7030
8038
  icon: "ShoppingBag",
7031
8039
  group: COMMERCE_GROUP,
7032
- component: settingsTab("StoreGeneralSettings", () => React8.createElement(StoreGeneralSettings, { currency: curr, provider: options?.provider })),
8040
+ component: settingsTab("StoreGeneralSettings", () => React9.createElement(StoreGeneralSettings, { currency: curr, provider: options?.provider })),
7033
8041
  order: 30,
7034
8042
  permission: READ
7035
8043
  },
@@ -7038,7 +8046,7 @@ function createShopPlugin(options) {
7038
8046
  label: "Pagamentos",
7039
8047
  icon: "CreditCard",
7040
8048
  group: COMMERCE_GROUP,
7041
- component: settingsTab("PaymentsSettings", () => React8.createElement(PaymentsSettings, { provider: options?.provider })),
8049
+ component: settingsTab("PaymentsSettings", () => React9.createElement(PaymentsSettings, { provider: options?.provider })),
7042
8050
  order: 31,
7043
8051
  permission: READ
7044
8052
  },
@@ -7047,7 +8055,7 @@ function createShopPlugin(options) {
7047
8055
  label: "Checkout",
7048
8056
  icon: "ShoppingCart",
7049
8057
  group: COMMERCE_GROUP,
7050
- component: settingsTab("CheckoutSettings", () => React8.createElement(CheckoutSettings, { provider: options?.provider })),
8058
+ component: settingsTab("CheckoutSettings", () => React9.createElement(CheckoutSettings, { provider: options?.provider })),
7051
8059
  order: 32,
7052
8060
  permission: READ
7053
8061
  },
@@ -7056,7 +8064,7 @@ function createShopPlugin(options) {
7056
8064
  label: label("shipping", "Entrega"),
7057
8065
  icon: "Truck",
7058
8066
  group: COMMERCE_GROUP,
7059
- component: settingsTab("ShippingSettings", () => React8.createElement(ShippingSettings, { provider: options?.provider })),
8067
+ component: settingsTab("ShippingSettings", () => React9.createElement(ShippingSettings, { provider: options?.provider })),
7060
8068
  order: 33,
7061
8069
  permission: READ
7062
8070
  },
@@ -7065,7 +8073,7 @@ function createShopPlugin(options) {
7065
8073
  label: "Fulfillment",
7066
8074
  icon: "PackageCheck",
7067
8075
  group: COMMERCE_GROUP,
7068
- component: settingsTab("FulfillmentSettings", () => React8.createElement(FulfillmentSettings, { provider: options?.provider })),
8076
+ component: settingsTab("FulfillmentSettings", () => React9.createElement(FulfillmentSettings, { provider: options?.provider })),
7069
8077
  order: 34,
7070
8078
  permission: READ
7071
8079
  }
@@ -7078,27 +8086,33 @@ function createShopPlugin(options) {
7078
8086
  // ones — nobody is waiting on them, and what they declare instead is a call
7079
8087
  // budget and a cursor.
7080
8088
  //
7081
- // Two gateways on the shelf is the point: the store picks one, and the
7082
- // second one arriving changed no contract — only how it authorizes. NeoChef
7083
- // makes the same point about the back office, and one better: its provider
7084
- // is another Fayz app, and it still fits the shelf without a special case.
8089
+ // Four gateways on the shelf is the point: the store picks one, and each
8090
+ // one arriving changed no contract — only how it authorizes. Mercado Pago
8091
+ // pushes that furthest, because it is the first whose CARD is paid inside
8092
+ // the store: it tokenises in the browser, so there is no PAN for an edge
8093
+ // function to hold and no redirect to send the buyer through. The shelf
8094
+ // absorbed that too, as a capability the checkout reads rather than a branch
8095
+ // it hard-codes. NeoChef makes the same point about the back office, and one
8096
+ // better: its provider is another Fayz app, and it still fits without a
8097
+ // special case.
7085
8098
  connectors: [
7086
8099
  melhorEnvioConnectorDef,
7087
8100
  appmaxConnectorDef,
7088
8101
  abacatePayConnectorDef,
7089
8102
  stripeConnectConnectorDef,
7090
8103
  vindiConnectorDef,
8104
+ mercadoPagoConnectorDef,
7091
8105
  blingConnectorDef,
7092
8106
  neoChefConnectorDef
7093
8107
  ],
7094
- // What having a store makes plugable — the four above plus the ones still
8108
+ // What having a store makes plugable — the ones above plus the ones still
7095
8109
  // to be built. Carried over from the Fayz plugin page's "Integra com" row,
7096
8110
  // minus Nuvemshop: the platform catalogue has no row for it, and a slug the
7097
8111
  // directory cannot back is refused rather than shown.
7098
- integrations: ["shopify", "stripe", "mercadopago"]
8112
+ integrations: ["shopify", "stripe"]
7099
8113
  };
7100
8114
  }
7101
8115
 
7102
- export { ABACATEPAY_CONNECTOR_ID, ABACATEPAY_LATENCY_BUDGET_MS, APPMAX_CONNECTOR_ID, APPMAX_LATENCY_BUDGET_MS, BLING_CONNECTOR_ID, BLING_MARKETPLACE_SOURCE, BLING_MAX_WINDOW_MS, BLING_PAGE_SIZE, BLING_RATE_LIMIT, BLING_SAMPLE_SIZE, BLING_SIGNATURE_HEADER, BLING_TOKEN_LEAD_MS, BLING_WEBHOOK_DEADLINE_MS, BLING_WINDOW_OVERLAP_MS, MELHOR_ENVIO_CONNECTOR_ID, MELHOR_ENVIO_LATENCY_BUDGET_MS, NEOCHEF_CONNECTOR_ID, NEOCHEF_CURSOR_OVERLAP_MS, NEOCHEF_FUNCTION, NEOCHEF_MARK, NEOCHEF_PAGE_SIZE, NEOCHEF_RATE_LIMIT, NEOCHEF_SAMPLE_SIZE, NEOCHEF_TOKEN_LEAD_MS, STRIPE_CONNECT_CONNECTOR_ID, STRIPE_CONNECT_LATENCY_BUDGET_MS, VINDI_CONNECTOR_ID, VINDI_LATENCY_BUDGET_MS, abacatePayConnectorDef, acceptWebhook, advanceNeoChefCursor, appmaxConnectorDef, blingConnectionStore, blingConnectorDef, buildShopAnalyticsCards, createShopPlugin, firstWindow, listOrderDeliveries2 as listOrderDeliveries, melhorEnvioConnectorDef, neoChefConnectionStore, neoChefConnectorDef, orphanConnection, pendingOrders2 as pendingOrders, pullCatalogue2 as pullCatalogue, pullProducts, pushOrders2 as pushOrders, reconcile2 as reconcile, routeCallback, sampleProducts, stripeConnectConnectorDef, testNeoChef, toMirroredNeoChefProduct, toMirroredProduct, toNeoChefOrder, vindiConnectorDef, windowFor };
8116
+ export { ABACATEPAY_CONNECTOR_ID, ABACATEPAY_LATENCY_BUDGET_MS, APPMAX_CONNECTOR_ID, APPMAX_LATENCY_BUDGET_MS, BLING_CONNECTOR_ID, BLING_MARKETPLACE_SOURCE, BLING_MAX_WINDOW_MS, BLING_PAGE_SIZE, BLING_RATE_LIMIT, BLING_SAMPLE_SIZE, BLING_SIGNATURE_HEADER, BLING_TOKEN_LEAD_MS, BLING_WEBHOOK_DEADLINE_MS, BLING_WINDOW_OVERLAP_MS, MELHOR_ENVIO_CONNECTOR_ID, MELHOR_ENVIO_LATENCY_BUDGET_MS, MERCADOPAGO_CONNECTOR_ID, MERCADOPAGO_LATENCY_BUDGET_MS, NEOCHEF_CONNECTOR_ID, NEOCHEF_CURSOR_OVERLAP_MS, NEOCHEF_FUNCTION, NEOCHEF_MARK, NEOCHEF_PAGE_SIZE, NEOCHEF_RATE_LIMIT, NEOCHEF_SAMPLE_SIZE, NEOCHEF_TOKEN_LEAD_MS, STRIPE_CONNECT_CONNECTOR_ID, STRIPE_CONNECT_LATENCY_BUDGET_MS, VINDI_CONNECTOR_ID, VINDI_LATENCY_BUDGET_MS, abacatePayConnectorDef, acceptWebhook, advanceNeoChefCursor, appmaxConnectorDef, blingConnectionStore, blingConnectorDef, buildShopAnalyticsCards, createShopPlugin, firstWindow, listOrderDeliveries2 as listOrderDeliveries, melhorEnvioConnectorDef, mercadoPagoConnectorDef, neoChefConnectionStore, neoChefConnectorDef, orphanConnection, pendingOrders2 as pendingOrders, pullCatalogue2 as pullCatalogue, pullProducts, pushOrders2 as pushOrders, reconcile2 as reconcile, routeCallback, sampleProducts, stripeConnectConnectorDef, testNeoChef, toMirroredNeoChefProduct, toMirroredProduct, toNeoChefOrder, vindiConnectorDef, windowFor };
7103
8117
  //# sourceMappingURL=index.js.map
7104
8118
  //# sourceMappingURL=index.js.map