Le pides tests a un modelo y te entrega diez variaciones del camino feliz, justo donde casi nunca se esconden los bugs. Este es el prompt, listo para copiar y pegar, que le da la vuelta al asunto: primero arma una taxonomía de casos límite, después escribe un test enfocado por cada categoría, y obliga al modelo a decir qué categorías no aplican y por qué. Te llevas la plantilla, los placeholders que tienes que rellenar, variantes para funciones puras, async y handlers de API, y los detalles que convierten los tests generados en una falsa sensación de seguridad.

En resumen
- Los LLM sobre-prueban el camino feliz porque es lo más fácil de escribir. Los bugs viven en los bordes que se salta.
- Fuerza primero una taxonomía de casos límite y después un test por categoría. La cobertura deliberada le gana a un montón de tests casi calcados del camino feliz.
- Haz que 'qué categorías no aplican y por qué' sea una salida obligatoria, para que el modelo no infle la suite con tests irrelevantes.
- Trata las aserciones generadas como un borrador: el modelo adivina los mensajes de error y la forma del retorno. Córrelas una vez y ajusta los valores esperados contra el comportamiento real.
- Los tests son una especificación, no una red de seguridad en la que confías a ciegas. Un test generado que pasa contra un valor esperado equivocado es peor que no tener test.
Le pides a un modelo "escribe tests para esta función" y casi siempre te da lo mismo: cinco o seis casos que recorren el camino feliz con inputs válidos apenas distintos. Pasan, el número de cobertura sube, y despliegas una función que se cae la primera vez que alguien le pasa un array vacío o un string donde se esperaba un número. El problema no es que el modelo no sepa escribir tests. Es que el camino feliz es lo más fácil de generar, y los bugs viven en todo lo demás. Esta página te entrega un prompt que arregla el orden de las cosas: obliga al modelo a enumerar las formas en que un input podría romper la función antes de escribir un solo test, y después escribe un test deliberado por cada modo de fallo. Te llevas la plantilla, los placeholders, variantes para funciones puras, código async y handlers de API, y los detalles que vuelven peligrosos a los tests generados sin que te des cuenta.
01 · Por qué los modelos sobre-prueban el camino feliz
Un test de input válido que hace lo esperado es el más fácil de escribir, tanto para un modelo como para una persona. Sale directo del propósito de la función: la función suma dos números, así que el test suma dos números y comprueba el resultado. No hace falta imaginación. Por eso, cuando pides "unos tests", el modelo llena primero lo barato y se detiene apenas tiene una suite que se ve plausible.
El detalle es que el input válido rara vez rompe nada. Los bugs que llegan a producción viven en los bordes que el camino feliz nunca toca:
- Vacío y límite: el array vacío, el cero, el único elemento, el largo máximo, el off-by-one en el último índice.
- Ausente y mal formado: null, undefined, un campo faltante, el tipo equivocado, un número que llegó como string desde un formulario.
- Adversarial y raro: un par de surrogates unicode, un emoji que son dos unidades de código, un string que también es un número válido, una clave duplicada, un negativo donde diste por hecho un positivo.
- Concurrencia y orden: dos llamadas compitiendo, un callback que dispara dos veces, estado mutado entre la lectura y la escritura.
Ninguno de estos es exótico. Son justo los inputs que un sistema real le manda a una función un martes cualquiera con tráfico. La solución no es pedir "más tests", porque eso solo te da más tests del camino feliz. La solución es cambiar qué se genera primero.
Nota
El porcentaje de cobertura es una trampa aquí. Diez tests del camino feliz pueden marcar 100% de cobertura de líneas en una función a la que nunca se llamó con null. La cobertura de líneas mide qué líneas se ejecutaron, no qué inputs sobreviviste. La cobertura de casos límite es la que de verdad atrapa bugs, y esa no aparece en el mismo medidor.
02 · El prompt del generador de tests
Aquí tienes la plantilla. La jugada central es la estructura de dos pasos: primero enumera las categorías de input que rompen la función, y luego escribe exactamente un test enfocado por cada categoría. La línea de "qué categorías no aplican y por qué" es la que sostiene todo. Evita que el modelo invente tests irrelevantes solo para que la lista se vea completa. Pégala en un chat después de la función, o úsala como instrucción para un subagente que escribe tests.
Aquí tienes la firma de una función y su contrato. Quiero tests de casos límite,
no tests del camino feliz.
[pega la firma, el contrato / doc comment, y los tipos de los que depende]
PASO 1 — Taxonomía. Antes de escribir ningún test, lista las categorías de input
que podrían romper esta función. Considera al menos:
- vacío / largo cero
- límite (mínimo, máximo, primero, último, off-by-one)
- null / undefined / campo faltante
- tipo equivocado (string donde se espera un número, etc.)
- sobredimensionado / input muy grande
- valores duplicados / repetidos
- unicode / multi-byte / emoji
- números negativos o fuera de rango
- orden / concurrencia, si la función tiene estado o es async
- cualquier borde específico del dominio que no haya listado
Por CADA categoría, di en una línea: ¿aplica a ESTA función? Si no aplica, di por
qué (ej: "el input está tipado como tupla no vacía, el vacío es imposible"). No
escribas un test para una categoría que no puede darse.
PASO 2 — Tests. Por cada categoría que aplica, escribe UN test enfocado en
[mi framework, ej. Vitest]. Cada test debe:
- tener un nombre que enuncie el caso exacto ("retorna 0 para un array vacío").
- seguir arrange / act / assert, una aserción lógica por test.
- afirmar el comportamiento esperado EXACTO: el valor de retorno, O que lanza,
O cuál error/mensaje específico. Marca con // VERIFY cualquier valor esperado
que hayas inferido en vez de saberlo por el contrato.
- nunca repetir un camino que ya cubre otro test.
No pruebes el camino feliz más de una vez. Cierra con una lista corta: las
categorías que te saltaste y la razón.
Tres instrucciones son las que hacen el trabajo de verdad:
- "Taxonomía antes que los tests" obliga al modelo a pensar los modos de fallo como una categoría antes de casarse con casos concretos. Sin eso, va asociando libremente a partir del camino feliz.
- "¿Aplica? Si no, ¿por qué?" convierte la lista de categorías en un filtro, no en un checklist que hay que llenar. Una función tipada para recibir una tupla no vacía no debería recibir un test de input vacío, y el modelo debería decirlo en vez de escribir un test que ni siquiera compila.
- "Marca con // VERIFY cualquier valor esperado que inferiste" es la válvula de honestidad. El modelo no sabe qué string de error lanza tu código; con esto marca sus propias suposiciones en vez de presentártelas como hechos.
03 · La salida, y por qué igual tienes que correrla
Dale una función real y obtienes algo así: primero una pasada de taxonomía y luego tests dirigidos. Este ejemplo es un parser que convierte un string en un entero positivo o lanza una excepción.
import { describe, it, expect } from "vitest"
import { parsePositiveInt } from "./parse"
describe("parsePositiveInt: casos límite", () => {
it("parsea un entero positivo normal", () => {
expect(parsePositiveInt("42")).toBe(42)
})
it("lanza con un string vacío", () => {
expect(() => parsePositiveInt("")).toThrow(/empty/) // VERIFY mensaje
})
it("lanza con un string no numérico", () => {
expect(() => parsePositiveInt("abc")).toThrow()
})
it("lanza con un número negativo", () => {
expect(() => parsePositiveInt("-3")).toThrow(/positive/) // VERIFY mensaje
})
it("rechaza un float, no trunca en silencio", () => {
expect(() => parsePositiveInt("3.5")).toThrow() // VERIFY comportamiento
})
it("maneja espacios al inicio/final", () => {
expect(parsePositiveInt(" 7 ")).toBe(7) // VERIFY: ¿hace trim?
})
// saltados: unicode (input solo dígitos ASCII), concurrencia (fn pura)
})
Fíjate en los comentarios // VERIFY. El modelo en realidad no sabe si tu parser hace trim de los espacios, si lanza «empty» o «blank», o si trunca los floats en vez de rechazarlos. Todo eso lo dedujo a partir de un contrato plausible. Esos no son resultados de tests. Son hipótesis. El flujo es: corre la suite una vez, mira cuáles aserciones // VERIFY fallan, y decide en cada fallo si el que está mal es el test (corriges el valor esperado) o el código (encontraste un bug).
Atención
Un test generado que pasa contra un valor que el modelo adivinó es el peor resultado posible aquí. Se ve verde, así que confías en él, pero está afirmando un comportamiento que nunca confirmaste. Trata cada línea // VERIFY como si estuviera en rojo hasta que la hayas comprobado contra una ejecución real. Nunca hagas commit de una suite generada sin haberla corrido al menos una vez.
En eso consiste toda la disciplina: el modelo es bueno enumerando qué podría romperse y malo sabiendo cuál es tu comportamiento exacto. Déjalo encargarse de lo primero y tú te encargas de lo segundo. Esa división del trabajo es lo que hace que la salida sea confiable en vez de decorativa.
04 · Variantes para el código que de verdad escribes
El esqueleto se adapta a distintos tipos de código; cambias las categorías que importan y el estilo de las aserciones. Tres formas comunes:
Funciones puras
La plantilla por defecto ya está afinada para estas. Las categorías giran todas en torno al espacio de input: vacío, límite, tipo, unicode, rango. Agrega una línea pidiendo candidatos basados en propiedades si usas algo como fast-check: "Para cualquier categoría donde un solo ejemplo sea débil, sugiere una propiedad y un generador en vez de un caso fijo." Los límites son donde los tests de propiedades se ganan el sueldo.
Código async y con estado
Agrega de forma explícita las categorías de orden y tiempo, porque para una función pura ni existen:
- la promesa se rechaza, no solo resuelve
- dos llamadas compiten; la segunda llega antes de que la primera resuelva
- un timeout o una señal de abort dispara a mitad de camino
- el mismo evento se entrega dos veces (idempotencia)
- el estado se lee, luego otro camino lo muta y después se escribe
Dile al modelo que use fake timers o promesas controlables en vez de delays reales, para que los tests se mantengan rápidos y deterministas.
Handlers de rutas de API
Aquí el "input" es un request, así que la taxonomía se desplaza hacia la superficie del request:
- body faltante o mal formado, content-type equivocado
- body válido pero que no pasa la validación de negocio
- sin autenticar y sin autorizar (son casos distintos, un 401 no es un 403)
- la dependencia río abajo (DB, API upstream) falla o da timeout
- el mismo request reintentado (¿cobra doble, envía doble?)
Consejo
Para los handlers, pídele al modelo que afirme el status code y la forma del error, no solo que "retorna un error". Un handler que retorna 200 con un body de error es un bug real que una aserción vaga deja pasar de largo. Fija el contrato: qué status, qué código de error, qué campos.
05 · Detalles que convierten los tests generados en falsa confianza
Cada uno de estos se ve bien en el diff y debilita la suite sin que nadie lo note. Son la diferencia entre tests que atrapan bugs y tests que solo están ahí.
-
Confiar en los valores esperados que el modelo adivinó. Ya lo cubrimos, pero vale repetirlo: el modelo inventa mensajes de error y formas de retorno. La convención // VERIFY solo sirve si de verdad corres la suite y resuelves cada marca. Si te saltas ese paso, lo que afirmaste es pura ficción.
-
Aserciones demasiado laxas. Un test que solo comprueba "lanza" pasa igual de bien si lanzó el error correcto o un TypeError por un typo. Donde importe, afirma el tipo o el mensaje de error específico, pero amárralo a un substring estable, no a la frase completa, para que un retoque de redacción no rompa el test.
-
Probar el mock, no el código. Cuando el modelo mockea una dependencia, a veces afirma que el mock se llamó con ciertos args y se da por satisfecho, sin comprobar qué hizo la función con el resultado. Eso prueba tu setup del mock, no tu lógica. Asegúrate de que al menos una aserción sea sobre la salida o el efecto real de la función.
-
Dejar que infle la lista. Sin la cláusula de "cuáles no aplican y por qué", el modelo escribe un test de concurrencia para una función pura y uno de unicode para un parser numérico, solo por llenar categorías. Cada test irrelevante es mantenimiento que cargas para siempre. Conserva esa cláusula y rechaza tests para inputs imposibles.
-
Un test gigante con diez aserciones. A veces el modelo mete todos los casos en un solo bloque «it». Cuando falla, no hay forma de saber qué caso se rompió. Insiste en un test enfocado por caso. El nombre del fallo debería decirte exactamente qué hizo regresión.
-
Sin bordes del dominio. La taxonomía genérica atrapa bugs genéricos. No va a saber que tu "porcentaje de descuento" tiene que estar entre 0 y 100, ni que un pedido no puede enviarse antes de estar pagado. Agrega siempre los bordes específicos del dominio tú mismo; el modelo solo puede enumerar lo que infiere de los tipos.
Usado así, el prompt no es una forma de ahorrarte escribir tests. Es una forma de dejar de esquivar los tests difíciles. El modelo hace la parte en la que es bueno: enumerar modos de fallo más rápido y más completo de lo que lo harías tú a las 5 de la tarde de un viernes. Y tú haces la parte que él no puede: confirmar qué hace tu código de verdad. Córrelos una vez, resuelve cada // VERIFY, y terminas con una suite que documenta los bordes en vez de adular el camino feliz.
Puntos clave
- El fallo por defecto de un LLM que escribe tests es el camino feliz; la solución es forzar una taxonomía de inputs que rompen la función antes de escribir un solo test.
- Conserva la cláusula de 'qué categorías no aplican y por qué'. Convierte la lista de categorías en un filtro y evita que la suite se llene de tests irrelevantes.
- Cada valor esperado generado es una hipótesis. Márcalos con // VERIFY, corre la suite una vez, y resuelve cada marca contra el comportamiento real antes de hacer commit.
- Ajusta la taxonomía según la forma del código: el espacio de input para funciones puras, orden y tiempo para async, la superficie del request y los status codes para handlers de API.
- Agrega siempre tú mismo los bordes específicos del dominio. El modelo solo puede enumerar lo que infiere de los tipos, no tus reglas de negocio.
Preguntas frecuentes
¿Por qué pedir la taxonomía primero en vez de pedir directamente tests de casos límite?
Porque 'escribe tests de casos límite' sigue siendo lo bastante vago como para que el modelo se ancle en el camino feliz y agregue uno o dos extras obvios. Obligarlo a enumerar primero las categorías de input que rompen la función hace que la cobertura sea deliberada. Tiene que considerar vacío, null, límite, unicode y concurrencia como una categoría antes de casarse con casos concretos. Además, la lista se convierte en un checklist que puedes repasar: si falta una categoría que claramente aplica, ya sabes que el modelo se la saltó.
¿Puedo confiar en las aserciones generadas, o de verdad tengo que correrlas?
Tienes que correrlas. El modelo conoce las categorías de input que rompen el código en general, pero no conoce tu comportamiento específico: si tu parser hace trim de los espacios, si lanza 'empty' o 'blank', si trunca un float o lo rechaza. La convención // VERIFY existe justo para que marque esas suposiciones en vez de esconderlas. Corre la suite una vez, y por cada // VERIFY que falla decide si el valor esperado del test está mal o si encontraste un bug real. Un test generado que pasa en verde contra un valor adivinado es peor que no tener test, porque se ve confiable y no lo es.
¿Esto reemplaza el testing basado en propiedades con fast-check?
No, se complementan. Los tests de borde basados en ejemplos fijan inputs concretos que ya sabes que son traicioneros (el array vacío, el off-by-one), y van de maravilla como documentación y como red de regresión. Los tests basados en propiedades exploran todo el espacio de input y encuentran el límite que ni se te había ocurrido. La variante de la sección 04 hace que el modelo sugiera propiedades para las categorías donde un solo ejemplo se queda corto, y esa es la división correcta: ejemplos elegidos a mano para los casos que conoces, propiedades generadas para los que no.
El modelo escribió un test de concurrencia para una función pura. ¿Cómo lo evito?
Para eso es exactamente la cláusula '¿aplica? si no, ¿por qué?', así que déjala en el prompt. Una función pura no tiene estado compartido, así que un test de concurrencia ahí es ruido que mantendrías para siempre. Si aun así el modelo infla la lista, suele ser porque no tiene suficiente del contrato para saber qué categorías son imposibles. Dale la firma de tipos completa y el doc comment, no solo el nombre de la función, y marcará correctamente como no aplicables categorías como unicode (en un parser numérico) o concurrencia (en una función pura).
¿Debería usar esto para TDD, antes de que exista la función?
Sí, y ahí funciona incluso mejor, porque el contrato es el único input. No hay implementación que espiar, así que el modelo no puede terminar probando por accidente lo que el código hace en vez de lo que debería hacer. Escribe la firma y el contrato, genera la taxonomía y los tests en rojo, y luego implementa hasta que pasen. El único detalle: como todavía no hay comportamiento real contra el cual correr, cada valor esperado es un // VERIFY, así que revisa con cuidado las aserciones contra el contrato antes de ponerte a programar para que pasen.
¿En qué se diferencia de pedirle a Claude Code que haga '/test' a un archivo?
Una instrucción pelada de 'escribe tests', en un agente o en un chat, te da el mismo sesgo hacia el camino feliz. El modelo llena los huecos fáciles y se detiene. Este prompt es la instrucción que pondrías detrás de ese comando: cambia qué se genera primero al forzar el paso de la taxonomía, y hace que el modelo declare sus suposiciones con // VERIFY. Si corres un subagente que escribe tests en Claude Code, mete esta plantilla como su system prompt y dejará de devolverte diez variaciones del caso obvio.
¿Prefieres que lo hagamos por ti?
Esto mismo lo construimos para negocios como el tuyo. La primera conversación es gratis y sin compromiso.
Escríbenos por WhatsAppPrimera conversación gratis. Te responde el fundador.
Recursos relacionados

El skill autor de pruebas: tests que fallan por razones reales
Un skill de Claude Code que escribe tests para lo que de verdad importa: casos límite y rutas de error, no getters ni teatro de cobertura. Qué hace, cuándo debe activarse, cómo funciona por dentro y los detalles que vuelven una suite en verde una falsa sensación de seguridad.

Coach de negociación con modelo del oponente: un prompt que te aprieta antes de halagarte
Un prompt reutilizable que primero arma un modelo explícito de la contraparte, después la interpreta en personaje y se sale del papel para entrenarte tras cada jugada, así ensayas contra una posición real y no contra alguien que te da la razón y se rinde. Cópialo, llena el brief y corre un simulacro.

Un prompt para mensajes de commit que escribe el porqué, no el qué
La mayoría de los mensajes de commit solo repiten el diff, que es justo lo que git ya sabe. Este es un prompt para copiar y pegar que toma un diff en staging y lo convierte en un mensaje de Conventional Commit que explica la intención, se niega a mezclar cambios sin relación y es lo bastante mecánico para usarlo en cada commit. Te llevas la plantilla completa, las variables que puedes ajustar, cuatro variantes y las formas en que puede fallar para que estés pendiente.