@bacano/sdk 1.1.10 → 1.1.12

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.
@@ -0,0 +1,192 @@
1
+ # Gestión de tipos de Content para agentes
2
+
3
+ `@bacano/sdk/content-management` permite que una herramienta administrativa o
4
+ un agente autorizado consulte, cree y mantenga **tipos y componentes** de
5
+ Bacano Content. No permite administrar entradas, productos, ventas, clientes ni
6
+ ningún otro dato de la empresa.
7
+
8
+ Este contrato es independiente del cliente público del storefront. Debe usarse
9
+ solo desde un proceso local o servidor confiable; nunca desde el navegador ni
10
+ dentro del bundle del sitio web.
11
+
12
+ ## Activar un acceso temporal
13
+
14
+ 1. En Bacano, abre **Sitios web → Contenido → Tipos** o **Componentes**.
15
+ 2. Selecciona **Conectar agente**.
16
+ 3. Elige 15, 30 o 60 minutos y genera el acceso.
17
+ 4. Copia el token al entorno local del agente.
18
+
19
+ El token está firmado, pertenece a una sola empresa y solo contiene el alcance
20
+ `content_types:manage`. Cada petición vuelve a comprobar que el usuario que lo
21
+ generó continúa vinculado a la empresa y conserva `websites.manage`. Al vencer
22
+ o perder ese permiso deja de funcionar.
23
+
24
+ No guardes el token en Git, Figma, Content, logs, variables públicas ni archivos
25
+ que se empaqueten con el storefront.
26
+
27
+ ## Crear el cliente
28
+
29
+ ```ts
30
+ import { createContentManagementClient } from "@bacano/sdk/content-management";
31
+
32
+ const content = createContentManagementClient({
33
+ apiUrl: process.env.BACANO_API_URL!,
34
+ accessToken: process.env.BACANO_CONTENT_ACCESS_TOKEN!,
35
+ });
36
+
37
+ const context = await content.getContext();
38
+ console.log(context.companyId, context.expiresAt);
39
+ ```
40
+
41
+ El cliente ofrece:
42
+
43
+ - `getContext()` para confirmar empresa, alcance y vencimiento.
44
+ - `getReferences()` para consultar categorías, subcategorías, listas de opciones
45
+ y unidades de medida válidas.
46
+ - `listTypes()` y `getType(idOrKey)`.
47
+ - `createType(input)` y `updateType(idOrKey, input)`.
48
+ - `inspectLifecycle(idOrKey, operation)`.
49
+ - `applyLifecycle(idOrKey, operation)`.
50
+
51
+ ## Flujo seguro para un agente
52
+
53
+ 1. Ejecutar `getContext()` y confirmar la empresa con el usuario.
54
+ 2. Ejecutar `listTypes()` y `getReferences()` antes de proponer cambios.
55
+ 3. Reutilizar tipos, componentes, listas y keys existentes cuando correspondan.
56
+ 4. Crear primero los componentes que serán referenciados por otros tipos.
57
+ 5. Crear o actualizar en `draft`.
58
+ 6. Volver a consultar el tipo y presentar un resumen del resultado.
59
+ 7. Activarlo únicamente cuando el usuario haya aprobado la estructura.
60
+ 8. Para archivar, restaurar o eliminar, ejecutar primero
61
+ `inspectLifecycle()` y mostrar dependencias y bloqueos.
62
+ 9. Ejecutar `applyLifecycle()` solo después de una confirmación explícita.
63
+
64
+ `updateType()` reemplaza la definición completa. Antes de usarlo debes obtener
65
+ el tipo con `getType()`, conservar los IDs de campos existentes y enviar todos
66
+ los campos y secciones que deban permanecer. La omisión de un campo se interpreta
67
+ como una eliminación estructural y puede requerir
68
+ `confirmDestructiveChanges: true`.
69
+
70
+ ## Ejemplo: componente y tipo editorial
71
+
72
+ ```ts
73
+ await content.createType({
74
+ key: "cta_button",
75
+ name: "Botón de llamada a la acción",
76
+ kind: "component",
77
+ status: "draft",
78
+ fields: [
79
+ {
80
+ key: "label",
81
+ label: "Texto",
82
+ type: "text",
83
+ required: true,
84
+ sortOrder: 1,
85
+ settings: { isVisible: true, maxLength: 80 },
86
+ },
87
+ {
88
+ key: "url",
89
+ label: "Enlace",
90
+ type: "link",
91
+ required: true,
92
+ sortOrder: 2,
93
+ settings: { isVisible: true },
94
+ },
95
+ ],
96
+ });
97
+
98
+ await content.createType({
99
+ key: "home_hero",
100
+ name: "Hero del inicio",
101
+ kind: "entry",
102
+ status: "draft",
103
+ usages: ["website_content"],
104
+ sections: [{ key: "content", title: "Contenido", sortOrder: 1 }],
105
+ fields: [
106
+ {
107
+ key: "title",
108
+ label: "Título",
109
+ type: "text",
110
+ required: true,
111
+ sortOrder: 1,
112
+ sectionKey: "content",
113
+ settings: { isVisible: true, maxLength: 120 },
114
+ },
115
+ {
116
+ key: "actions",
117
+ label: "Acciones",
118
+ type: "repeater",
119
+ required: false,
120
+ sortOrder: 2,
121
+ sectionKey: "content",
122
+ settings: { isVisible: true, minRows: 0, maxRows: 3 },
123
+ children: [
124
+ {
125
+ key: "button",
126
+ label: "Botón",
127
+ type: "component",
128
+ required: true,
129
+ sortOrder: 1,
130
+ componentKey: "cta_button",
131
+ settings: { isVisible: true },
132
+ },
133
+ ],
134
+ },
135
+ ],
136
+ });
137
+ ```
138
+
139
+ ## Ajustes de campos
140
+
141
+ Todos los campos aceptan `helpText`, `defaultValue`, `isPrivate` e
142
+ `isVisible`. Usa además los ajustes correspondientes al tipo:
143
+
144
+ | Tipo | Ajustes principales |
145
+ | --- | --- |
146
+ | `text`, `textarea`, `slug` | `minLength`, `maxLength`, `pattern` |
147
+ | `rich_text`, `rich_text_full` | `richTextMode` |
148
+ | `media` | `mediaMultiple`, `acceptedMediaKinds` |
149
+ | `date` | `dateMode`: `date` o `datetime` |
150
+ | `number` | `min`, `max`, `step`, `precision` |
151
+ | `measure` | `measureDimensionCode`, `measureAllowedUnitCodes`, `measurePublicUnitCode` |
152
+ | `select` | `selectSource`, `selectOptions`, `selectSourceKey`, `selectMultiple` |
153
+ | `reference` | `referenceType`, `referenceContentTypeKey`, `referenceMultiple` |
154
+ | `repeater` | `minRows`, `maxRows` y `children` |
155
+ | `component` | `componentKey` |
156
+
157
+ Para una lista administrable usa `selectSource: "option_list"` y una key
158
+ devuelta por `getReferences().optionLists`. Para medidas usa exclusivamente los
159
+ códigos devueltos por `getReferences().measureDimensions`; no inventes unidades.
160
+
161
+ Una ficha técnica usa `usages: ["product_specification"]` y al menos un alcance
162
+ en `specificationScopes`. Sus campos técnicos pueden declarar
163
+ `isFilterable: true` únicamente cuando las reglas de Bacano lo permitan. Los
164
+ otros usos editoriales son `website_content` y `product_additional`.
165
+
166
+ ## Ciclo de vida
167
+
168
+ ```ts
169
+ const preview = await content.inspectLifecycle("home_hero", "delete");
170
+
171
+ if (preview.allowed && userConfirmed) {
172
+ await content.applyLifecycle("home_hero", "delete");
173
+ }
174
+ ```
175
+
176
+ Nunca ignores `dependencies`, `allowed` o `errors`. Archivar es preferible a
177
+ eliminar cuando existe contenido histórico. El backend aplica las mismas reglas
178
+ de dependencias, keys, componentes, secciones y cambios destructivos que el
179
+ backoffice; el SDK no crea un camino alterno.
180
+
181
+ ## Límites de esta versión
182
+
183
+ - No crea ni modifica entradas de Content.
184
+ - No carga archivos ni media.
185
+ - No crea listas de opciones.
186
+ - No funciona desde el cliente público del storefront.
187
+ - No acepta GraphQL directo ni credenciales administrativas permanentes.
188
+
189
+ El resultado esperado del flujo Figma es: el agente propone el blueprint, el
190
+ usuario habilita el acceso temporal, el agente crea o ajusta los tipos y
191
+ componentes aprobados, y el equipo editorial completa las entradas desde
192
+ Bacano.
package/README.md CHANGED
@@ -21,6 +21,21 @@ React hooks are available from `@bacano/sdk/react` and require `react >= 18`
21
21
  as a peer dependency. The core client has no runtime dependencies and works
