Módulo 3: Contratos entre workflows

2. Qué es un contrato de workflow

Descripción

Al terminar esta lección vas a poder definir con precisión qué es un contrato de workflow —no como una metáfora, sino como un objeto con partes concretas— y vas a poder identificar sus cuatro piezas en cualquier sub-workflow que te encuentres: los campos de entrada con sus tipos y su obligatoriedad, la forma de la salida cuando todo va bien, la forma de la salida cuando algo falla, y los efectos que el sub-workflow produce en el mundo. También vas a entender por qué poner ese contrato por escrito, aunque el sistema ya "funcione", es la diferencia entre un cambio seguro y una bomba de tiempo.

Esto importa porque en la lección anterior quedó claro que el contrato entre dos workflows existe siempre, lo escribas o no. Pero "existe" no es suficiente para protegerlo: un contrato que solo vive en tu cabeza y en la forma en que casualmente conectaste los nodos no se puede revisar, no se puede validar y no se puede versionar. Para hacer cualquiera de esas tres cosas —que son los temas de las lecciones 5, 6 y 3 respectivamente— necesitas primero saber exactamente de qué está hecho un contrato. Esta lección le pone anatomía a la promesa.

Conexión con el módulo: la lección 1 te mostró que un sistema de varios workflows es un sistema de promesas, y que esas promesas se rompen en silencio. Esta lección define la promesa con precisión, para que las siguientes puedan operar sobre ella. La lección 3 toma esta anatomía y la convierte en un esquema concreto que escribes al lado del workflow. La lección 4 te muestra dónde vive físicamente la frontera por donde cruza este contrato. Y todo lo que valides (lección 5) y versiones (lección 6) es, en el fondo, este contrato defendido. Hay un puente importante hacia afuera del módulo: el concepto de "contrato de una tool" que enseña la guía de chatbots y agentes es un pariente directo de lo que ves aquí —un caso particular donde el que llama es un modelo de IA en vez de otro workflow—, y la lección 7 los va a unir explícitamente.

La promesa, con precisión: la firma de una función

En la lección anterior usamos el menú de un restaurante como imagen. Es una buena imagen para la intuición, pero para trabajar necesitamos algo más preciso. La analogía exacta de un contrato de workflow es la firma de una función en programación.

No te asustes si no programas: la idea es más simple que la palabra. Una función es una caja que recibe unos datos, hace algo con ellos y devuelve un resultado. Su firma es la línea que dice qué recibe y qué devuelve, sin decir cómo lo hace por dentro. Piensa en una máquina expendedora. Su firma es: "recibe una moneda y el número de un producto; devuelve el producto o te regresa la moneda". Esa promesa te alcanza para usarla. No necesitas saber cómo está el mecanismo de resortes por dentro, ni en qué orden se mueven los engranajes. Metes la moneda, marcas B4, sale el chocolate. La firma es el contrato; los engranajes son la implementación.

Un sub-workflow es exactamente una máquina expendedora. check-credit tiene una firma: "recibe un customer_id, un order_id y un amount; devuelve un approved y un available_credit". Con esa firma, order-triage puede usarlo sin saber nada de sus engranajes —sin saber si consulta el crédito en una base de datos, en una hoja de cálculo o en una API—. Mete la moneda (los tres campos de entrada), marca el producto (llama al sub-workflow), y sale el chocolate (el resultado).

Aquí está la definición que vamos a usar todo el módulo, y conviene leerla despacio:

Un contrato de workflow es la promesa estable, entre quien llama y quien responde, sobre qué campos entran —con qué tipos y cuáles obligatorios—, qué forma tiene la salida cuando todo va bien, qué forma tiene la salida cuando algo falla, y qué efectos produce el sub-workflow en el mundo.

Cada palabra de esa definición carga peso. "Estable", porque un contrato que cambia todo el tiempo no es un contrato, es una negociación. "Entre quien llama y quien responde", porque un contrato tiene dos partes y las dos tienen obligaciones. "Con qué tipos y cuáles obligatorios", porque un campo que a veces es número y a veces es texto, o que a veces está y a veces no, rompe al que confiaba en él. Y las dos formas de salida —éxito y fallo— porque un sub-workflow que solo promete qué devuelve cuando todo sale bien deja a su llamador a ciegas justo cuando algo sale mal, que es cuando más lo necesita.

