@fayz-ai/plugin-shop 0.11.2 → 0.12.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 (37) hide show
  1. package/dist/{ShopPage-YB5ZWECJ.js → ShopPage-VVLOH3RF.js} +67 -28
  2. package/dist/ShopPage-VVLOH3RF.js.map +1 -0
  3. package/dist/index.d.ts +1 -0
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/index.js +684 -141
  6. package/dist/index.js.map +1 -1
  7. package/dist/integrations/abacatepay/connectorDef.d.ts.map +1 -1
  8. package/dist/integrations/abacatepay/data.d.ts +40 -0
  9. package/dist/integrations/abacatepay/data.d.ts.map +1 -0
  10. package/dist/integrations/abacatepay/limits.d.ts +26 -0
  11. package/dist/integrations/abacatepay/limits.d.ts.map +1 -0
  12. package/dist/integrations/abacatepay/sync.d.ts +88 -0
  13. package/dist/integrations/abacatepay/sync.d.ts.map +1 -0
  14. package/dist/integrations/neochef/connectorDef.d.ts +12 -0
  15. package/dist/integrations/neochef/connectorDef.d.ts.map +1 -0
  16. package/dist/integrations/neochef/data.d.ts +54 -0
  17. package/dist/integrations/neochef/data.d.ts.map +1 -0
  18. package/dist/integrations/neochef/index.d.ts +8 -0
  19. package/dist/integrations/neochef/index.d.ts.map +1 -0
  20. package/dist/integrations/neochef/limits.d.ts +26 -0
  21. package/dist/integrations/neochef/limits.d.ts.map +1 -0
  22. package/dist/integrations/neochef/mapping.d.ts +31 -0
  23. package/dist/integrations/neochef/mapping.d.ts.map +1 -0
  24. package/dist/integrations/neochef/mark.d.ts +21 -0
  25. package/dist/integrations/neochef/mark.d.ts.map +1 -0
  26. package/dist/integrations/neochef/types.d.ts +71 -0
  27. package/dist/integrations/neochef/types.d.ts.map +1 -0
  28. package/dist/integrations/vindi/connectorDef.d.ts +25 -6
  29. package/dist/integrations/vindi/connectorDef.d.ts.map +1 -1
  30. package/dist/views/ContentPage.d.ts.map +1 -1
  31. package/dist/views/NewOrderCharge.d.ts +14 -2
  32. package/dist/views/NewOrderCharge.d.ts.map +1 -1
  33. package/dist/views/NewOrderPage.d.ts.map +1 -1
  34. package/dist/views/new-order-rules.d.ts +10 -1
  35. package/dist/views/new-order-rules.d.ts.map +1 -1
  36. package/package.json +5 -5
  37. package/dist/ShopPage-YB5ZWECJ.js.map +0 -1
