Módulo 2: Idempotencia: que repetir no duplique

2. Qué es la idempotencia, con manzanas

Descripción

Al terminar esta lección vas a poder definir la idempotencia sin titubear, reconocer a simple vista si una operación es idempotente o no, y explicar por qué esa distinción es la que decide cuáles pasos de tu workflow son peligrosos al repetirse. Vas a tener dos analogías que no se te van a olvidar —el botón del piso de un ascensor y el interruptor de la luz— y vas a entender por qué la mitad de los métodos de HTTP que ya usas todos los días te estaban regalando idempotencia sin que lo supieras, mientras que la otra mitad es exactamente donde nace el duplicado.

Esto importa porque la idempotencia no es una técnica que aplicas al final, como una capa de pintura. Es una propiedad que una operación tiene o no tiene, y saber leerla te cambia la forma de mirar un workflow. Cuando puedes recorrer los nodos de order-triage y decir "este es idempotente por naturaleza, este no lo es y hay que protegerlo, este da igual", ya tienes la mitad del trabajo hecho. El resto del módulo son las herramientas para proteger los que no lo son; pero primero hay que saber cuáles son.

Conexión con el módulo: la lección 1 te dio la definición en una frase y el mapa. Esta la desarma con calma y te entrena el ojo para clasificar operaciones. Es la base de todo lo que sigue: la lección 3 elige la clave que va a hacer idempotente a un efecto, pero para eso primero tienes que saber qué es un efecto y por qué no lo es ya. Las lecciones 4 y 5 son las dos formas de convertir un efecto no idempotente en uno idempotente —el upsert y la cabecera—; ninguna tiene sentido si no distingues antes las dos categorías. Guárdate la heurística central de esta lección, la de "fijar un valor" contra "modificar lo que había", porque la vas a usar en las seis lecciones restantes.

La definición, y por qué la palabra suena rara

Empecemos por lo básico, incluida la palabra, que asusta más de lo que debería.

Idempotencia viene del latín: idem ("el mismo") y potentia (aquí, en el sentido de "elevado a una potencia"). La idea original es matemática: una operación es idempotente si aplicarla varias veces da el mismo resultado que aplicarla una vez. El ejemplo clásico es multiplicar por 1. Toma un número, multiplícalo por 1, y tienes el mismo número. Vuelve a multiplicarlo por 1: sigue siendo el mismo. Puedes multiplicar por 1 mil veces y nunca cambia nada después de la primera —y en realidad, ni siquiera después de la primera—. Comparemos con multiplicar por 2: cada aplicación duplica, así que dos aplicaciones no dan lo mismo que una. Multiplicar por 1 es idempotente; multiplicar por 2 no.

Nosotros no vamos a hacer matemáticas. Nos quedamos con la definición traducida al mundo de los sistemas, que es la que gobierna este módulo:

Una operación es idempotente si ejecutarla muchas veces deja el sistema en el mismo estado que ejecutarla una sola vez.

Fíjate en la palabra estado, porque es la clave. La idempotencia no habla de si la operación "hace algo" cada vez —de hecho puede hacer trabajo cada vez—; habla de en qué queda el mundo después. Si después de una ejecución el mundo quedó de cierta forma, y después de cinco ejecuciones el mundo quedó exactamente de esa misma forma, la operación es idempotente. No importa que las cinco ejecuciones hayan gastado electricidad, tiempo de CPU o cinco toques de tu dedo. Importa el estado final.

El interruptor de la luz y el botón del ascensor

Vamos a dos objetos que tienes en tu casa, porque juntos capturan casi todo lo que necesitas saber.

El interruptor de la luz es el ejemplo perfecto de idempotencia. No hablo del interruptor de subir/bajar, sino de uno que tuviera dos botones separados: uno que dice "encender" y otro que dice "apagar". Aprieta "apagar". La luz se apaga. Aprieta "apagar" otra vez. La luz... sigue apagada. Y otra vez. Y diez veces más. El estado del sistema —"la luz está apagada"— es idéntico después de un toque o de diez. El botón "apagar" fija el estado a un valor: apagada. Fijar un estado a un valor absoluto es la forma más pura de idempotencia, porque el valor no depende de lo que había antes. "Que la luz quede apagada" da lo mismo si estaba encendida, apagada, o si ya la apagaste tres veces.

