Módulo 3: Contratos entre workflows
3. Diseñar el esquema de entrada y de salida
Descripción
Al terminar esta lección vas a poder tomar el contrato que en la lección anterior escribiste en lenguaje humano y convertirlo en un esquema preciso: una lista de campos, cada uno con su nombre exacto, su tipo, su marca de obligatorio u opcional y, cuando corresponda, su valor por defecto. Vas a saber diseñar la forma de la respuesta —tanto la de éxito como la de error— usando un mismo "sobre" consistente que le facilita la vida a cualquier llamador. Y vas a saber dónde y cómo documentar ese esquema para que viva pegado al workflow, no perdido en la cabeza de quien lo escribió.
Esto importa porque un contrato en prosa —"recibe el ID del cliente y el monto"— sirve para conversar, pero no para construir. En el momento en que quieras que n8n reconozca los campos en la frontera (lección 4), rechace una entrada inválida (lección 5) o te avise que un cambio rompe a los llamadores (lección 6), necesitas que el contrato esté escrito con la precisión de un esquema: sin ambigüedad sobre si amount es número o texto, sin duda sobre si currency puede faltar. Esta lección es el paso de la intención al plano.
Conexión con el módulo: la lección 2 te dio las cuatro piezas del contrato en abstracto. Esta las aterriza en un esquema concreto que puedes escribir hoy. Las decisiones que tomes aquí —qué tipo tiene cada campo, cuáles son opcionales, qué forma tiene la respuesta— son las que la lección 4 va a declarar en el nodo Execute Sub-workflow Trigger, las que la lección 5 va a validar, y las que la lección 6 va a versionar. Diseñar bien el esquema aquí te ahorra dolor en las tres lecciones que siguen. Un esquema flojo se paga después.
El esquema como un formulario bien hecho
Ya sabes qué es un contrato: la firma de la máquina expendedora. Ahora tenemos que escribir esa firma con la precisión suficiente para que una máquina —y otra persona del equipo— la lean sin interpretar. Para eso, la imagen útil es un formulario en papel, del tipo que llenas en un trámite.
Un buen formulario no te deja adivinar. Cada casilla tiene una etiqueta que dice exactamente qué va ahí: "Nombre (como aparece en tu identificación)". Los campos obligatorios llevan un asterisco rojo; los que puedes dejar en blanco, no. Algunos traen un valor ya puesto que puedes cambiar o dejar: "País: México". Y el formulario te dice qué forma tiene cada dato: "Fecha (DD/MM/AAAA)", para que no escribas el mes donde va el día. Un formulario mal hecho —etiquetas vagas, sin marcar qué es obligatorio, sin formato— produce respuestas basura, y quien las procesa después sufre. Un formulario bien hecho produce datos limpios porque no dejó lugar a la ambigüedad.
Un esquema de entrada es ese formulario, pero para un workflow en vez de para una persona. Diseñarlo bien es exactamente el mismo oficio: etiquetas precisas, tipos claros, obligatorios marcados, valores por defecto donde ayuden. Y diseñarlo mal produce el mismo desastre: un sub-workflow que recibe datos inconsistentes y produce resultados inconsistentes.
Un esquema tiene, entonces, cuatro decisiones por cada campo. Vamos una por una, porque cada una es una promesa distinta.
Decisión 1: el nombre exacto
El nombre de un campo no es una etiqueta amigable; es la llave con la que el llamador y el que responde se encuentran. order-triage escribe amount y check-credit lee amount: si uno escribe amount y el otro lee Amount con mayúscula, no se encuentran. Los nombres son literales y sensibles a cada carácter.
Tres reglas prácticas para nombrar, que vienen de la convención de todo el ecosistema y del mercado real:
Los nombres van en inglés. customer_id, no id_cliente. available_credit, no credito_disponible. Es la convención de toda la guía y de cualquier equipo tech de la región: el código, los nombres de campo y los datos hablan inglés, aunque la prosa y los comentarios sean en español.
Elige un estilo y no lo mezcles. En este ecosistema usamos snake_case —palabras en minúscula unidas por guion bajo— para los campos de datos: customer_id, order_id, available_credit. Lo que importa no es tanto cuál estilo elijas, sino que no lo mezcles: un contrato con customer_id y orderId a la vez es un contrato que invita a equivocarse, porque nadie recuerda cuál campo usaba cuál convención.
Prefiere lo específico sobre lo flexible. Ya lo adelantó la lección 2: un nombre vago como data, value o input parece flexible y es frágil, porque tienta a que cada quien meta ahí cosas distintas. Un nombre específico como available_credit es rígido en el buen sentido: dice qué es, y por eso dura. Si te descubres queriendo llamar un campo data, casi siempre es señal de que ese campo debería ser dos o tres campos con nombres propios.
Decisión 2: el tipo
El tipo de un campo dice de qué está hecho su valor, y en n8n los tipos que vas a usar en un esquema de entrada son cuatro. Conviene conocerlos con su comportamiento, porque elegir mal el tipo es la fuente del error más silencioso del módulo.
| Tipo | Qué guarda | Ejemplo en Cumbre |
|---|---|---|
string | Texto | customer_id, order_id, reason |
number | Un número, con o sin decimales | amount, available_credit |
boolean | Verdadero o falso, nada más | approved |
json | Un objeto o una lista (datos anidados) | line_items (la lista de productos de un pedido) |
La distinción que más cuesta y más importa es entre string y number. Un amount de 1842.50 como número es una cosa; el texto "1842.50" es otra completamente distinta, aunque en la pantalla se vean casi igual. Con el número puedes comparar, sumar, restar. Con el texto, "1842.50" es mayor o menor que otro texto por orden alfabético, no por valor —"1842.50" comparado con "900.00" da que el primero es menor, porque "1" viene antes que "9" en el alfabeto—. Un amount que llega como texto donde el contrato pedía número es la causa clásica de que check-credit apruebe un pedido que debía rechazar. Por eso el tipo no es un detalle administrativo: es una promesa sobre qué operaciones son seguras con ese valor.
El tipo json es para datos que tienen estructura interna: una lista de cosas, o un objeto con sus propios campos. Si un sub-workflow necesita recibir el pedido completo con su arreglo line_items, ese campo es json, porque adentro lleva más estructura que un solo valor.
Decisión 3: obligatorio u opcional (y el valor por defecto)
Cada campo lleva una marca: ¿el llamador tiene que mandarlo, o puede omitirlo?
Un campo obligatorio es uno sin el cual el sub-workflow no puede hacer su trabajo. customer_id en check-credit es obligatorio: sin saber de quién hablas, no hay crédito que consultar. Marcar un campo como obligatorio le da al sub-workflow el derecho de rechazar la llamada si falta —el derecho que vas a ejercer en la lección 5—.
Un campo opcional es uno que el sub-workflow sabe cómo suplir si no llega. Y aquí entra una pieza clave del diseño: el valor por defecto. Un campo opcional casi nunca debería quedar simplemente "vacío" cuando falta; debería tener un valor por defecto sensato que el sub-workflow use en su lugar. currency en check-credit puede ser opcional con un valor por defecto de "MXN": si el llamador no manda la moneda, el sub-workflow asume pesos mexicanos y sigue trabajando, en vez de detenerse o —peor— seguir con la moneda en blanco.
El valor por defecto es lo que hace que "opcional" signifique algo concreto en vez de ser un hueco peligroso. Piénsalo con el formulario de papel: la casilla "País" que ya trae escrito "México" es un campo opcional con valor por defecto. Si la dejas en blanco, no queda un vacío que rompe el trámite; queda "México". Un campo opcional sin valor por defecto es una casilla en blanco que alguien más aguas abajo va a tener que adivinar cómo llenar —y adivinar es justo lo que un contrato existe para evitar—.
Una guía práctica para decidir la obligatoriedad: pregúntate "si este campo no llega, ¿el sub-workflow puede producir un resultado correcto?". Si la respuesta es no, es obligatorio. Si la respuesta es "sí, usando tal valor razonable", es opcional con ese valor como default. Si la respuesta es "sí, pero no sé con qué valor"... entonces todavía no terminaste de diseñarlo: o es obligatorio, o le falta decidir su default.
Decisión 4: la forma de la respuesta (el sobre)
Las tres decisiones anteriores diseñan la entrada. La cuarta diseña la salida, y tiene una sutileza propia que vale la pena tratar con cuidado, porque es donde más equipos improvisan.
El problema: un sub-workflow puede terminar de dos maneras muy distintas —con éxito o con fallo— y el llamador necesita saber, de un vistazo, en cuál de las dos está antes de intentar leer nada. Si check-credit a veces devuelve { approved, available_credit } y a veces devuelve { error, code, message }, el llamador tiene que adivinar cuál de las dos formas le tocó esta vez. Adivinar, otra vez, es lo que queremos eliminar.
La solución es un sobre consistente: una envoltura común para toda respuesta, con un campo fijo que dice de entrada si esto fue éxito o fallo, y adentro el contenido que corresponda a cada caso. Piénsalo como un sobre de correo con una casilla en la esquina que siempre está: "ENTREGA" o "DEVOLUCIÓN". Antes de abrir el sobre, esa casilla ya te dijo qué esperar adentro. No tienes que leer la carta entera para saber si tu paquete llegó o te lo regresaron.
Para check-credit, el sobre se ve así:
// Respuesta en ÉXITO — la casilla "ok" dice true
{
"ok": true,
"customer_id": "CUST-118",
"approved": true,
"available_credit": 5157.50
}
// Respuesta en FALLO — la casilla "ok" dice false
{
"ok": false,
"error": {
"code": "INVALID_INPUT",
"message": "El campo amount es obligatorio y debe ser un número."
}
}
Fíjate en lo que gana el llamador: lo primero que hace order-triage al recibir la respuesta es leer ok. Si es true, sabe que puede leer approved con confianza. Si es false, sabe que debe leer error.code y error.message y tomar su rama de fallo. Nunca tiene que adivinar en qué caso está: la casilla del sobre se lo dijo antes de abrir. Esta forma —un campo discriminador fijo, más el contenido de cada caso— es una de las decisiones de diseño que más facilita la vida de todos los que van a llamar a tu sub-workflow.
No hay una única forma "correcta" del sobre; hay formas consistentes y formas improvisadas. Podrías usar ok: true/false, o status: "success"/"error", o success: true/false. Lo que importa —igual que con la convención de nombres— es que elijas una y la uses en todos tus sub-workflows, para que un llamador que ya usó uno sepa leer los demás sin aprender de nuevo. En Cumbre eligieron ok, y así queda para toda la guía.
Ejemplo trabajado: diseñar el esquema completo de check-credit
Juntemos las cuatro decisiones en el esquema terminado de check-credit, campo por campo, tomando cada decisión en voz alta para que veas el razonamiento y no solo el resultado.
Entrada. Empezamos por lo que el sub-workflow necesita recibir.
customer_id— ¿de qué está hecho? Un identificador de cliente, texto:string. ¿Obligatorio? Sin él no hay crédito que consultar: obligatorio. Sin default.order_id— texto también:string. ¿Obligatorio? Sí, porque queremos poder rastrear a qué pedido perteneció cada consulta de crédito: obligatorio.amount— el total del pedido, un número:number. Obligatorio, y aquí el tipo es crítico:number, nuncastring, porque lo vamos a comparar contra el crédito.currency— la moneda del monto, texto:string. ¿Obligatorio? No: si no llega, podemos asumir la moneda base de Cumbre. Opcional, con valor por defecto"MXN".
Salida en éxito. Lo que promete devolver si todo va bien, dentro del sobre ok: true.
customer_id— se devuelve tal cual entró, para que el llamador sepa de quién es esta respuesta sin tener que recordar qué mandó.approved— verdadero o falso:boolean. Es el campo queorder-triagelee para decidir.available_credit— cuánto crédito le queda al cliente después de este pedido:number.
Salida en fallo. Dentro del sobre ok: false, un objeto error con:
code— un código estable de una lista corta y conocida:"INVALID_INPUT","CUSTOMER_NOT_FOUND". Texto, pero de un conjunto cerrado, no libre.message— la explicación legible para un humano que lea el log.
El esquema terminado, listo para documentar:
ESQUEMA — check-credit
ENTRADA
customer_id : string obligatorio
order_id : string obligatorio
amount : number obligatorio
currency : string opcional (default: "MXN")
SALIDA (sobre con discriminador "ok")
ÉXITO → { ok: true, customer_id: string, approved: boolean, available_credit: number }
FALLO → { ok: false, error: { code: string, message: string } }
codes posibles: "INVALID_INPUT", "CUSTOMER_NOT_FOUND"
EFECTOS
ninguno (solo lectura)
Qué esperar. Con este esquema en la mano, cuando en la lección 4 declares los campos de entrada en el nodo Execute Sub-workflow Trigger, vas a transcribir exactamente esta lista: cuatro campos, tres obligatorios de tipo string/number, uno opcional con default. Cuando en la lección 5 valides, vas a comprobar exactamente estas cuatro condiciones. Y cuando en la lección 6 versiones, vas a comparar cualquier cambio contra exactamente este documento. El esquema es la fuente única de verdad de las tres lecciones que siguen; escribirlo bien una vez te sirve tres veces.
Dónde vive el esquema: pegado al workflow
Un esquema que vive en un documento aparte, en una carpeta que nadie abre, se desactualiza el primer día. La regla de oro es que el contrato viva lo más cerca posible del workflow que describe, para que quien edite el workflow vea el contrato sin buscarlo.
En n8n, el lugar natural es un Sticky Note —una nota adhesiva que pones directamente en el lienzo del sub-workflow, junto a su primer nodo—. Es texto libre que no ejecuta nada; solo está ahí para que cualquiera que abra el workflow lea el contrato antes de tocar nada. Pones el esquema completo en un Sticky Note pegado al Execute Sub-workflow Trigger, y ya el contrato es imposible de ignorar: está en la misma pantalla donde alguien iría a cambiar algo.
Hay una segunda capa de documentación que la lección 4 va a construir, y conviene distinguirla de esta. El Sticky Note es documentación para humanos: la lee una persona. Los campos que declaras dentro del nodo Execute Sub-workflow Trigger son documentación para n8n: la lee la máquina, que con ellos sabe qué espera el sub-workflow y ayuda al llamador a mandarlo. Las dos capas describen el mismo contrato; una en prosa para el equipo, otra en campos para el motor. Un contrato bien documentado tiene las dos, y las dos dicen lo mismo —cuando divergen, empiezan los problemas—.
Un ejemplo concreto vale por mil tipos
Hay una tercera pieza de documentación, más humilde que las otras dos y sorprendentemente valiosa: un ejemplo real de una entrada y de una salida, con valores de verdad. El esquema dice amount : number obligatorio; el ejemplo dice "amount": 1842.50. Los dos describen lo mismo, pero el ejemplo lo hace de una forma que el cerebro entiende de un vistazo, sin traducir tipos a valores.
La razón es la que ya conoces del ejemplo trabajado del pedido canónico de Cumbre a lo largo de la guía: una tabla de tipos te dice la forma; un objeto lleno te muestra la forma funcionando. Cuando alguien va a llamar a check-credit por primera vez, un ejemplo de entrada le ahorra la mitad de las dudas —"ah, customer_id se ve así: CUST-118; el amount va con decimales"— que ninguna tabla de tipos resuelve tan rápido.
Por eso conviene que el Sticky Note del contrato incluya, además del esquema, un par de ejemplos concretos:
// Ejemplo de ENTRADA válida
{
"customer_id": "CUST-118",
"order_id": "ORD-2041",
"amount": 1842.50,
"currency": "MXN"
}
// Ejemplo de SALIDA en éxito
{
"ok": true,
"customer_id": "CUST-118",
"approved": true,
"available_credit": 5157.50
}
// Ejemplo de SALIDA en fallo (faltó amount)
{
"ok": false,
"error": {
"code": "INVALID_INPUT",
"message": "El campo amount es obligatorio y debe ser un número."
}
}
Este ejemplo no es solo para leer. En la lección 4 vas a ver que el nodo Execute Sub-workflow Trigger ofrece un modo llamado "Define using JSON example", donde le pegas un objeto como el de arriba y n8n deduce el esquema a partir de él. Es decir: el ejemplo concreto que escribes para que un humano entienda es, en n8n 2.0, también una forma de declararle el esquema a la máquina. Un solo objeto bien elegido sirve para las dos capas de documentación a la vez. Cuando llegues a esa lección, el ejemplo que armes aquí no se desperdicia: se pega y se convierte en contrato ejecutable.
Menos es más: cada campo es una promesa que vas a tener que cumplir
Una tentación al diseñar un esquema es agregar campos "por si acaso": un metadata genérico, un extra_info que quizás algún día sirva, tres campos de salida que ningún llamador pide todavía. Resiste esa tentación, y la razón es la definición misma de contrato.
Cada campo que pones en el esquema es una promesa que te comprometes a cumplir para siempre —o al menos hasta que versiones—. Un campo de salida que agregaste "por si acaso" es un campo que algún llamador podría empezar a leer, y desde ese momento ya no lo puedes quitar sin romperlo. Un contrato con veinte campos es un contrato con veinte promesas que mantener, la mayoría de las cuales nadie te pidió. La estabilidad de la que hablamos en la lección 2 es más fácil de sostener sobre un contrato pequeño y preciso que sobre uno grande y especulativo.
La disciplina es la inversa de "por si acaso": incluye solo los campos que un llamador real necesita hoy, con los nombres y tipos más precisos que puedas. Si mañana surge una necesidad nueva, agregar un campo opcional es un cambio compatible y seguro (lección 6). Empezar chico y crecer con cuidado es sostenible; empezar grande y tener que podar es doloroso, porque podar un contrato es romperlo. check-credit tiene cuatro campos de entrada y tres de salida útil, y con eso hace todo lo que Cumbre necesita. No le sobra ninguno.
Errores comunes
Usar string para todo, incluido lo que es número (práctico). Qué pasa: alguien diseña el esquema y marca amount como string porque "de todas formas es texto que se ve como número", y más adelante check-credit compara ese texto contra el crédito y obtiene resultados absurdos —aprueba pedidos que debía rechazar—. Por qué pasa: en muchos formularios y en algunos canales de entrada los números llegan como texto (el canal rep_csv de Cumbre es famoso por esto), y es tentador reflejar esa realidad sucia en el contrato en vez de exigir el tipo correcto. Cómo detectarlo: para cada campo del esquema, pregúntate "¿voy a comparar, sumar o restar este valor?". Si sí, tiene que ser number; si está como string, ahí está el error. Cómo corregirlo: el contrato exige el tipo correcto, number para todo lo que sea aritmética. Que un canal mande el número como texto no es problema del contrato, es problema de quien llama: le toca convertirlo antes de llamar, o el sub-workflow lo va a rechazar en la validación de la lección 5. El contrato define cómo deben llegar los datos, no cómo llegan cuando están sucios.
Opcionales sin valor por defecto (conceptual). Qué pasa: se marca currency como opcional pero no se le define un default; cuando un llamador no lo manda, el sub-workflow sigue con currency en blanco y produce un cálculo con la moneda vacía o falla varios nodos después. Por qué pasa: marcar "opcional" se siente completo, y es fácil olvidar que "opcional" sin default no dice qué hacer cuando el campo falta —solo dice que puede faltar—. Cómo detectarlo: recorre cada campo opcional del esquema y verifica que tenga un valor por defecto escrito al lado; si alguno no lo tiene, el diseño está a medias. Cómo corregirlo: todo campo opcional lleva un default explícito y sensato, y el sub-workflow usa ese default cuando el campo no llega. Si no encuentras un default razonable para un campo, es una señal fuerte de que ese campo en realidad era obligatorio.
Una forma de salida distinta para el éxito y para el fallo, sin discriminador (práctico). Qué pasa: el sub-workflow devuelve { approved, available_credit } cuando va bien y { error, message } cuando va mal, sin un campo común que diga cuál es cuál; el llamador termina revisando "¿existe el campo approved? entonces fue éxito" —una heurística frágil que se rompe en cuanto agregas un campo—. Por qué pasa: cada forma se diseña por separado, en el momento en que se necesita, sin pensar en que el llamador las va a recibir por el mismo cable y necesita distinguirlas rápido. Cómo detectarlo: mira tu respuesta de éxito y tu respuesta de fallo lado a lado; si no comparten un campo fijo que diga de entrada cuál es cuál, falta el discriminador. Cómo corregirlo: envuelve ambas en el mismo sobre con un campo discriminador (ok: true/false), de modo que el llamador lea ese campo primero y sepa sin ambigüedad qué rama tomar. Es una línea de diseño que le ahorra un dolor de cabeza a cada llamador, presente y futuro.
Ejercicios
Ejercicio 1 — Diseña el esquema de issue-refund. En la lección 2 escribiste el contrato de issue-refund en prosa. Ahora conviértelo en un esquema con las cuatro decisiones por campo. Recibe order_id, amount y reason; devuelve refund_id y status en éxito. Decide el tipo de cada campo, cuáles son obligatorios, si alguno merece ser opcional con default, y escribe la salida usando el mismo sobre ok de check-credit.
Ver solución
ESQUEMA — issue-refund
ENTRADA
order_id : string obligatorio — el pedido a reembolsar
amount : number obligatorio — monto a reembolsar (number, es dinero: se compara y valida)
reason : string obligatorio — motivo, texto libre
SALIDA (sobre con discriminador "ok")
ÉXITO → { ok: true, order_id: string, refund_id: string, status: string }
FALLO → { ok: false, error: { code: string, message: string } }
codes posibles: "INVALID_INPUT", "ORDER_NOT_FOUND", "ALREADY_REFUNDED"
EFECTOS
SÍ produce efecto: emite un reembolso. La llamada debe ser idempotente (Módulo 2).
Las tres entradas son obligatorias: no hay reembolso sin saber sobre qué pedido, por cuánto y por qué. amount es number, no string, por la misma razón que en check-credit: es dinero, se compara y se valida. Ninguna entrada merece ser opcional aquí —a diferencia de currency en check-credit, no hay un campo del que puedas asumir un default sensato sin arriesgarte, porque todos son esenciales para un efecto irreversible—. La salida usa el mismo sobre ok, con un code de fallo que incluye "ALREADY_REFUNDED", propio de un efecto que no se puede repetir.
Por qué funciona: reusar el sobre ok de check-credit no es pereza, es diseño: cualquiera que ya sabe leer la respuesta de un sub-workflow de Cumbre sabe leer la de todos. Y notar que ningún campo de issue-refund merece ser opcional te enseña que la obligatoriedad depende del sub-workflow, no de una regla fija: un default sensato es un lujo que solo algunos campos, en algunos sub-workflows, pueden darse.
Ejercicio 2 — Corrige un esquema flojo. Te pasan este esquema de entrada para un sub-workflow apply-discount que aplica un descuento a un pedido. Encuentra al menos tres problemas de diseño y propón el arreglo:
ENTRADA
data : json obligatorio — la info del descuento
amount : string obligatorio — cuánto descontar
pct : number opcional — porcentaje
Ver solución
Problema 1: data es un nombre vago. Un campo llamado data de tipo json no dice qué contiene, y tienta a que cada llamador meta ahí cosas distintas. Arreglo: reemplazarlo por campos específicos —probablemente order_id: string y lo que sea que "la info del descuento" signifique de verdad, con nombres propios—.
Problema 2: amount es string pero es un monto de dinero. Se va a comparar o calcular, así que tiene que ser number. Como está, invita al error silencioso de comparar texto como si fuera número. Arreglo: amount : number.
Problema 3: pct es opcional pero no tiene valor por defecto. Si un llamador no lo manda, el sub-workflow no sabe qué porcentaje aplicar. Además el nombre pct es una abreviatura poco clara. Arreglo: renombrar a discount_pct : number, y decidir su default —por ejemplo 0, que significa "sin descuento" si no se especifica— o, si no hay default sensato, hacerlo obligatorio.
Bonus: el esquema no define ninguna salida ni forma de error. Un contrato a medias. Arreglo: agregar la salida en éxito y en fallo con el sobre ok.
Por qué funciona: los tres problemas son los tres errores comunes de esta lección juntos en un solo esquema —nombre vago, tipo equivocado, opcional sin default—. Un esquema flojo casi nunca falla por una sola cosa; falla por acumulación de decisiones que "parecían suficientes" en el momento y que juntas producen un contrato que no se puede validar ni versionar con confianza.
Ejercicio 3 — Obligatorio u opcional. Para un sub-workflow send-order-confirmation que le manda un correo de confirmación al cliente, decide para cada campo si es obligatorio u opcional, y si es opcional, cuál sería su valor por defecto. Justifica cada uno con la pregunta "si este campo no llega, ¿el sub-workflow puede producir un resultado correcto?".
(a) customer_email — a quién se le manda el correo.
(b) order_id — qué pedido se confirma.
(c) language — el idioma del correo ("es" o "en").
(d) include_invoice — si adjuntar o no la factura.
Ver solución
(a) customer_email — obligatorio, sin default. Sin la dirección no hay a quién mandarle el correo; no existe un default sensato (¿mandárselo a quién?). Si no llega, el sub-workflow no puede producir un resultado correcto.
(b) order_id — obligatorio, sin default. Sin saber qué pedido se confirma, el correo no tendría contenido con sentido. No hay pedido "por defecto".
(c) language — opcional, default "es". Si no llega, el sub-workflow puede producir un resultado perfectamente correcto usando el idioma base de Cumbre. Es el caso de libro de un opcional con default.
(d) include_invoice — opcional, default false. Si no llega, la opción segura y sensata es no adjuntar la factura; el correo sale bien igual. Un booleano opcional casi siempre tiene como default la opción más conservadora.
Por qué funciona: la pregunta "¿puede producir un resultado correcto sin este campo?" separa limpiamente los dos casos. Para customer_email y order_id la respuesta es no —son el corazón de la tarea—; para language e include_invoice la respuesta es "sí, usando este valor razonable", que es la definición exacta de un opcional con default. Fíjate que la dificultad no es técnica: es de negocio. Decidir el default de include_invoice es decidir qué comportamiento es el seguro cuando nadie especificó, y esa es una decisión de diseño, no de sintaxis.
Resumen y siguiente paso
En esta lección convertiste el contrato en prosa de la lección 2 en un esquema preciso, tratándolo como un formulario bien hecho: sin ambigüedad, con etiquetas claras y obligatorios marcados. Viste las cuatro decisiones que tomas por cada campo —el nombre exacto (en inglés, con un estilo consistente, específico antes que flexible), el tipo (con el abismo entre string y number como el detalle que más duele equivocar), la obligatoriedad, y el valor por defecto que le da sentido concreto a "opcional"—. Diseñaste la forma de la respuesta con un sobre consistente y un campo discriminador (ok: true/false) que le dice al llamador de entrada si fue éxito o fallo, sin que tenga que adivinar. Escribiste el esquema completo de check-credit, que va a ser la fuente de verdad de las tres lecciones que siguen. Aprendiste que el esquema vive pegado al workflow —un Sticky Note para humanos, y en la lección 4, los campos del trigger para la máquina—. Y adoptaste la disciplina de "menos es más": cada campo es una promesa que vas a tener que cumplir, así que solo incluyes los que un llamador real necesita hoy.
Antes de avanzar a la lección 4 deberías poder: tomar un contrato en prosa y escribir su esquema con las cuatro decisiones por campo; elegir el tipo correcto distinguiendo lo que es número de lo que solo se ve como número; y diseñar una respuesta con sobre discriminador.
Hasta aquí el esquema vive en papel y en un Sticky Note. La lección 4 lo lleva al lugar donde de verdad ocurre la llamada: la frontera del nodo Execute Sub-workflow. Vas a ver cómo order-triage invoca a check-credit, cómo se declaran los campos de entrada de tu esquema dentro del nodo Execute Sub-workflow Trigger para que n8n los reconozca, y por qué esa frontera —un solo punto de entrada— es lo que hace que todo el contrato sea gobernable.
Recursos
- Execute Sub-workflow Trigger — n8n Docs — el nodo donde vas a declarar los campos de entrada del esquema que diseñaste aquí, con sus modos de definición de datos de entrada.
- Data structure — n8n Docs — cómo se representan los tipos (
string,number,boolean, objetos y listas) en los items que viajan entre nodos. - Sticky notes — n8n Docs — cómo poner una nota en el lienzo para documentar el contrato pegado al workflow que describe.
- Data mapping in the UI — n8n Docs — cómo se conectan los campos entre nodos, útil para entender por qué los nombres exactos del esquema importan tanto.