<?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: Kus Cámara</title>
    <description>The latest articles on DEV Community by Kus Cámara (@kuscamara).</description>
    <link>https://dev.to/kuscamara</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%2F151069%2Fffb4e069-8987-4ab4-8c2e-983f227d6c96.jpg</url>
      <title>DEV Community: Kus Cámara</title>
      <link>https://dev.to/kuscamara</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kuscamara"/>
    <language>en</language>
    <item>
      <title>Creación de agentes AI con PydanticAI – Introducción</title>
      <dc:creator>Kus Cámara</dc:creator>
      <pubDate>Wed, 05 Nov 2025 01:35:33 +0000</pubDate>
      <link>https://dev.to/kuscamara/creacion-de-agentes-ai-con-pydanticai-introduccion-h8</link>
      <guid>https://dev.to/kuscamara/creacion-de-agentes-ai-con-pydanticai-introduccion-h8</guid>
      <description>&lt;p&gt;En los últimos meses he tenido la suerte de explorar el mundo de los agentes AI, concretamente utilizando la librería &lt;a href="https://ai.pydantic.dev/" rel="noopener noreferrer"&gt;PydanticAI&lt;/a&gt;. A fecha de la escritura de este post, no hay mucha información técnica en español sobre el tema, y dado que la librería es relativamente “nueva”, he decidido escribir una serie de posts para compartir lo que voy aprendiendo. &lt;/p&gt;

&lt;p&gt;DISCLAIMER: No soy una experta, por lo que deberías tomar este post con la misma cautela que una respuesta de ChatGPT 😅&lt;/p&gt;

&lt;h2&gt;
  
  
  Concepto de Agente AI
&lt;/h2&gt;

&lt;p&gt;Definir qué es exactamente un agente no es tarea sencilla. Si te das una vuelta por LinkedIn, encontrarás fácilmente debates sobre si algo es realmente un agente, un workflow, o si tus agentes son menos agentes que los míos por haber usado herramientas &lt;em&gt;low-code&lt;/em&gt;. Fuentes como HuggingFace, nos hablan incluso de &lt;a href="https://huggingface.co/docs/smolagents/conceptual_guides/intro_agents" rel="noopener noreferrer"&gt;niveles de agencia&lt;/a&gt; en lugar de establecer una clasificación binaria.&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%2Fw0ounpkrfbflb0odgwxn.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%2Fw0ounpkrfbflb0odgwxn.png" alt="Captura de pantalla de una tabla comparativa de niveles de agencia en HuggingFace" width="800" height="408"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Polémicas aparte, para el propósito de este post, nos quedaremos con la siguiente definición. Un agente es la combinación de los siguientes componentes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Un LLM&lt;/strong&gt; (GPT, Claude, Gemini, etc.) -&amp;gt; el cerebro del agente. Responsable de procesar instrucciones en lenguaje natural, tomar decisiones sobre acciones a realizar y utilizar herramientas para interactuar con su entorno.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Un &lt;em&gt;system prompt&lt;/em&gt;&lt;/strong&gt; -&amp;gt; un conjunto de instrucciones que configuran el comportamiento del LLM, definiendo su propósito y especialización, tono, medidas de seguridad (&lt;em&gt;guardrails&lt;/em&gt;), etc.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Herramientas (&lt;em&gt;tools&lt;/em&gt;)&lt;/strong&gt; -&amp;gt; funciones que el agente puede utilizar para llevar a cabo tareas específicas, como acceder a bases de datos, realizar búsquedas en la web o interactuar con APIs.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Probar LLMs sin arruinarse
&lt;/h2&gt;

&lt;p&gt;Sabiendo que lo primero que necesitamos es un LLM y que los más populares no son baratos, nos interesa encontrar formas de experimentar sin dejarnos una pasta.&lt;/p&gt;

&lt;p&gt;Existen varias opciones que podemos probar:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;a href="https://docs.github.com/es/github-models" rel="noopener noreferrer"&gt;GitHub Models&lt;/a&gt;:&lt;/strong&gt; GitHub te permite &lt;a href="https://docs.github.com/es/github-models/use-github-models/prototyping-with-ai-models#experimenting-with-ai-models-using-the-api" rel="noopener noreferrer"&gt;experimentar de forma gratuita&lt;/a&gt; (con limitaciones de uso) con varios modelos LLM alojados en su plataforma. Es una excelente opción para empezar sin necesidad de configurar nada localmente. Solo necesitas una cuenta de GitHub y una API key.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;a href="https://aistudio.google.com/" rel="noopener noreferrer"&gt;Google AI Studio&lt;/a&gt;:&lt;/strong&gt; Google también permite probar varios modelos de la serie Gemini de forma gratuita (con limitaciones) mediante Google AI Studio. Los modelos Gemini destacan por ofrecer una ventana de contexto enorme y un buen equilibrio entre rendimiento y coste. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;a href="https://ollama.com/" rel="noopener noreferrer"&gt;Ollama&lt;/a&gt;:&lt;/strong&gt; Si prefieres trabajar completamente offline, Ollama te permite ejecutar modelos LLM de forma local en tu máquina. Eso sí, es importante elegir modelos adecuados para agentes (con capacidad de &lt;em&gt;thinking&lt;/em&gt; y uso de tools) y que puedan funcionar con fluidez en tu equipo. En mi caso, usando un MacBook Air M3 con 16GB de RAM, los modelos con mejor rendimiento son &lt;strong&gt;qwen2.5:7b-instruct&lt;/strong&gt;, &lt;strong&gt;llama3.1:8b&lt;/strong&gt;, &lt;strong&gt;gemma2:9b&lt;/strong&gt; y &lt;strong&gt;mistral:7b-instruct&lt;/strong&gt;. Si bien estos modelos están lejos de la calidad de los grandes LLMs comerciales, pueden ser más que suficientes para experimentar y aprender.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://ai.pydantic.dev/models/overview/" rel="noopener noreferrer"&gt;PydanticAI soporta estas tres opciones y otras&lt;/a&gt; como Anthropic, HuggingFace, OpenAI, etc.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creando un agente con PydanticAI
&lt;/h2&gt;

&lt;p&gt;Una vez que tenemos un LLM y alguna idea de agente que queremos construir, podemos ponernos manos a la obra con PydanticAI. &lt;/p&gt;