El botón del piso en un ascensor es la misma idea, y es la analogía que vamos a repetir todo el módulo porque describe exactamente lo que le va a pasar a la segunda llegada del webhook de Cumbre. Entras al ascensor, quieres el piso 5, aprietas el "5". Se enciende. Impaciente, lo aprietas cuatro veces más. ¿Vas al piso 25? ¿Se llaman cinco ascensores? No: el botón registra "destino = piso 5" y los toques siguientes no cambian nada, porque el destino ya es 5 y ponerlo en 5 otra vez lo deja en 5. El botón fija un estado —el destino— a un valor absoluto. Es idempotente.

Ahora el contraejemplo, para que la distinción quede grabada. El botón de "agregar al carrito" en una tienda mal programada. Le das clic y agrega una unidad. Le das clic otra vez y agrega otra. Cinco clics, cinco unidades. Este botón no fija el estado a un valor; lo modifica en relación con lo que había —"toma lo que hay y súmale uno"—. Y ahí está la raíz de toda no-idempotencia: cuando una operación depende de lo que había antes para decidir el resultado, repetirla acumula.

Guárdate esta heurística, porque es la más útil de la lección y la vas a aplicar decenas de veces:

Fijar un estado a un valor absoluto ("que quede en 5", "que quede apagada", "que el pedido ORD-2041 exista con estos datos") es idempotente. Modificar el estado en relación con lo que había ("súmale uno", "agrega una fila", "crea un cobro nuevo") no lo es.

La palabra que delata a la operación no idempotente casi siempre es un verbo de creación o de incremento: agregar, crear, insertar, sumar, incrementar, enviar (otro). La palabra que delata a la idempotente es un verbo de asignación: poner en, fijar, establecer, dejar en, que sea.

Ejemplo trabajado: clasifica los nodos de order-triage

Vamos a recorrer el workflow de Cumbre nodo por nodo y clasificar cada operación. Este es el ejercicio mental que quiero que hagas automáticamente al mirar cualquier workflow.

Recuerda el flujo:

Webhook  ──►  AI Agent  ──►  HTTP Request (POST /charges + correo + escritura al CRM)

El Webhook. Su operación es "recibir un pedido y ponerlo a disposición del workflow". ¿Fija un estado o lo modifica acumulando? Recibir un dato no cambia nada permanente en el mundo de afuera: es puro dato que entra. Recibir el mismo pedido dos veces no deja dos rastros dañinos por sí mismo. Idempotente, o mejor dicho, inofensivo. No hay que protegerlo.

El AI Agent. Su operación es "leer el pedido y producir una clasificación": prioridad, canal, urgencia. Esto es una lectura razonada —consume el pedido y devuelve una decisión, pero no cambia nada afuera—. Clasificar ORD-2041 dos veces produce dos decisiones, y aquí hay un matiz honesto que la lección 7 va a desarrollar: un modelo de lenguaje puede dar clasificaciones ligeramente distintas en dos corridas, porque no es perfectamente determinista. Pero incluso si difieren, la clasificación en sí no cobra ni manda correos; es un cálculo. Mientras el agente solo clasifique y no actúe, repetirlo desperdicia cómputo pero no deja daño permanente. Esencialmente idempotente, con la advertencia del no-determinismo que veremos en su momento.

El HTTP Request. Aquí está el problema, y son en realidad tres efectos escondidos en un nodo:

  • Crea un cobro (POST /charges). Cada corrida crea un cobro nuevo, con un identificador nuevo. Es "agregar al carrito": modifica el estado sumando. No idempotente.
  • Manda un correo de confirmación. Cada corrida manda un correo nuevo. Sumar un correo. No idempotente.
  • Escribe el registro del pedido en el CRM. Si lo hace con un "insertar fila nueva", cada corrida agrega una fila. No idempotente tal como está —y la lección 4 lo va a arreglar convirtiéndolo en un upsert—.

