<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Bezael Pérez</title>
    <description>The latest articles on DEV Community by Bezael Pérez (@bezael).</description>
    <link>https://dev.to/bezael</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F69894%2F8c8240fd-1dd9-4fbe-8277-26c618359d1b.jpg</url>
      <title>DEV Community: Bezael Pérez</title>
      <link>https://dev.to/bezael</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/bezael"/>
    <language>en</language>
    <item>
      <title>Spec-Driven Development: el método para construir con IA sin perder el control</title>
      <dc:creator>Bezael Pérez</dc:creator>
      <pubDate>Thu, 09 Apr 2026 07:40:32 +0000</pubDate>
      <link>https://dev.to/bezael/spec-driven-development-el-metodo-para-construir-con-ia-sin-perder-el-control-5ce5</link>
      <guid>https://dev.to/bezael/spec-driven-development-el-metodo-para-construir-con-ia-sin-perder-el-control-5ce5</guid>
      <description>&lt;p&gt;Tienes una idea. Le pides a Claude o Cursor que la construya. En 20 minutos tienes algo que funciona.&lt;/p&gt;

&lt;p&gt;Dos semanas después, el proyecto es una bomba de tiempo. Cada feature nueva rompe algo. Nadie sabe por qué el código hace lo que hace. Y cada prompt al agente sale peor que el anterior porque el contexto se ha ido acumulando sin orden.&lt;/p&gt;

&lt;p&gt;Eso no es un problema de la IA. Es un problema de proceso.&lt;/p&gt;




&lt;h2&gt;
  
  
  El vibe coding tiene un límite
&lt;/h2&gt;

&lt;p&gt;El vibe coding es real. Funciona. Para prototipos, para explorar ideas, para hacer algo pequeño en un fin de semana.&lt;/p&gt;

&lt;p&gt;Pero tiene un problema de escala.&lt;/p&gt;

&lt;p&gt;Cuando el proyecto crece, el conocimiento sobre él vive en tu cabeza. En el historial de chats con el agente. En comentarios dispersos. Cuando quieres cambiar algo, tienes que reconstruir el contexto desde cero cada vez.&lt;/p&gt;

&lt;p&gt;El agente ejecuta bien. El problema es que ejecuta lo que le dices. Y si lo que le dices es ambiguo, incompleto o contradictorio, el resultado también lo es.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Spec-Driven Development (SDD)&lt;/strong&gt; &lt;a href="https://www.udemy.com/course/construye-con-ia-de-la-idea-al-producto-con-claude-code/?referralCode=AECD9EA3796054DEDD5D" rel="noopener noreferrer"&gt;resuelve exactamente eso&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Qué es SDD
&lt;/h2&gt;

&lt;p&gt;Una frase lo resume:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;La spec se convierte en el artefacto primario que dirige todo el trabajo posterior: implementación, tests, documentación, verificación.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;En el desarrollo tradicional, el código es la fuente de verdad. La documentación sirve al código.&lt;/p&gt;

&lt;p&gt;SDD invierte esa relación.&lt;/p&gt;

&lt;p&gt;La spec no sirve al código. &lt;strong&gt;El código sirve a la spec.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Defines el comportamiento esperado antes de que empiece la implementación. Ese comportamiento está escrito en un documento que el agente puede leer, seguir y contra el que puede verificar su trabajo.&lt;/p&gt;

&lt;p&gt;El agente ejecuta. Tú piensas. Cada uno en su sitio.&lt;/p&gt;




&lt;h2&gt;
  
  
  Lo que SDD no es
&lt;/h2&gt;

&lt;p&gt;Antes de seguir, tres malentendidos comunes:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No es documentación que nadie lee.&lt;/strong&gt; Una spec de SDD vive con el código, se versiona con el código, y cuando el proyecto cambia, la spec cambia. No es un PDF en Confluence que nadie ha abierto en dos años.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No es waterfall.&lt;/strong&gt; SDD describe el destino, no el camino. El destino puede cambiar. Lo que no cambia es el principio: antes de que el agente ejecute, alguien tiene que haber pensado qué se quiere conseguir.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No mata la velocidad.&lt;/strong&gt; La velocidad que te dio la IA se queda. Lo que cambia es que ahora va en la dirección correcta.&lt;/p&gt;




&lt;h2&gt;
  
  
  Las 7 fases del flujo
&lt;/h2&gt;

&lt;p&gt;Matt Pocock publicó en 2025 un video sobre su flujo de trabajo con IA — más de 250k visualizaciones — donde describió siete momentos que ocurren en cualquier proyecto serio. Independientemente de la herramienta.&lt;/p&gt;

&lt;p&gt;Aquí están.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fase 1: Idea
&lt;/h3&gt;

&lt;p&gt;Todo empieza aquí. La trampa es ir demasiado rápido a la siguiente fase.&lt;/p&gt;

&lt;p&gt;Tienes la idea. La entiendes en tu cabeza. Quieres empezar.&lt;/p&gt;

&lt;p&gt;El problema: "la entiendes en tu cabeza" no significa que esté suficientemente definida para que un agente la ejecute bien. En tu cabeza hay contexto implícito, restricciones que das por sentadas, decisiones que no has tomado conscientemente.&lt;/p&gt;

&lt;p&gt;Una pregunta para salir de la fase 1: ¿puedo describir lo que quiero en dos frases que no sean ambiguas?&lt;/p&gt;

&lt;p&gt;Si la respuesta es no, todavía estás aquí.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fase 2: Research &lt;em&gt;(opcional)&lt;/em&gt;
&lt;/h3&gt;

&lt;p&gt;Si hay algo desconocido en el proyecto — una API nueva, una integración que nunca hiciste — necesitas entenderlo antes de especificar.&lt;/p&gt;

&lt;p&gt;Formato: un archivo &lt;code&gt;research.md&lt;/code&gt;. Temporal, vive mientras dura el sprint. No es documentación permanente. Es caché.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fase 3: Prototipo &lt;em&gt;(opcional)&lt;/em&gt;
&lt;/h3&gt;

&lt;p&gt;El prototipo ocurre &lt;strong&gt;antes&lt;/strong&gt; de la spec, no después.&lt;/p&gt;

&lt;p&gt;Si le pides al agente que construya sin haber tocado el problema primero, el agente toma todas las decisiones de diseño, de UX, de arquitectura. El resultado puede ser perfectamente funcional y completamente distinto a lo que habrías elegido tú.&lt;/p&gt;

&lt;p&gt;El prototipo es rápido y desechable. Una vez que lo tienes, lo commiteas. La spec puede referenciarlo. Entonces sí: a especificar.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fase 4: PRD (La spec)
&lt;/h3&gt;

&lt;p&gt;El Product Requirements Document. &lt;strong&gt;El artefacto central de todo el método.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;El PRD describe el destino, no el viaje. Qué tiene que ser verdad cuando el trabajo esté hecho. Quién lo usa. Qué puede hacer. Qué no puede hacer. Qué asumes sobre el estado actual. Qué criterios vas a usar para verificar que está hecho.&lt;/p&gt;

&lt;p&gt;No cómo implementarlo.&lt;/p&gt;

&lt;p&gt;Un PRD bien escrito tiene menos de 500 palabras. Si es más largo, está describiendo implementación.&lt;/p&gt;

&lt;p&gt;Ejemplo mínimo de estructura:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gu"&gt;## Objetivo&lt;/span&gt;
[Qué problema resuelve esta feature en una frase]

&lt;span class="gu"&gt;## Usuarios&lt;/span&gt;
[Quién lo usa y en qué contexto]

&lt;span class="gu"&gt;## Comportamiento esperado&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; [El usuario puede hacer X]
&lt;span class="p"&gt;-&lt;/span&gt; [El sistema responde con Y]
&lt;span class="p"&gt;-&lt;/span&gt; [Cuando Z ocurre, el resultado es W]

&lt;span class="gu"&gt;## Fuera de scope&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; [Qué NO incluye esta versión]

&lt;span class="gu"&gt;## Assumptions&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; [Qué das por sentado sobre el estado actual del sistema]

&lt;span class="gu"&gt;## Criterios de aceptación&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; [ ] [Verificable, concreto, testeable]
&lt;span class="p"&gt;-&lt;/span&gt; [ ] [...]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;La sección de &lt;code&gt;assumptions&lt;/code&gt; es la que más impacto tiene en la calidad del código generado. Es también la que más gente se salta.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fase 5: Kanban
&lt;/h3&gt;

&lt;p&gt;El PRD define el destino. El kanban define el camino.&lt;/p&gt;

&lt;p&gt;El PRD se convierte en issues. Cada issue es una unidad de trabajo que el agente puede ejecutar de forma independiente.&lt;/p&gt;

&lt;p&gt;Dos principios para dividirlo:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Vertical slices.&lt;/strong&gt; Cada issue implementa algo de punta a punta: interfaz, lógica, base de datos. No issues por capa. No "issue de frontend" + "issue de backend". Eso genera dependencias artificiales y hace imposible verificar hasta que todos están hechos.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tracer bullets.&lt;/strong&gt; Los unknowns más arriesgados van primero. Si no sabes si algo es técnicamente posible de la forma en que lo imaginaste, ese es el primer issue.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fase 6: Execution loop
&lt;/h3&gt;

&lt;p&gt;Aquí el agente toma el control.&lt;/p&gt;

&lt;p&gt;El loop es simple: el agente lee el issue, implementa, comenta y cierra. Pasa al siguiente.&lt;/p&gt;

&lt;p&gt;Lo que nunca es opcional: &lt;strong&gt;leer el código que el agente produce&lt;/strong&gt;. No para microgestionar. Para mantener el criterio sobre lo que se está construyendo. El agente ejecuta bien. Pero tú sigues siendo el responsable del resultado.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fase 7: QA
&lt;/h3&gt;

&lt;p&gt;El trabajo está hecho. Pero no está terminado.&lt;/p&gt;

&lt;p&gt;QA significa verificar que el comportamiento real del sistema coincide con el comportamiento especificado en el PRD. El agente puede ayudar: dale el PRD y el código y pídele que genere un plan de QA. El plan lo genera él. Las pruebas las ejecutas tú.&lt;/p&gt;

&lt;p&gt;Los bugs y las inconsistencias que encuentres se convierten en nuevos issues. Vuelven a la fase 6.&lt;/p&gt;




&lt;h2&gt;
  
  
  El flujo en una imagen
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;IDEA
  └─ ¿Está suficientemente definida?

RESEARCH (si aplica)
  └─ research.md — caché temporal de lo desconocido

PROTOTIPO (si aplica)
  └─ Exploración rápida, desechable — commitear como referencia

PRD
  └─ Destino, no viaje
  └─ Máx. ~500 palabras — criterios de aceptación testeables

KANBAN
  └─ Vertical slices — tracer bullets primero
  └─ Blocking relationships — dependencias explícitas

EXECUTION LOOP
  └─ Agente ejecuta issue por issue
  └─ Humano lee el código producido

QA
  └─ Agente genera el plan — humano ejecuta
  └─ Bugs → nuevos issues → vuelve al loop
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Por qué ahora
&lt;/h2&gt;

&lt;p&gt;Thoughtworks, en su radar tecnológico de 2025, describió SDD como "una de las prácticas más importantes que emergieron con la IA generativa."&lt;/p&gt;

&lt;p&gt;GitHub SpecKit tiene más de 72.000 estrellas y soporte para más de veinte plataformas: Claude Code, GitHub Copilot, Amazon Q, Gemini CLI. No es un proyecto de nicho. Es infraestructura.&lt;/p&gt;

&lt;p&gt;¿Por qué importa ahora y no antes?&lt;/p&gt;

&lt;p&gt;Porque antes, las specs las leían desarrolladores humanos. Un desarrollador puede llenar huecos, hacer preguntas, pedir aclaraciones.&lt;/p&gt;

&lt;p&gt;Un agente no puede. Un agente rellena los huecos con lo que le parece más probable.&lt;/p&gt;

&lt;p&gt;Y lo que le parece más probable no siempre es lo que tú querías.&lt;/p&gt;

&lt;p&gt;Cuando el ejecutor es una IA, la calidad de las instrucciones importa más que nunca. SDD es la respuesta a eso.&lt;/p&gt;




&lt;h2&gt;
  
  
  Para empezar hoy
&lt;/h2&gt;

&lt;p&gt;Tengo una plantilla gratuita de PRD que puedes usar en tu próximo proyecto. Incluye la estructura completa, ejemplos de criterios de aceptación y la sección de assumptions que la mayoría se salta.&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://dominicode.com/spec-driven-development" rel="noopener noreferrer"&gt;dominicode.com/spec-driven-development&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Bezael — &lt;a href="https://dominicode.com" rel="noopener noreferrer"&gt;DominiCode&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>productivity</category>
      <category>programming</category>
    </item>
    <item>
      <title>Tu app de vibe coding funciona. Eso es exactamente el problema.</title>
      <dc:creator>Bezael Pérez</dc:creator>
      <pubDate>Wed, 08 Apr 2026 16:20:17 +0000</pubDate>
      <link>https://dev.to/bezael/tu-app-de-vibe-coding-funciona-eso-es-exactamente-el-problema-4k88</link>
      <guid>https://dev.to/bezael/tu-app-de-vibe-coding-funciona-eso-es-exactamente-el-problema-4k88</guid>
      <description>&lt;p&gt;Tu app funciona.&lt;/p&gt;

&lt;p&gt;La abriste en el navegador. Hiciste clic en los botones. Todo responde.&lt;/p&gt;

&lt;p&gt;Hiciste el deploy. Nadie se quejó.&lt;/p&gt;

&lt;p&gt;Y ahí está el problema.&lt;/p&gt;

&lt;p&gt;Porque "funciona" no significa lo mismo que "está bien". Y cuando le delegas el código a una IA sin revisarlo, la diferencia entre los dos puede ser un endpoint sin autenticación, un token JWT que no expira nunca, o una contraseña guardada en texto plano en tu base de datos.&lt;/p&gt;

&lt;p&gt;No porque la IA sea mala. Sino porque la IA optimiza para "hacer que funcione el prompt". No para "hacer que sea seguro en producción".&lt;/p&gt;




&lt;h2&gt;
  
  
  Lo que la IA no te dice cuando genera tu código
&lt;/h2&gt;

&lt;p&gt;La IA generó tu endpoint. Lo probaste con Postman. Devolvió 200.&lt;/p&gt;

&lt;p&gt;Pero no te dijo que cualquier usuario autenticado puede acceder a los datos de cualquier otro usuario simplemente cambiando el ID en la URL.&lt;/p&gt;

&lt;p&gt;La IA configuró tu autenticación con JWT. Los tokens se generan. Los usuarios entran.&lt;/p&gt;

&lt;p&gt;Pero no te dijo que esos tokens no tienen fecha de expiración. Que si uno se filtra, es válido para siempre.&lt;/p&gt;

&lt;p&gt;La IA puso CORS para que el frontend pudiera llamar al backend.&lt;/p&gt;

&lt;p&gt;Pero no te dijo que lo configuró con &lt;code&gt;origin: '*'&lt;/code&gt;. Que eso significa que cualquier dominio del mundo puede hacer peticiones a tu API.&lt;/p&gt;

&lt;p&gt;Esto no es teoría. Es lo que aparece cuando auditas una app de vibe coding. Una tras otra.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;code&gt;/vibe-audit&lt;/code&gt; — 30 segundos para saber en qué lío estás
&lt;/h2&gt;

&lt;p&gt;Construí &lt;strong&gt;AI Workflow Kit&lt;/strong&gt; porque me cansé de revisar código generado por IA de manera manual y encontrar los mismos 20 problemas cada vez.&lt;/p&gt;

&lt;p&gt;La instalación:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx ai-workflow-kit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Y luego, en cualquier proyecto:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/vibe-audit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Eso es todo. La skill escanea el proyecto completo, lee los archivos más críticos, y genera un reporte con severidad, evidencia concreta y el fix sugerido.&lt;/p&gt;

&lt;p&gt;No te dice "hay un problema de seguridad". Te dice exactamente dónde está, qué línea, qué puede pasar si no lo arreglas, y cómo arreglarlo.&lt;/p&gt;




&lt;h2&gt;
  
  
  Los problemas que encuentra. Siempre.
