@bacano/sdk 1.1.20 → 1.2.2
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 +4 -0
- package/STOREFRONT_FORMS.md +7 -0
- package/STOREFRONT_MARKETING.md +161 -6
- package/index.cjs +322 -86
- package/index.js +322 -86
- package/marketing/browser-adapters.d.ts +30 -0
- package/marketing/clarity.d.ts +1 -1
- package/marketing/form-interactions.d.ts +12 -0
- package/marketing/gtm.d.ts +6 -0
- package/marketing/index.d.ts +2 -0
- package/marketing/interactions.d.ts +19 -0
- package/marketing/types.d.ts +5 -2
- package/package.json +1 -1
- package/react/cta-tracking.d.ts +5 -0
- package/react/form-sections.d.ts +16 -0
- package/react/form-tracking.d.ts +11 -0
- package/react/forms.cjs +230 -93
- package/react/forms.d.ts +6 -2
- package/react/forms.js +222 -85
- package/react/marketing-provider.d.ts +2 -1
- package/react.cjs +411 -136
- package/react.d.ts +1 -0
- package/react.js +394 -119
- package/skills/bacano-web/SKILL.md +5 -0
- package/skills/bacano-web/references/forms.md +5 -0
- package/skills/bacano-web/references/interactions.md +147 -0
- package/skills/bacano-web/references/report.md +5 -0
- package/types.d.ts +4 -0
|
@@ -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.
|
package/types.d.ts
CHANGED
|
@@ -267,6 +267,10 @@ export interface WebsiteMarketingPublicConfig {
|
|
|
267
267
|
clarity?: {
|
|
268
268
|
projectId: string;
|
|
269
269
|
} | null;
|
|
270
|
+
/** Optional web container. Does not replace native GA4/Meta destinations. */
|
|
271
|
+
gtm?: {
|
|
272
|
+
containerId: string;
|
|
273
|
+
} | null;
|
|
270
274
|
};
|
|
271
275
|
}
|
|
272
276
|
export type WebsiteCustomerIdentifierType = "AUTO" | "EMAIL" | "PHONE" | "DOCUMENT";
|