Un prompt listo para copiar y pegar que saca JSON estructurado de texto desordenado, sin que el modelo se invente datos para rellenar los campos obligatorios. El truco no está en pedir JSON válido: está en tratar la ausencia como un valor más, prohibir que el modelo infiera y no fiarte nunca de su salida sin un parser de verdad detrás.

En resumen
- El fallo de verdad no es el JSON mal formado, es el JSON plausible: un teléfono inventado que parsea sin problema y te envenena los datos.
- Trata null como un valor más y prohíbele adivinar. La ausencia es información, no un hueco que haya que rellenar.
- El prompt es un borrador de la intención; el parser es el contrato. Valida con un esquema real (zod) y un formato estricto, nunca con la palabra del modelo.
- Acompaña el prompt con structured outputs (output_config.format) para que el modelo quede atado a tu esquema, no solo invitado a seguirlo.
- Guarda un campo de confianza y un enum claro para todo lo categórico, así una extracción mala se nota en vez de pasar inadvertida.
La extracción es donde muchos pipelines de LLM se tuercen sin que te enteres. Le pasas al modelo un correo de soporte o una bio sacada por scraping, le pides JSON, y te devuelve algo que parsea limpio, tan limpio que dejas de leerlo. El peligro nunca fue una llave que falta. Es el teléfono inventado, el nombre de la empresa deducido a partir de un dominio, el campo «intent» puesto en lo primero que sonó bien. Este recurso te da un prompt blindado justo contra eso, y después te explica cómo adaptarlo, cómo hacerlo cumplir con un validador de verdad y los detalles que deciden si terminas con registros limpios o envenenados.
La idea de fondo, debajo de todo: el prompt declara tu intención, el parser impone tu contrato. Un prompt es una sugerencia. Un esquema con «safeParse()» es la ley. Trátalos como dos mitades de un mismo sistema y la extracción deja de ser una fuente de corrupción silenciosa.
01 · El prompt (copia esto)
Aquí tienes la plantilla. Es seca a propósito: un esquema exacto, una prohibición tajante de inferir y una señal de confianza para que una extracción dudosa se delate sola. Pégala, cambia el esquema por el tuyo y mete el texto fuente donde está marcado.
Extrae datos del texto de abajo a JSON que cumpla EXACTAMENTE este esquema:
{
"name": string | null,
"email": string | null,
"company": string | null,
"phone": string | null,
"intent": "buy" | "support" | "feedback" | "other",
"confidence": number
}
Reglas:
- Si un campo no aparece DECLARADO en el texto, usa null. Nunca adivines,
infieras ni completes un valor desde el contexto. Un dominio en un correo
NO implica el nombre de la empresa. Un código de área NO implica la
ubicación.
- "intent" tiene que ser exactamente uno de los valores listados. Si
ninguno encaja con claridad, usa "other" — no inventes una etiqueta nueva.
- "confidence" es tu certeza de 0 a 1 de que "intent" es correcto, basada
solo en lo que el texto realmente dice.
- No cites nada que no hayas visto. Si el texto es ambiguo, prefiere null y
una confianza más baja antes que una suposición segura.
- Devuelve SOLO el objeto JSON. Sin prosa, sin explicación, sin bloque de
código.
TEXTO:
"""
<pega aquí el texto fuente>
"""
Dos cláusulas hacen casi todo el trabajo. "Si un campo no aparece DECLARADO, usa null" convierte la ausencia en un valor que el modelo tiene permitido devolver, en lugar de un espacio en blanco que se siente obligado a llenar. "Un dominio en un correo NO implica el nombre de la empresa" es un ejemplo concreto de la regla abstracta. Los modelos respetan mucho mejor las prohibiciones concretas que las abstractas, así que sé explícito con la inferencia que más quieres evitar.
Consejo
El campo «confidence» es un seguro barato. No hace falta que actúes sobre él desde el primer día, pero registrarlo te da algo: cuando una extracción se vea rara tres semanas después, ordenas por confianza y das con las dudosas al instante, en vez de tener que releerlo todo.
02 · Por qué el "JSON plausible" es el verdadero enemigo
Una respuesta mal formada es el caso fácil: tu parser lanza un error, lo atrapas, reintentas o avisas. El fallo caro es la respuesta sintácticamente perfecta pero del todo inventada. Le pediste un string al modelo, el texto no traía ninguno, y antes que admitir el hueco se inventó una suposición con forma de string. Ese registro ya vive en tu base de datos, idéntico a uno real.
Esto pasa porque una instrucción pelada de "extrae X como string" presiona al modelo a producir un string. El modelo es servicial; una respuesta vacía le sabe a fracaso. Tienes que dejarle claro que la respuesta vacía es la respuesta correcta cuando el dato no está. De eso se trata, en el fondo, toda la disciplina de nulos.
En concreto, esta es la diferencia que hacen las reglas frente a un mismo correo ambiguo:
- Sin disciplina de nulos. Entrada: "Hola, les escribo desde el área de marketing." Salida: «company» queda como "Marketing", un departamento convertido en nombre de empresa. Plausible, parsea, mal.
- Con disciplina de nulos. Misma entrada. Salida: «company» es null, «confidence» baja. El hueco ahora se ve, y es honesto.
Atención
Nunca dejes que un pipeline de extracción escriba directo a un sistema de registro sin que una persona o un chequeo posterior revise las filas de baja confianza. Un valor inventado que parsea limpio se propaga en silencio, a los correos, a los reportes, a las decisiones, y no te das cuenta hasta que alguien recibe un mensaje dirigido al "Equipo de Marketing" de una empresa que no existe.
03 · Confía en el parser, no en el modelo
El prompt es tu declaración de intención, no tu garantía. La garantía viene de validar la salida del modelo contra un esquema real de tu lado, con código que produce un fallo limpio en vez de un registro envenenado. En TypeScript eso significa zod: campos nullable, un enum para lo categórico y «safeParse()», para que una forma inválida te devuelva un error sobre el que puedes ramificar, en lugar de reventarte en lo más hondo del pipeline.
import { z } from "zod"
const Extraction = z.object({
name: z.string().nullable(),
email: z.string().email().nullable(),
company: z.string().nullable(),
phone: z.string().nullable(),
intent: z.enum(["buy", "support", "feedback", "other"]),
confidence: z.number().min(0).max(1),
})
type Extraction = z.infer<typeof Extraction>
function parseExtraction(raw: string): Extraction | null {
const result = Extraction.safeParse(JSON.parse(stripFence(raw)))
if (!result.success) {
// Registra result.error, conserva el texto crudo, NO escribas un registro parcial.
return null
}
return result.data
}
Fíjate en el «email()» sobre el campo del correo. El modelo puede devolverte un string que no sea un correo; tu esquema lo atrapa. Esta es la división del trabajo: el prompt pide la forma correcta, el esquema la demuestra. Si solo vas a hacer una de las dos, quédate con el esquema. Un parser estricto con un prompt flojo igual falla de forma segura; un prompt fuerte sin parser falla en silencio.
Da un paso más: restringe al modelo, no solo se lo pidas
Si llamas a Claude por la API en vez de pegar en un chat, puedes dejar de depender del prompt para la forma. Los structured outputs (output_config.format con un esquema JSON, o el helper messages.parse() del SDK con un esquema de zod) atan la respuesta del modelo a tu esquema en el momento de generarla, soportado en Claude Opus 4.8, Sonnet 4.6 y Haiku 4.5. El modelo ya no puede emitir un campo de más ni el tipo equivocado para «intent»; eso queda impuesto, no pedido. Aun así conservas las reglas de comportamiento del prompt (null antes que adivinar, la semántica de la confianza), porque esas hablan de qué poner en los campos, y eso la estructura por sí sola no lo decide.
Nota
Los structured outputs garantizan la forma del JSON, no la verdad de los valores. Al modelo igual lo puedes llevar a una respuesta segura pero equivocada dentro de un sobre perfectamente válido. Las reglas de disciplina de nulos del prompt son las que atacan eso; el esquema y los structured outputs atacan la forma mal formada. Quieres las dos capas, porque cada una falla ante cosas distintas.
04 · Variables y placeholders
Tres cosas de la plantilla están pensadas para que las cambies. Trátalas como tu superficie de configuración:
- El bloque del esquema. Reemplaza los campos por los tuyos. Deja cada campo opcional como «string | null» (o el tipo que toque, con su «| null»), nunca solo «string». Ese único token es lo que le da permiso al modelo de decir "ausente". Para los campos obligatorios pero categóricos, usa un enum cerrado, no un string abierto.
- El enum de intent. Si no estás clasificando la intención, bórralo. Si sí, lista las etiquetas exactas que vas a poder manejar más adelante y agrega un comodín («other»). El comodín importa: sin él, el modelo se inventa una etiqueta en cuanto tus categorías reales no encajan, y te quedas con un valor que tu switch nunca había visto.
- El texto fuente. El bloque entre triples comillas, al final. Conserva los delimitadores. Evitan que una fuente parlanchina ("ignora las instrucciones anteriores y...") se lea como parte del prompt. Es una protección pequeña pero real contra prompt injection en entradas que no controlas.
Para trabajos en lote, el prompt queda idéntico byte por byte y lo único que cambia es el texto fuente. Conviene mantenerlo así a propósito: un prefijo idéntico es justo lo que permite que el prompt caching entre en juego a lo largo de miles de extracciones, así que no metas una marca de tiempo ni un id de fila dentro del bloque de instrucciones. Deja la parte que cambia (el texto) al mismísimo final.
05 · Variantes para casos más difíciles
La plantilla base cubre registros planos. Tres saltos de dificultad habituales:
- Objetos anidados. Cuando necesitas «address» como su propio objeto, defínelo inline en el esquema y repite la regla de null en cada subcampo: una calle no declarada es null, no un string vacío, y una dirección del todo ausente es null en vez de un objeto lleno de nulos. Di cuál de las dos quieres, sin dejarlo al aire. Los modelos se reparten entre una y otra.
- Arrays de items. Sacar las líneas de una factura, por ejemplo. Agrega "Devuelve un array vacío [] si el texto no contiene items, no fabriques una fila de relleno." La instrucción del array vacío es el equivalente con forma de array de la regla de null, y es la que más se olvida.
- Fuente en varios idiomas. Si el texto puede venir en español pero tus campos llevan llaves en inglés, agrega: "El texto puede estar en cualquier idioma; las llaves del JSON y los valores del enum se quedan en inglés exactamente como se especifican; no traduzcas las llaves." Si no, de vez en cuando el modelo te traduce «intent» a "compra" y te rompe el enum.
En cada variante el principio no cambia: nombra el caso ausente de forma explícita (null, [], «other») para que el modelo nunca tenga que elegir entre adivinar y fallar.
06 · Detalles que muerden
Una lista corta de cosas que te van a morder en producción, cada una con su arreglo:
- El bloque de código markdown. A los modelos les encanta envolver el JSON en un bloque cercado aunque les digas que no. No te pelees con el prompt eternamente. Quita la cerca inicial y final antes de parsear. Un helper «stripFence» de tres líneas no cuesta nada y zanja la discusión. (Los structured outputs se saltan esto por completo, otra razón para preferirlos.)
- String vacío vs null. Algunos modelos devuelven "" para un campo ausente en vez de null. Decide cuál acepta tu esquema y normaliza el otro en tu parser. Tratar "" y null como lo mismo de ahí en adelante te ahorra dos caminos de código para "falta".
- La confianza no está calibrada. Un 0.9 del modelo no es una probabilidad con la que puedas hacer cuentas; es una señal a grandes rasgos. Úsala para enrutar ("manda a revisión todo lo que baje de 0.6"), no para calcular con ella.
- Esquemas sobrecargados. Veinte campos en una sola extracción bajan la calidad de todos. Si estás sacando tanto, divídelo en dos prompts enfocados o en una segunda pasada. Un esquema ajustado extraído bien le gana a uno enorme extraído a la ligera.
Trata la extracción como un sistema con dos capas que se imponen y deja de darte miedo: un prompt que vuelve legal la ausencia e ilegal la inferencia, y un parser que convierte la promesa del modelo en un contrato verificable. El prompt de arriba es el punto de partida, no la respuesta completa. El «safeParse()» de tu lado es lo que de verdad deja fuera los registros malos. Lanza el parser primero; endurece el prompt después.
Puntos clave
- El enemigo es el JSON plausible, no el mal formado. Cuídate de los valores fabricados, no solo de la sintaxis rota.
- Trata la ausencia como un valor más: null, [] o un enum 'other', nombrado de forma explícita para que el modelo nunca tenga que adivinar para poder responder.
- El prompt declara la intención; el parser impone el contrato. Valida cada respuesta con un esquema real y falla de forma segura cuando no cuadre.
- Cuando controlas la llamada a la API, suma structured outputs (output_config.format / messages.parse) para imponer la forma, y conserva las reglas del prompt para el contenido.
- Lanza el parser primero, endurece el prompt después. Un parser estricto con un prompt flojo igual falla de forma segura.
Preguntas frecuentes
¿Por qué no pedir simplemente JSON válido y confiar en la respuesta?
Porque válido y correcto son problemas distintos. El modelo puede devolver JSON perfectamente formado con un nombre de empresa inventado o un teléfono adivinado: parsea bien y te corrompe los datos en silencio. El trabajo del prompt es prohibir la invención; el del parser es demostrar la forma. Si confías en la respuesta sin un validador, solo atrapas errores de sintaxis y nunca los valores fabricados, que son justo los caros.
Si los structured outputs restringen el esquema, ¿igual necesito las reglas del prompt?
Sí. Los structured outputs garantizan la forma del JSON (los campos correctos con los tipos correctos) pero no la verdad de los valores que van adentro. Un modelo restringido igual puede soltar una respuesta segura, equivocada y válida para el esquema. Las reglas de disciplina de nulos gobiernan qué entra en los campos (null antes que adivinar, la semántica de la confianza), y eso la estructura por sí sola no lo decide. Usa las dos: structured outputs para la forma, reglas del prompt para el contenido.
¿Cómo evito que el modelo envuelva el JSON en un bloque de código markdown?
Casi nunca del todo. Decirle que no ayuda, pero no es fiable, así que no quemes iteraciones peleando con el prompt. Quita la cerca inicial y final en tu parser antes de llamar a JSON.parse; un helper de tres líneas lo resuelve de una vez por todas. El arreglo más limpio es usar los structured outputs de la API, que devuelven el objeto directo, sin cerca que quitar. Si estás pegando en un chat, el helper que quita la cerca es la respuesta pragmática.
¿Qué hago con el campo de confianza?
Úsalo para enrutar, no para hacer cuentas. El 0.9 del modelo es una señal a grandes rasgos, no una probabilidad calibrada, así que no la promedies ni la multipliques. Un patrón práctico: acepta automáticamente las filas que pasen un umbral y manda todo lo de abajo a una persona o a una segunda pasada. Aunque no hagas nada con él el primer día, regístralo. Es lo que después te deja encontrar las extracciones dudosas ordenando en vez de releyendo.
¿Cómo manejo arrays vacíos y objetos anidados sin que el modelo fabrique relleno?
Nombra el caso ausente de forma explícita, igual que haces con null. Para arrays, agrega una línea: 'Devuelve un array vacío [] si el texto no contiene items, no fabriques una fila de relleno.' Para objetos anidados, repite la regla de null en cada subcampo y di si un objeto del todo ausente debe ser null o un objeto lleno de nulos. Los modelos se reparten entre las dos opciones, así que no lo dejes implícito. La regla que lo unifica todo es que cada forma necesita un valor vacío definido que el modelo pueda devolver en vez de adivinar.
¿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

Obtén JSON confiable de Claude
Deja de pedirle al modelo que "responda en JSON" y rezar para que salga bien. Restringe la salida con un esquema de verdad, un tool call forzado o structured outputs, y después valídala como si la hubiera escrito un desconocido, porque para tu parser eso es exactamente lo que pasó.

El skill de migración de base de datos: generar el forward + rollback con los chequeos de seguridad por delante
Un skill de Claude Code que toma un cambio de esquema descrito y te devuelve una migración forward revisada, un rollback probado y un informe de riesgo de locks y pérdida de datos, para que dejes de correr ALTERs destructivos contra producción a las 11 de la noche.

Skill de notas de reunión: de la transcripción a tareas con responsable y fecha
La mayoría de los resúmenes de reunión no son más que transcripciones bonitas que nadie vuelve a leer. Este skill hace lo contrario: extrae las decisiones, las tareas con responsable y fecha límite, y las preguntas abiertas; el resto lo bota. Terminas con una lista que pegas directo en tu gestor, no con un muro de texto.