@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.
Files changed (68) hide show
  1. package/README.md +342 -7
  2. package/STOREFRONT_CLARITY.md +140 -0
  3. package/STOREFRONT_CONSTRUCTION.md +92 -0
  4. package/STOREFRONT_LEADS.md +130 -0
  5. package/STOREFRONT_LEGAL.md +66 -0
  6. package/STOREFRONT_MARKETING.md +286 -0
  7. package/STOREFRONT_PERFORMANCE.md +265 -0
  8. package/STOREFRONT_PREVIEW.md +474 -0
  9. package/client.d.ts +7 -1
  10. package/errors.d.ts +9 -1
  11. package/graphql.cjs +63 -0
  12. package/graphql.d.ts +2 -2
  13. package/graphql.js +63 -0
  14. package/index.cjs +1542 -192
  15. package/index.d.ts +8 -6
  16. package/index.js +1542 -192
  17. package/marketing/attribution.d.ts +51 -0
  18. package/marketing/clarity.d.ts +3 -0
  19. package/marketing/domain.d.ts +1 -0
  20. package/marketing/ga4.d.ts +2 -0
  21. package/marketing/index.d.ts +5 -0
  22. package/marketing/runtime.d.ts +2 -0
  23. package/marketing/sanitize.d.ts +2 -0
  24. package/marketing/types.d.ts +109 -0
  25. package/media.d.ts +45 -2
  26. package/modules/auth.d.ts +3 -2
  27. package/modules/availability.d.ts +2 -2
  28. package/modules/cart-session.d.ts +2 -2
  29. package/modules/cart.d.ts +3 -3
  30. package/modules/catalog.d.ts +15 -3
  31. package/modules/content.d.ts +2 -2
  32. package/modules/marketing.d.ts +5 -0
  33. package/modules/orders.d.ts +2 -2
  34. package/modules/profile.d.ts +1 -1
  35. package/modules/static-catalog.d.ts +13 -0
  36. package/modules/website.d.ts +7 -1
  37. package/package.json +14 -3
  38. package/react/context-registry.d.ts +9 -0
  39. package/react/context.d.ts +1 -1
  40. package/react/hooks.d.ts +23 -2
  41. package/react/marketing-context.d.ts +3 -0
  42. package/react/marketing-provider.d.ts +10 -0
  43. package/react/preview/cache.d.ts +23 -0
  44. package/react/preview/context.d.ts +5 -0
  45. package/react/preview/navigation.d.ts +9 -0
  46. package/react/preview/provider.d.ts +3 -0
  47. package/react/preview/shell.d.ts +3 -0
  48. package/react/preview/toolbar.d.ts +3 -0
  49. package/react/preview/types.d.ts +60 -0
  50. package/react/preview.cjs +742 -0
  51. package/react/preview.d.ts +14 -0
  52. package/react/preview.js +730 -0
  53. package/react/website-gate.d.ts +22 -0
  54. package/react.cjs +3432 -1780
  55. package/react.d.ts +8 -4
  56. package/react.js +3428 -1772
  57. package/skills/bacano-web/SKILL.md +109 -0
  58. package/skills/bacano-web/agents/openai.yaml +4 -0
  59. package/skills/bacano-web/references/common.md +91 -0
  60. package/skills/bacano-web/references/informational.md +39 -0
  61. package/skills/bacano-web/references/report.md +52 -0
  62. package/skills/bacano-web/references/storefront.md +116 -0
  63. package/skills/bacano-web/scripts/audit-http.mjs +247 -0
  64. package/skills/bacano-web/scripts/cli.mjs +222 -0
  65. package/skills/bacano-web/scripts/postinstall.mjs +8 -0
  66. package/transport/documents.d.ts +8 -6
  67. package/transport/graphql.d.ts +13 -1
  68. 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 | Contents |
27
- | --- | --- |
28
- | `@bacano/sdk` | Core client, catalog, cart, checkout, orders, profile |
29
- | `@bacano/sdk/react` | `BacanoProvider` and React hooks |
30
- | `@bacano/sdk/graphql` | Raw GraphQL documents, for advanced use |
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.