La anatomía de un contrato: sus cuatro piezas

Desarmemos la definición en las cuatro piezas concretas que vas a buscar en cualquier sub-workflow. Es la misma lista que vas a llenar cuando diseñes el tuyo en la lección 3.

Pieza 1: los campos de entrada. Qué datos necesita recibir el sub-workflow para hacer su trabajo. Cada campo tiene tres atributos: un nombre exacto (customer_id, no customerId ni cliente), un tipo (texto, número, booleano, objeto o lista), y una marca de obligatorio u opcional. Un campo obligatorio es uno sin el cual el sub-workflow no puede funcionar; uno opcional es uno que, si no llega, el sub-workflow sabe cómo arreglárselas —normalmente con un valor por defecto—.

Pieza 2: la forma de la salida en éxito. Qué devuelve el sub-workflow cuando hace su trabajo sin problemas. Igual que la entrada, es un conjunto de campos con nombres y tipos exactos. Es la parte del contrato que el llamador lee para continuar: order-triage lee approved de aquí.

Pieza 3: la forma de la salida en fallo. Qué devuelve el sub-workflow cuando no puede hacer su trabajo —porque la entrada era inválida, porque el cliente no existe, porque algo salió mal—. Esta pieza es la que más se olvida y la que más duele olvidar. Un sub-workflow que en caso de fallo a veces devuelve un objeto vacío, a veces un error críptico y a veces nada, obliga a cada llamador a adivinar qué pasó. Un contrato serio promete una forma de error estable: un campo que diga "esto falló" y otro que diga por qué.

Pieza 4: los efectos declarados. Qué cambia el sub-workflow en el mundo cuando corre. check-credit es una lectura: consulta el crédito y responde, sin cambiar nada —lo puedes llamar diez veces y el mundo queda igual—. issue-refund es un efecto: emite un reembolso, mueve dinero, y llamarlo dos veces produce dos reembolsos si no está protegido. Declarar esto en el contrato no es un adorno: le dice al llamador si es seguro reintentar la llamada o si tiene que tratarla con el cuidado de algo irreversible. Aquí es donde el contrato se da la mano con la idempotencia del módulo 2.

Una forma compacta de ver las cuatro piezas juntas, para check-credit:

CONTRATO — check-credit

ENTRADA (lo que el llamador debe mandar)
  customer_id : string   (obligatorio)  — de quién se checa el crédito
  order_id    : string   (obligatorio)  — a qué pedido pertenece la consulta
  amount      : number   (obligatorio)  — total del pedido a comparar contra el crédito

SALIDA EN ÉXITO (lo que el sub-workflow promete devolver si todo va bien)
  customer_id      : string   — se devuelve tal cual, para que el llamador sepa de quién es
  approved         : boolean  — true si hay crédito suficiente, false si no
  available_credit : number   — crédito que le queda al cliente después de este pedido

SALIDA EN FALLO (lo que promete devolver si no puede hacer su trabajo)
  error   : true            — bandera de que esto no salió bien
  code    : string          — código estable: "INVALID_INPUT", "CUSTOMER_NOT_FOUND"
  message : string          — explicación legible para un humano

EFECTOS
  ninguno — check-credit solo lee; llamarlo N veces deja el mundo igual

Eso es un contrato. No es código, no es todavía un esquema formal de n8n —eso viene en la lección 3—; es la promesa escrita en un lenguaje que cualquier persona del equipo puede leer. Y ya solo por estar escrita, hace algo que el contrato invisible de la lección 1 no podía: se puede revisar antes de tocar nada.

Ejemplo trabajado: leer un contrato antes de llamar

Vamos a ponernos del lado del llamador para ver por qué el contrato escrito cambia todo. Imagina que eres la persona que va a conectar order-triage con check-credit, y que nunca viste check-credit por dentro. Lo único que tienes es el contrato de arriba.

