Category: TypeScript

  • Cómo tipar las respuestas de una LLM utilizando Zod y TypeScript

    Cómo tipar las respuestas de una LLM utilizando Zod y TypeScript

    Cómo tipar las respuestas de una LLM con Zod + TypeScript

    Tiempo estimado de lectura: 4 min

    • Valida en runtime: TypeScript desaparece en runtime; usa Zod para convertir datos inciertos en contratos verificables.
    • Esquema primero: diseña el esquema Zod como fuente única, extrae tipos con z.infer<> y serializa el esquema en el prompt.
    • Elige parse vs safeParse: .parse() para fallos severos, .safeParse() para autocorrección y reintentos.
    • Producción robusta: logging contextual, reintentos limitados, métricas y SLOs para gestionar errores LLM.

    Saber cómo tipar las respuestas de una LLM con Zod + TypeScript aparece en las primeras líneas porque es lo que evita que un agente, workflow o microservicio se rompa en producción. El modelo devuelve JSON, a menudo; nunca con la forma exacta que esperabas. .parse() y z.infer<> no son trucos: son la defensa que convierte datos inciertos en contratos verificables.

    Resumen rápido (lectores con prisa)

    Qué: Usa Zod para validar y transformar respuestas de LLM en runtime.

    Cuándo: Siempre que vayas a persistir datos o ejecutar acciones críticas basadas en la salida de una LLM.

    Por qué importa: TypeScript no valida en runtime; sin validación, datos malformados pueden romper sistemas en producción.

    Cómo funciona: Diseña el esquema Zod como fuente única, extrae tipos con z.infer<>, limpia la salida si es necesario y valida con .parse() o .safeParse().

    Por qué JSON.parse() + as es una trampa (y qué rompe)

    El patrón clásico:

    const obj = JSON.parse(response) as MyType;

    Es práctico. Es una mentira. TypeScript desaparece en runtime; as no valida nada. Los fallos típicos de LLMs:

    • JSON envuelto en bloques Markdown (“`json … “`).
    • Strings donde esperabas números (“15” vs 15).
    • Campos faltantes, claves renombradas o truncamiento por token limit.
    • Estructuras válidas pero semánticamente inválidas (amount: -5).

    Si confías en casts, esos problemas llegan a tu base de datos o a la lógica que ejecuta acciones. Validación en runtime es obligatoria.

    El patrón sólido: esquema primero, prompt segundo

    1. Diseña el esquema Zod como la única fuente de verdad.
    2. Extrae el tipo estático con z.infer<>.
    3. Incluye una versión legible del esquema en el prompt.
    4. Valida la respuesta con .safeParse() / .parse() antes de usarla.

    Este patrón convierte la salida estocástica de la LLM en una entrada controlada para tu sistema.

    Ejemplo práctico mínimo

    import { z } from 'zod';
    
    const TaskSchema = z.object({
      title: z.string().min(5),
      priority: z.enum(['low','medium','high']),
      estimatedHours: z.number().positive().optional(),
    });
    
    type Task = z.infer;

    Al pedir al modelo que devuelva JSON, serializa el esquema (o su forma) en el prompt para guiar la salida.

    Limpieza defensiva y parse()

    Los LLMs suelen añadir markdown. Limpia antes de parsear:

    function cleanJson(raw: string) {
      return raw.replace(/```json\n?|\n?```/g, '').trim();
    }
    
    const parsed = JSON.parse(cleanJson(rawOutput));
    const valid = TaskSchema.parse(parsed); // lanza ZodError si falla

    Usa .parse() cuando quieras detener el flujo y tratar la anomalía como error severo (útil en endpoints HTTP que deben devolver 4xx/5xx claros).

    .safeParse() y auto‑corrección de prompts

    En agentes autónomos o workflows escalables (n8n, agentes con herramientas), es preferible no lanzar. .safeParse() devuelve { success, data?, error? } para manejar fallos:

    const result = TaskSchema.safeParse(parsed);
    if (!result.success) {
      // registrar, emitir métricas y reintentar con autocorrección
      const zodErrors = result.error.flatten();
      // reintentar: enviar a la LLM el error y pedir JSON corregido
    }

    Patrón de autocorrección: incluye los errores de Zod en un nuevo prompt — el LLM suele corregir la estructura en el siguiente intento.

    Structured Outputs no elimina Zod

    Structured Outputs de OpenAI ayuda a reducir errores de formato (Structured Outputs). Aun así:

    • No controla cortes por token o fallos de red.
    • No valida la semántica (p. ej., números positivos).
    • No sustituye la necesidad de validar localmente antes de persistir o ejecutar acciones.

    Zod sigue siendo la última línea de defensa.

    Operativa en producción: logging, reintentos y SLOs

    • Registra errores de validación con contexto (prompt, response, zod.flatten()).
    • Implementa reintentos limitados con backoff y un máximo de autocorrecciones.
    • Mide métricas: tasa de validación fallida por modelo y prompt, latencia de corrección.
    • Decide SLOs: p. ej., si tras 2 reintentos sigue fallando, encolar para revisión humana.

    Esto transforma un fallo LLM en un incidente manejable, no en una caída silenciosa.

    Consejos prácticos y anti‑patrones

    • No uses as para confiar en la salida del modelo. Siempre valida.
    • Mantén los esquemas Zod como fuente única; evita duplicar interfaces manualmente.
    • Evita validaciones laxas (p. ej., z.any()) en bordes críticos.
    • Serializa el esquema de forma legible en el prompt, no como dump técnico que el modelo no entenderá.
    • Usa discriminated unions para estados exclusivos; obligan a la LLM a responder con casos válidos.

    Conclusión técnica

    Tipar las respuestas de una LLM con Zod + TypeScript no es una cuestión de estilo: es ingeniería de fiabilidad. .parse() y z.infer<> convierten la salida no determinista de una IA en datos verificables. Si construyes agentes, pipelines en n8n o features que actúan sobre sistemas críticos, aplicar este patrón es la diferencia entre sistemas que escalan y sistemas que requieren vigilancia humana constante. Implementa el esquema primero, valida siempre y deja que la LLM vuelva a intentarlo cuando falle — pero que falle donde tú lo controles.

    Para equipos que implementan flujos autónomos y pipelines de IA, una práctica útil es centralizar patrones de validación y autocorrección; esto es exactamente el tipo de trabajo que se experimenta en Dominicode Labs, donde se documentan plantillas y prácticas para agentes y workflows.

    FAQ

    ¿Qué es Zod y por qué usarlo?

    Zod es una librería de validación y parsing para JavaScript/TypeScript que permite definir esquemas y validar datos en runtime. Se usa para asegurar que los datos provenientes de fuentes no confiables (como LLMs) cumplen un contrato antes de ser consumidos por la aplicación.

    ¿Por qué no usar JSON.parse() + as?

    Porque as es solo una aserción en TypeScript y no realiza validación en runtime. JSON.parse() + as asume que la estructura es correcta; si no lo es, introducirás datos inválidos en tu sistema.

    ¿Cuándo usar .parse() vs .safeParse()?

    Usa .parse() cuando quieras que una anomalía detenga el flujo y sea tratada como error severo (por ejemplo, en endpoints HTTP que deben devolver 4xx/5xx). Usa .safeParse() cuando prefieras manejar el fallo (registrar, reintentar con autocorrección) sin lanzar una excepción.

    ¿Cómo incluir el esquema en el prompt?

    Serializa una versión legible del esquema en el prompt (por ejemplo, un objeto JSON con tipos esperados o una tabla de campos requeridos/formatos). Evita dumps técnicos largos; prioriza ejemplos y restricciones clave que la LLM entienda fácilmente.

    ¿Los Structured Outputs de OpenAI reemplazan a Zod?

    No. Structured Outputs reduce errores de formato, pero no controla cortes por token, fallos de red ni valida semántica (p. ej., números positivos). Zod sigue siendo necesario para validación en runtime.

    ¿Qué debo registrar cuando falla la validación?

    Registra el prompt, la respuesta cruda, el resultado de result.error.flatten() o el stack de la excepción, e información contextual (modelo, versión del prompt, intento actual). Estos datos facilitan autocorrección y análisis de causa.

  • Cómo elegir entre Hono, NestJS y Express en 2026

    Cómo elegir entre Hono, NestJS y Express en 2026

    Hono vs Express vs NestJS: cuál usar en 2026

    Tiempo estimado de lectura: 4 min

    Ideas clave

    • Decide por ejecución y mantenimiento: Edge/Serverless → Hono; contenedores y equipos grandes → NestJS; Express solo para legacy.
    • Hono sigue estándares Web (Fetch API) → despliegue directo en Cloudflare Workers, Deno y Bun; bundles mínimos y cold starts bajos.
    • NestJS aporta estructura, DI y patterns enterprise que facilitan gobernanza en equipos grandes.
    • Patrón recomendado: combinar Hono en el perímetro y NestJS en el core para optimizar latencia y mantenibilidad.

    Tabla de contenidos

    Resumen rápido (lectores con prisa)

    Hono es un microframework basado en la Fetch API ideal para despliegues Edge/Serverless con cold starts mínimos. NestJS es un framework estructurado con DI pensado para equipos grandes y aplicaciones empresariales. Express queda como opción legacy; para nuevo desarrollo en Node considera Fastify. Usa Hono en el perímetro y NestJS en el core cuando necesitas ambas propiedades.

    Introducción

    Hono vs Express vs NestJS: cuál usar en 2026 es la pregunta que debes resolver antes del primer commit. No es trivia: elegir mal condiciona latencia, coste, pruebas y, sobre todo, la capacidad del equipo para mantener código sano durante años.

    Si vas a desplegar en Cloudflare Workers, Deno o Bun, Hono cambia las reglas. Si tu sistema vive en Kubernetes y lo mantienen varios equipos, NestJS también cambia las reglas. Express… debería quedarse en mantenimiento.

    Hono vs Express vs NestJS: criterio de elección en 2026

    Primera regla: responde a dos preguntas concretas antes de elegir.

    • ¿Dónde se ejecutará el código? (Edge / Serverless vs contenedor)
    • ¿Quién lo mantendrá? (1–3 devs vs equipos >5)

    Hono está diseñado sobre estándares Web (Fetch API). Eso le da dos ventajas técnicas inmediatas: ejecuta sin cambios en Cloudflare Workers y Deno, y los bundles son mínimos → cold starts casi nulos. Ideal para servicios perimetrales: autenticación perimetral, proxies, transformaciones ligeras y APIs públicas de alta concurrencia.

    NestJS es lo opuesto deliberado: peso inicial mayor, pero estructura y DI que escalan en equipos grandes. Si necesitas módulos, interceptores, pipes, testing con mocks y un modelo mental homogéneo entre 10–50 ingenieros, NestJS reduce la deuda humana.

    Express sigue vivo por legacy. Técnicamente tiene problemas: acoplamiento a primitivas de Node (IncomingMessage/ServerResponse), soporte TypeScript no nativo y mala compatibilidad con runtimes Edge sin polyfills. Para proyectos nuevos, Fastify es una alternativa Node que merece consideración por rendimiento y plugins modernos.

    Performance y cold starts: cuándo importa realmente

    Si tu SLA pide latencia global baja y tus endpoints reciben picos distribuidos geográficamente, los cold starts importan. Hono arranca en milisegundos; NestJS arranca en cientos. Esa diferencia se traduce en UX y factura cuando se escala en funciones serverless.

    Ejemplo práctico:

    • API pública de alta concurrencia (CDN + Edge): Hono en Workers.
    • Sistema de facturación con colas, auditoría y scheduling: NestJS en contenedores.

    No mezcles requisitos. Si necesitas ambos, separa responsabilidades: Hono en el perímetro, NestJS en el núcleo.

    Escalabilidad de equipo y mantenibilidad

    NestJS gana por goleada en proyectos donde:

    • Múltiples equipos aportan features.
    • Necesitas contratos claros (interfaces, DTOs, guards).
    • Quieres testing coherente con DI y mocks.

    Hono exige disciplina. Sin una capa organizativa —convenios de carpeta, inyección manual, testing— terminas con endpoints inconexos. Aún así, para equipos pequeños o equipos senior que aceptan convenciones internas, Hono es una opción mantenible.

    Seguridad, observabilidad y ecosistema

    NestJS integra patterns enterprise: interceptores para logging, guards para auth, módulos para integración de colas (BullMQ), microservicios gRPC, etc. Si necesitas trazabilidad distribuida y middlewares estandarizados, te lo pone más fácil.

    Hono no te impide instrumentar trazas, pero requiere que diseñes la integración desde cero. Para infra ligera y métricas puntuales está bien; para auditoría y cumplimiento, NestJS acelera la adopción.

    Reglas prácticas para decidir (lista accionable)

    1. Despliegue Edge (Cloudflare Workers, Deno, Bun) → Hono. Docs: https://hono.dev, Cloudflare Workers: https://developers.cloudflare.com/workers
    2. Núcleo empresarial en Kubernetes → NestJS. Docs: https://docs.nestjs.com
    3. Proyecto nuevo pequeño, necesitas rendimiento en Node → Fastify sobre Express. Fastify: https://www.fastify.io
    4. Mantener app legacy en Express → parche, migración gradual a Fastify/NestJS o mantener si coste de migración es mayor.
    5. Necesitas tipado extremo end-to-end con frontend TS → Hono RPC o compartir DTOs desde NestJS.
    6. Requisito de cold starts <20ms → Hono (Edge); NestJS requiere contenedores siempre calientes.

    Patrón recomendado: combinar, no elegir a ciegas

    La arquitectura más práctica en 2026 no es monolítica en framework. Usa NestJS para la lógica de negocio, colas, trabajos en background y microservicios críticos; usa Hono para la capa perimetral, autenticación global, webhooks y endpoints que deben estar cerca del usuario.

    Ventaja: reduces coste y latencia donde importa, y mantienes previsibilidad y pruebas allí donde importa más: en el core.

    Conclusión

    Hono vs Express vs NestJS: cuál usar en 2026 no es una competición de popularidad. Es una cuestión de contexto. Si necesitas extremar latencia y desplegar en Edge, Hono gana. Si necesitas gobernanza de código, DI y un stack mantenible por equipos grandes, NestJS gana. Express sigue siendo válido solo para mantener legacy; para nuevo desarrollo, elige Fastify si trabajas exclusivamente en Node.

    Decide según ejecución y mantenimiento, no por hype. La mejor arquitectura combina herramientas: perímetro ligero (Hono) + corazón estructurado (NestJS). Eso te dará velocidad hoy y coherencia mañana.

    FAQ

    Respuesta: ¿Cuándo debo usar Hono en lugar de NestJS?

    Usa Hono cuando despliegues en runtimes Edge/Serverless (Cloudflare Workers, Deno, Bun) y necesites cold starts mínimos y alta concurrencia en endpoints públicos. Usa NestJS cuando necesites estructura, DI y gobernanza para equipos grandes.

    Respuesta: ¿Express todavía tiene sentido para proyectos nuevos?

    No para la mayoría de proyectos nuevos. Express se mantiene por legacy. Para nuevo desarrollo en Node, considera Fastify por rendimiento y plugins modernos.

    Respuesta: ¿Cómo afecta el entorno de ejecución a la elección del framework?

    El entorno define compatibilidad y cold starts. Hono está diseñado para la Fetch API y funciona en Workers/Deno/Bun sin polyfills. NestJS está pensado para contenedores y necesita procesos calientes para minimizar latencia.

    Respuesta: ¿Qué alternativas a Express recomiendan para Node moderno?

    Fastify es la alternativa recomendada por rendimiento y ecosistema de plugins. Evalúa Fastify sobre Express para proyectos nuevos en Node.

    Respuesta: ¿Puedo mezclar Hono y NestJS en la misma plataforma?

    Sí. Patrón recomendado: Hono en el perímetro (autenticación global, webhooks, endpoints edge) y NestJS en el núcleo (lógica de negocio, colas, trabajos). Separar responsabilidades reduce coste y mejora latencia.

    Respuesta: ¿Qué considerar sobre cold starts en serverless?

    Si tu SLA requiere latencias muy bajas y tienes picos geográficos, los cold starts importan. Hono puede arrancar en milisegundos en Edge; NestJS suele requerir contenedores calientes y arranques en cientos de ms.

  • Cómo Zod resuelve la validación de datos en TypeScript

    Cómo Zod resuelve la validación de datos en TypeScript

    Zod: la librería que todo dev TypeScript debería conocer en 2026

    Tiempo estimado de lectura: 3 min

    Ideas clave

    • Zod valida, transforma e infiere tipos en runtime, cerrando la brecha de type erasure de TypeScript.
    • Define esquemas una sola vez: validación runtime y tipos TypeScript derivados automáticamente.
    • Para DX y rapidez de integración en apps empresariales, Zod suele ser la mejor opción; para bundle size en edge, considera Valibot.
    • Patrones prácticos: esquema + inferencia, safeParse y transformaciones/refinements.

    Introducción

    Zod: la librería que todo dev TypeScript debería conocer en 2026. Lo digo sin dramatismos: si trabajas con TypeScript y datos externos, Zod debería ser parte de tu kit. Resuelve el problema fundamental que TypeScript no puede cubrir en tiempo de ejecución: validar, transformar y garantizar contratos cuando el compilador ya no está.

    Resumen rápido (lectores con prisa)

    Zod es una librería de validación y transformación de datos para runtime que infiere tipos TypeScript a partir de esquemas. Úsalo donde recibes datos externos (APIs, formularios, ficheros) para asegurar contratos en producción. Priorízalo por su balance entre experiencia de desarrollador e integración con herramientas modernas.

    Por qué Zod importa (y qué problema técnico resuelve)

    TypeScript y la brecha en runtime

    TypeScript te protege durante el desarrollo, pero los tipos se pierden al compilar (type erasure). Eso deja una brecha entre “lo que esperamos” y “lo que llega” — APIs externas, formularios, cron jobs, ficheros CSV. Sin validación en runtime, esa brecha se traduce en bugs impredecibles en producción.

    Zod colapsa dos responsabilidades que históricamente iban por separado: definición de esquema (validación runtime) y tipos TypeScript (tipado estático). Defines un esquema una sola vez; Zod infiere el tipo para que no haya duplicidad ni desincronización entre validación y tipado.

    Zod: la librería que todo dev TypeScript debería conocer en 2026 — comparación técnica

    No es magia; es diseño de librería pensado para TypeScript. Frente a alternativas:

    Yup

    • Yup: nació para JavaScript. Su soporte TypeScript es parcheado; la inferencia es frágil y obliga a aserciones manuales en proyectos estrictos.

    Valibot

    • Valibot: arquitectura modular y tree-shaking superior. Excelente para entornos edge donde cada KB cuenta.

    Zod

    • Zod: equilibrio entre DX, integración y robustez. API legible, transformaciones y refinements potentes, y amplia adopción en herramientas (tRPC, React Hook Form).

    Decisión práctica:

    • Si priorizas DX y rapidez de integración en aplicaciones empresariales: Zod.
    • Si necesitas minimizar bundle en Workers/Edge: Valibot.
    • Si mantienes legado con Yup y no puedes refactorizar ahora: sigue con Yup, pero planifica migración.

    Tutorial práctico: patrones esenciales con Zod

    Tres patrones que uso en producción: esquema + inferencia, safeParse y transformaciones.

    1) Definir esquema e inferir tipo

    // TypeScript
    import { z } from "zod";
    
    export const ProductSchema = z.object({
      id: z.string().uuid(),
      name: z.string().min(1, "Nombre obligatorio"),
      price: z.number().positive("Precio > 0"),
      category: z.enum(["tech", "food"]),
      publishedAt: z.coerce.date().optional(),
    });
    
    export type Product = z.infer<typeof ProductSchema>;

    Nota: z.coerce.date() convierte strings ISO a Date durante la validación. No necesitas un DTO separado.

    2) safeParse: predecible en producción

    const result = ProductSchema.safeParse(raw);
    
    if (!result.success) {
      // result.error.format() -> errores por campo listos para UI/LOG
      console.error(result.error.format());
      throw new Error("Payload inválido");
    }
    
    const product: Product = result.data;

    Nota: parse lanza excepciones; safeParse devuelve un objeto discriminado que encaja mejor en flujos robustos.

    3) Transformaciones y refinements

    const PriceSchema = z.string()
      .regex(/^\d+(\.\d{1,2})?$/)
      .transform(v => parseFloat(v));
    type Price = z.infer<typeof PriceSchema>; // number
    const PasswordSchema = z.object({
      password: z.string().min(8),
      confirm: z.string(),
    }).refine(data => data.password === data.confirm, {
      message: "Las contraseñas no coinciden",
      path: ["confirm"]
    });

    Integración con Angular y formularios reactivos

    El error arquitectónico común: mezclar reglas de negocio con validators en el componente. Mejor patrón: extraer la lógica a Zod y mapear errores a los controles.

    Flujo sugerido

    1. spec/schema.ts con esquemas Zod como fuente de verdad.
    2. Validador personalizado en Angular que ejecuta schema.safeParse(form.getRawValue()).
    3. Mapear error.format() a control.setErrors({ zod: mensaje }).

    Así la lógica es portable (frontend/backend), testeable y mantiene tipado estricto. Para patrones completos con Signals y Standalone Components ve el curso de integración.

    Cuando preferir otra opción

    • Valibot si tu constraint es bundle size en entornos edge.
    • Yup si tienes una base de código legacy donde migrar no es viable a corto plazo.
    • Pero en la mayoría de proyectos empresariales, Zod ofrece mejor balance entre seguridad, DX e integración con herramientas modernas.

    Conclusión y siguiente paso práctico

    Zod no es una moda: es una decisión arquitectónica que reduce errores por desincronía entre tipos y datos reales. Centraliza contratos, facilita transformaciones y mejora la trazabilidad de errores.

    Si quieres llevar esto al siguiente nivel —transformaciones avanzadas, validaciones asincrónicas y patrones de dominio— el curso práctico es la forma más rápida de internalizar buenas prácticas: Zod: Validación y Transformación de datos con TypeScript

    Aprender Zod hoy evita bugs mañana. Y eso, en producción, paga más que cualquier micro-optimización.

    FAQ

    ¿Qué es Zod?

    Zod es una librería de validación y transformación de datos para JavaScript/TypeScript que permite definir esquemas en runtime y derivar tipos TypeScript automáticamente.

    ¿Cuándo debería validar con Zod?

    Valida en cualquier borde del sistema donde recibas datos no confiables: llamadas API, formularios del usuario, ficheros externos o integraciones de terceros.

    ¿Zod reemplaza los tipos de TypeScript?

    No reemplaza los tipos estáticos del compilador; los complementa. Zod permite derivar tipos TypeScript desde un esquema único y aplica validación en runtime donde el compilador no llega.

    ¿Qué diferencia hay entre parse y safeParse?

    parse lanza una excepción si la validación falla. safeParse devuelve un objeto discriminado con success booleano y datos o error, lo que facilita flujos robustos sin excepciones inesperadas.

    ¿Puedo usar Zod en el frontend y backend con el mismo esquema?

    Sí. Mantener esquemas Zod compartidos permite que la lógica de validación y las transformaciones sean portables y consistentes entre cliente y servidor.

    ¿Cuándo elegir Valibot o Yup en lugar de Zod?

    Elige Valibot si la restricción principal es el tamaño del bundle en entornos edge. Mantén Yup solo si tienes un legado amplio que no puedes refactorizar aún; planifica migración a librerías con mejor tipado si es posible.

  • Cómo usar SDKs de AI tipados en TypeScript para reducir errores

    Cómo usar SDKs de AI tipados en TypeScript para reducir errores

    Typed AI SDKs: por qué usar el SDK de Anthropic o OpenAI en TypeScript y no JavaScript puro

    Tiempo estimado de lectura: 3 min

    • TypeScript tipado reduce errores silenciosos: convierte fallos indetectables en errores visibles en desarrollo.
    • Zod aporta validación en runtime: evita confiar en casts y valida la forma real de respuestas del modelo.
    • Patrón “esquema primero, prompt segundo”: serializa el esquema en el prompt y valida antes de persistir.

    Typed AI SDKs: por qué usar el SDK de Anthropic o OpenAI en TypeScript y no JavaScript puro — si vas a poner LLMs en producción, esa decisión cambia el perfil de riesgo de tu sistema. TypeScript no arregla la aleatoriedad del modelo, pero convierte fallos indetectables en errores visibles mientras desarrollas. Eso es todo; y es suficiente.

    Resumen rápido (lectores con prisa)

    Un SDK tipado (OpenAI o Anthropic) + validación runtime (Zod) convierte errores silenciosos en errores detectables durante desarrollo. Diseña el esquema primero, serialízalo en el prompt, parsea y valida la respuesta antes de usarla.

    Introducción

    Cuando integras un LLM en un flujo de trabajo (agentes, pipelines n8n, microservicios de extracción) no luchas contra la IA: luchas contra su impredecibilidad. JavaScript puro acepta promesas rotas y propiedades ausentes hasta que explotjan en producción. Un SDK tipado (OpenAI o Anthropic) empuja la mayoría de esos errores al compilador.

    Fuentes prácticas:

    Errores en compile time, no en producción

    – Modelos como valores literales: los SDK tipados exponen uniones de strings para modelos. Intentar model: 'gpt-5' fallará en el editor, no en prod.

    – Parámetros obligatorios: el compilador te obliga a rellenar lo que la API realmente necesita.

    – Propiedades opcionales: TS fuerza comprobaciones (?., if) antes de operar con datos potencialmente nulos.

    Resultado: menos hotfixes nocturnos. Detectas que algo está mal cuando escribes, no cuando lo usan clientes.

    Autocompletado real: productividad que importa

    IntelliSense deja de ser un lujo y pasa a ser documentación viva. Parámetros como temperature, response_format o function_call aparecen en el editor con sus tipos exactos. En equipos, esto reduce discusiones sobre “¿qué forma tenía ese objeto?” y evita JSON mal formado en llamadas a herramientas.

    La trampa del casting y por qué Zod es obligatorio

    TypeScript desaparece en runtime. Hacer const x = JSON.parse(s) as MyType es mentirle al compilador. Si el modelo devuelve "age":"veinticinco" habrás metido basura en tu flujo.

    Zod ofrece validación en tiempo de ejecución y genera el tipo TypeScript desde el esquema. Patrones recomendados:

    Patrones recomendados

    • Definir el esquema Zod como fuente única de verdad.
    • Incluir el esquema (o un resumen) en el prompt para guiar al LLM.
    • Parsear y validar la respuesta con Zod antes de usarla.

    Ejemplo práctico (OpenAI/Anthropic + Zod):

    import { z } from 'zod';
    import OpenAI from 'openai'; // o Anthropic desde '@anthropic-ai/sdk'
    
    const UserProfileSchema = z.object({
      fullName: z.string(),
      age: z.number().int().positive(),
      email: z.string().email(),
      tags: z.array(z.string()).max(5),
    });
    
    type UserProfile = z.infer<typeof UserProfileSchema>;
    
    const client = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
    
    async function extractUserProfile(text: string): Promise<UserProfile> {
      const resp = await client.chat.completions.create({
        model: 'gpt-4o',
        messages: [
          { role: 'system', content: `Devuelve solo JSON válido que cumpla este esquema: ${JSON.stringify(UserProfileSchema.shape)}` },
          { role: 'user', content: text },
        ],
        // response_format o similar según SDK
      });
    
      const raw = resp.choices?.[0]?.message?.content;
      if (!raw) throw new Error('Respuesta vacía del modelo');
    
      // Validación runtime: si falla, aquí lo capturas y reintentas o lo registras
      const parsed = UserProfileSchema.parse(JSON.parse(raw));
      return parsed;
    }

    Si usas Anthropic, adapta la llamada al cliente: la idea es la misma: pedir JSON estructurado y validar con Zod.

    Patrones que escalan: esquema primero, prompt segundo

    1. Diseña el esquema Zod.
    2. Infiere tipos TypeScript con z.infer.
    3. Serializa el esquema (o una versión legible) en el prompt.
    4. Valida la respuesta antes de persistir o procesar.

    Este patrón convierte al LLM en una fuente estocástica que vive dentro de un perímetro controlado. No reduces la tasa de “alucinaciones”, pero transformas una alucinación en un error tratable y reproducible.

    Cuándo aplicar este enfoque

    – Sistemas críticos: facturación, reconciliaciones, autorizaciones.

    – Workflows orquestados en n8n donde agentes ejecutan cambios de estado.

    – Microservicios que procesan datos externos y alimentan otras partes del sistema.

    Evítalo solo en prototipos desechables donde la velocidad de exploración sea prioritaria frente a la robustez.

    Conclusión

    Usar Typed AI SDKs de Anthropic o OpenAI en TypeScript y validarlos con Zod no es postureo técnico: es una estrategia de mitigación de riesgo. Cambias errores silenciosos por fallos detectables en desarrollo, mejoras la DX y pones una barrera defensiva entre la naturaleza impredecible del LLM y la integridad de tus datos. Implementa el patrón “esquema primero, prompt segundo” y tu siguiente incidente nocturno será opcional, no inevitable.

    Para trabajos relacionados con automatización, agentes y workflows en entornos de producción puedes explorar más prácticas y experimentos en Dominicode Labs. Se integra como una continuación lógica de los patrones descritos y recursos para orquestación y pruebas.

    FAQ

    Respuesta: TypeScript detecta discrepancias de tipos en tiempo de compilación, obligando a llenar parámetros obligatorios y a tratar opcionales. Reduce errores silenciosos que aparecerían solo en producción.

    Respuesta: Comprobaciones manuales ayudan, pero son repetitivas y propensas a olvidos. Zod ofrece esquemas reutilizables y validación automatizada en runtime que complementa a TypeScript.

    Respuesta: No. Zod aporta validación en runtime; TypeScript aporta seguridad en compile time. Juntos cubren ambos límites: desarrollo y ejecución.

    Respuesta: Serializa el esquema o un resumen legible (ej.: propiedades y tipos esperados) y pídelo explícitamente en el prompt. Luego parsea y valida la respuesta con Zod antes de usarla.

    Respuesta: Sí. El enfoque es independiente del proveedor: adapta la llamada al cliente de Anthropic pero mantiene la misma estrategia de pedir JSON estructurado y validar con Zod.

    Respuesta: Evítalo en prototipos desechables donde la velocidad de exploración prima sobre la robustez. Para sistemas críticos, es la opción recomendada.

  • Impacto del compilador Go en TypeScript 7.0 y su migración

    Impacto del compilador Go en TypeScript 7.0 y su migración

    TypeScript con compilador en Go: qué cambia en tu día a día

    Tiempo estimado de lectura: 4 min

    • Rendimiento: TypeScript 7.0 mueve el motor de tipos fuera de Node.js a un binario en Go, prometiendo hasta 10x en chequeo de tipos en repos grandes.
    • Compatibilidad: Dejar de ser paquete Node.js rompe integraciones que importan la API JS del compilador (p. ej. transformers, ts-morph).
    • Migración práctica: Requiere auditoría de dependencias, estrategias de CI para binarios y puentes (IPC/FFI) para plugins críticos.
    • Balance: Beneficios reales en CI y editor, pero coste organizativo técnico en tooling y librerías.

    TypeScript con compilador en Go: qué cambia en tu día a día es la pregunta que tu equipo debería haberse hecho ayer. TypeScript 7.0 reescribe el motor de validación de tipos fuera de Node.js y lo ejecuta como binario en Go. La promesa es velocidad (hasta 10x en chequeo de tipos en repos grandes). La consecuencia práctica: breaking changes reales en tooling, plugins y pipelines. Este artículo ordena el impacto, da criterio y propone un plan de migración accionable.

    Resumen rápido (lectores con prisa)

    Qué es: TypeScript 7.0 reescribe el motor de tipos como binario en Go, fuera de Node.js.

    Cuándo usarlo: Cuando busques chequeos de tipos significativamente más rápidos y puedas auditar dependencias que tocan el AST.

    Por qué importa: Reduce tiempo en CI y mejora la latencia del IDE, pero rompe integraciones que dependen de la API JS del compilador.

    Cómo funciona (resumen): El compilador pasa de paquete Node.js a binario/WASM; consumidores que importan typescript pierden acceso directo a la API JS y se requieren puentes (IPC/FFI) para mantener compatibilidad.

    TypeScript con compilador en Go: impacto técnico y operativo

    Rendimiento

    Mover el compilador de JavaScript a Go no es un simple cambio de implementación; cambia el modelo de extensión y la superficie de integración.

    • Rendimiento: el chequeo de tipos (tsc) se beneficia de concurrencia nativa (goroutines) y memoria más eficiente. Pipelines que tardaban minutos pasan a segundos.

    Forma de consumo

    • Forma de consumo: deja de ser un paquete Node.js importable; pasa a ser un binario (o potencialmente WASM). Los consumidores que usaban import ts from 'typescript' pierden acceso directo a la API JS del compilador.

    Plugins y transformers

    • Plugins y transformers: APIs como ts.Program, ts.Transformer y librerías como ts-morph quedan en una zona de incompatibilidad. Reconstruir puentes implica IPC/FFI y coste de latencia.

    Estrictez y errores emergentes

    • Estrictez y errores emergentes: el nuevo compilador puede ser más estricto en resolución de módulos y rutas, sacando a la luz configuraciones frágiles en tsconfig.json.

    Fuentes útiles: TypeScript, esbuild, swc, Vite.

    Qué mejora en tu día a día (y sin fricción)

    • CI/CD: tsc --noEmit deja de ser el cuello de botella. Menos tiempo en runners reduce facturación en GitHub Actions, CircleCI, etc.
    • IDE: TSServer consume menos RAM y responde mejor en proyectos con tipos complejos (Zod, Prisma, tRPC — Zod, Prisma, tRPC).
    • Monorepos: escalabilidad de chequeos por paquete, menor bloqueo en cambios amplios.

    Estos beneficios son reales y medibles. Pero no son automáticos para todos los proyectos.

    Qué se rompe en la práctica

    • Frameworks que dependen del AST en tiempo de compilación: Angular, NestJS y ORMs que usan transformers y decoradores verán fallos si no migran sus pipelines.
    • Herramientas de generación de código y scripts que importan typescript quedarán sin la API JS directa.
    • Plugins de bundlers y herramientas de análisis estático (p. ej. ts-morph) requieren reescritura o compatibilidad por puente.

    La migración implica trabajo no trivial por parte de mantenedores de librerías. No es sólo actualizar package.json.

    Plan de acción para equipos (prioritario y práctico)

    1. Auditoría de superficie de riesgo

    • Busca en tu árbol de dependencias cualquier uso de import 'typescript' o require('typescript').
    • Lista paquetes que usan transformers o ts.Program. Estas son las integraciones en riesgo inmediato.

    2. Aísla la transpilación del type-checking

    • Usa Vite/esbuild para bundling y transpile rápido.
    • Corre tsc --noEmit como job paralelo en CI. Esto desacopla cambios de runtime del chequeo estricto.

    3. Congela versiones y crea una ventana de transición

    • Si dependes de plugins no migrados (decoradores de NestJS, Angular compilers), fija TypeScript a ~5.x hasta validar compatibilidad.
    • Comunica fechas internas de evaluación y migración para dependencias clave.

    4. Prepara CI y runners

    • Asegúrate de que los runners pueden descargar y ejecutar binarios (amd64/arm64).
    • Considera imágenes Docker con el binario del compilador preinstalado para reproducibilidad.

    5. Estrategia de puentes para plugins

    • Para librerías críticas, evalúa dos alternativas: 1) reescribir transformadores en Go o 2) implementar un proceso IPC que exponga el AST a JS (aceptable temporalmente, pero con latencia).
    • Prioriza reescrituras sólo si la librería es central; para consumo general, pide a mantenedores roadmap y plazos.

    6. Testing y despliegue gradual

    • Canary builds: ejecuta el compilador en Go en un subset de repos o paquetes.
    • Monitorea regresiones en CI (errores nuevos de tipo o resolución) y en IDEs de desarrolladores.

    Criterio técnico final

    Adoptar TypeScript 7.0 por rendimiento sin auditoría es una apuesta peligrosa. El enfoque correcto es pragmático: separar responsabilidades, auditar dependencias que manipulan AST, preparar CI para binarios y coordinar migraciones con mantenedores de librerías críticas.

    La ventaja económica y productiva es real: feedback loops más rápidos, menos microinterrupciones del IDE y menos coste de CI. El precio es técnico y organizativo: trabajo de migración en tooling. Quien lo planifique hoy evita picos de trabajo y bloqueos de entrega mañana.

    FAQ

    ¿Qué cambia con TypeScript 7.0?

    TypeScript 7.0 reescribe el motor de validación de tipos fuera de Node.js y lo ejecuta como binario en Go. Como consecuencia, deja de ser un paquete Node.js importable y puede ofrecer mejoras de velocidad (hasta 10x en chequeo de tipos en repos grandes) gracias a concurrencia nativa y manejo de memoria en Go.

    ¿Afecta esto a mi CI?

    Sí. tsc --noEmit puede dejar de ser el cuello de botella y reducir el tiempo de ejecución en runners, lo que baja costes en servicios como GitHub Actions o CircleCI. Sin embargo, es necesario garantizar que los runners puedan descargar y ejecutar binarios en las arquitecturas requeridas.

    ¿Qué pasa con plugins que usan transformers?

    APIs que manipulan el AST o dependen de la API JS del compilador (por ejemplo, ts-morph) quedan en riesgo de incompatibilidad. Las alternativas son reescribir transformadores en Go o implementar puentes IPC/FFI para exponer el AST a procesos JS, con coste de latencia.

    ¿Debo migrar inmediatamente?

    No necesariamente. Es recomendable auditar dependencias que usen typescript, congelar versiones críticas (p. ej. ~5.x) y crear una ventana de transición hasta validar compatibilidad de librerías y plugins antes de adoptar en producción.

    ¿Cómo mitigo riesgos en monorepos?

    Aísla la transpilación del type-checking usando herramientas como Vite o esbuild, ejecuta chequeos de tipos como jobs paralelos en CI y aplica canary builds para ejecutar el compilador en Go en subsets de paquetes antes del despliegue general.

  • Asegura el tipo de datos en function calling usando TypeScript

    Asegura el tipo de datos en function calling usando TypeScript

    Function calling tipado con TypeScript: deja de adivinar lo que devuelve el modelo

    Tiempo estimado de lectura: 4 min

    • Ideas clave:
    • Los LLMs fallan en formato y semántica: validar la salida evita errores en producción.
    • Define esquemas con Zod, deriva tipos con z.infer<>, y valida antes de ejecutar herramientas.
    • Usa .parse() para fallar rápido en endpoints y .safeParse() para autocorrección en agentes.
    • Mide y registra: trazabilidad completa (prompt, response, error de Zod, tool invocada).

    Introducción

    Cuando un agente llama a una herramienta, el modelo genera un JSON con argumentos. Asumir que ese JSON tendrá la forma correcta es la fuente de la mayoría de fallos en producción. Implementar Function calling tipado con TypeScript: deja de adivinar lo que devuelve el modelo no es opcional: es ingeniería defensiva. Con Zod validas en runtime, con z.infer<> obtienes tipos sincronizados y con un framework que integre ambos cierras el círculo.

    Fuentes útiles: Vercel AI SDK, Zod, OpenAI Structured Outputs.

    Resumen rápido (lectores con prisa)

    Qué es: Validación tipada de la salida de modelos mediante Zod y TypeScript.

    Cuándo usarlo: Siempre que un LLM invoque herramientas, modifique estado o llame APIs críticas.

    Por qué importa: Previene errores por campos faltantes, tipos incorrectos o JSON mal formado en producción.

    Cómo funciona: Define esquemas Zod, deriva tipos con z.infer<>, valida con .parse() o .safeParse() antes de ejecutar.

    Por qué tipar el function calling importa ahora

    Los LLMs fallan de formas predecibles: omiten campos, envían strings en vez de números, rodean JSON con Markdown o inventan claves. Si procesas ese output con JSON.parse() y as Tipo, renuncias a la seguridad de TypeScript en runtime. El resultado: escrituras corruptas en bases de datos, llamadas a APIs con parámetros inválidos y bugs que sólo aparecen semanas después.

    La alternativa técnica es clara:

    • declarar el esquema con Zod,
    • derivar el tipo TypeScript con z.infer<>,
    • validar antes de ejecutar la herramienta.

    Eso convierte la entrada del agente en un contrato matemático que protege tu lógica de negocio.

    Arquitectura práctica: esquema → validación → ejecución

    Patrón recomendado:

    1. Define el esquema Zod y añádele descripciones que el LLM pueda leer.
    2. Expón ese esquema en el prompt (o úsalo con Structured Outputs).
    3. Valida la respuesta del LLM con .safeParse() o .parse() antes de llamar a la función.
    4. Si falla, captura el ZodError, loguéalo y opcionalmente reintenta con autocorrección.

    Código mínimo (ejemplo de consulta de divisas)

    import { tool } from 'ai'; // p. ej. Vercel AI SDK
    import { z } from 'zod';
    
    const ExchangeSchema = z.object({
      base: z.string().length(3).toUpperCase().describe('Moneda base ISO 4217, ej. USD'),
      target: z.string().length(3).toUpperCase().describe('Moneda destino ISO 4217, ej. EUR'),
    });
    
    type ExchangeParams = z.infer;
    
    export const getExchangeRate = tool({
      description: 'Devuelve el tipo de cambio entre dos monedas',
      parameters: ExchangeSchema,
      execute: async ({ base, target }: ExchangeParams) => {
        const res = await fetch(`https://api.exchangerate-api.com/v4/latest/${base}`);
        if (!res.ok) throw new Error('API externa falló');
        const data = await res.json();
        return { rate: data.rates[target] };
      }
    });
    

    Si la validación falla, execute nunca se ejecuta: el SDK/Zod detiene la cadena y devuelve un error estructurado.

    .parse() vs .safeParse() y autocorrección

    Usa .parse() cuando quieras fallar rápido (endpoints HTTP que deben devolver 4xx/5xx). Usa .safeParse() en agentes y workflows que puedan auto‑corregirse sin intervención humana.

    Patrón de autocorrección:

    1. LLM genera JSON.
    2. .safeParse() devuelve success: false y error.
    3. Serializas error.flatten() y lo inyectas en un nuevo prompt: “Tu respuesta falló por X. Corrige el JSON.”
    4. Reintentás N veces con backoff; si sigue fallando, encolas para revisión humana.

    Ese ciclo convierte errores estructurales en una conversación de corrección con el modelo, robusta y trazable.

    Operaciones y observabilidad

    No basta con validar: mide y actúa.

    Métricas recomendadas:

    • tasa de validación fallida por prompt/modelo,
    • latencia media de autocorrección,
    • número de reintentos hasta éxito,
    • porcentaje de degradaciones a intervención humana.

    Registra siempre: prompt, raw response, resultado de Zod (.error.flatten()), y el tool invocado. Eso te da trazabilidad: prompt → response → validación → acción. Sin esos registros no hay postmortem útil.

    Decisiones arquitectónicas y trade‑offs

    – Structured Outputs (OpenAI) y generateObject reducen errores de formato pero no sustituyen la validación semántica: un amount: -5 puede pasar el schema si no validas signo y rango. Siempre valida con Zod (https://zod.dev/).

    – Tipar desde el día 0 exige disciplina: los esquemas son contratos que obligan a diseñar prompts claros y a mantener tests de integración. La deuda que previene compensa la inversión inicial.

    – En entornos orquestados (n8n, XState) preferir que el LLM decida la herramienta y que la ejecución quede en una máquina de estado puede ser más seguro para acciones críticas. Igual aplica: la entrada debe validarse antes de actuar.

    Conclusión: deja de adivinar, empieza a garantizar

    Function calling tipado con TypeScript: deja de adivinar lo que devuelve el modelo — es una fórmula sencilla y comprobada: define el esquema (Zod), extrae el tipo (z.infer<>), valida antes de ejecutar y automatiza la corrección cuando tenga sentido. Esa disciplina transforma un LLM impredecible en un componente confiable de tu arquitectura. Si tu agente escribe en bases de datos, llama APIs facturadas o toma decisiones que afectan a clientes, no hay excusas: valida antes de ejecutar y loguea todo. Así se construyen agentes que pueden correr solos, y no problemas que sólo aparecen en producción.

    Para continuar explorando prácticas operativas y experimentos en automatización e IA aplicada, consulta Dominicode Labs. Esta referencia complementa las técnicas descritas y ofrece recursos prácticos para implementar pipelines seguros y trazables en producción.

    FAQ

    ¿Por qué no basta con hacer JSON.parse() y castear a un tipo?

    Porque JSON.parse() solo asegura formato JSON válido, no la semántica ni la presencia y tipo de campos esperados. Castear con as Tipo ignora la verificación en runtime, lo que permite entradas inválidas que pueden provocar errores en bases de datos o llamadas a APIs en producción.

    ¿Cuándo debo usar .parse() en lugar de .safeParse()?

    Usa .parse() en contextos donde quieras fallar rápido y retornar un error (por ejemplo endpoints HTTP que deben devolver 4xx/5xx). Usa .safeParse() cuando el flujo puede intentar autocorrección o reintentos antes de degradar a intervención humana.

    ¿Qué hago si .safeParse() falla continuamente?

    Serializa el error con error.flatten(), inyecta esa información en un nuevo prompt pidiendo corrección, y reintenta N veces con backoff. Si sigue fallando, encola la unidad para revisión humana y registra el incidente para análisis posterior.

    ¿Debo exponer el esquema Zod en el prompt?

    Sí: exponer el esquema ayuda al modelo a generar la estructura correcta (especialmente con Structured Outputs). Aun así, la validación con Zod debe ejecutarse en runtime; el esquema en el prompt no sustituye la verificación.

    ¿Qué debo registrar para tener trazabilidad adecuada?

    Registra el prompt, la respuesta cruda del modelo, el resultado de Zod (error.flatten()), y la herramienta (tool) invocada. Esos datos permiten reconstruir el flujo prompt → response → validación → acción para postmortems.

    ¿Los Structured Outputs sustituyen la validación con Zod?

    No. Structured Outputs y utilidades como generateObject reducen errores de formato, pero no validan semántica ni rangos (por ejemplo, amount: -5 podría pasar). Sigue validando con Zod en runtime.

  • Cómo manejar errores en agentes de IA usando TypeScript

    Cómo manejar errores en agentes de IA usando TypeScript

    Error handling en agentes de IA: TypeScript te obliga a hacerlo bien

    Tiempo estimado de lectura: 3 min

    Ideas clave

    • Evita try/catch genéricos: ocultan la causa raíz y llevan a reintentos inútiles o a falsos positivos.
    • Usa Result<T, E>: convierte errores en valores tipados que el orquestador y el LLM pueden razonar.
    • Forzar exhaustividad con never: el compilador impide desplegar manejadores incompletos.
    • Persistir errores en memoria: guarda fallos estructurados en la memoria episódica para decisiones posteriores.
    • Instrumenta y prueba: telemetría, tests y SLAs para garantizar conducta operativa.

    Tabla de contenidos

    Introducción

    Error handling en agentes de IA: TypeScript te obliga a hacerlo bien. Dilo alto. Si tu agente llama herramientas, escribe datos o encadena flujos, el modo en que tratas los fallos decide si tendrás un sistema autocorrectivo o un monstruo que consume tokens y rompe tablas.

    Los try/catch genéricos son la forma más rápida de cegar a un agente. TypeScript, usado con disciplina, no te da magia: te da garantías. Garantías que convierten excepciones impredecibles en valores tipados que el agente puede razonar, registrar y recuperar.

    Resumen rápido (lectores con prisa)

    Qué: Trata errores como valores con Result<T,E> en lugar de throw.

    Cuándo: Siempre que tu agente llame herramientas, escriba en DB o coordine flujos.

    Por qué importa: Evita reintentos ciegos, reduce tokens gastados y hace al agente reparable.

    Cómo funciona: Devuelve {ok: true|false} con error tipado y usa checks exhaustivos con never para obligar a manejar nuevos casos.

    Por qué los try/catch genéricos matan tus agentes

    Un humano reintenta después de un HTTP 500. Un agente no. Cuando un LLM recibe solo “Error executing tool”, pierde la causa raíz. ¿Duplicado en la DB? ¿Timeout? ¿Bad payload? Sin esa información, el agente hará una de dos cosas útiles para nadie: reintentar la misma acción hasta agotar tu presupuesto o “alucinar” que la acción fue exitosa para seguir el flujo.

    En sistemas agénticos cada fallo es una señal. Ocultarla con un mensaje opaco equivale a apagar los sensores del coche mientras conduces rápido.

    Result<T, E>: convertir errores en datos con contrato

    La respuesta práctica es sencilla: deja de tirar (throw) y empieza a devolver. El patrón Result<T, E> transforma errores en valores discriminados que obligan al consumidor a lidiar con ellos.

    type Result<T, E> =
      | { ok: true; value: T }
      | { ok: false; error: E };

    Ejemplo de herramienta:

    async function createUser(email: string): Promise<Result<User, 'DUPLICATE_EMAIL' | 'DB_TIMEOUT' | 'INVALID_SCHEMA'>> {
      // nunca throw; siempre retorna ok: true | ok: false
    }

    ¿Qué cambia? Ahora el orquestador recibe información accionable. Si ok: false y error === ‘DUPLICATE_EMAIL’, inyectas esa razón en la memoria episódica y el LLM pregunta por otro email en vez de repetir la misma INSERT. Transformas ruido en contexto.

    Documentación útil sobre Result (concepto inspirado en Rust): Documentación útil sobre Result (concepto inspirado en Rust)

    never y exhaustive checks: el compilador como guardarraíl

    Pasa esto en equipos: alguien añade un nuevo error y olvida manejarlo. Resultado: el agente se comporta de forma impredecible en producción. Aquí entra el patrón de comprobación exhaustiva con never.

    Define tus errores como unión discriminada:

    type AgentError =
      | { type: 'DatabaseError'; code: number }
      | { type: 'AuthError'; reason: string }
      | { type: 'ValidationError'; issues: string[] };

    Y formatea para el LLM con un switch que fuerza exhaustividad:

    function formatErrorForLLM(error: AgentError): string {
      switch (error.type) {
        case 'DatabaseError': return `DB failure (${error.code}). No retry.`;
        case 'AuthError': return `Auth failed: ${error.reason}. Re-auth required.`;
        case 'ValidationError': return `Invalid: ${error.issues.join(', ')}. Fix and retry.`;
        default:
          const _exhaustive: never = error;
          return _exhaustive;
      }
    }

    Si alguien añade { type: 'RateLimit' } a AgentError y no actualiza este switch, TypeScript fallará en compilación. Lee sobre exhaustiveness checks: Lee sobre exhaustiveness checks

    Eso no es paranoia: es disciplina. Tu CI/CD no permitirá desplegar un agente que no explica cada fallo.

    Integración práctica con memoria y orquestación

    Errores tipados deben viajar junto con resultados hacia la memoria episódica. Diseño minimalista:

    • La tool retorna Result.
    • El orquestador persiste tanto éxitos como fallos en la memoria episódica (Redis) y semántica (vector DB si aplica).
    • El prompt incluye el fallo estructurado antes de la siguiente llamada al LLM.
    • El LLM decide la acción (retry con modificación, pedir datos al usuario, escalar).

    Además: valida entradas con Zod/JSON Schema antes de llamar a la tool. Un buen schema reduce la superficie de errores y hace que los fallos restantes sean reales y manejables.

    Operacional: telemetría, tests y SLAs

    No sirve con tipos si no lo verificas. Tienes que medir:

    • Retries por tipo de error (P50/P95).
    • Frecuencia de errores no mapeados (build-breakers).
    • Tokens gastados en reintentos.
    • Casos escalados a humano.

    Incluye tests que simulen fallos de DB, timeouts y errores de validación. Tu pipeline debe romper si un nuevo error llega a producción sin un handler en el switch exhaustivo.

    Cierre operativo

    El try/catch genérico es cómodo. También es la razón por la que tu agente se convierte en una caja negra con facturas altas y datos rotos. Convertir errores en valores tipados (Result<T,E>) y forzar comprobaciones exhaustivas con never no es estilo: es seguridad operativa.

    Aplica esto ya, instrumenta telemetría y haz que tu build falle si alguien olvida un caso de error. En el próximo artículo mostraremos cómo serializar esos errores en memoria episódica y usar feedback estructurado para que el agente aprenda a autocorregirse sin intervención humana.

    Dominicode Labs

    Para equipos que diseñan agentes y pipelines de orquestación, es útil contar con recursos y experimentos que muestren patrones de integración entre memoria episódica y errores tipados. Continúa explorando técnicas y prototipos en Dominicode Labs para ver implementaciones prácticas y ejemplos aplicados a agentes y workflows.

    FAQ

    ¿Por qué no debo usar try/catch genéricos en agentes?

    Porque ocultan la causa raíz y priven al agente de información accionable. Un mensaje opaco lleva a reintentos inútiles o a continuar el flujo como si la acción hubiera tenido éxito.

    ¿Qué es Result<T,E> y cómo cambia la arquitectura?

    Es un patrón que devuelve un valor tipado indicando éxito o fallo ({ok: true; value} | {ok: false; error}). Obliga al consumidor a tratar explícitamente los errores y permite que orquestadores y LLMs actúen sobre causas concretas.

    ¿Cómo obliga TypeScript a manejar nuevos errores?

    Mediante comprobaciones exhaustivas usando never en un switch sobre una unión discriminada. Si se añade un nuevo caso y no se maneja, el compilador falla.

    ¿Dónde guardo los errores para que el agente los use?

    Persiste fallos en la memoria episódica (ej. Redis) y en la memoria semántica si aplica (vector DB). El orquestador debe guardar tanto éxitos como fallos para proporcionar contexto al LLM.

    ¿Qué métricas debo instrumentar primero?

    Retries por tipo de error (P50/P95), frecuencia de errores no mapeados, tokens gastados en reintentos y casos escalados a humano.

    ¿Cómo debo testear los manejadores de error?

    Incluye tests que simulen fallos de DB, timeouts y errores de validación; el pipeline debe fallar si un nuevo error llega a producción sin handler.

    ¿Qué herramientas de validación recomiendan antes de llamar a una tool?

    Valida entradas con Zod o JSON Schema para reducir la superficie de errores y asegurar que los fallos restantes sean reales y manejables.

  • Crea un agente en TypeScript con Mastra para soporte técnico

    Crea un agente en TypeScript con Mastra para soporte técnico

    Crea tu primer agente en TypeScript con Mastra

    Tiempo estimado de lectura: 6 min

    • Mastra es un framework nativo en TypeScript para construir agentes y flujos de IA con validación de esquemas en runtime.
    • Separar Agent, Tool y Model permite pruebas unitarias y facilita cambiar proveedor de LLM.
    • Usa Zod para convertir inputs/outputs del LLM en contratos verificables.
    • Incluye un ejemplo práctico listo para ejecutar (agente de soporte técnico) y reglas operativas para producción.

    Crear tu primer agente en TypeScript con Mastra es la forma más práctica de evitar la fragmentación entre Node.js y microservicios Python. Si tu stack es TypeScript, mantener todo en el mismo lenguaje reduce latencia, elimina fricciones en despliegue y preserva tipos end-to-end. En las siguientes secciones veremos qué es Mastra, su arquitectura y un ejemplo práctico listo para ejecutar.

    Resumen rápido (lectores con prisa)

    Mastra es un framework en TypeScript para agentes de IA que integra validación de esquemas con Zod.

    Separa responsabilidades en Agent, Tool y Model para pruebas y cambio de proveedor de LLM.

    Usa Zod para que las invocaciones a tools sean contratos verificables en runtime.

    Incluye un ejemplo práctico (agente de soporte) y recomendaciones para producción.

    Documentación y repositorio oficiales están disponibles para referencia.

    Crea tu primer agente en TypeScript con Mastra: por qué importa y cuándo usarlo

    Mastra es un framework nativo en TypeScript para construir agentes y flujos de IA. Su ventaja principal no es solo técnica: es contractual. Al integrar Zod para la validación de esquemas como parte del runtime, convierte las llamadas del LLM en contratos verificables, no en texto libre que “parece correcto”. Documentación: mastra.ai/docs. Repositorio: github.com/mastra-ai/mastra.

    Úsalo cuando quieras:

    • Ejecutar agentes dentro del mismo proceso Node/Next.js.
    • Forzar esquemas de entrada/salida con Zod.
    • Testear herramientas aisladas sin invocar modelos.

    No es la solución si necesitas capacidades avanzadas aún no soportadas por Mastra; evalúa la madurez del proyecto antes de producción.

    Arquitectura mínima: Agent, Tool y Model

    Divide responsabilidades desde el inicio:

    • Agent: orquesta la conversación, mantiene historial e instrucciones del sistema.
    • Tool: funciones asíncronas fuertemente tipadas (Zod). Son la única forma controlada en la que el agente “toca” el mundo.
    • Model: abstracción del proveedor de LLM (OpenAI, Anthropic, etc.). Cambiar proveedor debe ser un cambio mínimo.

    Esa separación permite testing unitario de herramientas y garantiza que la lógica de negocio no quede oculta en un prompt.

    Paso a paso: ejemplo práctico (Agente de soporte técnico)

    Instalación rápida

    mkdir mastra-agent && cd mastra-agent
    npm init -y
    npm install @mastra/core zod
    npm install -D typescript @types/node tsx
    npx tsc --init
    export OPENAI_API_KEY="tu_api_key"

    1) Define la herramienta (Tool)

    Controla exactamente qué parámetros acepta el LLM:

    // src/tools/check-service.ts
    import { createTool } from '@mastra/core';
    import { z } from 'zod';
    
    export const checkServiceTool = createTool({
      id: 'check-service-status',
      description: 'Consulta el estado operativo de un servicio interno por nombre.',
      inputSchema: z.object({
        service: z.enum(['api', 'database', 'frontend']),
      }),
      execute: async ({ context }) => {
        // Reemplaza por consultas reales a monitorización/DB
        const statuses = { api: 'degraded', database: 'operational', frontend: 'operational' };
        return { service: context.service, status: statuses[context.service], checkedAt: new Date().toISOString() };
      },
    });

    2) Instancia el agente

    Define instrucciones claras (system prompt) que actúen como contrato operativo:

    // src/agent.ts
    import { Agent } from '@mastra/core/agent';
    import { checkServiceTool } from './tools/check-service';
    
    export const supportAgent = new Agent({
      name: 'TechSupportAgent',
      instructions: `
        Eres un ingeniero de soporte de nivel 1.
        Antes de concluir que un servicio está caído, INVÓCAME la herramienta de estado.
        Responde con datos y recomendaciones técnicas claras.
      `,
      model: { provider: 'OPENAI', name: 'gpt-4o-mini' },
      tools: { checkServiceTool },
    });

    3) Ejecuta el flujo

    Ejecuta el agente y deja que Mastra coordine modelo y tools:

    // src/main.ts
    import { supportAgent } from './agent';
    
    async function main() {
      const prompt = 'Los usuarios reportan lentitud. ¿Está la API bien?';
      const res = await supportAgent.generate(prompt);
      console.log(res.text);
    }
    
    main().catch(console.error);

    Lanza con npx tsx src/main.ts. Mastra coordina al LLM y las herramientas: si el modelo decide invocar checkServiceTool, el framework valida el input con Zod, ejecuta la función y alimenta la respuesta final.

    Reglas prácticas para llevar esto a producción

    1. Observabilidad desde el día 0: logs estructurados en cada invocación de tool (request id, input validado, tiempo, resultado).

    2. Errores como datos: las herramientas deben devolver errores tipados que el agente pueda razonar y comunicar (no lanzar excepciones sin contexto).

    3. Testing aislado: tests unitarios para execute y tests de integración para la orquestación. No confundas velocidad con cobertura.

    4. CI/CD: bloquea merges que introduzcan nuevas dependencias sin revisión de licencia/reputación. En entornos corporativos, valida paquetes recomendados por LLMs.

    5. Ownership y ADRs: cualquier herramienta crítica debe tener un Architecture Decision Record que explique por qué existe y cómo se prueba.

    Para guiar verificaciones de seguridad y dependencia sigue prácticas de NIST/OWASP (por ejemplo: owasp.org).

    Conclusión: Mantén el criterio humano donde importa

    Mastra te da la ergonomía de TypeScript y contratos verificables con Zod. Eso reduce alucinaciones estructurales y acelera integración en stacks web. Pero la ganancia real viene cuando aplicas disciplina: diseño previo, validación estricta, observabilidad y pruebas. Implementa el agente, mide cómo toma decisiones y exige que cualquier acción en producción esté documentada y testeada. En la próxima entrega de Dominicode mostraremos cómo añadir memoria persistente y encadenar múltiples tools para workflows complejos.

    Dominicode Labs

    Si trabajas en automatización, agentes o workflows y quieres ejemplos prácticos y guías operativas, consulta Dominicode Labs. Encontrarás recursos que complementan patrones de diseño y pruebas para agentes en producción.

    FAQ

    Mastra es un framework nativo en TypeScript para construir agentes y flujos de IA que integra Zod para validación de esquemas en runtime.

    Cuando tu stack principal es TypeScript/Node.js y quieres ejecutar agentes en proceso, forzar esquemas de entrada/salida y testear herramientas sin invocar modelos externos.

    Integra Zod como parte del runtime: las entradas a las tools se validan contra esquemas definidos y solo se ejecutan si cumplen el contrato tipado.

    Sí. La arquitectura separa Model como una abstracción, de modo que cambiar proveedor (OpenAI, Anthropic, etc.) debe ser un cambio mínimo en la configuración.

    Observabilidad desde el día 0, errores como datos tipados, testing aislado, control de dependencias en CI/CD y ADRs para herramientas críticas.

    La documentación oficial está en mastra.ai/docs y el repositorio en github.com/mastra-ai/mastra.

  • Aprende a implementar el Facade Pattern en Angular para simplificar tu código

    Aprende a implementar el Facade Pattern en Angular para simplificar tu código

    Como implementar Facade Pattern en Angular

    ¿Cansado de componentes que parecen controlar todo el backend desde la plantilla? Aprender como implementar Facade Pattern en Angular es la forma más práctica de devolverles una sola responsabilidad: presentar datos y disparar eventos. Aquí no hay dogma: solo reglas que evitan que tu código se convierta en un monstruo difícil de cambiar.

    Resumen rápido (lectores con prisa)

    Facade es una capa intermedia entre la vista y la complejidad del estado y servicios.

    Cuando usarlo: para aislar la UI del ruido de RxJS/Store y facilitar cambios futuros.

    Por qué importa: centraliza orquestación y simplifica tests de componentes.

    Cómo funciona: la fachada expone una API semántica (Signals/Observables y métodos) que la UI consume.

    Tiempo estimado de lectura: 4 min

    Ideas clave

    • Facade: capa entre vista y complejidad (Stores, servicios HTTP, websockets).
    • Permite aislar componentes de la API del estado y de RxJS; facilita el cambio de implementación.
    • Centraliza orquestación y simplifica tests: mockeas una sola clase.
    • No sustituye al Store; la fachada orquesta y adapta, el Store sigue siendo el motor.
    • Considera exponer Signals para simplificar la UI (toSignal).

    Tabla de contenidos

    Como implementar Facade Pattern en Angular: qué y por qué

    El Facade es una capa intermedia entre la vista y la complejidad (Stores, servicios HTTP, websockets, etc.). En Angular sirve para:

    • Aislar componentes de la API del estado (NgRx, Akita) y de RxJS.
    • Centralizar orquestación (llamar varios servicios, combinar selectores).
    • Simplificar tests: mockeas una sola clase en lugar de todo el Store.

    No confundas Facade con reemplazo de NgRx. El Store sigue siendo tu motor; la fachada es el volante que usa el conductor. Documentación útil: NgRx Store. También verás utilidad en convertir selectores a Signals: toSignal.

    Implementación paso a paso (ejemplo práctico)

    Escenario: lista de usuarios, indicador de carga y refrescar.

    1) Capa de estado (puede ser NgRx o un servicio con BehaviorSubject)

    @Injectable({ providedIn: 'root' })
    export class UserApi {
      getUsers(): Observable { return this.http.get('/api/users'); }
    }

    2) Facade (el contrato que consume la UI)

    // user.facade.ts
    import { Injectable, inject } from '@angular/core';
    import { Store } from '@ngrx/store';
    import { loadUsers } from './state/user.actions';
    import { selectAllUsers, selectLoading } from './state/user.selectors';
    import { toSignal } from '@angular/core/rxjs-interop';
    
    @Injectable({ providedIn: 'root' })
    export class UserFacade {
      private store = inject(Store);
    
      // Exponer Signals hace la UI más simple
      users = toSignal(this.store.select(selectAllUsers), { initialValue: [] });
      loading = toSignal(this.store.select(selectLoading), { initialValue: false });
    
      load(): void {
        this.store.dispatch(loadUsers());
      }
    
      refresh(): void {
        // posibles operaciones compuestas: cancelar, volver a cargar, métricas, etc.
        this.load();
      }
    }

    Usa métodos semánticos (load, refresh, selectUser) y nunca expongas acciones crudas desde el componente.

    3) Componente (ligero)

    @Component({ /* ... */ })
    export class UserListComponent implements OnInit {
      facade = inject(UserFacade);
    
      ngOnInit() { this.facade.load(); }
    
      onRefresh() { this.facade.refresh(); }
    }

    La plantilla puede leer facade.users() y facade.loading() — sin pipes async, sin selectores en el componente.

    Alternativa sin NgRx: BehaviorSubject interno en la fachada

    Si no usas NgRx, la fachada puede orquestar servicios y exponer Observables/Signals:

    private users$ = new BehaviorSubject([]);
    get users() { return this.users$.asObservable(); }
    
    load() {
      this.userApi.getUsers().subscribe(this.users$);
    }

    Esto mantiene la UI igual y te permite cambiar la implementación sin tocar componentes.

    Testing: por qué es más fácil

    En tests unitarios del componente mockeas UserFacade:

    const mockFacade = { users: of([{id:1,name:'A'}]), loading: of(false), load: jest.fn() };
    TestBed.overrideProvider(UserFacade, { useValue: mockFacade });

    Resultado: tests más rápidos y menos acoplamiento a detalles de RxJS o del Store.

    Buenas prácticas y anti-patrones

    • Fachadas por dominio, no un AppFacade gigante. Preferible: AuthFacade, CartFacade, UserFacade.
    • No mantengas estado duplicado en la fachada. Si ya existe un Store, la fachada delega, no re-implementa.
    • Expone API semántica, no selectores ni acciones. El componente debe leer “usuarios” y llamar “load()”, no importar selectores.
    • Considera Signals en la fachada para simplificar la UI: toSignal(…) en lugar del async pipe.
    • Evita lógica de negocio pesada en la fachada; su foco es orquestación y adaptación.

    Criterio para decidir si usar Facade

    Usa Facade cuando:

    • Tienes un equipo mixto (UI vs backend) y quieres proteger al equipo de UI del ruido de RxJS.
    • Prevés cambios futuros en la solución de estado.
    • Quieres tests de componente simples y rápidos.

    No lo uses cuando:

    • El proyecto es un MVP pequeño con un desarrollador y flujo de trabajo rápido.
    • La fachada añade más ficheros que valor real.

    Recursos y lecturas

    FAQ

    Respuesta:

    El Facade Pattern en Angular es una capa intermedia que aísla la UI de la complejidad del estado y servicios (Stores, HTTP, websockets). Proporciona una API semántica que la vista consume.

    Respuesta:

    Úsala cuando quieras proteger al equipo de UI del ruido de RxJS, prever cambios en la solución de estado o simplificar tests. Evita fachadas si el proyecto es un MVP pequeño con un solo desarrollador.

    Respuesta:

    No. La fachada no reemplaza al Store: el Store sigue siendo el motor. La fachada actúa como adaptador o volante que orquesta y simplifica el acceso al estado.

    Respuesta:

    Debe exponer una API semántica: Observables o Signals (por ejemplo mediante toSignal) y métodos como load(), refresh(), selectUser(). No expongas selectores ni acciones crudas directamente.

    Respuesta:

    En tests de componentes mockeas la fachada completa en lugar del Store, reduciendo el acoplamiento a RxJS y al Store. Eso hace los tests más rápidos y simples.

    Respuesta:

    Anti-patrones: crear un AppFacade monolítico por dominio, duplicar estado en la fachada cuando ya existe un Store, y poner lógica de negocio compleja dentro de la fachada en lugar de orquestación y adaptación.

  • Cómo crear una librería npm tipada en TypeScript para desarrolladores

    Cómo crear una librería npm tipada en TypeScript para desarrolladores

    Cómo hacer una librería npm tipada con TypeScript

    Tiempo estimado de lectura: 5 min

    • Entrega dual: código ejecutable + declaraciones de tipo (.d.ts) para buena DX.
    • Configuración esencial: tsconfig con declaration: true y salida limpia en dist/.
    • Empaquetado: mapear main/module/types en package.json y validar con npm pack y tsc en proyecto consumidor.
    • Exportaciones claras: centralizar API en src/index.ts y usar export type para tipos.
    • Validación: pruebas de tipos en CI con tsd y tests locales antes de publish.

    Introducción

    Saber cómo hacer una librería npm tipada con TypeScript significa entregar dos cosas a la vez: código que la máquina ejecuta y tipos que el desarrollador consume. Desde el primer archivo hasta el .d.ts final, la meta es que quien instale tu paquete tenga autocompletado y verificaciones de tipo sin tocar su configuración. Esta guía va directo al flujo de producción: configuración, estructura, packaging y validación local.

    Resumen rápido (lectores con prisa)

    Una librería npm tipada combina JavaScript/TypeScript compilado y declaraciones de tipo (.d.ts). Usa tsc con declaration: true para generar tipos; centraliza la API en src/index.ts; apunta types en package.json al .d.ts maestro; valida con npm pack y ejecuta tsc --noEmit en un proyecto consumidor.

    Cómo hacer una librería npm tipada con TypeScript — pasos y criterios

    1. Estructura mínima

    • src/ — código TypeScript.
    • dist/ — artefactos compilados (generado).
    • package.json, tsconfig.json, README.md.

    2. Instala y prepara TypeScript

    npm init -y
    npm install typescript --save-dev
    npx tsc --init

    3. tsconfig.json recomendable (base)

    {
      "compilerOptions": {
        "target": "ES2020",
        "module": "CommonJS",
        "moduleResolution": "node",
        "declaration": true,
        "declarationMap": true,
        "outDir": "./dist",
        "rootDir": "./src",
        "strict": true,
        "esModuleInterop": true,
        "skipLibCheck": true,
        "forceConsistentCasingInFileNames": true
      },
      "include": ["src/**/*"],
      "exclude": ["node_modules", "**/*.test.ts"]
    }
    • declaration: true es obligatorio: genera .d.ts.
    • declarationMap: true mejora la experiencia “Go to definition” en IDEs.
    • outDir y rootDir mantienen el árbol limpio.

    Documentación oficial TypeScript: TypeScript declaration files publishing

    4. Código: exportaciones claras y barrel file

    Centraliza la API pública en src/index.ts. Exporta valores y tipos explícitamente:

    // src/types.ts
    export interface LibraryConfig { timeout: number; }
    
    // src/core.ts
    import type { LibraryConfig } from './types';
    export function init(cfg: LibraryConfig) { /* ... */ }
    
    // src/index.ts
    export { init } from './core';
    export type { LibraryConfig } from './types';
    

    Usa export type para separar contratos estáticos (no existen en runtime). Evita filtrar internals por accidente.

    5. package.json: el contrato que vincula .js y .d.ts

    {
      "name": "mi-libreria-tipada",
      "version": "1.0.0",
      "main": "dist/cjs/index.js",
      "module": "dist/esm/index.js",
      "types": "dist/index.d.ts",
      "files": ["dist"],
      "scripts": {
        "build": "npm run build:cjs && npm run build:esm",
        "build:cjs": "tsc --outDir dist/cjs --module commonjs",
        "build:esm": "tsc --outDir dist/esm --module ESNext",
        "prepublishOnly": "npm run build"
      },
      "exports": {
        ".": {
          "types": "./dist/index.d.ts",
          "require": "./dist/cjs/index.js",
          "import": "./dist/esm/index.js"
        }
      }
    }
    • types debe apuntar al .d.ts maestro.
    • files actúa como whitelist; evita subir src o configs.
    • exports (condicional) habilita resolución moderna y puede mapear CJS/ESM. Coloca types junto a la condición raíz para que TypeScript lo resuelva bien.

    Si publicas scoped package (p. ej. @scope/name), recuerda npm publish --access public para paquetes públicos.

    6. ¿Generar .d.ts con tsc o con bundler?

    • Para proyectos simples o librerías de funciones: usa tsc con declaration: true. Es robusto y sencillo.
    • Para bundles complejos (single-file output), usa un plugin para generar tipos como rollup-plugin-dts o la opción tsup --dts. Si eliges esta vía, verifica que el .d.ts final coincida con la estructura exportada.

    Herramientas útiles:

    7. Validación local antes de publicar

    1. npm pack — crea un .tgz. Ábrelo y verifica contenido: dist/, package.json, README.md.

      Docs: npm pack docs

    2. Instalación local en proyecto cliente:
      cd ../project-test
      npm install /ruta/a/mi-libreria-tipada-1.0.0.tgz

      Importa y ejecuta tsc --noEmit en el proyecto consumidor: si types está mal, fallará aquí.

    3. Tests de tipos automáticos: integra tsd en tu CI para asegurar que la API expuesta no cambia rompeints:

      tsd

    8. Dependencias y peerDependencies

    • Declara en peerDependencies librerías que el consumidor debe proveer (React, por ejemplo) para evitar duplicados.
    • Pon utilidades que la librería necesita en dependencies.

    9. Checklist rápido antes de publish

    • [ ] npm run build genera dist con .js y .d.ts.
    • [ ] package.json apunta a main, module (si aplica) y types.
    • [ ] files incluye solo dist, README.md, LICENSE.
    • [ ] npm pack inspeccionado.
    • [ ] Prueba de instalación local y tsc --noEmit en proyecto consumidor.
    • [ ] Tests de tipos (tsd) pasados en CI.

    10. Comandos finales para publicar

    npm login
    npm publish --access public

    (Usa --access public para paquetes scope públicos.)

    Conclusión

    Construir una librería tipada no es mágico: es disciplina. Configura tsc para emitir declaraciones, expone solo lo necesario y valida el paquete en un entorno consumidor antes de pulsar publish. El tiempo que inviertes en estos pasos se recupera con menos issues en integraciones y una mejor experiencia para quienes usan tu paquete.

    FAQ

    ¿Por qué es obligatorio generar .d.ts?

    Las declaraciones (.d.ts) proporcionan tipos a los consumidores de tu librería sin necesidad de que compilen tu código TypeScript. Sin ellas, los usuarios perderían autocompletado y las comprobaciones de tipo.

    ¿Puedo usar un bundler para generar tipos?

    Sí. Para bundles single-file es común usar plugins como rollup-plugin-dts o herramientas como tsup --dts. Verifica que el resultado refleje la API exportada.

    ¿Qué debe apuntar el campo types en package.json?

    types debe apuntar al .d.ts maestro que describe la API pública, por ejemplo dist/index.d.ts.

    ¿Cómo pruebo la experiencia del consumidor antes de publicar?

    Usa npm pack para generar un .tgz, instálalo en un proyecto de prueba con npm install /ruta/mi-libreria.tgz y ejecuta tsc --noEmit en el proyecto consumidor.

    ¿Debo usar peerDependencies para React?

    Sí. Declara frameworks como React en peerDependencies para evitar múltiples copias y problemas de compatibilidad en el proyecto consumidor.

    ¿Qué herramientas recomiendo para pruebas de tipos en CI?

    Integra tsd en tu CI para pruebas automáticas de tipos. Revisa tsd para más detalles.

    ¿Qué archivos incluir en el paquete publicado?

    Usa files en package.json para incluir solo dist, README.md y LICENSE. Evita subir src o configuraciones internas.