&lt;p&gt;En este ejemplo vamos a hacer algo sencillo y poco original: un agente que recomienda un plato de cocina en función de los ingredientes que el usuario le proporcione. Se llamará &lt;strong&gt;DelicIA&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Configuración del proyecto Python e instalación de dependencias
&lt;/h3&gt;

&lt;p&gt;He usado &lt;a href="https://docs.astral.sh/uv/" rel="noopener noreferrer"&gt;uv&lt;/a&gt; como gestor de dependencias y entornos virtuales, pero puedes usar el que prefieras y adaptar los comandos según corresponda.&lt;/p&gt;

&lt;p&gt;Crear un proyecto nuevo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uv init delicia-agent
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instalación de dependencias:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uv add pydantic_ai
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Creación del entorno virtual y activación:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uv venv .venv
&lt;span class="nb"&gt;source&lt;/span&gt; .venv/bin/activate  &lt;span class="c"&gt;# Linux/Mac&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Configuración del agente
&lt;/h2&gt;

&lt;p&gt;El archivo &lt;code&gt;main.py&lt;/code&gt; se genera automáticamente al crear el proyecto con &lt;code&gt;uv&lt;/code&gt;.&lt;br&gt;&lt;br&gt;
Como LLM he optado por usar GitHub Models con &lt;code&gt;gpt-4o&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# main.py
&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pydantic_ai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Agent&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pydantic_ai.models.openai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;OpenAIChatModel&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pydantic_ai.providers.github&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;GitHubProvider&lt;/span&gt;

&lt;span class="c1"&gt;# Configuración del modelo LLM usando GitHub Models
&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OpenAIChatModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;openai/gpt-4o&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;provider&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;GitHubProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;github_pat_XXXXXXXXXXXXXXXXXXXXXX&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Definición del agente
&lt;/span&gt;&lt;span class="n"&gt;delicia&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;DelicIA&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;# System prompt
&lt;/span&gt;    &lt;span class="n"&gt;instructions&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;You are DelicIA, an AI specialized in providing delicious and healthy recipes. &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Your goal is to help users find recipes that can be made with the ingredients they provide. &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Provide a concise response with the recipe name and the preparation steps.&lt;/span&gt;&lt;span class="sh"&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;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;delicia&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_cli&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prog_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;🥘 DelicIA&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Estamos usando el método &lt;code&gt;to_cli()&lt;/code&gt; de PydanticAI para interactuar con el agente desde la línea de comandos. Al ejecutar el script, se nos presentará un prompt para introducir los ingredientes, y el agente nos responderá con una recomendación de plato siguiendo las instrucciones definidas en el &lt;em&gt;system prompt&lt;/em&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ejecución del agente
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uv run python main.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Demo de ejecución&lt;/strong&gt;&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%2Fawvzyp723lazxpzy27ax.gif" 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%2Fawvzyp723lazxpzy27ax.gif" alt="Demostración de la ejecución del agente DelicIA en la línea de comandos" width="760" height="422"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Acceso a herramientas (&lt;em&gt;tools&lt;/em&gt;)
&lt;/h2&gt;

&lt;p&gt;Hasta ahora hemos podido materializar en el código dos de los componentes clave de un agente: el &lt;strong&gt;LLM y el &lt;em&gt;system prompt&lt;/em&gt;&lt;/strong&gt; con instrucciones básicas. Podríamos extender el &lt;em&gt;system prompt&lt;/em&gt; mucho más con instrucciones para evitar respuestas no deseadas, ejemplos del formato de las respuestas a modo de &lt;em&gt;few-shot learning&lt;/em&gt;, etc. Sin embargo, para que un agente sea realmente útil, y agente, necesita poder interactuar con su entorno a través de herramientas (&lt;em&gt;tools&lt;/em&gt;).&lt;/p&gt;

&lt;p&gt;Hay varias formas de &lt;a href="https://ai.pydantic.dev/tools/" rel="noopener noreferrer"&gt;definir herramientas en PydanticAI&lt;/a&gt;. En nuestro caso vamos a usar una &lt;em&gt;function tool&lt;/em&gt; que simulará una consulta a una base de datos de recetas. Por simplicidad, usaremos un diccionario con un mapeo de ingredientes a recetas.&lt;/p&gt;

&lt;p&gt;Para declarar una función como &lt;em&gt;tool&lt;/em&gt; podemos usar el decorador &lt;code&gt;@tool&lt;/code&gt;, si necesitamos acceso al contexto del agente, o &lt;code&gt;@tool_plain&lt;/code&gt; si no necesitamos acceso al contexto. En nuestro caso, usaremos &lt;code&gt;@tool_plain&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@delicia.tool_plain&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_recipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ingredient&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    Returns a dish recipe based on the provided ingredient.
    Args:
        ingredient (str): The main ingredient for the recipe.
    Returns:
        str: A recipe suggestion or a message indicating no pre-established recipe is available.
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;recipes_dict&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;patatas&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Tortilla de patatas&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tomate&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Gazpacho andaluz&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pollo&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Pollo al ajillo&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;arroz&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Paella valenciana&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;huevos&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Huevos revueltos con jamón&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pescado&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Pescado a la plancha con limón&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pasta&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Espaguetis carbonara&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;ingredient_lower&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ingredient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;ingredient_lower&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;recipes_dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;recipes_dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ingredient_lower&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;croquetas&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;¿Cómo sabe el agente qué hace esta herramienta y qué parámetros necesita?&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;Mediante la documentación de la función y los argumentos proporcionados en el docstring. PydanticAI utiliza esta información para generar un &lt;em&gt;schema&lt;/em&gt; que describe la herramienta y sus parámetros. El LLM es el que se encarga de razonar sobre lo que debe hacer y las herramientas que tiene a su disposición para llevar a cabo las tareas solicitadas por el usuario.&lt;/p&gt;

&lt;p&gt;Dependiendo del modelo que uses, puede que necesites ser más explícito en el &lt;em&gt;system prompt&lt;/em&gt; e indicar que debe usar las herramientas disponibles. Incluso puede ser necesario incluir ejemplos de uso para guiar al modelo sobre cómo invocarlas correctamente y con qué parámetros. En nuestro caso con GPT-4o no ha sido necesario.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Demo de ejecución con tool&lt;/strong&gt;&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%2Fg2h60p2ch6phez6g789t.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%2Fg2h60p2ch6phez6g789t.png" alt="Demostración de la ejecución del agente DelicIA usando la tool get_recipe" width="800" height="287"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Esta parte puede resultar un poco confusa. Nuestra &lt;em&gt;tool&lt;/em&gt; con una consulta ficticia a una base de datos devuelve solamente el nombre del plato. Sin embargo, la respuesta que estamos obteniendo del agente es mucho más completa, incluyendo los pasos de preparación. Lo que ocurre aquí es que el LLM incorpora la información devuelta por la tool en su razonamiento y genera una respuesta más elaborada con el objetivo de cumplir la instrucción dada en el &lt;em&gt;system prompt&lt;/em&gt;, que es proporcionar una receta completa.&lt;/p&gt;

