@bacano/sdk 1.1.9 → 1.1.10

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 CHANGED
@@ -44,6 +44,42 @@ descubrimiento. `check` confirma que está vigente. Ninguno ejecuta la auditorí
44
44
  modifica el storefront ni despliega; esa revisión comienza únicamente cuando
45
45
  el usuario o agente invoca la skill.
46
46
 
47
+ ### Implementación desde Figma
48
+
49
+ El SDK también incluye `bacano-figma-web`, la skill oficial para convertir un
50
+ diseño Figma en una web informativa o tienda que conserve el cascarón, Static
51
+ First y contratos públicos de Bacano:
52
+
53
+ ```bash
54
+ pnpm exec bacano-figma-web install --agent all
55
+ pnpm exec bacano-figma-web check
56
+ pnpm exec bacano-figma-web doctor
57
+ ```
58
+
59
+ Después invocarla como `$bacano-figma-web` en Codex, `/bacano-figma-web` en
60
+ Claude Code o solicitar “usar Bacano Figma Web” en Antigravity. La skill guía la
61
+ conexión al MCP remoto oficial, MCP local oficial de Figma Desktop, Desktop
62
+ Bridge comunitario o REST autorizado. Prefiere siempre el MCP oficial y nunca
63
+ guarda ni imprime tokens.
64
+
65
+ `doctor` comprueba instalación, endpoint local, puertos del bridge y solamente
66
+ la presencia del token REST. El OAuth del MCP remoto debe confirmarse dentro del
67
+ agente con las herramientas de Figma. La extracción se conserva como snapshot
68
+ normalizado. Cuando el equipo de Content y desarrollo trabajan en tiempos
69
+ distintos, la skill genera un blueprint de entrega y permite fixtures locales
70
+ con el mismo contrato de los componentes. Los tipos y entradas reales se crean
71
+ desde Bacano; un fixture activo bloquea la aprobación de producción.
72
+
73
+ El snapshot puede validarse con:
74
+
75
+ ```bash
76
+ pnpm exec bacano-figma-web validate-snapshot .bacano/designs/<proyecto>/manifest.json
77
+ ```
78
+
79
+ Las instrucciones completas viajan dentro de
80
+ `skills/bacano-figma-web/references/`; no es necesario copiar documentación ni
81
+ crear integraciones particulares en cada storefront.
82
+
47
83
  Every storefront must follow [`STOREFRONT_MARKETING.md`](./STOREFRONT_MARKETING.md)
48
84
  when instrumenting analytics, advertising signals, contact actions, ecommerce
49
85
  events, attribution, or preview behavior. It is the canonical implementation
package/package.json CHANGED
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "@bacano/sdk",
3
- "version": "1.1.9",
3
+ "version": "1.1.10",
4
4
  "description": "TypeScript SDK for building websites on the Bacano ERP",
5
5
  "main": "./index.cjs",
6
6
  "module": "./index.js",
7
7
  "types": "./index.d.ts",
8
8
  "bin": {
9
+ "bacano-figma-web": "./skills/bacano-figma-web/scripts/cli.mjs",
9
10
  "bacano-web": "./skills/bacano-web/scripts/cli.mjs"
10
11
  },
