@bacano/sdk 1.0.1 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +342 -7
- package/STOREFRONT_CLARITY.md +140 -0
- package/STOREFRONT_CONSTRUCTION.md +92 -0
- package/STOREFRONT_LEADS.md +130 -0
- package/STOREFRONT_LEGAL.md +66 -0
- package/STOREFRONT_MARKETING.md +286 -0
- package/STOREFRONT_PERFORMANCE.md +265 -0
- package/STOREFRONT_PREVIEW.md +474 -0
- package/client.d.ts +7 -1
- package/errors.d.ts +9 -1
- package/graphql.cjs +63 -0
- package/graphql.d.ts +2 -2
- package/graphql.js +63 -0
- package/index.cjs +1542 -192
- package/index.d.ts +8 -6
- package/index.js +1542 -192
- package/marketing/attribution.d.ts +51 -0
- package/marketing/clarity.d.ts +3 -0
- package/marketing/domain.d.ts +1 -0
- package/marketing/ga4.d.ts +2 -0
- package/marketing/index.d.ts +5 -0
- package/marketing/runtime.d.ts +2 -0
- package/marketing/sanitize.d.ts +2 -0
- package/marketing/types.d.ts +109 -0
- package/media.d.ts +45 -2
- package/modules/auth.d.ts +3 -2
- package/modules/availability.d.ts +2 -2
- package/modules/cart-session.d.ts +2 -2
- package/modules/cart.d.ts +3 -3
- package/modules/catalog.d.ts +15 -3
- package/modules/content.d.ts +2 -2
- package/modules/marketing.d.ts +5 -0
- package/modules/orders.d.ts +2 -2
- package/modules/profile.d.ts +1 -1
- package/modules/static-catalog.d.ts +13 -0
- package/modules/website.d.ts +7 -1
- package/package.json +14 -3
- package/react/context-registry.d.ts +9 -0
- package/react/context.d.ts +1 -1
- package/react/hooks.d.ts +23 -2
- package/react/marketing-context.d.ts +3 -0
- package/react/marketing-provider.d.ts +10 -0
- package/react/preview/cache.d.ts +23 -0
- package/react/preview/context.d.ts +5 -0
- package/react/preview/navigation.d.ts +9 -0
- package/react/preview/provider.d.ts +3 -0
- package/react/preview/shell.d.ts +3 -0
- package/react/preview/toolbar.d.ts +3 -0
- package/react/preview/types.d.ts +60 -0
- package/react/preview.cjs +742 -0
- package/react/preview.d.ts +14 -0
- package/react/preview.js +730 -0
- package/react/website-gate.d.ts +22 -0
- package/react.cjs +3432 -1780
- package/react.d.ts +8 -4
- package/react.js +3428 -1772
- package/skills/bacano-web/SKILL.md +109 -0
- package/skills/bacano-web/agents/openai.yaml +4 -0
- package/skills/bacano-web/references/common.md +91 -0
- package/skills/bacano-web/references/informational.md +39 -0
- package/skills/bacano-web/references/report.md +52 -0
- package/skills/bacano-web/references/storefront.md +116 -0
- package/skills/bacano-web/scripts/audit-http.mjs +247 -0
- package/skills/bacano-web/scripts/cli.mjs +222 -0
- package/skills/bacano-web/scripts/postinstall.mjs +8 -0
- package/transport/documents.d.ts +8 -6
- package/transport/graphql.d.ts +13 -1
- package/types.d.ts +202 -0
package/README.md
CHANGED
|
@@ -21,13 +21,118 @@ React hooks are available from `@bacano/sdk/react` and require `react >= 18`
|
|
|
21
21
|
as a peer dependency. The core client has no runtime dependencies and works
|
|
22
22
|
without React.
|
|
23
23
|
|
|
24
|
+
### Auditoría del storefront
|
|
25
|
+
|
|
26
|
+
El SDK incluye la skill oficial `bacano-web`. Instalarla al iniciar un
|
|
27
|
+
storefront y ejecutarla antes de publicar o después de un cambio relevante del
|
|
28
|
+
SDK, hosting, autenticación, pagos o medición:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pnpm add @bacano/sdk
|
|
32
|
+
pnpm exec bacano-web install --agent all
|
|
33
|
+
pnpm exec bacano-web check
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Después invocarla desde el agente elegido:
|
|
37
|
+
|
|
38
|
+
- Codex: `$bacano-web`
|
|
39
|
+
- Claude Code: `/bacano-web`
|
|
40
|
+
- Antigravity: solicitar una auditoría Bacano Web o elegirla desde `/skills`
|
|
41
|
+
|
|
42
|
+
`install` solo instala una copia canónica de la skill y sus enlaces de
|
|
43
|
+
descubrimiento. `check` confirma que está vigente. Ninguno ejecuta la auditoría,
|
|
44
|
+
modifica el storefront ni despliega; esa revisión comienza únicamente cuando
|
|
45
|
+
el usuario o agente invoca la skill.
|
|
46
|
+
|
|
47
|
+
Every storefront must follow [`STOREFRONT_MARKETING.md`](./STOREFRONT_MARKETING.md)
|
|
48
|
+
when instrumenting analytics, advertising signals, contact actions, ecommerce
|
|
49
|
+
events, attribution, or preview behavior. It is the canonical implementation
|
|
50
|
+
contract for storefront agents and ships inside the npm package.
|
|
51
|
+
|
|
52
|
+
Commercial forms and newsletter subscriptions must follow
|
|
53
|
+
[`STOREFRONT_LEADS.md`](./STOREFRONT_LEADS.md). Storefronts own the form UI;
|
|
54
|
+
Bacano owns consent evidence, identity resolution, attribution and idempotent
|
|
55
|
+
lead capture through the SDK.
|
|
56
|
+
|
|
57
|
+
Static and hybrid stores must also follow
|
|
58
|
+
[`STOREFRONT_PERFORMANCE.md`](./STOREFRONT_PERFORMANCE.md). It defines the
|
|
59
|
+
official build bootstrap, live-state refresh, responsive media and request
|
|
60
|
+
ownership rules. It also documents the shared public-config bootstrap, deferred
|
|
61
|
+
marketing scripts and route-scoped authentication required for fast public
|
|
62
|
+
catalog pages.
|
|
63
|
+
|
|
64
|
+
Storefronts that can be hidden temporarily must mount the runtime gate defined
|
|
65
|
+
in [`STOREFRONT_CONSTRUCTION.md`](./STOREFRONT_CONSTRUCTION.md). It supports
|
|
66
|
+
static SiteGround exports without rebuilding, responsive Bacano media and
|
|
67
|
+
signed temporary preview links.
|
|
68
|
+
|
|
69
|
+
#### Instalación y alcance de la skill
|
|
70
|
+
|
|
71
|
+
El paquete incluye `skills/bacano-web`, una skill portátil para que un agente
|
|
72
|
+
audite el repositorio, build y dominio antes de producción. Primero confirma si
|
|
73
|
+
el proyecto es una **web informativa** o una **tienda virtual** y aplica un
|
|
74
|
+
checklist distinto; no exige carrito, checkout o pagos a un sitio informativo.
|
|
75
|
+
|
|
76
|
+
Ubicaciones del proyecto después de instalar la skill:
|
|
77
|
+
|
|
78
|
+
| Agente | Ruta de descubrimiento | Invocación |
|
|
79
|
+
| ----------- | --------------------------- | ----------------------------- |
|
|
80
|
+
| Codex | `.codex/skills/bacano-web` | `$bacano-web` |
|
|
81
|
+
| Claude Code | `.claude/skills/bacano-web` | `/bacano-web` |
|
|
82
|
+
| Antigravity | `.agents/skills/bacano-web` | Solicitud natural o `/skills` |
|
|
83
|
+
|
|
84
|
+
Para instalarla solo para un agente puede indicarse explícitamente:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
pnpm exec bacano-web install --agent antigravity
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
También puede ejecutarse sin `--agent`; en una terminal interactiva pregunta si
|
|
91
|
+
se usará Antigravity, Claude Code, Codex o todos. Mantiene una sola copia física
|
|
92
|
+
en `.agents/skills/bacano-web` y crea enlaces de descubrimiento para los otros
|
|
93
|
+
agentes seleccionados.
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
pnpm exec bacano-web check
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
El paquete incluye un aviso `postinstall` como ayuda adicional, aunque algunos
|
|
100
|
+
gestores pueden ocultar su salida. El aviso nunca modifica el repositorio y
|
|
101
|
+
puede desactivarse en entornos controlados con
|
|
102
|
+
`BACANO_SDK_HIDE_SKILL_NOTICE=1`.
|
|
103
|
+
|
|
104
|
+
La skill realiza auditorías de solo lectura por defecto. Primero detecta las
|
|
105
|
+
capacidades realmente activas y separa gates de publicación (`P0`), calidad
|
|
106
|
+
profesional (`P1`) y mejoras (`P2`), para no bloquear una web por funciones
|
|
107
|
+
apagadas. Sus informes usan estados claros con emojis: `✅ APROBADO`,
|
|
108
|
+
`⚠️ ADVERTENCIA`, `❌ FALLÓ`, `➖ NO APLICA` y `🧪 NO VERIFICADO`.
|
|
109
|
+
|
|
24
110
|
### Entry points
|
|
25
111
|
|
|
26
|
-
| Import
|
|
27
|
-
|
|
|
28
|
-
| `@bacano/sdk`
|
|
29
|
-
| `@bacano/sdk/react`
|
|
30
|
-
| `@bacano/sdk/
|
|
112
|
+
| Import | Contents |
|
|
113
|
+
| --------------------------- | ----------------------------------------------------- |
|
|
114
|
+
| `@bacano/sdk` | Core client, catalog, cart, checkout, orders, profile |
|
|
115
|
+
| `@bacano/sdk/react` | `BacanoProvider` and React hooks |
|
|
116
|
+
| `@bacano/sdk/react/preview` | Optional live preview controller and toolbar |
|
|
117
|
+
| `@bacano/sdk/graphql` | Raw GraphQL documents, for advanced use |
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## Live Content Preview
|
|
122
|
+
|
|
123
|
+
Static storefronts can show current published Bacano Content without rebuilding
|
|
124
|
+
their public pages. Import the isolated preview entrypoint only from the
|
|
125
|
+
storefront preview route:
|
|
126
|
+
|
|
127
|
+
```tsx
|
|
128
|
+
import { BacanoPreviewShell } from "@bacano/sdk/react/preview";
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
The public storefront remains static. Preview V1 never exposes drafts and
|
|
132
|
+
automatically disables the SDK marketing runtime inside its tree. See
|
|
133
|
+
[`STOREFRONT_PREVIEW.md`](./STOREFRONT_PREVIEW.md), the canonical contract for
|
|
134
|
+
provider hierarchy, responsibilities, security, agent checklist, and the
|
|
135
|
+
validated Baby Rex reference implementation.
|
|
31
136
|
|
|
32
137
|
---
|
|
33
138
|
|
|
@@ -148,6 +253,43 @@ const filtered = await client.catalog.getProducts({
|
|
|
148
253
|
// Single product
|
|
149
254
|
const product = await client.catalog.getProduct("product-uuid");
|
|
150
255
|
|
|
256
|
+
// Static builds: fetch the complete sellable catalog and its first renderable
|
|
257
|
+
// catalog page. This is the canonical source for initial HTML, routes,
|
|
258
|
+
// metadata, sitemap and product page data.
|
|
259
|
+
const bootstrap = await client.catalog.getStaticCatalogBootstrap({
|
|
260
|
+
categoryListKey: "categorias-web",
|
|
261
|
+
attributeListKey: "atributos",
|
|
262
|
+
pageSize: 100,
|
|
263
|
+
initialPageSize: 24,
|
|
264
|
+
maxProducts: 10_000,
|
|
265
|
+
sortBy: "name_asc",
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
const productByRoute = new Map(
|
|
269
|
+
bootstrap.products.map((item) => [item.slug || item.id, item]),
|
|
270
|
+
);
|
|
271
|
+
|
|
272
|
+
// Cards and filters use product.variants. Detail selectors use the official
|
|
273
|
+
// helper so sold-out sizes remain visible and older snapshots stay compatible.
|
|
274
|
+
const detailVariants = getProductDetailVariants(product!);
|
|
275
|
+
|
|
276
|
+
// Do not call getProductBySlug() once per route during the same build.
|
|
277
|
+
// Reuse productByRoute from the snapshot instead.
|
|
278
|
+
// Render bootstrap.initialPage in the initial catalog HTML instead of
|
|
279
|
+
// publishing an empty skeleton that waits for a browser request.
|
|
280
|
+
|
|
281
|
+
// After hydration, refresh only volatile state for visible variants.
|
|
282
|
+
const liveState = await client.catalog.getProductLiveState(
|
|
283
|
+
detailVariants.map((variant) => variant.id),
|
|
284
|
+
);
|
|
285
|
+
// Each item includes maxPurchasableQuantity for product and cart controls.
|
|
286
|
+
|
|
287
|
+
// Responsive storage candidates are provided by the SDK.
|
|
288
|
+
const srcSet = formatMediaSrcSet(product.media[0]?.responsiveSources ?? []);
|
|
289
|
+
|
|
290
|
+
// Editorial ContentMedia uses heroUrl plus intrinsic-width-aware
|
|
291
|
+
// responsiveSources. See STOREFRONT_PERFORMANCE.md for the <picture> contract.
|
|
292
|
+
|
|
151
293
|
// Automatic recommendations for the product detail. This is an independent,
|
|
152
294
|
// lightweight request; Bacano resolves ranking, availability and public price.
|
|
153
295
|
const recommendations = await client.catalog.getProductRecommendations({
|
|
@@ -211,9 +353,18 @@ const availability = await client.catalog.checkAvailability([
|
|
|
211
353
|
"variant-uuid-1",
|
|
212
354
|
"variant-uuid-2",
|
|
213
355
|
]);
|
|
214
|
-
// → [{ productVariantId, inStock: true, label: "In Stock"
|
|
356
|
+
// → [{ productVariantId, inStock: true, label: "In Stock",
|
|
357
|
+
// maxPurchasableQuantity: 8 }]
|
|
215
358
|
```
|
|
216
359
|
|
|
360
|
+
`maxPurchasableQuantity` is Bacano's current public purchase limit after active
|
|
361
|
+
reservations, capped by the maximum accepted by public cart and checkout
|
|
362
|
+
actions. It is not a raw warehouse inventory field. Use it to limit quantity
|
|
363
|
+
selectors and the total units of that variant already present in the local
|
|
364
|
+
cart. Refresh the variants in one batch when opening the cart or checkout; do
|
|
365
|
+
not request availability after every `+` click. This value can change after it
|
|
366
|
+
is read, so Bacano still revalidates stock when checkout starts and finishes.
|
|
367
|
+
|
|
217
368
|
### 4. Local Cart And Checkout Start
|
|
218
369
|
|
|
219
370
|
The website checkout remains guest-first. Buyer credentials and email
|
|
@@ -337,6 +488,20 @@ collecting contact or address data. Bacano resolves current prices, stock,
|
|
|
337
488
|
website and shipping policy; the storefront must format the structured values
|
|
338
489
|
instead of hardcoding or recalculating them.
|
|
339
490
|
|
|
491
|
+
For the policy to be enabled, the website must have delivery enabled, a
|
|
492
|
+
positive free-shipping threshold, and at least one active shipping service
|
|
493
|
+
enabled for that website whose collection mode charges shipping with the sale
|
|
494
|
+
(`with_sale`). Shipping collected directly by the carrier is intentionally not
|
|
495
|
+
overridden by this promotion. These prerequisites are configured in Bacano;
|
|
496
|
+
the storefront must not infer or reproduce them.
|
|
497
|
+
|
|
498
|
+
`getShippingPolicy()` is cached for five minutes per SDK client. This keeps
|
|
499
|
+
catalog navigation from repeatedly requesting the same configuration. After
|
|
500
|
+
changing the policy in Bacano, recreate the client or reload the storefront to
|
|
501
|
+
validate it immediately. `getFreeShippingProgress()` always validates the
|
|
502
|
+
submitted variants and quantities against Bacano's current prices and returns
|
|
503
|
+
`enabled: false` with nullable amounts when the policy does not apply.
|
|
504
|
+
|
|
340
505
|
`getShippingQuotes` returns the final shipping price for an address. Before a
|
|
341
506
|
guest checkout exists, pass `items`. Once an active cart session exists, the
|
|
342
507
|
SDK intentionally prefers the persisted cart and ignores loose items so
|
|
@@ -624,6 +789,166 @@ configured identity provider owns their lifecycle.
|
|
|
624
789
|
|
|
625
790
|
## React Integration
|
|
626
791
|
|
|
792
|
+
### Storefront marketing and measurement
|
|
793
|
+
|
|
794
|
+
GA4, Meta, and Microsoft Clarity are configured per website in Bacano and are
|
|
795
|
+
disabled by default.
|
|
796
|
+
The existing public-config request returns only active public destination IDs;
|
|
797
|
+
provider secrets never reach the SDK. The runtime remains a no-op outside the
|
|
798
|
+
website's canonical production domain.
|
|
799
|
+
|
|
800
|
+
Mount one provider at the storefront root, using the same public config already
|
|
801
|
+
loaded for buyer accounts:
|
|
802
|
+
|
|
803
|
+
```tsx
|
|
804
|
+
import { getWebsitePublicConfig } from "@bacano/sdk";
|
|
805
|
+
import { BacanoMarketingProvider, useMarketing } from "@bacano/sdk/react";
|
|
806
|
+
|
|
807
|
+
const publicConfig = await getWebsitePublicConfig({ apiUrl, websiteSlug });
|
|
808
|
+
|
|
809
|
+
<BacanoMarketingProvider publicConfig={publicConfig}>
|
|
810
|
+
<Storefront />
|
|
811
|
+
</BacanoMarketingProvider>;
|
|
812
|
+
|
|
813
|
+
function AddToCartButton({ variant }) {
|
|
814
|
+
const marketing = useMarketing();
|
|
815
|
+
|
|
816
|
+
async function add() {
|
|
817
|
+
await updateCartSuccessfully(variant.id);
|
|
818
|
+
marketing.track({
|
|
819
|
+
eventName: "add_to_cart",
|
|
820
|
+
value: variant.price,
|
|
821
|
+
items: [
|
|
822
|
+
{
|
|
823
|
+
itemId: variant.id, // always product_variant_id
|
|
824
|
+
itemName: variant.productName,
|
|
825
|
+
sku: variant.sku,
|
|
826
|
+
price: variant.price,
|
|
827
|
+
quantity: 1,
|
|
828
|
+
},
|
|
829
|
+
],
|
|
830
|
+
});
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
return <button onClick={add}>Add to cart</button>;
|
|
834
|
+
}
|
|
835
|
+
```
|
|
836
|
+
|
|
837
|
+
The browser contract is versioned and accepts `page_view`, `search`,
|
|
838
|
+
`view_item_list`, `select_item`, `view_item`, `add_to_cart`,
|
|
839
|
+
`remove_from_cart`, `view_cart`, `begin_checkout`, `add_shipping_info`,
|
|
840
|
+
`add_payment_info`, and `contact`. Emit events only after the related operation
|
|
841
|
+
has succeeded. Use one manual `page_view` source for SPA navigation because the
|
|
842
|
+
GA4 adapter disables automatic page views.
|
|
843
|
+
|
|
844
|
+
When Microsoft Clarity is active, this same provider loads it automatically and
|
|
845
|
+
forwards canonical browser events as Clarity custom events. Do not install a
|
|
846
|
+
second Clarity snippet or package in the storefront. Setup, privacy, and
|
|
847
|
+
verification are documented in [`STOREFRONT_CLARITY.md`](./STOREFRONT_CLARITY.md).
|
|
848
|
+
|
|
849
|
+
Contact actions can identify their stable storefront location with the optional
|
|
850
|
+
`contactPlacement` field. It accepts only `product_detail`, `floating`,
|
|
851
|
+
`header`, `footer`, or `contact_page`; visible labels and arbitrary strings are
|
|
852
|
+
not part of the public contract. The runtime ignores the field on events other
|
|
853
|
+
than `contact`.
|
|
854
|
+
|
|
855
|
+
```tsx
|
|
856
|
+
marketing.track({
|
|
857
|
+
eventName: "contact",
|
|
858
|
+
contactMethod: "email",
|
|
859
|
+
contactPlacement: "footer",
|
|
860
|
+
});
|
|
861
|
+
```
|
|
862
|
+
|
|
863
|
+
A product contact keeps the same variant identity, value, and item payload used
|
|
864
|
+
by the ecommerce events:
|
|
865
|
+
|
|
866
|
+
```tsx
|
|
867
|
+
marketing.track({
|
|
868
|
+
eventName: "contact",
|
|
869
|
+
contactMethod: "whatsapp",
|
|
870
|
+
contactPlacement: "product_detail",
|
|
871
|
+
value: price * quantity,
|
|
872
|
+
items: [
|
|
873
|
+
{
|
|
874
|
+
itemId: variant.id,
|
|
875
|
+
itemName: product.name,
|
|
876
|
+
sku: variant.sku,
|
|
877
|
+
price,
|
|
878
|
+
quantity,
|
|
879
|
+
itemBrand: product.brand?.name,
|
|
880
|
+
itemCategory: product.category,
|
|
881
|
+
itemVariant: "Talla: 12 meses / Color: Rosado",
|
|
882
|
+
},
|
|
883
|
+
],
|
|
884
|
+
});
|
|
885
|
+
```
|
|
886
|
+
|
|
887
|
+
GA4 receives this field as `contact_placement`; Meta receives the same
|
|
888
|
+
`contact_placement` key while preserving `contact_method`, `content_ids`, and
|
|
889
|
+
`contents`.
|
|
890
|
+
|
|
891
|
+
For checkout selectors, an automatic default is not yet a customer choice.
|
|
892
|
+
Emit `add_shipping_info` and `add_payment_info` immediately for an explicit,
|
|
893
|
+
valid selection, or once after a successful checkout submission when the
|
|
894
|
+
customer kept an automatic default. Deduplicate both paths with the selected
|
|
895
|
+
delivery/payment identity. On product lists, `itemId` and `price` must come
|
|
896
|
+
from the same public, price-bearing variant; the advertised variant is the
|
|
897
|
+
lowest-price variant with stable `order` and ID tie-breakers.
|
|
898
|
+
|
|
899
|
+
Transfer the runtime attribution snapshot when checkout starts and again when
|
|
900
|
+
it is submitted. Bacano freezes the touchpoints on the order; the second
|
|
901
|
+
snapshot may only fill missing GA4 `client_id` and `session_id` values that the
|
|
902
|
+
Google tag resolved asynchronously:
|
|
903
|
+
|
|
904
|
+
```typescript
|
|
905
|
+
await client.cart.startCheckout({
|
|
906
|
+
contact,
|
|
907
|
+
items,
|
|
908
|
+
marketingAttribution: marketing.getAttribution() ?? undefined,
|
|
909
|
+
});
|
|
910
|
+
|
|
911
|
+
await client.checkout.submit({
|
|
912
|
+
contact,
|
|
913
|
+
marketingAttribution: marketing.getAttribution() ?? undefined,
|
|
914
|
+
advertisingConsent: publicConfig.marketing.consentPolicy
|
|
915
|
+
? {
|
|
916
|
+
policyId: publicConfig.marketing.consentPolicy.id,
|
|
917
|
+
policyVersion: publicConfig.marketing.consentPolicy.version,
|
|
918
|
+
statementHash: publicConfig.marketing.consentPolicy.statementHash,
|
|
919
|
+
granted: optionalAdvertisingCheckbox,
|
|
920
|
+
}
|
|
921
|
+
: undefined,
|
|
922
|
+
});
|
|
923
|
+
```
|
|
924
|
+
|
|
925
|
+
`order_placed`, `purchase`, and `refund` are server-authoritative events. A
|
|
926
|
+
storefront must never emit them from a thank-you page or payment response route.
|
|
927
|
+
URLs are centrally sanitized to retain only approved UTM and click identifiers.
|
|
928
|
+
Google or Meta failures are isolated from catalog, cart, checkout, and payment.
|
|
929
|
+
This isolation also applies to Bacano's server-side outbox producers: losing a
|
|
930
|
+
marketing event must never roll back an order, payment, or refund transition.
|
|
931
|
+
The runtime captures GA4 browser identity and Meta browser context (`fbp`,
|
|
932
|
+
`fbc`, and user agent) internally, so storefronts must not read cookies or
|
|
933
|
+
construct provider identifiers themselves. Contact information remains governed
|
|
934
|
+
by the website's explicit advertising-consent policy.
|
|
935
|
+
|
|
936
|
+
An active, validated integration can enable a temporary diagnostic window from
|
|
937
|
+
Bacano. GA4 events receive `debug_mode`; Meta server events receive its encrypted
|
|
938
|
+
Test Events code. The mode is off by default, expires automatically, is cleared
|
|
939
|
+
when an integration is paused or reconfigured, and never exposes the Meta code
|
|
940
|
+
through public config or the SDK.
|
|
941
|
+
|
|
942
|
+
The canonical contract and acceptance checklist live in
|
|
943
|
+
`docs/websites/MARKETING_MEASUREMENT_STOREFRONTS_CHECKLIST.md` in the Bacano ERP
|
|
944
|
+
repository.
|
|
945
|
+
|
|
946
|
+
Website legal documents and the optional informational cookie notice are
|
|
947
|
+
available in `publicConfig.legal`. Their canonical contract lives in
|
|
948
|
+
[`STOREFRONT_LEGAL.md`](./STOREFRONT_LEGAL.md), which ships inside this package.
|
|
949
|
+
The notice does not gate the marketing runtime; storefronts must not reinterpret
|
|
950
|
+
it as a consent manager.
|
|
951
|
+
|
|
627
952
|
### Setup
|
|
628
953
|
|
|
629
954
|
Wrap your app with `BacanoProvider`:
|
|
@@ -1223,6 +1548,7 @@ The SDK throws typed errors you can catch and handle:
|
|
|
1223
1548
|
```typescript
|
|
1224
1549
|
import {
|
|
1225
1550
|
WebsiteError,
|
|
1551
|
+
RateLimitError,
|
|
1226
1552
|
AuthError,
|
|
1227
1553
|
CheckoutError,
|
|
1228
1554
|
NotInitializedError,
|
|
@@ -1248,6 +1574,8 @@ try {
|
|
|
1248
1574
|
console.log("GraphQL errors:", err.graphqlErrors);
|
|
1249
1575
|
} else if (err instanceof NotInitializedError) {
|
|
1250
1576
|
// client.init() was not called
|
|
1577
|
+
} else if (err instanceof RateLimitError) {
|
|
1578
|
+
console.log("Retry after seconds:", err.retryAfterSeconds);
|
|
1251
1579
|
} else if (err instanceof WebsiteError) {
|
|
1252
1580
|
// Other SDK error (network, rate limit, etc.)
|
|
1253
1581
|
console.log("Error code:", err.code);
|
|
@@ -1257,6 +1585,11 @@ try {
|
|
|
1257
1585
|
}
|
|
1258
1586
|
```
|
|
1259
1587
|
|
|
1588
|
+
`RATE_LIMITED` errors expose `status = 429` and `retryAfterSeconds`. Normal SDK
|
|
1589
|
+
operations do not retry mutations automatically. The static catalog snapshot
|
|
1590
|
+
is a read-only build contract and safely retries up to four times, never before
|
|
1591
|
+
the server's `Retry-After` window and with a three-minute cumulative wait cap.
|
|
1592
|
+
|
|
1260
1593
|
Common setup errors:
|
|
1261
1594
|
|
|
1262
1595
|
| Code / message | Meaning | Fix |
|
|
@@ -1296,7 +1629,7 @@ import type {
|
|
|
1296
1629
|
ProductCategory,
|
|
1297
1630
|
ProductSubcategory,
|
|
1298
1631
|
Brand,
|
|
1299
|
-
ProductAvailability, // { productVariantId, inStock, label }
|
|
1632
|
+
ProductAvailability, // { productVariantId, inStock, label, maxPurchasableQuantity }
|
|
1300
1633
|
GetProductsOptions, // { categoryName, search, colors, tags, minPrice, maxPrice, inStock, attributes, sortBy, ... }
|
|
1301
1634
|
GetProductFiltersOptions,
|
|
1302
1635
|
ProductFilters, // { categories, brands, colors, priceRange, attributes }
|
|
@@ -1304,6 +1637,8 @@ import type {
|
|
|
1304
1637
|
ProductSortBy, // 'name_asc' | 'name_desc' | 'price_asc' | 'price_desc' | 'newest'
|
|
1305
1638
|
SearchSuggestion, // { type: 'product' | 'brand' | 'category', text, id? }
|
|
1306
1639
|
GetSearchSuggestionsOptions,
|
|
1640
|
+
GetStaticCatalogSnapshotOptions,
|
|
1641
|
+
StaticCatalogSnapshot,
|
|
1307
1642
|
|
|
1308
1643
|
// Cart
|
|
1309
1644
|
StartGuestCheckoutInput,
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# Microsoft Clarity en storefronts Bacano
|
|
2
|
+
|
|
3
|
+
Este documento define la integración oficial de Microsoft Clarity para todos
|
|
4
|
+
los storefronts que usan `@bacano/sdk`. Clarity aporta mapas de calor,
|
|
5
|
+
grabaciones de sesión y análisis de comportamiento; no reemplaza GA4, Meta ni
|
|
6
|
+
los eventos comerciales autoritativos de Bacano.
|
|
7
|
+
|
|
8
|
+
## Responsabilidades
|
|
9
|
+
|
|
10
|
+
El comercio crea y administra su proyecto en Microsoft Clarity. Se recomienda
|
|
11
|
+
un proyecto diferente por dominio. En Bacano, dentro de **Sitios web > Marketing
|
|
12
|
+
y medición > Microsoft Clarity**, registra el `Project ID`, guarda, prueba la
|
|
13
|
+
conexión y la activa.
|
|
14
|
+
|
|
15
|
+
El `Project ID` es público. Bacano lo guarda por `website_id`; no existe un
|
|
16
|
+
secreto de servidor para esta integración. El SDK solo lo recibe cuando la
|
|
17
|
+
integración está activa, validada y la petición corresponde al dominio canónico
|
|
18
|
+
de producción.
|
|
19
|
+
|
|
20
|
+
El storefront es responsable únicamente de:
|
|
21
|
+
|
|
22
|
+
1. Usar una versión del SDK que incluya este contrato.
|
|
23
|
+
2. Montar una sola vez el `BacanoMarketingProvider` existente.
|
|
24
|
+
3. Seguir emitiendo los eventos canónicos mediante `useMarketing().track()`.
|
|
25
|
+
|
|
26
|
+
No debe instalar otro paquete de Clarity, copiar el snippet de Microsoft,
|
|
27
|
+
consultar GraphQL ni construir una segunda petición de configuración.
|
|
28
|
+
|
|
29
|
+
```tsx
|
|
30
|
+
import { getWebsitePublicConfig } from "@bacano/sdk";
|
|
31
|
+
import { BacanoMarketingProvider } from "@bacano/sdk/react";
|
|
32
|
+
|
|
33
|
+
const publicConfig = await getWebsitePublicConfig({ apiUrl, websiteSlug });
|
|
34
|
+
|
|
35
|
+
<BacanoMarketingProvider publicConfig={publicConfig} scriptLoadStrategy="idle">
|
|
36
|
+
<Storefront />
|
|
37
|
+
</BacanoMarketingProvider>;
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Cuando Clarity está activo, el provider carga una sola vez
|
|
41
|
+
`https://www.clarity.ms/tag/{projectId}`. En localhost, preview y dominios no
|
|
42
|
+
canónicos se mantiene como `no-op`.
|
|
43
|
+
|
|
44
|
+
Si el storefront aplica una Content Security Policy, debe permitir los
|
|
45
|
+
endpoints oficiales de Clarity (`https://*.clarity.ms` y
|
|
46
|
+
`https://c.bing.com`) en las directivas correspondientes de scripts y
|
|
47
|
+
conexiones. Conviene probar primero la política en modo report-only y revisar
|
|
48
|
+
la consola del navegador antes de aplicarla.
|
|
49
|
+
|
|
50
|
+
La matriz distribuible de CSP para Clarity, GA4 y Meta vive únicamente en
|
|
51
|
+
[`STOREFRONT_MARKETING.md`](./STOREFRONT_MARKETING.md). No mantener una segunda
|
|
52
|
+
lista dentro del storefront: componer desde allí solo los proveedores activos
|
|
53
|
+
y añadir por separado los orígenes propios de autenticación, pagos o video.
|
|
54
|
+
|
|
55
|
+
Las imágenes y videos públicos entregados por
|
|
56
|
+
`/storage/v1/files/{fileId}` aceptan lectura credentialless desde renderizadores
|
|
57
|
+
externos. Bacano valida que el archivo pertenezca a media pública de una empresa
|
|
58
|
+
con un storefront activo antes de servirlo y mantiene privados los archivos no
|
|
59
|
+
elegibles. El storefront no debe crear un proxy, reenviar cookies ni modificar
|
|
60
|
+
las URLs de media para que aparezcan en las reproducciones de Clarity.
|
|
61
|
+
|
|
62
|
+
Una política CSP solo puede componerse con datos conocidos durante el build.
|
|
63
|
+
El runtime no intenta modificar una cabecera CSP después de cargar la página.
|
|
64
|
+
Si un proveedor incorpora un origen dinámico no documentado —por ejemplo, un
|
|
65
|
+
endpoint auxiliar de Meta alojado en infraestructura cloud— no se deben abrir
|
|
66
|
+
comodines amplios como `*.on.aws` o `*.run.app`. Ese origen debe validarse y
|
|
67
|
+
declararse de manera exacta en el hosting hasta que el proveedor ofrezca un
|
|
68
|
+
contrato estable que Bacano pueda publicar de forma segura.
|
|
69
|
+
|
|
70
|
+
## Eventos y alcance
|
|
71
|
+
|
|
72
|
+
Clarity captura automáticamente navegación, clics, desplazamiento, errores y
|
|
73
|
+
la representación necesaria para sus mapas y grabaciones. Adicionalmente, el
|
|
74
|
+
SDK reenvía cada evento canónico de navegador como un evento personalizado de
|
|
75
|
+
Clarity con el mismo nombre, por ejemplo:
|
|
76
|
+
|
|
77
|
+
- `view_item`
|
|
78
|
+
- `add_to_cart`
|
|
79
|
+
- `begin_checkout`
|
|
80
|
+
- `contact`
|
|
81
|
+
|
|
82
|
+
Esto permite filtrar grabaciones y mapas por pasos del embudo sin instrumentar
|
|
83
|
+
handlers paralelos. El storefront no debe llamar a `window.clarity()`.
|
|
84
|
+
|
|
85
|
+
Clarity es un destino exclusivo de navegador. `order_placed`, `purchase` y
|
|
86
|
+
`refund` continúan enviándose de forma autoritativa a GA4 y Meta desde Bacano;
|
|
87
|
+
no se crea un outbox de servidor para Clarity.
|
|
88
|
+
|
|
89
|
+
## Privacidad y seguridad
|
|
90
|
+
|
|
91
|
+
- No enviar email, teléfono, documento, dirección, tokens ni otros datos
|
|
92
|
+
personales a eventos o etiquetas de Clarity.
|
|
93
|
+
- Clarity enmascara por defecto entradas, números y correos. Los storefronts
|
|
94
|
+
deben añadir `data-clarity-mask` a cualquier zona personalizada sensible que
|
|
95
|
+
requiera protección adicional.
|
|
96
|
+
- No usar `data-clarity-unmask` en checkout, cuenta, autenticación o formularios
|
|
97
|
+
con datos personales.
|
|
98
|
+
- Microsoft indica que Clarity no debe utilizarse en sitios o aplicaciones
|
|
99
|
+
dirigidos a menores de 18 años. El comercio debe evaluar su audiencia antes
|
|
100
|
+
de activarlo.
|
|
101
|
+
- El aviso informativo de cookies de Bacano no es un gestor de consentimiento.
|
|
102
|
+
Si un comercio opera en una jurisdicción que exige consentimiento previo,
|
|
103
|
+
debe implementar un mecanismo de consentimiento válido antes de activar la
|
|
104
|
+
recolección completa.
|
|
105
|
+
|
|
106
|
+
No se utiliza la API `identify`: Bacano no entrega a Clarity identidades de
|
|
107
|
+
clientes ni compradores.
|
|
108
|
+
|
|
109
|
+
## Verificación
|
|
110
|
+
|
|
111
|
+
Después de desplegar el storefront en su dominio canónico:
|
|
112
|
+
|
|
113
|
+
1. Abrir el sitio sin modo preview.
|
|
114
|
+
2. Confirmar en la pestaña Network la carga de `clarity.ms/tag/{projectId}` y
|
|
115
|
+
solicitudes de recolección a Clarity.
|
|
116
|
+
3. Navegar, abrir un producto y agregarlo al carrito.
|
|
117
|
+
4. Revisar sesiones en tiempo real, mapas y eventos personalizados dentro del
|
|
118
|
+
proyecto correcto de Clarity.
|
|
119
|
+
5. Abrir una reproducción y confirmar que las imágenes públicas de productos y
|
|
120
|
+
Content cargan desde `api.bacanoerp.com/storage/v1/files/`.
|
|
121
|
+
6. Confirmar que localhost, preview y otro dominio no envíen datos.
|
|
122
|
+
7. Confirmar que una falla o bloqueo del script no afecte catálogo, carrito o
|
|
123
|
+
checkout.
|
|
124
|
+
|
|
125
|
+
Microsoft puede tardar algunos minutos en mostrar la primera información. Una
|
|
126
|
+
prueba válida en Bacano confirma el Project ID y la disponibilidad del tag; no
|
|
127
|
+
demuestra que un navegador concreto esté libre de bloqueadores.
|
|
128
|
+
|
|
129
|
+
## Checklist para agentes de storefront
|
|
130
|
+
|
|
131
|
+
- [ ] Uso un único `BacanoMarketingProvider`.
|
|
132
|
+
- [ ] No instalé el snippet ni un paquete paralelo de Clarity.
|
|
133
|
+
- [ ] Los eventos continúan saliendo solo de `useMarketing().track()`.
|
|
134
|
+
- [ ] Preview y localhost no cargan Clarity.
|
|
135
|
+
- [ ] Las zonas sensibles permanecen enmascaradas.
|
|
136
|
+
- [ ] No uso `identify`, PII ni etiquetas con datos personales.
|
|
137
|
+
- [ ] Las imágenes públicas se reconstruyen sin un proxy del storefront.
|
|
138
|
+
- [ ] Validé carga, recolección, sesiones y eventos en producción.
|
|
139
|
+
|
|
140
|
+
El contrato general de eventos permanece en `STOREFRONT_MARKETING.md`.
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# Sitio en construcción para storefronts Bacano
|
|
2
|
+
|
|
3
|
+
Este documento define el contrato oficial para ocultar temporalmente un sitio
|
|
4
|
+
Bacano sin reconstruir su export estático. Aplica a SiteGround y otros hostings
|
|
5
|
+
estáticos; una futura integración con CloudFront podrá aplicar la misma
|
|
6
|
+
configuración en el edge.
|
|
7
|
+
|
|
8
|
+
## Responsabilidades
|
|
9
|
+
|
|
10
|
+
- Bacano conserva la configuración por `website_id`, valida las imágenes y
|
|
11
|
+
entrega únicamente datos públicos.
|
|
12
|
+
- `public-config` decide si la pantalla debe aplicarse. Localhost, desarrollo y
|
|
13
|
+
orígenes no canónicos nunca quedan bloqueados.
|
|
14
|
+
- El SDK administra la consulta en vivo, la pantalla responsive y el acceso
|
|
15
|
+
temporal de vista previa.
|
|
16
|
+
- El storefront solo monta `BacanoWebsiteGate`. No consulta GraphQL, no lee
|
|
17
|
+
`websites.config` y no construye URLs de Storage.
|
|
18
|
+
|
|
19
|
+
## Integración React
|
|
20
|
+
|
|
21
|
+
El gate debe envolver autenticación, marketing y la aplicación. De esta forma
|
|
22
|
+
Clerk, GA4, Meta y Clarity no se cargan mientras la pantalla esté activa.
|
|
23
|
+
|
|
24
|
+
```tsx
|
|
25
|
+
"use client";
|
|
26
|
+
|
|
27
|
+
import { BacanoWebsiteGate } from "@bacano/sdk/react";
|
|
28
|
+
|
|
29
|
+
export function WebsiteVisibility({ children }: { children: React.ReactNode }) {
|
|
30
|
+
return (
|
|
31
|
+
<BacanoWebsiteGate
|
|
32
|
+
config={{
|
|
33
|
+
apiUrl: process.env.NEXT_PUBLIC_BACANO_API_URL!,
|
|
34
|
+
websiteSlug: process.env.NEXT_PUBLIC_BACANO_WEBSITE_SLUG!,
|
|
35
|
+
}}
|
|
36
|
+
>
|
|
37
|
+
{children}
|
|
38
|
+
</BacanoWebsiteGate>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Después del gate se montan `ClerkProvider`, `BacanoProvider`,
|
|
44
|
+
`BacanoMarketingProvider` y el resto del storefront. Puede entregarse
|
|
45
|
+
`initialConfig` si el build ya obtuvo `public-config`; el gate vuelve a
|
|
46
|
+
consultarlo antes de montar el sitio para detectar cambios posteriores al
|
|
47
|
+
despliegue y evitar mostrar fugazmente el contenido estático.
|
|
48
|
+
|
|
49
|
+
La pantalla oficial usa la imagen mobile hasta 767 px y la desktop desde 768
|
|
50
|
+
px. En ambos casos emplea las fuentes responsivas generadas por Bacano. El
|
|
51
|
+
storefront puede proporcionar `constructionFallback` si requiere una UI
|
|
52
|
+
propia, pero no debe alterar la decisión `construction.enforced`.
|
|
53
|
+
|
|
54
|
+
## Vista previa temporal
|
|
55
|
+
|
|
56
|
+
Desde Bacano, un usuario con `websites.manage` puede generar un enlace válido
|
|
57
|
+
durante dos horas:
|
|
58
|
+
|
|
59
|
+
```text
|
|
60
|
+
https://tienda.example/#bacano-preview=<token-firmado>
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
El fragmento no viaja al hosting ni se incluye en el `Referer`. El gate lo
|
|
64
|
+
guarda en `sessionStorage`, limpia la barra de direcciones y valida el token
|
|
65
|
+
contra `public-config`. La vista previa termina al cerrar esa sesión, al vencer
|
|
66
|
+
el token o al borrar los datos del navegador.
|
|
67
|
+
|
|
68
|
+
No convertir el token en query string, cookie permanente ni variable de build.
|
|
69
|
+
|
|
70
|
+
## Seguridad y límites de hosting estático
|
|
71
|
+
|
|
72
|
+
En SiteGround esta es una barrera de presentación ejecutada por JavaScript. No
|
|
73
|
+
impide descargar el HTML estático ni devuelve HTTP 503, por lo que no debe
|
|
74
|
+
usarse como control de acceso para información sensible. El contenido privado
|
|
75
|
+
nunca debe formar parte del export público. Mientras la pantalla está activa,
|
|
76
|
+
el gate añade `noindex,nofollow`; en un hosting estático esto sigue dependiendo
|
|
77
|
+
de que el crawler ejecute JavaScript.
|
|
78
|
+
|
|
79
|
+
Cuando Bacano opere el storefront en CloudFront, la misma bandera podrá
|
|
80
|
+
resolverse antes de servir la página, devolver 503 y aplicar `noindex` desde el
|
|
81
|
+
edge. El contrato del SDK y la configuración por website permanecen válidos.
|
|
82
|
+
|
|
83
|
+
## Checklist del storefront
|
|
84
|
+
|
|
85
|
+
- `BacanoWebsiteGate` está por fuera de auth y marketing.
|
|
86
|
+
- La configuración se consulta en runtime con `cache: no-store`.
|
|
87
|
+
- La pantalla activa no inicializa Clerk, GA4, Meta ni Clarity.
|
|
88
|
+
- Localhost y dominios de desarrollo siguen mostrando la web.
|
|
89
|
+
- Las imágenes desktop y mobile usan `srcset` y `sizes`.
|
|
90
|
+
- El enlace temporal abre el storefront y desaparece de la barra de dirección.
|
|
91
|
+
- Un token vencido no concede acceso y se elimina de `sessionStorage`.
|
|
92
|
+
- Un fallo de `public-config` degrada de forma abierta y no tumba el sitio.
|