@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
@@ -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,4 @@
1
+ interface:
2
+ display_name: "Bacano Web"
3
+ short_description: "Audita sitios Bacano antes de producción"
4
+ default_prompt: "Usa $bacano-web para evaluar si este sitio Bacano está listo para producción."
@@ -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("&amp;", "&"),
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));