Para quién es: perfiles técnicos y responsables de pymes y organizaciones que ya han probado un chat de IA y quieren dar el paso a integrarla en sus procesos. Los capítulos 1, 2 y 13 no requieren programar.
Cómo usarla: cada capítulo termina con algo que funciona. Todo el código está comprobado con TypeScript en modo estricto.
Qué es (y qué no es) un modelo de lenguaje
Un modelo de lenguaje como Claude predice, palabra a palabra, la continuación más probable de un texto. Ha aprendido de enormes cantidades de texto y código, y por eso redacta, resume, traduce, razona sobre documentos y escribe programas.
Tres ideas que evitan el 80 % de las decepciones:
- No es una base de datos. No «sabe» tus precios ni tu horario. Si se lo das en el contexto, lo usa; si no, puede inventarlo.
- Trabaja con tokens. Un token son unas ¾ partes de una palabra. Pagas por tokens de entrada y de salida, y cada modelo tiene un límite de contexto.
- No recuerda entre llamadas. Cada petición a la API es independiente. La «memoria» de una conversación es el historial que tú le reenvías.
Qué modelo usar
Anthropic ofrece varias familias. Como regla práctica: un modelo rápido y barato (Haiku) para clasificar, extraer y resumir en volumen; uno equilibrado (Sonnet) para la mayoría de aplicaciones; y los más potentes (Opus y superiores) para razonamiento complejo y agentes. En esta guía usamos claude-sonnet-5. Consulta la página oficial de modelos antes de elegir, porque cambian a menudo.
Pedir bien: prompts que funcionan
La calidad de la respuesta depende sobre todo de la calidad de la petición. Un buen prompt de sistema tiene cuatro partes: rol, contexto, reglas y ejemplos. Separarlas con etiquetas tipo XML ayuda al modelo a no mezclarlas.
Eres el asistente de atención al cliente de una clínica dental de Gijón.
<contexto>
Horario: L-V 9:00-14:00 y 16:00-20:00. Urgencias: 985 000 000.
</contexto>
<reglas>
- Responde en español, en menos de 80 palabras.
- Nunca des diagnósticos: deriva siempre al dentista.
- Si no sabes algo, dilo y ofrece llamar a recepción.
</reglas>
<ejemplo>
Paciente: ¿Abrís el sábado?
Asistente: Los sábados no abrimos. Te esperamos de lunes a viernes, de 9 a 14 y de 16 a 20 h.
</ejemplo>Buenas prácticas
- Sé explícito: «en menos de 80 palabras» funciona mejor que «sé breve».
- Explica el porqué de las reglas: el modelo generaliza mejor cuando entiende la intención.
- Da uno o dos ejemplos del formato exacto que esperas.
- Para tareas complejas, pide que razone antes de responder.
- Pon los documentos largos al principio y la pregunta al final.
Tu primera llamada a la API con TypeScript
Necesitas Node.js 20 o superior y una clave de API de la consola de Anthropic. Crea un proyecto e instala el SDK oficial:
npm init -y
npm pkg set type=module
npm i @anthropic-ai/sdk
npm i -D typescript tsx @types/node# .env.local (nunca al repositorio; en Vercel: Settings → Environment Variables)
ANTHROPIC_API_KEY=sk-ant-...import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic(); // lee ANTHROPIC_API_KEY del entorno
const msg = await client.messages.create({
model: "claude-sonnet-5",
max_tokens: 1024,
system: "Eres el asistente de una academia de Oviedo. Respondes en español, breve y claro.",
messages: [{ role: "user", content: "¿Qué horario tiene el curso de ofimática?" }],
});
for (const block of msg.content) {
if (block.type === "text") console.log(block.text);
}
console.log(msg.usage); // tokens de entrada y salida: tu coste realEjecútalo con npx tsx --env-file=.env.local primera-llamada.ts. Fíjate en tres piezas: system define el comportamiento, messages es la conversación y usage te dice cuántos tokens has gastado.
Conversaciones y respuestas en tiempo real
Para un chat necesitas dos cosas: guardar el historial y mostrar la respuesta mientras se genera (streaming). La experiencia de usuario cambia por completo: el primer texto aparece en menos de un segundo.
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
const historial: Anthropic.MessageParam[] = [];
export async function responder(texto: string): Promise<string> {
historial.push({ role: "user", content: texto });
const stream = client.messages.stream({
model: "claude-sonnet-5",
max_tokens: 1024,
messages: historial,
});
stream.on("text", (t) => process.stdout.write(t)); // se ve mientras escribe
const final = await stream.finalMessage();
const respuesta = final.content
.map((b) => (b.type === "text" ? b.text : ""))
.join("");
historial.push({ role: "assistant", content: respuesta });
return respuesta;
}En producción, el historial no vive en memoria sino en una tabla de Supabase (por ejemplo mensajes con conversacion_id, rol y contenido). Así cada cliente recupera su conversación aunque vuelva días después. Es exactamente lo que hace un asistente de WhatsApp con historial por contacto.
Datos estructurados y herramientas (tool use)
Aquí la IA deja de ser un chat y empieza a trabajar dentro de tus procesos. Dos técnicas:
1. Salida estructurada
Pides un JSON con un esquema concreto y el SDK te lo devuelve validado y tipado. Ideal para leer facturas, clasificar correos o rellenar fichas.
import Anthropic from "@anthropic-ai/sdk";
import { zodOutputFormat } from "@anthropic-ai/sdk/helpers/zod";
import { z } from "zod";
const client = new Anthropic();
const Factura = z.object({
proveedor: z.string(),
fecha: z.string().describe("Formato AAAA-MM-DD"),
total: z.number(),
iva: z.number(),
});
const texto = "Suministros Nalón SL · 12/09/2026 · Base 200 € · IVA 21 % 42 € · Total 242 €";
const res = await client.messages.parse({
model: "claude-sonnet-5",
max_tokens: 1024,
messages: [{ role: "user", content: `Extrae los datos de esta factura:\n${texto}` }],
output_config: { format: zodOutputFormat(Factura) },
});
console.log(res.parsed_output); // objeto tipado: { proveedor, fecha, total, iva }2. Herramientas
Describes funciones de tu sistema y el modelo decide cuándo llamarlas. Tu código las ejecuta y le devuelve el resultado. El modelo nunca toca tu base de datos directamente: tú controlas qué hace cada herramienta.
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
const tools: Anthropic.Tool[] = [
{
name: "consultar_plazas",
description: "Devuelve las plazas libres de un curso por su código.",
input_schema: {
type: "object",
properties: { codigo: { type: "string", description: "Código del curso, p. ej. ADGG0208" } },
required: ["codigo"],
},
},
];
async function consultarPlazas(codigo: string) {
// Aquí iría tu consulta real (Supabase, ERP, hoja de cálculo…)
return { codigo, plazasLibres: 4 };
}
const messages: Anthropic.MessageParam[] = [
{ role: "user", content: "¿Quedan plazas en el ADGG0208?" },
];
let res = await client.messages.create({ model: "claude-sonnet-5", max_tokens: 1024, tools, messages });
while (res.stop_reason === "tool_use") {
messages.push({ role: "assistant", content: res.content });
const resultados: Anthropic.ToolResultBlockParam[] = [];
for (const b of res.content) {
if (b.type === "tool_use" && b.name === "consultar_plazas") {
const { codigo } = b.input as { codigo: string };
resultados.push({ type: "tool_result", tool_use_id: b.id, content: JSON.stringify(await consultarPlazas(codigo)) });
}
}
messages.push({ role: "user", content: resultados });
res = await client.messages.create({ model: "claude-sonnet-5", max_tokens: 1024, tools, messages });
}
console.log(res.content.find((b) => b.type === "text")?.text);Conectar la IA con tus documentos (RAG con Supabase)
RAG (Retrieval-Augmented Generation) significa: antes de preguntar al modelo, buscas en tus documentos los fragmentos relevantes y se los pasas como contexto. Así responde con tu información y no con la de internet.
- Divide tus documentos (normativa, catálogo, preguntas frecuentes) en trozos de unos pocos párrafos.
- Convierte cada trozo en un vector numérico (embedding) y guárdalo en Postgres con pgvector.
- Con cada pregunta, calcula su vector, busca los trozos más parecidos y pásalos a Claude.
-- 1) Activa pgvector y crea la tabla de trozos de documentos
create extension if not exists vector;
create table documentos (
id bigint generated always as identity primary key,
contenido text not null,
embedding vector(384) -- 384 dimensiones = modelo gte-small
);
create index on documentos using hnsw (embedding vector_cosine_ops);
-- 2) Función de búsqueda por similitud que llamarás con supabase.rpc()
create or replace function buscar_documentos(consulta vector(384), limite int default 5)
returns table (id bigint, contenido text, similitud float)
language sql stable as $$
select id, contenido, 1 - (embedding <=> consulta) as similitud
from documentos
order by embedding <=> consulta
limit limite;
$$;
-- 3) Seguridad: sin políticas, nadie lee la tabla desde el navegador
alter table documentos enable row level security;Supabase incluye un modelo de embeddings pequeño (gte-small) que puedes usar desde una Edge Function, sin contratar otro proveedor:
// supabase/functions/embedding/index.ts (Edge Function, Deno)
const session = new Supabase.ai.Session("gte-small");
Deno.serve(async (req) => {
const { texto } = await req.json();
const embedding = await session.run(texto, { mean_pool: true, normalize: true });
return Response.json({ embedding });
});import Anthropic from "@anthropic-ai/sdk";
import { createClient } from "@supabase/supabase-js";
const supabase = createClient(process.env.SUPABASE_URL!, process.env.SUPABASE_SERVICE_ROLE_KEY!);
const client = new Anthropic();
// embedding: vector de la pregunta, calculado con el mismo modelo que usaste al indexar
export async function preguntar(pregunta: string, embedding: number[]) {
const { data: trozos, error } = await supabase.rpc("buscar_documentos", {
consulta: embedding,
limite: 5,
});
if (error) throw error;
const contexto = (trozos as { contenido: string }[]).map((t) => t.contenido).join("\n---\n");
const res = await client.messages.create({
model: "claude-sonnet-5",
max_tokens: 1024,
system:
"Responde solo con la información del contexto. Si no está, di que no lo sabes.\n\n<contexto>\n" +
contexto + "\n</contexto>",
messages: [{ role: "user", content: pregunta }],
});
return res.content.find((b) => b.type === "text")?.text;
}MCP: un enchufe estándar para herramientas y datos
El Model Context Protocol es un estándar abierto para conectar asistentes de IA con sistemas externos. En lugar de programar la integración para cada aplicación, escribes un servidor MCP una vez y lo usan Claude Desktop, Claude Code, tus agentes y otros clientes compatibles.
Ejemplo: un servidor MCP que da acceso a la ficha de alumnos de una academia.
npm i @modelcontextprotocol/sdk zodimport { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
const server = new McpServer({ name: "academia", version: "1.0.0" });
server.registerTool(
"buscar_alumno",
{
title: "Buscar alumno",
description: "Busca un alumno por DNI y devuelve sus matrículas activas",
inputSchema: { dni: z.string().length(9) },
},
async ({ dni }) => {
const matriculas = [{ curso: "ADGG0208", estado: "activa" }]; // tu consulta real
return { content: [{ type: "text", text: JSON.stringify({ dni, matriculas }) }] };
}
);
await server.connect(new StdioServerTransport());Para usarlo desde Claude Desktop o Claude Code, se registra en su configuración:
{
"mcpServers": {
"academia": {
"command": "npx",
"args": ["tsx", "/ruta/a/mcp-academia/server.ts"]
}
}
}Agentes: la IA que ejecuta tareas de principio a fin
Un agente es un modelo en bucle: planifica, usa herramientas, revisa el resultado y decide el siguiente paso hasta terminar. Programar ese bucle a mano (capítulo 5) es posible, pero el Claude Agent SDK te lo da hecho, con herramientas para leer y escribir archivos, buscar, ejecutar comandos y conectar servidores MCP.
npm i @anthropic-ai/claude-agent-sdkimport { query } from "@anthropic-ai/claude-agent-sdk";
for await (const mensaje of query({
prompt: "Revisa los CSV de ./ventas, detecta duplicados y escribe un resumen en informe.md",
options: {
model: "claude-sonnet-5",
allowedTools: ["Read", "Glob", "Grep", "Write"],
permissionMode: "acceptEdits",
maxTurns: 20,
},
})) {
if (mensaje.type === "result" && mensaje.subtype === "success") {
console.log(mensaje.result);
console.log(`Coste: ${mensaje.total_cost_usd} USD`);
}
}Cuándo usar un agente
- Sí: tareas de varios pasos con resultado verificable (conciliar ficheros, preparar informes, migrar datos, revisar documentación).
- No: respuestas que deben ser inmediatas o que siguen siempre el mismo camino. Ahí basta una llamada con herramientas.
Skills: enseñar procedimientos a la IA
Una skill es una carpeta con un archivo SKILL.md (instrucciones) y, si hace falta, plantillas, scripts o datos. El modelo solo ve el nombre y la descripción; cuando la tarea encaja, carga el resto. Así puedes tener decenas de procedimientos sin llenar el contexto.
---
name: presupuestos
description: Prepara presupuestos de formación y consultoría con las tarifas
y condiciones vigentes. Úsala cuando pidan un presupuesto o una propuesta económica.
---
# Presupuestos
1. Lee `tarifas.md` para el precio por jornada y los descuentos.
2. Usa siempre la plantilla `plantilla.md` (no inventes apartados).
3. IVA: 21 % salvo formación exenta (indícalo si aplica).
4. Validez de la oferta: 30 días.
5. Guarda el resultado como `presupuesto-<cliente>.md`.La descripción es lo más importante: decide cuándo se activa la skill. Escríbela como «qué hace + cuándo usarla».
import { query } from "@anthropic-ai/claude-agent-sdk";
// Carga las skills de .claude/skills/ del proyecto y activa solo la que queremos
for await (const m of query({
prompt: "Prepara el presupuesto para Talleres Llanera: 3 jornadas de formación en IA",
options: {
settingSources: ["project"],
skills: ["presupuestos"],
allowedTools: ["Read", "Write"],
},
})) {
if (m.type === "result" && m.subtype === "success") console.log(m.result);
}Programar con un asistente: Claude Code
Claude Code es un asistente de programación que trabaja en tu terminal o editor: lee el proyecto, propone cambios, ejecuta pruebas y hace commits. No sustituye a quien programa. Multiplica a quien sabe qué quiere construir.
npm i -g @anthropic-ai/claude-code
cd mi-proyecto
claudeLa pieza clave es el archivo CLAUDE.md en la raíz del proyecto: las reglas que el asistente leerá siempre.
# CLAUDE.md — reglas del proyecto
- Stack: Next.js (App Router) + TypeScript estricto + Supabase.
- Nunca uses la service_role key en código de cliente.
- Toda tabla nueva lleva RLS activado y sus políticas en la misma migración.
- Textos de la interfaz en español de España.
- Antes de terminar: `npm run lint && npm run build` sin errores.Flujo de trabajo que funciona
- Explorar: «Lee el módulo de reservas y explícame cómo funciona». Aún sin tocar nada.
- Planificar: «Propón un plan para añadir cancelaciones». Revisa el plan antes de aprobarlo.
- Implementar en pasos pequeños, con pruebas.
- Revisar el diff como revisarías el de un compañero.
Tareas programadas y automatizaciones
Gran parte del valor de la IA en una pyme no está en el chat, sino en procesos que corren solos: el resumen diario de consultas, la clasificación del correo entrante, el aviso de facturas pendientes.
Con Vercel, una tarea programada es una ruta de tu aplicación más una línea en vercel.json:
{
"crons": [
{ "path": "/api/resumen-diario", "schedule": "0 7 * * 1-5" }
]
}// app/api/resumen-diario/route.ts (Next.js en Vercel)
import Anthropic from "@anthropic-ai/sdk";
import { createClient } from "@supabase/supabase-js";
export async function GET(req: Request) {
// Vercel envía este secreto en las llamadas del cron: así nadie más puede dispararlo
if (req.headers.get("authorization") !== `Bearer ${process.env.CRON_SECRET}`) {
return new Response("No autorizado", { status: 401 });
}
const supabase = createClient(process.env.SUPABASE_URL!, process.env.SUPABASE_SERVICE_ROLE_KEY!);
const ayer = new Date(Date.now() - 86_400_000).toISOString();
const { data: consultas } = await supabase
.from("consultas")
.select("texto, canal")
.gte("creado_en", ayer);
const res = await new Anthropic().messages.create({
model: "claude-haiku-4-5",
max_tokens: 800,
messages: [{
role: "user",
content: `Resume en 5 viñetas los temas más repetidos:\n${JSON.stringify(consultas)}`,
}],
});
const resumen = res.content.find((b) => b.type === "text")?.text ?? "";
await supabase.from("resumenes").insert({ fecha: ayer.slice(0, 10), resumen });
return Response.json({ ok: true });
}Fíjate en dos decisiones: se protege con CRON_SECRET y usa claude-haiku-4-5, porque resumir es una tarea sencilla y así el coste es mínimo.
Desplegar: Vercel + Supabase en producción
La arquitectura recomendada para una pyme es sencilla y barata de mantener:
- Vercel: la web y las rutas de API en Next.js. Cada
git pushpublica una versión nueva y cada rama tiene su URL de prueba. - Supabase: base de datos Postgres, autenticación, almacenamiento de archivos, pgvector y Edge Functions.
- Anthropic API: llamada siempre desde el servidor, nunca desde el navegador.
# .env.local (nunca al repositorio; en Vercel: Settings → Environment Variables)
ANTHROPIC_API_KEY=...
SUPABASE_URL=https://xxxx.supabase.co
SUPABASE_ANON_KEY=... # pública: solo sirve con RLS bien configurado
SUPABASE_SERVICE_ROLE_KEY=... # secreta: solo en el servidor
CRON_SECRET=... # protege tus tareas programadasSeguridad a nivel de fila (RLS)
En Supabase, la clave pública viaja al navegador. Lo que protege tus datos son las políticas RLS: reglas en la base de datos que deciden qué filas ve cada usuario.
-- Cada usuario ve solo las conversaciones de su empresa
alter table conversaciones enable row level security;
create policy "leer_mi_empresa" on conversaciones
for select to authenticated
using (empresa_id = (select empresa_id from perfiles where id = auth.uid()));Controlar el coste
- Guarda
usagede cada llamada en una tabla y revisa el gasto por cliente y por función. - Fija límites de gasto en la consola de Anthropic.
- Usa el modelo más pequeño que cumpla y aprovecha el prompt caching para prompts largos que se repiten.
IA responsable: datos, normativa y calidad
Una solución de IA que funciona pero no cumple la normativa es un riesgo, no un activo. Cuatro frentes:
Protección de datos (RGPD)
- Minimiza: envía al modelo solo los datos necesarios. Seudonimiza cuando puedas.
- Informa en tu política de privacidad de qué proveedores tratan los datos (Anthropic, Vercel, Supabase) y firma sus acuerdos de encargado del tratamiento.
- Elige región de datos en la UE para Supabase cuando trates datos personales.
Reglamento Europeo de IA (AI Act)
Desde febrero de 2025 es exigible la alfabetización en IA: las organizaciones que usan IA deben asegurar que su personal tiene conocimientos suficientes. Las demás obligaciones entran por fases, entre ellas la de transparencia (avisar de que se está hablando con una IA) y las de sistemas de alto riesgo. Revisa el calendario oficial antes de cada proyecto y, en cualquier caso, identifica siempre tu chatbot como IA: es buena práctica y genera confianza.
Evaluar antes de publicar
- Crea un conjunto de 20 a 50 preguntas reales con la respuesta esperada.
- Ejecútalo con cada cambio de prompt o de modelo y compara resultados.
- Incluye preguntas trampa: fuera de tema, datos que no tiene, intentos de saltarse las reglas.
Supervisión humana
Define qué decisiones nunca toma la IA sola: precios especiales, reclamaciones, cualquier cosa con consecuencias legales o de salud. La IA prepara, la persona decide.
¿Lo aplicamos a tu organización?
Formación a medida para tu equipo, auditoría tecnológica o acompañamiento en tu primer proyecto de IA.
Hablemos