&lt;h3&gt;
  
  
  Otros usos de las &lt;em&gt;tools&lt;/em&gt;
&lt;/h3&gt;

&lt;p&gt;Las &lt;em&gt;tools&lt;/em&gt; no se limitan a obtener datos de fuentes externas. También son ideales para realizar cálculos, formatear datos o cualquier tarea que requiera la ejecución de código.&lt;/p&gt;

&lt;p&gt;Por ejemplo, si necesitamos la fecha actual, es mucho más eficiente y fiable usar una &lt;em&gt;tool&lt;/em&gt; que llame a &lt;code&gt;datetime.now()&lt;/code&gt; que delegarlo al LLM. El LLM, muy probablemente, nos devolverá una fecha inventada en el pasado (los LLMs no tienen acceso a información en tiempo real) y además estaríamos consumiendo tokens innecesariamente. En general, cualquier operación determinista que pueda resolverse con código es una buena candidata para ser implementada como &lt;em&gt;tool&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Salida estructurada
&lt;/h2&gt;

&lt;p&gt;Ya podemos decir que tenemos un agente, sin embargo, me gustaría mostrar una característica más de PydanticAI: la capacidad de definir un formato específico para la salida del agente. Esto puede ser útil cuando queremos que la salida del agente pueda ser consumida programáticamente por otras herramientas.&lt;/p&gt;

&lt;p&gt;Para ello, vamos a necesitar &lt;a href="https://docs.pydantic.dev/latest/" rel="noopener noreferrer"&gt;Pydantic&lt;/a&gt; para definir un modelo que represente la estructura de la salida que queremos. &lt;/p&gt;

&lt;p&gt;Instalamos la dependencia:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uv add pydantic
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;En nuestro caso, definiremos un modelo &lt;code&gt;RecipeOutput&lt;/code&gt; con los campos &lt;code&gt;name&lt;/code&gt; y &lt;code&gt;preparation_steps&lt;/code&gt;. Podríamos añadir campos como &lt;code&gt;cooking_time&lt;/code&gt;, &lt;code&gt;difficulty&lt;/code&gt;, &lt;code&gt;ingredients&lt;/code&gt;, etc..&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pydantic&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BaseModel&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RecipeOutput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseModel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;preparation_steps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Luego, al definir el agente, podemos usar el parámetro &lt;code&gt;output_type&lt;/code&gt; para especificar que queremos que la salida del agente siga la estructura definida en &lt;code&gt;RecipeOutput&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;delicia&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;DelicIA&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;instructions&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;You are DelicIA, an AI specialized in providing delicious and healthy recipes. &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Your goal is to help users find recipes that can be made with the ingredients they provide. &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Provide a concise response with the recipe name and the preparation steps.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;output_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;RecipeOutput&lt;/span&gt;  &lt;span class="c1"&gt;# Especificamos el tipo de salida
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Al ejecutar el agente, la respuesta será una instancia de &lt;code&gt;RecipeOutput&lt;/code&gt;, que podemos serializar fácilmente a JSON o manipular directamente como un objeto Python.&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%2Fdt8lg7tn72txrci43hy3.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%2Fdt8lg7tn72txrci43hy3.png" alt="Captura de pantalla de la salida del agente con formato estructurado" width="800" height="286"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusión y próximos pasos
&lt;/h2&gt;

&lt;p&gt;Hemos visto cómo crear un agente AI básico utilizando PydanticAI, incluyendo la configuración del LLM, el &lt;em&gt;system prompt&lt;/em&gt;, la definición de herramientas (&lt;em&gt;tools&lt;/em&gt;) y la estructuración de la salida utilizando Pydantic.&lt;/p&gt;

&lt;p&gt;En próximos posts veremos temas como:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Uso de MCPs&lt;/li&gt;
&lt;li&gt;Instrumentación mediante Pydantic Logfire&lt;/li&gt;
&lt;li&gt;Estrategias de reintentos para gestionar &lt;em&gt;rate limits&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;Optimización de mensajes para ajustarse a las ventanas de contexto de los LLMs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Si te interesan estos temas y estás empezando, no puedo dejar de recomendar los &lt;a href="https://huggingface.co/learn" rel="noopener noreferrer"&gt;cursos gratuitos de HuggingFace&lt;/a&gt;.&lt;/p&gt;

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

</description>
      <category>ai</category>
      <category>python</category>
      <category>spanish</category>
      <category>pydanticai</category>
    </item>
    <item>
      <title>Píldoras TypeScript: type narrowing con "as const"</title>
      <dc:creator>Kus Cámara</dc:creator>
      <pubDate>Sat, 03 Aug 2024 21:07:45 +0000</pubDate>
      <link>https://dev.to/kuscamara/pildoras-typescript-type-narrowing-con-as-const-g5</link>
      <guid>https://dev.to/kuscamara/pildoras-typescript-type-narrowing-con-as-const-g5</guid>
      <description>&lt;p&gt;En muchas ocasiones te habrás encontrado con un error de tipo "string no es asignable a... Whatever" al asignar a una variable un valor compatible con el tipo esperado. Esto &lt;strong&gt;ocurre cuando TypeScript infiere un tipo más amplio que el que se espera&lt;/strong&gt; para una variable, como por ejemplo, cuando asignamos un string a una variable que debería ser de tipo literal.&lt;/p&gt;

&lt;p&gt;Para entenderlo mejor, vamos a verlo con un ejemplo. &lt;/p&gt;

&lt;p&gt;Tenemos un tipo &lt;code&gt;Severity&lt;/code&gt; definido como un string unión de los valores: "low", "medium" y "high".&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="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Severity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;low&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;medium&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;high&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Podríamos decir que &lt;code&gt;Severity&lt;/code&gt; es un subtipo de &lt;code&gt;string&lt;/code&gt; que, de todos los strings posibles, solo admite esos tres.&lt;/p&gt;

