CodeGym /Cursos /ChatGPT Apps /Tolerancia a fallos: rollback de pasos, reintentos y cont...

Tolerancia a fallos: rollback de pasos, reintentos y control de errores

ChatGPT Apps
Nivel 11 , Lección 3
Disponible

1. Por qué en ChatGPT App los errores son la norma y no una emergencia

En la lección anterior hablamos de cómo dividir la tarea en pasos y construir un workflow de varios pasos en ChatGPT App. Ahora añadimos a este esquema la realidad honesta: errores, timeouts e interrupciones por parte del usuario.

En la web clásica, la lógica suele construirse alrededor del «camino feliz», y los errores se perciben como algo raro y excepcional — una página de error 500 en rojo, etc. En ChatGPT App el panorama es distinto: trabajas en un sistema distribuido con una LLM, APIs externas, MCP, un widget y, además, con un usuario que puede cerrar la pestaña en cualquier momento. Los errores e interrupciones son el pan de cada día.

Hay varias particularidades que complican la vida:

  • En primer lugar, una LLM es no determinista. Incluso con el mismo prompt puede tomar una decisión ligeramente distinta: invocar otra herramienta, cambiar parámetros o incluso decidir que mejor «preguntar de nuevo».
  • En segundo lugar, limitaciones de red e infraestructura. Un tool‑call desde ChatGPT tiene timeouts (normalmente decenas de segundos), al igual que tu backend de Next.js/Vercel. Si una API externa se ralentiza, todo puede cortarse a mitad.
  • En tercer lugar, está el factor UX: el usuario se distrajo, cerró el chat y regresó al día siguiente, y tú no puedes mantener una transacción abierta en la base de datos todo ese tiempo.

De aquí el mensaje principal de la lección:

Workflow tolerante a fallos = un guion en el que asumimos de antemano que cualquier paso puede caerse y definimos explícitamente qué ocurre entonces.

El error no es solo un motivo para mostrar un mensaje al usuario, sino también una señal para el modelo, que puede cambiar de estrategia, proponer un rollback, probar otra herramienta o cerrar el escenario con cuidado.

2. El paisaje de errores en el workflow: cuáles existen

Para manejar bien las caídas, primero hay que saber distinguirlas. En una aplicación LLM basada en ChatGPT Apps suelen darse varias clases de errores.

Errores técnicos. Es todo lo clásico de los sistemas distribuidos: timeouts de red, 5xx de tus APIs o externas, caídas del servidor MCP, un bug en el código del handler de la herramienta. Por ejemplo, en GiftGenius tu MCP‑tool search_products accede al catálogo y este responde 503 Service Unavailable. Es candidato para reintento automático (retry).

Errores lógicos (del modelo). Incluyen rechazos del modelo (decide que la solicitud viola la política), alucinaciones o, por ejemplo, un JSON roto al responder la herramienta. El modelo pudo generar argumentos incorrectos para un tool‑call y tu validación JSON no los aceptó. Esto suele ser un error de datos de entrada, no de infraestructura.

Errores de negocio. Tratan del sentido: el producto se agotó, el presupuesto del usuario es demasiado bajo para los filtros elegidos, el código promocional no es válido, la reserva ya caducó. En GiftGenius es la situación «de 500 candidatos ninguno encaja con las restricciones dadas». Aquí el retry rara vez ayuda: hay que cambiar parámetros o explicar al usuario que la restricción no es realista.

Interrupciones de UX. El propio usuario rompe el guion: cierra ChatGPT, pulsa «Atrás» en el widget, cancela la acción, cambia la respuesta en el paso anterior. También hay que considerarlo flujo normal, no error. Es importante saber restaurar y hacer rollback del estado en estos casos, de lo que hablaremos más adelante.

Un caso problemático aparte, en la intersección entre errores lógicos y técnicos, son los bucles infinitos del agente: el modelo recibe un error, piensa «hmm, lo intentaré otra vez», otra vez error, y así hasta que se acabe el contexto o el presupuesto. Protegerse de este comportamiento es una parte importante del diseño de errores.