Con ese contrato en la mano, puedes responder todas las preguntas que necesitas para hacer la llamada, sin abrir el sub-workflow ni una vez:

  • ¿Qué tengo que mandarle? Tres campos: customer_id, order_id, amount. Los tres obligatorios. Si me falta uno, ya sé —sin probar— que la llamada va a fallar.
  • ¿De qué tipo va cada uno? customer_id y order_id son texto; amount es número. Ese detalle importa: si le mando amount como el texto "1842.50" en vez del número 1842.50, la comparación de crédito puede dar cualquier cosa. El contrato me lo advirtió antes de que me equivocara.
  • ¿Qué me va a devolver si todo va bien? Un approved booleano, que es lo que voy a leer para decidir. Ya sé cómo se llama el campo exacto: approved. No is_approved, no credit_ok. approved.
  • ¿Qué me va a devolver si algo sale mal? Un objeto con error: true, un code y un message. Así que puedo escribir de antemano la rama de mi workflow que maneja el fallo, en vez de descubrir en producción que check-credit a veces devuelve algo raro.

Qué esperar. Cuando conectas los dos workflows guiándote por el contrato, la primera ejecución hace exactamente lo que predijiste: le mandas los tres campos, recibes { customer_id, approved, available_credit }, y tu rama de decisión lee approved y funciona. Y cuando pruebas a mandarle un pedido sin customer_id, recibes { error: true, code: "INVALID_INPUT", message: "..." } —la forma de fallo que el contrato prometía—, y tu rama de error la captura limpiamente. En ningún momento tuviste que abrir check-credit para descubrir cómo se comporta. El contrato fue todo lo que necesitaste.

Compara eso con el mundo sin contrato de la lección 1: ahí, para saber qué campo leer, tenías que abrir check-credit, seguir sus nodos hasta el final, ver qué producía el último, y esperar que nadie lo cambiara. Con el contrato, la máquina expendedora tiene su etiqueta pegada al frente. Metes la moneda, marcas B4, y ya sabes que sale chocolate.

Contrato e implementación: la ventanilla es sagrada

Hay una distinción que sostiene todo el módulo y conviene clavarla ahora: el contrato y la implementación son dos cosas separadas, y solo una de las dos es una promesa.

La implementación es cómo check-credit hace su trabajo por dentro: qué nodos usa, en qué orden, si consulta una base de datos o una hoja de cálculo, si el cálculo del crédito disponible resta los pedidos pendientes con un nodo Code o con tres nodos visuales. Todo eso son los engranajes de la máquina expendedora. Y todo eso —esta es la parte liberadora— puedes cambiarlo cuando quieras sin romper a nadie, siempre que la firma se mantenga. Si mañana check-credit cambia de consultar una hoja de cálculo a consultar una base de datos Postgres, pero sigue recibiendo los mismos tres campos y devolviendo los mismos tres, ningún llamador se entera ni le importa. Cambiaste la cocina; la ventanilla quedó igual.

El contrato es la firma: las dos flechas del diagrama de la lección 1, lo que entra y lo que sale por la frontera. Eso sí es una promesa, y romperla rompe a todos los que confiaban en ella. Renombrar approved no es cambiar la cocina; es mover la ventanilla, y del otro lado hay meseros que ya no encuentran el plato.

De aquí sale la regla operativa más útil de todo el módulo, y ya la nombramos en la lección 1: puedes cambiar la implementación libremente; el contrato solo se cambia con cuidado y versionándolo. Cuando el equipo de Cumbre entienda esta separación, va a poder mejorar sus sub-workflows por dentro todo lo que quiera —hacerlos más rápidos, más limpios, más baratos— sin miedo, porque sabrá exactamente cuál es la línea que no se cruza sin avisar. Ese miedo a tocar nada "no vaya a ser que algo se rompa" que paraliza a los equipos sin contratos, desaparece cuando la frontera está escrita.

Un contrato tiene dos partes, y las dos deben cumplir

Volvamos a la palabra "entre" de la definición: el contrato es entre quien llama y quien responde. No es una lista de obligaciones de un solo lado. Es un acuerdo con deberes para los dos, y esto es más que una formalidad —cambia cómo diseñas—.