&lt;p&gt;Por otra parte tenemos un objeto &lt;code&gt;messages&lt;/code&gt; en el que las claves son un &lt;code&gt;Severity&lt;/code&gt; y los valores son strings.&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Severity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;low&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;😕 not good&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;medium&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;😖 ugly&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;high&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;😱 AAAAHHH!!!&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Queremos acceder a los valores de &lt;code&gt;messages&lt;/code&gt; y para ello definimos una variable &lt;code&gt;severity&lt;/code&gt; con el valor "low".&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="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;severity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;low&lt;/span&gt;&lt;span class="dl"&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;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;severity&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="c1"&gt;//          ^? Error: Element implicitly has an 'any' type because&lt;/span&gt;
&lt;span class="c1"&gt;// expression of type 'string' can't be used to index type &lt;/span&gt;
&lt;span class="c1"&gt;// 'Record&amp;lt;Severity, string&amp;gt;'.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Aunque el valor que hemos asignado a la variable &lt;code&gt;severity&lt;/code&gt; es compatible con el tipo &lt;code&gt;Severity&lt;/code&gt;, TypeScript nos muestra un error que viene a decirnos que las claves de &lt;code&gt;messages&lt;/code&gt; deben ser de tipo &lt;code&gt;Severity&lt;/code&gt; y no de tipo &lt;code&gt;string&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Para entender por qué ocurre esto, &lt;strong&gt;necesitamos entender cómo TypeScript infiere los tipos de las variables a las que no asignamos un tipo explícitamente&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Si hacemos hover en nuestro editor o en el &lt;a href="https://www.typescriptlang.org/play/?#code/C4TwDgpgBAyhBuEBOBLUUC8UDkAbA9gO7ZQA+OAthACYoCuFJ52AFigOYvYBQ3AxvgB2AZ2BQqw4QEN2EYQC4oAJQgCk1ADxxEqUABooo1IPYA+TFADe3KDgLFF2QLwbgVD2og-GPb581bHptKGnpGRydAND2oOnZcEH9A1g4uMMBGPagAQUz0gAlcgEICngBfXlwIMWEEZDQQCzwiHm4JaVlhAG1KnRqAXV4+-qA" rel="noopener noreferrer"&gt;Playground de TypeScript&lt;/a&gt; sobre &lt;code&gt;severity&lt;/code&gt; veremos que TypeScript lo infiere como &lt;code&gt;string&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Esto es así porque hemos declarado &lt;code&gt;severity&lt;/code&gt; usando &lt;code&gt;let&lt;/code&gt; y por lo tanto podemos reasignarle un valor que no sea compatible con &lt;code&gt;Severity&lt;/code&gt; en cualquier momento.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwijj64n3sxd36ihjghgs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwijj64n3sxd36ihjghgs.png" alt="Tipo string inferido para la variable severity declarada con let" width="625" height="283"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;En estos casos, &lt;strong&gt;TypeScript aplica lo que se conoce como "&lt;em&gt;widening&lt;/em&gt;" y asigna a la variable el tipo más amplio&lt;/strong&gt; que sea compatible con el valor que le hemos asignado inicialmente. &lt;/p&gt;

&lt;p&gt;Ojo, digo "inicialmente" porque TypeScript espera que cambiemos el valor de una variable en &lt;em&gt;runtime&lt;/em&gt;, pero no su tipo (de &lt;code&gt;string&lt;/code&gt; a &lt;code&gt;number&lt;/code&gt;, etc.). Si esta es nuestra intención, debemos especificarlo explícitamente.&lt;/p&gt;

&lt;p&gt;No ocurre lo mismo si declaramos la variable con &lt;code&gt;const&lt;/code&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;severity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;low&lt;/span&gt;&lt;span class="dl"&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;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;severity&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="c1"&gt;// OK&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Puesto que el uso de &lt;code&gt;const&lt;/code&gt; no permite la reasignación de la variable, TypeScript infiere el tipo como el tipo literal "low" y el error desaparece.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft9zd310er9p8el81vjjk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft9zd310er9p8el81vjjk.png" alt="Tipo literal " width="625" height="283"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Por lo tanto, como regla general (con alguna excepción), &lt;strong&gt;TypeScript infiere el tipo más amplio para las variables de tipo primitivo declaradas con &lt;code&gt;let&lt;/code&gt; y el tipo literal para las variables declaradas con &lt;code&gt;const&lt;/code&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Esto en cuanto a las variables de tipo primitivo, pero &lt;strong&gt;¿qué ocurre con los objetos?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Vamos a verlo con otro ejemplo.&lt;/p&gt;

&lt;p&gt;Tenemos una interfaz &lt;code&gt;Issue&lt;/code&gt; con estas propiedades:&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="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Issue&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;severity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Severity&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Y una función &lt;code&gt;printIssue&lt;/code&gt; que recibe un objeto de tipo &lt;code&gt;Issue&lt;/code&gt; y hace algo con él, que nos da igual.&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="kr"&gt;declare&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;printIssue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;issue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Issue&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Si pasamos a &lt;code&gt;printIssue&lt;/code&gt; un objeto con una estructura compatible con &lt;code&gt;Issue&lt;/code&gt;, TypeScript nos mostrará un error indicando que los tipos de &lt;code&gt;severity&lt;/code&gt; no son compatibles.&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;issue&lt;/span&gt; &lt;span class="o"&gt;=&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;123&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;severity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;low&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;printIssue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;issue&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;//         ^? Error: Argument of type '{ id: string; severity:&lt;/span&gt;
&lt;span class="c1"&gt;// string; }' is not assignable to parameter of type 'Issue'. &lt;/span&gt;
&lt;span class="c1"&gt;// Types of property 'severity' are incompatible.          &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;En el caso de los objetos, independientemente de que los declaremos con &lt;code&gt;let&lt;/code&gt; o &lt;code&gt;const&lt;/code&gt;, &lt;strong&gt;TypeScript infiere el tipo de sus propiedades como si hubieran sido declaradas con &lt;code&gt;let&lt;/code&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Aunque no podamos asignar un nuevo valor a una variable de tipo objeto declarada con &lt;code&gt;const&lt;/code&gt;, sí que podemos modificar sus propiedades.&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="nx"&gt;issue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;severity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;other&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Si llevamos un tiempo trabajando con TypeScript, sabremos que podemos solucionar este problema de varias formas, y una de ellas es usando &lt;code&gt;as const&lt;/code&gt; sobre la propiedad incompatible.&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;issue&lt;/span&gt; &lt;span class="o"&gt;=&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;123&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;severity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;low&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kd"&gt;const&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;Con el uso de &lt;code&gt;as const&lt;/code&gt; le estamos indicando a TypeScript que infiera el tipo más pequeño posible (&lt;em&gt;narrowing&lt;/em&gt;) para la propiedad &lt;code&gt;severity&lt;/code&gt; en lugar del tipo más amplio &lt;code&gt;string&lt;/code&gt;. En este caso, se infiere el tipo literal "low".&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fil8blvmpfav7l5j6kf7w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fil8blvmpfav7l5j6kf7w.png" alt="Uso de as const sobre la propiedad " width="625" height="283"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Otra forma de solucionar este problema, a efectos didácticos, es usar &lt;code&gt;Object.freeze&lt;/code&gt; sobre el objeto.&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;issue&lt;/span&gt; &lt;span class="o"&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;freeze&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;123&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;severity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;low&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Dado que &lt;code&gt;Object.freeze&lt;/code&gt; devuelve un objeto de solo lectura, TypeScript infiere el tipo literal para sus propiedades.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fununqnafeecfrxs6vj46.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fununqnafeecfrxs6vj46.png" alt="Uso de Object.freeze sobre el objeto asignado a la variable issue" width="625" height="283"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Object.freeze&lt;/code&gt; tiene una característica que puede ser un inconveniente en algunos casos, y es que la inmutabilidad que proporciona es "&lt;em&gt;shallow&lt;/em&gt;", es decir, solo afecta a las propiedades del objeto, pero no a sus propiedades anidadas, que pueden seguir siendo modificadas.&lt;/p&gt;