22
22
  without React.
23
23
 
24
+ ### Gestión de tipos de Content para agentes
25
+
26
+ Un agente autorizado puede consultar, crear y mantener tipos y componentes con
27
+ el entrypoint administrativo `@bacano/sdk/content-management`. El acceso se
28
+ genera temporalmente desde Bacano, está limitado a una empresa y nunca debe
29
+ incluirse en el storefront:
30
+
31
+ ```ts
32
+ import { createContentManagementClient } from "@bacano/sdk/content-management";
33
+ ```
34
+
35
+ Lee [`CONTENT_TYPE_MANAGEMENT.md`](./CONTENT_TYPE_MANAGEMENT.md) antes de usarlo.
36
+ La guía contiene el flujo de autorización, contratos de campos, referencias,
37
+ cambios destructivos y límites de seguridad.
38
+
24
39
  ### Auditoría del storefront
25
40
 
26
41
  El SDK incluye la skill oficial `bacano-web`. Instalarla al iniciar un
@@ -67,8 +82,10 @@ la presencia del token REST. El OAuth del MCP remoto debe confirmarse dentro del
67
82
  agente con las herramientas de Figma. La extracción se conserva como snapshot
68
83
  normalizado. Cuando el equipo de Content y desarrollo trabajan en tiempos