El que responde promete su salida. check-credit se compromete a que, si recibe una entrada válida, va a devolver approved, available_credit y customer_id con esos nombres y esos tipos; y a que, si algo falla, va a devolver la forma de error prometida. Nunca va a devolver un objeto vacío sin explicación, ni un campo con un nombre distinto según el día. Esa es su parte del trato.

El que llama promete su entrada. order-triage se compromete a mandar los tres campos obligatorios, con los tipos correctos. No puede mandar amount como texto y luego quejarse de que la comparación falló; el contrato decía número. No puede omitir customer_id y esperar que check-credit adivine de quién hablar. Esa es su parte.

Esta simetría tiene una consecuencia práctica que vas a usar en la lección 5. Como el llamador puede incumplir su parte —por error, por un bug, por un dato sucio que venía de un canal como rep_csv—, el que responde no puede confiar ciegamente en que la entrada llegó bien. Tiene que verificar que se cumplió la promesa de entrada antes de actuar. Ese acto de verificar en la puerta —"¿me mandaste de verdad lo que el contrato exige?"— es la validación en la frontera, y es tan importante que tiene lección propia. Por ahora quédate con la idea: un contrato de dos partes significa que el que responde tiene el derecho —y la responsabilidad— de rechazar una entrada que no cumple, en vez de intentar trabajar con basura y producir un resultado sin sentido.

Por qué escribirlo, si el sistema ya funciona

Es la objeción razonable: si order-triage y check-credit ya se entienden y todo corre, ¿para qué el trabajo extra de escribir el contrato? El sistema funciona sin el documento.

La respuesta es que el documento no hace que el sistema funcione hoy —eso ya pasa—; hace que el sistema sobreviva al cambio de mañana. Y los sistemas de automatización viven para cambiar. Veamos qué gana concretamente el contrato escrito, más allá de la teoría.

Gana que el cambio se vuelve una decisión consciente. Sin contrato escrito, la persona que renombró approved no tenía forma de saber que estaba tocando una promesa; para ella era solo un campo con un nombre pobre. Con el contrato escrito al lado del workflow, ese campo aparece listado como parte de la salida prometida, y renombrarlo deja de ser un descuido para convertirse en lo que realmente es: romper un contrato, una decisión que se toma a propósito y con un plan de migración (lección 6), no de pasada un martes por la tarde.

Gana que cualquiera del equipo puede usar el sub-workflow sin ingeniería inversa. En Cumbre son doce personas. La que escribió check-credit no va a ser siempre la que lo conecte a un workflow nuevo. Sin contrato, cada nuevo llamador tiene que abrir el sub-workflow, seguir sus nodos y deducir su firma —y cada deducción es una oportunidad de equivocarse—. Con contrato, la firma está escrita: se lee en treinta segundos y se usa bien a la primera.

Gana que la validación y el versionado se vuelven posibles. No puedes validar contra un contrato que no existe por escrito —¿validar contra qué?—. No puedes distinguir un cambio compatible de uno rompiente sin un contrato de referencia contra el cual comparar. Las dos herramientas más potentes de este módulo, las lecciones 5 y 6, se apoyan sobre un contrato explícito. El documento no es burocracia; es el cimiento de todo lo que sigue.

Dicho esto, con honestidad: escribir el contrato tiene un costo, y no todo sub-workflow lo necesita con el mismo rigor. Un sub-workflow trivial que solo tú usas y que nunca vas a cambiar puede vivir con un contrato ligero —una nota de dos líneas—. El rigor se justifica cuando el sub-workflow tiene más de un llamador, cuando maneja un efecto que importa, o cuando lo va a mantener alguien distinto de quien lo escribió. check-credit, que decide si un pedido sigue o se detiene, cae de lleno en esa categoría. La regla no es "contrato pesado para todo"; es "el contrato explícito es proporcional a cuánto duele que se rompa".

La palabra que carga todo el peso: "estable"

Vuelve a la definición una vez más y fíjate en un adjetivo que es fácil leer por encima: la promesa es estable. No es un detalle de estilo; es lo que convierte una forma de datos en un contrato. Cualquiera puede describir qué campos entran y salen de un sub-workflow hoy. Lo que hace que esa descripción sea un contrato es el compromiso de que va a seguir siendo la misma mañana, y pasado, y dentro de tres meses cuando otra persona la conecte a un workflow nuevo.