&lt;p&gt;Si nos interesa marcar un objeto como &lt;code&gt;readonly&lt;/code&gt; en profundidad, podemos aplicar &lt;code&gt;as const&lt;/code&gt; a todo el objeto.&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;issue&lt;/span&gt; &lt;span class="o"&gt;=&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;123&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;severity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;low&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;another&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;prop&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;value&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;as&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Y esto es todo en cuanto a inferencia de tipos y &lt;code&gt;as const&lt;/code&gt; como técnica de "&lt;em&gt;narrowing&lt;/em&gt;", pero tenemos otras formas de evitar que TypeScript infiera tipos más amplios de los que esperamos, como por ejemplo, especificando el tipo de las variables explícitamente, usando &lt;code&gt;satisfies&lt;/code&gt; o aprovechando el "&lt;em&gt;excess property checking&lt;/em&gt;", pero eso lo dejamos para otra píldora.&lt;/p&gt;

&lt;p&gt;Si has llegado hasta aquí, ¡gracias! Y si además te ha resultado útil o simplemente te ha gustado, ayúdame a llegar a más gente compartiéndolo 😊&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>spanish</category>
      <category>javascript</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Testing de componentes React: ¿cuándo y por qué necesitamos usar act()?</title>
      <dc:creator>Kus Cámara</dc:creator>
      <pubDate>Sat, 21 Oct 2023 11:43:44 +0000</pubDate>
      <link>https://dev.to/kuscamara/testing-de-componentes-react-cuando-y-por-que-necesitamos-usar-act-in5</link>
      <guid>https://dev.to/kuscamara/testing-de-componentes-react-cuando-y-por-que-necesitamos-usar-act-in5</guid>
      <description>&lt;p&gt;¿A quién no le ha pasado? Cuando hacemos test de componentes React, de vez en cuando nos encontramos con este warning:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Warning: An update to MyComponent inside a test was not wrapped in act(...).

When testing, code that causes React state updates should be wrapped into act(...):

act(() =&amp;gt; {
  /* fire events that update state */
});
/* assert on the output */
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Aunque el mensaje es bastante claro (una vez que lo entendemos), es habitual no entender ni por qué aparece, ni cómo solucionarlo, dando como resultado que acabemos ignorándolo. &lt;/p&gt;

&lt;h2&gt;
  
  
  Actualizaciones de estado y rerenders
&lt;/h2&gt;

&lt;p&gt;Cuando hacemos una actualización de estado en un componente, React se encarga de volver a renderizarlo para actualizar el DOM si es necesario. Esto ocurre automáticamente como respuesta a los eventos de usuario (clicks, input, etc.) gestionados por React. Ahora bien, los tests son otra historia.&lt;/p&gt;

&lt;p&gt;Para demostrarlo, vamos a suponer que tenemos un componente muy sencillo que muestra un botón y un texto que cambia al hacer click sobre ese botón.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;MyComponent&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;initialText&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="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setText&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;initialText&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="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&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;setText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;New text&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Click me&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;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;Cuando interactuamos con este componente en el navegador, React se encarga de volver a renderizarlo (volver a ejecutar &lt;code&gt;render&lt;/code&gt;) cuando necesita actualizar su estado, dando como resultado en este caso una actualización del DOM.&lt;/p&gt;

&lt;p&gt;Por el contrario, cuando renderizamos este componente en un test, React no espera que ocurran actualizaciones de estado entre lo que renderizamos inicialmente y las aserciones que hacemos sobre el DOM. &lt;/p&gt;

&lt;p&gt;Para entenderlo mejor, digamos que el trabajo de React en un test termina después de ejecutar el &lt;code&gt;render&lt;/code&gt;. Y para terminar de endender el nombre de esa función &lt;code&gt;act()&lt;/code&gt;, vamos a suponer que &lt;strong&gt;de las tres A (&lt;em&gt;Arrange&lt;/em&gt;, &lt;em&gt;Act&lt;/em&gt; y &lt;em&gt;Assert&lt;/em&gt;)&lt;/strong&gt; React solo espera que en un test ocurran la primera y la última:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="nx"&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;shows the text specified in "initialText"&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;// arrange&lt;/span&gt;
  &lt;span class="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;MyComponent&lt;/span&gt; &lt;span class="na"&gt;initialText&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"any"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;// assert&lt;/span&gt;
  &lt;span class="nx"&gt;expect&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;getByText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;any&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nx"&gt;toBeInTheDocument&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;Normalmente en un test vamos a necesitar simular ciertos eventos para comprobar que el resultado de una interacción es el esperado. En este caso queremos comprobar que al hacer click sobre el botón, el texto cambia a "New text". &lt;/p&gt;