69
84
  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.
85
+ con el mismo contrato de los componentes. Las entradas reales continúan
86
+ creándose desde Bacano. Con autorización temporal, la skill puede crear o
87
+ ajustar tipos y componentes mediante el entrypoint administrativo oficial; un
88
+ fixture activo bloquea la aprobación de producción.
72
89
 
73
90
  El snapshot puede validarse con:
74
91
 
@@ -0,0 +1,135 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // libs/sdk/src/content-management.ts
21
+ var content_management_exports = {};
22
+ __export(content_management_exports, {
23
+ ContentManagementError: () => ContentManagementError,
24
+ createContentManagementClient: () => createContentManagementClient
25
+ });
26
+ module.exports = __toCommonJS(content_management_exports);
27
+ var ContentManagementError = class extends Error {
28
+ code;
29
+ status;
30
+ details;
31
+ constructor(input) {
32
+ super(input.message);
33
+ this.name = "ContentManagementError";
34
+ this.code = input.code ?? "CONTENT_MANAGEMENT_ERROR";
35
+ this.status = input.status;
36
+ this.details = input.details;
37
+ }
38
+ };
39
+ function normalizeApiUrl(apiUrl) {
40
+ const normalized = apiUrl.trim().replace(/\/+$/, "");
41
+ if (!normalized) {
42
+ throw new ContentManagementError({
43
+ message: "apiUrl is required.",
44
+ code: "CONTENT_MANAGEMENT_CONFIG_INVALID",
45
+ status: 0
46
+ });
47
+ }
48
+ return normalized;
49
+ }
50
+ function encodeIdentifier(identifier) {
51
+ const normalized = identifier.trim();
52
+ if (!normalized) {
53
+ throw new ContentManagementError({
54
+ message: "A content type id or key is required.",
55
+ code: "CONTENT_TYPE_IDENTIFIER_INVALID",
56
+ status: 0
57
+ });
58
+ }
59
+ return encodeURIComponent(normalized);
60
+ }
61
+ function createContentManagementClient(config) {
62
+ const apiUrl = normalizeApiUrl(config.apiUrl);
63
+ const fetchImplementation = config.fetch ?? globalThis.fetch;
64
+ if (!fetchImplementation) {
65
+ throw new ContentManagementError({
66
+ message: "A fetch implementation is required.",
67
+ code: "CONTENT_MANAGEMENT_CONFIG_INVALID",
68
+ status: 0
69
+ });
70
+ }
71
+ const getAccessToken = async () => {
72
+ const token = typeof config.accessToken === "function" ? await config.accessToken() : config.accessToken;
73
+ const normalized = token.trim();
74
+ if (!normalized) {
75
+ throw new ContentManagementError({
76
+ message: "A temporary content management access token is required.",
77
+ code: "CONTENT_MANAGEMENT_ACCESS_REQUIRED",
78
+ status: 0
79
+ });
80
+ }
81
+ return normalized;
82
+ };
83
+ const request = async (path, init) => {
84
+ const response = await fetchImplementation(
85
+ `${apiUrl}/api/v1/content-management${path}`,
86
+ {
87
+ ...init,
88
+ headers: {
89
+ Accept: "application/json",
90
+ Authorization: `Bearer ${await getAccessToken()}`,
91
+ ...init?.body ? { "Content-Type": "application/json" } : {},
92
+ ...init?.headers
93
+ }
94
+ }
95
+ );
96
+ let payload = null;
97
+ try {
98
+ payload = await response.json();
99
+ } catch {
100
+ }
101
+ if (!response.ok || !payload || payload.success !== true) {
102
+ const error = payload && "error" in payload ? payload.error : null;
103
+ throw new ContentManagementError({
104
+ message: error?.message ?? `Content management request failed with HTTP ${response.status}.`,
105
+ code: error?.code,
106
+ status: response.status,
107
+ details: error?.details
108
+ });
109
+ }
110
+ return payload.data;
111
+ };
112
+ const lifecycle = (identifier, operation, dryRun) => request(
113
+ `/types/${encodeIdentifier(identifier)}/lifecycle`,
114
+ {
115
+ method: "POST",
116
+ body: JSON.stringify({ operation, dryRun })
117
+ }
118
+ );
119
+ return {
120
+ getContext: () => request("/context"),
121
+ getReferences: () => request("/references"),
122
+ listTypes: () => request("/types"),
123
+ getType: (identifier) => request(`/types/${encodeIdentifier(identifier)}`),
124
+ createType: (input) => request("/types", {
125
+ method: "POST",
126
+ body: JSON.stringify(input)
127
+ }),
128
+ updateType: (identifier, input) => request(`/types/${encodeIdentifier(identifier)}`, {
129
+ method: "PUT",
130
+ body: JSON.stringify(input)
131
+ }),
132
+ inspectLifecycle: (identifier, operation) => lifecycle(identifier, operation, true),
133
+ applyLifecycle: (identifier, operation) => lifecycle(identifier, operation, false)
134
+ };
135
+ }
@@ -0,0 +1,144 @@
1
+ export type ContentManagementTypeKind = "entry" | "component";
2
+ export type ContentManagementTypeStatus = "draft" | "active" | "archived";
3
+ export type ContentManagementTypeUsage = "website_content" | "product_additional" | "product_specification";
4
+ export type ContentManagementFieldType = "boolean" | "component" | "date" | "link" | "measure" | "media" | "number" | "reference" | "repeater" | "rich_text" | "rich_text_full" | "select" | "slug" | "text" | "textarea";
5
+ export interface ContentManagementChildFieldInput {
6
+ id?: string | null;
7
+ key: string;
8
+ label: string;
9
+ type: ContentManagementFieldType;
10
+ required?: boolean;
11
+ sortOrder: number;
12
+ settings?: Record<string, unknown>;
13
+ componentKey?: string | null;
14
+ }
15
+ export interface ContentManagementFieldInput extends ContentManagementChildFieldInput {
16
+ sectionKey?: string | null;
17
+ children?: ContentManagementChildFieldInput[];
18
+ }
19
+ export interface ContentManagementTypeInput {
20
+ key: string;
21
+ name: string;
22
+ description?: string;
23
+ kind: ContentManagementTypeKind;
24
+ status?: "draft" | "active";
25
+ settings?: Record<string, unknown>;
26
+ usages?: ContentManagementTypeUsage[];
27
+ sections?: Array<{
28
+ key: string;
29
+ title: string;
30
+ description?: string;
31
+ sortOrder: number;
32
+ }>;
33
+ specificationScopes?: Array<{
34
+ category?: string | null;
35
+ subcategoryId?: string | null;
36
+ }>;
37
+ deletedPaths?: string[];
38
+ confirmDestructiveChanges?: boolean;
39
+ fields?: ContentManagementFieldInput[];
40
+ }
41
+ export interface ContentManagementType extends Omit<ContentManagementTypeInput, "status"> {
42
+ id: string;
43
+ companyId: string;
44
+ description: string;
45
+ status: ContentManagementTypeStatus;
46
+ settings: Record<string, unknown>;
47
+ usages: ContentManagementTypeUsage[];
48
+ sections: Array<{
49
+ id: string;
50
+ key: string;
51
+ title: string;
52
+ description: string;
53
+ sortOrder: number;
54
+ }>;
55
+ specificationScopes: Array<{
56
+ id: string;
57
+ category: string | null;
58
+ subcategoryId: string | null;
59
+ }>;
60
+ fields: ContentManagementFieldInput[];
61
+ createdAt: string;
62
+ updatedAt: string;
63
+ }
64
+ export interface ContentManagementContext {
65
+ companyId: string;
66
+ scope: "content_types:manage";
67
+ expiresAt: string;
68
+ }
69
+ export interface ContentManagementReferences {
70
+ categories: Array<{
71
+ code: string;
72
+ name: string;
73
+ subcategories: Array<{
74
+ id: string;
75
+ name: string;
76
+ }>;
77
+ }>;
78
+ optionLists: Array<{
79
+ key: string;
80
+ name: string;
81
+ isExtensible: boolean;
82
+ options: Array<{
83
+ value: string;
84
+ label: string;
85
+ }>;
86
+ }>;
87
+ measureDimensions: Array<{
88
+ code: string;
89
+ name: string;
90
+ canonicalUnitCode: string;
91
+ units: Array<{
92
+ code: string;
93
+ name: string;
94
+ symbol: string;
95
+ }>;
96
+ }>;
97
+ }
98
+ export type ContentManagementLifecycleOperation = "archive" | "restore" | "delete";
99
+ export interface ContentManagementLifecycleResult {
100
+ success: boolean;
101
+ allowed: boolean;
102
+ applied: boolean;
103
+ entity_id: string | null;
104
+ entity_type: "type" | "component" | null;
105
+ operation: ContentManagementLifecycleOperation | null;
106
+ name: string | null;
107
+ status: string | null;
108
+ dependencies: Array<{
109
+ code: string;
110
+ label: string;
111
+ count: number;
112
+ blocking: boolean;
113
+ }>;
114
+ message: string | null;
115
+ errors: string[];
116
+ }
117
+ export type ContentManagementAccessTokenProvider = () => string | Promise<string>;
118
+ export interface ContentManagementClientConfig {
119
+ apiUrl: string;
120
+ accessToken: string | ContentManagementAccessTokenProvider;
121
+ fetch?: typeof fetch;
122
+ }
123
+ export declare class ContentManagementError extends Error {
124
+ readonly code: string;
125
+ readonly status: number;
126
+ readonly details: unknown;
127
+ constructor(input: {
128
+ message: string;
129
+ code?: string;
130
+ status: number;
131
+ details?: unknown;
132
+ });
133
+ }
134
+ export interface ContentManagementClient {
135
+ getContext(): Promise<ContentManagementContext>;
136
+ getReferences(): Promise<ContentManagementReferences>;
137
+ listTypes(): Promise<ContentManagementType[]>;
138
+ getType(identifier: string): Promise<ContentManagementType>;
139
+ createType(input: ContentManagementTypeInput): Promise<ContentManagementType>;
140
+ updateType(identifier: string, input: ContentManagementTypeInput): Promise<ContentManagementType>;
141
+ inspectLifecycle(identifier: string, operation: ContentManagementLifecycleOperation): Promise<ContentManagementLifecycleResult>;
142
+ applyLifecycle(identifier: string, operation: ContentManagementLifecycleOperation): Promise<ContentManagementLifecycleResult>;
143
+ }
144
+ export declare function createContentManagementClient(config: ContentManagementClientConfig): ContentManagementClient;
@@ -0,0 +1,114 @@
1
+ // libs/sdk/src/content-management.ts
2
+ var ContentManagementError = class extends Error {
3
+ code;
4
+ status;
5
+ details;
6
+ constructor(input) {
7
+ super(input.message);
8
+ this.name = "ContentManagementError";
9
+ this.code = input.code ?? "CONTENT_MANAGEMENT_ERROR";
10
+ this.status = input.status;
11
+ this.details = input.details;
12
+ }
13
+ };
14
+ function normalizeApiUrl(apiUrl) {
15
+ const normalized = apiUrl.trim().replace(/\/+$/, "");
16
+ if (!normalized) {
17
+ throw new ContentManagementError({
18
+ message: "apiUrl is required.",
19
+ code: "CONTENT_MANAGEMENT_CONFIG_INVALID",
20
+ status: 0
21
+ });
22
+ }
23
+ return normalized;
24
+ }
25
+ function encodeIdentifier(identifier) {
26
+ const normalized = identifier.trim();
27
+ if (!normalized) {
28
+ throw new ContentManagementError({
29
+ message: "A content type id or key is required.",
30
+ code: "CONTENT_TYPE_IDENTIFIER_INVALID",
31
+ status: 0
32
+ });
33
+ }
34
+ return encodeURIComponent(normalized);
35
+ }
36
+ function createContentManagementClient(config) {
37
+ const apiUrl = normalizeApiUrl(config.apiUrl);
38
+ const fetchImplementation = config.fetch ?? globalThis.fetch;
39
+ if (!fetchImplementation) {
40
+ throw new ContentManagementError({
41
+ message: "A fetch implementation is required.",
42
+ code: "CONTENT_MANAGEMENT_CONFIG_INVALID",
43
+ status: 0
44
+ });
45
+ }
46
+ const getAccessToken = async () => {
47
+ const token = typeof config.accessToken === "function" ? await config.accessToken() : config.accessToken;
48
+ const normalized = token.trim();
49
+ if (!normalized) {
50
+ throw new ContentManagementError({
51
+ message: "A temporary content management access token is required.",
52
+ code: "CONTENT_MANAGEMENT_ACCESS_REQUIRED",
53
+ status: 0
54
+ });
55
+ }
56
+ return normalized;
57
+ };
58
+ const request = async (path, init) => {
59
+ const response = await fetchImplementation(
60
+ `${apiUrl}/api/v1/content-management${path}`,
61
+ {
62
+ ...init,
63
+ headers: {
64
+ Accept: "application/json",
65
+ Authorization: `Bearer ${await getAccessToken()}`,
66
+ ...init?.body ? { "Content-Type": "application/json" } : {},
67
+ ...init?.headers
68
+ }
69
+ }
70
+ );
71
+ let payload = null;
72
+ try {
73
+ payload = await response.json();
74
+ } catch {
75
+ }
76
+ if (!response.ok || !payload || payload.success !== true) {
77
+ const error = payload && "error" in payload ? payload.error : null;
78
+ throw new ContentManagementError({
79
+ message: error?.message ?? `Content management request failed with HTTP ${response.status}.`,
80
+ code: error?.code,
81
+ status: response.status,
82
+ details: error?.details
83
+ });
84
+ }
85
+ return payload.data;
86
+ };
87
+ const lifecycle = (identifier, operation, dryRun) => request(
88
+ `/types/${encodeIdentifier(identifier)}/lifecycle`,
89
+ {
90
+ method: "POST",
91
+ body: JSON.stringify({ operation, dryRun })
92
+ }
93
+ );
94
+ return {
95
+ getContext: () => request("/context"),
96
+ getReferences: () => request("/references"),
97
+ listTypes: () => request("/types"),
98
+ getType: (identifier) => request(`/types/${encodeIdentifier(identifier)}`),
99
+ createType: (input) => request("/types", {
100
+ method: "POST",
101
+ body: JSON.stringify(input)
102
+ }),
103
+ updateType: (identifier, input) => request(`/types/${encodeIdentifier(identifier)}`, {
104
+ method: "PUT",
105
+ body: JSON.stringify(input)
106
+ }),
107
+ inspectLifecycle: (identifier, operation) => lifecycle(identifier, operation, true),
108
+ applyLifecycle: (identifier, operation) => lifecycle(identifier, operation, false)
109
+ };
110
+ }
111
+ export {
112
+ ContentManagementError,
113
+ createContentManagementClient
114
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bacano/sdk",
3
- "version": "1.1.10",
3
+ "version": "1.1.12",
4
4
  "description": "TypeScript SDK for building websites on the Bacano ERP",
5
5
  "main": "./index.cjs",
6
6
  "module": "./index.js",
@@ -23,6 +23,11 @@
23
23
  "import": "./graphql.js",
24
24
  "require": "./graphql.cjs"
25
25
  },
