@bacano/sdk 1.1.20 → 1.2.1

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.
@@ -55,6 +55,11 @@ otros contratos únicamente cuando esas capacidades existan. Leer
55
55
  Si existen formularios administrados desde Bacano, leer obligatoriamente
56
56
  `STOREFRONT_FORMS.md` y
57
57
  [references/forms.md](references/forms.md) antes de auditarlos o modificarlos.
58
+ Cuando marketing esté activo y existan CTA o formularios, leer
59
+ [references/interactions.md](references/interactions.md): inventariar los CTA,
60
+ asignar un propietario por evento y usar los helpers oficiales del SDK. Esta
61
+ instrumentación aplica tanto a webs informativas como a tiendas; no medir
62
+ clics decorativos ni activar proveedores sin autorización.
58
63
 
59
64
  ## 3. Auditar sin alterar el sistema
60
65
 
@@ -60,6 +60,11 @@ ya resuelve la identidad; no ejecutar una segunda captura por campaña.
60
60
 
61
61
  ## Verificaciones obligatorias
62
62
 
63
+ Con marketing activo, aplicar también [interactions.md](interactions.md).
64
+ `BacanoForm` ya instrumenta su ciclo: no añadir listeners paralelos. Renderers
65
+ personalizados usan `useBacanoFormTracking`; modal/drawer debe comunicar su
66
+ visibilidad real. Verificar la distinción entre intento, persistencia y lead.
67
+
63
68
  - Formulario activo: estructura, labels, required, ayudas, opciones y envío.
64
69
  - Formulario pausado e inexistente: mensaje seguro y ningún envío.
65
70
  - Versión desactualizada: refresh oficial y conservación controlada de datos.