11
12
  "scripts": {
@@ -0,0 +1,142 @@
1
+ ---
2
+ name: bacano-figma-web
3
+ description: Convierte diseños de Figma en webs y tiendas construidas con @bacano/sdk, preservando el cascarón, contratos, Static First y comportamiento oficial de Bacano. Usar al implementar una web desde enlaces, frames o archivos Figma; configurar o diagnosticar Figma MCP, Figma Desktop MCP, Desktop Bridge o REST; extraer un diseño por páginas; mapearlo a Content y componentes; o comparar visualmente la implementación con Figma.
4
+ ---
5
+
6
+ # Bacano Figma Web
7
+
8
+ Implementar Figma como fuente visual y Bacano como fuente funcional. No copiar
9
+ una captura como HTML aislado ni reconstruir catálogo, carrito, checkout,
10
+ autenticación, marketing o Content por fuera de `@bacano/sdk`.
11
+
12
+ ## 1. Confirmar el alcance
13
+
14
+ Antes de extraer el diseño, identificar:
15
+
16
+ - Web informativa o tienda virtual.
17
+ - Enlace al archivo, página, sección o frame de Figma.
18
+ - Rutas que se implementarán y sus frames desktop/mobile.
19
+ - Repositorio y cascarón Bacano que se reutilizarán.
20
+ - Fuente Figma disponible y política de fallback.
21
+
22
+ Si falta una decisión que cambie materialmente el resultado, hacer una sola
23
+ pregunta corta. No solicitar un token por chat ni pedir que se guarde en el
24
+ repositorio.
25
+
26
+ ## 2. Resolver la conexión con Figma
27
+
28
+ Ejecutar primero:
29
+
30
+ ```bash
31
+ pnpm exec bacano-figma-web doctor
32
+ ```
33
+
34
+ Leer [references/connection-official.md](references/connection-official.md) si
35
+ falta instalar o autenticar el MCP oficial. Usar este orden:
36
+
37
+ 1. MCP remoto oficial de Figma.
38
+ 2. MCP local oficial de Figma Desktop.
39
+ 3. Desktop Bridge comunitario, local y con versión revisada/fijada.
40
+ 4. Snapshot mediante REST, únicamente con autorización explícita.
41
+
42
+ No cambiar silenciosamente de fuente. Si la fuente seleccionada se agota,
43
+ responde `429` o no expone una capacidad necesaria, conservar el avance y pedir
44
+ permiso antes del fallback, salvo que el usuario haya aprobado modo automático.
45
+ Leer la guía específica únicamente cuando se elija
46
+ [Desktop Bridge](references/connection-desktop-bridge.md) o
47
+ [REST](references/connection-rest-api.md).
48
+
49
+ ## 3. Extraer una vez y normalizar
50
+
51
+ Leer [references/extraction-contract.md](references/extraction-contract.md).
52
+ Crear o reutilizar un snapshot normalizado en `.bacano/designs/<proyecto>/`.
53
+ No consultar cada sección por separado si una lectura de página/frame entrega
54
+ el contexto suficiente.
55
+
56
+ Evaluar y registrar como `DISPONIBLE`, `PARCIAL` o `AUSENTE`:
57
+
58
+ - Variables y estilos.
59
+ - Componentes y variantes.
60
+ - Auto Layout, constraints y geometría.
61
+ - Nombres de capas.
62
+ - Frames desktop/mobile.
63
+ - Estados e interacciones.
64
+
65
+ La ausencia de variables o componentes no bloquea por sí sola. Inferir desde
66
+ geometría y evidencia visual, pero registrar cada inferencia. Nunca incluir PAT,
67
+ cookies, secretos, correos privados ni contenido no necesario en el snapshot.
68
+
69
+ Validar el manifiesto:
70
+
71
+ ```bash
72
+ pnpm exec bacano-figma-web validate-snapshot .bacano/designs/<proyecto>/manifest.json
73
+ ```
74
+
75
+ ## 4. Mapear a Bacano antes de programar
76
+
77
+ Leer [references/bacano-mapping.md](references/bacano-mapping.md) y
78
+ [references/content-workflow.md](references/content-workflow.md). Preparar una
79
+ matriz ruta → frame → componente → fuente de datos → estado responsive y
80
+ determinar si el trabajo comienza desde Figma, desde Bacano Content o desde
81
+ ambas fuentes.
82
+
83
+ - Reutilizar el starter, tokens y componentes existentes.
84
+ - Consumir Bacano y Content mediante `@bacano/sdk`.
85
+ - Mantener un solo `BacanoProvider` y un solo cliente por árbol.
86
+ - Preservar Static First: estructura estática y estado comercial vivo mediante
87
+ los contratos específicos del SDK.
88
+ - No usar GraphQL directo, endpoints internos ni lógica comercial duplicada.
89
+ - No codificar textos editables si pertenecen a Bacano Content.
90
+ - No alterar Baby Rex u otra referencia salvo que ese sea el proyecto indicado.
91
+
92
+ Si Content todavía no existe, generar un blueprint de entrega y fixtures
93
+ temporales fieles al contenido visible de Figma. Los fixtures deben usar el
94
+ mismo view model que consumen los componentes y entrar por un único adaptador;
95
+ nunca repartir datos de prueba dentro de la UI. Si Content ya existe, mapearlo
96
+ sin sobrescribir estructuras o entradas. En esta versión la skill no crea ni
97
+ modifica contenido en Bacano: esas acciones permanecen en el backoffice y
98
+ requieren autorización independiente.
99
+
100
+ Antes de modificar código, indicar brevemente qué rutas y componentes se
101
+ implementarán y qué decisiones de diseño son inferidas.
102
+
103
+ ## 5. Implementar por cortes verificables
104
+
105
+ Trabajar por página o conjunto pequeño de secciones:
106
+
107
+ 1. Estructura semántica y responsive.
108
+ 2. Tokens, tipografía, espacios y media.
109
+ 3. Estados interactivos y accesibilidad.
110
+ 4. Integración Bacano real.
111
+ 5. Comparación visual y correcciones.
112
+
113
+ No añadir paquetes, fuentes, iconos o assets si el proyecto ya ofrece un
114
+ equivalente. Descargar assets mediante la fuente Figma autorizada; no incrustar
115
+ URLs temporales ni construir transformaciones de Storage manualmente.
116
+
117
+ ## 6. Validar diseño y contrato
118
+
119
+ Leer [references/visual-validation.md](references/visual-validation.md).
120
+ Comparar con evidencia desktop y mobile, incluyendo viewport, captura y
121
+ diferencias relevantes. Probar además typecheck, lint, tests y build del
122
+ proyecto.
123
+
124
+ Antes de aprobar producción, comprobar que la fuente editorial efectiva sea
125
+ Bacano Content. Un fixture, placeholder o fallback temporal accesible en el
126
+ build de producción es un bloqueo, aunque visualmente coincida con Figma.
127
+
128
+ Al finalizar, invocar `$bacano-web` si está disponible para la auditoría de
129
+ producción completa. Esta skill valida fidelidad y correcta integración desde
130
+ Figma; `bacano-web` valida que el sitio pueda publicarse profesionalmente.
131
+
132
+ ## 7. Entregar un cierre claro
133
+
134
+ Reportar:
135
+
136
+ - Fuente Figma y fallback usados.
137
+ - Páginas/frames extraídos e implementados.
138
+ - Snapshot y versión/fecha de extracción.
139
+ - Componentes Bacano reutilizados.
140
+ - Inferencias y diferencias aprobadas.
141
+ - Validaciones ejecutadas y pendientes reales.
142
+ - Brechas del SDK, sin workarounds directos a API o GraphQL.
@@ -0,0 +1,4 @@
1
+ interface:
2
+ display_name: "Bacano Figma Web"
3
+ short_description: "Convierte diseños Figma en sitios Bacano"
4
+ default_prompt: "Usa $bacano-figma-web para implementar este diseño de Figma con el cascarón y los contratos oficiales de Bacano."
@@ -0,0 +1,38 @@
1
+ # Mapeo de Figma a Bacano
2
+
3
+ Figma decide presentación. Bacano decide datos, permisos, estados y reglas de
4
+ negocio.
5
+
6
+ ## Matriz obligatoria
7
+
8
+ Antes de programar, registrar por ruta:
9
+
10
+ | Ruta | Frame | Componente | Fuente | Responsive | Estado |
11
+ | --- | --- | --- | --- | --- | --- |
12
+ | `/` | Home desktop/mobile | `Hero` | Content | 2 frames | pendiente |
13
+
14
+ Fuentes posibles: Content, catálogo estático, live state, public config,
15
+ checkout, auth u otra operación documentada del SDK.
16
+
17
+ ## Reglas
18
+
19
+ - Usar componentes y tokens existentes antes de crear otros.
20
+ - Mapear textos/imágenes editoriales a Content cuando deban ser administrables.
21
+ - Si el equipo de Content y desarrollo trabajan en tiempos distintos, seguir
22
+ [content-workflow.md](content-workflow.md) y mantener un único contrato de
23
+ datos entre fixtures temporales y Bacano real.
24
+ - Usar `responsiveSources` y helpers oficiales para media; no construir URLs de
25
+ Storage manualmente.
26
+ - Mantener HTML semántico, canonical, metadata, JSON-LD y sitemap.
27
+ - En tiendas, conservar rutas de producto y `product_variant_id` definidos por
28
+ el contrato del SDK.
29
+ - La estructura del catálogo puede venir del build; precio, disponibilidad y
30
+ límites se actualizan con live state/cart oficial.
31
+ - Montar proveedores globales una sola vez.
32
+ - Preview reutiliza los mismos componentes y cliente; no crear un storefront
33
+ paralelo para “parecerse” a Figma.
34
+ - Marketing se emite mediante el runtime/hook oficial, nunca desde clicks
35
+ duplicados escritos solo para una página.
36
+
37
+ Si el SDK no ofrece una capacidad, documentar la brecha. No llamar GraphQL o
38
+ endpoints internos como workaround.
@@ -0,0 +1,45 @@
1
+ # Desktop Bridge comunitario
2
+
3
+ “Figma Desktop Bridge” no es el MCP local oficial de Figma. Esta guía se refiere
4
+ al bridge incluido por el proyecto comunitario `southleft/figma-console-mcp`,
5
+ que conecta un plugin de Figma Desktop con su servidor MCP.
6
+
7
+ Usarlo solo cuando el MCP oficial no exponga datos locales necesarios, por
8
+ ejemplo determinadas variables o descripciones accesibles mediante Plugin API.
9
+
10
+ ## Reglas de seguridad
11
+
12
+ - Preferir modo local.
13
+ - Revisar el repositorio y fijar una versión o commit aprobado; no depender de
14
+ `@latest` en automatizaciones reproducibles.
15
+ - Mantener Figma Desktop abierto y el plugin activo durante la extracción.
16
+ - No exponer ni redirigir a Internet los puertos locales `9223`–`9232`.
17
+ - Guardar el PAT en el almacén de secretos o entorno del cliente MCP, nunca en
18
+ `.mcp.json`, el repositorio, el snapshot o los logs.
19
+ - No habilitar cloud relay o escritura sin autorización explícita.
20
+
21
+ ## Instalación orientativa
22
+
23
+ El proyecto upstream publica instrucciones actualizadas en:
24
+
25
+ - https://github.com/southleft/figma-console-mcp
26
+ - https://github.com/southleft/figma-console-mcp/blob/main/figma-desktop-bridge/README.md
27
+
28
+ Seguir la versión revisada de esas instrucciones para:
29
+
30
+ 1. Instalar/configurar el servidor MCP comunitario.
31
+ 2. Importar el manifest del plugin generado por el servidor.
32
+ 3. Abrir el plugin dentro del archivo Figma.
33
+ 4. Confirmar la conexión local del bridge.
34
+ 5. Ejecutar una lectura mínima antes de extraer todas las páginas.
35
+
36
+ El comando `pnpm exec bacano-figma-web doctor` solo detecta puertos locales
37
+ compatibles. No demuestra que el plugin esté conectado, que el archivo sea el
38
+ correcto ni que el servidor sea confiable; esas tres condiciones deben
39
+ verificarse dentro del agente.
40
+
41
+ ## Salida esperada
42
+
43
+ Normalizar la respuesta del bridge al mismo manifiesto definido en
44
+ `extraction-contract.md`. No permitir que detalles propios del bridge se filtren
45
+ a los componentes React ni a los contratos públicos de Bacano.
@@ -0,0 +1,127 @@
1
+ # Conexión oficial con Figma
2
+
3
+ Elegir preferiblemente el MCP remoto oficial. Requiere autorización de la
4
+ cuenta Figma, pero no mantener Figma Desktop abierto.
5
+
6
+ ## MCP remoto oficial
7
+
8
+ Endpoint oficial:
9
+
10
+ ```text
11
+ https://mcp.figma.com/mcp
12
+ ```
13
+
14
+ ### Codex CLI
15
+
16
+ ```bash
17
+ codex mcp add figma --url https://mcp.figma.com/mcp
18
+ ```
19
+
20
+ Completar OAuth cuando Codex lo solicite. En la aplicación Codex también puede
21
+ instalarse/conectarse el plugin oficial de Figma desde Plugins.
22
+
23
+ ### Claude Code
24
+
25
+ Figma recomienda primero su plugin oficial:
26
+
27
+ ```bash
28
+ claude plugin install figma@claude-plugins-official
29
+ ```
30
+
31
+ Configuración manual equivalente:
32
+
33
+ ```bash
34
+ claude mcp add --transport http figma https://mcp.figma.com/mcp
35
+ ```
36
+
37
+ Usar `--scope user` únicamente si la conexión debe estar disponible en todos
38
+ los proyectos de ese usuario.
39
+
40
+ ### VS Code y clientes MCP compatibles
41
+
42
+ ```json
43
+ {
44
+ "inputs": [],
45
+ "servers": {
46
+ "figma": {
47
+ "url": "https://mcp.figma.com/mcp",
48
+ "type": "http"
49
+ }
50
+ }
51
+ }
52
+ ```
53
+
54
+ No copiar tokens dentro del archivo. El cliente debe realizar OAuth.
55
+
56
+ ### Cursor
57
+
58
+ Figma recomienda instalar su plugin oficial desde el chat del agente:
59
+
60
+ ```text
61
+ /add-plugin figma
62
+ ```
63
+
64
+ En otros agentes, comprobar primero que aparezcan en el catálogo de clientes
65
+ admitidos por Figma. No asumir que cualquier cliente MCP puede completar el
66
+ OAuth del servidor remoto.
67
+
68
+ ## MCP local oficial de Figma Desktop
69
+
70
+ Usarlo cuando se necesite trabajar con la selección actual del archivo o el MCP
71
+ remoto no sea apropiado para la organización.
72
+
73
+ 1. Actualizar y abrir Figma Desktop.
74
+ 2. Abrir un archivo Design.
75
+ 3. Activar Dev Mode con `Shift+D`.
76
+ 4. En el panel Inspect, habilitar Desktop MCP.
77
+ 5. Mantener Figma Desktop y el archivo abiertos.
78
+
79
+ Endpoint local:
80
+
81
+ ```text
82
+ http://127.0.0.1:3845/mcp
83
+ ```
84
+
85
+ Claude Code:
86
+
87
+ ```bash
88
+ claude mcp add --transport http figma-desktop http://127.0.0.1:3845/mcp
89
+ ```
90
+
91
+ Configuración genérica:
92
+
93
+ ```json
94
+ {
95
+ "servers": {
96
+ "figma-desktop": {
97
+ "url": "http://127.0.0.1:3845/mcp",
98
+ "type": "http"
99
+ }
100
+ }
101
+ }
102
+ ```
103
+
104
+ ## Verificación dentro del agente
105
+
106
+ La CLI de Bacano no puede inspeccionar el registro MCP ni el OAuth privado de
107
+ todos los agentes. Dentro de la conversación, comprobar:
108
+
109
+ 1. Que exista una herramienta Figma disponible.
110
+ 2. Ejecutar `whoami` cuando el servidor la exponga.
111
+ 3. Leer un frame pequeño mediante su enlace exacto.
112
+ 4. Confirmar que el agente recibe contexto de diseño, no solo una captura.
113
+
114
+ El servidor remoto trabaja mediante un enlace exacto al frame/capa. La
115
+ selección actual sin enlace está disponible únicamente con el servidor Desktop.
116
+
117
+ Según Figma, los límites dependen del plan y asiento. Actualmente un asiento Dev
118
+ o Full en Professional admite hasta 200 lecturas diarias y 10 por minuto; Figma
119
+ puede modificar esos límites. Ante `429`, conservar el snapshot y reanudar
120
+ después, sin repetir nodos ya extraídos.
121
+
122
+ Fuentes oficiales:
123
+
124
+ - https://developers.figma.com/docs/figma-mcp-server/remote-server-installation/
125
+ - https://developers.figma.com/docs/figma-mcp-server/local-server-installation/
126
+ - https://developers.figma.com/docs/figma-mcp-server/tools-and-prompts/
127
+ - https://developers.figma.com/docs/figma-mcp-server/rate-limits-access/
@@ -0,0 +1,35 @@
1
+ # REST API como respaldo
2
+
3
+ Usar REST solamente cuando el usuario autorice crear un snapshot y el MCP
4
+ oficial/Desktop/Bridge no estén disponibles. REST entrega datos más crudos y no
5
+ reemplaza el contexto preparado por `get_design_context`.
6
+
7
+ ## Autenticación
8
+
9
+ Figma admite OAuth y personal access tokens. Para una tarea local controlada,
10
+ inyectar el token mediante el entorno del proceso:
11
+
12
+ ```bash
13
+ export FIGMA_ACCESS_TOKEN="..."
14
+ ```
15
+
16
+ No escribirlo en `.env` versionado, argumentos de terminal persistidos,
17
+ manifiestos, código cliente, capturas o registros. `doctor` informa únicamente
18
+ si la variable existe; nunca lee ni muestra su valor.
19
+
20
+ ## Extracción responsable
21
+
22
+ 1. Obtener el file key desde el enlace autorizado.
23
+ 2. Consultar solo páginas/nodos necesarios.
24
+ 3. Registrar versión/fecha del archivo en el snapshot.
25
+ 4. Descargar imágenes/assets una vez y conservar su procedencia.
26
+ 5. Respetar `429` y encabezados de espera; no crear reintentos agresivos.
27
+ 6. No guardar el JSON completo si contiene páginas ajenas al alcance.
28
+
29
+ El snapshot resultante debe seguir `extraction-contract.md`. Nunca incluir el
30
+ token ni URLs firmadas temporales como contrato permanente.
31
+
32
+ Fuentes oficiales:
33
+
34
+ - https://developers.figma.com/docs/rest-api/authentication/
35
+ - https://developers.figma.com/docs/rest-api/rate-limits/
@@ -0,0 +1,158 @@
1
+ # Flujo de contenido entre Figma, desarrollo y Bacano
2
+
3
+ Diseño, programación y gestión editorial pueden comenzar en cualquier orden.
4
+ La coordinación se hace mediante un blueprint de contenido; Figma no se
5
+ convierte en base de datos y los componentes no dependen de datos temporales.
6
+
7
+ ## 1. Clasificar la propiedad de cada dato
8
+
9
+ Antes de crear campos o fixtures, asignar cada valor a una fuente:
10
+
11
+ | Información | Fuente habitual |
12
+ | --- | --- |
13
+ | Títulos, textos, banners y bloques editoriales | Bacano Content |
14
+ | Productos, variantes, precios y disponibilidad | Catálogo y live state del SDK |
15
+ | Legal, branding y configuración del sitio | `publicConfig` |
16
+ | Carrito, checkout, cliente y pedidos | Módulo correspondiente del SDK |
17
+ | Etiquetas permanentes de interfaz | Código e i18n del storefront |
18
+
19
+ No duplicar en Content información que ya tiene una fuente funcional. Un valor
20
+ visible en Figma no implica que sea editorial.
21
+
22
+ ## 2. Elegir el punto de partida
23
+
24
+ ### Figma primero
25
+
26
+ 1. Extraer el contenido público visible y sus assets autorizados.
27
+ 2. Proponer tipos, keys, campos y entradas en `content-blueprint.json`.
28
+ 3. Crear fixtures locales que implementen el view model de la página.
29
+ 4. Implementar y validar el diseño contra esos fixtures.
30
+ 5. Entregar el blueprint al equipo de Content para crearlo en Bacano.
31
+ 6. Conectar las keys reales sin modificar los componentes visuales.
32
+
33
+ ### Bacano Content primero
34
+
35
+ 1. Inventariar tipos, campos, keys y entradas existentes mediante contratos
36
+ públicos documentados.
37
+ 2. Mapear los frames de Figma al contenido real.
38
+ 3. Señalar incompatibilidades de estructura antes de programar.
39
+ 4. Reutilizar las keys existentes; no crear aliases o estructuras paralelas
40
+ únicamente para hacer coincidir un frame.
41
+
42
+ ### Fuentes en paralelo
43
+
44
+ Comparar el blueprint propuesto con Bacano. Clasificar cada sección como
45
+ `ready`, `missing`, `needs-mapping` o `blocked` y asignar responsable. No
46
+ sobrescribir datos reales ni asumir que el diseño más reciente autoriza una
47
+ migración editorial.
48
+
49
+ ## 3. Blueprint de entrega
50
+
51
+ Guardar el archivo junto al snapshot:
52
+
53
+ ```text
54
+ .bacano/designs/<proyecto>/content-blueprint.json
55
+ ```
56
+
57
+ Estructura recomendada:
58
+
59
+ ```json
60
+ {
61
+ "schemaVersion": 1,
62
+ "project": "mi-storefront",
63
+ "source": "figma",
64
+ "routes": [
65
+ {
66
+ "route": "/",
67
+ "sections": [
68
+ {
69
+ "sectionKey": "home-hero",
70
+ "figmaNodeId": "1:20",
71
+ "component": "HomeHero",
72
+ "ownership": "content",
73
+ "contentTypeKey": "home_hero",
74
+ "entryKey": "home-main-hero",
75
+ "status": "missing",
76
+ "fields": [
77
+ {
78
+ "key": "title",
79
+ "type": "text",
80
+ "required": true,
81
+ "sampleValue": "Compra para cada etapa"
82
+ }
83
+ ],
84
+ "fixture": "fixtures/home-hero.json"
85
+ }
86
+ ]
87
+ }
88
+ ]
89
+ }
90
+ ```
91
+
92
+ El blueprint es una propuesta y una evidencia de coordinación, no una orden de
93
+ escritura. No incluir IDs internos innecesarios, secretos, datos personales ni
94
+ respuestas crudas de GraphQL. Mantener keys estables y registrar cambios que
95
+ rompan el contrato.
96
+
97
+ ## 4. Fixtures temporales
98
+
99
+ - Usar contenido real del diseño cuando esté disponible; evitar Lorem ipsum.
100
+ - Guardarlos en un único directorio y cargarlos mediante un adaptador explícito.
101
+ - Hacer que implementen el view model que consumen los componentes, no una
102
+ copia inventada de respuestas GraphQL ni de internals del SDK.
103
+ - Mantener el mapper de Bacano separado: `Content` real y fixture deben terminar
104
+ en el mismo view model.
105
+ - Incluir estados vacío, incompleto y contenido largo en pruebas separadas; no
106
+ convertirlos en el contenido principal de la página.
107
+ - No crear fixtures para precios, stock, autenticación, carrito, checkout,
108
+ pedidos o atribución.
109
+
110
+ Una forma válida de separación es:
111
+
112
+ ```text
113
+ fixture ───────┐
114
+ ├─> PageContentViewModel ─> componentes
115
+ Bacano mapper ─┘
116
+ ```
117
+
118
+ El cambio de fuente debe ocurrir en el loader o repositorio de contenido, nunca
119
+ mediante condicionales repartidos por los componentes.
120
+
121
+ ## 5. Entrega al equipo de Content
122
+
123
+ Entregar una tabla legible con:
124
+
125
+ - Ruta y sección visible.
126
+ - Tipo y entry key sugeridos.
127
+ - Campos, tipo, obligatoriedad y ejemplo tomado de Figma.
128
+ - Assets desktop/mobile, proporción y uso esperado.
129
+ - Estado y responsable.
130
+ - Dependencias entre componentes, listas y entradas.
131
+
132
+ El equipo de Content crea o ajusta la estructura desde Bacano. En esta versión
133
+ la skill no ejecuta escrituras, importaciones ni cargas de media en Bacano. Si
134
+ la creación real es necesaria para continuar, solicitar confirmación al usuario
135
+ en vez de simular que ocurrió.
136
+
137
+ ## 6. Cambio a Bacano real
138
+
139
+ Antes de retirar el fixture:
140
+
141
+ 1. Confirmar que las keys reales coinciden con el blueprint.
142
+ 2. Validar campos requeridos, media y contenido largo.
143
+ 3. Comparar la página con Figma usando Content real.
144
+ 4. Verificar preview si la capacidad está activa.
145
+ 5. Eliminar el fixture del camino de ejecución, no necesariamente de los tests.
146
+
147
+ En producción no se permite fallback silencioso a fixtures. Si una página
148
+ depende de contenido temporal, reportar `❌ NO LISTO` con las rutas y secciones
149
+ afectadas. Los fixtures pueden conservarse como datos de pruebas unitarias o
150
+ visuales siempre que no entren en el bundle o loader de producción.
151
+
152
+ ## 7. Automatización futura
153
+
154
+ La creación automática debe pertenecer a una herramienta administrativa
155
+ separada del SDK público del storefront. Requiere autenticación con alcance por
156
+ empresa/website, previsualización de cambios, idempotencia, auditoría y
157
+ confirmación antes de escribir. Nunca exponer esas credenciales en el navegador
158
+ ni convertir el blueprint en una importación implícita.
@@ -0,0 +1,102 @@
1
+ # Contrato de extracción normalizado
2
+
3
+ Cada fuente Figma debe producir el mismo checkpoint para que cambiar de MCP a
4
+ Desktop o REST no obligue a rehacer la implementación.
5
+
6
+ ## Estructura sugerida
7
+
8
+ ```text
9
+ .bacano/designs/<proyecto>/
10
+ ├── manifest.json
11
+ ├── tokens.json
12
+ ├── content-blueprint.json
13
+ ├── fixtures/
14
+ ├── assets/
15
+ └── screenshots/
16
+ ```
17
+
18
+ `content-blueprint.json` y `fixtures/` son necesarios únicamente cuando el
19
+ diseño contiene información editorial que todavía no existe o no está lista en
20
+ Bacano. Su contrato se define en
21
+ [content-workflow.md](content-workflow.md). No crear fixtures para catálogo,
22
+ precios, inventario, checkout, identidad ni otras fuentes funcionales del SDK.
23
+
24
+ No modificar `.gitignore` automáticamente. Preguntar o seguir la política del
25
+ proyecto antes de versionar imágenes o archivos grandes.
26
+
27
+ Una política opcional, sin secretos, puede guardarse en
28
+ `.bacano/designs/config.json`:
29
+
30
+ ```json
31
+ {
32
+ "preferredSource": "figma-official",
33
+ "fallbackSources": [
34
+ "figma-desktop",
35
+ "desktop-bridge",
36
+ "rest-snapshot"
37
+ ],
38
+ "fallbackMode": "ask",
39
+ "reuseCachedExtraction": true
40
+ }
41
+ ```
42
+
43
+ `fallbackMode` debe ser `ask` por defecto. Usar `auto` únicamente cuando el
44
+ usuario haya autorizado expresamente todas las fuentes declaradas.
45
+
46
+ ## Manifiesto mínimo
47
+
48
+ ```json
49
+ {
50
+ "schemaVersion": 1,
51
+ "project": "mi-storefront",
52
+ "source": {
53
+ "adapter": "figma-official",
54
+ "fileKey": "opcional",
55
+ "fileVersion": "opcional",
56
+ "extractedAt": "2026-08-24T12:00:00.000Z"
57
+ },
58
+ "capabilities": {
59
+ "variables": "partial",
60
+ "components": "available",
61
+ "autoLayout": "available",
62
+ "responsiveFrames": "partial",
63
+ "interactions": "partial"
64
+ },
65
+ "pages": [
66
+ {
67
+ "route": "/",
68
+ "name": "Home",
69
+ "status": "extracted",
70
+ "desktop": { "nodeId": "1:2", "url": "https://figma.com/..." },
71
+ "mobile": { "nodeId": "1:3", "url": "https://figma.com/..." }
72
+ }
73
+ ],
74
+ "assumptions": []
75
+ }
76
+ ```
77
+
78
+ Valores de `adapter`:
79
+
80
+ - `figma-official`: MCP remoto oficial.
81
+ - `figma-desktop`: MCP local oficial.
82
+ - `desktop-bridge`: bridge comunitario.
83
+ - `rest-snapshot`: REST autorizado.
84
+
85
+ Capacidades: `available`, `partial` o `missing`.
86
+ Estado de página: `pending`, `extracted`, `implemented` o `validated`.
87
+
88
+ Cada página requiere `route`, `name`, `status` y al menos un frame desktop o
89
+ mobile con `nodeId` o `url`. No guardar secretos ni respuestas MCP completas.
90
+
91
+ ## Estrategia de consultas
92
+
93
+ - Obtener metadata suficiente para construir el mapa de páginas.
94
+ - Extraer cada frame principal una vez.
95
+ - Solicitar subnodos solo si el contexto principal es incompleto.
96
+ - Reutilizar tokens/assets comunes entre páginas.
97
+ - Al reanudar, comparar `fileVersion` y repetir únicamente nodos cambiados.
98
+ - Conservar las inferencias en `assumptions` para revisión humana.
99
+
100
+ Para sitios medianos, una extracción organizada suele requerir decenas de
101
+ lecturas, no una consulta por cada capa. La cifra exacta depende de la calidad y
102
+ estructura del archivo, no únicamente del número de páginas.
@@ -0,0 +1,39 @@
1
+ # Validación visual y funcional
2
+
3
+ Validar por cortes para evitar corregir 70 secciones al final.
4
+
5
+ ## Evidencia mínima por página
6
+
7
+ - URL/ruta implementada.
8
+ - Frame y node ID de referencia.
9
+ - Viewport desktop y mobile utilizados.
10
+ - Captura de referencia y captura de implementación.
11
+ - Diferencias intencionales e inferencias.
12
+ - Resultado de accesibilidad/interacción aplicable.
13
+
14
+ ## Orden de revisión
15
+
16
+ 1. Estructura, jerarquía y contenido.
17
+ 2. Breakpoints, anchuras y flujo responsive.
18
+ 3. Tipografía, color, espacios y radios.
19
+ 4. Imágenes, recortes, densidad y estado de carga.
20
+ 5. Hover, focus, teclado, formularios y navegación.
21
+ 6. Estados loading, vacío, error y contenido largo.
22
+ 7. Integración Bacano y ausencia de datos inventados.
23
+ 8. Contenido editorial real o fixture temporal claramente identificado.
24
+
25
+ Usar Playwright o la herramienta de navegador disponible para capturas y
26
+ traces. Una comparación pixel a pixel puede ayudar, pero no debe ocultar fallos
27
+ semánticos o funcionales.
28
+
29
+ ## Cierre técnico
30
+
31
+ - Typecheck, lint, tests y build del proyecto.
32
+ - Rutas generadas y sitemap esperado.
33
+ - Sin errores de consola/recurso.
34
+ - Sin GraphQL directo ni solicitudes duplicadas evitables.
35
+ - HTML estático presente cuando el contrato es Static First.
36
+ - Media responsive sin upscale visible.
37
+ - Ningún fixture, placeholder o fallback editorial temporal en el build de
38
+ producción.
39
+ - `$bacano-web` ejecutado al final cuando corresponda.
@@ -0,0 +1,309 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { createHash } from "node:crypto";
4
+ import {
5
+ cpSync,
6
+ existsSync,
7
+ lstatSync,
8
+ mkdirSync,
9
+ readdirSync,
10
+ readFileSync,
11
+ readlinkSync,
12
+ rmSync,
13
+ symlinkSync,
14
+ } from "node:fs";
15
+ import { connect } from "node:net";
16
+ import { dirname, relative, resolve } from "node:path";
17
+ import { createInterface } from "node:readline/promises";
18
+ import { fileURLToPath } from "node:url";
19
+ import { validateSnapshot } from "./snapshot.mjs";
20
+
21
+ const skillName = "bacano-figma-web";
22
+ const scriptDirectory = dirname(fileURLToPath(import.meta.url));
23
+ const sourceSkill = resolve(scriptDirectory, "..");
24
+ const projectRoot = process.cwd();
25
+ const canonicalSkill = resolve(projectRoot, ".agents", "skills", skillName);
26
+
27
+ const adapterPaths = {
28
+ antigravity: canonicalSkill,
29
+ claude: resolve(projectRoot, ".claude", "skills", skillName),
30
+ codex: resolve(projectRoot, ".codex", "skills", skillName),
31
+ };
32
+
33
+ function usage() {
34
+ console.log(`Bacano Figma Web
35
+
36
+ Uso:
37
+ bacano-figma-web install [--agent antigravity|claude|codex|all] [--force]
38
+ bacano-figma-web check
39
+ bacano-figma-web doctor [--json]
40
+ bacano-figma-web validate-snapshot <ruta/manifest.json>
41
+
42
+ La copia canónica vive en .agents/skills/${skillName}.
43
+ Claude y Codex reciben enlaces a esa misma copia.`);
44
+ }
45
+
46
+ function option(name) {
47
+ const index = process.argv.indexOf(name);
48
+ return index >= 0 ? process.argv[index + 1] : null;
49
+ }
50
+
51
+ function relativeDisplay(path) {
52
+ const value = relative(projectRoot, path);
53
+ return value || ".";
54
+ }
55
+
56
+ function listFiles(directory, base = directory) {
57
+ return readdirSync(directory, { withFileTypes: true })
58
+ .flatMap((entry) => {
59
+ const path = resolve(directory, entry.name);
60
+ if (entry.isDirectory()) return listFiles(path, base);
61
+ if (!entry.isFile()) return [];
62
+ return [{ path, name: relative(base, path) }];
63
+ })
64
+ .sort((left, right) => left.name.localeCompare(right.name));
65
+ }
66
+
67
+ function skillDigest(directory) {
68
+ const hash = createHash("sha256");
69
+ for (const file of listFiles(directory)) {
70
+ hash.update(file.name);
71
+ hash.update("\0");
72
+ hash.update(readFileSync(file.path));
73
+ hash.update("\0");
74
+ }
75
+ return hash.digest("hex");
76
+ }
77
+
78
+ function isSkillDirectory(directory) {
79
+ return existsSync(resolve(directory, "SKILL.md"));
80
+ }
81
+
82
+ function removeExisting(path, force) {
83
+ if (!existsSync(path)) return;
84
+ if (!force) {
85
+ throw new Error(
86
+ `${relativeDisplay(path)} ya existe. Usa --force para reemplazarlo.`,
87
+ );
88
+ }
89
+ rmSync(path, { recursive: true, force: true });
90
+ }
91
+
92
+ function installCanonical(force) {
93
+ if (resolve(sourceSkill) === canonicalSkill) return;
94
+
95
+ if (isSkillDirectory(canonicalSkill)) {
96
+ if (skillDigest(canonicalSkill) === skillDigest(sourceSkill)) return;
97
+ removeExisting(canonicalSkill, force);
98
+ } else if (existsSync(canonicalSkill)) {
99
+ removeExisting(canonicalSkill, force);
100
+ }
101
+
102
+ mkdirSync(dirname(canonicalSkill), { recursive: true });
103
+ cpSync(sourceSkill, canonicalSkill, { recursive: true });
104
+ }
105
+
106
+ function installAdapter(agent, force) {
107
+ const target = adapterPaths[agent];
108
+ if (!target || target === canonicalSkill) return;
109
+
110
+ if (existsSync(target)) {
111
+ const stat = lstatSync(target);
112
+ if (stat.isSymbolicLink()) {
113
+ const resolvedLink = resolve(dirname(target), readlinkSync(target));
114
+ if (resolvedLink === canonicalSkill) return;
115
+ }
116
+ removeExisting(target, force);
117
+ }
118
+
119
+ mkdirSync(dirname(target), { recursive: true });
120
+ const targetReference =
121
+ process.platform === "win32"
122
+ ? canonicalSkill
123
+ : relative(dirname(target), canonicalSkill);
124
+ symlinkSync(
125
+ targetReference,
126
+ target,
127
+ process.platform === "win32" ? "junction" : "dir",
128
+ );
129
+ }
130
+
131
+ async function chooseAgent() {
132
+ const explicit = option("--agent");
133
+ if (explicit) return explicit;
134
+
135
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
136
+ throw new Error(
137
+ "Indica --agent antigravity, --agent claude, --agent codex o --agent all.",
138
+ );
139
+ }
140
+
141
+ const prompt = createInterface({
142
+ input: process.stdin,
143
+ output: process.stdout,
144
+ });
145
+ const answer = await prompt.question(
146
+ "¿Qué agente usará la skill? [1] Antigravity [2] Claude Code [3] Codex [4] Todos: ",
147
+ );
148
+ prompt.close();
149
+
150
+ return (
151
+ { 1: "antigravity", 2: "claude", 3: "codex", 4: "all" }[answer.trim()] ??
152
+ answer.trim().toLowerCase()
153
+ );
154
+ }
155
+
156
+ function validateAgent(agent) {
157
+ if (!["antigravity", "claude", "codex", "all"].includes(agent)) {
158
+ throw new Error(`Agente no reconocido: ${agent || "(vacío)"}.`);
159
+ }
160
+ }
161
+
162
+ async function installSkill() {
163
+ const agent = await chooseAgent();
164
+ validateAgent(agent);
165
+ const force = process.argv.includes("--force");
166
+
167
+ installCanonical(force);
168
+ if (agent === "all") {
169
+ installAdapter("claude", force);
170
+ installAdapter("codex", force);
171
+ } else {
172
+ installAdapter(agent, force);
173
+ }
174
+
175
+ console.log("Skill Bacano Figma Web instalada.");
176
+ console.log(`Fuente canónica: ${relativeDisplay(canonicalSkill)}`);
177
+ if (agent === "claude" || agent === "all") {
178
+ console.log(`Claude Code: ${relativeDisplay(adapterPaths.claude)}`);
179
+ }
180
+ if (agent === "codex" || agent === "all") {
181
+ console.log(`Codex: ${relativeDisplay(adapterPaths.codex)}`);
182
+ }
183
+ console.log("Ejecuta bacano-figma-web doctor para revisar la conexión.");
184
+ }
185
+
186
+ function checkPath(name, path) {
187
+ if (!existsSync(path)) {
188
+ return { name, path: relativeDisplay(path), status: "missing" };
189
+ }
190
+ if (!isSkillDirectory(path)) {
191
+ return { name, path: relativeDisplay(path), status: "invalid" };
192
+ }
193
+ return {
194
+ name,
195
+ path: relativeDisplay(path),
196
+ status:
197
+ skillDigest(path) === skillDigest(sourceSkill) ? "current" : "outdated",
198
+ link: lstatSync(path).isSymbolicLink(),
199
+ };
200
+ }
201
+
202
+ function skillStatus() {
203
+ const results = [
204
+ checkPath("canonical", canonicalSkill),
205
+ checkPath("antigravity", adapterPaths.antigravity),
206
+ checkPath("claude", adapterPaths.claude),
207
+ checkPath("codex", adapterPaths.codex),
208
+ ];
209
+ return results.filter(
210
+ (result, index) =>
211
+ results.findIndex((candidate) => candidate.path === result.path) ===
212
+ index,
213
+ );
214
+ }
215
+
216
+ function check() {
217
+ const results = skillStatus();
218
+ console.table(results);
219
+ if (results[0]?.status !== "current") {
220
+ console.error(
221
+ "Bacano Figma Web no está instalada o está desactualizada. Ejecuta bacano-figma-web install.",
222
+ );
223
+ process.exitCode = 1;
224
+ return;
225
+ }
226
+ if (results.slice(1).some((result) => result.link === false)) {
227
+ console.warn(
228
+ "Hay copias físicas en adaptadores. Reinstala con --force para evitar divergencias.",
229
+ );
230
+ }
231
+ if (results.some((result) => result.status === "outdated")) {
232
+ process.exitCode = 1;
233
+ }
234
+ }
235
+
236
+ function canConnect(port, timeout = 250) {
237
+ return new Promise((finish) => {
238
+ const socket = connect({ host: "127.0.0.1", port });
239
+ const close = (available) => {
240
+ socket.destroy();
241
+ finish(available);
242
+ };
243
+ socket.setTimeout(timeout);
244
+ socket.once("connect", () => close(true));
245
+ socket.once("timeout", () => close(false));
246
+ socket.once("error", () => close(false));
247
+ });
248
+ }
249
+
250
+ async function doctor() {
251
+ const bridgeChecks = await Promise.all(
252
+ Array.from({ length: 10 }, (_, index) => 9223 + index).map(
253
+ async (port) => ({
254
+ port,
255
+ available: await canConnect(port),
256
+ }),
257
+ ),
258
+ );
259
+ const report = {
260
+ skill: skillStatus()[0]?.status ?? "missing",
261
+ officialRemoteMcp: "verify-in-agent",
262
+ officialDesktopMcp: (await canConnect(3845))
263
+ ? "detected-on-127.0.0.1:3845"
264
+ : "not-detected",
265
+ communityBridgePorts: bridgeChecks
266
+ .filter((result) => result.available)
267
+ .map((result) => result.port),
268
+ restToken: process.env.FIGMA_ACCESS_TOKEN ? "configured" : "not-configured",
269
+ };
270
+
271
+ if (process.argv.includes("--json")) {
272
+ console.log(JSON.stringify(report, null, 2));
273
+ return;
274
+ }
275
+
276
+ console.log("Diagnóstico Bacano Figma Web");
277
+ console.table([
278
+ { control: "Skill instalada", result: report.skill },
279
+ {
280
+ control: "MCP remoto oficial",
281
+ result: "Verificar OAuth y whoami dentro del agente",
282
+ },
283
+ { control: "MCP local oficial", result: report.officialDesktopMcp },
284
+ {
285
+ control: "Desktop Bridge comunitario",
286
+ result: report.communityBridgePorts.length
287
+ ? `Puerto(s) detectado(s): ${report.communityBridgePorts.join(", ")}`
288
+ : "No detectado",
289
+ },
290
+ { control: "Token REST (solo presencia)", result: report.restToken },
291
+ ]);
292
+ console.log(
293
+ "Este diagnóstico no lee secretos ni puede certificar el OAuth del cliente MCP.",
294
+ );
295
+ }
296
+
297
+ const command = process.argv[2] ?? "help";
298
+
299
+ try {
300
+ if (command === "install") await installSkill();
301
+ else if (command === "check") check();
302
+ else if (command === "doctor") await doctor();
303
+ else if (command === "validate-snapshot")
304
+ validateSnapshot(process.argv[3], projectRoot);
305
+ else usage();
306
+ } catch (error) {
307
+ console.error(error instanceof Error ? error.message : String(error));
308
+ process.exitCode = 1;
309
+ }
@@ -0,0 +1,139 @@
1
+ import { existsSync, readFileSync, statSync } from "node:fs";
2
+ import { relative, resolve } from "node:path";
3
+
4
+ function object(value) {
5
+ return typeof value === "object" && value !== null && !Array.isArray(value);
6
+ }
7
+
8
+ function validateFrame(frame, path, errors) {
9
+ if (!object(frame)) {
10
+ errors.push(`${path} debe ser un objeto.`);
11
+ return;
12
+ }
13
+ if (typeof frame.nodeId !== "string" && typeof frame.url !== "string") {
14
+ errors.push(`${path} requiere nodeId o url.`);
15
+ }
16
+ }
17
+
18
+ function inspectSecrets(value, errors, path = "manifest") {
19
+ const secretNames = new Set([
20
+ "accesstoken",
21
+ "authorization",
22
+ "cookie",
23
+ "password",
24
+ "personalaccesstoken",
25
+ "secret",
26
+ ]);
27
+ if (Array.isArray(value)) {
28
+ value.forEach((item, index) =>
29
+ inspectSecrets(item, errors, `${path}[${index}]`),
30
+ );
31
+ return;
32
+ }
33
+ if (!object(value)) return;
34
+ for (const [key, child] of Object.entries(value)) {
35
+ if (secretNames.has(key.toLowerCase())) {
36
+ errors.push(`${path}.${key} no puede contener secretos.`);
37
+ }
38
+ inspectSecrets(child, errors, `${path}.${key}`);
39
+ }
40
+ }
41
+
42
+ export function validateManifest(manifest) {
43
+ const errors = [];
44
+ const adapters = new Set([
45
+ "figma-official",
46
+ "figma-desktop",
47
+ "desktop-bridge",
48
+ "rest-snapshot",
49
+ ]);
50
+ const capabilities = new Set(["available", "partial", "missing"]);
51
+ const pageStatuses = new Set([
52
+ "pending",
53
+ "extracted",
54
+ "implemented",
55
+ "validated",
56
+ ]);
57
+
58
+ if (!object(manifest)) return ["El manifiesto debe ser un objeto JSON."];
59
+ if (manifest.schemaVersion !== 1) errors.push("schemaVersion debe ser 1.");
60
+ if (typeof manifest.project !== "string" || !manifest.project.trim()) {
61
+ errors.push("project es obligatorio.");
62
+ }
63
+ if (!object(manifest.source)) {
64
+ errors.push("source es obligatorio.");
65
+ } else {
66
+ if (!adapters.has(manifest.source.adapter)) {
67
+ errors.push("source.adapter no es compatible.");
68
+ }
69
+ if (
70
+ typeof manifest.source.extractedAt !== "string" ||
71
+ Number.isNaN(Date.parse(manifest.source.extractedAt))
72
+ ) {
73
+ errors.push("source.extractedAt debe ser una fecha ISO válida.");
74
+ }
75
+ }
76
+ if (manifest.capabilities !== undefined) {
77
+ if (!object(manifest.capabilities)) {
78
+ errors.push("capabilities debe ser un objeto.");
79
+ } else {
80
+ for (const [name, status] of Object.entries(manifest.capabilities)) {
81
+ if (!capabilities.has(status)) {
82
+ errors.push(`capabilities.${name} no tiene un estado válido.`);
83
+ }
84
+ }
85
+ }
86
+ }
87
+ if (!Array.isArray(manifest.pages) || manifest.pages.length === 0) {
88
+ errors.push("pages requiere al menos una página.");
89
+ } else {
90
+ const routes = new Set();
91
+ manifest.pages.forEach((page, index) => {
92
+ const path = `pages[${index}]`;
93
+ if (!object(page)) {
94
+ errors.push(`${path} debe ser un objeto.`);
95
+ return;
96
+ }
97
+ if (typeof page.route !== "string" || !page.route.startsWith("/")) {
98
+ errors.push(`${path}.route debe comenzar por /.`);
99
+ } else if (routes.has(page.route)) {
100
+ errors.push(`${path}.route está duplicada.`);
101
+ } else {
102
+ routes.add(page.route);
103
+ }
104
+ if (typeof page.name !== "string" || !page.name.trim()) {
105
+ errors.push(`${path}.name es obligatorio.`);
106
+ }
107
+ if (!pageStatuses.has(page.status)) {
108
+ errors.push(`${path}.status no es válido.`);
109
+ }
110
+ if (!page.desktop && !page.mobile) {
111
+ errors.push(`${path} requiere un frame desktop o mobile.`);
112
+ }
113
+ if (page.desktop) validateFrame(page.desktop, `${path}.desktop`, errors);
114
+ if (page.mobile) validateFrame(page.mobile, `${path}.mobile`, errors);
115
+ });
116
+ }
117
+ inspectSecrets(manifest, errors);
118
+ return errors;
119
+ }
120
+
121
+ export function validateSnapshot(path, projectRoot) {
122
+ if (!path) throw new Error("Indica la ruta de manifest.json.");
123
+ const manifestPath = resolve(projectRoot, path);
124
+ if (!existsSync(manifestPath) || !statSync(manifestPath).isFile()) {
125
+ throw new Error(`No existe el manifiesto: ${path}`);
126
+ }
127
+ const manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
128
+ const errors = validateManifest(manifest);
129
+ if (errors.length) {
130
+ console.error("Snapshot inválido:");
131
+ for (const error of errors) console.error(`- ${error}`);
132
+ process.exitCode = 1;
133
+ return;
134
+ }
135
+ console.log(`Snapshot válido: ${relative(projectRoot, manifestPath)}`);
136
+ console.log(
137
+ `${manifest.pages.length} página(s), fuente ${manifest.source.adapter}.`,
138
+ );
139
+ }
@@ -4,5 +4,9 @@ if (!process.env.CI && process.env.BACANO_SDK_HIDE_SKILL_NOTICE !== "1") {
4
4
  Instálala una vez: pnpm exec bacano-web install --agent all
5
5
  Verifica su estado: pnpm exec bacano-web check
6
6
  Luego invócala desde tu agente para iniciar la auditoría.
7
+
8
+ Para implementar un diseño Figma con el cascarón Bacano:
9
+ Instala la skill: pnpm exec bacano-figma-web install --agent all
10
+ Revisa la conexión: pnpm exec bacano-figma-web doctor
7
11
  `);
8
12
  }