Qué esperar de este análisis. Después de clasificar, tu mapa de order-triage se ve así: dos nodos verdes (webhook, agente) que puedes repetir sin miedo, y un nodo rojo (el HTTP Request) con tres efectos que hay que proteger. Ese mapa es el plan de trabajo de todo el módulo. No vas a tocar los verdes. Vas a envolver los rojos con una clave (lección 3), un upsert para el CRM (lección 4) y una cabecera para el cobro (lección 5).

Fíjate en lo que acabas de hacer: no memorizaste una regla, aplicaste la heurística. "¿Esta operación fija un valor o suma sobre lo que había?" Crear un cobro suma. Recibir un webhook no cambia nada. Escribir "que el pedido ORD-2041 exista con estos datos" fijaría un valor —y por eso el upsert, que hace exactamente eso, va a salvarnos—.

Los métodos de HTTP: idempotencia que ya usabas sin saberlo

Si vienes de la guía de APIs del ecosistema, ya llamaste APIs con distintos "verbos" de HTTP —GET, POST, PUT, DELETE—. Resulta que esos verbos vienen con una promesa de idempotencia escrita en el estándar mismo de HTTP, y entenderla te da un vocabulario preciso para el resto del módulo.

El estándar de HTTP clasifica los métodos en dos propiedades que conviene no confundir: seguros (no cambian nada en el servidor) e idempotentes (repetirlos deja el mismo estado). Todo método seguro es idempotente, pero no al revés.

Método¿Seguro?¿Idempotente?Qué significa en la práctica
GETSolo lee. Pídelo mil veces: no cambia nada. La lectura pura del módulo 1.
HEADComo GET pero solo trae encabezados. Igual de inofensivo.
PUTNo"Pon este recurso en este estado exacto." Fija un valor.
DELETENo"Que este recurso deje de existir." Fija un valor (el de no-existir).
POSTNoNo"Crea algo nuevo." Cada llamada crea otra cosa. Aquí nace el duplicado.
PATCHNoGeneralmente no"Modifica parcialmente." Depende de cómo esté escrito; a menudo suma.

Detente en las filas que importan.

GET es la lectura pura. Es el nodo verde por excelencia. Consultar el estado de un pedido, leer una fila del CRM, pedir la lista de productos: repetir un GET no tiene consecuencias, y por eso el módulo 1 lo llamó una lectura y lo declaró seguro de repetir. Casi todo lo que un AI Agent hace para "informarse" antes de decidir son GET.

PUT es idempotente aunque cambie cosas. Esto sorprende a mucha gente. PUT modifica el servidor —no es seguro— pero es idempotente. La razón es exactamente la heurística del interruptor: PUT significa "pon el recurso /orders/ORD-2041 en este estado completo". Es fijar un valor absoluto. Hazlo una vez o diez: el recurso queda en ese estado, siempre el mismo. Por eso, cuando puedas elegir entre expresar un efecto como POST (crear) o como PUT (poner en un estado), el PUT te regala idempotencia. Esa idea es la semilla del upsert de la lección 4.

DELETE es idempotente, y aquí hay una sutileza que confunde. Borrar el pedido ORD-2041 una vez lo deja borrado. Borrarlo otra vez... lo deja borrado igual. El estado —"el pedido ya no existe"— es idéntico las dos veces. Por eso DELETE es idempotente. Ahora, cuidado: la respuesta puede cambiar. La primera vez el servidor quizás responde 200 OK ("lo borré"), y la segunda 404 Not Found ("no existe ese pedido"). Alguien mira eso y dice "¡las respuestas son distintas, entonces no es idempotente!". Error. La idempotencia se mide por el estado del sistema, no por el código de respuesta. El estado quedó igual las dos veces; que la segunda respuesta sea un 404 es solo el servidor diciéndote "ya estaba hecho". Esta distinción —estado vs respuesta— vale oro, y vuelve en la lección 5.

POST no es idempotente, y por eso es el sospechoso de siempre. POST significa "crea un recurso nuevo". Por definición, cada llamada produce uno nuevo. El POST /charges de Cumbre crea un cobro nuevo cada vez; ahí está el segundo cargo. Cuando veas un POST que produce un efecto, enciende la alarma: es candidato número uno a duplicar. La lección 5 se dedica precisamente a domesticar un POST peligroso con una cabecera de idempotencia.