Piénsalo con un enchufe de pared. La razón por la que puedes comprar cualquier lámpara y confiar en que va a encajar en cualquier toma de tu casa no es que la lámpara y la toma se hayan puesto de acuerdo esta mañana —es que la forma del enchufe lleva décadas siendo la misma—. Esa estabilidad es lo que hace que un fabricante de lámparas en un país y un electricista en otro puedan trabajar sin hablarse jamás: los dos confían en una forma que no cambia. Si cada fabricante moviera las clavijas cuando le pareciera que quedan "más elegantes", el enchufe dejaría de ser un contrato y volvería a ser una negociación caso por caso.

Un contrato de workflow es ese enchufe. order-triage y check-credit pueden ser mantenidos por personas distintas, en semanas distintas, sin coordinarse, exactamente porque la forma de la llamada es estable. En el momento en que esa forma se vuelve movediza —hoy approved, la semana que viene credit_approved, según a quién le pareció más claro—, deja de ser un contrato y vuelve a ser el restaurante sin menú de la lección 1.

Esto tiene una consecuencia de diseño que conviene tener desde ya, aunque la desarrolle la lección 6: un contrato se diseña para durar, no para el requerimiento de hoy. Cuando escribas el esquema de check-credit en la próxima lección, no vas a elegir los nombres pensando solo en lo que necesitas esta semana; vas a elegirlos pensando en que dentro de un año seguirán ahí. Un nombre vago como data o value —que parece flexible— es en realidad frágil, porque tienta a que cada quien meta ahí cosas distintas y la promesa se disuelva. Un nombre preciso como available_credit es rígido en el buen sentido: dice exactamente qué es, y esa rigidez es la que lo hace durar. La estabilidad no se logra dejando el contrato abierto; se logra haciéndolo específico y comprometiéndose a respetarlo.

Errores comunes

Documentar solo la salida en éxito y olvidar la de fallo (conceptual). Qué pasa: alguien escribe el contrato de check-credit listando cuidadosamente approved y available_credit, y da el contrato por completo; el día que la entrada llega inválida, el sub-workflow devuelve cualquier cosa —un objeto a medias, un error interno de n8n— y el llamador no sabe interpretarlo. Por qué pasa: cuando pruebas un sub-workflow, casi siempre lo pruebas con datos buenos, así que el camino de éxito es el único que ves; el de fallo queda invisible hasta que ocurre en producción. Cómo detectarlo: mira tu contrato y pregúntate "si le mando una entrada inválida, ¿qué dice el contrato que me va a devolver?". Si no hay respuesta escrita, el contrato está a la mitad. Cómo corregirlo: define la forma de error como parte del contrato, siempre, con la misma seriedad que la de éxito —una bandera error, un code estable y un message legible—. La lección 5 usa exactamente esa forma para rechazar entradas malas de manera limpia.

Confundir el contrato con la implementación (conceptual). Qué pasa: alguien documenta el "contrato" de check-credit describiendo sus nodos internos —"primero consulta la hoja X, luego resta con un nodo Code, luego compara"—, y llama a eso el contrato. Cuando optimiza esos nodos por dentro, cree que cambió el contrato y entra en pánico buscando llamadores que actualizar. Por qué pasa: es natural describir algo por cómo funciona en vez de por qué promete; la firma es más abstracta que los engranajes. Cómo detectarlo: revisa tu contrato y tacha toda línea que hable de cómo trabaja el sub-workflow por dentro; lo que quede —qué entra y qué sale por la frontera— es el contrato de verdad. Cómo corregirlo: escribe el contrato solo en términos de la frontera: entrada, salida en éxito, salida en fallo, efectos. Nunca menciones un nodo interno. Si tu contrato nombra un nodo, estás documentando la cocina, no la ventanilla.

