Category: TypeScript

  • Cómo utilizar tipos condicionales en TypeScript para mejorar la robustez

    Cómo utilizar tipos condicionales en TypeScript para mejorar la robustez

    ¿Quieres que tus tipos de TypeScript hagan el trabajo sucio por ti —y de verdad— en vez de darte una falsa sensación de seguridad?

    Tiempo estimado de lectura: 4 min

    • Los tipos condicionales (T extends U ? X : Y) se evalúan en tiempo de compilación y permiten metaprogramación robusta.
    • infer captura partes de tipos para extraer retornos, parámetros y elementos internos sin runtime.
    • Patrones clave: Unwrap para Promises, evitar distribución con tuplas, manipular tuples con Head/Tail, y extraer retornos async.
    • Precauciones: complejidad excesiva, deuda técnica y necesidad de validación runtime para datos externos.

    Bien. Aquí tienes una guía práctica que no promete magia, pero sí te salva de la gimnasia de tipos inútil y de bugs que aparecen a las 2AM.

    Resumen rápido (lectores con prisa)

    Los condicionales de tipos son ternarios que TypeScript evalúa en tiempo de compilación. infer permite extraer partes de un tipo coincidente. Úsalos para utilidades tipo-level (Unwrap, AsyncReturn, Head/Tail) y evita su abuso: documenta, limita y combina con validación runtime cuando el input viene de la red.

    Introducción

    Primero, lo obvio: el condicional de tipos es un ternario que vive en el compilador. Sintaxis: T extends U ? X : Y. Se evalúa en tiempo de compilación. No en runtime. Eso lo convierte en una espada afilada: poderosa, pero cortante.

    Condicionales de tipos

    Sintaxis

    Sintaxis: T extends U ? X : Y. Se evalúa en tiempo de compilación.

    Ejemplo mínimo

    type IsString<T> = T extends string ? true : false;
    type A = IsString<'hola'>; // true
    type B = IsString<42>;     // false
    

    Sencillo. Útil para construir utilidades.

    infer

    Qué es

    infer captura piezas del tipo que estás inspeccionando. Es la manera de decirle a TS: “si esto encaja, arráncame esto otro”.

    Ejemplo clásico — extraer el tipo de retorno

    type MyReturn<T> = T extends (...args: any[]) => infer R ? R : never;
    type Fn = () => { id: number };
    type R = MyReturn<Fn>; // { id: number }
    

    Útil. Limpio. Poderoso.

    Patrones

    Patrón 1 — Unwrap de Promises (recursivo)

    Cuando trabajas con APIs, las promesas anidadas son una plaga. Esto te limpia el resultado:

    type Unwrap<T> = T extends Promise<infer U> ? Unwrap<U> : T;
    type X = Unwrap<Promise<Promise<{ ok: true }>>>; // { ok: true }
    

    Patrón 2 — Tipos distributivos y el problema que nadie lee

    Si T es una unión, el condicional se aplica a cada miembro (distribuye). A veces quieres eso. A veces no.

    Distribución:

    type ExcludeFunc<T> = T extends Function ? never : T;
    type U = ExcludeFunc<string | (() => void)>; // string
    

    Evitar distribución

    Envuélvelo en una tupla.

    type NoDist<T> = [T] extends [Function] ? never : T;
    

    Memorízalo. Te salvará de errores raros.

    Patrón 3 — Extraer elementos de tuples/arrays

    infer también domina tuplas. Necesitas sacar head/tail o el último elemento:

    type Head<T extends any[]> = T extends [infer H, ...any[]] ? H : never;
    type Tail<T extends any[]> = T extends [any, ...infer R] ? R : never;
    

    Esto abre puertas para manipulaciones tipo-level sin hacer trampas.

    Patrón 4 — AsyncReturnType para funciones async

    type AsyncReturn<T> = T extends (...args: any[]) => Promise<infer R> ? R : never;
    

    Ideal para tipos de efectos y sagas.

    Casos reales donde esto brilla

    • Clientes HTTP tipados por endpoint (súper seguro, sin as adivinatorio).
    • Librerías que exponen utilidades genéricas robustas.
    • Metaprogramación de APIs internas (mapea rutas a tipos de response).
    • Sistemas de validación y mapeo que dependen de signatures de funciones.

    Ejemplo rápido: tipos por endpoint

    interface Endpoints {
      '/users': { id: string, name: string }[];
      '/status': { ok: boolean };
    }
    
    function fetchApi<T extends keyof Endpoints>(url: T): Promise<Endpoints[T]> {
      return fetch(url).then(r => r.json());
    }
    

    No más any. No más guessing.

    Pitfalls — Lo que te rompe la vida si no tienes cuidado

    • Complexity blowup: tipos gigantes ralentizan el compilador. TS puede confundirse y tirar errores crípticos.
    • Abuso: si tu equipo no entiende, se convierte en deuda técnica invisible.
    • Debug hard: errores en tipos a veces son opacos; documenta y divide tipos largos.
    • Runtime vs compile-time: los tipos no validan data externa. Para eso, runtime schemas (Zod) son tus amigos.

    Buenas prácticas — rápido y aplicable

    • Usa tipos condicionales en librerías y núcleos infra. No en cada componente.
    • Nombra aliases: type AsyncReturn<T> = ... — no pongas 10 ternarios directos.
    • Comenta. Sí, los tipos necesitan comentarios.
    • Mantén límites: si un tipo tiene más de 30–40 líneas, replantea.
    • Combina con validación runtime donde el input viene de la red.
    • Testea tipos con tsd o expect-type para evitar regresiones.

    Trucos avanzados en dos líneas

    • Forzar no-distribución: [T] extends [U] ? X : Y.
    • Obtener parámetros de una función: type Params<T> = T extends (...a: infer A) => any ? A : never;
    • Extraer propiedades no-función: usa mapped types con condicionales para filtrar claves.

    Metáfora rápida para que no lo olvides

    Los tipos condicionales son el sistema inmunitario del código. Te protegen si los pones en el lugar correcto. Si los usas por todos lados sin control, te provocan una reacción autoinmune: complejidad que te derriba.

    ¿Quieres algo práctico que puedas pegar ya?

    Puedo mandarte:

    • Un cheat-sheet con 25 utilidades (Unwrap, AsyncReturn, Head, Tail, PickByValue, etc.).
    • Un pequeño repo con ejemplos y tests de tsd.
    • Un snippet para un cliente HTTP tipado por endpoints + validación Zod integrada.

    Responde “Envíame el cheat-sheet” y te lo paso listo para copiar. No es teoría. Es la diferencia entre escribir tipos y dejar que los tipos te salven. Esto no acaba aquí.

    FAQ

    ¿Qué es un condicional de tipos en TypeScript?

    Es una expresión tipo-level con la forma T extends U ? X : Y que el compilador evalúa en tiempo de compilación para producir un tipo según la condición.

    ¿Qué hace infer?

    infer permite capturar una parte del tipo que coincide en un condicional, por ejemplo para extraer el tipo de retorno de una función o el inner type de una Promise.

    ¿Cómo evito que un condicional distribuya sobre una unión?

    Envuelve el tipo en una tupla: [T] extends [U] ? X : Y. Eso evita la distribución sobre cada miembro de la unión.

    ¿Los tipos garantizan que los datos externos sean válidos?

    No. Los tipos existen en tiempo de compilación. Para datos externos debes usar validación runtime (por ejemplo Zod) y combinarla con tipos TypeScript.

    ¿Cuándo debo usar estos patrones vs validación runtime?

    Usa tipos condicionales y infer para modeling y seguridad interna del código. Añade validación runtime cuando aceptas datos de la red o de usuarios.

    ¿Qué herramientas recomiendas para testear tipos?

    Testea tipos con frameworks como tsd o utilidades como expect-type para evitar regresiones en tipos complejos.

  • TypeScript 7.0 en Go: Mejoras significativas en rendimiento y paralelismo

    TypeScript 7.0 en Go: Mejoras significativas en rendimiento y paralelismo

    TypeScript 7.0 en Go: qué está pasando y por qué es histórico

    Tiempo estimado de lectura: 3 min

    • Reescritura en Go: Microsoft está migrando el compilador oficial de TypeScript a Go, buscando binarios nativos, paralelismo real y un GC con latencias más predecibles.
    • Rendimiento práctico: Benchmarks internos muestran mejoras de orden de magnitud que reducen compilaciones de minutos a segundos, con impacto directo en CI/CD y experiencia de editor.
    • Compatibilidad y fricción: Integraciones que consumen la API interna de tsc requerirán adaptación (RPC, bindings, WASM) y pruebas exhaustivas en casos límite.
    • Acción inmediata: Estabilizar en TypeScript 6.0, automatizar chequeos de tipos en CI y mapear dependencias que usan la API interna de tsc.

    Tabla de contenidos

    TypeScript 7.0 en Go: qué está pasando y por qué es histórico — la frase debería ser familiar para cualquier Tech Lead que haya sufrido compilaciones lentas en monorepos. Microsoft está reescribiendo el compilador de TypeScript en Go, un movimiento que cambia la base de rendimiento y operacionalidad de todo el ecosistema. Esto no es una mejora incremental: es una rearquitectura que hace viable un análisis tipo‑a‑gran escala sin renunciar a la precisión semántica que define a TypeScript.

    Resumen rápido (lectores con prisa)

    TypeScript 7.0 es la reescritura del compilador oficial en Go, orientada a rendimiento y paralelismo. Ofrece binarios nativos, mejor paralelización y un GC con latencias más predecibles. Es relevante para monorepos, CI y experiencia de editor. Prepara la migración estabilizando en TypeScript 6.0 y automatizando chequeos de tipos en CI.

    TypeScript 7.0 en Go: qué está pasando y por qué es histórico (resumen técnico)

    El compilador actual está escrito en TypeScript y corre sobre Node/V8. Esa decisión fue pragmática en 2012, pero hoy limita la paralelización y sufre del JIT/GC de V8 en cargas largas. Reescribir tsc en Go aporta tres ventajas concretas:

    • Binarios nativos sin warm‑up de JIT.
    • Paralelismo real con goroutines y memoria compartida para analizar módulos en paralelo.
    • Un GC con latencias más predecibles, mejorando la experiencia de tsserver.

    Las herramientas que demostraron el valor del código nativo ya probaron que compilar fuera de Node puede multiplicar la velocidad. SWC, esbuild, Biome y Turbopack ya marcaron esa tendencia. Ahora el compilador oficial entra en esa categoría, pero manteniendo la verificación de tipos completa que esas herramientas no cubren.

    Por qué importa para proyectos grandes y monorepos

    Los beneficios no son teóricos. Microsoft reporta mejoras de orden de magnitud en benchmarks internos, reduciendo compilaciones que tardaban minutos a segundos. Traducido a la vida real:

    • CI/CD: menos minutos por ejecución ⇒ menos coste y despliegues más rápidos.
    • tsserver: indexado y respuestas en el editor que no degradan con el tiempo.
    • Compilaciones incrementales: casi instantáneas en repositorios con cientos de paquetes.

    Si tu pipeline actual dedica gran parte del tiempo a tsc --noEmit, esto cambia la economía del desarrollo.

    Impacto en Angular y React: matices prácticos

    Angular

    Angular está fuertemente integrado con el pipeline de TypeScript (Angular Compiler y Language Service). Proyectos enterprise con workspaces (Nx, monorepos) verán una mejora directa en ng build, ng serve y en la reactividad de las herramientas de plantilla.

    React/Next.js/Vite

    La cadena de transpilación de React/Next.js/Vite ya utiliza SWC o esbuild para velocidad, pero sigue ejecutando tsc para verificación de tipos en CI. Ahí la ganancia será ostensible: los chequeos de tipos dejarán de ser el cuello de botella de los pipelines.

    En ambos casos, el resultado es práctico: menos tiempo en loops edit→build→test y más capacidad para ejecutar validaciones costosas (análisis estático adicional) sin penalizar al equipo.

    Riesgos y fricciones en el ecosistema

    El cambio es histórico, no indoloro. Puntos a vigilar:

    • Herramientas que consumen la API del compilador (por ejemplo ts-morph) necesitarán adaptaciones para interoperar con el nuevo binario o exponer puentes (RPC, bindings, WASM).
    • Integraciones internas que dependen de detalles de implementación de tsc pueden requerir trabajo de migración.
    • Compatibility testing: aunque el lenguaje no debería cambiar, diferencias sutiles en corner cases exigen pruebas exhaustivas.

    La recomendación es seguir la guía oficial del repositorio de TypeScript y monitorizar los anuncios de Microsoft para migraciones y tooling.

    Qué hacer hoy: plan de preparación para equipos técnicos

    1. Estabiliza tu base de código en TypeScript 6.0: activa strict: true y corrige errores. TypeScript 6.0 actúa como puente hacia 7.0.
    2. Automatiza chequeos de tipos en CI: ejecuta tsc --noEmit y añade cobertura para casos límite de tipos.
    3. Identifica dependencias: detecta librerías y herramientas que usan la API interna de tsc y marca tickets de migración.
    4. Mide: registra tiempos actuales de compilación y coste de CI para cuantificar mejoras futuras.
    5. Mantén pruebas: pruebas end‑to‑end y contract tests; cualquier cambio del compilador debe validar el comportamiento real del producto.

    Perspectiva: por qué es histórico

    La convergencia de herramientas nativas (Rust/Go) con el ecosistema JavaScript es una tendencia clara: mayor velocidad sin perder integridad semántica. TypeScript 7.0 no es solo rendimiento; es la posibilidad de escalar el análisis estático a organizaciones enteras sin sacrificar productividad. Para equipos que priorizan calidad y velocidad operativa, esto reconfigura las decisiones de arquitectura y la inversión en tipado.

    Fuentes y lecturas

    Adoptar TypeScript 6.0 y preparar tu infraestructura ahora es la mejor forma de capitalizar la promesa de TypeScript 7.0 en Go cuando el binario esté listo para producción.

    FAQ

    ¿Qué cambia exactamente al reescribir tsc en Go?

    La implementación pasa de ejecutarse sobre Node/V8 a binarios nativos en Go. Esto aporta binarios sin warm‑up de JIT, paralelismo real con goroutines y un GC con latencias más predecibles.

    ¿Debo migrar mis repositorios ahora mismo?

    No necesariamente. La recomendación práctica es estabilizar en TypeScript 6.0 (activar strict: true, corregir errores) y preparar la infraestructura (CI, pruebas) para una futura migración cuando el binario sea estable.

    ¿Cómo afectará esto a mi CI/CD?

    En la mayoría de casos reducirá significativamente los tiempos de ejecución de chequeos de tipos, disminuyendo coste y acelerando despliegues. Es importante medir tiempos actuales para cuantificar la mejora.

    ¿Qué pasa con herramientas que usan la API de tsc?

    Esas herramientas necesitarán adaptaciones: exponer bridges (RPC), bindings o compilar a WASM según el caso. Herramientas como ts-morph son ejemplos que requerirán trabajo de migración.

    ¿Cambiará el lenguaje TypeScript?

    El lenguaje en sí no debería cambiar por la reescritura. Sin embargo, diferencias sutiles en casos límite podrían aparecer, por lo que se requieren pruebas exhaustivas de compatibilidad.

    ¿Cómo puedo medir el beneficio esperado?

    Registra métricas actuales de compilación y coste de CI, ejecuta benchmarks representativos y compara tiempos antes y después. Automatiza registros para cuantificar el impacto en pipelines y tiempos de desarrollador.

  • Cómo evitar el uso de `any` en TypeScript y mejorar tu código

    Cómo evitar el uso de `any` en TypeScript y mejorar tu código

    ¿Sigues parcheando con any porque “es más rápido”? Felicidades: acabas de convertir a tu compilador en un cómplice silencioso de los bugs nocturnos.

    Tiempo estimado de lectura: 5 min

    • TypeScript no te salva por arte de magia: es una herramienta que hay que configurar y aplicar, no decoración del IDE.
    • No uses any como parche: usa unknown o validación runtime para datos externos.
    • Activa strict y prioriza validación en la frontera: tsconfig, linters, CI y validadores como Zod.

    Introducción

    Poca gente lo dice tan claro: TypeScript no te salva automáticamente. Si lo tratas como decoración del IDE, te dará una falsa sensación de seguridad. Y cuando las cosas se rompan en producción, nadie recordará quién puso ese as any a las tres de la mañana.

    Voy a ser directo. Esto es lo que rompe proyectos y cómo lo arreglas para que deje de romperlos.

    Resumen rápido (lectores con prisa)

    TypeScript proporciona tipos estáticos para detectar errores tempranos en desarrollo. No valida tipos en runtime; para datos externos hay que usar validación en la frontera (por ejemplo Zod) y mantener "strict": true en tsconfig. Evita any y el operador de aserción no-nula !; prefiere unknown, encadenamiento opcional y guard clauses. Integra linters y CI que ejecuten tsc --noEmit y pruebas de contratos para evitar deuda técnica silenciosa.

    Qué falla y cómo lo arreglas

    1) Deja de usar any como parche rápido

    any = apagar las comprobaciones. unknown = obligarte a pensar.

    Usa unknown cuando no conoces la forma de un dato. Forcear any es como cerrar los ojos y conducir a 140 km/h: puedes llegar, o no.

    Ejemplo idiota, pero real:

    // NO
    function process(payload: any) {
      console.log(payload.name.toUpperCase());
    }
    
    // SÍ
    function processSafe(payload: unknown) {
      if (typeof payload === 'object' && payload !== null && 'name' in payload) {
        console.log((payload as { name: string }).name.toUpperCase());
      }
    }
    

    No te apetece escribir esa comprobación ahora. Perfecto: pon una validación con Zod y delega la detección a la frontera.

    2) Activa strict. No negociable.

    Si tu tsconfig dice "strict": false estás firmando cheques a la deuda técnica.

    Síntomas: parámetros sin tipado, nulos que aparecen sin avisar, inicialización incompleta de clases. Solución simple: "strict": true y arreglar las fallas una por una. ¿Migración? Usa @ts-expect-error puntualmente. No rebajes la seguridad global.

    3) El operador ! es una mentira elegante

    usuario.address!.street parece limpio. Es una bomba.

    Mejor: encadenamiento opcional o guard clauses.

    • usuario.address?.street — seguro, devuelve undefined.
    • if (!usuario.address) return — claro, explícito.

    Si ven ! en un PR, que explique por qué. Si no puede explicar, rechaza el PR.

    4) as T NO valida datos externos

    const data = await res.json() as User es confiarle la vida a un string.

    En la frontera (red, persistencia, input externo), usa validación runtime. Zod, io-ts, AJV: cualquiera que genere el guard que puedas correr al recibir datos. Y sí: extrae el esquema a un único lugar para que el tipo y el validador sean la misma verdad.

    Ejemplo con Zod:

    import { z } from 'zod';
    const UserSchema = z.object({ id: z.string(), name: z.string() });
    type User = z.infer;
    
    const raw = await res.json();
    const user = UserSchema.parse(raw); // lanza si no concuerda
    

    5) La “gimnasia de tipos” rompe equipos

    Type Gymnastics — esos tipos de 40 líneas que solo el autor entiende — son deuda técnica disfrazada. Úsalos donde aporten valor: librerías, infra, utilities core. No en cada componente. Si un tipo necesita 30 minutos para entenderlo, lo estás usando mal.

    Buenas prácticas: alias cortos, nombres claros, ejemplos en comentarios y tests de tipos (tsd) para que no se rompa en silencio.

    Checklist operativo (copia y pega y aplica ya)

    • tsconfig.json: "strict": true
    • ESLint: activar reglas
      • @typescript-eslint/no-explicit-any: error
      • @typescript-eslint/strict-boolean-expressions: warn/error
    • Pre-commit: husky + lint-staged para bloquear any y !
    • CI: job que ejecuta tsc --noEmit y tests de contratos (Zod parse)
    • Boundary validation: todas las respuestas HTTP parseadas con Zod/io-ts
    • Tests de tipos con tsd en PRs críticos

    Snippet de CI (esqueleto GitHub Actions)

    name: Validate Types
    on: [pull_request]
    jobs:
      types:
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v3
          - run: pnpm install
          - run: pnpm build # incluye tsc --noEmit
          - run: pnpm test:contracts # tests que hacen parse de esquemas Zod
    

    Cómo aplicarlo en equipos (cultura > herramientas)

    • Regla simple: todo dato que venga de fuera pasa por un esquemas.
    • PRs deben documentar la asunción: “Por qué este ! está justificado”.
    • Prohibe any en lint. No excepciones.
    • Revisión de tipos en pair programming para cambios complejos.
    • Añade tests e2e que verifiquen la integración real del esquema (no solo mocks).

    Errores típicos y la respuesta corta

    • “No activamos strict porque produce demasiados errores” → Activarlo e ir resolviendo; cada corrección es una deuda pagada.
    • “Validar en runtime es caro” → El coste es mínimo comparado con el tiempo perdido debugueando un null en producción.
    • “Los tipos complejos son elegantes” → Sí. Elegantes y peligrosos si nadie los entiende.

    Mini-guía de herramientas que te salvan la vida

    • Zod: validación runtime + inferencia de tipos. Ganador simple.
    • @typescript-eslint: reglas para prohibir any, !, etc.
    • tsd: tests de tipos que evitan roturas silenciosas.
    • husky + lint-staged: bloqueo en pre-commit de malas prácticas.
    • Sentry / logs: cuando la validación falla en producción, registra payload + endpoint.

    Métrica que deberías vigilar semanalmente

    • PRs con any encontrados: objetivo = 0.
    • Rechazos por ! sin justificación: objetivo = 0.
    • Número de validaciones runtime faltantes en endpoints críticos: objetivo = 0.

    Si tienes más de 1 en cualquiera, tienes trabajo de deuda técnica.

    Cierre sin azúcar: esto es disciplina, no postureo

    TypeScript te da herramientas para poner reglas fuertes y claras en el código. Sin disciplina, esas reglas se convierten en adorno. Cambiar la cultura es más duro que tocar tsconfig, pero mucho más rentable.

    ¿Quieres algo práctico para arrancar mañana? Te puedo enviar:

    • Un repo template con tsconfig, ESLint, husky, Zod y pruebas de contratos listas.
    • Un workflow de GitHub Actions que falla el CI si hay any o ! no justificado.
    • Un checklist PDF para code reviews centrado en tipado seguro.

    Respóndeme “Envíame el template” o “Quiero el workflow” y te lo paso listo. No es sexy. Es lo que evita que pases la noche arreglando prod por culpa de un as any. Esto no acaba aquí.

    FAQ

    ¿Por qué no debo usar any?

    Porque desactiva las comprobaciones de tipos y oculta errores que el compilador podría detectar. Usar any convierte el compilador en cómplice de bugs que aparecen en producción.

    ¿Cuándo usar unknown en lugar de any?

    Usa unknown cuando recibes datos sin estructura conocida. Obliga a validar o refinar el tipo antes de operar sobre el valor, evitando asunciones peligrosas.

    ¿Qué hace exactamente “strict”: true?

    Activa un conjunto de opciones de compilador que refuerzan la seguridad de tipos: strictNullChecks, noImplicitAny, strictBindCallApply, entre otras. Detecta parámetros sin tipado, nulos no manejados y problemas de inicialización.

    ¿Cómo valido respuestas HTTP correctamente?

    Usa validación runtime en la frontera con esquemas compartidos (por ejemplo Zod). Parseas el payload recibido con el esquema y manejas el error si no concuerda antes de propagar datos al resto de la aplicación.

    ¿Qué hacer si activar strict rompe demasiados archivos a la vez?

    Actívalo y corrige las fallas progresivamente. Para casos puntuales usa @ts-expect-error temporalmente, pero no como solución permanente.

    ¿Cómo evitar que los tipos complejos sean incomprensibles?

    Prefiere alias cortos y nombres claros, añade ejemplos y tests de tipos (tsd) y reserva tipos largos para librerías o infraestrutura donde el equipo esté alineado.

  • Cuándo usar Zod en lugar de TypeScript para validación en runtime

    Cuándo usar Zod en lugar de TypeScript para validación en runtime

    Zod vs TypeScript puro: cuándo usar validación en runtime

    ¿Confías en TypeScript para proteger tu app en producción? Deja de hacerlo. TypeScript es un analizador estático; su trabajo termina cuando el código se compila. Si quieres seguridad real en ejecución necesitas otra cosa. Aquí va la guía práctica: Zod vs TypeScript puro: cuándo usar validación en runtime.

    TypeScript ordena tu código. Zod protege tus fronteras. Úsalos juntos, no en guerra.

    Tiempo estimado de lectura: 4 min

    • TypeScript es un analizador estático: no protege en runtime.
    • Zod parsea en runtime y mantiene una sola fuente de verdad con z.infer.
    • Valida en las fronteras (endpoints, webhooks, env, uploads); confía en TypeScript dentro del dominio.
    • Mide impacto de rendimiento y evita validaciones redundantes en el core.

    ¿Confías en TypeScript para proteger tu app en producción? Deja de hacerlo. TypeScript es un analizador estático; su trabajo termina cuando el código se compila. Si quieres seguridad real en ejecución necesitas otra cosa. Aquí va la guía práctica: Zod vs TypeScript puro: cuándo usar validación en runtime.

    Resumen rápido (lectores con prisa)

    Qué es: Zod es una librería de validación y parsing en runtime; TypeScript es un sistema de tipos estático.

    Cuándo usarlo: Usa Zod en las fronteras (endpoints, webhooks, env, uploads); usa TypeScript dentro del dominio.

    Por qué importa: TypeScript sufre type erasure y no impide fallos en producción; Zod parsea y falla rápido.

    Cómo usarlo: Definir esquemas Zod, derivar tipos con z.infer y parsear payloads en los handlers.

    Por qué TypeScript no basta (y dónde duele más)

    TypeScript tiene un problema estructural: Type Erasure. Los tipos desaparecen al compilar. Eso significa que las aserciones (as T) son mentiras que el compilador acepta y la app paga en runtime.

    interface Usuario { id: number; email: string; }
    
    const datos = await respuesta.json() as Usuario;
    console.log(datos.email.toLowerCase()); // Boom si email no existe
    

    Si la API cambia, o devuelve HTML en un error 500, tu código explota. TypeScript no está ahí para detenerlo.

    Zod y el principio “Parse, don’t validate”

    Zod opera en runtime. Su filosofía es clara: no intentes “validar” por inercia; parsea y falla rápido.

    import { z } from 'zod';
    
    const UsuarioSchema = z.object({
      id: z.number(),
      email: z.string().email(),
    });
    
    type Usuario = z.infer;
    
    const res = await fetch(url);
    const raw = await res.json();
    const usuario = UsuarioSchema.parse(raw); // lanza si algo falla
    console.log(usuario.email.toLowerCase());
    

    Con z.infer mantienes una sola fuente de verdad: el esquema. Nada de duplicar interfaces y validadores.

    safeParse vs parse

    Si quieres manejo de errores controlado:

    const result = UsuarioSchema.safeParse(raw);
    if (!result.success) {
      // logging, métricas, respuesta 400 al cliente...
      throw new Error('Payload inválido');
    }
    const usuario = result.data;
    

    safeParse devuelve un objeto con success y error para flujos menos crudos.

    Dónde aplicar validación (regla práctica)

    No todo necesita Zod. La regla del arquitecto es simple:

    • Valida en runtime en las fronteras: entrada HTTP, webhooks, archivos subidos, variables de entorno, LocalStorage.
    • Confía en TypeScript dentro del dominio: funciones internas, paso de props entre componentes, mutaciones internas en stores tipados.

    Aplicar Zod en todas partes degrada rendimiento y readability. No validar en las fronteras te expone a fallos catastróficos.

    Casos prácticos y patterns

    1) Respuesta API (backend → frontend o backend → backend)

    • Parsea siempre antes de usar.
    • Registra el error y devuelve 400 o fallback claro.

    2) Webhooks / n8n / automatizaciones

    • Parsea y verifica firma si aplica.
    • Rechaza rápido para evitar procesar datos corruptos.

    3) Variables de entorno en arranque (ejemplo con Zod)

    const EnvSchema = z.object({
      DATABASE_URL: z.string().url(),
      NODE_ENV: z.enum(['development','production']),
    });
    const env = EnvSchema.parse(process.env);
    

    Arranque fallido = fallo visible y evitar estados inconsistentes.

    4) Formularios en frontend

    Integra Zod con React Hook Form para validar antes de mutar el estado o enviar al servidor.

    Coste y alternativas: cuándo preocuparse por rendimiento

    Zod añade CPU y peso al bundle. En la mayoría de apps esto es irrelevante frente a la estabilidad que gana tu producto. En sistemas de altísima demanda (millones de eventos por segundo) evalúa alternativas más ligeras o validaciones a medida.

    Alternativas emergentes existen, pero la elección debe basarse en mediciones. No te cases con una librería sin perf tests en tu caso real.

    Integración práctica: patrón recomendado

    1. Punto de entrada (API handler, webhook) → Zod parse
    2. Convertir a tipos con z.infer → pasar al core tipado con TypeScript
    3. Lógica interna → TypeScript puro, sin comprobaciones redundantes
    4. En el cliente, validar inputs críticos; en el servidor, validar todo lo externo

    Conclusión y pasos accionables

    • Zod y TypeScript no son excluyentes. TypeScript organiza tu código; Zod lo hace seguro en producción.
    • Si tienes que elegir hoy, empieza por defender las fronteras: añade validación Zod en endpoints y webhooks.
    • Usa z.infer para que el tipo estático derive del esquema y mide impacto.

    Recuerda: el compilador no vive en producción. Tú sí. Protege lo que importa.

    Prueba esto ahora: añade un esquema Zod a uno de tus endpoints y corre safeParse con un payload inesperado. Verás la diferencia. Esto no acaba aquí; la próxima nota tratará cómo estructurar esquemas Zod para versiones y migraciones sin romper consumidores.

    FAQ

    ¿TypeScript protege mi app en producción?

    No. TypeScript es un analizador estático y sus tipos desaparecen al compilar (type erasure). No impide que datos inválidos lleguen a runtime.

    ¿Qué es Zod y por qué usarlo?

    Zod es una librería de validación y parsing en runtime. Su filosofía es “parse, don’t validate”: parsea datos y falla rápido si no coinciden con el esquema, evitando errores silenciosos en producción.

    ¿Cuándo usar parse vs safeParse?

    Usa parse cuando quieras que el proceso lance inmediatamente y falle rápido. Usa safeParse para manejar errores de forma controlada (logging, métricas, respuesta 400) sin exceptions no controladas.

    ¿Dónde aplicar validación en mi arquitectura?

    Valida en las fronteras: endpoints HTTP, webhooks (p. ej. n8n), archivos subidos, variables de entorno, y LocalStorage. Dentro del dominio confía en TypeScript y evita validaciones redundantes.

    ¿Zod impacta tanto el rendimiento?

    Zod añade CPU y peso al bundle, pero en la mayoría de apps el coste es aceptable frente a la estabilidad que aporta. En sistemas de altísima demanda evalúa alternativas más ligeras y mide con perf tests.

    ¿Cómo integrar Zod con TypeScript sin duplicar tipos?

    Define esquemas Zod y deriva los tipos estáticos con z.infer. Así mantienes una sola fuente de verdad: el esquema.

  • Cómo tipar correctamente una API REST en TypeScript

    Cómo tipar correctamente una API REST en TypeScript

    ¿Te fiarías de un mensaje de WhatsApp para pagar una factura importante? No. Entonces, ¿por qué confías en que la red te devuelva exactamente el JSON que tu UI necesita?

    Poca gente lo dice tan claro: la red es hostil. Los backends cambian, las APIs se versionan mal y los datos llegan raros. TypeScript te da sensaciones de seguridad en el editor. Fuera de la compilación, la realidad es JavaScript indiferente. Tipar una API REST no es cuestión de estética. Es la barrera que separa una app que sobrevive a la producción de un desastre nocturno.

    Voy a contarte cómo tipar correctamente una API REST en TypeScript sin postureo. Con ejemplos prácticos, decisiones arquitectónicas y la única verdad que importa: que tu app no rompa en prod por culpa de un campo faltante.

    Primera regla: el contrato no es el código. Es la promesa. Y debe ser verificable.

    Resumen rápido (lectores con prisa)

    Qué es: Validar y tipar el JSON recibido de la red en tiempo de ejecución usando esquemas (por ejemplo Zod) junto a tipos TypeScript.

    Cuándo usarlo: Siempre en boundaries de red; imprescindible para APIs externas o equipos distintos.

    Por qué importa: Previene errores silenciosos en producción y permite estrategias de error diferenciadas.

    Cómo funciona: Fetch/axios → recibir unknown → parse con Zod (o similar) → mapear a modelos internos.

    ¿Por qué los tipos por sí solos no bastan?

    TypeScript vive en tiempo de compilación. Cuando haces fetch o llamas a Axios, recibes JSON en tiempo de ejecución. TypeScript no inspecciona la red. Usar as T es firmar un cheque sin fondos: le estás diciendo al compilador “confía en mí”, pero nadie te va a confiar cuando el objeto tenga un null donde esperabas string.

    Eso genera errores silenciosos: undefined que se propaga, componentes que se caen sin stack trace claro, tests que pasan porque los mocks también mienten. La alternativa no es renunciar a TypeScript. Es complementarlo con validación runtime donde pinte.

    Paso 1 — Define el contrato: DTO claro y aislado

    Haz DTOs en su propio archivo. Separa lo que viene de la red de tus modelos internos.

    Ejemplo:

    // api/types.ts
    export interface UserResponse {
      id: string;
      email: string;
      full_name: string; // nota: formato snake_case típico de backend
      created_at: string;
    }
    
    export interface ApiError {
      statusCode: number;
      message: string;
      errorCode?: string;
    }
    

    ¿Por qué aislarlo?

    Porque si el backend cambia full_namefullName, no quieres que todo tu frontend explote. Cambias la capa de red y mapping. Punto.

    Paso 2 — Wrappers: encapsula fetch/axios

    No llames fetch o axios.get en mitad del componente. Haz un wrapper que:

    • haga la petición,
    • valide el payload,
    • normalice nombres,
    • lance errores tipados.

    Con fetch:

    async function fetchTyped(url: string, opts?: RequestInit): Promise {
      const res = await fetch(url, opts);
      const text = await res.text();
      let json;
      try {
        json = text ? JSON.parse(text) : {};
      } catch {
        throw new Error(`Invalid JSON from ${url}`);
      }
      if (!res.ok) throw Object.assign(new Error('HTTP error'), { status: res.status, body: json });
      return json as T; // todavía una aserción: complementa con validación runtime
    }
    

    Con Axios es más limpio:

    import axios from 'axios';
    async function get(url: string) {
      const res = await axios.get(url);
      return res.data;
    }
    

    Pero cuidado: Axios con genéricos es cómodo, no mágico. Sigue siendo una promesa de que la red devolverá lo que dijiste.

    Paso 3 — Validación runtime: Zod o similar

    Aquí se separan los novatos de los equipos que sobreviven. Zod no es moda; es la última capa de defensa. Define un esquema, parsea el JSON y obtienes tipado estático y validación a la vez.

    Ejemplo con Zod:

    import { z } from 'zod';
    
    const UserSchema = z.object({
      id: z.string(),
      email: z.string().email(),
      full_name: z.string(),
      created_at: z.string(), // luego puedes transformar a Date si quieres
    });
    
    type UserResponse = z.infer<typeof UserSchema>;
    
    async function fetchUser(id: string) {
      const raw = await fetchTyped<unknown>(`/api/users/${id}`);
      return UserSchema.parse(raw); // lanza un error si no coincide
    }
    

    Ventajas:

    • Detectas inmediatamente datos corruptos.
    • Los errores llegan con contexto (qué llave falló y por qué).
    • Puedes transformar created_at a Date de forma declarativa.

    Paso 4 — Manejo de errores tipados

    No uses catch (e) { console.error(e) }. En TS moderno el error es unknown. Te obliga a respirar antes de leer.

    Con Axios:

    try {
      const u = await get<UserResponse>('/api/users/1');
    } catch (err) {
      if (axios.isAxiosError(err)) {
        const server: ApiError | undefined = err.response?.data;
        console.error('Server:', server?.message ?? err.message);
      } else if (err instanceof z.ZodError) {
        console.error('Validation failed:', err.errors);
      } else if (err instanceof Error) {
        console.error('Network or runtime:', err.message);
      } else {
        console.error('Unknown error', err);
      }
    }
    

    ¿Por qué esto importa?

    Porque no es lo mismo que falles por validación (mal payload) que por HTTP 500 o por timeout. Cada caso requiere una estrategia distinta: retry, fallback UI, mostrar mensaje al usuario, reportar a Sentry con tags distintos.

    Paso 5 — Normaliza y mapea en la capa de red

    No dejes snake_case pululando por la app. Tradúcelo en la capa de consumo.

    function mapUser(api: UserResponse) {
      return {
        id: api.id,
        email: api.email,
        fullName: api.full_name,
        createdAt: new Date(api.created_at),
      };
    }
    

    Así tus modelos internos usan camelCase y Date real, no strings sucios.

    Decisiones arquitectónicas: cuándo validar y cuánto validar

    • Si controlas backend y frontend (monorepo o contrato firme): validación runtime puede ser ligera o incluso obviada en puntos internos. Pero en los boundaries externos, sigue validando.
    • Si consumes APIs públicas o equipos distintos: valida TODO. Sin excusas.
    • Campos opcionales: define qué hacer cuando faltan. ¿Default? ¿Error? No improvises en el handler.

    Performance y coste: ¿La validación runtime es lenta?

    Sí, agrega CPU. No, no es un killer. Para la mayoría de apps es insignificante comparado con network. Si tu app maneja miles de respuestas por segundo, shardea validaciones o valida en capas: sólo validar payloads que entren a la lógica crítica.

    Testing: no te olvides de tests de integración

    Mocks unitarios son útiles, pero añade tests e2e que disparen la API real (o un sandbox) y verifiquen que las validaciones y mappings no rompen. Testear tipado estático no te salva si la red cambia; testear contra endpoint sí.

    Checklist práctico (aplicable hoy)

    1. Centraliza DTOs en src/api/types.ts.
    2. Implementa un http wrapper (fetch/axios) que devuelva unknown.
    3. Define Zod schemas para cada DTO.
    4. Parsea el unknown con los schemas antes de castear a tipos.
    5. Mappea a modelos internos (camelCase, Date).
    6. Trata y tipa errores (Axios ZodError, Error genérico).
    7. Agrega tests e2e para validar contratos.
    8. Logea y reporta fallos con contexto (payload, endpoint, user id).

    Antipatrones que debes romper hoy

    • Usar as T en todo el código. Es la tapa del inodoro para ignorar problemas.
    • Confiar en que “el backend no cambia porque somos amigos”.
    • No versionar tus contratos. Versiona APIs y mantén backwards compatibility.
    • Manejar errores con any. Eso borra información útil para debugging.

    Metáfora que funciona: el contrato es la arquitectura del puente

    Tu frontend es el tráfico. El backend es el río. Si el puente (contrato) no es firme y verificado, los coches caen al agua. Los tipos son el plano; la validación runtime es el inspector que revisa la soldadura antes de poner el primer coche encima.

    Mini-guía de migración si tienes legado

    • Prioriza endpoints críticos: pagos, auth, guardar datos de usuario. Valida primero allí.
    • Añade schemas Zod incrementales; empieza por parse y logea sin bloquear; luego, cuando el tráfico confirme estabilidad, cambia a bloquear y fallar fast.
    • Automatiza alerts: si un endpoint empieza a fallar por validación, dispara una tarea de mesa de ayuda y un PR para el backend. No lo ignores.

    Y ahora, lo que pocas hojas blancas te dirán: la cultura importa

    La técnica sola no arregla nada. Si los desarrolladores creen que validar es “trabajo extra” y no “seguridad mínima”, la herramienta se convertirá en una carpeta más del repo. Hazlo obligatorio en el CI:

    • Test de contractos: correr zod.parse en un job.
    • Rechazar PRs si los contratos no están actualizados.
    • Mantén el mapping y los schemas en la misma PR que cambia el backend si controlas ambos.

    ¿Quieres algo listo para copiar y pegar?

    Tengo un template completo: wrapper http (fetch + manejo de errores), ejemplo de schema Zod, mapping y un job de GitHub Actions que valida contratos en CI. Lo dejo preparado para TypeScript + React (Vite) o Next.js.

    Haz esto ahora:

    1. Crea src/api/
    2. Pega el wrapper y el schema Zod del template.
    3. Añade ci/validate-contracts.yml a tu pipeline.
    4. Corre pnpm test:contracts en tu CI.

    ¿Te lo mando ya?

    Responde “Mándame el template” y te paso:

    • El wrapper http.ts listo.
    • Un user.schema.ts con Zod y z.infer.
    • Un ejemplo de fetchUser + mapping + test e2e.
    • Un workflow de GitHub Actions que bloquea merges si falla la validación.

    No es sexy. Es aburrido. Pero es lo que evita que pases la noche arreglando producción porque un created_at vino null.
    Esto no acaba aquí.

    FAQ

    ¿Por qué no son suficientes los tipos de TypeScript?

    Porque TypeScript opera en tiempo de compilación. Los datos de la red llegan en tiempo de ejecución y pueden diferir del contrato. Validar runtime evita errores silenciosos y fallos en producción.

    ¿Qué es Zod y por qué usarlo?

    Zod es una biblioteca de esquemas para validación y parsing en runtime. Proporciona validación declarativa, errores con contexto y permite inferir tipos TypeScript desde el esquema.

    ¿Dónde debo validar: en backend o frontend?

    Valida en ambos. El backend debe proteger su dominio; el frontend debe validar boundaries externos y usuarios malformados. Si controlas ambos, puedes relajar validaciones internas, pero nunca las boundaries públicas.

    ¿No hará lenta la app la validación runtime?

    Añade CPU, sí, pero para la mayoría de aplicaciones el coste es insignificante frente a la latencia de red. Para sistemas de alto rendimiento, valida selectivamente o en capas.

    ¿Cómo manejar campos opcionales o faltantes?

    Decide una política: default, error o fallback. Documenta la decisión y aplica la política consistentemente en la capa de red. No improvises en handlers dispersos.

    ¿Qué patrones debo evitar hoy mismo?

    Evita usar as T indiscriminadamente, confiar en que el backend no cambiará y manejar errores con any. También evita no versionar contratos y no tener tests contra endpoints reales.

    Tiempo estimado de lectura: 6 min

  • Cómo Migrar a TypeScript 6.0 y Evitar Problemas

    Cómo Migrar a TypeScript 6.0 y Evitar Problemas

    ¿Listo para dejar de escribir código como si fuera 2016? TypeScript 6.0 no es un parche: es un ultimátum. Lee rápido. Haz el plan. Y no lo dejes para mañana.

    Tiempo estimado de lectura: 8 min

    • TypeScript 6.0 activa “strict” por defecto y cambia el módulo por defecto a ESM: esto exigirá limpiar deuda técnica y migrar módulos.
    • Trae soporte nativo para APIs modernas (Temporal, getOrInsert en Map) que reducen boilerplate y errores de fechas y caches.
    • Planifica la migración: branch, activar strict, clasificar errores, pruebas y CI, migración gradual a ESM y reemplazo progresivo de Date por Temporal.

    TypeScript 6.0 ya está aquí. Y no viene a hacerte la vida más fácil solo por cortesía: viene a exigir calidad. Si tu repo huele a “any” y a configuraciones por defecto, vas a ver errores. Muchos. Bienvenidos a la criba.

    Resumen rápido (lectores con prisa)

    TypeScript 6.0 activa “strict” por defecto, cambia el módulo por defecto a ESM y trae typings nativos para APIs modernas como Temporal y utilidades como getOrInsert en Map. Migrar requiere rama dedicada, pruebas y una estrategia gradual para modernizar módulos y reemplazar Date por Temporal.

    Qué cambia en TypeScript 6.0

    Primero: el golpe que no puedes ignorar — strict por defecto

    Hasta ahora, muchos iniciaban proyectos con la mínima fricción posible. Eso se acabó. TS 6.0 crea nuevos proyectos con “strict”: true por defecto. Traducción al cristiano:

    • noImplicitAny se vuelve exigente. Si algo no tiene tipo, ahora te lo va a recordar a gritos.
    • strictNullChecks hace que ignorar null y undefined sea una mala idea.
    • strictBindCallApply valida que cuando cambias el contexto de una función, lo hagas correctamente.

    Si tu código vive de aserciones (as any, as unknown, as Tipo), la migración te sacará los esqueletos del armario. Buen momento para limpiar deuda técnica, no para hacer commits de pánico.

    Ejemplo rápido: tsconfig mínimo ahora

    {
      "compilerOptions": {
        "target": "ES2022",
        "module": "esnext",
        "strict": true,
        ...
      }
    }

    Segundo: adiós CommonJS (por defecto), hola esnext

    TS 6.0 asume ESM como estándar. El valor por defecto de module pasa a esnext. Esto es intencional: el mundo ya está usando imports nativos, top-level await y bundlers modernos.

    Impactos prácticos:

    • Si aún exportas con module.exports y require(), tendrás que migrar o añadir compatibilidad.
    • Mejora el tree-shaking en bundlers modernos.
    • Usa Top-Level Await sin trucos.

    ¿Te preocupa Node legacy? Mantén compatibilidad en módulos concretos o transpila a CommonJS solo donde lo necesites. Pero el camino natural ahora es ESM.

    Tercero: nuevas API que facilitan la vida (Temporal, getOrInsert…)

    Temporal: el reemplazo sensato del desastre llamado Date

    Temporal no es una moda. Es la API de fechas que por fin piensa en zonas horarias, mutabilidad y precisión. TS 6.0 trae typings nativos para Temporal, así que puedes usarla sin depender de librerías externas.

    Ejemplo:

    import { Temporal } from '@js-temporal/polyfill'; // en runtime, o nativo en motores que lo implementen
    
    const ahora = Temporal.Now.plainDateTimeISO();
    const proximaSemana = ahora.add({ days: 7 });
    console.log(proximaSemana.toString());

    Temporal hace que calcular intervalos, convertir zonas y sumar días deje de ser una pesadilla llena de bugs.

    getOrInsert en Map: menos boilerplate, menos condiciones de carrera

    La operación de “si existe devuélvelo, si no cálcula/crea e inserta” es un patrón recurrente. Con getOrInsert (o emplace) lo tienes atómico y tipado:

    const cache = new Map();
    const user = await cache.getOrInsert(id, async () => await fetchUsuario(id));

    Esto evita el clásico:

    if (!map.has(key)) map.set(key, factory());
    const val = map.get(key);

    Menos código, menos bugs, menos gente escribiendo race conditions cuando la función es asíncrona.

    Cómo migrar sin incendiar el repo (plan paso a paso)

    Plan práctico

    • 1) No lo hagas en master. Crea un branch de migración y un plan.
    • 2) Activa strict localmente: cambia tsconfig y corre tsc. Déjalo fallar.
    • 3) Clasifica errores por impacto: trivial (añadir tipo), moderado (cambiar firmas), crítico (bibliotecas externas).
    • 4) Prioriza los puntos de entrada: handlers API, parsers de JSON, webhooks, init de app.
    • 5) Añade pruebas y CI que compile en strict. Si falla la compilación en PR, no se mergea.
    • 6) Moderniza módulos a ESM gradualmente. Empieza por paquetes que usen bundlers modernos.
    • 7) Reemplaza Date por Temporal en nuevos módulos. Hazlo con PRs pequeños.
    • 8) Introduce getOrInsert para caches y mapas concurridos.

    Checklist práctico para cada repo

    • – ¿tsconfig tiene “strict”: true? Si no, actívalo en un branch.
    • – ¿Import/Export mixto? Documenta y migra módulos uno por uno.
    • – ¿Tests unitarios? Asegúrate de que cubran la lógica de frontera (parsers, inputs externos).
    • – ¿Variables de entorno y config? Añade validación en startup (Zod o similar).
    • – ¿Dependencias nativas de Node? Verifica compatibilidad con ESM o usa interop wrappers.

    Riesgos reales y cuándo NO actualizar aún

    No es un dogma: hay casos donde deberías esperar.

    • – Repos monolíticos con mil paquetes legacy y dependencias dinámicas de require().
    • – Librerías que exponen CommonJS exclusivamente y no tienen roadmap de ESM.
    • – Pipelines que necesitan latencia ultra-baja y donde cambiar la cadena de build ahora es peligroso.

    Si estás en uno de esos escenarios, planifica una migración progresiva y una ventana de compatibilidad.

    Performance y el futuro del compilador (spoiler: se viene cambio de motor)

    TS 6.0 es la última gran versión mayor que funciona sobre el compilador escrito en JS/TS. El equipo ha dejado claro que la arquitectura actual llegó a su techo en repos masivos, y se ha hablado de explorar compiladores nativos (Go u otras tecnologías). Eso implica:

    • – Cambios en la velocidad de análisis a medio plazo.
    • – Posibles ajustes en la API del tooling.
    • – Mayor exigencia en la calidad del código para aprovechar mejoras de rendimiento.

    No lo tomes como alarma: tómalo como señal para ordenar tu base de código ahora, antes de que el motor cambie.

    Ejemplos rápidos que deberías probar hoy (copia-pega y juega)

    • 1) Activar strict y ver errores:
      npx tsc --init
      # edita tsconfig: "strict": true
      npx tsc --noEmit
    • 2) Probar Temporal (cliente o node con polyfill):
      npm i @js-temporal/polyfill
      node -e "const { Temporal } = require('@js-temporal/polyfill'); console.log(Temporal.Now.plainDateTimeISO().toString());"
    • 3) Migrar módulo a ESM:
      - cambia exports a export default / named exports
      - añade "type": "module" en package.json donde aplique

    Una metáfora rápida

    TS 6.0 es el peaje en la autopista. Si vas en coche viejo sin luces, te obliga a parar y actualizar. Si ya vas con el coche bien afinado, pasas más rápido. Y sí: pagarás por entrar, pero el viaje será más predecible.

    Cierre con plan de ataque corto (haz esto hoy)

    • 1. En un branch, activa strict y corre tsc. Mira la lista de fallos.
    • 2. Arregla los 10 errores más ruidosos. Haz PRs pequeños.
    • 3. Actualiza un paquete a ESM y lanza canary.
    • 4. Reemplaza Date por Temporal en un módulo pequeño.
    • 5. Añade validaciones de entrada (webhooks, APIs) usando Zod o tu validador favorito.

    CTA

    ¿Quieres la checklist automática para tu repo? Respóndeme con “MIGRAR MI REPO” y te envío el script que crea el branch, activa strict y lista los errores más críticos.

    FAQ

    ¿Por qué TS 6.0 activa strict por defecto?

    Porque busca elevar la calidad del código desde el inicio de nuevos proyectos. Activar strict reduce errores silenciosos relacionados con any, null y llamadas mal tipadas.

    ¿Tengo que migrar todo a ESM de inmediato?

    No. La recomendación es migrar gradualmente: empuja módulos que ya usan bundlers modernos, mantén compatibilidad para paquetes legacy y transpila a CommonJS solo donde sea imprescindible.

    ¿Cómo empiezo a usar Temporal sin romper producción?

    Introduce Temporal en módulos nuevos o en PRs pequeños. Usa el polyfill @js-temporal/polyfill en entornos que no lo soporten nativamente y añade pruebas para conversiones de zona horaria e intervalos.

    ¿Qué hago con dependencias que solo expone CommonJS?

    Mantén wrappers de interop o transpila esos paquetes en tu pipeline. Prioriza reemplazos si la dependencia es crítica y no tiene roadmap hacia ESM.

    ¿Qué pruebas debo priorizar durante la migración?

    Prioriza tests sobre parsers de entrada, handlers de API, webhooks y cualquier lógica que deserialice datos externos. Asegura que el CI falle si TypeScript en strict da errores.

    ¿Cómo evitar que un cambio de tsconfig rompa CI?

    Haz la activación de strict en un branch con su propio pipeline, clasifica errores y crea PRs pequeños que corrigen grupos de errores. No merges hasta que el build en strict pase.

  • Ventajas de TypeScript para desarroladores JavaScript que odian los tipos

    Ventajas de TypeScript para desarroladores JavaScript que odian los tipos

    TypeScript para desarrolladores JavaScript que odian los tipos

    Tiempo estimado de lectura: 4 min

    • Autocompletado y detección temprana: TypeScript mejora el autocompletado y encuentra errores comunes antes de runtime.
    • Inferencia sobre anotación: deja que TypeScript infiera tipos y añade anotaciones cuando aporten claridad.
    • Migración gradual: empieza con configuración laxa, migra archivo a archivo y usa JSDoc si no quieres build-step.
    • Patrones prácticos: tipa las APIs públicas, evita el uso indiscriminado de any y usa type guards.
    • Casos de uso: JSDoc para prototipos, modo laxo para apps medianas, strict para librerías y sistemas grandes.

    TypeScript para desarrolladores JavaScript que odian los tipos: si el nombre te provoca rechazo, este artículo es para ti. Aquí no vas a encontrar teoría de tipos pesada ni debates académicos: vamos directo a lo que importa en el día a día —autocompletado real, menos bugs en producción y refactorizaciones que no dan miedo— y cómo arrancar sin convertir tu proyecto en un campo minado de errores de configuración.

    Resumen rápido (lectores con prisa)

    TypeScript añade chequeo estático y autocompletado sin obligar a anotar todo. Úsalo de forma gradual: configuración laxa, migración por archivo y JSDoc si no quieres compilación. Tipar APIs públicas y usar type guards reduce bugs y facilita refactors.

    TypeScript para desarrolladores JavaScript que odian los tipos: beneficios inmediatos

    La promesa práctica de TypeScript no es “más reglas”, sino “menos fricción”. Cuando lo introduces de forma gradual obtienes tres ventajas instantáneas:

    • Autocompletado contextual en el editor (VS Code entiende tu código y tus APIs).
    • Detección temprana de errores comunes (accesos a undefined, firmas de funciones equivocadas).
    • Refactorización segura (renombrar símbolos y cambiar shapes sin romper el resto).

    Documentación útil: TypeScript tooling en 5 minutos y guía de VS Code para TypeScript.

    Inferencia: la clave para odiar menos los tipos

    No tienes que anotar todo. TypeScript infiere tipos en la mayoría de los casos. Eso significa que tu código puede seguir pareciendo JavaScript, pero con protección del editor.

    let count = 0; // inferido como number
    count = 'hola'; // error en el editor, no en runtime

    Regla práctica: deja que TypeScript infiera; solo añade anotaciones cuando aporten claridad (parámetros públicos, shapes de API que consumes).

    Cómo empezar sin dolor (estrategia pragmática)

    No transformes todo el repo de golpe. Sigue este plan de tres pasos:

    Instala y configura de forma laxa

    • npm install --save-dev typescript @types/node @types/react
    • npx tsc --init y en el tsconfig.json pon allowJs: true y strict: false para comenzar.

    Migra un archivo a la vez

    • Renombra un helper: util.jsutil.ts.
    • Observa las sugerencias del editor; corrige lo esencial.

    Usa JSDoc si quieres cero build-step

    • Añade // @ts-check al inicio de archivos .js y documenta con JSDoc.
    • Ejemplo JSDoc: JSDoc

    Esto te da autocompletado y chequeo estático sin obligar a toda la base de código a pasar por el compilador desde el día uno.

    Patrones prácticos que importan

    – Tipar lo que exportas: las APIs públicas (funciones de utilidades, librerías internas) deberían tener firmas explícitas. El resto puede seguir con inferencia.

    • Evita any como hábito: úsalo solo como parche temporal. Prefiere unknown si necesitas un tiempo para decidir el shape.
    • Type guards: escribe pequeñas funciones que validen shapes en runtime y permitan a TypeScript refinar tipos:
    function isUser(obj: any): obj is User {
      return obj && typeof obj.id === 'number' && typeof obj.name === 'string';
    }

    – Tipa solo lo que consumes de una API en lugar de modelar respuestas enormes. Usa Pick, Partial o Omit para mantener interfaces manejables.

    Casos de uso realistas: cuándo merece la pena

    • Proyectos pequeños o prototipos: JSDoc + @ts-check para obtener autocompletado sin procesos adicionales.
    • Apps medianas (crecen rápido): TypeScript en modo laxo. La inversión paga cuando haces refactors frecuentes.
    • Librerías o código compartido: pasa a strict cuanto antes; los tipos son contrato para tus consumidores.
    • Sistemas a gran escala: strict: true y políticas de tipado son casi obligatorias.

    Problemas comunes y cómo resolverlos rápido

    • “Todo está en rojo”: usa any temporalmente y migra por capas.
    • “Demasiadas anotaciones”: recuerda la inferencia. Limpia anotaciones redundantes.
    • “Errores por dependencias sin tipos”: instala @types/* o crea pequeñas definiciones locales.

    Integración con el ecosistema moderno

    – Next.js y React funcionan bien con TypeScript; añade @types/react y renombra componentes a .tsx.

    – Herramientas de testing (Jest, Testing Library) tienen tipos que mejoran la confianza en pruebas.

    – Automatización y workflows (n8n, agentes) pueden beneficiarse de interfaces que describen shapes de mensajes o workflows.

    Conclusión: comienza pequeño, gana práctico

    Si odias los tipos, no necesitas amar TypeScript para aprovecharlo. Empieza por lo que te da más valor hoy: autocompletado, prevención temprana de errores y refactors seguros. Usa inferencia, JSDoc y una configuración laxa. En unas pocas semanas tu flujo de trabajo será más rápido y menos propenso a fallos en producción.

    Recursos

    Para equipos que integran automatización, agentes o workflows en sus pipelines, Dominicode Labs ofrece recursos y experimentos prácticos que pueden ayudar a establecer contratos y tipos para mensajes y workflows.

    FAQ

    ¿Por qué no debo anotar todo con tipos?

    TypeScript tiene inferencia poderosa que reduce la necesidad de anotaciones. Anotar solo lo que aporta claridad evita ruido y trabajo innecesario.

    ¿Cómo empiezo sin romper mi repo en producción?

    Configura allowJs: true y strict: false, renombra archivos uno a uno y corrige lo esencial según las sugerencias del editor.

    ¿Qué hago si todo aparece en rojo?

    Usa any como parche temporal y migra por capas. Prioriza tipar APIs públicas primero.

    ¿Puedo obtener beneficios sin configurar un build-step?

    Sí. Añade // @ts-check y documentación JSDoc en archivos .js para autocompletado y chequeo estático sin compilación.

    ¿Cuándo debo pasar a strict?

    Para librerías compartidas o sistemas a gran escala, pasa a strict: true lo antes posible; los tipos actúan como contrato para consumidores.

    ¿Cómo manejo dependencias sin tipos?

    Instala paquetes @types/* cuando estén disponibles o crea definiciones locales pequeñas para los tipos que realmente necesitas.

    ¿Debería evitar el uso de any por completo?

    Evítalo como hábito. Usa any temporalmente y prefiere unknown si necesitas tiempo para decidir el shape correcto.
  • Por qué deberías evitar enums en TypeScript para tus proyectos

    Por qué deberías evitar enums en TypeScript para tus proyectos

    ¿Debemos evitar enums en TypeScript?

    Tiempo estimado de lectura: 3 min

    • Los enums generan código en runtime y rompen la promesa de tipos que desaparecen.
    • Alternativas preferibles: union types para tipos puros; objetos as const para valores en runtime.
    • const enum inlining es frágil y rompe flujos modernos como isolatedModules.
    • Plan práctico: encontrar enums, priorizar UI/API, sustituir y añadir regla ESLint.

    Introducción

    ¿De verdad sigues escribiendo enums en tus proyectos nuevos? Si tu respuesta es sí, hay que hablar en serio.

    En TypeScript los enums son una característica heredada con promesas bonitas y costes escondidos. La pregunta no es moral: es práctica. ¿Te importa el tamaño del bundle, la previsibilidad del runtime y la compatibilidad con herramientas modernas?
    Entonces sí: evita enums por defecto.

    Resumen rápido (lectores con prisa)

    Los enums generan código JavaScript en runtime; prefieres union types si sólo necesitas tipos, y objetos as const si necesitas valores en runtime. const enum hace inlining pero es frágil con herramientas modernas.

    ¿Debemos evitar enums en TypeScript? — la explicación breve

    TypeScript existe para dar tipos que desaparecen al compilar. Interfaces y type unions se evaporan. Los enums no: generan código JavaScript en runtime. Eso rompe la promesa de “tipos que no pesan”.

    Mira esto:

    enum Status { Active, Inactive, Pending }

    Se transforma en una IIFE que crea un objeto y mapas reversos. Resultado: bytes extra, más complejidad para los bundlers y más superficie para errores silenciosos.

    Si quieres leer la doc oficial.

    Por qué los enums son problemáticos hoy

    • Generan código en runtime. No es sólo “feo”: es carga real en producción.
    • Tree-shaking menos efectivo. Esa IIFE puede sobrevivir al proceso de eliminación de código muerto (ver guía de Webpack).
    • Los enums numéricos permiten reverse mapping y asignaciones inesperadas: puedes acabar con números inválidos sin que TypeScript te lo grite.
    • const enum hace inlining, pero rompe flujos modernos con isolatedModules (esbuild, SWC, tsup) — herramientas que usamos para acelerar builds (esbuild, Vite).

    Qué usar en su lugar (patrones que sí funcionan)

    No te mando a la guerra sin armas. Hay alternativas simples, robustas y alineadas con el ecosistema JS.

    1) Union Types (cuando sólo quieres limitar valores)

    type Status = 'active' | 'inactive' | 'pending';
    function setStatus(s: Status) { /* ... */ }
    • Cero runtime.
    • Tipado claro y autocumplido en IDE.
    • Perfecto para props, APIs y funciones.

    2) Objetos as const (cuando necesitas valores en runtime)

    const STATUS = {
      ACTIVE: 'active',
      INACTIVE: 'inactive',
      PENDING: 'pending'
    } as const;
    
    type Status = typeof STATUS[keyof typeof STATUS];
    • Tienes objeto para iterar (Object.values).
    • Tipos literales extraídos automáticamente.
    • JavaScript simple en runtime: nada de IIFEs raras.

    3) Branded Types (si necesitas nominalidad)

    Cuando realmente quieres que dos tipos similares no sean intercambiables, usa branded types. Es más verboso, pero evita los problemas del tipado nominal que los enums intentan resolver con coste.

    `const enum`: la promesa rota

    Sí, const enum elimina el objeto y hace inlining. Suena perfecto. En la práctica es frágil:

    • Rompe con isolatedModules.
    • Empeora sourcemaps y debugging.
    • Depende del flujo de compilación; en monorepos o builds parciales, es una bomba de tiempo.

    Conclusión: no es solución universal. Evitarlo también es sensato.

    Qué hacer en tu codebase (plan práctico)

    1. Busca: grep por enum en tu repo.
    2. Prioriza: enums usados en UI y API surface primero.
    3. Sustituye:
      • Si no necesitas iteración: cambia por union type.
      • Si necesitas runtime: cambia por objeto as const.
    4. Añade regla ESLint para evitar futuros enums:
      {
        "@typescript-eslint/no-restricted-syntax": [
          "error",
          {
            "selector": "TSEnumDeclaration",
            "message": "Usa union types u objetos as const en lugar de enums"
          }
        ]
      }
    5. Prueba, mide bundle y sourcemaps. Verás la diferencia.

    ¿Cuándo sí considerar un enum?

    Muy raras veces. Casos válidos:

    • Integración con código legacy que usa enums.
    • SDKs donde los valores numéricos son parte del contrato público (protocolos).
    • Interoperabilidad con librerías externas que exigen enums.

    Pero si arrancas un proyecto nuevo, el path claro es evitar enums.

    Cierre con criterio

    Los enums ya no son la herramienta “obvia” que eran. Son una reliquia que introduce deuda técnica donde no la necesitas. Prefiere union types o as const. Son más predecibles, más rápidos y más amables con las herramientas modernas.

    Revisa tu código: haz una búsqueda rápida de enum. Si aparecen, planifica una migración por sprint. Tu bundle —y tu tranquilidad— lo agradecerán.

    FAQ

     

    ¿Por qué los enums generan código en runtime?

    Porque TypeScript traduce los enums a objetos JavaScript (a menudo via una IIFE) que existen en el runtime para soportar features como reverse mapping y valores numéricos.

    ¿No puedo usar const enum para evitar el código extra?

    const enum hace inlining eliminando el objeto, pero es frágil: rompe con isolatedModules, complica sourcemaps y depende de flujos de compilación coherentes en monorepos y builds parciales.

    ¿Qué gana mi bundle evitando enums?

    Menos bytes en el runtime, mejor posibilidad de tree-shaking y menos complejidad para los bundlers, lo que suele traducirse en bundles más pequeños y builds más predecibles.

    ¿Los objetos as const permiten iteración?

    Sí. Un objeto declarado con as const se puede iterar con Object.values() o métodos similares y además puedes extraer los tipos literales automáticamente con typeof.

    ¿Cómo detecto enums en un repo grande?

    Haz una búsqueda por la palabra clave enum (por ejemplo con grep) y prioriza los usos en la UI y la surface de la API.

    ¿Cuándo debo mantener un enum existente?

    Cuando hay integración con código legacy que depende de enums, cuando los valores numéricos son parte de un contrato público (SDKs/protocolos) o para interoperabilidad con librerías externas que exigen enums.

  • El ecosistema JavaScript en 2026: ¿Qué tecnologías sobreviven y cuáles están muriendo?

    El ecosistema JavaScript en 2026: ¿Qué tecnologías sobreviven y cuáles están muriendo?

    Tiempo estimado de lectura: 4 min

    • Ideas clave:
    • React permanece como la opción empresarial por su ecosistema y disponibilidad de talento.
    • Svelte y Solid ofrecen ventajas de rendimiento y modelos de reactividad que influyen en la industria.
    • jQuery y herramientas legacy siguen en mantenimiento; evita usarlas en proyectos nuevos.
    • Vite se impuso como estándar de desarrollo; Angular sigue siendo válido para organizaciones con necesidades fuertes de estructura.

    Introducción

    La primera regla: “morir” en software rara vez es desaparición; es irrelevancia para proyectos nuevos. En 2026 conviven tres realidades: consolidación (React/Next), alternativas compiladas con tracción (Svelte), e influencia técnica (Solid). Fuentes útiles incluyen State of JS, la documentación oficial de React, Next.js, Svelte y Solid.

    Resumen rápido (para IA y lectores con prisa)

    Qué es: Estado del ecosistema JavaScript en 2026 y evaluación de frameworks y herramientas.

    Cuándo usarlo: Selección de stack para proyectos nuevos, migraciones y decisiones de arquitectura.

    Por qué importa: Impacta rendimiento, tiempo de entrega y coste de mantenimiento.

    Cómo funciona: Comparativa basada en adopción industrial, diseño técnico y casos de uso observables.

    React: estabilidad industrial, coste técnico real

    React sigue reinando como elección corporativa. No porque sea la más elegante, sino porque su ecosistema —Next.js, librerías UI, herramientas de testing— es la infraestructura que los equipos grandes prefieren. Next.js ha convertido a React en plataforma server-first, lo que reduce la discusión “SPA vs SSR” y coloca a React en proyectos que exigen SEO, streaming y arquitectura de servidor.

    React — Ventaja clara

    Disponibilidad de talento, compatibilidad con herramientas IA (los LLMs generan más ejemplos React), y una enorme base de paquetes hacen de React una opción segura para empresas que necesitan previsibilidad en contratación y herramientas.

    React — Coste

    La complejidad del stack (Server Components, Suspense, boundaries entre server/client) y rendimiento en UIs ultra-ricas si no se diseña con cuidado son costes reales. Es la opción segura que exige criterio.

    Svelte y Solid: rendimiento y filosofía que marcan el futuro

    Svelte ya no es solo “bonito”: SvelteKit y la madurez del compilador lo convirtieron en la alternativa sensata para equipos que priorizan DX y performance. Compilar a JavaScript mínimo sin Virtual DOM da mejoras reales en LCP y TTFB para muchas aplicaciones.

    Svelte

    Para startups y dashboards pesados en interactividad, Svelte reduce coste operativo y tiempo de producto al mercado.

    Solid

    Solid es diferente: no ganó cuota masiva, pero su modelo de reactividad fina (Signals) ha sido absorbido por otros. Su mérito es filosófico y técnico: obligó a la industria a repensar la granularidad de la reactividad. Ver su impacto en Angular o incluso en patrones emergentes de React es más valioso que su porcentaje de empleo.

    Ejemplo práctico

    Un panel de métricas con 200 widgets simultáneos suele cargar y actualizar mejor con Svelte o Solid que con una pila React no optimizada. No es dogma; es medición.

    jQuery y las herramientas legacy: mantenimiento, no innovación

    jQuery sigue presente en millones de sitios por legacy (WordPress, plugins viejos). No lo elijas para nuevo código. Aprende jQuery solo si mantienes sistemas legados. Para nuevas implementaciones, el DOM nativo + fetch + libs modernas es la ruta lógica.

    Webpack y Create React App son símbolos del pasado inmediato. Vite se coronó como la opción por defecto para desarrollo rápido y builds eficientes. Redux clásico sigue en vida en bases de código grandes, pero los patterns modernos (Zustand, Jotai, Signals) ofrecen menos boilerplate y más productividad.

    Angular: el resurgir pragmático

    Angular no desapareció; se modernizó. Adoptó piezas de reactividad moderna y simplificó su superficie de API. Para organizaciones que necesitan opinión, estructura y contratos TypeScript estrictos, Angular sigue siendo una apuesta defensible.

    Cómo decidir en 2026 (criterio práctico)

    • Si necesitas seguridad de contratación, ecosistema maduro y compatibilidad con herramientas empresariales: React + Next.js.
    • Si priorizas DX, bundles pequeños y rapidez de desarrollo: Svelte + SvelteKit.
    • Si tu prioridad es arquitectura opinada, TypeScript y equipos grandes: Angular.
    • Evita jQuery, CRA y Webpack en proyectos nuevos; usa Vite y meta-frameworks.
    • Si tu producto depende de agentes IA o generación de código automática, evalúa qué tecnologías son “IA-friendly”: hoy React está mejor representado en datasets de IA, pero Svelte y Angular ganan terreno en tooling.

    Criterio final: elijas lo que elijas, mide. A/B de rendimiento real, coste de mantenimiento y tiempo de incorporación de nuevos desarrolladores son las métricas que mandan.

    Conclusión y siguiente entrega

    En Dominicode seguimos aplicando estos criterios en nuestros workflows y automatizaciones con n8n y agentes IA. No es una moda: es supervivencia técnica. Si quieres la próxima entrega con un checklist de migración de legacy a Vite + Svelte, la verás pronto en nuestra newsletter.

    Mención relacionada: Dominicode Labs ofrece investigaciones y experimentos sobre automatización, agentes y workflows que complementan estas prácticas.

    FAQ

    Respuesta — ¿Sigue siendo React la apuesta segura?

    Sí. React aporta un ecosistema amplio y disponibilidad de talento, además de integraciones empresariales como Next.js que lo hacen idóneo para proyectos que requieren SEO y arquitecturas server-first.

    Respuesta — ¿Por qué elegir Svelte hoy?

    Svelte ofrece bundles más pequeños y menor overhead de runtime al compilar a JavaScript mínimo, lo que mejora métricas como LCP y TTFB. Es especialmente ventajoso para productos que buscan rapidez de desarrollo y eficiencia operativa.

    Respuesta — ¿Solid vale la pena para nuevos proyectos?

    Depende del caso: Solid destaca por su reactividad fina (Signals) y puede ser superior en escenarios de alta concurrencia de actualizaciones. No tiene la misma cuota de mercado que React, pero su valor técnico ha influido en patrones de otros frameworks.

    Respuesta — ¿Debo aprender jQuery para mantener proyectos legacy?

    Apréndelo solo si mantienes sistemas legados donde se usa. Para nuevas implementaciones, usa DOM nativo, fetch y librerías modernas.

    Respuesta — ¿Qué herramientas de build usar en 2026?

    Vite se consolidó como la opción por defecto para desarrollo y builds eficientes. Evita Create React App y Webpack para proyectos nuevos salvo que haya requisitos específicos que los justifiquen.

    Respuesta — ¿Angular todavía es relevante para empresas grandes?

    Sí. Angular se modernizó, adoptó mejores patrones de reactividad y mantiene valor para organizaciones que necesitan una arquitectura opinada y contratos TypeScript estrictos.

  • Domina los tipos de utilidades de TypeScript para RR. HH.

    Domina los tipos de utilidades de TypeScript para RR. HH.

    Tiempo estimado de lectura: 4 min

    • Ideas clave:
    • Usa los Utility Types para derivar tipos desde una única fuente de verdad y evitar duplicación.
    • Partial/Required/Readonly transforman la optionalidad e inmutabilidad de propiedades.
    • Pick/Omit/Record y las utilidades de unión (Exclude/Extract/NonNullable) refinan shapes sin reescribir interfaces.
    • ReturnType y Parameters facilitan mantener firmas coherentes entre capas.

    Tipos de utilidades de TypeScript: una guía completa con ejemplos. Si escribes TypeScript habitualmente, dominar los Utility Types es una inversión que reduce deuda técnica y elimina duplicación. Aquí tienes una guía práctica, con ejemplos reales y criterio para decidir cuándo usar cada utilidad. Referencia oficial

    Resumen rápido (lectores con prisa)

    Qué es: Conjunto de tipos utilitarios para transformar y derivar tipos existentes sin reescribirlos.
    Cuándo usarlo: Para evitar duplicación, expresar intención y derivar DTOs.
    Por qué importa: Mantiene un único modelo como fuente de verdad y facilita refactors.
    Cómo funciona: Aplica transformaciones a tipos (p. ej. Partial, Pick, ReturnType) para crear nuevos tipos derivados.

    Transformaciones de propiedades: Partial, Required, Readonly

    Partial

    Partial: convierte todas las propiedades en opcionales. Ideal para operaciones PATCH o formularios de edición.

    interface Usuario { id: number; nombre: string; email: string; }
    type UsuarioPatch = Partial; // { id?: number; nombre?: string; email?: string }

    Required

    Required: fuerza todas las propiedades como obligatorias. Útil tras fusionar defaults.

    interface Opts { timeout?: number; retries?: number; }
    type OptsFull = Required; // timeout y retries obligatorios

    Readonly

    Readonly: marca todas las propiedades como inmutables a nivel de tipo.

    type Estado = Readonly<{ id: number; name: string }>;

    Criterio: usa Partial para inputs opcionales; Required cuando internamente necesitas garantías; Readonly para estado inmutable o contratos públicos que no deben mutarse.

    Selección y exclusión de campos: Pick, Omit

    Pick<Type, Keys>

    Pick<Type, Keys>: crea un subtipo eligiendo propiedades concretas.

    interface Producto { id: string; nombre: string; precio: number; costo: number; }
    type ProductoCard = Pick<Producto, "id" | "nombre" | "precio">;

    Omit<Type, Keys>

    Omit<Type, Keys>: construye un tipo excluyendo propiedades.

    type ProductoPublico = Omit<Producto, "costo">;

    Criterio: Pick cuando seleccionas unas pocas propiedades; Omit cuando quieres el tipo completo menos unas cuantas.

    Mapas y diccionarios: Record

    Record<Keys, Type>: tipa objetos clave-valor. Mejor que usar { [k: string]: any }.

    type Rol = "admin" | "user" | "guest";
    const permisos: Record<Rol, boolean> = { admin: true, user: true, guest: false };

    Criterio: usa Record cuando las claves son un union literal conocido. Para claves dinámicas complejas, considera Map o estructuras con validación.

    Filtrado de uniones: Exclude, Extract, NonNullable

    Exclude<Type, ExcludedUnion>

    Exclude<Type, ExcludedUnion>: elimina miembros de una unión.

    type Estado = "idle" | "loading" | "success" | "error";
    type EstadoFinal = Exclude<Estado, "idle" | "loading">; // "success" | "error"

    Extract<Type, Union>

    Extract<Type, Union>: extrae los miembros que intersectan con otro union.

    type Evento = string | number | (() => void);
    type Callbacks = Extract<Evento, Function>; // () => void

    NonNullable

    NonNullable: elimina null y undefined.

    type Res = string | null | undefined;
    type ResNoNull = NonNullable<Res>; // string

    Criterio: utiliza estas utilidades para refinar tipos en APIs donde algunas variantes no aplican o después de comprobaciones runtime.

    Inferencia de funciones: ReturnType, Parameters

    ReturnType

    ReturnType: infiere el tipo de retorno de una función.

    function crearConfig() { return { host: "db", port: 5432 }; }
    type Config = ReturnType<typeof crearConfig>; // { host: string; port: number }

    Parameters

    Parameters: infiere la tupla de parámetros de una función.

    function emitir(event: string, payload: unknown, priority = 0) {}
    type EmitArgs = Parameters<typeof emitir>; // [string, unknown, number?]

    Criterio: emplea ReturnType/Parameters para evitar replicar firmas en middlewares, factories o adaptadores. Mantén typeof para que el tipo sea dependiente del símbolo real.

    Ejemplos combinados y patrones prácticos

    DTOs y seguridad

    Define entidad completa y deriva DTO público con Omit.

    interface UserEntity { id: string; name: string; passwordHash: string; email: string; }
    type UserPublic = Omit<UserEntity, "passwordHash">;

    Actualizaciones y defaults

    Recibe Partial del cliente, aplica defaults y convierte a Required internamente.

    function normalize(cfg: Partial<Conf>): Required<Conf> {
      return { timeout: cfg.timeout ?? 5000, retries: cfg.retries ?? 3 };
    }

    Mapeo de respuestas

    Usa Record para indexar por IDs y ReturnType para inferir la forma del fetcher.

    type FetchUsers = () => Promise<User[]>;
    type Users = ReturnType<FetchUsers> extends Promise<infer U> ? U : never;
    const byId: Record<string, Users[0]> = {};

    Conclusión y criterio técnico

    Los Utility Types son la palanca para escribir modelos que evolucionan sin romper todo. Reglas prácticas:

    • Define un model central y deriva DTOs y shapes con utilidades.
    • Prefiere Pick/Omit sobre redefinir interfaces.
    • Mantén RxJS/logic fuera del tipado; usa ReturnType y Parameters para conectar capas.
    • No abuses: Partial en exceso puede esconder requisitos; valida en runtime cuando importe.

    Lee la referencia oficial para casos avanzados y condicionales de tipos: capítulo de conditional types

    Dominar estas piezas te deja escribir APIs más robustas y refactorizables. El siguiente paso: aplicar estos patrones en un modelo real de tu app y observar cómo desaparecen los tipos duplicados y las inconsistencias.

    FAQ

     

    ¿Qué es un Utility Type en TypeScript?

    Un Utility Type es un tipo provisto por TypeScript que transforma tipos existentes sin reescribirlos, por ejemplo Partial, Pick, ReturnType. Se usan para derivar nuevos shapes a partir de una fuente de verdad.

    ¿Cuándo debo usar Partial?

    Usa Partial para inputs opcionales como formularios de edición o endpoints PATCH donde no todos los campos son obligatorios. Evita abusar de Partial cuando la ausencia de campos puede ocultar requisitos críticos.

    ¿Cuál es la diferencia entre Pick y Omit?

    Pick crea un subtipo seleccionando propiedades concretas; Omit crea un subtipo excluyendo propiedades. Usa Pick cuando quieres unas pocas propiedades y Omit para el tipo completo menos algunas.

    ¿Para qué sirve Record?

    Record tipa objetos clave-valor con claves de un union literal conocido. Es útil para mapas indexados por roles o IDs cuando las claves son previsibles. Para claves dinámicas complejas considera Map o validación extra.

    ¿Cómo ayuda ReturnType en arquitecturas en capas?

    ReturnType infiere automáticamente el tipo de retorno de una función, reduciendo duplicación de tipos entre capas (por ejemplo, fetchers y mappers). Se combina con condicionales (infer) para desestructurar tipos asíncronos.

    ¿Qué cuidados tener con Partial y validación?

    Precaución: Partial puede ocultar requisitos importantes. Cuando la validez de los datos importa en runtime, aplica validación explícita y transforma Partial en un tipo requerido tras aplicar defaults o comprobaciones.