26
+ "./content-management": {
27
+ "types": "./content-management.d.ts",
28
+ "import": "./content-management.js",
29
+ "require": "./content-management.cjs"
30
+ },
26
31
  "./react": {
27
32
  "types": "./react.d.ts",
28
33
  "import": "./react.js",
@@ -93,9 +93,12 @@ Si Content todavía no existe, generar un blueprint de entrega y fixtures
93
93
  temporales fieles al contenido visible de Figma. Los fixtures deben usar el
94
94
  mismo view model que consumen los componentes y entrar por un único adaptador;
95
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.
96
+ sin sobrescribir estructuras o entradas. Si el usuario pide materializar el
97
+ blueprint, leer la sección de automatización de
98
+ [references/content-workflow.md](references/content-workflow.md) y el contrato
99
+ `CONTENT_TYPE_MANAGEMENT.md` incluido en `@bacano/sdk`. Solo crear o modificar
100
+ tipos y componentes mediante el acceso temporal oficial; las entradas y media
101
+ permanecen en el backoffice.
99
102
 
100
103
  Antes de modificar código, indicar brevemente qué rutas y componentes se
101
104
  implementarán y qué decisiones de diseño son inferidas.
@@ -129,10 +129,12 @@ Entregar una tabla legible con:
129
129
  - Estado y responsable.
130
130
  - Dependencias entre componentes, listas y entradas.
131
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ó.
132
+ El equipo de Content puede crear o ajustar la estructura desde Bacano. Si el
133
+ usuario autoriza que el agente materialice el blueprint, usar exclusivamente
134
+ `@bacano/sdk/content-management` y el token temporal generado desde **Conectar
135
+ agente**. Confirmar la empresa, inventariar primero, crear componentes antes de
136
+ sus consumidores y trabajar inicialmente en borrador. La skill no crea entradas,
137
+ importaciones ni cargas de media.
136
138
 
137
139
  ## 6. Cambio a Bacano real
138
140
 
@@ -149,10 +151,19 @@ depende de contenido temporal, reportar `❌ NO LISTO` con las rutas y secciones
149
151
  afectadas. Los fixtures pueden conservarse como datos de pruebas unitarias o
150
152
  visuales siempre que no entren en el bundle o loader de producción.
151
153
 
152
- ## 7. Automatización futura
154
+ ## 7. Automatización administrativa
153
155
 
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.
156
+ La gestión de tipos pertenece al entrypoint separado
157
+ `@bacano/sdk/content-management`, no al cliente público del storefront. Antes de
158
+ escribir:
159
+
160
+ 1. Pedir al usuario que genere un acceso temporal de 15, 30 o 60 minutos.
161
+ 2. Guardarlo solo en el entorno local del proceso del agente.
162
+ 3. Ejecutar `getContext()`, `listTypes()` y `getReferences()`.
163
+ 4. Mostrar el plan y esperar aprobación cuando altere estructuras existentes.
164
+ 5. Crear primero componentes y luego tipos consumidores, siempre en borrador.
165
+ 6. Para archivar o eliminar, inspeccionar dependencias y confirmar nuevamente.
166
+
167
+ Nunca exponer el token en el navegador, commit, blueprint, salida de consola o
168
+ chat. Nunca convertir el blueprint en una importación implícita ni usar GraphQL
169
+ directo. Las entradas y media permanecen bajo gestión editorial en Bacano.