&lt;/h2&gt;

&lt;p&gt;Después de auditar decenas de apps generadas con IA, hay 20 patrones que aparecen una y otra vez. Estos son los más comunes:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Los críticos — los que pueden costarte el proyecto:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Secretos hardcodeados.&lt;/strong&gt; La IA pone API keys, contraseñas y URLs de base de datos directamente en el código porque "funciona más rápido en el prompt". Aparece en casi el 100% de los proyectos generados sin revisión.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Lo que encuentras&lt;/span&gt;
&lt;span class="nx"&gt;OPENAI_API_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sk-...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;admin123&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;SECRET&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;abc123&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Sin validación de inputs.&lt;/strong&gt; Los endpoints confían ciegamente en &lt;code&gt;req.body&lt;/code&gt;. Cualquier usuario puede mandarte lo que quiera. Incluyendo &lt;code&gt;role: "admin"&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;role&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;role&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;  &lt;span class="c1"&gt;// el usuario se da a sí mismo rol de admin&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;IDOR — acceso a recursos de otros usuarios.&lt;/strong&gt; El endpoint &lt;code&gt;GET /api/orders/:id&lt;/code&gt; no verifica que ese pedido sea tuyo. Cambia el número en la URL. Accedes a los datos de cualquier otro usuario.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Contraseñas sin hashear.&lt;/strong&gt; No siempre. Pero ocurre, especialmente cuando el prompt fue vago o rápido.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;JWT sin expiración.&lt;/strong&gt; &lt;code&gt;jwt.sign(payload, secret)&lt;/code&gt; sin &lt;code&gt;expiresIn&lt;/code&gt;. Token válido para siempre. Si se filtra — en logs, en localStorage, en cualquier sitio — el atacante tiene acceso permanente.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stack traces expuestos al cliente.&lt;/strong&gt; &lt;code&gt;res.json({ error: err.stack })&lt;/code&gt; en el manejador de errores. En producción esto le da al atacante rutas internas, versiones de librerías y lógica de la aplicación.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Los importantes — los que te explotan en producción:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Queries sin paginación.&lt;/strong&gt; &lt;code&gt;db.findMany()&lt;/code&gt; sin límite. En desarrollo con 10 registros, perfecto. En producción con 50.000, la base de datos se cae.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;APIs de terceros sin rate limiting.&lt;/strong&gt; La IA conecta tu app a OpenAI, Stripe, o cualquier API externa y llama directamente sin control. Un bot puede hacer miles de llamadas y arruinarte la factura o hacer que el proveedor banee tu cuenta.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sin estados de loading o error en el frontend.&lt;/strong&gt; La IA genera el happy path perfecto. Si la API tarda o falla, la app se queda en blanco o explota en silencio.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;console.logs de desarrollo en producción.&lt;/strong&gt; La IA logea todo para depurar. Esos logs exponen datos internos y contaminan los logs de producción.&lt;/p&gt;




&lt;p&gt;El reporte que genera &lt;code&gt;/vibe-audit&lt;/code&gt; tiene este formato:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# Vibe Audit — nombre-del-proyecto&lt;/span&gt;
Auditado: fecha

&lt;span class="gu"&gt;## Resumen&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; 🔴 Críticos: N  (bloquear producción o son riesgos de seguridad)
&lt;span class="p"&gt;-&lt;/span&gt; 🟡 Importantes: N (afectan estabilidad o mantenibilidad)
&lt;span class="p"&gt;-&lt;/span&gt; 🔵 Mejoras: N   (deuda técnica, calidad)

&lt;span class="gu"&gt;## 🔴 Críticos&lt;/span&gt;

&lt;span class="gu"&gt;### Contraseñas sin hashear&lt;/span&gt;
&lt;span class="gs"&gt;**Dónde:**&lt;/span&gt; &lt;span class="sb"&gt;`src/auth/register.ts`&lt;/span&gt; línea 23
&lt;span class="gs"&gt;**Evidencia:**&lt;/span&gt;
[código problemático]
&lt;span class="gs"&gt;**Riesgo:**&lt;/span&gt; Si la base de datos se compromete, todas las contraseñas quedan expuestas en texto plano
&lt;span class="gs"&gt;**Fix sugerido:**&lt;/span&gt;
[código corregido]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No generalidades. Código concreto. Línea exacta. Fix aplicable.&lt;/p&gt;




&lt;h2&gt;
  
  
  El resto del kit
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;/vibe-audit&lt;/code&gt; es la razón de ser del kit. Pero no está solo.&lt;/p&gt;

&lt;p&gt;Una vez que tu app está auditada y los problemas críticos resueltos, el workflow se mantiene limpio con el resto de las herramientas:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Skills:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;/commit&lt;/code&gt; — lee el diff real y genera un commit semántico. Sin copy-paste, sin "fix stuff"&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/pr&lt;/code&gt; — PR con descripción, plan de tests y checklist de revisión&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/review @archivo&lt;/code&gt; — revisa bugs, seguridad y rendimiento con criterios de ingeniería real&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/plan [tarea]&lt;/code&gt; — fuerza planificación antes de ejecutar. Para tareas complejas, un plan aprobado vale más que código rápido&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/debug [problema]&lt;/code&gt; — diagnóstico con hipótesis antes de proponer fixes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Agentes especializados:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;/frontend&lt;/code&gt; — componentes siguiendo el sistema de diseño del proyecto&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/api&lt;/code&gt; — endpoints con validación, auth y manejo de errores&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/test&lt;/code&gt; — tests por comportamiento, no por implementación&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/refactor&lt;/code&gt; — mejora el código sin cambiar el comportamiento&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/docs&lt;/code&gt; — JSDoc, README, ADR según el contexto&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Hooks automáticos&lt;/strong&gt; — sin activación, corren solos:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Bloquea comandos destructivos antes de que se ejecuten&lt;/li&gt;
&lt;li&gt;Escanea archivos staged buscando API keys antes de cada commit&lt;/li&gt;
&lt;li&gt;Formatea con Prettier o Biome después de cada edición&lt;/li&gt;
&lt;li&gt;Corre ESLint y devuelve los errores a Claude para que los corrija&lt;/li&gt;
&lt;li&gt;Notificación de escritorio cuando Claude termina (Mac/Linux/Windows)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Funciona con &lt;strong&gt;Claude Code&lt;/strong&gt;, &lt;strong&gt;Cursor&lt;/strong&gt; y &lt;strong&gt;GitHub Copilot&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  La diferencia entre vibe coding y vibe coding bien hecho
&lt;/h2&gt;

&lt;p&gt;El vibe coding no va a desaparecer. Ni debería.&lt;/p&gt;

&lt;p&gt;La velocidad es real. La productividad es real. Poder construir en horas lo que antes tardaba días es real.&lt;/p&gt;

&lt;p&gt;El problema no es la velocidad. El problema es confundir "rápido" con "hecho".&lt;/p&gt;

&lt;p&gt;Tu app necesita los dos. Velocidad para llegar al mercado. Y un &lt;code&gt;/vibe-audit&lt;/code&gt; para saber qué hay dentro antes de que alguien más lo descubra por ti.&lt;/p&gt;




&lt;p&gt;Una línea:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx ai-workflow-kit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;GitHub: &lt;a href="https://github.com/bezael/ai-workflow-kit" rel="noopener noreferrer"&gt;https://github.com/bezael/ai-workflow-kit&lt;/a&gt;&lt;/p&gt;

</description>
      <category>claudecode</category>
      <category>vibecoding</category>
      <category>ia</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Your vibe coding app works. That's exactly the problem.</title>
      <dc:creator>Bezael Pérez</dc:creator>
      <pubDate>Wed, 08 Apr 2026 16:20:02 +0000</pubDate>
      <link>https://dev.to/bezael/your-vibe-coding-app-works-thats-exactly-the-problem-1omm</link>
      <guid>https://dev.to/bezael/your-vibe-coding-app-works-thats-exactly-the-problem-1omm</guid>
      <description>&lt;p&gt;Your app works.&lt;/p&gt;

&lt;p&gt;You opened it in the browser. Clicked the buttons. Everything responds.&lt;/p&gt;

&lt;p&gt;You deployed it. Nobody complained.&lt;/p&gt;

&lt;p&gt;And that's exactly the problem.&lt;/p&gt;

&lt;p&gt;Because "works" doesn't mean the same thing as "is correct". And when you delegate code to an AI without reviewing it, the gap between those two can be an endpoint with no authentication, a JWT token that never expires, or a password stored in plaintext in your database.&lt;/p&gt;

&lt;p&gt;Not because the AI is bad. But because the AI optimizes for "make the prompt work". Not for "make it safe in production".&lt;/p&gt;




&lt;h2&gt;
  
  
  What the AI doesn't tell you when it generates your code
&lt;/h2&gt;

&lt;p&gt;The AI generated your endpoint. You tested it with Postman. It returned 200.&lt;/p&gt;

&lt;p&gt;But it didn't tell you that any authenticated user can access any other user's data just by changing the ID in the URL.&lt;/p&gt;

&lt;p&gt;The AI set up your JWT authentication. Tokens are generated. Users log in.&lt;/p&gt;

&lt;p&gt;But it didn't tell you those tokens have no expiration date. That if one leaks, it's valid forever.&lt;/p&gt;

&lt;p&gt;The AI configured CORS so the frontend could call the backend.&lt;/p&gt;

&lt;p&gt;But it didn't tell you it set &lt;code&gt;origin: '*'&lt;/code&gt;. That means any domain in the world can make requests to your API.&lt;/p&gt;

&lt;p&gt;This isn't theory. It's what shows up when you audit a vibe-coded app. Every single time.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;code&gt;/vibe-audit&lt;/code&gt; — 30 seconds to know what mess you're in
&lt;/h2&gt;

&lt;p&gt;I built &lt;strong&gt;AI Workflow Kit&lt;/strong&gt; because I got tired of manually reviewing AI-generated code and finding the same 20 problems every time.&lt;/p&gt;

&lt;p&gt;Installation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx ai-workflow-kit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, in any project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/vibe-audit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. The skill scans the entire project, reads the most critical files, and generates a report with severity, concrete evidence, and the suggested fix.&lt;/p&gt;

&lt;p&gt;It doesn't tell you "there's a security problem". It tells you exactly where it is, which line, what can happen if you don't fix it, and how to fix it.&lt;/p&gt;




&lt;h2&gt;
  
  
  The problems it finds. Every time.
&lt;/h2&gt;

&lt;p&gt;After auditing dozens of AI-generated apps, there are 20 patterns that show up again and again. These are the most common:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The critical ones — the ones that can kill your project:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hardcoded secrets.&lt;/strong&gt; The AI puts API keys, passwords, and database URLs directly in the code because "it works faster in the prompt". Shows up in almost 100% of projects generated without review.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// What you find&lt;/span&gt;
&lt;span class="nx"&gt;OPENAI_API_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sk-...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;admin123&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;SECRET&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;abc123&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;No input validation.&lt;/strong&gt; Endpoints blindly trust &lt;code&gt;req.body&lt;/code&gt;. Any user can send you whatever they want. Including &lt;code&gt;role: "admin"&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;role&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;role&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;  &lt;span class="c1"&gt;// user gives themselves admin role&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;IDOR — access to other users' resources.&lt;/strong&gt; The &lt;code&gt;GET /api/orders/:id&lt;/code&gt; endpoint doesn't verify that order belongs to you. Change the number in the URL. You access any other user's data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Unhashed passwords.&lt;/strong&gt; Not always. But it happens, especially when the prompt was vague or rushed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;JWT without expiration.&lt;/strong&gt; &lt;code&gt;jwt.sign(payload, secret)&lt;/code&gt; without &lt;code&gt;expiresIn&lt;/code&gt;. Token valid forever. If it leaks — in logs, in localStorage, anywhere — the attacker has permanent access.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stack traces exposed to the client.&lt;/strong&gt; &lt;code&gt;res.json({ error: err.stack })&lt;/code&gt; in the error handler. In production this gives an attacker internal paths, library versions, and application logic.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;The important ones — the ones that blow up in production:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Queries without pagination.&lt;/strong&gt; &lt;code&gt;db.findMany()&lt;/code&gt; without a limit. In development with 10 records, perfect. In production with 50,000, the database goes down.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Third-party APIs without rate limiting.&lt;/strong&gt; The AI connects your app to OpenAI, Stripe, or any external API and calls directly with no control. A bot can fire thousands of requests and wreck your bill or get your account banned by the provider.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No loading or error states in the frontend.&lt;/strong&gt; The AI generates the perfect happy path. If the API is slow or fails, the app goes blank or crashes silently.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Development console.logs in production.&lt;/strong&gt; The AI logs everything for debugging. Those logs expose internal data and pollute your production logs.&lt;/p&gt;




&lt;p&gt;The report &lt;code&gt;/vibe-audit&lt;/code&gt; generates looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# Vibe Audit — project-name&lt;/span&gt;
Audited: date

&lt;span class="gu"&gt;## Summary&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; 🔴 Critical: N  (block production or are security risks)
&lt;span class="p"&gt;-&lt;/span&gt; 🟡 Important: N (affect stability or maintainability)
&lt;span class="p"&gt;-&lt;/span&gt; 🔵 Improvements: N   (technical debt, quality)

&lt;span class="gu"&gt;## 🔴 Critical&lt;/span&gt;

&lt;span class="gu"&gt;### Unhashed passwords&lt;/span&gt;
&lt;span class="gs"&gt;**Where:**&lt;/span&gt; &lt;span class="sb"&gt;`src/auth/register.ts`&lt;/span&gt; line 23
&lt;span class="gs"&gt;**Evidence:**&lt;/span&gt;
[problematic code]
&lt;span class="gs"&gt;**Risk:**&lt;/span&gt; If the database is compromised, all passwords are exposed in plaintext
&lt;span class="gs"&gt;**Suggested fix:**&lt;/span&gt;
[corrected code]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No vague warnings. Concrete code. Exact line. Actionable fix.&lt;/p&gt;




&lt;h2&gt;
  
  
  The rest of the kit
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;/vibe-audit&lt;/code&gt; is the reason the kit exists. But it doesn't stand alone.&lt;/p&gt;

&lt;p&gt;Once your app is audited and the critical issues resolved, the rest of the tools keep the workflow clean:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Skills:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;/commit&lt;/code&gt; — reads the real diff and generates a semantic commit. No copy-paste, no "fix stuff"&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/pr&lt;/code&gt; — PR with description, test plan, and reviewer checklist&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/review @file&lt;/code&gt; — reviews bugs, security, and performance with real engineering criteria&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/plan [task]&lt;/code&gt; — forces planning before execution. For complex tasks, an approved plan is worth more than fast code&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/debug [problem]&lt;/code&gt; — diagnosis with hypotheses before proposing fixes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Specialized agents:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;/frontend&lt;/code&gt; — components following the project's design system&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/api&lt;/code&gt; — endpoints with validation, auth, and error handling&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/test&lt;/code&gt; — behavior-driven tests, not implementation tests&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/refactor&lt;/code&gt; — improves code without changing behavior&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/docs&lt;/code&gt; — JSDoc, README, or ADR depending on context&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Automatic hooks&lt;/strong&gt; — no activation needed, they just run:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Blocks destructive commands before they execute&lt;/li&gt;
&lt;li&gt;Scans staged files for API keys before every commit&lt;/li&gt;
&lt;li&gt;Formats with Prettier or Biome after every edit&lt;/li&gt;
&lt;li&gt;Runs ESLint and feeds errors back to Claude to fix&lt;/li&gt;
&lt;li&gt;Desktop notification when Claude finishes (Mac/Linux/Windows)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Works with &lt;strong&gt;Claude Code&lt;/strong&gt;, &lt;strong&gt;Cursor&lt;/strong&gt;, and &lt;strong&gt;GitHub Copilot&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  The difference between vibe coding and vibe coding done right
&lt;/h2&gt;

&lt;p&gt;Vibe coding isn't going away. And it shouldn't.&lt;/p&gt;

&lt;p&gt;The speed is real. The productivity is real. Being able to build in hours what used to take days is real.&lt;/p&gt;

&lt;p&gt;The problem isn't the speed. The problem is mistaking "fast" for "done".&lt;/p&gt;