La conclusión que te llevas: cuando diseñes un efecto, pregúntate si puedes expresarlo como un PUT (fijar un estado) en vez de un POST (crear). Muchas veces sí, y esa sola decisión te da idempotencia gratis. Cuando no puedas —cuando la API solo ofrezca POST— necesitas las herramientas del módulo. Y una advertencia sobre PATCH, que aparece en la tabla como "generalmente no": su idempotencia depende de cómo esté escrito. Un PATCH que dice "pon el status en shipped" fija un valor y es idempotente; uno que dice "súmale 100 al saldo" acumula y no lo es. No confíes en el verbo PATCH por sí solo; mira qué hace la operación por dentro, con la misma heurística de fijar-vs-acumular.

Ejemplo trabajado: estado vs respuesta, con un DELETE de verdad

La distinción entre estado y respuesta suena a filosofía hasta que la ves en peticiones concretas. Hagámosla concreta. Imagina que Cumbre tiene una API interna para sus pedidos y quieres cancelar ORD-2041 borrándolo. Vas a hacer la misma petición dos veces, como si un reintento la disparara doble.

Primera petición:

DELETE /orders/ORD-2041

Qué esperar en la respuesta:

HTTP/1.1 200 OK
{ "deleted": "ORD-2041" }

El servidor borró el pedido. En este momento, el estado del servidor es: ORD-2041 ya no existe.

Segunda petición (idéntica, disparada por el reintento):

DELETE /orders/ORD-2041

Qué esperar en la respuesta:

HTTP/1.1 404 Not Found
{ "error": "order ORD-2041 does not exist" }

Aquí es donde mucha gente se equivoca. La respuesta cambió: 200 la primera vez, 404 la segunda. La tentación es concluir "¡no es idempotente, las respuestas son distintas!".

Pero mira el estado del servidor después de cada petición:

RespuestaEstado del servidor después
1.ª petición200 OKORD-2041 no existe
2.ª petición404 Not FoundORD-2041 no existe

El estado es idéntico las dos veces: el pedido no existe. Eso es lo que define la idempotencia, y por eso DELETE es idempotente. La diferencia en la respuesta es solo el servidor informándote cuál de las dos peticiones hizo el trabajo real: la primera hizo el borrado, la segunda encontró que ya estaba hecho. Ninguna de las dos dejó al pedido en un estado distinto del que buscabas.

Guarda esta imagen, porque en la lección 5 vas a ver el mismo fenómeno con una pasarela de pago: la primera llamada con una Idempotency-Key crea el cobro y responde "creado"; la segunda, con la misma clave, responde "ya lo tenía" y te devuelve el mismo cobro sin crear otro. Respuestas que se sienten distintas, estado que es el mismo. Aprende a mirar el estado.

Operaciones que ya son idempotentes por naturaleza (y no tienes que hacer nada)

Parte de la madurez con este tema es saber cuándo no hacer nada. No todo efecto necesita protección; algunos ya nacen idempotentes, y envolverlos en maquinaria de idempotencia es trabajo desperdiciado y código más frágil.

Estas operaciones ya son idempotentes tal como están:

Fijar un campo a un valor fijo. "Marca el pedido ORD-2041 como status: shipped." Ejecútalo una vez o diez: el estado queda shipped. No hay que protegerlo. Es un PUT disfrazado.

Borrar por identificador. "Elimina el archivo temporal tmp-2041." Si ya no existe, borrarlo otra vez no hace daño. Idempotente.

Escribir un archivo completo con el mismo contenido. "Guarda este reporte en report-2041.pdf." Sobrescribir con el mismo contenido deja el mismo archivo. Idempotente (a diferencia de agregar una línea a un archivo, que sí acumula).

Asignar a alguien a un rol que ya tiene. "Que CUST-118 sea cliente mayorista." Si ya lo es, asignarlo otra vez no crea un segundo cliente mayorista. Idempotente.

Y estas no lo son, por más inofensivas que parezcan:

Incrementar un contador. "Suma uno a las visitas de este pedido." Clásico no idempotente. Cada corrida suma.

