@bacano/sdk 1.1.12 → 1.1.15
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/CONTENT_TYPE_MANAGEMENT.md +284 -29
- package/README.md +18 -13
- package/content-management-types.d.ts +291 -0
- package/content-management.cjs +107 -5
- package/content-management.d.ts +2 -132
- package/content-management.js +107 -5
- package/package.json +1 -1
- package/skills/bacano-figma-web/SKILL.md +30 -10
- package/skills/bacano-figma-web/references/bacano-mapping.md +3 -3
- package/skills/bacano-figma-web/references/content-media.md +57 -0
- package/skills/bacano-figma-web/references/content-workflow.md +50 -14
- package/skills/bacano-figma-web/references/extraction-contract.md +3 -0
- package/skills/bacano-figma-web/references/implementation-quality.md +67 -0
- package/skills/bacano-figma-web/references/visual-validation.md +4 -0
package/content-management.js
CHANGED
|
@@ -33,6 +33,34 @@ function encodeIdentifier(identifier) {
|
|
|
33
33
|
}
|
|
34
34
|
return encodeURIComponent(normalized);
|
|
35
35
|
}
|
|
36
|
+
function entryListPath(input = {}) {
|
|
37
|
+
const search = new URLSearchParams();
|
|
38
|
+
if (input.contentType) search.set("contentType", input.contentType);
|
|
39
|
+
if (input.locale) search.set("locale", input.locale);
|
|
40
|
+
if (input.status) search.set("status", input.status);
|
|
41
|
+
if (input.limit !== void 0) search.set("limit", String(input.limit));
|
|
42
|
+
if (input.offset !== void 0) search.set("offset", String(input.offset));
|
|
43
|
+
const query = search.toString();
|
|
44
|
+
return query ? `/entries?${query}` : "/entries";
|
|
45
|
+
}
|
|
46
|
+
function mediaListPath(input = {}) {
|
|
47
|
+
const search = new URLSearchParams();
|
|
48
|
+
if (input.search) search.set("search", input.search);
|
|
49
|
+
if (input.limit !== void 0) search.set("limit", String(input.limit));
|
|
50
|
+
if (input.offset !== void 0) search.set("offset", String(input.offset));
|
|
51
|
+
const query = search.toString();
|
|
52
|
+
return query ? `/media?${query}` : "/media";
|
|
53
|
+
}
|
|
54
|
+
function uploadBody(file) {
|
|
55
|
+
if ("arrayBuffer" in file) return file;
|
|
56
|
+
if (file instanceof ArrayBuffer) return file;
|
|
57
|
+
const bytes = new Uint8Array(file.byteLength);
|
|
58
|
+
bytes.set(file);
|
|
59
|
+
return bytes.buffer;
|
|
60
|
+
}
|
|
61
|
+
function encodedHeader(value) {
|
|
62
|
+
return encodeURIComponent(value.trim());
|
|
63
|
+
}
|
|
36
64
|
function createContentManagementClient(config) {
|
|
37
65
|
const apiUrl = normalizeApiUrl(config.apiUrl);
|
|
38
66
|
const fetchImplementation = config.fetch ?? globalThis.fetch;
|
|
@@ -63,7 +91,7 @@ function createContentManagementClient(config) {
|
|
|
63
91
|
headers: {
|
|
64
92
|
Accept: "application/json",
|
|
65
93
|
Authorization: `Bearer ${await getAccessToken()}`,
|
|
66
|
-
...init?.body ? { "Content-Type": "application/json" } : {},
|
|
94
|
+
...typeof init?.body === "string" ? { "Content-Type": "application/json" } : {},
|
|
67
95
|
...init?.headers
|
|
68
96
|
}
|
|
69
97
|
}
|
|
@@ -84,8 +112,8 @@ function createContentManagementClient(config) {
|
|
|
84
112
|
}
|
|
85
113
|
return payload.data;
|
|
86
114
|
};
|
|
87
|
-
const lifecycle = (identifier, operation, dryRun) => request(
|
|
88
|
-
|
|
115
|
+
const lifecycle = (resource, identifier, operation, dryRun) => request(
|
|
116
|
+
`/${resource}/${encodeIdentifier(identifier)}/lifecycle`,
|
|
89
117
|
{
|
|
90
118
|
method: "POST",
|
|
91
119
|
body: JSON.stringify({ operation, dryRun })
|
|
@@ -104,8 +132,82 @@ function createContentManagementClient(config) {
|
|
|
104
132
|
method: "PUT",
|
|
105
133
|
body: JSON.stringify(input)
|
|
106
134
|
}),
|
|
107
|
-
inspectLifecycle: (identifier, operation) => lifecycle(identifier, operation, true),
|
|
108
|
-
applyLifecycle: (identifier, operation) => lifecycle(identifier, operation, false)
|
|
135
|
+
inspectLifecycle: (identifier, operation) => lifecycle("types", identifier, operation, true),
|
|
136
|
+
applyLifecycle: (identifier, operation) => lifecycle("types", identifier, operation, false),
|
|
137
|
+
listEntries: (input) => request(entryListPath(input)),
|
|
138
|
+
getEntry: (identifier, locale) => {
|
|
139
|
+
const query = locale ? `?locale=${encodeURIComponent(locale.trim())}` : "";
|
|
140
|
+
return request(
|
|
141
|
+
`/entries/${encodeIdentifier(identifier)}${query}`
|
|
142
|
+
);
|
|
143
|
+
},
|
|
144
|
+
createEntry: (input) => request("/entries", {
|
|
145
|
+
method: "POST",
|
|
146
|
+
body: JSON.stringify(input)
|
|
147
|
+
}),
|
|
148
|
+
updateEntry: (identifier, input) => request(
|
|
149
|
+
`/entries/${encodeIdentifier(identifier)}`,
|
|
150
|
+
{
|
|
151
|
+
method: "PUT",
|
|
152
|
+
body: JSON.stringify(input)
|
|
153
|
+
}
|
|
154
|
+
),
|
|
155
|
+
inspectEntryLifecycle: (identifier, operation) => lifecycle("entries", identifier, operation, true),
|
|
156
|
+
applyEntryLifecycle: (identifier, operation) => lifecycle("entries", identifier, operation, false),
|
|
157
|
+
listMedia: (input) => request(mediaListPath(input)),
|
|
158
|
+
uploadImage: (input) => {
|
|
159
|
+
const fileName = input.fileName.trim();
|
|
160
|
+
if (!fileName) {
|
|
161
|
+
throw new ContentManagementError({
|
|
162
|
+
message: "A descriptive image file name is required.",
|
|
163
|
+
code: "CONTENT_MEDIA_FILE_NAME_REQUIRED",
|
|
164
|
+
status: 0
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
return request("/media/images", {
|
|
168
|
+
method: "POST",
|
|
169
|
+
headers: {
|
|
170
|
+
"Content-Type": "application/octet-stream",
|
|
171
|
+
"X-Bacano-File-Name": encodedHeader(fileName),
|
|
172
|
+
...input.altText ? { "X-Bacano-Alt-Text": encodedHeader(input.altText) } : {}
|
|
173
|
+
},
|
|
174
|
+
body: uploadBody(input.file)
|
|
175
|
+
});
|
|
176
|
+
},
|
|
177
|
+
listGroups: () => request("/groups"),
|
|
178
|
+
getGroup: (identifier) => request(
|
|
179
|
+
`/groups/${encodeIdentifier(identifier)}`
|
|
180
|
+
),
|
|
181
|
+
createGroup: (input) => request("/groups", {
|
|
182
|
+
method: "POST",
|
|
183
|
+
body: JSON.stringify(input)
|
|
184
|
+
}),
|
|
185
|
+
updateGroup: (identifier, input) => request(
|
|
186
|
+
`/groups/${encodeIdentifier(identifier)}`,
|
|
187
|
+
{
|
|
188
|
+
method: "PUT",
|
|
189
|
+
body: JSON.stringify(input)
|
|
190
|
+
}
|
|
191
|
+
),
|
|
192
|
+
inspectGroupLifecycle: (identifier, operation) => lifecycle("groups", identifier, operation, true),
|
|
193
|
+
applyGroupLifecycle: (identifier, operation) => lifecycle("groups", identifier, operation, false),
|
|
194
|
+
listOptionLists: () => request("/option-lists"),
|
|
195
|
+
getOptionList: (identifier) => request(
|
|
196
|
+
`/option-lists/${encodeIdentifier(identifier)}`
|
|
197
|
+
),
|
|
198
|
+
createOptionList: (input) => request("/option-lists", {
|
|
199
|
+
method: "POST",
|
|
200
|
+
body: JSON.stringify(input)
|
|
201
|
+
}),
|
|
202
|
+
updateOptionList: (identifier, input) => request(
|
|
203
|
+
`/option-lists/${encodeIdentifier(identifier)}`,
|
|
204
|
+
{
|
|
205
|
+
method: "PUT",
|
|
206
|
+
body: JSON.stringify(input)
|
|
207
|
+
}
|
|
208
|
+
),
|
|
209
|
+
inspectOptionListLifecycle: (identifier, operation) => lifecycle("option-lists", identifier, operation, true),
|
|
210
|
+
applyOptionListLifecycle: (identifier, operation) => lifecycle("option-lists", identifier, operation, false)
|
|
109
211
|
};
|
|
110
212
|
}
|
|
111
213
|
export {
|
package/package.json
CHANGED
|
@@ -13,7 +13,9 @@ autenticación, marketing o Content por fuera de `@bacano/sdk`.
|
|
|
13
13
|
|
|
14
14
|
Antes de extraer el diseño, identificar:
|
|
15
15
|
|
|
16
|
-
- Web informativa o
|
|
16
|
+
- Web informativa, tienda virtual o sitio híbrido. Si permite una compra
|
|
17
|
+
completa, aplicar también los contratos de tienda a su superficie
|
|
18
|
+
transaccional.
|
|
17
19
|
- Enlace al archivo, página, sección o frame de Figma.
|
|
18
20
|
- Rutas que se implementarán y sus frames desktop/mobile.
|
|
19
21
|
- Repositorio y cascarón Bacano que se reutilizarán.
|
|
@@ -74,11 +76,13 @@ pnpm exec bacano-figma-web validate-snapshot .bacano/designs/<proyecto>/manifest
|
|
|
74
76
|
|
|
75
77
|
## 4. Mapear a Bacano antes de programar
|
|
76
78
|
|
|
77
|
-
Leer [references/bacano-mapping.md](references/bacano-mapping.md)
|
|
78
|
-
[references/content-workflow.md](references/content-workflow.md)
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
79
|
+
Leer [references/bacano-mapping.md](references/bacano-mapping.md),
|
|
80
|
+
[references/content-workflow.md](references/content-workflow.md),
|
|
81
|
+
[references/content-media.md](references/content-media.md) y
|
|
82
|
+
[references/implementation-quality.md](references/implementation-quality.md).
|
|
83
|
+
Preparar una matriz ruta → frame → componente → fuente de datos → Content →
|
|
84
|
+
responsive → preview → estado, y determinar si el trabajo comienza desde
|
|
85
|
+
Figma, desde Bacano Content o desde ambas fuentes.
|
|
82
86
|
|
|
83
87
|
- Reutilizar el starter, tokens y componentes existentes.
|
|
84
88
|
- Consumir Bacano y Content mediante `@bacano/sdk`.
|
|
@@ -96,9 +100,19 @@ nunca repartir datos de prueba dentro de la UI. Si Content ya existe, mapearlo
|
|
|
96
100
|
sin sobrescribir estructuras o entradas. Si el usuario pide materializar el
|
|
97
101
|
blueprint, leer la sección de automatización de
|
|
98
102
|
[references/content-workflow.md](references/content-workflow.md) y el contrato
|
|
99
|
-
`CONTENT_TYPE_MANAGEMENT.md` incluido en `@bacano/sdk`.
|
|
100
|
-
|
|
101
|
-
|
|
103
|
+
`CONTENT_TYPE_MANAGEMENT.md` incluido en `@bacano/sdk`. Crear o modificar tipos,
|
|
104
|
+
componentes, grupos, listas y entradas únicamente mediante el acceso temporal
|
|
105
|
+
y los scopes oficiales. Inventariar esos recursos antes de crear otros y
|
|
106
|
+
preparar entradas en borrador por defecto; la media permanece en el
|
|
107
|
+
backoffice salvo que el usuario conceda `content_media:upload`. Con ese scope,
|
|
108
|
+
buscar primero imágenes reutilizables y cargar bytes únicamente mediante
|
|
109
|
+
`listMedia()` y `uploadImage()`; publicar requiere permiso y aprobación
|
|
110
|
+
explícitos.
|
|
111
|
+
|
|
112
|
+
Al proponer keys nuevas, resolver primero el idioma según
|
|
113
|
+
`CONTENT_TYPE_MANAGEMENT.md`: instrucción explícita, convención existente y
|
|
114
|
+
después idioma principal del website. No traducir ni renombrar contratos ya
|
|
115
|
+
utilizados por el storefront.
|
|
102
116
|
|
|
103
117
|
Antes de modificar código, indicar brevemente qué rutas y componentes se
|
|
104
118
|
implementarán y qué decisiones de diseño son inferidas.
|
|
@@ -113,9 +127,15 @@ Trabajar por página o conjunto pequeño de secciones:
|
|
|
113
127
|
4. Integración Bacano real.
|
|
114
128
|
5. Comparación visual y correcciones.
|
|
115
129
|
|
|
130
|
+
No dar una página por validada únicamente porque compila. Comparar estructura,
|
|
131
|
+
contenido, responsive e interacciones del corte antes de continuar y registrar
|
|
132
|
+
lo pendiente cuando la revisión visual quede a cargo del usuario.
|
|
133
|
+
|
|
116
134
|
No añadir paquetes, fuentes, iconos o assets si el proyecto ya ofrece un
|
|
117
135
|
equivalente. Descargar assets mediante la fuente Figma autorizada; no incrustar
|
|
118
|
-
URLs temporales ni construir transformaciones de Storage manualmente.
|
|
136
|
+
URLs temporales ni construir transformaciones de Storage manualmente. La carga
|
|
137
|
+
administrativa admite JPEG, PNG o WebP raster; video, documentos, SVG y
|
|
138
|
+
animaciones continúan bajo revisión editorial en el backoffice.
|
|
119
139
|
|
|
120
140
|
## 6. Validar diseño y contrato
|
|
121
141
|
|
|
@@ -7,9 +7,9 @@ negocio.
|
|
|
7
7
|
|
|
8
8
|
Antes de programar, registrar por ruta:
|
|
9
9
|
|
|
10
|
-
| Ruta | Frame | Componente | Fuente | Responsive | Estado |
|
|
11
|
-
| --- | --- | --- | --- | --- | --- |
|
|
12
|
-
| `/` | Home desktop/mobile | `Hero` | Content | 2 frames | pendiente |
|
|
10
|
+
| Ruta | Frame | Componente | Fuente | Content | Responsive | Preview | Estado |
|
|
11
|
+
| --- | --- | --- | --- | --- | --- | --- | --- |
|
|
12
|
+
| `/` | Home desktop/mobile | `Hero` | Content | `hero_inicio` | 2 frames | activa | pendiente |
|
|
13
13
|
|
|
14
14
|
Fuentes posibles: Content, catálogo estático, live state, public config,
|
|
15
15
|
checkout, auth u otra operación documentada del SDK.
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# Imágenes de Content desde un agente
|
|
2
|
+
|
|
3
|
+
Usar este flujo solo cuando `getContext().scopes` incluya
|
|
4
|
+
`content_media:upload`. El token temporal continúa siendo administrativo: no se
|
|
5
|
+
incluye en el storefront, Figma, archivos del proyecto, logs ni respuestas al
|
|
6
|
+
usuario.
|
|
7
|
+
|
|
8
|
+
## Orden obligatorio
|
|
9
|
+
|
|
10
|
+
1. Clasificar el asset: hero desktop/mobile, card, galería, logo, decorativo u
|
|
11
|
+
otro uso editorial.
|
|
12
|
+
2. Buscar en `listMedia()` con términos semánticos. Reutilizar únicamente cuando
|
|
13
|
+
sea el mismo asset, no solo una imagen parecida.
|
|
14
|
+
3. Exportar desde la fuente Figma autorizada los bytes originales necesarios.
|
|
15
|
+
4. Para imágenes informativas, preparar un nombre descriptivo y `altText`
|
|
16
|
+
contextual. No usar nombres de capas como `Frame 32` o `image_1`.
|
|
17
|
+
5. Cargar con `uploadImage()`. Nunca usar GraphQL, URLs de Figma, base64 dentro
|
|
18
|
+
de una entrada ni endpoints directos de Nhost Storage.
|
|
19
|
+
6. Guardar en Content el `fileId` devuelto y volver a consultar la entrada.
|
|
20
|
+
7. Mantener en borrador hasta validar imagen, encuadre, responsive y texto
|
|
21
|
+
alternativo en contexto.
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
import { readFile } from "node:fs/promises";
|
|
25
|
+
import { createContentManagementClient } from "@bacano/sdk/content-management";
|
|
26
|
+
|
|
27
|
+
const content = createContentManagementClient({
|
|
28
|
+
apiUrl: process.env.BACANO_API_URL!,
|
|
29
|
+
accessToken: process.env.BACANO_CONTENT_ACCESS_TOKEN!,
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const matches = await content.listMedia({ search: "hero juguetes" });
|
|
33
|
+
const media = matches.items[0] ?? await content.uploadImage({
|
|
34
|
+
file: await readFile("./exports/hero-juguetes-desktop.png"),
|
|
35
|
+
fileName: "Hero juguetes para bebé desktop",
|
|
36
|
+
altText: "Niña jugando con bloques de colores",
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
console.log(media.fileId);
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Contrato de la primera versión
|
|
43
|
+
|
|
44
|
+
- Admite JPEG, PNG y WebP raster no animado.
|
|
45
|
+
- Máximo 10 MB de entrada y 100 megapíxeles.
|
|
46
|
+
- Bacano valida bytes reales, corrige orientación y normaliza a WebP.
|
|
47
|
+
- El nombre se convierte a un filename SEO seguro.
|
|
48
|
+
- Los duplicados binarios normalizados se reutilizan dentro de la misma
|
|
49
|
+
empresa; nunca entre empresas.
|
|
50
|
+
- SVG, GIF animado, video y documentos se cargan desde el backoffice.
|
|
51
|
+
- El agente no reemplaza ni elimina físicamente media.
|
|
52
|
+
- `altText` de biblioteca es fallback. El texto alternativo final pertenece al
|
|
53
|
+
uso dentro de la entrada; una imagen decorativa usa `alt=""` en ese contexto.
|
|
54
|
+
|
|
55
|
+
Si el asset no cabe en este contrato, reportar `BLOQUEADO` y pedir que el equipo
|
|
56
|
+
editorial lo prepare en la biblioteca. No convertirlo de forma improvisada ni
|
|
57
|
+
introducir paquetes o servicios paralelos.
|
|
@@ -19,12 +19,30 @@ Antes de crear campos o fixtures, asignar cada valor a una fuente:
|
|
|
19
19
|
No duplicar en Content información que ya tiene una fuente funcional. Un valor
|
|
20
20
|
visible en Figma no implica que sea editorial.
|
|
21
21
|
|
|
22
|
+
## Modelar sin extremos
|
|
23
|
+
|
|
24
|
+
No convertir una página extensa en un único tipo gigante ni crear un componente
|
|
25
|
+
para cada bloque visual. Separar una sección cuando tenga ciclo editorial,
|
|
26
|
+
responsable, reutilización o repetición propios. Crear un componente compartido
|
|
27
|
+
solo cuando coincidan tanto la estructura como el significado y la abstracción
|
|
28
|
+
reduzca duplicación real.
|
|
29
|
+
|
|
30
|
+
- Usar grupos para organizar entradas relacionadas por dominio o página.
|
|
31
|
+
- Usar repetibles para pasos, integrantes, preguntas, tarjetas y otras
|
|
32
|
+
colecciones homogéneas.
|
|
33
|
+
- Mantener directamente en el tipo los campos de un bloque exclusivo y simple.
|
|
34
|
+
- Crear primero listas y componentes realmente compartidos; después los tipos
|
|
35
|
+
consumidores.
|
|
36
|
+
- No hacer depender la UI del shape crudo de Content: fixture y mapper real
|
|
37
|
+
deben converger en el mismo view model.
|
|
38
|
+
|
|
22
39
|
## 2. Elegir el punto de partida
|
|
23
40
|
|
|
24
41
|
### Figma primero
|
|
25
42
|
|
|
26
43
|
1. Extraer el contenido público visible y sus assets autorizados.
|
|
27
|
-
2.
|
|
44
|
+
2. Resolver el idioma y proponer tipos, keys, campos y entradas en
|
|
45
|
+
`content-blueprint.json` siguiendo `CONTENT_TYPE_MANAGEMENT.md`.
|
|
28
46
|
3. Crear fixtures locales que implementen el view model de la página.
|
|
29
47
|
4. Implementar y validar el diseño contra esos fixtures.
|
|
30
48
|
5. Entregar el blueprint al equipo de Content para crearlo en Bacano.
|
|
@@ -66,22 +84,22 @@ Estructura recomendada:
|
|
|
66
84
|
"route": "/",
|
|
67
85
|
"sections": [
|
|
68
86
|
{
|
|
69
|
-
"sectionKey": "
|
|
87
|
+
"sectionKey": "hero-inicio",
|
|
70
88
|
"figmaNodeId": "1:20",
|
|
71
89
|
"component": "HomeHero",
|
|
72
90
|
"ownership": "content",
|
|
73
|
-
"contentTypeKey": "
|
|
74
|
-
"entryKey": "
|
|
91
|
+
"contentTypeKey": "hero_inicio",
|
|
92
|
+
"entryKey": "hero-principal-inicio",
|
|
75
93
|
"status": "missing",
|
|
76
94
|
"fields": [
|
|
77
95
|
{
|
|
78
|
-
"key": "
|
|
96
|
+
"key": "titulo",
|
|
79
97
|
"type": "text",
|
|
80
98
|
"required": true,
|
|
81
99
|
"sampleValue": "Compra para cada etapa"
|
|
82
100
|
}
|
|
83
101
|
],
|
|
84
|
-
"fixture": "fixtures/
|
|
102
|
+
"fixture": "fixtures/hero-inicio.json"
|
|
85
103
|
}
|
|
86
104
|
]
|
|
87
105
|
}
|
|
@@ -92,7 +110,9 @@ Estructura recomendada:
|
|
|
92
110
|
El blueprint es una propuesta y una evidencia de coordinación, no una orden de
|
|
93
111
|
escritura. No incluir IDs internos innecesarios, secretos, datos personales ni
|
|
94
112
|
respuestas crudas de GraphQL. Mantener keys estables y registrar cambios que
|
|
95
|
-
rompan el contrato.
|
|
113
|
+
rompan el contrato. Las propiedades técnicas del formato del blueprint
|
|
114
|
+
permanecen en inglés; los valores que representan tipos, componentes, campos y
|
|
115
|
+
entradas definidos por la empresa siguen el idioma resuelto para el proyecto.
|
|
96
116
|
|
|
97
117
|
## 4. Fixtures temporales
|
|
98
118
|
|
|
@@ -132,9 +152,12 @@ Entregar una tabla legible con:
|
|
|
132
152
|
El equipo de Content puede crear o ajustar la estructura desde Bacano. Si el
|
|
133
153
|
usuario autoriza que el agente materialice el blueprint, usar exclusivamente
|
|
134
154
|
`@bacano/sdk/content-management` y el token temporal generado desde **Conectar
|
|
135
|
-
agente**. Confirmar la empresa, inventariar primero, crear
|
|
136
|
-
sus consumidores y trabajar inicialmente en borrador. La
|
|
137
|
-
|
|
155
|
+
agente**. Confirmar la empresa y los scopes, inventariar primero, crear
|
|
156
|
+
componentes antes de sus consumidores y trabajar inicialmente en borrador. La
|
|
157
|
+
skill puede preparar entradas cuando el usuario conceda
|
|
158
|
+
`content_entries:manage`. Si además se concede `content_media:upload`, debe
|
|
159
|
+
buscar y cargar imágenes por el contrato descrito en
|
|
160
|
+
[content-media.md](content-media.md). No publica sin el scope específico.
|
|
138
161
|
|
|
139
162
|
## 6. Cambio a Bacano real
|
|
140
163
|
|
|
@@ -159,11 +182,24 @@ escribir:
|
|
|
159
182
|
|
|
160
183
|
1. Pedir al usuario que genere un acceso temporal de 15, 30 o 60 minutos.
|
|
161
184
|
2. Guardarlo solo en el entorno local del proceso del agente.
|
|
162
|
-
3. Ejecutar `getContext()`, `
|
|
185
|
+
3. Ejecutar `getContext()`, comprobar sus `scopes`, y después `listTypes()`,
|
|
186
|
+
`listGroups()`, `listOptionLists()` y `getReferences()`.
|
|
163
187
|
4. Mostrar el plan y esperar aprobación cuando altere estructuras existentes.
|
|
164
|
-
5. Crear primero
|
|
165
|
-
|
|
188
|
+
5. Crear primero listas y componentes, luego tipos consumidores, y mantener las
|
|
189
|
+
estructuras nuevas en borrador hasta validarlas.
|
|
190
|
+
6. Si se concedió `content_entries:manage`, inventariar entradas por tipo, key y
|
|
191
|
+
locale; crear las faltantes en borrador y actualizar únicamente con
|
|
192
|
+
`expectedUpdatedAt`.
|
|
193
|
+
7. No fusionar por nombres, no usar HTML crudo y no relacionar URLs remotas. La
|
|
194
|
+
media debe existir en Bacano y pertenecer a la empresa autorizada; resolver
|
|
195
|
+
su `fileId` mediante `listMedia()` o `uploadImage()`.
|
|
196
|
+
8. Para archivar o eliminar tipos, entradas, grupos o listas, inspeccionar
|
|
197
|
+
dependencias y confirmar nuevamente. Una actualización de lista debe enviar
|
|
198
|
+
todos los elementos que deben permanecer, incluidos sus IDs existentes.
|
|
199
|
+
9. Publicar o modificar una entrada publicada solo con
|
|
200
|
+
`content_entries:publish` y una aprobación humana explícita.
|
|
166
201
|
|
|
167
202
|
Nunca exponer el token en el navegador, commit, blueprint, salida de consola o
|
|
168
203
|
chat. Nunca convertir el blueprint en una importación implícita ni usar GraphQL
|
|
169
|
-
directo.
|
|
204
|
+
directo. Sin `content_media:upload`, toda media permanece bajo gestión
|
|
205
|
+
editorial en el backoffice.
|
|
@@ -87,6 +87,9 @@ Estado de página: `pending`, `extracted`, `implemented` o `validated`.
|
|
|
87
87
|
|
|
88
88
|
Cada página requiere `route`, `name`, `status` y al menos un frame desktop o
|
|
89
89
|
mobile con `nodeId` o `url`. No guardar secretos ni respuestas MCP completas.
|
|
90
|
+
Antes de implementar, comprobar que las rutas no apuntan accidentalmente al
|
|
91
|
+
mismo frame o `nodeId`. Una reutilización intencional debe quedar registrada en
|
|
92
|
+
`assumptions`.
|
|
90
93
|
|
|
91
94
|
## Estrategia de consultas
|
|
92
95
|
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# Calidad de implementación desde Figma
|
|
2
|
+
|
|
3
|
+
Aplicar estas reglas al construir la interfaz. Son criterios de resultado, no
|
|
4
|
+
una obligación de usar una librería concreta.
|
|
5
|
+
|
|
6
|
+
## Responsive e inferencias
|
|
7
|
+
|
|
8
|
+
Si Figma no incluye un frame móvil, construir una adaptación mobile first y
|
|
9
|
+
registrarla como inferencia. Conservar jerarquía, contenido, orden semántico y
|
|
10
|
+
acciones principales; apilar columnas cuando corresponda y evitar overflow
|
|
11
|
+
horizontal. Verificar al menos móvil, tablet y escritorio.
|
|
12
|
+
|
|
13
|
+
No convertir automáticamente toda colección en carrusel. Usarlo únicamente
|
|
14
|
+
cuando mejore la exploración y asegurar scroll táctil, teclado, controles
|
|
15
|
+
accesibles, dimensiones estables y pausa durante la interacción. Respetar
|
|
16
|
+
`prefers-reduced-motion` cuando exista autoplay o movimiento.
|
|
17
|
+
|
|
18
|
+
## Componentes e interacción
|
|
19
|
+
|
|
20
|
+
Inspeccionar dependencias y componentes del proyecto antes de añadir paquetes.
|
|
21
|
+
No imponer HeroUI, Embla, Motion, CSS Modules u otra librería; reutilizar el
|
|
22
|
+
sistema existente y pedir autorización antes de introducir una dependencia
|
|
23
|
+
visual relevante.
|
|
24
|
+
|
|
25
|
+
Todo control interactivo necesita estado de hover cuando aplique,
|
|
26
|
+
`focus-visible`, teclado, contraste y área táctil suficiente. Usar
|
|
27
|
+
`aria-current="page"` en la navegación activa y no comunicar estados solo con
|
|
28
|
+
color. Las animaciones no deben cambiar el layout final, dejar contenido útil
|
|
29
|
+
oculto sin JavaScript ni ejecutarse contra la preferencia de movimiento
|
|
30
|
+
reducido.
|
|
31
|
+
|
|
32
|
+
## Perfil de exportación estática
|
|
33
|
+
|
|
34
|
+
Cuando el hosting y el proyecto estén definidos como export estático, comprobar
|
|
35
|
+
la configuración efectiva del framework. En Next.js suele incluir
|
|
36
|
+
`output: "export"`, `trailingSlash: true` y una estrategia de imágenes
|
|
37
|
+
compatible con exportación estática.
|
|
38
|
+
|
|
39
|
+
Materializar durante el build contenido, rutas, metadata, sitemap y JSON-LD.
|
|
40
|
+
Las rutas dinámicas editoriales deben enumerar todas las entradas publicadas y
|
|
41
|
+
fallar de forma explícita si el snapshot esperado está vacío o incompleto.
|
|
42
|
+
|
|
43
|
+
No introducir Route Handlers, API Routes, Server Actions, middleware, ISR,
|
|
44
|
+
`force-dynamic` ni secretos de servidor en un proyecto que debe producir solo
|
|
45
|
+
archivos estáticos. Esta prohibición no aplica automáticamente a un storefront
|
|
46
|
+
híbrido o SSR: primero clasificar su arquitectura.
|
|
47
|
+
|
|
48
|
+
Mantener un único bootstrap de configuración pública y un cliente Bacano por
|
|
49
|
+
worker o árbol. Reutilizar esa misma información para páginas, metadata,
|
|
50
|
+
sitemap, JSON-LD y provider; no consultar cada ruta por separado si existe un
|
|
51
|
+
snapshot oficial compartido.
|
|
52
|
+
|
|
53
|
+
## Preview y rutas editoriales
|
|
54
|
+
|
|
55
|
+
Mantener una allowlist finita de patrones editoriales. Los índices pueden ser
|
|
56
|
+
explícitos y los slugs de detalle pueden derivarse de las entradas publicadas;
|
|
57
|
+
no aceptar una ruta arbitraria solo porque coincide con una cadena. Preview
|
|
58
|
+
reutiliza componentes y mappers de producción, permanece fuera del sitemap y
|
|
59
|
+
no habilita funciones transaccionales o marketing.
|
|
60
|
+
|
|
61
|
+
## Diagnóstico antes de cambiar código
|
|
62
|
+
|
|
63
|
+
Un `ORIGIN_NOT_ALLOWED` o un `403` al resolver la configuración durante el
|
|
64
|
+
build suele indicar que el origen utilizado no está autorizado para el website.
|
|
65
|
+
Verificar dominio canónico, orígenes permitidos y ambiente antes de alterar el
|
|
66
|
+
cliente SDK. Una advertencia aislada sobre ausencia de caché del build no prueba
|
|
67
|
+
por sí sola que el storefront esté defectuoso.
|
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
Validar por cortes para evitar corregir 70 secciones al final.
|
|
4
4
|
|
|
5
|
+
Para cada corte, distinguir `implementado`, `validado`, `pendiente de revisión
|
|
6
|
+
humana`, `no aplica` y `bloqueado`. Si el usuario conserva la validación visual,
|
|
7
|
+
no convertir su ausencia en una aprobación automática.
|
|
8
|
+
|
|
5
9
|
## Evidencia mínima por página
|
|
6
10
|
|
|
7
11
|
- URL/ruta implementada.
|