&lt;p&gt;Your app needs both. Speed to reach the market. And a &lt;code&gt;/vibe-audit&lt;/code&gt; to know what's inside before someone else finds out for you.&lt;/p&gt;




&lt;p&gt;One line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx ai-workflow-kit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;GitHub: &lt;a href="https://github.com/bezael/ai-workflow-kit" rel="noopener noreferrer"&gt;https://github.com/bezael/ai-workflow-kit&lt;/a&gt;&lt;/p&gt;

</description>
      <category>claudecode</category>
      <category>vibecoding</category>
      <category>ai</category>
      <category>webdev</category>
    </item>
    <item>
      <title>De Senior Dev a Tech Lead: la transición donde más talento se pierde
¿Te ha pasado?</title>
      <dc:creator>Bezael Pérez</dc:creator>
      <pubDate>Wed, 29 Oct 2025 18:08:40 +0000</pubDate>
      <link>https://dev.to/bezael/de-senior-dev-a-tech-lead-la-transicion-donde-mas-talento-se-pierdete-ha-pasado-15kf</link>
      <guid>https://dev.to/bezael/de-senior-dev-a-tech-lead-la-transicion-donde-mas-talento-se-pierdete-ha-pasado-15kf</guid>
      <description>&lt;p&gt;

&lt;/p&gt;
&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/bezael/por-que-los-desarrolladores-que-pasan-a-tech-lead-fracasan-305k" class="crayons-story__hidden-navigation-link"&gt;Por qué los desarrolladores que pasan a tech lead fracasan…&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/bezael" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F69894%2F8c8240fd-1dd9-4fbe-8277-26c618359d1b.jpg" alt="bezael profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/bezael" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Bezael Pérez
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Bezael Pérez
                
              
              &lt;div id="story-author-preview-content-2969164" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/bezael" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F69894%2F8c8240fd-1dd9-4fbe-8277-26c618359d1b.jpg" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Bezael Pérez&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/bezael/por-que-los-desarrolladores-que-pasan-a-tech-lead-fracasan-305k" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Oct 28 '25&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/bezael/por-que-los-desarrolladores-que-pasan-a-tech-lead-fracasan-305k" id="article-link-2969164"&gt;
          Por qué los desarrolladores que pasan a tech lead fracasan…
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/liderazgo"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;liderazgo&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/tecnologia"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;tecnologia&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/management"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;management&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/bezael/por-que-los-desarrolladores-que-pasan-a-tech-lead-fracasan-305k" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;1&lt;span class="hidden s:inline"&gt; reaction&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/bezael/por-que-los-desarrolladores-que-pasan-a-tech-lead-fracasan-305k#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              Comments


              &lt;span class="hidden s:inline"&gt;Add Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            2 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;




</description>
      <category>liderazgo</category>
      <category>tecnologia</category>
      <category>management</category>
    </item>
    <item>
      <title>Por qué los desarrolladores que pasan a tech lead fracasan…</title>
      <dc:creator>Bezael Pérez</dc:creator>
      <pubDate>Tue, 28 Oct 2025 16:57:24 +0000</pubDate>
      <link>https://dev.to/bezael/por-que-los-desarrolladores-que-pasan-a-tech-lead-fracasan-305k</link>
      <guid>https://dev.to/bezael/por-que-los-desarrolladores-que-pasan-a-tech-lead-fracasan-305k</guid>
      <description>&lt;h3&gt;
  
  
  De Senior Dev a Tech Lead: la transición donde más talento se pierde
&lt;/h3&gt;

&lt;p&gt;¿Te ha pasado?&lt;br&gt;&lt;br&gt;
Eras un desarrollador senior brillante… y un día te dicen:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“A partir de ahora, serás Tech Lead.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Lo tomas como un ascenso.&lt;br&gt;&lt;br&gt;
Pero en realidad, es un cambio de profesión.&lt;br&gt;&lt;br&gt;
Y aquí es donde muchos (muchísimos) desarrolladores fracasan.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;¿Por qué?&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Porque siguen midiendo su valor con las métricas equivocadas.&lt;/p&gt;




&lt;h2&gt;
  
  
  El conflicto central
&lt;/h2&gt;

&lt;p&gt;Tu valor ya no está en tu código.&lt;/p&gt;

&lt;p&gt;Como &lt;strong&gt;Senior Dev&lt;/strong&gt;, te valoran por tu &lt;em&gt;output individual&lt;/em&gt;:&lt;br&gt;&lt;br&gt;
la calidad de tu código, lo rápido que resuelves problemas, lo bien que entregas.&lt;/p&gt;

&lt;p&gt;Como &lt;strong&gt;Tech Lead&lt;/strong&gt;, te valoran por el &lt;em&gt;output de tu equipo&lt;/em&gt;:&lt;br&gt;&lt;br&gt;
rendimiento colectivo, claridad, bienestar, producto final y alineación con negocio.&lt;/p&gt;

&lt;p&gt;El día que entiendes eso, &lt;strong&gt;todo cambia&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  🚨 Los 5 errores más comunes (y cómo evitarlos)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. El síndrome del héroe
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;“Es más rápido si lo hago yo.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Falla:&lt;/strong&gt; te conviertes en cuello de botella.&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Solución:&lt;/strong&gt; delega, confía y enseña. Tu trabajo ya no es escribir código, es que se escriba bien.&lt;/p&gt;




&lt;h3&gt;
  
  
  2. El micromanagement disfrazado de “calidad”
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Code reviews de 50 comentarios, querer aprobarlo todo.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Falla:&lt;/strong&gt; ahogas la autonomía del equipo.&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Solución:&lt;/strong&gt; define estándares, automatiza lo repetitivo y revisa lo que realmente importa.&lt;/p&gt;




&lt;h3&gt;
  
  
  3. Falla de comunicación
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;“Pero si técnicamente es obvio…”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Falla:&lt;/strong&gt; olvidas que no todos hablan tu idioma.&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Solución:&lt;/strong&gt; traduce el lenguaje técnico al de negocio y viceversa. El contexto es poder.&lt;/p&gt;




&lt;h3&gt;
  
  
  4. Olvidar el factor humano
&lt;/h3&gt;

&lt;p&gt;Tu equipo no son tickets en Jira.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solución:&lt;/strong&gt; haz &lt;em&gt;1:1&lt;/em&gt;, pregunta cómo están, protege su foco y reconoce sus logros.&lt;/p&gt;




&lt;h3&gt;
  
  
  5. Seguir queriendo ser el mejor programador
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Solución:&lt;/strong&gt; recalibra tu éxito.&lt;br&gt;&lt;br&gt;
Ya no eres el más rápido, eres quien hace que los demás lleguen más lejos.&lt;/p&gt;




&lt;h2&gt;
  
  
  💡 En resumen
&lt;/h2&gt;

&lt;p&gt;Pasar a &lt;strong&gt;Tech Lead&lt;/strong&gt; no es subir un peldaño, es &lt;strong&gt;cambiar de escalera&lt;/strong&gt;.&lt;br&gt;&lt;br&gt;
Dejas de ser un &lt;em&gt;doer&lt;/em&gt; para convertirte en un &lt;em&gt;facilitador&lt;/em&gt;.&lt;br&gt;&lt;br&gt;
Un &lt;strong&gt;multiplicador de fuerza&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Si lo haces bien, tu impacto se multiplica.&lt;br&gt;&lt;br&gt;
Si no, acabarás quemado… y tu equipo también.&lt;/p&gt;




&lt;p&gt;📘 Estoy preparando una &lt;strong&gt;guía completa&lt;/strong&gt; sobre &lt;em&gt;“Cómo no fracasar en la transición de Senior Dev a Tech Lead”&lt;/em&gt;, con ejemplos y &lt;em&gt;prompts de IA&lt;/em&gt; para cada reto.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://liderazgo.dominicode.com/?utm_source=dev_to" rel="noopener noreferrer"&gt;¿Te gustaría que la comparta en &lt;strong&gt;PDF gratis&lt;/strong&gt; cuando esté lista?&lt;/a&gt; &lt;/p&gt;

</description>
      <category>liderazgo</category>
      <category>tecnologia</category>
      <category>management</category>
    </item>
    <item>
      <title>Jest 30: Más Rápido, Inteligente y Listo para el Futuro. ¿Es Hora de Actualizar?</title>
      <dc:creator>Bezael Pérez</dc:creator>
      <pubDate>Thu, 26 Jun 2025 05:51:57 +0000</pubDate>
      <link>https://dev.to/bezael/jest-30-mas-rapido-inteligente-y-listo-para-el-futuro-es-hora-de-actualizar-59ea</link>
      <guid>https://dev.to/bezael/jest-30-mas-rapido-inteligente-y-listo-para-el-futuro-es-hora-de-actualizar-59ea</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F31vpsyyftm59eus44r0p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F31vpsyyftm59eus44r0p.png" alt="testing unit jest" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Después de una espera que pareció una eternidad en el vertiginoso mundo del desarrollo de software, el equipo de Jest ha lanzado la versión 30. Y permíteme decirte, la espera ha valido la pena. Esto no es una simple actualización incremental; es una reinvención fundamental que aborda deudas técnicas de larga data, responde a las críticas de la comunidad y reafirma a Jest como un titán en el panorama de las pruebas de JavaScript.&lt;/p&gt;

&lt;p&gt;Durante años, los desarrolladores hemos tenido una relación de amor-odio con Jest. Adoramos su simplicidad de "cero configuración" y su potente ecosistema, pero a menudo nos hemos enfrentado a tiempos de ejecución lentos y a un consumo de memoria descontrolado en proyectos grandes. Jest 30 no solo pule los bordes ásperos, sino que reconstruye los cimientos.&lt;/p&gt;




&lt;h2&gt;
  
  
  ¿Por qué esta actualización es un punto de inflexión?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Adiós a la Lentitud
&lt;/h3&gt;

&lt;p&gt;El problema más notorio de Jest siempre ha sido el rendimiento. La versión 30 ataca este problema en dos frentes con una eficacia brutal:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Módulos Ultrarrápidos:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
El principal cuello de botella era la forma en que Jest encontraba los módulos. En proyectos grandes, especialmente monorepos, la búsqueda recursiva de archivos &lt;code&gt;package.json&lt;/code&gt; consumía una cantidad desproporcionada de tiempo. Jest 30 reemplaza este sistema con &lt;code&gt;unrs-resolver&lt;/code&gt;, un nuevo componente de alto rendimiento que aprovecha la velocidad de Rust.&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Resultados:&lt;/strong&gt; Los benchmarks oficiales muestran &lt;strong&gt;tiempos de ejecución hasta un 37% más rápidos&lt;/strong&gt; en aplicaciones TypeScript de gran tamaño.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Control de Fugas de Memoria:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
¿Quién no ha visto fallar su pipeline de CI por un misterioso error de "heap out of memory"? Jest 30 introduce una solución nativa con la opción &lt;code&gt;globalsCleanup&lt;/code&gt;. Esta característica detecta y limpia automáticamente las variables globales que se "filtran" entre pruebas, una de las principales causas de las fugas de memoria.&lt;br&gt;&lt;br&gt;
En el mismo benchmark, esta opción &lt;strong&gt;redujo el uso máximo de memoria en un increíble 77%&lt;/strong&gt; (de 7.8 GB a 1.8 GB).  &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Esto no es una optimización, es un cambio radical que puede estabilizar por completo tus pruebas.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;/ol&gt;




&lt;h3&gt;
  
  
  Nuevas Herramientas para el Desarrollador
&lt;/h3&gt;

&lt;p&gt;Más allá de la velocidad, Jest 30 enriquece nuestra caja de herramientas con funcionalidades que simplifican patrones de prueba comunes y mejoran la legibilidad del código.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;expect.arrayOf(matcher)&lt;/code&gt;:&lt;/strong&gt;
Una de las adiciones más solicitadas. Ahora puedes verificar que cada elemento de un array cumple con un matcher específico de forma declarativa, sin necesidad de bucles manuales.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;  &lt;span class="c1"&gt;// Verifica que cada objeto en el array tiene un 'id' de tipo string.&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;arrayOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;objectContaining&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;using spy&lt;/code&gt; para una Gestión de Mocks Impecable:&lt;/strong&gt;
Aprovechando la nueva sintaxis de "Explicit Resource Management" de JavaScript, ahora podemos crear espías que se restauran automáticamente al salir del ámbito.
¡Adiós a los olvidos de &lt;code&gt;mockRestore()&lt;/code&gt; en los bloques &lt;code&gt;afterEach&lt;/code&gt;!
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;  &lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;llama a console.warn&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// El espía se restaura automáticamente al final del test. ¡Magia!&lt;/span&gt;
    &lt;span class="nx"&gt;using&lt;/span&gt; &lt;span class="nx"&gt;consoleWarnSpy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;jest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;spyOn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;warn&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;mockImplementation&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{});&lt;/span&gt;

    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;warn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;un aviso&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;consoleWarnSpy&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toHaveBeenCalled&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Reintentos de Pruebas Configurables:&lt;/strong&gt;
Las pruebas inestables (&lt;em&gt;flaky tests&lt;/em&gt;) son la pesadilla de la integración continua. &lt;code&gt;jest.retryTimes()&lt;/code&gt; ahora permite configurar un retardo entre reintentos (&lt;code&gt;waitBeforeRetry&lt;/code&gt;) o reintentar una prueba fallida de inmediato (&lt;code&gt;retryImmediately&lt;/code&gt;), dándote un control mucho más fino.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  La Migración: ¿Vale la Pena el Esfuerzo?
&lt;/h2&gt;

