Tag: Agentes IA

  • Por qué Spec-Driven Development (SDD) triplica tu velocidad cuando programas con agentes de IA

    Por qué Spec-Driven Development (SDD) triplica tu velocidad cuando programas con agentes de IA

    Hace unas semanas estaba viendo trabajar a un desarrollador senior con bastante experiencia. Usaba una de las mejores herramientas de IA del mercado.

    Su flujo era este: escribía un prompt en el chat ("Agrégame la autenticación con OAuth y guarda el token en cookies HTTP-only"), la IA generaba 150 líneas de código, el código fallaba, le volvía a pedir que corrigiera el error, la IA cambiaba tres archivos sin avisar, se rompía el tipo de una interfaz… y de repente llevaba dos horas haciendo el famoso "prompt ping-pong".

    Tenía la sensación de ir rapidísimo porque la IA escribía texto a toda velocidad. Pero al final de la jornada, la mitad de su tiempo lo había pasado arreglando las suposiciones que la IA había tenido que inventar porque nadie se las definía.

    El problema no era la IA. El problema es que estaba intentando construir una casa pidiéndole al albañil que pusiera ladrillos sin enseñarle los planos. Aquí es donde Spec-Driven Development (SDD) transforma la forma en que los desarrolladores senior trabajan con los agentes de IA.

    El espejismo del "Vibe Coding" sin rumbo

    Nos han vendido que programar con IA consiste en hablarle en lenguaje natural como si fuera un colega y dejar que el LLM deduzca todo lo demás.

    Para un script de 20 líneas o un prototipo que vas a tirar mañana, funciona. Para software en producción con arquitecturas reales, es una trampa.

    Cuando no defines las reglas del juego antes de pedir código, obligas al agente de IA a tomar decenas de decisiones implícitas:

    • ¿Qué nombres le da a las variables y modelos?
    • ¿Cómo maneja los casos de borde y errores?
    • ¿Qué contrato sigue la API?
    • ¿Qué dependencias o utilidades existentes en el proyecto debe reutilizar?

    Si el agente adivina mal una sola de esas cosas, el código generado es basura técnica que tendrás que mantener tú. Como explicamos en nuestro artículo sobre por qué tu spec falla con un agente de IA, la falta de claridad en las restricciones es la causa número uno de código roto.

    ¿Qué es Spec-Driven Development (SDD)?

    Spec-Driven Development no es burocracia ni escribir documentación de 50 páginas que nadie lee.

    SDD consiste en invertir el flujo de trabajo: en lugar de usar la IA para que redacte código directamente desde tu cabeza, utilizas la IA para definir una especificación estructurada y verificable ANTES de escribir la primera línea de código.

    En nuestro workflow de producción, una especificación SDD se divide en tres piezas muy concretas:

    1. spec.md (La Especificación Funcional y Técnica)

    Define el QUÉ y el POR QUÉ.

    • Contexto del problema y objetivo.
    • Requisitos funcionales explícitos.
    • Contratos de datos, tipos e interfaces.
    • Reglas de negocio y lo que NO debe hacer el sistema.

    2. plan.md (La Arquitectura e Impacto)

    Define el CÓMO.

    • Qué archivos se modifican, cuáles se crean y cuáles se eliminan.
    • Estrategia de testing y verificación.
    • Modificaciones en dependencias o firmas de API.

    3. tasks.md (El Plan de Ejecución)

    Define el ORDEN.

    • Lista de tareas atómicas e independientes que el agente de IA puede ejecutar paso a paso sin perder contexto ni alucinar.

    Eso sí, ten en cuenta que no siempre necesitas cargar con toda la estructura: en nuestra guía sobre cuándo NO usar Spec-Driven Development detallamos los escenarios donde un enfoque más directo resulta más eficiente.

    El cambio mental: De programador a Director de Arquitectura

    Mira lo que ocurre cuando le das a un agente (como Claude Code, AGY o Cursor) una especificación bien acotada:

    # Spec: Interceptor de Telemetría HTTP
    ## Requisitos
    - Interceptar todas las peticiones salientes HttpClient.
    - Añadir el header X-Correlation-ID usando un UUID v4 si no existe previamente.
    - Si la petición responde con status 401, reintentar una sola vez tras renovar el token vía AuthService.refreshToken().
    - NO interceptar peticiones hacia /api/v1/auth/login.
    
    ## Contrato
    - Firma de error devuelta: ApiErrorResponse { code: string; message: string; timestamp: number }.
    

    Cuando un agente lee este archivo antes de tocar el código:

    1. El contexto entra limpio: El LLM no necesita adivinar el nombre del header ni la estrategia de reintento.
    2. Las respuestas son deterministas: El código generado encaja al primer intento con la arquitectura de tu aplicación.
    3. El tiempo de revisión tiende a cero: En lugar de leer 300 líneas de diff intentando adivinar qué pretendía hacer la IA, solo verificas que el código cumple los puntos de la spec.

    Cómo empezar con SDD hoy mismo

    No necesitas instalar un framework complejo ni cambiar la estructura de tu empresa.

    La próxima vez que vayas a pedirle una funcionalidad a tu agente de IA, haz esto:

    1. Escribe un archivo .md rápido en tu proyecto describiendo qué quieres lograr, qué archivos se verán afectados y cuáles son los tipos/interfaces involucrados.
    2. Pásale la spec al agente y pídele: "Revisa esta especificación. Identifica ambigüedades o contradicciones antes de proponer cambios".
    3. Una vez alineados en la especificación, pídele que genere la solución siguiendo las tareas definidas.

    Te aseguro una cosa: escribir esa spec te llevará 4 minutos. Te ahorrará 45 minutos de depuración descontrolada.

    Programar rápido con IA no consiste en teclear prompts más deprisa. Consiste en pensar con claridad antes de pedir el código.


    Si quieres llevar tus habilidades al siguiente nivel, explora los Cursos de Dominicode donde profundizamos en arquitecturas modernas y herramientas de desarrollo. Además, en Dominicode Labs acompañamos a developers a construir productos reales y workflows autónomos asistidos por IA.

    Preguntas frecuentes

    ¿SDD reemplaza a TDD (Test-Driven Development)?

    No, se complementan. SDD define el contrato y las expectativas de alto nivel antes de construir, mientras que TDD asegura la corrección del código a nivel unitario durante la implementación.

    ¿Cuánto tiempo lleva escribir una especificación SDD?

    Para una tarea típica de feature, redactar una spec básica toma entre 3 y 8 minutos. Ese pequeño esfuerzo inicial ahorra habitualmente horas de refactorización y depuración.

    ¿Qué herramientas son ideales para trabajar con Spec-Driven Development?

    SDD es agnóstico a la herramienta, pero brilla especialmente con agentes CLI como Claude Code y AGY, o entornos con contexto profundo como Cursor y Windsurf.

    ¿Es necesario usar SDD para correcciones de bugs pequeñas?

    Para bugs triviales o cambios de una sola línea no es necesario crear una spec completa. SDD es más valioso en tareas que involucran múltiples archivos, lógica de negocio o contratos de interfaz.


    Por Bezael Pérez — Developer senior con más de 15 años de experiencia y fundador de Dominicode.

  • Context Engineering: Cómo estructurar la memoria de tus agentes de IA para eliminar alucinaciones

    Context Engineering: Cómo estructurar la memoria de tus agentes de IA para eliminar alucinaciones

    Hace unas semanas estaba ayudando a un desarrollador senior a configurar su entorno de trabajo con herramientas de IA. Para asegurarse de que el agente no cometiera errores, pegó en la ventana del chat un bloque gigante de 12.000 tokens que incluía la documentación entera del proyecto, 15 reglas de linteo, 4 archivos de tipos y la estructura del árbol de carpetas.

    Cuando le pidió a la IA que implementara un módulo simple, la IA ignoró por completo las reglas situadas en la mitad del texto y generó importaciones obsoletas.

    El desarrollador exclamó frustrado: "¡Le di toda la información en el prompt y aun así alucina!".

    El problema no era la falta de información; era el exceso de ruido mal estructurado. Context Engineering no es escribir mejores prompts (Prompt Engineering). Es la disciplina de diseñar la arquitectura de información que alimenta a la ventana de contexto de los modelos LLM para maximizar la atención del modelo y erradicar las alucinaciones.

    El fenómeno "Lost in the Middle" y la curva de atención

    Los modelos de lenguaje basados en la arquitectura Transformer no leen el texto de la misma manera que los humanos.

    Cuando la ventana de contexto supera los miles de tokens, ocurre un fenómeno estudiado minuciosamente por investigadores conocido como "Lost in the Middle" (Perdido en el medio):

    • La IA presta máxima atención a los primeros tokens del prompt (Primacy Bias), que corresponden habitualmente al System Prompt.
    • La IA presta máxima atención a los últimos tokens recibidos (Recency Bias), que corresponden a la última instrucción del usuario.
    • La información situada en el tercio central de la ventana de contexto sufre una caída drástica de atención, aumentando el riesgo de alucinaciones o instrucciones ignoradas.
    Nivel de Atención del LLM
     ▲
    1.0 ┼──────┐                                 ┌──────┐
        │      │                                 │      │
    0.5 ┤      └───────────┐         ┌───────────┘      │
        │                  │         │                  │
    0.0 ┴──────────────────┴─────────┴──────────────────┴──►
        [System Prompt]    [Zona Central]     [Último Prompt]
          (Alta Atención)  (PERDIDO EN EL MEDIO) (Alta Atención)
    

    Prompt Engineering vs. Context Engineering

    • Prompt Engineering: Se enfoca en el redactado del mensaje. "Escribe una función en TypeScript limpia y responde en formato JSON".
    • Context Engineering: Se enfoca en la gestión dinámica del espacio de memoria. ¿Qué archivos se deben incluir? ¿En qué formato se presentan los datos? ¿Cómo se poda el historial de conversación cuando se sobrecarga?

    Como demostramos en nuestro análisis sobre por qué tu spec falla con un agente de IA, entregar especificaciones ambiguas o mal estructuradas es la razón principal por la que los agentes generan código inservible.

    4 Pilares de Context Engineering para Developers

    1. Etiquetado Semántico con XML y Markdown

    Los modelos LLM avanzados (como Anthropic Claude) han sido entrenados específicamente para interpretar etiquetas XML como delimitadores de contexto. En lugar de enviar texto plano continuo, envuelve la información en secciones etiquetadas:

    <system_instructions>
      Eres un desarrollador Senior en TypeScript. Sigue estrictamente las reglas definidas en <coding_standards>.
    </system_instructions>
    
    <coding_standards>
      - Usa siempre tipos estrictos sin 'any'.
      - Utiliza el patrón Result para manejo de errores.
    </coding_standards>
    
    <context_files>
      <file path="src/types/user.ts">
        export interface User { id: string; email: string; }
      </file>
    </context_files>
    
    <user_request>
      Crea una función para validar el correo de la interfaz User.
    </user_request>
    

    2. Podado Dinámico de Contexto (Context Pruning)

    No arrastres el historial de chat indefinidamente. Si llevas 20 mensajes iterando sobre una funcionalidad, el historial acumulado satura la memoria. Limpia el contexto generando un resumen del estado actual e inicia una sesión limpia con los artefactos actualizados.

    Como analizamos al calcular el coste de subagentes al cambiar de modelo, reducir el volumen de tokens enviados reduce los costes y acelera la velocidad de respuesta.

    3. Graph Engineering (Indexación de Dependencias)

    En lugar de enviarle al agente archivos enteros de 1.000 líneas, utiliza herramientas de indexación que entreguen únicamente las firmas de funciones, interfaces y grafos de dependencias requeridos. Revisa nuestra guía completa de graph engineering para aprender a crear mapas de código precisos.

    4. Separación de Tareas mediante Subagentes

    Delegar sub-tareas a subagentes independientes garantiza que cada subagente trabaje en su propia ventana de contexto de 2.000 tokens hiperenfocada, devolviendo únicamente el resultado consolidado al hilo principal.


    Diseñar el contexto adecuado es lo que transforma a un asistente conversacional genérico en una herramienta de ingeniería precisa y predecible.

    Si quieres aprender a dominar arquitecturas avanzadas de desarrollo asistido por IA, descubre los Cursos de Dominicode. Y si quieres aplicar estas técnicas en proyectos reales de producción junto a desarrolladores senior, súmate a Dominicode Labs.

    Preguntas frecuentes

    ¿Por qué los modelos con ventanas de 1 millón de tokens siguen necesitando Context Engineering?

    Aunque un modelo pueda "procesar" 1 millón de tokens técnicamente, la calidad del razonamiento y la precisión en la recuperación de datos disminuyen a medida que aumenta la ventana. Mantener la información acotada y estructurada garantiza la máxima precisión.

    ¿Cuál es la diferencia entre RAG (Retrieval-Augmented Generation) y Context Engineering?

    RAG es una técnica específica de Context Engineering que utiliza búsquedas semánticas o vectoriales para seleccionar qué fragmentos de información recuperar de una base de datos. Context Engineering engloba la estrategia completa de empaquetado, podado, etiquetado y presentación de esos fragmentos al modelo.

    ¿Es mejor enviar código en formato JSON, XML o Markdown?

    Markdown con bloques de código delimitados por tres acentos graves (“`) y etiquetas XML (<file>, <spec>) es la combinación óptima. Los modelos actuales reconocen esta estructura de forma nativa por la abundancia de repositorios de GitHub en sus datos de entrenamiento.

    ¿Cómo afecta el idioma del contexto a la precisión del modelo?

    Los modelos de lenguaje procesan los tokens de instrucciones en inglés con una ligera ventaja de atención debido a la densidad de datos de entrenamiento. Sin embargo, para la lógica de negocio y comentarios del proyecto en español, mantener el contexto en español estructurado mediante etiquetas XML ofrece resultados excelentes sin pérdida de coherencia.


    Por Bezael Pérez — Developer senior con más de 15 años de experiencia y fundador de Dominicode.

  • Cómo crear skills y subagentes personalizados para automatizar tu flujo diario de desarrollo con IA

    Cómo crear skills y subagentes personalizados para automatizar tu flujo diario de desarrollo con IA

    Cada mañana, durante semanas, me sorprendía a mí mismo haciendo exactamente lo mismo. Abría mi herramienta de IA y pasaba los primeros 10 minutos explicándole la arquitectura de mi proyecto, las normas de linteo de nuestro equipo, qué librerías no debía usar y cómo estructurar los tests unitarios.

    Si cambiaba de conversación o abría un nuevo hilo para otra tarea, tenía que volver a escribirlo todo de nuevo.

    Estaba tratando a los asistentes de inteligencia artificial como un becario que llega nuevo a la oficina cada dos horas y sufre amnesia. Ahí fue cuando me di cuenta de que el verdadero salto de productividad no está en perfeccionar los prompts, sino en construir skills y subagentes personalizados que encapsulen tu conocimiento y el de tu equipo en tu flujo de desarrollo con IA.

    El problema del prompt de 500 líneas en la ventana de contexto

    Muchos desarrolladores intentan solucionar este problema pegando gigantescos bloques de contexto en el system prompt o en archivos de instrucciones globales.

    Eso crea dos problemas graves:

    1. Degradación del contexto: Si sobrecargas la ventana inicial del modelo con reglas que solo aplican a una tarea específica (por ejemplo, cómo migrar la base de datos), el LLM pierde precisión al razonar sobre la tarea actual.
    2. Coste descontrolado de tokens: Cada mensaje que envías vuelve a procesar todo ese system prompt gigante. Como analizamos en nuestro artículo sobre el coste de subagentes al cambiar de modelo, acumular tokens innecesarios encarece y ralentiza drásticamente la ejecución.

    La solución arquitectónica correcta es separar el conocimiento en dos conceptos: Skills (habilidades bajo demanda) y Subagentes (agentes especializados con ventana de contexto aislada).

    ¿Qué es una Skill y cuándo usarla?

    Una Skill es una carpeta de instrucciones y recursos que se activa solo cuando el agente la necesita para resolver una tarea concreta.

    Piensa en una skill como el "manual de procedimientos" para una tarea específica:

    • Crear una nueva spec arquitectónica.
    • Configurar el tracking de analítica.
    • Auditar la accesibilidad UI de una página.
    ---
    name: angular-signals-migration
    description: Guía paso a paso para migrar componentes de RxJS BehaviorSubject a Angular Signals en v22+
    ---
    
    # Instrucciones de Migración
    1. Reemplaza `BehaviorSubject<T>` por `signal<T>`.
    2. Para valores derivados, utiliza `computed()`. No uses `effect()` para modificar estado.
    3. Asegúrate de actualizar la plantilla eliminando el pipe `async`.
    

    Cuando tu agente (como Claude Code o AGY) detecta que tu petición requiere migrar componentes, lee este SKILL.md bajo demanda, aplica las reglas y libera el espacio cuando termina.

    ¿Qué es un Subagente personalizado?

    Un Subagente es un agente secundario que se lanza en una conversación en segundo plano completamente aislada.

    Recibe un rol específico (por ejemplo: Code Reviewer, Database Debugger o SEO Auditor), un conjunto acotado de herramientas y su propia ventana de contexto. Cuando termina su labor, devuelve únicamente el resultado sintetizado al agente principal.

    Al igual que explicamos en nuestro post sobre graph engineering, estructurar la información en nodos especializados evita que la IA se pierda en un laberinto de contexto irrelevante.

    Ejemplo de definición de Subagente

    ---
    name: code-reviewer-senior
    description: Revisa pull requests buscando vulnerabilidades de seguridad, memory leaks y falta de tipos estrictos.
    tools: read_file, grep_search
    ---
    
    # Rol: Senior Code Reviewer
    Eres un auditor de código ultrarreciso. Revisa las líneas modificadas en la PR y evalúa:
    1. ¿Hay algún `any` implícito o explícito en TypeScript?
    2. ¿Se están liberando los subs de observables no finitos?
    3. Devuelve únicamente una lista de hallazgos críticos prioritarios.
    

    Guía paso a paso para crear tu primera Skill

    Para implementar skills en tu repositorio o configuración global de IA:

    1. Estructura el directorio

    Crea una carpeta dentro de .agents/skills/ (o la ruta de configuraciones de tu herramienta):

    .agents/
      skills/
        db-migration/
          SKILL.md
          template.sql
    

    2. Escribe el SKILL.md con Frontmatter claro

    Define en la cabecera YAML el nombre y una descripción precisa de cuándo debe activarse la skill. El agente utilizará la descripción para saber cuándo consultar estas instrucciones.

    3. Mantén los pasos de ejecución atómicos

    Define un flujo paso a paso que el agente pueda verificar en cada etapa antes de continuar.


    El resultado es inmediato: dejas de repetir las mismas explicaciones una y otra vez. Tu equipo comparte la misma carpeta de .agents/ en el repositorio Git, garantizando que todos los desarrolladores (y sus agentes de IA) sigan exactamente los mismos estándares.

    Si deseas ver más sobre la integración de IA en tu organización, revisa nuestra guía sobre cómo formar a tu equipo de desarrollo en IA en 6 semanas.

    Para seguir perfeccionando tu workflow, consulta los Cursos de Dominicode donde profundizamos en desarrollo asistido por IA. Y si buscas construir productos reales en comunidad, te esperamos en Dominicode Labs.

    Preguntas frecuentes

    ¿En qué se diferencia una Skill de un Prompt tradicional?

    Un prompt tradicional se envía manualmente en cada mensaje. Una Skill es modular, vive en el disco como archivo de código y es descubierta y cargada de forma autónoma por la IA solo cuando la tarea lo requiere.

    ¿Puedo compartir mis skills con otros miembros de mi equipo?

    Sí, al almacenar la carpeta .agents/skills/ dentro del propio repositorio de Git, todo el equipo comparte automáticamente las mismas instrucciones y mejores prácticas del proyecto.

    ¿Los subagentes consumen más tokens que una conversación normal?

    Inicialmente, lanzar un subagente consume tokens de inicialización, pero a medio y largo plazo ahorra miles de tokens porque evita arrastrar el historial de chat acumulado de la sesión principal.

    ¿Qué herramientas soportan el uso de Skills y Subagentes?

    Herramientas avanzadas como Claude Code, Google Antigravity (AGY), Cursor y entornos habilitados con arquitecturas de agentes permiten definir e invocar skills y subagentes de forma nativa.


    Por Bezael Pérez — Developer senior con más de 15 años de experiencia y fundador de Dominicode.

  • Cómo conectar Claude Code a tus DBs y APIs mediante MCP (Model Context Protocol)

    Cómo conectar Claude Code a tus DBs y APIs mediante MCP (Model Context Protocol)

    Durante mucho tiempo, la mayor limitación de los asistentes de desarrollo basados en IA no era su capacidad para escribir código, sino su ceguera ante el mundo real.

    Le pedías a la IA que investigara un bug sutil en producción, y el modelo empezaba a inventar tablas que no existían, asumir tipos de columnas equivocados o sugerir llamadas a endpoints obsoletos. Tenías que hacer de "puente humano": copiar la respuesta del terminal, pegarla en el chat, pedirle una SQL, ejecutarla tú en tu cliente de base de datos y pegarle el resultado.

    Ese trabajo manual se acabó. Model Context Protocol (MCP) es el estándar abierto propuesto por Anthropic que permite a asistentes como Claude Code conectarse de forma nativa a tus bases de datos, APIs de staging, repositorios y servicios internos.

    ¿Qué es exactamente Model Context Protocol (MCP)?

    Piensa en MCP (Model Context Protocol) como el estándar USB-C para los modelos de lenguaje.

    Antes de MCP, si querías que un LLM interactuara con Postgres, tu API GraphQL o un canal de Slack, tenías que escribir integraciones ad-hoc y wrappers frágiles para cada herramienta.

    MCP unifica todo bajo una arquitectura cliente-servidor muy simple:

    • Host (o Cliente MCP): Tu entorno de desarrollo o agente (por ejemplo, Claude Code, AGY o Cursor).
    • Servidor MCP: Un proceso ligero que expone herramientas (tools), recursos (resources) y prompts hacia el cliente mediante un protocolo JSON-RPC estándar over stdio o HTTP/SSE.

    Cuando el agente necesita saber qué tablas existen en tu base de datos, llama a la herramienta list_tables expuesta por tu servidor MCP, recibe la respuesta estructurada y actúa en consecuencia sin que tú tengas que mover un dedo.

    Cómo configurar un servidor MCP en Claude Code

    Conectar Claude Code a una base de datos o servicio externo es cuestión de minutos. Puedes usar servidores MCP creados por la comunidad o construir el tuyo propio.

    1. Usar un servidor existente (Ejemplo: PostgreSQL / Supabase)

    Puedes añadir un servidor MCP directamente a la configuración de tu entorno con un comando sencillo:

    claude mcp add postgres npx -y @modelcontextprotocol/server-postgres postgresql://user:pass@localhost:5432/mydb
    

    A partir de ese momento, Claude Code tiene acceso a herramientas seguras como query para inspeccionar esquemas y ejecutar consultas de lectura cuando se lo pidas en lenguaje natural.

    2. Crear tu propio servidor MCP personalizado en TypeScript

    Si tienes una API interna o reglas de negocio propietarias, puedes construir tu propio servidor MCP en TypeScript con muy pocas líneas:

    import { Server } from "@modelcontextprotocol/sdk/server/index.js";
    import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
    import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
    
    const server = new Server(
      { name: "mi-api-interna", version: "1.0.0" },
      { capabilities: { tools: {} } }
    );
    
    // 1. Listar herramientas disponibles para la IA
    server.setRequestHandler(ListToolsRequestSchema, async () => ({
      tools: [{
        name: "buscar_usuario_por_email",
        description: "Busca los datos de un usuario en el entorno de staging por su email",
        inputSchema: {
          type: "object",
          properties: { email: { type: "string" } },
          required: ["email"]
        }
      }]
    }));
    
    // 2. Ejecutar la lógica cuando la IA invoca la herramienta
    server.setRequestHandler(CallToolRequestSchema, async (request) => {
      if (request.params.name === "buscar_usuario_por_email") {
        const { email } = request.params.arguments as { email: string };
        const user = await miApiStaging.getUser(email);
        return { content: [{ type: "text", text: JSON.stringify(user) }] };
      }
      throw new Error("Herramienta no encontrada");
    });
    
    const transport = new StdioServerTransport();
    await server.connect(transport);
    

    Seguridad: Evitando riesgos en producciones reales

    Darle acceso a un agente de IA a tus bases de datos y servicios requiere precauciones claras:

    1. Principio de mínimo privilegio: Configura tus servidores MCP con credenciales de solo lectura para entornos de desarrollo o staging.
    2. Protección contra inyecciones: Tal como explicamos en nuestro análisis sobre inyección indirecta de prompts en agentes de IA, nunca permitas que datos no confiables provenientes de la base de datos o de usuarios modifiquen el comportamiento del agente sin sanitizar.
    3. Control de contexto: Utiliza técnicas de graph engineering para estructurar los datos expuestos por tus herramientas MCP y evitar saturar la memoria del modelo.

    El protocolo MCP cambia drásticamente la relación entre el desarrollador y la IA. Dejas de copiar y pegar respuestas del terminal para convertir a tu agente en un miembro activo del equipo que consulta métricas, ejecuta tests y verifica estados en tiempo real.

    Si quieres llevar tus habilidades al siguiente nivel y dominar la integración de agentes con infraestructuras reales, explora los Cursos de Dominicode. Y si buscas construir proyectos con arquitecturas avanzadas de IA, entra en Dominicode Labs.

    Preguntas frecuentes

    ¿MCP funciona solo con Claude Code o con cualquier cliente de IA?

    MCP es un estándar abierto. Aunque fue creado por Anthropic, puede ser implementado por cualquier cliente, IDE o framework de agentes (como Cursor, Antigravity, VS Code o agentes personalizados).

    ¿Es seguro conectar una base de datos de producción a través de MCP?

    Se recomienda conectar únicamente entornos de desarrollo, staging o réplicas de solo lectura. Para operaciones de escritura en producción, el servidor MCP debe solicitar siempre confirmación explícita del usuario antes de ejecutar cualquier cambio.

    ¿Qué diferencia hay entre una llamada a una API tradicional y un servidor MCP?

    Una llamada a API tradicional requiere que tú programes la petición exacta en tu código. Un servidor MCP le enseña a la IA la firma de la herramienta para que el modelo decida de forma autónoma cuándo y cómo invocarla según el contexto de la conversación.

    ¿Dónde puedo encontrar servidores MCP listos para usar?

    Existen repositorios oficiales y comunitarios con servidores MCP para PostgreSQL, GitHub, Slack, Puppeteer, Brave Search, Google Drive y decenas de servicios populares.


    Por Bezael Pérez — Developer senior con más de 15 años de experiencia y fundador de Dominicode.

  • Grok 4.5, Fable 5 y DeepSeek V4: las cifras que no existen

    Grok 4.5, Fable 5 y DeepSeek V4: las cifras que no existen

    El 26 de julio publiqué una comparativa entre Opus 5, GPT-5.6 y Kimi K3. Diez días después me senté a completarla con los que faltaban: Grok 4.5, Claude Fable 5 y DeepSeek V4 Flash.

    No llegué a escribir esa actualización.

    Me atasqué en el primer paso, el más tonto: abrir el leaderboard oficial de Terminal-Bench 2.1 y copiar las cifras que todo ranking de modelos para programar de agosto de 2026 da por buenas.

    Buena parte de esas cifras no está ahí. No es que estén desactualizadas: es que no aparecen. Ni con ese número, ni en ese puesto.

    Así que este post no es la lista de los tres modelos que me faltaban. Es lo que encontré mientras la buscaba. Si lo que quieres es qué modelo usar para qué trabajo según el coste real por tarea, eso está entero en Opus 5 vs GPT-5.6 vs Kimi K3, la comparativa base que este post completa.

    El leaderboard oficial de Terminal-Bench 2.1, sin intermediarios

    Qué es Terminal-Bench 2.1 y qué puntúa en realidad

    Terminal-Bench 2.1 es un benchmark que mide si un sistema de IA completa tareas reales de desarrollo dentro de una terminal —instalar dependencias, ejecutar los tests, arreglar lo que falla—, no si el modelo escribe código elegante. Cada fila del leaderboard puntúa tres cosas a la vez: el modelo, el harness (el agente que lo envuelve: Claude Code, Codex, Terminus 2, Cursor CLI) y el effort (cuánto cómputo se le permite gastar por tarea). Cambia cualquiera de las tres y cambia el número.

    Esto es lo que hay en el leaderboard público de Terminal-Bench, consultado el 5 de agosto de 2026. Corto en la fila 11 por espacio:

    # Agente + modelo Score Effort
    1 Claude Code + Fable 5 83,8% ± 1,2% xhigh
    2 Codex + GPT-5.5 83,1% xhigh
    3 Terminus 2 + Fable 5 80,4% high
    4 Cursor CLI + Grok 4.5 79,3% high
    5 Claude Code + Opus 4.8 78,9% high
    6 Codex + GPT-5.6 Terra 78,4% max
    7 Terminus 2 + GPT-5.5 78,0% xhigh
    8 mini-SWE-agent + Muse Spark 1.1 76,2% xhigh
    9 Codex + GPT-5.6 Luna 75,7% max
    10 Claude Code + Sonnet 5 74,6% high
    11 Terminus 2 + Gemini 3 Pro 73,9% high

    Lee la segunda columna otra vez. No dice "modelo". Dice agente y modelo. Y la última añade el effort.

    Ahora mira lo que no está.

    Claude Opus 5 no aparece. El Opus de esa lista es el 4.8, con 78,9%. Llevo semanas viendo circular un "Opus 5: 89,1% en Terminal-Bench 2.1" por blogs agregadores y no he encontrado de dónde sale. No digo que sea falso: digo que no está en la fuente oficial, que no lo puedo verificar y que quien lo publicó tampoco enlazó a nada.

    "GPT-5.6 Sol, 89,5%" tampoco aparece. Las variantes de GPT-5.6 que sí figuran son Terra (78,4%) y Luna (75,7%), ambas por debajo de GPT-5.5 en Codex, que marca 83,1%. En la única medición pública que conozco, la 5.6 no supera a la 5.5 programando en terminal.

    Kimi K3 tampoco aparece. Eso no lo convierte en mal modelo —sigo pensando lo que escribí en si te conviene Kimi K3 para tu agente—, solo significa que ahí no hay ejecución publicada. Y el 88,3 que reporta Moonshot es autoreportado, exactamente igual que el de DeepSeek que verás más abajo.

    Un benchmark ausente no es un suspenso. Es un dato que no existe. Y un dato que no existe no se cita.

    Por qué el mismo modelo puntúa distinto: el harness pesa tanto como el modelo

    El mismo modelo cambia de puesto según el agente que lo envuelve: Fable 5 saca 83,8% con Claude Code y 80,4% con Terminus 2. Esos 3,4 puntos son toda la distancia entre el puesto 1 y el puesto 3 del leaderboard.

    Vuelve a la tabla y búscalo dos veces. Está ahí.

    Lo que separa esas dos filas es el andamiaje que rodea al modelo, no el modelo. GPT-5.5 hace lo mismo: 83,1% en Codex, 78,0% en Terminus 2.

    Por eso "cuál es el mejor modelo para programar" es una pregunta mal formulada. Terminal-Bench no puntúa modelos, puntúa sistemas.

    Es la idea que sostiene todo lo que enseño en Construye con IA: el resultado sale del sistema completo, no del modelo que eliges en un desplegable. Cuando alguien te cuenta que su equipo va más rápido "porque usa X modelo", casi siempre lo que cambió fue el harness.

    Cuánto cuesta Grok 4.5 de verdad: el precio se dobla a partir de 200K tokens

    Grok 4.5 es el mejor situado de los tres que faltaban: puesto 4 del leaderboard de Terminal-Bench 2.1 con Cursor CLI y 79,3%, por delante de Opus 4.8 y de las dos variantes medidas de GPT-5.6. Este es el que peor llevo haberme dejado fuera en julio.

    En el Intelligence Index de Artificial Analysis marca 54 puntos en el corte del 5 de agosto de 2026. No te doy su puesto, y el motivo es el propio post: en la comparativa de julio yo mismo publiqué 58,9 para GPT-5.6 Sol y 57 para Kimi K3 en ese mismo índice. Con esos números, 54 no es un cuarto puesto.

    Es una puntuación. El puesto depende del corte y del subconjunto de modelos que estés mirando, y por eso no vale como argumento.

    El titular comercial es 500K de contexto a $2 el millón. Es cierto a medias, y la mitad falsa está en la documentación de xAI:

    Tamaño del prompt Entrada / millón Salida / millón
    Menos de 200K tokens $2 $6
    200K tokens o más $4 $12

    El precio se dobla exactamente cuando empiezas a usar el contexto por el que compraste el modelo.

    Con un agente que acumula ficheros, salidas de tests y logs, cruzar los 200K no es un caso raro: es el martes por la tarde. Y no lo cruzas tú de forma consciente, lo cruza el agente a mitad de sesión.

    La parte buena: el input cacheado cuesta $0,50 por millón, un 75% menos que la tarifa de $2 y un 87,5% menos que la de $4. Si tu agente reenvía el mismo contexto en cada turno —y casi todos lo hacen—, el ahorro real está ahí, no en la tarifa de lista.

    En el Coding Agent Index de Artificial Analysis, Grok 4.5 saca 76 en el harness de Grok Build, empatado con GPT-5.5 en Codex y justo por debajo de Fable 5 en Claude Code. Otra vez modelo y harness moviéndose juntos.

    Ficha rápida: 500K de contexto, knowledge cutoff el 1 de febrero de 2026, disponible como grok-4.5 en Grok Build, en Cursor para todos los planes y en la consola de xAI.

    DeepSeek V4 Flash 0731: barato de verdad, medido por ellos mismos

    Salió el 31 de julio con pesos abiertos y licencia MIT, la misma categoría de modelo abierto que repasé en el ranking de LLM locales de 2026. MoE disperso, 13B de parámetros activos sobre 284B totales, 1.048.576 tokens de contexto y hasta 65.536 de salida.

    $0,14 de entrada y $0,28 de salida por millón. Con cache-hit, $0,0028. Fable 5 cuesta más de 70 veces más por token de entrada: eso no es una diferencia de gama, es otra categoría de decisión.

    En el Intelligence Index marca 50 puntos, cifra seria para lo que cuesta.

    Ahora la parte incómoda, que es el motivo por el que este modelo está en el post.

    DeepSeek reporta 82,7 en Terminal-Bench 2.1 —subiendo 20,9 puntos desde el 61,8 de su Preview— y 54,4 en DeepSWE. Con 82,7 entraría tercero en el leaderboard de Terminal-Bench 2.1, a cuatro décimas de GPT-5.5 en Codex (83,1%) y por detrás de Fable 5 (83,8%).

    Esa cifra es autoreportada por DeepSeek en su propia tabla y no aparece en el leaderboard oficial de tbench.ai.

    No acuso a nadie de mentir. Señalo una distinción que muchos rankings borran sin avisar: un número autoreportado y uno medido de forma independiente no valen lo mismo, aunque los dos lleven decimal.

    El fabricante corre el benchmark en sus condiciones, con su harness y su criterio de cuándo parar.

    No hay nada ilegítimo en eso. Simplemente no es comparable con una entrada verificada, y mezclar las dos cosas en la misma tabla es lo que convierte una comparativa en marketing.

    Si vas a probar DeepSeek V4 Flash, pruébalo por el precio y la licencia MIT, que son hechos comprobables. No por el 82,7.

    Y recuerda que el precio por token no es el gasto: un modelo barato que necesita el doble de turnos para cerrar la misma tarea sale caro, un efecto que desarrollé en por qué sube el coste de los subagentes al cambiar de modelo.

    Cuánto cuesta Fable 5: lidera el leaderboard y su tarifa miente a su favor

    Fable 5 es el número 1 real: 83,8% ± 1,2% con Claude Code y effort xhigh. GA desde el 9 de junio de 2026, 1M de contexto, 128k de salida máxima.

    También es el más caro por bastante: $10 de entrada y $50 de salida por millón, según la documentación de Anthropic. Adaptive thinking siempre activo —no se puede apagar— y una latencia que su propia doc califica de "slower".

    El dato que casi nadie cita es este: Fable 5 usa el tokenizer que llegó con Opus 4.7, y el mismo texto produce alrededor de un 30% más de tokens que en modelos anteriores a esa versión. Es el mismo mecanismo que expliqué con el sobrecoste de escribir en español: la tarifa por millón no se mueve, pero el número de millones sí.

    Haz la cuenta. Frente a un modelo con el tokenizer viejo, sus $10 se comportan como $13 y sus $50 como $65 sobre el mismo texto.

    Su coste efectivo es peor que su tarifa de lista. Es la tesis del post de julio otra vez: el precio que pagas no es el que aparece en la página de pricing.

    Mythos 5: recomendado en rankings, imposible de contratar

    Mythos 5 tiene las mismas specs y el mismo precio que Fable 5. Y da igual, porque no lo puedes usar: no hay alta self-serve, es por invitación dentro de Project Glasswing, orientado a ciberseguridad defensiva.

    Aun así lo he visto en varias listas de "mejores modelos para programar de agosto de 2026", con su fila, su precio y su recomendación de uso, como si bastara una tarjeta.

    Cuando un ranking te recomienda un producto que no está a la venta, ya sabes cuánto lo ha probado quien lo escribió.

    Precios por millón de tokens en agosto de 2026: las cifras que sí puedes comprobar

    Estos son los precios de lista publicados por cada proveedor a 5 de agosto de 2026, en dólares por millón de tokens:

    Modelo Entrada / millón Salida / millón Contexto
    Claude Fable 5 $10 $50 1M
    Claude Opus 5 $5 $25 1M
    GPT-5.6 Sol $5 $30 1,05M
    Claude Sonnet 5 $3 ($2 intro hasta el 31 ago 2026) $15 ($10 intro) 1M
    Kimi K3 $3 $15 1M
    Grok 4.5 (prompt < 200K) $2 $6 500K
    Grok 4.5 (prompt ≥ 200K) $4 $12 500K
    DeepSeek V4 Flash 0731 $0,14 $0,28 1M

    Una nota de honestidad, porque predico con el ejemplo o no predico: al recopilar esto me encontré a Grok 4.5 con 54 puntos presentado como cuarto y a DeepSeek V4 Flash con 50 presentado como tercero. Las dos cosas no pueden ser ciertas a la vez.

    Son cortes distintos de un índice que se recalcula constantemente. Por eso en este post doy puntuaciones y fechas, y no puestos: el puesto caduca antes de que le des a publicar.

    Cómo verificar un ranking de modelos para programar en 3 filtros

    Abre el ranking que tengas a mano ahora mismo y pásale tres filtros.

    1. Busca el enlace a la fuente. Si la cifra no apunta a un leaderboard o a una doc oficial, trátala como rumor.
    2. Comprueba si el número es autoreportado. Un score en la web del fabricante y uno en tbench.ai no son la misma clase de dato.
    3. Mira si nombra el harness y el effort. Si dice "Fable 5: 83,8%" sin decir Claude Code y xhigh, quien lo escribió no ha leído la tabla que cita.

    Te quedará mucho menos ranking del que tenías. Bien.

    Y una decisión práctica que sí puedo defender: elige el modelo al final, no al principio. Define primero qué tarea automatizas, con qué agente y con qué criterio de "terminado". Es la lógica del libro de Spec-Driven Development, y aquí se nota más que en ningún sitio: con la spec escrita, cambiar de modelo es una variable de entorno y una medición; sin ella, es fe.

    Mi conclusión cabe en una frase: no existe el mejor modelo para programar, existe el mejor sistema, y el único número que decide tu caso es el que midas tú.

    En Dominicode Labs hacemos justo eso: pasar los modelos nuevos por tareas reales, con la factura y los logs delante, antes de meterlos en producción.

    Preguntas frecuentes sobre los modelos para programar de agosto de 2026

    ¿Qué modelo lidera el leaderboard de Terminal-Bench 2.1 en agosto de 2026?

    En el leaderboard oficial de Terminal-Bench 2.1 el primer puesto es Claude Code con Fable 5 (83,8%), seguido de Codex con GPT-5.5 (83,1%). Fíjate en que ambos resultados nombran un agente y un nivel de esfuerzo, no solo un modelo.

    El mismo Fable 5 baja a 80,4% con Terminus 2. Liderar un leaderboard no es lo mismo que ser el modelo que te conviene: si lo que buscas es la recomendación por tipo de trabajo —agente largo, repo grande, volumen alto—, está desarrollada en Opus 5 vs GPT-5.6 vs Kimi K3.

    ¿Cuánto cuesta realmente Grok 4.5?

    $2 de entrada y $6 de salida por millón de tokens mientras el prompt se mantenga por debajo de 200K tokens. A partir de 200K, la tarifa pasa a $4 y $12: se dobla.

    El input cacheado cuesta $0,50 por millón: un 75% menos que la tarifa de $2 y un 87,5% menos que la de $4 de los prompts largos. Ahí está el ahorro real en un agente que reenvía el mismo contexto en cada turno.

    ¿Es fiable el 82,7 de DeepSeek V4 en Terminal-Bench 2.1?

    Es una cifra autoreportada por DeepSeek en su propia tabla, no una entrada verificada del leaderboard oficial de tbench.ai. Ahí no aparece.

    Eso no significa que sea falsa: significa que no ha pasado por una medición independiente y que no es comparable con la tabla oficial. Lo comprobable de ese modelo es su precio ($0,14 / $0,28 por millón) y su licencia MIT con pesos abiertos.

    ¿Por qué Claude Opus 5 no aparece en el leaderboard de Terminal-Bench 2.1?

    Porque no hay una ejecución publicada de Opus 5 en el leaderboard oficial. El Opus que sí figura es el 4.8, con 78,9% usando Claude Code.

    Las cifras de "Opus 5 al 89,1%" que circulan por blogs agregadores no están en la fuente primaria y no he podido verificarlas. Ausencia de resultado no equivale a mal resultado: equivale a que no hay medición pública.

    ¿Merece la pena Fable 5 a $10 / $50 por millón?

    Sale a cuenta cuando un fallo del modelo te cuesta más de una hora de revisión humana; para volumen alto de tareas repetitivas, casi nunca.

    Hay además un matiz que empeora el cálculo: Fable 5 usa el tokenizer introducido con Opus 4.7, que genera alrededor de un 30% más de tokens sobre el mismo texto que los modelos anteriores a esa versión. Su coste efectivo es peor que su tarifa.

    ¿Qué diferencia hay entre un score autoreportado y uno del leaderboard oficial?

    Un score autoreportado lo ejecuta el propio fabricante, con su harness, su nivel de esfuerzo y su criterio de cuándo dar una tarea por terminada. Uno del leaderboard oficial lo ejecuta un tercero con condiciones fijas e iguales para todos.

    Los dos llevan decimales y parecen el mismo tipo de dato, pero no son comparables. Mezclarlos en la misma tabla, sin marcar cuál es cuál, es lo que convierte una comparativa en marketing.

    ¿DeepSeek V4 Flash es de código abierto?

    Tiene pesos abiertos y licencia MIT desde el 31 de julio de 2026. Es un MoE disperso con 13B de parámetros activos sobre 284B totales, 1.048.576 tokens de contexto y hasta 65.536 de salida.

    La licencia y el precio ($0,14 de entrada y $0,28 de salida por millón) son los dos hechos comprobables del modelo. Su 82,7 en Terminal-Bench 2.1 no lo es: es autoreportado.

    ¿Puedo usar Claude Mythos 5 en mi proyecto?

    No, salvo que tengas invitación. Comparte specs y precio con Fable 5, pero solo está disponible dentro de Project Glasswing, orientado a ciberseguridad defensiva, y no tiene alta self-serve.

    Si lo ves recomendado en un ranking de modelos para programar, ese ranking no lo ha probado.


    Por Bezael Pérez — Developer senior con más de 15 años de experiencia y fundador de Dominicode.

  • Medir el consumo de tokens de un agente: en qué se te van

    Medir el consumo de tokens de un agente: en qué se te van

    El mes pasado revisé el agente de un cliente. Se comía unos 340 dólares al mes de API y el equipo quería bajarlo.

    Lo primero que hicieron fue lo que hace todo el mundo: recortar el system prompt. Lo dejaron en la mitad, de 1.800 tokens a 900.

    El ahorro real fue del 1,2% por llamada.

    No porque recortar el prompt sea mala idea. Porque nadie se había parado a medir el consumo de tokens antes de tocar nada. El system prompt era el 2,3% de cada petición. El 67% se lo comían los resultados de las herramientas, que nadie había mirado.

    Llevo meses viendo la misma escena. Hay una biblioteca entera de trucos para gastar menos —caching, recortes, modelos más baratos— y casi nadie tiene el paso previo: saber en qué se le van los tokens. Optimizan a ciegas y aciertan por casualidad.

    Este post es ese paso previo.

    Un agente no gasta tokens: reenvía tokens

    La confusión empieza aquí. La gente piensa en "el prompt" como si fuera una cosa que mandas una vez.

    En un agente, cada input que envías se reparte en cuatro bloques:

    1. System prompt. Tus instrucciones. Fijo, se manda igual en cada llamada.
    2. Definiciones de herramientas. Nombres, descripciones y JSON Schema de cada tool, más el system prompt interno que la API añade para habilitar tool use — en Claude Opus 5 son 286 tokens con tool_choice: auto, y 406 con any o tool. También fijo.
    3. Historial de la conversación. Todos los turnos previos: lo que dijo el usuario, lo que respondió el modelo, cada bloque tool_use que emitió.
    4. Resultados de herramientas. El contenido de cada tool_result: el JSON que devolvió tu API, el fichero que leyó, los 40 kB de HTML que trajo el scraper.

    Los dos primeros son constantes. Los dos últimos crecen.

    Y crecen de la peor manera posible, porque la Messages API no tiene estado. En el turno 12 no mandas el turno 12: mandas los turnos 1 a 12 otra vez, enteros, incluida la respuesta de 8.000 tokens que devolvió aquella herramienta en el turno 3 y que ya nadie va a volver a leer.

    Ahí está el efecto que casi nadie ve. Si cada turno añade d tokens al contexto, el total de una sesión de n turnos no crece con n, crece con n²/2. En cristiano: duplicar los turnos de tu agente no duplica el coste, lo multiplica por casi cuatro.

    Un ejemplo con números redondos. Base fija (system + tools) de 6.000 tokens, y cada turno añade otros 6.000 entre respuesta del modelo y resultado de herramienta. La columna de la derecha es el contenido único: el contexto que llega a ver el último turno, a 6.000 tokens por turno.

    Sesión Input acumulado Contenido único
    6 turnos 126.000 tokens 36.000 tokens
    12 turnos 468.000 tokens 72.000 tokens
    24 turnos 1.800.000 tokens 144.000 tokens

    En la sesión de 12 turnos pagas 468.000 tokens de input para procesar 72.000 tokens de material distinto. Seis veces y media. Con Claude Opus 5 a 5 $/millón de input, esa sesión te cuesta 2,34 dólares solo en entrada.

    Si tienes esto claro, ya sabes por qué a aquel equipo recortar el system prompt le ahorró un 1,2%.

    El desglose de un turno real

    Esto es lo que salió al medir el turno 12 de aquel agente de research:

    Componente Tokens % del input
    System prompt 1.800 2,3%
    Definiciones de herramientas (6 tools) 4.200 5,4%
    Historial de la conversación 19.400 24,9%
    Resultados de herramientas 52.600 67,4%
    Total input 78.000 100%

    Mira la última columna y haz las cuentas tú mismo.

    Partir el system prompt por la mitad ahorra 900 tokens: el 1,2% de la llamada. Meter un limit en la query de la base de datos y recortar un 40% los resultados de herramientas ahorra 21.040 tokens: el 27%.

    Mismo esfuerzo de ingeniería. Más de veinte veces más impacto.

    Este desglose no es universal, y ese es justo el punto. Un chatbot de soporte sin herramientas tiene el reparto invertido. Un agente de código con MCP servers cargados puede tener 30.000 tokens solo en definiciones de tools. Por eso el número que importa es el tuyo, no el mío.

    Cómo medir el consumo de tokens de un agente

    Medir el consumo de tokens de un agente es atribuir cada token de input a uno de los cuatro bloques que lo componen —system prompt, definiciones de herramientas, historial y resultados de herramientas— para saber qué porcentaje del gasto genera cada uno. La API de Anthropic da dos instrumentos para hacerlo: uno mira hacia atrás y otro mira hacia delante.

    1. El campo usage de cada respuesta

    Cada respuesta de la Messages API trae un objeto usage. Registra los cuatro campos, siempre, desde el primer día:

    import Anthropic from '@anthropic-ai/sdk';
    import type { MessageCreateParamsNonStreaming } from '@anthropic-ai/sdk/resources/messages';
    
    const client = new Anthropic();
    
    interface RegistroUso {
      ts: string;
      sesion: string;
      turno: number;
      input: number;
      output: number;
      cacheWrite: number;
      cacheRead: number;
    }
    
    const registro: RegistroUso[] = [];
    
    export async function llamada(
      params: MessageCreateParamsNonStreaming,
      meta: { sesion: string; turno: number },
    ) {
      const res = await client.messages.create(params);
      const u = res.usage;
    
      registro.push({
        ts: new Date().toISOString(),
        sesion: meta.sesion,
        turno: meta.turno,
        input: u.input_tokens,
        output: u.output_tokens,
        cacheWrite: u.cache_creation_input_tokens ?? 0,
        cacheRead: u.cache_read_input_tokens ?? 0,
      });
    
      return res;
    }
    

    Aquí hay una trampa que se traga a mucha gente. input_tokens no son todos los tokens que enviaste. Son solo los que van después del último punto de corte de caché. La documentación de Anthropic lo define así:

    total_input = cache_read_input_tokens + cache_creation_input_tokens + input_tokens
    

    Si activas prompt caching y sigues graficando input_tokens a secas, verás una caída espectacular que no significa nada. No has reducido el contexto: lo has movido de columna.

    El coste real se calcula con las tres columnas y sus precios respectivos. Para Claude Opus 5:

    const PRECIO_OPUS_5 = {
      input: 5 / 1_000_000,
      output: 25 / 1_000_000,
      cacheWrite5m: 6.25 / 1_000_000,
      cacheRead: 0.5 / 1_000_000,
    } as const;
    
    export const costeUSD = (r: RegistroUso) =>
      r.input * PRECIO_OPUS_5.input +
      r.output * PRECIO_OPUS_5.output +
      r.cacheWrite * PRECIO_OPUS_5.cacheWrite5m +
      r.cacheRead * PRECIO_OPUS_5.cacheRead;
    

    Precios verificados en la documentación de pricing de Anthropic en agosto de 2026. Si usas otro modelo, cambia la tabla — no los copies de un post de hace seis meses.

    2. El endpoint de conteo para atribuir por componente

    usage te da el total. No te dice cuánto pesa cada bloque. Para eso está /v1/messages/count_tokens, que en el SDK de TypeScript es client.messages.countTokens().

    Acepta los mismos bloques de entrada que messages.createmodel, system, tools, messages— y devuelve { input_tokens: number }. No acepta max_tokens. Es gratis y tiene su propio límite de peticiones, separado del de generación: 2.000 RPM en el tier Start.

    La técnica es medir por diferencias:

    import type { MessageParam, Tool } from '@anthropic-ai/sdk/resources/messages';
    
    // el mismo client del primer bloque
    const MODEL = 'claude-opus-5';
    const PING: MessageParam[] = [{ role: 'user', content: '.' }];
    
    const contar = async (p: {
      system?: string;
      tools?: Tool[];
      messages: MessageParam[];
    }) => (await client.messages.countTokens({ model: MODEL, ...p })).input_tokens;
    
    /** Sustituye el contenido de cada tool_result por un carácter. */
    function vaciarToolResults(messages: MessageParam[]): MessageParam[] {
      return messages.map((m) => {
        if (typeof m.content === 'string') return m;
        return {
          ...m,
          content: m.content.map((b) =>
            b.type === 'tool_result' ? { ...b, content: '.' } : b,
          ),
        };
      });
    }
    
    export async function desglosar(input: {
      system: string;
      tools: Tool[];
      messages: MessageParam[];
    }) {
      const [piso, conSystem, conTools, sinResultados, total] = await Promise.all([
        contar({ messages: PING }),
        contar({ system: input.system, messages: PING }),
        contar({ tools: input.tools, messages: PING }),
        contar({ ...input, messages: vaciarToolResults(input.messages) }),
        contar(input),
      ]);
    
      const system = conSystem - piso;
      const tools = conTools - piso;
      const toolResults = total - sinResultados;
    
      return {
        system,
        tools,
        toolResults,
        historial: total - system - tools - toolResults - piso,
        piso, // andamiaje fijo de la API: sin esta fila, las otras cuatro no suman el total
        total,
      };
    }
    

    Cinco llamadas en paralelo y tienes tu tabla. Lánzalo contra una conversación real serializada de producción, no contra un caso de prueba de tres turnos.

    Tres avisos. El conteo es una estimación y puede desviarse ligeramente del cobro real. El . con el que sustituyes cada tool_result cuenta como token, así que toolResults sale un pelín corto y esa diferencia se te va a historial. Y el endpoint responde con el tokenizador del model que le pases: los modelos desde Opus 4.7 usan uno nuevo que produce en torno a un 30% más de tokens para el mismo texto, así que un conteo hecho contra un modelo viejo no sirve para presupuestar uno nuevo. El idioma añade su propia variación sobre esa base: en español el mismo texto tokeniza un 26% más caro.

    Los cuatro pasos, en orden

    1. Serializa una conversación real de producción, de 10 turnos o más, a un objeto { system, tools, messages }.
    2. Registra el usage de cada llamada con la sesión y el turno como etiquetas.
    3. Pasa esa conversación por desglosar() y obtén los cuatro componentes.
    4. Ordena las filas por porcentaje y ataca solo la primera.

    Qué hacer con cada hallazgo

    Ya tienes el número. Ahora el diagnóstico. Cada fila de la tabla apunta a una intervención distinta:

    Si el bloque fijo (system + tools) es grande pero cache_read_input_tokens sale en cero, no tienes un problema de tamaño, tienes uno de caché. Estás pagando 5 $/millón por reprocesar en cada turno lo mismo que podrías estar leyendo a 0,50 $. Es la palanca con mejor ratio impacto/esfuerzo de esta lista y la expliqué entera en prompt caching en la API de Claude.

    Con los números de antes: de esos 468.000 tokens de input, con la caché refrescándose en cada turno solo 72.000 se escriben (a 6,25 $/millón) y los otros 396.000 se leen (a 0,50 $/millón). La sesión pasa de 2,34 dólares a 0,65. Un 72% menos, sin tocar una sola instrucción.

    Si el system prompt sí es el problema de verdad —pasa, sobre todo dentro de herramientas que inyectan contexto por su cuenta—, entonces sí toca recortar. En Claude Code buena parte de ese peso no lo has escrito tú, y se quita con un flag: lo cuento en el flag que recorta el system prompt dinámico.

    Si el contenido está en español, el desglose ya viene con un recargo de fábrica. El mismo texto tokeniza alrededor de un 26% más caro que en inglés, y eso afecta a tus prompts, a tus tool results y a la salida del modelo. Antes de recortar nada, léete el recargo del tokenizador en español: a veces la optimización correcta es escribir el system prompt en inglés y responder en español.

    Si lo que se disparó es el número de turnos, tu problema no está en ninguna fila de la tabla: está en cuántas veces la construyes. Ojo especialmente después de cambiar de modelo, porque cuánto delega es una propiedad del modelo y se mueve entre versiones — el mecanismo completo está en por qué sube el coste de los subagentes al cambiar de modelo.

    Y si los resultados de herramientas son el 60-70%, como en el caso que abre este post, tienes tres salidas por orden de esfuerzo: paginar y filtrar en tu propia tool antes de devolver nada, resumir los resultados antiguos, o dejar que la API vaya limpiando los tool results caducados.

    Lo último se hace con la estrategia clear_tool_uses_20250919 de context editing, que todavía está en beta. Elegir entre las tres es exactamente el trabajo que describo en gestión estratégica del contexto.

    Un apunte sobre la primera opción, que es la que más gente se salta. Si tu herramienta devuelve el objeto completo de la base de datos porque "por si acaso el modelo lo necesita", estás pagando por cada campo en cada turno restante de la sesión. Definir el contrato de salida de cada tool con un esquema estricto —y devolver solo eso— es una decisión de coste, no de estilo. Un esquema de salida con Zod en la frontera de cada tool tira los campos que no declaraste antes de que lleguen al contexto.

    Empieza por la tabla

    No apliques ni un truco de ahorro esta semana.

    Coge una conversación real de producción, pásala por desglosar() y monta la tabla de cuatro filas. Eso es todo el trabajo de medir el consumo de tokens de un agente: media hora. Y lo más probable es que el mayor porcentaje esté donde no lo esperabas, porque el componente que más pesa suele ser el que nadie mira: el que se genera solo.

    Después optimiza. En ese orden.

    Instrumentar el gasto antes de tocarlo es parte del mismo hábito que enseño en Construye con IA: decidir con datos en lugar de con intuición, también cuando lo que decides es dónde recortar. Y si quieres ver desgloses de agentes reales, con sus facturas y sus tablas delante, eso lo hacemos en Dominicode Labs.

    Preguntas frecuentes sobre el consumo de tokens

    ¿Por qué mi factura sube si no he cambiado el prompt?

    Porque en un agente el coste no depende solo del prompt, depende de cuántos turnos dura cada sesión. El historial se reenvía entero en cada llamada, así que el gasto crece de forma cuadrática con el número de turnos: pasar de 12 a 24 turnos multiplica el input acumulado por casi cuatro, no por dos. Si tu agente empezó a necesitar más iteraciones para cerrar la misma tarea, la factura sube sin que hayas tocado una línea.

    ¿El endpoint de conteo de tokens cuesta dinero?

    No. /v1/messages/count_tokens es gratuito. Sí tiene límite de peticiones por minuto según tu tier —2.000 RPM en Start, 4.000 en Build y 8.000 en Scale—, pero es un límite independiente del de generación de mensajes: usar uno no consume el cupo del otro. No hay excusa de coste para no medir.

    ¿Sirve un tokenizador local como tiktoken para medir esto?

    Para una estimación rápida y offline, vale. Para presupuestar, no. Anthropic no publica su tokenizador y el conteo depende del modelo concreto: los modelos desde Claude Opus 4.7 usan un tokenizador nuevo que genera alrededor de un 30% más de tokens para el mismo texto que los anteriores. Un tokenizador de otra familia de modelos te dará un número que no se parece al que te van a cobrar.

    ¿Por qué input_tokens baja tanto al activar el prompt caching?

    Depende de qué estés midiendo. input_tokens cuenta solo lo que va después del último punto de corte de caché, así que activar caching hace que ese número se desplome sin que hayas reducido el contexto ni un token. Para saber lo que realmente enviaste tienes que sumar cache_read_input_tokens y cache_creation_input_tokens. Grafica siempre las tres series juntas, nunca input_tokens solo.

    ¿Cada cuánto hay que volver a medir el consumo de tokens?

    Cada vez que cambies de modelo, añadas o quites herramientas, o toques lo que devuelve una tool. Los tres modifican el reparto. Lo eficiente es no repetirla a mano: deja el registro de usage corriendo en producción con la sesión y el turno como etiquetas, y el desglose por componente pásalo cuando el coste medio por sesión se salga de su rango normal.

    ¿Y si la conclusión es que necesito menos turnos, no menos tokens?

    Es la conclusión más común y la más incómoda, porque no se arregla con un flag. Un agente que tarda 20 turnos en algo que debería resolver en 6 casi siempre tiene un problema de definición de la tarea, no de contexto. Ahí la palanca no es técnica: es escribir mejor qué tiene que hacer antes de dejarlo correr, que es de lo que va el libro de Spec-Driven Development.


    Por Bezael Pérez — Developer senior con más de 15 años de experiencia y fundador de Dominicode.

  • Por qué tu spec falla con un agente de IA: 7 fallos y su arreglo

    Por qué tu spec falla con un agente de IA: 7 fallos y su arreglo

    La spec tenía 900 palabras, títulos bien puestos, listas numeradas y hasta un diagrama. El agente la leyó entera y construyó otra cosa.

    El dev que me la pasó estaba convencido de que el problema era el modelo. Probó con otro. Mismo resultado.

    Le hice una sola pregunta: cuando el agente termine, ¿qué comando ejecutas para saber si lo ha hecho bien?

    Silencio. No había ninguno.

    Ese silencio es el diagnóstico completo. La mayoría de las specs que fallan —también las de quien ya aplica Spec-Driven Development a diario— no fallan por estar mal escritas. Fallan por no ser verificables. Y una spec que no se puede comprobar no es una especificación: es una carta de intenciones. Un agente no ejecuta intenciones.

    El SDD no consiste en redactar un documento bonito antes de programar — de eso hablé cuando expliqué por qué el Spec-Driven Development evita el caos. Consiste en escribir un contrato que una máquina pueda dar por cumplido o por incumplido, sin que tú tengas que opinar.

    Esto no es otro tutorial de redacción. Para la estructura desde cero ya tienes la anatomía de una spec para Claude Code. Esto es el diagnóstico de la spec que ya escribiste y no funcionó.

    Siete fallos, ordenados por lo que más veo. Los cinco primeros los detectas leyendo el documento. Los dos últimos, no.

    Busca tu síntoma: por qué tu spec falla con un agente de IA

    Lo que ves cuando el agente termina El fallo en la spec El arreglo
    Dice "está hecho" y no sabes si está hecho Criterios de éxito no comprobables Un comando debajo de cada criterio
    Hace las cosas como las hace todo el mundo, no como tu repo Ambigüedad sin marcar Señala el fichero que manda
    Toca ficheros que nadie le pidió No hay límites Sección "Fuera de alcance"
    Ignora la mitad de tus instrucciones técnicas Mezcla el qué con el cómo Cierra el qué, deja el cómo abierto
    catch vacíos y fallos que devuelven 200 Solo describe el camino feliz Tabla de estados de error
    Empieza bien y se desmadra a la mitad La spec es demasiado grande Pártela por unidad verificable
    Cumple la spec, pero la spec ya no es verdad Spec desactualizada Vive en el repo o se borra

    Fallo 1: criterios de éxito que nadie puede comprobar

    Un criterio de éxito es comprobable cuando existe un comando que lo declara cumplido o incumplido sin que nadie opine. Este es el fallo padre: los otros seis son variaciones suyas.

    Mira esta spec. La he leído con distintos nombres decenas de veces:

    ## Feature: listado de productos
    
    Endpoint para listar el catálogo.
    Tiene que ser rápido y soportar filtros.
    La respuesta debe ser consistente con el resto de la API.
    Gestionar bien los errores.
    

    Cuatro frases. Tres deseos y un título. ¿Rápido comparado con qué? ¿Consistente con cuál de los catorce endpoints que ya tienes? ¿Gestionar bien es devolver un 400 o un 422?

    Ninguna de esas preguntas la puede responder el agente ejecutando algo. Así que las responde inventando, y tú te enteras después.

    Ahora la misma feature escrita para que se pueda comprobar:

    ## Feature: GET /products
    
    ### Criterios de aceptación
    1. `GET /products?limit=20` devuelve 200 con
       `{ items: Product[], nextCursor: string | null }`.
    2. Paginación por cursor sobre `created_at DESC, id DESC`.
       `GET /products?cursor=<nextCursor>` devuelve la página siguiente
       sin repetir ni saltarse elementos. Nada de offset/limit.
    3. `limit` acepta 1-100, por defecto 20.
       Fuera de rango devuelve 400 con `{ code: 'INVALID_LIMIT' }`.
    4. p95 por debajo de 200 ms con 10.000 productos en tabla.
    
    ### Cómo se verifica
    - `bun test test/products.e2e.ts` en verde (cubre los puntos 1 a 3).
    - `bun run bench:products` imprime el p95 y sale con código 1
      si supera los 200 ms.
    

    La diferencia no es la longitud. Es que la segunda tiene una sección Cómo se verifica.

    "Que sea rápido" es un deseo. "Que GET /products responda por debajo de 200 ms de p95 con 10.000 registros, y aquí está el comando que lo mide" es un criterio. El primero obliga a que alguien juzgue. El segundo se cierra solo.

    Regla corta: debajo de cada criterio, el comando que lo prueba. Si no puedes escribir el comando, no tienes un criterio, tienes una preferencia.

    Y cuando el criterio es ejecutable pasa lo interesante: puedes delegar el ciclo entero —escribe, ejecuta, lee el fallo, corrige— en lugar de revisar cada iteración a mano. Es el flujo diario que describí en cómo usar Claude Code a diario, y depende por completo de que exista ese comando.

    Fallo 2: la ambigüedad la rellena el modelo, no tú

    Todo hueco de la spec se rellena. Siempre. La pregunta no es si el agente va a improvisar, es con qué.

    Y improvisa con lo más común de su entrenamiento: la mediana de internet. Tu repo no es la mediana de internet.

    Si tu API pagina por cursor y la spec solo dice "con paginación", el agente escribe offset y limit, porque es el patrón que domina en el código público con el que se entrenó. Si tu proyecto devuelve Result en vez de lanzar, escribirá try/catch. Si tus tests usan Testing Library, te meterá un TestBed clásico.

    Ninguno de esos es un error del modelo. Son la respuesta estadísticamente correcta a una pregunta que no hiciste.

    Aquí está el matiz que casi nadie aplica: la spec no tiene que decirlo todo. Tiene que decir dónde no se puede improvisar.

    Y la forma más barata de decirlo no es describir tu patrón en tres párrafos. Es apuntar al código que ya lo hace:

    ### Referencias obligatorias
    - Paginación: copia el patrón de `src/orders/orders.controller.ts` (solo lectura).
      Si hay conflicto entre este documento y ese fichero, manda el fichero.
    - Errores: usa los helpers de `src/common/http-errors.ts`.
      Prohibido lanzar `Error` pelado.
    
    ### Libre elección
    - Nombres internos, orden de los métodos, dónde partes los helpers.
      No preguntes por esto.
    

    Ese último bloque parece de relleno y no lo es. Marcar lo que sí es libre evita el otro extremo: el agente que se para cada dos minutos a preguntar cómo llamar a una variable.

    Tu trabajo no es documentar el proyecto entero dentro de la spec. Es marcar las tres o cuatro fronteras donde una decisión razonable sería, en tu repo, la decisión equivocada.

    Fallo 3: no dice qué NO hacer

    Los desastres que he visto con agentes casi nunca vienen de lo que el agente no hizo. Vienen de lo que hizo de más.

    Le pides un endpoint y te reformatea 40 ficheros porque detectó que el estilo era inconsistente. Le pides un filtro y te instala una librería de query building. Le pides un fix y "de paso" refactoriza el módulo de auth, que estaba feo.

    Todo eso es técnicamente razonable. Ninguna spec lo prohibía.

    Los límites son parte del contrato, no una nota al margen:

    ### Fuera de alcance
    - No tocar `src/auth/**` ni `src/orders/**`.
    - No añadir dependencias. La paginación sale del query builder
      que ya está en `src/common/pagination.ts`.
    - No crear migraciones. Si hace falta un índice, lo propones
      en el PR y paras.
    - No cambiar la forma de respuesta de endpoints existentes.
    - No reformatear ficheros que no toque la feature.
    

    Cinco líneas. Te ahorran la revisión de un diff de 40 ficheros donde lo que importa está en tres. Es, por cierto, uno de los patrones que aparece una y otra vez en los errores comunes al adoptar Claude Code: falta de guardarraíles, no falta de capacidad.

    No es opinión mía: las buenas prácticas oficiales de Claude Code lo dicen con todas las letras — las specs más útiles "nombran los ficheros e interfaces implicados, declaran qué queda fuera de alcance, y terminan con un paso de verificación end-to-end que demuestra que la feature funciona". Los tres primeros fallos de esta lista son exactamente esas tres cosas, en negativo.

    Fallo 4: la spec que ya decide la implementación

    Este falla al revés que los anteriores. No peca de vaga, peca de mandona.

    Crea un `ProductsCacheInterceptor` en `src/products/interceptors/`.
    Usa un `Map<string, { data: Product[]; ts: number }>` en memoria.
    TTL de 60 s, limpieza con un `setInterval` cada 30 s.
    La clave del Map es `JSON.stringify(req.query)`.
    

    Eso no es una spec. Es pseudocódigo con saltos de línea.

    Y tiene un agujero que probablemente no has visto: JSON.stringify(req.query) genera claves distintas para ?limit=20&cursor=x y ?cursor=x&limit=20. Son la misma petición. El agente puede implementar ese documento al pie de la letra, perfectamente, y aun así pegarle dos veces a la base de datos.

    La versión que sí es un contrato:

    ### Criterio
    Dos peticiones con los mismos parámetros a `GET /products` en menos de 60 s
    golpean la base de datos una sola vez, independientemente
    del orden de los parámetros en la query string.
    
    ### Cómo se verifica
    `bun test test/products.cache.e2e.ts` — el test espía el
    repositorio y afirma que solo hubo una query.
    
    ### Restricciones
    Sin dependencias nuevas. Sin Redis: todavía no está en infra.
    

    Fíjate en la paradoja. La versión que no dice cómo implementarlo es más exigente que la que lo dictaba línea a línea. La primera se puede cumplir y estar mal. La segunda no se puede fingir.

    Además, cuando cierras el cómo, cierras también las soluciones mejores que la tuya. Y en cachés, colas y consultas, el agente propone alternativas buenas más a menudo de lo que resulta cómodo admitir.

    Tú decides el qué observable. Él decide el cómo. El test decide quién tiene razón.

    Fallo 5: la spec solo describe el camino feliz

    Abre tu última spec y cuenta cuántas líneas hablan de qué pasa cuando algo falla. Lo normal es cero.

    Aquí está la trampa: el agente no deja el manejo de errores sin hacer. Lo inventa. Y su versión inventada suele ser un catch que loguea y sigue, o un 200 con array vacío cuando la base de datos no responde. Un endpoint que miente en lugar de fallar.

    Cuatro filas arreglan esto:

    Caso Respuesta Qué se loguea
    cursor malformado 400 INVALID_CURSOR warn, sin volcar el cursor entero
    limit fuera de 1-100 400 INVALID_LIMIT nada
    Timeout de la base de datos (>2 s) 503 DB_TIMEOUT error, con query y duración
    Fila de producto sin precio se excluye del listado warn con el id

    La última fila separa una spec escrita por alguien que ha estado de guardia de una escrita de memoria. Los datos sucios existen, y si no decides tú qué hacer con ellos, decide el agente.

    Dos minutos de escritura. Es lo que hay entre un endpoint y un endpoint que puedes dejar sin mirar.

    Los dos fallos que no ves leyendo la spec

    Los cinco anteriores se detectan releyendo el documento. Estos dos solo aparecen cuando comparas la spec con el repo y con el tamaño del trabajo.

    Fallo 6: la spec es demasiado grande

    La unidad de una spec no es la feature, es el paso verificable. Si la sección "Cómo se verifica" no cabe en cinco líneas, no tienes una spec: tienes tres disfrazadas de una.

    El síntoma es inconfundible: el agente empieza bien y se desmadra a la mitad, porque cada decisión que toma amplía la superficie de las siguientes.

    Partir el trabajo en unidades que se cierran una a una es, literalmente, la mitad del método que enseño en Construye con IA. La otra mitad es no volver a abrir una unidad ya cerrada.

    Fallo 7: la spec está desactualizada

    La spec dice que el endpoint devuelve items, el código lleva tres semanas devolviendo data. El agente no tiene forma de saber cuál manda. Unas veces sigue al documento y otras al código, y ninguna de las dos es una elección tuya.

    La regla que uso: la spec vive en el repo, entra en el mismo PR que el código, y cuando se contradicen gana el código. Entonces actualizas la spec o la borras. Una spec muerta es peor que no tener spec, porque es contexto con autoridad que resulta ser mentira.

    El test de 30 segundos: ¿tu spec es verificable?

    Una spec es verificable si responde a tres preguntas por escrito. No hace falta reescribir nada para saberlo. Coge la que ibas a pasarle al agente y hazle estas tres:

    1. ¿Qué comando prueba que está terminada?
    2. ¿Qué ficheros no puede tocar?
    3. ¿Qué pasa exactamente cuando falla?

    Si las tres tienen respuesta escrita en el documento, la spec funciona. Si falta una, ya sabes dónde está el bug — y no está en el modelo.

    Empieza hoy por la primera. Coge tus criterios de aceptación y escribe debajo de cada uno el comando que lo demuestra. Los que se queden sin comando, o los conviertes en algo medible o los sacas de la spec, porque no van a pasar de deseo.

    Todo el sistema —el contrato verificable, los límites, cómo partir el trabajo y cómo mantener la spec viva junto al código— es lo que ordené en SDD: Spec-Driven Development. Si este post te ha señalado tres fallos en tu documento, ahí tienes el método completo para que no vuelvan.

    Y si prefieres verlo sobre proyectos reales, con specs de gente que las está usando en producción, es una de las conversaciones habituales en Dominicode Labs.

    Preguntas frecuentes

    ¿Cómo sé si mi spec es verificable?

    Una spec es verificable si cada criterio de aceptación tiene debajo un comando que devuelve verde o rojo sin que nadie opine. Un test, un benchmark, un script de validación, un curl con la respuesta esperada.

    La prueba rápida: pásale la spec a alguien que no conozca el proyecto y pídele que te diga si está hecha sin abrir el código. Si necesita preguntarte algo, el agente también lo habría necesitado, solo que él no pregunta.

    ¿Qué hago con los criterios que no se pueden medir con un comando?

    Los conviertes en algo observable o los sacas de la spec. "Que la UI sea intuitiva" no es un criterio, pero "que el flujo de compra se complete en tres clics desde la ficha de producto y no haya ningún campo obligatorio sin marcar" sí lo es, y se comprueba con un test end-to-end.

    Cuando de verdad no se puede —criterios estéticos, tono de los textos, decisiones de marca— déjalo fuera de la spec y márcalo como revisión humana explícita. Lo que no puede pasar es que quede dentro como si el agente pudiera resolverlo.

    ¿Cuánto debe ocupar una spec para un agente de IA?

    No la mides en palabras, la mides en unidades verificables. Una spec debe cubrir un trozo de trabajo que se cierre con una tanda de comprobaciones y una revisión.

    Si la sección "Cómo se verifica" ocupa más de cinco líneas o mezcla áreas del sistema que no comparten test, pártela. Dos specs de 300 palabras funcionan mejor que una de 900.

    ¿La spec sustituye a los tests?

    No, los ordena. La spec dice qué tiene que ser cierto y el test lo comprueba en cada ejecución.

    En la práctica la relación es más estrecha de lo que parece: si los criterios de aceptación están bien escritos, los nombres de tus tests salen casi copiados de ellos. Cuando un criterio no se deja convertir en un nombre de test, casi siempre es que el criterio estaba vago.

    ¿Por qué el agente ignora partes de mi spec?

    Rara vez las ignora. Lo habitual es que las haya interpretado de una forma que a ti no se te ocurrió, porque estaban abiertas a más de una lectura.

    Revisa esas partes buscando dos cosas: adjetivos sin unidad ("rápido", "robusto", "limpio") y sustantivos que en tu proyecto tienen un significado propio ("paginación", "validación", "caché"). Son los dos sitios donde el modelo rellena con lo más común de su entrenamiento en lugar de con lo que hace tu repo.

    ¿Qué hago si el agente toca ficheros que nadie le pidió?

    Añade una sección "Fuera de alcance" a la spec y trátala como parte del contrato, no como una nota al margen. Cinco líneas bastan: qué directorios no se tocan, que no se añaden dependencias, que no se crean migraciones, que no se cambia la forma de respuesta de endpoints existentes y que no se reformatea nada que la feature no toque. Los desastres con agentes casi nunca vienen de lo que no hicieron, sino de lo que hicieron de más, y ninguna spec se lo prohibía.

    ¿La spec tiene que decir cómo implementar la feature?

    No, y decirlo suele empeorar el resultado. Una spec que dicta la implementación línea a línea se puede cumplir al pie de la letra y aun así estar mal, porque el agente reproduce también tus errores de diseño. Una spec que fija el comportamiento observable y el comando que lo verifica no se puede fingir. Tú decides el qué, el agente decide el cómo, y el test decide quién tenía razón.

    ¿Dónde guardo la spec y quién manda si contradice al código?

    En el repositorio, junto al código, y entra en el mismo pull request que la implementación. Fuera del repo se queda desactualizada en semanas y se convierte en contexto falso.

    Cuando spec y código se contradicen, manda el código y la spec se corrige o se borra en ese mismo momento. Déjalo escrito dentro del propio documento: es la línea que evita que el agente tenga que adivinar cuál de las dos fuentes es la buena.


    Por Bezael Pérez — Developer senior con más de 15 años de experiencia y fundador de Dominicode.

  • Cuándo NO usar Spec-Driven Development: 6 casos que te frenan

    Cuándo NO usar Spec-Driven Development: 6 casos que te frenan

    Escribí una spec de tres páginas —objetivo, contratos de datos, casos de error, criterios de aceptación— para una integración que no llegó a existir.

    A la mañana siguiente abrí la documentación de la API y descubrí que el endpoint sobre el que se apoyaba la mitad de mis decisiones no devolvía lo que yo daba por hecho. Tiré el documento entero.

    La spec no estaba mal escrita. Estaba escrita antes de tiempo.

    Escribí el libro de Spec-Driven Development. Lo uso casi todos los días y sigo pensando que es la diferencia entre dirigir a un agente y rezarle. Por eso mismo puedo decirte esto sin que suene a excusa: hay trabajo donde SDD no compensa, y confundir "el método funciona" con "el método aplica siempre" te cuesta más horas de las que te ahorra.

    Una spec es un seguro, y a veces la prima cuesta más que el siniestro

    Escribir una spec tiene dos costes.

    El obvio es el tiempo. Ese lo ves y lo aceptas, porque sabes que la vas a recuperar en el primer malentendido que no ocurre.

    El segundo no lo ve casi nadie: una spec fija decisiones. Ese es su trabajo. Y fijar decisiones es fantástico cuando tienes la información para tomarlas, y carísimo cuando no la tienes, porque conviertes una suposición en un contrato y luego construyes encima.

    Todo el debate se reduce a comparar dos cantidades: lo que cuesta el error que la spec previene, y lo que cuesta escribirla ahora, con la información que tienes ahora. Cuando el error es caro e irreversible, la prima es barata a cualquier precio. Cuando el error se arregla con un git revert y un café, estás pagando un seguro contra un rasguño.

    La regla, en una frase: no escribas una spec cuando el coste de deshacer el error sea menor que el coste de escribirla; escríbela siempre que el error no se deshaga con un comando.

    Si has llegado aquí sin el contexto previo, en Spec-Driven Development: evita el caos de la IA está el método entero. Este post es la otra mitad: los seis sitios donde estuve pagando de más hasta que aprendí a mirar la factura.

    1. Cuando todavía no sabes lo que quieres

    Una spec responde a "qué vamos a construir". Un spike responde a "¿esto es siquiera posible?".

    Son preguntas distintas, y la segunda no se contesta escribiendo. Se contesta ejecutando. Cuando estás evaluando si una librería aguanta tu caso, si esa API devuelve lo que promete o si el modelo entiende tus documentos, el código no es la implementación de la decisión: es el instrumento de medida.

    Especificar ahí es adivinar con formato de documento. Y un documento bien maquetado tiene un efecto raro sobre el cerebro: le da a una suposición el aspecto de un hecho.

    Lo que sí necesita un spike son guardarraíles, y son tres:

    1. Una pregunta concreta escrita antes de empezar ("¿puedo procesar 500 facturas en menos de un minuto con este proveedor?"). Si no sabes formularla, no es un spike, es procrastinación con IDE.
    2. Una caja de tiempo. Cuatro horas, un día. Lo que quieras, pero decidido antes.
    3. El compromiso de tirar el código. Rama aparte, sin tests, sin abstracciones. Es YAGNI aplicado al documento: no especifiques por si acaso.

    El peligro real de un spike nunca fue no tener spec. Es que el prototipo se quede. El prototipo demuestra, no se promociona.

    Cuando llega la respuesta, escribes la spec. La exploración no sustituye a la spec: la alimenta. Ese salto del prototipo que demuestra al producto que se sostiene es justo lo que trabajamos en Construye con IA.

    2. Cuando el cambio cabe en tu cabeza y git revert lo deshace

    Subir un timeout. Añadir un índice. Cambiar el copy de un botón. Un campo más en un formulario que ya existe.

    Un diff de veinte líneas, un archivo, reversible en un comando. Escribir spec.md + plan.md + tasks.md para eso no es rigor. Es ceremonia.

    Y la ceremonia hace un daño que no se contabiliza: enseña a todo el mundo —incluido tú— que el proceso es un trámite. En cuanto una spec se percibe como trámite, todas las specs pierden autoridad. También las que sí importaban.

    El test que uso es de tres preguntas. ¿Puedes describir el cambio completo en una frase, sin "y" ni "además"? ¿El diff cabe en una pantalla? ¿Deshacerlo es un comando? Tres síes: abre el editor y hazlo.

    3. Cuando el código no va a sobrevivir a la semana

    Un script para limpiar una tabla una vez. Un notebook para sacar un número que te ha pedido alguien. Un endpoint de debug que borras el viernes.

    Una spec sirve para que otra persona —o tú dentro de seis meses— entienda una decisión. Si no va a haber ni otra persona ni dentro de seis meses, el documento no tiene a quién servir. El criterio de aceptación es ejecutarlo y mirar el resultado.

    La trampa está en otro sitio: el código temporal tiene la mala costumbre de volverse permanente. En cuanto ese script se ejecuta una segunda vez, deja de ser desechable y entra en el caso contrario.

    Mi regla: si dudo, la escribo. La duda ya es la señal de que la tarea es más grande de lo que parecía. Y si escribir la spec te está costando de verdad, echa un vistazo a la anatomía de una spec, porque a lo mejor el problema no es la tarea, es que estás escribiendo cuarenta líneas donde bastaban seis.

    4. Los bugs no se especifican, se reproducen

    Un bug no es una funcionalidad que falta. Es una diferencia entre lo que el sistema hace y lo que ya estaba dicho que tenía que hacer.

    O sea: la especificación ya existe. La escribiste cuando construiste la feature, o vive implícita en el comportamiento que todo el mundo daba por bueno hasta el martes pasado. Escribir un documento nuevo para describir algo que ya está descrito es duplicar la verdad, no aclararla.

    El artefacto correcto es un test que falla.

    Reproduce, aísla, escribe el test en rojo, arréglalo, deja el test dentro. Ese test es la spec de ese bug, con una ventaja que ningún markdown te da: es ejecutable, y cuando envejece te avisa el CI. Si alguien vuelve a romper eso dentro de seis meses, se entera antes que tú. Cómo se combina eso con dejar que la IA escriba la implementación lo desarrollé en TDD y spec-first con IA.

    Hay dos excepciones. La primera: nadie sabe decirte cuál sería el comportamiento correcto. Eso no es un bug, es un requisito sin decidir disfrazado de bug, y sí se especifica. La segunda: sabes perfectamente qué tiene que pasar, pero el arreglo es más grande que el fallo —rehacer la caché, tocar el modelo de concurrencia, cambiar un contrato que ya consume alguien—. El test sigue siendo obligatorio, pero describe el síntoma, no el rediseño. Eso se especifica igual.

    5. Cuando el dominio cambia bajo tus pies

    Si el requisito muda cada semana, la spec envejece más rápido de lo que tardas en ejecutarla. Y entonces tienes dos verdades: el documento y el código.

    Cuando hay dos verdades, los humanos resuelven el conflicto solos: dejan de leer el documento. Molesto, pero se sobrevive.

    El problema es el agente. Un agente no distingue una spec vigente de una obsoleta. No tiene forma de saber que ese párrafo lo invalidó una llamada del jueves. La lee, la trata como fuente de verdad y construye encima con toda la seguridad del mundo.

    La salida no es abandonar el método, es acortar el alcance. Specs de una semana en vez de specs de un trimestre. Especifica la parte del dominio que ya está congelada y deja explícitamente marcado lo que sigue en discusión. Una sección de "esto todavía no está decidido" vale más que tres páginas de decisiones falsas.

    6. Cuando la spec se ha convertido en teatro

    El síntoma es fácil de reconocer: escribes la spec después de tener el código.

    Eso no es Spec-Driven Development. Es documentación retroactiva, que no tiene nada de malo salvo el nombre que le pongas. El valor de la spec está en el orden, no en el archivo. Si el código ya existe, el documento no puede cambiar ni una sola decisión, que era exactamente para lo que servía.

    Hay dos síntomas más. El primero: nadie la lee, y lo sabes porque nadie te ha discutido nunca una línea. El segundo: las specs se copian de la anterior cambiando los nombres.

    Cuando aparece cualquiera de los tres, el problema no es el método. Es que lo estás aplicando en tareas donde no aportaba, y la gente lo ha notado antes que tú.

    Cuándo usar Spec-Driven Development y cuándo no: la tabla

    Tipo de trabajo ¿Spec? Por qué
    Spike para saber si algo es viable No — pregunta y caja de tiempo El código es la investigación; la spec fijaría decisiones sin información
    Cambio pequeño y reversible No — el propio diff Revertirlo cuesta menos que documentarlo
    Bug reproducible No — un test en rojo El test fija el comportamiento y además lo vigila el CI
    Código que no sobrevive a la semana No — el propio código El documento no tiene a quién servir
    Spec escrita después del código No — llámalo documentación Ya no puede cambiar ninguna decisión, que era su único trabajo
    Requisito que cambia cada semana Corta y con caducidad La spec envejece más rápido de lo que se ejecuta
    Feature nueva en un producto vivo Otra persona la va a tocar y el error se paga meses después
    Migración o cambio del modelo de datos Sí, siempre El error no se deshace con git
    Trabajo que cruza servicios o equipos Sí, siempre La spec es el punto de sincronización, no el papeleo
    Tarea que delegas entera a un agente Sí, siempre Lo que no escribas, lo rellena inventando
    Auth, pagos, datos personales Sí, siempre El coste del fallo no es técnico

    Bajar de artefacto no es dejar de pensar

    La alternativa a una spec no es el caos: es un artefacto más barato. Hay cinco niveles, y solo el primero es una spec completa.

    1. Spec completa — spec, plan y tareas. Para trabajo caro, compartido o delegado entero.
    2. Spec corta — tres párrafos: objetivo, criterio de aceptación y qué queda fuera. El escalón que más uso.
    3. Un plan — el modo plan de Claude Code, leído y aprobado antes de que toque nada. Treinta segundos de lectura que evitan revisar un diff de novecientas líneas, como conté en cómo usar Claude Code a diario.
    4. Un test que falla — para bugs y para cualquier cosa con un criterio binario.
    5. Nada — y el diff es toda la conversación.

    La pregunta buena nunca fue "¿spec sí o spec no?". Es: ¿cuál es el artefacto más barato que evita que esto salga mal?

    Dónde Spec-Driven Development gana siempre

    La tabla ya dice dónde la spec no se discute: migraciones, modelo de dominio, auth, dinero. Nada de eso es nuevo. Lo que ha cambiado la ecuación, desde que los agentes de código empezaron a ejecutar tareas de varias horas sin supervisión, es la última fila: el trabajo que delegas entero a un agente.

    Antes la spec competía contra "lo hago yo, que ya me entiendo". Ahora compite contra revisar cuatrocientas líneas que alguien escribió interpretando lo que tú no llegaste a decir. Cuando delegas, cada hueco de la especificación se rellena con una invención plausible, y las invenciones plausibles son las más caras de detectar. Lo desarrollé en qué pasa cuando pides código sin spec.

    En ese escenario la spec deja de ser documentación. Es el prompt más caro que vas a escribir, y el único que se amortiza.

    Lo que haría yo mañana

    Coge la siguiente tarea de tu lista y hazte una sola pregunta antes de abrir el editor:

    Si esto sale mal, ¿cuánto cuesta deshacerlo?

    Si la respuesta es "un git revert", empieza a picar. Si la respuesta es "una migración de datos", "una llamada con otro equipo" o "un incidente en producción", escribe la spec antes de tocar una línea.

    No necesitas más criterio que ese. Los seis casos de este post son solo ejemplos de esas dos respuestas.

    El libro de Spec-Driven Development va justo de esto: hay un capítulo entero sobre la spec más corta que puedes escribir, además de las plantillas, el flujo con el agente y qué hacer cuando la spec y el código se separan. Si prefieres ver cómo lo aplica gente que ya lo tiene metido en su día a día, esa conversación pasa en Dominicode Labs.

    Saber cuándo no usar un método es la parte que nadie te enseña, y es la que separa aplicarlo de entenderlo.

    Preguntas frecuentes

    ¿Entonces Spec-Driven Development no sirve para proyectos pequeños?

    Sirve, pero el tamaño del proyecto no es la variable correcta. La variable es cuánto cuesta deshacer la tarea si sale mal y cuánta gente toca ese código. Un proyecto pequeño con una migración de datos y un cobro de por medio necesita spec. Un proyecto enorme donde vas a cambiar el texto de un botón, no. Y si dudas, escríbela: la duda suele significar que la tarea es más grande de lo que parecía.

    ¿Qué escribo en lugar de una spec cuando la tarea no la necesita?

    Bajas un escalón de artefacto, no bajas a cero. Hay cinco: spec completa, spec corta (objetivo, criterio de aceptación y qué queda fuera), un plan aprobado antes de tocar código, un test que falla, y nada. La mayoría del trabajo diario vive en los escalones dos y tres, no en el uno. La pregunta útil no es "¿spec sí o no?" sino cuál es el artefacto más barato que evita que eso salga mal.

    ¿Qué escribo en lugar de una spec cuando arreglo un bug?

    Un test que falla. Reproduce el fallo, aísla el caso mínimo, escribe el test en rojo, arregla el código y deja el test dentro del repositorio. Ese test cumple la misma función que una spec —fijar el comportamiento esperado— con dos ventajas: es ejecutable y el CI lo comprueba solo. La excepción es cuando nadie sabe cuál debería ser el comportamiento correcto; eso no es un bug, es un requisito sin decidir, y ese sí se especifica.

    ¿Puedo escribir la spec después de tener el código?

    Puedes, pero llámalo por su nombre: documentación. El valor de una spec está en el orden, porque su trabajo es cambiar decisiones antes de que se conviertan en código. Escrita después, no puede cambiar ninguna. Sirve para onboarding y para dejar constancia, y eso tiene su utilidad, pero no está gobernando nada.

    ¿Qué hago si los requisitos cambian cada semana?

    Acorta el alcance de la spec en lugar de abandonarla. Especifica solo la parte del dominio que ya está cerrada, marca de forma explícita lo que sigue en discusión y ponle caducidad al documento. El riesgo grande no es no tener spec, es tener una obsoleta: un agente no sabe distinguirla de una vigente y construirá encima con total seguridad.

    ¿Y si voy a delegar la tarea completa a un agente de código?

    Entonces escribe la spec aunque la tarea parezca pequeña. Cuando delegas, cada hueco que dejes sin especificar lo rellena el modelo con una suposición razonable, y esas suposiciones son las más difíciles de detectar revisando el diff. Ahí la spec no compite contra tu tiempo de escribir código: compite contra tu tiempo de revisar código ajeno, que siempre es más caro.


    Por Bezael Pérez — Developer senior con más de 15 años de experiencia y fundador de Dominicode.

  • Qué es el graph engineering: el mapa que tu agente no tiene

    Qué es el graph engineering: el mapa que tu agente no tiene

    Le pedí a un agente que renombrara una función. getUserDatafetchUserProfile. Dos minutos de trabajo.

    Hizo grep, encontró siete referencias, las cambió, corrió los tests. Verde. Commit.

    Reventó al día siguiente. La función también se invocaba desde un mapa de handlers, handlers[action], con el nombre viajando como string dentro de un JSON de configuración. Grep encontró siete referencias. Había doce.

    El agente no falló por falta de contexto ni por usar un modelo flojo. Falló porque grep solo compara cadenas y nadie le dio un mapa de relaciones. De eso va el graph engineering.

    Qué es el graph engineering (y el lío que hay con el nombre)

    Graph engineering es la práctica de representar tu código y tu documentación como un grafo explícito de relaciones: los nodos son símbolos —archivos, funciones, clases, conceptos— y las aristas son las relaciones reales entre ellos: importa, llama, hereda, contiene, referencia.

    En vez de que el agente busque texto y adivine, navega aristas.

    Antes de seguir, un aviso honesto: el término no tiene una definición canónica única en 2026. Se usa para dos cosas distintas.

    La primera es el grafo de orquestación. Nodos como unidades de ejecución, aristas como flujo de control: LangGraph, org graphs, work graphs. Ahí el "graph engineering" es diseñar cómo se conectan varios agentes. Es la conversación que arrancó Peter Steinberger en julio de 2026 con una pregunta de seis palabras"Are we still talking loops or did we shift to graphs yet?" — y que es la continuación natural de lo que conté en loop engineering.

    La segunda es el grafo de recuperación. Nodos como símbolos de tu código, aristas como dependencias reales. Aquí no se decide qué agente actúa después: se decide qué sabe el agente antes de tocar nada.

    Este post va de la segunda. Y no compiten: una es control de flujo, la otra es recuperación. Misma palabra, dos capas del stack.

    Si necesitas el atajo: cuando hables de LangGraph o de coordinar varios agentes, es la primera. Cuando hables de qué código ve tu agente antes de editar, es la segunda.

    Las tres preguntas que ni grep ni los embeddings responden

    Hay tres preguntas sobre tu código que ni la búsqueda por texto ni la búsqueda semántica pueden responder:

    1. Si cambio esto, ¿qué se rompe? El radio de impacto a uno, dos o tres saltos. Grep te da el primer nivel. El transitivo no lo ve nadie.
    2. ¿Quién llama a quién? El call graph completo, con su dirección. Grep te dice que dos archivos mencionan AuthService. No te dice cuál lo consume y cuál lo define.
    3. ¿Qué depende de qué — y qué no depende de nada? Los nodos con grado cero son código muerto, y salen solos. Buscar código muerto con grep es un ejercicio de paciencia.

    Las tres son preguntas sobre topología, no sobre contenido. Por eso hacen falta aristas — y por eso las dos herramientas que usas hoy se quedan cortas.

    Tu agente tiene dos formas de encontrar código, y las dos tienen el mismo agujero.

    Grep busca coincidencia exacta de texto. Es preciso, rápido y determinista. No sabe nada de significado ni de estructura. Si la referencia está construida en runtime, no existe para grep.

    Los embeddings buscan parecido semántico. Encuentran la función de autenticación aunque se llame verificarCredenciales. Pero "se parece" no es "está conectado con". Un chunk sobre logging y otro sobre logging viven cerca en el espacio vectorial aunque uno nunca llame al otro. Es la limitación estructural de RAG que ya toqué en RAG vs fine-tuning.

    Las dos herramientas responden "¿dónde aparece esto?". Ninguna responde "¿con qué está conectado esto?".

    Resumido, con la tercera vía al lado:

    Grep Embeddings Grafo de código
    Pregunta que responde ¿Dónde aparece esta cadena? ¿Dónde hay algo parecido a esto? ¿Con qué está conectado esto?
    Qué necesitas saber antes El nombre exacto Una descripción aproximada Que el símbolo exista
    Ve el segundo salto No No Sí — affected --depth 2
    Ve llamadas indirectas No No Sí, marcadas como INFERRED
    Nivel de certeza Binario: aparece o no aparece Puntuación de similitud EXTRACTED o INFERRED
    Coste de mantenerlo Cero Reindexar + coste de embeddings Re-extracción AST, sin LLM
    Dónde se rompe La referencia se construye en runtime Dos cosas se parecen pero no se llaman Código muy dinámico: DI por string, metaprogramación

    Anatomía del grafo: nodos, aristas y confianza

    Un grafo de código tiene tres piezas: nodos (los símbolos: archivos, funciones, clases), aristas (las relaciones entre ellos) y un nivel de confianza por arista.

    Lo concreto. Construí un grafo con graphify —CLI open source, parseo AST local con tree-sitter, sin vector store— sobre un proyecto pequeño que tengo por ahí. Pequeño a propósito: quería poder verificar a mano cada arista antes de creerme nada. Salieron 115 nodos y 240 aristas.

    Los nodos llevan poco: id, etiqueta, archivo de origen y línea. Lo interesante está en las aristas.

    {
      "source": "src_chunker",
      "target": "src_chunker_needs_chunking",
      "relation": "contains",
      "confidence": "EXTRACTED",
      "source_file": "src/chunker.py",
      "source_location": "L10"
    }
    

    Los ocho tipos de relación que aparecieron en ese grafo:

    Relación Qué conecta ¿La ve grep?
    imports / imports_from Archivo → módulo o símbolo importado Sí, si el nombre aparece literal
    contains Archivo → función o clase que declara Parcialmente
    calls Función → función que invoca Solo el primer nivel
    references Símbolo usado sin invocarlo Sí, si el nombre aparece literal
    inherits Clase → clase base
    method Clase → método que le pertenece
    indirect_call Llamada resuelta en runtime No

    Fíjate en la última fila. indirect_call es exactamente la llamada que grep no ve.

    Y ahora el campo que más me interesa de todo esto, el que casi nadie menciona: confidence. Cada arista viene marcada como EXTRACTED o INFERRED. En mi grafo: 197 extraídas, 43 inferidas.

    EXTRACTED significa que la relación está literalmente en el AST. El parser la leyó, no la dedujo. INFERRED significa que la resolvió el motor uniendo puntos — una llamada cuyo destino tuvo que deducirse.

    Eso cambia cómo usas el resultado. Una arista EXTRACTED la das por buena. Una INFERRED es una hipótesis con nombre y apellidos que puedes ir a verificar al archivo y la línea que te da. Ni los embeddings ni grep te dan esa distinción: grep afirma sin matices, y el score de un embedding te dice cuánto se parece algo, nunca de dónde sale la relación. Aquí lo que se etiqueta es la procedencia.

    Un explain sobre un nodo devuelve esto:

    Node: needs_chunking()
      Source:    src/chunker.py L10
      Degree:    6
    
    Connections (6):
      <-- main() [calls] [INFERRED]
      <-- transcribe() [calls] [INFERRED]
      <-- chunker.py [contains] [EXTRACTED]
      --> Path [references] [EXTRACTED]
      <-- test_needs_chunking_false_for_small_file() [calls] [INFERRED]
      <-- test_needs_chunking_true_for_large_file() [calls] [INFERRED]
    

    Seis líneas. Ahí está el vecindario directo de esa función, con la dirección de cada arista y el nivel de confianza de cada una. Para llegar a lo mismo con grep necesitas varias pasadas y saber de antemano qué buscar.

    Pero el vecindario directo no es el radio de impacto. Para eso hay un comando aparte, que es el que responde literalmente a la pregunta 1: un recorrido inverso por las aristas que tú elijas, a la profundidad que tú digas.

    graphify affected "needs_chunking" --depth 2 --relation calls
    
    Affected nodes for needs_chunking()
    Relations: calls
    Depth: 2
    - test_needs_chunking_false_for_small_file() [calls] tests/test_chunker.py:L24
    - test_needs_chunking_true_for_large_file() [calls] tests/test_chunker.py:L30
    - main() [calls] transcribe.py:L17
    - transcribe() [calls] watch.py:L36
    - test_output_flag_saves_to_specified_path() [calls] tests/test_integration.py:L34
    - test_file_not_found_exits_with_code_1() [calls] tests/test_integration.py:L48
    - test_unsupported_format_exits_with_code_1() [calls] tests/test_integration.py:L55
    - test_api_key_not_in_output() [calls] tests/test_integration.py:L65
    - .on_created() [calls] watch.py:L72
    

    Mira la diferencia. De las seis conexiones del explain, solo cuatro eran llamadas entrantes. El affected a dos saltos da nueve, y las cinco nuevas son las interesantes: los cuatro tests de integración y el handler .on_created() del watcher no tocan needs_chunking directamente, llegan a través de main() y transcribe().

    Ese es el segundo nivel. El que revienta en producción al día siguiente y el que ninguna búsqueda por texto te va a dar, porque no hay ninguna cadena que buscar: la relación existe en la topología, no en el código fuente de esos archivos.

    Aquí está la tesis, y quiero decirla sin vender humo: el grafo no te garantiza encontrar la referencia indirecta. Te da una categoría donde esa relación puede existir y quedar marcada. Grep ni siquiera tiene esa categoría. Esa es toda la diferencia, y es suficiente.

    Cómo usar un grafo de código con un agente de coding, en 3 pasos

    Tres piezas.

    Uno: construyes el grafo y lo dejas en el repo. graphify-out/graph.json más un reporte en markdown. Es un artefacto de tu proyecto, como el lockfile.

    uv tool install graphifyy   # doble "y" mientras reclaman el nombre en PyPI;
                                # el comando y el skill siguen siendo graphify
    graphify install            # registra el skill en tu agente
    graphify update .           # re-extrae solo lo que cambió, sin LLM
    

    Dos: le das al agente una regla de precedencia. Sin esto no sirve de nada, porque el modelo tira de grep por costumbre. En el CLAUDE.md del proyecto:

    - Para preguntas sobre el código, ejecuta primero `graphify query "<pregunta>"`.
      Usa `graphify path "<A>" "<B>"` para relaciones, `graphify explain "<X>"`
      para un concepto concreto y `graphify affected "<X>"` antes de modificar o
      borrar algo. Devuelven un subgrafo acotado, mucho más pequeño que el reporte
      completo o la salida cruda de grep.
    - Después de modificar código, ejecuta `graphify update .`.
    

    Esa regla es la diferencia entre tener un grafo y usarlo. Es la misma idea de fondo que trabajo en el curso de Construye con IA: el agente no es más listo por tener más herramientas, sino por tener reglas claras de cuándo usar cuál.

    Tres: el grafo entra en la ventana como subgrafo, no como volcado. Un explain devuelve seis líneas donde un grep te vuelca cada aparición del término y tú decides después: recuperas menos tokens y mejores, que es el objetivo del context engineering.

    Ojo con una cosa: graphify query no devuelve una respuesta en prosa. Devuelve un recorrido BFS con los nodos encontrados. Es una herramienta de recuperación dentro del harness, no un chatbot. Quien interpreta el subgrafo sigue siendo el modelo.

    Y un apunte de higiene: el proyecto publica cifras de benchmark en su README. Son autoreportadas. Trátalas como lo que son y mide en tu repo.

    Cuándo NO merece la pena montar un grafo de código

    No todo proyecto necesita esto. Cuatro casos donde el grafo estorba más de lo que ayuda.

    Proyectos pequeños. Si el código entra entero en la ventana, el agente ya tiene el grafo en la cabeza y mejor resuelto. Montar recuperación para veinte archivos es sobreingeniería.

    Código muy dinámico. Metaprogramación intensa, inyección de dependencias por string, event buses, decoradores que reescriben comportamiento en runtime. El AST no puede ver lo que solo existe cuando el proceso arranca. El grafo saldrá con más aristas INFERRED que EXTRACTED, o directamente con huecos. Sigue siendo mejor que grep, pero baja mucho el techo.

    Y sí: el bug con el que abrí este post vive justo en esta frontera. Un nombre viajando dentro de un JSON no está en ningún AST. Lo que cambia es que el grafo marca ese hueco como INFERRED o lo deja sin arista, y eso es una señal que puedes leer. Grep te devuelve siete referencias con la misma cara de seguridad que si fueran las doce.

    Si no puedes mantenerlo actualizado. Un grafo obsoleto es peor que no tener grafo, porque el agente confía en él. Necesitas graphify update en un hook de pre-commit, en CI o con graphify watch. Si esto no está automatizado, no lo montes: en dos semanas tienes un mapa de un territorio que ya no existe.

    Si lo que buscas es "qué debería hacer este sistema". El grafo describe el código que existe, no la intención. Para eso el artefacto es la spec — que es, por cierto, otra forma de estructura explícita, y la razón por la que escribí el libro de Spec-Driven Development. El grafo cuenta el presente. La spec define el futuro.

    Y un apunte de madurez: graphify va por la 0.9.x. No es 1.0 todavía, y se nota. Herramienta útil, no infraestructura estable.

    Cómo empezar con graph engineering hoy

    Coge tu repo más feo. El que da miedo tocar.

    Construye el grafo, ejecuta un explain sobre la función que más te intimida y mira su grado. Si el número te sorprende, acabas de descubrir por qué ese refactor lleva meses aplazado.

    Si quieres ver cómo encaja esto con el resto del stack —agentes, MCP, memoria, specs— lo trabajamos a fondo en Dominicode Labs, con proyectos reales y no con ejemplos de juguete.

    Preguntas frecuentes

    ¿Graph engineering es lo mismo que GraphRAG?

    No exactamente. GraphRAG es la implementación de Microsoft que usa un LLM para extraer entidades y relaciones de texto no estructurado, detectar comunidades y resumirlas. Está pensado para corpus documentales.

    Graph engineering es el concepto general de estructurar conocimiento como grafo. Aplicado a código, el grafo se extrae del AST de forma determinista, sin LLM y sin coste por token. GraphRAG es una implementación posible, no la única ni la más barata para código.

    ¿Qué diferencia hay entre graph engineering y loop engineering?

    El loop engineering diseña el bucle de ejecución del agente: qué hace, cómo verifica el resultado y cuándo vuelve a intentarlo. El graph engineering, en la acepción de este post, diseña lo que el agente sabe antes de entrar en ese bucle: un mapa de relaciones de tu código en vez de una búsqueda de texto.

    No compiten. Un agente con un buen bucle y sin mapa repite el mismo error más rápido. Si tus fallos vienen de contexto estructural incompleto, el grafo rinde antes que otra iteración del loop.

    ¿Funciona con TypeScript o solo con Python?

    Los ejemplos de este post salen de un proyecto en Python, pero la extracción es por AST con tree-sitter y las gramáticas que trae cubren los lenguajes habituales: Python, TypeScript, JavaScript, Go, Rust, Java, C, C++, Ruby, C#, Kotlin, Scala y PHP.

    Con TypeScript hay un matiz: cuanto más tira el proyecto de inyección por token, decoradores y factories, más aristas caen en INFERRED. El grafo sigue siendo mejor que grep, pero léelo sabiendo qué parte es hipótesis.

    ¿Necesito una base de datos de grafos como Neo4j?

    Para un repo, no. El grafo de un proyecto normal cabe en un JSON en disco y se recorre con un BFS en memoria. Herramientas como graphify funcionan así, sin servidor y sin dependencias externas.

    Neo4j tiene sentido cuando el grafo es un producto en sí mismo, se consulta desde varios servicios o supera lo que quieres cargar en memoria. Para dar contexto estructural a un agente en tu máquina, es infraestructura que no necesitas.

    ¿El grafo sustituye a los embeddings y a la búsqueda semántica?

    No, y montarlo como sustituto es un error. Responden preguntas distintas.

    Los embeddings responden "¿dónde hay algo parecido a esto?" y toleran que no sepas los nombres exactos. El grafo responde "¿con qué está conectado esto?" y exige que el símbolo exista. Lo razonable es tener las dos vías y una regla de precedencia: para preguntas de estructura, grafo; para exploración difusa, semántica; para strings literales, grep.

    ¿Cada cuánto hay que reconstruir el grafo?

    En cada cambio de código relevante, y automatizado. La re-extracción incremental de código no necesita LLM, así que el coste es tiempo de CPU, no dinero.

    Lo práctico es un hook de pre-commit, un paso en CI o un proceso en watch mientras trabajas. Reconstruirlo a mano cuando te acuerdas es la vía rápida a un grafo obsoleto, y un grafo obsoleto le miente al agente con toda la confianza del mundo.

    ¿Sirve en monorepos grandes?

    Es donde más rinde, precisamente porque el código ya no cabe en la ventana de contexto y grep devuelve ruido. La pega es operativa: la visualización HTML se vuelve pesada por encima de unos miles de nodos, y para eso está la opción de saltarla y quedarte solo con el JSON consultable, que es lo que consume el agente.

    Y si tu organización tiene varios repos en vez de uno solo, puedes fusionar sus grafos en uno para cruzar dependencias entre paquetes.


    Por Bezael Pérez — Developer senior con más de 15 años de experiencia y fundador de Dominicode.

  • Agentes de voz en tiempo real: la latencia es el producto

    Agentes de voz en tiempo real: la latencia es el producto

    Un cliente me pidió una demo de un asistente telefónico para reservas. La monté en un fin de semana: transcripción con Whisper, un LLM para razonar, un TTS decente para responder. En mis pruebas funcionaba. Entendía todo, respondía bien, la voz sonaba natural.

    Se la enseñé por teléfono a alguien de su equipo. Preguntó por una mesa para el sábado. Silencio. Y entonces hizo lo que hace cualquiera cuando no le contestan: dijo "¿hola?".

    Ese "¿hola?" me enseñó lo único que de verdad importa al construir agentes de voz en tiempo real: el agente no había fallado. Había tardado 1,2 segundos en empezar a hablar. Y 1,2 segundos, en una conversación, no se perciben como lentitud. Se perciben como que la llamada se ha cortado.

    La latencia no es una métrica que optimizas al final. Es el producto.

    Qué es un agente de voz en tiempo real

    Un agente de voz en tiempo real es un sistema que escucha al usuario, decide qué responder y contesta hablando, todo dentro de la misma conversación y sin pasar por turnos escritos. Se diferencia de un chatbot en que el canal es audio continuo, y de un asistente de voz clásico en que quien decide es un LLM con acceso a herramientas, no un árbol de intenciones.

    Se construye de dos maneras: encadenando reconocimiento de voz (STT), modelo de lenguaje (LLM) y síntesis de voz (TTS), o con un modelo speech-to-speech nativo que recibe audio y emite audio. La diferencia entre las dos no está en lo bonita que suena la voz. Está en la latencia, y en cuánta información sobrevive por el camino.

    El listón lo puso la evolución, no OpenAI

    Hay un dato que explica por qué 1,2 segundos rompen la ilusión. Un estudio publicado en PNAS por Stivers y su equipo midió los huecos entre turnos en conversaciones reales de diez lenguas, de comunidades indígenas tradicionales a lenguas mayoritarias.

    El resultado fue incómodamente uniforme. La moda del hueco entre que uno termina de preguntar y el otro empieza a responder cae entre 0 y +200 ms en todas las lenguas estudiadas, con una moda global de 0 ms y una mediana entre lenguas de +100 ms. Las diferencias entre unas lenguas y otras caben en un margen de 250 ms respecto a la media global.

    Piensa en lo que implica. Nadie escucha una pregunta, la entiende, formula la respuesta y la articula en 200 milisegundos. No da tiempo. Lo que hacemos los humanos es predecir: empezamos a construir la respuesta mucho antes de que el otro termine.

    Tu agente no predice. Espera. Y ese hueco es exactamente donde la gente cuelga.

    Este es el presupuesto de latencia con el que trabajo cuando monto uno de estos. No son cifras de ningún benchmark: es la repartición que me funciona para no pasarme del umbral.

    Tramo Objetivo Qué lo dispara
    Detección de fin de turno (VAD) 200-500 ms silence_duration_ms alto, eagerness: "low"
    Primer token del modelo 200-400 ms contexto largo sin caché, modelo grande
    Primer audio de salida 100-300 ms TTS sin streaming
    Red y jitter 50-200 ms WebSocket en móvil, sin buffer adaptativo
    Total percibido por debajo de 800 ms por encima de 1 s el usuario dice "¿hola?"

    Por qué el pipeline encadenado suena mal en un agente de voz

    El diseño por defecto del developer que viene de chatbots de texto es el pipeline encadenado: STT → LLM → TTS. Es el que se entiende y el que puedes montar con tres proveedores que ya conoces.

    Tiene dos problemas de fondo que no se arreglan cambiando de proveedor.

    El primero es que las latencias suman. Cada etapa tiene su propio tiempo hasta el primer byte, y ninguna empieza hasta que la anterior le da algo con lo que trabajar. Puedes tener un STT rápido, un LLM rápido y un TTS rápido, y aun así un conjunto lento, porque mides cada pieza aislada mientras el usuario mide la cadena entera.

    Se mitiga con streaming agresivo — transcripciones parciales al modelo, tokens al TTS según salen — pero eso convierte tu pipeline en un problema de concurrencia, no en tres llamadas HTTP.

    El segundo es peor, porque no se arregla con ingeniería. El texto intermedio es un cuello de botella de información.

    Cuando alguien dice "no…" con duda, alargando la vocal, y cuando dice "No." tajante, tu STT te entrega la misma palabra. Has tirado a la basura el tono, la vacilación, el énfasis, la prisa, el enfado. Toda la información que un humano usa para decidir cómo responder desaparece antes de que el modelo la vea. Después le pides al TTS que reconstruya emoción a partir de texto plano, y suena a lo que es: una reconstrucción.

    Esto no mata el pipeline encadenado. Lo coloca en su sitio: es la arquitectura correcta para transcribir audio por lotes y extraer datos — una nota de voz, una reunión grabada, un mensaje asíncrono. Ahí Whisper sigue siendo excelente, y lo conté en detalle en captura y procesamiento de audio en Angular usando Whisper.

    Lo que no puedes es coger la arquitectura de transcripción por lotes y esperar que sostenga una conversación.

    Qué cambia con speech-to-speech en un agente de voz

    El modelo speech-to-speech elimina el viaje de ida y vuelta por el texto. Entra audio, sale audio, y el mismo modelo que entiende es el que habla.

    La consecuencia técnica es que la prosodia sobrevive. El modelo no lee una transcripción de lo que dijiste: procesa el audio, con sus pausas y su entonación, igual que procesa cualquier otra modalidad. Si te resulta raro que un modelo "escuche", la idea general está en qué es un modelo multimodal: el audio también acaba siendo tokens.

    La consecuencia práctica es que desaparecen dos saltos de red y dos colas de espera.

    A cambio pierdes flexibilidad. No puedes cambiar la voz sin cambiar de modelo, no tienes un punto intermedio en texto donde auditar lo que el agente está a punto de decir, y estás casado con un proveedor. Es un trade-off real, y hay negocios regulados donde ese checkpoint de texto no es negociable.

    Pero si tu producto es una conversación, la conversación gana.

    Los cuatro problemas de un agente de voz en producción

    Aquí es donde la demo del fin de semana se separa del producto.

    Barge-in: el agente tiene que callarse

    Cuando el usuario empieza a hablar mientras el agente habla, el agente debe cortar. Inmediatamente. Un agente que termina su frase mientras tú le hablas encima resulta insoportable en tres segundos.

    El detalle sucio es que cancelar la respuesta no basta. Al cortar, el servidor cree que ha dicho todo el audio que generó, pero el usuario solo ha oído lo que le dio tiempo a reproducirse. Si no corriges esa divergencia, el historial de la conversación contiene frases que el usuario nunca escuchó, y el modelo seguirá razonando como si las hubiera dicho.

    Por eso existe conversation.item.truncate: le dices al servidor en qué milisegundo exacto se quedó el audio realmente reproducido. Y ese milisegundo tiene que venir de tu reproductor, no de los bytes que has recibido del socket. Recibir no es reproducir. Este es el bug número uno de todo el que monta esto por primera vez.

    Detección de turno: saber cuándo ha terminado de hablar

    El VAD por volumen — silencio durante X milisegundos, luego respondo — funciona bien hasta que alguien dice "quiero reservar para… espera que mire… el sábado". Ese "espera que mire" incluye una pausa, y tu agente entra a saco a media frase.

    La Realtime API de OpenAI ofrece dos modos: server_vad, que trocea por silencio con parámetros de threshold, prefix_padding_ms y silence_duration_ms, y semantic_vad, que usa un modelo para estimar la probabilidad de que hayas terminado y ajusta el timeout de forma dinámica, controlado con eagerness (low, auto/medium, high).

    La regla práctica: eagerness: "low" cuando el usuario tiene que pensar o recordar datos, high cuando son respuestas cortas de sí/no. Esto se nota más en el resultado final que cambiar de modelo.

    Transporte: WebSocket no es la respuesta por defecto

    La documentación oficial es clara. WebRTC para clientes de navegador y móvil que capturan o reproducen audio directamente. WebSocket cuando tu servidor ya recibe audio crudo de un pipeline de medios o de una centralita. SIP para telefonía.

    La razón de fondo es que WebRTC trae de fábrica lo que en WebSocket tendrías que construir tú: control de jitter, adaptación a pérdida de paquetes, cancelación de eco. En una wifi doméstica no notarás la diferencia. En 4G en movimiento, sí — y es justo el escenario donde vive un agente de voz de verdad.

    Desde el navegador, el flujo recomendado pasa por tu backend: el cliente genera la oferta SDP, tu servidor la reenvía a https://api.openai.com/v1/realtime/calls con la configuración de sesión y devuelve la respuesta. Nunca expongas la API key en el cliente; para eso están los secretos efímeros de /v1/realtime/client_secrets.

    Coste: el audio se paga como audio

    Muchos proyectos se caen justo aquí, después de la demo. Precios oficiales consultados el 31 de julio de 2026, por millón de tokens:

    Modelo Audio entrada Audio entrada cacheada Audio salida
    gpt-realtime-2.1 $32,00 $0,40 $64,00
    gpt-realtime-2.1-mini $10,00 $0,30 $20,00
    gemini-3.1-flash-live-preview $3,00 (~$0,005/min) $12,00 (~$0,018/min)

    Lo interesante no es la cifra absoluta, es la comparación dentro del mismo modelo. gpt-realtime-2.1 cobra $4,00 por millón de tokens de texto de entrada y $32,00 por millón de tokens de audio de entrada. Ocho veces más por el mismo millón de tokens, solo por la modalidad.

    El otro número que deberías tener tatuado: el audio de entrada cacheado cuesta $0,40 frente a $32,00. Ochenta veces menos. En una conversación larga, donde cada turno reenvía todo el contexto anterior, el caché deja de ser una optimización y pasa a ser la diferencia entre un producto viable y uno que no. Si nunca has mirado de cerca cómo se cuentan los tokens y por qué el idioma influye en la factura, escribí sobre ello en tokens en español y el coste del tokenizador.

    El modelo mini cuesta exactamente 3,2 veces menos en audio no cacheado, tanto de entrada como de salida. Para el 80% de los agentes de voz reales — reservas, soporte de primer nivel, cualificación de leads — es más que suficiente.

    El código: una sesión realtime de verdad

    Esto es Node/Bun con TypeScript, a nivel de protocolo. Lo pongo así a propósito: los SDKs te esconden justo las partes que necesitas entender.

    import WebSocket from "ws";
    import { z } from "zod";
    
    // Tus dos piezas: el reproductor de audio del cliente y tu API de negocio.
    declare const player: {
      enqueue(chunk: Buffer): void;
      stop(): void;
      playedMs(): number; // ms realmente reproducidos al usuario
    };
    declare const reservas: { buscar(q: unknown): Promise<unknown> };
    
    const MODEL = "gpt-realtime-2.1";
    
    const ws = new WebSocket(`wss://api.openai.com/v1/realtime?model=${MODEL}`, {
      headers: { Authorization: `Bearer ${process.env.OPENAI_API_KEY}` },
    });
    
    const send = (event: Record<string, unknown>) => ws.send(JSON.stringify(event));
    
    ws.on("open", () => {
      send({
        type: "session.update",
        session: {
          type: "realtime",
          instructions:
            "Eres el asistente de reservas de Bar Nostrum. Frases cortas. " +
            "Nunca leas listas largas en voz alta: ofrece dos opciones como mucho.",
          audio: {
            input: {
              // OJO: format es un objeto, no la cadena "pcm16" de la beta antigua.
              format: { type: "audio/pcm", rate: 24000 },
              turn_detection: {
                type: "semantic_vad",
                eagerness: "low",         // el usuario tiene que recordar fechas
                interrupt_response: true, // permite barge-in
              },
            },
            output: { voice: "cedar" },
          },
          tools: [
            {
              type: "function",
              name: "buscar_disponibilidad",
              description: "Consulta mesas libres para una fecha y nº de comensales.",
              parameters: {
                type: "object",
                properties: {
                  fecha: { type: "string", description: "Formato YYYY-MM-DD" },
                  comensales: { type: "integer" },
                },
                required: ["fecha", "comensales"],
              },
            },
          ],
        },
      });
    });
    

    Ahora el bucle de eventos. Fíjate en player.playedMs(): ese es el punto donde casi todo el mundo se equivoca.

    let currentItemId: string | null = null;
    
    ws.on("message", async (raw) => {
      const event = JSON.parse(raw.toString());
    
      switch (event.type) {
        // El usuario habla mientras el agente habla → barge-in
        case "input_audio_buffer.speech_started": {
          if (!currentItemId) break;
    
          // Defensivo: con interrupt_response:true el servidor ya cancela solo.
          // Lo dejo por si algún día bajas a server_vad sin interrupción.
          send({ type: "response.cancel" });
          send({
            type: "conversation.item.truncate",
            item_id: currentItemId,
            content_index: 0,
            // CLAVE: lo que el usuario ha OÍDO, no lo que has recibido del socket.
            audio_end_ms: player.playedMs(),
          });
          player.stop();
          currentItemId = null;
          break;
        }
    
        case "response.output_audio.delta": {
          currentItemId = event.item_id;
          player.enqueue(Buffer.from(event.delta, "base64"));
          break;
        }
    
        case "response.function_call_arguments.done": {
          await handleToolCall(event.call_id, event.arguments);
          break;
        }
    
        case "response.done": {
          currentItemId = null;
          break;
        }
      }
    });
    

    Y la tool call, que es donde esto deja de ser una demo:

    const BuscarDisponibilidad = z.object({
      fecha: z.iso.date(),
      comensales: z.number().int().min(1).max(20),
    });
    
    async function handleToolCall(callId: string, rawArgs: string) {
      // 1. Valida SIEMPRE. El modelo alucina argumentos igual que alucina texto.
      const parsed = BuscarDisponibilidad.safeParse(JSON.parse(rawArgs));
      if (!parsed.success) {
        return sendToolOutput(callId, { error: "argumentos_invalidos" });
      }
    
      // 2. Si la tool tarda, habla antes de que el silencio se note.
      const filler = setTimeout(() => {
        send({
          type: "response.create",
          response: {
            // CLAVE: out-of-band. Sin esto, cuando la tool resuelva lanzarás un
            // segundo response.create mientras el relleno sigue generando audio.
            conversation: "none",
            instructions:
              "Di una frase muy corta indicando que estás consultando. " +
              "No inventes el resultado.",
          },
        });
      }, 400);
    
      const mesas = await reservas.buscar(parsed.data);
      clearTimeout(filler);
    
      sendToolOutput(callId, mesas);
    }
    
    function sendToolOutput(callId: string, output: unknown) {
      send({
        type: "conversation.item.create",
        item: {
          type: "function_call_output",
          call_id: callId,
          output: JSON.stringify(output),
        },
      });
      send({ type: "response.create" });
    }
    

    Ese setTimeout de 400 ms resuelve el silencio incómodo. Mientras la tool consulta tu API, el agente dice "déjame que lo mire" y luego encadena con el resultado. Es lo que hace un humano al teléfono, y sin ello cualquier consulta que tarde más de medio segundo se siente como una caída.

    La validación con Zod no es opcional. Los argumentos de una tool call son texto generado por un modelo, y tratarlos como datos de confianza es la misma clase de error que confiar en el body de una petición HTTP sin validarlo. Si quieres afinar esa capa, la trabajo a fondo en el curso de Zod para validación en TypeScript.

    Si prefieres abstracción, el SDK de agentes de OpenAI para JavaScript expone RealtimeAgent, RealtimeSession y un helper backgroundResult para tools largas. El diseño de herramientas es el mismo que expliqué en el servidor de herramientas con el Agent SDK de Anthropic.

    Cuándo NO usar un agente de voz

    La voz se está metiendo en sitios donde estorba.

    No uses voz cuando el usuario tenga que dar datos exactos y largos: un IBAN, un email, una referencia alfanumérica. Deletrear "bezael arroba dominicode punto com" por teléfono es peor experiencia que un input de texto, siempre.

    No uses voz cuando el resultado sea una lista para comparar. La voz es un canal estrictamente serial: no puedes escanear, ni volver atrás, ni ver tres precios a la vez.

    No uses voz cuando el error sea caro e irreversible. Confirmar una transferencia bancaria con un VAD que puede cortarte a media frase es pedir un incidente.

    La voz gana en tres escenarios: cuando las manos están ocupadas, cuando el input es abierto y ambiguo — describir un problema es más rápido hablando que rellenando diez campos — y cuando el canal ya es voz porque el usuario ha llamado por teléfono.

    Si tu caso no encaja en ninguno, un formulario gana. Y un formulario cuesta cero dólares por millón de tokens.

    Qué hacer hoy

    Ese número —los milisegundos hasta que el agente empieza a hablar— es tu producto. El prompt, la voz y las funcionalidades solo importan si está por debajo del umbral en el que la gente deja de sentir que habla con una máquina rota. Así lo mido:

    1. Monta una sesión realtime mínima con una sola tool enganchada.
    2. Instrumenta dos marcas de tiempo: input_audio_buffer.speech_stopped y el primer response.output_audio.delta.
    3. Mide 20 turnos con tu red real y tu dispositivo real. En localhost todo va rápido.
    4. Si la mediana pasa de 800 ms, ataca en este orden: caché de audio de entrada, gpt-realtime-2.1-mini, WebRTC en lugar de WebSocket, y frase de relleno para las tools lentas.
    5. Vuelve a medir. Repite hasta que nadie diga "¿hola?".

    Si quieres construir esto con método en lugar de a golpe de prueba y error, es el enfoque que enseño en Construye con IA: de la idea al producto con Claude Code. Y si prefieres no pelearte con esto en solitario, en Dominicode Labs es donde desmontamos este tipo de arquitecturas entre developers que están construyendo cosas parecidas.

    Preguntas frecuentes

    ¿Merece la pena todavía el pipeline STT → LLM → TTS?

    Sí, pero para otro problema. Es la arquitectura correcta cuando necesitas un checkpoint de texto auditable, cuando quieres cambiar de proveedor de voz sin tocar el resto, o cuando el trabajo es transcripción y análisis por lotes en lugar de conversación. Para una conversación en tiempo real, un modelo speech-to-speech nativo te ahorra saltos de red y conserva la información prosódica que el texto intermedio destruye.

    ¿WebSocket o WebRTC para un agente de voz en tiempo real?

    La documentación de OpenAI recomienda WebRTC para clientes de navegador y móvil que capturan o reproducen audio directamente, WebSocket cuando tu servidor ya recibe audio crudo de un pipeline de medios, y SIP para telefonía. La diferencia se nota sobre todo en redes móviles: WebRTC trae control de jitter, adaptación a pérdida de paquetes y cancelación de eco de serie.

    ¿Cuánto cuesta un agente de voz con IA?

    A 31 de julio de 2026, gpt-realtime-2.1 cuesta $32 por millón de tokens de audio de entrada y $64 de salida; el modelo mini, $10 y $20. Gemini publica precios por minuto para gemini-3.1-flash-live-preview: unos $0,005 por minuto de audio de entrada y $0,018 de salida. La clave del coste real no es el precio de lista sino el caché: en el modelo grande, el audio de entrada cacheado baja de $32 a $0,40 por millón.

    ¿Cómo evito que el agente corte al usuario cuando hace una pausa para pensar?

    Usa detección de turno semántica en lugar de VAD por volumen. semantic_vad estima con un modelo la probabilidad de que el usuario haya terminado, en lugar de contar milisegundos de silencio, y ajusta el timeout dinámicamente. Con eagerness: "low" das más margen, que es lo que quieres cuando el usuario tiene que recordar una fecha o un dato.

    ¿Puedo usar Claude para un agente de voz en tiempo real?

    No como modelo speech-to-speech nativo: los modelos de Anthropic aceptan texto e imagen como entrada y devuelven texto, no audio. Ahora bien, como cerebro de un pipeline encadenado con STT y TTS externos funcionan muy bien — Fable 5 para el trabajo más exigente, Opus 5 y Sonnet 5 para el grueso, y Haiku 4.5 cuando la latencia manda por encima de todo. Es la opción sólida si necesitas ese checkpoint de texto intermedio o si ya tienes tus herramientas montadas sobre el Agent SDK de Anthropic. Para latencia conversacional pura, hoy la vía nativa pasa por los modelos realtime de OpenAI o Gemini Live.

    ¿Cuánta latencia es aceptable en un agente de voz?

    Por debajo de 800 ms desde que el usuario deja de hablar hasta que el agente emite el primer audio. A partir de un segundo la gente deja de percibirlo como lentitud y empieza a percibirlo como una llamada cortada. El estudio de Stivers y su equipo en PNAS muestra que en las diez lenguas analizadas los hablantes minimizan el silencio entre turnos, con una moda global de 0 ms y una mediana de +100 ms.

    ¿Se puede conectar un agente de voz a una centralita o a un número de teléfono?

    Sí. Para telefonía la vía es SIP: la Realtime API acepta conexiones SIP además de WebRTC y WebSocket, y proveedores como Twilio o Telnyx enrutan el número hacia tu sesión. Si tu pipeline de medios ya te entrega audio crudo en el servidor, WebSocket también sirve. WebRTC está pensado para clientes de navegador y móvil.


    Por Bezael Pérez — Developer senior con más de 15 años de experiencia y fundador de Dominicode.