1. Por qué medir el workflow
En resumen: sin analítica vives en el modo «me parece», no en el modo «lo sé».
En lecciones anteriores de este módulo ya dividimos el escenario en pasos, repartimos los roles entre GPT, el widget y el MCP, comentamos el tool‑gating y el almacenamiento de estado entre pasos. Ahora miraremos la misma construcción con ojos de analítica: si todo esto funciona como pensábamos y dónde se atascan realmente los usuarios.
En la web clásica todos están habituados a los embudos: cuánta gente llegó al landing, cuántos metieron un producto en el carrito, cuántos llegaron al pago. En ChatGPT App es lo mismo, solo que en lugar de páginas tienes pasos del workflow, y en lugar de «clic en el botón Comprar» — una combinación de réplica del usuario, llamada a herramienta e interacción con el widget.
Cuando construyes un escenario complejo sin métricas, no ves:
- en qué paso la gente abandona con más frecuencia;
- dónde se quedan atascados y leen durante un minuto (o simplemente se fueron a por un té y no volvieron);
- qué paso no aporta ningún valor y solo irrita;
- cómo los cambios en los prompts o el tool gating afectan al comportamiento.
El objetivo de la analítica por pasos es sencillo: aprender a aumentar la proporción de escenarios completados, reducir el tiempo hasta el resultado y disminuir la cantidad de errores y solicitudes al soporte.
A partir de ahora, el workflow no es solo un objeto arquitectónico ni solo una misión de UX. También es algo medible con cifras.
2. El embudo del escenario en ChatGPT App
En la web clásica, el embudo se ve lineal: Landing → Product → Cart → Checkout. En ChatGPT App la imagen es un poco más animada: el usuario puede «saltarse» un paso con palabras, el modelo a veces puede omitir un paso, y el widget y el texto del diálogo pueden desincronizarse.
Aun así, la idea básica es la misma: tenemos una secuencia de pasos y, en cada uno, una parte de los usuarios avanza y otra parte no.
Tomemos nuestro GiftGenius:
- collect_recipient — ChatGPT y el widget recogen datos básicos del destinatario (sexo, edad, relación, intereses).
- collect_budget — concretamos el presupuesto y la moneda.
- suggest_ideas — MCP / agente selecciona ideas y devuelve tarjetas de regalos en el widget.
- review_selection — el usuario da like / oculta ideas y elige 1–2 favoritas.
- checkout — se crea una intención de compra y se tramita el pedido.
Como embudo, se puede dibujar así:
flowchart TD
A[Inicio del workflow] --> B["1\. Destinatario"]
B --> C["2\. Presupuesto"]
C --> D["3\. Ideas de regalos"]
D --> E["4\. Selección del regalo"]
E --> F["5\. Checkout"]
Pero es importante recordar: el usuario puede escribir en el chat «Vayamos directamente al pago» o «Muestra primero las opciones caras», y el modelo puede decidir saltarse parte de los pasos. Por eso, la analítica por pasos en ChatGPT App no trata solo de pantallas de UI, sino también del comportamiento del modelo: qué pasos se recorren realmente, en qué orden y quién inició la transición — el usuario, el widget o GPT.
3. Métricas básicas por paso
Empecemos por lo clásico de la analítica de producto y lo adaptamos un poco a ChatGPT App.
Para cada workflow necesitamos al menos cuatro indicadores básicos.
Para mayor comodidad, reunámoslos en una tabla:
| Métrica | Qué significa | Pregunta típica |
|---|---|---|
| Start rate | Cuántos usuarios han iniciado el escenario | ¿Nuestra App se muestra a alguien? |
| Completion rate | Cuántos usuarios llegan al final | ¿Hasta qué punto el escenario lleva al resultado? |
| Conversion per step | Proporción de usuarios que pasan del paso N al paso N+1 | ¿En qué paso exactamente tenemos la fuga? |
| Drop-off per step | Proporción de usuarios que abandonan en el paso N | ¿En qué paso se rinde la gente con más frecuencia? |
A estos casi siempre se añaden métricas de esfuerzo:
- tiempo medio en el paso (dónde se «atascan»);
- número de interacciones en el paso (cuántos mensajes/clics hicieron falta);
- proporción de pasos que terminaron en error o requirieron un reintento.
En el contexto de los escenarios con LLM se añaden cosas todavía más específicas, como la precisión de la elección de la herramienta por parte del modelo o la proporción de respuestas «alucinadas» en un paso concreto, pero esto ya es un tema avanzado al que llegaremos en los módulos finales.
Para escenarios de comercio electrónico, por encima de los pasos se añaden métricas de negocio:
- conversión a pago desde el inicio del workflow;
- conversión a pago desde un paso concreto (por ejemplo, desde «sugerir ideas»);
- ticket medio;
- porcentaje de cancelaciones/devoluciones.
Importante: todas estas cifras no viven aisladas, sino que entre ellas hay una historia causal. Un paso con un drop‑off alto no siempre es malo: quizá filtra a usuarios no adecuados y solo continúan los que realmente obtienen valor del escenario. Por eso, la analítica no es solo «contar porcentajes», sino saber contar una historia con los datos.
Para poder calcular todos esos porcentajes y embudos, necesitamos eventos en bruto: quién, cuándo y qué paso recorrió (o no). En la siguiente sección acordaremos el formato de esos eventos.
4. Cómo son los eventos de analítica
Antes de escribir código, hay que acordar el formato del «evento» (event) que enviaremos desde el widget y el backend.
Normalmente, un evento de analítica contiene:
- quién: identificador de usuario o al menos de sesión;
- qué workflow y qué versión;
- qué paso;
- qué ocurrió (tipo de evento);
- si fue exitoso, cuánto tiempo tomó;
- un poco de metadatos (locale, dispositivo, etc.).
Un esquema simplificado de eventos para el workflow puede describirse así:
export type WorkflowEventType =
| "workflow_started"
| "workflow_finished"
| "step_started"
| "step_completed"
| "step_failed";
export interface WorkflowAnalyticsEvent {
eventId: string; // uuid
timestamp: string; // cadena ISO
userId?: string; // si es posible desanonimizar
conversationId?: string; // id de conversación de ChatGPT (si está disponible)
workflowId: string; // nuestro identificador interno
workflowType: "gift_selection";
workflowVersion: string; // por ejemplo, "1.2.0" o "1.2.0-A"
stepName?: string; // collect_budget, suggest_ideas, etc.
eventType: WorkflowEventType;
toolName?: string; // si está relacionado con una llamada de herramienta
success?: boolean;
errorCode?: string | null;
durationMs?: number;
metadata?: Record<string, unknown>;
}
Algunos matices:
En primer lugar, workflowVersion es muy importante si piensas hacer tests A/B: sin ella nunca sabrás qué variante del escenario ofrece mejores cifras.
En segundo lugar, conversationId u otro correlation ID permite correlacionar eventos: pasos en el widget, llamadas a herramientas en MCP y el diálogo de texto. En módulos posteriores hablaremos de trazabilidad y observabilidad, pero adquirir desde el principio el hábito de pensar en identificadores «de extremo a extremo» es muy útil.
En tercer lugar, no hay que incluir en el evento todo lo que se te ocurra: es mejor evitar o anonimizar estrictamente textos completos de mensajes, e‑mail, direcciones y otros PII — de esto hablaremos más cerca del final.
5. Instrumentación en el widget (Next.js + Apps SDK)
Ahora lo interesante: cómo hacer que nuestro widget de GiftGenius reporte silenciosamente los pasos cuando el usuario recorre el escenario.
Supongamos que en lecciones anteriores ya hiciste algo como esto:
// components/GiftWizard.tsx
type StepId = "recipient" | "budget" | "ideas" | "review" | "checkout";
export function GiftWizard() {
const [currentStep, setCurrentStep] = useState<StepId>("recipient");
const [workflowId] = useState(() => crypto.randomUUID());
// ... aquí se renderizan los distintos pasos
}
Añadamos una pequeña «capa de analítica» en forma de hook.
Hook useWorkflowAnalytics
Hagamos un wrapper que conoce workflowId, workflowVersion y sabe enviar un evento a nuestra ruta de API de Next.js /api/workflow-analytics.
// lib/useWorkflowAnalytics.ts
import { useCallback } from "react";
import type { WorkflowAnalyticsEvent, WorkflowEventType } from "./types";
const WORKFLOW_VERSION = "1.0.0";
export function useWorkflowAnalytics(
workflowId: string,
workflowType: WorkflowAnalyticsEvent["workflowType"] = "gift_selection"
) {
const sendEvent = useCallback(
async (payload: Omit<WorkflowAnalyticsEvent, "eventId" | "timestamp" | "workflowType" | "workflowVersion" | "workflowId">) => {
const event: WorkflowAnalyticsEvent = {
eventId: crypto.randomUUID(),
timestamp: new Date().toISOString(),
workflowId,
workflowType,
workflowVersion: WORKFLOW_VERSION,
...payload,
};
// envío simple al API; en producción puedes añadir buffer/debounce
await fetch("/api/workflow-analytics", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(event),
});
},
[workflowId, workflowType]
);
const trackStepEvent = useCallback(
async (stepName: string, eventType: WorkflowEventType, extra?: Partial<WorkflowAnalyticsEvent>) => {
await sendEvent({ stepName, eventType, ...extra });
},
[sendEvent]
);
return { sendEvent, trackStepEvent };
}
Lo importante aquí es que el hook no depende del UI de un paso concreto. Simplemente conoce qué es stepName y eventType. Los componentes concretos le dirán: «aquí he iniciado el paso», «aquí lo he terminado», etc.
Enviamos workflow_started y workflow_finished
En el componente GiftWizard podemos registrar el inicio y el final del escenario en el momento del montaje y desmontaje:
// components/GiftWizard.tsx
export function GiftWizard() {
const [currentStep, setCurrentStep] = useState<StepId>("recipient");
const [workflowId] = useState(() => crypto.randomUUID());
const { sendEvent } = useWorkflowAnalytics(workflowId);
useEffect(() => {
void sendEvent({ eventType: "workflow_started" });
return () => {
void sendEvent({ eventType: "workflow_finished" });
};
}, [sendEvent]);
// ...
}
Por supuesto, terminar en el desmontaje es una aproximación burda: el usuario puede simplemente minimizar el chat o irse a otro diálogo. Pero incluso una métrica tan grosera ya da una idea de cuántos escenarios «llegan a algún sitio».
Seguimos los eventos por paso
Ahora haremos que cada paso informe por sí mismo a la analítica. Para empezar, añadimos un wrapper simple:
interface StepProps {
stepId: StepId;
onNext: () => void;
trackStepEvent: (stepName: string, eventType: WorkflowEventType, extra?: Partial<WorkflowAnalyticsEvent>) => Promise<void>;
}
function StepRecipient({ stepId, onNext, trackStepEvent }: StepProps) {
useEffect(() => {
void trackStepEvent(stepId, "step_started");
}, [stepId, trackStepEvent]);
const handleSubmit = async () => {
// ... validación, guardado en widgetState
await trackStepEvent(stepId, "step_completed");
onNext();
};
return (
<div>
{/* campos del formulario del destinatario */}
<button onClick={handleSubmit}>Siguiente</button>
</div>
);
}
En GiftWizard pasamos trackStepEvent:
export function GiftWizard() {
// ...
const { trackStepEvent } = useWorkflowAnalytics(workflowId);
const goToNext = () => {
setCurrentStep((prev) => NEXT_STEP[prev]);
};
if (currentStep === "recipient") {
return (
<StepRecipient
stepId="recipient"
onNext={goToNext}
trackStepEvent={trackStepEvent}
/>
);
}
// otros pasos...
}
De forma análoga, en pasos donde hay posibles errores (por ejemplo, una solicitud a una API externa en suggest_ideas), puedes enviar en caso de fallo "step_failed" con errorCode, y en caso de carga exitosa de opciones — "step_completed".
Así obtenemos:
- una lista clara de eventos: cuándo empiezan y terminan los pasos;
- la posibilidad de calcular el tiempo del paso: la diferencia entre "step_started" y "step_completed";
- visibilidad de qué pasos terminan más a menudo con "step_failed".
6. Instrumentación en backend / MCP
La analítica del lado cliente está bien, pero el widget vive en un mundo bastante frágil: el navegador del usuario, un iframe, restricciones de sandboxing y todo lo relacionado. Por eso, en paralelo conviene registrar eventos en el lado del servidor — en herramientas MCP o en el backend‑API de tu App.
Por ejemplo, tienes la herramienta suggest_gifts, que hace el trabajo pesado: consulta el product feed, aplica filtros y devuelve regalos. Dentro de esa herramienta puedes registrar tanto la lógica de negocio como eventos analíticos.
Un handler hipotético de una MCP‑tool en TypeScript puede verse así:
// mcp/tools/suggestGifts.ts
import type { SuggestGiftsArgs } from "../schemas";
import { logWorkflowEvent } from "../analytics/log";
export async function handleSuggestGifts(args: SuggestGiftsArgs, context: { workflowId: string; stepName: string }) {
const startedAt = Date.now();
try {
// ... lógica principal de selección de ideas
await logWorkflowEvent({
workflowId: context.workflowId,
workflowType: "gift_selection",
workflowVersion: "1.0.0",
stepName: context.stepName,
eventType: "step_completed",
toolName: "suggest_gifts",
success: true,
durationMs: Date.now() - startedAt,
});
return {
content: [{ type: "text", text: "He encontrado 5 ideas de regalo." }],
_meta: {
// datos en bruto para el widget
},
};
} catch (e) {
await logWorkflowEvent({
workflowId: context.workflowId,
workflowType: "gift_selection",
workflowVersion: "1.0.0",
stepName: context.stepName,
eventType: "step_failed",
toolName: "suggest_gifts",
success: false,
errorCode: "SUGGEST_FAILED",
durationMs: Date.now() - startedAt,
});
throw e;
}
}
Y logWorkflowEvent puede escribir en la misma tabla/almacén donde van los eventos del frontend, solo con la marca "source": "backend".
Por qué la analítica del servidor es más fiable
En primer lugar, la llamada a una herramienta o ocurrió, o no — es un hecho claro, no una heurística de «el usuario parece que pulsó el botón».
En segundo lugar, en el servidor es más fácil agregar datos: puedes contar cuántas veces se invoca cada herramienta, cuál es su durationMs medio y qué proporción de llamadas termina en error.
En tercer lugar, así ves la diferencia entre un problema de UX (el usuario no llega al paso donde se invoca la herramienta) y un problema técnico (sí llegan, pero la herramienta falla a menudo).
7. Cómo leer los datos: buscamos cuellos de botella
Supongamos que ya implementaste el envío de eventos "workflow_started", "step_started", "step_completed" y "step_failed" desde el widget y MCP, y que se han acumulado suficientes datos en el almacén. Imaginemos que ya reuniste cierta cantidad de datos sobre GiftGenius y obtuviste estadísticas resumidas por paso. En la tabla de abajo se muestran cifras hipotéticas para 1000 workflows iniciados:
| Paso | Inicio del paso | Paso completado | Drop‑off en el paso | Tiempo medio (s) |
|---|---|---|---|---|
| recipient | 1000 | 950 | 5% | 12 |
| budget | 950 | 700 | 26% | 35 |
| ideas | 700 | 680 | 3% | 8 |
| review | 680 | 500 | 26% | 40 |
| checkout | 500 | 420 | 16% | 20 |
En qué fijarse aquí:
En primer lugar, el paso budget es un cuello de botella obvio. Alto drop‑off (26 %) y un tiempo medio notablemente mayor. Quizá preguntas demasiado sobre monedas/impuestos, las frases no se entienden o la gente simplemente no tiene claro el presupuesto. Es un buen candidato para simplificar el paso, dividirlo en dos subpasos o cambiar la formulación de las preguntas.
En segundo lugar, review también provoca un abandono fuerte. Quizá el UI de tarjetas de regalos esté sobrecargado o el usuario no entienda qué significa «dar like» a un regalo. Tal vez el modelo devuelve demasiadas opciones y el widget parece una lista interminable. Aquí conviene mirar no solo las cifras, sino también capturas de pantalla/registros de sesión (si los tienes), o al menos recorrer manualmente el escenario como un usuario.
En tercer lugar, checkout pierde un 16 % — para un escenario de comercio es mucho dinero. Pero hay que entender dónde se pierden: en la tramitación del pedido, en errores del proveedor de pagos o en que el usuario simplemente se arrepintió. Esto ya no es una cuestión puramente de UX, sino la suma de UX + restricciones de negocio.
Es importante saber distinguir problemas de UI y del modelo.
- Si los usuarios vuelven a menudo al paso anterior y cambian respuestas, es una señal de pregunta confusa o mal formulada.
- Si el paso termina rápido, pero la llamada a la herramienta en él falla a menudo, es un problema de backend/MCP.
- Si el paso dura mucho y no hay errores ni vueltas atrás, quizá el usuario simplemente está leyendo un texto largo que no le aporta mucho.
8. Experimentos y tests A/B del workflow
Las cifras por sí solas no mejoran nada. Para que la analítica sirva, hay que saber hacer experimentos: cambiar pasos y comparar si ha mejorado.
En el contexto de ChatGPT App, el experimento típico es comparar dos versiones de un paso o de la secuencia de pasos:
- un wizard largo con varias pantallas simples frente a un formulario único complejo;
- distintas formulaciones de preguntas;
- distinto orden de pasos (por ejemplo, preguntar el presupuesto antes o después);
- diferentes estrategias de tool gating (menos tools en el primer paso, más en el segundo).
Un buen hábito es fijar la versión del escenario en workflowVersion y añadir ahí el identificador del experimento, por ejemplo "1.3.0-A" y "1.3.0-B".
El A/B split más simple en el widget
Por supuesto, en producción querrás un assignment estable a nivel de usuario o sesión (vía backend), pero para un ejemplo didáctico basta con elegir la variante al azar.
// lib/useWorkflowVariant.ts
import { useMemo } from "react";
export type WorkflowVariant = "A" | "B";
export function useWorkflowVariant(): WorkflowVariant {
return useMemo(() => {
return Math.random() < 0.5 ? "A" : "B";
}, []);
}
En GiftWizard determinamos la variante y la pasamos a la analítica:
export function GiftWizard() {
const [workflowId] = useState(() => crypto.randomUUID());
const variant = useWorkflowVariant();
const { sendEvent, trackStepEvent } = useWorkflowAnalytics(
workflowId,
"gift_selection"
);
useEffect(() => {
void sendEvent({
eventType: "workflow_started",
metadata: { variant },
});
}, [sendEvent, variant]);
// después puedes cambiar textos/estructura de los pasos según la variant
}
En el servidor, puedes sustituir el "1.0.0" fijo por algo como "1.1.0-A" y "1.1.0-B" o simplemente registrar metadata.variant y agrupar por él en la analítica.
El sentido principal de un test A/B: elegir de antemano la métrica objetivo. Por ejemplo: «queremos subir el completion rate del escenario del 42 % al 50 %» o «reducir el tiempo en el paso budget en un 20 %». Sin una métrica objetivo, cualquier reestructuración del workflow se parecerá a una reforma del tipo «movimos el armario; parece más bonito».
9. Privacidad y ética de datos
Ya mencionamos de pasada que en metadata y en los eventos de analítica es mejor no llevar PII en bruto. Mientras hablamos de métricas, es fácil entusiasmarse y empezar a registrar todo. Pero recuerda que trabajas dentro de ChatGPT y el usuario puede esperar razonablemente que sus mensajes personales no salgan hacia una analítica externa en bruto.
Algunas reglas sencillas que conviene seguir ya ahora, antes de los módulos sobre seguridad y Store:
- En primer lugar, no registres los textos completos de los mensajes del usuario. En su lugar, puedes guardar la longitud del mensaje, el tipo de respuesta (número, «sí/no», selección de lista) o señales anonimizadas como «respuesta vacía / incompleta / modificada».
- En segundo lugar, no registres información claramente identificable (PII) si no la necesitas para la lógica de negocio: e‑mail, teléfonos, direcciones, nombres completos. Si no puedes prescindir de ello, guárdalos en otro entorno protegido y limita estrictamente el acceso.
- En tercer lugar, trata con cuidado el contexto del diálogo. Si guardas conversationId, asegúrate de no intentar «fusionar» diálogos separados en super‑perfiles sin una razón justificada y base legal.
- En cuarto lugar, presta atención a las políticas de OpenAI y a los requisitos del Store (llegaremos a esto en detalle en los módulos de publicación y seguridad), donde se indica explícitamente qué datos se pueden sacar de ChatGPT y cuáles no. En la fase de diseño de la analítica, es útil planificar desde el principio la anonimización y la minimización de datos para no tener que reescribir medio sistema después.
Y, por último, recuerda que la analítica de UX no es vigilancia total y desde luego no es un surveillance al estilo de Big Brother. El objetivo es mejorar los escenarios y reducir la frustración del usuario, no construir un panel de Big Brother del tipo «quién a las 2:37 de la madrugada no llegó al checkout».
10. Errores típicos en la analítica UX del workflow
Error n.º 1: «Aún no estamos en producción; las métricas vendrán después».
Muy a menudo, los desarrolladores empiezan a pensar en la analítica cuando la App ya está siendo usada por gente real. Como resultado, los eventos se introducen «a posteriori», los datos salen incompletos y comparar la versión antigua y la nueva del escenario es casi imposible. Es mejor incorporar desde el principio el embudo mínimo ("workflow_started", "step_started", "step_completed", "workflow_finished") mientras el código aún es relativamente sencillo.
Error n.º 2: Registrar solo éxitos e ignorar errores.
A veces en los logs solo hay "step_completed", y nadie escribe "step_failed" porque «no debería fallar». Como resultado, ves que poca gente llega a cierto paso, pero no entiendes si se han ido por su cuenta o los expulsaron los errores. Registra siempre tanto las finalizaciones exitosas del paso como las no exitosas, con al menos un errorCode grosero.
Error n.º 3: Ausencia total de vinculación a la versión del workflow.
Cambias textos, el orden de los pasos, introduces tool gating, pero en los eventos siempre figura el mismo workflowVersion: "1.0.0". Al mes miras las gráficas y no puedes entender qué fue antes de los cambios y qué después. Fijar la versión del escenario y, si es necesario, la variante A/B es un elemento obligatorio de la analítica.
Error n.º 4: Analítica demasiado detallada sin motivo.
El extremo opuesto es construir desde el principio el «esquema ideal» de eventos con 50 campos, registrar cada clic al píxel y cada reescritura de un carácter. Primero, esto puede violar la privacidad. Segundo, tales datos son difíciles de analizar y te ahogarás en ruido. Es mejor empezar con un conjunto pequeño de eventos y métricas que realmente respondan a preguntas de producto concretas, y después ampliar el sistema según sea necesario.
Error n.º 5: Incoherencia en los nombres de pasos y escenarios.
A veces en el código el paso se llama budget, en la analítica — collect_budget, y en el informe — «el paso con la pregunta sobre dinero». A las dos semanas nadie recuerda qué es qué. En la fase de diseño del workflow, es útil acordar identificadores estables de pasos (stepName) y usarlos en el UI, en los logs y en los informes.
Error n.º 6: Tener métricas que nadie usa.
La historia más triste: recopilaste cuidadosamente un montón de datos, configuraste el envío de eventos desde el widget y el MCP, pero luego nadie abre los dashboards ni toma decisiones. La analítica por la analítica no sirve; pregúntate siempre: «¿Qué decisión podré tomar basándome en esta métrica?». Si no hay respuesta, esa métrica no la necesitas por ahora.
GO TO FULL VERSION