Category: TypeScript

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

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

    Crea tu primer agente en TypeScript con Mastra

    Tiempo estimado de lectura: 6 min

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

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

    Resumen rápido (lectores con prisa)

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

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

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

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

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

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

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

    Úsalo cuando quieras:

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

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

    Arquitectura mínima: Agent, Tool y Model

    Divide responsabilidades desde el inicio:

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

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

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

    Instalación rápida

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

    1) Define la herramienta (Tool)

    Controla exactamente qué parámetros acepta el LLM:

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

    2) Instancia el agente

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

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

    3) Ejecuta el flujo

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

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

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

    Reglas prácticas para llevar esto a producción

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

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

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

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

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

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

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

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

    Dominicode Labs

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

    FAQ

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

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

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

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

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

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

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

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

    Como implementar Facade Pattern en Angular

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

    Resumen rápido (lectores con prisa)

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

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

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

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

    Tiempo estimado de lectura: 4 min

    Ideas clave

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

    Tabla de contenidos

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

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

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

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

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

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

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

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

    2) Facade (el contrato que consume la UI)

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

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

    3) Componente (ligero)

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

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

    Alternativa sin NgRx: BehaviorSubject interno en la fachada

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

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

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

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

    En tests unitarios del componente mockeas UserFacade:

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

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

    Buenas prácticas y anti-patrones

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

    Criterio para decidir si usar Facade

    Usa Facade cuando:

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

    No lo uses cuando:

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

    Recursos y lecturas

    FAQ

    Respuesta:

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

    Respuesta:

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

    Respuesta:

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

    Respuesta:

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

    Respuesta:

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

    Respuesta:

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

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

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

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

    Tiempo estimado de lectura: 5 min

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

    Introducción

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

    Resumen rápido (lectores con prisa)

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

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

    1. Estructura mínima

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

    2. Instala y prepara TypeScript

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

    3. tsconfig.json recomendable (base)

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

    Documentación oficial TypeScript: TypeScript declaration files publishing

    4. Código: exportaciones claras y barrel file

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

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

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

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

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

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

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

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

    Herramientas útiles:

    7. Validación local antes de publicar

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

      Docs: npm pack docs

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

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

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

      tsd

    8. Dependencias y peerDependencies

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

    9. Checklist rápido antes de publish

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

    10. Comandos finales para publicar

    npm login
    npm publish --access public

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

    Conclusión

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

    FAQ

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

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

    ¿Puedo usar un bundler para generar tipos?

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

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

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

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

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

    ¿Debo usar peerDependencies para React?

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

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

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

    ¿Qué archivos incluir en el paquete publicado?

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

  • 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.