@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,109 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: bacano-web
|
|
3
|
+
description: Audita y prepara sitios construidos con @bacano/sdk antes de publicarlos o después de un despliegue. Usar cuando se solicite evaluar una web Bacano, revisar si está lista para producción, investigar errores página por página, validar SEO, rendimiento, seguridad, legal, medición, Content, autenticación o, en tiendas virtuales, catálogo, carrito, checkout, pagos y pedidos.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Bacano Web
|
|
7
|
+
|
|
8
|
+
Evaluar el sitio con evidencia del repositorio, del build y, cuando exista, del
|
|
9
|
+
dominio desplegado. No declarar el sitio listo basándose únicamente en una
|
|
10
|
+
revisión visual.
|
|
11
|
+
|
|
12
|
+
## 1. Confirmar el tipo de sitio
|
|
13
|
+
|
|
14
|
+
Antes de auditar, confirmar uno de estos alcances:
|
|
15
|
+
|
|
16
|
+
- **Web informativa**: contenido, formularios y contacto, sin compra completa.
|
|
17
|
+
- **Tienda virtual**: catálogo vendible y flujo de carrito/checkout/pago.
|
|
18
|
+
|
|
19
|
+
Si el usuario no lo indicó expresamente, preguntar exactamente una pregunta
|
|
20
|
+
corta y esperar: **“¿Esta web será informativa o una tienda virtual?”**
|
|
21
|
+
|
|
22
|
+
No aplicar la lista transaccional a una web informativa. Si un sitio híbrido
|
|
23
|
+
permite comprar, clasificarlo como tienda virtual.
|
|
24
|
+
|
|
25
|
+
Después construir una matriz de alcance con las capacidades detectadas como
|
|
26
|
+
`ACTIVA`, `INACTIVA` o `NO_APLICA`: autenticación, formularios comerciales,
|
|
27
|
+
GA4, Meta, Clarity, catálogo publicitario, cupones, medios de pago y métodos de
|
|
28
|
+
entrega. Obtenerla primero del código y de la configuración pública. Preguntar
|
|
29
|
+
solo si una capacidad realmente usada en el lanzamiento continúa ambigua.
|
|
30
|
+
|
|
31
|
+
Una capacidad inactiva no bloquea la salida y sus pruebas son `NO APLICA`, no
|
|
32
|
+
`NO VERIFICADO`. Una capacidad activa y visible debe probarse en proporción a
|
|
33
|
+
su riesgo.
|
|
34
|
+
|
|
35
|
+
## 2. Leer los contratos aplicables
|
|
36
|
+
|
|
37
|
+
Leer siempre [references/common.md](references/common.md). Después leer solo:
|
|
38
|
+
|
|
39
|
+
- [references/informational.md](references/informational.md) para web informativa.
|
|
40
|
+
- [references/storefront.md](references/storefront.md) para tienda virtual.
|
|
41
|
+
|
|
42
|
+
Si `@bacano/sdk` está disponible, leer sus contratos canónicos aplicables antes
|
|
43
|
+
de proponer cambios. Como mínimo revisar `STOREFRONT_LEGAL.md`,
|
|
44
|
+
`STOREFRONT_MARKETING.md` y `STOREFRONT_PERFORMANCE.md`; añadir
|
|
45
|
+
`AUTHENTICATION.md`, `STOREFRONT_LEADS.md`, `STOREFRONT_CLARITY.md` y
|
|
46
|
+
`STOREFRONT_PREVIEW.md` únicamente cuando esas capacidades existan. Leer
|
|
47
|
+
`STOREFRONT_CONSTRUCTION.md` cuando el website permita ocultarse temporalmente.
|
|
48
|
+
|
|
49
|
+
## 3. Auditar sin alterar el sistema
|
|
50
|
+
|
|
51
|
+
Por defecto realizar una auditoría de solo lectura. No editar código, cambiar
|
|
52
|
+
configuración externa, publicar, crear cuentas, enviar formularios reales ni
|
|
53
|
+
ejecutar compras/pagos sin autorización explícita.
|
|
54
|
+
|
|
55
|
+
1. Inspeccionar stack, scripts, rutas, variables públicas esperadas y versión
|
|
56
|
+
efectiva de `@bacano/sdk`.
|
|
57
|
+
2. Registrar la matriz de capacidades y cuáles son críticas para el
|
|
58
|
+
lanzamiento actual.
|
|
59
|
+
3. Identificar una sola fuente para Bacano, Content, legal, marketing y auth.
|
|
60
|
+
4. Ejecutar typecheck, lint, pruebas y build usando los comandos del proyecto.
|
|
61
|
+
5. Comparar router, salida del build y sitemap; ninguna ruta pública debe
|
|
62
|
+
omitirse silenciosamente.
|
|
63
|
+
6. Inspeccionar HTML generado, metadata, JSON-LD, media y enlaces internos.
|
|
64
|
+
7. Si existe un dominio, auditar sus rutas reales y la consola del navegador.
|
|
65
|
+
8. Probar vistas representativas desktop y mobile.
|
|
66
|
+
9. Retestar individualmente cada hallazgo antes de considerarlo real.
|
|
67
|
+
|
|
68
|
+
Para una comprobación HTTP inicial puede ejecutarse:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
node <skill-dir>/scripts/audit-http.mjs https://ejemplo.com --max 500
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
El script no reemplaza pruebas de navegador, accesibilidad, interacción ni
|
|
75
|
+
flujos autenticados.
|
|
76
|
+
|
|
77
|
+
Si esta skill se leyó directamente dentro de `node_modules` y aún no está
|
|
78
|
+
instalada en el proyecto, ejecutar `pnpm exec bacano-web install` antes de
|
|
79
|
+
continuar. Verificar después con `pnpm exec bacano-web check`.
|
|
80
|
+
|
|
81
|
+
## 4. Evitar falsos positivos
|
|
82
|
+
|
|
83
|
+
- Limitar una auditoría masiva a dos navegadores concurrentes y espaciar las
|
|
84
|
+
rutas. Un `429` provocado por el auditor no es un defecto del storefront.
|
|
85
|
+
- Repetir después de la ventana de rate limit cualquier `429`, recurso
|
|
86
|
+
cross-origin o fallo transitorio.
|
|
87
|
+
- No reportar `net::ERR_ABORTED` aislado como error si corresponde a beacons
|
|
88
|
+
cancelados al cerrar o navegar la página.
|
|
89
|
+
- Distinguir “no verificado” de “falló”. Login, pago, correo y webhooks no se
|
|
90
|
+
consideran aprobados si no se ejecutó el flujo real.
|
|
91
|
+
- No recomendar comodines amplios en CSP para silenciar la consola.
|
|
92
|
+
|
|
93
|
+
## 5. Entregar un cierre verificable
|
|
94
|
+
|
|
95
|
+
Usar [references/report.md](references/report.md). Cada control debe quedar como
|
|
96
|
+
`✅ APROBADO`, `⚠️ ADVERTENCIA`, `❌ FALLÓ`, `➖ NO APLICA` o
|
|
97
|
+
`🧪 NO VERIFICADO`, con evidencia concreta. Mantener siempre la palabra del
|
|
98
|
+
estado junto al emoji para que el informe siga siendo buscable y procesable.
|
|
99
|
+
|
|
100
|
+
Clasificar controles por su efecto en la salida:
|
|
101
|
+
|
|
102
|
+
1. `P0 · Gate`: bloquea publicación si falla; si no se pudo verificar, el
|
|
103
|
+
dictamen queda incompleto.
|
|
104
|
+
2. `P1 · Calidad`: requisito profesional que permite salir solo con riesgo
|
|
105
|
+
explícito y responsable.
|
|
106
|
+
3. `P2 · Mejora`: evolución deseable que nunca cambia por sí sola el dictamen.
|
|
107
|
+
|
|
108
|
+
No decir “listo para producción” mientras exista un `❌ FALLÓ` P0 o una
|
|
109
|
+
capacidad esencial activa permanezca `🧪 NO VERIFICADO`.
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# Controles comunes
|
|
2
|
+
|
|
3
|
+
Aplicar `P0` solo a capacidades publicadas o comprometidas para el lanzamiento.
|
|
4
|
+
Usar `P1` para calidad profesional y `P2` para evolución. No convertir una
|
|
5
|
+
función apagada en un bloqueo artificial.
|
|
6
|
+
|
|
7
|
+
## Repositorio y arquitectura
|
|
8
|
+
|
|
9
|
+
- Confirmar rama, estado de Git, lockfile y versión instalada del SDK.
|
|
10
|
+
- Identificar framework, modo estático/híbrido/SSR, hosting y dominio canónico.
|
|
11
|
+
- Verificar un único cliente/provider de Bacano y ausencia de GraphQL directo.
|
|
12
|
+
- Si el sitio admite modo construcción, verificar `BacanoWebsiteGate` por fuera
|
|
13
|
+
de auth y marketing, actualización en runtime y vista previa temporal.
|
|
14
|
+
- Confirmar que fallos de Content, marketing o servicios externos degradan sin
|
|
15
|
+
vaciar la página ni bloquear navegación.
|
|
16
|
+
- Ejecutar los controles definidos por el proyecto: instalación reproducible,
|
|
17
|
+
typecheck, lint, pruebas, E2E disponibles y build de producción.
|
|
18
|
+
|
|
19
|
+
## Rutas y contenido
|
|
20
|
+
|
|
21
|
+
- Inventariar rutas desde sitemap, export estático y router; no depender de una
|
|
22
|
+
muestra manual arbitraria.
|
|
23
|
+
- Comparar rutas declaradas, páginas generadas y URLs del sitemap. Explicar
|
|
24
|
+
cualquier diferencia y fallar si el build omite contenido público de forma
|
|
25
|
+
silenciosa.
|
|
26
|
+
- Verificar HTTP, errores de consola, excepciones, recursos fallidos, enlaces
|
|
27
|
+
internos, 404, redirecciones y canonical.
|
|
28
|
+
- Comprobar que preview, callbacks, cuenta, checkout y respuestas de pago no se
|
|
29
|
+
indexen cuando corresponda.
|
|
30
|
+
- Validar estados vacío, loading, error y ausencia de Content opcional.
|
|
31
|
+
|
|
32
|
+
## SEO y compartición
|
|
33
|
+
|
|
34
|
+
- Título, descripción, canonical, robots, Open Graph y Twitter por ruta.
|
|
35
|
+
- `robots.txt`, sitemap con URLs canónicas y ausencia de rutas privadas.
|
|
36
|
+
- Jerarquía de encabezados, idioma, texto alternativo, enlaces internos y
|
|
37
|
+
JSON-LD coherente con el contenido visible.
|
|
38
|
+
- Confirmar 404 real, canonical único, ausencia de slugs/URLs duplicadas y
|
|
39
|
+
redirección consistente entre `www`, dominio raíz y HTTP/HTTPS.
|
|
40
|
+
- Usar `hreflang` solo cuando existan versiones regionales o de idioma reales.
|
|
41
|
+
- Favicon, iconos, manifest y vista previa al compartir.
|
|
42
|
+
|
|
43
|
+
## Rendimiento y media
|
|
44
|
+
|
|
45
|
+
- HTML inicial útil; no publicar únicamente skeletons.
|
|
46
|
+
- Imágenes con dimensiones, `srcset`, `sizes`, formatos optimizados y lazy load
|
|
47
|
+
bajo el pliegue. No construir transformaciones de Storage manualmente.
|
|
48
|
+
- Revisar desktop y mobile, DPR 1/2, CLS, LCP, fuentes y JavaScript inicial.
|
|
49
|
+
Tratar una medición sintética aislada como diagnóstico, no como verdad de
|
|
50
|
+
campo; repetir y distinguir regresión sostenida de ruido.
|
|
51
|
+
- Confirmar caché y CORS/CORP de media pública sin proxies locales.
|
|
52
|
+
|
|
53
|
+
## Accesibilidad y experiencia
|
|
54
|
+
|
|
55
|
+
- Navegación por teclado, foco visible, landmarks, labels, contraste y zoom.
|
|
56
|
+
- Formularios con errores específicos, estados de envío y prevención de doble
|
|
57
|
+
envío. No usar solo color para comunicar estado.
|
|
58
|
+
- Probar anchos móviles, tablet y escritorio sin scroll interno accidental.
|
|
59
|
+
|
|
60
|
+
## Legal y privacidad
|
|
61
|
+
|
|
62
|
+
- Consumir términos, envíos, cambios/devoluciones, privacidad y cookies desde
|
|
63
|
+
`publicConfig.legal`, manejando documentos nulos.
|
|
64
|
+
- No duplicar documentos legales en Content o en constantes del storefront.
|
|
65
|
+
- Mostrar el aviso informativo solo si Bacano lo activa y no presentarlo como
|
|
66
|
+
gestor de consentimiento.
|
|
67
|
+
- No exponer PII, tokens, secretos ni identificadores internos en URL, HTML,
|
|
68
|
+
logs, eventos o almacenamiento inseguro.
|
|
69
|
+
- Detectar placeholders visibles, texto de ejemplo y datos legales incompletos.
|
|
70
|
+
Un documento publicado con marcadores como `[RAZÓN SOCIAL]` es un `P0`.
|
|
71
|
+
|
|
72
|
+
## Seguridad y terceros
|
|
73
|
+
|
|
74
|
+
- HTTPS, HSTS cuando aplique, `X-Content-Type-Options`, `Referrer-Policy`,
|
|
75
|
+
`Permissions-Policy` y CSP efectiva.
|
|
76
|
+
- Construir CSP solo con proveedores realmente activos. Revisar la matriz
|
|
77
|
+
canónica de `STOREFRONT_MARKETING.md`.
|
|
78
|
+
- No usar `unsafe-eval`, comodines arbitrarios ni secretos públicos.
|
|
79
|
+
- Confirmar que CSP no bloquee Clerk, pagos, GA4, Meta o Clarity habilitados.
|
|
80
|
+
- Confirmar que la pantalla de construcción no se presente como seguridad ni
|
|
81
|
+
permita que contenido privado forme parte del export estático.
|
|
82
|
+
|
|
83
|
+
## Operación
|
|
84
|
+
|
|
85
|
+
- Validar dominio canónico, redirects, DNS, SSL, variables de producción y
|
|
86
|
+
rollback.
|
|
87
|
+
- Confirmar monitoreo de errores y logs sin datos personales.
|
|
88
|
+
- Registrar fecha, ambiente, versión/commit y qué pruebas requieren cuentas o
|
|
89
|
+
acciones externas para completarse.
|
|
90
|
+
- Verificar que el equipo conoce qué cambios requieren rebuild/despliegue y que
|
|
91
|
+
existe un mecanismo reproducible para ejecutarlo y revertirlo.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Checklist de web informativa
|
|
2
|
+
|
|
3
|
+
Aplicar además de los controles comunes.
|
|
4
|
+
|
|
5
|
+
## Contenido y navegación
|
|
6
|
+
|
|
7
|
+
- Home, páginas institucionales, servicios, blog, contacto y legales cargan
|
|
8
|
+
contenido real y conservan rutas estables.
|
|
9
|
+
- Menú, footer, breadcrumbs y enlaces de Content no apuntan a borradores o 404.
|
|
10
|
+
- Preview usa `@bacano/sdk/react/preview`, muestra solo contenido publicado,
|
|
11
|
+
permanece `noindex` y no dispara marketing.
|
|
12
|
+
|
|
13
|
+
## Formularios y contacto
|
|
14
|
+
|
|
15
|
+
- Distinguir formularios informativos/PQRS de capturas comerciales.
|
|
16
|
+
- Newsletter, cupón o interés comercial usan
|
|
17
|
+
`client.marketing.captureLead()` y el texto vigente de autorización.
|
|
18
|
+
- Formularios generales tienen destino, antispam/rate limit, idempotencia y
|
|
19
|
+
mensaje de recepción sin promesas falsas.
|
|
20
|
+
- WhatsApp, teléfono y email emiten `contact` con ubicación canónica cuando
|
|
21
|
+
marketing está activo.
|
|
22
|
+
|
|
23
|
+
## Medición
|
|
24
|
+
|
|
25
|
+
- Una sola fuente de `page_view` para navegación inicial y SPA.
|
|
26
|
+
- Medir contacto, formularios y navegación relevante sin inventar ecommerce.
|
|
27
|
+
- GA4, Meta y Clarity cargan solo en dominio canónico y nunca en preview/local.
|
|
28
|
+
- No exigir catálogo, carrito, checkout, métodos de entrega, pagos ni eventos
|
|
29
|
+
ecommerce a esta clase de sitio.
|
|
30
|
+
|
|
31
|
+
## Cierre mínimo
|
|
32
|
+
|
|
33
|
+
- Probar cada formulario sin crear registros duplicados.
|
|
34
|
+
- Verificar entrega o persistencia real cuando el formulario sea esencial.
|
|
35
|
+
- Comprobar páginas principales y legales en desktop/mobile.
|
|
36
|
+
- Validar indexación, performance y consola en todas las rutas publicadas.
|
|
37
|
+
- Marcar autenticación, leads o medición como `➖ NO APLICA` cuando estén
|
|
38
|
+
realmente apagados; si están visibles, aplicar sus contratos y pruebas de
|
|
39
|
+
cierre.
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Formato del informe
|
|
2
|
+
|
|
3
|
+
## Contexto
|
|
4
|
+
|
|
5
|
+
- Tipo: web informativa o tienda virtual.
|
|
6
|
+
- Repositorio, rama y commit.
|
|
7
|
+
- Ambiente y dominio.
|
|
8
|
+
- Framework, hosting y versión efectiva de `@bacano/sdk`.
|
|
9
|
+
- Fecha de revisión.
|
|
10
|
+
|
|
11
|
+
## Alcance detectado
|
|
12
|
+
|
|
13
|
+
Antes del dictamen incluir una tabla corta:
|
|
14
|
+
|
|
15
|
+
| Capacidad | Estado | ¿Bloquea este lanzamiento? | Evidencia |
|
|
16
|
+
| --------------------------------------------------- | ------------------------- | -------------------------- | ---------------------- |
|
|
17
|
+
| Auth, leads, GA4, Meta, Clarity, pagos, entregas... | ACTIVA/INACTIVA/NO_APLICA | Sí/No | configuración o código |
|
|
18
|
+
|
|
19
|
+
No exigir pruebas a una capacidad `INACTIVA` o `NO_APLICA`.
|
|
20
|
+
|
|
21
|
+
## Resultado ejecutivo
|
|
22
|
+
|
|
23
|
+
Indicar una sola conclusión:
|
|
24
|
+
|
|
25
|
+
- `🟢 LISTO`: sin fallos P0 y con flujos esenciales verificados.
|
|
26
|
+
- `🟡 LISTO CON ADVERTENCIAS`: sin bloqueos P0, con riesgos P1 aceptables.
|
|
27
|
+
- `🔴 NO LISTO`: existe al menos un `❌ FALLÓ` P0 confirmado.
|
|
28
|
+
- `🟠 INCOMPLETO`: falta verificar al menos un P0 activo y no hay evidencia
|
|
29
|
+
suficiente para declararlo fallido.
|
|
30
|
+
|
|
31
|
+
No usar `NO LISTO` únicamente porque una función apagada no se probó. Si
|
|
32
|
+
coexisten un fallo P0 y pruebas pendientes, prevalece `🔴 NO LISTO`.
|
|
33
|
+
|
|
34
|
+
## Evidencia
|
|
35
|
+
|
|
36
|
+
| Prioridad | Estado | Área | Control | Evidencia | Acción |
|
|
37
|
+
| --------- | ------------------------------------------------------------------------- | ---- | ------- | ------------------------ | ------ |
|
|
38
|
+
| P0/P1/P2 | ✅ APROBADO / ⚠️ ADVERTENCIA / ❌ FALLÓ / ➖ NO APLICA / 🧪 NO VERIFICADO | ... | ... | comando, URL o resultado | ... |
|
|
39
|
+
|
|
40
|
+
Usar siempre emoji y palabra. Los emojis solos no son suficientemente claros
|
|
41
|
+
ni procesables.
|
|
42
|
+
|
|
43
|
+
## Pruebas ejecutadas
|
|
44
|
+
|
|
45
|
+
Listar comandos y sus resultados, rutas auditadas, navegadores/viewports y
|
|
46
|
+
flujos externos realmente ejecutados. Separar pruebas locales de producción.
|
|
47
|
+
|
|
48
|
+
## Riesgos y próximos pasos
|
|
49
|
+
|
|
50
|
+
Ordenar por severidad. Cada hallazgo debe incluir alcance, causa confirmada o
|
|
51
|
+
hipótesis explícita, corrección propuesta y prueba de cierre. No mezclar
|
|
52
|
+
mejoras opcionales con bloqueos de salida.
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# Checklist de tienda virtual
|
|
2
|
+
|
|
3
|
+
Aplicar además de los controles comunes.
|
|
4
|
+
|
|
5
|
+
## P0 · Publicación y cobertura del catálogo
|
|
6
|
+
|
|
7
|
+
- Generar rutas, HTML, metadata, sitemap y JSON-LD desde un único
|
|
8
|
+
`getStaticCatalogBootstrap()`; no consultar un producto por ruta durante el
|
|
9
|
+
mismo build.
|
|
10
|
+
- Comparar `bootstrap.total`, productos únicos del snapshot, rutas de producto
|
|
11
|
+
generadas, URLs de producto del sitemap y páginas del export. Toda diferencia
|
|
12
|
+
debe ser intencional y explicable; el límite configurado no puede truncar el
|
|
13
|
+
catálogo silenciosamente.
|
|
14
|
+
- Hacer fallar el build de producción si el snapshot está vacío, incompleto o
|
|
15
|
+
no corresponde al website esperado. Los placeholders solo sirven en local.
|
|
16
|
+
- Usar `/producto/{slug}` como ruta canónica cuando el proyecto adopte ese
|
|
17
|
+
contrato; la variante publicitaria usa `?variant={product_variant_id}` sin
|
|
18
|
+
alterar canonical.
|
|
19
|
+
- Precio, SKU, stock, variante seleccionada, JSON-LD, carrito, WhatsApp y
|
|
20
|
+
eventos deben referirse a la misma variante.
|
|
21
|
+
- El HTML inicial debe contener producto, precio y enlaces reales sin depender
|
|
22
|
+
de JavaScript. Después de hidratar, refrescar únicamente estado volátil por
|
|
23
|
+
lotes con `getProductLiveState()` y respetar `maxPurchasableQuantity`.
|
|
24
|
+
- Si el refresh vivo falla, conservar el snapshot sin vaciar ni bloquear la
|
|
25
|
+
página. Al mismo tiempo, checkout debe revalidar autoritativamente precio y
|
|
26
|
+
stock ante concurrencia.
|
|
27
|
+
- Verificar el comportamiento cuando una variante queda agotada, cambia de
|
|
28
|
+
precio o limita cantidad entre build y compra.
|
|
29
|
+
- Definir cómo se dispara un rebuild al publicar, retirar, renombrar o cambiar
|
|
30
|
+
imágenes/descripción. El refresh vivo no reemplaza esa actualización
|
|
31
|
+
editorial del snapshot.
|
|
32
|
+
- Recomendaciones y relacionados no bloquean el contenido principal.
|
|
33
|
+
|
|
34
|
+
## P0 · SEO comercial
|
|
35
|
+
|
|
36
|
+
- Cada producto público tiene título, descripción, canonical, Open Graph,
|
|
37
|
+
imagen compartible y JSON-LD `Product` coherente con la variante visible.
|
|
38
|
+
- Sitemap contiene cada slug canónico una sola vez y excluye UUID, query de
|
|
39
|
+
variante, preview, checkout, cuenta, pedido y respuesta de pago.
|
|
40
|
+
- La ruta elegida desde feed, anuncio, tarjeta, relacionado y carrito resuelve
|
|
41
|
+
el mismo producto; URLs antiguas importantes redirigen o se documentan.
|
|
42
|
+
- Producto retirado responde 404/410 o se reconstruye dentro del SLA definido;
|
|
43
|
+
nunca queda comprable solo por existir en un export viejo.
|
|
44
|
+
|
|
45
|
+
## Carrito y checkout
|
|
46
|
+
|
|
47
|
+
- Carrito invitado persiste y sincroniza sin duplicar líneas.
|
|
48
|
+
- Probar alta, baja, cambios de cantidad, límites de stock, cupón, envío gratis,
|
|
49
|
+
impuestos, moneda y redondeo.
|
|
50
|
+
- Probar solo los modos activos entre COUNTER, PICKUP y SHIPPING; propia y
|
|
51
|
+
transportadora cuando apliquen, incluyendo contraentrega y pagos parciales.
|
|
52
|
+
- Verificar errores accionables, idempotencia y doble clic/reintento.
|
|
53
|
+
- Nunca inventar reglas de ventas o estados en el storefront.
|
|
54
|
+
|
|
55
|
+
## Pagos y pedidos
|
|
56
|
+
|
|
57
|
+
- Ejecutar al menos un happy path real o sandbox por cada método crítico
|
|
58
|
+
publicado. Probar pago inmediato, transferencia, Wompi y contraentrega solo
|
|
59
|
+
si están activos; los estados alternos de cada proveedor quedan en su matriz
|
|
60
|
+
de riesgo.
|
|
61
|
+
- La página de respuesta no emite `purchase`; Bacano lo hace desde hechos
|
|
62
|
+
autoritativos. Repetir webhook/conciliación no duplica conversiones.
|
|
63
|
+
- Pedido, pago, entrega y desembolso permanecen conceptos separados.
|
|
64
|
+
- Probar consulta pública segura y cuenta autenticada sin filtrar pedidos de
|
|
65
|
+
otra persona, website o canal.
|
|
66
|
+
|
|
67
|
+
## Autenticación
|
|
68
|
+
|
|
69
|
+
- Clerk posee registro, inicio de sesión, recuperación, verificación y sesión.
|
|
70
|
+
- Una aplicación Clerk por website; solo publishable key en navegador.
|
|
71
|
+
- Un `BacanoProvider` recibe `getAccessToken`; no se captura ni guarda el token.
|
|
72
|
+
- Si las cuentas están activas o se anuncian, probar registro/inicio y el flujo
|
|
73
|
+
principal autenticado; recuperación, verificación, perfil, direcciones y
|
|
74
|
+
pedidos se clasifican según lo publicado. Clerk no rompe checkout invitado.
|
|
75
|
+
- Si las cuentas están apagadas, todas estas pruebas son `➖ NO APLICA`.
|
|
76
|
+
|
|
77
|
+
## Marketing y catálogos publicitarios
|
|
78
|
+
|
|
79
|
+
- Instrumentar únicamente con `useMarketing().track()` después del éxito.
|
|
80
|
+
- `product_variant_id` es `itemId`; `order_id` es `transaction_id`.
|
|
81
|
+
- Auditar `view_item_list`, `select_item`, `view_item`, carrito, checkout y
|
|
82
|
+
contactos; conversiones autoritativas nunca salen del navegador.
|
|
83
|
+
- Primera y última fuente no directa sobreviven navegación interna y quedan
|
|
84
|
+
congeladas en el pedido.
|
|
85
|
+
- Feeds Meta/Google usan la misma variante, ruta, precio y disponibilidad que
|
|
86
|
+
la landing. Probar URLs del feed y productos omitidos.
|
|
87
|
+
- GA4, Meta, Clarity, leads y feeds se auditan únicamente si están activos.
|
|
88
|
+
Cada proveedor activo debe demostrar al menos una recepción real antes de
|
|
89
|
+
declarar esa capacidad lista, sin convertir su caída en bloqueo de compra.
|
|
90
|
+
|
|
91
|
+
## P1 · Calidad profesional
|
|
92
|
+
|
|
93
|
+
- Media responsive con dimensiones, `srcset`, `sizes`, carga prioritaria solo
|
|
94
|
+
para LCP y lazy loading bajo el pliegue; sin transformaciones manuales.
|
|
95
|
+
- Catálogo, filtros y carrito conservan contenido útil durante errores
|
|
96
|
+
transitorios y no producen saltos de layout evitables.
|
|
97
|
+
- JSON-LD, feed publicitario y estado vivo no contradicen precio, moneda,
|
|
98
|
+
disponibilidad ni variante mostrados.
|
|
99
|
+
- La generación es reproducible con lockfile y deja evidencia de conteos,
|
|
100
|
+
snapshot, duración y artefacto.
|
|
101
|
+
|
|
102
|
+
## P2 · Evolución
|
|
103
|
+
|
|
104
|
+
- Datos de campo de Core Web Vitals, pruebas sintéticas periódicas, monitoreo de
|
|
105
|
+
enlaces rotos y alertas de divergencia entre feed, sitemap y catálogo.
|
|
106
|
+
- Matrices exhaustivas de estados no críticos, navegadores adicionales y
|
|
107
|
+
automatización de rebuild según el hosting.
|
|
108
|
+
|
|
109
|
+
## Cierre transaccional
|
|
110
|
+
|
|
111
|
+
- Ejecutar al menos una compra real o sandbox por método crítico activo.
|
|
112
|
+
- Confirmar pedido en Bacano, estado económico, inventario, envío y correo.
|
|
113
|
+
- Confirmar GA4/Meta del embudo y conversión sin duplicados.
|
|
114
|
+
- Si un flujo activo esencial no se ejecutó, marcarlo `🧪 NO VERIFICADO`, no
|
|
115
|
+
`APROBADO`; el dictamen es `🟠 INCOMPLETO` salvo que exista además un fallo
|
|
116
|
+
probado.
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const args = process.argv.slice(2);
|
|
4
|
+
const baseArg = args[0];
|
|
5
|
+
|
|
6
|
+
if (!baseArg || baseArg === "--help") {
|
|
7
|
+
console.log(
|
|
8
|
+
"Usage: node audit-http.mjs <origin-or-url> [--sitemap <url>] [--max <n>] [--delay <ms>]",
|
|
9
|
+
);
|
|
10
|
+
process.exit(baseArg ? 0 : 1);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function option(name, fallback) {
|
|
14
|
+
const index = args.indexOf(name);
|
|
15
|
+
return index >= 0 && args[index + 1] ? args[index + 1] : fallback;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const startUrl = new URL(baseArg);
|
|
19
|
+
const maxRoutes = Math.max(1, Number(option("--max", "500")) || 500);
|
|
20
|
+
const delayMs = Math.max(0, Number(option("--delay", "250")) || 0);
|
|
21
|
+
const explicitSitemap = option("--sitemap", null);
|
|
22
|
+
|
|
23
|
+
function sleep(ms) {
|
|
24
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async function fetchResult(url) {
|
|
28
|
+
const startedAt = Date.now();
|
|
29
|
+
try {
|
|
30
|
+
const response = await fetch(url, {
|
|
31
|
+
redirect: "follow",
|
|
32
|
+
headers: { "user-agent": "bacano-web-readiness/1" },
|
|
33
|
+
});
|
|
34
|
+
return {
|
|
35
|
+
requestedUrl: url,
|
|
36
|
+
finalUrl: response.url,
|
|
37
|
+
status: response.status,
|
|
38
|
+
durationMs: Date.now() - startedAt,
|
|
39
|
+
headers: Object.fromEntries(response.headers.entries()),
|
|
40
|
+
body: await response.text(),
|
|
41
|
+
};
|
|
42
|
+
} catch (error) {
|
|
43
|
+
return {
|
|
44
|
+
requestedUrl: url,
|
|
45
|
+
finalUrl: null,
|
|
46
|
+
status: null,
|
|
47
|
+
durationMs: Date.now() - startedAt,
|
|
48
|
+
headers: {},
|
|
49
|
+
body: "",
|
|
50
|
+
error: error instanceof Error ? error.message : String(error),
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function sitemapUrls(xml) {
|
|
56
|
+
return [...xml.matchAll(/<loc>\s*([^<]+?)\s*<\/loc>/gi)].map((match) =>
|
|
57
|
+
match[1].replaceAll("&", "&"),
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function pageUrls(html, base) {
|
|
62
|
+
const urls = [];
|
|
63
|
+
for (const match of html.matchAll(
|
|
64
|
+
/<a\b[^>]*href\s*=\s*["']([^"']+)["'][^>]*>/gi,
|
|
65
|
+
)) {
|
|
66
|
+
try {
|
|
67
|
+
const url = new URL(match[1], base);
|
|
68
|
+
url.hash = "";
|
|
69
|
+
if (url.origin === startUrl.origin) urls.push(url.href);
|
|
70
|
+
} catch {
|
|
71
|
+
// Ignore malformed or unsupported links.
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return urls;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function sameOrigin(url) {
|
|
78
|
+
try {
|
|
79
|
+
return new URL(url).origin === startUrl.origin;
|
|
80
|
+
} catch {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function tagAttribute(tag, name) {
|
|
86
|
+
const match = tag.match(
|
|
87
|
+
new RegExp(`\\b${name}\\s*=\\s*(?:"([^"]*)"|'([^']*)'|([^\\s>]+))`, "i"),
|
|
88
|
+
);
|
|
89
|
+
return match?.[1] ?? match?.[2] ?? match?.[3] ?? null;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function findMeta(html, key, value) {
|
|
93
|
+
const tags = html.match(/<meta\b[^>]*>/gi) ?? [];
|
|
94
|
+
const normalized = value.toLowerCase();
|
|
95
|
+
for (const tag of tags) {
|
|
96
|
+
if (tagAttribute(tag, key)?.toLowerCase() === normalized) {
|
|
97
|
+
return tagAttribute(tag, "content");
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function findLink(html, rel) {
|
|
104
|
+
const tags = html.match(/<link\b[^>]*>/gi) ?? [];
|
|
105
|
+
const normalized = rel.toLowerCase();
|
|
106
|
+
for (const tag of tags) {
|
|
107
|
+
const rels = tagAttribute(tag, "rel")?.toLowerCase().split(/\s+/) ?? [];
|
|
108
|
+
if (rels.includes(normalized)) return tagAttribute(tag, "href");
|
|
109
|
+
}
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function stripMarkup(html) {
|
|
114
|
+
return html
|
|
115
|
+
.replace(/<script\b[\s\S]*?<\/script>/gi, " ")
|
|
116
|
+
.replace(/<style\b[\s\S]*?<\/style>/gi, " ")
|
|
117
|
+
.replace(/<[^>]+>/g, " ")
|
|
118
|
+
.replace(/\s+/g, " ")
|
|
119
|
+
.trim();
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function auditHtml(html) {
|
|
123
|
+
const title =
|
|
124
|
+
html.match(/<title\b[^>]*>([\s\S]*?)<\/title>/i)?.[1]?.trim() ?? null;
|
|
125
|
+
const visibleText = stripMarkup(html);
|
|
126
|
+
const placeholders = [
|
|
127
|
+
...new Set(
|
|
128
|
+
visibleText.match(
|
|
129
|
+
/\[[A-ZÁÉÍÓÚÜÑ][A-ZÁÉÍÓÚÜÑ0-9 ._/-]{2,}\]|\b(?:TODO|FIXME|LOREM IPSUM)\b/gi,
|
|
130
|
+
) ?? [],
|
|
131
|
+
),
|
|
132
|
+
].slice(0, 20);
|
|
133
|
+
|
|
134
|
+
return {
|
|
135
|
+
title,
|
|
136
|
+
description: findMeta(html, "name", "description"),
|
|
137
|
+
canonical: findLink(html, "canonical"),
|
|
138
|
+
robots: findMeta(html, "name", "robots"),
|
|
139
|
+
openGraphTitle: findMeta(html, "property", "og:title"),
|
|
140
|
+
openGraphImage: findMeta(html, "property", "og:image"),
|
|
141
|
+
twitterCard: findMeta(html, "name", "twitter:card"),
|
|
142
|
+
h1Count: (html.match(/<h1\b/gi) ?? []).length,
|
|
143
|
+
jsonLdCount: (
|
|
144
|
+
html.match(/<script\b[^>]*type=["']application\/ld\+json["'][^>]*>/gi) ??
|
|
145
|
+
[]
|
|
146
|
+
).length,
|
|
147
|
+
visibleTextLength: visibleText.length,
|
|
148
|
+
placeholders,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const home = await fetchResult(startUrl.href);
|
|
153
|
+
const sitemapCandidates = explicitSitemap
|
|
154
|
+
? [new URL(explicitSitemap, startUrl).href]
|
|
155
|
+
: [new URL("/sitemap.xml", startUrl).href];
|
|
156
|
+
|
|
157
|
+
const routes = new Set([startUrl.href]);
|
|
158
|
+
const sitemapReports = [];
|
|
159
|
+
|
|
160
|
+
for (const url of pageUrls(home.body, startUrl)) {
|
|
161
|
+
if (routes.size < maxRoutes) routes.add(url);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const visitedSitemaps = new Set();
|
|
165
|
+
while (sitemapCandidates.length > 0) {
|
|
166
|
+
const candidate = sitemapCandidates.shift();
|
|
167
|
+
if (!candidate || visitedSitemaps.has(candidate)) continue;
|
|
168
|
+
visitedSitemaps.add(candidate);
|
|
169
|
+
const report = await fetchResult(candidate);
|
|
170
|
+
sitemapReports.push({ url: candidate, status: report.status });
|
|
171
|
+
if (report.status !== 200) continue;
|
|
172
|
+
|
|
173
|
+
const urls = sitemapUrls(report.body);
|
|
174
|
+
if (/<sitemapindex\b/i.test(report.body)) {
|
|
175
|
+
for (const url of urls) {
|
|
176
|
+
if (sameOrigin(url)) sitemapCandidates.push(url);
|
|
177
|
+
}
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
for (const url of urls) {
|
|
182
|
+
if (sameOrigin(url) && routes.size < maxRoutes) routes.add(url);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const pages = [];
|
|
187
|
+
for (const url of routes) {
|
|
188
|
+
if (url === startUrl.href) {
|
|
189
|
+
pages.push({
|
|
190
|
+
url,
|
|
191
|
+
finalUrl: home.finalUrl,
|
|
192
|
+
status: home.status,
|
|
193
|
+
durationMs: home.durationMs,
|
|
194
|
+
error: home.error,
|
|
195
|
+
html: auditHtml(home.body),
|
|
196
|
+
});
|
|
197
|
+
} else {
|
|
198
|
+
if (delayMs) await sleep(delayMs);
|
|
199
|
+
const report = await fetchResult(url);
|
|
200
|
+
pages.push({
|
|
201
|
+
url,
|
|
202
|
+
finalUrl: report.finalUrl,
|
|
203
|
+
status: report.status,
|
|
204
|
+
durationMs: report.durationMs,
|
|
205
|
+
error: report.error,
|
|
206
|
+
html: auditHtml(report.body),
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const requiredHeaders = [
|
|
212
|
+
"content-security-policy",
|
|
213
|
+
"strict-transport-security",
|
|
214
|
+
"x-content-type-options",
|
|
215
|
+
"referrer-policy",
|
|
216
|
+
"permissions-policy",
|
|
217
|
+
];
|
|
218
|
+
|
|
219
|
+
const output = {
|
|
220
|
+
auditedAt: new Date().toISOString(),
|
|
221
|
+
origin: startUrl.origin,
|
|
222
|
+
discoveredRoutes: routes.size,
|
|
223
|
+
sitemapReports,
|
|
224
|
+
summary: {
|
|
225
|
+
successful: pages.filter((page) => page.status === 200).length,
|
|
226
|
+
redirectsOrOther: pages.filter(
|
|
227
|
+
(page) => page.status && page.status !== 200 && page.status < 400,
|
|
228
|
+
).length,
|
|
229
|
+
failed: pages.filter((page) => !page.status || page.status >= 400).length,
|
|
230
|
+
productRoutes: pages.filter((page) =>
|
|
231
|
+
new URL(page.url).pathname.startsWith("/producto/"),
|
|
232
|
+
).length,
|
|
233
|
+
missingTitle: pages.filter((page) => !page.html.title).length,
|
|
234
|
+
missingDescription: pages.filter((page) => !page.html.description).length,
|
|
235
|
+
missingCanonical: pages.filter((page) => !page.html.canonical).length,
|
|
236
|
+
invalidH1Count: pages.filter((page) => page.html.h1Count !== 1).length,
|
|
237
|
+
pagesWithPlaceholders: pages.filter(
|
|
238
|
+
(page) => page.html.placeholders.length > 0,
|
|
239
|
+
).length,
|
|
240
|
+
},
|
|
241
|
+
homeSecurityHeaders: Object.fromEntries(
|
|
242
|
+
requiredHeaders.map((header) => [header, home.headers[header] ?? null]),
|
|
243
|
+
),
|
|
244
|
+
pages,
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
console.log(JSON.stringify(output, null, 2));
|