/** * Google Docs operation enum + esquema del formulario — una sola copia para la * api, el Dashboard y el broker. * * ── Lo que quita ── * La lista de cinco estaba escrita a mano **siete veces**: dos en la entidad * (enum de Mongoose y unión de tipos), dos en el DTO (enum de Swagger y `@IsIn`), * y tres en el dashboard (`lib/api.ts`, el array `OPERATIONS` de la página y el * `OP_LABELS` del lienzo). El `operation` del DTO era además un `string` pelado: * sólo lo sujetaba el `@IsIn` en tiempo de ejecución. * * ── Lo fácil de Docs ── * Como Mongo, **no hay capa de renombrado**: el `name` de cada param es la prop * de la entidad tal cual, y el `apiUpdate` de la página es un passthrough. El * ejecutor lee `entity.documentId`, `entity.contentTemplate`… con esos mismos * nombres. * * ── El orden de pantalla, y por qué cambió ── * Hoy es el mismo que en Sheets, Drive, Telegram, Mongo y Postgres — primero * qué hacer, luego sobre qué: * * ── selector de operación ── * DocumentPicker + pegar ID (o el título, si es createDoc) * Content / Search+Replace / Rows+Columns * * Estuvo al revés, con el desplegable EN MEDIO y el razonamiento «qué documento * → qué hacer → con qué», y este comentario decía que un refactor no debía * tocarlo. Se cambió a propósito el 2026-08-12 (dashboard#128) por dos motivos: * la operación es la que decide QUÉ CAMPOS aparecen —`createDoc` ni siquiera * pide documento, pide título—, y ser el único nodo con otro orden es una * sorpresa por nodo. * * Los `slot` siguen haciendo falta: separan la identidad del documento de los * campos de la operación, aunque ahora las dos vayan debajo del selector. Un * bucle único ordenaría por el array de params y no por ese criterio. * * ── Qué exige el motor (y por qué está escrito aquí) ── * Este esquema declaraba CERO obligatorios en las cinco operaciones, y el * ejecutor —`api/src/nodes/docs-actions/docs-actions.service.ts`— rechaza cinco * cosas. Esa distancia no se veía como un error de validación sino como un * nodo que no guía: sin ningún obligatorio no hay nada que esperar, ningún paso * desbloquea al siguiente, y el guiado que el panel SÍ monta no se nota. * * Los cinco 400 del ejecutor, uno por línea, que son los cinco `required` de * este fichero: * * :323 readDoc documentId 'Document ID is required' * :440 appendText documentId 'Document ID is required' * :447 appendText contentTemplate 'Content template is required' * :498 replaceText documentId 'Document ID is required' * :505 replaceText searchText 'Search text is required' * :560 insertTable documentId 'Document ID is required' * * Y los cuatro que NO se marcan, con el defecto que los salva: `documentTitle` * cae en `entity.name`, `contentTemplate` de `createDoc` en un documento vacío, * `tableRows`/`tableCols` en `|| 3`, y `replaceWith` vacío es un BORRADO, no un * hueco. Cada uno lo explica en su sitio. */ export declare const DOCS_OPERATIONS: readonly ["readDoc", "createDoc", "appendText", "replaceText", "insertTable"]; export type DocsOperation = (typeof DOCS_OPERATIONS)[number]; /** Type guard — útil al validar entrada que no controlas (DTOs, tool calls). */ export declare function isDocsOperation(value: unknown): value is DocsOperation; export type DocsParamType = /** * El `DocumentPicker` en vivo (lista los Docs de la credencial) **y** debajo * un `TemplateInput` para pegar un ID o una plantilla. Son dos controles para * un solo campo, como el `driveFile` de Drive. */ 'docPicker' /** `TemplateInput`: una línea con autocompletado de `{{payload.*}}`. */ | 'template' /** `TemplateEditor` con `language="html"`. Ya no lo usa ningún param de * Docs (ver `markdownTemplate`); se conserva por si vuelve a hacer falta. */ | 'htmlTemplate' /** * `MarkdownField`: una caja de texto con pestañas Write / Preview y la * leyenda de la sintaxis (`# Título`, `**negrita**`, `- lista`, `[enlace]`). * * [2026-09-12] El `Content` de Docs era `htmlTemplate` —un editor de HTML * con Beautify— y la api lo metía en el documento con `insertText`, TAL CUAL: * un `

` aparecía como texto. Él: «¿podemos hacer algo más sencillo para * gente no técnica, tipo Markdown, como el README de los repos?». Ahora es * Markdown y el motor lo convierte en formato del documento (títulos, * negritas, listas, enlaces): `createDoc` por importación de HTML vía Drive, * `appendText` traduciendo a peticiones de la API de Docs. La entidad lleva * `contentFormat` (`markdown` | `text`); los nodos de antes se leen como * `text` y siguen igual. */ | 'markdownTemplate' /** * ``. **Ojo**: en el cable esto viaja como NÚMERO, no * como string. Es la razón del 400 que este cambio arregla — ver `min`/`max`. */ | 'number'; /** * Antes o después del selector de operación. * * `'document'` es la identidad del documento, que se elige antes de decidir qué * hacer con él. `'config'` son los campos de la operación. */ export type DocsParamSlot = 'document' | 'config'; export interface DocsParamSpec { /** La prop de la entidad. Sin renombrado: es también la clave del cable. */ name: string; label: string; type: DocsParamType; slot: DocsParamSlot; /** * Lo exige el EJECUTOR: sin él, `docs-actions.service.ts` devuelve un 400 y * el nodo no corre. * * No es «lo que conviene rellenar», y la diferencia importa en las dos * direcciones. De menos, un obligatorio que falta deja la familia con cero y * `esGuiable` (`check-cuantos-faltan-por-guiar.mjs`) la echa del censo: el * panel monta `useGuidedConfig`, pero sin nada que esperar ningún paso * desbloquea al siguiente y el guiado se ve como si no existiera — que es el * fallo que este campo arregla. De más, marcar algo que el ejecutor SÍ acepta * vacío esconde tras un hueco imposible un nodo que habría funcionado, porque * `frena()` sólo mira `obligatorio && !contestado`. * * Por eso cada `required: true` de este fichero apunta a la línea que lo * exige, y los que no lo llevan dicen dónde el ejecutor pone su defecto. */ required?: boolean; /** Texto de ayuda. Llano: el paquete no lleva React. */ description?: string; placeholder?: string; /** Sólo `number`. Los topes NO son iguales en los dos: 50 filas, 20 columnas. */ min?: number; max?: number; /** * Sólo `number`. Coincide con el `default` del `@Prop` de Mongoose, que es de * donde salía el 3×3 que nadie podía cambiar. */ default?: number; } export interface DocsOperationSpec { /** La del desplegable de la página de detalle y del panel: «Read Document». */ label: string; /** * La de la píldora del lienzo: «Read». * * **Son dos etiquetas a propósito**, como en Mongo y Calendar: la tarjeta del * lienzo son 260px. Antes había una TERCERA convención, porque el panel pintaba * el `operation` **crudo** y decía «insertTable» donde el lienzo decía «Table» * y la página «Insert Table». */ labelShort: string; /** Sub-línea del desplegable. */ description: string; /** Orden de pantalla dentro de cada `slot`. */ params: DocsParamSpec[]; } export declare const DOCS_OPERATION_SPECS: Record;