3. Estrategias básicas: retry, fail‑fast, rollback e implicación del usuario

Cualquier error puede verse como un punto de ramificación: o intentamos repetir el paso, o hacemos rollback, o involucramos al usuario. Y, lo que es importante, estas estrategias se combinan.

Para fallos técnicos y temporales (la red parpadeó, la API devolvió 503) es lógico hacer retry limitado con backoff. Para errores lógicos y de negocio («el validador no aceptó el presupuesto», «los productos se han agotado»), repetir es inútil; hay que hacer fail‑fast y pedir al usuario que cambie la entrada o los parámetros.

Para operaciones que ya han cambiado algo en el mundo externo (crear un pedido, hacer una reserva), se necesita rollback — ya sea como «paso atrás» en la UI/contexto, o como acciones compensatorias reales (cancelar el pedido, devolver el dinero).

Por último, hay decisiones que por definición requieren al usuario: por ejemplo, si el sistema de pagos rechaza por «tarjeta denegada por el banco», no puedes arreglarlo automáticamente. El modelo debe explicar correctamente qué ha pasado y proponer opciones: probar otra tarjeta, reducir el importe o desistir de la compra.

Para un workflow robusto es muy útil, para cada paso, listar qué tipos de errores son posibles y qué haces en cada caso — auto‑retry, rollback, solicitar al usuario o simplemente registrar y terminar la rama.

4. Reintentos (retry) y backoff: cuándo y cómo hacerlos

Empecemos por la reacción más natural del desarrollador: «Bueno, probemos una vez más». La idea es correcta, pero como siempre, el diablo está en los detalles.

Qué errores se pueden reintentar

Una buena heurística de la práctica de integraciones dice: los errores de red y 5xx se pueden intentar de nuevo con una pausa, y los 4xx, probablemente no.

Es decir, si recibes 503, 504 o simplemente no llegó respuesta de una API externa, tiene sentido repetir la solicitud con un breve retraso. Si el servidor devolvió 400 Bad Request o 422 Unprocessable Entity, lo más probable es que el problema sea de datos, y repetir con los mismos parámetros no cambiará nada.

Utilidad sencilla callWithRetry en TypeScript

Escribamos una pequeña utilidad para la capa MCP o backend que podamos usar en las herramientas:

type RetryOptions = {
  maxRetries: number;
  baseDelayMs: number;
};

async function callWithRetry<T>(
  fn: () => Promise<T>,
  { maxRetries, baseDelayMs }: RetryOptions
): Promise<T> {
  let attempt = 0;

  // no necesitamos bucles infinitos
  while (true) {
    try {
      return await fn();
    } catch (err: any) {
      attempt++;
      const status = err?.status ?? err?.response?.status;

      // no reintentamos 4xx
      const isClientError = typeof status === "number" && status >= 400 && status < 500;
      if (attempt > maxRetries || isClientError) {
        throw err;
      }

      const delay = Math.min(baseDelayMs * 2 ** (attempt - 1), 10_000);
      // pequeña pausa para no golpear a la API en estampida
      const jitter = Math.random() * 200;

      await new Promise((r) => setTimeout(r, delay + jitter));
    }
  }
}

Esta función:

  • repite la llamada a fn un número limitado de veces;
  • usa backoff exponencial con un pequeño ruido aleatorio (jitter) para evitar el «efecto estampida» ante reintentos simultáneos;
  • detiene los reintentos en 4xx.

Es útil usarla, por ejemplo, dentro de un instrumento MCP que consulta el catálogo de productos o una API interna de recomendaciones.

Dónde exactamente hacer retry

