Category: TypeScript

  • Cómo usar httpResource() en Angular para manejar peticiones HTTP

    Cómo usar httpResource() en Angular para manejar peticiones HTTP

    httpResource() — Resource API para HTTP: Peticiones Reactivas con Signals

    ¿Te imaginas definir una petición HTTP como si fuera una variable y olvidarte de orquestar cancelaciones, estados y subscriptions? Eso es lo que propone httpResource() — Resource API para HTTP: defines los parámetros como señales y el recurso se actualiza solo cuando cambian las señales dependientes.

    Tiempo estimado de lectura: 3 min

    • Convierte llamadas de red en estado reactivo accesible desde Signals.
    • Automatiza cancelaciones y recargas cuando las señales dependientes cambian.
    • Reduce boilerplate respecto al patrón Observable + suscripción para patrones “estado → petición”.
    • No reemplaza a RxJS para orquestaciones temporales o streams continuos.

    Introducción

    httpResource() convierte llamadas de red en estado reactivo. Si trabajas con Angular y Signals, esto no es una mejora cosmética: es una forma distinta de pensar la carga de datos. En lugar de construir Observables, suscribirte y gestionar cancelaciones manuales, consumes estado.

    Resumen rápido (lectores con prisa)

    Definición: httpResource() expone peticiones HTTP como señales reactivas que actualizan automáticamente su estado al cambiar dependencias.

    Cuándo usarlo: Para cargas impulsadas por estado (filtros, rutas, paginación).

    Por qué importa: Simplifica cancelaciones automáticas, estados y recargas sin boilerplate.

    Limitación clave: No sustituye a RxJS para orquestación temporal o streams continuos.

    httpResource() — Resource API para HTTP: qué es y por qué importa

    La Resource API expone operaciones asíncronas como señales. httpResource() es la implementación para peticiones HTTP: envuelve la llamada y te devuelve señales listas para consumir (value, loading, error, status y un método reload).

    Angular documenta el enfoque reactividad en su guía oficial (ver Reactive primitives). El HttpClient sigue siendo el motor de la petición; httpResource() es la interfaz que lo conecta con Signals.

    Actualmente la API está en Developer Preview en las versiones recientes, por lo que conviene probar con criterio en proyectos que puedan tolerar cambios en la firma antes de que se estabilice.

    Cómo funciona, sin demasiada magia

    Piensa en httpResource() como un computed() que hace fetch. Definís una función que lee señales. Angular registra dependencias. Cuando cualquiera cambia:

    • la petición anterior se aborta si aún está en curso,
    • se lanza una nueva petición con los parámetros actualizados,
    • las señales (value, isLoading, error, status) reflejan el ciclo de vida automáticamente.

    Ejemplo mínimo

    readonly userId = signal(1);
    
    readonly userResource = httpResource(() => ({
      url: `/api/users/${this.userId()}`,
      method: 'GET'
    }));

    Cambia userId() y el recurso hace el resto. No hay switchMap, no hay takeUntil, no hay memoria de suscripciones.

    Qué recibes “de serie” y por qué eso importa

    Al usar httpResource() obtienes señales listas para consumir en la plantilla o en lógica reactiva:

    • .value() — datos resueltos.
    • .isLoading() — booleano para spinners/skeletons.
    • .error() — información del fallo.
    • .status()idle | loading | resolved | error.
    • .reload() — fuerza una re-ejecución.

    Esto elimina mucho boilerplate: ya no necesitas declarar isLoading, data, error y actualizar cada uno en handlers separados.

    Race conditions: ya no es una preocupación recurrente

    Las condiciones de carrera eran el talón de Aquiles cuando el usuario cambiaba filtros rápido y una respuesta tardía pisaba datos nuevos. Con httpResource() la cancelación es automática: si la señal que define la petición cambia, las peticiones intermedias se abortan. Resultado: datos consistentes y menos código defensivo.

    Eso no significa que el problema desaparezca en todos los contextos, pero sí que desaparece en el patrón más común: “estado cambia → cargar datos para la vista”.

    Cuándo usarlo — y cuándo no

    Usos recomendados

    • La obtención de datos está impulsada por estado (filtros, ruta, paginación).
    • Quieres minimizar boilerplate y unificar el modelo mental del equipo en Signals.
    • Buscas evitar errores por manejo manual de cancelaciones.

    Cuándo no usarlo

    • Necesitas orquestación compleja de eventos (WebSockets, SSE, streams continuos).
    • Requieres transformaciones temporales avanzadas (debounceTime, windowing, combinaciones complejas).
    • Estás construyendo pipelines de datos que dependen del tiempo y eventos más que del estado.

    RxJS sigue siendo la herramienta correcta para flujos de eventos; httpResource() es la herramienta correcta para “estado → petición” limpio y declarativo.

    Impacto arquitectónico real

    El cambio más importante no es técnico: es mental. Cuando el equipo compra la narrativa Signals-first, los componentes y su testing se vuelven más simples. Menos suscripciones olvidadas. Menos efectos colaterales. Mejor onboarding para quien llega nuevo al repo.

    No es magia: es coherencia. httpResource() reduce superficie para errores y acelera decisiones arquitectónicas sobre dónde debe vivir la lógica de carga de datos.

    Recursos y siguientes pasos

    Angular Reactivity Guide

    HttpClient (persistencia del motor)

    Si estás en Angular 19+ y ya trabajas con Signals, empieza por prototipar una pantalla con httpResource() y compara la legibilidad, tests y bugs con la versión RxJS/HttpClient. No lo adoptes por moda: mídelo. Y si en el prototipo funciona, lo siguiente es reescribir un flujo real y ver cuántas líneas de código desaparecen.

    Esto no acaba aquí: hay decisiones de testing, caching y error handling que merecen otra pieza. Si querés, escribo la segunda parte con patrones de testing y estrategias de cache para httpResource().

    FAQ

    ¿Qué es exactamente httpResource()?

    Es una implementación de la Resource API para peticiones HTTP que expone el resultado y el estado de la petición como señales reactivas. Envuelve el mecanismo del HttpClient y ofrece .value(), .isLoading(), .error(), .status() y .reload().

    ¿Cómo se integra con HttpClient?

    El HttpClient sigue siendo el motor que efectúa las peticiones. httpResource() actúa como la interfaz declarativa que lee señales y delega la ejecución y cancelación al HttpClient.

    ¿Qué señales ofrece por defecto?

    Provee .value() (datos), .isLoading() (booleano), .error() (detalle del fallo), .status() (idle | loading | resolved | error) y .reload().

    ¿El API cancela peticiones automáticamente?

    Sí. Si la señal que define la petición cambia mientras una petición está en curso, la petición anterior se aborta automáticamente para evitar condiciones de carrera comunes.

    ¿Cuándo debo seguir usando RxJS?

    Cuando necesitas orquestación compleja de eventos, streams continuos (WebSockets, SSE) o transformaciones temporales avanzadas (debounceTime, windowing, combinaciones complejas), RxJS sigue siendo la herramienta adecuada.

    ¿Es estable la API en producción?

    La API estaba en Developer Preview en versiones recientes; conviene probar con criterio en proyectos que puedan tolerar cambios en la firma antes de adoptarla ampliamente en producción.

  • Aprende sobre el nuevo Authoring Format en Angular y Signals

    Aprende sobre el nuevo Authoring Format en Angular y Signals

    Nuevo Authoring Format (Signal Components): qué es y cómo prepararte

    Tiempo estimado de lectura: 3 min

    Ideas clave

    • Nuevo Authoring Format: replantea la autoría de componentes para hacer la reactividad nativa en la sintaxis.
    • Dos enfoques: funciones con Signals vs Single File Components (SFC).
    • Impactos: cambia Language Service, compilador, tooling, ciclos de vida y compatibilidad con Web Components.
    • Recomendación práctica: migrar a Standalone Components, adoptar Signals y desacoplar lógica.

    Introducción

    El término Nuevo Authoring Format (Signal Components) aparece cada vez más en las discusiones oficiales de Angular. En las primeras líneas: el Nuevo Authoring Format (Signal Components) propone cambiar cómo se escriben los componentes, pasando de clases y decoradores a formatos más funcionales—ya sea funciones con Signals o Single File Components tipo Vue/Svelte. Esto no es un capricho sintáctico: tiene consecuencias profundas en compilador, tooling y arquitectura de aplicaciones.

    Resumen rápido (lectores con prisa)

    Qué es: Un nuevo formato de autoría para componentes de Angular que prioriza la reactividad nativa (Signals) y alternativas sintácticas funcionales o SFC.

    Cuándo usarlo: Es una dirección en discusión; hoy, adopta Standalone Components y Signals para prepararte.

    Por qué importa: Afecta inferencia de tipos, tooling, rendimiento y compatibilidad con Web Components.

    Cómo funciona (alto nivel): Componentes como funciones crean Signals y devuelven representación; SFC agrupa lógica y template en un archivo.

    Nuevo Authoring Format (Signal Components): qué está en juego

    Angular lleva años evolucionando: Signals, Zoneless y Standalone Components han sido el preludio. El siguiente paso es replantear la autoría de componentes para que la reactividad sea nativa en la sintaxis, no un patrón insertado dentro de clases. El equipo mantiene discusiones públicas (ver RFCs y hilos) y la dirección es clara aunque no definitiva.

    ¿Por qué importa? Porque cambiar la forma de autoría impacta en:

    • Inferencia de tipos y Language Service.
    • Minificación, tree-shaking y rendimiento del bundle.
    • Ergonomía del desarrollador (DX) y curva de aprendizaje.
    • Compatibilidad con Web Components y herramientas del ecosistema.

    Referencias útiles:

    Dos enfoques en discusión

    El equipo de Angular evalúa principalmente dos caminos. Ninguno está finalizado; pueden mezclarse o descartar ambos.

    Funciones con Signals

    • El componente es una función que crea Signals y devuelve la representación (o templates vinculados).
    • Elimina el contexto this, favorece closures y composición.
    • Ejemplo conceptual:
    export const UserCard = component(() => {
      const name = signal('Ada');
      const greeting = computed(() => `Hola, ${name()}`);
      return html`<p>${greeting()}</p>`;
    });

    Beneficios: compresión mejor por bundlers, composición natural, menos errores por binding de contexto.

    Single File Components (SFC)

    • Un archivo con bloques <script> y <template> (estilo .vue/.svelte).
    • Separación visual lógica/template sin el decorador @Component.
    • Mejora la legibilidad y facilita la adopción por desarrolladores nuevos.
    • Ejemplo conceptual:
    <script>
      const count = signal(0);
      const double = computed(() => count() * 2);
    </script>
    
    <template>
      <button (click)="count.set(count() + 1)">{{ double() }}</button>
    </template>

    Beneficios: DX clara, flujo secuencial, herramientas de análisis más directas.

    Impactos técnicos que debes considerar

    Language Service y tooling

    • Sin decoradores opacos, el Language Service puede ofrecer autocompletado y detección de errores más precisos en templates.
    • Requiere cambios en el análisis estático y en la forma en que el compilador Ivy mapea lógica y vista.

    Ciclos de vida y hooks

    • ngOnInit y ngOnDestroy podrían migrar a hooks funcionales reutilizables (p. ej. onMount, onDestroy), que facilitan testing y composición.

    Compatibilidad con Web Components

    • Web Components exigen kebab-case en nombres (p. ej. my-element). Un formato sin selector obliga a definir reglas para exportación como custom elements o mecanismos automáticos de derivación de selectores.

    Retrocompatibilidad

    • Angular tenderá a mantener convivencia entre modelos: las clases seguirán funcionando durante varias versiones, y la migración se hará mediante schematics y herramientas.

    Qué hacer hoy (criterio práctico para Tech Leads)

    No bloquees desarrollo ni intentes replicar experimentalmente la sintaxis en producción. Haz esto en su lugar:

    1. Migra a Standalone Components. Es el requisito técnico más claro para cualquier nuevo formato.
    2. Adopta Signals en tus componentes actuales: signal, computed, effect. Esto reduce la brecha conceptual entre hoy y el nuevo formato.
    3. Extrae lógica de negocio a funciones puras o servicios. Mantén la presentación lo más delgada posible. Así la migración será principalmente sintáctica.
    4. Mejora la cobertura de tests (unit + E2E) en formularios y flujos críticos; los cambios en autoría pueden exponer fricciones en bindings y hooks.
    5. Establece convenciones de naming y scripts de codemods para renombrados masivos: facilita que un futuro schematic haga el trabajo fino.

    Conclusión

    El Nuevo Authoring Format (Signal Components) no es solo una reforma estética: es la pieza que permite a Angular consolidar Signals como primer ciudadano del framework. La opción final (funciones vs SFC) aún no está cerrada, pero la dirección estratégica es evidente: menos decoradores, mejor inferencia, DX más directa y componentes concebidos para la reactividad desde el primer token.

    Prepara tu base de código: adopta Standalone Components, usa Signals y desacopla la lógica. Cuando Angular estabilice la sintaxis, no querrás estar rehaciendo arquitectura; querrás ejecutar schematics y seguir adelante.

    FAQ

    ¿Qué es el Nuevo Authoring Format (Signal Components)?

    Es una propuesta para cambiar la forma de escribir componentes en Angular, priorizando la reactividad nativa mediante Signals y alternativas sintácticas como componentes basados en funciones o Single File Components.

    ¿Cuáles son los enfoques que se están evaluando?

    Principalmente dos: componentes como funciones que crean Signals y devuelven la representación, y Single File Components (SFC) con bloques separados de <script> y <template>, al estilo .vue/.svelte.

    ¿Por qué afecta al Language Service y al tooling?

    Porque remover decoradores opacos y usar patrones funcionales implica cambios en el análisis estático. El Language Service puede ofrecer autocompletado y detección de errores más precisos si el compilador y mapeos entre lógica y vista se ajustan a la nueva sintaxis.

    ¿Cómo debo preparar mi código hoy?

    Migra a Standalone Components, adopta Signals (signal, computed, effect), extrae lógica de negocio a funciones puras o servicios y mejora la cobertura de tests para formularios y flujos críticos.

    ¿Qué pasa con la compatibilidad con Web Components?

    Hay que definir reglas de exportación y naming: Web Components exigen kebab-case en nombres (p. ej. my-element), por lo que un formato sin selector debe ofrecer mecanismos automáticos o convenciones para generar selectores compatibles.

    ¿Se mantendrán las clases y decoradores actuales?

    Sí. Angular tenderá a mantener convivencia entre modelos durante varias versiones y facilitará migraciones mediante schematics y herramientas; las clases seguirán funcionando mientras se realiza la transición.

  • Mejora la inferencia de tipos en TypeScript con el operador satisfies

    Mejora la inferencia de tipos en TypeScript con el operador satisfies

    satisfies operator: el operador de TypeScript que no estás usando

    Tiempo estimado de lectura: 5 min

    • Valida sin perder inferencia: El operador satisfies valida que una expresión cumpla un tipo sin cambiar el tipo inferido del valor.
    • Mejora la DX en repos grandes: Conserva literales y autocompletado donde las anotaciones tradicionales causan upcast o las aserciones apagan la seguridad.
    • Casos de uso claros: Constants, routes, design tokens y mapeos estáticos se benefician más.
    • Complemento con as const: Úsalo junto a as const para validación más inmutabilidad absoluta.
    • No es para runtime: No reemplaza validación en tiempo de ejecución para datos dinámicos.

    Introducción

    satisfies operator: el operador de TypeScript que no estás usando es la frase que deberías leer en todos los PRs donde alguien fuerza tipos con as o sacrifica la inferencia de literales. Introducido en TypeScript 4.9, satisfies arregla —sin drama— un problema de tipado que hemos parcheado mal durante años.

    Resumen rápido (lectores con prisa)

    Qué es: Un operador de TypeScript que valida que un valor cumple un tipo sin cambiar la inferencia del valor.
    Cuándo usarlo: Para constantes exportadas, rutas, tokens de diseño y mapeos estáticos donde quieres conservar literales.
    Por qué importa: Mantiene autocompletado y seguridad de tipos en grandes bases de código.
    Cómo funciona: Verifica el contrato en tiempo de compilación y deja intacta la inferencia literal.

    ¿Qué hace exactamente el satisfies operator y por qué importa?

    El operador satisfies valida que una expresión cumpla con un tipo, pero no cambia el tipo inferido del valor. Es decir: verifica el contrato y deja intacta la inferencia literal del valor. Eso suena pequeño; en equipos con bases de código grandes es una diferencia estructural.

    Comparación con anotación y aserción

    • La anotación const x: T = ... valida pero hace upcast: pierde literales.
    • La aserción const x = ... as T fuerza sin validar: apaga la seguridad.
    • const x = ... satisfies T valida y conserva la inferencia.

    Ejemplo práctico: tema de colores que no deberías arruinar

    Sin satisfies, acabas escribiendo código que obliga al IDE a perder información útil:

    type Color = string | [number, number, number];
    
    const theme: Record = {
      primary: "blue",
      secondary: [255, 0, 0]
    };
    
    theme.primary.startsWith("b"); // Error: theme.primary es Color, no string literal
    

    Con satisfies:

    const theme = {
      primary: "blue",
      secondary: [255, 0, 0]
    } satisfies Record;
    
    theme.primary.startsWith("b"); // OK — TypeScript sabe que es string
    theme.secondary[0];            // OK — sabe que es number
    

    Validación sin amputación de tipos. Eso es todo.

    Casos de uso donde satisfies aporta valor real

    – Configuraciones públicas (constants.ts). Valores exportados y consumidos desde varios módulos se benefician de mantener literales.
    – Rutas y diccionarios (routing). keyof typeof ROUTES debe devolver claves concretas, no string.
    – Design tokens y paletas. Necesitas diferenciar hex strings de tuplas RGB sin perder método de string/array.
    – API clients estáticos o mapeos entre endpoints y tipos de respuesta.

    Ejemplo de rutas

    type RouteConfig = Record;
    
    const ROUTES = {
      home:      { path: "/",    protected: false },
      dashboard: { path: "/app", protected: true  },
    } satisfies RouteConfig;
    
    // keyof typeof ROUTES => "home" | "dashboard"
    

    Si hubieras usado : RouteConfig, perderías esas claves y con ellas, seguridad y autocompletado.

    Patrón avanzado: satisfies + as const

    Úsalos juntos cuando quieras validación + inmutabilidad absoluta:

    const ENDPOINTS = {
      users:   "/api/users",
      session: "/api/session",
    } satisfies Record as const;
    
    // ENDPOINTS.users es literal "/api/users" y readonly
    

    Esto es ideal para inyectar constantes que se comparten por toda la app sin riesgo de mutación accidental.

    Cuándo no usar satisfies

    No es una bala de plata. No lo apliques en cada tipo local ni donde el valor no necesite exportarse o conservar literales.

    • Definición y consumo inmediato en el mismo scope → anotación clásica puede ser más clara.
    • Datos dinámicos desde la red → valida en runtime (Zod, io-ts) y transforma antes de confiar en tipos.

    Recuerda: satisfies es para diseño estático y claridad, no para validar payloads de clientes externos en producción.

    Cómo adoptarlo en un repo sin romper nada

    1. Audit rápido: busca ficheros constants, theme, routes, tokens.
    2. Busca patrones problemáticos: as const seguido de as Type, o const X: Record<...> = {...}.
    3. Reemplaza por satisfies donde quieras conservar literales.
    4. Añade tests de tipo (tsd) para casos críticos y ejecuta tsc --noEmit en CI.
    5. Actualiza guía de estilo y explica el porqué en CONTRIBUTING.md.

    Comando grep útil:
    grep -R --line-number -E "as const.*as |: Record<|: {[A-Za-z0-9_]+: .*}" src/

    Impacto en equipo y mantenimiento

    Adoptar satisfies reduce errores sutiles: llamadas a funciones con claves incorrectas, fallos de autocompletado que llevan a as any, y la necesidad de escribir casts defensivos. A nivel de DX, mejora el autocompletado y la intención del código. A nivel de arquitectura, reduce deuda técnica silenciosa: menos as T, menos // @ts-ignore.

    Cierre práctico

    No es una moda; es una herramienta ergonométrica del tipo system. Si tu repo exporta constantes que consumen otros módulos, haz una pasada hoy mismo y reemplaza las anotaciones que hacen upcast por satisfies. Empieza por constants.ts, theme.ts, routes.ts. Verás menos PRs con as any y más código que documenta intención y comportamiento real.

    Implementarlo es simple. Ignorarlo es caro. Esto no acaba aquí: la próxima vez que veas un as en un PR, pregúntate si deberías usar satisfies en su lugar.

    Referencia oficial (anuncio): la sección relevante y la documentación general.

    FAQ

    ¿Qué hace exactamente satisfies?

    Valida que una expresión cumple con un tipo en tiempo de compilación sin cambiar la inferencia literal del valor.

    ¿Cuándo debo usarlo en lugar de una anotación de tipo?

    Cuando quieras validar un valor pero conservar literales y autocompletado, especialmente en constantes exportadas o mapeos estáticos.

    ¿Puede reemplazar validación en runtime?

    No. Para datos dinámicos desde la red sigue utilizando validadores en runtime (Zod, io-ts) y transforma antes de confiar en tipos estáticos.

    ¿Cómo se combina con as const?

    Úsalos juntos para validación estática más inmutabilidad absoluta; por ejemplo, ... satisfies Record<string,string> as const mantiene literales y readonly.

    ¿Afecta a keyof typeof?

    Sí. Usar satisfies en mapeos permite que keyof typeof infiera claves concretas en lugar de string.

    ¿Romperá código existente si lo introduzco en un repo grande?

    En general no; es una herramienta de seguridad estática. Haz una migración por áreas (constants, theme, routes) y añade pruebas de tipo en CI.

    ¿Dónde empiezo una auditoría?

    Busca ficheros constants, theme, routes, tokens y patrones como as const seguido de as Type o : Record.
  • Implementación del Model Context Protocol en Angular 21

    Implementación del Model Context Protocol en Angular 21

    Hacer post sobre los MCPs de angular 21

    Tiempo estimado de lectura: 6 min

    • MCP estandariza cómo los agentes de IA leen, razonan y proponen cambios en repositorios Angular 21.
    • Orden de inspectores (list_projects → get_best_practices → search_documentation → find_examples → onpush_zoneless_migration) minimiza cambios destructivos.
    • MCP exige trazabilidad: cada recomendación debe incluir referencia a la documentación oficial y reglas del proyecto.
    • Integración práctica: CI/pipelines y IDEs deben ejecutar inspectores antes de ofrecer o aplicar cambios.
    • Riesgos reales: reduce alucinaciones pero no elimina límites del análisis estático; requiere revisión humana.

    Introducción

    Hacer post sobre los MCPs de angular 21 empieza por entender que no hablamos de una feature menor: hablamos de cómo los agentes de IA leerán, razonan y propondrán cambios en tu código sin romper la arquitectura. En Angular 21, donde Signals, Standalone Components y la migración zoneless son la norma, el Model Context Protocol (MCP) pasa de ser un extra a una herramienta de gobernanza técnica indispensable.

    Resumen rápido (lectores con prisa)

    Qué es: Un protocolo que estandariza cómo los modelos de lenguaje interactúan con repositorios para descubrir topología, consultar documentación y aplicar reglas antes de sugerir cambios.

    Cuándo usarlo: En monorepos, pipelines automatizados y entornos donde agentes de IA pueden proponer cambios en código base (migraciones, refactors, PRs).

    Por qué importa: Evita propuestas que compilan pero introducen deuda técnica; garantiza trazabilidad y reglas específicas por versión.

    Cómo funciona (resumen): Ejecuta inspectores en orden (list_projects → get_best_practices → search_documentation → find_examples → onpush_zoneless_migration) y adjunta referencias oficiales a cada recomendación.

    Por qué importa ahora

    Angular 21 consolida patrones que rompen supuestos antiguos: la inyección por constructor deja paso a inject(), la reactividad local se orienta a Signals y zone.js tiende a desaparecer. Un LLM no contextualizado propondrá soluciones que compilan pero introducen deuda técnica. El MCP asegura que el agente primero lea la topología y las reglas del proyecto y luego proponga —no al revés.

    Componentes del flujo MCP en Angular 21

    • Descubrimiento de topología: el agente usa Nx para mapear aplicaciones, librerías y dependencias mediante list_projects.
    • Inyección de prácticas por versión: get_best_practices detecta la versión y aplica reglas (p. ej. evitar NgModules cuando no proceden).
    • Validación documental en tiempo real: search_documentation consulta angular.dev antes de afirmar APIs o signaturas.
    • Ejemplos concretos y actualizados: find_examples recupera implementaciones modernas (Signals, inject(), formularios reactivos).
    • Coaching contextual: ai_tutor ajusta el nivel técnico según el rol del usuario.
    • Auditoría zoneless: onpush_zoneless_migration analiza compatibilidad al eliminar zone.js.

    Ejemplo práctico (flujo mínimo aplicable)

    1) Ejecuta list_projects: el agente devuelve el mapa del monorepo con apps y librerías.

    2) Ejecuta get_best_practices: se cargan reglas específicas para Angular 21 (uso de Signals, restricciones sobre RxJS local).

    3) Pide search_documentation para la API concreta (p. ej. Signals API).

    4) Solicita find_examples para ver implementaciones validadas.

    5) Si el objetivo es migrar, corre onpush_zoneless_migration y compila la lista de refactorizaciones.

    Este orden evita que el agente proponga cambios destructivos en zonas equivocadas del repo.

    Casos de uso concretos y recomendaciones

    • Revisiones automáticas de PR: integra MCP en pipelines de CI para que el agente haga una pre-auditoría. Usa n8n o tu runner de CI para ejecutar los inspectores en cada PR.
    • Onboarding y documentación viva: ai_tutor puede generar guías de cambios y checklist de migración adaptados al repositorio. Útil para equipos nuevos que deben adoptar Signals y patrones zoneless.
    • Auditorías zoneless: no confíes en una única ejecución. Los detectores estáticos identifican patrones vulnerables (suscripciones sin limpieza, efectos colaterales) pero requieren revisión humana en casos límite.

    Criterio técnico que debes aplicar

    • Nunca concedas permisos de escritura antes de ejecutar list_projects y get_best_practices.
    • Exige trazabilidad: cada recomendación debe venir con la referencia a la documentación oficial (angular.dev).
    • Valida migraciones zoneless con pruebas E2E y monitoreo en staging; los cambios en detección de cambios pueden aparecer solo en escenarios complejos.
    • Mantén una “zona de seguridad” en la que la IA puede proponer cambios no destructivos (documentación, tests, refactorizaciones no críticas) y otra donde solo humanos aprueban (cambios en librerías compartidas, infraestructuras críticas).

    Integración con herramientas (práctico)

    • IDEs con IA: plug-ins que implementen MCP deben ejecutar inspectores antes de ofrecer snippets.
    • Automatización: orquesta inspectores con n8n para que cada PR dispare una auditoría RAG (Read-Only).
    • Registro y auditoría: guarda outputs de inspectores (mapa de topología, reglas aplicadas, fragmentos de doc) junto al PR para trazabilidad histórica.

    Riesgos y límites reales

    MCP reduce alucinaciones, no las elimina totalmente. Hay límites del análisis estático: efectos en tiempo de ejecución, race conditions y casos complejos de detección de cambios pueden escapar. Además, dar permisos de escritura sin límites en monorepos empresariales sigue siendo una superficie de riesgo alta.

    Conclusión (lo que ganas)

    Hacer post sobre los MCPs de angular 21 no es solo hablar de una integración técnica; es establecer un contrato de confianza entre IA y equipo. Con MCP, los agentes dejan de ser generadores indiscriminados y pasan a ser asistentes que conocen tu repo, tus reglas y tus límites. Implementados con disciplina (orden de inspectores, trazabilidad y revisión humana), los MCPs reducen deuda técnica, aceleran migraciones y convierten la IA en parte fiable del flujo de desarrollo.

    Dominicode Labs

    Para equipos que orquestan agentes y pipelines, una referencia práctica de investigación y experimentación es Dominicode Labs. Integrar MCP con flujos de trabajo y pruebas reproducibles ayuda a mantener trazabilidad y mejorar la adopción de prácticas zoneless.

    FAQ

    ¿Qué es exactamente un MCP?

    Un Model Context Protocol (MCP) es un conjunto de inspectores y flujos estandarizados que permiten a modelos de lenguaje interactuar con un repositorio de forma gobernada: descubrir topología, cargar reglas de práctica por versión y validar documentación antes de generar cambios.

    ¿Cuándo debo ejecutar inspectores en mi flujo?

    Siempre antes de conceder permisos de escritura a un agente: al menos ejecutar list_projects y get_best_practices. Para migraciones, añadir search_documentation, find_examples y onpush_zoneless_migration.

    ¿Cómo garantiza el MCP que no se rompa la arquitectura?

    No lo garantiza por completo, pero reduce riesgos al exigir que el agente conozca la topología y las reglas específicas del proyecto antes de proponer cambios. Además obliga a adjuntar referencias y un plan de refactorización verificable.

    ¿Qué referencias documentales se deben adjuntar a las recomendaciones?

    Las referencias deben ser enlaces a la documentación oficial pertinente en angular.dev (por ejemplo la Signals API) y, cuando corresponda, recursos técnicos como repositorios oficiales (p. ej. zone.js).

    ¿Puede un MCP eliminar la necesidad de revisión humana?

    No. MCP reduce alucinaciones y añade trazabilidad, pero las decisiones críticas (cambios en librerías compartidas, infraestructuras) deben seguir pasando por revisión humana.

    ¿Cómo integrar MCP en CI con n8n?

    Orquesta los inspectores como pasos en la pipeline: cada PR dispara un flujo de RAG (Read-Only) en el que list_projects y get_best_practices se ejecutan primero, seguidos por validaciones documentales y generación de un reporte adjunto al PR. Una opción práctica es usar n8n para encadenar esos inspectores.

    ¿Qué precauciones tomar en migraciones zoneless?

    Validar compatibilidad con onpush_zoneless_migration, ejecutar pruebas E2E en staging y monitorizar cambios en detección de cambios. No confiar exclusivamente en análisis estático: casos de race conditions y efectos en tiempo de ejecución requieren supervisión humana.

  • Cómo migrar a Temporal en TypeScript 6.0 y evitar problemas con fechas

    Cómo migrar a Temporal en TypeScript 6.0 y evitar problemas con fechas

    ¿Cansado de que las fechas te arruinen la noche? Bienvenido a Temporal

    Tiempo estimado de lectura: 6 min

    • Temporal reemplaza a Date para lógica de negocio: menos bugs y contratos más claros.
    • Almacena Instants en DB (ISO UTC canonical) y convierte a zonas locales solo en UI.
    • Polyfill hoy, tipos con TypeScript 6.0: feature-detect y carga condicional.
    • Migración por capas y tests: evita PR monstruo, prioriza fronteras y añade reglas ESLint.

    Introducción

    Voy directo: Temporal no es una nueva API bonita para mirar. Es el fin del “setDate + pray”. Si adoptas esto con criterio, reduces bugs, clarificas contratos y dejas de escribir parsers para timestamps por toda la app. Te explico cómo hacerlo sin romper producción.

    Resumen rápido (lectores con prisa)

    Temporal es la API moderna para manejar tiempo en JS. Usa Instant para puntos absolutos (DB/logs), ZonedDateTime para eventos con zona y PlainDate para fechas sin hora. TypeScript 6.0 trae tipos; usa el polyfill donde el runtime no lo implemente.

    Configuración y polyfill

    TypeScript 6.0 trae los tipos de Temporal, pero el runtime puede no implementarlo aún en todos los motores.

    Para usarlo hoy en Node o navegadores, instala el polyfill oficial:

    npm install @js-temporal/polyfill

    En el punto de entrada (server o client) haz feature-detect y carga el polyfill solo si hace falta:

    if (!globalThis.Temporal) {
      await import('@js-temporal/polyfill');
    }

    Asegura tsconfig:

    {
      "compilerOptions": {
        "target": "esnext",
        "lib": ["esnext"],
        "strict": true
      }
    }

    Qué cambia en tu arquitectura (reglas rápidas)

    • No más new Date() para lógica de negocio.
    • Almacena timestamps en DB como Instant (ISO) — UTC canonical.
    • Muestra/convierte a zonas locales con ZonedDateTime solo donde importe (UI, emails, calendarios).
    • En tests y serialización: convierte a strings para persistir; rehidrata con Temporal.from() al cargar.

    Casos prácticos y patrones — copia, pega y aplícalo

    1) Obtener “ahora” correctamente

    // Instant (UTC preciso, para auditoría/logs)
    const nowInstant = Temporal.Now.instant();
    
    // ZonedDateTime (evento con zona)
    const nowZoned = Temporal.Now.zonedDateTimeISO();
    
    // PlainDate (cumpleaños, sin hora ni zona)
    const today = Temporal.Now.plainDateISO();

    2) Sumar y restar sin mutar

    const hoy = Temporal.Now.plainDateISO();
    const dentroDe7Dias = hoy.add({ days: 7 }); // creado nuevo, hoy no cambia

    3) Guardar en BD (mejor práctica)

    • Guarda Instants como strings: instant.toString() → “2024-05-01T12:00:00Z”.
    • Razonamiento: Instant es un punto absoluto; si reconstituyes en otro país, sigues teniendo el mismo momento.
    const timestamp = Temporal.Now.instant().toString();
    // INSERT INTO events (created_at) VALUES (timestamp);

    Recuperación:

    const instantFromDb = Temporal.Instant.from(dbValue);
    const zonedInMadrid = instantFromDb.toZonedDateTimeISO('Europe/Madrid');

    4) Mostrar en UI (zona del usuario)

    const instant = Temporal.Instant.from(event.created_at);
    const inUserTZ = instant.toZonedDateTimeISO(user.timeZone);
    const formatted = inUserTZ.toLocaleString('es-ES', { dateStyle: 'medium', timeStyle: 'short' });

    5) Schedules y DST (sin manualidades)

    const vuelo = Temporal.ZonedDateTime.from('2024-10-15T14:30:00+09:00[Asia/Tokyo]');
    const llegadaMadrid = vuelo.withTimeZone('Europe/Madrid');
    // Temporal aplica reglas de DST correctamente.

    Migración práctica y estrategia (no pegues un PR monstruo)

    Plan corto y efectivo:

    1. Detecta usos

    • grep/rg por new Date(, Date.now(), .toISOString(), getUTC*, setUTC*.
    • Haz una lista priorizada: endpoints, parsers de JSON, jobs, filas de cola.

    2. Protege el repo

    Añade rule de ESLint que prohíba new Date en código nuevo:

    // .eslintrc.json
    "rules": {
      "no-restricted-syntax": [
        "error",
        {
          "selector": "NewExpression[callee.name='Date']",
          "message": "Usa Temporal en lugar de Date"
        }
      ]
    }

    3. Cambia en capas

    • Fronteras primero: parsers de requests, handlers, webhooks.
    • Library layer: utilidades de fecha centralizadas.
    • UI: formateadores y locales.

    4. Persistencia y API contract

    • Define y documenta: “todos los timestamps en la DB son Instant ISO strings”.
    • Añade validaciones en los endpoints que aceptan timestamps (Zod / Zod schemas o runtime checks).

    5. Tests y CI

    • En jest/mocha, añade el polyfill en setupTests: import '@js-temporal/polyfill';
    • Añade pruebas para zona horaria, DST y serialización.

    Peculiaridades y errores que verás (y cómo arreglarlos)

    • Serialización: Temporal types no siempre serializan como esperas en JSON.stringify. Convierte a string explícitamente.
    • Redux / Hydration: almacena ISO strings en store si necesitas serialización. Temporal objects son inmutables pero no pensados para serializar automáticamente.
    • Comparaciones: usa Temporal.PlainDate.compare o Instant.avoid numeric timezone math.
    • Interoperabilidad con libs: elimina gradualmente date-fns/moment; si dependes de ellas, mantén adaptadores hasta reemplazar lógica.

    Decisiones de diseño: Instant vs ZonedDateTime vs PlainDate

    • Instant → logs, audit, DB primary timestamp.
    • ZonedDateTime → eventos programados que dependen de la hora local.
    • PlainDate → cumpleaños, fechainterna sin hora.
    • Duration → expiraciones, TTLs, duraciones humanas.

    Ejemplo real: reserva de vuelo (correcto)

    • Guardar en DB: Vuelo.departureInstant (Instant)
    • Guardar metadatos: Vuelo.departureTZ = 'Asia/Tokyo'
    • Mostrar en UI: instant.toZonedDateTimeISO(tz).toLocaleString(...)

    Performance y bundle

    El polyfill tiene coste. Si tu app es frontend, lazy-load the polyfill: solo usuarios que lo necesiten (browsers sin Temporal nativo) lo cargarán.

    En server (Node), añade polyfill en arranque. No suele ser un problema de perf si lo colocas correctamente.

    Checklist rápido antes de mergear cambios de fecha

    • Todos los endpoints aceptan/retornan ISO Instant cuando corresponde.
    • Tests cubren conversiones entre zonas y casos límites (fin de mes, cambio DST).
    • No quedan new Date() en la lógica de negocio.
    • Documentación para frontend/backend: cómo serializar y rehidratar.

    Metáfora breve: por qué vale la pena

    Date era un martillo con un tornillo. Temporal es el juego de herramientas correcto para el tiempo. No es más trabajo: es menos debugging.

    Cierre (CTA claro)

    ¿Quieres el script que escanea tu repo, lista todas las ocurrencias de Date y genera un plan de migración automático por prioridad? Respóndeme con “MIGRAR FECHAS” y te lo entrego: branch de prueba, report con 100 entradas ordenadas, y PR template para cada cambio.

    Esto no acaba aquí. La siguiente nota: cómo reescribir utilidades de date-fns a Temporal con transformaciones seguras y codemods semi-automáticos. ¿Lo hacemos?

    Si te interesa integrar estas prácticas con flujos de trabajo y automatización de repos, revisa Dominicode Labs para plantillas y herramientas que aceleran migraciones y codemods.

    FAQ

    ¿Por qué usar Temporal en lugar de Date?

    Temporal proporciona tipos específicos (Instant, ZonedDateTime, PlainDate) que aclaran contratos y evitan errores comunes asociados a zonas y mutabilidad de Date.

    ¿Debo cambiar todo el código de golpe?

    No. La estrategia recomendada es migrar por capas: fronteras primero (parsers/handlers), luego librerías internas y finalmente UI. Evita PRs monolíticos.

    Cómo debo almacenar timestamps en la base de datos?

    Almacena Instants como ISO strings en UTC (ej. “2024-05-01T12:00:00Z”). Reconstituye con Temporal.Instant.from() al leer.

    Qué pasa con la serialización y Redux?

    Temporal objects no siempre serializan con JSON.stringify. Guarda ISO strings en el store si necesitas serializar/hidratar.

    Necesito un polyfill en producción?

    Sí si tus entornos (navegadores/Node) no implementan Temporal nativamente. Usa feature-detect y lazy-load en frontend; carga en arranque en server.

    Cómo asegurar que no queden Date en el repo?

    Usa búsquedas (grep/rg) y añade una regla ESLint que prohíba new Date. Prioriza endpoints y parsers para corregir primero las fronteras.

  • Migración a Signal Forms en Angular 22: Mejorando formularios reactivos

    Migración a Signal Forms en Angular 22: Mejorando formularios reactivos

    Signal Forms estable: el nuevo estándar de formularios en Angular 22

    Tiempo estimado de lectura: 4 min

    • Signal Forms reemplaza Observables por Signals nativos para exponer valor, validez y estados de control.
    • Mejora ergonomía y rendimiento al evitar suscripciones manuales y emitir reactividad síncrona y dirigida.
    • Encaja con una arquitectura Zoneless para re-rendering quirúrgico y menor sobrecarga en formularios complejos.
    • Migración pragmática: usar toSignal() y desacoplar validadores reduce el coste de adopción.

    Introducción

    Signal Forms estable aparece como la evolución natural que consolida el manejo de formularios basado en Signals en Angular 22. Signal Forms estable reemplaza la dependencia de RxJS en la capa de interfaz por un modelo de reactividad síncrono y explícito, alineado con la arquitectura Zoneless y Signals centrales del framework.

    La propuesta no es solo sintaxis; es un cambio de ergonomía y rendimiento. Aquí explico qué cambia, por qué importa y cómo preparar una migración pragmática en proyectos reales.

    Resumen rápido (lectores con prisa)

    Signal Forms expone estado de formularios como Signals nativos en lugar de Observables. Use Signals para leer estado y computed() para derivar valores síncronos. Migración pragmática: convertir valueChanges a Signals con toSignal() y desacoplar validadores.

    ¿Qué es Signal Forms estable y qué problema resuelve?

    Signal Forms estable expone el estado del formulario —valor, validez, touched/dirty— como Signals nativos en lugar de streams Observables. Los problemas que resuelve de forma directa:

    • Evita gestión manual de suscripciones (memory leaks).
    • Elimina desfases causados por emisiones asíncronas en validaciones cruzadas.
    • Encaja de forma nativa con una arquitectura Zoneless, donde Signals notifican de forma quirúrgica qué partes del DOM actualizar.

    Fuente y discusión activa sobre el diseño: discusiones en GitHub. Para contexto sobre Signals y reactividad en Angular: guía de Signals en Angular.

    Cambio conceptual: de observar eventos a leer estado derivado

    Con ReactiveFormsModule hoy suelen usarse propiedades como valueChanges o statusChanges (Observables). Signal Forms cambia el patrón: en lugar de suscribirte, lees un Signal o creas computed() que derive estados complejos.

    Comparativa rápida:

    // ReactiveForms (actual)
    readonly isValid$ = this.form.statusChanges.pipe(
      map(s => s === 'VALID'),
      distinctUntilChanged()
    );
    
    // Signal Forms (conceptual)
    readonly isValid = computed(() => this.form.status() === 'VALID');
    

    computed() es síncrono, no requiere teardown manual y se integra con el grafo de dependencias para reevaluar solo cuando los valores relevantes cambian.

    Validaciones cruzadas y tracking de dependencias

    Las validaciones cruzadas son donde RxJS más fricción introduce: operadores para evitar bucles, distinct checks, y micro-delays. Con Signals, el runtime realiza dependency tracking: si una validación depende de A y B, se reevaluará solo cuando A o B cambien, sin operadores adicionales ni emisiones redundantes.

    Esto reduce tanto complejidad como carga en el hilo principal en formularios con muchos campos interrelacionados (CRMs, ERPs).

    Integración con Zoneless y pipeline de rendimiento

    La llegada de Signal Forms completa el modelo Zoneless (ver: guía Zoneless). En una app Zoneless:

    • Cada actualización de control escribe en un Signal.
    • Angular identifica qué templates dependen de ese Signal.
    • Solo esos nodos se re-renderizan.

    Resultado: menos trabajo innecesario en eventos de alta frecuencia y trazas de depuración limpias (sin contaminación por Zone.js). Para contexto sobre migraciones Zoneless y Signals, consulta: guía de Signals en Angular.

    Ejemplo práctico de interoperabilidad temporal

    Antes de la estabilización completa, la forma pragmática de probar el patrón es convertir valueChanges a Signal con toSignal (rxjs-interop).

    import { toSignal } from '@angular/core/rxjs-interop';
    
    this.formValue = toSignal(this.form.valueChanges, { initialValue: this.form.value });
    
    computed(() => {
      const value = this.formValue();
      // derivaciones y validaciones sincronas aquí
    });
    

    Esto ofrece un puente entre ReactiveForms y lo que Signal Forms hará nativo.

    Guía de rxjs-interop: guía de rxjs-interop.

    Estado de la API y compatibilidad futura

    La API está en RFC y discusión, y lo más probable es que conviva con ReactiveFormsModule durante varias versiones para minimizar rupturas. El equipo de Angular apunta a compatibilidad con validadores existentes y a proporcionar herramientas de migración (schematics) en el momento del lanzamiento estable.

    Sigue las discusiones oficiales: discusiones en GitHub.

    Estrategia práctica para Tech Leads y equipos

    No reescribas todo hoy. Sí aplica estas medidas para reducir el coste de migración:

    • Desacopla la lógica de validación y transformación del FormGroup. Mantén funciones puras o servicios para reglas de negocio.
    • Introduce toSignal() donde tenga sentido para que los templates y la lógica consuman estado de forma síncrona.
    • Establece ChangeDetectionStrategy.OnPush en componentes nuevos para asegurar un modelo de render predecible.
    • Automatiza pruebas E2E que cubran flujos de validación cruzada y efectos secundarios.
    • Reserva una fase de migración por componentes: empezar por formularios sencillos y luego los complejos.

    Checklist mínimo antes de activar Signal Forms en producción

    • Lógica desacoplada (validadores fuera del FormGroup).
    • Cobertura de pruebas para validación y envíos.
    • Observabilidad en producción para comparar métricas (TTI, LCP).

    Conclusión: por qué importa para tu arquitectura

    Signal Forms estable no es solo una API nueva: es la culminación de la transición de Angular hacia reactividad explícita y rendimiento previsiblemente escalable. Para proyectos a largo plazo, representa menos boilerplate, menor riesgo de fugas de memoria y una integración natural con la estrategia Zoneless.

    Prepara tu código hoy —desacopla, prueba y adopta interoperabilidad con toSignal— y tu equipo tendrá una migración suave cuando Angular 22 estabilice Signal Forms. La mejora no será estética: será tangible en rendimiento y mantenibilidad.

    Fuentes y recursos

    FAQ

    Respuesta: ¿Qué es Signal Forms?

    Signal Forms expone el estado del formulario —valor, validez, touched/dirty— como Signals nativos en lugar de Observables, permitiendo lecturas síncronas y derivaciones con computed().

    Respuesta: ¿Cuándo debería considerar migrar a Signal Forms?

    Considéralo cuando busques reducir suscripciones manuales, eliminar desfases en validaciones cruzadas, o al adoptar una arquitectura Zoneless para mejoras de rendimiento predecible.

    Respuesta: ¿Cómo afecta Signal Forms a las validaciones cruzadas?

    El runtime de Signals realiza dependency tracking, por lo que las validaciones que dependen de múltiples campos solo se reevaluarán cuando cambien esas dependencias, evitando emisiones redundantes y operadores adicionales de RxJS.

    Respuesta: ¿Puedo mezclar ReactiveForms y Signal Forms?

    Sí. Antes de la estabilización completa, una estrategia pragmática es convertir valueChanges a Signal con toSignal() (rxjs-interop) para interoperabilidad temporal.

    Respuesta: ¿Qué beneficios de rendimiento puedo esperar?

    Menos re-renderings innecesarios, menos trabajo en eventos de alta frecuencia y menor riesgo de fugas por manejo de suscripciones. La integración Zoneless permite re-rendering quirúrgico de los nodos que dependen de un Signal.

    Respuesta: ¿Qué precauciones antes de activar en producción?

    Asegura lógica desacoplada (validadores fuera del FormGroup), cobertura de pruebas para validación y envíos, y observabilidad en producción para comparar métricas (TTI, LCP).

  • Cómo migrar tu código a TypeScript 6.0 con `strict` activado

    Cómo migrar tu código a TypeScript 6.0 con `strict` activado

    ¿Tu repo se va a romper cuando actualices a TypeScript 6.0? Probablemente sí. Si no, felicidades: estás en el 10% que ya hacía las cosas bien.

    Tiempo estimado de lectura: 7 min

    • Strict: true en TS 6.0 es un cambio por defecto que destapará muchos errores latentes.
    • Activa banderas gradualmente (noImplicitThis, useUnknownInCatchVariables, noImplicitAny, etc.) y haz PRs pequeños.
    • Plan de migración de 6 semanas práctico y enfocado para repos medianos.
    • Herramientas y atajos (skipLibCheck, unknown vs any, validadores) para acelerar sin sacrificar seguridad.

    Introducción

    Poca gente habla de esto en voz alta: “strict: true por defecto en TS 6.0” no es una actualización menor. Es un detector de mierda técnica. Es un escáner que va a mostrar lo que has estado ignorando con // TODO: arreglar después.

    Esto no es moralina. Es ingeniería. Y sí: te voy a dar el plan para sobrevivir sin incendiar el CI.

    Resumen rápido (lectores con prisa)

    Strict: true en TS 6.0 activa varias banderas que convierten advertencias silenciosas en errores compilables. Migrar requiere activar banderas por fases, priorizar endpoints/serializadores y usar atajos como skipLibCheck y unknown para avanzar sin bloquear el desarrollo.

    Primero, lo obvio (pero que nadie quiere admitir)

    • Si ya tienes “strict”: true: no cambia nada.
    • Si no lo tenías, al actualizar a TS 6.0 el compilador se pondrá estricto y te lloverán errores.
    • Puedes parchear rápido con “strict”: false en tsconfig, pero eso es anestesia. No arregla la enfermedad.

    strict no es una única regla. Es un paraguas que enciende varias banderas. Cada una toca piezas distintas del código y cada una duele distinto.

    Piensa en strict como un filtro de rayos X: te muestra huesos rotos que antes eran invisibles.

    Las banderas, qué hacen y por qué importan (con ejemplos reales)

    1) noImplicitAny — El fin de los `any` silenciosos

    Qué hace: hace error todo lugar donde TS no pueda inferir el tipo y quedaría any.

    Problema típico:

    const fn = (payload) => { return payload.name; }

    Antes: compila. Después: error.

    Arreglo:

    const fn = (payload: { name: string }) => payload.name;

    Por qué importa: porque any es una mentira que llega a producción sin avisar.

    2) strictNullChecks — La regla que evita el “Cannot read properties of undefined”

    Qué hace: separa null y undefined de otros tipos.

    Problema típico:

    function greet(name: string) { return name.toLowerCase(); }
    const maybeName: string | null = getName();
    greet(maybeName); // Boom si es null

    Arreglo:

    function greet(name: string) { return name.toLowerCase(); }
    const maybeName = getName();
    if (maybeName) greet(maybeName);

    O escribe la firma:

    function greet(name: string | null) { if (!name) return; return name.toLowerCase(); }

    Por qué importa: obliga a pensar en ausencia de datos. Nada peor que toLowerCase() explotando en prod.

    3) strictFunctionTypes — Contravarianza correcta

    Qué hace: comprueba que los tipos de parámetros en funciones sean seguros al asignarlas.

    Ejemplo peligroso:

    type Handler = (e: Event) => void;
    function specificHandler(e: MouseEvent) { /* usa e.clientX */ }
    const h: Handler = specificHandler; // antes ok, ahora error

    Arreglo: Alinea firmas o usa overloads. No metas handlers con supuestos concretos donde se espera generalidad.

    4) strictBindCallApply — Validación de bind/call/apply

    Qué hace: valida que los argumentos pasados con call/apply/bind casen con la firma original.

    Caso:

    function sum(a: number, b: number) { return a + b; }
    sum.call(null, "str", 3); // antes pasaba, ahora no

    Por qué importa: evita bugs raros cuando se manipula this y argumentos dinámicos.

    5) strictPropertyInitialization — Clases más predecibles

    Qué hace: exige inicializar propiedades o declararlas como posiblemente undefined.

    Antes:

    class Foo { value: number; constructor() {} }
    const f = new Foo(); console.log(f.value); // undefined

    Con la bandera activada: error en compilación. Arreglo:

    class Foo { value: number = 0; }

    O si la inicialización viene después:

    class Foo { value!: number } // use with care
    o
    class Foo { value?: number }

    6) noImplicitThis — Control del “this”

    Qué hace: si TS no sabe qué es this en una función, da error.

    Ejemplo:

    const obj = {
      x: 1,
      getX() { return this.x; }
    }
    const f = obj.getX;
    f(); // this es global — error ahora

    Arreglo: tipa this en la función:

    getX(this: { x: number }) { return this.x; }

    7) useUnknownInCatchVariables — catch seguro

    Qué hace: cambia catch (e) de any a unknown.

    Ejemplo:

    try { ... } catch (e) { console.log(e.message); } // ahora error

    Arreglo:

    try { ... } catch (e: unknown) {
      if (e instanceof Error) console.log(e.message);
    }

    Esto fuerza buenos patrones de manejo de errores y evita suposiciones peligrosas.

    Estrategia práctica: cómo migrar sin suicidarte

    No intentes arreglar 5.000 errores en un PR. Eso no es noble; es destructivo.

    Plan de 6 semanas (realista para repos medianos)

    Semana 0 — Preparación

    • Crea branch “ts-migration/strict”.
    • Añade CI que compile en strict y que no bloquee master todavía.
    • Si la migración debe ser suave, temporalmente coloca “strict”: false para que CI siga pasando mientras trabajas.

    Semana 1 — Protección inmediata

    • Activa noImplicitThis y useUnknownInCatchVariables.
    • Razon: cambios pequeños, refactors simples, impacto bajo.
    • Haz PRs pequeños. Revisa con atención en code owners.

    Semana 2 — Sanear `any`

    • Activa noImplicitAny.
    • Prioriza endpoints y parsers: handlers de API, converters, deserializadores.
    • Empieza a reemplazar any por tipos concretos o unknown + validación.

    Semana 3-4 — Funciones y propiedades

    • Activa strictFunctionTypes.
    • Refactoriza callbacks y firmas exportadas.
    • Activa strictPropertyInitialization y arregla inicializaciones en clases públicas.

    Semana 5 — El gran salto

    • Activa strictNullChecks.
    • Este es el más complicado. Reescribe funciones que asumían no-null implícito.
    • Amplía tests unitarios. Añade pruebas para casos nulos/undefined.

    Semana 6 — Finalización

    • Ejecuta npx tsc --noEmit en todo el repo.
    • Revisa errores residuales; limpia // @ts-ignore que se usó como parche.
    • Mueve strict a true en tsconfig principal y mergea.

    Comandos útiles que vas a repetir hasta odiarlos

    • Inicializar config: npx tsc --init
    • Compilar sin emitir: npx tsc --noEmit
    • Compilar un proyecto con build mode: npx tsc -b
    • Detectar errores en workspace monorepo: npx lerna exec -- npx tsc --noEmit (o tu equivalente)

    Trucos y atajos para acelerar la migración

    • Usa skipLibCheck: true mientras migras para no pelear con tipos de dependencias.
    • Introduce types o index.d.ts temporales sólo para las partes que bloquean.
    • Prefiere unknown a any y escribe validadores pequeños si es necesario.
    • Añade eslint rule para prohibir // @ts-ignore excepto con razón y ticket.

    Checklist mínimo antes de mergear a master

    • CI compila con strict: true.
    • Tests cubren entradas nulas y errores.
    • No any sin ticket que lo justifique.
    • Documentación de breaking changes en PR.
    • Plan de rollback (tag + branch) por si algo explota en prod.

    Errores comunes que vas a ver (y cómo solucionarlos rápido)

    • "Object is possibly 'null'." → Optional chaining o comprobación previa.
    • "Parameter implicitly has an 'any' type." → Añade tipo explícito o infiere con generics.
    • "Property has no initializer and is not definitely assigned in the constructor." → Inicializa o marca como optional.

    Metáfora corta: ¿por qué todo esto vale la pena?

    Poner strict por defecto es como obligar a instalar cinturón de seguridad en todos los coches nuevos. Al principio fastidia, pero salva vidas y reduce reclamaciones. Te obliga a pensar, a documentar y a dejar menos trampas para producción.

    Argumentos para convencer a la gerencia

    • Menos bugs en producción = menos on-call nocturno.
    • Código más legible = onboarding más rápido.
    • Mejores contratos = menos regresiones en refactors.

    Cuando NO migrar ahora

    • Repos monolíticos gigantes sin tests.
    • Sistemas que requieren compatibilidad inmediata con CommonJS profundo.
    • Migraciones sincronizadas de múltiples equipos donde el riesgo de romper pipelines es alto.

    Si caes en estos casos, planifica la migración, no la improvises.

    Cierre agresivo pero útil

    No es una moda. Es una obligación técnica. Si tu equipo no puede permitirse el tiempo de migrar, al menos que lo documente y tenga roadmap. No dejes el problema para “cuando tengamos tiempo” — ese cuando nunca llega.

    ¿Quieres una ayuda real y directa?

    Respóndeme con “MIGRAR MI REPO” y te envío:

    • Un script que crea el branch de migración,
    • Activa strict en un tsconfig de prueba,
    • Ejecuta npx tsc --noEmit,
    • Genera un reporte con los 100 errores más comunes ordenados por archivo y tipo.

    Esto no acaba aquí. Si lo ejecutas, empezaremos la siguiente nota: cómo redactar PRs pequeños para migraciones de types (plantillas incluidas). ¿Lo quieres?

    FAQ

    ¿Qué es “strict: true” en TypeScript 6.0?

    Es una configuración por defecto que habilita un conjunto de banderas (noImplicitAny, strictNullChecks, strictFunctionTypes, etc.) que convierten inferencias inseguras en errores de compilación.

    ¿Por qué voy a ver tantos errores al actualizar?

    Porque el compilador ahora detecta patrones que antes pasaban silenciosamente (usos implícitos de any, accesos a null, firmas incompatibles, etc.). Son errores latentes que la nueva configuración expone.

    ¿Puedo parchear temporalmente la migración?

    Sí: puedes mantener "strict": false en el tsconfig principal mientras trabajas en un branch de migración y añadir CI que compile en modo estricto sin bloquear master.

    ¿Cuál es la bandera que menos impacto tiene al principio?

    Activar noImplicitThis y useUnknownInCatchVariables suele tener impacto bajo y es buena para comenzar la limpieza sin grandes refactors.

    ¿UseUnknownInCatchVariables romperá mi manejo de errores actual?

    Puede exigir cambios si tu código asume que catch ofrece un objeto con propiedades específicas. La solución típica es tipar como unknown y validar con instanceof Error u otros guard checks.

    ¿Qué hacer con librerías de terceros con tipos rotos?

    Usa skipLibCheck: true temporalmente y/o añade types o index.d.ts auxiliares para las partes que bloquean la migración.

    ¿Cómo evitar PRs enormes durante la migración?

    Divide la migración en PRs pequeños por área funcional; prioriza endpoints y parsers; documenta los cambios y pide revisión por code owners.

  • Aprende a tipar correctamente props, hooks y contextos en TypeScript y React

    Aprende a tipar correctamente props, hooks y contextos en TypeScript y React

    TypeScript + React: cómo tipar correctamente props, hooks y contextos

    Tiempo estimado de lectura: 4 min

    • Tipado explícito evita errores silenciosos: evita atajos como as any y prefiere contratos claros.
    • Props y refs: evita React.FC, usa referencias DOM con null inicial y valores mutables con valor inicial.
    • Contextos seguros: inicializa con null y expón hooks que hagan fail-fast si se usan fuera del provider.
    • Handlers y hooks: aprovecha los tipos de React (ChangeEvent, FormEvent) y deja que TS infiera cuando sea seguro.

    ¿Quieres dejar de parchear bugs con as any y que tu base de código deje de tener sorpresas en producción? Bien. Esto es lo que realmente necesitas saber sobre TypeScript + React: cómo tipar correctamente props, hooks y contextos. No es teoría. Son patrones que evitan errores silenciosos, mejoran el autocompletado y hacen que el código sea mantenible cuando el equipo crece.

    Resumen rápido (lectores con prisa)

    Tipar React con TypeScript reduce errores en producción y mejora DX. Evita React.FC, inicializa contextos con null y valida con hooks, usa refs con null para DOM y valores iniciales para mutables, y aprovecha los tipos sintéticos de eventos de React.

    Evita React.FC: tipa los parámetros explícitamente

    React.FC fue útil en tutoriales, pero introduce problemas: children implícitos, genéricos torpes y ruido. Tipar la función es más claro y explícito.

    interface ButtonProps {
      label: string;
      onClick: () => void;
      variant?: 'primary' | 'secondary';
      children?: React.ReactNode;
    }
    
    export function Button({ label, onClick, variant = 'primary', children }: ButtonProps) {
      return <button className={`btn-${variant}`} onClick={onClick}>{children ?? label}</button>;
    }
    

    React.ReactNode cubre todo lo que necesitas para children. Punto.

    useState: deja que TS infiera cuando pueda, explícito cuando haga falta

    Si el estado empieza con un primitivo, no especifiques el tipo. Si empieza vacío y luego será un objeto, usa una unión con null.

    interface User { id: string; email: string; }
    
    const [count, setCount] = useState(0);            // OK, inferido
    const [user, setUser] = useState<User | null>(null); // OK, explícito
    

    ¿Por qué? Porque evitarás tener que castear más adelante y te proteges contra undefined al acceder a propiedades.

    useRef: dos usos, dos reglas

    useRef sirve para referencias DOM y para valores mutables que no disparan re-render. Los tipos cambian según el valor inicial.

    • DOM refs: inicializa con null y maneja optional chaining.
    • Valores mutables: inicializa con el valor y muta .current.
    const inputRef = useRef<HTMLInputElement | null>(null);
    const renderCount = useRef(0);
    
    inputRef.current?.focus();
    renderCount.current += 1;
    

    No uses as para saltarte el null check. Esa falsedad te estallará en runtime.

    Eventos del DOM: tipa cada handler

    No uses any. React expone tipos sintéticos bien definidos. Úsalos y disfruta del autocompletado.

    const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
      console.log(e.target.value);
    };
    
    const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
      e.preventDefault();
    };
    

    Esto evita errores tontos como leer propiedades inexistentes.

    useContext: seguro, explícito y con fail-fast

    No hagas createContext({} as ThemeContext). Ese as silencia al compilador y deja el error para producción.

    Patrón correcto: contexto con null y hook personalizado que comprueba la presencia del provider.

    interface ThemeContextType { theme: 'light'|'dark'; toggle: () => void; }
    const ThemeContext = createContext<ThemeContextType | null>(null);
    
    export function useTheme() {
      const ctx = useContext(ThemeContext);
      if (!ctx) throw new Error('useTheme debe usarse dentro de ThemeProvider');
      return ctx;
    }
    

    Fail-fast: si alguien usa el hook fuera del provider, fallas rápido y el stack trace dice dónde.

    forwardRef: firma invertida, atención al tipo genérico

    La firma de forwardRef es contraintuitiva: el primer genérico es el tipo de ref, el segundo las props.

    interface InputProps { label: string }
    
    export const CustomInput = forwardRef<HTMLInputElement, InputProps>(({ label, ...props }, ref) => (
      <label>
        {label}
        <input ref={ref} {...props} />
      </label>
    ));
    CustomInput.displayName = 'CustomInput';
    

    Siempre define displayName para facilitar debugging en React DevTools.

    Tips prácticos que cambian proyectos

    • Exporta type con export type cuando sean solo contratos. Eso deja claro que no hay runtime.
    • No uses as para “callar” al compilador. Es un atajo que se vuelve deuda.
    • Si necesitas tipos genéricos en componentes, tipa explícitamente props y evita React.FC.
    • Para APIs y carga asíncrona, combina Zod (o similar) con z.infer si necesitas validación runtime y tipos derivados.

    Checklist rápido antes de push

    • ¿Contextos inicializados con null y validados por hooks? ✔
    • ¿useRef con null para DOM y con valor inicial para mutables? ✔
    • ¿Handlers con React.ChangeEvent / FormEvent? ✔
    • ¿No hay as any salvo casos documentados? ✔

    Cierra con criterio

    Tipar React no es un ejercicio académico. Es la forma más barata de prevenir fallos en producción y mejorar el DX de tu equipo. Haz estas tres cosas hoy:

    1. Revisa contextos: elimina as y añade hooks defensivos.
    2. Estándariza useRef y useState según lo explicado.
    3. Añade displayName a los componentes con forwardRef.

    Aplica esto en tu repo. Si algo rompe después, sabrás exactamente por qué. Esto no acaba aquí. Hay más patrones (componentes polimórficos, inferencia con generics, overloads en hooks) que merecen otra nota.

    FAQ

    ¿Por qué evitar React.FC?

    Porque introduce children implícitos, dificulta genéricos y añade ruido. Tipar explícitamente los parámetros es más claro y evita sorpresas.

    ¿Cuándo especificar el tipo en useState?

    No lo especifiques si el estado inicia con un primitivo (deja que TS infiera). Si el estado inicia vacío y luego será un objeto, usa una unión con null (por ejemplo User | null).

    ¿Cómo tipar correctamente useRef para DOM?

    Inicializa la ref con null y usa el tipo del elemento: useRef<HTMLInputElement | null>(null). Accede con optional chaining (inputRef.current?.focus()).

    ¿Qué hacer si alguien usa un context fuera del provider?

    Exponer un hook que haga fail-fast: si el contexto es null, lanzar un error claro (por ejemplo throw new Error('useTheme debe usarse dentro de ThemeProvider')).

    ¿Es aceptable usar as en alguna situación?

    Evita as salvo casos documentados y justificables. Usarlo para “callar” al compilador oculta problemas que aparecerán en runtime.

    ¿Cómo mejorar la validación de APIs y mantener tipos?

    Combina validación runtime con librerías como Zod y usa z.infer para derivar tipos TypeScript a partir de los esquemas de validación.

  • Cómo Angular 21 Optimiza la Asincronía Sin Zone.js

    Cómo Angular 21 Optimiza la Asincronía Sin Zone.js

    Sin Zone.js — Native Async/Await: Angular 21 y la arquitectura Zoneless

    Sin Zone.js — Native Async/Await es el cambio arquitectónico que Angular 21 trae para cerrar años de parches y trampas alrededor de la asincronía. En lugar de confiar en interceptores globales, el framework apuesta por Signals y async/await nativo, lo que redefine cómo se detectan cambios, cómo se escribe lógica asíncrona y cómo se depura una aplicación Angular a escala.

    Resumen rápido (lectores con prisa)

    Angular 21 elimina Zone.js y usa Signals como fuente de verdad. Usa async/await nativo sin envoltorios. Resultado: detección de cambios localizada, trazas de error más limpias y menos bundle inicial.

    Cuándo: migraciones planificadas por fases. Cómo: mover estado a signals y usar OnPush; auditar subscribes y callbacks externos.

    Tiempo estimado de lectura

    Tiempo estimado de lectura: 5 min

    Ideas clave

    • Signals reemplazan a Zone.js: reactividad explícita y renderizado localizado.
    • Async/await nativo: sin transpile ni parches que oculten trazas y comportamiento.
    • Mejoras medibles: bundle inicial más pequeño, trazas limpias y menos comprobaciones globales.
    • Migración requiere auditoría: refactor de suscripciones y adopción de OnPush y signals.

    Sin Zone.js — Native Async/Await: qué cambia y por qué importa

    Zone.js surgió como una solución pragmática: interceptar (monkey‑patch) APIs del navegador —setTimeout, fetch, Promises, addEventListener— para saber cuándo lanzar la detección de cambios. Funcionó, pero con costes claros: comprobaciones globales innecesarias, trazas de pila contaminadas y fricción con APIs modernas. Más aún, Zone.js no intercepta await a nivel de motor V8, lo que obligó a técnicas de transpile para mantener el comportamiento esperado.

    Angular 21 elimina esa capa. En su lugar, Signals actúan como la fuente de verdad: cuando un signal cambia, Angular calcula qué partes de la vista dependen de él y actualiza solo esas piezas. El async/await se usa tal cual lo diseñó ECMAScript —sin envoltorios ni polyfills— y el navegador ejecuta la asincronía de manera óptima.

    Documentación clave:

    Cómo funciona el modelo Zoneless en práctica

    La filosofía es simple: reactividad explícita y renderizado localizado. Un servicio realiza una llamada asíncrona con async/await, actualiza signals y la vista reacciona únicamente a esos cambios.

    Ejemplo: servicio con signals

    @Injectable({ providedIn: 'root' })
    export class UserService {
      readonly users = signal([]);
      readonly loading = signal(false);
    
      async loadUsers(): Promise {
        this.loading.set(true);
        try {
          const response = await fetch('/api/users');
          this.users.set(await response.json());
        } finally {
          this.loading.set(false);
        }
      }
    }

    Ejemplo: template con signals

    En el template, consumir signals es directo y elimina muchos patrones previos (pipes async, subscribes que mutan propiedades de clase):

    <if (loading()) {>
      <app-skeleton />
    <} @else {>
      @for (user of users()) {
        <app-user-card [user]="user" />
      }
    }

    Angular gestiona internamente las dependencias entre signals y templates; no necesitas Zone.js para “avivar” la vista.

    Beneficios técnicos medibles

    • Reducción del bundle: eliminar Zone.js suele suponer ~100 KB minificados menos en el bundle inicial, con impacto directo en LCP.
    • Trazas de error limpias: las stack traces muestran tu código, no los callbacks internos de Zone.js, lo que reduce tiempo de diagnóstico.
    • Rendimiento en runtime: eventos de alta frecuencia (scroll, mousemove) dejan de disparar comprobaciones globales; solo los cambios efectivos actualizan la UI.
    • Compatibilidad natural con APIs modernas: fetch, Web Streams, WebSockets y librerías modernas funcionan sin trampas de detección.

    Riesgos y puntos a auditar antes de migrar

    Eliminar Zone.js no es solo quitar un import. Patrones comunes que romperán incluyen:

    • Mutar propiedades de clase dentro de subscribe() esperando que la vista se refresque automáticamente.
    • Dependencia implícita en NgZone.run() para actualizaciones desde callbacks externos.
    • Componentes sin ChangeDetectionStrategy.OnPush que confían en comprobaciones globales.

    Identifica y refactoriza estos puntos antes de desactivar Zone.js.

    Estrategia práctica de migración

    Pasos prácticos recomendados para una migración segura y por fases.

    Checklist rápida

    1. Establece ChangeDetectionStrategy.OnPush en toda la base de componentes.
    2. Migrar estado de componentes a signal() y computed(). Empieza por componentes hoja.
    3. Reemplaza suscripciones RxJS que mutan estado por toSignal() (@angular/core/rxjs-interop) o por flujos que actualicen signals explícitamente.
    4. Ejecuta pruebas E2E y de accesibilidad; valida rendimiento en escenarios reales.
    5. Activa el modo zoneless (p. ej. provideExperimentalZonelessChangeDetection() o su equivalente estable) cuando el 100% del estado dependiente de la vista esté en signals.

    Checklist rápida para auditoría antes de activar zoneless:

    • Todos los componentes críticos usan OnPush.
    • No existen mutaciones de estado implícitas en subscribes.
    • CI ejecuta pruebas de integración que cubren flujos asíncronos.
    • Observabilidad en producción: trazas y métricas para comparar comportamiento pre/post migración.

    Conclusión: menos magia, más control

    Sin Zone.js — Native Async/Await no es una moda; es la materialización de un principio: reactividad explícita y alineada con las plataformas. Para equipos que priorizan rendimiento, claridad y una depuración más rápida, Angular 21 ofrece un modelo más predecible y eficiente.

    Empieza la migración por componentes críticos, automatiza las pruebas de integración y planifica la adopción en fases: la reducción de complejidad será visible y medible. En la próxima entrega publicaremos una checklist automatizable y scripts de migración para acelerar este proceso en proyectos reales.

    FAQ

    ¿Qué es exactamente lo que reemplaza a Zone.js en Angular 21?

    Signals y el uso de async/await nativo. Signals actúan como la fuente de verdad para dependencias y actualizaciones de vista.

    ¿Por qué las trazas de error mejoran sin Zone.js?

    Porque se elimina el monkey‑patching y las trampas que insertan callbacks intermedios en las stack traces; las trazas muestran código de la aplicación y no callbacks internos.

    ¿Qué patrones rompen al desactivar Zone.js?

    Patrones que mutan estado dentro de subscribe(), dependencia en NgZone.run() y componentes que esperan comprobaciones globales en lugar de OnPush y signals.

    ¿Cuánto se reduce el bundle al quitar Zone.js?

    Eliminar Zone.js suele suponer aproximadamente 100 KB minificados menos en el bundle inicial, según la observación mencionada en el artículo.

    ¿Cuál es el primer paso práctico para migrar?

    Establecer ChangeDetectionStrategy.OnPush en los componentes y mover estado a signal() y computed(), empezando por componentes hoja.

    ¿Debo activar el modo zoneless inmediatamente en producción?

    No. Activarlo cuando el 100% del estado dependiente de la vista esté en signals y después de haber ejecutado pruebas E2E y de integración que validen flujos asíncronos.
  • 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.