Agregar a una lista. "Añade CUST-118 a la lista de notificados." Si la lista permite repetidos, agregas dos veces. (Si la lista fuera un conjunto que ignora repetidos, sería idempotente —y ese es justo el truco del upsert—).

Enviar un mensaje. Correo, WhatsApp, notificación: cada envío es un mensaje más en la bandeja de alguien. No idempotente, y particularmente molesto porque el daño lo ve un humano.

Crear un recurso sin identificador propio. "Crea un cobro." Sin una clave que diga "este cobro y el anterior son el mismo", cada creación es nueva.

El patrón, otra vez, es el mismo: mira si la operación fija algo o lo acumula. Si fija, respira: ya es idempotente. Si acumula, es un cliente del resto de este módulo.

Hay un caso que vale la pena destacar porque es el puente exacto a la lección 4: "agregar a un conjunto". Un conjunto, en el sentido matemático, es una colección que no admite repetidos. Si tienes un conjunto de clientes notificados y "agregas" CUST-118 cuando ya estaba, el conjunto no cambia —sigue teniendo un solo CUST-118—. Agregar a un conjunto es idempotente, precisamente porque el conjunto ignora el segundo intento. Compáralo con agregar a una lista, que sí admite repetidos y por eso acumula. Esta diferencia —conjunto vs lista— es la esencia del upsert: un upsert convierte tu tabla de la base de datos en un "conjunto por clave", donde intentar insertar dos veces el mismo order_id no crea una segunda fila. Guarda la imagen; en dos lecciones la vas a construir.

Y una nota de humildad práctica: n8n trae algunas ayudas de deduplicación de fábrica —un nodo para remover duplicados, opciones en ciertos triggers para ignorar items ya vistos—. Son útiles y las vas a conocer, pero pertenecen al módulo 4, donde se estudia dónde vive el estado que recuerda "esto ya lo vi". En este módulo construimos la idempotencia con las herramientas más fundamentales —la clave, el upsert, la cabecera— para que entiendas el mecanismo antes de usar el atajo. Un atajo que no entiendes es un atajo que no puedes depurar cuando falla.

Errores comunes

Creer que "idempotente" significa "no hace nada la segunda vez" (conceptual). Qué pasa: alguien entiende que una operación idempotente "se salta" las ejecuciones repetidas, y espera que el nodo no corra o quede en gris la segunda vez. Por qué pasa: la intuición de "no duplicar" se traduce a "no ejecutar", pero no es lo mismo. Una operación idempotente sí puede correr completa cada vez —el botón del ascensor registra tu toque las cinco veces—; lo que garantiza es que el resultado no cambia. Cómo detectarlo: si esperas ver que el segundo HTTP Request "no se dispare" y te alarmas cuando sí se dispara, estás confundiendo idempotencia con no-ejecución. Cómo corregirlo: separa las dos ideas. La operación corre; el efecto no se acumula. El PUT a status: shipped se ejecuta las diez veces y hace su trabajo las diez; simplemente el estado final es el mismo. Es más robusto así, porque no dependes de un mecanismo que "salte" ejecuciones.

Confundir la respuesta con el estado (conceptual). Qué pasa: se prueba un DELETE repetido, la segunda vez devuelve 404, y se concluye que "no es idempotente porque la respuesta cambió". O al revés: una API devuelve 200 OK las dos veces y se concluye que "todo bien, es idempotente", cuando en realidad creó dos recursos. Por qué pasa: es natural juzgar por lo que te devuelve la API, que es lo que ves. Pero la idempotencia se mide por el estado del servidor después, no por el código de respuesta. Cómo detectarlo: pregúntate siempre "¿cuántos recursos hay ahora?", no "¿qué me respondió?". Un 200 no prueba que no duplicaste, y un 404 no prueba que sí. Cómo corregirlo: para verificar idempotencia, ve al estado —cuenta las filas, cuenta los cobros—, no a la respuesta. El DELETE que responde 404 la segunda vez es perfectamente idempotente porque el pedido sigue sin existir, que es lo único que importa.