package/dist/index.js CHANGED
@@ -2,8 +2,8 @@ import { shopCustomerLookup, resolve, invalidateProductLists, listProductsDetail
2
2
  import React8, { useState, useEffect, useRef } from 'react';
3
3
  import { createConnectionStore, connectionSettings, connectionStatus, settingsStatus, connectionRuns, tl, getSupabaseClientOptional, registerMessageDefaults, connectorRuntimeToken, CONNECTOR_RUNTIME_TOKEN_HEADER, 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, 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, Sparkles, 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, 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';
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
9
  import { advanceWindow, createRateLimiter, sliceWindow, paginate, reconcileMirror, serializeCursor } from '@fayz-ai/core/connector-runtime';
@@ -309,7 +309,7 @@ function AxisEditor({ axis, index, count, disabled, onChange, onMove, onRemove }
309
309
  onChange({ ...axis, values: merged });
310
310
  setDraft("");
311
311
  }
312
- return /* @__PURE__ */ jsxs("li", { className: "rounded-lg border bg-card p-3", children: [
312
+ return /* @__PURE__ */ jsxs("li", { className: "rounded-card border bg-card p-3", children: [
313
313
  /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
314
314
  /* @__PURE__ */ jsx(
315
315
  "input",
@@ -2676,7 +2676,7 @@ function PaymentsSettings({ provider }) {
2676
2676
  ] }),
2677
2677
  /* @__PURE__ */ jsx(AddConnectorButton, { onClick: () => setAdding(true) })
2678
2678
  ] }),
2679
- /* @__PURE__ */ jsx("div", { className: "rounded-lg border bg-card shadow-sm", children: payment.length === 0 ? /* @__PURE__ */ jsx("p", { className: "p-4 text-sm text-muted-foreground", children: "Nenhum provedor de pagamento dispon\xEDvel para este app ainda." }) : /* @__PURE__ */ jsxs("ul", { className: "divide-y", role: "radiogroup", "aria-label": "Quem cobra", children: [
2679
+ /* @__PURE__ */ jsx("div", { className: "rounded-card border bg-card shadow-sm", children: payment.length === 0 ? /* @__PURE__ */ jsx("p", { className: "p-4 text-sm text-muted-foreground", children: "Nenhum provedor de pagamento dispon\xEDvel para este app ainda." }) : /* @__PURE__ */ jsxs("ul", { className: "divide-y", role: "radiogroup", "aria-label": "Quem cobra", children: [
2680
2680
  payment.map((connector) => /* @__PURE__ */ jsx(
2681
2681
  PaymentProviderRow,
2682
2682
  {
@@ -2894,7 +2894,7 @@ function HandlingSettings({ settings, provider }) {
2894
2894
  ] }),
2895
2895
  /* @__PURE__ */ jsx("p", { className: "mt-0.5 text-[10px] text-muted-foreground", children: "Um perfil \xE9 um grupo de produtos com zonas pr\xF3prias \u2014 refrigerados, fr\xE1geis, volumosos, sob encomenda. Um perfil com s\xF3 as suas zonas e o gateway desligado \xE9 como se diz \xABisto n\xE3o vai de transportadora\xBB. Produto sem perfil usa o geral, que \xE9 o de todos hoje." })
2896
2896
  ] }),
2897
- /* @__PURE__ */ jsx("div", { className: "rounded-lg border bg-card px-5 shadow-sm", children: /* @__PURE__ */ jsxs(
2897
+ /* @__PURE__ */ jsx("div", { className: "rounded-card border bg-card px-5 shadow-sm", children: /* @__PURE__ */ jsxs(
2898
2898
  "button",
2899
2899
  {
2900
2900
  type: "button",
@@ -3180,7 +3180,7 @@ function ShippingSettings({ provider }) {
3180
3180
  /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: "De onde voc\xEA despacha" }),
3181
3181
  /* @__PURE__ */ jsx("p", { className: "mt-0.5 text-[10px] text-muted-foreground", children: "Os locais que guardam estoque e despacham pedidos. O CEP do local padr\xE3o \xE9 a origem que a transportadora usa para cotar o frete \u2014 \xE9 aqui que ele se cadastra, uma vez. Cada local pode ter o seu pr\xF3prio despachante ou seguir a decis\xE3o da loja." })
3182
3182
  ] }),
3183
- /* @__PURE__ */ jsx("div", { className: "rounded-lg border bg-card px-5 shadow-sm", children: /* @__PURE__ */ jsxs(
3183
+ /* @__PURE__ */ jsx("div", { className: "rounded-card border bg-card px-5 shadow-sm", children: /* @__PURE__ */ jsxs(
3184
3184
  "button",
3185
3185
  {
3186
3186
  type: "button",
@@ -3208,7 +3208,7 @@ function ShippingSettings({ provider }) {
3208
3208
  /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: "Zonas de entrega" }),
3209
3209
  /* @__PURE__ */ jsx("p", { className: "mt-0.5 text-[10px] text-muted-foreground", children: "Faixas de CEP com pre\xE7o, prazo e frete gr\xE1tis pr\xF3prios. Um CEP fora de todas as zonas cai no pre\xE7o base acima; se a loja n\xE3o tiver pre\xE7o base nem zona que o cubra, o checkout diz que n\xE3o entrega ali. Uma faixa tamb\xE9m pode ser uma recusa \u2014 \xABn\xE3o entrego aqui\xBB \u2014 e ela vence qualquer zona e qualquer gateway." })
3210
3210
  ] }),
3211
- /* @__PURE__ */ jsx("div", { className: "rounded-lg border bg-card px-5 shadow-sm", children: /* @__PURE__ */ jsxs(
3211
+ /* @__PURE__ */ jsx("div", { className: "rounded-card border bg-card px-5 shadow-sm", children: /* @__PURE__ */ jsxs(
3212
3212
  "button",
3213
3213
  {
3214
3214
  type: "button",
@@ -3262,7 +3262,7 @@ function ShippingSettings({ provider }) {
3262
3262
  /* @__PURE__ */ jsx("p", { className: "mt-0.5 text-[10px] text-muted-foreground", children: "Conectado, ele cota o frete para o CEP do comprador em vez de a loja adivinhar. O pre\xE7o cotado \xE9 gravado no servidor e \xE9 ele que o pedido cobra \u2014 o navegador escolhe entre op\xE7\xF5es, nunca informa um valor. Quais transportadoras aparecem vem da conta do gateway, n\xE3o desta lista." }),
3263
3263
  !carriersConnected && /* @__PURE__ */ jsx("p", { className: "mt-1 text-[10px] text-muted-foreground", children: "Nenhum conectado \u2014 a loja entrega por conta pr\xF3pria, pelo pre\xE7o base e pelas zonas acima. Conectar \xE9 opcional e pode ficar para depois." })
3264
3264
  ] }),
3265
- /* @__PURE__ */ jsx("div", { className: "rounded-lg border bg-card shadow-sm", children: /* @__PURE__ */ jsx("ul", { className: "divide-y", children: carriers.map((connector) => /* @__PURE__ */ jsx(ConnectorRow, { connector, onOpen: openJourney }, connector.id)) }) })
3265
+ /* @__PURE__ */ jsx("div", { className: "rounded-card border bg-card shadow-sm", children: /* @__PURE__ */ jsx("ul", { className: "divide-y", children: carriers.map((connector) => /* @__PURE__ */ jsx(ConnectorRow, { connector, onOpen: openJourney }, connector.id)) }) })
3266
3266
  ] }),
3267
3267
  /* @__PURE__ */ jsx(HandlingSettings, { settings: storeSettings, provider }),
3268
3268
  /* @__PURE__ */ jsx("div", { className: "flex min-h-5 items-center gap-1.5 px-1 text-xs text-muted-foreground", children: error ? /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1.5 text-destructive", children: [
@@ -3630,6 +3630,39 @@ var appmaxConnectorDef = {
3630
3630
  disconnect: () => store2.disconnect(),
3631
3631
  ExtraPanel: AppmaxExtraPanel
3632
3632
  };
3633
+ var ABACATEPAY_FUNCTION = "abacatepay-sync";
3634
+ function client() {
3635
+ const supabase = getSupabaseClientOptional();
3636
+ if (!supabase) throw new Error("Sem conex\xE3o com o banco para falar com a AbacatePay.");
3637
+ return supabase;
3638
+ }
3639
+ async function invoke(body) {
3640
+ const tenantId = getShopTenantId();
3641
+ if (!tenantId) throw new Error("Sem loja selecionada para sincronizar com a AbacatePay.");
3642
+ const runtimeToken = await connectorRuntimeToken({ tenantId });
3643
+ const { data, error } = await client().functions.invoke(ABACATEPAY_FUNCTION, {
3644
+ body: { ...body, tenantId },
3645
+ headers: { [CONNECTOR_RUNTIME_TOKEN_HEADER]: runtimeToken }
3646
+ });
3647
+ if (!error) return data;
3648
+ const status = Number(error?.context?.status ?? 0);
3649
+ let message2 = "";
3650
+ try {
3651
+ const payload = await error?.context?.json?.();
3652
+ message2 = String(payload?.message ?? payload?.error ?? "").trim();
3653
+ } catch {
3654
+ }
3655
+ if (!message2 && status === 404) {
3656
+ message2 = "Esta loja n\xE3o tem uma conta AbacatePay conectada.";
3657
+ }
3658
+ throw new Error(message2 || error.message);
3659
+ }
3660
+ function pushCatalogue() {
3661
+ return invoke({ action: "push_products" });
3662
+ }
3663
+ function reconcileCatalogue() {
3664
+ return invoke({ action: "reconcile" });
3665
+ }
3633
3666
  var ABACATEPAY_CONNECTOR_ID = "abacatepay";
3634
3667
  var ABACATEPAY_LATENCY_BUDGET_MS = 6e3;
3635
3668
  var ABACATEPAY_FIELDS = [
@@ -3649,8 +3682,13 @@ var ABACATEPAY_SETTINGS = [
3649
3682
  type: "multi-select",
3650
3683
  options: [
3651
3684
  { value: "PIX", label: "Pix" },
3652
- { value: "BOLETO", label: "Boleto" },
3653
- { value: "CARD", label: "Cart\xE3o de cr\xE9dito" }
3685
+ { value: "BOLETO", label: "Boleto" }
3686
+ // Credit card does NOT belong here. AbacatePay discontinued it for new
3687
+ // clients and new integrations, so offering the checkbox would let a
3688
+ // merchant switch on a method the acquirer refuses — and the first to find
3689
+ // out would be a buyer. `CARD` stays in METHOD_LABELS because an older
3690
+ // connection may have the option stored and its row still has to describe
3691
+ // itself.
3654
3692
  ],
3655
3693
  requiredToOperate: true
3656
3694
  }
@@ -3665,6 +3703,97 @@ function methodsDetail2(value) {
3665
3703
  const labels = list.map((v) => METHOD_LABELS2[String(v).trim()] ?? String(v).trim()).filter(Boolean);
3666
3704
  return labels.length > 0 ? labels.join(", ") : void 0;
3667
3705
  }
3706
+ function CatalogueSync() {
3707
+ const [busy, setBusy] = useState(null);
3708
+ const [result, setResult] = useState(null);
3709
+ const [error, setError] = useState(null);
3710
+ const run = async (kind) => {
3711
+ setBusy(kind);
3712
+ setError(null);
3713
+ try {
3714
+ setResult(await (kind === "push" ? pushCatalogue() : reconcileCatalogue()));
3715
+ } catch (caught) {
3716
+ setError(caught instanceof Error ? caught.message : "N\xE3o conseguimos sincronizar agora.");
3717
+ } finally {
3718
+ setBusy(null);
3719
+ }
3720
+ };
3721
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
3722
+ /* @__PURE__ */ jsxs("div", { children: [
3723
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-foreground", children: "Cat\xE1logo" }),
3724
+ /* @__PURE__ */ jsx("p", { className: "mt-0.5 text-xs text-muted-foreground", children: "O link de pagamento \xE9 aberto na p\xE1gina da AbacatePay, e ela cobra por produtos que precisam existir l\xE1. Envie os seus antes de mandar um link para um cliente." })
3725
+ ] }),
3726
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-2", children: [
3727
+ /* @__PURE__ */ jsx(
3728
+ "button",
3729
+ {
3730
+ type: "button",
3731
+ onClick: () => {
3732
+ void run("push");
3733
+ },
3734
+ disabled: busy !== null,
3735
+ className: "inline-flex min-h-9 items-center rounded-md bg-primary px-3 text-xs font-medium text-primary-foreground transition hover:opacity-90 disabled:opacity-60",
3736
+ children: busy === "push" ? "Enviando\u2026" : "Enviar produtos"
3737
+ }
3738
+ ),
3739
+ /* @__PURE__ */ jsx(
3740
+ "button",
3741
+ {
3742
+ type: "button",
3743
+ onClick: () => {
3744
+ void run("reconcile");
3745
+ },
3746
+ disabled: busy !== null,
3747
+ className: "inline-flex min-h-9 items-center rounded-md border px-3 text-xs font-medium transition hover:bg-muted/50 disabled:opacity-60",
3748
+ children: busy === "reconcile" ? "Conferindo\u2026" : "Conferir pre\xE7os"
3749
+ }
3750
+ )
3751
+ ] }),
3752
+ error && /* @__PURE__ */ jsx("p", { className: "text-xs text-destructive", children: error }),
3753
+ result && /* @__PURE__ */ jsxs("div", { className: "space-y-1.5 rounded-md border bg-muted/30 p-3 text-xs", children: [
3754
+ /* @__PURE__ */ jsxs("p", { className: "text-foreground", children: [
3755
+ result.pushed > 0 ? `${result.pushed} produto${result.pushed > 1 ? "s" : ""} enviado${result.pushed > 1 ? "s" : ""}.` : "Nenhum produto novo para enviar.",
3756
+ " ",
3757
+ /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground", children: [
3758
+ result.unchanged,
3759
+ " j\xE1 ",
3760
+ result.unchanged === 1 ? "estava" : "estavam",
3761
+ " l\xE1, de ",
3762
+ result.total,
3763
+ "."
3764
+ ] })
3765
+ ] }),
3766
+ result.hasMore && /* @__PURE__ */ jsx("p", { className: "text-muted-foreground", children: "Ainda h\xE1 produtos na fila \u2014 clique novamente para continuar." }),
3767
+ result.withoutImage?.length > 0 && /* @__PURE__ */ jsxs("p", { className: "text-muted-foreground", children: [
3768
+ result.withoutImage.length,
3769
+ " produto",
3770
+ result.withoutImage.length > 1 ? "s foram enviados" : " foi enviado",
3771
+ " sem foto \u2014 a AbacatePay recusou a imagem. O produto vende normalmente; s\xF3 aparece sem imagem na p\xE1gina dela."
3772
+ ] }),
3773
+ result.skipped?.length > 0 && /* @__PURE__ */ jsxs("p", { className: "text-muted-foreground", children: [
3774
+ result.skipped.length,
3775
+ " ignorado",
3776
+ result.skipped.length > 1 ? "s" : "",
3777
+ " por estar sem nome ou com pre\xE7o zerado."
3778
+ ] }),
3779
+ result.failed?.length > 0 && /* @__PURE__ */ jsxs("div", { className: "text-destructive", children: [
3780
+ /* @__PURE__ */ jsxs("p", { children: [
3781
+ result.failed.length,
3782
+ " recusado",
3783
+ result.failed.length > 1 ? "s" : "",
3784
+ " pela AbacatePay:"
3785
+ ] }),
3786
+ /* @__PURE__ */ jsx("ul", { className: "mt-0.5 list-disc pl-4", children: result.failed.slice(0, 3).map((f) => /* @__PURE__ */ jsx("li", { children: f.error }, f.productId)) })
3787
+ ] }),
3788
+ result.drifted && result.drifted.length > 0 && /* @__PURE__ */ jsxs("p", { className: "text-amber-600 dark:text-amber-500", children: [
3789
+ result.drifted.length,
3790
+ " produto",
3791
+ result.drifted.length > 1 ? "s mudaram" : " mudou",
3792
+ " de pre\xE7o aqui depois de enviado. A AbacatePay n\xE3o permite alterar um produto j\xE1 criado, ent\xE3o o link ainda cobra o pre\xE7o antigo \u2014 refa\xE7a o produto l\xE1 se o novo valor precisa valer."
3793
+ ] })
3794
+ ] })
3795
+ ] });
3796
+ }
3668
3797
  function AbacatePayExtraPanel() {
3669
3798
  const [connected, setConnected] = useState(false);
3670
3799
  const [loaded, setLoaded] = useState(false);
@@ -3681,17 +3810,20 @@ function AbacatePayExtraPanel() {
3681
3810
  };
3682
3811
  }, []);
3683
3812
  if (!loaded || !connected) return null;
3684
- return /* @__PURE__ */ jsxs("div", { className: "space-y-2 border-t pt-4 text-xs text-muted-foreground", children: [
3685
- /* @__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. Cart\xE3o vai para a p\xE1gina segura da AbacatePay e volta com o resultado \u2014 assim nenhum n\xFAmero de cart\xE3o passa pelo seu site." }),
3686
- /* @__PURE__ */ jsx("p", { children: "Quem decide se a cobran\xE7a \xE9 de verdade \xE9 a chave. Uma chave de teste simula o pagamento e n\xE3o movimenta dinheiro; a de produ\xE7\xE3o cobra o cliente. \xC9 a mesma tela para as duas, ent\xE3o confira no painel da AbacatePay qual chave voc\xEA colou aqui." }),
3687
- /* @__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.' })
3813
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-4 border-t pt-4", children: [
3814
+ /* @__PURE__ */ jsx(CatalogueSync, {}),
3815
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2 text-xs text-muted-foreground", children: [
3816
+ /* @__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. Cart\xE3o de cr\xE9dito a AbacatePay encerrou para contas novas \u2014 para vender no cart\xE3o, conecte outro provedor em Pagamentos." }),
3817
+ /* @__PURE__ */ jsx("p", { children: "Quem decide se a cobran\xE7a \xE9 de verdade \xE9 a chave. Uma chave de teste simula o pagamento e n\xE3o movimenta dinheiro; a de produ\xE7\xE3o cobra o cliente. \xC9 a mesma tela para as duas, ent\xE3o confira no painel da AbacatePay qual chave voc\xEA colou aqui." }),
3818
+ /* @__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.' })
3819
+ ] })
3688
3820
  ] });
3689
3821
  }
3690
3822
  var abacatePayConnectorDef = {
3691
3823
  id: ABACATEPAY_CONNECTOR_ID,
3692
3824
  hostPluginId: "shop",
3693
3825
  name: "AbacatePay",
3694
- description: "Pix, boleto e cart\xE3o com uma chave s\xF3, sem contrato de adquirente e sem tirar o cliente da loja.",
3826
+ description: "Pix e boleto com uma chave s\xF3, sem contrato de adquirente e sem tirar o cliente da loja.",
3695
3827
  icon: "CreditCard",
3696
3828
  category: "payments",
3697
3829
  // One pasted key, no consent screen: AbacatePay has no OAuth and no app to
@@ -3740,7 +3872,7 @@ var abacatePayConnectorDef = {
3740
3872
  invokedAt: [
3741
3873
  {
3742
3874
  key: "shop.checkout.session_open",
3743
- label: "O cliente finaliza a compra e a cobran\xE7a \xE9 aberta na AbacatePay (Pix, boleto ou cart\xE3o)",
3875
+ label: "O cliente finaliza a compra e a cobran\xE7a \xE9 aberta na AbacatePay (Pix ou boleto)",
3744
3876
  touches: ["order", "payment"]
3745
3877
  },
3746
3878
  {
@@ -3928,26 +4060,80 @@ var VINDI_ENVIRONMENT_SETTING = {
3928
4060
  ],
3929
4061
  requiredToOperate: true
3930
4062
  };
4063
+ var VINDI_CARD_METHOD = "3";
3931
4064
  var VINDI_SETTINGS = [
3932
4065
  VINDI_ENVIRONMENT_SETTING,
3933
4066
  {
3934
4067
  key: "methods",
3935
4068
  label: "Formas de pagamento",
3936
- description: "O que o cliente pode escolher no checkout.",
4069
+ description: "O que o cliente pode escolher. O cart\xE3o \xE9 pago na p\xE1gina segura da Vindi.",
3937
4070
  type: "multi-select",
3938
4071
  options: [
3939
4072
  { value: "27", label: "Pix" },
3940
4073
  { value: "28", label: "Bolepix (boleto com QR)" },
3941
- { value: "6", label: "Boleto" }
4074
+ { value: "6", label: "Boleto" },
4075
+ { value: VINDI_CARD_METHOD, label: "Cart\xE3o de cr\xE9dito (p\xE1gina da Vindi)" }
3942
4076
  ],
3943
4077
  requiredToOperate: true
4078
+ },
4079
+ /**
4080
+ * The seller's postcode, which Vindi's cart endpoint refuses to open without.
4081
+ *
4082
+ * NOT `requiredToOperate` in the static list — a store selling Pix has never
4083
+ * needed it, and turning it into a requirement would put every working
4084
+ * connection into "falta configurar" the day this version ships. It becomes
4085
+ * required the moment card is enabled: see `requiredSettingsFor`.
4086
+ */
4087
+ {
4088
+ key: "sellerPostalCode",
4089
+ label: "CEP da loja",
4090
+ description: "De onde a loja vende. A Vindi exige esse dado para abrir a p\xE1gina de cart\xE3o.",
4091
+ type: "text",
4092
+ placeholder: "01310-100",
4093
+ pattern: "\\d{5}-?\\d{3}",
4094
+ patternHint: "Oito d\xEDgitos, com ou sem h\xEDfen."
4095
+ },
4096
+ /**
4097
+ * How many instalments Vindi's page may offer.
4098
+ *
4099
+ * A `select` rather than free text because it is a number with a ceiling the
4100
+ * merchant does not know: Vindi refuses a split above what the account was
4101
+ * accredited for, and a typed "24" would be a checkout that fails at the last
4102
+ * click. The default is 1 — a store that never thought about instalments
4103
+ * should not discover it is financing purchases.
4104
+ */
4105
+ {
4106
+ key: "maxSplit",
4107
+ label: "Parcelas no cart\xE3o",
4108
+ description: "Em quantas vezes o cliente pode dividir. Depende do que a Vindi liberou para a conta.",
4109
+ type: "select",
4110
+ options: [
4111
+ { value: "1", label: "\xC0 vista" },
4112
+ { value: "2", label: "At\xE9 2x" },
4113
+ { value: "3", label: "At\xE9 3x" },
4114
+ { value: "6", label: "At\xE9 6x" },
4115
+ { value: "10", label: "At\xE9 10x" },
4116
+ { value: "12", label: "At\xE9 12x" }
4117
+ ],
4118
+ defaultValue: "1"
3944
4119
  }
3945
4120
  ];
4121
+ function requiredSettingsFor(settings) {
4122
+ const raw = settings?.methods;
4123
+ const list = Array.isArray(raw) ? raw : String(raw ?? "").split(",");
4124
+ const hasCard = list.map((v) => String(v).trim()).includes(VINDI_CARD_METHOD);
4125
+ return VINDI_SETTINGS.map((setting) => setting.key === "sellerPostalCode" && hasCard ? { ...setting, requiredToOperate: true } : setting);
4126
+ }
3946
4127
  var store5 = createConnectionStore(VINDI_CONNECTOR_ID, {
3947
4128
  fields: VINDI_FIELDS,
3948
4129
  settings: VINDI_SETTINGS
3949
4130
  });
3950
- var METHOD_LABELS4 = { "27": "Pix", "28": "Bolepix", "6": "Boleto" };
4131
+ var METHOD_LABELS4 = {
4132
+ "27": "Pix",
4133
+ "28": "Bolepix",
4134
+ "6": "Boleto",
4135
+ [VINDI_CARD_METHOD]: "Cart\xE3o"
4136
+ };
3951
4137
  var ENVIRONMENT_LABELS = { sandbox: "Sandbox", production: "Produ\xE7\xE3o" };
3952
4138
  function connectionDetail(settings) {
3953
4139
  const environment = ENVIRONMENT_LABELS[String(settings?.environment ?? "")];
@@ -3974,7 +4160,12 @@ function VindiExtraPanel() {
3974
4160
  }, []);
3975
4161
  if (!loaded) return null;
3976
4162
  return /* @__PURE__ */ jsx("div", { className: "space-y-2 border-t pt-4 text-xs text-muted-foreground", children: connected ? /* @__PURE__ */ jsxs(Fragment, { children: [
3977
- /* @__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. Cart\xE3o ainda n\xE3o \u2014 ele vai chegar pela p\xE1gina segura da Vindi, para que nenhum n\xFAmero de cart\xE3o passe pelo seu site." }),
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
+ ] }),
3978
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." }),
3979
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.' })
3980
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." }) });
@@ -3983,7 +4174,7 @@ var vindiConnectorDef = {
3983
4174
  id: VINDI_CONNECTOR_ID,
3984
4175
  hostPluginId: "shop",
3985
4176
  name: "Vindi",
3986
- description: "Pix e boleto pela Vindi. A loja recebe na conta dela, com o cliente pagando sem sair da p\xE1gina.",
4177
+ description: "Pix, boleto e cart\xE3o pela Vindi. A loja recebe na conta dela; o cart\xE3o \xE9 digitado na p\xE1gina segura da Vindi.",
3987
4178
  icon: "CreditCard",
3988
4179
  // Declared rather than borrowed from the catalogue, because the catalogue row
3989
4180
  // is a change in another repo that has to be seeded before it exists anywhere:
@@ -4021,7 +4212,7 @@ var vindiConnectorDef = {
4021
4212
  kind: "data",
4022
4213
  resource: "payment",
4023
4214
  actions: ["write"],
4024
- reason: "Registrar aqui a cobran\xE7a aberta e a confirma\xE7\xE3o do pagamento. Dados do cart\xE3o n\xE3o passam por este app.",
4215
+ reason: "Registrar aqui a cobran\xE7a aberta e a confirma\xE7\xE3o do pagamento. Dados do cart\xE3o n\xE3o passam por este app \u2014 s\xE3o digitados na p\xE1gina da Vindi.",
4025
4216
  sensitive: true
4026
4217
  }
4027
4218
  ],
@@ -4035,7 +4226,7 @@ var vindiConnectorDef = {
4035
4226
  invokedAt: [
4036
4227
  {
4037
4228
  key: "shop.checkout.session_open",
4038
- label: "O cliente finaliza a compra e a cobran\xE7a \xE9 aberta na Vindi (Pix ou boleto)",
4229
+ label: "O cliente finaliza a compra e a cobran\xE7a \xE9 aberta na Vindi (Pix, boleto ou a p\xE1gina de cart\xE3o)",
4039
4230
  touches: ["order", "payment"]
4040
4231
  },
4041
4232
  {
@@ -4054,7 +4245,10 @@ var vindiConnectorDef = {
4054
4245
  connected: !!connection?.active,
4055
4246
  detail: connectionDetail(connection?.settings),
4056
4247
  plane: "request",
4057
- ...settingsStatus(VINDI_SETTINGS, connection)
4248
+ // Judged against what THIS connection needs, not against a constant: a
4249
+ // store that ticked card owes a postcode, and one that did not owes
4250
+ // nothing. See `requiredSettingsFor`.
4251
+ ...settingsStatus(requiredSettingsFor(connection?.settings), connection)
4058
4252
  }),
4059
4253
  { store: () => store5 }
4060
4254
  ),
@@ -4582,116 +4776,138 @@ function MediaDetail({
4582
4776
  document.addEventListener("keydown", onKey);
4583
4777
  return () => document.removeEventListener("keydown", onKey);
4584
4778
  }, [onNavigate]);
4585
- return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
4586
- /* @__PURE__ */ jsx(PageHeader, { title: file.name, parentLabel: "Conte\xFAdo", onBack }),
4587
- position.total > 1 && /* @__PURE__ */ jsx("div", { className: "flex items-center justify-end gap-3", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 text-xs text-muted-foreground", children: [
4588
- /* @__PURE__ */ jsx(
4589
- "button",
4590
- {
4591
- type: "button",
4592
- "aria-label": "Arquivo anterior",
4593
- disabled: position.index === 0,
4594
- onClick: () => onNavigate(-1),
4595
- className: "rounded-md border p-1 transition hover:bg-muted disabled:opacity-40",
4596
- children: /* @__PURE__ */ jsx(ChevronLeft, { className: "h-4 w-4" })
4597
- }
4598
- ),
4599
- /* @__PURE__ */ jsxs("span", { className: "px-1 tabular-nums", children: [
4600
- position.index + 1,
4601
- " de ",
4602
- position.total
4603
- ] }),
4604
- /* @__PURE__ */ jsx(
4605
- "button",
4606
- {
4607
- type: "button",
4608
- "aria-label": "Pr\xF3ximo arquivo",
4609
- disabled: position.index === position.total - 1,
4610
- onClick: () => onNavigate(1),
4611
- className: "rounded-md border p-1 transition hover:bg-muted disabled:opacity-40",
4612
- children: /* @__PURE__ */ jsx(ChevronRight, { className: "h-4 w-4" })
4613
- }
4614
- )
4615
- ] }) }),
4616
- /* @__PURE__ */ jsxs("div", { className: "fz-split", children: [
4617
- /* @__PURE__ */ jsx(Card, { className: "flex min-h-[320px] items-center justify-center overflow-hidden bg-muted p-2", children: file.kind === "image" ? /* @__PURE__ */ jsx(
4618
- "img",
4619
- {
4620
- src: file.url,
4621
- alt: file.name,
4622
- className: "max-h-[70vh] w-auto max-w-full object-contain",
4623
- onLoad: (e) => {
4624
- const img = e.currentTarget;
4625
- if (img.naturalWidth) onMeasured({ width: img.naturalWidth, height: img.naturalHeight });
4779
+ return (
4780
+ // `fz-container` é o ponto de MEDIÇÃO: sem ele a container query de
4781
+ // `fz-split` nunca casa e as duas colunas ficam empilhadas para sempre,
4782
+ // por mais larga que a janela esteja.
4783
+ /* @__PURE__ */ jsxs("div", { className: "fz-container space-y-4", children: [
4784
+ /* @__PURE__ */ jsx(PageHeader, { title: file.name, parentLabel: "Conte\xFAdo", onBack }),
4785
+ position.total > 1 && /* @__PURE__ */ jsx("div", { className: "flex items-center justify-end gap-3", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 text-xs text-muted-foreground", children: [
4786
+ /* @__PURE__ */ jsx(
4787
+ "button",
4788
+ {
4789
+ type: "button",
4790
+ "aria-label": "Arquivo anterior",
4791
+ disabled: position.index === 0,
4792
+ onClick: () => onNavigate(-1),
4793
+ className: "rounded-md border p-1 transition hover:bg-muted disabled:opacity-40",
4794
+ children: /* @__PURE__ */ jsx(ChevronLeft, { className: "h-4 w-4" })
4626
4795
  }
4627
- },
4628
- file.url
4629
- ) : /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-2 p-12 text-muted-foreground", children: [
4630
- /* @__PURE__ */ jsx(KindIcon, { kind: file.kind, className: "h-10 w-10" }),
4631
- /* @__PURE__ */ jsx("span", { className: "text-sm", children: "Pr\xE9-visualiza\xE7\xE3o n\xE3o dispon\xEDvel" })
4632
- ] }) }),
4633
- /* @__PURE__ */ jsxs(Card, { className: "space-y-4 p-4", children: [
4634
- /* @__PURE__ */ jsxs("div", { children: [
4635
- /* @__PURE__ */ jsx("p", { className: "break-words font-medium", title: file.name, children: file.name }),
4636
- /* @__PURE__ */ jsx("p", { className: "mt-0.5 text-xs text-muted-foreground", children: /* @__PURE__ */ jsx(FileMeta, { file, dimensions }) }),
4637
- isLowResolution(file, dimensions) && /* @__PURE__ */ jsxs("p", { className: "mt-2 rounded-md bg-warning/10 px-2 py-1.5 text-xs font-medium text-warning", children: [
4638
- "Baixa resolu\xE7\xE3o \u2014 no zoom da loja essa foto fica borrada. Troque por uma de pelo menos ",
4639
- LOW_RESOLUTION_PX,
4640
- "px no lado maior."
4641
- ] })
4642
- ] }),
4643
- /* @__PURE__ */ jsxs("div", { children: [
4644
- /* @__PURE__ */ jsx("p", { className: "text-[11px] uppercase tracking-wide text-muted-foreground", children: "Onde \xE9 usada" }),
4645
- file.reference ? /* @__PURE__ */ jsx(RecordLink, { reference: file.reference, className: "mt-1 text-sm" }) : /* @__PURE__ */ jsx("p", { className: "mt-1 text-sm text-muted-foreground", children: "Biblioteca \u2014 este arquivo n\xE3o est\xE1 preso a nenhum registro." }),
4646
- file.reference?.isCover && /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: "\xC9 a capa: \xE9 ela que aparece no cat\xE1logo, no carrinho e no pedido." })
4796
+ ),
4797
+ /* @__PURE__ */ jsxs("span", { className: "px-1 tabular-nums", children: [
4798
+ position.index + 1,
4799
+ " de ",
4800
+ position.total
4647
4801
  ] }),
4648
- /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
4649
- /* @__PURE__ */ jsxs(Button, { disabled: true, className: "w-full", title: "Em breve", children: [
4650
- /* @__PURE__ */ jsx(Sparkles, { className: "mr-2 h-4 w-4" }),
4651
- "Melhorar com IA"
4802
+ /* @__PURE__ */ jsx(
4803
+ "button",
4804
+ {
4805
+ type: "button",
4806
+ "aria-label": "Pr\xF3ximo arquivo",
4807
+ disabled: position.index === position.total - 1,
4808
+ onClick: () => onNavigate(1),
4809
+ className: "rounded-md border p-1 transition hover:bg-muted disabled:opacity-40",
4810
+ children: /* @__PURE__ */ jsx(ChevronRight, { className: "h-4 w-4" })
4811
+ }
4812
+ )
4813
+ ] }) }),
4814
+ /* @__PURE__ */ jsxs("div", { className: "fz-split", children: [
4815
+ /* @__PURE__ */ jsx(Card, { className: "flex min-h-[320px] items-center justify-center overflow-hidden bg-muted p-2", children: file.kind === "image" ? /* @__PURE__ */ jsx(
4816
+ "img",
4817
+ {
4818
+ src: file.url,
4819
+ alt: file.name,
4820
+ className: "max-h-[70vh] w-auto max-w-full object-contain",
4821
+ onLoad: (e) => {
4822
+ const img = e.currentTarget;
4823
+ if (img.naturalWidth) onMeasured({ width: img.naturalWidth, height: img.naturalHeight });
4824
+ }
4825
+ },
4826
+ file.url
4827
+ ) : /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-2 p-12 text-muted-foreground", children: [
4828
+ /* @__PURE__ */ jsx(KindIcon, { kind: file.kind, className: "h-10 w-10" }),
4829
+ /* @__PURE__ */ jsx("span", { className: "text-sm", children: "Pr\xE9-visualiza\xE7\xE3o n\xE3o dispon\xEDvel" })
4830
+ ] }) }),
4831
+ /* @__PURE__ */ jsxs(Card, { className: "space-y-4 p-4", children: [
4832
+ /* @__PURE__ */ jsxs("div", { children: [
4833
+ /* @__PURE__ */ jsx("p", { className: "break-words font-medium", title: file.name, children: file.name }),
4834
+ /* @__PURE__ */ jsx("p", { className: "mt-0.5 text-xs text-muted-foreground", children: /* @__PURE__ */ jsx(FileMeta, { file, dimensions }) }),
4835
+ isLowResolution(file, dimensions) && /* @__PURE__ */ jsxs("p", { className: "mt-2 rounded-md bg-warning/10 px-2 py-1.5 text-xs font-medium text-warning", children: [
4836
+ "Baixa resolu\xE7\xE3o \u2014 no zoom da loja essa foto fica borrada. Troque por uma de pelo menos ",
4837
+ LOW_RESOLUTION_PX,
4838
+ "px no lado maior."
4839
+ ] })
4652
4840
  ] }),
4653
- /* @__PURE__ */ jsx(Button, { asChild: true, variant: "outline", className: "w-full", children: /* @__PURE__ */ jsxs("a", { href: downloadUrlOf(file), download: file.name, children: [
4654
- /* @__PURE__ */ jsx(Download, { className: "mr-2 h-4 w-4" }),
4655
- "Baixar"
4656
- ] }) }),
4657
- onReplace && /* @__PURE__ */ jsxs(Fragment, { children: [
4658
- /* @__PURE__ */ jsx(
4659
- "input",
4660
- {
4661
- ref: replaceRef,
4662
- type: "file",
4663
- accept: file.kind === "image" ? "image/*" : void 0,
4664
- className: "hidden",
4665
- onChange: (e) => {
4666
- const picked = e.target.files?.[0];
4667
- e.target.value = "";
4668
- if (picked) onReplace(picked);
4669
- }
4670
- }
4671
- ),
4672
- /* @__PURE__ */ jsxs(
4673
- Button,
4841
+ /* @__PURE__ */ jsxs("div", { children: [
4842
+ /* @__PURE__ */ jsx("p", { className: "text-[11px] uppercase tracking-wide text-muted-foreground", children: "Onde \xE9 usada" }),
4843
+ file.reference ? /* @__PURE__ */ jsx(RecordLink, { reference: file.reference, className: "mt-1 text-sm" }) : /* @__PURE__ */ jsx("p", { className: "mt-1 text-sm text-muted-foreground", children: "Biblioteca \u2014 este arquivo n\xE3o est\xE1 preso a nenhum registro." }),
4844
+ file.reference?.isCover && /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: "\xC9 a capa: \xE9 ela que aparece no cat\xE1logo, no carrinho e no pedido." })
4845
+ ] }),
4846
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
4847
+ file.kind === "image" && /* @__PURE__ */ jsx(
4848
+ AskAIButton,
4674
4849
  {
4675
- variant: "outline",
4676
4850
  className: "w-full",
4677
- disabled: replacing,
4678
- onClick: () => replaceRef.current?.click(),
4679
- children: [
4680
- /* @__PURE__ */ jsx(RefreshCw, { className: `mr-2 h-4 w-4 ${replacing ? "animate-spin" : ""}` }),
4681
- replacing ? "Trocando\u2026" : "Trocar arquivo"
4682
- ]
4851
+ size: "lg",
4852
+ label: "Melhorar com IA",
4853
+ media: { url: file.url, name: file.name },
4854
+ draft: "Melhora essa imagem",
4855
+ hint: "o que voc\xEA quer mudar nessa foto",
4856
+ intro: {
4857
+ persona: "Est\xFAdio de imagem",
4858
+ pitch: `Vamos melhorar ${file.name}.`,
4859
+ description: "Posso limpar o fundo, corrigir luz e cor, e gerar uma vers\xE3o em resolu\xE7\xE3o maior para o cat\xE1logo.",
4860
+ suggestions: [
4861
+ { label: "Fundo branco", prompt: "Remove o fundo e deixa branco" },
4862
+ { label: "Mais n\xEDtida", prompt: "Aumenta a resolu\xE7\xE3o e a nitidez" },
4863
+ { label: "Corrigir a luz", prompt: "Corrige a ilumina\xE7\xE3o e as cores" }
4864
+ ]
4865
+ }
4683
4866
  }
4684
4867
  ),
4685
- file.reference && /* @__PURE__ */ jsx("p", { className: "text-[11px] text-muted-foreground", children: "A troca mant\xE9m a posi\xE7\xE3o da foto na galeria do produto." })
4686
- ] }),
4687
- onDelete && /* @__PURE__ */ jsxs(Button, { variant: "ghost", className: "w-full text-destructive hover:text-destructive", onClick: onDelete, children: [
4688
- /* @__PURE__ */ jsx(Trash2, { className: "mr-2 h-4 w-4" }),
4689
- "Excluir"
4868
+ /* @__PURE__ */ jsx(Button, { asChild: true, variant: "outline", className: "w-full", children: /* @__PURE__ */ jsxs("a", { href: downloadUrlOf(file), download: file.name, children: [
4869
+ /* @__PURE__ */ jsx(Download, { className: "mr-2 h-4 w-4" }),
4870
+ "Baixar"
4871
+ ] }) }),
4872
+ onReplace && /* @__PURE__ */ jsxs(Fragment, { children: [
4873
+ /* @__PURE__ */ jsx(
4874
+ "input",
4875
+ {
4876
+ ref: replaceRef,
4877
+ type: "file",
4878
+ accept: file.kind === "image" ? "image/*" : void 0,
4879
+ className: "hidden",
4880
+ onChange: (e) => {
4881
+ const picked = e.target.files?.[0];
4882
+ e.target.value = "";
4883
+ if (picked) onReplace(picked);
4884
+ }
4885
+ }
4886
+ ),
4887
+ /* @__PURE__ */ jsxs(
4888
+ Button,
4889
+ {
4890
+ variant: "outline",
4891
+ className: "w-full",
4892
+ disabled: replacing,
4893
+ onClick: () => replaceRef.current?.click(),
4894
+ children: [
4895
+ /* @__PURE__ */ jsx(RefreshCw, { className: `mr-2 h-4 w-4 ${replacing ? "animate-spin" : ""}` }),
4896
+ replacing ? "Trocando\u2026" : "Trocar arquivo"
4897
+ ]
4898
+ }
4899
+ ),
4900
+ file.reference && /* @__PURE__ */ jsx("p", { className: "text-[11px] text-muted-foreground", children: "A troca mant\xE9m a posi\xE7\xE3o da foto na galeria do produto." })
4901
+ ] }),
4902
+ onDelete && /* @__PURE__ */ jsxs(Button, { variant: "ghost", className: "w-full text-destructive hover:text-destructive", onClick: onDelete, children: [
4903
+ /* @__PURE__ */ jsx(Trash2, { className: "mr-2 h-4 w-4" }),
4904
+ "Excluir"
4905
+ ] })
4690
4906
  ] })
4691
4907
  ] })
4692
4908
  ] })
4693
4909
  ] })
4694
- ] });
4910
+ );
4695
4911
  }
4696
4912
  function RecordLink({ reference, className = "" }) {
4697
4913
  return /* @__PURE__ */ jsxs(
@@ -4730,16 +4946,16 @@ var BLING_SAMPLE_SIZE = 100;
4730
4946
  var BLING_SIGNATURE_HEADER = "x-bling-signature-256";
4731
4947
  var BLING_WEBHOOK_DEADLINE_MS = 5e3;
4732
4948
  var BLING_FUNCTION = "bling-sync";
4733
- function client() {
4949
+ function client2() {
4734
4950
  const supabase = getSupabaseClientOptional();
4735
4951
  if (!supabase) throw new Error("Sem conex\xE3o com o banco para falar com o Bling.");
4736
4952
  return supabase;
4737
4953
  }
4738
- async function invoke(body) {
4954
+ async function invoke2(body) {
4739
4955
  const tenantId = getShopTenantId();
4740
4956
  if (!tenantId) throw new Error("Sem loja selecionada para sincronizar com o Bling.");
4741
4957
  const runtimeToken = await connectorRuntimeToken({ tenantId });
4742
- const { data, error } = await client().functions.invoke(BLING_FUNCTION, {
4958
+ const { data, error } = await client2().functions.invoke(BLING_FUNCTION, {
4743
4959
  body: { ...body, tenantId },
4744
4960
  headers: { [CONNECTOR_RUNTIME_TOKEN_HEADER]: runtimeToken }
4745
4961
  });
@@ -4747,19 +4963,19 @@ async function invoke(body) {
4747
4963
  return data;
4748
4964
  }
4749
4965
  function pullCatalogue(mode) {
4750
- return invoke({ action: "pull_products", ...{ mode } });
4966
+ return invoke2({ action: "pull_products", ...{ mode } });
4751
4967
  }
4752
- function pushCatalogue() {
4753
- return invoke({ action: "push_products" });
4968
+ function pushCatalogue2() {
4969
+ return invoke2({ action: "push_products" });
4754
4970
  }
4755
4971
  function pushCustomers() {
4756
- return invoke({ action: "push_customers" });
4972
+ return invoke2({ action: "push_customers" });
4757
4973
  }
4758
4974
  function pushOrders() {
4759
- return invoke({ action: "push_orders" });
4975
+ return invoke2({ action: "push_orders" });
4760
4976
  }
4761
4977
  function pushStock() {
4762
- return invoke({ action: "push_stock" });
4978
+ return invoke2({ action: "push_stock" });
4763
4979
  }
4764
4980
  async function countRows(table, apply) {
4765
4981
  const tenantId = getShopTenantId();
@@ -4836,7 +5052,7 @@ async function listOrderDeliveries(limit = 10) {
4836
5052
  });
4837
5053
  }
4838
5054
  function reconcile() {
4839
- return invoke({ action: "reconcile" });
5055
+ return invoke2({ action: "reconcile" });
4840
5056
  }
4841
5057
 
4842
5058
  // src/integrations/bling/connectorDef.tsx
@@ -5062,7 +5278,7 @@ var blingConnectorDef = {
5062
5278
  },
5063
5279
  pending: pendingProducts,
5064
5280
  run: async () => {
5065
- const result = await pushCatalogue();
5281
+ const result = await pushCatalogue2();
5066
5282
  return { message: result.message, status: result.status };
5067
5283
  }
5068
5284
  },
@@ -5275,6 +5491,311 @@ async function acceptWebhook(request, options) {
5275
5491
  }
5276
5492
  return { status: 200, queued: true };
5277
5493
  }
5494
+
5495
+ // src/integrations/neochef/limits.ts
5496
+ var NEOCHEF_CONNECTOR_ID = "neochef";
5497
+ var NEOCHEF_RATE_LIMIT = { perSecond: 5, perDay: 5e4 };
5498
+ var NEOCHEF_PAGE_SIZE = 100;
5499
+ var NEOCHEF_SAMPLE_SIZE = 100;
5500
+ var NEOCHEF_CURSOR_OVERLAP_MS = 6e4;
5501
+ var NEOCHEF_TOKEN_LEAD_MS = 10 * 6e4;
5502
+
5503
+ // src/integrations/neochef/mark.ts
5504
+ var NEOCHEF_MARK = "data:image/png;base64,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";
5505
+ var NEOCHEF_FUNCTION = "neochef-sync";
5506
+ function client3() {
5507
+ const supabase = getSupabaseClientOptional();
5508
+ if (!supabase) throw new Error("Sem conex\xE3o com o banco para falar com o NeoChef.");
5509
+ return supabase;
5510
+ }
5511
+ async function invoke3(body) {
5512
+ const tenantId = getShopTenantId();
5513
+ if (!tenantId) throw new Error("Sem loja selecionada para sincronizar com o NeoChef.");
5514
+ const runtimeToken = await connectorRuntimeToken({ tenantId });
5515
+ const { data, error } = await client3().functions.invoke(NEOCHEF_FUNCTION, {
5516
+ body: { ...body, tenantId },
5517
+ headers: { [CONNECTOR_RUNTIME_TOKEN_HEADER]: runtimeToken }
5518
+ });
5519
+ if (error) throw new Error(error.message);
5520
+ return data;
5521
+ }
5522
+ function testNeoChef() {
5523
+ return invoke3({ action: "test" });
5524
+ }
5525
+ function pullCatalogue2(mode) {
5526
+ return invoke3({ action: "pull_products", ...mode ? { mode } : {} });
5527
+ }
5528
+ function pushOrders2() {
5529
+ return invoke3({ action: "push_orders" });
5530
+ }
5531
+ function reconcile2() {
5532
+ return invoke3({ action: "reconcile" });
5533
+ }
5534
+ async function pendingOrders2() {
5535
+ const tenantId = getShopTenantId();
5536
+ const supabase = getSupabaseClientOptional();
5537
+ if (!tenantId || !supabase) return null;
5538
+ 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
+ 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");
5541
+ if (error) return null;
5542
+ return (data ?? []).filter((row) => !already.has(row.id)).length;
5543
+ }
5544
+ async function listOrderDeliveries2(limit = 10) {
5545
+ const tenantId = getShopTenantId();
5546
+ const supabase = getSupabaseClientOptional();
5547
+ 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);
5549
+ const rows = orders ?? [];
5550
+ if (rows.length === 0) return [];
5551
+ 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));
5552
+ const byOrder = new Map(
5553
+ (filed ?? []).map((row) => [row.order_id, row])
5554
+ );
5555
+ return rows.map((order) => {
5556
+ const subject = order.order_number ? `Pedido ${order.order_number}` : "Pedido";
5557
+ const link = byOrder.get(order.id);
5558
+ if (link) {
5559
+ return {
5560
+ subject,
5561
+ subjectId: order.id,
5562
+ status: "delivered",
5563
+ externalId: link.external_number ?? link.external_id,
5564
+ at: link.synced_at ?? order.paid_at,
5565
+ ...order.customer_name ? { detail: String(order.customer_name) } : {}
5566
+ };
5567
+ }
5568
+ if (String(order.status ?? "") === "cancelled") {
5569
+ return {
5570
+ subject,
5571
+ subjectId: order.id,
5572
+ status: "failed",
5573
+ detail: "Cancelado \u2014 n\xE3o sobe para a cozinha.",
5574
+ at: order.created_at
5575
+ };
5576
+ }
5577
+ return {
5578
+ subject,
5579
+ subjectId: order.id,
5580
+ status: "pending",
5581
+ detail: "Ainda n\xE3o subiu \u2014 vai na pr\xF3xima sincroniza\xE7\xE3o.",
5582
+ at: order.paid_at ?? order.created_at
5583
+ };
5584
+ });
5585
+ }
5586
+
5587
+ // src/integrations/neochef/connectorDef.tsx
5588
+ var neoChefConnectionStore = () => createConnectionStore(NEOCHEF_CONNECTOR_ID, {
5589
+ settingsKeys: ["kitchenId", "backfillFrom", "stockOwner"]
5590
+ });
5591
+ var neoChefConnectorDef = {
5592
+ id: NEOCHEF_CONNECTOR_ID,
5593
+ hostPluginId: "shop",
5594
+ name: "NeoChef",
5595
+ description: "Traz o card\xE1pio, o pre\xE7o e o estoque do NeoChef para a loja e devolve cada pedido pago como pedido na cozinha.",
5596
+ // The first question anyone connecting asks, answered before they connect.
5597
+ howItSyncs: [
5598
+ "De tempos em tempos, sozinho: desce do NeoChef o que mudou l\xE1 e sobe o que estiver pendente daqui.",
5599
+ "A qualquer momento, no bot\xE3o Sincronizar agora.",
5600
+ "Nada sobe duas vezes: cada pedido guarda o n\xFAmero que recebeu na cozinha.",
5601
+ "Produto desativado no NeoChef sai de venda na loja \u2014 nenhum produto \xE9 apagado."
5602
+ ],
5603
+ icon: "ChefHat",
5604
+ // The app's own mark, so the row wears the same face the consent screen does.
5605
+ // `icon` stays as the fallback the mark resolver advances to — see mark.ts.
5606
+ logoUrl: NEOCHEF_MARK,
5607
+ authKind: "oauth",
5608
+ // `erp` and not `ecommerce`: NeoChef is the back office, not the storefront.
5609
+ // Same reading of §2 that moved Bling off the word 'commerce'.
5610
+ category: "erp",
5611
+ // Both ways, and the connector says so before anyone connects: the catalogue
5612
+ // comes in, the paid order goes out.
5613
+ direction: "inbound",
5614
+ planes: ["sync"],
5615
+ docsUrl: "https://fayz.ai/docs/integrations/neochef",
5616
+ /**
5617
+ * The consent names these, entity by entity, on NeoChef's own screen — this
5618
+ * list is what the STORE says it will do with them, which is the other half
5619
+ * of the same sentence.
5620
+ *
5621
+ * `shop_product` is read as well as written because the catalogue is a
5622
+ * MIRROR: `reconcileMirror` has to see what is here before it can decide what
5623
+ * a product missing from a page means.
5624
+ */
5625
+ permissions: [
5626
+ {
5627
+ kind: "data",
5628
+ resource: "shop_product",
5629
+ actions: ["read", "write"],
5630
+ reason: "Trazer do NeoChef o card\xE1pio, o pre\xE7o e o saldo do estoque, comparando com o que j\xE1 est\xE1 na loja para n\xE3o duplicar nem tirar de venda por engano."
5631
+ },
5632
+ {
5633
+ kind: "data",
5634
+ resource: "shop_order",
5635
+ actions: ["read", "write"],
5636
+ // `write` is the LINK, not the order: without it every sync would file the
5637
+ // same sale again.
5638
+ reason: "Ler o pedido pago para lan\xE7\xE1-lo na cozinha, e guardar aqui o n\xFAmero que ele recebeu l\xE1. O conte\xFAdo do pedido n\xE3o \xE9 alterado.",
5639
+ sensitive: true
5640
+ },
5641
+ {
5642
+ kind: "event",
5643
+ resource: "shop.order_paid",
5644
+ actions: ["listen"],
5645
+ reason: "Saber que um pedido foi pago, para mand\xE1-lo para a cozinha."
5646
+ },
5647
+ {
5648
+ kind: "event",
5649
+ resource: "shop.order_cancelled",
5650
+ actions: ["listen"],
5651
+ reason: "Saber que um pedido foi cancelado, para n\xE3o mand\xE1-lo para a cozinha."
5652
+ }
5653
+ ],
5654
+ // Declared, so the scheduler owns the clock. The budget is ours and not the
5655
+ // provider's — see limits.ts.
5656
+ rateLimit: NEOCHEF_RATE_LIMIT,
5657
+ // A connect screen that waits for a whole catalogue is a connect screen nobody
5658
+ // finishes, so the first load answers with a page and the backfill follows.
5659
+ firstLoad: { sampleSize: NEOCHEF_SAMPLE_SIZE, samplePages: 1, stream: "products" },
5660
+ // With underscores. `shop.order.paid` is not a key anything publishes — the
5661
+ // shop builds its facts as `shop.${def.event}` over SHOP_EMAIL_EVENTS.
5662
+ subscribesTo: ["shop.order_paid", "shop.order_cancelled"],
5663
+ managedAutomations: [
5664
+ { eventKey: "shop.order_paid", label: "Mandar o pedido pago para a cozinha do NeoChef" },
5665
+ { eventKey: "shop.order_cancelled", label: "Parar de mandar para a cozinha um pedido cancelado" }
5666
+ ],
5667
+ // What HAPPENS, not what is declared. Nothing here names a moment that has no
5668
+ // code behind it.
5669
+ invokedAt: [
5670
+ {
5671
+ key: "shop.order.push",
5672
+ label: "O pedido pago sobe como pedido na cozinha",
5673
+ plane: "sync",
5674
+ trigger: "scheduled",
5675
+ touches: ["shop_order"]
5676
+ },
5677
+ {
5678
+ key: "shop.catalogue.pull",
5679
+ label: "O card\xE1pio, o pre\xE7o e o estoque do NeoChef descem para a loja",
5680
+ plane: "sync",
5681
+ trigger: "scheduled",
5682
+ touches: ["shop_product"]
5683
+ }
5684
+ ],
5685
+ // Read from the row the list already batched — one request for N connectors.
5686
+ ...connectionStatus(
5687
+ NEOCHEF_CONNECTOR_ID,
5688
+ (connection) => {
5689
+ if (!connection?.active) return { connected: false };
5690
+ const kitchen = connection.settings?.kitchenId;
5691
+ return {
5692
+ connected: connection.status === "connected",
5693
+ detail: kitchen ? `Cozinha ${kitchen}` : void 0,
5694
+ plane: "sync"
5695
+ };
5696
+ },
5697
+ { store: neoChefConnectionStore }
5698
+ ),
5699
+ // The whole connect step. Declaring `oauth` and no `startOAuth` is what renders
5700
+ // the Conectar button disabled with nothing on screen saying why.
5701
+ startOAuth: fayzConnectorOAuth(NEOCHEF_CONNECTOR_ID),
5702
+ // The other half of the consent: the platform stores the token and the pool
5703
+ // stores "is it on?". With nobody writing the second, the connector answers
5704
+ // "não conectado" to a merchant who has just authorized.
5705
+ saveConnection: async () => {
5706
+ await neoChefConnectionStore().save({ active: true });
5707
+ },
5708
+ // Disconnect FREEZES the mirror. A merchant who unplugs the kitchen still has
5709
+ // a store, and a connector that empties the catalogue on disconnect is one
5710
+ // nobody dares to disconnect.
5711
+ disconnect: async () => {
5712
+ await neoChefConnectionStore().disconnect();
5713
+ },
5714
+ disconnectNote: "Desconectar congela o espelho: nenhum produto \xE9 apagado e o estoque para de ser atualizado.",
5715
+ ...connectionRuns(neoChefConnectionStore),
5716
+ // The managed automation says a paid order goes to the kitchen; this says
5717
+ // whether it arrived.
5718
+ listDeliveries: (options) => listOrderDeliveries2(options?.limit ?? 10),
5719
+ syncActions: [
5720
+ {
5721
+ key: "reconcile",
5722
+ label: "Sincronizar agora",
5723
+ direction: "inbound",
5724
+ // The one that is right on an ordinary day; the rest fold away.
5725
+ primary: true,
5726
+ description: "Traz o que mudou no NeoChef e envia o que est\xE1 pendente daqui \u2014 card\xE1pio, pre\xE7o, estoque e pedidos pagos.",
5727
+ run: async () => {
5728
+ const result = await reconcile2();
5729
+ const legs = Object.values(result.push ?? {}).map((leg) => leg?.message ? String(leg.message) : null).filter(Boolean);
5730
+ return [`${result.pull?.fetched ?? 0} produto(s) lidos do NeoChef`, ...legs].join(" \xB7 ");
5731
+ }
5732
+ },
5733
+ {
5734
+ key: "pull_products",
5735
+ label: "Trazer card\xE1pio do NeoChef",
5736
+ direction: "inbound",
5737
+ description: "Pre\xE7o e estoque do NeoChef passam a valer aqui. Nada \xE9 apagado da loja.",
5738
+ run: async () => {
5739
+ const result = await pullCatalogue2("full");
5740
+ return `${result.fetched} produto(s) lidos, ${result.inserted} criado(s), ${result.updated} atualizado(s)`;
5741
+ }
5742
+ },
5743
+ {
5744
+ key: "push_orders",
5745
+ label: "Enviar pedidos para a cozinha",
5746
+ direction: "outbound",
5747
+ description: "Cada pedido pago vira um pedido na cozinha. Pedido cancelado n\xE3o sobe, e um j\xE1 enviado n\xE3o sobe duas vezes.",
5748
+ pending: pendingOrders2,
5749
+ run: async () => {
5750
+ const result = await pushOrders2();
5751
+ return { message: result.message, status: result.status };
5752
+ }
5753
+ }
5754
+ ]
5755
+ };
5756
+
5757
+ // src/integrations/neochef/mapping.ts
5758
+ function toNumber2(value) {
5759
+ if (value === null || value === void 0 || value === "") return null;
5760
+ const n = typeof value === "number" ? value : Number(String(value).replace(",", "."));
5761
+ return Number.isFinite(n) ? n : null;
5762
+ }
5763
+ function toMirroredNeoChefProduct(raw) {
5764
+ if (!raw || raw.id === null || raw.id === void 0 || raw.id === "") return null;
5765
+ const inactive = raw.is_active === false || String(raw.status ?? "active").toLowerCase() === "archived";
5766
+ return {
5767
+ externalId: String(raw.id),
5768
+ sku: raw.sku ? String(raw.sku) : null,
5769
+ name: String(raw.name ?? "").trim() || `Produto ${raw.id}`,
5770
+ price: toNumber2(raw.price),
5771
+ // The kitchen owns this number. The store mirrors it; it does not decide it.
5772
+ inventoryCount: toNumber2(raw.stock),
5773
+ status: inactive ? "archived" : "active",
5774
+ imageUrl: raw.image_url ? String(raw.image_url) : null,
5775
+ // Unlike Bling, the far side DOES return its change stamp, so the cursor can
5776
+ // advance from the data instead of from the window we asked for.
5777
+ updatedAt: raw.updated_at ? String(raw.updated_at) : null
5778
+ };
5779
+ }
5780
+ function toNeoChefOrder(order) {
5781
+ return {
5782
+ kind: order.pickup === true ? "takeout" : "delivery",
5783
+ // The store's own number, so a human can match the two screens. Falls back
5784
+ // to the id rather than to empty: an order with no reference is unmatchable.
5785
+ reference_number: String(order.referenceNumber ?? order.id),
5786
+ // It arrives paid and confirmed — the store already took the money, and a
5787
+ // kitchen that has to re-confirm it is a kitchen doing the work twice.
5788
+ status: "confirmed",
5789
+ total: toNumber2(order.total),
5790
+ subtotal: toNumber2(order.subtotal),
5791
+ notes: order.notes ? String(order.notes) : null
5792
+ };
5793
+ }
5794
+ function advanceNeoChefCursor(products, overlapMs) {
5795
+ const stamps = products.map((product) => product.updatedAt ? Date.parse(product.updatedAt) : NaN).filter((value) => Number.isFinite(value));
5796
+ if (stamps.length === 0) return null;
5797
+ return new Date(Math.max(...stamps) - overlapMs).toISOString();
5798
+ }
5278
5799
  var ORDERS = "plg_shop_rep_orders";
5279
5800
  var ITEMS = "plg_shop_rep_order_items";
5280
5801
  var CUSTOMERS = "plg_shop_rep_customers";
@@ -6102,7 +6623,7 @@ function buildShopAnalyticsCards() {
6102
6623
  }
6103
6624
 
6104
6625
  // src/index.ts
6105
- var ShopPage = React8.lazy(() => import('./ShopPage-YB5ZWECJ.js').then((m) => ({ default: m.ShopPage })));
6626
+ var ShopPage = React8.lazy(() => import('./ShopPage-VVLOH3RF.js').then((m) => ({ default: m.ShopPage })));
6106
6627
  function createShopPlugin(options) {
6107
6628
  const curr = { code: "BRL", locale: "pt-BR", symbol: "R$", ...options?.currency };
6108
6629
  setShopTenantResolver(() => useOrganizationStore.getState().currentOrg?.id);
@@ -6133,6 +6654,24 @@ function createShopPlugin(options) {
6133
6654
  const ProductsCrudPage = createCrudPage(buildShopProductEntity(curr.code, resolveProvider), {
6134
6655
  dataProvider: createShopProductDataProvider(resolveProvider),
6135
6656
  feature: "shop",
6657
+ // O produto da loja não tem tabela própria — vive atrás do provider de
6658
+ // commerce —, então as primitivas de dado do agente não o alcançam. A porta
6659
+ // funciona porque a lista empresta o próprio store: ver CrudAgentSurface.
6660
+ aiCreate: {
6661
+ hint: "Descreva o produto\u2026",
6662
+ description: "Descreva o produto e eu cadastro.",
6663
+ intro: {
6664
+ role: "operations",
6665
+ persona: "Cadastro de produtos",
6666
+ pitch: "Diga o produto que voc\xEA quer cadastrar.",
6667
+ description: "Nome, pre\xE7o, descri\xE7\xE3o, estoque \u2014 diga o que souber, do jeito que falar, e eu preencho o resto com o que faz sentido. Nasce como rascunho e voc\xEA confere antes de publicar.",
6668
+ suggestions: [
6669
+ { label: "Camiseta preta, R$ 79, estoque 20" },
6670
+ { label: "Cadastre 5 produtos de exemplo para eu testar a loja" },
6671
+ { label: "Um combo com os dois produtos mais vendidos" }
6672
+ ]
6673
+ }
6674
+ },
6136
6675
  stats: (items, total) => [
6137
6676
  { id: "total", label: "Produtos", value: total, icon: Package },
6138
6677
  { id: "active", label: "Ativos", value: items?.filter((p) => p.status === "active").length ?? 0, icon: Eye },
@@ -6397,6 +6936,7 @@ function createShopPlugin(options) {
6397
6936
  const READ = { feature: "shop", action: "read" };
6398
6937
  return {
6399
6938
  id: "shop",
6939
+ defaultAgentRole: "operations",
6400
6940
  name: options?.navLabel ?? "Shop",
6401
6941
  icon: "ShoppingBag",
6402
6942
  version: "1.0.0",
@@ -6534,19 +7074,22 @@ function createShopPlugin(options) {
6534
7074
  // screens (commerce-stack.md §0). Melhor Envio, AppMax and AbacatePay are
6535
7075
  // REQUEST-plane connectors: the buyer is waiting, so each declares a latency
6536
7076
  // budget and, because the carrier and the gateways want opposite answers to
6537
- // the same failure, its own `onFailure`. Bling is the SYNC-plane one —
6538
- // nobody is waiting on it, and what it declares instead is a call budget and
6539
- // a windowed cursor.
7077
+ // the same failure, its own `onFailure`. Bling and NeoChef are the SYNC-plane
7078
+ // ones — nobody is waiting on them, and what they declare instead is a call
7079
+ // budget and a cursor.
6540
7080
  //
6541
7081
  // Two gateways on the shelf is the point: the store picks one, and the
6542
- // second one arriving changed no contract — only how it authorizes.
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.
6543
7085
  connectors: [
6544
7086
  melhorEnvioConnectorDef,
6545
7087
  appmaxConnectorDef,
6546
7088
  abacatePayConnectorDef,
6547
7089
  stripeConnectConnectorDef,
6548
7090
  vindiConnectorDef,
6549
- blingConnectorDef
7091
+ blingConnectorDef,
7092
+ neoChefConnectorDef
6550
7093
  ],
6551
7094
  // What having a store makes plugable — the four above plus the ones still
6552
7095
  // to be built. Carried over from the Fayz plugin page's "Integra com" row,
@@ -6556,6 +7099,6 @@ function createShopPlugin(options) {
6556
7099
  };
6557
7100
  }
6558
7101
 
6559
- 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, STRIPE_CONNECT_CONNECTOR_ID, STRIPE_CONNECT_LATENCY_BUDGET_MS, VINDI_CONNECTOR_ID, VINDI_LATENCY_BUDGET_MS, abacatePayConnectorDef, acceptWebhook, appmaxConnectorDef, blingConnectionStore, blingConnectorDef, buildShopAnalyticsCards, createShopPlugin, firstWindow, melhorEnvioConnectorDef, orphanConnection, pullProducts, routeCallback, sampleProducts, stripeConnectConnectorDef, toMirroredProduct, vindiConnectorDef, windowFor };
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 };
6560
7103
  //# sourceMappingURL=index.js.map
6561
7104
  //# sourceMappingURL=index.js.map