Un error común es intentar repetir todas las solicitudes en cascada, incluso en niveles que no controlas. En el ecosistema de ChatGPT tienes varios lugares para reintentos:

  • dentro de tu propio backend/MCP (como hicimos con callWithRetry);
  • dentro de un worker/cola en segundo plano (en módulos futuros hablaremos más de colas de jobs y DLQ);
  • a veces, en el propio widget, cuando se trata de una petición ligera «actualizar la lista» sin efectos secundarios.

Es importante no duplicar la lógica: si tu worker ya hace tres reintentos con backoff, no tiene sentido encima colgar otros cinco en el widget. Y, por supuesto, nunca hagas while(true) { try ... } — es una forma segura de hacerte un DDoS a ti mismo.

5. Idempotencia de los pasos: protección frente a duplicados

Los reintentos crean un segundo problema: cómo no ejecutar la misma acción dos veces. En el mundo LLM esto es especialmente agudo: el modelo puede invocar accidentalmente la misma herramienta varias veces, ChatGPT puede repetir un tool‑call tras un timeout, el usuario puede pulsar «Regenerate» y luego la UI o el agente añadir otro llamado por su cuenta.

La idea de idempotencia es simple: un paso se considera idempotente si su ejecución repetida con los mismos datos de entrada no crea efectos secundarios adicionales. Solicitar un product feed — ok, recalcular recomendaciones — ok, pero cobrar dos veces o crear un segundo pedido con los mismos datos — nada ok.

Idempotency key en ChatGPT App

Patrón clásico: para cada paso lógico con efectos secundarios generas una idempotency_key (normalmente un UUID), la pasas a través del modelo a la herramienta MCP y allí guardas la correspondencia «clave → resultado». Si la herramienta se invoca una segunda vez con la misma clave, no repite la acción, sino que devuelve el resultado ya guardado.

En nuestro GiftGenius hay un paso create_order. Imagina que el usuario pulsa el botón «Pagar», el modelo invoca la herramienta, el pago se realiza, pero la respuesta se pierde por el camino. El modelo o la plataforma deciden repetir la llamada, y si no tenemos idempotencia, obtendremos un duplicado de pedido o un doble cargo.

Ejemplo sencillo de herramienta idempotente en TypeScript

Hagamos un handler muy simplificado de la herramienta MCP create_order con clave de idempotencia. Para simplificar usamos un Map en memoria; en la vida real será una BD o un caché.

type CreateOrderInput = {
  userId: string;
  items: Array<{ sku: string; qty: number }>;
  idempotencyKey: string;
};

type CreateOrderResult = { orderId: string; status: "created" };

const idempotencyStore = new Map<
  string,
  { paramsHash: string; result: CreateOrderResult }
>();

export async function createOrderTool(input: CreateOrderInput): Promise<CreateOrderResult> {
  const { idempotencyKey, ...rest } = input;
  const paramsHash = JSON.stringify(rest);

  const existing = idempotencyStore.get(idempotencyKey);
  if (existing) {
    // si la clave ya existe, verificamos que los parámetros coinciden
    if (existing.paramsHash !== paramsHash) {
      throw new Error("Idempotency key reuse with different params");
    }
    return existing.result;
  }

  // aquí realizamos la creación real del pedido y el cobro
  const result: CreateOrderResult = {
    orderId: "order_" + Math.random().toString(36).slice(2),
    status: "created",
  };

  idempotencyStore.set(idempotencyKey, { paramsHash, result });
  return result;
}

Aquí:

  • exigimos idempotencyKey en los datos de entrada de la herramienta;
  • guardamos con ella el hash de parámetros (aquí, por simplicidad, JSON.stringify);
  • si se repite la llamada con la misma clave pero datos distintos, lo consideramos un error;
  • si se repite con los mismos datos, devolvemos el resultado anterior.

En un proyecto real conviene:

  • guardar las claves en una BD con TTL (para que la tabla no crezca sin control);
  • loguear idempotency_key e incluirla en los campos _meta de los mensajes MCP, para seguirla cómodamente por Inspector y paneles.