&lt;p&gt;Los métodos que Testing Library nos proporciona para simular las interacciones de usuario &lt;strong&gt;(&lt;code&gt;fireEvent&lt;/code&gt; y &lt;code&gt;userEvent&lt;/code&gt;) utilizan internamente &lt;code&gt;act()&lt;/code&gt;&lt;/strong&gt; y por eso no necesitamos usarlo en la mayoría de los casos. Esto es, en mi opinión, lo que contribuye a que no tengamos claro por qué a veces aparece ese warning, ya que no siempre es evidente que ya lo estamos usando.&lt;/p&gt;

&lt;p&gt;Para demostrar esto, en lugar de &lt;code&gt;fireEvent&lt;/code&gt; o &lt;code&gt;userEvent&lt;/code&gt; vamos a usar el método &lt;code&gt;click()&lt;/code&gt; nativo del botón:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="nx"&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;changes initial text after clicking the 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="c1"&gt;// arrange&lt;/span&gt;
  &lt;span class="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;MyComponent&lt;/span&gt; &lt;span class="na"&gt;initialText&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"any"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;// 👉 esto provoca un warning de act()&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;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="nx"&gt;click&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; 

  &lt;span class="c1"&gt;// assert&lt;/span&gt;
  &lt;span class="nx"&gt;expect&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;getByText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;New text&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nx"&gt;toBeInTheDocument&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;El resultado de ejecutar este test será &lt;strong&gt;un warning de &lt;code&gt;act()&lt;/code&gt; y un test fallido&lt;/strong&gt;. Lo que React nos está diciendo con ese warning viene a ser algo así:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;¡Ey! Estás haciendo algo que actualiza el estado. &lt;strong&gt;Necesito que me avises para que pueda volver a renderizar el componente&lt;/strong&gt;. De lo contrario no te puedo garantizar que estés probando lo que esperas.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Y como hemos podido ver en este ejemplo, nos avisa por una buena razón: el DOM que estamos probando no está actualizado.&lt;/p&gt;

&lt;h2&gt;
  
  
  Avisando a React de que necesita volver a renderizar
&lt;/h2&gt;

&lt;p&gt;Ahora que ya hemos entendido por qué aparece el warning, vamos a ver cómo solucionarlo. &lt;/p&gt;

&lt;p&gt;Siempre que hagamos algo en un test que provoque un cambio de estado, tenemos que &lt;strong&gt;envolverlo en &lt;code&gt;act()&lt;/code&gt;&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="nx"&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;changes initial text after clicking the 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="c1"&gt;// arrange&lt;/span&gt;
  &lt;span class="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;MyComponent&lt;/span&gt; &lt;span class="na"&gt;initialText&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"any"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;// act (Soy un comentario innecesario pero didáctico 😅)&lt;/span&gt;
  &lt;span class="nx"&gt;act&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="nx"&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="nx"&gt;click&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

  &lt;span class="c1"&gt;// assert&lt;/span&gt;
  &lt;span class="nx"&gt;expect&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;getByText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;New text&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nx"&gt;toBeInTheDocument&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;O mucho mejor, utilizar los métodos que Testing Library nos proporciona para simular interacciones de usuario, que ya hacen uso de &lt;code&gt;act()&lt;/code&gt; &lt;em&gt;"under the hood"&lt;/em&gt;:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ejemplo con &lt;code&gt;fireEvent&lt;/code&gt;&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="nx"&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;changes initial text after clicking the 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="c1"&gt;// arrange&lt;/span&gt;
  &lt;span class="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;MyComponent&lt;/span&gt; &lt;span class="na"&gt;initialText&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"any"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;// fireEvent usa act() internamente&lt;/span&gt;
  &lt;span class="nx"&gt;fireEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;click&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;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="c1"&gt;// assert&lt;/span&gt;
  &lt;span class="nx"&gt;expect&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;getByText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;New text&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nx"&gt;toBeInTheDocument&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;Ejemplo con &lt;code&gt;userEvent&lt;/code&gt; (asíncrono)&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="nx"&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;changes initial text after clicking the button&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="c1"&gt;// arrange&lt;/span&gt;
  &lt;span class="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;MyComponent&lt;/span&gt; &lt;span class="na"&gt;initialText&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"any"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;// userEvent usa act() internamente&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;userEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;click&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;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="c1"&gt;// assert&lt;/span&gt;
  &lt;span class="nx"&gt;expect&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;getByText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;New text&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nx"&gt;toBeInTheDocument&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;h2&gt;
  
  
  ¿Cuándo es necesario usar act()?
&lt;/h2&gt;

&lt;p&gt;No todas las actualizaciones de estado ocurren inmediatamente después de una interacción de usuario. En ocasiones, &lt;strong&gt;el estado se actualiza como respuesta a un evento asíncrono&lt;/strong&gt;, como por ejemplo un &lt;code&gt;setTimeout&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Para ejemplificarlo, vamos a modificar un poco nuestro componente para que el texto cambie después de 1 segundo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;MyComponent&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;initialText&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="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setText&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;initialText&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;handleClick&lt;/span&gt; &lt;span class="o"&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;setTimeout&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;setText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;New text&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; 
    &lt;span class="mi"&gt;1000&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="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleClick&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Click me&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;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;En este caso, aunque usemos los métodos de Testing Library para simular la interacción de usuario, &lt;strong&gt;el test fallará&lt;/strong&gt; porque el cambio de estado no ocurre inmediatamente como respuesta a un click, sino después de 1 segundo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="nx"&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;changes initial text after clicking the 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="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;MyComponent&lt;/span&gt; &lt;span class="na"&gt;initialText&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"any"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;)&lt;/span&gt;

  &lt;span class="nx"&gt;fireEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;click&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;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="c1"&gt;// ❌ FAIL -&amp;gt; necesitamos esperar 1 segundo&lt;/span&gt;
  &lt;span class="nx"&gt;expect&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;getByText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;New text&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nx"&gt;toBeInTheDocument&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;Podemos sentir la tentación de solucionarlo usando las queries asíncronas de Testing Library, pero en ese caso no estaremos evitando que pase 1 preciado segundo en nuestro test:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="nx"&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;changes initial text after clicking the button&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="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;MyComponent&lt;/span&gt; &lt;span class="na"&gt;initialText&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"any"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;)&lt;/span&gt;

  &lt;span class="nx"&gt;fireEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;click&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;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="c1"&gt;// Las queries de tipo findBy* son asíncronas&lt;/span&gt;
  &lt;span class="k"&gt;await&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;findByText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;New text&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;La ejecución de este test tarda más de 1 segundo.   &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Vkxz02Ac--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5xm0zgck5tflvlmrz2f5.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Vkxz02Ac--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5xm0zgck5tflvlmrz2f5.gif" alt="Captura de la ejecución del test anterior (lento)" width="576" height="306"&gt;&lt;/a&gt;  &lt;/p&gt;

