Author: Dominicode

  • 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.
  • Implementando Plum para Gobernanza de Decisiones en Código

    Implementando Plum para Gobernanza de Decisiones en Código

    ¿Y si te dijera que tu “spec” es una tarjeta de bienvenida para el caos si no la conviertes en evidencia viva?

    Tiempo estimado de lectura: 6 min

    • Plum convierte decisiones de diseño y agentes en evidencia rastreable ligada a commits.
    • Fallar commits a propósito es el checkpoint que fuerza la aprobación humana y evita decisiones no registradas.
    • El flujo incluye init, extracción de decisiones, bloqueo de commits si hay decisiones pendientes y sincronización spec↔tests↔código.
    • Limitaciones reales: Pytest-only ahora, backfill difícil, deduping fuzzy y riesgo de ruido de interrupción.

    Introducción

    Poca gente habla claro de esto: cuando un Product Manager cambia una regla, la pregunta real no es “¿habrá que tocar el código?” sino “¿cómo sabré mañana quién decidió qué, por qué y con qué pruebas?”. Spoiler: la mayoría no lo sabe. Y con agentes de IA metidos en la cocina, ese “no saber” se vuelve que arda todo en silencio.

    Esto no es teoría bonita. Es práctica sucia. Y la herramienta que te salva la vida se llama Plum. Sí, Plum. La plomada. La que te dice si lo que has levantado está vertical o te lo estás inventando sobre la marcha.

    Resumen rápido (lectores con prisa)

    Qué es: Plum es un guardián operativo que convierte decisiones (humanas o de LLM) en artefactos rastreables ligados a commits.

    Cuándo usarlo: Cuando usas agentes o LLMs para tomar decisiones que afectan código, specs o tests.

    Por qué importa: Evita que las decisiones queden atrapadas en chats y que el repositorio pierda la memoria de intención.

    Cómo funciona (resumen): Hooks de Git + extracción de traces + bloqueo de commits hasta aprobación + sincronización spec↔tests↔código.

    Por qué hay que preocuparse ahora

    – Porque los LLMs generan código a ritmo industrial.

    – Porque cambios urgentes o hotfixes se meten directo al trunk.

    – Porque las decisiones que importan quedan atrapadas en chats —los famosos traces— y se evaporan al cerrar la sesión.

    – Porque las especificaciones se quedan en Markdown como si fueran altares estáticos, sin reflejar lo que el código realmente hace.

    La consecuencia: código que pasa tests pero no cumple intención. Tests que validan outputs, no contratos. Specs que no son contrato sino historia. Y equipos que no pueden responder cuando algo explota en producción.

    Cómo funciona Plum —sin poesía— pero con sentido

    1) plum init

    – Crea .plum y .plumignore.

    – Te pide dónde están tus specs (Markdown) y tus tests (por ahora Pytest).

    – Añade hooks a Git: el commit se convierte en punto de control, no en trámite.

    2) Haces código con un agente

    – El agente toma decisiones en el chat. Tú las apruebas o las ajustas.

    – Al intentar git commit, Plum hace su trabajo: compara diffs desde el último commit y escanea los traces del agente.

    3) Plum extrae decisiones

    – Deduplica (sí, imperfecto; más abajo explico por qué).

    – Te presenta: “estas son las decisiones que tomaste desde el último commit. ¿Las apruebas?”.

    – Si hay decisiones pendientes, el commit falla. Sí, falla a propósito. Tú apruebas o corriges.

    4) Aprobadas → actualizaciones y registro

    – Aprobadas => Plum actualiza la spec (Markdown) y genera un registro .jsonl con la decisión, la autoría (humano o LLM), rama, timestamps y vínculo al diff.

    5) Ejecutas plum sync

    – Plum te muestra las brechas entre spec, tests y código: requisitos sin tests, tests sin caso claro, etc.

    Por qué no puede ser “una skill” del agente

    Porque una skill es una sugerencia dentro del agente. Y las sugerencias se ignoran cuando hay prisa. Si quieres gobernanza necesitas un checkpoint externo e innegociable. Si el commit no falla, la herramienta es una opción más que nadie usa. Plum falla commits a propósito para forzar el acto reflexivo: “aprobación humana o nada”.

    La plomada no pinta paredes. Te evita derrumbes.

    Qué hay dentro del archivo JSONL y por qué importa

    El .jsonl no es un “log más”. Es un registro de intención con metadatos para auditoría.

    Ejemplo de entrada:

    – question: “¿Batchear updates de spec o aplicar por decisión?”

    – decision: “Batch spec updates across all decisions”

    – approved_by: user@example.com

    – proposed_by: LLM (o human)

    – branch: feature/x

    – diff_link: git://…

    – timestamps: created, approved, synced

    Ese registro responde a: quién decidió, qué decidió, por qué y cuándo. Lo que todo equipo serio debería exigir.

    Limitaciones reales (no las bonitas)

    • Pytest-only por ahora. Si usas otro runner, el análisis de cobertura falla. Esto es temporal, pero real.
    • Backfill: Plum funciona mejor si la spec va adelante del código. Analizar un monolito legacy y generar spec desde cero es una tarea distinta.
    • Decision deduping es fuzzy. Identificar “la misma decisión” entre conversaciones humanas y LLMs no es trivial. Depende del repo, del dominio y de tu tolerancia.
    • Ruido de interrupción. Si generas cinco decisiones por un hotfix, te puede cortar el flow. Por eso Plum necesita umbrales de interrupción configurables.
    • Rollbacks automáticos: si rechazas una decisión en la CLI, que se revierta el cambio en el código todavía requiere flujo claro entre agente y control de versiones. No siempre está resuelto. Lo ideal: rechazo en el CLI que abre un “rework” en el agente con rollback automático; hoy es work-in-progress.

    Diseño de umbrales —el arte de no volver loco al dev

    Velocidad es vida. Interrupciones matan. Así que Plum permite (y debe permitir) configurar tolerancias:

    • Modo “dangerously approve all” para prototipos.
    • Modo “auditable strict” para banking, salud, compliance.
    • Filtros por carpeta o tipo de archivo (ej.: cambios en README no generan decisiones).
    • Severidad: solo interrumpir cuando la decisión sea contradictoria con reglas previas o afecte invariantes del sistema.
    • Timebox: decisiones ligeras se acumulan y se presentan en lote, las críticas se presentan inmediatamente.

    Esto es clave: la herramienta debe ser lo suficientemente simple para que cada dev la mantenga en su cabeza. Si no, la ignorarán.

    DSPy y la búsqueda del determinismo

    No me gustan las soluciones que dependen sólo de LLMs para validar la validez de una regla. Cuando puedes usar código —tests, parsers, análisis sintáctico— úsalo. Donde no puedas, estructura las llamadas a LLMs. DSPy ayuda: define inputs/outputs tipados para las llamadas a modelos, reduce alucinaciones y permite testear las respuestas.

    Ejemplo práctico:

    • Deducción de decisión = tarea rápida → GPT-OSS (rápido).
    • Parse semántico de spec = tarea precisa → modelo con DSPy que devuelva JSON estricto.
    • Cuando falla la determinación, vuelve al humano.

    Por qué esto cambia la revisión de código

    Hoy revisas sintaxis y estilo. Mañana, sin estas herramientas, revisarás humo. Con Plum revisas intención. Ves “por qué existe esta función” y no sólo “si el PR es legible”. Es code review con memoria. Y esa memoria evita que los agentes te reproduzcan antiguas prohibiciones por olvido de contexto.

    Checklist mínimo para empezar (15 minutos)

    1. Pip install plum-dev
    2. plumb init (apunta al folder de specs y a tu carpeta de tests)
    3. Añade .plumignore para evitar ruido (README, docs, etc.)
    4. Ajusta umbrales: prototipo vs production.
    5. Corre un hotfix con agente y haz commit — observa el commit-fail, aprueba decisiones.
    6. Ejecuta plum sync y revisa cobertura spec↔tests↔código.
    7. Guarda el .jsonl en la rama y pásalo por review.

    Casos de uso concretos

    • Startups: modo “dangerously approve” para protos, switch a strict cuando tienes usuarios reales.
    • Fintech / Salud: strict desde el primer día, cada micro-decision auditada.
    • Open Source: Plum ayuda a traducir PRs dispersos en decisiones rastreables y aprobadas.

    Metáfora breve

    Tu repo es un edificio. Los agentes son una cuadrilla hiperactiva que puede añadir habitaciones a velocidad absurda. La spec es el plano. Si no actualizas planos y firmas cambios, un día entras y la escalera está en el baño. Plum es la plomada: no te dice cómo pintar, te dice si la pared está derecha.

    La urgencia práctica

    Si ya usas agentes y no capturas decisiones, estás construyendo un legado que nadie asumirá. La deuda técnica no es solo trabajo: es riesgo legal, fiscal y reputacional. La gobernanza no es un lujo, es supervivencia.

    ¿Quieres empezar ahora?

    Pruébalo: pip install plum-dev y corre plum init en una rama de feature.

    Si quieres que te lo haga más fácil, te doy 3 cosas ahora mismo:

    • Un template de .jsonl para registrar decisiones.
    • Un flujo de PR (CI) que bloquea merges hasta sync exitoso.
    • Un checklist para integrar Plum en 15 minutos.

    Respóndeme este mensaje y te lo envío. O instala plum-dev y me cuentas qué encuentras en tu primer commit con agente. Te prometo que descubrirás decisiones que no sabías que habías tomado.

    Si el artículo y su enfoque encajan con tus flujos de automatización, considera explorar más en Dominicode Labs como continuación lógica a la integración de herramientas y procesos en equipos técnicos.

    FAQ

    ¿Qué hace exactamente Plum cuando instalo y lo configuro?

    Instala hooks de Git, identifica dónde están tus specs (Markdown) y tests (Pytest por ahora), y añade puntos de control en commits para extraer y registrar decisiones tomadas por humanos o agentes.

    ¿Plum genera código o modifica mi base de código automático?

    No. Plum no genera código. Actualiza specs y registra decisiones; el código lo sigue haciendo la persona o el agente. Plum actúa como checkpoint y registro.

    ¿Qué pasa si no quiero que ciertos cambios sean bloqueados?

    Puedes configurar umbrales, filtros por carpeta/tipo de archivo y modos (ej.: “dangerously approve all”) para reducir interrupciones en prototipos o áreas no críticas.

    ¿Plum soporta todos los frameworks de tests?

    No: actualmente es Pytest-only. El análisis de cobertura falla con otros runners hasta que se añada soporte explícito.

    ¿Cómo se ve un registro de decisión y qué metadatos incluye?

    Un .jsonl incluye: question, decision, approved_by, proposed_by (LLM o human), branch, diff_link y timestamps (created, approved, synced).

    ¿Qué ocurre si Plum detecta decisiones conflictivas?

    Plum puede bloquear el commit y presentar las decisiones para aprobación. La resolución puede requerir rework en el agente o intervención humana; el flujo de rollback automático es work-in-progress.

    ¿Plum puede integrarse en CI para bloquear merges?

    Sí. Un flujo de PR (CI) puede bloquear merges hasta que plum sync sea exitoso y las brechas entre spec, tests y código hayan sido resueltas.

  • 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 diagnosticar errores CORS en Angular con responseType

    Cómo diagnosticar errores CORS en Angular con responseType

    responseType en HttpResponse: diagnóstico programático de CORS y Fetch API en Angular

    Tiempo estimado de lectura: 3 min

    • Diagnóstico preciso: responseType expone la clasificación de Fetch (basic, cors, opaque, error, opaqueredirect) para convertir status: 0 en información accionable.
    • Observabilidad: Usar interceptores para registrar fetchType permite distinguir fallos de red, bloqueos CORS y respuestas opacas.
    • Estrategia de reintentos: No aplicar reintentos genéricos; combinar responseType con límites y circuit breakers.
    • Compatibilidad: Habilitar Fetch con provideHttpClient(withFetch()) para acceder a esta propiedad; XHR no la expone.

    Introducción

    responseType en HttpResponse es la nueva propiedad que Angular expone en HttpResponse y HttpErrorResponse para reflejar el tipo de respuesta de la Fetch API (‘basic’, ‘cors’, ‘opaque’, ‘error’, ‘opaqueredirect’). En las primeras líneas: esto permite diagnosticar programáticamente fallos de red y bloqueos de CORS sin depender de DevTools ni de mensajes genéricos con status: 0.

    Resumen rápido (lectores con prisa)

    Qué es: una propiedad en HttpResponse/HttpErrorResponse que refleja Response.type de la Fetch API.

    Cuándo usarlo: cuando uses Fetch como motor en Angular (provideHttpClient(withFetch())).

    Por qué importa: convierte status: 0 en señales accionables (p. ej. distinguir ‘opaque’ de ‘error’).

    Cómo funciona: el navegador categoriza la respuesta (basic/cors/opaque/error/opaqueredirect) y Angular la expone en HttpResponse/HttpErrorResponse.

    responseType en HttpResponse: qué es y por qué importa

    La propiedad es la representación de Response.type de la Fetch API (documentada en MDN). Angular la hace accesible cuando el HttpClient usa Fetch como motor (activar con provideHttpClient(withFetch())). Si tu app sigue con XHR, no tendrás esta clasificación semántica porque XHR no expone esos metadatos.

    No confundas esto con la opción de petición responseType que define ‘json’|’blob’|’text’, ni con HttpEventType. Aquí hablamos de cómo el navegador trató la respuesta desde la perspectiva de seguridad/origen.

    Fuentes:

    Valores y significado práctico

    basic

    ‘basic’ — Same-origin. No hay CORS. Acceso completo a cabeceras y cuerpo.

    cors

    ‘cors’ — Cross-origin aprobado; el servidor ha respondido con las cabeceras CORS correctas.

    opaque

    ‘opaque’ — Petición cross-origin en modo no-cors: el navegador ejecuta la petición pero oculta cuerpo y cabeceras (respuesta inaccesible).

    error

    ‘error’ — Falla de red total: DNS, conexión rechazada o bloqueo de CORS severo que impidió la entrega.

    opaqueredirect

    ‘opaqueredirect’ — Redirección cross-origin en modo manual.

    Interpretar correctamente evita reintentos inútiles y mejora observabilidad: un ‘opaque’ no se arregla reintentando; es probable que la petición haya sido enviada en modo incorrecto o que falten cabeceras CORS.

    Implementación práctica: interceptor de diagnóstico

    Un interceptor global es el lugar natural para enriquecer logs y tomar decisiones automáticas:

    import { HttpInterceptorFn, HttpErrorResponse } from '@angular/common/http';
    import { catchError, throwError } from 'rxjs';
    
    export const corsDiagnosticInterceptor: HttpInterceptorFn = (req, next) => {
      return next(req).pipe(
        catchError((error: HttpErrorResponse) => {
          if (error.status === 0) {
            // En versiones antiguas puede requerirse "as any" si no está tipado aún
            const fetchType = (error as any).responseType as string | undefined;
    
            if (fetchType === 'opaque' || fetchType === 'error') {
              // Log estructurado para observabilidad
              const payload = {
                tag: 'network:cors',
                fetchType,
                url: req.url,
                method: req.method,
                timestamp: new Date().toISOString()
              };
              // Ej: enviar a Sentry/Datadog/YourLogger
              console.error('[Network/CORS]', payload);
            }
          }
          return throwError(() => error);
        })
      );
    };

    Detalles prácticos: añade contexto (userAgent, entorno, versiones) y evita logs verbosos en clientes con alto tráfico. Etiqueta adecuadamente para filtrar incidentes de red en tu SIEM.

    Decisiones automáticas y recomendaciones

    Cómo actuar según responseType:

    • ‘error’ + status 0: marcar como fallo de infraestructura; no reintentar automáticamente; alertar al equipo infra.
    • ‘opaque’: auditar la configuración de la petición (¿se envía mode: ‘no-cors’ accidentalmente?), revisar proxies y CDN que puedan transformar la petición.
    • ‘cors’ con payload inválido: tratar como error de backend normal y aplicar reintentos controlados si procede.
    • ‘basic’: investigar conectividad local u origen compartido.

    Evita políticas de reintento genérico: una estrategia inteligente combina responseType con límites de reintentos y circuit breakers.

    Requisitos, tipado y compatibilidad

    • Habilita Fetch: provideHttpClient(withFetch()). (Ver: Angular HttpClient)
    • Tipado: en versiones intermedias de Angular puede ser necesario castear (error as any). Revisa las notas de versión si la propiedad aún no aparece en .d.ts.
    • Testing: en E2E/Browsers reales verifica valores de Response.type. En unit tests, simula HttpErrorResponse con responseType para validar rutas de logging.

    Observabilidad y tiempo de resolución

    La granularidad que aporta responseType reduce el time-to-detect y time-to-resolve. En entornos donde integras APIs de terceros, proxies corporativos o autenticación centralizada (OAuth), distinguir entre ‘opaque’ y ‘error’ te ahorra reproducir errores en local y te deja respuestas accionables en dashboards.

    Conclusión

    responseType en HttpResponse no es solo un campo más; es una palanca para convertir errores opacos en señales operativas. Si manejas integraciones cross-origin en producción, habilitar Fetch y aprovechar esta propiedad en interceptores y pipelines de observabilidad es una mejora tangible en resiliencia y diagnóstico.

    FAQ

    ¿Qué es exactamente responseType en HttpResponse?

    Es la representación de Response.type de la Fetch API expuesta por Angular en HttpResponse y HttpErrorResponse cuando HttpClient usa Fetch como motor.

    ¿Necesito cambiar a Fetch para verlo?

    Sí. Habilita Fetch con provideHttpClient(withFetch()). Si tu app sigue con XHR, esa clasificación no estará disponible.

    ¿Cómo distingue ‘opaque’ de ‘error’?

    ‘opaque’ indica una petición cross-origin en modo no-cors con cuerpo y cabeceras ocultas; ‘error’ indica una falla de red total (DNS, conexión rechazada o bloqueo severo de CORS).

    ¿Debo reintentar cuando veo status 0?

    No automáticamente. Usa responseType para decidir: por ejemplo, no reintentar en ‘error’ y auditar la configuración en ‘opaque’.

    ¿Dónde conviene registrar fetchType?

    En un interceptor global, con logs estructurados que incluyan contexto (userAgent, entorno, versiones) y etiquetas para filtrar en tu SIEM.

    ¿Cómo pruebo esto en tests?

    En E2E y navegadores reales verifica Response.type. En unit tests simula HttpErrorResponse con responseType para validar rutas de logging y comportamiento del interceptor.

  • Cómo redactar especificaciones efectivas para IA en desarrollo de software

    Cómo redactar especificaciones efectivas para IA en desarrollo de software

    ¿Quieres que la IA escriba código que aguante en producción o prefieres pagar la reescritura con horas de sueño robadas?

    Tiempo estimado de lectura: 6 min

    • Sin una spec sólida, la IA falla: la salida suele ser “lo más probable” y no lo que tu sistema necesita.
    • Una spec funciona como contrato: entradas, salidas y reglas inmutables (TS, DB, validadores).
    • Proceso y repo: coloca SPEC.md y reglas globales en el repo; pide tests antes de código.
    • Diseña para fallos: idempotencia, retries, observabilidad y mocks de LLM en CI.

    Poca gente dice esto claro: sin una spec sólida, la IA no te ayuda —te traiciona con estilo. Te da un PR brillante, lo mergeas, y dos semanas después estás en modo bombero arreglando incoherencias, dependencias raras y bugs que solo existen porque nadie le dijo al modelo las reglas del juego.

    Esto no es teoría. Es un manual corto y agresivo para escribir specs que conviertan a la IA en ejecutora precisa, no en improvisadora talentosa.

    Resumen rápido (lectores con prisa)

    La IA no entiende contexto técnico; genera lo más probable. Para usarla en producción necesitas specs como contratos: define entradas, salidas, validadores y versiones exactas del stack. Pon la spec en el repo, exige tests (mock de LLM en CI) y diseña idempotencia, retries y observabilidad desde el inicio.

    Primera verdad incómoda: la IA no piensa, predice

    Los modelos son máquinas de probabilidades. No entienden GDPR, SLAs o el negocio que hay detrás del botón. Si no les das límites, rellenan con lo más probable de su entrenamiento. Y lo más probable suele ser un parche bonito… que no encaja en tu arquitectura.

    Qué hace una spec que realmente funcione con IA

    1) Contexto de negocio (el “por qué”) — 1 párrafo

    No le cuentes la historia de tu vida. Di en una frase qué problema resuelve esta feature y qué sería un fallo. Ejemplo: “Crear usuarios con verificación por email. Éxito = usuario activo; fracaso = intento de signup duplicado.” Con eso la IA prioriza seguridad y unicidad, no UX glam.

    2) Contratos de datos inmutables — el núcleo

    Define TODAS las formas de datos:

    • Interfaces TypeScript (ej. CreateUserRequest, UserResponse).
    • Esquema de DB (SQL/Prisma).
    • Validadores (Zod schemas).

    Si el código espera un JSON con { email: string, password: string } dilo. Congela esos contratos. Si cambian, cambia la spec. Esto transforma a la IA en un generador que cumple un contrato, no en un novelista.

    3) Stack y versiones exactas — sin ambigüedades

    “Usa Next.js” es basura. Di “Next.js 14 — App Router — Node 20 — Postgres 15 — pgvector”. Lista librerías permitidas y las prohibidas. Los modelos tienden a usar patrones históricos; dar versión evita sorpresas.

    4) Reglas negativas — lo que NO se debe hacer

    La IA ama instrucciones. Si le dices “No hagas X”, lo recuerda. Lista antipatrones:

    • No exponer variables de entorno en cliente.
    • No añadir dependencias sin revisión CVE.
    • No implementar persistencia eventual en endpoints críticos.

    5) Criterios de aceptación comprobables

    Exige tests. Define qué pruebas deben pasar:

    • Unit tests (ej. hashing de password).
    • Integration tests (ej. createUser -> DB -> verify hash).
    • Tests de resiliencia (reintentos en worker).

    Pedir tests antes que código hace que la IA produzca implementaciones testables.

    Cómo estructurar la spec en el repo (hazlo ya)

    No metas la spec en Google Docs o Notion y esperes que la IA la lea. Ponla en el repo. Que el agente la tenga al lado del código. Dos archivos mínimos:

    Archivos mínimos

    • .cursorrules / .github/copilot-instructions.md — Reglas globales: stack, estilos, convenciones de nombres, políticas de seguridad. Que el agente lo lea siempre.
    • SPEC.md (micro-spec por feature) — Contexto corto, contratos TS, endpoints, criterios de aceptación, reglas negativas, responsables.

    Micro-spec vs contexto global: menos es más

    Saturar la ventana de contexto con miles de archivos confunde. Alimenta a la IA con:

    • SPEC.md del módulo.
    • Tipos globales que realmente importan.
    • Un archivo de reglas globales.

    Menos ruido, más precisión. La IA trabaja mejor con densidad técnica, no con bibliotecas de historia.

    Patrón de trabajo: plan antes de código

    Nunca pidas “haz el CRUD”. Pide un plan en pasos:

    1. Interfaces + DB schema.
    2. Contratos OpenAPI.
    3. Tests de aceptación.
    4. Implementación por sprint.

    Aprueba cada fase. Así evitas que la IA genere código que contradiga los contratos que aprobaste.

    Herramientas que convienen y por qué

    – TypeScript + Zod: transforma respuestas en contratos verificables.

    – Prisma/SQL: esquemas claros y migraciones.

    – OpenAPI: contratos de endpoint.

    – pgvector (si usas vectores): evita añadir otro servicio.

    – n8n para orquestación: sacas la lógica de integración fuera del repo y manejas retries visuales.

    RAG y seguridad: nunca lo tomes a la ligera

    Si vas a indexar documentos para chatear con ellos, cada vector debe llevar tenant_id. Punto. No mezcles tenants. Nunca. El filtro por tenant debe aplicarse en la consulta, no en la app. Si mezclas vectores, estás invitando a fugas de datos.

    Idempotencia, retries y jobs: diseña para fallos

    Asume que la IA y los servicios fallarán. Diseña:

    • Jobs con estado (pending, processing, success, failed).
    • Workers idempotentes por job_id.
    • Dead-letter queues para errores irreparables.
    • Retries con backoff exponencial y circuit breaker.

    No idempotencia = facturación duplicada + datos duplicados. No es elegante. Es caro.

    Observabilidad desde el minuto cero

    Si no mides, no mejoras. Instrumenta:

    • Traces distribuidos (OpenTelemetry).
    • Métricas: latencia por modelo, tokens por job, coste por tenant.
    • Logs estructurados con context_id.
    • Dashboards y alertas (picos de coste, aumentos de error rates).

    Tests: mockea la IA

    No dependas de la API real en CI. Mockea respuestas de LLM —positivas y negativas— y tests que simulen timeouts, respuestas malformadas y ataques de prompt injection. Así la spec y los tests te protegen cuando la IA se sale del carril.

    Plantilla mínima de SPEC.md (rápida y usable)

    Pon esto en la raíz del módulo. No lo copies sin adaptar, pero úsalo como base.

    • Título: Objetivo en una frase.
    • Contexto: 2 párrafos máximos.
    • Stack: versiones exactas.
    • Contratos: interfaces TS + esquemas SQL/Prisma.
    • Endpoints: método, path, payloads (ej. OpenAPI snippet).
    • Regla negativas: lista corta.
    • Criterios de aceptación: tests concretos.
    • Responsables: quién aprueba merge.

    El nuevo rol del senior: menos héroe, más guardián

    El valor del senior hoy no es teclear más rápido. Es decidir fronteras. Es escribir specs que no fallen en producción. Si no tienes eso, la IA solo acelera el desastre.

    Checklist rápido antes de pedir código a la IA

    • ¿SPEC.md está en la raíz del módulo?
    • ¿Interfaces TS y esquemas DB están definidos?
    • ¿Reglas negativas claras?
    • ¿Tests de aceptación definidos?
    • ¿El prompt obliga a devolver JSON validable?

    Si respondes no a cualquiera, no pidas código.

    CTA

    Si quieres la plantilla SPEC.md lista para pegar y un prompt maestro para Claude que funcione hoy, respóndeme “Quiero la plantilla”.

    Te la envío lista para pegar en el repo y para que la IA empiece a generar código que no te rompa la vida.

    Para quienes trabajan en automatización, agentes y workflows este enfoque encaja con prácticas de laboratorio y experimentación. Más recursos y experimentos vinculados a estos patrones están disponibles en Dominicode Labs, que complementan las plantillas y ejemplos prácticos descritos arriba.

    FAQ

    Respuesta: ¿Por qué necesito una spec si la IA puede escribir código por mí?

    Porque la IA genera lo más probable, no lo correcto para tu negocio. Una spec transforma requisitos en contratos verificables que la IA puede cumplir de forma repetible.

    Respuesta: ¿Qué debe contener obligatoriamente un SPEC.md?

    Título objetivo, contexto corto, stack con versiones exactas, contratos (TS + DB), endpoints, reglas negativas, criterios de aceptación y responsables.

    Respuesta: ¿Cómo evito fugas de datos en sistemas RAG?

    Indexa vectores con tenant_id, aplica el filtro por tenant en la consulta y evita mezclar índices entre tenants.

    Respuesta: ¿Qué pruebas debo pedir antes de revisar un PR generado por IA?

    Unit tests, integration tests que verifiquen contratos y tests de resiliencia (timeouts, retries, respuestas malformadas).

    Respuesta: ¿Cómo integro mocks de LLM en CI sin perder cobertura realista?

    Mockea escenarios positivos y negativos, timeouts y prompt injections. Mantén casos representativos que reflejen errores reales observados en producción.

    Respuesta: ¿Qué reglas negativas son las más críticas?

    No exponer env vars en cliente; no añadir dependencias sin revisión CVE; no usar persistencia eventual en endpoints críticos; exigir tests antes del merge.

  • Cómo Vitest se convierte en el test runner por defecto en Angular 21

    Cómo Vitest se convierte en el test runner por defecto en Angular 21

    Vitest como test runner por defecto en Angular 21

    Tiempo estimado de lectura: 3 min

    • Ideas clave:
    • Angular 21 define a Vitest como runner de tests por defecto, unificando pipeline con Vite/esbuild.
    • Ventajas técnicas: pipeline unificado, ESM-first, feedback más rápido en –watch, menos dependencias puente.
    • Riesgos a validar: compatibilidad con TestBed, zone.js, elección de entorno DOM y migración incremental en monorepos.
    • Estrategia recomendada: probar en branch, alinear vite.config.ts, migrar por iteraciones y mantener Karma/Jest hasta completar la transición.

    Vitest como test runner por defecto cambia algo más que un paquete en package.json: alinea el pipeline de pruebas con el mismo motor que compila y sirve tu aplicación (Vite/esbuild). En las primeras líneas: esto reduce discrepancias entre entorno de desarrollo y entorno de tests, acelera el feedback en modo watch y simplifica la gestión de módulos ESM. Angular 21 fija Vitest por defecto; Karma y Jest siguen soportados, pero la dirección es clara.

    Resumen rápido (lectores con prisa)

    Vitest es un runner sobre Vite optimizado para ESM y esbuild.

    Significa un pipeline unificado entre dev y tests, menos transformaciones duplicadas y feedback más rápido en –watch.

    Valida TestBed, zone.js y entornos DOM (jsdom vs happy-dom) antes de migrar.

    Migración recomendada por fases; mantener Karma/Jest hasta completar la cobertura en Vitest.

    Vitest como test runner por defecto: qué significa para tu arquitectura

    Vitest es un runner construido sobre Vite y optimizado para flujos ESM y esbuild. Al adoptar Vitest por defecto, Angular deja de tener dos pipelines distintos (uno para build/serve y otro para tests), lo que elimina una fuente frecuente de errores: transforms distintos, alias no replicados o compatibilidades ESM que “se rompen” solo en CI.

    Ventajas técnicas (no marketing)

    Pipeline unificado

    Same toolchain para dev y tests. Menos config duplicada = menos fallos de integración.

    ESM-first

    Vitest trata paquetes ESM nativos sin hacks; adiós a transformIgnorePatterns interminables.

    Feedback más rápido en –watch

    Vite conoce el grafo de módulos y re-ejecuta solo lo necesario, reduciendo tiempos de feedback en desarrollo.

    Menos “glue” y dependencias puente

    Menos presets y adaptadores como los que Jest requería en proyectos Angular.

    Cuando la herramienta de testing comparte transformaciones exactas con tu dev server, tus tests reflejan la realidad del runtime.

    Ejemplo mínimo de integración y scripts

    Un ejemplo práctico de lo que cambias en un proyecto Angular:

    package.json (scripts)

    {
      "scripts": {
        "test": "vitest",
        "test:watch": "vitest --watch",
        "test:ci": "vitest --run"
      }
    }

    vite.config.ts (fragmento)

    import { defineConfig } from 'vite';
    import { angular } from '@analogjs/vite-plugin-angular'; // ejemplo de integración si aplica
    
    export default defineConfig({
      test: {
        environment: 'happy-dom', // o 'jsdom' según tus necesidades
        globals: true
      }
    });

    Estos archivos muestran la convergencia: tu vite.config.ts puede centralizar transformaciones y alias que antes había que duplicar para Jest o Karma.

    Riesgos y puntos a validar antes de migrar

    • TestBed y APIs específicas de Angular: sigue funcionando, pero revisa utilidades que dependan de polyfills o zone.js. Algunas configuraciones de test legacy pueden requerir ajustes.
    • Entornos DOM: Vitest soporta jsdom y happy-dom; valida cuál reproduce mejor tu suite (render tests, componentes con animaciones, etc.).
    • Monorepos y grandes bases de tests: migrar toda la suite puede ser costoso de golpe; planifica migraciones por paquetes o por dominios funcionales.
    • Cold starts en CI: aunque Vitest suele ser rápido, en suites masivas el rendimiento en cold start depende mucho de la configuración y del entorno del runner (compara con tus números reales).

    Estrategia práctica de migración (tech-lead friendly)

    1. Probar en un branch: instala Vitest y configura un ejemplo mínimo (un módulo o paquete).
    2. Alinear vite.config.ts: centraliza las transformaciones que usas en dev (alias, plugins).
    3. Comparar modos: ejecutar la suite en modo watch y en CI (run) para comparar tiempos y fallos.
    4. Migración por iteraciones: migrar grupos de tests por feature o carpeta, ajustando mocks y environment según sea necesario.
    5. Mantener Karma/Jest activos: mantenerlos durante la transición; eliminar cuando el 100% de tests pasen en Vitest y la monitorización confirme estabilidad.

    Si quieres una herramienta rápida para estimar el impacto, mide:

    • Tiempo total de test en CI antes/después.
    • Tasa de falsos negativos/positivos detectados por cambio de runner.
    • Tiempo medio de feedback en dev (–watch).

    CI/CD: ganancias reales

    En CI obtendrás beneficios prácticos:

    • Menor complejidad en runners (menos paquetes, menos instalación).
    • Startup más rápido y menor consumo de memoria en contenedores.
    • Pipeline más predecible al usar el mismo empaquetador que en dev.

    Ejemplo breve (GitHub Actions step):

    - name: Run tests
      run: npm ci && npm run test:ci

    Sin Webpack en la ecuación, la instalación y ejecución suelen ser más ágiles.

    Cuándo no migrar aún

    • Si tienes una suite de tests con integración muy fina en Jest (configuraciones Nx o monorepos con dependencias internas) y el coste de migración supera el beneficio inmediato.
    • Si dependes de plugins de Jest que no tienen equivalentes en Vitest y que son críticos para tu flujo.

    En esos casos, planifica la migración como iniciativa de medio plazo: arranca con nuevos proyectos y módulos para ganar experiencia.

    Conclusión

    Vitest como test runner por defecto en Angular 21 no es solo una nueva opción: es la consolidación de una decisión de coherencia técnica. Para proyectos nuevos, adopta Vitest. Para proyectos con Karma, planifica la migración cuanto antes. Para entornos Jest maduros, valora ROI y migra por fases. La apuesta central aquí es menos complejidad operativa, menos errores por discrepancias de herramientas y tests que realmente reflejan el comportamiento en producción.

    Lecturas y recursos:

    FAQ

    Respuesta: Vitest es un test runner construido sobre Vite, optimizado para flujos ESM y esbuild. Comparte transformaciones con el dev server de Vite, lo que reduce discrepancias entre desarrollo y tests.

    Respuesta: Angular 21 fija Vitest por defecto para alinear pipelines, reducir duplicación de configuración y mejorar la coherencia entre runtime de desarrollo y entorno de tests.

    Respuesta: Validar compatibilidad de TestBed y APIs específicas de Angular, dependencia en zone.js o polyfills, y elegir el entorno DOM adecuado (jsdom vs happy-dom).

    Respuesta: Depende de tu suite. jsdom puede ser más completo en APIs DOM; happy-dom suele ser más ligero. Probar ambos según componentes y animaciones es la vía correcta.

    Respuesta: Migrar por paquetes o dominios funcionales, no todo de golpe. Medir impacto en cada iteración y mantener herramientas legacy activas hasta validar cobertura completa en Vitest.

    Respuesta: Sí. Mantener Jest o Karma durante la transición es recomendable. Eliminar solo cuando todos los tests pasen en Vitest y la monitorización confirme estabilidad.

    Respuesta: Medir tiempo total de test en CI antes/después, tasa de falsos negativos/positivos por cambio de runner y tiempo medio de feedback en dev (–watch).

  • Cómo aplicar la regla del 60% en la gestión de contexto para LLMs

    Cómo aplicar la regla del 60% en la gestión de contexto para LLMs

    Gestión de contexto: la regla del 60% para sesiones en Claude Code

    Tiempo estimado de lectura: 5 min

    • Regla operativa: nunca dejes que una sesión consuma más del 60% de la ventana de contexto sin persistir el estado y limpiar la memoria.
    • Patrón de trabajo: dividir tareas en Research → Plan → Implement → Validate, con artefactos en disco y limpieza de contexto entre fases.
    • Artefactos clave: /CLAUDE.md, /RESEARCH.md, /PLAN.md, /TASK_STATE.md, /VALIDATION_REPORT.md y commits atómicos por módulo.
    • Señales y métricas: observar contradicciones, repeticiones de contexto y fallos por “olvidos”; medir % de tareas con rework y tiempo de retoma.

    Introducción

    La frase “gestión de contexto: la regla del 60%” no es un eslogan. Es la regla operativa que evita que sesiones largas con agentes como Claude Code produzcan código coherente hoy y deuda técnica mañana. Si trabajas con LLMs en ingeniería, aplica esto desde el primer día: nunca dejes que una sesión consuma más del 60% de la ventana de contexto sin persistir el estado y limpiar la memoria.

    Resumen rápido (lectores con prisa)

    La regla del 60% limita cuánto de la ventana de contexto puede usar una sesión antes de persistir el estado. Úsala para fragmentar trabajo en sesiones controladas y guardar artefactos versionados (archivos en el repo). Aplica especialmente con agentes que leen/escriben repositorios como Claude Code.

    Qué significa “Gestión de contexto: la regla del 60%” y por qué importa

    Los modelos de lenguaje tienen una ventana finita de tokens. Cuando esa ventana se aproxima a su límite —y en la práctica cuando supera el 60%— el modelo comienza a priorizar lo más reciente. Eso no produce errores ruidosos: produce decisiones de diseño que olvidan criterios definidos al inicio, bugs detectados temprano y validaciones que ya no se tienen en cuenta.

    La regla del 60% obliga a fragmentar el trabajo en sesiones controladas y a externalizar el estado en artefactos versionados (archivos en el repo). Con Claude Code esto es práctico y repetible porque el agente puede leer/escribir el repositorio: https://docs.anthropic.com/en/docs/agents-and-tools/claude-code/overview y https://www.anthropic.com/claude.

    El patrón operativo: 4 fases limpias por sesión

    Divide cualquier tarea compleja en cuatro fases: Research → Plan → Implement → Validate. Cada fase debe terminar con un artefacto en disco y una limpieza explícita del contexto antes de pasar a la siguiente.

    1) Research — auditoría

    – Objetivo: mapear dependencias, puntos de dolor y deuda técnica sin cambiar nada.

    – Salida: RESEARCH.md con módulos auditados, preguntas abiertas y riesgos priorizados.

    – Acción: cerrar sesión. No cargar más archivos que los estrictamente necesarios.

    2) Plan — diseño acotado

    – Objetivo: con RESEARCH.md + CLAUDE.md (contrato del proyecto) definir módulos, orden y criterios de aceptación.

    – Salida: PLAN.md con tareas atómicas y criterios verificables.

    – Acción: validar el plan con un humano; cerrar sesión.

    3) Implement — sesiones por módulo

    – Objetivo: una sesión por módulo. Cargar solo PLAN.md, CLAUDE.md y archivos del módulo.

    – Salida por módulo: commit atómico + actualización de TASK_STATE.md (estado por módulo) y tests unitarios.

    – Acción: limpiar contexto entre módulos (reiniciar sesión o instanciar subagente nuevo).

    4) Validate — verificación objetiva

    – Objetivo: sesión en blanco que lea PLAN.md y ejecute validaciones (tests unitarios, integración, contratos).

    – Salida: VALIDATION_REPORT.md con pass/fail y pasos de corrección.

    – Acción: abrir PR / merge si pasa; en caso contrario, agregar tareas correctivas al plan y repetir ciclo.

    Ejemplo práctico (prompt y artefactos)

    Estructura de archivos mínima:

    /CLAUDE.md
    /RESEARCH.md
    /PLAN.md
    /TASK_STATE.md
    /VALIDATION_REPORT.md
    /tasks/auth-migration.md
    

    Prompt de recuperación inicial (Research → Plan):

    Lee /RESEARCH.md y /CLAUDE.md. Propón un PLAN.md que divida la migración de Auth en módulos atómicos,
    cada uno con criterios de aceptación y tests mínimos. No implementes código.
    Guarda PLAN.md y termina la sesión.

    Prompt para Implement (módulo user-service):

    Lee PLAN.md y CLAUDE.md. Trabaja únicamente en src/services/user-service.* según el criterio de la tarea "UserService".
    Agrega tests unitarios que validen los criterios. Actualiza TASK_STATE.md antes de hacer commit.
    No toques otros módulos.

    Regla inquebrantable: actualizar TASK_STATE.md y hacer commit antes de terminar la sesión.

    Señales de que estás cruzando el 60% (y qué hacer)

    – Necesitas repetir contextos largos en prompts para que el agente recuerde una regla inicial.

    – El agente empieza a contradecir decisiones anteriores sin justificación.

    – Validaciones fallan por “olvidos” de requisitos que estaban en el RESEARCH.md.

    Si ves cualquiera de estas señales: persiste el estado en disco, cierra la sesión y reinicia con el artefacto correspondiente.

    Ventajas prácticas y métricas que importan

    Aplicar la regla del 60% reduce ruido y mejora trazabilidad:

    • Menos reverts por decisiones olvidadas.
    • Mayor porcentaje de tasks que pasan CI en el primer commit.
    • Tiempo de retoma por sesión < 5 minutos (leer artefacto) en vez de re-auditar todo.

    Mide: % de tareas con rework, número de bugs registrados en TASK_STATE.md, tiempo desde apertura de sesión hasta reanudación efectiva.

    Límites y advertencias

    Esto no sustituye especificaciones claras ni revisiones humanas. Si la planifica es ambigua, la IA persistirá ambigüedades más rápido. El patrón reduce riesgos operativos, no el riesgo conceptual de malas decisiones de diseño. Además, no necesitas este overhead para fixes rápidos o scripts aislados: aplica la regla cuando el alcance y la duración lo justifiquen.

    La regla del 60% es una disciplina: no es bonita, pero evita que la IA genere parches brillantes que fallan en integración. Si automatizas en serio, diseña tu flujo con RESEARCH.md, PLAN.md, TASK_STATE.md y VALIDATION_REPORT.md, obliga a commits atómicos y reinicia sesiones a tiempo. Con eso, la memoria del modelo deja de ser un talón de Aquiles y se convierte en parte auditable de tu pipeline.

    Continuación práctica y recursos: Dominicode Labs

    FAQ

    ¿Qué es la regla del 60%?

    Es una regla operativa que limita el uso de la ventana de contexto: nunca permitir que una sesión consuma más del 60% sin persistir estado y limpiar la memoria.

    ¿Cuándo debo aplicarla?

    Aplica siempre en sesiones largas con LLMs y agentes que manejan proyectos no triviales; evita su uso solo en fixes rápidos o scripts aislados.

    ¿Por qué importa con Claude Code?

    Porque Claude Code puede leer y escribir el repositorio; fragmentar el trabajo y persistir artefactos hace el flujo práctico y repetible.

    ¿Cuáles son los artefactos mínimos?

    /CLAUDE.md, /RESEARCH.md, /PLAN.md, /TASK_STATE.md, /VALIDATION_REPORT.md y archivos de tareas (por ejemplo /tasks/auth-migration.md).

    ¿Cómo se mide el éxito?

    Métricas: % de tareas con rework, número de bugs registrados en TASK_STATE.md y tiempo desde apertura de sesión hasta reanudación efectiva.

    ¿Qué hacer si detecto que crucé el 60%?

    Persistir el estado en disco, cerrar la sesión y reiniciar con el artefacto correspondiente.

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