6. Rollback de pasos y el patrón Saga

La idempotencia protege de duplicados, pero no resuelve otro caso: qué hacer si uno de los pasos a mitad del guion falla.

En e‑commerce es un problema clásico: ya creaste el pedido y reservaste el producto en almacén, y en la etapa de pago algo salió mal. No puedes simplemente «olvidarlo» — hay que revertir el estado anterior.

Rollback lógico vs técnico

En un workflow de ChatGPT hay dos niveles de rollback.

Rollback lógico — es volver al paso anterior del guion y corregir el contexto. Por ejemplo, en el paso «pago» ocurre un error y decides volver al paso «selección del método de pago» o incluso «elección del regalo». Entonces es importante:

  • actualizar el WorkflowContext en el backend (paso actual, parámetros elegidos);
  • informar al modelo del cambio de paso mediante tool‑call/ToolOutput, para que «olvide» la rama anterior y adapte el comportamiento;
  • actualizar la UI del widget para que los pasos y botones reflejen el nuevo estado.

Rollback técnico — es a nivel de negocio: cancelar entidades creadas, compensar efectos externos. Por ejemplo: cancelar el pedido, liberar la reserva de almacén, iniciar la devolución del pago. Esto es el patrón Saga: para cada paso «peligroso» diseñas de antemano una acción compensatoria.

Esquema forward/compensate para GiftGenius

Para un checkout simplificado de GiftGenius podemos dibujar esta secuencia:

flowchart TD
  A[Paso 1: create_order] --> B[Paso 2: reserve_items]
  B --> C[Paso 3: charge_card]

  C -->|éxito| D[Estado: completado]

  C -->|error| E[Compensación: cancel_reservation]
  E --> F[Compensación: cancel_order]
  F --> G[Estado: fallido + mensaje al usuario]

A cada acción que cambia el mundo externo (creación de pedido, reserva, pago) le corresponde una acción compensatoria (cancelación de pedido, liberación de la reserva, devolución). No siempre son simétricas ni siempre posibles una a una, pero el principio general es ese.

Mini‑ejemplo con compensación en código

Veamos un pequeño fragmento de código que ejecuta estos pasos:

async function completeCheckout(ctx: { userId: string }) {
  const order = await createOrderInDb(ctx.userId);

  try {
    await reserveItems(order.id);
    await chargeCard(order.id);
    return { orderId: order.id, status: "paid" as const };
  } catch (err) {
    // acciones compensatorias
    await safeCancelReservation(order.id);
    await safeCancelOrder(order.id);
    throw err;
  }
}

Aquí:

  • createOrderInDb, reserveItems, chargeCard — pasos de avance (forward);
  • safeCancelReservation y safeCancelOrder — pasos compensatorios, que a su vez deben ser idempotentes (si intentamos cancelar algo ya cancelado, no pasa nada).

Atención: ante un error no lo ocultamos, sino que lo propagamos. El modelo (vía ToolOutput) debe recibir un mensaje de error comprensible y, ya en lenguaje humano, explicarlo al usuario y proponer el siguiente paso.

7. Rollback de pasos y sincronización de estado: cómo evitar el desincronizado

Hay un tipo especial de «error» fácil de subestimar: el desincronizado de estado entre UI, backend y modelo.

Escenario típico:

  1. El usuario pasa por los pasos 1 → 2 → 3.
  2. En el paso 3 algo va mal y el usuario pulsa el botón «Atrás» en el widget.
  3. El widget devuelve honestamente su estado local al paso 2.
  4. Pero el modelo «recuerda» que estábamos en el paso 3 e incluso intentamos pagar. En el siguiente mensaje sigue hablando de pago aunque el usuario ve la pantalla de elección del regalo.

Para que esto no ocurra, es útil introducir un evento explícito de rollback de paso. Lo envía el widget al MCP/modelo — como invocación de herramienta o como ToolOutput.