&lt;p&gt;Seamos claros: Jest 30 introduce cambios rompedores. Requiere &lt;strong&gt;Node.js 18+&lt;/strong&gt; y &lt;strong&gt;TypeScript 5.4+&lt;/strong&gt;, y elimina los alias de matchers obsoletos (como &lt;code&gt;toBeCalled&lt;/code&gt; en lugar de &lt;code&gt;toHaveBeenCalled&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Sin embargo, la migración es más sencilla de lo que parece. La mayoría de los cambios de alias se pueden solucionar automáticamente con &lt;code&gt;eslint-plugin-jest&lt;/code&gt;. El esfuerzo de actualizar es una inversión directa en una base de código de pruebas más moderna, robusta y, sobre todo, más rápida.&lt;/p&gt;




&lt;h2&gt;
  
  
  El Futuro de las Pruebas y Cómo Dominarlo
&lt;/h2&gt;

&lt;p&gt;Esta actualización demuestra que Jest no solo está vivo, sino que está evolucionando para liderar la próxima era de las pruebas en JavaScript. Escribir pruebas no es solo una formalidad; es un arte que, cuando se domina, garantiza la calidad y la mantenibilidad de nuestras aplicaciones.&lt;/p&gt;

&lt;p&gt;Si estas mejoras en Jest 30 te inspiran a llevar tus pruebas al siguiente nivel, especialmente en el ecosistema de Angular, he volcado años de experiencia en mi libro: &lt;a href="https://leanpub.com/testing-angular-jest" rel="noopener noreferrer"&gt;Unit testing con Jest y Testing Library en Angular&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;Es la guía perfecta para dominar no solo las herramientas, sino las estrategias para escribir pruebas que realmente aporten valor y confianza a tu equipo.&lt;/p&gt;

&lt;p&gt;Las pruebas unitarias son la base de una pirámide de software sólida. Para una inmersión profunda en patrones prácticos, ejemplos del mundo real y cómo aprovechar al máximo herramientas como Jest y Testing Library, puedes encontrar más información y &lt;a href="https://www.angular-tests.dev/" rel="noopener noreferrer"&gt;adquirir el libro aquí&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusión: Un Nuevo Comienzo para Jest
&lt;/h2&gt;

&lt;p&gt;Jest 30 es una declaración de intenciones. Es una respuesta contundente a las alternativas modernas y una promesa de un futuro con ciclos de lanzamiento más consistentes y un desarrollo más audaz. Las mejoras de rendimiento son reales y tangibles, las nuevas funcionalidades son ergonómicas y potentes, y la hoja de ruta es clara.&lt;/p&gt;

&lt;p&gt;Si has estado luchando con la lentitud de Jest o te has sentido tentado por otras herramientas, esta es la actualización que estabas esperando. Es el momento de actualizar, explorar las nuevas funcionalidades y llevar tus pruebas a un nuevo nivel de eficiencia y fiabilidad. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;El futuro de las pruebas con Jest es brillante.&lt;/strong&gt;&lt;/p&gt;




</description>
      <category>jest</category>
      <category>testing</category>
      <category>javascript</category>
      <category>programming</category>
    </item>
    <item>
      <title>De Jasmine a Jest: Migración sin dolor en 30 minutos</title>
      <dc:creator>Bezael Pérez</dc:creator>
      <pubDate>Sun, 15 Jun 2025 19:36:36 +0000</pubDate>
      <link>https://dev.to/bezael/de-jasmine-a-jest-migracion-sin-dolor-en-30-minutos-2onm</link>
      <guid>https://dev.to/bezael/de-jasmine-a-jest-migracion-sin-dolor-en-30-minutos-2onm</guid>
      <description>&lt;p&gt;¿Te imaginas recortar a la mitad el tiempo que tardan tus pruebas de Angular con un simple giro de tuercas que casi nadie está aplicando todavía?&lt;br&gt;
Hoy vas a descubrir cómo hacerlo y por qué aplazarlo podría costarte horas de café frío y deploys retrasados.&lt;/p&gt;
&lt;h2&gt;
  
  
  La verdad incómoda sobre Karma
&lt;/h2&gt;

&lt;p&gt;Karma ha sido el “motor” oficial durante años.&lt;br&gt;
Pero en Angular 16 ya apareció en la lista de obsoletos y, desde la versión 17, vive con fecha de caducidad impresa.&lt;br&gt;
Cada lanzamiento de navegador, cada reconexión y cada pestaña oculta que se abre en segundo plano es un ladrillo más en la pared que separa tu código de la luz verde del &lt;em&gt;pipeline&lt;/em&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  Por qué Jest está ganando terreno en silencio
&lt;/h3&gt;

&lt;p&gt;Jest no necesita un navegador real para ejecutar tests: todo sucede en memoria, como quien hace cálculos sobre la servilleta del bar.&lt;br&gt;
Los informes son legibles, coloreados y te señalan el error con una flecha casi insultante.&lt;br&gt;
Además, el caché inteligente evita repetir trabajo y acelera aún más cada iteración.&lt;br&gt;
La comunidad ya lo adoptó; el propio equipo de Angular lo reconoce como la opción a futuro. ¿Vas a quedarte fuera?&lt;/p&gt;
&lt;h3&gt;
  
  
  Tu plan exprés de 30 minutos
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Actualiza dependencias&lt;/strong&gt;&lt;br&gt;
Añade el &lt;em&gt;preset&lt;/em&gt; oficial de Angular para Jest y retira los paquetes de Karma. Con una sola orden de instalación limpias el terreno.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Prepara la configuración&lt;/strong&gt;&lt;br&gt;
Reemplaza el archivo de Karma por el de Jest. Solo necesitas declarar el &lt;em&gt;preset&lt;/em&gt;, el directorio de cobertura y los mapeos de &lt;em&gt;paths&lt;/em&gt; que ya usas.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Adapta los &lt;em&gt;setup files&lt;/em&gt;&lt;/strong&gt;&lt;br&gt;
Si inicializabas zonas o &lt;em&gt;polyfills&lt;/em&gt; especiales, muévelos al archivo de arranque de Jest. El formato cambia, pero el contenido es prácticamente el mismo.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Ajusta los &lt;em&gt;test utilities&lt;/em&gt;&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;TestBed&lt;/code&gt; sigue funcionando; la diferencia es que las utilidades de Jest —&lt;em&gt;spies&lt;/em&gt;, temporizadores falsos, &lt;em&gt;snapshots&lt;/em&gt;— sustituyen a las de Jasmine sin dolor.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Corre la primera pasada&lt;/strong&gt;&lt;br&gt;
Ejecuta tus pruebas. Lo habitual es ver una ráfaga verde y un tiempo de ejecución que parece un error de imprenta: tan rápido que querrás presumir la captura.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;
  
  
  Lo que nadie te cuenta (pero necesitas saber)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cobertura instantánea&lt;/strong&gt;: Jest muestra el porcentaje de líneas cubiertas sin plugins extra.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Migración incremental&lt;/strong&gt;: puedes mantener algunos archivos en Jasmine mientras terminas la transición.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Integración continua simplificada&lt;/strong&gt;: adiós a los &lt;em&gt;launchers&lt;/em&gt; de navegador y a esa configuración críptica que nunca recuerdas.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  ¿Qué pasa si lo dejas para mañana?
&lt;/h3&gt;

&lt;p&gt;Karma desaparecerá del &lt;em&gt;CLI&lt;/em&gt; y tendrás que migrar deprisa, bajo presión y con todo el equipo mirando el reloj.&lt;br&gt;
El coste no será solo técnico: cada minuto de espera en cada &lt;em&gt;build&lt;/em&gt; se multiplicará por cada desarrollador y cada &lt;em&gt;pull request&lt;/em&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  Da el salto ahora mismo
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://angular-tests.dev" rel="noopener noreferrer"&gt;Haz clic aquí y descarga el ebook&lt;/a&gt; &lt;strong&gt;“Unit testing con Jest y Testing Library en Angular”&lt;/strong&gt;.&lt;br&gt;
Y tendrás tu proyecto corriendo con Jest y disfrutarás de test suites que vuelan.&lt;/p&gt;
&lt;h3&gt;
  
  
  Recuerda el beneficio final
&lt;/h3&gt;

&lt;p&gt;Migrar hoy significa ciclos de feedback rapidísimos, menos frustración y más tiempo para crear funcionalidades que importan.&lt;br&gt;
No dejes que un &lt;em&gt;runner&lt;/em&gt; obsoleto decida tu velocidad de desarrollo: adopta Jest y comprueba cómo tus pruebas dejan de ser un lastre para convertirse en tu mejor aliado.&lt;/p&gt;
&lt;h4&gt;
  
  
  Paso 1 – Instala lo esencial sin perder un minuto
&lt;/h4&gt;

&lt;p&gt;Abre tu terminal favorita y ejecuta el gestor de paquetes para añadir &lt;strong&gt;Jest&lt;/strong&gt;, &lt;strong&gt;jest-preset-angular&lt;/strong&gt; y &lt;strong&gt;@types/jest&lt;/strong&gt; como dependencias de desarrollo.&lt;br&gt;
Con esta sola acción dejas listo el motor, el adaptador específico para Angular y las definiciones de tipos que evitarán errores de compilación más adelante.&lt;/p&gt;

&lt;p&gt;Confirma al terminar que el archivo de bloqueos (ya sea &lt;code&gt;package-lock.json&lt;/code&gt; o &lt;code&gt;pnpm-lock.yaml&lt;/code&gt;) refleje las tres librerías; eso te garantiza entornos reproducibles en todo el equipo.&lt;/p&gt;

&lt;p&gt;¿Listo? En menos de lo que tarda en hervir el café, ya tienes las bases instaladas y puedes pasar al siguiente ajuste de configuración.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--save-dev&lt;/span&gt; jest jest-preset-angular @types/jest
&lt;span class="c"&gt;# o si usas yarn&lt;/span&gt;
yarn add &lt;span class="nt"&gt;--dev&lt;/span&gt; jest jest-preset-angular @types/jest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  2. Ajuste del &lt;code&gt;package.json&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;Imagina que el corazón de tu proyecto late en el &lt;code&gt;package.json&lt;/code&gt;.&lt;br&gt;
El próximo latido ­–el que activará Jest– se cocina con un simple ajuste.&lt;/p&gt;

&lt;p&gt;Abre ese archivo y localiza el bloque &lt;strong&gt;scripts&lt;/strong&gt;.&lt;br&gt;
Dentro verás la orden clásica que dispara Karma; bórrala sin piedad.&lt;/p&gt;

&lt;p&gt;En su lugar, coloca una instrucción que invoque &lt;strong&gt;Jest&lt;/strong&gt; de forma directa, sin corredores intermedios ni arranques de navegador.&lt;/p&gt;

&lt;p&gt;Con esta única sustitución, cada vez que escribas &lt;code&gt;npm run test&lt;/code&gt; (o el alias que uses) entrarás en la nueva era de tests turboalimentados.&lt;/p&gt;

&lt;p&gt;Guarda el cambio, ejecuta la prueba… y comprueba cómo el cronómetro se hunde: tu suite ahora corre a la velocidad que siempre quisiste.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"test"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npx jest"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  3. Configuración de &lt;code&gt;tsconfig.spec.json&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;¿Todavía ves el fantasma de &lt;strong&gt;Jasmine&lt;/strong&gt; acechando en tu &lt;code&gt;tsconfig.spec.json&lt;/code&gt;?&lt;br&gt;
Ese pequeño detalle frena a TypeScript y puede hacer que tus &lt;em&gt;imports&lt;/em&gt; de Jest luzcan como errores.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Localiza la puerta de entrada&lt;/strong&gt;&lt;br&gt;
Abre &lt;code&gt;tsconfig.spec.json&lt;/code&gt; y busca la propiedad &lt;code&gt;types&lt;/code&gt; dentro de &lt;code&gt;compilerOptions&lt;/code&gt;. Allí es donde “vive” la referencia a los tipos globales que entiende el compilador.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Expulsa al huésped equivocado&lt;/strong&gt;&lt;br&gt;
Si la lista contiene &lt;code&gt;"jasmine"&lt;/code&gt;, sustitúyelo por &lt;code&gt;"jest"&lt;/code&gt;. Puedes mantener otras entradas útiles (por ejemplo, &lt;code&gt;"node"&lt;/code&gt;) si ya estaban presentes: la regla es dejar solo el conjunto de tipos que realmente necesitas.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Guarda y respira&lt;/strong&gt;&lt;br&gt;
A partir de este cambio, TypeScript reconocerá todas las funciones globales de Jest (&lt;code&gt;describe&lt;/code&gt;, &lt;code&gt;it&lt;/code&gt;, &lt;code&gt;expect&lt;/code&gt;, etc.) sin que necesites &lt;em&gt;imports&lt;/em&gt; extra ni comentarios de bypass. Un simple &lt;code&gt;npm run test&lt;/code&gt; confirmará que las advertencias desaparecieron y que tu nueva configuración late con normalidad.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Con esa edición mínima habrás desalojado por completo los viejos tipos de Jasmine y tu proyecto quedará afinado para el nuevo corredor de pruebas.&lt;/p&gt;

&lt;p&gt;¿Listo para el siguiente ajuste o hay algo que quieras pulir antes de continuar?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"extends"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./tsconfig.json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"compilerOptions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"outDir"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./out-tsc/spec"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"types"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"jest"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"node"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Cambia&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"jasmine"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;por&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"jest"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"include"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"src/**/*.spec.ts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"src/**/*.d.ts"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  4. Creación de &lt;code&gt;jest.config.ts&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;¿Preparado para darle a Jest las llaves de tu proyecto?&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Crea el archivo&lt;/strong&gt;&lt;br&gt;
En la raíz, añade un nuevo &lt;code&gt;jest.config.ts&lt;/code&gt;. Piensa en él como la consola central desde la que controlarás cada test.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Importa el preset oficial&lt;/strong&gt;&lt;br&gt;
El primer paso dentro del archivo es “enchufar” &lt;code&gt;jest-preset-angular&lt;/code&gt;. Con eso, Jest entenderá componentes, pipes y templates como si llevara años hablando Angular.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Define la configuración&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;preset&lt;/strong&gt;: apunta a &lt;code&gt;'jest-preset-angular'&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;testEnvironment&lt;/strong&gt;: usa &lt;code&gt;'jsdom'&lt;/code&gt; para una simulación ligera de navegador.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;setupFilesAfterEnv&lt;/strong&gt;: referencia a un pequeño &lt;code&gt;jest.setup.ts&lt;/code&gt; donde inicialices &lt;code&gt;jest-preset-angular&lt;/code&gt;, fakeAsync zones o utilidades globales.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;transform&lt;/strong&gt;: indica a Jest que los archivos &lt;code&gt;.ts&lt;/code&gt; y &lt;code&gt;.html&lt;/code&gt; pasen por el transformador que trae el preset.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;moduleNameMapper&lt;/strong&gt;: replica aquí los alias de paths que ya tienes en tu &lt;code&gt;tsconfig&lt;/code&gt;, de modo que los imports sigan funcionando.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;coverageDirectory&lt;/strong&gt;: elige la carpeta donde quieras el informe de cobertura; así mantienes los resultados fuera de la carpeta de artefactos principales.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;transformIgnorePatterns&lt;/strong&gt;: excluye el código de terceros (por ejemplo, &lt;code&gt;node_modules/(?!(lodash-es|rxjs))&lt;/code&gt;) si necesitas transpilar alguna librería moderna.&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Guarda y prueba&lt;/strong&gt;
Con solo ese puñado de líneas, Jest sabe exactamente cómo arrancar tus specs de Angular. Lanza &lt;code&gt;npm run test&lt;/code&gt; y observa cómo se disparan los resultados: rápido, limpio y con un informe de cobertura listo para presumir.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Sin tocar una sola línea de tu código de producción, habrás levantado la nueva central de mando para tus pruebas unitarias. Tu stack ya funciona con Jest; lo siguiente es disfrutar de ciclos de feedback que casi no dan tiempo ni a parpadear.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Config&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;jest&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;preset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;jest-preset-angular&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;setupFilesAfterEnv&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;rootDir&amp;gt;/setup-jest.ts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;moduleDirectories&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node_modules&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;rootDir&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;testMatch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;**/+(*.)+(spec).+(ts)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  5. Creación de &lt;code&gt;setup-jest.ts&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;¿Sabías que un único archivo puede convocar todo el poder de Jest y al mismo tiempo convencer a Angular de que nada ha cambiado? Ese archivo se llama &lt;code&gt;setup-jest.ts&lt;/code&gt;, y su misión es dejar listo el escenario antes de que se ejecute la primera prueba.&lt;/p&gt;

&lt;h3&gt;
  
  
  Por qué importa
&lt;/h3&gt;

&lt;p&gt;Jest necesita que le cuentes cómo funcionan las zonas de Angular, cómo compilar plantillas y en qué momento «pinchar» el &lt;em&gt;TestBed&lt;/em&gt;. Sin esa preparación, tus &lt;em&gt;components&lt;/em&gt; quedarían mudos y tus &lt;em&gt;pipes&lt;/em&gt; perderían la voz.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lo que debe ocurrir dentro
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Encendido del preset&lt;/strong&gt;&lt;br&gt;
El archivo arranca cargando la utilería que traduce internamente los decoradores, las plantillas y las dependencias que Angular usa de forma nativa.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Parches y &lt;em&gt;polyfills&lt;/em&gt; opcionales&lt;/strong&gt;&lt;br&gt;
Si tu app depende de APIs del navegador que JSDOM no expone por defecto—como &lt;code&gt;matchMedia&lt;/code&gt; o ciertas animaciones—, aquí añades los parches mínimos para que Jest no tropiece.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Extensiones a las expectativas&lt;/strong&gt;&lt;br&gt;
Puedes incorporar ayudas de aserción adicionales—por ejemplo, comparadores de fechas o números aproximados—sin repetir imports en cada archivo de prueba.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Configuración global de tiempo&lt;/strong&gt;&lt;br&gt;
Ajustar un &lt;em&gt;timeout&lt;/em&gt; genérico evita que las pruebas se queden colgadas por operaciones lentas, manteniendo el reporte limpio y controlado.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Con esa coreografía, cada spec encuentra un Angular completamente operativo en milésimas de segundo, sin demoras ni advertencias extrañas. Guarda el archivo junto al resto de tu código y lánzate a ejecutar &lt;code&gt;npm run test&lt;/code&gt;: verás cómo todo fluye con la seguridad de saber que tu entorno está perfectamente preparado antes de que el cronómetro arranque.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;jest-preset-angular/setup-jest&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@angular/localize/init&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Necesario para Angular &amp;gt;= 9&lt;/span&gt;
&lt;span class="c1"&gt;// Workaround para ReferenceError: TextEncoder is not defined en Jest&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;TextEncoder&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node:util&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;defineProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;TextEncoder&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;writable&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TextEncoder&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  6. Deja atrás el lastre de Karma y Jasmine
&lt;/h3&gt;

&lt;p&gt;Ahora que tu suite vuela con Jest, mantener las viejas dependencias es como llevar un ancla colgada al tobillo. Deshazte de &lt;strong&gt;Karma&lt;/strong&gt;, &lt;strong&gt;Jasmine&lt;/strong&gt; y cualquier &lt;em&gt;launcher&lt;/em&gt; o &lt;em&gt;reporter&lt;/em&gt; relacionado: liberarás espacio, acelerarás las instalaciones de CI y evitarás conflictos de versiones en el futuro.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Depuración del &lt;code&gt;package.json&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
Despídete de cada librería que empiece por &lt;em&gt;karma-&lt;/em&gt; o &lt;em&gt;jasmine-&lt;/em&gt;. Al retirarlas, tu árbol de dependencias se encoge y las descargas en cada &lt;em&gt;pipeline&lt;/em&gt; se reducen a la mitad.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Limpieza de archivos de configuración&lt;/strong&gt;&lt;br&gt;
Borra &lt;code&gt;karma.conf.js&lt;/code&gt;, &lt;code&gt;test.ts&lt;/code&gt; (si lo tienes) y cualquier script de &lt;em&gt;npm&lt;/em&gt; que invoque Karma. Si tu CI aún abre un navegador para los tests, elimínalo también: ese paso dejó de tener sentido.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Revisión de scripts y &lt;em&gt;tasks&lt;/em&gt;&lt;/strong&gt;&lt;br&gt;
Comprueba que no queden comandos heredados en la carpeta de herramientas, ni pasos de build que referencien al viejo runner. Una búsqueda rápida por el repositorio te dará la certeza.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Actualización del &lt;em&gt;cache&lt;/em&gt; de CI&lt;/strong&gt;&lt;br&gt;
Sin Karma ni Chrome Headless en la ecuación, tu contenedor construirá menos capas y completará los tests antes de que acabes el primer sorbo de café.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Confirmación final&lt;/strong&gt;&lt;br&gt;
Ejecuta la suite con Jest en local y en la pipeline. Verás cómo la lista de paquetes descargados se acorta y los tiempos de ejecución marcan nuevos mínimos históricos.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Con esta purga, tu proyecto queda más ligero, tu presupuesto de CI respira y tu equipo trabaja sobre un stack coherente y enfocado en el futuro. Sigue avanzando; cada archivo antiguo que eliminas es un segundo menos de espera en tu próximo &lt;em&gt;deploy&lt;/em&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm uninstall karma karma-chrome-launcher karma-jasmine karma-coverage karma-jasmine-html-reporter @types/jasmine @types/jasminewd2 jasmine-core jasmine-spec-reporter
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Ejemplo mínimo: Conversión de una prueba tradicional a Jest con Testing Library
&lt;/h3&gt;

&lt;p&gt;Veamos un ejemplo sencillo de cómo se vería una prueba antes y después de la migración.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Componente &lt;code&gt;CounterComponent&lt;/code&gt; (ejemplo):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// counter.component.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;signal&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@angular/core&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;app-counter&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`
    &amp;lt;button (click)="decrement()"&amp;gt;-&amp;lt;/button&amp;gt;
    &amp;lt;span data-testid="count"&amp;gt;{{ count() }}&amp;lt;/span&amp;gt;
    &amp;lt;button (click)="increment()"&amp;gt;+&amp;lt;/button&amp;gt;
  `&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;standalone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CounterComponent&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;increment&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;decrement&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Prueba tradicional (Karma/Jasmine con &lt;code&gt;TestBed&lt;/code&gt;):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// counter.component.spec.ts (Antes)&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ComponentFixture&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;TestBed&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@angular/core/testing&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;CounterComponent&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./counter.component&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;CounterComponent&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="na"&gt;component&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CounterComponent&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="na"&gt;fixture&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ComponentFixture&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;CounterComponent&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="na"&gt;compiled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;HTMLElement&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nf"&gt;beforeEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;TestBed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configureTestingModule&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;imports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;CounterComponent&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;compileComponents&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="nx"&gt;fixture&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;TestBed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;CounterComponent&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nx"&gt;component&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fixture&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;componentInstance&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;compiled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fixture&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nativeElement&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;fixture&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;detectChanges&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Haves an initial count of 0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;component&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Renders the initial count of 0 in the span&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;countSpan&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;compiled&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[data-testid="count"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;countSpan&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Increment the count signal when increment() is called&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;component&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nx"&gt;component&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;increment&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;component&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Decrement the count signal when decrement() is called&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;component&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nx"&gt;component&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decrement&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;component&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Increment the count in the template when the "+" button is clicked&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;incrementButton&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;compiled&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelectorAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;countSpan&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;compiled&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[data-testid="count"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nx"&gt;incrementButton&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;fixture&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;detectChanges&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;component&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;countSpan&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Decrement the count in the template when the "-" button is clicked&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;decrementButton&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;compiled&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;countSpan&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;compiled&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[data-testid="count"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;countSpan&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nx"&gt;decrementButton&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;fixture&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;detectChanges&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;component&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;countSpan&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;-1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Prueba migrada (Jest):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ComponentFixture&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;TestBed&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@angular/core/testing&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;CounterComponent&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./counter.component&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;CounterComponent (con Jest) - Versión Mejorada&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="na"&gt;component&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CounterComponent&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="na"&gt;fixture&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ComponentFixture&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;CounterComponent&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="na"&gt;compiled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;HTMLElement&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getElement&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;HTMLElement&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;compiled&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getButton&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;HTMLButtonElement&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;compiled&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelectorAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;btn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;btn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getCountSpan&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;HTMLElement&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;getElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[data-testid="count"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;beforeEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;TestBed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configureTestingModule&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;imports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;CounterComponent&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;compileComponents&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="nx"&gt;fixture&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;TestBed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;CounterComponent&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;component&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fixture&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;componentInstance&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;compiled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fixture&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nativeElement&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;fixture&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;detectChanges&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Initial State and Rendering&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Initialize with a count of 0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;component&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Renders the initial count "0" inside the span&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;countSpan&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getCountSpan&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;countSpan&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;not&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toBeNull&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;countSpan&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Renders an increment and a decrement button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;incrementButton&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getButton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;+&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;decrementButton&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getButton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;-&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;incrementButton&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;not&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toBeNull&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;decrementButton&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;not&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toBeNull&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Business Logic&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Update the count signal from 0 to 1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;component&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;increment&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;component&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Update the count signal from 0 to -1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;component&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decrement&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;component&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Handles multiple operations correctly (e.g., increment twice, decrement once)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;component&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;increment&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="nx"&gt;component&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;increment&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="nx"&gt;component&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decrement&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;component&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;User Interaction&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Update the displayed count to "1" when the "+" button is clicked&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;incrementButton&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getButton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;+&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;incrementButton&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="nx"&gt;fixture&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;detectChanges&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;countSpan&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getCountSpan&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;component&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;countSpan&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Update the displayed count to "-1" when the "-" button is clicked&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;decrementButton&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getButton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;-&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;decrementButton&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="nx"&gt;fixture&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;detectChanges&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;countSpan&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getCountSpan&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;component&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;countSpan&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;-1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Displays "0" after clicking "+" and then "-"&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;incrementButton&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getButton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;+&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;decrementButton&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getButton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;-&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="nx"&gt;incrementButton&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="nx"&gt;fixture&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;detectChanges&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

      &lt;span class="nx"&gt;decrementButton&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="nx"&gt;fixture&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;detectChanges&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;countSpan&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getCountSpan&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;component&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;countSpan&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;¿Lo mejor de todo? Ahora tu archivo de pruebas no solo “pasa”, sino que respira mantenimiento futuro.&lt;/p&gt;

&lt;h3&gt;
  
  
  Por qué tus ajustes son oro puro
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Selectores resilientes&lt;/strong&gt;&lt;br&gt;
Buscar por texto (+ / −) anula la fragilidad del índice numérico. Mañana podrás insertar un botón “reset” en cualquier parte y el test ni se despeinará.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Describe anidados que cuentan una historia&lt;/strong&gt;&lt;br&gt;
Al separar Estado Inicial, Lógica y Interacción, cualquiera localiza la intención en segundos. Leer el archivo deja de ser un safari de &lt;code&gt;it()&lt;/code&gt; sueltos y pasa a ser un mapa bien señalizado.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Helpers DRY&lt;/strong&gt;&lt;br&gt;
Centralizar la obtención de elementos reduce el “ruido” en cada spec y concentra el cambio en un único punto. Si algún día migras de &lt;code&gt;data-testid&lt;/code&gt; a atributos ARIA, actualizarás solo esas funciones.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Cobertura real de flujos&lt;/strong&gt;&lt;br&gt;
Verificar la presencia de ambos botones y la secuencia + → − garantiza que tu contador no falla ante combinaciones encadenadas. Es un test de comportamiento, no una foto estática.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Cómo exprimir aún más tu nueva base
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Añade tests de accesibilidad&lt;/strong&gt;&lt;br&gt;
Usa &lt;code&gt;jest-axe&lt;/code&gt; para asegurarte de que los cambios visuales no rompen reglas WCAG. Mantendrás al equipo en verde respecto a inclusión sin esfuerzo manual.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Integra &lt;em&gt;snapshots&lt;/em&gt; selectivos&lt;/strong&gt;&lt;br&gt;
Útiles para componentes con mucho markup. Hazlo solo en piezas poco volátiles; así evitarás falsas alarmas en cada refactor visual menor.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Mide la cobertura de ramas&lt;/strong&gt;&lt;br&gt;
Activa &lt;code&gt;coverageReporters: ['text', 'lcov']&lt;/code&gt; y revisa los puntos ciegos. Verás con claridad qué caminos lógicos no pisan todavía tus pruebas.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Optimiza los fake timers&lt;/strong&gt;&lt;br&gt;
Para lógica basada en &lt;code&gt;setTimeout&lt;/code&gt; o &lt;code&gt;debounce&lt;/code&gt;, los temporizadores controlados de Jest aceleran las specs y evitan esperas innecesarias.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Revisa tu pipeline de CI&lt;/strong&gt;&lt;br&gt;
Ahora que Karma salió de escena, ajusta el caché de dependencias y elimina pasos de navegador. Ganas minutos en cada ejecución automática.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Has sentado un cimiento robusto: pruebas legibles, resistentes y fáciles de ampliar. A partir de aquí, cualquier nueva funcionalidad se incorporará con confianza y sin temer al efecto dominó.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bonus: Jest y Angular Testing Library
&lt;/h3&gt;

&lt;p&gt;¿Listo para ver cómo el mismo test respira libertad sin &lt;code&gt;fixture&lt;/code&gt;, sin &lt;code&gt;detectChanges()&lt;/code&gt; y sin ese ruido que encoge la vista?&lt;/p&gt;

&lt;p&gt;Observa esta versión con &lt;a href="https://bit.ly/testing-angular-jest" rel="noopener noreferrer"&gt;&lt;strong&gt;Angular Testing Library&lt;/strong&gt;&lt;/a&gt;: minimalista, robusta y pensada para el futuro.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prueba migrada (Jest y Angular Testing Library):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// counter.component.spec.ts (Después)&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fireEvent&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@testing-library/angular&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;CounterComponent&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./counter.component&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@testing-library/jest-dom&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Para matchers extendidos como.toBeInTheDocument()&lt;/span&gt;

&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;CounterComponent (Jest/Testing Library)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Helper centralizado: un solo punto de cambio&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;setup&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;CounterComponent&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// crea componente + fixture por ti&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getButton&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;label&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;getCount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByTestId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;count&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="nx"&gt;getButton&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Create and display initial count of 0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getCount&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;getCount&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;toHaveTextContent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Increment count when + button is clicked&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getButton&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;getCount&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;fireEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;getButton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;+&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;getCount&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;toHaveTextContent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Decrement count when - button is clicked&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getButton&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;getCount&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;fireEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;getButton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;-&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;getCount&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;toHaveTextContent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;-1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Sin fixtures ni &lt;code&gt;TestBed.compileComponents()&lt;/code&gt;&lt;/strong&gt;. &lt;code&gt;render()&lt;/code&gt; hace la magia tras bambalinas.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Selectores accesibles (&lt;code&gt;getByRole&lt;/code&gt;)&lt;/strong&gt;: resistentes a cambios de orden o estilo.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Flujos síncronos por defecto&lt;/strong&gt;: cuando algo sea realmente asíncrono, usa &lt;code&gt;await waitFor(() =&amp;gt; …)&lt;/code&gt; y listo.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;¿Te gustó la sensación de limpieza? En mi &lt;a href="https://angular-tests.dev" rel="noopener noreferrer"&gt;libro sobre testing moderno en Angular&lt;/a&gt;, profundizo en &lt;em&gt;querying&lt;/em&gt; accesible, patrones de &lt;em&gt;state observers&lt;/em&gt; y prácticas de rendimiento que hacen que tus tests vuelen.&lt;/p&gt;

&lt;h3&gt;
  
  
  ¿Prefieres aprender mirando? Aquí tienes tu atajo visual
&lt;/h3&gt;

&lt;p&gt;Sé que la instalación de &lt;strong&gt;Angular Testing Library&lt;/strong&gt; merece un tutorial aparte; lo verás detallado en el próximo artículo.&lt;/p&gt;

&lt;p&gt;Mientras tanto, si eres de los que absorben conceptos viendo cada clic en tiempo real, he preparado un vídeo exprés donde compruebas, paso a paso, cómo escribir un &lt;strong&gt;unit test de un &lt;code&gt;input required&lt;/code&gt; con Jest&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;▶️ *&lt;em&gt;Ver el vídeo en YouTube *&lt;/em&gt;  &lt;iframe src="https://www.youtube.com/embed/JMU_2dgzueI"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;h3&gt;
  
  
  Beneficios medibles: Tiempo de ejecución y reporte de cobertura
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;En pocas líneas:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Velocidad a otro nivel&lt;/strong&gt;&lt;br&gt;
Jest ejecuta tests en paralelo y en JSDOM; suites que tardaban 15 min con Karma pueden bajar a 1-2 min.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Cobertura con un flag&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;jest --coverage&lt;/code&gt; genera informes detallados sin plugins extra, así ves al instante qué ramas faltan por probar.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Mocking integrado y simple&lt;/strong&gt;&lt;br&gt;
Con &lt;code&gt;jest.fn()&lt;/code&gt; o &lt;code&gt;jest.mock()&lt;/code&gt; aísla servicios y APIs globales sin configurar librerías adicionales.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Snapshot testing&lt;/strong&gt;&lt;br&gt;
Captura la salida del componente y detecta al momento cualquier cambio inesperado en la UI.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;En conjunto, obtienes ciclos de feedback mucho más rápidos, builds de CI más baratas y una confianza mayor en cada refactor.&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusión
&lt;/h3&gt;

&lt;p&gt;Dar el salto de Karma/Jasmine a Jest es pasar de un motor con años de servicio a uno pensado para la velocidad y la claridad de hoy. En minutos tendrás:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pruebas que se ejecutan en paralelo y terminan antes de que apures el café.&lt;/li&gt;
&lt;li&gt;Cobertura de código con un simple &lt;code&gt;--coverage&lt;/code&gt;, sin plugins ni rodeos.&lt;/li&gt;
&lt;li&gt;Mocks y snapshots listos para blindar cada refactor.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Cada segundo ganado en el test runner vuelve a tu desarrollo: menos espera, más funciones nuevas, mayor confianza.&lt;/p&gt;

&lt;p&gt;¿Quieres ver cómo aplicar Jest a casos reales y tener ejemplos de producción listos para copiar y pegar? Descúbrelo en nuestro eBook y lleva tus pruebas al siguiente nivel. &lt;a href="https://bit.ly/testing-angular-jest" rel="noopener noreferrer"&gt;Haz clic aquí y conviértete&lt;/a&gt; en el desarrollador que rompe la barra de progreso antes de que empiece a cargar.&lt;/p&gt;

</description>
      <category>angular</category>
      <category>jest</category>
      <category>testing</category>
    </item>
    <item>
      <title>Cómo Crear un Framework de JavaScript desde Cero - La Historia de Brisa Build</title>
      <dc:creator>Bezael Pérez</dc:creator>
      <pubDate>Mon, 28 Oct 2024 22:50:00 +0000</pubDate>
      <link>https://dev.to/bezael/como-crear-un-framework-de-javascript-desde-cero-la-historia-de-brisa-build-28im</link>
      <guid>https://dev.to/bezael/como-crear-un-framework-de-javascript-desde-cero-la-historia-de-brisa-build-28im</guid>
      <description>&lt;p&gt;En el mundo de desarrollo web, los frameworks de JavaScript se han vuelto herramientas esenciales para la creación de aplicaciones modernas. Desde Angular y React hasta Vue y Svelte, cada uno ofrece un enfoque único para resolver problemas de arquitectura, rendimiento y experiencia de usuario. Sin embargo, muchos desarrolladores se han preguntado: ¿realmente necesitamos otro framework de JavaScript?&lt;/p&gt;

&lt;p&gt;Hoy quiero compartir la historia de cómo y por qué se creó Brisa, un nuevo framework de JavaScript, de la mano de su propio creador, Aral Roca. &lt;strong&gt;Brisa&lt;/strong&gt; no solo busca resolver problemas específicos de performance y modularidad en aplicaciones web, sino que también plantea una visión innovadora sobre cómo los desarrolladores pueden construir interfaces modernas sin perder de vista el funcionamiento interno de la web y el servidor.&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/WOTKdwxM93Q"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;¿Qué es Brisa?&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Brisa es un framework que ha sido desarrollado desde cero con el propósito de mejorar la comunicación entre el cliente y el servidor, aprovechando al máximo la tecnología de web components y signals. Uno de los principales problemas que Aral identificó en muchos frameworks actuales era la complejidad de manejar acciones del servidor y del cliente de forma eficiente. Con Brisa, cualquier evento puede ejecutarse en el servidor, y el código que se necesita en el cliente es mínimo.&lt;/p&gt;

&lt;p&gt;Un aspecto innovador de Brisa es que funciona sin necesidad de escribir código en el cliente para manejar eventos en el servidor. A través de un sistema llamado RPC (Remote Procedure Call) de solo 2 KB, Brisa permite que las interacciones se procesen directamente desde el servidor. Esto no solo facilita el desarrollo, sino que también reduce significativamente la cantidad de código necesario en el cliente, mejorando así el rendimiento general de la aplicación.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;El Origen de Brisa&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;La idea de crear Brisa nació cuando Aral tuvo una conversación inspiradora con Misko Hevery, el creador de Quick.js, durante un evento en Barcelona. La visión de Misko y su enfoque para mejorar la performance en aplicaciones web dejaron una marca en Aral. &lt;br&gt;
"Fue como si estuviera hablando de su propio hijo", dice Aral, recordando la conversación. Inspirado por este nivel de pasión y compromiso, Aral decidió que él también quería crear un framework, pero no uno más en el montón; quería algo que resolviera problemas que él mismo había enfrentado.&lt;/p&gt;

&lt;p&gt;Así, comenzó a trabajar en Brisa, un proyecto que combina lo mejor de las interacciones en tiempo real entre cliente y servidor, y que es lo suficientemente ligero como para integrarse sin fricciones con otros componentes web. Brisa surgió como un experimento que luego se convirtió en un framework completo, diseñado para la eficiencia y la escalabilidad en la web moderna.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Principales Características de Brisa&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Server Actions sin Us Client y Us Server: En Brisa, todo el código se ejecuta en el servidor por defecto. Solo en situaciones específicas se habilita la ejecución en el cliente, lo cual simplifica la gestión del código y evita complejidades innecesarias.&lt;/p&gt;

&lt;p&gt;Components Web Integrados: Los web components de Brisa son completamente modulares y se pueden utilizar en otros frameworks. Esto significa que cualquier componente creado en Brisa se puede reutilizar en React, Angular, o Vue, brindando a los desarrolladores mayor flexibilidad.&lt;/p&gt;

&lt;p&gt;Signals Avanzados: Brisa implementa un sistema de signals para la gestión de estados. Estos signals permiten que los componentes se actualicen automáticamente cuando cambian sus propiedades, sin necesidad de un render completo de la página. Esto optimiza el rendimiento y reduce la carga del cliente.&lt;/p&gt;

&lt;p&gt;RPC de Solo 2 KB: Uno de los aspectos más innovadores de Brisa es su sistema de RPC de solo 2 KB, que permite a los desarrolladores manejar interacciones desde el servidor sin necesidad de escribir código en el cliente. Esto resulta en un código mucho más ligero y simplificado, mejorando la eficiencia general de las aplicaciones.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz16b8w84z3rlvxdrihxf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz16b8w84z3rlvxdrihxf.png" alt="Image description" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;¿Realmente Necesitamos Otro Framework?&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Es comprensible que algunos desarrolladores se pregunten si realmente necesitamos otro framework de JavaScript. La respuesta de Aral Roca es clara: "Con cada nuevo framework vienen nuevas oportunidades de mejorar y de aprender". &lt;/p&gt;

&lt;p&gt;A lo largo de los años, hemos visto cómo cada framework ha aportado algo diferente y único. React nos enseñó sobre el virtual DOM, Angular sobre la modularidad en gran escala, y Vue sobre la simplicidad en el desarrollo de componentes. Brisa, por su parte, nos muestra cómo combinar la potencia de la plataforma web y las técnicas de renderizado desde el servidor para optimizar aplicaciones modernas.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Aplicaciones de Brisa en el Mundo Real&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Las ventajas de Brisa son particularmente valiosas en aplicaciones de alto rendimiento, como aquellas que requieren de cargas rápidas y mínima latencia en el manejo de datos en tiempo real. &lt;strong&gt;Brisa permite que la&lt;/strong&gt; comunicación entre el cliente y el servidor sea mucho más eficiente, lo cual resulta ideal para aplicaciones que manejan un gran volumen de interacciones de usuario. &lt;br&gt;
Además, su integración con los web components facilita la creación de aplicaciones modulares que pueden integrarse con otras tecnologías sin problemas.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;La Comunidad Detrás de Brisa&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Aral también enfatiza la importancia de la comunidad en el desarrollo de Brisa. Para él, el apoyo y las contribuciones de otros desarrolladores han sido fundamentales. “La mejor parte de trabajar en Brisa ha sido la oportunidad de aprender de otros desarrolladores y ver cómo ellos aplican este framework en sus propios proyectos”, comenta Aral. Brisa es un framework abierto y siempre se anima a la comunidad a contribuir, ya sea con ideas, reporte de problemas, o nuevas funcionalidades.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;¿Qué Sigue para Brisa?&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Aral tiene grandes planes para el futuro de Brisa. Con la posibilidad de que los signals sean incluidos en la web platform oficial gracias a TC39, el comité encargado de la &lt;strong&gt;evolución de JavaScript&lt;/strong&gt;, Brisa está en una excelente posición para volverse aún más eficiente y ligero. Si se incluye la funcionalidad de signals en el lenguaje base, las aplicaciones que usen Brisa podrían beneficiarse de un rendimiento aún mayor, con menos dependencia de bibliotecas externas.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;¿Quieres Probar Brisa?&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Si eres un desarrollador curioso o te interesa conocer más sobre la &lt;strong&gt;arquitectura de aplicaciones web avanzadas&lt;/strong&gt;, te invito a darle una oportunidad a Brisa. No solo verás un enfoque innovador para resolver problemas de rendimiento y modularidad, sino que también podrás aprender sobre las últimas tendencias en el desarrollo de JavaScript.&lt;/p&gt;

&lt;p&gt;¡Comparte tus pensamientos en los comentarios! &lt;br&gt;
¿Te entusiasma la idea de un nuevo framework como Brisa? &lt;br&gt;
¿Qué piensas sobre el futuro de los frameworks de JavaScript? &lt;/p&gt;

&lt;p&gt;¡Espero tus opiniones!&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>brisabuild</category>
      <category>programador</category>
    </item>
    <item>
      <title>Microfrontends con Module Federation</title>
      <dc:creator>Bezael Pérez</dc:creator>
      <pubDate>Sun, 09 Apr 2023 12:37:02 +0000</pubDate>
      <link>https://dev.to/bezael/microfrontends-con-module-federation-1apg</link>
      <guid>https://dev.to/bezael/microfrontends-con-module-federation-1apg</guid>
      <description>&lt;p&gt;En los últimos años, las aplicaciones web han evolucionado hacia arquitecturas más complejas y modulares, lo que ha llevado a la aparición de nuevos conceptos y tecnologías que facilitan el desarrollo y mantenimiento de dichas aplicaciones. En este contexto, surge el concepto de &lt;strong&gt;microfrontends&lt;/strong&gt; y la técnica conocida como &lt;em&gt;Module Federation&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;A continuación, se presenta el concepto de microfrontends, se describe brevemente &lt;em&gt;Module Federation&lt;/em&gt; y se establece la importancia de combinar ambos enfoques para mejorar la calidad y escalabilidad de las aplicaciones web modernas.&lt;/p&gt;

&lt;p&gt;Los microfrontends son un enfoque arquitectónico que busca dividir una aplicación web en partes más pequeñas e independientes, llamadas micro-app, permitiendo que cada parte sea desarrollada, probada y desplegada de forma autónoma.  &lt;/p&gt;

&lt;p&gt;Esta técnica se inspira en la arquitectura de microservicios, ampliamente utilizada en el desarrollo de aplicaciones backend, y busca replicar sus ventajas en el ámbito del frontend.&lt;/p&gt;

&lt;p&gt;Ambos conceptos, microfrontends y microservicios, están estrechamente relacionados ya que buscan abordar problemas similares en sus respectivas capas de aplicación: mejorar la escalabilidad, la mantenibilidad y la flexibilidad.&lt;/p&gt;

&lt;p&gt;Algunos de los beneficios de utilizar microfrontends incluyen la mejora en la escalabilidad, la facilidad de mantenimiento y la posibilidad de emplear diferentes tecnologías en cada micro-app del frontend.&lt;/p&gt;

&lt;h2&gt;
  
  
  Module Federation
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Module Federation&lt;/strong&gt; es una característica introducida en Webpack 5 que permite a múltiples aplicaciones JavaScript compartir dependencias y módulos de código en tiempo de ejecución.&lt;br&gt;&lt;br&gt;
Esto significa que las aplicaciones pueden cargar módulos de otras aplicaciones de forma dinámica, sin la necesidad de incluirlos en su propio código fuente.&lt;/p&gt;

&lt;p&gt;Esta técnica permite que diferentes equipos de desarrollo colaboren de manera más eficiente y ahorren recursos al evitar la duplicación de código en aplicaciones separadas.&lt;/p&gt;

&lt;p&gt;La combinación de microfrontends con &lt;em&gt;Module Federation&lt;/em&gt; ofrece una serie de ventajas significativas para el desarrollo de aplicaciones web modernas.  &lt;/p&gt;

&lt;p&gt;Al utilizar microfrontends, se facilita la distribución del trabajo entre diferentes equipos, permitiendo que cada uno se enfoque en su respectiva parte de la aplicación.&lt;br&gt;&lt;br&gt;
Además, el uso de &lt;em&gt;Module Federation&lt;/em&gt; permite compartir y reutilizar módulos de código entre los distintos microfrontends, lo que optimiza el rendimiento y reduce la duplicidad de código.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1680862050174%2F4f7ea4c7-edfe-466b-8d5f-8ce5b618f93a.png%2520align%3D" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1680862050174%2F4f7ea4c7-edfe-466b-8d5f-8ce5b618f93a.png%2520align%3D"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Beneficios Microfrontend
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Escalabilidad:&lt;/strong&gt; Dividir la aplicación de front-end en componentes más pequeños permite a los equipos trabajar de manera más eficiente y escalar sus esfuerzos de desarrollo. Esto también permite a las organizaciones asignar recursos de manera más eficiente a diferentes partes de la aplicación, en función de las necesidades y prioridades del negocio.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mantenibilidad:&lt;/strong&gt; Los componentes de un microfrontend son más fáciles de mantener, ya que cada uno es autónomo y tiene su propio conjunto de responsabilidades. Esto facilita la actualización, la corrección de errores y la mejora de cada componente sin afectar a otros componentes o al sistema en general.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Desacoplamiento:&lt;/strong&gt; Los microfrontends promueven un enfoque de desarrollo desacoplado, en el que cada componente puede evolucionar de forma independiente y a su propio ritmo. Esto permite a los equipos de desarrollo adoptar diferentes tecnologías y prácticas sin afectar a otros equipos o componentes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Facilita la colaboración entre equipos:&lt;/strong&gt; Al dividir el front-end en componentes más pequeños, diferentes equipos pueden trabajar de forma independiente en diferentes partes de la aplicación. Esto reduce la necesidad de coordinación y comunicación entre equipos, lo que puede agilizar el proceso de desarrollo y mejorar la productividad.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reutilización de componentes:&lt;/strong&gt; Los microfrontends permiten la creación de componentes reutilizables que pueden ser compartidos entre diferentes partes de la aplicación o incluso entre diferentes aplicaciones. Esto puede reducir la duplicación de esfuerzos y mejorar la coherencia en la experiencia del usuario.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mejora la resiliencia:&lt;/strong&gt; Al aislar los componentes y evitar la interdependencia entre ellos, los microfrontends pueden mejorar la resiliencia de la aplicación. Si un componente falla, es menos probable que afecte a otros componentes o a la aplicación en su conjunto.&lt;/p&gt;

&lt;p&gt;Antes de pasar a la implementación técnica, debemos comprender algunos conceptos básicos para entender como funciona &lt;em&gt;module federation&lt;/em&gt;.  &lt;/p&gt;

&lt;p&gt;&lt;em&gt;Module federation&lt;/em&gt; permite que una aplicación JavaScript ejecute código de forma dinámica desde otro paquete (aplicación) o se construya tanto en el cliente como en el servidor.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Module federation&lt;/em&gt; proporciona dos conceptos clave que debemos comprender antes de trabajar con ellas.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Host&lt;/strong&gt;: El contenedor encargado de cargar, micro-frontends o componentes en tiempo de ejecución desde otras aplicaciones.&lt;br&gt;&lt;br&gt;
El &lt;em&gt;host&lt;/em&gt;, también puede ser conocido, como &lt;em&gt;container&lt;/em&gt; o &lt;em&gt;shell&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Remote&lt;/strong&gt;: El &lt;em&gt;bundle&lt;/em&gt; de javascript que queremos cargar dentro de un host.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tu primer Microfrontend
&lt;/h3&gt;

&lt;p&gt;Voy a utilizar un &lt;em&gt;package&lt;/em&gt; de npm llamado &lt;a href="https://www.npmjs.com/package/create-mf-app" rel="noopener noreferrer"&gt;&lt;code&gt;create-mf-app&lt;/code&gt;&lt;/a&gt;, realmente podríamos hacerlo manualmente.&lt;br&gt;&lt;br&gt;
De hecho si ya tienes tus aplicaciones creadas solo debes añadir el fichero &lt;em&gt;webpack.config.js&lt;/em&gt; en cada una de tu aplicación.&lt;/p&gt;

&lt;p&gt;Ejecuta este comando&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

npx i create-mf-app


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;A partir de ahora en la terminal, verás varias opciones.&lt;br&gt;&lt;br&gt;
La primera es el nombre de nuestra aplicación, yo le he llamado &lt;strong&gt;&lt;em&gt;container&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

? Pick the name of your app: &lt;span class="o"&gt;(&lt;/span&gt;host&lt;span class="o"&gt;)&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;El tipo de nuestra aplicación, aquí tenemos tres opciones. &lt;em&gt;Application, API Server y LIbrary.&lt;/em&gt;&lt;br&gt;&lt;br&gt;
Vamos a escoger la primera opción*.* &lt;strong&gt;&lt;em&gt;Application&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

? Project Type: &lt;span class="o"&gt;(&lt;/span&gt;Use arrow keys&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; Application
  API Server
  Library


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Ahora es el turno del puerto, vamos a elegir el puerto &lt;strong&gt;&lt;em&gt;4000&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

? Port number: &lt;span class="o"&gt;(&lt;/span&gt;8080&lt;span class="o"&gt;)&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Para este ejemplo, yo voy a utilizar dos aplicaciones de React, anque recuerda que puedes usar varios frameworks.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

? Framework: &lt;span class="o"&gt;(&lt;/span&gt;Use arrow keys&lt;span class="o"&gt;)&lt;/span&gt;
  lit-html
  mithril
  preact
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; react
  solid-js
  svelte
  vanilla


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Yo, voy a escoger &lt;strong&gt;&lt;em&gt;TypeScript&lt;/em&gt;&lt;/strong&gt; type safe 👨‍💻💪&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

? Language:
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; typescript
  javascript


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Los estilos para esta demo no tienen importancia, pero yo escogí &lt;strong&gt;&lt;em&gt;Tailwind&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

&lt;span class="sb"&gt;`&lt;/span&gt;? CSS: &lt;span class="o"&gt;(&lt;/span&gt;Use arrow keys&lt;span class="o"&gt;)&lt;/span&gt;
  CSS
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; Tailwind
&lt;span class="sb"&gt;```&lt;/span&gt;

Y finalmente ya tendremos nuestra aplicación, los siguentes pasos serán acceder a la carperta y ejecutar &lt;span class="sb"&gt;`&lt;/span&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm start&lt;span class="sb"&gt;`&lt;/span&gt;

&lt;span class="sb"&gt;```&lt;/span&gt;bash
Your &lt;span class="s1"&gt;'host'&lt;/span&gt; project is ready to go.

Next steps:

▶️ &lt;span class="nb"&gt;cd &lt;/span&gt;container
▶️ npm &lt;span class="nb"&gt;install&lt;/span&gt;
▶️ npm start
&lt;span class="sb"&gt;```&lt;/span&gt;

Bien, para la creación de la segunda aplicación los pasos serán los mismos, pero con otro nombre obviamente. En mi caso le he llamado &lt;span class="k"&gt;***&lt;/span&gt;remote.&lt;span class="k"&gt;***&lt;/span&gt;  
En tu editor de codigo favorito, abre el proyecto.

Y nos centraremos en el &lt;span class="k"&gt;***&lt;/span&gt;webpack.config.js&lt;span class="k"&gt;***&lt;/span&gt;, y principalmente en el apartado de plugins, en el &lt;span class="k"&gt;*&lt;/span&gt;plugin&lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;**&lt;/span&gt;ModuleFederationPlugin&lt;span class="k"&gt;**&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;  
Podremos ver varias propiedades que utiliza webpack, para configurar el comportamiento de nuestra aplicación.

&lt;span class="sb"&gt;```&lt;/span&gt;javascript
  plugins: &lt;span class="o"&gt;[&lt;/span&gt;
    new ModuleFederationPlugin&lt;span class="o"&gt;({&lt;/span&gt;
      name: &lt;span class="s2"&gt;"container"&lt;/span&gt;,
      filename: &lt;span class="s2"&gt;"remoteEntry.js"&lt;/span&gt;,
      remotes: &lt;span class="o"&gt;{}&lt;/span&gt;,
      exposes: &lt;span class="o"&gt;{}&lt;/span&gt;,
      shared: &lt;span class="o"&gt;{&lt;/span&gt;
        ...deps,
        react: &lt;span class="o"&gt;{&lt;/span&gt;
          singleton: &lt;span class="nb"&gt;true&lt;/span&gt;,
          requiredVersion: deps.react,
        &lt;span class="o"&gt;}&lt;/span&gt;,
        &lt;span class="s2"&gt;"react-dom"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
          singleton: &lt;span class="nb"&gt;true&lt;/span&gt;,
          requiredVersion: deps[&lt;span class="s2"&gt;"react-dom"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;,
        &lt;span class="o"&gt;}&lt;/span&gt;,
      &lt;span class="o"&gt;}&lt;/span&gt;,
    &lt;span class="o"&gt;})&lt;/span&gt;,
    new HtmlWebPackPlugin&lt;span class="o"&gt;({&lt;/span&gt;
      template: &lt;span class="s2"&gt;"./src/index.html"&lt;/span&gt;,
    &lt;span class="o"&gt;})&lt;/span&gt;,
  &lt;span class="o"&gt;]&lt;/span&gt;,
&lt;span class="sb"&gt;```&lt;/span&gt;

&lt;span class="k"&gt;**&lt;/span&gt;ModuleFederationPlugin&lt;span class="k"&gt;**&lt;/span&gt;, es un plugin de webpack que permite compartir módulos entre diferentes aplicaciones y proyectos en tiempo de ejecución, sin tener que empaquetarlos en cada proyecto.

El plugin facilita la integración de varias aplicaciones en una sola página, lo que mejora el rendimiento y la experiencia del usuario.

En la configuración del plugin, se establece el nombre de la aplicación actual como container, &lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;name&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="k"&gt;*&lt;/span&gt; el nombre del archivo de entrada remota como &lt;span class="k"&gt;***&lt;/span&gt;remoteEntry.js&lt;span class="k"&gt;***&lt;/span&gt;, y se definen las propiedades &lt;span class="k"&gt;***&lt;/span&gt;remotes&lt;span class="k"&gt;***&lt;/span&gt;, &lt;span class="k"&gt;***&lt;/span&gt;exposes&lt;span class="k"&gt;***&lt;/span&gt; y &lt;span class="k"&gt;***&lt;/span&gt;shared&lt;span class="k"&gt;***&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;

La propiedad &lt;span class="k"&gt;***&lt;/span&gt;remotes&lt;span class="k"&gt;***&lt;/span&gt; se utiliza para especificar los módulos que se importarán de forma remota. En este caso, se establece como un objeto vacío, lo que significa que no hay módulos remotos.

La propiedad &lt;span class="k"&gt;***&lt;/span&gt;exposes&lt;span class="k"&gt;***&lt;/span&gt; se utiliza para definir los módulos que se exponen a otros proyectos. En este caso, se establece como un objeto vacío, lo que significa que no se están exponiendo módulos.  

Vamos a crear un componente de React en nuestra aplicación &lt;span class="k"&gt;***&lt;/span&gt;remote&lt;span class="k"&gt;***&lt;/span&gt;

&lt;span class="sb"&gt;```&lt;/span&gt;typescript
// Header.tsx
import React, &lt;span class="o"&gt;{&lt;/span&gt; FC &lt;span class="o"&gt;}&lt;/span&gt; from &lt;span class="s1"&gt;'react'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

interface HeaderProps &lt;span class="o"&gt;{}&lt;/span&gt;

const Header: FC&amp;lt;HeaderProps&amp;gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &amp;lt;div &lt;span class="nv"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'p-5 bg-blue-600 text-white text-3xl'&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;Header V1&amp;lt;/div&amp;gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nb"&gt;export &lt;/span&gt;default Header&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="sb"&gt;```&lt;/span&gt;

Ahora lo voy añadir a mi &lt;span class="k"&gt;***&lt;/span&gt;App.tsx&lt;span class="k"&gt;***&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;aunque realmente esto no es importante para el ejemplo&lt;span class="o"&gt;)&lt;/span&gt;  
Ahora nuestra aplicación remote debe lucir algo como esto.