Tratar todo efecto como peligroso y blindar de más (conceptual). Qué pasa: después de aprender el tema, alguien envuelve cada nodo en claves de idempotencia, upserts y verificaciones, incluso los que ya eran idempotentes por naturaleza. El workflow se llena de maquinaria innecesaria, más difícil de leer y de mantener. Por qué pasa: el miedo al duplicado, recién descubierto, se vuelve exceso de celo. Cómo detectarlo: si tienes un upsert protegiendo un nodo que solo hace PUT status: shipped, o una clave de idempotencia envolviendo un GET, estás blindando algo que ya estaba blindado de fábrica. Cómo corregirlo: clasifica primero (la heurística de esta lección), protege solo los que acumulan. Un PUT a un valor fijo, un DELETE por id, una escritura de archivo completo: déjalos en paz. La elegancia de un sistema confiable no es proteger todo, es proteger exactamente lo que lo necesita.

Ejercicios

Ejercicio 1 — Clasifica seis operaciones. Para cada una, di si es idempotente o no, y en una frase por qué (usa la heurística "fija un valor" vs "acumula sobre lo que había"):

(a) UPDATE orders SET status = 'shipped' WHERE order_id = 'ORD-2041' (b) INSERT INTO order_log (order_id, event) VALUES ('ORD-2041', 'received') (c) UPDATE inventory SET stock = stock - 1 WHERE sku = 'CF-ARA-500' (d) Un GET a /orders/ORD-2041 para leer su estado actual. (e) Enviar el correo de confirmación de ORD-2041 al cliente. (f) DELETE FROM cart_items WHERE cart_id = 'CART-9'

Ver solución

(a) Idempotente. Fija status al valor 'shipped'. Córrelo diez veces: el estado queda shipped. No depende de lo que había antes. Es un PUT en lenguaje SQL.

(b) No idempotente. INSERT crea una fila nueva cada vez. Diez ejecuciones, diez filas en order_log. Acumula. (Este es el patrón exacto que la lección 4 arregla con un upsert.)

(c) No idempotente. stock = stock - 1 modifica el estado en relación con lo que había —le resta uno a lo que sea que haya—. Diez ejecuciones restan diez. Es el "agregar al carrito" al revés: decrementa.

(d) Idempotente (y además seguro). Un GET solo lee. Repetirlo no cambia nada. Es la lectura pura.

(e) No idempotente. Cada envío es un correo más en la bandeja del cliente. Acumula, y encima el daño lo ve un humano. Necesita protección.

(f) Idempotente. Fija el estado a "no hay items en el carrito CART-9". Si ya está vacío, borrar otra vez lo deja vacío. El estado final es el mismo. (La respuesta puede decir "borré 0 filas" la segunda vez, pero el estado no cambió: recuerda estado vs respuesta.)

Por qué funciona: las seis se resuelven con la misma pregunta, no con memoria. SET status = 'valor' fija; INSERT y stock - 1 acumulan; GET solo lee; enviar un correo acumula; DELETE fija (a vacío). Esa pregunta —¿fija o acumula?— es la que quiero que hagas en automático.

Ejercicio 2 — El truco del POST a PUT. Cumbre necesita marcar en su CRM que el pedido ORD-2041 ya fue procesado. Un desarrollador propone dos diseños. Di cuál es idempotente y por qué, y qué problema tendría el otro si el webhook llega dos veces:

  • Diseño A: POST /processed_orders con cuerpo { "order_id": "ORD-2041", "processed_at": "..." } — crea un registro de "pedido procesado".
  • Diseño B: PUT /orders/ORD-2041 con cuerpo { "processed": true } — pone el pedido en estado "procesado".
Ver solución

El Diseño B es idempotente; el A no.

El Diseño B usa PUT sobre un recurso identificado (/orders/ORD-2041) para fijarlo a un estado: processed: true. Si el webhook llega dos veces, la segunda ejecución vuelve a poner processed: true sobre un pedido que ya está en processed: true. El estado no cambia. Un solo pedido, marcado como procesado. Perfecto.

El Diseño A usa POST para crear un registro nuevo cada vez. Si el webhook llega dos veces, se crean dos registros en /processed_orders, ambos con order_id: ORD-2041. Ahora la tabla de "pedidos procesados" tiene el mismo pedido dos veces, y cualquier reporte que cuente "cuántos pedidos procesamos" da un número inflado. Además, el processed_at sería distinto en cada uno, sembrando confusión sobre cuándo "de verdad" se procesó.

