Skip to content

Lección 05

Proyecto final: extractor de datos estructurados

Convierte texto libre (emails, facturas, mensajes) en JSON fiable con tests. El portfolio piece de este curso.


Lo que vamos a construir

Una función extract(text) que recibe texto libre y devuelve un objeto JavaScript con datos estructurados. Por ejemplo:

"Hola, soy Ana García, mi DNI es 12345678A y vivo en Calle Mayor 12, Madrid.
Mi teléfono es +34 600 123 456. Quiero reservar para el 15 de mayo a las 21:00."

…tiene que devolver:

{
  "nombre": "Ana García",
  "dni": "12345678A",
  "direccion": "Calle Mayor 12, Madrid",
  "telefono": "+34 600 123 456",
  "fechaReserva": "2026-05-15",
  "horaReserva": "21:00"
}

Paso 1 — Define el esquema

Antes del prompt, define el esquema en código. Esto es lo que va a hacer que tu sistema sea fiable.

import { z } from "zod";

const ReservaSchema = z.object({
  nombre: z.string(),
  dni: z.string().regex(/^\d{8}[A-Z]$/),
  direccion: z.string(),
  telefono: z.string(),
  fechaReserva: z.string().regex(/^\d{4}-\d{2}-\d{2}$/),
  horaReserva: z.string().regex(/^\d{2}:\d{2}$/),
});

type Reserva = z.infer<typeof ReservaSchema>;

Si el modelo se sale del esquema, lo cazamos en parse() y reintentamos.

Paso 2 — El prompt

Aplica todo lo aprendido:

const SYSTEM = `Eres un extractor de datos. Recibes un mensaje y devuelves SIEMPRE JSON válido con esta forma:

{
  "nombre": string,
  "dni": string (8 dígitos + 1 letra mayúscula),
  "direccion": string,
  "telefono": string (con prefijo internacional),
  "fechaReserva": "YYYY-MM-DD",
  "horaReserva": "HH:MM"
}

Reglas:
- Si un campo no está en el mensaje, devuélvelo como null.
- NO incluyas texto fuera del JSON.
- NO uses bloques markdown (sin \`\`\`).`;

const EJEMPLOS = `
Mensaje: "Soy Luis Pérez (DNI 87654321B). Reserva para el 1 de junio a las 14h."
{ "nombre": "Luis Pérez", "dni": "87654321B", "direccion": null, "telefono": null, "fechaReserva": "2026-06-01", "horaReserva": "14:00" }
`;

Paso 3 — La función

async function extract(texto: string): Promise<Reserva> {
  const res = await client.messages.create({
    model: "claude-haiku-4-5",
    max_tokens: 500,
    system: SYSTEM,
    messages: [
      { role: "user", content: `${EJEMPLOS}\n\nMensaje: "${texto}"` },
      { role: "assistant", content: "{" },
    ],
  });

  const raw = "{" + res.content[0].text;
  const json = JSON.parse(raw);
  return ReservaSchema.parse(json);
}

Fíjate en dos detalles clave:

  1. Assistant prefill con { — fuerza al modelo a empezar por JSON, sin texto introductorio.
  2. ReservaSchema.parse() — si el modelo se sale del esquema, lanza una excepción que puedes capturar y reintentar.

Paso 4 — Tests

Sin tests no es producción. Sin tests es magia.

import { describe, it, expect } from "vitest";

describe("extract", () => {
  it("extrae todos los campos cuando están", async () => {
    const r = await extract(
      "Soy Ana García (12345678A), Calle Mayor 12 Madrid, +34 600 123 456. Reserva 15/05 21:00."
    );
    expect(r.nombre).toBe("Ana García");
    expect(r.dni).toBe("12345678A");
    expect(r.fechaReserva).toBe("2026-05-15");
  });

  it("devuelve null para campos ausentes", async () => {
    const r = await extract("Soy Luis. Quiero reservar el 1 de junio.");
    expect(r.nombre).toBe("Luis");
    expect(r.dni).toBeNull();
  });

  it("normaliza la hora a HH:MM", async () => {
    const r = await extract("Reserva a las 9 de la noche.");
    expect(r.horaReserva).toBe("21:00");
  });
});

Paso 5 — Despliega

Mete tu función en un endpoint serverless (Cloudflare Workers, Vercel, Deno Deploy) y ya tienes una API de extracción que puede gestionar emails de tu negocio.

Coste estimado: menos de 0.001 € por extracción con claude-haiku-4-5.

Lo que llevas aprendido

  • Anatomía de un prompt: rol, contexto, instrucción, ejemplos, formato.
  • Zero-shot vs few-shot, y cuándo merece la pena cada uno.
  • Chain-of-thought para razonamiento.
  • JSON prefill para formato fiable.
  • Validación con zod para producción.

Siguiente paso

¿Quieres más? El siguiente curso es “Construye un chatbot con IA”, donde montamos un asistente con memoria conversacional y herramientas. Te espero allí.


NIVEL 2

Reto Pro

Reto Pro de esta lección

Misma idea, sin pistas, evaluada por tests automáticos.

Desbloquear Modo Pro · 19 € Pago único · acceso de por vida · sin suscripción
NIVEL 3

Hard Mode

Hard Mode

Variante extrema del reto. Tiempo límite y restricciones adicionales.

Desbloquear Modo Pro · 19 € Pago único · acceso de por vida · sin suscripción
BOSS

Hard Mode

🏆 Boss Challenge

Construye y despliega un clasificador de tickets en producción que tu instructor evaluará en vivo

Desbloquear Modo Pro · 19 € Pago único · acceso de por vida · sin suscripción