Por ejemplo, se puede crear una herramienta sencilla user_navigated_to_step que fije el paso actual y su estado:

type NavigateInput = {
  workflowId: string;
  stepId: string;
};

export async function userNavigatedToStep(input: NavigateInput) {
  await workflowRepo.setCurrentStep(input.workflowId, input.stepId);
  return {
    message: `User moved to step ${input.stepId}`,
  };
}

El widget, al pulsar «Atrás», invoca esta herramienta; el modelo ve su resultado en el histórico de tool‑calls y entiende que ahora debe continuar el diálogo partiendo del nuevo paso.

En la UI sería algo así como este manejador:

async function handleBackClick() {
  const { workflowId, prevStepId } = widgetState;

  await window.openai.tools.call("user_navigated_to_step", {
    workflowId,
    stepId: prevStepId,
  });

  setWidgetState((s) => ({ ...s, currentStepId: prevStepId }));
}

Punto clave: el backend/agente es la fuente de la verdad del paso actual, y el modelo lo ve a través de tools. Así, incluso al restaurar la sesión más tarde puedes sincronizar bien el contexto.

8. UX de errores: qué ve el usuario y qué ve el modelo

Ya hemos aprendido a sobrevivir técnicamente a los errores (retries, rollbacks, idempotencia, sincronización de estado). Falta que esto se vea bien tanto para el usuario como para el modelo.

Ni el mejor retry ni el mejor rollback te salvarán si el UX de errores es «como en los viejos servlets Java»: texto rojo, stack trace y un críptico «Unexpected error».

Para ChatGPT App hay dos audiencias del mensaje de error:

  • el usuario, que debe entender qué pasó y qué puede hacer a continuación;
  • el modelo, que debe recibir información suficientemente estructurada para decidir: reintentar, cambiar parámetros, proponer una alternativa o cerrar el escenario.

Buena práctica:

  • a nivel de MCP/herramientas, devolver un error estructurado con código, tipo, flag retryable y un texto técnico breve;
  • dar al modelo precisamente esa estructura (por ejemplo, en result.structuredContent), y no un kilómetro de stack trace;
  • en la UI mostrar al usuario un mensaje humano, breve.

Mini‑ejemplo de estructura de error que devuelve una herramienta:

type ToolError = {
  code: string;          // e.g. "PAYMENT_TIMEOUT"
  message: string;       // breve descripción técnica
  retryable: boolean;    // si se puede intentar de nuevo
};

throw {
  isError: true,
  error: <ToolError>{
    code: "PAYMENT_TIMEOUT",
    message: "Payment provider did not respond in time",
    retryable: true,
  },
};

El modelo ve retryable: true y puede probar otra herramienta o proponer al usuario repetir el intento.

En el lado del widget simplemente mapeas estos códigos a textos comprensibles para el usuario:

function ErrorBanner({ code }: { code: string }) {
  const text =
    code === "PAYMENT_TIMEOUT"
      ? "El proveedor de pagos no respondió a tiempo. Inténtalo de nuevo dentro de un minuto."
      : "Algo ha salido mal. Por favor, inténtalo de nuevo.";

  return <div className="error-banner">{text}</div>;
}

Y otro punto importante: no mostréis al usuario stacks de excepciones, tokens, secretos. Es feo e inseguro. La información técnica, registradla en vuestros logs; al usuario, dadle un mensaje breve y seguro.

Insight

En sistemas LLM como ChatGPT, las invocaciones incorrectas de herramientas son más la norma que la excepción. El modelo genera con frecuencia argumentos que no pasan la validación: tipos cambiados, campos ausentes, valores incorrectos, estructuras rotas. No es un error en el sentido ingenieril habitual: es parte de la naturaleza de un modelo estocástico, y hay que adaptar todo el interfaz de errores a ello.