Tratar un campo opcional como si fuera obligatorio, o al revés (práctico). Qué pasa: el contrato marca currency como opcional con un valor por defecto, pero el sub-workflow por dentro asume que siempre llega y falla cuando no; o al revés, marca amount como opcional cuando en realidad no puede trabajar sin él. Por qué pasa: la obligatoriedad de un campo es fácil de decidir de memoria y fácil de equivocar; suena a detalle menor y no lo es. Cómo detectarlo: para cada campo de entrada marcado como opcional, verifica que el sub-workflow de verdad sepa qué hacer cuando ese campo no llega —que tenga un valor por defecto real, no un undefined que explota tres nodos después—. Para cada obligatorio, verifica que el sub-workflow realmente no pueda funcionar sin él. Cómo corregirlo: la marca obligatorio/opcional no es decorativa; es una promesa sobre el comportamiento. Un campo opcional obliga al sub-workflow a manejar su ausencia; un obligatorio le da derecho a rechazar la llamada si falta. La lección 3 trata los valores por defecto en detalle y la lección 5 el rechazo.

Ejercicios

Ejercicio 1 — Escribe el contrato de issue-refund. El sub-workflow issue-refund emite un reembolso sobre un pedido. Recibe el order_id del pedido, el amount a reembolsar y una reason en texto libre. Devuelve, si todo va bien, un refund_id (el identificador del reembolso creado) y un status. Si falla, devuelve la misma forma de error que check-credit. A diferencia de check-credit, issue-refund produce un efecto: mueve dinero. Escribe su contrato completo con las cuatro piezas.

Ver solución
CONTRATO — issue-refund

ENTRADA
  order_id : string   (obligatorio)  — el pedido a reembolsar
  amount   : number   (obligatorio)  — monto a reembolsar en la moneda del pedido
  reason   : string   (obligatorio)  — motivo del reembolso, texto libre

SALIDA EN ÉXITO
  refund_id : string   — identificador del reembolso creado
  status    : string   — estado del reembolso, p. ej. "completed" o "pending"
  order_id  : string   — se devuelve para que el llamador sepa a qué pedido corresponde

SALIDA EN FALLO
  error   : true
  code    : string    — "INVALID_INPUT", "ORDER_NOT_FOUND", "ALREADY_REFUNDED"
  message : string

EFECTOS
  SÍ produce efecto: emite un reembolso (mueve dinero).
  Llamarlo dos veces con el mismo order_id, sin protección, produce DOS reembolsos.
  Por eso su llamada debe ser idempotente (Módulo 2) y su entrada, validada (Lección 5).

Por qué funciona: el contrato tiene las cuatro piezas, pero lo que lo hace correcto es la pieza 4. Declarar que issue-refund mueve dinero y que no es seguro llamarlo dos veces no es un comentario opcional: es la información que le dice al llamador que esta llamada exige el cuidado de un efecto irreversible, y que combina el contrato de este módulo con la idempotencia del anterior. Un contrato que omitiera esa línea sería técnicamente completo en su forma y peligrosamente incompleto en su sustancia.

Ejercicio 2 — Cocina o ventanilla. Para cada uno de estos cambios sobre check-credit, decide si toca la implementación (la cocina, cambio seguro) o el contrato (la ventanilla, cambio que puede romper llamadores), y justifica en una frase:

(a) Cambiar el cálculo del crédito disponible de un nodo Code a tres nodos visuales que hacen lo mismo. (b) Renombrar el campo de salida available_credit a remaining_credit. (c) Cambiar la fuente del crédito de una hoja de Google Sheets a una base de datos Postgres, devolviendo los mismos campos. (d) Cambiar el tipo del campo de entrada amount de número a texto.

Ver solución

(a) Cocina (seguro). Cambió cómo se calcula por dentro, pero la firma —qué entra y qué sale— queda idéntica. Ningún llamador se entera. Puedes hacerlo sin avisar a nadie.

(b) Ventanilla (rompe). Un campo de salida cambió de nombre. Todo llamador que leía available_credit ahora lee undefined. Es exactamente la rotura silenciosa de la lección 1, y exige versionar (lección 6).

(c) Cocina (seguro). La fuente de datos es implementación pura; mientras la salida devuelva los mismos campos con los mismos tipos, la ventanilla no se movió. Puedes migrar de Sheets a Postgres sin tocar a un solo llamador.

(d) Ventanilla (rompe). El tipo de un campo de entrada es parte del contrato. Un llamador que mandaba amount como número lo seguirá mandando como número, y ahora el sub-workflow espera texto: la promesa de entrada cambió. Cambiar un tipo es uno de los cambios rompientes clásicos.

Por qué funciona: el criterio no es "qué tan grande suena el cambio". Migrar de Sheets a Postgres suena enorme y es seguro; renombrar un campo suena trivial y rompe todo. La única pregunta que importa es: ¿cambió algo que cruza la frontera —un nombre, un tipo, la obligatoriedad de un campo, la forma de la salida—? Si sí, es ventanilla. Si el cambio es solo interno, es cocina.

Ejercicio 3 — El contrato de dos partes. order-triage empezó a mandarle a check-credit el campo amount como el texto "1842.50" en vez del número 1842.50, por un bug en un nodo anterior. check-credit intentó comparar ese texto contra el crédito disponible y produjo un resultado sin sentido —aprobó un pedido que debía rechazar—. ¿Quién incumplió el contrato, el que llama o el que responde? ¿Y qué debería haber hecho el que responde para protegerse?

Ver solución

Incumplió el que llama, order-triage: el contrato decía que amount es un número, y mandó un texto. Esa es una violación de la promesa de entrada, no de la de salida.

Pero —y aquí está el punto— eso no exime a check-credit. Como el contrato tiene dos partes y el llamador puede incumplir la suya (por un bug, por un dato sucio), el que responde no debe confiar ciegamente en que la entrada llegó bien. check-credit debió verificar que amount era de verdad un número antes de usarlo, y si no lo era, rechazar la llamada devolviendo la forma de error del contrato (error: true, code: "INVALID_INPUT") en vez de intentar comparar un texto contra un número y producir un resultado absurdo.

Por qué funciona: este ejercicio adelanta el corazón de la lección 5. El contrato de dos partes significa que cada lado tiene su responsabilidad, pero como el llamador es falible, el que responde carga con el deber de verificar en la puerta. Confiar en que la entrada siempre cumple es una apuesta que un sub-workflow serio no hace: el resultado sin sentido que aprobó un pedido que debía rechazarse es exactamente lo que produce esa confianza ciega. Rechazar temprano habría convertido un error silencioso en un error visible y contenido.

Resumen y siguiente paso

En esta lección le pusiste anatomía precisa a la promesa que la lección 1 dejó como intuición. Definiste un contrato de workflow como la firma de una máquina expendedora: qué monedas recibe y qué producto entrega, sin decir nada de sus engranajes. Viste sus cuatro piezas —los campos de entrada con nombre, tipo y obligatoriedad; la forma de la salida en éxito; la forma de la salida en fallo, que es la que más se olvida; y los efectos que el sub-workflow produce en el mundo— y las escribiste enteras para check-credit. Separaste el contrato de la implementación con la regla que sostiene todo el módulo: la cocina se cambia libremente, la ventanilla es sagrada. Entendiste que el contrato tiene dos partes con deberes propios —el que responde promete su salida, el que llama promete su entrada—, y que como el llamador es falible, el que responde tiene el deber de verificar en la puerta. Y viste por qué escribir el contrato, aunque el sistema ya funcione, es lo que le permite sobrevivir al cambio: vuelve consciente cada modificación, deja que cualquiera use el sub-workflow sin ingeniería inversa, y es el cimiento sobre el que se paran la validación y el versionado.

Antes de avanzar a la lección 3 deberías poder: nombrar las cuatro piezas de un contrato de memoria; escribir el contrato de un sub-workflow que te den; y decidir, ante un cambio, si toca la cocina o la ventanilla.

Lo que tienes hasta aquí es el contrato en un lenguaje humano —una nota que cualquiera lee—. Eso es suficiente para razonar, pero todavía no es algo que n8n pueda usar. La lección 3 convierte esta anatomía en un esquema concreto: cómo expresas los nombres, los tipos, los obligatorios, los opcionales y sus valores por defecto de una forma que sirva tanto para documentar al lado del workflow como para que n8n, más adelante, la reconozca en la frontera. Pasamos de "qué es un contrato" a "cómo se escribe uno que la máquina también entienda".

Recursos