@@ -0,0 +1,147 @@
1
+ # Instrumentación de CTA y formularios
2
+
3
+ Requiere SDK **1.2.0 o posterior**, una vez publicado. Leer los eventos y reglas
4
+ de `STOREFRONT_MARKETING.md`. No instalar snippets ni crear providers por página.
5
+
6
+ ## Migración
7
+
8
+ 1. Actualizar el SDK cuando Bacano confirme su publicación. Reinstalar la skill
9
+ con `pnpm exec bacano-web install` y verificar con `bacano-web check`.
10
+ 2. Conservar el único `BacanoProvider` y `BacanoMarketingProvider` existentes.
11
+ Su `publicConfig` viene de Bacano, no de IDs de proveedores hardcodeados.
12
+ 3. Inventariar CTA/formularios por ruta y asignar claves estables: `hero-contact`,
13
+ `pricing-contact`, `sticky-contact`. No nombres de personas ni respuestas.
14
+ 4. Sustituir listeners paralelos por los helpers. No duplicar los eventos que
15
+ `BacanoForm` ya conoce. Pasar `visible={open}` si permanece montado oculto.
16
+ 5. Revisar GA4 Enhanced Measurement → Form interactions para evitar otra fuente
17
+ de `form_start`. No cambiar cuentas remotas sin permiso.
18
+ 6. Probar build estático, eventos únicos, preview y exclusiones. Confirmar
19
+ recepción en cada proveedor por separado antes de afirmarla.
20
+
21
+ ## Landing informativa
22
+
23
+ Dentro de los providers globales. `initialForm` puede obtenerse durante el build
24
+ mediante el cliente oficial para conservar Static First.
25
+
26
+ ```tsx
27
+ "use client";
28
+ import { useBacanoCta } from "@bacano/sdk/react";
29
+ import { BacanoForm } from "@bacano/sdk/react/forms";
30
+ import type { WebsitePublishedForm } from "@bacano/sdk";
31
+
32
+ export function ContactSection({ initialForm }: { initialForm: WebsitePublishedForm }) {
33
+ const track = useBacanoCta({
34
+ ctaId: "hero-contact", placement: "hero", formKey: initialForm.key,
35
+ });
36
+ return <>
37
+ <a href="#contact" onClick={track}>Hablemos de tu proyecto</a>
38
+ <section id="contact">
39
+ <BacanoForm formKey={initialForm.key} initialForm={initialForm} />
40
+ </section>
41
+ </>;
42
+ }
43
+ ```
44
+
45
+ Un clic no emite otra apertura si el formulario ya estaba presentado. Para
46
+ modal/drawer pasar su estado `open` a `BacanoForm.visible`. El helper no gestiona
47
+ foco, animación ni navegación. Una preselección externa no cuenta como edición.
48
+
49
+ ## Renderizador personalizado, incluido multipaso
50
+
51
+ `useBacanoFormTracking(formKey, { visible })` devuelve un controlador estable:
52
+
53
+ - `start()`: tras una edición real no vacía, nunca por focus o inicialización.
54
+ No pasar el valor del campo al controlador.
55
+ - `stepComplete(step.key)`: únicamente tras validar y avanzar.
56
+ - `beginSubmit()`: antes de validar el envío final. Si devuelve false, ya existe
57
+ un intento pendiente y no debe enviarse otra solicitud.
58
+ - `fail("validation" | "network" | "rejected" | "unavailable")`: termina ese
59
+ intento. Nunca pasar `error.message` ni mensajes internos.
60
+ - `complete(result)`: pasar el resultado oficial tras verificar `accepted`.
61
+ No registra éxito sin UUID de persistencia.
62
+
63
+ Este hook se conecta al renderer multipaso existente; no duplica definiciones
64
+ de campos ni opciones:
65
+
66
+ ```tsx
67
+ import { useRef } from "react";
68
+ import { WebsiteError } from "@bacano/sdk";
69
+ import { useBacanoState } from "@bacano/sdk/react";
70
+ import { useBacanoForm, useBacanoFormTracking } from "@bacano/sdk/react/forms";
71
+ import type { BacanoFormValues } from "@bacano/sdk/react/forms";
72
+
73
+ export function useContactFlow(formKey: string, visible: boolean) {
74
+ const { client } = useBacanoState();
75
+ const definition = useBacanoForm(formKey);
76
+ const tracking = useBacanoFormTracking(formKey, {
77
+ visible: visible && definition.form?.status === "ACTIVE",
78
+ });
79
+ const submissionKey = useRef(crypto.randomUUID());
80
+ async function submit(values: BacanoFormValues, valid: boolean) {
81
+ if (!tracking.beginSubmit()) return;
82
+ if (!valid) { tracking.fail("validation"); return; }
83
+ if (!client || !definition.form) { tracking.fail("unavailable"); return; }
84
+ try {
85
+ const form = definition.form;
86
+ const result = await client.forms.submitResponse({
87
+ formKey: form.key, formVersion: form.version,
88
+ definitionHash: form.definitionHash,
89
+ idempotencyKey: submissionKey.current, values,
90
+ });
91
+ if (!result.accepted) { tracking.fail("rejected"); return result; }
92
+ tracking.complete(result);
93
+ return result;
94
+ } catch (error) {
95
+ tracking.fail(error instanceof WebsiteError && error.code === "NETWORK_ERROR"
96
+ ? "network" : "rejected");
97
+ throw error; // La UI ofrece reintento conservando datos y submissionKey.
98
+ }
99
+ }
100
+ return { ...definition, tracking, submit };
101
+ }
102
+ ```
103
+
104
+ El renderer lee `form.definition.steps`, secciones y campos oficiales. Tras
105
+ validar un avance llama a `tracking.stepComplete(step.key)`; al editar llama
106
+ a `tracking.start()` y al finalizar llama a `submit`. No usar este controlador
107
+ alrededor de `BacanoForm`, que ya posee esos eventos. Mantener una instancia por
108
+ formulario; si cambia su key, remontar el renderer.
109
+
110
+ ## Tienda virtual
111
+
112
+ La medición comercial existente no cambia. Consultar un producto no significa
113
+ `add_to_cart`, ni completar un formulario equivale a `purchase`.
114
+
115
+ ```tsx
116
+ "use client";
117
+ import { useState } from "react";
118
+ import { useBacanoCta } from "@bacano/sdk/react";
119
+ import { BacanoForm } from "@bacano/sdk/react/forms";
120
+
121
+ export function ProductInquiry() {
122
+ const [open, setOpen] = useState(false);
123
+ const track = useBacanoCta({
124
+ ctaId: "product-inquiry", placement: "product_detail", formKey: "asesoria",
125
+ });
126
+ return <>
127
+ <button onClick={() => { track(); setOpen(true); }}>Solicitar asesoría</button>
128
+ <section hidden={!open} aria-label="Asesoría">
129
+ <button onClick={() => setOpen(false)}>Cerrar</button>
130
+ <BacanoForm formKey="asesoria" visible={open} />
131
+ </section>
132
+ </>;
133
+ }
134
+ ```
135
+
136
+ ## Evidencia requerida
137
+
138
+ - Una acción, un propietario; sin emisiones adicionales por renders.
139
+ - Abrir/cerrar/reabrir; edición manual frente a preselección programática.
140
+ - Validación fallida y error de red con reintento, sin falso éxito.
141
+ - Honeypot/aceptación genérica: mismo mensaje, sin conversión.
142
+ - `utm_content` y demás atribución después de navegación interna.
143
+ - Preview, marketing desactivado y consentimiento pendiente: sin emisiones.
144
+ - Proveedores bloqueados: formulario y navegación siguen funcionando.
145
+ - Separar prueba automatizada, transporte y panel del proveedor. No afirmar
146
+ un embudo completo dentro de Bacano: se instrumentan interacciones externas
147
+ y se conserva la atribución de las respuestas oficiales.
@@ -65,6 +65,11 @@ que no hubo requests cuando no se capturó un trace real.
65
65
 
66
66
  ## Riesgos y próximos pasos
67
67
 
68
+ Cuando haya marketing activo, incluir inventario `CTA/formKey → ubicación →
69
+ evento → propietario → evidencia`. Separar pruebas automatizadas, transporte
70
+ observado y recepción confirmada en los paneles. Enumerar interacciones sin
71
+ instrumentar; un script cargado no demuestra que el evento llegó al proveedor.
72
+
68
73
  Ordenar por severidad. Cada hallazgo debe incluir alcance, causa confirmada o
69
74
  hipótesis explícita, corrección propuesta y prueba de cierre. No mezclar
70
75
  mejoras opcionales con bloqueos de salida.