&lt;p&gt;Lo normal y recomendable cuando usamos temporizadores en nuestros componentes, es "mockearlos" para evitar que ese tiempo se consuma realmente en nuestros tests. Para ello, Jest o Vi nos proporcionan el método &lt;code&gt;useFakeTimers()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;beforeEach&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;vi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;useFakeTimers&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="nx"&gt;afterEach&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;vi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clearAllTimers&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="nx"&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;changes initial text after clicking the button&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="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;MyComponent&lt;/span&gt; &lt;span class="na"&gt;initialText&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"any"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;)&lt;/span&gt;

  &lt;span class="nx"&gt;fireEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;click&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;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="c1"&gt;// 👉 avanzamos 1 segundo&lt;/span&gt;
  &lt;span class="nx"&gt;vi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;advanceTimersByTime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 

  &lt;span class="c1"&gt;// No necesitamos usar findBy* porque el cambio de estado ocurre inmediatamente&lt;/span&gt;
  &lt;span class="nx"&gt;expect&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;getByText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;New text&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nx"&gt;toBeInTheDocument&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;Sin embargo, este test fallará con el mismo warning de &lt;code&gt;act()&lt;/code&gt; que hemos visto anteriormente. Dado que la actualización de estado no ocurre como respuesta al &lt;code&gt;onClick&lt;/code&gt; del botón, sino al callback del &lt;code&gt;setTimeout&lt;/code&gt;, necesitamos avisar a React de que tiene que volver a renderizar el componente tras forzar el avance del temporizador.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="nx"&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;changes initial text after clicking the button&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="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;MyComponent&lt;/span&gt; &lt;span class="na"&gt;initialText&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"any"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;)&lt;/span&gt;

  &lt;span class="nx"&gt;fireEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;click&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;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="c1"&gt;// 👉 avanzamos 1 segundo y avisamos a React de que tiene que volver a renderizar&lt;/span&gt;
  &lt;span class="nx"&gt;act&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;vi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;advanceTimersByTime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&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="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getByText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;New text&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nx"&gt;toBeInTheDocument&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;¡Y voilà! Nuestro test no solo pasa, sino que además su ejecución es mucho más rápida gracias a los &lt;em&gt;fake timers&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--7fZFhpZO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m8nbk6k8gglabjikvw9x.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--7fZFhpZO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m8nbk6k8gglabjikvw9x.gif" alt="Captura de la ejecución del test anterior (rápido)" width="576" height="306"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;Siempre que en nuestros tests estemos provocando actualizaciones de estado, necesitamos avisar a React de que tiene que volver a renderizar el componente mediante &lt;code&gt;act()&lt;/code&gt; si no estamos usando alguno de los métodos de Testing Library que lo hacen internamente, como &lt;code&gt;fireEvent&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;Ignorar este warning puede provocar que nuestros tests no sean fiables (incluso cuando no fallen), ya que el DOM que estamos probando no está actualizado. &lt;/p&gt;

&lt;p&gt;Las queries asíncronas de Testing Library (&lt;code&gt;findBy*&lt;/code&gt;) también usan &lt;code&gt;act()&lt;/code&gt; internamente, pero no siempre son la solución adecuada, especialmente cuando usamos temporizadores en nuestros componentes que consumen tiempo real en nuestros tests.&lt;/p&gt;

</description>
      <category>testing</category>
      <category>react</category>
      <category>spanish</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Creando generadores (scaffolds) con Plop</title>
      <dc:creator>Kus Cámara</dc:creator>
      <pubDate>Fri, 29 Apr 2022 15:30:10 +0000</pubDate>
      <link>https://dev.to/kuscamara/creando-generadores-scaffolds-con-plop-3kna</link>
      <guid>https://dev.to/kuscamara/creando-generadores-scaffolds-con-plop-3kna</guid>
      <description>&lt;p&gt;En todo proyecto, más pronto que tarde, necesitamos un generador que nos ayude a &lt;strong&gt;mantener la consistencia&lt;/strong&gt; entre los archivos que vamos creando (componentes, tests, páginas, etc.). Un generador no solo es necesario para esto, sino para &lt;strong&gt;evitar trabajos repetitivos&lt;/strong&gt; a base de &lt;em&gt;copy &amp;amp; paste&lt;/em&gt; que normalmente &lt;strong&gt;se pueden automatizar&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://yeoman.io/"&gt;Yeoman&lt;/a&gt; es una de las herramientas más completas que podemos utilizar para crear generadores, pero a veces simplemente no necesitamos tanto o queremos tener nuestros templates dentro del proyecto en el que los vamos a usar para poder editarlos más fácilmente.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://plopjs.com/"&gt;Plop&lt;/a&gt; es una herramienta que nos permite crear nuestros propios generadores a partir de unos templates &lt;a href="https://handlebarsjs.com/"&gt;Handlebars&lt;/a&gt; y un archivo de configuración en el que establecemos las preguntas que haremos al usuario mediante &lt;a href="https://www.npmjs.com/package/inquirer"&gt;Inquirer&lt;/a&gt;. Y ya está. &lt;strong&gt;No necesitamos más que templates y preguntas&lt;/strong&gt; para rellenarlos.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creando un generador
&lt;/h2&gt;

&lt;p&gt;Vamos a ver cómo podemos crear un generador con Plop fácilmente con el ejemplo de un componente.&lt;/p&gt;

&lt;p&gt;Lo primero que vamos a necesitar, obviamente, es &lt;strong&gt;instalar Plop&lt;/strong&gt; como dependencia de desarrollo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i &lt;span class="nt"&gt;-D&lt;/span&gt; plop
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nuestros componentes se encuentran dentro de &lt;code&gt;src/components/ComponentName/&lt;/code&gt; y dentro de cada una de las carpetas de nuestros componentes tenemos un archivo con el nombre del componente, un archivo de test y un index que sirve como entry point. &lt;/p&gt;