El truco general: cuando puedas expresar un efecto como "pon este recurso en este estado" (un PUT sobre un id conocido) en vez de "crea un registro del hecho" (un POST), elige lo primero. Fijar un valor sobre un identificador estable es idempotencia de regalo, y el order_id que ya viene en el pedido es justo ese identificador estable —lo cual nos lleva de la mano a la lección 3—.

Por qué funciona: es la heurística fija-vs-acumula aplicada a una decisión de diseño real. El Diseño A "acumula registros del hecho"; el B "fija el estado del recurso". Mismo objetivo de negocio, idempotencia opuesta.

Ejercicio 3 — Audita order-triage completo. Sin volver a mirar el ejemplo trabajado, escribe para cada nodo de Webhook → AI Agent → HTTP Request si es idempotente o no, y para el que no lo sea, lista los efectos concretos que habría que proteger. Después compara con el ejemplo trabajado.

Ver solución

Webhook: idempotente (inofensivo). Recibir el mismo pedido dos veces es solo dato que entra; no deja rastro dañino por sí mismo. Nada que proteger.

AI Agent: esencialmente idempotente. Clasificar es una lectura razonada; produce una decisión pero no cambia nada afuera. La advertencia —que un modelo puede clasificar ligeramente distinto en dos corridas por no ser determinista— es real pero no es un efecto duplicado; es un tema de la lección 7. Nada que cobrar ni enviar.

HTTP Request: no idempotente. Esconde tres efectos que acumulan:

  1. Crear el cobro (POST /charges) — cada corrida, un cobro más. → lo protege la lección 5 (cabecera de idempotencia).
  2. Enviar el correo de confirmación — cada corrida, un correo más. → mismo mecanismo o una guarda antes de enviar.
  3. Escribir el registro en el CRM con un INSERT — cada corrida, una fila más. → lo protege la lección 4 (upsert por order_id).

Por qué funciona: reconstruiste el plan de trabajo del módulo entero. Dos nodos que no se tocan, un nodo con tres efectos y una herramienta asignada a cada uno. Si pudiste nombrar los tres efectos del HTTP Request, ya tienes claro qué hay que arreglar; el resto del módulo es cómo.

Resumen y siguiente paso

En esta lección desarmaste la definición: una operación es idempotente si ejecutarla muchas veces deja el sistema en el mismo estado que ejecutarla una vez —y la palabra clave es estado, no respuesta ni cantidad de trabajo—. La aterrizaste con el interruptor de la luz y el botón del piso del ascensor, que fijan un valor y por eso son idempotentes, frente al botón de "agregar al carrito", que acumula y por eso no lo es. Te quedaste con la heurística que gobierna todo el módulo: fijar un estado a un valor absoluto es idempotente; modificarlo en relación con lo que había, no. Recorriste order-triage clasificando cada nodo —webhook y agente, verdes; HTTP Request, rojo con tres efectos—. Y le pusiste vocabulario preciso con los métodos de HTTP: GET puro lee, PUT y DELETE fijan un valor y son idempotentes aunque cambien cosas, POST crea y es el sospechoso de siempre; con la sutileza de que la idempotencia se mide por el estado del servidor, no por el código de respuesta que te devuelve.

Antes de avanzar a la lección 3 deberías poder: clasificar cualquier operación como idempotente o no con la heurística fija-vs-acumula; explicar por qué PUT es idempotente y POST no; y decir por qué un DELETE que responde 404 la segunda vez sigue siendo idempotente.

Ya sabes reconocer un efecto que no es idempotente. La lección 3 empieza a repararlo, y lo primero que necesita cualquier reparación es un nombre: una clave de idempotencia que diga "este evento y aquel son el mismo". Vas a ver la decisión más importante y más traicionera del módulo —usar una clave que ya viene en el pedido (natural) o fabricar una tú (sintética)— y el error que arruina todo el mecanismo: elegir una clave que cambia en cada ejecución, como la hora de llegada, y con ello volver "nuevo" a cada reintento.

Recursos