@bacano/sdk 1.0.0 → 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
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# Captura de leads en storefronts Bacano
|
|
2
|
+
|
|
3
|
+
Este es el contrato canónico para formularios comerciales construidos por un
|
|
4
|
+
storefront. Bacano no impone un constructor visual: cada tienda decide sus
|
|
5
|
+
campos y diseño, pero toda captura debe entrar por `@bacano/sdk`.
|
|
6
|
+
|
|
7
|
+
## Responsabilidades
|
|
8
|
+
|
|
9
|
+
El storefront:
|
|
10
|
+
|
|
11
|
+
- Renderiza el formulario y valida su experiencia básica.
|
|
12
|
+
- Muestra exactamente `publicConfig.legal.directMarketingConsent.statement`.
|
|
13
|
+
- Mantiene la aceptación opcional desmarcada hasta una acción explícita.
|
|
14
|
+
- Genera una clave de idempotencia por envío y la conserva durante reintentos.
|
|
15
|
+
- Envía únicamente respuestas necesarias para el propósito informado.
|
|
16
|
+
|
|
17
|
+
Bacano:
|
|
18
|
+
|
|
19
|
+
- Deriva y valida el website y su dominio.
|
|
20
|
+
- Resuelve la campaña por su clave pública.
|
|
21
|
+
- Conserva la versión y huella del texto aceptado.
|
|
22
|
+
- Reutiliza la identidad, el lead o el cliente existente sin duplicarlos.
|
|
23
|
+
- Completa un correo o teléfono faltante únicamente cuando la coincidencia de
|
|
24
|
+
identidad es inequívoca; nunca reemplaza un dato existente.
|
|
25
|
+
- Conserva el contacto enviado dentro de la evidencia de autorización ligada a
|
|
26
|
+
la captura, incluso cuando el participante ya era cliente.
|
|
27
|
+
- Conserva la captura, atribución y respuestas como evidencia inmutable.
|
|
28
|
+
- Aplica límites de frecuencia, tamaño e idempotencia.
|
|
29
|
+
|
|
30
|
+
## Configuración previa
|
|
31
|
+
|
|
32
|
+
1. Configura el texto de **Autorización de comunicaciones comerciales** en la
|
|
33
|
+
pestaña Legal del website.
|
|
34
|
+
2. Crea una campaña en Bacano y actívala.
|
|
35
|
+
3. Usa su clave pública estable en el formulario.
|
|
36
|
+
|
|
37
|
+
Una campaña en borrador, pausada, finalizada, fuera de vigencia o asociada a
|
|
38
|
+
otro website no acepta capturas. Si el texto legal no existe, el storefront no
|
|
39
|
+
debe mostrar ni habilitar el formulario comercial.
|
|
40
|
+
|
|
41
|
+
## Implementación
|
|
42
|
+
|
|
43
|
+
```tsx
|
|
44
|
+
const consent = publicConfig.legal.directMarketingConsent;
|
|
45
|
+
|
|
46
|
+
if (!consent) return null;
|
|
47
|
+
|
|
48
|
+
// Renderiza consent.statement junto a un checkbox desmarcado.
|
|
49
|
+
// Ejecuta esta llamada únicamente después de validar el formulario y aceptar.
|
|
50
|
+
// Conserva esta clave en el estado del formulario mientras reintentas el envío.
|
|
51
|
+
const idempotencyKey = crypto.randomUUID();
|
|
52
|
+
const result = await client.marketing.captureLead({
|
|
53
|
+
campaignKey: "newsletter-baby-rex",
|
|
54
|
+
idempotencyKey,
|
|
55
|
+
commercialConsent: true,
|
|
56
|
+
contact: {
|
|
57
|
+
partyKind: "PERSON",
|
|
58
|
+
firstName: "Ana",
|
|
59
|
+
email: "ana@example.com",
|
|
60
|
+
},
|
|
61
|
+
answers: {
|
|
62
|
+
interest: "bebe-nina",
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Debe existir al menos correo o teléfono. Los nombres son opcionales. Para una
|
|
68
|
+
empresa pueden usarse `partyKind: "COMPANY"`, `legalName` y `commercialName`.
|
|
69
|
+
|
|
70
|
+
`source` se omite para formularios normales y será `WEBSITE`. Usa `QR`
|
|
71
|
+
únicamente cuando el formulario se abrió mediante una entrada QR controlada por
|
|
72
|
+
el storefront. UTM, referidor y click IDs se toman automáticamente de la
|
|
73
|
+
atribución segura del SDK.
|
|
74
|
+
|
|
75
|
+
El storefront no debe construir ni enviar manualmente el payload HTTP. El SDK
|
|
76
|
+
serializa este contrato como JSON y la ruta pública de Bacano lo procesa antes
|
|
77
|
+
de validar la campaña. Un SDK `1.0.14` o posterior conserva esta forma del
|
|
78
|
+
payload; no se requieren adaptadores específicos por tienda.
|
|
79
|
+
|
|
80
|
+
## Respuestas personalizadas
|
|
81
|
+
|
|
82
|
+
`answers` es un snapshot del formulario, no un constructor de campos del CRM.
|
|
83
|
+
|
|
84
|
+
- Máximo 50 claves, cada una de hasta 80 caracteres.
|
|
85
|
+
- Payload total aproximado máximo: 30 KB.
|
|
86
|
+
- No incluyas contraseñas, tokens, datos de pago, documentos adjuntos ni HTML.
|
|
87
|
+
- No dupliques correo o teléfono dentro de `answers`; usa `contact`.
|
|
88
|
+
- Un cambio futuro del formulario no modifica capturas anteriores.
|
|
89
|
+
|
|
90
|
+
## Idempotencia y privacidad
|
|
91
|
+
|
|
92
|
+
- La misma interacción debe reutilizar el mismo `idempotencyKey` hasta obtener
|
|
93
|
+
respuesta. Un nuevo envío voluntario usa una clave nueva.
|
|
94
|
+
- La respuesta es deliberadamente genérica y nunca revela si la persona ya era
|
|
95
|
+
lead o cliente.
|
|
96
|
+
- Si correo y teléfono pertenecen a identidades activas diferentes, Bacano no
|
|
97
|
+
sobrescribe ninguna de ellas y conserva el caso para revisión manual.
|
|
98
|
+
- No llames GraphQL, Hasura ni endpoints internos directamente.
|
|
99
|
+
- No captures un lead si el visitante no aceptó el texto comercial vigente.
|
|
100
|
+
- Una revocación posterior conserva el historial, pero impide comunicaciones
|
|
101
|
+
futuras hasta una nueva aceptación expresa.
|
|
102
|
+
|
|
103
|
+
## Beneficio de primera compra
|
|
104
|
+
|
|
105
|
+
Cuando la campaña activa tiene objetivo `FIRST_PURCHASE_COUPON` y referencia un
|
|
106
|
+
cupón online vigente, Bacano puede encolar el correo de bienvenida después de
|
|
107
|
+
aceptar la captura. Esta operación es completamente del servidor:
|
|
108
|
+
|
|
109
|
+
- el storefront no envía correos ni consulta Resend;
|
|
110
|
+
- no debe afirmar que el cupón ya fue enviado;
|
|
111
|
+
- repetir la misma captura no duplica el beneficio;
|
|
112
|
+
- una falla de correo nunca convierte la captura en error;
|
|
113
|
+
- Bacano revalida autorización, campaña, website, cupón y compras anteriores
|
|
114
|
+
inmediatamente antes del envío.
|
|
115
|
+
|
|
116
|
+
El contrato de `captureLead()` no cambia y no expone estados internos del
|
|
117
|
+
correo. La evidencia operativa queda en `outbound_emails` dentro de Bacano. El
|
|
118
|
+
newsletter masivo y las automatizaciones visuales continúan fuera de alcance.
|
|
119
|
+
|
|
120
|
+
## Checklist para agentes
|
|
121
|
+
|
|
122
|
+
- [ ] Leí este contrato antes de crear el formulario.
|
|
123
|
+
- [ ] Uso un único `BacanoClient` inicializado.
|
|
124
|
+
- [ ] Leo el texto desde `publicConfig.legal.directMarketingConsent`.
|
|
125
|
+
- [ ] La aceptación empieza desmarcada y es explícita.
|
|
126
|
+
- [ ] Uso `client.marketing.captureLead()` y no GraphQL directo.
|
|
127
|
+
- [ ] Conservo la clave de idempotencia durante reintentos.
|
|
128
|
+
- [ ] No envío secretos ni información innecesaria en `answers`.
|
|
129
|
+
- [ ] No afirmo que el correo o el cupón fueron entregados a partir de la
|
|
130
|
+
respuesta genérica de `captureLead()`.
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# Storefront legal settings
|
|
2
|
+
|
|
3
|
+
This document is the canonical contract for legal content exposed by the
|
|
4
|
+
Bacano SDK. Storefronts must use this configuration instead of maintaining
|
|
5
|
+
parallel legal documents in Content or querying GraphQL directly.
|
|
6
|
+
|
|
7
|
+
## Public contract
|
|
8
|
+
|
|
9
|
+
`getWebsitePublicConfig()` returns the current configuration for the resolved
|
|
10
|
+
website:
|
|
11
|
+
|
|
12
|
+
```ts
|
|
13
|
+
const config = await getWebsitePublicConfig(options);
|
|
14
|
+
|
|
15
|
+
config.legal = {
|
|
16
|
+
schemaVersion: 1,
|
|
17
|
+
version: 3,
|
|
18
|
+
contentHash: "...",
|
|
19
|
+
documents: {
|
|
20
|
+
terms: { type: "doc", content: [] },
|
|
21
|
+
shipping: { type: "doc", content: [] },
|
|
22
|
+
returns: { type: "doc", content: [] },
|
|
23
|
+
privacy: { type: "doc", content: [] },
|
|
24
|
+
cookies: { type: "doc", content: [] },
|
|
25
|
+
},
|
|
26
|
+
inquiriesChannel: "privacidad@example.com",
|
|
27
|
+
cookieNotice: {
|
|
28
|
+
enabled: true,
|
|
29
|
+
message: "Usamos cookies propias y de terceros...",
|
|
30
|
+
buttonLabel: "Entendido",
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
All documents and the inquiries channel are optional. Missing documents are
|
|
36
|
+
returned as `null`. Documents use Bacano's rich-text JSON format and must be
|
|
37
|
+
rendered safely; do not inject untrusted HTML.
|
|
38
|
+
|
|
39
|
+
The configuration is included in the existing public-config request. Do not
|
|
40
|
+
add a second request, direct GraphQL access, or local fallback copies of legal
|
|
41
|
+
text.
|
|
42
|
+
|
|
43
|
+
## Informational cookie notice
|
|
44
|
+
|
|
45
|
+
Render the notice only when `config.legal.cookieNotice.enabled` is `true`.
|
|
46
|
+
Display the message and button label supplied by Bacano, allow the visitor to
|
|
47
|
+
close it, and store that dismissal locally if the storefront should avoid
|
|
48
|
+
showing it again in the same browser.
|
|
49
|
+
|
|
50
|
+
The notice is informational. It is not a consent manager and does not gate,
|
|
51
|
+
delay, or reconfigure the GA4, Meta, or Microsoft Clarity runtime. Marketing remains governed by
|
|
52
|
+
`config.marketing`.
|
|
53
|
+
|
|
54
|
+
## Storefront checklist
|
|
55
|
+
|
|
56
|
+
- Preserve existing public legal routes and their SEO behavior.
|
|
57
|
+
- Source terms, shipping, returns, privacy, cookies, and the contact channel only from
|
|
58
|
+
`config.legal`.
|
|
59
|
+
- Handle every nullable document without crashing or inventing content.
|
|
60
|
+
- Remove only the obsolete legal Content keys; keep unrelated Content data.
|
|
61
|
+
- Do not add direct GraphQL calls or duplicate public-config requests.
|
|
62
|
+
- Verify the notice in enabled, disabled, and dismissed states.
|
|
63
|
+
- Verify GA4, Meta, and Clarity continue operating in every notice state.
|
|
64
|
+
- Do not present the informational notice as consent for Clarity. Review the
|
|
65
|
+
privacy rules in `STOREFRONT_CLARITY.md` before activating it.
|
|
66
|
+
- Run typecheck, lint, unit tests, E2E tests, and the production build.
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
# Marketing y medición en storefronts Bacano
|
|
2
|
+
|
|
3
|
+
Este documento es el contrato canónico para agentes y equipos que construyen un
|
|
4
|
+
storefront con `@bacano/sdk`. Una tienda no debe inventar nombres de eventos,
|
|
5
|
+
identificadores, adaptadores ni llamadas directas a proveedores.
|
|
6
|
+
|
|
7
|
+
## Regla principal
|
|
8
|
+
|
|
9
|
+
El storefront expresa hechos mediante `useMarketing().track()`. El SDK decide
|
|
10
|
+
si la medición está activa, sanea la URL y adapta el evento a GA4, Meta y
|
|
11
|
+
Microsoft Clarity. Nunca
|
|
12
|
+
se debe llamar directamente a `gtag`, `dataLayer`, `fbq`, Measurement Protocol
|
|
13
|
+
o Conversions API.
|
|
14
|
+
Clarity también se carga desde este runtime; no se instala su snippet de forma
|
|
15
|
+
paralela.
|
|
16
|
+
|
|
17
|
+
```tsx
|
|
18
|
+
import { useMarketing } from "@bacano/sdk/react";
|
|
19
|
+
|
|
20
|
+
const marketing = useMarketing();
|
|
21
|
+
|
|
22
|
+
marketing.track({
|
|
23
|
+
eventName: "contact",
|
|
24
|
+
contactMethod: "whatsapp",
|
|
25
|
+
contactPlacement: "product_detail",
|
|
26
|
+
});
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Un contacto general usa el mismo contrato sin inventar un producto:
|
|
30
|
+
|
|
31
|
+
```tsx
|
|
32
|
+
marketing.track({
|
|
33
|
+
eventName: "contact",
|
|
34
|
+
contactMethod: "email",
|
|
35
|
+
contactPlacement: "contact_page",
|
|
36
|
+
});
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
La caída o ausencia de marketing nunca puede bloquear catálogo, carrito,
|
|
40
|
+
checkout, pago, autenticación o navegación. En localhost, preview y orígenes no
|
|
41
|
+
canónicos el runtime es un `no-op` seguro.
|
|
42
|
+
|
|
43
|
+
## Identidades estables
|
|
44
|
+
|
|
45
|
+
- `WebsiteMarketingItem.itemId` siempre es `product_variant_id` de Bacano.
|
|
46
|
+
- SKU es información complementaria; nunca reemplaza `itemId`.
|
|
47
|
+
- Un evento de producto debe usar la variante realmente visible o elegida.
|
|
48
|
+
- `order_id` es el `transaction_id` de conversiones autoritativas.
|
|
49
|
+
- Los nombres, precios, cantidades y atributos deben corresponder al mismo
|
|
50
|
+
instante y variante.
|
|
51
|
+
|
|
52
|
+
Estas reglas permiten que eventos, feeds de catálogo, públicos y conversiones
|
|
53
|
+
se relacionen sin depender del slug o de la ruta del storefront.
|
|
54
|
+
|
|
55
|
+
## Cuándo emitir cada evento
|
|
56
|
+
|
|
57
|
+
| Evento | Emitir después de | No emitir por |
|
|
58
|
+
| ------------------- | -------------------------------------- | ---------------------------------- |
|
|
59
|
+
| `page_view` | Navegación resuelta y renderizada | Cada render de React |
|
|
60
|
+
| `search` | Resultados válidos para una búsqueda | Cada tecla |
|
|
61
|
+
| `view_item_list` | Lista visible con items reales | Skeleton o respuesta fallida |
|
|
62
|
+
| `select_item` | Selección que inicia navegación válida | Hover |
|
|
63
|
+
| `view_item` | Producto y variante cargados | Cambio irrelevante de estado |
|
|
64
|
+
| `add_to_cart` | Carrito actualizado | Clic antes de conocer el resultado |
|
|
65
|
+
| `remove_from_cart` | Carrito actualizado | Intento fallido |
|
|
66
|
+
| `view_cart` | Carrito abierto con su contenido | Montaje global de providers |
|
|
67
|
+
| `begin_checkout` | Carrito sincronizado con Bacano | Abrir un modal sin sincronizar |
|
|
68
|
+
| `add_shipping_info` | Entrega elegida o confirmada | Valores predeterminados invisibles |
|
|
69
|
+
| `add_payment_info` | Método elegido o confirmado | Método aún inválido |
|
|
70
|
+
| `contact` | Apertura intencional del canal | Render del botón |
|
|
71
|
+
|
|
72
|
+
`purchase`, `order_placed` y `refund` son eventos autoritativos del backend. El
|
|
73
|
+
storefront nunca los emite desde una página de éxito o respuesta de pago.
|
|
74
|
+
|
|
75
|
+
## Payload de productos
|
|
76
|
+
|
|
77
|
+
```tsx
|
|
78
|
+
marketing.track({
|
|
79
|
+
eventName: "add_to_cart",
|
|
80
|
+
value: unitPrice * quantity,
|
|
81
|
+
items: [
|
|
82
|
+
{
|
|
83
|
+
itemId: selectedVariant.id,
|
|
84
|
+
itemName: product.name,
|
|
85
|
+
sku: selectedVariant.sku,
|
|
86
|
+
price: unitPrice,
|
|
87
|
+
quantity,
|
|
88
|
+
itemBrand: product.brand?.name,
|
|
89
|
+
itemCategory: product.category,
|
|
90
|
+
itemVariant: selectedVariant.attributes
|
|
91
|
+
.map((attribute) => attribute.value)
|
|
92
|
+
.join(" / "),
|
|
93
|
+
},
|
|
94
|
+
],
|
|
95
|
+
});
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
- `value` representa el valor total de los items del evento.
|
|
99
|
+
- `price` es unitario y `quantity` es positiva.
|
|
100
|
+
- No se deben enviar productos ocultos, precios aproximados ni la primera
|
|
101
|
+
variante por conveniencia si el comprador eligió otra.
|
|
102
|
+
- El storefront no envía PII dentro de eventos de navegador.
|
|
103
|
+
|
|
104
|
+
## Contactos y WhatsApp
|
|
105
|
+
|
|
106
|
+
Todo enlace de WhatsApp, teléfono o correo debe emitir `contact`. Esto mide la
|
|
107
|
+
apertura del canal; no demuestra que el comprador haya enviado un mensaje o que
|
|
108
|
+
el comercio lo haya atendido.
|
|
109
|
+
|
|
110
|
+
Valores de `contactMethod`:
|
|
111
|
+
|
|
112
|
+
- `whatsapp`
|
|
113
|
+
- `phone`
|
|
114
|
+
- `email`
|
|
115
|
+
|
|
116
|
+
Valores de `contactPlacement`:
|
|
117
|
+
|
|
118
|
+
- `product_detail`
|
|
119
|
+
- `floating`
|
|
120
|
+
- `header`
|
|
121
|
+
- `footer`
|
|
122
|
+
- `contact_page`
|
|
123
|
+
- `checkout`
|
|
124
|
+
- `other` únicamente cuando ninguna ubicación canónica describe el contacto
|
|
125
|
+
|
|
126
|
+
El campo es opcional, no acepta textos visibles ni valores libres y el runtime
|
|
127
|
+
lo ignora cuando el evento no es `contact`. Una ubicación nueva debe añadirse
|
|
128
|
+
primero al contrato oficial del SDK.
|
|
129
|
+
|
|
130
|
+
Un contacto asociado a un producto debe incluir la variante, cantidad y valor:
|
|
131
|
+
|
|
132
|
+
```tsx
|
|
133
|
+
marketing.track({
|
|
134
|
+
eventName: "contact",
|
|
135
|
+
contactMethod: "whatsapp",
|
|
136
|
+
contactPlacement: "product_detail",
|
|
137
|
+
value: unitPrice * quantity,
|
|
138
|
+
items: [
|
|
139
|
+
{
|
|
140
|
+
itemId: selectedVariant.id,
|
|
141
|
+
itemName: product.name,
|
|
142
|
+
sku: selectedVariant.sku,
|
|
143
|
+
price: unitPrice,
|
|
144
|
+
quantity,
|
|
145
|
+
itemBrand: product.brand?.name,
|
|
146
|
+
itemCategory: product.category,
|
|
147
|
+
itemVariant: variantLabel,
|
|
148
|
+
},
|
|
149
|
+
],
|
|
150
|
+
});
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Un botón flotante o enlace general no debe atribuirse artificialmente a un
|
|
154
|
+
producto. GA4 recibe `method`, `contact_placement`, `items` y `value`; Meta
|
|
155
|
+
recibe el evento estándar `Contact`, `contact_method`, `contact_placement`,
|
|
156
|
+
`content_ids` y `contents` cuando existen items.
|
|
157
|
+
|
|
158
|
+
Clarity registra automáticamente el comportamiento visual de la sesión y
|
|
159
|
+
recibe `contact` como evento personalizado para filtrar grabaciones. No recibe
|
|
160
|
+
PII, items ni etiquetas construidas por el storefront. La guía específica está
|
|
161
|
+
en [`STOREFRONT_CLARITY.md`](./STOREFRONT_CLARITY.md).
|
|
162
|
+
|
|
163
|
+
## Fuente única y deduplicación
|
|
164
|
+
|
|
165
|
+
- Cada interacción tiene un único propietario de instrumentación.
|
|
166
|
+
- No se combinan handlers locales con seguimiento automático duplicado.
|
|
167
|
+
- `page_view` tiene una sola fuente para navegación inicial y SPA.
|
|
168
|
+
- Un cambio de cantidad puede expresarse como diferencia real de carrito; no se
|
|
169
|
+
vuelve a emitir todo el carrito como nuevas altas.
|
|
170
|
+
- Los efectos de React deben depender de identidades primitivas estables o
|
|
171
|
+
contar con una guarda explícita para evitar duplicados.
|
|
172
|
+
|
|
173
|
+
## URLs, atribución y privacidad
|
|
174
|
+
|
|
175
|
+
- No enviar URLs construidas manualmente; el runtime usa y sanea la ubicación
|
|
176
|
+
actual cuando `pageLocation` se omite.
|
|
177
|
+
- Nunca incluir email, teléfono, dirección, documento, tokens de pedido,
|
|
178
|
+
autenticación, OAuth o pago en nombres, URLs o parámetros.
|
|
179
|
+
- La atribución se obtiene con `marketing.getAttribution()` y se entrega al
|
|
180
|
+
contrato oficial de carrito/checkout.
|
|
181
|
+
- La autorización publicitaria del checkout es opcional, separada y nunca
|
|
182
|
+
bloquea la compra.
|
|
183
|
+
- Los datos de contacto solo pueden normalizarse y hashearse en el backend bajo
|
|
184
|
+
las reglas de autorización de Bacano.
|
|
185
|
+
|
|
186
|
+
## Componentes que todo storefront debe auditar
|
|
187
|
+
|
|
188
|
+
- Provider raíz y una única fuente de `page_view`.
|
|
189
|
+
- Buscador, resultados, categorías, colecciones y tarjetas de producto.
|
|
190
|
+
- Detalle, selección de variante y recomendaciones si están dentro del alcance.
|
|
191
|
+
- Carrito: alta, baja, cantidad y vista.
|
|
192
|
+
- Checkout: inicio, entrega, pago y consentimiento.
|
|
193
|
+
- Todos los enlaces de WhatsApp, teléfono y correo.
|
|
194
|
+
- Preview: marketing desactivado en todo su árbol.
|
|
195
|
+
|
|
196
|
+
## Content Security Policy
|
|
197
|
+
|
|
198
|
+
La CSP pertenece al storefront y se genera durante el build o en el hosting.
|
|
199
|
+
El runtime no puede ampliar una cabecera que el navegador ya recibió. Se deben
|
|
200
|
+
incluir únicamente los proveedores activos del website y conservar los demás
|
|
201
|
+
orígenes que realmente use la aplicación.
|
|
202
|
+
|
|
203
|
+
Para GA4 y destinos de Google Ads vinculados al mismo Google tag:
|
|
204
|
+
|
|
205
|
+
```text
|
|
206
|
+
script-src https://www.googletagmanager.com
|
|
207
|
+
connect-src https://www.googletagmanager.com
|
|
208
|
+
https://www.google-analytics.com
|
|
209
|
+
https://*.google-analytics.com
|
|
210
|
+
https://analytics.google.com
|
|
211
|
+
https://*.analytics.google.com
|
|
212
|
+
https://stats.g.doubleclick.net
|
|
213
|
+
https://googleads.g.doubleclick.net
|
|
214
|
+
https://www.googleadservices.com
|
|
215
|
+
img-src https://www.google-analytics.com
|
|
216
|
+
https://googleads.g.doubleclick.net
|
|
217
|
+
https://www.googleadservices.com
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
`https://www.googletagmanager.com` debe estar tanto en `script-src` como en
|
|
221
|
+
`connect-src`: Google tag puede usar `/td` mediante `fetch`, aunque el script
|
|
222
|
+
principal ya haya cargado correctamente.
|
|
223
|
+
|
|
224
|
+
Para Meta Pixel:
|
|
225
|
+
|
|
226
|
+
```text
|
|
227
|
+
script-src https://connect.facebook.net
|
|
228
|
+
connect-src https://www.facebook.com https://graph.facebook.com
|
|
229
|
+
img-src https://www.facebook.com
|
|
230
|
+
frame-src https://www.facebook.com
|
|
231
|
+
form-action 'self' https://www.facebook.com
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
Meta puede enviar `/tr/` mediante un formulario POST dirigido a un iframe
|
|
235
|
+
oculto. Permitir `connect-src` o `img-src` no sustituye `form-action` y
|
|
236
|
+
`frame-src`. Usar el origen exacto; no abrir `*.facebook.com` ni comodines de
|
|
237
|
+
infraestructura para silenciar errores.
|
|
238
|
+
|
|
239
|
+
Para Microsoft Clarity:
|
|
240
|
+
|
|
241
|
+
```text
|
|
242
|
+
script-src https://*.clarity.ms
|
|
243
|
+
connect-src https://*.clarity.ms https://c.bing.com
|
|
244
|
+
img-src https://*.clarity.ms https://c.bing.com
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
Clerk, Wompi, videos y cualquier otro proveedor conservan su propia lista de
|
|
248
|
+
orígenes. La CSP final es la unión mínima de las capacidades efectivamente
|
|
249
|
+
montadas. Probarla en consola, Network y, cuando sea posible, primero mediante
|
|
250
|
+
`Content-Security-Policy-Report-Only`.
|
|
251
|
+
|
|
252
|
+
## Validación obligatoria
|
|
253
|
+
|
|
254
|
+
Antes de declarar un storefront listo:
|
|
255
|
+
|
|
256
|
+
1. Ejecutar typecheck, lint, unit tests y build.
|
|
257
|
+
2. Probar que acciones fallidas no emitan eventos exitosos.
|
|
258
|
+
3. Confirmar que una acción produzca un solo evento.
|
|
259
|
+
4. Verificar IDs, variantes, cantidades, valores y moneda.
|
|
260
|
+
5. Revisar URLs para confirmar que no contienen tokens ni PII.
|
|
261
|
+
6. Validar GA4 en DebugView o tiempo real.
|
|
262
|
+
7. Validar Meta en Test Events y revisar `content_ids`.
|
|
263
|
+
8. Confirmar que preview y localhost no envíen eventos reales.
|
|
264
|
+
9. Si existe catálogo publicitario, confirmar que cada `itemId` coincide con el
|
|
265
|
+
`id` de la misma variante en la fuente de Meta o Google.
|
|
266
|
+
10. Si Clarity está activo, validar tag, recolección, sesiones y eventos
|
|
267
|
+
personalizados en el dominio canónico.
|
|
268
|
+
11. Confirmar que la CSP no bloquea los transportes reales de GA4, Meta o
|
|
269
|
+
Clarity, incluidos Google tag `/td` y Meta `/tr/` por POST/iframe.
|
|
270
|
+
|
|
271
|
+
## Checklist corto para agentes
|
|
272
|
+
|
|
273
|
+
- [ ] Leí este contrato antes de instrumentar.
|
|
274
|
+
- [ ] Uso únicamente `useMarketing().track()`.
|
|
275
|
+
- [ ] Uso `product_variant_id` como `itemId`.
|
|
276
|
+
- [ ] Emito eventos después del éxito, no antes.
|
|
277
|
+
- [ ] Instrumenté todos los contactos con método y ubicación.
|
|
278
|
+
- [ ] Los contactos de producto incluyen item, cantidad y valor.
|
|
279
|
+
- [ ] No emito conversiones autoritativas desde el navegador.
|
|
280
|
+
- [ ] No envío PII, tokens o secretos.
|
|
281
|
+
- [ ] No duplico `page_view` ni eventos por renders.
|
|
282
|
+
- [ ] Validé GA4, Meta, Clarity, preview y degradación segura.
|
|
283
|
+
|
|
284
|
+
La guía de gobierno, persistencia y eventos autoritativos vive en
|
|
285
|
+
`docs/websites/MARKETING_MEASUREMENT_STOREFRONTS_CHECKLIST.md` dentro del
|
|
286
|
+
repositorio Bacano. Este archivo es el contrato distribuible para storefronts.
|