&lt;p&gt;Estructura del proyecto:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;├── package.json
├── src
│   ├── index.ts
│   ├── components
│   │   └── MyComponent
│   │       ├── MyComponent.test.tsx
│   │       ├── MyComponent.tsx
│   │       └── index.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Somos libres de crear el archivo de configuración de Plop y sus templates donde nos dé la gana, y de hecho la documentación de Plop nos sugiere que creemos el archivo &lt;code&gt;plopfile.js&lt;/code&gt; de configuración en la raíz del proyecto, pero para mantener todo más organizado y localizable, los vamos a crear dentro de una carpeta llamada generators (también podría llamarse templates, scaffolds, etc.).&lt;/p&gt;

&lt;p&gt;La estructura de nuestro proyecto quedará así:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;├── package.json
├── generators
│   ├── component
│   │   ├── component.hbs
│   │   ├── component.test.hbs
│   │   └── index.hbs
│   └── plopfile.js
├── src
│   ├── index.ts
│   ├── components
│   │   └── MyComponent
│   │       ├── MyComponent.test.tsx
│   │       ├── MyComponent.tsx
│   │       └── index.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Los templates
&lt;/h3&gt;

&lt;p&gt;Lo primero que vamos a necesitar son los templates en formato Handlebars.  Handlebars nos permite usar mucho más que variables (condicionales, bucles, etc.), pero nuestro generador es tan simple que solo utiliza la variable del nombre del componente y un &lt;a href="https://plopjs.com/documentation/#built-in-helpers"&gt;helper (&lt;code&gt;{{pascalCase}}&lt;/code&gt;) que nos proporciona el propio Plop&lt;/a&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="p"&gt;{{&lt;/span&gt;&lt;span class="o"&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;hbs&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="p"&gt;{{&lt;/span&gt;&lt;span class="nx"&gt;pascalCase&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;&lt;span class="nx"&gt;Props&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="cm"&gt;/** Prop description */&lt;/span&gt;
  &lt;span class="nx"&gt;someProp&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * Edit me!
 */&lt;/span&gt;
&lt;span class="k"&gt;export&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;pascalCase&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&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="p"&gt;{{&lt;/span&gt;&lt;span class="nx"&gt;pascalCase&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;&lt;span class="nx"&gt;Props&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;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Hi&lt;/span&gt;&lt;span class="o"&gt;!&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/p&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;El resultado de transformar el código anterior sería el siguiente para un componente llamado &lt;code&gt;MyComponent&lt;/code&gt;, &lt;code&gt;my-component&lt;/code&gt; o &lt;code&gt;myComponent&lt;/code&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;// MyComponent.tsx&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;MyComponentProps&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="cm"&gt;/** Prop description */&lt;/span&gt;
  &lt;span class="nl"&gt;someProp&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="p"&gt;}&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * Edit me!
 */&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;MyComponent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({}:&lt;/span&gt; &lt;span class="nx"&gt;MyComponentProps&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;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Hi&lt;/span&gt;&lt;span class="o"&gt;!&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/p&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Las preguntas y acciones
&lt;/h3&gt;

&lt;p&gt;Una vez que tenemos preparados los templates, podemos pasar a configurar las preguntas que haremos para obtener las variables que utilizamos en ellos.  Para esto utilizaremos el archivo de configuración de Plop.&lt;/p&gt;

&lt;p&gt;En este archivo podemos cargar otros generadores o bien configurar directamente las preguntas y acciones, que es lo que vamos a hacer en nuestro ejemplo, ya que de momento solo tenemos un generador.&lt;/p&gt;

&lt;p&gt;Para las preguntas utilizaremos el &lt;a href="https://github.com/SBoudrias/Inquirer.js/#prompt-types"&gt;tipo de Inquirer&lt;/a&gt; que nos interese dependiendo de si queremos obtener un input, una opción de entre varias, un yes o no, etc.&lt;/p&gt;

&lt;p&gt;En nuestro caso solo necesitamos una pregunta de tipo input para que el usuario escriba el nombre del componente y además no necesitamos preocuparnos de validar el formato porque el helper &lt;code&gt;{{pascalCase}}&lt;/code&gt; que utilizamos se ocupa de ello:&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;// plopfile.js&lt;/span&gt;
&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;plop&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;plop&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setGenerator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;component&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;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Creates a new component&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;prompts&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="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;input&lt;/span&gt;&lt;span class="dl"&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Component name&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;actions&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;Por último necesitamos especificar las acciones que queremos ejecutar una vez que hemos obtenido los parámetros necesarios a partir de las respuestas del usuario. &lt;/p&gt;

&lt;p&gt;En nuestro caso solo necesitamos añadir nuevos archivos, para lo que utilizaremos acciones de tipo &lt;a href="https://plopjs.com/documentation/#add"&gt;&lt;code&gt;add&lt;/code&gt;&lt;/a&gt; especificando el destino mediante &lt;code&gt;path&lt;/code&gt; y el archivo utilizado como template mediante &lt;code&gt;templateFile&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="c1"&gt;// plopfile.js&lt;/span&gt;
&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;plop&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;plop&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setGenerator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;component&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;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Creates a new component&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;prompts&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="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;input&lt;/span&gt;&lt;span class="dl"&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Component name&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;actions&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="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;add&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../src/components/{{pascalCase name}}/{{pascalCase name}}.tsx&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;templateFile&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;component/component.hbs&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;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;add&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../src/components/{{pascalCase name}}/{{pascalCase name}}.test.tsx&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;templateFile&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;component/component.test.hbs&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;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;add&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../src/components/{{pascalCase name}}/index.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;templateFile&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;component/index.hbs&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;Por último solo nos faltaría añadir un &lt;strong&gt;npm-script&lt;/strong&gt; para facilitar la ejecución de nuestro generador mediante un comando. Como hemos creado el archivo de configuración de Plop en una ubicación distinta a la raíz, necesitamos especificar la ubicación de este archivo como parámetro:&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;"generate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"plop --plopfile ./generators/plopfile.js"&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;p&gt;De forma bastante sencilla y rápida podemos tener generadores que podemos actualizar fácilmente en cualquier momento según las necesidades que vayan surgiendo. Plop además no se limita a crear archivos, sino que también &lt;strong&gt;puede actualizar los existentes&lt;/strong&gt;, con lo que nos puede servir para mantener nuestra base de código homogénea siempre que hagamos cambios en los templates.&lt;/p&gt;

&lt;p&gt;That's all. Happy templating!&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>spanish</category>
      <category>tooling</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