&lt;span class="o"&gt;![](&lt;/span&gt;https://cdn.hashnode.com/res/hashnode/image/upload/v1680882324209/64f93a21-735b-439b-bf45-fa6144d72c8e.png &lt;span class="nv"&gt;align&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"center"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;

Great 🙂

Ahora que tenemos nuestro componente Header lo vamos a exponer.  
Volvamos al fichero &lt;span class="k"&gt;***&lt;/span&gt;webpack.config.js&lt;span class="k"&gt;***&lt;/span&gt; y en la propiedad &lt;span class="k"&gt;***&lt;/span&gt;exposes&lt;span class="k"&gt;***&lt;/span&gt; añadimos lo siguiente.  

&lt;span class="sb"&gt;```&lt;/span&gt;javascript
      exposes: &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="s1"&gt;'./Header'&lt;/span&gt;: &lt;span class="s1"&gt;'./src/Header.tsx'&lt;/span&gt;,
      &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="sb"&gt;```&lt;/span&gt;

Con esto estamos exponiendo nuestro header para que pueda ser consumido por otras aplicaciones.

Para comprobar que se está sirviendo de la manera correcta, podemos acceder a la siguiente url &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="k"&gt;*&lt;/span&gt;http://localhost:4001/remoteEntry.js&lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="o"&gt;](&lt;/span&gt;http://localhost:4001/remoteEntry.js&lt;span class="o"&gt;)&lt;/span&gt;  
&lt;span class="k"&gt;**&lt;/span&gt;Asegurate de reiniciar el la aplicación&lt;span class="k"&gt;**&lt;/span&gt;

&lt;span class="o"&gt;![](&lt;/span&gt;https://cdn.hashnode.com/res/hashnode/image/upload/v1680893338479/cc17ed48-cf66-4415-ade4-98cc0f1e4bc5.png &lt;span class="nv"&gt;align&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"center"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;

Lo siguiente, que haremos es consumir el &lt;span class="k"&gt;*&lt;/span&gt;Header&lt;span class="k"&gt;*&lt;/span&gt; expuesto por el &lt;span class="k"&gt;*&lt;/span&gt;remote.&lt;span class="k"&gt;*&lt;/span&gt;

Volvamos al fichero &lt;span class="k"&gt;***&lt;/span&gt;webpack.config.js&lt;span class="k"&gt;***&lt;/span&gt; y en la propiedad &lt;span class="k"&gt;***&lt;/span&gt;remotes&lt;span class="k"&gt;***&lt;/span&gt; pero del &lt;span class="k"&gt;**&lt;/span&gt;container.&lt;span class="k"&gt;**&lt;/span&gt;

&lt;span class="sb"&gt;```&lt;/span&gt;javascript
  plugins: &lt;span class="o"&gt;[&lt;/span&gt;
    new ModuleFederationPlugin&lt;span class="o"&gt;({&lt;/span&gt;
      name: &lt;span class="s2"&gt;"container"&lt;/span&gt;,
      filename: &lt;span class="s2"&gt;"remoteEntry.js"&lt;/span&gt;,
      remotes: &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="s1"&gt;'components'&lt;/span&gt;: &lt;span class="s1"&gt;'remote@http://localhost:4001/remoteEntry.js'&lt;/span&gt;,
      &lt;span class="o"&gt;}&lt;/span&gt;,
      exposes: &lt;span class="o"&gt;{}&lt;/span&gt;,
      shared: &lt;span class="o"&gt;{&lt;/span&gt;
        ...deps,
        react: &lt;span class="o"&gt;{&lt;/span&gt;
          singleton: &lt;span class="nb"&gt;true&lt;/span&gt;,
          requiredVersion: deps.react,
        &lt;span class="o"&gt;}&lt;/span&gt;,
        &lt;span class="s2"&gt;"react-dom"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
          singleton: &lt;span class="nb"&gt;true&lt;/span&gt;,
          requiredVersion: deps[&lt;span class="s2"&gt;"react-dom"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;,
        &lt;span class="o"&gt;}&lt;/span&gt;,
      &lt;span class="o"&gt;}&lt;/span&gt;,
    &lt;span class="o"&gt;})&lt;/span&gt;,
    new HtmlWebPackPlugin&lt;span class="o"&gt;({&lt;/span&gt;
      template: &lt;span class="s2"&gt;"./src/index.html"&lt;/span&gt;,
    &lt;span class="o"&gt;})&lt;/span&gt;,
  &lt;span class="o"&gt;]&lt;/span&gt;,
&lt;span class="sb"&gt;```&lt;/span&gt;

Ahora estamos listos para utilizar nuestro componentes remote.

&lt;span class="sb"&gt;```&lt;/span&gt;typescript
// App.tsx  
import React from &lt;span class="s1"&gt;'react'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
import ReactDOM from &lt;span class="s1"&gt;'react-dom'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

import &lt;span class="s1"&gt;'./index.scss'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
import Header from &lt;span class="s1"&gt;'components/Header'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

const App &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;
  &amp;lt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &amp;lt;Header /&amp;gt;
    &amp;lt;div &lt;span class="nv"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'max-w-6xl mx-auto mt-10 text-3xl'&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &amp;lt;div&amp;gt;Name: container&amp;lt;/div&amp;gt;
      &amp;lt;div&amp;gt;Framework: react&amp;lt;/div&amp;gt;
      &amp;lt;div&amp;gt;Language: TypeScript&amp;lt;/div&amp;gt;
      &amp;lt;div&amp;gt;CSS: Tailwind&amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  &amp;lt;/&amp;gt;
&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
ReactDOM.render&lt;span class="o"&gt;(&lt;/span&gt;&amp;lt;App /&amp;gt;, document.getElementById&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'app'&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="sb"&gt;```&lt;/span&gt;

Modificamos la aplicación container, el componente &lt;span class="k"&gt;***&lt;/span&gt;App.tsx&lt;span class="k"&gt;***&lt;/span&gt;

&lt;span class="o"&gt;![](&lt;/span&gt;https://cdn.hashnode.com/res/hashnode/image/upload/v1680973639230/acf14603-1e78-4b78-87e9-1a9d5c32bf57.png &lt;span class="nv"&gt;align&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"center"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;

Deberías ver algo como esto en tu aplicación container.

Esto solo es un pequeña muestra de lo que podríamos lograr con Webpack 5 y &lt;span class="k"&gt;*&lt;/span&gt;Module federation.&lt;span class="k"&gt;*&lt;/span&gt;

&lt;span class="o"&gt;[&lt;/span&gt;Aquí tienes el reposito de la demo]&lt;span class="o"&gt;(&lt;/span&gt;https://github.com/domini-code/module-federation-react&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>modulefederation</category>
      <category>react</category>
      <category>microfrontends</category>
    </item>
    <item>
      <title>Mejorar el rendimiento de *ngFor con trackBy</title>
      <dc:creator>Bezael Pérez</dc:creator>
      <pubDate>Sat, 16 May 2020 11:51:52 +0000</pubDate>
      <link>https://dev.to/bezael/mejorar-el-rendimiento-de-ngfor-con-trackby-5m0</link>
      <guid>https://dev.to/bezael/mejorar-el-rendimiento-de-ngfor-con-trackby-5m0</guid>
      <description>&lt;p&gt;En un principio mi intención era hablar sobre la directiva ngFor*, mientras me documentaba y revisaba apuntes, recordé  trackBy. &lt;/p&gt;

&lt;p&gt;¿Por qué nos olvidamos de trackBy? &lt;/p&gt;

&lt;p&gt;La verdad que no lo sé, pero todo lo que pueda ayudar mejorar el perfomance de mi aplicación es bienvenido. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://i.giphy.com/media/3o7aTLkyh3yAG6DEuQ/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/3o7aTLkyh3yAG6DEuQ/giphy.gif" alt="ngFor angular"&gt;&lt;/a&gt;&lt;br&gt;
En este post vamos a hablar sobe trackBy, como nos puede ayudar a mejorar el rendimiento. &lt;/p&gt;

&lt;h2&gt;
  
  
  *ngFor
&lt;/h2&gt;

&lt;p&gt;La directiva ngFor es comúnmente usada para iterar sobre un array. &lt;br&gt;
ngFor exporta algunas variables locales que podemos utilizar durante la iteración actual: &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;index&lt;/strong&gt;: iteración de bucle actual para cada contexto del template.&lt;br&gt;
&lt;strong&gt;first&lt;/strong&gt;: valor booleano que indica si el elemento es el primero en la iteración.&lt;br&gt;
&lt;strong&gt;last&lt;/strong&gt;: valor booleano que indica si el elemento es el último en la iteración.&lt;br&gt;
&lt;strong&gt;even&lt;/strong&gt;: valor que indica si este elemento tiene un índice par.&lt;br&gt;
&lt;strong&gt;odd&lt;/strong&gt;: valor booleano que indica si este elemento tiene un índice impar.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fp75jj2oil3coy8ldzlyx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fp75jj2oil3coy8ldzlyx.png" alt="ngFor angular"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;En este ejemplo la directiva recorrería todo el array de items. &lt;br&gt;
Y se renderiza el contenido de item. &lt;/p&gt;

&lt;p&gt;Angular convertirá este código en lo siguiente. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fk7n9id1463dbn9o4qqty.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fk7n9id1463dbn9o4qqty.png" alt="ngFor angular"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Hasta aquí todo bien, ningún problema a la vista. &lt;/p&gt;

&lt;p&gt;Pero internamente cada vez se añada, modifiques o elimines un item del array, Angular a través de la directiva *ngFor volverá a redibujar completemente el DOM desde cero. Y esto es una operación costosa. &lt;/p&gt;

&lt;p&gt;En aplicaciones medianas o grandes esto puede llegar a ser un problema. &lt;/p&gt;

&lt;p&gt;Debería existir algo capaz de encargarse de realizar un seguimiento de los cambios en el array por lo tanto en el los elementos y que solo hiciera los cambios o el cambio en los items necesarios. &lt;br&gt;
&lt;a href="https://i.giphy.com/media/LMhbdKMCkxtP4hXLxJ/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/LMhbdKMCkxtP4hXLxJ/giphy.gif" alt="ngFor angular"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Y sí, existe y es trackBy. &lt;/p&gt;

&lt;p&gt;Vamos a mejorar nuestro ejemplo anterior. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fqavizw2i7fguklwlczye.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fqavizw2i7fguklwlczye.png" alt="performance"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fn5naoyyp8aavj0ng1xa3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fn5naoyyp8aavj0ng1xa3.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;En nuestro .ts&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fp9mzsr7kpq1zu9zmgrkp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fp9mzsr7kpq1zu9zmgrkp.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  trackBy
&lt;/h3&gt;

&lt;p&gt;Es una función que define cómo realizar un seguimiento de los cambios para los elementos en el iterable.&lt;/p&gt;

&lt;p&gt;Ahora cada vez que se agregue, mueva o elimine elementos en el array,&lt;br&gt;&lt;br&gt;
Cuando se agregan, mueven o eliminan elementos en el iterable, la directiva, sólo volverá a redibujar los items que han cambiado. &lt;/p&gt;

&lt;p&gt;Cuando se proporciona esta función, la directiva utiliza el resultado de llamar a esta función para identificar el nodo del elemento, en lugar de la identidad del objeto en sí.&lt;/p&gt;

&lt;p&gt;La función recibe dos entradas, el índice de iteración y el ID del objeto del nodo (item).&lt;/p&gt;

&lt;p&gt;Pues esto es todo por este post! &lt;br&gt;
Si te ha gustado recuerdo compartirlo. &lt;/p&gt;

&lt;p&gt;Hasta la próxima&lt;/p&gt;

</description>
      <category>angular</category>
      <category>ngfor</category>
      <category>trackby</category>
      <category>rendimientoangular</category>
    </item>
    <item>
      <title>Formularios reactivos en Angular 8 </title>
      <dc:creator>Bezael Pérez</dc:creator>
      <pubDate>Mon, 27 Jan 2020 18:57:33 +0000</pubDate>
      <link>https://dev.to/bezael/formularios-reactivos-en-angular-8-3hmm</link>
      <guid>https://dev.to/bezael/formularios-reactivos-en-angular-8-3hmm</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--95cbMYD3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/lcbt1z5gksg1xk61dee7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--95cbMYD3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/lcbt1z5gksg1xk61dee7.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Los formularios son parte esencial de nuestras aplicaciones.&lt;br&gt;
En Angular tenemos dos enfoques de formularios, Template forms y Reactive Forms. &lt;/p&gt;

&lt;p&gt;Desde un login, un formulario para reservar un hotel, o formularios para realizar trámites gubernamentales. Cualquiera de estos formularios lo podríamos hacer con Reactive Forms.  &lt;/p&gt;

&lt;p&gt;Aunque en una primera instancia para formularios más sencillos podríamos usar template forms. &lt;/p&gt;

&lt;p&gt;Y para casos más avanzados podemos usar reactive forms. &lt;/p&gt;

&lt;p&gt;Pero esto es solo son supuestos.&lt;/p&gt;

&lt;p&gt;Los formularios reactivos utilizan un enfoque explícito e inmutable.&lt;br&gt;
Los datos de dichos formularios están enlazados con nuestro objeto de manera sincrónica. &lt;/p&gt;

&lt;p&gt;Ya sabemos que tenemos dos opciones para trabajar con formularios en Angular; En esta ocasión me voy a centrar en los formularios reactivos.&lt;/p&gt;

&lt;p&gt;Los formularios reactivos son más robustos (escalables) que los &lt;br&gt;
formularios template driven.&lt;/p&gt;

&lt;p&gt;Si tu aplicación está basada en formularios, te recomiendo que utilizes formularios reactivos.&lt;/p&gt;

&lt;p&gt;Bueno, basta de hablarte de las bondades de los formularios reactivos y vamos a ver un ejemplo. &lt;/p&gt;

&lt;p&gt;Vamos a crear un formulario de contacto básico. Y guardaremos cada consulta en Firebase Cloud Firestore. &lt;/p&gt;

&lt;p&gt;Y por supuesto tendremos validaciones en nuestro formulario. &lt;br&gt;
Empecemos creando un nuevo proyecto de Angular. &lt;/p&gt;

&lt;p&gt;Yo voy a utilizar Angular 8, pero este ejemplo es compatible prácticamente al 100% en otras versiones. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ng new reactiveForms&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Para este ejemplo no necesitamos rutas.&lt;br&gt;
&lt;strong&gt;Would you like to add Angular routing? (y/N)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Para los estilos escogemos &lt;strong&gt;&lt;code&gt;SCSS&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Asegurate de estar dentro de la carpeta del proyecto y vamos a crear un componente.&lt;br&gt;&lt;br&gt;
Creamos un componente para pintar nuestro formulario. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;ng g c components/contact&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--brao3BED--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/0lysupj84yhc0crzp9pz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--brao3BED--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/0lysupj84yhc0crzp9pz.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Vamos al componente principal &lt;code&gt;app.component.html &lt;/code&gt;&lt;br&gt;
Sustituye todo lo que hay ahora por lo siguiente.&lt;br&gt;
&lt;strong&gt;&lt;code&gt; &lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Luego de este cambio podemos arrancar nuestro server.&lt;br&gt;
&lt;strong&gt;&lt;code&gt; ng serve &lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Ahora podemos acceder a la url &lt;a href="http://localhost:4200/"&gt;http://localhost:4200/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Nada fancy hasta ahora, pero ya estamos listos para trabajar con nuestro formulario. &lt;/p&gt;

&lt;p&gt;Ahora que tenemos nuestra app y el componente creado. &lt;/p&gt;

&lt;p&gt;Vamos a crearnos una aplicación en la consola de Firebase. &lt;/p&gt;

&lt;h3&gt;
  
  
  Es necesario que tengas una cuenta ya creada de Firebase
&lt;/h3&gt;

&lt;p&gt;Primero vamos a &lt;a href="https://console.firebase.google.com"&gt;https://console.firebase.google.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Verás algo como esto, es posible que tú lo veas distinto si no tienes ningún proyecto creado, como puedes ver yo tengo algunos proyectos. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--97jPAcon--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/pyx0mu85qwos09c9rt91.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--97jPAcon--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/pyx0mu85qwos09c9rt91.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Haz clic en &lt;strong&gt;AÑADIR PROYECTO&lt;/strong&gt;&lt;br&gt;
Elige un nombre para tu proyecto -&amp;gt; aceptas las condiciones y Hacemos clic en &lt;strong&gt;CREAR PROYECTO&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Luego de varias preguntas llegarás al proyecto. &lt;br&gt;
Podrás ver algo como esto.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ICZWD15K--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/i4t2rtcrgrhkxpp8bivl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ICZWD15K--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/i4t2rtcrgrhkxpp8bivl.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;En Firebase podemos trabajar con aplicaciones móviles Android e ios y por supuesto web. &lt;br&gt;
Vamos a hacer clic en icono de aplicaciones web (el tercero del recuadro).&lt;/p&gt;

&lt;p&gt;Elige una nombre para registrar tu app.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ULgRm5XN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/mc377mzjsts52ox7vj3q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ULgRm5XN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/mc377mzjsts52ox7vj3q.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Finalmente ya tenemos nuestra app creada, lo que  se puede ver en el pantallazo anterior, es la información que utilizaremos para conectarnos a Firebase. &lt;/p&gt;

&lt;p&gt;La parte importante para nosotros es el contenido de la variable &lt;code&gt;firebaseConfig&lt;/code&gt;&lt;br&gt;&lt;br&gt;
Asegurate de crear tu propia app. &lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--NrV-xJS5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/o3qiwd2a7xs49639957q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--NrV-xJS5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/o3qiwd2a7xs49639957q.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Debemos crear una base de datos. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--QKkazuYM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/ox2xdd225bmgg1tt450y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--QKkazuYM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/ox2xdd225bmgg1tt450y.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;En el menú de las izquierda selecciona Database y luego haz clic en Crear base de datos.&lt;/p&gt;

&lt;p&gt;Elige:  Empezar con el modo de prueba Y por último haremos clic en Listo.&lt;br&gt;
Una vez que ya tenemos los datos de configuración para conectarnos a firebase. &lt;/p&gt;

&lt;p&gt;Vamos a nuestro proyecto y en el fichero &lt;strong&gt;&lt;code&gt;environment.ts &lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--5uCuiLeB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/cz3y3mjbxqe936viob49.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--5uCuiLeB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/cz3y3mjbxqe936viob49.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Vamos a realizar la instalación de las dependencia de firebase y &lt;strong&gt;@angular/fire&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Desde nuestra consola ejecutamos el siguiente comando.&lt;br&gt;
Vamos a aprovechar las ventajas de los Schematics.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt; ng add @angular/fire &lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  OJO, el proyecto debes estar creado en firebase console
&lt;/h3&gt;

&lt;p&gt;Con este comando instalamos varios packages. &lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--oXq-Zw_r--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/wq51wsrj7alum3snll26.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--oXq-Zw_r--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/wq51wsrj7alum3snll26.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Nota: Este tutorial está realizado en la versión &lt;strong&gt;5.5.7&lt;/strong&gt; de firebase y &lt;strong&gt;5.3.0&lt;/strong&gt; de @angular/fire &lt;/p&gt;

&lt;p&gt;Ahora vamos al fichero &lt;code&gt;app.module&lt;/code&gt; para realizar unas importaciones. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--K0y6KWoV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/n0x98k2eyyb5yf8p54ji.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--K0y6KWoV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/n0x98k2eyyb5yf8p54ji.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Importamos dos módulos de &lt;strong&gt;@angular/fire&lt;/strong&gt;, el fichero de environment y el module que nos va permitir trabajar con formularios reactivos. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--dpgHD5MK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/uh3bd6zgpggaqf9ixc1h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--dpgHD5MK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/uh3bd6zgpggaqf9ixc1h.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;En el apartado de imports inyectamos el módulo de formularios reactivos, el Firestore Module e inicializamos el AngularFire Module pasandole nuestro fichero de configuración. &lt;/p&gt;

&lt;p&gt;Este sería el resultado final. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--7SbxPMB---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/plscu0cpi2v1t6a9u7pk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--7SbxPMB---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/plscu0cpi2v1t6a9u7pk.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Necesitamos crear un service, para crear los métodos que vamos a utilizar para guardar las consultas de nuestro formulario. &lt;/p&gt;

&lt;p&gt;Desde la consola ejecutamos el comando &lt;br&gt;
&lt;strong&gt;&lt;code&gt; ng g s services/data &lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--4KZ4gfTJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/mpupbzsao5u281e6g9xh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--4KZ4gfTJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/mpupbzsao5u281e6g9xh.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ahora abrimos el fichero &lt;code&gt;data.service.ts &lt;/code&gt;&lt;br&gt;
Y realizamos las siguientes importaciones. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--dIfLk3-K--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/si5c0p2jwiikx3dumzj0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--dIfLk3-K--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/si5c0p2jwiikx3dumzj0.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A continuación declaramos una propiedad &lt;br&gt;
&lt;code&gt; contactCollection &lt;/code&gt;&lt;/p&gt;

&lt;p&gt;En el método constructor le asignamos nuestra colección contacts, que será donde guardaremos la información. &lt;/p&gt;

&lt;p&gt;Creamos el método saveMessage, para guardar en nuestra colección. &lt;/p&gt;

&lt;p&gt;Este sería el resultado final del &lt;code&gt;data.service.ts  &lt;/code&gt;&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--3QhkbHg3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/ki8mryryicjk24nbz5ag.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--3QhkbHg3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/ki8mryryicjk24nbz5ag.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  En el componente
&lt;/h2&gt;

&lt;p&gt;En el componente &lt;code&gt; contact.component.ts &lt;/code&gt; &lt;br&gt;
Este sería el resultado final del fichero,  a continuación paso a comentar los más destacado. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--iXUo0Bqz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dominicode.com/wp-content/uploads/finalContact.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--iXUo0Bqz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dominicode.com/wp-content/uploads/finalContact.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;En la línea 2 &amp;amp; 3 hago las importaciones del DataService y también necesitamos importar &lt;strong&gt;&lt;code&gt;FormControl, FormGroup &amp;amp; Validators &lt;/code&gt;&lt;/strong&gt; para trabajar con el módulo de formulario reactivo. &lt;/p&gt;

&lt;p&gt;Creamos una propiedad llamada contactForm de tipo FormGroup.&lt;br&gt;
La propiedad emailPattern, es tan solo una Regex para validar si el usuario ha introducido una email válido.&lt;/p&gt;

&lt;p&gt;Luego, lo más destacable puede el método createForm donde creamos la instancia del formulario y asignamos la validaciones pertinentes. &lt;br&gt;
Las propias validaciones en sí, son muy descriptivas. &lt;/p&gt;

&lt;p&gt;Todos nuestros campos serán requeridos y con el método &lt;strong&gt;minLength()&lt;/strong&gt;, indicamos el número mínimo para este campo.  &lt;/p&gt;

&lt;p&gt;También tenemos un método para hacer el reset de nuestro formulario, y onSaveForm para llamar el método &lt;strong&gt;saveMessage&lt;/strong&gt; y le pasamos todo nuestro formulario. &lt;/p&gt;

&lt;p&gt;Procedemos a trabajar con el HTML. &lt;br&gt;
En el fichero &lt;strong&gt;&lt;code&gt; contact.component.html &lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
Vamos a tener el html. Aquí no hay mucho que explicar. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--XxGtR2zx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dominicode.com/wp-content/uploads/htmlContact.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--XxGtR2zx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dominicode.com/wp-content/uploads/htmlContact.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Los más reseñable es la linea 3 &lt;br&gt;
&lt;strong&gt;&lt;code&gt;  &lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
Donde enlazamos nuestra instancia con el formulario. &lt;/p&gt;

&lt;p&gt;Y cuando ocurre el evento onSubmit llamamos al método &lt;strong&gt;onSaveForm&lt;/strong&gt;.&lt;br&gt;
Si arrancas la aplicación verás que no luce muy bien. &lt;/p&gt;

&lt;p&gt;Vamos a añadir estilos. Este será el resultado final y recuerda que tienes el repo, donde puedes copiar estos estilos. &lt;br&gt;
 &lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--3PX05cwX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dominicode.com/wp-content/uploads/cssContact.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--3PX05cwX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dominicode.com/wp-content/uploads/cssContact.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Arranca el server y vamos a probar nuestra app.&lt;br&gt;
Como puedes ver el resultado es un formulario básico, que guarda las consultas en Firebase. &lt;/p&gt;

&lt;p&gt;Tienes la &lt;a href="https://formulariosreactivos-85287.firebaseapp.com"&gt;demo aquí&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Si quieres ver &lt;a href="https://www.youtube.com/watch?v=vLfn5dqzbYA&amp;amp;list=PL_9MDdjVuFjEOZwlkucIUKeBILv-gLeN_&amp;amp;ab_channel=DominiCode"&gt;el video en Youtube&lt;/a&gt;, te dejo el link por aquí, también tienes &lt;a href="https://github.com/bezael/angular-reactive-form"&gt;el código aquí&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Un saludo y no olvides compartir. Gracias!!!&lt;/p&gt;

</description>
      <category>angular</category>
      <category>firebase</category>
      <category>reactiveforms</category>
      <category>angular8</category>
    </item>
  </channel>
</rss>