Idea clave: el mensaje de error no es una señal de «se ha roto», sino una instrucción para corregir el siguiente intento. Su audiencia principal es el propio modelo. Si el mensaje está estructurado y contiene indicaciones precisas, el modelo puede corregir automáticamente los parámetros y repetir la llamada correctamente. Este es exactamente el principio en que se basan técnicas como Tool‑Reflection: una retroalimentación correcta mejora la siguiente acción del agente sin intervención humana.

Recomiendo atenerse a estos requisitos para el formato de los errores:

  • el mensaje debe indicar el campo concreto que no pasó la validación — evitando generalidades tipo «Invalid parameters»;
  • es importante describir explícitamente el formato esperado o los valores permitidos, para que el modelo pueda elegir lo adecuado;
  • el mensaje debe ser breve, formal y estructurado: campos como error_type, field, expected o allowed_values ayudan mucho al modelo;
  • si es posible, da un ejemplo mínimo de entrada correcta — a menudo aumenta la precisión de recuperación del modelo.

El feedback de error ideal para el modelo contiene dos hechos: qué ha fallado y instrucciones de cómo corregirlo.

9. Logging y métricas de errores del workflow

Incluso si el UX de errores es cuidado, para entender qué se rompe de verdad no bastan los mensajes al usuario. Se necesitan logs estructurados y métricas por pasos.

Conjunto mínimo útil al loguear cada paso del workflow:

  • user_id o al menos session_id;
  • workflow_id y step_id;
  • estado del paso (success, failed, retry, rolled_back);
  • error_code (si lo hubo);
  • idempotency_key y correlation_id, si el paso está relacionado con llamadas externas.

En MCP y Agents existen campos _meta; es cómodo poner ahí idempotency_key y correlation_id para verlos en logs e Inspector.

Ejemplo muy simple de logging en Node.js/TypeScript (puedes usar console o winston/pino):

function logStepFailure(params: {
  userId?: string;
  workflowId: string;
  stepId: string;
  errorCode: string;
  idempotencyKey?: string;
}) {
  console.error(
    JSON.stringify({
      level: "error",
      event: "workflow_step_failed",
      ...params,
      timestamp: new Date().toISOString(),
    })
  );
}

Estos logs son fáciles de parsear, construir paneles y medir:

  • la conversión entre pasos;
  • los tipos de error más frecuentes;
  • la proporción de pasos que terminaron en retry vs fallo definitivo.

No todo error debe convertirse en alerta en producción. Críticos — caída de MCP, timeouts sistemáticos, fallos masivos en un determinado paso — sí, deben ir a monitorización. Pero «no hay resultados para la búsqueda de regalos» es un evento de negocio, no un incidente.

10. Hacemos crecer GiftGenius: un paso de checkout robusto

Ahora juntemos todo: reintentos, idempotencia, Saga, sincronización de estado, UX de errores y logging — en el ejemplo de un paso en nuestra app de aprendizaje GiftGenius: el checkout.

Qué ya tenemos

Hasta este momento ya:

  • tenemos un workflow multietapa: recopilación de información → generación de ideas → elección del regalo → checkout;
  • el tool gating está configurado: en el paso de checkout solo está disponible el conjunto de herramientas de comercio (create_order, get_payment_methods, etc.);
  • tenemos WorkflowContext, que guarda el regalo elegido, presupuesto, userId y el paso actual.

Qué añadimos en esta lección

Para el paso de checkout incorporaremos:

  1. idempotency_key para la herramienta create_order;
  2. retry ante errores temporales del proveedor de pagos;
  3. compensación en operaciones parcialmente exitosas;
  4. un UX de errores correcto en el widget.

Generación de la clave de idempotencia en el widget al pulsar el botón «Pagar»:

import { v4 as uuid } from "uuid";

async function handlePayClick() {
  const idempotencyKey = uuid();
  setWidgetState((s) => ({ ...s, idempotencyKey }));

  await window.openai.tools.call("create_order", {
    userId: widgetState.userId,
    items: [/* ... */],
    idempotencyKey,
  });
}

En la herramienta create_order — el handler idempotente que escribimos arriba: guarda la clave y el resultado, y en caso de repetición no crea un pedido nuevo.

El código que interactúa con la API de pagos se puede envolver con callWithRetry para intentar varios cargos ante fallos de red. Y no olvides añadir el flag retryable: true en el error, para que el modelo entienda que puede proponer reintentar.

Si tras crear el pedido y cobrar con éxito algo se rompe (por ejemplo, no llega a tiempo un webhook externo), lo registramos con correlation_id y workflow_id y a continuación:

  • probamos un retry en segundo plano (en un futuro módulo sobre colas y eventos);
  • o marcamos explícitamente el paso como failed, invocamos acciones compensatorias y explicamos al usuario qué ha pasado.

11. Errores típicos al diseñar workflows tolerantes a fallos

Error n.º 1: «Reintentamos todo hasta que funcione».
Repetir automáticamente cualquier paso hasta la victoria es una forma segura de crearte tu propio infierno local. Los errores de red y de tipo 5xx se pueden intentar de nuevo con backoff y límite de intentos. Pero 4xx, errores de negocio y fallos lógicos del modelo hay que arreglarlos con datos o explicárselos al usuario. De lo contrario, obtendrás un comportamiento inestable, facturas raras y logs llenos de ruido.

Error n.º 2: Ausencia de idempotencia donde hay dinero y pedidos.
Si una herramienta como create_order o charge_card no es idempotente, cualquier llamada repetida (por timeout, «Regenerate», bug en el agente) puede llevar a duplicados. En escenarios LLM los reintentos son bastante más frecuentes que en un frontend REST clásico, por lo que la idempotency_key no es «un extra bonito», sino una condición obligatoria para pasos de pago y otros críticos.

Error n.º 3: No hay acciones compensatorias (falta Saga).
Creaste el pedido, reservaste el producto y, al pagar, falló y solo mostraste al usuario «algo ha salido mal». Como resultado, quedan pseudo‑pedidos en el sistema, reservas, «flecos» financieros. Para cada paso que cambia el mundo externo, conviene pensar qué harás si falla el siguiente: cancelar, devolver, marcar como «expired», etc.

Error n.º 4: Dejar que el agente entre en un bucle infinito de reintentos.
Si no limitas el número de intentos (por ejemplo, con maxRetries en helpers o con max_iterations en la lógica del agente) y no marcas los errores como retryable: false allí donde los reintentos no sirven, el modelo puede ciclarse: «Lo intento otra vez… otra vez…». Esto quema tokens, tiempo y nervios.

Error n.º 5: Desincronizado de estado entre UI y modelo al hacer rollback.
A menudo los desarrolladores implementan el botón «Atrás» solo en la UI, olvidando sincronizar el paso con backend y modelo. El usuario ve el paso 2, pero el modelo sigue «viviendo» en el 3 y hace propuestas extrañas. La solución: eventos explícitos como user_navigated_to_step y actualizar el WorkflowContext en cada transición.

Error n.º 6: Mensajes técnicos para el usuario y ausencia de logs para los desarrolladores.
El usuario recibe «Error: ECONNRESET at TcpSocket.onEnd…» y tú — cero información sobre qué paso y para qué workflow_id se rompió. Enfoque sensato: para el usuario — texto breve y claro y una propuesta de qué hacer después; para el desarrollador — log estructurado con workflow_id, step_id, error_code, idempotency_key y correlation_id.

Error n.º 7: Ausencia de estrategia de alertas.
O alertan por todo, incluyendo «no hay regalos adecuados para tu filtro muy estrecho», o no alertan por nada, incluyendo la caída real de MCP. Intenta separar fallos sistémicos críticos (caída del servicio, timeouts masivos, pérdida de webhooks) de eventos de negocio esperados. Los primeros van a monitorización y on‑call; los segundos, simplemente a analítica.

Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION