<?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>Forem: Isaac Ojeda</title>
    <description>The latest articles on Forem by Isaac Ojeda (@isaacojeda).</description>
    <link>https://forem.com/isaacojeda</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%2F566499%2F40a29082-f0cc-45c9-8411-de7b1e987f47.png</url>
      <title>Forem: Isaac Ojeda</title>
      <link>https://forem.com/isaacojeda</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/isaacojeda"/>
    <language>en</language>
    <item>
      <title>EF Core: tu query funciona, tus pruebas pasan… y estás leyendo 50,000 filas para devolver 3</title>
      <dc:creator>Isaac Ojeda</dc:creator>
      <pubDate>Sat, 14 Mar 2026 22:44:46 +0000</pubDate>
      <link>https://forem.com/isaacojeda/ef-core-tu-query-funciona-tus-pruebas-pasan-y-estas-leyendo-50000-filas-para-devolver-3-1c9e</link>
      <guid>https://forem.com/isaacojeda/ef-core-tu-query-funciona-tus-pruebas-pasan-y-estas-leyendo-50000-filas-para-devolver-3-1c9e</guid>
      <description>&lt;p&gt;El código lleva meses en producción. Nadie se ha quejado. Todas las pruebas pasan.&lt;/p&gt;

&lt;p&gt;Luego llega un cliente grande, con años de historial acumulado, y esa pantalla que siempre cargó “bien” empieza a tardar varios segundos. A veces ni siquiera falla: simplemente se vuelve torpe, pesada, impredecible.&lt;/p&gt;

&lt;p&gt;Empiezas a revisar y descubres algo incómodo: el problema siempre estuvo ahí. Solo necesitaba suficientes datos para hacerse visible.&lt;/p&gt;

&lt;p&gt;Ese es uno de los patrones más traicioneros cuando trabajas con EF Core. No porque el código esté roto, sino porque &lt;strong&gt;funciona&lt;/strong&gt;. Devuelve los datos correctos. Pasa QA. Sobrevive en producción durante meses. Y aun así, por debajo, puede estar ejecutando consultas mucho más caras de lo que parecen.&lt;/p&gt;

&lt;p&gt;En este artículo quiero mostrar dos trampas comunes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;cuando materializas demasiado pronto con &lt;code&gt;ToList()&lt;/code&gt; y el filtro termina ocurriendo en memoria;&lt;/li&gt;
&lt;li&gt;cuando dejas lógica no traducible dentro de la proyección y la consulta deja de ser tan eficiente como parece.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;La idea no es memorizar reglas raras de EF Core. La idea es desarrollar un reflejo: &lt;strong&gt;siempre revisar el SQL real que se genera&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Por qué casi nadie lo detecta a tiempo
&lt;/h2&gt;

&lt;p&gt;Este problema rara vez aparece de golpe.&lt;/p&gt;

&lt;p&gt;En local, todo corre sobre una base pequeña, con datos de prueba y una máquina rápida. Las consultas tardan milisegundos y nadie siente la necesidad de inspeccionar el SQL.&lt;/p&gt;

&lt;p&gt;En QA o staging, la infraestructura puede parecerse a producción, pero el volumen de datos sigue sin representar el mundo real. El problema ya existe, solo que todavía no pesa.&lt;/p&gt;

&lt;p&gt;En producción, con clientes pequeños, los tiempos siguen siendo aceptables. No hay alertas. No hay tickets. No hay razón aparente para abrir el capó.&lt;/p&gt;

&lt;p&gt;Y entonces llega el cliente con años de historial, decenas de miles de registros y patrones reales de uso. Ahí, por primera vez, la consulta muestra su costo verdadero.&lt;/p&gt;

&lt;p&gt;Lo más difícil de este escenario no es corregirlo. Lo más difícil es que cuando por fin se vuelve visible, el código ya lleva tanto tiempo vivo que nadie recuerda con claridad por qué se escribió así.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;En EF Core, el peligro no siempre es que algo falle. A veces el peligro es que funcione… pero de la forma más cara posible.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Problema 1: el &lt;code&gt;ToList()&lt;/code&gt; que mueve el filtro fuera de SQL
&lt;/h2&gt;

&lt;h3&gt;
  
  
  El origen: una excepción legítima
&lt;/h3&gt;

&lt;p&gt;Todo empieza con algo bastante normal: quieres usar lógica de negocio propia dentro de un &lt;code&gt;Where&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;pedidos&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;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Pedidos&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;EsDelUltimoMes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FechaCreacion&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;PedidoResumenDto&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Cliente&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cliente&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Nombre&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Total&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Total&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToListAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;EsDelUltimoMes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DateTime&lt;/span&gt; &lt;span class="n"&gt;fecha&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="n"&gt;fecha&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Now&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddMonths&lt;/span&gt;&lt;span class="p"&gt;(-&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ese código no es traducible tal como está. &lt;code&gt;EsDelUltimoMes&lt;/code&gt; es un método local de C#, y EF Core no sabe convertirlo a SQL. En EF Core moderno, eso normalmente termina en una excepción de traducción.&lt;/p&gt;

&lt;p&gt;Algo como esto:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;InvalidOperationException: The LINQ expression could not be translated.
Either rewrite the query in a form that can be translated, or switch to
client evaluation explicitly by inserting a call to 'AsEnumerable',
'AsAsyncEnumerable', 'ToList', or 'ToListAsync'.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;La excepción no miente. Pero es muy fácil leerla de forma peligrosa.&lt;/p&gt;

&lt;h3&gt;
  
  
  La “solución” que parece arreglar todo
&lt;/h3&gt;

&lt;p&gt;Alguien ve que el mensaje menciona &lt;code&gt;ToList()&lt;/code&gt;, lo agrega, y el problema desaparece:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;pedidos&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Pedidos&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToList&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;EsDelUltimoMes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FechaCreacion&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;PedidoResumenDto&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Cliente&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cliente&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Nombre&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Total&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Total&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToList&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Y sí: ahora funciona. Compila, devuelve resultados correctos y pasa las pruebas.&lt;/p&gt;

&lt;p&gt;Pero ya no estás filtrando en la base de datos. Estás trayendo todos los registros primero, y luego aplicando la lógica en memoria.&lt;/p&gt;

&lt;p&gt;En otras palabras, el SQL que sale ya no se parece a la intención original. Se parece más a esto:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ClienteId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Total&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Activo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FechaCreacion&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;Pedidos&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sin &lt;code&gt;WHERE&lt;/code&gt;. Sin proyección útil. Sin límite.&lt;/p&gt;

&lt;p&gt;Si la tabla tiene 50,000 filas, esas 50,000 filas viajan completas desde la base de datos hasta tu aplicación. Solo después haces el filtro en C# para devolver quizá 3 resultados al usuario.&lt;/p&gt;

&lt;p&gt;Ese es el tipo de problema que no se nota con 200 registros, pero sí con años de operación acumulada.&lt;/p&gt;

&lt;h3&gt;
  
  
  La forma correcta
&lt;/h3&gt;

&lt;p&gt;La solución real es expresar el filtro usando operadores que EF Core sí conozca y pueda traducir.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;fechaLimite&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Now&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddMonths&lt;/span&gt;&lt;span class="p"&gt;(-&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;pedidos&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;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Pedidos&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FechaCreacion&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;fechaLimite&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;PedidoResumenDto&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Cliente&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cliente&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Nombre&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Total&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Total&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToListAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ahora sí el filtro vive donde debe vivir: en SQL.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Nombre&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Total&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;Pedidos&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;
&lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;Clientes&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ClienteId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FechaCreacion&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="s1"&gt;'2025-02-14'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;La regla práctica aquí no es “nunca uses métodos”. Es esta: &lt;strong&gt;mientras la lógica se exprese con operadores y funciones que EF Core conoce, podrá empujarla a SQL&lt;/strong&gt;. Cuando no pueda, corres el riesgo de que la evaluación ocurra fuera de la base de datos o de que EF directamente falle al traducir.&lt;/p&gt;

&lt;p&gt;Si necesitas lógica de negocio más compleja, aplícala después de materializar, pero solo sobre un conjunto de datos que ya filtraste correctamente en la base.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Nota: en algunos escenarios complejos, usar Raw SQL es perfectamente válido. No es una derrota; es una herramienta más.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Problema 2: la proyección parece pequeña, pero la consulta deja de ser eficiente
&lt;/h2&gt;

&lt;p&gt;El segundo problema es más silencioso.&lt;/p&gt;

&lt;p&gt;Aquí el &lt;code&gt;Where&lt;/code&gt; sí se traduce. La consulta sí filtra en la base de datos. Los resultados son correctos. El detalle está en la proyección.&lt;/p&gt;

&lt;h3&gt;
  
  
  El escenario
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;pedidos&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;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Pedidos&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FechaCreacion&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;fechaLimite&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;PedidoResumenDto&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Cliente&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cliente&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Nombre&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Total&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Total&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Estatus&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ObtenerEtiqueta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Activo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToListAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nf"&gt;ObtenerEtiqueta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;activo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="n"&gt;activo&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="s"&gt;"Activo"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Inactivo"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A simple vista, parece una proyección pequeña. Solo quieres tres campos.&lt;/p&gt;

&lt;p&gt;El problema es que &lt;code&gt;ObtenerEtiqueta&lt;/code&gt; sigue siendo lógica local de C#. EF Core no puede traducir ese método directamente. En la proyección final, eso puede hacer que parte del trabajo termine resolviéndose del lado del cliente.&lt;/p&gt;

&lt;p&gt;Y cuando eso pasa, la consulta puede traer &lt;strong&gt;más información de la necesaria&lt;/strong&gt; para completar esa proyección en memoria.&lt;/p&gt;

&lt;p&gt;La parte peligrosa no es solo “que funcione”. Es que desde el código parece una consulta mínima, cuando en realidad el SQL puede estar cargando más columnas de las que tú creías.&lt;/p&gt;

&lt;p&gt;No siempre será literalmente un &lt;code&gt;SELECT *&lt;/code&gt;, pero sí puede ocurrir que EF traiga suficiente información como para volver la proyección más costosa de lo que aparenta en C#.&lt;/p&gt;

&lt;h3&gt;
  
  
  La forma correcta
&lt;/h3&gt;

&lt;p&gt;Si el formateo no necesita vivir en SQL, sepáralo de la consulta:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;pedidos&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;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Pedidos&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FechaCreacion&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;fechaLimite&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;PedidoResumenDto&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Cliente&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cliente&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Nombre&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Total&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Total&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;EsActivo&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Activo&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToListAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Dejar que la capa presentación muestre los datos&lt;/span&gt;
&lt;span class="c1"&gt;// o&lt;/span&gt;
&lt;span class="c1"&gt;// 👇🏼&lt;/span&gt;
&lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;pedido&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;pedidos&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;pedido&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Estatus&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ObtenerEtiqueta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pedido&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EsActivo&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Así la base de datos solo devuelve lo necesario, y el formateo ocurre después, en memoria, pero ya sobre un conjunto pequeño y bien proyectado.&lt;/p&gt;

&lt;p&gt;Si la lógica sí puede expresarse con algo traducible, entonces mejor dejarla en la consulta:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;pedidos&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;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Pedidos&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FechaCreacion&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;fechaLimite&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;PedidoResumenDto&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Cliente&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cliente&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Nombre&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Total&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Total&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Estatus&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Activo&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="s"&gt;"Activo"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Inactivo"&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToListAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Una expresión ternaria simple como esa normalmente sí se traduce a &lt;code&gt;CASE WHEN&lt;/code&gt; en SQL.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cómo inspeccionar lo que EF Core realmente está haciendo
&lt;/h2&gt;

&lt;p&gt;Si no ves el SQL, estás adivinando.&lt;/p&gt;

&lt;p&gt;Y adivinar con consultas que hoy corren sobre 500 filas y mañana correrán sobre 5 millones suele salir caro.&lt;/p&gt;

&lt;h3&gt;
  
  
  Opción 1: logs en desarrollo
&lt;/h3&gt;

&lt;p&gt;Puedes habilitar logs del contexto para ver el SQL ejecutado:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddDbContext&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;AppDbContext&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseSqlServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;connectionString&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
           &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;LogLevel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Information&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
           &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EnableSensitiveDataLogging&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;EnableSensitiveDataLogging()&lt;/code&gt; conviene dejarlo solo en desarrollo.&lt;/p&gt;

&lt;p&gt;En la salida, busca entradas como esta:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Executed DbCommand (X ms) [Parameters=[...], CommandType='Text', CommandTimeout='30']
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ahí está la consulta real.&lt;/p&gt;

&lt;h3&gt;
  
  
  Opción 2: &lt;code&gt;ToQueryString()&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Cuando quieres revisar una consulta específica, esta suele ser la forma más directa:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Pedidos&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FechaCreacion&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;fechaLimite&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;PedidoResumenDto&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Cliente&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cliente&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Nombre&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Total&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Total&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Estatus&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Activo&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="s"&gt;"Activo"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Inactivo"&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToQueryString&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;pedidos&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;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToListAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;ToQueryString()&lt;/code&gt; funciona sobre cualquier &lt;code&gt;IQueryable&lt;/code&gt; antes de materializarlo. Si tienes dudas sobre traducción, úsalo. Te ahorra suposiciones.&lt;/p&gt;

&lt;h2&gt;
  
  
  Un detalle extra en consultas de lectura
&lt;/h2&gt;

&lt;p&gt;Si esa consulta solo existe para mostrar datos y no vas a modificar las entidades después, vale la pena considerar &lt;code&gt;AsNoTracking()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;pedidos&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;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Pedidos&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AsNoTracking&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FechaCreacion&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;fechaLimite&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;PedidoResumenDto&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Cliente&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cliente&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Nombre&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Total&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Total&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToListAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No arregla problemas de traducción ni evita consultas mal diseñadas. Pero sí reduce trabajo del change tracker en escenarios de solo lectura, y eso puede sumar bastante cuando la pantalla consulta muchos registros.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sobre asistentes de código y queries “que funcionan”
&lt;/h2&gt;

&lt;p&gt;Los asistentes de código suelen optimizar para darte algo que compile, que se vea razonable y que devuelva el resultado esperado.&lt;/p&gt;

&lt;p&gt;Eso está bien. Pero una consulta correcta funcionalmente no siempre es una consulta sana desde el punto de vista de traducción o performance.&lt;/p&gt;

&lt;p&gt;El asistente no ve el volumen real de tu tabla. No está mirando el plan de ejecución. No sabe si ese query hoy devuelve 20 registros o si mañana va a inspeccionar medio millón para construir una pantalla.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Por eso esta revisión sigue siendo responsabilidad del developer&lt;/strong&gt;: no basta con que la consulta compile y pase pruebas. Hay que confirmar qué SQL genera y dónde se está ejecutando realmente cada parte de la lógica.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resumen
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Situación&lt;/th&gt;
&lt;th&gt;Qué ocurre&lt;/th&gt;
&lt;th&gt;Cómo detectarlo&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Método local no traducible dentro de &lt;code&gt;Where&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;EF Core normalmente falla al traducir o te obliga a mover la evaluación fuera de SQL&lt;/td&gt;
&lt;td&gt;Excepción de traducción&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;ToList()&lt;/code&gt; antes del filtro&lt;/td&gt;
&lt;td&gt;La consulta se materializa completa y el filtrado ocurre en memoria&lt;/td&gt;
&lt;td&gt;Revisando el código y el SQL generado&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lógica no traducible en la proyección final&lt;/td&gt;
&lt;td&gt;Parte de la proyección puede resolverse en cliente y traer más datos de los necesarios&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;ToQueryString()&lt;/code&gt;, logs y revisión de columnas consultadas&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;La idea central de todo esto es simple: &lt;strong&gt;no confíes en que una query está bien solo porque funciona&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;En EF Core, una consulta puede ser correcta en resultados y aun así ser innecesariamente cara. Y la forma más confiable de descubrirlo no es mirando el LINQ: es mirando el SQL.&lt;/p&gt;

&lt;h2&gt;
  
  
  ¿Qué sigue?
&lt;/h2&gt;

&lt;p&gt;Este es el primero de una serie sobre cosas que EF Core hace de forma poco obvia y que solo se vuelven dolorosas cuando los datos son reales.&lt;/p&gt;

&lt;p&gt;El siguiente artículo va sobre el problema N+1: esa consulta que en desarrollo parece hacer unas pocas llamadas a la base, pero en producción termina haciendo cientos o miles.&lt;/p&gt;

&lt;p&gt;Si alguna vez te pasó algo parecido, seguramente no fue porque el código estuviera “mal” en apariencia. Fue porque el costo real estaba escondido en un lugar que casi nadie revisa a tiempo.&lt;/p&gt;

</description>
      <category>efcore</category>
      <category>dotnet</category>
      <category>csharp</category>
    </item>
    <item>
      <title>Primeros pasos con Microsoft Agent Framework: construyendo un chatbot de soporte con C#</title>
      <dc:creator>Isaac Ojeda</dc:creator>
      <pubDate>Tue, 24 Feb 2026 03:33:14 +0000</pubDate>
      <link>https://forem.com/isaacojeda/primeros-pasos-con-microsoft-agent-framework-construyendo-un-chatbot-de-soporte-con-c-46m0</link>
      <guid>https://forem.com/isaacojeda/primeros-pasos-con-microsoft-agent-framework-construyendo-un-chatbot-de-soporte-con-c-46m0</guid>
      <description>&lt;h2&gt;
  
  
  Introducción
&lt;/h2&gt;

&lt;p&gt;Microsoft lleva años apostando por herramientas para construir aplicaciones con IA: primero llegó &lt;strong&gt;Semantic Kernel&lt;/strong&gt;, después &lt;strong&gt;AutoGen&lt;/strong&gt;, y ahora ambos convergen en su sucesor directo: &lt;strong&gt;Microsoft Agent Framework (MAF)&lt;/strong&gt;. Creado por los mismos equipos, MAF unifica lo mejor de ambos mundos — la estabilidad y características enterprise de Semantic Kernel con las abstracciones simples para agentes multi-turno de AutoGen — y agrega nuevas capacidades como workflows basados en grafos y un sistema robusto de manejo de estado.&lt;/p&gt;

&lt;p&gt;Al momento de escribir este artículo, MAF está en &lt;strong&gt;public preview&lt;/strong&gt;, disponible para .NET y Python bajo licencia MIT.&lt;/p&gt;

&lt;p&gt;En este artículo vamos a aprender los conceptos fundamentales del framework y a construir algo concreto: una API con ASP.NET Core 10 que expone un chatbot de soporte técnico capaz de responder preguntas basándose en documentación Markdown interna, manteniendo el contexto de la conversación entre mensajes.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;El código completo está disponible en &lt;a href="https://github.com/isaacOjeda/DevToPosts/tree/main/AgentFramework/SupportBot" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Desarrollo
&lt;/h2&gt;

&lt;h3&gt;
  
  
  ¿Qué es Microsoft Agent Framework?
&lt;/h3&gt;

&lt;p&gt;MAF gira en torno a dos conceptos centrales que vale la pena entender antes de escribir código:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Agents&lt;/strong&gt; son sistemas que usan un LLM para procesar entradas, tomar decisiones, llamar herramientas y generar respuestas. Son ideales cuando la tarea es dinámica y no puedes predecir de antemano exactamente qué pasos se van a necesitar — como una conversación de soporte donde el usuario puede preguntar cualquier cosa.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Workflows&lt;/strong&gt; son secuencias de pasos definidas explícitamente, conectadas en un grafo. Son la elección correcta cuando el flujo es predecible y necesitas control determinista sobre la ejecución — como un pipeline de procesamiento de datos o un flujo de aprobaciones.&lt;/p&gt;

&lt;p&gt;La documentación oficial tiene una regla de oro que me parece honesta y útil: &lt;em&gt;"If you can write a function to handle the task, do that instead of using an AI agent."&lt;/em&gt; No todo necesita un agente. En este artículo construimos un agente porque la naturaleza conversacional e impredecible del soporte técnico es exactamente el caso de uso para el que están diseñados.&lt;/p&gt;

&lt;h3&gt;
  
  
  Los 5 conceptos clave
&lt;/h3&gt;

&lt;p&gt;El tutorial oficial de Microsoft Learn organiza el aprendizaje en 5 pasos progresivos. Aquí los resumo en mis propias palabras antes de ver el código:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Tu primer agente.&lt;/strong&gt; Un &lt;code&gt;AIAgent&lt;/code&gt; se crea a partir de un cliente de chat (Azure OpenAI, OpenAI, etc.) con instrucciones y un nombre. Es stateless por diseño — el mismo agente puede atender múltiples conversaciones en paralelo. Correrlo es tan simple como &lt;code&gt;await agent.RunAsync("pregunta")&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Tools.&lt;/strong&gt; Cualquier método C# decorado con &lt;code&gt;[Description]&lt;/code&gt; se puede convertir en una herramienta que el agente puede llamar cuando lo necesite, usando &lt;code&gt;AIFunctionFactory.Create()&lt;/code&gt;. El LLM decide autónomamente cuándo y con qué argumentos invocarla. Esto es lo que le da al agente la capacidad de actuar sobre el mundo real.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Multi-turn conversations.&lt;/strong&gt; Como el agente es stateless, el historial de la conversación vive en un &lt;code&gt;AgentSession&lt;/code&gt;. Se crea con &lt;code&gt;agent.CreateSessionAsync()&lt;/code&gt; y se pasa en cada llamada. El agente recuerda todo lo que ocurrió en esa sesión.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Memory y Persistencia.&lt;/strong&gt; La sesión es serializable a &lt;code&gt;JsonElement&lt;/code&gt;. Esto permite guardarla en cualquier almacenamiento (memoria, base de datos, Redis) y reconstruirla después con &lt;code&gt;agent.DeserializeSessionAsync()&lt;/code&gt;. Para un chat de soporte, esto significa que el usuario puede retomar una conversación donde la dejó, incluso si el servidor se reinició.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Workflows.&lt;/strong&gt; Para orquestar múltiples agentes o pasos en secuencias definidas, se usa &lt;code&gt;WorkflowBuilder&lt;/code&gt;. Se definen &lt;code&gt;Executors&lt;/code&gt; (unidades de procesamiento) y se conectan con &lt;code&gt;Edges&lt;/code&gt;. En este artículo no los implementamos porque no añaden valor real al caso de uso — pero al final menciono cuándo sí tendría sentido usarlos.&lt;/p&gt;

&lt;h3&gt;
  
  
  El ejemplo: SupportBot
&lt;/h3&gt;

&lt;p&gt;El escenario es simple: una empresa tiene documentación interna en archivos &lt;code&gt;.md&lt;/code&gt; — guías de usuario, FAQs, manuales de módulos. En lugar de que los empleados busquen manualmente en esos archivos, un chatbot lee la documentación y responde en lenguaje natural, manteniendo el hilo de la conversación.&lt;/p&gt;

&lt;p&gt;Deliberadamente dejé fuera RAG y embeddings. La búsqueda por keywords en archivos planos es suficiente para demostrar cómo funciona MAF, y mantiene el ejemplo enfocado en el framework, no en infraestructura de búsqueda vectorial.&lt;/p&gt;

&lt;p&gt;La estructura del proyecto es la siguiente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SupportBot/
├── Program.cs
├── appsettings.json
├── Docs/
│   ├── accesos.md
│   ├── facturacion.md
│   └── reportes.md
├── Agents/
│   └── SupportAgentFactory.cs
├── Tools/
│   └── DocumentationTool.cs
├── Models/
│   ├── ChatRequest.cs
│   └── ChatResponse.cs
└── Sessions/
    └── InMemorySessionStore.cs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet new webapi &lt;span class="nt"&gt;-n&lt;/span&gt; SupportBot
&lt;span class="nb"&gt;cd &lt;/span&gt;SupportBot

dotnet add package Azure.AI.OpenAI &lt;span class="nt"&gt;--prerelease&lt;/span&gt;
dotnet add package Azure.Identity
dotnet add package Microsoft.Agents.AI &lt;span class="nt"&gt;--prerelease&lt;/span&gt;
dotnet add package Microsoft.Agents.AI.OpenAI &lt;span class="nt"&gt;--prerelease&lt;/span&gt;
dotnet add package Microsoft.Extensions.AI &lt;span class="nt"&gt;--prerelease&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Todos los paquetes de &lt;code&gt;Microsoft.Agents.AI&lt;/code&gt; están en prerelease — el flag es necesario.&lt;/p&gt;

&lt;h3&gt;
  
  
  La Tool: DocumentationTool
&lt;/h3&gt;

&lt;p&gt;Aquí está el corazón del ejemplo. &lt;code&gt;DocumentationTool&lt;/code&gt; es la herramienta que el agente llamará cuando necesite buscar información para responder al usuario.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.ComponentModel&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;SupportBot.Tools&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DocumentationTool&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;_docsPath&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;DocumentationTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;docsPath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;_docsPath&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;docsPath&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Busca en la documentación interna del sistema información sobre un tema específico. Úsala siempre antes de responder una pregunta del usuario."&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nf"&gt;GetDocumentation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"El tema o concepto sobre el que buscar. Por ejemplo: 'accesos', 'facturación', 'reportes', 'contraseña', etc."&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
        &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;topic&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;Directory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_docsPath&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"No hay documentación disponible en este momento."&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;files&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Directory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetFiles&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_docsPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"*.md"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"No hay documentación disponible en este momento."&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;keywords&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;topic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToLowerInvariant&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sc"&gt;' '&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;StringSplitOptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RemoveEmptyEntries&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;matchingFiles&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;fileName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetFileNameWithoutExtension&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;ToLowerInvariant&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadAllText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;ToLowerInvariant&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;keywords&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;fileName&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;||&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
        &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;ToList&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;filesToRead&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;matchingFiles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;matchingFiles&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToList&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;StringBuilder&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;filesToRead&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;fileName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetFileNameWithoutExtension&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadAllText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AppendLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"=== &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;fileName&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToUpperInvariant&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s"&gt; ==="&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AppendLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AppendLine&lt;/span&gt;&lt;span class="p"&gt;();&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;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToString&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;Hay dos detalles importantes aquí. El primero es que los atributos &lt;code&gt;[Description]&lt;/code&gt; no son decorativos — MAF los usa para construir el schema que el LLM recibe, y de su calidad depende que el modelo entienda bien cuándo y cómo llamar la herramienta. El segundo es la estrategia de fallback: si no encuentra archivos que hagan match con el tema, retorna todos los documentos. Es una decisión pragmática — preferimos que el agente tenga demasiado contexto a que se quede sin información para responder.&lt;/p&gt;

&lt;h3&gt;
  
  
  El Agente: SupportAgentFactory
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Azure.AI.OpenAI&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.Agents.AI&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.Extensions.AI&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;SupportBot.Tools&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;SupportBot.Agents&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SupportAgentFactory&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;AIAgent&lt;/span&gt; &lt;span class="n"&gt;_agent&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;SupportAgentFactory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IConfiguration&lt;/span&gt; &lt;span class="n"&gt;configuration&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IWebHostEnvironment&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;endpoint&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;configuration&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"AzureOpenAI:Endpoint"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
            &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;InvalidOperationException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"AzureOpenAI:Endpoint no configurado"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;deploymentName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;configuration&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"AzureOpenAI:DeploymentName"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="s"&gt;"gpt-4o-mini"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;apiKey&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;configuration&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"AzureOpenAI:ApiKey"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
            &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;InvalidOperationException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"AzureOpenAI:ApiKey no configurado"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;docsPath&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Combine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ContentRootPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Docs"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;docTool&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;DocumentationTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;docsPath&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;instructions&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"""
&lt;/span&gt;            &lt;span class="n"&gt;Eres&lt;/span&gt; &lt;span class="n"&gt;un&lt;/span&gt; &lt;span class="n"&gt;asistente&lt;/span&gt; &lt;span class="n"&gt;de&lt;/span&gt; &lt;span class="n"&gt;soporte&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="err"&gt;é&lt;/span&gt;&lt;span class="n"&gt;cnico&lt;/span&gt; &lt;span class="n"&gt;interno&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;Tu&lt;/span&gt; &lt;span class="err"&gt;ú&lt;/span&gt;&lt;span class="n"&gt;nica&lt;/span&gt; &lt;span class="n"&gt;fuente&lt;/span&gt; &lt;span class="n"&gt;de&lt;/span&gt; &lt;span class="n"&gt;informaci&lt;/span&gt;&lt;span class="err"&gt;ó&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;
            &lt;span class="n"&gt;es&lt;/span&gt; &lt;span class="n"&gt;la&lt;/span&gt; &lt;span class="n"&gt;documentaci&lt;/span&gt;&lt;span class="err"&gt;ó&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="n"&gt;del&lt;/span&gt; &lt;span class="n"&gt;sistema&lt;/span&gt; &lt;span class="n"&gt;que&lt;/span&gt; &lt;span class="n"&gt;puedes&lt;/span&gt; &lt;span class="n"&gt;consultar&lt;/span&gt; &lt;span class="n"&gt;con&lt;/span&gt; &lt;span class="n"&gt;la&lt;/span&gt; &lt;span class="n"&gt;herramienta&lt;/span&gt; &lt;span class="n"&gt;GetDocumentation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;

            &lt;span class="n"&gt;Reglas&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="n"&gt;SIEMPRE&lt;/span&gt; &lt;span class="n"&gt;consulta&lt;/span&gt; &lt;span class="n"&gt;la&lt;/span&gt; &lt;span class="n"&gt;documentaci&lt;/span&gt;&lt;span class="err"&gt;ó&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="n"&gt;antes&lt;/span&gt; &lt;span class="n"&gt;de&lt;/span&gt; &lt;span class="n"&gt;responder&lt;/span&gt; &lt;span class="n"&gt;cualquier&lt;/span&gt; &lt;span class="n"&gt;pregunta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
            &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="n"&gt;Responde&lt;/span&gt; &lt;span class="n"&gt;de&lt;/span&gt; &lt;span class="n"&gt;forma&lt;/span&gt; &lt;span class="n"&gt;clara&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;concisa&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="n"&gt;en&lt;/span&gt; &lt;span class="n"&gt;el&lt;/span&gt; &lt;span class="n"&gt;mismo&lt;/span&gt; &lt;span class="n"&gt;idioma&lt;/span&gt; &lt;span class="n"&gt;que&lt;/span&gt; &lt;span class="n"&gt;el&lt;/span&gt; &lt;span class="n"&gt;usuario&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
            &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="n"&gt;Si&lt;/span&gt; &lt;span class="n"&gt;la&lt;/span&gt; &lt;span class="n"&gt;documentaci&lt;/span&gt;&lt;span class="err"&gt;ó&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="n"&gt;no&lt;/span&gt; &lt;span class="n"&gt;contiene&lt;/span&gt; &lt;span class="n"&gt;la&lt;/span&gt; &lt;span class="n"&gt;respuesta&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dilo&lt;/span&gt; &lt;span class="n"&gt;claramente&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
            &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="n"&gt;No&lt;/span&gt; &lt;span class="n"&gt;inventes&lt;/span&gt; &lt;span class="n"&gt;informaci&lt;/span&gt;&lt;span class="err"&gt;ó&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="n"&gt;que&lt;/span&gt; &lt;span class="n"&gt;no&lt;/span&gt; &lt;span class="n"&gt;est&lt;/span&gt;&lt;span class="err"&gt;é&lt;/span&gt; &lt;span class="n"&gt;en&lt;/span&gt; &lt;span class="n"&gt;la&lt;/span&gt; &lt;span class="n"&gt;documentaci&lt;/span&gt;&lt;span class="err"&gt;ó&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
            &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="n"&gt;Si&lt;/span&gt; &lt;span class="n"&gt;el&lt;/span&gt; &lt;span class="n"&gt;usuario&lt;/span&gt; &lt;span class="n"&gt;saluda&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="n"&gt;hace&lt;/span&gt; &lt;span class="n"&gt;preguntas&lt;/span&gt; &lt;span class="n"&gt;generales&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;responde&lt;/span&gt; &lt;span class="n"&gt;amablemente&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="n"&gt;pregunta&lt;/span&gt; &lt;span class="n"&gt;en&lt;/span&gt; &lt;span class="n"&gt;qu&lt;/span&gt;&lt;span class="err"&gt;é&lt;/span&gt; &lt;span class="n"&gt;puedes&lt;/span&gt; &lt;span class="n"&gt;ayudar&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
            &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="n"&gt;No&lt;/span&gt; &lt;span class="n"&gt;respondas&lt;/span&gt; &lt;span class="n"&gt;preguntas&lt;/span&gt; &lt;span class="n"&gt;fuera&lt;/span&gt; &lt;span class="n"&gt;del&lt;/span&gt; &lt;span class="n"&gt;contexto&lt;/span&gt; &lt;span class="n"&gt;del&lt;/span&gt; &lt;span class="n"&gt;sistema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
            &lt;span class="s"&gt;""";
&lt;/span&gt;
        &lt;span class="n"&gt;_agent&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;AzureOpenAIClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Uri&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ClientModel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ApiKeyCredential&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetChatClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;deploymentName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AsIChatClient&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AsAIAgent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;instructions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;instructions&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="s"&gt;"SupportAgent"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;AIFunctionFactory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;docTool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetDocumentation&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="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;AIAgent&lt;/span&gt; &lt;span class="n"&gt;Agent&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_agent&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 flujo de construcción es una cadena: &lt;code&gt;AzureOpenAIClient&lt;/code&gt; → &lt;code&gt;GetChatClient()&lt;/code&gt; → &lt;code&gt;AsIChatClient()&lt;/code&gt; → &lt;code&gt;AsAIAgent()&lt;/code&gt;. El último paso es donde se convierten las tools registradas y las instrucciones en la configuración que el agente usará en cada conversación.&lt;/p&gt;

&lt;p&gt;Nótese que el agente se construye una sola vez y se registra como singleton. Como es stateless, puede atender todas las conversaciones concurrentes sin problema — el estado de cada conversación vive en su propia &lt;code&gt;AgentSession&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Manejo de Sesiones: InMemorySessionStore
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Collections.Concurrent&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Text.Json&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;SupportBot.Sessions&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;InMemorySessionStore&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;ConcurrentDictionary&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;JsonElement&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_sessions&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;TryGetSession&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;sessionId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="n"&gt;JsonElement&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_sessions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TryGetValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sessionId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;SaveSession&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;sessionId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;JsonElement&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_sessions&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;sessionId&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;DeleteSession&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;sessionId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_sessions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TryRemove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sessionId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="n"&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;El store guarda sesiones como &lt;code&gt;JsonElement&lt;/code&gt; — el formato en que MAF las serializa nativamente. &lt;code&gt;ConcurrentDictionary&lt;/code&gt; garantiza thread-safety sin necesidad de locks manuales. Para producción, esto se reemplazaría por Redis o una base de datos, pero la interfaz del store no cambiaría.&lt;/p&gt;

&lt;h3&gt;
  
  
  Los Endpoints: Program.cs
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Text.Json&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.Agents.AI&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;SupportBot.Agents&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;SupportBot.Models&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;SupportBot.Sessions&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;WebApplication&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddSingleton&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;SupportAgentFactory&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddSingleton&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;InMemorySessionStore&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MapPost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/chat"&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="n"&gt;ChatRequest&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;SupportAgentFactory&lt;/span&gt; &lt;span class="n"&gt;agentFactory&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;InMemorySessionStore&lt;/span&gt; &lt;span class="n"&gt;sessionStore&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IsNullOrWhiteSpace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SessionId&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;Results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;BadRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"SessionId es requerido."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IsNullOrWhiteSpace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Message&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;Results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;BadRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Message es requerido."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;agentFactory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="n"&gt;AgentSession&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sessionStore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TryGetSession&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SessionId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;serializedSession&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;session&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;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;DeserializeSessionAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;serializedSession&lt;/span&gt;&lt;span class="p"&gt;);&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="n"&gt;session&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;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateSessionAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;response&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;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RunAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;updatedSession&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;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SerializeSessionAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;sessionStore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SaveSession&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SessionId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;updatedSession&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;Results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ChatResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SessionId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MapDelete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/chat/{sessionId}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;sessionId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;InMemorySessionStore&lt;/span&gt; &lt;span class="n"&gt;sessionStore&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;sessionStore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;DeleteSession&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sessionId&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;Results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;NoContent&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="n"&gt;app&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;El endpoint &lt;code&gt;POST /chat&lt;/code&gt; sigue un patrón claro: cargar o crear sesión → ejecutar el agente → serializar y guardar la sesión actualizada → retornar la respuesta. El &lt;code&gt;sessionId&lt;/code&gt; lo genera el cliente (puede ser un GUID) y lo manda en cada request para mantener el hilo de la conversación.&lt;/p&gt;

&lt;p&gt;Un detalle de la versión RC1: &lt;code&gt;SerializeSessionAsync&lt;/code&gt; es async, a diferencia de lo que indica la documentación inicial del framework.&lt;/p&gt;

&lt;h3&gt;
  
  
  El agente en acción
&lt;/h3&gt;

&lt;p&gt;Con la API corriendo, así se ve una conversación multi-turn real:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Primer mensaje:&lt;/strong&gt;&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="err"&gt;POST&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;/chat&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;"sessionId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"usr-42"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Hola, no puedo entrar al sistema, olvidé mi contraseña"&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;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;"sessionId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"usr-42"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"reply"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"¡Hola! Para resetear tu contraseña sigue estos pasos: ve a la pantalla de login y haz clic en '¿Olvidaste tu contraseña?', ingresa tu correo corporativo y recibirás un enlace válido por 24 horas. Si no recibes el correo en 10 minutos, revisa tu carpeta de spam o contacta a TI."&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;&lt;strong&gt;Mensaje de seguimiento (mismo sessionId):&lt;/strong&gt;&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="err"&gt;POST&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;/chat&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;"sessionId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"usr-42"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"¿Y si tampoco recuerdo mi correo corporativo?"&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;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;"sessionId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"usr-42"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"reply"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"En ese caso, la cuenta puede quedar bloqueada tras 5 intentos fallidos. Te recomiendo contactar directamente al equipo de soporte en soporte@empresa.com o llamar al ext. 100 para que puedan verificar tu identidad y ayudarte a recuperar el acceso."&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;El agente recuerda que estamos hablando de un problema de acceso y responde en contexto, sin necesidad de que el usuario repita la situación.&lt;/p&gt;

&lt;h3&gt;
  
  
  ¿Cuándo agregarías Workflows aquí?
&lt;/h3&gt;

&lt;p&gt;En este ejemplo los Workflows no añaden valor — el flujo conversacional es impredecible por naturaleza y el agente lo maneja bien solo. Pero hay escenarios donde sí tendría sentido introducirlos:&lt;/p&gt;

&lt;p&gt;Si quisieras &lt;strong&gt;clasificar automáticamente la intención&lt;/strong&gt; antes de responder (¿es una pregunta de accesos, facturación o reportes?) y enrutar a agentes especializados según el tema, un Workflow con un executor de clasificación seguido de ejecutores especializados sería la arquitectura correcta.&lt;/p&gt;

&lt;p&gt;Si quisieras &lt;strong&gt;escalar a un humano&lt;/strong&gt; cuando el agente no encuentra respuesta, un Workflow con un paso de "human-in-the-loop" integrado en el grafo lo haría de forma limpia y auditable.&lt;/p&gt;

&lt;p&gt;La regla es simple: si puedes predecir los pasos, usa un Workflow. Si la conversación es abierta e impredecible, deja que el agente decida.&lt;/p&gt;




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

&lt;p&gt;Microsoft Agent Framework simplifica genuinamente la construcción de agentes conversacionales en .NET. La abstracción de &lt;code&gt;AIAgent&lt;/code&gt;, el manejo de sesiones serializable y el sistema de tools via atributos &lt;code&gt;[Description]&lt;/code&gt; permiten tener algo funcional con muy poco código de infraestructura — lo que queda es lógica de negocio real.&lt;/p&gt;

&lt;p&gt;El hecho de que aún esté en preview se nota en algunos detalles: la API cambia entre versiones (como el caso de &lt;code&gt;SerializeSessionAsync&lt;/code&gt; que en RC1 es async), y la documentación a veces no refleja el estado actual del código. Dicho esto, para proyectos nuevos donde el timeline lo permite, ya vale la pena apostar por él en lugar de Semantic Kernel — es el camino hacia adelante según el propio equipo de Microsoft.&lt;/p&gt;

&lt;p&gt;Los siguientes pasos naturales para este proyecto serían reemplazar el &lt;code&gt;InMemorySessionStore&lt;/code&gt; por Redis o SQL Server para persistencia real, agregar autenticación al endpoint, e integrar un frontend — pero eso es material para otro artículo.&lt;/p&gt;




&lt;h2&gt;
  
  
  Referencias
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://learn.microsoft.com/en-us/agent-framework/overview/agent-framework-overview" rel="noopener noreferrer"&gt;Microsoft Agent Framework Overview&lt;/a&gt; — Microsoft Learn&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://learn.microsoft.com/en-us/agent-framework/get-started/" rel="noopener noreferrer"&gt;Get Started: Steps 1–5&lt;/a&gt; — Microsoft Learn&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/microsoft/agent-framework" rel="noopener noreferrer"&gt;Repositorio oficial con samples&lt;/a&gt; — GitHub&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://devblogs.microsoft.com/semantic-kernel/semantic-kernel-and-microsoft-agent-framework/" rel="noopener noreferrer"&gt;Anuncio oficial: Semantic Kernel y Microsoft Agent Framework&lt;/a&gt; — Microsoft Dev Blog&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://learn.microsoft.com/en-us/agent-framework/migration-guide/from-semantic-kernel/" rel="noopener noreferrer"&gt;Migration Guide from Semantic Kernel&lt;/a&gt; — Microsoft Learn&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>dotnet</category>
      <category>csharp</category>
      <category>aspnetcore</category>
    </item>
    <item>
      <title>Sistema de Control de Jobs en Tiempo Real con Channels y Background Services en .NET</title>
      <dc:creator>Isaac Ojeda</dc:creator>
      <pubDate>Sat, 01 Nov 2025 22:42:58 +0000</pubDate>
      <link>https://forem.com/isaacojeda/sistema-de-control-de-jobs-en-tiempo-real-con-channels-y-background-services-en-net-529e</link>
      <guid>https://forem.com/isaacojeda/sistema-de-control-de-jobs-en-tiempo-real-con-channels-y-background-services-en-net-529e</guid>
      <description>&lt;h2&gt;
  
  
  Introducción
&lt;/h2&gt;

&lt;p&gt;En el desarrollo moderno de aplicaciones, es común necesitar ejecutar &lt;strong&gt;procesos en segundo plano&lt;/strong&gt; que se comuniquen con nuestra API de forma eficiente y segura. Tradicionalmente, esto se resolvía con implementaciones complejas usando locks, colas manuales o infraestructura externa como RabbitMQ. Sin embargo, .NET ofrece una solución simple pero elegante: &lt;strong&gt;System.Threading.Channels&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;En este artículo, exploraremos cómo construir un sistema de control de jobs en tiempo real utilizando:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🔧 &lt;strong&gt;Channels&lt;/strong&gt; para comunicación thread-safe entre componentes&lt;/li&gt;
&lt;li&gt;🔄 &lt;strong&gt;Background Services&lt;/strong&gt; para tareas recurrentes&lt;/li&gt;
&lt;li&gt;🚀 &lt;strong&gt;Minimal APIs&lt;/strong&gt; para endpoints modernos y limpios&lt;/li&gt;
&lt;li&gt;⚡ &lt;strong&gt;TaskCompletionSource&lt;/strong&gt; para comunicación bidireccional&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Al finalizar, tendrás un proyecto funcional que puedes adaptar para casos de uso reales como procesamiento de emails, análisis de imágenes, generación de reportes, y más.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Nota: El código fuente siempre lo encontrarás en mi github -&amp;gt; &lt;a href="https://github.com/isaacOjeda/DevToPosts/tree/main/ApiBackgroundChannels" rel="noopener noreferrer"&gt;DevToPosts/ApiBackgroundChannels at main · isaacOjeda/DevToPosts&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  ¿Qué son los Channels?
&lt;/h2&gt;

&lt;p&gt;Los &lt;strong&gt;Channels&lt;/strong&gt; en .NET son estructuras de datos thread-safe diseñadas para escenarios &lt;strong&gt;productor-consumidor&lt;/strong&gt;. Piensa en ellos como una "tubería" donde un lado escribe datos y el otro los lee, sin preocuparte por locks o sincronización manual.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;¿Por qué usarlos?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ Thread-safe por diseño&lt;/li&gt;
&lt;li&gt;✅ Alta performance con bajo overhead&lt;/li&gt;
&lt;li&gt;✅ Backpressure integrado (control de flujo)&lt;/li&gt;
&lt;li&gt;✅ Ideal para comunicación entre hilos/tareas&lt;/li&gt;
&lt;li&gt;✅ Alternativa simple a colas externas (RabbitMQ, Redis) para escenarios internos&lt;/li&gt;
&lt;li&gt;✅ Optimizado para async/await (usa &lt;code&gt;ValueTask&lt;/code&gt; internamente)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Arquitectura del Proyecto
&lt;/h2&gt;

&lt;p&gt;Este proyecto demuestra cómo controlar un &lt;strong&gt;Background Job&lt;/strong&gt; desde una API usando Channels para comunicación bidireccional:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌──────────────┐         ┌─────────┐         ┌──────────────────┐
│  API Request │ ──────&amp;gt; │ Channel │ ──────&amp;gt; │ Background Job   │
│  (Productor) │         │ (Cola)  │         │ (Consumidor)     │
└──────────────┘         └─────────┘         └──────────────────┘
      ↑                                             │
      └────── TaskCompletionSource ─────────────────┘
                     (Respuesta)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Paso 1: Definir el Modelo de Comunicación
&lt;/h2&gt;

&lt;p&gt;Primero, necesitamos estructuras para enviar comandos y recibir respuestas:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;enum&lt;/span&gt; &lt;span class="n"&gt;CommandType&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;Start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Stop&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;GetStatus&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;JobCommand&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;CommandType&lt;/span&gt; &lt;span class="n"&gt;Type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;TaskCompletionSource&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;JobStatus&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;?&lt;/span&gt; &lt;span class="n"&gt;ResponseTask&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&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="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;JobStatus&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;IsRunning&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;ExecutionCount&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;LastExecutionTime&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Message&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&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="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Empty&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;💡 Clave:&lt;/strong&gt; &lt;code&gt;TaskCompletionSource&lt;/code&gt; nos permite crear una Task que completaremos manualmente cuando tengamos la respuesta, haciendo posible la comunicación bidireccional.&lt;/p&gt;

&lt;h2&gt;
  
  
  Paso 2: Crear el Background Service (Consumidor)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;JobProcessor&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;BackgroundService&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;Channel&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;JobCommand&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_channel&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;_isJobRunning&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;_executionCount&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;ExecuteAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;stoppingToken&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;_logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogInformation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"JobProcessor iniciado. Esperando comandos..."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// ✅ Patrón recomendado por Microsoft: WaitToReadAsync + TryRead&lt;/span&gt;
        &lt;span class="c1"&gt;// Más eficiente que ReadAllAsync para alta concurrencia&lt;/span&gt;
        &lt;span class="k"&gt;while&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;_channel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WaitToReadAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stoppingToken&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_channel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TryRead&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;try&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;ProcessCommandAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stoppingToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Exception&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;_logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Error procesando comando"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                    &lt;span class="c1"&gt;// ✅ Notificar errores al productor&lt;/span&gt;
                    &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResponseTask&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;TrySetException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&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;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;ProcessCommandAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;JobCommand&lt;/span&gt; &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;CommandType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Start&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;_isJobRunning&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Task&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="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;RunRecurringJobAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

                &lt;span class="c1"&gt;// Enviar respuesta al productor&lt;/span&gt;
                &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResponseTask&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;SetResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;JobStatus&lt;/span&gt; 
                &lt;span class="p"&gt;{&lt;/span&gt; 
                    &lt;span class="n"&gt;IsRunning&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
                    &lt;span class="n"&gt;Message&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Job iniciado"&lt;/span&gt; 
                &lt;span class="p"&gt;});&lt;/span&gt;
                &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

            &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;CommandType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Stop&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;_isJobRunning&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResponseTask&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;SetResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;JobStatus&lt;/span&gt; 
                &lt;span class="p"&gt;{&lt;/span&gt; 
                    &lt;span class="n"&gt;IsRunning&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
                    &lt;span class="n"&gt;Message&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Job detenido"&lt;/span&gt; 
                &lt;span class="p"&gt;});&lt;/span&gt;
                &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

            &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;CommandType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetStatus&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResponseTask&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;SetResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;JobStatus&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;IsRunning&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_isJobRunning&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;ExecutionCount&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_executionCount&lt;/span&gt;
                &lt;span class="p"&gt;});&lt;/span&gt;
                &lt;span class="k"&gt;break&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;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;WaitToReadAsync()&lt;/code&gt; + &lt;code&gt;TryRead()&lt;/code&gt;&lt;/strong&gt;: Patrón recomendado por Microsoft para mejor performance&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;TrySetException()&lt;/code&gt;&lt;/strong&gt;: Propaga errores al productor de forma segura&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bucle anidado&lt;/strong&gt;: Procesa múltiples comandos en batch cuando están disponibles&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Paso 3: Configurar el Channel y el Servicio
&lt;/h2&gt;

&lt;p&gt;En &lt;code&gt;Program.cs&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ✅ Bounded Channel con opciones optimizadas (recomendado por Microsoft)&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddSingleton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Channel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CreateBounded&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;JobCommand&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;BoundedChannelOptions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;FullMode&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;BoundedChannelFullMode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Wait&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Backpressure automático&lt;/span&gt;
        &lt;span class="n"&gt;SingleWriter&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Múltiples endpoints pueden escribir&lt;/span&gt;
        &lt;span class="n"&gt;SingleReader&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;   &lt;span class="c1"&gt;// Solo un BackgroundService consume&lt;/span&gt;
    &lt;span class="p"&gt;}));&lt;/span&gt;

&lt;span class="c1"&gt;// Registrar el Background Service&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddHostedService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;JobProcessor&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;💡 ¿Bounded vs Unbounded?&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Característica&lt;/th&gt;
&lt;th&gt;Unbounded&lt;/th&gt;
&lt;th&gt;Bounded&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Capacidad&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Ilimitada&lt;/td&gt;
&lt;td&gt;Limitada (configurable)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Memoria&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Puede crecer sin control&lt;/td&gt;
&lt;td&gt;Controlada&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Backpressure&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Sí (automático)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Uso recomendado&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Productores lentos&lt;/td&gt;
&lt;td&gt;Productores rápidos&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Performance&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Writes síncronos&lt;/td&gt;
&lt;td&gt;Writes pueden ser async&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Modos de Bounded Channel (&lt;code&gt;FullMode&lt;/code&gt;):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;Wait&lt;/code&gt;&lt;/strong&gt; (recomendado): Espera hasta que haya espacio (backpressure)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;DropWrite&lt;/code&gt;&lt;/strong&gt;: Descarta el nuevo elemento&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;DropOldest&lt;/code&gt;&lt;/strong&gt;: Descarta el elemento más antiguo&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;DropNewest&lt;/code&gt;&lt;/strong&gt;: Descarta el elemento más reciente&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Opciones de optimización:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;SingleWriter&lt;/code&gt;&lt;/strong&gt;: &lt;code&gt;true&lt;/code&gt; = mejor performance si solo un productor escribe&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;SingleReader&lt;/code&gt;&lt;/strong&gt;: &lt;code&gt;true&lt;/code&gt; = mejor performance si solo un consumidor lee&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;AllowSynchronousContinuations&lt;/code&gt;&lt;/strong&gt;: &lt;code&gt;false&lt;/code&gt; (default) para evitar bloqueos&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Paso 4: Crear los Endpoints (Productores)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;IEndpointRouteBuilder&lt;/span&gt; &lt;span class="nf"&gt;MapJobEndpoints&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt; &lt;span class="n"&gt;IEndpointRouteBuilder&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;jobGroup&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MapGroup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"api/job"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="n"&gt;jobGroup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MapPost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"start"&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="n"&gt;Channel&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;JobCommand&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Crear TaskCompletionSource para esperar la respuesta&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;tcs&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;TaskCompletionSource&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;JobStatus&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;

        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;command&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;JobCommand&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;Type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;CommandType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;ResponseTask&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tcs&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;

        &lt;span class="c1"&gt;// ✅ WriteAsync maneja backpressure automáticamente&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Esperar respuesta del consumidor&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;status&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;tcs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Task&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;Results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="c1"&gt;// Endpoints similares para stop y status...&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;app&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;💡 Flujo:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;API recibe request → Crea &lt;code&gt;TaskCompletionSource&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Escribe comando en el Channel con &lt;code&gt;WriteAsync()&lt;/code&gt; (maneja backpressure)&lt;/li&gt;
&lt;li&gt;Espera que el consumidor complete la Task&lt;/li&gt;
&lt;li&gt;Retorna la respuesta al cliente&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  ¿Por qué este patrón?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Sin Channels:
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ❌ Locks manuales, propenso a errores&lt;/span&gt;
&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="kt"&gt;object&lt;/span&gt; &lt;span class="n"&gt;_lock&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;Queue&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Command&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_queue&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;AddCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Command&lt;/span&gt; &lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;lock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_lock&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;_queue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Enqueue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cmd&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;h3&gt;
  
  
  Con Channels:
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ✅ Thread-safe automático, limpio, con backpressure&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Casos de Uso Reales con Channels
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. &lt;strong&gt;Cola de Emails/Notificaciones&lt;/strong&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Microsoft recomienda Bounded para prevenir OutOfMemory&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;emailChannel&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Channel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CreateBounded&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;EmailMessage&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;BoundedChannelOptions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;FullMode&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;BoundedChannelFullMode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Wait&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="c1"&gt;// API recibe requests → Encola en Channel → Background envía emails en lotes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. &lt;strong&gt;Procesamiento de Imágenes&lt;/strong&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;Channel&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ImageProcessingJob&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;imageChannel&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// Upload de imágenes → Channel → Worker redimensiona/optimiza en background&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. &lt;strong&gt;Logs Centralizados&lt;/strong&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// DropOldest para logs: si está lleno, descarta los más antiguos&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;logChannel&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Channel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CreateBounded&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;LogEntry&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;BoundedChannelOptions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;FullMode&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;BoundedChannelFullMode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DropOldest&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. &lt;strong&gt;Rate Limiting / Throttling&lt;/strong&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;boundedChannel&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Channel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CreateBounded&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="m"&gt;100&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// Limita a 100 requests concurrentes, el resto espera (backpressure)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  5. &lt;strong&gt;Event Sourcing Interno&lt;/strong&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;Channel&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;DomainEvent&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;eventChannel&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// Eventos de dominio → Channel → Múltiples handlers procesan en paralelo&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  6. &lt;strong&gt;Pipeline de Datos (ejemplo oficial de Microsoft)&lt;/strong&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Patrón de processing pipeline con múltiples stages&lt;/span&gt;
&lt;span class="n"&gt;Channel&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;RawData&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;inputChannel&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;Channel&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ProcessedData&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;outputChannel&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// Stage 1: Raw → Validated → Stage 2: Validated → Enriched&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Usa Channels cuando:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ Comunicación dentro de la misma aplicación&lt;/li&gt;
&lt;li&gt;✅ Necesitas alta performance y bajo latency&lt;/li&gt;
&lt;li&gt;✅ Quieres simplicidad sin infraestructura externa&lt;/li&gt;
&lt;li&gt;✅ Trabajas con async/await&lt;/li&gt;
&lt;li&gt;✅ Necesitas backpressure automático&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Usa Queue externo (RabbitMQ/Azure Service Bus) cuando:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;❌ Necesitas comunicación entre múltiples aplicaciones/servicios&lt;/li&gt;
&lt;li&gt;❌ Requieres persistencia de mensajes&lt;/li&gt;
&lt;li&gt;❌ Necesitas escalabilidad horizontal&lt;/li&gt;
&lt;li&gt;❌ Requieres garantías de entrega (at-least-once, exactly-once)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  ¿Por qué usar Channels?
&lt;/h3&gt;

&lt;p&gt;A lo largo de este tutorial, hemos visto cómo Channels ofrece ventajas significativas:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Simplicidad&lt;/strong&gt;: No necesitas infraestructura externa para empezar&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Performance&lt;/strong&gt;: Diseñados desde cero para async/await con &lt;code&gt;ValueTask&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Seguridad&lt;/strong&gt;: Thread-safe por diseño, sin preocupaciones por race conditions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Control&lt;/strong&gt;: Backpressure automático previene sobrecarga del sistema&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Flexibilidad&lt;/strong&gt;: Configuración granular según tus necesidades específicas&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Impacto en tu arquitectura
&lt;/h3&gt;

&lt;p&gt;Este patrón es especialmente valioso cuando:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Estás construyendo &lt;strong&gt;aplicaciones monolíticas modernas&lt;/strong&gt; que necesitan procesamiento asíncrono&lt;/li&gt;
&lt;li&gt;Quieres &lt;strong&gt;reducir costos&lt;/strong&gt; de infraestructura eliminando dependencias de message brokers&lt;/li&gt;
&lt;li&gt;Necesitas &lt;strong&gt;optimizar performance&lt;/strong&gt; con procesamiento en memoria&lt;/li&gt;
&lt;li&gt;Buscas &lt;strong&gt;simplicidad operacional&lt;/strong&gt; sin sacrificar escalabilidad vertical&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Los Channels de .NET demuestran que no siempre necesitas herramientas complejas para resolver problemas complejos. A veces, la solución más elegante es la que viene incorporada en tu framework.&lt;/p&gt;

&lt;h2&gt;
  
  
  Próximos Pasos
&lt;/h2&gt;

&lt;p&gt;¿Listo para llevar este conocimiento al siguiente nivel? Aquí tienes algunas ideas para expandir este proyecto:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. &lt;strong&gt;Implementar Múltiples Consumidores&lt;/strong&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Escalar procesamiento con múltiples workers&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddHostedService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;JobProcessor&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt; &lt;span class="c1"&gt;// Worker 1&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddHostedService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;JobProcessor&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt; &lt;span class="c1"&gt;// Worker 2&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddHostedService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;JobProcessor&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt; &lt;span class="c1"&gt;// Worker 3&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Aprenderás:&lt;/strong&gt; Paralelización, distribución de carga, sincronización entre workers&lt;/p&gt;

&lt;h3&gt;
  
  
  2. &lt;strong&gt;Integrar Observabilidad&lt;/strong&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Métricas con System.Diagnostics.Metrics&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;meter&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Meter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"BackgroundJobs"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;jobsProcessed&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;meter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CreateCounter&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;long&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="s"&gt;"jobs_processed"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;queueDepth&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;meter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateObservableGauge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"queue_depth"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Count&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;Aprenderás:&lt;/strong&gt; OpenTelemetry, métricas personalizadas, dashboards con Grafana/Prometheus&lt;/p&gt;

&lt;h3&gt;
  
  
  3. &lt;strong&gt;Implementar Persistencia&lt;/strong&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Guardar estado en caso de restart&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PersistentJobProcessor&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;BackgroundService&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;IJobStateRepository&lt;/span&gt; &lt;span class="n"&gt;_repository&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;ExecuteAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Recuperar jobs pendientes al iniciar&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_repository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RestorePendingJobsAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="c1"&gt;// ... continuar procesamiento normal&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;Aprenderás:&lt;/strong&gt; State management, recovery strategies, durabilidad&lt;/p&gt;

&lt;h3&gt;
  
  
  4. &lt;strong&gt;Agregar Pipeline de Procesamiento&lt;/strong&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Pipeline multi-etapa&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;rawChannel&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Channel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CreateBounded&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;RawData&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="m"&gt;100&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;validatedChannel&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Channel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CreateBounded&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ValidatedData&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="m"&gt;100&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;enrichedChannel&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Channel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CreateBounded&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;EnrichedData&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="m"&gt;100&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Stage 1: Validación&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddHostedService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ValidationProcessor&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
&lt;span class="c1"&gt;// Stage 2: Enriquecimiento&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddHostedService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;EnrichmentProcessor&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
&lt;span class="c1"&gt;// Stage 3: Persistencia&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddHostedService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;PersistenceProcessor&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Aprenderás:&lt;/strong&gt; Pipeline pattern, ETL processes, data transformation&lt;/p&gt;

&lt;h3&gt;
  
  
  5. &lt;strong&gt;Implementar Rate Limiting Avanzado&lt;/strong&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Rate limiter con ventanas deslizantes&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RateLimitedJobProcessor&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;BackgroundService&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;RateLimiter&lt;/span&gt; &lt;span class="n"&gt;_rateLimiter&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;ExecuteAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;while&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;_channel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WaitToReadAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;lease&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;_rateLimiter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AcquireAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lease&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsAcquired&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="c1"&gt;// Procesar job&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;&lt;strong&gt;Aprenderás:&lt;/strong&gt; Rate limiting patterns, token bucket, leaky bucket&lt;/p&gt;

&lt;h3&gt;
  
  
  6. &lt;strong&gt;Crear Dashboard de Monitoreo&lt;/strong&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// SignalR para updates en tiempo real&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddSignalR&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Notificar estado a clientes conectados&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_hubContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Clients&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;All&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SendAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"JobStatusUpdate"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;QueueDepth&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ProcessingRate&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;jobsPerSecond&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ActiveJobs&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;activeJobCount&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;Aprenderás:&lt;/strong&gt; Real-time updates, SignalR, live dashboards&lt;/p&gt;

&lt;h3&gt;
  
  
  7. &lt;strong&gt;Añadir Resiliencia&lt;/strong&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Polly para retry policies&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;retryPolicy&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Policy&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Handle&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WaitAndRetryAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;retryAttempt&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; 
        &lt;span class="n"&gt;TimeSpan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FromSeconds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Pow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;retryAttempt&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;retryPolicy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ExecuteAsync&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="p"&gt;=&amp;gt;&lt;/span&gt; 
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;ProcessJobAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;command&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;Aprenderás:&lt;/strong&gt; Retry patterns, circuit breakers, fallback strategies&lt;/p&gt;

&lt;h3&gt;
  
  
  8. &lt;strong&gt;Implementar Health Checks&lt;/strong&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Health check para el channel&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddHealthChecks&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddCheck&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ChannelHealthCheck&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="s"&gt;"channel_health"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddCheck&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;JobProcessorHealthCheck&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="s"&gt;"job_processor_health"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ChannelHealthCheck&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IHealthCheck&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;HealthCheckResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;CheckHealthAsync&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;queueDepth&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_channel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Count&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;queueDepth&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="m"&gt;1000&lt;/span&gt; 
            &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;HealthCheckResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Healthy&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; 
            &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;HealthCheckResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Degraded&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Queue depth high"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Aprenderás:&lt;/strong&gt; Health monitoring, readiness/liveness probes, Kubernetes integration&lt;/p&gt;

&lt;h3&gt;
  
  
  9. &lt;strong&gt;Migrar a Arquitectura Distribuida&lt;/strong&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Cuando crezcas más allá de un solo servidor&lt;/span&gt;
&lt;span class="c1"&gt;// Considera migrar a:&lt;/span&gt;
&lt;span class="c1"&gt;// - Azure Service Bus para messaging distribuido&lt;/span&gt;
&lt;span class="c1"&gt;// - Azure Queue Storage para simplicidad y bajo costo&lt;/span&gt;
&lt;span class="c1"&gt;// - RabbitMQ para control total&lt;/span&gt;
&lt;span class="c1"&gt;// - Redis Streams para alta performance&lt;/span&gt;

&lt;span class="c1"&gt;// Mantén la misma interfaz, cambia la implementación&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;IJobQueue&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;EnqueueAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;JobCommand&lt;/span&gt; &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;JobCommand&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;DequeueAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Implementación con Channels (actual)&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;InMemoryJobQueue&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IJobQueue&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Implementación con Azure Service Bus (futuro)&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ServiceBusJobQueue&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IJobQueue&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;Aprenderás:&lt;/strong&gt; Estrategias de migración, abstracciones, arquitectura evolutiva&lt;/p&gt;

&lt;h3&gt;
  
  
  Recursos para Continuar Aprendiendo
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Documentación Oficial:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/dotnet/api/system.threading.channels" rel="noopener noreferrer"&gt;System.Threading.Channels API Reference&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-9.0&amp;amp;tabs=visual-studio" rel="noopener noreferrer"&gt;Background tasks with hosted services in ASP.NET Core | Microsoft Learn&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/dotnet/core/extensions/channels" rel="noopener noreferrer"&gt;Channels Library Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/aspnet/core/fundamentals/host/hosted-services" rel="noopener noreferrer"&gt;Background Services in ASP.NET Core&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Artículos Avanzados:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://devblogs.microsoft.com/dotnet/an-introduction-to-system-threading-channels/" rel="noopener noreferrer"&gt;An Introduction to System.Threading.Channels&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/dotnet/standard/parallel-programming/how-to-implement-a-producer-consumer-dataflow-pattern" rel="noopener noreferrer"&gt;Producer/Consumer Patterns with TPL Dataflow&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>dotnet</category>
      <category>architecture</category>
      <category>spanish</category>
      <category>csharp</category>
    </item>
    <item>
      <title>[Parte 4] Construyendo un Agente Conversacional con el Agent Framework de Semantic Kernel</title>
      <dc:creator>Isaac Ojeda</dc:creator>
      <pubDate>Sat, 19 Jul 2025 18:56:35 +0000</pubDate>
      <link>https://forem.com/isaacojeda/parte-4-construyendo-un-agente-conversacional-con-el-agent-framework-de-semantic-kernel-2nn8</link>
      <guid>https://forem.com/isaacojeda/parte-4-construyendo-un-agente-conversacional-con-el-agent-framework-de-semantic-kernel-2nn8</guid>
      <description>&lt;h2&gt;
  
  
  🧠 Crea tu propio agente conversacional con Semantic Kernel
&lt;/h2&gt;

&lt;p&gt;¿Te imaginas construir un asistente conversacional que entienda lenguaje natural y, además, sea capaz de ejecutar funciones reales de tu sistema? Con el &lt;strong&gt;Agent Framework de Semantic Kernel&lt;/strong&gt;, eso ya no es solo posible, sino sorprendentemente sencillo.&lt;/p&gt;

&lt;p&gt;En este tutorial te voy a mostrar cómo crear un &lt;strong&gt;agente inteligente especializado en gestión de facturas&lt;/strong&gt;, diseñado para ayudar a abogados en una notaría pública. Este agente no solo entiende lo que le pides, sino que también puede:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Consultar el estado de una factura.&lt;/li&gt;
&lt;li&gt;Crear prefacturas automáticamente.&lt;/li&gt;
&lt;li&gt;Ver facturas vencidas.&lt;/li&gt;
&lt;li&gt;Marcar facturas como pagadas.&lt;/li&gt;
&lt;li&gt;Obtener información detallada de un cliente.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Todo esto lo logra invocando funciones reales en C#, conectadas a una base de datos real y expuestas mediante un plugin personalizado.&lt;/p&gt;

&lt;p&gt;Durante el artículo veremos paso a paso:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cómo configurar Semantic Kernel con Azure OpenAI.&lt;/li&gt;
&lt;li&gt;Cómo definir tus propias funciones con &lt;code&gt;[KernelFunction]&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Cómo crear un &lt;code&gt;ChatCompletionAgent&lt;/code&gt; con instrucciones personalizadas.&lt;/li&gt;
&lt;li&gt;Cómo ejecutar al agente dentro de una API HTTP.&lt;/li&gt;
&lt;li&gt;Y cómo mantener conversaciones con historial persistente.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Además, el proyecto incluye un cliente web de ejemplo (tipo chat) para que puedas probarlo todo en vivo y adaptar la solución a tus propios casos de uso.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;🔗 Todo el código está disponible en este repositorio:&lt;br&gt;&lt;br&gt;
&lt;a href="https://github.com/isaacOjeda/DevToPosts/tree/main/SemanticKernelSeries/SemanticKernelLearning04" rel="noopener noreferrer"&gt;github.com/isaacOjeda/DevToPosts/tree/main/SemanticKernelSeries/SemanticKernelLearning04&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&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%2Fc7f8juqe3rrci13y7v6c.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%2Fc7f8juqe3rrci13y7v6c.png" alt=" " width="800" height="791"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  🧠 ¿Qué es el Agent Framework en Semantic Kernel?
&lt;/h2&gt;

&lt;p&gt;El &lt;strong&gt;Agent Framework&lt;/strong&gt; es una de las capacidades más potentes introducidas en las versiones recientes de &lt;a href="https://github.com/microsoft/semantic-kernel" rel="noopener noreferrer"&gt;Semantic Kernel&lt;/a&gt;: un sistema que permite &lt;strong&gt;crear agentes conversacionales inteligentes&lt;/strong&gt; con la capacidad de razonar, planificar e invocar funciones definidas por el desarrollador para lograr un objetivo.&lt;/p&gt;

&lt;p&gt;A diferencia de un chatbot tradicional basado en reglas, un agente en Semantic Kernel:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Comprende instrucciones en lenguaje natural.&lt;/li&gt;
&lt;li&gt;Decide qué acciones tomar en base al contexto disponible.&lt;/li&gt;
&lt;li&gt;Invoca funciones (también llamadas “skills” o “plugins”) que tú defines en código.&lt;/li&gt;
&lt;li&gt;Puede encadenar múltiples pasos de razonamiento para llegar a un resultado.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Este modelo es ideal para construir asistentes especializados con conocimientos de negocio y herramientas personalizadas.&lt;/p&gt;

&lt;h3&gt;
  
  
  🎯 Caso de uso: un asistente de facturación
&lt;/h3&gt;

&lt;p&gt;En este tutorial vamos a construir un ejemplo real usando el Agent Framework: un &lt;strong&gt;asistente conversacional para abogados en una notaría pública&lt;/strong&gt;, cuya función es &lt;strong&gt;gestionar facturas&lt;/strong&gt; mediante lenguaje natural.&lt;/p&gt;

&lt;p&gt;El asistente será capaz de:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Verificar si una factura está pagada o vencida.&lt;/li&gt;
&lt;li&gt;Crear prefacturas para clientes nuevos.&lt;/li&gt;
&lt;li&gt;Consultar el listado de facturas pendientes de pago.&lt;/li&gt;
&lt;li&gt;Marcar una factura como pagada.&lt;/li&gt;
&lt;li&gt;Obtener información detallada de un cliente y su historial de facturación.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Todo esto será posible gracias a una capa de funciones en C# expuestas al agente mediante anotaciones &lt;code&gt;[KernelFunction]&lt;/code&gt;, y alimentadas por un servicio de backend que accede a la base de datos.&lt;/p&gt;

&lt;h3&gt;
  
  
  🧬 ¿Por qué usar agentes en lugar de comandos o controladores tradicionales?
&lt;/h3&gt;

&lt;p&gt;Con el Agent Framework:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;El usuario no necesita conocer los comandos disponibles.&lt;/li&gt;
&lt;li&gt;El agente elige por sí mismo la función que debe usar.&lt;/li&gt;
&lt;li&gt;Puedes cambiar o extender el comportamiento del sistema sin modificar el frontend.&lt;/li&gt;
&lt;li&gt;La experiencia es más natural, fluida y adaptable a distintos usuarios.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Esto hace que construir asistentes LLM especializados con Semantic Kernel sea una excelente opción para &lt;strong&gt;interfaces inteligentes&lt;/strong&gt; sobre sistemas de negocio existentes, especialmente en entornos con operaciones rutinarias, como la gestión de facturas.&lt;/p&gt;

&lt;h2&gt;
  
  
  ⚙️ Requisitos previos y setup del entorno
&lt;/h2&gt;

&lt;p&gt;Antes de construir nuestro asistente de facturación, necesitamos preparar nuestro proyecto con las dependencias necesarias y configurar el acceso a un modelo de lenguaje compatible (en este caso, Azure OpenAI).&lt;/p&gt;

&lt;p&gt;Este ejemplo está construido sobre .NET 9, con soporte para SQLite como base de datos local, y aprovecha la integración entre Semantic Kernel y Azure OpenAI.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;🗂️ &lt;strong&gt;Puedes consultar el código completo y funcional de este proyecto en el repositorio:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
🔗 &lt;a href="https://github.com/isaacOjeda/DevToPosts/tree/main/SemanticKernelSeries/SemanticKernelLearning04" rel="noopener noreferrer"&gt;github.com/isaacOjeda/DevToPosts/tree/main/SemanticKernelSeries/SemanticKernelLearning04&lt;/a&gt;&lt;br&gt;&lt;br&gt;
Allí encontrarás todos los archivos de configuración, modelos, servicios y ejemplos necesarios para correr este asistente de forma local o integrarlo en tu aplicación.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  📦 Paquetes necesarios
&lt;/h3&gt;

&lt;p&gt;El Agent Framework se encuentra dentro del paquete &lt;code&gt;Microsoft.SemanticKernel.Agents.Core&lt;/code&gt;, por lo que debes incluir explícitamente las dependencias en tu archivo &lt;code&gt;.csproj&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;ItemGroup&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"Microsoft.SemanticKernel"&lt;/span&gt; &lt;span class="na"&gt;Version=&lt;/span&gt;&lt;span class="s"&gt;"1.60.0"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"Microsoft.SemanticKernel.Agents.Core"&lt;/span&gt; &lt;span class="na"&gt;Version=&lt;/span&gt;&lt;span class="s"&gt;"1.60.0"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"Microsoft.AspNetCore.OpenApi"&lt;/span&gt; &lt;span class="na"&gt;Version=&lt;/span&gt;&lt;span class="s"&gt;"9.0.7"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"Microsoft.EntityFrameworkCore.Sqlite"&lt;/span&gt; &lt;span class="na"&gt;Version=&lt;/span&gt;&lt;span class="s"&gt;"9.0.0"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"Microsoft.EntityFrameworkCore.Design"&lt;/span&gt; &lt;span class="na"&gt;Version=&lt;/span&gt;&lt;span class="s"&gt;"9.0.0"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"Microsoft.VisualStudio.Azure.Containers.Tools.Targets"&lt;/span&gt; &lt;span class="na"&gt;Version=&lt;/span&gt;&lt;span class="s"&gt;"1.22.1"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/ItemGroup&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Esto nos da acceso al núcleo de Semantic Kernel, las capacidades de agentes, así como herramientas necesarias para la persistencia y desarrollo web.&lt;/p&gt;

&lt;h3&gt;
  
  
  🧪 Configurando el Kernel
&lt;/h3&gt;

&lt;p&gt;El kernel es el núcleo que conecta el modelo de lenguaje con tus plugins. En este ejemplo, usamos Azure OpenAI para procesar instrucciones en lenguaje natural. A continuación se muestra cómo configurar el kernel con los valores de &lt;code&gt;appsettings.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;IServiceCollection&lt;/span&gt; &lt;span class="nf"&gt;AddSemanticKernel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt; &lt;span class="n"&gt;IServiceCollection&lt;/span&gt; &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IConfiguration&lt;/span&gt; &lt;span class="n"&gt;configuration&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;azureOpenAIConfig&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;configuration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetSection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"AzureOpenAI"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddAzureOpenAIChatCompletion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;deploymentName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;azureOpenAIConfig&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"DeploymentName"&lt;/span&gt;&lt;span class="p"&gt;]!,&lt;/span&gt;
        &lt;span class="n"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;azureOpenAIConfig&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"ApiKey"&lt;/span&gt;&lt;span class="p"&gt;]!,&lt;/span&gt;
        &lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;azureOpenAIConfig&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"Endpoint"&lt;/span&gt;&lt;span class="p"&gt;]!,&lt;/span&gt;
        &lt;span class="n"&gt;modelId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;azureOpenAIConfig&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"ModelId"&lt;/span&gt;&lt;span class="p"&gt;]!&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddTransient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;serviceProvider&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Kernel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;serviceProvider&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;services&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 configuración en &lt;code&gt;appsettings.json&lt;/code&gt; se vería algo así:&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="nl"&gt;"AzureOpenAI"&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;"DeploymentName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"notaria-assistant"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"ApiKey"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"TU_API_KEY"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Endpoint"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://tuservicio.openai.azure.com/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"ModelId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"gpt-35-turbo"&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;Con esto, el &lt;code&gt;Kernel&lt;/code&gt; queda disponible vía inyección de dependencias y listo para clonar, personalizar o conectar a nuestros agentes.&lt;/p&gt;

&lt;h3&gt;
  
  
  🧱 Servicios base y modelos de dominio
&lt;/h3&gt;

&lt;p&gt;Nuestro agente necesita acceder a datos del mundo real, por lo que construimos un servicio &lt;code&gt;InvoiceService&lt;/code&gt; con métodos como:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;GetInvoiceByNumberAsync&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;CreateInvoiceAsync&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;MarkInvoiceAsPaidAsync&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;GetUnpaidInvoicesAsync&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;GetCustomerByEmailAsync&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Este servicio será inyectado en el plugin que veremos en la próxima sección, y expone la lógica de negocio que el agente puede usar para resolver instrucciones. Si ya tienes una capa de servicios en tu aplicación, puedes integrarla directamente.&lt;/p&gt;

&lt;h2&gt;
  
  
  🧩 Definir tus funciones (skills/plugins)
&lt;/h2&gt;

&lt;p&gt;Una de las capacidades más poderosas del Agent Framework es permitir que tus agentes ejecuten &lt;strong&gt;funciones reales escritas en C#&lt;/strong&gt;. Estas funciones se registran como "plugins" usando decoradores especiales, y luego el agente las puede invocar cuando detecta que son necesarias para cumplir una instrucción del usuario.&lt;/p&gt;

&lt;p&gt;En este caso, hemos creado un plugin llamado &lt;code&gt;InvoicesPlugin&lt;/code&gt;, que expone cinco funciones relacionadas con facturación. Cada una está decorada con el atributo &lt;code&gt;[KernelFunction]&lt;/code&gt;, lo que la hace visible para Semantic Kernel.&lt;/p&gt;

&lt;h3&gt;
  
  
  🧠 ¿Qué es un plugin?
&lt;/h3&gt;

&lt;p&gt;Un plugin en Semantic Kernel es simplemente una &lt;strong&gt;clase con métodos públicos asincrónicos&lt;/strong&gt; decorados con &lt;code&gt;[KernelFunction]&lt;/code&gt;. Además, puedes usar &lt;code&gt;[Description]&lt;/code&gt; en los parámetros y métodos para mejorar la comprensión del agente sobre lo que hace cada función.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;🗂️ El código completo del plugin puede consultarse en el repositorio:&lt;br&gt;&lt;br&gt;
&lt;a href="https://github.com/isaacOjeda/DevToPosts/tree/main/SemanticKernelSeries/SemanticKernelLearning04" rel="noopener noreferrer"&gt;Ver InvoicesPlugin.cs en GitHub&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  🧾 Ejemplo 1: Verificar el estado de una factura
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;KernelFunction&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Verifica el estado de pago de una factura específica usando su número de factura."&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;VerifyPaymentAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Número de la factura a verificar (ej: INV-202412-0001)"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;numeroFactura&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;invoice&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;invoiceService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetInvoiceByNumberAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;numeroFactura&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;invoice&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;$"❌ No se encontró ninguna factura con el número: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;numeroFactura&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;StringBuilder&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AppendLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"📋 **Factura: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;invoice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InvoiceNumber&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;**"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AppendLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"🔸 Estado: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;GetStatusText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;invoice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Status&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// ...otros campos visuales&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToString&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;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;Nota:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
En este ejemplo, todas las funciones del plugin reciben y retornan datos en formato &lt;code&gt;string&lt;/code&gt;, ya que el enfoque está en mantener el ejemplo simple y fácil de seguir.&lt;br&gt;&lt;br&gt;
Sin embargo, el Agent Framework de Semantic Kernel &lt;strong&gt;soporta objetos complejos como parámetros de entrada y salida&lt;/strong&gt;. Puedes retornar clases personalizadas, listas u objetos anidados, y el agente será capaz de interpretarlos, formatearlos o incluso razonar sobre ellos en sus respuestas.&lt;/p&gt;

&lt;p&gt;En un escenario real, podrías devolver una &lt;code&gt;InvoiceDetail&lt;/code&gt; con propiedades estructuradas en lugar de una cadena formateada, lo que abre la puerta a integraciones más ricas o adaptadas al canal (por ejemplo, APIs, UIs o chatbots).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Este método se encarga de buscar una factura por número y devolver su estado, fecha de vencimiento, monto y otra información útil. Como puedes ver, el resultado se formatea para ser legible y amigable, incluyendo emojis para mejorar la experiencia.&lt;/p&gt;

&lt;h3&gt;
  
  
  ✍️ Ejemplo 2: Crear una prefactura
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;KernelFunction&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Realiza una prefactura para un cliente usando email, descripción, monto y días hasta el vencimiento."&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;CreateInvoiceDraftAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;clienteEmail&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;descripcion&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;decimal&lt;/span&gt; &lt;span class="n"&gt;monto&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;diasVencimiento&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;customer&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;invoiceService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetCustomerByEmailAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clienteEmail&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;customer&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;$"❌ No se encontró ningún cliente con el email: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;clienteEmail&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;dueDate&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UtcNow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddDays&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;diasVencimiento&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;invoice&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;invoiceService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateInvoiceAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;customer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;descripcion&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;monto&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dueDate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Factura generada automáticamente"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;$"✅ Prefactura creada: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;invoice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InvoiceNumber&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt; para &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;customer&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="s"&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;Este método genera una factura en estado "borrador", útil para cuando un abogado quiere adelantar una facturación para un cliente específico.&lt;/p&gt;

&lt;h3&gt;
  
  
  🧠 ¿Por qué usar &lt;code&gt;[KernelFunction]&lt;/code&gt;?
&lt;/h3&gt;

&lt;p&gt;Al decorar tus funciones con este atributo, estás dando al agente la capacidad de:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Descubrir qué herramientas tiene disponibles.&lt;/li&gt;
&lt;li&gt;Seleccionar de forma automática la función correcta en base a la intención del usuario.&lt;/li&gt;
&lt;li&gt;Combinar funciones si es necesario (por ejemplo, buscar cliente → generar factura).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Gracias a esto, no necesitas escribir prompts complejos ni reglas condicionales. El agente decide qué hacer en tiempo de ejecución.&lt;/p&gt;

&lt;h2&gt;
  
  
  🤖 Crear un agente y asignarle objetivos
&lt;/h2&gt;

&lt;p&gt;Una vez que tenemos definido nuestro plugin con las funciones necesarias, es hora de &lt;strong&gt;crear un agente&lt;/strong&gt; que lo utilice. Un agente es una instancia del tipo &lt;code&gt;ChatCompletionAgent&lt;/code&gt; que sabe cómo interactuar con un modelo de lenguaje, tiene acceso a un conjunto de funciones y puede operar con base en instrucciones personalizadas.&lt;/p&gt;

&lt;p&gt;En este paso lo conectamos todo: el kernel, el plugin, y la intención del asistente.&lt;/p&gt;

&lt;h3&gt;
  
  
  🧠 ¿Qué es un &lt;code&gt;ChatCompletionAgent&lt;/code&gt;?
&lt;/h3&gt;

&lt;p&gt;El &lt;code&gt;ChatCompletionAgent&lt;/code&gt; es una implementación del Agent Framework de Semantic Kernel que permite interactuar con modelos de lenguaje compatibles (como GPT-4 o GPT-3.5) de forma conversacional. Este tipo de agente:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Usa un kernel preconfigurado con modelos y funciones.&lt;/li&gt;
&lt;li&gt;Puede tener instrucciones personalizadas (“persona” o “rol”).&lt;/li&gt;
&lt;li&gt;Decide automáticamente qué función usar, si corresponde.&lt;/li&gt;
&lt;li&gt;Mantiene un contexto conversacional.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🏗️ Registrando el agente con DI
&lt;/h3&gt;

&lt;p&gt;En este ejemplo, configuramos el agente como un servicio usando la extensión &lt;code&gt;AddAgents&lt;/code&gt;. Esto permite que el agente se cree con acceso al kernel y al &lt;code&gt;InvoicesPlugin&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddKeyedTransient&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ChatCompletionAgent&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="s"&gt;"AssistantAgent"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;kernel&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetRequiredService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Kernel&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;agentKernel&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;kernel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Clone&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// Registramos el plugin de facturas&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;invoiceService&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetRequiredService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;InvoiceService&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
    &lt;span class="n"&gt;agentKernel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ImportPluginFromObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;InvoicesPlugin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;invoiceService&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ChatCompletionAgent&lt;/span&gt;&lt;span class="p"&gt;()&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="s"&gt;"Asistente"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Instructions&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"""
&lt;/span&gt;            &lt;span class="n"&gt;Eres&lt;/span&gt; &lt;span class="n"&gt;un&lt;/span&gt; &lt;span class="n"&gt;asistente&lt;/span&gt; &lt;span class="n"&gt;de&lt;/span&gt; &lt;span class="n"&gt;abogados&lt;/span&gt; &lt;span class="n"&gt;en&lt;/span&gt; &lt;span class="n"&gt;una&lt;/span&gt; &lt;span class="n"&gt;notar&lt;/span&gt;&lt;span class="err"&gt;í&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="err"&gt;ú&lt;/span&gt;&lt;span class="n"&gt;blica&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; 
            &lt;span class="n"&gt;Tu&lt;/span&gt; &lt;span class="n"&gt;tarea&lt;/span&gt; &lt;span class="n"&gt;es&lt;/span&gt; &lt;span class="n"&gt;ayudar&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;los&lt;/span&gt; &lt;span class="n"&gt;abogados&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;gestionar&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="n"&gt;verificar&lt;/span&gt; &lt;span class="n"&gt;el&lt;/span&gt; &lt;span class="n"&gt;estado&lt;/span&gt; &lt;span class="n"&gt;de&lt;/span&gt; &lt;span class="n"&gt;las&lt;/span&gt; &lt;span class="n"&gt;facturas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;

            &lt;span class="n"&gt;Funciones&lt;/span&gt; &lt;span class="n"&gt;disponibles&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="n"&gt;Verificar&lt;/span&gt; &lt;span class="n"&gt;el&lt;/span&gt; &lt;span class="n"&gt;estado&lt;/span&gt; &lt;span class="n"&gt;de&lt;/span&gt; &lt;span class="n"&gt;pago&lt;/span&gt; &lt;span class="n"&gt;de&lt;/span&gt; &lt;span class="n"&gt;facturas&lt;/span&gt; &lt;span class="n"&gt;espec&lt;/span&gt;&lt;span class="err"&gt;í&lt;/span&gt;&lt;span class="n"&gt;ficas&lt;/span&gt;
            &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="n"&gt;Crear&lt;/span&gt; &lt;span class="n"&gt;prefacturas&lt;/span&gt; &lt;span class="n"&gt;para&lt;/span&gt; &lt;span class="n"&gt;clientes&lt;/span&gt;
            &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="n"&gt;Consultar&lt;/span&gt; &lt;span class="n"&gt;facturas&lt;/span&gt; &lt;span class="n"&gt;pendientes&lt;/span&gt; &lt;span class="n"&gt;de&lt;/span&gt; &lt;span class="n"&gt;pago&lt;/span&gt;
            &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="n"&gt;Marcar&lt;/span&gt; &lt;span class="n"&gt;facturas&lt;/span&gt; &lt;span class="n"&gt;como&lt;/span&gt; &lt;span class="n"&gt;pagadas&lt;/span&gt;
            &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="n"&gt;Consultar&lt;/span&gt; &lt;span class="n"&gt;informaci&lt;/span&gt;&lt;span class="err"&gt;ó&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="n"&gt;de&lt;/span&gt; &lt;span class="n"&gt;clientes&lt;/span&gt;

            &lt;span class="n"&gt;Siempre&lt;/span&gt; &lt;span class="n"&gt;proporciona&lt;/span&gt; &lt;span class="n"&gt;informaci&lt;/span&gt;&lt;span class="err"&gt;ó&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="n"&gt;clara&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="n"&gt;detallada&lt;/span&gt; &lt;span class="n"&gt;sobre&lt;/span&gt; &lt;span class="n"&gt;el&lt;/span&gt; &lt;span class="n"&gt;estado&lt;/span&gt; &lt;span class="n"&gt;de&lt;/span&gt; &lt;span class="n"&gt;las&lt;/span&gt; &lt;span class="n"&gt;facturas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
            &lt;span class="n"&gt;Usa&lt;/span&gt; &lt;span class="n"&gt;emojis&lt;/span&gt; &lt;span class="n"&gt;para&lt;/span&gt; &lt;span class="n"&gt;hacer&lt;/span&gt; &lt;span class="n"&gt;las&lt;/span&gt; &lt;span class="n"&gt;respuestas&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="err"&gt;á&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="n"&gt;visuales&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="err"&gt;á&lt;/span&gt;&lt;span class="n"&gt;ciles&lt;/span&gt; &lt;span class="n"&gt;de&lt;/span&gt; &lt;span class="n"&gt;entender&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
        &lt;span class="s"&gt;""",
&lt;/span&gt;        &lt;span class="n"&gt;Kernel&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;agentKernel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Arguments&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;KernelArguments&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;OpenAIPromptExecutionSettings&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;FunctionChoiceBehavior&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;FunctionChoiceBehavior&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Auto&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="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;agent&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;blockquote&gt;
&lt;p&gt;🧩 Este agente se construye a partir de un &lt;code&gt;Kernel&lt;/code&gt; clonado. Así puedes tener múltiples agentes con diferentes plugins, instrucciones o configuraciones sin interferencias entre ellos.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  🗣️ Instrucciones: cómo le decimos al agente quién es
&lt;/h3&gt;

&lt;p&gt;La propiedad &lt;code&gt;Instructions&lt;/code&gt; permite definir la personalidad, contexto y objetivos del agente. Aquí estamos usando un sistema de "prompt largo" al estilo system message:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;Instructions&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"""
&lt;/span&gt;    &lt;span class="n"&gt;Eres&lt;/span&gt; &lt;span class="n"&gt;un&lt;/span&gt; &lt;span class="n"&gt;asistente&lt;/span&gt; &lt;span class="n"&gt;de&lt;/span&gt; &lt;span class="n"&gt;abogados&lt;/span&gt; &lt;span class="n"&gt;en&lt;/span&gt; &lt;span class="n"&gt;una&lt;/span&gt; &lt;span class="n"&gt;notar&lt;/span&gt;&lt;span class="err"&gt;í&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="err"&gt;ú&lt;/span&gt;&lt;span class="n"&gt;blica&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; 
    &lt;span class="n"&gt;Tu&lt;/span&gt; &lt;span class="n"&gt;tarea&lt;/span&gt; &lt;span class="n"&gt;es&lt;/span&gt; &lt;span class="n"&gt;ayudar&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;los&lt;/span&gt; &lt;span class="n"&gt;abogados&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;gestionar&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="n"&gt;verificar&lt;/span&gt; &lt;span class="n"&gt;el&lt;/span&gt; &lt;span class="n"&gt;estado&lt;/span&gt; &lt;span class="n"&gt;de&lt;/span&gt; &lt;span class="n"&gt;las&lt;/span&gt; &lt;span class="n"&gt;facturas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="s"&gt;"""
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Este mensaje guía al modelo para que entienda el contexto en el que opera, el lenguaje que debe usar y el tipo de resultados esperados.&lt;/p&gt;

&lt;h3&gt;
  
  
  ⚙️ Control de funciones automáticas
&lt;/h3&gt;

&lt;p&gt;El fragmento clave para habilitar la selección automática de funciones es:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;Arguments&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;KernelArguments&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;OpenAIPromptExecutionSettings&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;FunctionChoiceBehavior&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;FunctionChoiceBehavior&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Auto&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;Esto permite que el modelo analice la intención del usuario y decida si debe invocar alguna función del plugin registrado. No es necesario indicarle explícitamente el nombre de la función: el agente elige por sí mismo en función del mensaje recibido.&lt;/p&gt;

&lt;p&gt;Con este paso, ya tenemos un &lt;strong&gt;asistente especializado&lt;/strong&gt; corriendo sobre Semantic Kernel, con acceso completo a las funciones del plugin de facturación y configurado para operar en el contexto de una notaría pública.&lt;/p&gt;

&lt;h2&gt;
  
  
  🚀 Ejecutar el agente y manejar conversaciones
&lt;/h2&gt;

&lt;p&gt;Una vez que nuestro agente está registrado y conectado a sus funciones, llega el momento de &lt;strong&gt;ponerlo en acción&lt;/strong&gt;. En este ejemplo, creamos una API HTTP que permite a cualquier cliente (como una app web o móvil) interactuar con el asistente de forma conversacional.&lt;/p&gt;

&lt;p&gt;Todo el ciclo se orquesta desde un conjunto de endpoints dentro de &lt;code&gt;AgentEndpoints.cs&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  🌐 Endpoint para iniciar una conversación
&lt;/h3&gt;

&lt;p&gt;El primer paso en cualquier interacción es &lt;strong&gt;crear un hilo de conversación&lt;/strong&gt;. Esto permite almacenar el historial y mantener contexto entre múltiples mensajes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MapPost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/start"&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="n"&gt;ConversationService&lt;/span&gt; &lt;span class="n"&gt;conversationService&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;conversationId&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;conversationService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateConversationAsync&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;Results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;ConversationId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;conversationId&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;Este endpoint genera un &lt;code&gt;ConversationId&lt;/code&gt; único que luego será utilizado en cada mensaje posterior. La conversación se almacena en la base de datos con su historial asociado.&lt;/p&gt;

&lt;h3&gt;
  
  
  💬 Enviar una pregunta al agente
&lt;/h3&gt;

&lt;p&gt;El segundo endpoint es el más importante: recibe un mensaje del usuario y devuelve la respuesta del agente. Lo interesante es que &lt;strong&gt;reconstruye el historial completo&lt;/strong&gt; antes de invocar el modelo, lo que permite mantener coherencia y continuidad.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MapPost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&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="n"&gt;AskQuestionRequest&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;FromKeyedServices&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"AssistantAgent"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt; &lt;span class="n"&gt;ChatCompletionAgent&lt;/span&gt; &lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ConversationService&lt;/span&gt; &lt;span class="n"&gt;conversationService&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ... Obtener historial desde base de datos&lt;/span&gt;
    &lt;span class="c1"&gt;// ... Agregar mensaje del usuario actual&lt;/span&gt;
    &lt;span class="c1"&gt;// ... Crear ChatHistoryAgentThread y lanzar la invocación&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  📜 Cómo se construye el historial
&lt;/h3&gt;

&lt;p&gt;El historial de conversación se crea a partir de los mensajes previos almacenados en la base de datos:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;chatHistory&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ChatHistory&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;dbMessage&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;conversation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Messages&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dbMessage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Role&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"user"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;chatHistory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddUserMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dbMessage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dbMessage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Role&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"assistant"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;chatHistory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddAssistantMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dbMessage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Content&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;Después se añade el nuevo mensaje del usuario, y se encapsula todo en un &lt;code&gt;ChatHistoryAgentThread&lt;/code&gt;, que es el tipo de contexto que entiende el agente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;chatHistory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddUserMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Question&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;thread&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ChatHistoryAgentThread&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chatHistory&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ThreadId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  🧠 Ejecutar al agente y procesar respuestas
&lt;/h3&gt;

&lt;p&gt;El agente se invoca de forma asíncrona usando &lt;code&gt;InvokeAsync&lt;/code&gt;, lo que permite recibir múltiples respuestas si el modelo decide dividir su salida (especialmente útil cuando hay invocación de funciones):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;responses&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;InvokeAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Question&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;thread&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;allResponses&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;agentResponse&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;responses&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;agentResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Content&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Empty&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IsNullOrWhiteSpace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;allResponses&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content&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;Luego se combinan todas las respuestas en un solo mensaje y se guarda nuevamente en la base de datos:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;finalResponse&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"\n\n"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;allResponses&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;conversationService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddMessageAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ThreadId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;finalResponse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"assistant"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  ✅ Respuesta final
&lt;/h3&gt;

&lt;p&gt;El resultado es una experiencia completamente conversacional, persistente y con funciones automatizadas. El cliente (por ejemplo, una SPA en Blazor o React) solo necesita enviar preguntas con un &lt;code&gt;ConversationId&lt;/code&gt; y mostrar las respuestas generadas.&lt;/p&gt;

&lt;h3&gt;
  
  
  📌 ¿Por qué usar historial y contexto?
&lt;/h3&gt;

&lt;p&gt;Esto es clave para que el agente pueda entender preguntas como:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“¿Y esa factura de Juan ya se pagó?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Sin historial, el modelo no sabría a qué se refiere “esa factura”. Al mantener contexto, el agente puede hacer inferencias y ejecutar funciones con mayor precisión.&lt;/p&gt;

&lt;p&gt;Con esto, tenemos todo lo necesario para una experiencia de agente conversacional &lt;strong&gt;real, persistente, funcional y extensible&lt;/strong&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;📁 &lt;strong&gt;Nota:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Recuerda que el código completo está disponible en el repositorio, incluyendo el contexto de la base de datos y la implementación completa de &lt;code&gt;ConversationService&lt;/code&gt;, que se encarga de crear conversaciones, almacenar mensajes y reconstruir el historial.&lt;/p&gt;

&lt;p&gt;🔗 &lt;a href="https://github.com/isaacOjeda/DevToPosts/tree/main/SemanticKernelSeries/SemanticKernelLearning04" rel="noopener noreferrer"&gt;Ver el proyecto en GitHub&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  🧰 Extras, buenas prácticas y posibles extensiones
&lt;/h2&gt;

&lt;p&gt;Hasta este punto ya tienes un agente conversacional funcional, conectado a un modelo de lenguaje, con un plugin personalizado, y persistencia completa de las conversaciones. Sin embargo, el Agent Framework de Semantic Kernel ofrece muchas oportunidades para llevar tu solución al siguiente nivel.&lt;/p&gt;

&lt;p&gt;A continuación, te comparto algunas ideas para extender o mejorar tu asistente.&lt;/p&gt;

&lt;h3&gt;
  
  
  🔗 Agentes con múltiples plugins
&lt;/h3&gt;

&lt;p&gt;El &lt;code&gt;Kernel&lt;/code&gt; puede importar múltiples plugins, no solo uno. Por ejemplo, podrías tener:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;InvoicesPlugin&lt;/code&gt;: para gestión de facturas.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;CalendarPlugin&lt;/code&gt;: para gestionar eventos o vencimientos.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;EmailPlugin&lt;/code&gt;: para enviar notificaciones automáticas.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;agentKernel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ImportPluginFromObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;CalendarPlugin&lt;/span&gt;&lt;span class="p"&gt;(...));&lt;/span&gt;
&lt;span class="n"&gt;agentKernel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ImportPluginFromObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;EmailPlugin&lt;/span&gt;&lt;span class="p"&gt;(...));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Esto permite que el mismo agente decida qué herramienta usar dependiendo del mensaje del usuario, sin que tengas que escribir lógica adicional.&lt;/p&gt;

&lt;h3&gt;
  
  
  🧠 Agentes que planifican múltiples pasos
&lt;/h3&gt;

&lt;p&gt;El Agent Framework incluye soporte experimental para &lt;strong&gt;planificación automática&lt;/strong&gt;, donde el modelo puede decidir ejecutar múltiples funciones encadenadas para lograr un objetivo complejo.&lt;/p&gt;

&lt;p&gt;Por ejemplo:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Crea una prefactura para Carlos y márcala como pagada.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Esto podría generar un plan con dos pasos: &lt;code&gt;CreateInvoiceDraftAsync&lt;/code&gt; → &lt;code&gt;MarkInvoiceAsPaidAsync&lt;/code&gt;.&lt;br&gt;&lt;br&gt;
El agente puede manejar estas secuencias si se configura con &lt;code&gt;FunctionCalling&lt;/code&gt; en modo &lt;code&gt;Auto()&lt;/code&gt; y si las funciones están bien descritas.&lt;/p&gt;

&lt;h3&gt;
  
  
  🧪 Testing y depuración
&lt;/h3&gt;

&lt;p&gt;Cuando construyes agentes que llaman funciones reales, es importante:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Probar con mensajes ambiguos o incompletos para ver cómo reacciona el agente.&lt;/li&gt;
&lt;li&gt;Monitorear los logs de funciones invocadas.&lt;/li&gt;
&lt;li&gt;Verificar qué parámetros está eligiendo el modelo.&lt;/li&gt;
&lt;li&gt;Utilizar &lt;code&gt;FunctionResult&lt;/code&gt; para obtener metadata sobre las llamadas.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;También puedes capturar los mensajes generados por el modelo para analizarlos más adelante, por ejemplo, almacenando las decisiones o planes generados en una tabla de auditoría.&lt;/p&gt;

&lt;h3&gt;
  
  
  ⚠️ Cuida la experiencia conversacional
&lt;/h3&gt;

&lt;p&gt;Algunos consejos prácticos para que el asistente se sienta más natural:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Usa emojis y lenguaje cercano si el público lo permite (como hicimos aquí).&lt;/li&gt;
&lt;li&gt;Asegúrate de que las respuestas siempre incluyan lo esencial, incluso si no hay datos (ej: “no hay facturas vencidas”).&lt;/li&gt;
&lt;li&gt;Controla el tamaño del historial para evitar prompts excesivamente largos.&lt;/li&gt;
&lt;li&gt;Maneja errores con mensajes claros: si una factura no existe, si el email es inválido, etc.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🧩 Integración con interfaces visuales
&lt;/h3&gt;

&lt;p&gt;Aunque este ejemplo se enfoca en la API HTTP y el backend del agente, la interacción no termina ahí. Puedes conectar este agente conversacional a diferentes interfaces gráficas según tu contexto:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Una aplicación web (por ejemplo, Blazor, React, Angular).&lt;/li&gt;
&lt;li&gt;Un chatbot embebido en tu intranet o sitio público.&lt;/li&gt;
&lt;li&gt;Clientes móviles nativos.&lt;/li&gt;
&lt;li&gt;Canales como Microsoft Teams, Telegram o WhatsApp.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;💬 &lt;strong&gt;Importante:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
El repositorio del proyecto incluye un &lt;strong&gt;chat funcional ya implementado&lt;/strong&gt; que se comunica con esta API y permite mantener conversaciones con el agente.&lt;br&gt;&lt;br&gt;
Este cliente está disponible como referencia y punto de exploración: puedes ver cómo se estructuran los hilos, cómo se muestra el historial y cómo se consume el endpoint &lt;code&gt;/api/agent&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;🔗 &lt;a href="https://github.com/isaacOjeda/DevToPosts/tree/main/SemanticKernelSeries/SemanticKernelLearning04" rel="noopener noreferrer"&gt;Ver el repositorio en GitHub&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Este ejemplo es ideal para experimentar con el flujo completo: desde el frontend que envía preguntas, hasta el agente que responde y persiste el historial.&lt;/p&gt;

&lt;h3&gt;
  
  
  🌱 ¿Qué más podrías construir con este enfoque?
&lt;/h3&gt;

&lt;p&gt;El Agent Framework es ideal para automatizar tareas repetitivas en sistemas internos. Algunos ejemplos reales donde este enfoque encaja:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Asistentes de soporte técnico interno (consultar errores, reiniciar servicios).&lt;/li&gt;
&lt;li&gt;Asistentes legales que redactan contratos o extraen cláusulas.&lt;/li&gt;
&lt;li&gt;Agentes que combinan datos de distintas fuentes (facturas + clientes + CRM).&lt;/li&gt;
&lt;li&gt;Robots de backoffice que ayudan a generar informes o consolidar información.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;📌 &lt;strong&gt;Consejo final:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Piensa en tu agente como un &lt;em&gt;colega digital&lt;/em&gt;: cuantas más herramientas le des, más tareas podrá resolver sin que tú tengas que intervenir manualmente.&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;p&gt;El &lt;strong&gt;Agent Framework de Semantic Kernel&lt;/strong&gt; abre una puerta poderosa para construir aplicaciones inteligentes que combinan lenguaje natural con lógica de negocio real. En este artículo, creamos un &lt;strong&gt;agente conversacional especializado en facturación&lt;/strong&gt;, capaz de razonar sobre peticiones del usuario e invocar funciones de una aplicación en .NET.&lt;/p&gt;

&lt;p&gt;Hemos visto cómo:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Configurar un &lt;code&gt;Kernel&lt;/code&gt; con Azure OpenAI.&lt;/li&gt;
&lt;li&gt;Exponer funciones reales como plugins usando &lt;code&gt;[KernelFunction]&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Construir y personalizar un agente con instrucciones específicas.&lt;/li&gt;
&lt;li&gt;Mantener conversaciones persistentes con historial contextual.&lt;/li&gt;
&lt;li&gt;Integrar todo en una API funcional, lista para usarse desde un cliente.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Este enfoque es aplicable a muchísimos casos reales: desde automatización interna hasta asistentes legales, técnicos o administrativos.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 Lo mejor de todo es que puedes extender esta base fácilmente: agregar nuevos plugins, integrar fuentes de datos adicionales, o escalar hacia canales como webchat, Teams o bots en producción.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Si te interesa explorar más sobre cómo usar Semantic Kernel en escenarios del mundo real, o quieres construir asistentes más sofisticados, este es apenas el comienzo.&lt;/p&gt;

&lt;p&gt;🔗 &lt;strong&gt;Recuerda que el código completo está disponible aquí:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;a href="https://github.com/isaacOjeda/DevToPosts/tree/main/SemanticKernelSeries/SemanticKernelLearning04" rel="noopener noreferrer"&gt;github.com/isaacOjeda/DevToPosts/tree/main/SemanticKernelSeries/SemanticKernelLearning04&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  📚 Referencias
&lt;/h2&gt;

&lt;p&gt;Si quieres seguir explorando el Agent Framework de Semantic Kernel y todas sus posibilidades en C#, aquí tienes enlaces oficiales de documentación que complementan lo visto en este artículo:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/semantic-kernel/frameworks/agent/?pivots=programming-language-csharp" rel="noopener noreferrer"&gt;Semantic Kernel Agent Framework | Microsoft Learn&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/semantic-kernel/frameworks/agent/agent-architecture?pivots=programming-language-csharp" rel="noopener noreferrer"&gt;Semantic Kernel Agent Architecture | Microsoft Learn&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/semantic-kernel/frameworks/agent/agent-api?pivots=programming-language-csharp" rel="noopener noreferrer"&gt;The Semantic Kernel Common Agent API surface | Microsoft Learn&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/semantic-kernel/frameworks/agent/agent-functions?pivots=programming-language-csharp" rel="noopener noreferrer"&gt;Configuring Agents with Semantic Kernel Plugins. | Microsoft Learn&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/semantic-kernel/frameworks/agent/agent-types/chat-completion-agent?pivots=programming-language-csharp" rel="noopener noreferrer"&gt;Exploring the Semantic Kernel ChatCompletionAgent | Microsoft Learn&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/training/paths/develop-ai-agents-on-azure/?source=learn" rel="noopener noreferrer"&gt;Develop AI Agents on Azure - Training | Microsoft Learn&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>azure</category>
      <category>csharp</category>
      <category>openai</category>
      <category>semantickernel</category>
    </item>
    <item>
      <title>Semantic Kernel – Parte 03: Embeddings y Retrieval-Augmented Generation (RAG)</title>
      <dc:creator>Isaac Ojeda</dc:creator>
      <pubDate>Sun, 16 Feb 2025 17:00:31 +0000</pubDate>
      <link>https://forem.com/isaacojeda/semantic-kernel-parte-03-embeddings-y-retrieval-augmented-generation-rag-4fjn</link>
      <guid>https://forem.com/isaacojeda/semantic-kernel-parte-03-embeddings-y-retrieval-augmented-generation-rag-4fjn</guid>
      <description>&lt;h2&gt;
  
  
  &lt;strong&gt;Introducción&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;En esta tercera parte de nuestra serie sobre &lt;strong&gt;Semantic Kernel&lt;/strong&gt;, nos adentramos en la integración de &lt;strong&gt;Embeddings&lt;/strong&gt; y &lt;strong&gt;Retrieval-Augmented Generation (RAG)&lt;/strong&gt; para mejorar la generación de contenido y la recuperación de información. Utilizando herramientas como &lt;strong&gt;Ollama&lt;/strong&gt; para la creación de embeddings y &lt;strong&gt;Qdrant&lt;/strong&gt; como vector store, podemos construir sistemas más inteligentes capaces de generar respuestas más precisas basadas en datos externos y actualizados, en lugar de depender únicamente de los datos con los que fue entrenado el modelo. A través de esta configuración, aprovechamos la sinergia entre el poder de los modelos de lenguaje y la capacidad de búsqueda semántica para proporcionar soluciones más efectivas a las consultas de los usuarios. En este artículo, exploraremos cómo implementar estas tecnologías en &lt;strong&gt;Semantic Kernel&lt;/strong&gt;, con ejemplos prácticos para configurar los servicios y gestionar los embeddings de manera eficiente.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;¿Qué son los Embeddings?&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Los &lt;strong&gt;embeddings&lt;/strong&gt; son representaciones numéricas de texto en un espacio vectorial de alta dimensión. Estas representaciones permiten medir la similitud semántica entre palabras, frases o documentos completos. En el contexto de &lt;strong&gt;Semantic Kernel&lt;/strong&gt;, los embeddings se utilizan para mejorar la búsqueda de información y la generación de respuestas basadas en conocimiento almacenado.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Características clave de los Embeddings:&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Capturan el significado semántico del texto.&lt;/li&gt;
&lt;li&gt;Permiten encontrar contenido relacionado aunque no se utilicen exactamente las mismas palabras.&lt;/li&gt;
&lt;li&gt;Se almacenan en bases de datos especializadas llamadas &lt;strong&gt;vector stores&lt;/strong&gt; (como &lt;strong&gt;Qdrant&lt;/strong&gt;).&lt;/li&gt;
&lt;li&gt;Se generan con modelos de lenguaje avanzados, como los proporcionados por &lt;strong&gt;Ollama&lt;/strong&gt; u &lt;strong&gt;Open AI&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;¿Qué es RAG (Retrieval-Augmented Generation)?&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Retrieval-Augmented Generation (RAG)&lt;/strong&gt; es una técnica que combina la recuperación de información con la generación de respuestas mediante un modelo de lenguaje. En lugar de confiar solo en los datos con los que fue entrenado el modelo, RAG permite buscar información relevante en bases de conocimiento externas y utilizarla para generar respuestas más precisas y actualizadas.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;Funcionamiento de RAG en Semantic Kernel:&lt;/strong&gt;
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Consulta del usuario&lt;/strong&gt;: Un usuario hace una pregunta o solicitud.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Búsqueda semántica&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;Se generan embeddings de la consulta.&lt;/li&gt;
&lt;li&gt;Se comparan con los embeddings almacenados en el vector store (Qdrant) para recuperar información relevante.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Generación de respuesta&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;La información recuperada se pasa como contexto al modelo de lenguaje.&lt;/li&gt;
&lt;li&gt;Se genera una respuesta más precisa y fundamentada en los datos encontrados.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Beneficios de RAG en Semantic Kernel:&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Mejor comprensión de consultas&lt;/strong&gt;: Permite obtener respuestas relevantes incluso si el usuario no usa palabras exactas.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Conocimiento actualizado&lt;/strong&gt;: Se pueden agregar nuevos datos al sistema sin necesidad de reentrenar el modelo de IA.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Optimización del procesamiento&lt;/strong&gt;: Reduce el costo computacional al recuperar solo la información relevante en lugar de analizar todo el corpus de datos.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Configurando Semantic Kernel con Embeddings y un Vector Store&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;En esta sección, configuraremos &lt;strong&gt;Semantic Kernel&lt;/strong&gt; para trabajar con &lt;strong&gt;Embeddings&lt;/strong&gt; y un &lt;strong&gt;Vector Store&lt;/strong&gt;, utilizando &lt;strong&gt;Aspire&lt;/strong&gt; para orquestar los servicios.&lt;/p&gt;

&lt;p&gt;Los servicios principales que vamos a configurar son:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;llama3.2&lt;/strong&gt;: Modelo generador de texto.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;nomic-embed-text&lt;/strong&gt;: Modelo generador de embeddings.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Qdrant&lt;/strong&gt;: Almacenamiento y recuperación de embeddings.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Aspire&lt;/strong&gt;: Orquestación y despliegue de los servicios.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Configuración de Aspire&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Aspire nos permite definir y gestionar los servicios necesarios en un solo archivo de configuración. En este caso, configuramos &lt;strong&gt;Qdrant&lt;/strong&gt; como nuestro vector store y &lt;strong&gt;llama3.2&lt;/strong&gt; y &lt;strong&gt;nomic-embed-text&lt;/strong&gt; como proveedor de embeddings.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Nota 💡: Esto ya lo hemos visto en los otros posts, siempre usa como referencia el código fuente (&lt;a href="https://github.com/isaacOjeda/DevToPosts/tree/main/SemanticKernelSeries/SemanticKernelLearning03" rel="noopener noreferrer"&gt;DevToPosts/SemanticKernelSeries/SemanticKernelLearning03 at main · isaacOjeda/DevToPosts&lt;/a&gt;) para más información.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DistributedApplication&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Configuración de Qdrant&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;qdrant&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddQdrant&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"qdrant"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithLifetime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ContainerLifetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Persistent&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Configuración de Ollama&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;ollama&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddOllama&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ollama"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithDataVolume&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithOpenWebUI&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Modelos de Ollama&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;llamaModel&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ollama&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"llama"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"llama3.2"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;embedding&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ollama&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"embed-text"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"nomic-embed-text"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Configuración del API Service con referencias a los modelos y Qdrant&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;apiService&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddProject&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Projects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SemanticKernelLearning03_ApiService&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="s"&gt;"apiservice"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithReference&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;llamaModel&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithReference&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;embedding&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithReference&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;qdrant&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Construcción y ejecución de Aspire&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Build&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt; &lt;strong&gt;Qdrant&lt;/strong&gt; se configura como un servicio persistente.

&lt;ul&gt;
&lt;li&gt;Se hace de esta forma para que no sea tan lento de inicializar en cada inicio de Aspire.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt; &lt;strong&gt;Ollama&lt;/strong&gt; se configura con almacenamiento de datos y una interfaz web.

&lt;ul&gt;
&lt;li&gt; Se añaden los modelos &lt;strong&gt;"llama3.2"&lt;/strong&gt; para generación de texto y &lt;strong&gt;"nomic-embed-text"&lt;/strong&gt; para embeddings.
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Por último se agrega nuestra API en ASP.NET Core.
&lt;h3&gt;
  
  
  &lt;strong&gt;Configuración de Semantic Kernel con Aspire&lt;/strong&gt;
&lt;/h3&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;Aspire se encargará de inyectar las &lt;strong&gt;cadenas de conexión&lt;/strong&gt; en la API, según los nombres definidos en la configuración.&lt;/p&gt;

&lt;p&gt;Para hacer que &lt;strong&gt;Semantic Kernel&lt;/strong&gt; utilice correctamente los servicios orquestados por Aspire, debemos procesar estas cadenas de conexión. Como lo hemos hecho en ejemplos anteriores, parsearemos la información recibida y estructuraremos mejor nuestra configuración.&lt;/p&gt;

&lt;p&gt;Para lograr esto, &lt;strong&gt;separaremos la configuración de dependencias en un archivo específico&lt;/strong&gt;, lo que nos permitirá mantener un código más limpio y modular.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;Creando &lt;code&gt;DependencyConfig.cs&lt;/code&gt; para gestionar las dependencias&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;En este archivo, crearemos el método de extensión &lt;code&gt;AddSemanticKernel&lt;/code&gt;, el cual:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Obtiene las cadenas de conexión inyectadas por Aspire.
&lt;/li&gt;
&lt;li&gt;Extrae los detalles necesarios de &lt;strong&gt;Ollama&lt;/strong&gt; (para generación de texto y embeddings).
&lt;/li&gt;
&lt;li&gt;Extrae la información de conexión a &lt;strong&gt;Qdrant&lt;/strong&gt; (Vector Store).
&lt;/li&gt;
&lt;li&gt;Registra los servicios de &lt;strong&gt;Semantic Kernel&lt;/strong&gt; con las dependencias configuradas.
&lt;/li&gt;
&lt;li&gt;Agrega un servicio de &lt;strong&gt;búsqueda semántica&lt;/strong&gt; (&lt;code&gt;ITextSearch&lt;/code&gt;) que permite realizar búsquedas en una colección de vectores, el cual usaremos más adelante.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Aquí está el código en &lt;code&gt;DependencyConfig.cs&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DependencyConfig&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;AddSemanticKernel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt; &lt;span class="n"&gt;WebApplicationBuilder&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;completionModel&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
            &lt;span class="nf"&gt;GetModelDetailsFromConnectionString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Configuration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetConnectionString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"llama"&lt;/span&gt;&lt;span class="p"&gt;)!);&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;embeddingModel&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
            &lt;span class="nf"&gt;GetModelDetailsFromConnectionString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Configuration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetConnectionString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"embed-text"&lt;/span&gt;&lt;span class="p"&gt;)!);&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
            &lt;span class="nf"&gt;GetQdrantDetailsFromConnectionString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Configuration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetConnectionString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"qdrant"&lt;/span&gt;&lt;span class="p"&gt;)!);&lt;/span&gt;

        &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddKernel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddOllamaChatCompletion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;completionModel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddOllamaTextEmbeddingGeneration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;embeddingModel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddQdrantVectorStore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddKeyedTransient&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ITextSearch&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;BlogPost&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;VectorName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;vectorStore&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetRequiredService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IVectorStore&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;embeddingGenerationService&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetRequiredService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ITextEmbeddingGenerationService&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;

            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;blogposts&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;vectorStore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetCollection&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Guid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;BlogPost&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;BlogPost&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;VectorName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="n"&gt;blogposts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateCollectionIfNotExistsAsync&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;ConfigureAwait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;textSearch&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;VectorStoreTextSearch&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;BlogPost&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;blogposts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;embeddingGenerationService&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;textSearch&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;&lt;strong&gt;Explicación del código&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Obtenemos los detalles de conexión&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;GetModelDetailsFromConnectionString&lt;/code&gt; extrae el modelo y endpoint desde las cadenas de conexión de &lt;strong&gt;Ollama&lt;/strong&gt; (chat y embeddings).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GetQdrantDetailsFromConnectionString&lt;/code&gt; extrae el &lt;strong&gt;host, puerto y API key&lt;/strong&gt; de Qdrant.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Registramos los servicios de Semantic Kernel&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;AddOllamaChatCompletion&lt;/code&gt; para generación de texto con &lt;strong&gt;Ollama&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;AddOllamaTextEmbeddingGeneration&lt;/code&gt; para generación de &lt;strong&gt;embeddings&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;AddQdrantVectorStore&lt;/code&gt; para almacenar y recuperar datos desde &lt;strong&gt;Qdrant&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Configuramos un servicio de búsqueda semántica (&lt;code&gt;ITextSearch&lt;/code&gt;)&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Se obtiene la colección de &lt;strong&gt;vectores&lt;/strong&gt; asociada a &lt;code&gt;BlogPost&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Si la colección no existe, se &lt;strong&gt;crea automáticamente&lt;/strong&gt; en Qdrant.&lt;/li&gt;
&lt;li&gt;Se inicializa un objeto &lt;code&gt;VectorStoreTextSearch&amp;lt;BlogPost&amp;gt;&lt;/code&gt;, permitiendo realizar &lt;strong&gt;búsquedas semánticas&lt;/strong&gt; en la colección.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Almacenando Embeddings en Qdrant&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Para almacenar datos en Qdrant, primero debemos definir nuestro modelo de datos que será indexado en la base de datos vectorial.&lt;br&gt;&lt;br&gt;
En este ejemplo, queremos guardar una serie de posts y realizar búsquedas semánticas sobre su contenido.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Advertencia ⚠️: Las búsquedas vectoriales que estamos viendo aquí están en &lt;strong&gt;Preview (Alpha)&lt;/strong&gt;, por lo que podrían cambiar en el futuro. Actualmente, &lt;strong&gt;no se recomienda su uso en producción&lt;/strong&gt;, a menos que estés dispuesto a evolucionar junto con el framework.&lt;br&gt;
Anteriormente, se utilizaban los &lt;strong&gt;Memory de Semantic Kernel&lt;/strong&gt; para búsquedas similares, pero estos están en proceso de ser considerados &lt;strong&gt;"Legacy"&lt;/strong&gt;, por lo que su uso a largo plazo podría no ser viable.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;
  
  
  &lt;strong&gt;Definición del modelo &lt;code&gt;BlogPost&lt;/code&gt;&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;El modelo &lt;code&gt;BlogPost&lt;/code&gt; representará las entradas del blog en nuestra base de datos vectorial.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BlogPost&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;VectorName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"blogposts"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;VectorStoreRecordKey&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;TextSearchResultLink&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;Guid&lt;/span&gt; &lt;span class="n"&gt;BlogPostId&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;VectorStoreRecordData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IsFilterable&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;TextSearchResultName&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Title&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;VectorStoreRecordData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IsFullTextSearchable&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;TextSearchResultValue&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Description&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;VectorStoreRecordVector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;768&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DistanceFunction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DotProductSimilarity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IndexKind&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Hnsw&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;ReadOnlyMemory&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;float&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;?&lt;/span&gt; &lt;span class="n"&gt;DescriptionEmbedding&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;VectorStoreRecordData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IsFilterable&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;Tags&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Atributos de &lt;code&gt;BlogPost&lt;/code&gt;&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;BlogPostId&lt;/code&gt;&lt;/strong&gt;: Identificador único del post. Se marca como clave primaria con &lt;code&gt;[VectorStoreRecordKey]&lt;/code&gt; y como enlace de resultados de búsqueda con &lt;code&gt;[TextSearchResultLink]&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;Title&lt;/code&gt;&lt;/strong&gt;: Almacena el título del post. Usado como filtro con &lt;code&gt;[VectorStoreRecordData(IsFilterable = true)]&lt;/code&gt; y como nombre principal en los resultados de búsqueda con &lt;code&gt;[TextSearchResultName]&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;Description&lt;/code&gt;&lt;/strong&gt;: Contiene el contenido del post. Es indexado para búsquedas de texto completo con &lt;code&gt;[VectorStoreRecordData(IsFullTextSearchable = true)]&lt;/code&gt; y su valor es mostrado en los resultados con &lt;code&gt;[TextSearchResultValue]&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;DescriptionEmbedding&lt;/code&gt;&lt;/strong&gt;: Representación vectorial de &lt;code&gt;Description&lt;/code&gt;. Almacenado como un vector de 768 dimensiones usando &lt;code&gt;[VectorStoreRecordVector]&lt;/code&gt;, con &lt;code&gt;DotProductSimilarity&lt;/code&gt; como función de distancia y &lt;code&gt;Hnsw&lt;/code&gt; para indexación eficiente.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;Tags&lt;/code&gt;&lt;/strong&gt;: Etiquetas relacionadas con el post. Pueden ser usadas como filtros en consultas con &lt;code&gt;[VectorStoreRecordData(IsFilterable = true)]&lt;/code&gt;.
### &lt;strong&gt;Creando un endpoint para almacenar &lt;code&gt;BlogPost&lt;/code&gt; en Qdrant&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Para guardar nuevos &lt;code&gt;BlogPost&lt;/code&gt;, crearemos un endpoint que recibirá los datos del post, generará su embedding y lo almacenará en la base de datos vectorial.&lt;/p&gt;

&lt;p&gt;Nuestro servicio ya tiene configurados &lt;code&gt;ITextEmbeddingGenerationService&lt;/code&gt; (para generar embeddings) e &lt;code&gt;IVectorStore&lt;/code&gt; (para interactuar con Qdrant).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CreateBlogPost&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;Tags&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Guid&lt;/span&gt; &lt;span class="n"&gt;BlogPostId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Handler&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;ITextEmbeddingGenerationService&lt;/span&gt; &lt;span class="n"&gt;_embeddingService&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;IVectorStore&lt;/span&gt; &lt;span class="n"&gt;_vectorStore&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;Handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ITextEmbeddingGenerationService&lt;/span&gt; &lt;span class="n"&gt;embeddingService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IVectorStore&lt;/span&gt; &lt;span class="n"&gt;vectorStore&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;_embeddingService&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;embeddingService&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="n"&gt;_vectorStore&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;vectorStore&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;Handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;blogposts&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_vectorStore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetCollection&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Guid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;BlogPost&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;BlogPost&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;VectorName&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;blogposts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateCollectionIfNotExistsAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;embeddingContents&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;_embeddingService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GenerateEmbeddingAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;newBlogPost&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;BlogPost&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;BlogPostId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Guid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;NewGuid&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
                &lt;span class="n"&gt;Title&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;Description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;DescriptionEmbedding&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;embeddingContents&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;Tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Tags&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;blogposts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UpsertAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;newBlogPost&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;newBlogPost&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BlogPostId&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;&lt;strong&gt;Explicación del código&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Manejador (&lt;code&gt;Handler&lt;/code&gt;)&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Recibe los servicios &lt;code&gt;ITextEmbeddingGenerationService&lt;/code&gt; y &lt;code&gt;IVectorStore&lt;/code&gt; a través del constructor.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lógica del método &lt;code&gt;Handle&lt;/code&gt;&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Obtiene la colección &lt;code&gt;blogposts&lt;/code&gt; de Qdrant.&lt;/li&gt;
&lt;li&gt;Si la colección no existe, la crea con &lt;code&gt;CreateCollectionIfNotExistsAsync()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Genera el embedding del &lt;code&gt;Description&lt;/code&gt; usando &lt;code&gt;_embeddingService.GenerateEmbeddingAsync()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Crea un &lt;code&gt;BlogPost&lt;/code&gt; con los datos proporcionados y el embedding generado.&lt;/li&gt;
&lt;li&gt;Guarda el post en Qdrant con &lt;code&gt;UpsertAsync()&lt;/code&gt;, que inserta o actualiza el registro.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Nota 💡&lt;/strong&gt;: Para ver cómo este handler se integra con Minimal APIs, revisa el código fuente del proyecto. Para mantener este artículo conciso, omitiremos detalles específicos.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;Probando el endpoint con &lt;code&gt;api.http&lt;/code&gt;&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Para poblar la base de datos vectorial, podemos hacer una solicitud HTTP de prueba.&lt;/p&gt;

&lt;p&gt;Ejemplo en &lt;code&gt;SemanticKernelLearning03.ApiService.http&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@SemanticKernelLearning03.ApiService_HostAddress = https://localhost:7391

POST {{SemanticKernelLearning03.ApiService_HostAddress}}/api/blog-posts
Content-Type: application/json

{
  "Title": "Introducción a ASP.NET Core",
  "Description": "Una guía completa sobre cómo comenzar con ASP.NET Core y sus características principales.",
  "Tags": ["ASP.NET Core", "C#", "Web Development"]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;Verificando los datos en Qdrant&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Si ejecutamos nuestra solución con Aspire y realizamos la solicitud anterior, podemos inspeccionar los datos en el dashboard de Qdrant y verificar que la colección y los registros existen.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;Consideraciones sobre el tamaño de los vectores&lt;/strong&gt;
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Este ejemplo usa &lt;strong&gt;nomic-embed-text&lt;/strong&gt;, que genera vectores de &lt;strong&gt;768 dimensiones&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Qdrant debe estar configurado para aceptar este tamaño de vector.&lt;/li&gt;
&lt;li&gt;Si usas modelos de OpenAI u otros proveedores, verifica el tamaño del vector antes de insertarlo en Qdrant.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Recuperando Información desde Qdrant&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Para demostrar cómo realizar búsquedas semánticas en los vectores almacenados en Qdrant, podemos hacerlo de distintas maneras: usando el vector directamente o utilizando una abstracción que &lt;strong&gt;Semantic Kernel&lt;/strong&gt; nos proporciona, llamada &lt;code&gt;ITextSearch&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ITextSearch&lt;/code&gt; tiene distintas implementaciones, como &lt;code&gt;BingTextSearch&lt;/code&gt; (que realiza búsquedas en Bing), pero en nuestro caso usaremos &lt;code&gt;VectorStoreTextSearch&lt;/code&gt;, que está diseñado específicamente para realizar búsquedas en bases de datos vectoriales.&lt;/p&gt;

&lt;p&gt;Esta clase necesita conocer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;El modelo de embeddings&lt;/strong&gt; que estamos usando.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;El nombre de la colección en Qdrant&lt;/strong&gt; donde haremos la búsqueda.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;El proceso realmente es sencillo y podríamos hacerlo manualmente construyendo consultas directamente contra la base de datos vectorial. Sin embargo, en este caso, aprovecharemos &lt;code&gt;ITextSearch&lt;/code&gt; para simplificar el proceso.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Nota 💡: Para más información sobre búsquedas vectoriales en Semantic Kernel, consulta la documentación oficial de Microsoft:&lt;br&gt;&lt;br&gt;
&lt;a href="https://learn.microsoft.com/en-us/semantic-kernel/concepts/vector-store-connectors/vector-search?pivots=programming-language-csharp" rel="noopener noreferrer"&gt;Vector search using Semantic Kernel Vector Store connectors (Preview) | Microsoft Learn&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Implementación del Endpoint &lt;code&gt;SearchBlogPost&lt;/code&gt;&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Para realizar la búsqueda semántica, crearemos un nuevo endpoint llamado &lt;code&gt;SearchBlogPost&lt;/code&gt;, que permitirá encontrar los &lt;strong&gt;posts más relevantes&lt;/strong&gt; basados en una consulta en lenguaje natural.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SearchBlogPost&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Guid&lt;/span&gt; &lt;span class="n"&gt;BlogPostId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Handler&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nf"&gt;FromKeyedServices&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BlogPost&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;VectorName&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt; &lt;span class="n"&gt;ITextSearch&lt;/span&gt; &lt;span class="n"&gt;textSearch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;Handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;KernelSearchResults&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TextSearchResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;textResults&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;textSearch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetTextSearchResultsAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;Top&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;Skip&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;responses&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TextSearchResult&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;textResults&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithCancellation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;responses&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="n"&gt;Guid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Link&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                    &lt;span class="n"&gt;result&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="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;));&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;responses&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;&lt;strong&gt;Explicación del Código&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Uso de &lt;code&gt;ITextSearch&lt;/code&gt;&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Lo recibimos en el constructor con &lt;code&gt;[FromKeyedServices(BlogPost.VectorName)]&lt;/code&gt;. Esto asegura que estamos inyectando la instancia correcta configurada para nuestra colección de &lt;strong&gt;blog posts&lt;/strong&gt; en Qdrant.

&lt;ul&gt;
&lt;li&gt;Esto lo hemos configurado en &lt;code&gt;DependencyConfig&lt;/code&gt; anteriormente.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Realizamos la búsqueda semántica&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Llamamos a &lt;code&gt;GetTextSearchResultsAsync(...);&lt;/code&gt;, donde:

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;request.Query&lt;/code&gt; es el término de búsqueda ingresado.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Top = 2&lt;/code&gt; indica que queremos los &lt;strong&gt;dos resultados más relevantes&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Skip = 0&lt;/code&gt; significa que no saltaremos ningún resultado.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Procesamos los resultados&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Iteramos sobre &lt;code&gt;textResults.Results&lt;/code&gt; para extraer:

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;result.Link&lt;/code&gt;: Contiene el &lt;code&gt;BlogPostId&lt;/code&gt; almacenado como string, lo convertimos a &lt;code&gt;Guid&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;result.Name&lt;/code&gt;: Corresponde al título del blog post.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;result.Value&lt;/code&gt;: Contiene la descripción del blog post.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Devolvemos la lista de resultados&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Cada &lt;code&gt;Response&lt;/code&gt; representa un &lt;strong&gt;blog post relevante&lt;/strong&gt; basado en la búsqueda semántica.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Una vez que el endpoint &lt;code&gt;SearchBlogPost&lt;/code&gt; está registrado, podemos comenzar a realizar búsquedas semánticas para encontrar los posts más relevantes según el texto ingresado.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ejemplo de Consulta&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Podemos hacer una búsqueda con la siguiente solicitud:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;### Busquedas Semanticas
@Query=arquitectura de software
GET {{SemanticKernelLearning03.ApiService_HostAddress}}/api/blog-posts?Query={{Query}}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Respuesta Esperada&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Si hay blog posts relevantes en la base de datos, la API devolverá un listado de los más coincidentes. Un ejemplo de respuesta podría ser:&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="err"&gt;  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;    &lt;/span&gt;&lt;span class="nl"&gt;"blogPostId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"949b7164-2021-4fcc-b58e-9887fdd00d0e"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;    &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Clean Architecture: Diseño de Software Modular y Escalable"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;    &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Clean Architecture es un enfoque de diseño de software ..."&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;    &lt;/span&gt;&lt;span class="nl"&gt;"blogPostId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2340f1ac-e810-4067-90f5-a71899c6d42a"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;    &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Principios SOLID en el Desarrollo de Software"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;    &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Los principios SOLID son un conjunto de cinco principios de..."&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&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;Si revisamos el repositorio, podemos notar que hay posts de distintos temas (por ejemplo, &lt;strong&gt;Machine Learning, Bases de Datos, DevOps&lt;/strong&gt;), pero la búsqueda regresó los más relevantes según el texto ingresado. Esto demuestra que el motor de búsqueda semántico está funcionando correctamente.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;Personalizando la Búsqueda&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Podemos ajustar la consulta cambiando:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;La cantidad de resultados (&lt;code&gt;Top&lt;/code&gt;)&lt;/strong&gt;: Actualmente devuelve &lt;strong&gt;dos posts&lt;/strong&gt;, pero podríamos aumentar este valor si queremos más opciones.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;El modelo de embeddings&lt;/strong&gt;: Si cambiamos el modelo de embeddings usado para almacenar los vectores, los resultados pueden variar en precisión y relevancia.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Integración con RAG (Retrieval-Augmented Generation)&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;En esta sección, vamos a implementar un nuevo endpoint que va más allá de una búsqueda semántica. En lugar de solo buscar contenido relevante, vamos a generar texto de manera dinámica a partir de una consulta proporcionada por el usuario. Lo interesante aquí es que el contexto para la generación de la respuesta será el contenido de los &lt;strong&gt;Posts del Blog&lt;/strong&gt; que coincidan con la consulta del usuario.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;¿Cómo funciona?&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Si el usuario hace una pregunta como:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cuál es el objetivo principal de clean architecture?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;En lugar de simplemente devolver una lista de posts relacionados, el modelo de Semantic Kernel buscará los posts más relevantes relacionados con la consulta, y usará esos resultados como contexto para generar una respuesta detallada. El modelo no solo devolverá contenido estático, sino que también incorporará la información más relevante y actualizada disponible en los posts, permitiendo una respuesta más precisa y contextualizada.&lt;/p&gt;

&lt;p&gt;Esta técnica es útil porque, por lo general, los modelos de lenguaje (LLMs) se entrenan con datos estáticos que no contienen información actualizada o específica del dominio. Al integrar la &lt;strong&gt;Recuperación de Información (RAG)&lt;/strong&gt;, aprovechamos los datos disponibles en tiempo real para mejorar la relevancia y exactitud de las respuestas generadas.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;¿Cómo lo implementamos?&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;En el post anterior, vimos cómo crear &lt;strong&gt;Plugins&lt;/strong&gt; o &lt;strong&gt;Funciones Semánticas&lt;/strong&gt;, y cómo Semantic Kernel se encarga de invocar automáticamente esos plugins cuando es necesario. Ahora, vamos a utilizar un plugin de búsqueda vectorial para recuperar los posts relevantes y luego construir un &lt;strong&gt;prompt personalizado&lt;/strong&gt; que permita al modelo generar una respuesta coherente utilizando esos posts como contexto.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;El Endpoint y el Código&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;El endpoint que vamos a implementar es el siguiente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;QuestionsBlogPost&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Kernel&lt;/span&gt; &lt;span class="n"&gt;kernel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;FromKeyedServices&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BlogPost&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;VectorName&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt; &lt;span class="n"&gt;ITextSearch&lt;/span&gt; &lt;span class="n"&gt;textSearch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;Handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;searchPlugin&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;textSearch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateWithGetTextSearchResults&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"SearchPlugin"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;kernel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Plugins&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;searchPlugin&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;promptTemplate&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"""
&lt;/span&gt;                                    &lt;span class="n"&gt;Utiliza&lt;/span&gt; &lt;span class="n"&gt;solamente&lt;/span&gt; &lt;span class="n"&gt;los&lt;/span&gt; &lt;span class="n"&gt;siguientes&lt;/span&gt; &lt;span class="n"&gt;contenidos&lt;/span&gt; &lt;span class="n"&gt;para&lt;/span&gt; &lt;span class="n"&gt;contestar&lt;/span&gt; &lt;span class="n"&gt;las&lt;/span&gt; &lt;span class="n"&gt;preguntas&lt;/span&gt; &lt;span class="n"&gt;realizadas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; 
                                    &lt;span class="n"&gt;Si&lt;/span&gt; &lt;span class="n"&gt;no&lt;/span&gt; &lt;span class="n"&gt;sabes&lt;/span&gt; &lt;span class="n"&gt;la&lt;/span&gt; &lt;span class="n"&gt;respuesta&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hazle&lt;/span&gt; &lt;span class="n"&gt;saber&lt;/span&gt; &lt;span class="n"&gt;al&lt;/span&gt; &lt;span class="n"&gt;usuario&lt;/span&gt; &lt;span class="n"&gt;que&lt;/span&gt; &lt;span class="n"&gt;no&lt;/span&gt; &lt;span class="n"&gt;se&lt;/span&gt; &lt;span class="n"&gt;encontr&lt;/span&gt;&lt;span class="err"&gt;ó&lt;/span&gt; &lt;span class="n"&gt;informaci&lt;/span&gt;&lt;span class="err"&gt;ó&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="n"&gt;relevante&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;

                                    &lt;span class="n"&gt;Al&lt;/span&gt; &lt;span class="n"&gt;final&lt;/span&gt; &lt;span class="n"&gt;de&lt;/span&gt; &lt;span class="n"&gt;tu&lt;/span&gt; &lt;span class="n"&gt;respuesta&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;incluye&lt;/span&gt; &lt;span class="n"&gt;el&lt;/span&gt; &lt;span class="n"&gt;nombre&lt;/span&gt; &lt;span class="n"&gt;del&lt;/span&gt; &lt;span class="n"&gt;contenido&lt;/span&gt; &lt;span class="n"&gt;utilizado&lt;/span&gt; &lt;span class="n"&gt;como&lt;/span&gt; &lt;span class="n"&gt;referencia&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;

                                    &lt;span class="p"&gt;{{&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SearchPlugin&lt;/span&gt;&lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="n"&gt;GetTextSearchResults&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)}}&lt;/span&gt;  
                                        &lt;span class="p"&gt;{{&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="n"&gt;each&lt;/span&gt; &lt;span class="k"&gt;this&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="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="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{{&lt;/span&gt;&lt;span class="n"&gt;Value&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="n"&gt;each&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;  
                                    &lt;span class="p"&gt;{{/&lt;/span&gt;&lt;span class="k"&gt;with&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;  

                                    &lt;span class="n"&gt;Pregunta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

                                    &lt;span class="p"&gt;{{&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;
                                    &lt;span class="s"&gt;""";
&lt;/span&gt;
            &lt;span class="n"&gt;KernelArguments&lt;/span&gt; &lt;span class="n"&gt;arguments&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&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="s"&gt;"query"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Query&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

            &lt;span class="n"&gt;HandlebarsPromptTemplateFactory&lt;/span&gt; &lt;span class="n"&gt;promptTemplateFactory&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;response&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;kernel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;InvokePromptAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;promptTemplate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;templateFormat&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;HandlebarsPromptTemplateFactory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HandlebarsTemplateFormat&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;promptTemplateFactory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;promptTemplateFactory&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToString&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;&lt;strong&gt;Explicación del Código&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;Handler&lt;/code&gt;&lt;/strong&gt;: Esta clase maneja la lógica de la consulta, invoca el plugin de búsqueda, crea el prompt con el contexto adecuado y genera la respuesta. Recibe un objeto &lt;strong&gt;&lt;code&gt;Kernel&lt;/code&gt;&lt;/strong&gt; y una instancia de &lt;strong&gt;&lt;code&gt;ITextSearch&lt;/code&gt;&lt;/strong&gt; que se utiliza para realizar la búsqueda de contenido relevante en los posts del blog.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;searchPlugin&lt;/code&gt;&lt;/strong&gt;: Este es el plugin de búsqueda que se crea utilizando el método &lt;strong&gt;&lt;code&gt;CreateWithGetTextSearchResults&lt;/code&gt;&lt;/strong&gt;. Este plugin se encarga de realizar la búsqueda semántica en los posts y recuperar los resultados más relevantes. El plugin se añade al &lt;strong&gt;&lt;code&gt;Kernel&lt;/code&gt;&lt;/strong&gt; para que pueda ser invocado cuando sea necesario.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;promptTemplate&lt;/code&gt;&lt;/strong&gt;: Es el template que se utilizará para generar el texto. En este caso, se utiliza &lt;strong&gt;Handlebars&lt;/strong&gt; para permitir la inserción de los resultados de la búsqueda dentro del prompt. La idea es que el modelo genere una respuesta usando solamente los contenidos encontrados en la búsqueda. El template también incluye instrucciones para que el modelo devuelva el nombre y el valor de los posts utilizados como referencia.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{{#with (SearchPlugin-GetTextSearchResults query)}}  
    {{#each this}}  
    Name: {{Name}}  
    Value: {{Value}}  
    -----------------
    {{/each}}  
{{/with}}  
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Al final, se agrega la pregunta del usuario para que el modelo la utilice como parte de la respuesta:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Pregunta:
{{query}}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;KernelArguments&lt;/code&gt;&lt;/strong&gt;: Este objeto contiene los argumentos que se pasan al modelo, en este caso, la consulta realizada por el usuario.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;InvokePromptAsync&lt;/code&gt;&lt;/strong&gt;: Este método invoca el modelo utilizando el template y los argumentos proporcionados, lo que genera una respuesta basada en los contenidos recuperados.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Flujo Completo&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;El usuario hace una consulta&lt;/strong&gt; (por ejemplo, "¿Qué es Clean Architecture?").&lt;/li&gt;
&lt;li&gt;El &lt;strong&gt;&lt;code&gt;Kernel&lt;/code&gt;&lt;/strong&gt; busca los posts relevantes utilizando el &lt;strong&gt;plugin de búsqueda vectorial&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;El &lt;strong&gt;prompt personalizado&lt;/strong&gt; es generado e incluye los resultados de la búsqueda.&lt;/li&gt;
&lt;li&gt;El &lt;strong&gt;modelo LLM&lt;/strong&gt; genera una respuesta utilizando los posts recuperados como contexto.&lt;/li&gt;
&lt;li&gt;La &lt;strong&gt;respuesta generada&lt;/strong&gt; se devuelve al usuario, incluyendo la referencia a los contenidos utilizados.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Este enfoque permite mejorar la calidad de las respuestas generadas por el modelo, ya que se aprovecha la información más actualizada y relevante disponible en el dominio. Además, al usar el contenido de los posts, el modelo tiene un contexto más específico y adaptado a las necesidades del usuario, lo que mejora la relevancia y precisión de la respuesta.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Probando el Endpoint&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Una vez que hemos configurado y creado nuestro endpoint, es hora de probarlo. Para ello, podemos hacer una solicitud HTTP como la siguiente, que ilustrará cómo interactuar con el servicio de generación de texto mediante la integración de RAG.&lt;/p&gt;

&lt;p&gt;Ejemplo de solicitud HTTP:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;###
@Question=cuál es el objetivo principal de clean architecture?
GET {{SemanticKernelLearning03.ApiService_HostAddress}}/api/blog-posts/qa?Query={{Question}}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cuando se ejecuta esta solicitud, obtendremos una respuesta estructurada que incluye no solo la respuesta generada por el modelo, sino también la referencia al contenido utilizado para generar esa respuesta.&lt;/p&gt;

&lt;p&gt;Respuesta esperada:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;El objetivo principal de Clean Architecture es promover la separación de preocupaciones y la modularidad en el diseño de software, lo que permite a las aplicaciones ser más mantenibles y escalables a lo largo del tiempo.

Reference: Clean Architecture: Diseño de Software Modular y Escalable.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;En este caso, la respuesta del endpoint es completamente dinámica y se genera utilizando los resultados de búsqueda obtenidos de los posts del blog relacionados con la pregunta. Como puedes observar, no solo obtenemos un resultado que responde a la consulta, sino que también proporcionamos el nombre del contenido utilizado como referencia, lo cual permite al usuario conocer la fuente de la información generada.&lt;/p&gt;

&lt;p&gt;En el ejemplo, configuramos el &lt;code&gt;promptTemplate&lt;/code&gt; para que el modelo de lenguaje solo utilizara los contenidos relevantes encontrados a través de la búsqueda de texto vectorial. De esta forma, garantizamos que la generación del modelo sea específica y relevante para el contexto proporcionado por los posts del blog.&lt;/p&gt;

&lt;p&gt;Este template establece claramente la regla para que el modelo solo use los resultados de búsqueda como contexto, lo cual optimiza la relevancia de las respuestas generadas.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Escalabilidad y Flexibilidad del Prompt&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;El prompt puede ser adaptado para ser más específico y mejorar la calidad de las respuestas generadas, por ejemplo, agregando instrucciones más detalladas sobre el formato de la respuesta o proporcionando una estructura más precisa para guiar al modelo. La flexibilidad del sistema permite ajustar el nivel de detalle según la necesidad del caso de uso.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ejemplo de otro Prompt:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Enlista los puntos más importantes al querer optimizar consultas SQL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Resultado generado:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;¡Claro! A continuación, te presento los puntos más importantes para optimizar consultas SQL:

1. **Índices**: Los índices ayudan a acelerar la búsqueda de datos en una base de datos. Deben ser creados en las columnas que se utilizan en la cláusula WHERE o en la cláusula ORDER BY.
2. **Normalización y desnormalización de datos**: La normalización ayuda a reducir la cantidad de datos duplicados en una base de datos, mientras que la desnormalización puede mejorar el rendimiento en ciertas situaciones.
.
.
.

Referencia: Optimización de Consultas SQL en Aplicaciones .NET
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Como vemos, el modelo genera una lista de los puntos más importantes y proporciona la referencia adecuada para la información. Este tipo de integración con Semantic Kernel y RAG hace que sea posible tener respuestas altamente relevantes y específicas basadas en contenido actual y relacionado.&lt;/p&gt;

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

&lt;p&gt;A lo largo de este post, hemos aprendido a configurar y utilizar Semantic Kernel para aprovechar el poder de los embeddings y la técnica de Retrieval-Augmented Generation (RAG). Desde la generación y almacenamiento de vectores en Qdrant, hasta la realización de búsquedas semánticas y la generación de respuestas contextualizadas mediante prompts personalizados, hemos visto cómo combinar distintas tecnologías para crear soluciones de IA avanzadas y especializadas.&lt;/p&gt;

&lt;p&gt;Esta integración no solo mejora la precisión y relevancia de las respuestas, sino que también demuestra el potencial de unir funciones semánticas, RAG y bases de datos vectoriales para abordar desafíos reales en el manejo de información. Con estas herramientas, podrás desarrollar aplicaciones que se adaptan al contexto de tus datos, ofreciendo respuestas dinámicas y actualizadas.&lt;/p&gt;

&lt;p&gt;¡El camino hacia aplicaciones inteligentes y contextualmente precisas está abierto! Sigue experimentando y optimizando estas técnicas para llevar tus proyectos al siguiente nivel.&lt;/p&gt;

&lt;h2&gt;
  
  
  Referencias
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/semantic-kernel/concepts/text-search/out-of-the-box-textsearch/vectorstore-textsearch?pivots=programming-language-csharp" rel="noopener noreferrer"&gt;Using the Semantic Kernel Vector Store text search (Preview) | Microsoft Learn&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/semantic-kernel/concepts/text-search/text-search-plugins?pivots=programming-language-csharp" rel="noopener noreferrer"&gt;Semantic Kernel Text Search Plugins (Preview) | Microsoft Learn&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/semantic-kernel/concepts/text-search/text-search-vector-stores?pivots=programming-language-csharp" rel="noopener noreferrer"&gt;Semantic Kernel Text Search with Vector Stores (Preview) | Microsoft Learn&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/semantic-kernel/concepts/prompts/handlebars-prompt-templates?pivots=programming-language-csharp" rel="noopener noreferrer"&gt;Using the Handlebars prompt template language | Microsoft Learn&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>csharp</category>
      <category>semantickernel</category>
      <category>ollama</category>
      <category>dotnet</category>
    </item>
    <item>
      <title>Extendiendo Semantic Kernel: Creando Plugins para Consultas Dinámicas</title>
      <dc:creator>Isaac Ojeda</dc:creator>
      <pubDate>Thu, 16 Jan 2025 17:34:53 +0000</pubDate>
      <link>https://forem.com/isaacojeda/extendiendo-semantic-kernel-creando-plugins-para-consultas-dinamicas-56j</link>
      <guid>https://forem.com/isaacojeda/extendiendo-semantic-kernel-creando-plugins-para-consultas-dinamicas-56j</guid>
      <description>&lt;h3&gt;
  
  
  &lt;strong&gt;1. Introducción&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;En el tutorial &lt;a href="https://dev.to/isaacojeda/semantic-kernel-crea-un-api-para-generacion-de-texto-con-ollama-y-aspire-686"&gt;anterior&lt;/a&gt;, exploramos cómo configurar y utilizar &lt;strong&gt;Semantic Kernel&lt;/strong&gt; junto con &lt;strong&gt;Aspire&lt;/strong&gt; y &lt;strong&gt;Ollama&lt;/strong&gt; para crear un API básico que generaba resúmenes de texto. Ahora, vamos a dar un paso más y enfocarnos en una de las características más potentes de Semantic Kernel: los &lt;strong&gt;plugins&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;En este tutorial, aprenderás a crear y utilizar plugins en Semantic Kernel. Los plugins son clases o componentes que encapsulan funciones específicas, permitiendo extender las capacidades del kernel con nuevas habilidades personalizadas.&lt;/p&gt;

&lt;p&gt;Para ejemplificarlo, desarrollaremos dos plugins:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Un plugin que retorna la hora actual en formato UTC.&lt;/li&gt;
&lt;li&gt;Un plugin que utiliza datos de geolocalización y clima para proporcionar información meteorológica de una ciudad específica.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Los plugins en Semantic Kernel permiten agregar habilidades modulares y reutilizables. Esto no solo facilita la escalabilidad del proyecto, sino que también abre las puertas para integrar servicios externos o lógica personalizada de manera sencilla y eficiente.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Nota 💡: Aquí podrás encontrar el código fuente de este tutorial: &lt;a href="https://github.com/isaacOjeda/DevToPosts/tree/main/SemanticKernelSeries/SemanticKernelLearning02" rel="noopener noreferrer"&gt;DevToPosts/SemanticKernelSeries/SemanticKernelLearning02 at main · isaacOjeda/DevToPosts&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;2. Qué son los Plugins en Semantic Kernel&lt;/strong&gt;
&lt;/h3&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;¿Qué es un Plugin en Semantic Kernel?&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Un &lt;strong&gt;plugin&lt;/strong&gt; en Semantic Kernel es una clase que contiene métodos decorados con atributos especiales, como &lt;code&gt;[KernelFunction]&lt;/code&gt;, para exponer su funcionalidad al kernel. Esto permite que esas funciones sean llamadas por el kernel como si fueran partes integradas de su sistema.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;¿Por qué usar Plugins?&lt;/strong&gt;
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Modularidad:&lt;/strong&gt; Los plugins encapsulan lógica específica, lo que los hace fáciles de mantener y reutilizar.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Flexibilidad:&lt;/strong&gt; Puedes crear plugins para realizar tareas como interactuar con APIs externas, procesar datos o realizar cálculos.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Integración sencilla:&lt;/strong&gt; Los plugins se registran fácilmente en el kernel, haciéndolos disponibles para su uso en el API o dentro de flujos más complejos.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;Componentes clave de un Plugin&lt;/strong&gt;
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Métodos decorados con &lt;code&gt;[KernelFunction]&lt;/code&gt;:&lt;/strong&gt; Esto marca las funciones que el kernel puede invocar.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Descripción:&lt;/strong&gt; Los métodos pueden incluir descripciones para documentar su propósito, facilitando su descubrimiento.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dependencias externas:&lt;/strong&gt; Los plugins pueden interactuar con servicios externos, como APIs, utilizando patrones conocidos como &lt;code&gt;IHttpClientFactory&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;Plugins que construiremos en este tutorial&lt;/strong&gt;
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;TimeInformationService:&lt;/strong&gt; Un plugin que proporciona la hora actual en UTC.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;WeatherInformationService:&lt;/strong&gt; Un plugin que utiliza una API externa para obtener información de geolocalización y clima.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ambos plugins serán integrados en nuestro kernel y expuestos a través del API que desarrollamos en el tutorial anterior.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;¿Cómo funcionan los Plugins en Semantic Kernel?&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Los plugins registrados en el kernel pueden ser invocados en tiempo de ejecución mediante prompts o directamente desde el código. El kernel gestiona automáticamente la ejecución de estos métodos, permitiendo integrarlos en flujos de procesamiento complejos.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;3. Desarrollo del Ejemplo&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Ahora que entendemos qué son los plugins en &lt;strong&gt;Semantic Kernel&lt;/strong&gt;, vamos a desarrollar e integrar dos plugins personalizados en nuestro proyecto:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;TimeInformationService:&lt;/strong&gt; Un plugin simple que devuelve la hora actual en UTC.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;WeatherInformationService:&lt;/strong&gt; Un plugin más avanzado que utiliza una API externa para obtener información sobre el clima en una ciudad.&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;Paso 1: Crear los Plugins&lt;/strong&gt;
&lt;/h4&gt;

&lt;h5&gt;
  
  
  &lt;strong&gt;TimeInformationService&lt;/strong&gt;
&lt;/h5&gt;

&lt;p&gt;El siguiente plugin proporciona la hora actual en formato UTC. Es un ejemplo sencillo para entender cómo funcionan los plugins básicos en Semantic Kernel.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;span class="c1"&gt;/// A plugin that returns the current time.&lt;/span&gt;
&lt;span class="c1"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TimeInformationService&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;KernelFunction&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Retrieves the current time in UTC."&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nf"&gt;GetCurrentUtcTime&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UtcNow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"R"&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;Aquí destacamos:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;El atributo &lt;code&gt;[KernelFunction]&lt;/code&gt; expone el método al kernel.&lt;/li&gt;
&lt;li&gt;El método devuelve la hora en formato "R" (RFC1123), que es legible y estándar.&lt;/li&gt;
&lt;/ul&gt;

&lt;h5&gt;
  
  
  &lt;strong&gt;WeatherInformationService&lt;/strong&gt;
&lt;/h5&gt;

&lt;p&gt;Este plugin es más complejo. Obtiene información sobre una ciudad (coordenadas, país, etc.) y también el clima actual utilizando servicios externos.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;WeatherInformationService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IHttpClientFactory&lt;/span&gt; &lt;span class="n"&gt;httpClientFactory&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="n"&gt;KernelFunction&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Get weather information for a city."&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;WeatherInfo&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;GetWeatherByCity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;cityName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IsNullOrWhiteSpace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cityName&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ArgumentException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"City name cannot be null or empty."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;nameof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cityName&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

        &lt;span class="c1"&gt;// Step 1: Get city coordinates&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;city&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;GetCityCoordinatesAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cityName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Step 2: Get weather data&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;weatherResult&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;GetWeatherDataAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Latitude&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Longitude&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;WeatherInfo&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;City&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;city&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="n"&gt;Country&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Country&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;Temperature&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;weatherResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CurrentWeather&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Temperature&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;WindSpeed&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;weatherResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CurrentWeather&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Windspeed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;WeatherCode&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;weatherResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CurrentWeather&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Weathercode&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;GeocodingResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;GetCityCoordinatesAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;cityName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// ...For more info, check the source code&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;WeatherApiResponse&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;GetWeatherDataAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;latitude&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;longitude&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// ...For more info, check the source code&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;Puntos clave:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Este plugin utiliza &lt;code&gt;IHttpClientFactory&lt;/code&gt; para realizar llamadas HTTP a APIs externas.

&lt;ul&gt;
&lt;li&gt;Como puedes ver, la inyección de dependencias funciona sin problema, por lo que aquí podríamos inyectar lo que se necesite que el plugin realice&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;El Kernel utiliza los nombres de los métodos y los parámetros para darles significado y así usarlo según el prompt solicitado.&lt;/li&gt;

&lt;/ul&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;Paso 2: Registrar los Plugins en el Kernel&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Para que el kernel pueda usar estos plugins, debemos registrarlos en la configuración del servicio:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;kernel&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddKernel&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Registrar el servicio de hora&lt;/span&gt;
&lt;span class="n"&gt;kernel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ImportPlugin&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TimeInformationService&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Registrar el servicio de clima&lt;/span&gt;
&lt;span class="n"&gt;kernel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ImportPlugin&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;WeatherInformationService&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  &lt;strong&gt;Paso 3: Exponer los Plugins Utilizando el Kernel&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;En lugar de llamar directamente a los métodos de los servicios, utilizaremos el &lt;strong&gt;Kernel&lt;/strong&gt; de Semantic Kernel para invocar los plugins mediante prompts. Esto nos permitirá combinar capacidades y hacer el uso más dinámico de los plugins.&lt;/p&gt;

&lt;p&gt;Haremos la prueba con un simple endpoint que invoque el prompt&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MapPost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/api/chat"&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="n"&gt;ChatRequest&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Kernel&lt;/span&gt; &lt;span class="n"&gt;kernel&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;OpenAIPromptExecutionSettings&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;FunctionChoiceBehavior&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;FunctionChoiceBehavior&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Auto&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="c1"&gt;// Aquí podríamos hacer un Prompt más explícito para el modelo si nuestra intención fuera&lt;/span&gt;
    &lt;span class="c1"&gt;// solo contestar preguntas sobre el clima.&lt;/span&gt;

    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;response&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;kernel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;InvokePromptAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Question&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;settings&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;Results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Result&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToString&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;h4&gt;
  
  
  &lt;strong&gt;Explicación del Código&lt;/strong&gt;
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Uso del Kernel&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;El método &lt;code&gt;InvokePromptAsync&lt;/code&gt; utiliza el kernel para procesar el prompt y determinar qué función de los plugins debe ejecutarse.&lt;/li&gt;
&lt;li&gt;En este caso, el Kernel automáticamente seleccionará el método correcto (&lt;code&gt;GetCurrentUtcTime&lt;/code&gt; o &lt;code&gt;GetWeatherByCity&lt;/code&gt;) basado en el texto del prompt.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Integración Flexible&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;La estructura permite enviar cualquier pregunta o texto, dejando que el Kernel interprete y seleccione el método adecuado.

&lt;ul&gt;
&lt;li&gt;También podemos hacer prompts donde seamos explícitos de que Plugin queremos que el modelo utilice, pero por ahora lo dejamos en automático y que el modelo decida.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;4. Probar los Endpoints&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Con los endpoints configurados y los plugins integrados en el &lt;strong&gt;Kernel&lt;/strong&gt;, es momento de validarlos y observar cómo funcionan.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;Ejemplo 1: Consultar el Clima&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Utilizando &lt;strong&gt;Postman&lt;/strong&gt;, envía la siguiente solicitud al endpoint configurado:&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="err"&gt; &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"question"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Cuál es el clima actual en Londres"&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;Al depurar la aplicación, notarás que el modelo entiende la intención del prompt, lo asocia con nuestro plugin y determina que queremos el clima actual de Londres. Esto sucede porque hemos registrado una función específica que devuelve esta información:&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%2Fb1omsz3jsf1r2gqu742m.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%2Fb1omsz3jsf1r2gqu742m.png" alt="Image description" width="800" height="158"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;La respuesta sería algo similar a:&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="err"&gt; &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"result"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"El clima actual en Londres es frío y nublado, con una temperatura de 8.6°C y un viento que sopla a 6.8 km/h. La codificación del tiempo es 3, lo que indica condiciones climáticas mezcladas, con algunas nubes y posibles lluvias."&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  &lt;strong&gt;Ejemplo 2: Consultar Información de una Ciudad&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Ahora, realiza una consulta diferente, como esta:&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="err"&gt; &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"question"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Cuál es la población actual de Madrid?"&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;En este caso, el modelo invocará automáticamente otra función registrada en el plugin que se encarga de obtener información sobre una ciudad:&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%2F7pf76oqlglt0322h9whj.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%2F7pf76oqlglt0322h9whj.png" alt="Image description" width="800" height="144"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;La respuesta será algo como:&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="err"&gt; &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"result"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"La población actual de Madrid es de aproximadamente 3,255,594 personas, según la información proporcionada por el servicio de información geográfica."&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;¿Notas el poder detrás de esta integración? Con esta configuración, el &lt;strong&gt;Kernel&lt;/strong&gt; puede realizar tareas que antes requerían múltiples servicios separados. Todo se reduce a cómo estructuramos los prompts y las funciones de nuestros plugins.&lt;/p&gt;

&lt;p&gt;¡Y esto es solo el principio! Aún no hemos explorado características avanzadas como &lt;strong&gt;memoria semántica&lt;/strong&gt; y &lt;strong&gt;RAG (Retrieval-Augmented Generation)&lt;/strong&gt;, pero con lo que ya tenemos, podemos construir soluciones extremadamente flexibles y personalizadas para cualquier dominio o industria.&lt;/p&gt;

&lt;p&gt;Con prompts más específicos, puedes resolver problemas complejos y automatizar procesos avanzados. La única limitación es tu creatividad y las necesidades de tu proyecto.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;5. Extender Funcionalidades y Consideraciones Finales&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Ahora que hemos visto cómo registrar plugins y probarlos con el &lt;strong&gt;Kernel&lt;/strong&gt;, reflexionemos sobre cómo podemos extender estas funcionalidades para adaptarlas a necesidades más complejas. Aquí algunas ideas y recomendaciones para el siguiente nivel:&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;Incorporar Más Plugins&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;La flexibilidad de &lt;strong&gt;Semantic Kernel&lt;/strong&gt; nos permite agregar más funciones a nuestros plugins o crear nuevos plugins para cubrir otras áreas de interés, como:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Análisis de texto:&lt;/strong&gt; Procesar grandes volúmenes de datos textuales para obtener resúmenes, extraer palabras clave o identificar entidades.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Integraciones externas:&lt;/strong&gt; Conectar con APIs de terceros para realizar tareas como manejo de correos electrónicos, acceso a sistemas empresariales o realizar transacciones.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Procesamiento de imágenes:&lt;/strong&gt; Usar servicios como OCR para reconocer texto en imágenes o realizar análisis visual.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;Usar Memoria Semántica&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;La &lt;strong&gt;memoria semántica&lt;/strong&gt; de Semantic Kernel puede almacenar y recuperar información contextualmente relevante. Por ejemplo:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Recordar interacciones previas para mejorar la experiencia del usuario.&lt;/li&gt;
&lt;li&gt;Contextualizar consultas complejas basándose en datos históricos.&lt;/li&gt;
&lt;li&gt;Implementar personalización profunda en aplicaciones que requieran un historial de acciones o preferencias.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;Optimización de Prompts&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Un diseño adecuado de prompts es clave para mejorar la precisión de los resultados. Algunas recomendaciones:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Sea específico:&lt;/strong&gt; Prompts detallados producen resultados más enfocados.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use ejemplos:&lt;/strong&gt; Proporcionar ejemplos en el prompt puede ayudar al modelo a entender la intención de manera más clara.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Evalúe y ajuste:&lt;/strong&gt; Experimente con diferentes configuraciones para obtener mejores resultados en diferentes escenarios.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;Escalabilidad y Producción&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Si bien en estos ejemplos usamos &lt;strong&gt;Ollama&lt;/strong&gt; localmente para evitar problemas de infraestructura, en un entorno de producción puedes cambiar sin modificar tu código base y usar servicios como &lt;strong&gt;OpenAI&lt;/strong&gt; o &lt;strong&gt;Azure OpenAI&lt;/strong&gt; para aprovechar su escalabilidad y confiabilidad. Esto implica:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Definir claves API para entornos seguros.&lt;/li&gt;
&lt;li&gt;Configurar políticas de acceso según las necesidades de tu organización.&lt;/li&gt;
&lt;li&gt;Monitorear el uso y los costos para optimizar el rendimiento.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;Casos de Uso Empresariales&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Con los elementos ya implementados, podemos visualizar algunos casos prácticos donde estas capacidades sean útiles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Asistentes de soporte:&lt;/strong&gt; Resolver consultas comunes en tiempo real con información siempre actualizada.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automatización de tareas repetitivas:&lt;/strong&gt; Por ejemplo, generar reportes basados en datos o notificaciones programadas.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sistemas personalizados:&lt;/strong&gt; Aplicaciones inteligentes que se adapten dinámicamente a las necesidades de los usuarios, como dashboards interactivos o chatbots avanzados.
### &lt;strong&gt;Conclusión&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Con &lt;strong&gt;Semantic Kernel&lt;/strong&gt;, hemos transformado lo que tradicionalmente sería una combinación de servicios aislados en una solución unificada y potente. Desde entender intenciones en lenguaje natural hasta ejecutar acciones basadas en plugins específicos, el Kernel nos proporciona una plataforma flexible y extensible para la creación de aplicaciones inteligentes.&lt;/p&gt;

&lt;p&gt;La verdadera ventaja de esta tecnología radica en su capacidad de evolución. Puedes comenzar con casos de uso simples, como los ejemplos de este tutorial, y escalar hacia soluciones más complejas que integren múltiples fuentes de datos, memoria semántica y funcionalidades avanzadas.&lt;/p&gt;

&lt;p&gt;Tu creatividad y las necesidades de tu proyecto dictarán el camino. Ahora que tienes las bases, ¿qué construirás con &lt;strong&gt;Semantic Kernel&lt;/strong&gt;?&lt;/p&gt;

</description>
      <category>csharp</category>
      <category>ai</category>
      <category>semantickernel</category>
      <category>ollama</category>
    </item>
    <item>
      <title>Semantic Kernel: Crea un API para Generación de Texto con Ollama y Aspire</title>
      <dc:creator>Isaac Ojeda</dc:creator>
      <pubDate>Tue, 14 Jan 2025 20:18:42 +0000</pubDate>
      <link>https://forem.com/isaacojeda/semantic-kernel-crea-un-api-para-generacion-de-texto-con-ollama-y-aspire-686</link>
      <guid>https://forem.com/isaacojeda/semantic-kernel-crea-un-api-para-generacion-de-texto-con-ollama-y-aspire-686</guid>
      <description>&lt;h3&gt;
  
  
  &lt;strong&gt;1. Introducción&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;En este tutorial, aprenderás cómo usar &lt;strong&gt;Semantic Kernel&lt;/strong&gt; para integrar Large Language Models (LLM) y construir un servicio REST que resuma texto. Utilizaremos &lt;strong&gt;Ollama&lt;/strong&gt; como motor local de modelos de lenguaje, lo que nos permitirá evitar el uso de servicios en la nube en esta fase de desarrollo.&lt;/p&gt;

&lt;p&gt;El objetivo principal es configurar un entorno de trabajo funcional en C# que permita a los desarrolladores:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Conectar un modelo de lenguaje local (&lt;em&gt;llama3.2&lt;/em&gt;).&lt;/li&gt;
&lt;li&gt;Crear un kernel para manejar tareas personalizadas.&lt;/li&gt;
&lt;li&gt;Exponer la funcionalidad como un endpoint REST en una API ASP.NET Core.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Al finalizar, tendrás un servicio de API que podrá recibir texto como entrada y devolver un resumen en una sola oración. Esto no solo te ayudará a entender cómo usar Semantic Kernel, sino que también te dará una base sólida para construir aplicaciones prácticas basadas en IA generativa.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;2. Introducción a Semantic Kernel&lt;/strong&gt;
&lt;/h3&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;2.1. ¿Qué es Semantic Kernel?&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Semantic Kernel es un SDK de código abierto que permite a los desarrolladores crear sus propios agentes personalizados de inteligencia artificial (IA). Al combinar modelos de lenguaje de gran escala (LLMs) con código nativo, los desarrolladores pueden crear agentes de IA que entiendan y respondan a solicitudes en lenguaje natural para realizar una variedad de tareas.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;2.2 ¿Qué es un agente de IA?&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Un agente de IA es un programa diseñado para alcanzar objetivos predeterminados. Los agentes de IA están impulsados por modelos de lenguaje de gran escala (LLMs) entrenados con cantidades masivas de datos. Estos agentes pueden completar una amplia variedad de tareas con mínima o ninguna intervención humana. Algunos ejemplos de lo que pueden hacer los agentes de IA incluyen:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Escribir código.&lt;/li&gt;
&lt;li&gt;Redactar correos electrónicos.&lt;/li&gt;
&lt;li&gt;Resumir reuniones.&lt;/li&gt;
&lt;li&gt;Proporcionar recomendaciones.&lt;/li&gt;
&lt;li&gt;¡Y mucho más!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Semantic Kernel integra modelos de lenguaje como OpenAI, Azure OpenAI y Hugging Face con lenguajes de programación convencionales como C#, Python y Java. Los desarrolladores pueden crear "plugins" para interactuar con los LLMs y realizar diversas tareas. Además, el SDK de Semantic Kernel incluye plugins predefinidos que pueden mejorar rápidamente una aplicación. Esto permite que los desarrolladores utilicen LLMs en sus propias aplicaciones sin necesidad de aprender los detalles específicos de la API del modelo.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;2.3 Componentes clave del SDK de Semantic Kernel&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Capa de orquestación de IA&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
El núcleo del stack de Semantic Kernel es una capa de orquestación de IA que permite la integración fluida de modelos de IA y plugins. Esta capa combina estos componentes para crear interacciones innovadoras con los usuarios.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Conectores&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
El SDK ofrece un conjunto de conectores que permiten a los desarrolladores integrar LLMs en sus aplicaciones existentes. Estos conectores actúan como un puente entre el código de la aplicación y los modelos de IA.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Plugins&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
El SDK opera con plugins, que funcionan como el "cuerpo" de la aplicación de IA. Los plugins incluyen solicitudes (prompts) que el modelo de IA debe responder y funciones para realizar tareas especializadas. Los desarrolladores pueden usar plugins predefinidos o crear los suyos propios.&lt;/p&gt;
&lt;h3&gt;
  
  
  &lt;strong&gt;3. Desarrollo del Ejemplo&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;En esta sección, construiremos un API REST que utiliza &lt;strong&gt;Semantic Kernel&lt;/strong&gt; y el modelo de lenguaje &lt;strong&gt;llama3.2&lt;/strong&gt; de &lt;strong&gt;Ollama&lt;/strong&gt; para resumir texto en una sola oración. El objetivo es entender cómo configurar el entorno y crear una habilidad básica que pueda ser utilizada desde cualquier aplicación a través de un endpoint.&lt;/p&gt;
&lt;h4&gt;
  
  
  &lt;strong&gt;3.1. Configuración con Aspire&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Antes de entrar en los detalles del API, configuramos el entorno utilizando &lt;strong&gt;Aspire&lt;/strong&gt;. Este framework facilita la construcción de aplicaciones distribuidas, simplificando la gestión de dependencias y servicios.&lt;/p&gt;

&lt;p&gt;Lo que he hecho para simplificar la configuración, es utilizar Visual Studio para crear la solución ejemplo de Aspire con .NET 9:&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%2F9av06ysmd1bwoiyyj7is.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%2F9av06ysmd1bwoiyyj7is.png" alt="Image description" width="624" height="657"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Esta plantilla, también incluye un proyecto de Blazor, en este tutorial no lo utilizaremos, pero al finalizar, puedes utilizar ese proyecto para consumir los endpoints realizados y ya tener una aplicación funcional.&lt;/p&gt;

&lt;p&gt;El código de Aspire tiene los siguientes propósitos:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Configurar Ollama:&lt;/strong&gt; Define el modelo &lt;code&gt;llama3.2&lt;/code&gt; como el motor de lenguaje. Esto nos permite trabajar con un modelo local, eliminando la necesidad de infraestructura compleja en desarrollo.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Modularidad:&lt;/strong&gt; Cada proyecto (como &lt;code&gt;SemanticKernelLearning01_ApiService&lt;/code&gt;) se configura como un módulo independiente, permitiendo una estructura clara y extensible.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Nota:&lt;/strong&gt; Usar Ollama local es ideal para pruebas y desarrollo, ya que no requiere configurar una infraestructura en la nube. Sin embargo, en producción puedes cambiar a OpenAI o Azure OpenAI sin modificar el código, aprovechando la flexibilidad de Semantic Kernel.&lt;/p&gt;
&lt;h4&gt;
  
  
  &lt;strong&gt;Código de Aspire:&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Primero necesitamos los siguientes paquetes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;  &lt;span class="nt"&gt;&amp;lt;ItemGroup&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"Aspire.Hosting.AppHost"&lt;/span&gt; &lt;span class="na"&gt;Version=&lt;/span&gt;&lt;span class="s"&gt;"9.0.0"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"CommunityToolkit.Aspire.Hosting.Ollama"&lt;/span&gt; &lt;span class="na"&gt;Version=&lt;/span&gt;&lt;span class="s"&gt;"9.1.0"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/ItemGroup&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DistributedApplication&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;ollama&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="n"&gt;builder&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddOllama&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ollama"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="c1"&gt;// &amp;lt;-- Utilizará docker para usar ollama y sus modelos &lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithDataVolume&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;   &lt;span class="c1"&gt;// &amp;lt;-- Volumen de Docker para persistir modelos descargados&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithOpenWebUI&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;   &lt;span class="c1"&gt;// &amp;lt;-- UI Estilo ChatGPT&lt;/span&gt;

&lt;span class="c1"&gt;// Descarga el modelo llama3.2 con nombre "llama"&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;llamaModel&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ollama&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"llama"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"llama3.2"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Nuestra API depende de Ollama, por lo que se referencia y espera a que esté listo&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddProject&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Projects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SemanticKernelLearning01_ApiService&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="s"&gt;"apiservice"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithReference&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;llamaModel&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WaitFor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;llamaModel&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Build&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Aquí configuramos Ollama como motor de generación de texto y vinculamos el modelo &lt;code&gt;llama3.2&lt;/code&gt;. También iniciamos el servicio API como un módulo dentro del proyecto.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;3.2. Configuración del API REST&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;En el siguiente bloque de código, configuramos un API REST utilizando &lt;strong&gt;ASP.NET Core&lt;/strong&gt; y &lt;strong&gt;Semantic Kernel&lt;/strong&gt;. Aquí se define un endpoint que acepta texto como entrada y devuelve su resumen generado por el modelo de lenguaje.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Puntos clave del código:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Kernel y Ollama:&lt;/strong&gt; Se inicializa el kernel de Semantic Kernel y se conecta con Ollama usando la configuración definida en Aspire.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Habilidad de Resumen:&lt;/strong&gt; La lógica para resumir texto se encapsula en un prompt que el modelo interpreta para generar respuestas.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Exposición del Endpoint:&lt;/strong&gt; Se define un endpoint &lt;code&gt;/api/summarizer&lt;/code&gt; que recibe un objeto JSON con el texto a resumir y devuelve el resultado.&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;Código del API REST:&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Paquetes necesarios:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;  &lt;span class="nt"&gt;&amp;lt;ItemGroup&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"Microsoft.AspNetCore.OpenApi"&lt;/span&gt; &lt;span class="na"&gt;Version=&lt;/span&gt;&lt;span class="s"&gt;"9.0.0"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"Microsoft.SemanticKernel"&lt;/span&gt; &lt;span class="na"&gt;Version=&lt;/span&gt;&lt;span class="s"&gt;"1.33.0"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"Microsoft.SemanticKernel.Connectors.Ollama"&lt;/span&gt; &lt;span class="na"&gt;Version=&lt;/span&gt;&lt;span class="s"&gt;"1.33.0-alpha"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/ItemGroup&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Program.cs&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;WebApplication&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddServiceDefaults&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddProblemDetails&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddOpenApi&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;kernel&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddKernel&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;modelId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;GetOllamaConnectionString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="cp"&gt;#pragma warning disable SKEXP0070
&lt;/span&gt;&lt;span class="n"&gt;kernel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddOllamaTextGeneration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;modelId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Está en alpha, puede cambiar.&lt;/span&gt;
&lt;span class="cp"&gt;#pragma warning restore SKEXP0070
&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseExceptionHandler&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Environment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IsDevelopment&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MapOpenApi&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MapDefaultEndpoints&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MapPost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/api/summarizer"&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="n"&gt;TextCompletionRequest&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ITextGenerationService&lt;/span&gt; &lt;span class="n"&gt;textGenerationService&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;$"""
&lt;/span&gt;                 &lt;span class="n"&gt;Summarize&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;following&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;one&lt;/span&gt; &lt;span class="n"&gt;sentence&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  

                 &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
                 &lt;span class="s"&gt;""";
&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;response&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;textGenerationService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetTextContentsAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&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;response&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="n"&gt;app&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  &lt;strong&gt;3.3. Explicación General del Flujo&lt;/strong&gt;
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Creación del Kernel:&lt;/strong&gt;
Se configura el kernel para usar Ollama como motor de generación de texto con el modelo &lt;code&gt;llama3.2&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Definición del Endpoint:&lt;/strong&gt;
El endpoint &lt;code&gt;/api/summarizer&lt;/code&gt; recibe un JSON con una propiedad &lt;code&gt;Text&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Generación del Resumen:&lt;/strong&gt;
El texto recibido se envía al modelo junto con un prompt que instruye al modelo para resumirlo en una oración.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Devolución del Resultado:&lt;/strong&gt;
El resumen generado se devuelve como respuesta al cliente que llamó al API.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Código restante:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Uri&lt;/span&gt; &lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;modelId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;GetOllamaConnectionString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Este ConnectionString es establecida por Aspire&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;connectionString&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Configuration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetConnectionString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"llama"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;connectionBuilder&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;DbConnectionStringBuilder&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;ConnectionString&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;connectionString&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="n"&gt;Uri&lt;/span&gt; &lt;span class="n"&gt;endpoint&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Uri&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;connectionBuilder&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"Endpoint"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;ToString&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;modelId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;connectionBuilder&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"Model"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;ToString&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="n"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;modelId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Simple DTO&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;TextCompletionRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Nota 💡: Recuerda que siempre puedes descargar el código desde desde este &lt;a href="https://github.com/isaacOjeda/DevToPosts/tree/main/SemanticKernelSeries/SemanticKernelLearning01" rel="noopener noreferrer"&gt;Repositorio&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;3.4 Probando la solución&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Si corremos el proyecto de Aspire, automáticamente orquestará lo necesario para que la API pueda comunicarse con ollama:&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%2Ftv7ykzjwjcw13l0q4l7h.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%2Ftv7ykzjwjcw13l0q4l7h.png" alt="Image description" width="800" height="221"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Su primera ejecución será lenta, ya que tendrá que descargar el modelo &lt;code&gt;llama3.2&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Existen muchos modelos que podemos ejecutar, tanto para Text Generation y para Embeddings (revisado en próxmos post), puedes revisar más aquí: &lt;a href="https://ollama.com/" rel="noopener noreferrer"&gt;Ollama&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Probemos el endpoint (utilizando Visual Studio y un archivo .http):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@host = https://localhost:7384

### Text Generation
POST {{host}}/api/summarizer
Content-Type: application/json

{
    "text": "Mariana found an ancient book in her grandfather’s library, with a bookmark pointing to a page that read, “Recite these words and your destiny will change.” Intrigued, she read it out loud, and in an instant, she found herself in a bustling market in a medieval town. \rNo one seemed surprised by her modern clothing; in fact, one merchant greeted her as if he knew her. As she searched for answers, an old man explained that she was the heir to a lost legacy and had to make a choice: stay and lead a kingdom on the brink of chaos, or return to her everyday life. \rWith the book in her hands, Mariana took a deep breath and closed her eyes, choosing the challenge she had secretly always wanted."
}

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

&lt;/div&gt;



&lt;p&gt;Respuesta:&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="err"&gt;  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;    &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Mariana discovers an ancient book that transports her to a medieval town, where she learns she is the heir to a lost legacy and must make a choice between returning to her ordinary life or leading a kingdom on the brink of chaos."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;    &lt;/span&gt;&lt;span class="nl"&gt;"modelId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"llama3.2"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&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;La ejecución de modelos con ollama claro que suele ser más lento, para acelerar su ejecución debemos de contar con una GPU y de preferencia Nvidia. &lt;/p&gt;

&lt;p&gt;Para operaciones sencillas, me ha funcionado bien y como vemos, la respuesta ha funcionado, todo ejecutandose en nuestra computadora.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;4. Conclusión&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;En este tutorial, exploramos los fundamentos de &lt;strong&gt;Semantic Kernel&lt;/strong&gt; y cómo integrarlo con &lt;strong&gt;Ollama&lt;/strong&gt; para construir un servicio funcional capaz de generar resúmenes de texto. A través de este ejercicio:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Aprendiste a configurar un entorno básico utilizando &lt;strong&gt;Aspire&lt;/strong&gt;, simplificando la administración de servicios distribuidos.&lt;/li&gt;
&lt;li&gt;Descubriste cómo conectar Semantic Kernel con un motor de lenguaje local, como &lt;strong&gt;llama3.2&lt;/strong&gt; de Ollama, para evitar complicaciones de infraestructura en desarrollo.&lt;/li&gt;
&lt;li&gt;Implementaste un API REST con un endpoint práctico que utiliza generación de texto como una habilidad central.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Este ejercicio muestra lo versátil que puede ser Semantic Kernel para crear aplicaciones inteligentes que aprovechan los modelos de lenguaje. Además, la flexibilidad de la arquitectura permite cambiar fácilmente entre diferentes proveedores de modelos, como OpenAI o Azure, sin necesidad de modificar el código base.&lt;/p&gt;

&lt;p&gt;Este tutorial es solo el inicio. A partir de aquí, puedes explorar y agregar nuevas habilidades, integrar bases de datos vectoriales para contextualizar las respuestas o expandir el API con capacidades adicionales.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;5. Próximos Pasos&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Ahora que ya tienes una base sólida para trabajar con &lt;strong&gt;Semantic Kernel&lt;/strong&gt; y &lt;strong&gt;Ollama&lt;/strong&gt;, considera avanzar con los siguientes temas para expandir tus conocimientos:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Ampliar tus habilidades en Semantic Kernel:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Aprende a crear habilidades más complejas con múltiples pasos y dependencias.&lt;/li&gt;
&lt;li&gt;Explora cómo usar conectores para integrar datos externos, como archivos, APIs, o bases de datos.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Incorporar contexto mediante bases de datos vectoriales:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Experimenta con herramientas como Qdrant o Pinecone para agregar contexto a las respuestas basadas en vectores.&lt;/li&gt;
&lt;li&gt;Usa embeddings para conectar preguntas con datos relevantes en tiempo real.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Migrar a un entorno de producción:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Configura la misma API para usar OpenAI o Azure OpenAI como motores de generación de texto.&lt;/li&gt;
&lt;li&gt;Aprende a manejar credenciales y seguridad en aplicaciones en producción.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Construir una interfaz gráfica:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Usa &lt;strong&gt;Blazor&lt;/strong&gt; para crear una interfaz interactiva donde los usuarios puedan interactuar con tus habilidades de Semantic Kernel.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Desarrollar tutoriales más avanzados:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Enseña a otros cómo resolver problemas reales, como análisis de sentimientos, clasificación de texto o generación de contenido adaptado al contexto.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>csharp</category>
      <category>ollama</category>
      <category>ai</category>
      <category>semantickernel</category>
    </item>
    <item>
      <title>Integration Tests en .NET con TestContainers: Pruebas de Base de Datos Aisladas</title>
      <dc:creator>Isaac Ojeda</dc:creator>
      <pubDate>Thu, 07 Nov 2024 21:23:19 +0000</pubDate>
      <link>https://forem.com/isaacojeda/integration-tests-en-net-con-testcontainers-pruebas-de-base-de-datos-aisladas-1pi6</link>
      <guid>https://forem.com/isaacojeda/integration-tests-en-net-con-testcontainers-pruebas-de-base-de-datos-aisladas-1pi6</guid>
      <description>&lt;h2&gt;
  
  
  Introducción a las Pruebas de Integración
&lt;/h2&gt;

&lt;p&gt;En el desarrollo de software, las pruebas de integración son esenciales para verificar que los distintos módulos de una aplicación trabajen correctamente en conjunto. A diferencia de las pruebas unitarias, que se centran en unidades individuales de código, las pruebas de integración se enfocan en asegurarse de que los componentes del sistema interactúan adecuadamente entre sí y, en muchos casos, dependen de servicios externos, como bases de datos, servicios de autenticación o APIs de terceros.&lt;/p&gt;

&lt;p&gt;En este artículo, vamos a trabajar con una Web API de ejemplo que utiliza SQL Server como base de datos. La idea es configurar un entorno de pruebas de integración utilizando &lt;a href="https://www.testcontainers.org/" rel="noopener noreferrer"&gt;TestContainers&lt;/a&gt;, una librería que permite levantar contenedores Docker de forma programática y en tiempo de ejecución, facilitando así la creación de entornos de prueba aislados y controlados.&lt;/p&gt;

&lt;p&gt;Para ejecutar nuestras pruebas, usaremos &lt;strong&gt;xUnit&lt;/strong&gt; como framework de testing y &lt;strong&gt;Respawn&lt;/strong&gt; para resetear la base de datos entre pruebas, lo que ayuda a garantizar que cada prueba comience con un estado limpio y consistente. Gracias a TestContainers, podemos levantar un contenedor de SQL Server específicamente para nuestras pruebas, eliminando la dependencia de una base de datos local o compartida y asegurando que el entorno sea reproducible en cualquier máquina.&lt;/p&gt;

&lt;p&gt;El código fuente completo de esta configuración y las pruebas está disponible en &lt;a href="https://github.com/isaacOjeda/DevToPosts/tree/main/TestContainers" rel="noopener noreferrer"&gt;este repositorio de GitHub&lt;/a&gt;. Si quieres seguir este tutorial, puedes clonar el repositorio y ajustar los pasos según sea necesario para tu propio proyecto.&lt;/p&gt;

&lt;h2&gt;
  
  
  Herramientas y Librerías Utilizadas
&lt;/h2&gt;

&lt;p&gt;Para implementar pruebas de integración con un entorno aislado y controlado, usaremos las siguientes librerías:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;xUnit&lt;/strong&gt;: Un framework de pruebas en .NET, ideal para organizar y ejecutar nuestras pruebas de integración.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Testcontainers.MsSql&lt;/strong&gt;: Permite levantar un contenedor de SQL Server en Docker exclusivamente para nuestras pruebas, evitando dependencias locales y asegurando un entorno consistente.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Respawn&lt;/strong&gt;: Resetea la base de datos entre pruebas, manteniendo un estado limpio y libre de datos residuales para cada ejecución.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;FluentAssertions&lt;/strong&gt;: Simplifica la escritura de aserciones, haciéndolas más legibles y expresivas.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Microsoft.AspNetCore.Mvc.Testing&lt;/strong&gt;: Utilizada para crear un servidor web en memoria para poder exponer los endpoints y poder ejecutar las pruebas de inicio a fin.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Creación de un Contenedor SQL Server con TestContainers
&lt;/h2&gt;

&lt;p&gt;En esta sección, configuraremos un contenedor de SQL Server para nuestras pruebas de integración usando &lt;strong&gt;TestContainers&lt;/strong&gt; y un &lt;strong&gt;ICollectionFixture&lt;/strong&gt; en xUnit. Esto permitirá que todas las pruebas dentro de una misma colección compartan una instancia de contenedor SQL Server, evitando configuraciones repetitivas y asegurando un entorno consistente.&lt;/p&gt;

&lt;h3&gt;
  
  
  Uso de ICollectionFixture y CollectionDefinition en xUnit
&lt;/h3&gt;

&lt;p&gt;xUnit proporciona el concepto de &lt;code&gt;ICollectionFixture&lt;/code&gt; para compartir configuraciones entre pruebas en una misma colección. Con &lt;code&gt;ICollectionFixture&lt;/code&gt;, podemos crear una configuración compartida que solo se inicializa una vez y está disponible para todas las pruebas que pertenecen a una colección específica. Esto es ideal cuando tenemos configuraciones costosas o que queremos evitar inicializar en cada prueba, como en este caso, un contenedor Docker para SQL Server.&lt;/p&gt;

&lt;p&gt;La configuración se organiza en dos partes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;ICollectionFixture&lt;/strong&gt;: Define una clase de configuración compartida, en este caso &lt;code&gt;SqlServerContainerFixture&lt;/code&gt;, que contiene toda la lógica para levantar y detener el contenedor SQL Server.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CollectionDefinition&lt;/strong&gt;: Agrupa las pruebas que comparten la misma configuración. Asociamos las pruebas a esta colección usando el nombre de la colección (en este caso, &lt;code&gt;"SqlServerContainerFixture"&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Creación del Contenedor SQL Server
&lt;/h3&gt;

&lt;p&gt;A continuación, se muestra el código de &lt;code&gt;SqlServerContainerFixture&lt;/code&gt;, que configura y administra el ciclo de vida de un contenedor SQL Server. Este fixture implementa &lt;code&gt;IAsyncLifetime&lt;/code&gt;, que permite ejecutar lógica de inicialización y limpieza de recursos de forma asincrónica.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SqlServerContainerFixture&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IAsyncLifetime&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;FixtureName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"SqlServerContainerFixture"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;MsSqlContainer&lt;/span&gt; &lt;span class="n"&gt;SqlServerContainer&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;ConnectionString&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;SqlServerContainer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetConnectionString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;SqlServerContainerFixture&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Configuramos el contenedor SQL Server usando TestContainers&lt;/span&gt;
        &lt;span class="n"&gt;SqlServerContainer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;MsSqlBuilder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"mcr.microsoft.com/mssql/server:2022-latest"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// Imagen de SQL Server&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithPassword&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Passw0rd!"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// Contraseña obligatoria para SQL Server&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Inicializa el contenedor de forma asincrónica antes de que las pruebas comiencen a ejecutarse&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;InitializeAsync&lt;/span&gt;&lt;span class="p"&gt;()&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;SqlServerContainer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;StartAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Detiene y limpia el contenedor después de que todas las pruebas hayan finalizado&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;DisposeAsync&lt;/span&gt;&lt;span class="p"&gt;()&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;SqlServerContainer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;StopAsync&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;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Propiedades y Constructor&lt;/strong&gt;: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;SqlServerContainer&lt;/code&gt;: Es el contenedor de SQL Server configurado a través de la clase &lt;code&gt;MsSqlBuilder&lt;/code&gt; de TestContainers, donde especificamos la imagen docker, la contraseña y el puerto para el contenedor.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ConnectionString&lt;/code&gt;: Proporciona una cadena de conexión a SQL Server, obtenida directamente del contenedor para que las pruebas puedan conectarse a esta base de datos.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Async Lifecycle con IAsyncLifetime&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;InitializeAsync()&lt;/code&gt;: Este método se ejecuta antes de que cualquier prueba en la colección se ejecute. Llama a &lt;code&gt;StartAsync()&lt;/code&gt; para levantar el contenedor de SQL Server.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;DisposeAsync()&lt;/code&gt;: Este método se ejecuta una vez que todas las pruebas de la colección han finalizado, liberando recursos al detener el contenedor con &lt;code&gt;StopAsync()&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  Definición de la Colección de Pruebas
&lt;/h4&gt;

&lt;p&gt;A continuación, definimos la colección de pruebas asociada al fixture &lt;code&gt;SqlServerContainerFixture&lt;/code&gt;. Esto se hace con &lt;code&gt;[CollectionDefinition]&lt;/code&gt;, que simplemente establece un nombre para la colección y vincula el fixture a todas las pruebas de la misma colección:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;CollectionDefinition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SqlServerContainerFixture&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FixtureName&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DatabaseCollection&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ICollectionFixture&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;SqlServerContainerFixture&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Esta clase no necesita código. Su único propósito es asociar&lt;/span&gt;
    &lt;span class="c1"&gt;// el CollectionDefinition con ICollectionFixture.&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;[CollectionDefinition]&lt;/code&gt;: Asocia un nombre de colección, en este caso &lt;code&gt;"SqlServerContainerFixture"&lt;/code&gt;, con el fixture &lt;code&gt;SqlServerContainerFixture&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ICollectionFixture&amp;lt;SqlServerContainerFixture&amp;gt;&lt;/code&gt;: Define la dependencia del fixture en todas las pruebas que pertenezcan a la colección.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Al agrupar nuestras pruebas en esta colección, cada una podrá acceder al contenedor de SQL Server compartido, eliminando la necesidad de inicializar un nuevo contenedor para cada prueba individualmente. Esto hace que las pruebas sean más eficientes y garantiza un entorno de datos consistente.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configuración de Reseteo de Base de Datos con Respawn y WebApplicationFactory
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Uso de &lt;code&gt;WebApplicationFactory&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;WebApplicationFactory&lt;/code&gt;&lt;/strong&gt; es una clase proporcionada por Microsoft que permite crear un servidor de pruebas en memoria basado en la configuración de la aplicación real. Esto es útil cuando necesitas probar la integración de tu Web API, ya que te permite simular el comportamiento de la aplicación sin necesidad de un servidor físico o configuración externa.&lt;/p&gt;

&lt;p&gt;En este caso, estamos creando un &lt;strong&gt;&lt;code&gt;WebApplicationFixture&lt;/code&gt;&lt;/strong&gt; que extiende &lt;code&gt;WebApplicationFactory&amp;lt;Program&amp;gt;&lt;/code&gt;. Esta clase personalizada actúa como un "wrapper" para iniciar la aplicación en un entorno de pruebas, y también maneja la configuración de la base de datos y su reseteo entre pruebas utilizando &lt;strong&gt;Respawn&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Configuración de Respawn para el Reseteo de la Base de Datos
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Respawn&lt;/strong&gt; es una librería que facilita el reseteo rápido y eficiente de la base de datos, lo cual es especialmente útil en pruebas de integración. Lo que hace Respawn es eliminar los datos de las tablas entre pruebas, asegurando que cada prueba tenga un entorno limpio y consistente sin la necesidad de restaurar toda la base de datos.&lt;/p&gt;

&lt;p&gt;En nuestro &lt;code&gt;WebApplicationFixture&lt;/code&gt;, estamos utilizando &lt;strong&gt;&lt;code&gt;IAsyncLifetime&lt;/code&gt;&lt;/strong&gt; para manejar la inicialización y limpieza asincrónica de los recursos antes y después de que se ejecuten las pruebas. En particular, se usa para la configuración de Respawn:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;WebApplicationFixture&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SqlServerContainerFixture&lt;/span&gt; &lt;span class="n"&gt;sqlServerContainerFixture&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;WebApplicationFactory&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Program&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;,&lt;/span&gt; &lt;span class="n"&gt;IAsyncLifetime&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="n"&gt;Respawner&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;_respawner&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Inicialización de Respawn y base de datos&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="n"&gt;IAsyncLifetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;InitializeAsync&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateScope&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;dbSeed&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ServiceProvider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetRequiredService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;AppDbContextSeed&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;

        &lt;span class="c1"&gt;// Inicializar Respawn&lt;/span&gt;
        &lt;span class="n"&gt;_respawner&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;Respawner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sqlServerContainerFixture&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ConnectionString&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;RespawnerOptions&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;TablesToIgnore&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"__EFMigrationsHistory"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;

        &lt;span class="c1"&gt;// Asegurar que la base de datos esté creada y con datos de prueba&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;dbSeed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EnsureCreatedAsync&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;dbSeed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SeedAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Reseteo de la base de datos después de las pruebas&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="n"&gt;IAsyncLifetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;DisposeAsync&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_respawner&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&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;}&lt;/span&gt;

        &lt;span class="c1"&gt;// Reseteamos la base de datos a su estado inicial&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_respawner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ResetAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sqlServerContainerFixture&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ConnectionString&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;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;InitializeAsync()&lt;/code&gt;&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;Respawner.CreateAsync()&lt;/code&gt;&lt;/strong&gt;: Configuramos Respawn con la cadena de conexión del contenedor SQL Server para reiniciar las tablas relevantes antes de cada clase de pruebas. Las tablas especificadas en &lt;code&gt;TablesToIgnore&lt;/code&gt; no se resetean.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;dbSeed.EnsureCreatedAsync()&lt;/code&gt; y &lt;code&gt;dbSeed.SeedAsync()&lt;/code&gt;&lt;/strong&gt;: Aseguran que la base de datos esté creada y se tengan datos iniciales para las pruebas.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;DisposeAsync()&lt;/code&gt;&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;Cuando las pruebas de una clase han finalizado, &lt;strong&gt;&lt;code&gt;DisposeAsync()&lt;/code&gt;&lt;/strong&gt; es responsable de ejecutar el método &lt;strong&gt;&lt;code&gt;ResetAsync()&lt;/code&gt;&lt;/strong&gt; de Respawn, que limpia la base de datos, dejando las tablas listas para la siguiente clase de pruebas.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  Configuración de la Base de Datos con &lt;code&gt;ConfigureWebHost&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;Dentro de la clase &lt;strong&gt;&lt;code&gt;WebApplicationFixture&lt;/code&gt;&lt;/strong&gt;, también estamos utilizando el método &lt;strong&gt;&lt;code&gt;ConfigureWebHost&lt;/code&gt;&lt;/strong&gt; para ajustar la configuración de la aplicación de pruebas, especialmente la conexión a la base de datos. Esto es necesario porque la aplicación por defecto podría estar configurada para usar una base de datos local o un entorno diferente, por lo que tenemos que "reconfigurar" la cadena de conexión para que apunte al contenedor SQL Server levantado con TestContainers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;ConfigureWebHost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IWebHostBuilder&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseEnvironment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"IntegrationTests"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ConfigureTestServices&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;services&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Eliminar configuraciones anteriores de la base de datos&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;descriptor&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SingleOrDefault&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ServiceType&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DbContextOptions&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;AppDbContext&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;));&lt;/span&gt;

        &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;descriptor&lt;/span&gt;&lt;span class="p"&gt;!);&lt;/span&gt;

        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;descriptorAppDbContext&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SingleOrDefault&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ServiceType&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AppDbContext&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

        &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;descriptorAppDbContext&lt;/span&gt;&lt;span class="p"&gt;!);&lt;/span&gt;

        &lt;span class="c1"&gt;// Configurar la base de datos para que utilice el contenedor SQL Server&lt;/span&gt;
        &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddDbContext&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;AppDbContext&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseSqlServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sqlServerContainerFixture&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ConnectionString&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;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;UseEnvironment("IntegrationTests")&lt;/code&gt;&lt;/strong&gt;: Establece el entorno de pruebas para que la aplicación cargue las configuraciones específicas para pruebas de integración (de ser necesario, podríamos tener un &lt;code&gt;appsettings.IntegrationTests.json&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;ConfigureTestServices()&lt;/code&gt;&lt;/strong&gt;: Reconfigura los servicios de la aplicación para que la conexión de la base de datos apunte al contenedor SQL Server. Primero, eliminamos cualquier configuración previa de &lt;code&gt;DbContext&lt;/code&gt; y luego agregamos la nueva configuración que utiliza la cadena de conexión proporcionada por el contenedor.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Con esta configuración, &lt;strong&gt;&lt;code&gt;WebApplicationFixture&lt;/code&gt;&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Levanta la aplicación en memoria utilizando &lt;strong&gt;&lt;code&gt;WebApplicationFactory&lt;/code&gt;&lt;/strong&gt; para simular el servidor real.&lt;/li&gt;
&lt;li&gt;Utiliza &lt;strong&gt;Respawn&lt;/strong&gt; para reiniciar la base de datos antes de cada clase de pruebas, asegurando un entorno limpio.&lt;/li&gt;
&lt;li&gt;Configura la conexión a la base de datos para que apunte al contenedor SQL Server levantado con &lt;strong&gt;TestContainers&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Escribir una Prueba de Integración Básica
&lt;/h2&gt;

&lt;p&gt;Las pruebas de integración en este contexto validan la comunicación entre nuestra aplicación y la base de datos SQL Server. Usando &lt;code&gt;WebApplicationFixture&lt;/code&gt; y &lt;code&gt;SqlServerContainerFixture&lt;/code&gt;, simulamos el entorno de la aplicación y reiniciamos el estado de la base de datos para cada clase de pruebas, asegurando que cada prueba comience desde un estado limpio.&lt;/p&gt;

&lt;h3&gt;
  
  
  Estructura de la Clase de Prueba &lt;code&gt;CreateProductTests&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;La clase &lt;code&gt;CreateProductTests&lt;/code&gt; contiene dos pruebas básicas para el endpoint de creación de productos:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Una prueba para validar que la creación de un producto es exitosa.&lt;/li&gt;
&lt;li&gt;Una prueba para verificar el comportamiento cuando se intenta crear un producto con una categoría inexistente.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;TestContainers.Integration.Tests.Endpoints.ProductTests&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Collection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SqlServerContainerFixture&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FixtureName&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CreateProductTests&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IClassFixture&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;WebApplicationFixture&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;WebApplicationFixture&lt;/span&gt; &lt;span class="n"&gt;_factory&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;AppDbContext&lt;/span&gt; &lt;span class="n"&gt;_dbContext&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;HttpClient&lt;/span&gt; &lt;span class="n"&gt;_client&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;CreateProductTests&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;WebApplicationFixture&lt;/span&gt; &lt;span class="n"&gt;factory&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;_factory&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;factory&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;_dbContext&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;factory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DbContext&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;_client&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;factory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HttpClient&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;[Collection]&lt;/strong&gt;: Esta anotación asegura que &lt;code&gt;SqlServerContainerFixture&lt;/code&gt; se utilice para todas las pruebas dentro de esta clase, compartiendo la misma configuración de base de datos.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;IClassFixture&amp;lt;WebApplicationFixture&amp;gt;&lt;/code&gt;&lt;/strong&gt;: &lt;code&gt;WebApplicationFixture&lt;/code&gt; proporciona acceso a un cliente HTTP en memoria (&lt;code&gt;HttpClient&lt;/code&gt;) para simular llamadas HTTP y a &lt;code&gt;AppDbContext&lt;/code&gt; para realizar operaciones directas en la base de datos durante la configuración de pruebas.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Primera Prueba: Creación Exitosa de un Producto
&lt;/h4&gt;

&lt;p&gt;La prueba &lt;code&gt;CreateProduct_ReturnsProduct&lt;/code&gt; verifica que la creación de un producto con una categoría válida sea exitosa y que los datos devueltos sean correctos.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Fact&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;CreateProduct_ReturnsProduct&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Arrange&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;category&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;GetFirstCategory&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Product&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="s"&gt;"Test Product"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Test Description"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Price&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;10.0m&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;CategoryId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="c1"&gt;// Act&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;response&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;_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;PostAsJsonAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/products"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Assert&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EnsureSuccessStatusCode&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;createdProduct&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;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReadFromJsonAsync&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;

    &lt;span class="n"&gt;createdProduct&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ShouldNotBeNull&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;createdProduct&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="nf"&gt;ShouldBeEqualTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;product&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="n"&gt;createdProduct&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Price&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ShouldBeEqualTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Price&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;ol&gt;
&lt;li&gt;
&lt;strong&gt;Arrange&lt;/strong&gt;: Se obtiene una categoría válida desde la base de datos (&lt;code&gt;GetFirstCategory&lt;/code&gt;) y se crea un objeto &lt;code&gt;Product&lt;/code&gt; con propiedades válidas.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Act&lt;/strong&gt;: Se envía una solicitud HTTP &lt;code&gt;POST&lt;/code&gt; al endpoint &lt;code&gt;/products&lt;/code&gt; con los datos del producto. Esto simula una creación de producto en el sistema.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Assert&lt;/strong&gt;: Se verifica que la respuesta sea exitosa y que los datos del producto creado coincidan con los esperados.&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  Segunda Prueba: Creación Fallida de un Producto con Categoría Inexistente
&lt;/h4&gt;

&lt;p&gt;La prueba &lt;code&gt;CreateProduct_Fails_WhenCategoryDoesNotExist&lt;/code&gt; verifica que la creación de un producto falle cuando se especifica una categoría inválida.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Fact&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;CreateProduct_Fails_WhenCategoryDoesNotExist&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Arrange&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Product&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="s"&gt;"Test Product"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Test Description"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Price&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;10.0m&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;CategoryId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="c1"&gt;// Categoría inválida&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="c1"&gt;// Act&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;response&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;_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;PostAsJsonAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/products"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Assert&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusCode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ShouldBeEqualTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HttpStatusCode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BadRequest&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;ol&gt;
&lt;li&gt;
&lt;strong&gt;Arrange&lt;/strong&gt;: Se crea un objeto &lt;code&gt;Product&lt;/code&gt; con una categoría inexistente (CategoryId = 0).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Act&lt;/strong&gt;: Se envía una solicitud HTTP &lt;code&gt;POST&lt;/code&gt; al endpoint &lt;code&gt;/products&lt;/code&gt; con el producto, esperando un fallo.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Assert&lt;/strong&gt;: Se verifica que el servidor responde con un estado &lt;code&gt;BadRequest&lt;/code&gt; (400), indicando que la categoría especificada no existe.&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  Método Auxiliar &lt;code&gt;GetFirstCategory&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;El método &lt;code&gt;GetFirstCategory&lt;/code&gt; es una función auxiliar que facilita la recuperación de la primera categoría disponible en la base de datos. Este método permite a las pruebas obtener un &lt;code&gt;CategoryId&lt;/code&gt; válido para productos.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Category&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;GetFirstCategory&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_dbContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Categories&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FirstAsync&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;Este enfoque de pruebas ayuda a mantener las pruebas de integración eficientes y confiables, verificando los comportamientos principales del endpoint de creación de productos y manejando casos tanto exitosos como de error.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cuando Usar Integration Tests o Unit Tests
&lt;/h2&gt;

&lt;p&gt;Las pruebas de software se dividen principalmente en &lt;strong&gt;pruebas unitarias&lt;/strong&gt; e &lt;strong&gt;integración&lt;/strong&gt;, y cada tipo tiene su propósito y contexto de uso. La elección entre uno u otro depende del objetivo de la prueba y del nivel de aislamiento necesario. A continuación, te explicamos cuándo es adecuado utilizar cada tipo de prueba.&lt;/p&gt;

&lt;h3&gt;
  
  
  Unit Tests: Aislando el Comportamiento de una Funcionalidad Específica
&lt;/h3&gt;

&lt;p&gt;Las &lt;strong&gt;pruebas unitarias&lt;/strong&gt; son el tipo más básico de pruebas. Están diseñadas para verificar el comportamiento de una unidad de código de manera aislada, sin depender de recursos externos como bases de datos, servicios web o sistemas de archivos. Este tipo de pruebas se centran en una pequeña parte del sistema, como un método o función individual, asegurando que se ejecute correctamente bajo diferentes condiciones.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cuándo usar Unit Tests&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cuando deseas comprobar el comportamiento de una &lt;strong&gt;función o método específico&lt;/strong&gt; en aislamiento.&lt;/li&gt;
&lt;li&gt;Cuando necesitas verificar que una &lt;strong&gt;lógica de negocio interna&lt;/strong&gt; funciona correctamente.&lt;/li&gt;
&lt;li&gt;Para asegurar que el código cumple con los requisitos del &lt;strong&gt;comportamiento esperado&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Cuando los cambios realizados no deberían afectar otras partes del sistema (por ejemplo, al refactorizar código o agregar nuevas características pequeñas).&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Rápidos de ejecutar&lt;/strong&gt;: debido a que son pequeños y no requieren recursos externos.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Aislamiento&lt;/strong&gt;: puedes asegurarte de que las dependencias externas no interfieran.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fáciles de automatizar&lt;/strong&gt;: las pruebas unitarias suelen ser fáciles de configurar y ejecutar en una suite de pruebas automatizadas.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Integration Tests: Validando la Interacción entre Componentes
&lt;/h3&gt;

&lt;p&gt;Las &lt;strong&gt;pruebas de integración&lt;/strong&gt; van un paso más allá, probando cómo diferentes unidades de código interactúan entre sí y con sistemas externos, como bases de datos, API, o sistemas de mensajería. Se aseguran de que los componentes del sistema funcionen juntos de la forma esperada.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cuándo usar Integration Tests&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cuando necesitas verificar cómo los &lt;strong&gt;módulos o servicios interactúan entre sí&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Si deseas probar la integración con &lt;strong&gt;sistemas externos&lt;/strong&gt; como bases de datos, servicios web, o colas de mensajes.&lt;/li&gt;
&lt;li&gt;Para validar que los cambios no afecten negativamente la interacción de diferentes capas del sistema.&lt;/li&gt;
&lt;li&gt;Para garantizar que la &lt;strong&gt;configuración de infraestructura&lt;/strong&gt; y las dependencias externas (como bases de datos) estén correctamente conectadas y funcionando.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cobertura completa de flujo&lt;/strong&gt;: Validan el flujo real de datos a través de los diferentes componentes del sistema, lo que aumenta la confianza en la funcionalidad de alto nivel.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Identificación de problemas de integración&lt;/strong&gt;: Detectan problemas en la forma en que los componentes interactúan entre sí, lo cual no se podría verificar con pruebas unitarias.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reducción de errores en producción&lt;/strong&gt;: Aseguran que el sistema se comporte correctamente en un entorno más cercano al real.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  ¿Unit Tests o Integration Tests? ¿Cuándo usar cada uno?
&lt;/h4&gt;

&lt;p&gt;A menudo, la clave está en la &lt;strong&gt;combinación adecuada&lt;/strong&gt; de ambos tipos de pruebas:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Unit Tests&lt;/strong&gt;: Son el primer paso en el ciclo de vida del desarrollo, permitiendo detectar errores rápidamente y de forma aislada.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Integration Tests&lt;/strong&gt;: Deben complementarse con las pruebas unitarias para garantizar que todos los componentes funcionen correctamente cuando se ensamblen.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Es decir:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Si estás desarrollando una pequeña funcionalidad que no interactúa con sistemas externos, &lt;strong&gt;prioriza las pruebas unitarias&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Si estás trabajando con interacción entre sistemas, bases de datos o servicios externos, &lt;strong&gt;añade pruebas de integración&lt;/strong&gt; para validar estos comportamientos más complejos.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ambos tipos de pruebas son fundamentales para una estrategia de pruebas completa, y saber cuándo usar cada uno te ayudará a mantener una buena cobertura de pruebas y mejorar la calidad de tu software.&lt;/p&gt;

&lt;h2&gt;
  
  
  Consideraciones para Escenarios Complejos
&lt;/h2&gt;

&lt;p&gt;Cuando trabajamos con escenarios más allá de las simples operaciones CRUD, es crucial que nuestras pruebas de integración sean flexibles y realistas. A continuación, algunos puntos a considerar:&lt;/p&gt;

&lt;h4&gt;
  
  
  Datos de Configuración Inicial (Seed Data)
&lt;/h4&gt;

&lt;p&gt;En escenarios complejos, es probable que necesites un conjunto de datos predefinido para representar un estado inicial específico de la base de datos. Utilizar datos de configuración inicial (o "seed data") permite que las pruebas se ejecuten en condiciones que simulan situaciones de negocio más avanzadas.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Estrategia de Configuración&lt;/strong&gt;: Puedes agregar datos de configuración en la fase de inicialización del &lt;code&gt;WebApplicationFixture&lt;/code&gt;. Esto permite que los datos de prueba específicos estén listos cada vez que se ejecuten las pruebas.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Uso de Factories o Builders&lt;/strong&gt;: Para configurar datos complejos, considera el uso de patrones como &lt;code&gt;Factory&lt;/code&gt; o &lt;code&gt;Builder&lt;/code&gt; que ayuden a crear objetos con propiedades adecuadas para el escenario, manteniendo el código de prueba más limpio y reutilizable.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Simulación de Estados de la Aplicación
&lt;/h4&gt;

&lt;p&gt;Algunas pruebas pueden requerir que la aplicación esté en un estado específico antes de ejecutar el test (como el "estado de procesamiento" o "finalizado" de una orden). Para simular estos estados:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Configuración de Estados con Estados Mock o Flags&lt;/strong&gt;: Configura los estados directamente en las entidades de prueba, o utiliza servicios mock (como mocks de notificaciones, correos electrónicos o servicios externos) para simular estados sin requerir que la aplicación ejecute todos los pasos.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test Doubles&lt;/strong&gt;: Para escenarios que dependen de servicios externos, considera el uso de "doubles" (dummies, mocks, etc.) que representen estos servicios sin necesidad de hacer llamadas reales. Esto ayuda a centrar la prueba en la lógica de negocio sin complicaciones adicionales.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Verificación de Efectos Colaterales
&lt;/h4&gt;

&lt;p&gt;Cuando las pruebas de integración implican actualizaciones de datos o envío de eventos a otros sistemas, es importante verificar que los efectos colaterales ocurren como se espera.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Simulación de Eventos&lt;/strong&gt;: Para los sistemas basados en eventos, puedes verificar que los eventos se envíen correctamente o incluso simular la recepción de eventos que la aplicación debe procesar.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Validación de Estados Finales&lt;/strong&gt;: Después de realizar una operación, verifica que las tablas relevantes y los estados de las entidades reflejen los cambios esperados.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Los escenarios complejos pueden agregar valor significativo a las pruebas de integración, ayudando a identificar problemas difíciles de reproducir en condiciones normales. Aplicar una configuración cuidadosa, incluyendo datos de configuración inicial, control de transacciones, y mocks de estados y dependencias, permitirá manejar estos escenarios de forma efectiva y aumentar la robustez de las pruebas de integración.&lt;/p&gt;

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

&lt;p&gt;Las pruebas de integración son esenciales para garantizar que los componentes de una aplicación funcionen de manera coherente y sin problemas cuando se combinan. A lo largo de este tutorial, hemos visto cómo configurar un entorno de pruebas sólido y flexible utilizando herramientas como &lt;strong&gt;xUnit&lt;/strong&gt;, &lt;strong&gt;Testcontainers&lt;/strong&gt;, &lt;strong&gt;Respawn&lt;/strong&gt;, y &lt;strong&gt;WebApplicationFactory&lt;/strong&gt; en .NET, permitiendo realizar pruebas con una base de datos SQL Server en contenedor y manteniendo un servidor de pruebas en memoria.&lt;/p&gt;

&lt;p&gt;Al seguir las prácticas descritas, desde el uso de contenedores hasta la optimización de la infraestructura de pruebas, puedes asegurar que tu aplicación se mantenga estable a lo largo del ciclo de desarrollo. Este enfoque no solo minimiza errores que solo podrían detectarse en producción, sino que también facilita la identificación y resolución de problemas antes de que escalen.&lt;/p&gt;

&lt;p&gt;Recuerda que, aunque estas pruebas pueden ser más costosas en términos de tiempo de ejecución y configuración que las pruebas unitarias, la inversión en pruebas de integración robustas proporciona un valor significativo. Estas pruebas ayudan a prevenir fallos graves y aseguran una experiencia de usuario final consistente. Adoptar buenas prácticas de optimización y aprovechar herramientas modernas para la gestión de entornos de prueba te permitirá mantener una suite de pruebas rápida y eficiente, fortaleciendo la confiabilidad de tus aplicaciones en el tiempo.&lt;/p&gt;

&lt;h2&gt;
  
  
  Referencias
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.testing.webapplicationfactory-1?view=aspnetcore-8.0" rel="noopener noreferrer"&gt;WebApplicationFactory Class (Microsoft.AspNetCore.Mvc.Testing) | Microsoft Learn&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/aspnet/core/test/integration-tests?view=aspnetcore-8.0" rel="noopener noreferrer"&gt;Integration tests in ASP.NET Core | Microsoft Learn&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://xunit.net/docs/shared-context#assembly-fixture" rel="noopener noreferrer"&gt;Shared Context between Tests &amp;gt; xUnit.net&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dotnet.testcontainers.org/modules/mssql/" rel="noopener noreferrer"&gt;Microsoft SQL Server - Testcontainers for .NET&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>dotnet</category>
      <category>testing</category>
      <category>csharp</category>
      <category>containers</category>
    </item>
    <item>
      <title>Flexibilidad y Escalabilidad: Usando Strategy y Factory Patterns</title>
      <dc:creator>Isaac Ojeda</dc:creator>
      <pubDate>Sat, 26 Oct 2024 18:18:52 +0000</pubDate>
      <link>https://forem.com/isaacojeda/flexibilidad-y-escalabilidad-usando-strategy-y-factory-patterns-5fe9</link>
      <guid>https://forem.com/isaacojeda/flexibilidad-y-escalabilidad-usando-strategy-y-factory-patterns-5fe9</guid>
      <description>&lt;h1&gt;
  
  
  Introducción
&lt;/h1&gt;

&lt;p&gt;En el desarrollo de software, construir aplicaciones modulares y flexibles es esencial para poder adaptarse a cambios de requerimientos y tecnologías. Los patrones de diseño como &lt;strong&gt;Strategy&lt;/strong&gt; y &lt;strong&gt;Factory&lt;/strong&gt; son herramientas poderosas que nos permiten crear soluciones robustas, facilitando la extensión y el mantenimiento del código. Estos patrones nos ayudan a diseñar componentes intercambiables, permitiendo que la lógica central (Core) de la aplicación permanezca independiente de los detalles específicos de cada implementación. En este artículo, exploraremos cómo emplear ambos patrones para gestionar diferentes proveedores de notificaciones de forma dinámica y escalable.&lt;/p&gt;

&lt;p&gt;Implementaremos un sistema de notificaciones en el que &lt;code&gt;INotificationsStrategy&lt;/code&gt; actúa como la interfaz central que define el contrato para enviar notificaciones, mientras que el &lt;strong&gt;Factory Pattern&lt;/strong&gt; se encarga de construir la estrategia de notificación adecuada según el proveedor configurado. Además, utilizaremos el patrón &lt;strong&gt;Decorator&lt;/strong&gt; para simplificar la inyección de dependencias, inspirándonos en la arquitectura de ASP.NET Core. Veremos cómo esta combinación de patrones nos permite intercambiar servicios de notificación de manera sencilla, hacerlos configurables y extender nuestro sistema sin modificar el Core de la aplicación.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Código Fuente: &lt;a href="https://github.com/isaacOjeda/DevToPosts/tree/main/StrategyFactoryPattern" rel="noopener noreferrer"&gt;DevToPosts/StrategyFactoryPattern at main · isaacOjeda/DevToPosts · GitHub&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Strategy Pattern
&lt;/h2&gt;

&lt;p&gt;El &lt;strong&gt;Strategy Pattern&lt;/strong&gt; es un &lt;a href="https://refactoring.guru/es/design-patterns/strategy" rel="noopener noreferrer"&gt;patrón de diseño de comportamiento&lt;/a&gt; que permite definir una familia de algoritmos, encapsular cada uno y hacerlos intercambiables. Este patrón permite que el algoritmo varíe independientemente del cliente que lo utiliza.&lt;/p&gt;

&lt;p&gt;Utilizando el Strategy Pattern, podemos aplicar diferentes estrategias de ejecución para un mismo comportamiento en función de configuraciones o condiciones externas. Este patrón es ideal cuando necesitamos flexibilidad en la lógica de negocio que se aplicará según diferentes condiciones sin modificar el código principal.&lt;/p&gt;

&lt;h2&gt;
  
  
  Factory Pattern
&lt;/h2&gt;

&lt;p&gt;El &lt;strong&gt;Factory Pattern&lt;/strong&gt;  es un &lt;a href="https://refactoring.guru/es/design-patterns/factory-method" rel="noopener noreferrer"&gt;patrón de diseño creacional&lt;/a&gt; que abstrae el proceso de creación de objetos, permitiendo a las clases delegar la responsabilidad de instanciar sus dependencias. La fábrica selecciona y devuelve la instancia correcta según las necesidades del cliente.&lt;/p&gt;

&lt;p&gt;El factory elimina la &lt;strong&gt;necesidad de conocer detalles de las clases concretas en el código del cliente,&lt;/strong&gt; centralizando la creación de objetos y reduciendo el acoplamiento. Este patrón es fundamental cuando el tipo de instancia a crear depende de configuraciones externas o de los datos en tiempo de ejecución.&lt;/p&gt;

&lt;p&gt;Al combinar el Strategy Pattern con el Factory Pattern, se logra una arquitectura flexible y escalable, ideal para aplicaciones que deben manejar múltiples implementaciones de un servicio (por ejemplo, múltiples proveedores de notificaciones). La fábrica es responsable de crear la estrategia apropiada en tiempo de ejecución, permitiendo al cliente utilizar la estrategia correcta sin necesidad de lógica adicional.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ventajas, Desventajas y Consideraciones de Uso
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Ventajas
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Flexibilidad y Extensibilidad&lt;/strong&gt;: Implementar patrones como Strategy y Factory permite integrar fácilmente distintas estrategias o servicios sin afectar el Core de la aplicación. Esto minimiza el riesgo de errores al realizar cambios y facilita la expansión de funcionalidades.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Separación de Responsabilidades&lt;/strong&gt;: Estos patrones promueven principios como el de Responsabilidad Única y la Inversión de Dependencias, lo que resulta en un código más organizado y fácil de mantener.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Facilidad para Pruebas&lt;/strong&gt;: Al estar desacopladas las implementaciones, resulta sencillo realizar pruebas unitarias y de integración mediante mocks o stubs de las interfaces.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Escalabilidad&lt;/strong&gt;: A medida que la aplicación crece, puedes añadir nuevas estrategias o servicios sin modificar la lógica del Core, lo cual facilita la evolución del sistema.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Desventajas
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Complejidad Añadida&lt;/strong&gt;: En proyectos pequeños o con lógica sencilla, la implementación de estos patrones puede resultar en una complejidad innecesaria, generando más trabajo del que realmente aporta.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Impacto en el Rendimiento&lt;/strong&gt;: La abstracción adicional puede ralentizar levemente la ejecución. En aplicaciones de alto rendimiento, es fundamental medir si el costo es justificable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Curva de Aprendizaje&lt;/strong&gt;: Para desarrolladores sin experiencia en patrones de diseño, esta estructura puede ser difícil de comprender y mantener.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Consideraciones para su uso
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Cuándo utilizar estos patrones&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Si el sistema interactúa con múltiples proveedores o servicios que podrían cambiar, o si el sistema debe ser flexible y fácil de extender, estos patrones son ideales.&lt;/li&gt;
&lt;li&gt;Cuando se usa una arquitectura modular (como Hexagonal o Clean Architecture) y se necesita proteger el Core de la aplicación de los detalles de implementación.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cuándo no utilizarlos&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;En sistemas con una sola implementación fija que no necesita cambiarse.&lt;/li&gt;
&lt;li&gt;En aplicaciones simples, donde la abstracción adicional puede considerarse sobreingeniería.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Implementando Strategy Pattern Con Factory
&lt;/h1&gt;

&lt;p&gt;En este ejemplo, implementamos una infraestructura de notificaciones utilizando &lt;strong&gt;Strategy&lt;/strong&gt; para definir varias maneras de enviar notificaciones (a través de &lt;strong&gt;SendGrid&lt;/strong&gt; o &lt;strong&gt;SMTP&lt;/strong&gt;) y &lt;strong&gt;Factory&lt;/strong&gt; para decidir cuál estrategia utilizar en función de la configuración.&lt;/p&gt;

&lt;h2&gt;
  
  
  Application Layer
&lt;/h2&gt;

&lt;p&gt;No entraremos en detalle sobre la organización específica del código, pero estas estrategias suelen emplearse en arquitecturas como &lt;strong&gt;Hexagonal&lt;/strong&gt; o &lt;strong&gt;Clean Architecture&lt;/strong&gt;. Aunque no nos centraremos en cómo estructurar el código, sí es esencial entender qué elementos forman parte del "Core" de la aplicación y cuáles corresponden a detalles externos de los que queremos aislarnos.&lt;/p&gt;

&lt;p&gt;Definir un contrato en el Core es fundamental. Este contrato determina las entradas y salidas esperadas sin depender de detalles de implementación específicos. De esta forma, el Core actúa como una "caja negra": no le interesa cómo se implementa cada detalle, solo que se cumpla el contrato definido. Esta flexibilidad hace que el código sea altamente intercambiable, y aquí es donde los patrones &lt;strong&gt;Strategy&lt;/strong&gt; y &lt;strong&gt;Factory&lt;/strong&gt; se vuelven especialmente útiles.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Nota 💡&lt;/strong&gt;: He implementado estas estrategias en diversos escenarios (almacenamiento, pasarelas de pago, procesamiento de documentos, etc.), y este &lt;a href="https://www.youtube.com/watch?v=aBOrVRKK3fA&amp;amp;t=1s&amp;amp;ab_channel=JonoWilliams" rel="noopener noreferrer"&gt;vídeo&lt;/a&gt; me inspiró a escribir este artículo, ya que es una técnica valiosa en muchos de mis proyectos.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Notifications &amp;gt; INotificationsStrategy
&lt;/h3&gt;

&lt;p&gt;Definimos una interfaz &lt;code&gt;INotificationsStrategy&lt;/code&gt;, que actúa como contrato para las distintas estrategias de notificación. Cada implementación de esta interfaz representará un proveedor de notificaciones distinto (por ejemplo, &lt;strong&gt;SendGrid&lt;/strong&gt; y &lt;strong&gt;SMTP&lt;/strong&gt;).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;INotificationsStrategy&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;SendNotificationResponse&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;SendNotification&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SendNotificationRequest&lt;/span&gt; &lt;span class="n"&gt;request&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 interfaz asegura que cualquier servicio de notificación que implementemos tendrá el mismo método &lt;code&gt;SendNotification&lt;/code&gt;. Esto permite que los clientes (la aplicación) usen cualquier servicio de notificación de manera intercambiable, asegurando consistencia en la estructura del código.&lt;/p&gt;

&lt;p&gt;Es fundamental que &lt;code&gt;SendNotificationRequest&lt;/code&gt; y &lt;code&gt;SendNotificationResponse&lt;/code&gt; permanezcan en el Core de la aplicación y estén completamente desvinculadas de los detalles específicos de cada proveedor de notificaciones. Estas clases son las piezas de información que el Core necesita para realizar una notificación, y al definirlas de forma abstracta, nos aseguramos de que el Core pueda operar independientemente de los detalles específicos de cada implementación.&lt;/p&gt;

&lt;p&gt;Dentro de &lt;code&gt;SendNotificationRequest&lt;/code&gt;, podríamos incluir datos como el correo electrónico, número de teléfono u otros detalles del usuario que son comunes en las notificaciones, sin que estos datos dependan de un proveedor específico. Cada implementación puede necesitar configuraciones únicas, pero el Core debería mantenerse lo más aislado posible de estas particularidades.&lt;/p&gt;

&lt;p&gt;Por ejemplo, una implementación podría requerir una API Key para acceder a su servicio, mientras que otra podría exigir autenticación mediante un usuario y clave secreta para obtener un token. Aunque estas diferencias pueden ser significativas, el Core no debe preocuparse por estos detalles de autenticación y configuración. Los patrones Strategy y Factory nos ayudan a gestionar estas variaciones, permitiéndonos abstraer esas diferencias y mantener el Core limpio y centrado en su función principal.&lt;/p&gt;

&lt;h3&gt;
  
  
  Notifications &amp;gt; INotificationsFactory
&lt;/h3&gt;

&lt;p&gt;La &lt;code&gt;INotificationsFactory&lt;/code&gt; es la interfaz del factory. Define métodos para crear instancias de servicios de notificación basándose en el proveedor configurado.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;INotificationsFactory&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="n"&gt;INotificationsStrategy&lt;/span&gt; &lt;span class="nf"&gt;CreateNotificationService&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 fábrica encapsula la lógica de selección de proveedores. Esto significa que cualquier cambio en el método de selección no afectará a los consumidores de los servicios de notificación.&lt;/p&gt;

&lt;h3&gt;
  
  
  Notifications &amp;gt; NotificationsConfig
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;NotificationsConfig&lt;/code&gt; es una clase de configuración que contiene las configuraciones de los distintos proveedores de notificaciones.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;NotificationsConfig&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;NotificationProvider&lt;/span&gt; &lt;span class="n"&gt;Provider&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;SendGridConfig&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;SendGrid&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;SmtpConfig&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;Smtp&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&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="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SendGridConfig&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="c1"&gt;//...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SmtpConfig&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="c1"&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 propiedad &lt;code&gt;Provider&lt;/code&gt; en &lt;code&gt;NotificationsConfig&lt;/code&gt; define el proveedor predeterminado configurado en nuestra aplicación. Este proveedor se establece a través de &lt;code&gt;appsettings.json&lt;/code&gt;, lo que facilita configurar y ajustar el servicio de notificaciones sin necesidad de modificar el código.&lt;/p&gt;

&lt;p&gt;Si bien en este ejemplo usamos &lt;code&gt;appsettings.json&lt;/code&gt;, el propósito del &lt;strong&gt;Strategy Pattern&lt;/strong&gt; es permitir la intercambiabilidad de estrategias en tiempo de ejecución. Esto significa que, además de ser configurable en el archivo de configuración, la estrategia de notificación podría adaptarse dinámicamente según las preferencias del usuario, del tenant o cualquier otra lógica específica de la aplicación. Este enfoque añade flexibilidad al sistema y ayuda a personalizar el servicio para distintos contextos o necesidades en tiempo real.&lt;/p&gt;

&lt;h2&gt;
  
  
  Infrastructure Layer
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Notifications &amp;gt; SendGrid
&lt;/h3&gt;

&lt;p&gt;Implementamos &lt;code&gt;SendGridNotificationService&lt;/code&gt;, que utiliza el proveedor de SendGrid para enviar notificaciones. Esta clase es una implementación concreta de &lt;code&gt;INotificationsStrategy&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SendGridNotificationService&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;INotificationsStrategy&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;ILogger&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;SendGridNotificationService&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_logger&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;SendGridNotificationService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ILogger&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;SendGridNotificationService&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="n"&gt;_logger&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;SendNotificationResponse&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;SendNotification&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SendNotificationRequest&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="n"&gt;_logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogInformation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Using SendGrid to send notification"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="c1"&gt;// SendGrid logic here&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;SendNotificationResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Notification sent successfully"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&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;h3&gt;
  
  
  Notifications &amp;gt; Smtp
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;SmtpNotificationsService&lt;/code&gt; es otra implementación de &lt;code&gt;INotificationsStrategy&lt;/code&gt;, que envía notificaciones utilizando un servidor SMTP.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SmtpNotificationsService&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;INotificationsStrategy&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;ILogger&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;SmtpNotificationsService&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_logger&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;SmtpNotificationsService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ILogger&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;SmtpNotificationsService&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="n"&gt;_logger&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;SendNotificationResponse&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;SendNotification&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SendNotificationRequest&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="n"&gt;_logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogInformation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Using SMTP to send notification"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="c1"&gt;// Smtp logic here&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;SendNotificationResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Notification sent successfully"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&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;Cada clase implementa su propia lógica de envío de notificaciones, pero siguen el mismo contrato &lt;code&gt;INotificationsStrategy&lt;/code&gt;. Esto asegura que el código cliente puede cambiar entre distintas implementaciones sin conocer detalles de su funcionamiento interno.&lt;/p&gt;

&lt;h3&gt;
  
  
  NotificationsService y NotificationsFactory
&lt;/h3&gt;

&lt;p&gt;En &lt;code&gt;NotificationsFactory&lt;/code&gt;, creamos las instancias de los servicios de notificación basándonos en el proveedor configurado.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;NotificationsFactory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="n"&gt;ILogger&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;NotificationsFactory&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="n"&gt;IServiceProvider&lt;/span&gt; &lt;span class="n"&gt;serviceProvider&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="n"&gt;IOptions&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;NotificationsConfig&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;notificationsConfig&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;INotificationsFactory&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;INotificationsStrategy&lt;/span&gt; &lt;span class="nf"&gt;CreateNotificationService&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;provider&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;notificationsConfig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Provider&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogInformation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Creating notification service for provider {Provider}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;provider&lt;/span&gt; &lt;span class="k"&gt;switch&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="n"&gt;NotificationProvider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SendGrid&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;serviceProvider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetRequiredService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;SendGridNotificationService&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(),&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="n"&gt;NotificationProvider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Smtp&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;serviceProvider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetRequiredService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;SmtpNotificationsService&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(),&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;NotImplementedException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"Notification provider &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt; is not implemented"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;INotificationsStrategy&lt;/span&gt; &lt;span class="nf"&gt;CreateNotificationService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NotificationProvider&lt;/span&gt; &lt;span class="n"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="c1"&gt;//...&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;NotificationsFactory&lt;/code&gt; centraliza la creación de objetos. Dependiendo del proveedor especificado, crea una instancia del servicio correspondiente sin que el cliente necesite saber cómo se instancia.&lt;/p&gt;

&lt;p&gt;Ejemplo de &lt;code&gt;appsettings.json&lt;/code&gt;:&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="err"&gt; &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"Notifications"&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="err"&gt; &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"Provider"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Smtp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"SendGrid"&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="err"&gt; &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"ApiKey"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"SG.1234567890"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt; &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="err"&gt; &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"Smtp"&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="err"&gt; &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"Server"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"smtp.server.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"Port"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;587&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"Username"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"username"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"Password"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"password"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt; &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="err"&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;Aunque en este punto podemos utilizar el Factory directamente en cualquier parte de nuestro código, podemos aplicar el patrón &lt;strong&gt;Decorator&lt;/strong&gt; para simplificar aún más el uso de &lt;code&gt;INotificationsStrategy&lt;/code&gt; y hacer que la creación de instancias sea más transparente. Para ello, crearemos la clase &lt;code&gt;NotificationService&lt;/code&gt;, que será el punto de acceso principal cuando solicitemos la dependencia &lt;code&gt;INotificationsStrategy&lt;/code&gt;. Esto significa que nuestro código usará &lt;code&gt;NotificationService&lt;/code&gt;, mientras que &lt;code&gt;NotificationService&lt;/code&gt; se encargará internamente de invocar al Factory para crear la instancia de la estrategia correcta.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Nota 💡:&lt;/strong&gt; Este método de creación de objetos está inspirado en la implementación de &lt;code&gt;IStringLocalizer&lt;/code&gt; y &lt;code&gt;IStringLocalizerFactory&lt;/code&gt; en ASP.NET Core. Al usar &lt;code&gt;IStringLocalizer&lt;/code&gt;, el framework invoca internamente el Factory para crear las instancias requeridas en función de la configuración establecida.&lt;br&gt;
Más información: &lt;a href="https://github.com/dotnet/aspnetcore/blob/main/src/Localization/Abstractions/src/StringLocalizerOfT.cs" rel="noopener noreferrer"&gt;aspnetcore/src/Localization/Abstractions/src/StringLocalizerOfT.cs at main · dotnet/aspnetcore&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Nota 2💡:&lt;/strong&gt; Explorar el código fuente puede ser de gran ayuda. Revisar, leer y ejecutar el código no solo profundiza el conocimiento de estos conceptos, sino que también puede ayudarte a ver cómo los patrones se aplican en la práctica.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;NotificationService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;INotificationsFactory&lt;/span&gt; &lt;span class="n"&gt;notificationsFactory&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;INotificationsStrategy&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="n"&gt;INotificationsStrategy&lt;/span&gt; &lt;span class="n"&gt;notificationsStrategy&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;notificationsFactory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateNotificationService&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;SendNotificationResponse&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;SendNotification&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SendNotificationRequest&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;notificationsStrategy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SendNotification&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&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;h2&gt;
  
  
  Program.cs
&lt;/h2&gt;

&lt;p&gt;En el archivo &lt;code&gt;Program.cs&lt;/code&gt;, configuramos la inyección de dependencias. Aquí registramos &lt;code&gt;INotificationsFactory&lt;/code&gt;, &lt;code&gt;INotificationsStrategy&lt;/code&gt; y las implementaciones específicas de cada proveedor.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;WebApplication&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddScoped&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;INotificationsStrategy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;NotificationService&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddScoped&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;INotificationsFactory&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;NotificationsFactory&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddScoped&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;SendGridNotificationService&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddScoped&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;SmtpNotificationsService&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;

&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Configure&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;NotificationsConfig&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Configuration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetSection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Notifications"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MapPost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/send"&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="n"&gt;SendNotificationRequest&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;INotificationsStrategy&lt;/span&gt; &lt;span class="n"&gt;notificationService&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;response&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;notificationService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SendNotification&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsSuccess&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;Results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;BadRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="n"&gt;app&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;Al combinar los patrones &lt;strong&gt;Strategy&lt;/strong&gt; y &lt;strong&gt;Factory&lt;/strong&gt;, hemos creado un sistema de notificaciones flexible y fácil de extender, en el que distintas implementaciones de notificación pueden integrarse sin afectar la lógica central de la aplicación. Esta estructura modular permite que el código sea adaptable y escalable, lo que es fundamental en entornos en los que los requisitos y las tecnologías cambian constantemente. La incorporación de un patrón &lt;strong&gt;Decorator&lt;/strong&gt; adicional facilita la inyección de dependencias, manteniendo el uso de las notificaciones simple y organizado.&lt;/p&gt;

&lt;p&gt;Estos patrones aportan claridad y robustez a la arquitectura del sistema, permitiendo que el Core de la aplicación se mantenga desacoplado de los detalles de implementación específicos de cada proveedor de notificaciones. Este enfoque también fomenta la reutilización y facilita el mantenimiento a largo plazo, ya que se pueden agregar o modificar servicios sin necesidad de realizar cambios significativos. La implementación de patrones de diseño bien aplicados no solo mejora la estructura del código, sino que también enriquece el proceso de desarrollo, proporcionando una base sólida para futuros requerimientos y funcionalidades.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>aspnetcore</category>
      <category>csharp</category>
      <category>patterns</category>
    </item>
    <item>
      <title>Estrategias para Encapsular Dependencias Externas: Creación componentes personalizados en Angular</title>
      <dc:creator>Isaac Ojeda</dc:creator>
      <pubDate>Wed, 14 Aug 2024 18:00:53 +0000</pubDate>
      <link>https://forem.com/isaacojeda/estrategias-para-encapsular-dependencias-externas-creacion-componentes-personalizados-en-angular-25f0</link>
      <guid>https://forem.com/isaacojeda/estrategias-para-encapsular-dependencias-externas-creacion-componentes-personalizados-en-angular-25f0</guid>
      <description>&lt;h3&gt;
  
  
  Introducción
&lt;/h3&gt;

&lt;p&gt;En el desarrollo de aplicaciones web modernas, la reutilización y la mantenibilidad del código son cruciales para garantizar el éxito a largo plazo. Una de las formas de lograrlo es mediante la creación de componentes personalizados, que no solo encapsulan funcionalidad específica, sino que también sirven como una capa de abstracción sobre las bibliotecas externas. Este enfoque permite minimizar la dependencia directa de terceros, reduciendo el impacto de futuros cambios en estas bibliotecas.&lt;/p&gt;

&lt;p&gt;En este artículo, exploraremos cómo crear un componente personalizado en Angular que actúe como una abstracción sobre un componente de Kendo UI. También abordaremos la importancia de la abstracción en sistemas de largo plazo y cómo las dependencias externas pueden convertirse en un problema en sistemas grandes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creación de Componentes Personalizados
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Creación del Date Picker
&lt;/h4&gt;

&lt;p&gt;Imaginemos que estás utilizando Kendo UI en tu proyecto Angular para manejar componentes UI como el Date Picker. Sin embargo, has notado que cualquier actualización en Kendo UI podría forzarte a cambiar decenas o incluso cientos de archivos en tu código base. Para mitigar este riesgo, puedes crear un componente personalizado que encapsule la lógica del Date Picker, permitiéndote modificar la implementación subyacente sin afectar el resto de tu aplicación.&lt;/p&gt;

&lt;p&gt;Aquí te mostramos cómo hacerlo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;EventEmitter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;forwardRef&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Output&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@angular/core&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ControlValueAccessor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;NG_VALUE_ACCESSOR&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@angular/forms&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;custom-date-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;template&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`
    &amp;lt;kendo-datepicker [format]="format" [value]="value" (valueChange)="onValueChange($event)" [disabled]="disabled"&amp;gt;
    &amp;lt;/kendo-datepicker&amp;gt;
  `&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;providers&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;provide&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NG_VALUE_ACCESSOR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;useExisting&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;forwardRef&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;DateInputComponent&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="na"&gt;multi&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DateInputComponent&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;ControlValueAccessor&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Input&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Input&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nx"&gt;format&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;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dd/MM/yyyy&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Output&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nx"&gt;valueChange&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;EventEmitter&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;EventEmitter&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;Date&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;public&lt;/span&gt; &lt;span class="nx"&gt;disabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;onChange&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&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="p"&gt;};&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;onTouched&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="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="nf"&gt;writeValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;registerOnChange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onChange&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;registerOnTouched&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onTouched&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;setDisabledState&lt;/span&gt;&lt;span class="p"&gt;?(&lt;/span&gt;&lt;span class="nx"&gt;isDisabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;disabled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;isDisabled&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;onValueChange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onChange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onTouched&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;valueChange&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="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;Este componente personalizado &lt;code&gt;DateInputComponent&lt;/code&gt; actúa como una abstracción sobre el componente &lt;code&gt;kendo-datepicker&lt;/code&gt;, permitiendo el binding bidireccional de su valor y emitiendo eventos cuando el valor cambia. Esto permite que tu aplicación utilice un Date Picker sin estar acoplada directamente a la implementación de Kendo.&lt;/p&gt;

&lt;h3&gt;
  
  
  Explicación del Código del &lt;code&gt;DateInputComponent&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;El &lt;code&gt;DateInputComponent&lt;/code&gt; es un componente Angular personalizado que encapsula un &lt;code&gt;kendo-datepicker&lt;/code&gt;, proporcionando una interfaz consistente y reutilizable para manejar entradas de fecha en tu aplicación. A continuación, desglosamos cada parte clave del componente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;EventEmitter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;forwardRef&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Output&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@angular/core&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ControlValueAccessor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;NG_VALUE_ACCESSOR&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@angular/forms&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Importaciones y Configuración Inicial
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Angular Core Imports&lt;/strong&gt;: Importamos las funcionalidades principales de Angular como &lt;code&gt;Component&lt;/code&gt;, &lt;code&gt;Input&lt;/code&gt;, &lt;code&gt;Output&lt;/code&gt;, y &lt;code&gt;EventEmitter&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ControlValueAccessor&lt;/strong&gt;: Esta interfaz permite que nuestro componente actúe como un puente entre el valor de un formulario y la vista del componente, permitiendo la integración con formularios reactivos y basados en plantillas.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;NG_VALUE_ACCESSOR&lt;/strong&gt;: Utilizamos este token para registrar nuestro componente como un proveedor de &lt;code&gt;ControlValueAccessor&lt;/code&gt;, lo que permite que Angular lo reconozca como un input de formulario.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Decorador @Component
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;darwin-date-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;template&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`
    &amp;lt;kendo-datepicker [format]="format" [value]="value" (valueChange)="onValueChange($event)" [disabled]="disabled"&amp;gt;
    &amp;lt;/kendo-datepicker&amp;gt;
  `&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;styleUrls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./date-input.component.css&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;providers&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;provide&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NG_VALUE_ACCESSOR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;useExisting&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;forwardRef&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;DateInputComponent&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="na"&gt;multi&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;selector&lt;/strong&gt;: Define el nombre del elemento HTML que representará este componente en la aplicación.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;template&lt;/strong&gt;: El componente encapsula un &lt;code&gt;kendo-datepicker&lt;/code&gt;, pasando las propiedades como &lt;code&gt;format&lt;/code&gt;, &lt;code&gt;value&lt;/code&gt;, y &lt;code&gt;disabled&lt;/code&gt;. También maneja el evento &lt;code&gt;valueChange&lt;/code&gt; para actualizar el valor del componente.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;providers&lt;/strong&gt;: Registra el componente como un &lt;code&gt;ControlValueAccessor&lt;/code&gt;, permitiendo que funcione en formularios de Angular.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Propiedades y Constructor
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DateInputComponent&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;BaseInput&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;Date&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;ControlValueAccessor&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Input&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Input&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nx"&gt;format&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;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dd/MM/yyyy&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Output&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nx"&gt;valueChange&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;EventEmitter&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;Date&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;public&lt;/span&gt; &lt;span class="nx"&gt;disabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;onChange&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&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="p"&gt;};&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;onTouched&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="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;valueChange&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;EventEmitter&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="o"&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;ul&gt;
&lt;li&gt;
&lt;strong&gt;@Input() value&lt;/strong&gt;: Este es el valor del &lt;code&gt;kendo-datepicker&lt;/code&gt;, que se enlaza bidireccionalmente con el formulario.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;@Input() format&lt;/strong&gt;: Permite configurar el formato de fecha desde el exterior del componente. Si no se proporciona un formato, utiliza uno por defecto.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;@Output() valueChange&lt;/strong&gt;: Este evento se emite cuando el valor de la fecha cambia, permitiendo a otros componentes reaccionar a los cambios de valor.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;disabled&lt;/strong&gt;: Controla si el input de fecha está habilitado o deshabilitado.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;onChange&lt;/strong&gt; y &lt;strong&gt;onTouched&lt;/strong&gt;: Estas funciones son definidas para cumplir con la interfaz &lt;code&gt;ControlValueAccessor&lt;/code&gt;, permitiendo que Angular controle el estado y los cambios del input.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Métodos Implementados
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;writeValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;registerOnChange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onChange&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;registerOnTouched&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onTouched&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;setDisabledState&lt;/span&gt;&lt;span class="p"&gt;?(&lt;/span&gt;&lt;span class="nx"&gt;isDisabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;disabled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;isDisabled&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;onValueChange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onChange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onTouched&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;writeValue(obj: any)&lt;/strong&gt;: Este método se invoca cuando Angular necesita actualizar el valor del componente desde el formulario. Simplemente asigna el valor recibido a la propiedad &lt;code&gt;value&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;registerOnChange(fn: any)&lt;/strong&gt; y &lt;strong&gt;registerOnTouched(fn: any)&lt;/strong&gt;: Angular pasa funciones a estos métodos para manejar los cambios en el componente y cuando el usuario interactúa con él. Guardamos estas funciones para llamarlas posteriormente.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;setDisabledState(isDisabled: boolean)&lt;/strong&gt;: Permite a Angular habilitar o deshabilitar el componente según sea necesario.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;onValueChange(value: Date)&lt;/strong&gt;: Este método se invoca cuando el usuario selecciona una fecha nueva. Actualiza el valor del componente y notifica a Angular sobre el cambio, garantizando que la vista y el modelo del formulario estén sincronizados.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Resumen
&lt;/h3&gt;

&lt;p&gt;El &lt;code&gt;DateInputComponent&lt;/code&gt; es un ejemplo de cómo encapsular la funcionalidad de componentes de terceros, como Kendo UI, dentro de tu propia interfaz abstracta. Este enfoque no solo te proporciona mayor flexibilidad y control sobre tu código, sino que también te protege contra cambios en las bibliotecas externas, minimizando el impacto en tu base de código cuando necesitas actualizar o reemplazar esas dependencias.&lt;/p&gt;

&lt;h4&gt;
  
  
  Unit Testing
&lt;/h4&gt;

&lt;p&gt;Es fundamental garantizar que tu componente personalizado funcione correctamente en todas las situaciones posibles. Para ello, puedes crear pruebas unitarias que validen su comportamiento:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ComponentFixture&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;TestBed&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@angular/core/testing&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;DateInputComponent&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./date-input.component&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;FormsModule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ReactiveFormsModule&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@angular/forms&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;BrowserAnimationsModule&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@angular/platform-browser/animations&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;DateInputsModule&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@progress/kendo-angular-dateinputs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

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

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

  &lt;span class="nf"&gt;beforeEach&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;fixture&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;TestBed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;DateInputComponent&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;component&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fixture&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;componentInstance&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;fixture&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;detectChanges&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;should create&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;component&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBeTruthy&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;should emit valueChange event when value changes&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;spyOn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;component&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;valueChange&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;emit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;newValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2024-01-01&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;component&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onValueChange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newValue&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;component&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;valueChange&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;emit&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toHaveBeenCalledWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newValue&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;should reflect the value in the template&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;newValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2024-01-01&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;component&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newValue&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;fixture&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;detectChanges&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;datePickerElement&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;HTMLElement&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fixture&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nativeElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;kendo-datepicker&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;datePickerElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ng-reflect-value&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2024-01-01T00:00:00.000Z&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Estas pruebas aseguran que el componente funcione correctamente en términos de creación, emisión de eventos, y sincronización del valor con la plantilla.&lt;/p&gt;

&lt;h3&gt;
  
  
  Uso del Componente Personalizado
&lt;/h3&gt;

&lt;p&gt;Una vez que has creado tu componente &lt;code&gt;DateInputComponent&lt;/code&gt;, puedes usarlo en formularios tanto reactivos como basados en plantillas. A continuación, se muestra cómo integrar este componente en ambos enfoques.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;Nota:&lt;/strong&gt; Este componente no se encuentra en ningún módulo, será necesario que hagas tu módulo de componentes y luego utilizarlo en algún otro módulo o importar todos tus componentes directamente en el módulo donde lo necesites.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  Uso con Reactive Forms
&lt;/h4&gt;

&lt;p&gt;Los formularios reactivos en Angular son ideales para manejar formularios complejos, donde se necesita un mayor control sobre la validación y la lógica del formulario. Aquí te muestro cómo utilizar el componente &lt;code&gt;DateInputComponent&lt;/code&gt; en un formulario reactivo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;OnInit&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@angular/core&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;FormBuilder&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;FormGroup&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@angular/forms&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;app-reactive-form-example&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`
    &amp;lt;form [formGroup]="form"&amp;gt;
      &amp;lt;label for="date"&amp;gt;Select Date:&amp;lt;/label&amp;gt;
      &amp;lt;custom-date-input formControlName="date" #dateInput&amp;gt;&amp;lt;/custom-date-input&amp;gt;
    &amp;lt;/form&amp;gt;
    &amp;lt;p&amp;gt;Selected Date: {{dateInput.value | date: 'dd/MM/yyyy'}}&amp;lt;/p&amp;gt;
  `&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ReactiveFormExampleComponent&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;OnInit&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;FormGroup&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;fb&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;FormBuilder&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

  &lt;span class="nf"&gt;ngOnInit&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;form&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;group&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&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;En este ejemplo:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Se utiliza &lt;code&gt;FormBuilder&lt;/code&gt; para crear un formulario reactivo con un control de fecha (&lt;code&gt;date&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;El componente &lt;code&gt;DateInputComponent&lt;/code&gt; se integra con el formulario utilizando &lt;code&gt;formControlName&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;La fecha seleccionada se muestra debajo del formulario utilizando la propiedad &lt;code&gt;value&lt;/code&gt; del control de fecha.&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  Uso con Template-Driven Forms
&lt;/h4&gt;

&lt;p&gt;Los formularios basados en plantillas son más adecuados para formularios más simples, donde el control directo de la lógica y la validación del formulario no es tan crucial. Aquí se muestra cómo usar el componente &lt;code&gt;DateInputComponent&lt;/code&gt; en un formulario basado en plantillas:&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;app-template-driven-form-example&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`
    &amp;lt;form #form="ngForm"&amp;gt;
      &amp;lt;label for="date"&amp;gt;Select Date:&amp;lt;/label&amp;gt;
      &amp;lt;custom-date-input name="date" [(ngModel)]="date"&amp;gt;&amp;lt;/custom-date-input&amp;gt;
    &amp;lt;/form&amp;gt;
    &amp;lt;p&amp;gt;Selected Date: {{ date | date }}&amp;lt;/p&amp;gt;
  `&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TemplateDrivenFormExampleComponent&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&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 ejemplo:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;El componente &lt;code&gt;DateInputComponent&lt;/code&gt; se utiliza dentro de un formulario basado en plantillas.&lt;/li&gt;
&lt;li&gt;Se usa &lt;code&gt;[(ngModel)]&lt;/code&gt; para crear un binding bidireccional entre el valor del componente y la propiedad &lt;code&gt;date&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;La fecha seleccionada se muestra debajo del formulario utilizando el valor de &lt;code&gt;date&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

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

&lt;p&gt;La abstracción es una herramienta poderosa en el desarrollo de software, especialmente en proyectos de larga duración. Al encapsular componentes de bibliotecas externas en componentes personalizados, puedes proteger tu código de los cambios en estas dependencias, mejorando así la mantenibilidad y reduciendo el riesgo de errores en el futuro.&lt;/p&gt;

&lt;p&gt;En sistemas grandes, la dependencia directa de bibliotecas externas puede llevar a problemas de actualización y mantenimiento, ya que los cambios en esas bibliotecas pueden requerir modificaciones extensivas en el código. Al crear una capa de abstracción, puedes aislar estas dependencias y asegurar que las futuras actualizaciones de las bibliotecas tengan un impacto mínimo en tu aplicación.&lt;/p&gt;

&lt;p&gt;Este enfoque no solo facilita la gestión del código a largo plazo, sino que también mejora la flexibilidad y la capacidad de adaptación de tu aplicación a nuevas tecnologías y necesidades emergentes. La clave es encontrar un equilibrio entre reutilización y abstracción para mantener un código limpio, modular y fácil de mantener.&lt;/p&gt;

</description>
      <category>angular</category>
      <category>webdev</category>
      <category>typescript</category>
    </item>
    <item>
      <title>Localización en ASP.NET Core: Cómo Implementar Traducciones Dinámicas Usando una Base de Datos</title>
      <dc:creator>Isaac Ojeda</dc:creator>
      <pubDate>Mon, 12 Aug 2024 13:07:38 +0000</pubDate>
      <link>https://forem.com/isaacojeda/localizacion-en-aspnet-core-como-implementar-traducciones-dinamicas-usando-una-base-de-datos-4k90</link>
      <guid>https://forem.com/isaacojeda/localizacion-en-aspnet-core-como-implementar-traducciones-dinamicas-usando-una-base-de-datos-4k90</guid>
      <description>&lt;h1&gt;
  
  
  Introducción
&lt;/h1&gt;

&lt;p&gt;En aplicaciones modernas, la localización es un aspecto crucial para ofrecer una experiencia personalizada a los usuarios de diferentes culturas e idiomas. ASP.NET Core nos proporciona una manera sencilla de implementar la localización utilizando archivos de recursos, pero este enfoque puede no ser ideal para todos los escenarios. Los archivos de recursos (archivos XML), pueden volverse difíciles de gestionar a medida que crecen en tamaño y número, especialmente cuando se trata de aplicaciones que soportan múltiples idiomas y requieren actualizaciones frecuentes.&lt;/p&gt;

&lt;p&gt;En este artículo, exploraremos cómo podemos ir más allá de las opciones predeterminadas y construir una solución de localización personalizada utilizando una base de datos como fuente de los textos localizados. Este enfoque nos ofrece una mayor flexibilidad y desacoplamiento del código, permitiendo agregar y modificar traducciones sin necesidad de redeploys ni cambios en el código fuente.&lt;/p&gt;

&lt;p&gt;Veremos cómo implementar esta solución utilizando ASP.NET Core, Entity Framework Core, y el concepto de &lt;code&gt;IStringLocalizer&lt;/code&gt; e &lt;code&gt;IStringLocalizerFactory&lt;/code&gt; para crear una infraestructura que pueda adaptarse a las necesidades específicas de cualquier proyecto. Al final de este artículo, tendrás una base sólida para implementar localización basada en bases de datos, permitiendo a tus aplicaciones manejar traducciones de manera más eficiente y escalable.&lt;/p&gt;

&lt;h1&gt;
  
  
  Localización personalizada en ASP.NET Core
&lt;/h1&gt;

&lt;p&gt;La implementación predeterminada de localización en ASP.NET Core se basa en archivos de recursos (resources files). Estos archivos son un arma de doble filo: puedes amarlos u odiarlos.&lt;/p&gt;

&lt;p&gt;Cuando trabajas con múltiples idiomas y una gran cantidad de textos por traducir, los archivos XML pueden volverse difíciles de mantener y gestionar. A menos que estés usando Visual Studio, manejar estos archivos puede ser un verdadero dolor de cabeza.&lt;/p&gt;

&lt;p&gt;Algunos frameworks, como &lt;a href="https://abp.io/docs/1.0/Localization" rel="noopener noreferrer"&gt;ABP&lt;/a&gt;, ofrecen soluciones basadas en archivos JSON, lo cual es un avance. Sin embargo, lo que quiero demostrar en este artículo es que no estamos limitados a las opciones que otros nos ofrecen. Podemos implementar la localización de la manera que mejor se adapte a nuestras necesidades.&lt;/p&gt;

&lt;p&gt;En la siguiente sección, exploraremos cómo implementar una solución de localización que no se base en archivos XML o JSON, sino en una base de datos.&lt;/p&gt;

&lt;h2&gt;
  
  
  Localización por Base de Datos
&lt;/h2&gt;

&lt;p&gt;Implementar la localización utilizando una base de datos tiene sus ventajas y desventajas, que vamos a analizar en detalle.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ventajas&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Desacoplamiento del Proyecto&lt;/strong&gt;: Una de las principales ventajas es que los recursos de localización no están embebidos en el código ni en el repositorio, lo que permite que las traducciones evolucionen sin necesidad de modificar el código ni realizar nuevos deployments. Esto facilita la gestión y reduce el tiempo de inactividad.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Facilidad de Expansión&lt;/strong&gt;: Agregar nuevos idiomas es tan simple como insertar registros adicionales en la base de datos. No es necesario cambiar el código ni hacer nuevos despliegues, lo que permite una expansión rápida y menos propensa a errores.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Centralización y Mantenimiento&lt;/strong&gt;: Al centralizar todas las traducciones en una base de datos, es más fácil mantener y auditar los recursos. Puedes aplicar reglas de negocio, implementar sistemas de validación y tener una vista centralizada de todos los textos traducidos.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Interfaz de Usuario para Edición&lt;/strong&gt;: Puedes desarrollar una interfaz administrativa que permita a los usuarios no técnicos agregar o modificar traducciones directamente en la base de datos, sin necesidad de la intervención del equipo de desarrollo.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Desventajas&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Rendimiento y Caché&lt;/strong&gt;: Es crucial implementar un sistema de caché efectivo, ya que acceder a la base de datos para cada traducción podría impactar negativamente el rendimiento de la aplicación.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Complejidad de Gestión&lt;/strong&gt;: Aunque esta solución es flexible, la gestión de las traducciones en la base de datos puede ser complicada, especialmente en sistemas con múltiples culturas e idiomas. Se requiere una estrategia clara para evitar duplicidades, inconsistencias y garantizar que las traducciones estén siempre actualizadas.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dependencia de la Base de Datos&lt;/strong&gt;: La localización depende de la disponibilidad y el rendimiento de la base de datos. Si la base de datos experimenta problemas, la localización podría verse afectada, impactando la experiencia del usuario final.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sin Soporte Out-of-the-Box&lt;/strong&gt;: A diferencia de los archivos de recursos predeterminados, esta solución requiere más configuración y personalización, lo que implica un mayor esfuerzo inicial en términos de desarrollo y pruebas.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  LocalizationResource
&lt;/h3&gt;

&lt;p&gt;Para implementar la localización en una base de datos, utilizaremos Entity Framework Core para manejar la persistencia. La clase &lt;code&gt;LocalizationResource&lt;/code&gt; será la encargada de representar cada recurso localizado.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;namespace&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="nn"&gt;CustomLocalization.Localization.Data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="nc"&gt;LocalizationResource&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt;    &lt;/span&gt;&lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="err"&gt;    &lt;/span&gt;&lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;Culture&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="err"&gt;    &lt;/span&gt;&lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;Key&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="err"&gt;    &lt;/span&gt;&lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="err"&gt;    &lt;/span&gt;&lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;AssemblyName&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="err"&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;Las propiedades incluidas son:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Id&lt;/strong&gt;: Identificador único del recurso.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Culture&lt;/strong&gt;: La cultura del recurso (por ejemplo, es-MX, en-US).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Key&lt;/strong&gt;: La clave utilizada para identificar el recurso.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Value&lt;/strong&gt;: La cadena de texto traducida.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AssemblyName&lt;/strong&gt;: El nombre del ensamblado donde se encuentra el recurso localizado (esto es útil cuando se localizan clases específicas).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  LocalizationDbContext
&lt;/h3&gt;

&lt;p&gt;El contexto de la base de datos también es bastante sencillo y sigue las convenciones estándar de Entity Framework Core:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="nn"&gt;Microsoft.EntityFrameworkCore&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;namespace&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="nn"&gt;CustomLocalization.Localization.Data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="nc"&gt;LocalizationDbContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DbContextOptions&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;LocalizationDbContext&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="nf"&gt;DbContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt;    &lt;/span&gt;&lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;DbSet&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;LocalizationResource&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;LocalizationResources&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="err"&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;h3&gt;
  
  
  DatabaseStringLocalizer
&lt;/h3&gt;

&lt;p&gt;Aquí es donde comienza la parte interesante. Necesitaremos dos componentes clave: &lt;code&gt;IStringLocalizer&lt;/code&gt; y &lt;code&gt;IStringLocalizerFactory&lt;/code&gt;. Estos trabajan en conjunto, de manera similar a cómo ASP.NET Core utiliza archivos XML de recursos, para construir un &lt;code&gt;IStringLocalizer&amp;lt;SomeClass&amp;gt;&lt;/code&gt; utilizando el Factory que vamos a definir.&lt;/p&gt;

&lt;p&gt;La implementación de &lt;code&gt;IStringLocalizer&lt;/code&gt; utilizando Entity Framework Core podría verse así:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="nn"&gt;CustomLocalization.Localization.Data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="nn"&gt;Microsoft.Extensions.Localization&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;namespace&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="nn"&gt;CustomLocalization.Localization&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="nc"&gt;DatabaseStringLocalizer&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;IStringLocalizer&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt;    &lt;/span&gt;&lt;span class="k"&gt;private&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="k"&gt;readonly&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;LocalizationDbContext&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;_context&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="err"&gt;    &lt;/span&gt;&lt;span class="k"&gt;private&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="k"&gt;readonly&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;_culture&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="err"&gt;    &lt;/span&gt;&lt;span class="k"&gt;private&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="k"&gt;readonly&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;_assemblyName&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="err"&gt;    &lt;/span&gt;&lt;span class="k"&gt;private&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="k"&gt;readonly&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;LocalizationResource&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;_assemblyResources&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="err"&gt;    &lt;/span&gt;&lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="nf"&gt;DatabaseStringLocalizer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LocalizationDbContext&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;culture&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;assemblyName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="err"&gt;    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt;        &lt;/span&gt;&lt;span class="n"&gt;_context&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="err"&gt;        &lt;/span&gt;&lt;span class="n"&gt;_culture&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;culture&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="err"&gt;        &lt;/span&gt;&lt;span class="n"&gt;_assemblyName&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;assemblyName&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

         &lt;span class="c1"&gt;// TODO: Guardar en caché es un MUST&lt;/span&gt;
&lt;span class="err"&gt;        &lt;/span&gt;&lt;span class="n"&gt;_assemblyResources&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;_context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LocalizationResources&lt;/span&gt;
&lt;span class="err"&gt;            &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Culture&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="p"&gt;==&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;_culture&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AssemblyName&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="p"&gt;==&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;_assemblyName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="err"&gt;            &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToList&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="err"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="err"&gt;    &lt;/span&gt;&lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;LocalizedString&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="err"&gt;    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt;        &lt;/span&gt;&lt;span class="k"&gt;get&lt;/span&gt;
&lt;span class="err"&gt;        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt;            &lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;_assemblyResources&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FirstOrDefault&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Key&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="p"&gt;==&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="err"&gt;            &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="p"&gt;==&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="err"&gt;            &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt;                &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="nf"&gt;LocalizedString&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="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="err"&gt;            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="err"&gt;            &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="nf"&gt;LocalizedString&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="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="err"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="err"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="err"&gt;    &lt;/span&gt;&lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;LocalizedString&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="k"&gt;params&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="kt"&gt;object&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="err"&gt;    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt;        &lt;/span&gt;&lt;span class="k"&gt;get&lt;/span&gt;
&lt;span class="err"&gt;        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt;            &lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;_assemblyResources&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FirstOrDefault&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Key&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="p"&gt;==&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="err"&gt;            &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="p"&gt;==&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="err"&gt;            &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt;                &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="nf"&gt;LocalizedString&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="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="err"&gt;            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="err"&gt;            &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="nf"&gt;LocalizedString&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="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="err"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="err"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="err"&gt;    &lt;/span&gt;&lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;IEnumerable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;LocalizedString&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="nf"&gt;GetAllStrings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;includeParentCultures&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="err"&gt;    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;_assemblyResources&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="nf"&gt;LocalizedString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="err"&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;h4&gt;
  
  
  Explicación del Código
&lt;/h4&gt;

&lt;p&gt;En la clase &lt;code&gt;DatabaseStringLocalizer&lt;/code&gt;, estamos implementando la interfaz &lt;code&gt;IStringLocalizer&lt;/code&gt;, que es esencial para manejar la localización en ASP.NET Core. Este &lt;code&gt;StringLocalizer&lt;/code&gt; se conecta a la base de datos para buscar las cadenas de texto traducidas según la cultura actual y el ensamblado que se está utilizando.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Constructor&lt;/strong&gt;: El constructor toma como parámetros el contexto de base de datos &lt;code&gt;LocalizationDbContext&lt;/code&gt;, la cultura actual y el nombre del ensamblado. Luego, consulta la base de datos para obtener todos los recursos localizados que coincidan con la cultura y el ensamblado, almacenándolos en una lista.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Indexer&lt;/strong&gt;: El indexador &lt;code&gt;this[string name]&lt;/code&gt; se utiliza para obtener el texto traducido correspondiente a una clave. Si no se encuentra ninguna traducción, se devuelve la clave original, indicando que la cadena no ha sido localizada. ==Esto es útil para evitar errores en caso de que falten traducciones==.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GetAllStrings&lt;/strong&gt;: Este método devuelve todas las cadenas localizadas disponibles para la cultura y ensamblado especificados. Es útil para casos donde se necesita mostrar una lista completa de traducciones, como en una interfaz de administración.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  StringLocalizationFactory
&lt;/h3&gt;

&lt;p&gt;El &lt;code&gt;StringLocalizationFactory&lt;/code&gt; es responsable de crear instancias de &lt;code&gt;IStringLocalizer&lt;/code&gt; basadas en la cultura actual y el recurso solicitado. Este factory sigue el patrón de diseño Factory, delegando la creación de objetos a una clase especializada.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="nn"&gt;CustomLocalization.Localization.Data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="nn"&gt;Microsoft.Extensions.Localization&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="nn"&gt;System.Globalization&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;namespace&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="nn"&gt;CustomLocalization.Localization&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="nc"&gt;StringLocalizationFactory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IServiceProvider&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;serviceProvider&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;IStringLocalizerFactory&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt;    &lt;/span&gt;&lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;IStringLocalizer&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Type&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;resourceSource&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="err"&gt;    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt;        &lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;currentCulture&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;CultureInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CurrentCulture&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="err"&gt;        &lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;assembly&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;resourceSource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FullName&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="err"&gt;        &lt;/span&gt;&lt;span class="k"&gt;using&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="nn"&gt;var&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;serviceProvider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateScope&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="err"&gt;        &lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ServiceProvider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetRequiredService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;LocalizationDbContext&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;

&lt;span class="err"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="nf"&gt;DatabaseStringLocalizer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;currentCulture&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;assembly&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="err"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="err"&gt;    &lt;/span&gt;&lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;IStringLocalizer&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;baseName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;location&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="err"&gt;    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt;        &lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;currentCulture&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;CultureInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CurrentCulture&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="err"&gt;        &lt;/span&gt;&lt;span class="k"&gt;using&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="nn"&gt;var&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;serviceProvider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateScope&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="err"&gt;        &lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ServiceProvider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetRequiredService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;LocalizationDbContext&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;

&lt;span class="err"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="nf"&gt;DatabaseStringLocalizer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;currentCulture&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;baseName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="err"&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;h4&gt;
  
  
  Explicación del Código
&lt;/h4&gt;

&lt;p&gt;El &lt;code&gt;StringLocalizationFactory&lt;/code&gt; facilita la creación de &lt;code&gt;IStringLocalizer&lt;/code&gt; utilizando la cultura actual y el recurso solicitado. Es un factory singleton, lo que significa que crea instancias de localizadores según sea necesario, y utiliza un scope para manejar las dependencias.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Create(Type resourceSource)&lt;/strong&gt;: Este método toma como parámetro un tipo (&lt;code&gt;Type&lt;/code&gt;) que representa la clase o ensamblado que solicita el &lt;code&gt;IStringLocalizer&lt;/code&gt;. Utilizando la cultura actual y el nombre completo del ensamblado, crea un nuevo &lt;code&gt;DatabaseStringLocalizer&lt;/code&gt; que obtiene las traducciones correspondientes de la base de datos.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Create(string baseName, string location)&lt;/strong&gt;: Este método es una sobrecarga del anterior y permite crear un &lt;code&gt;IStringLocalizer&lt;/code&gt; utilizando un nombre base (&lt;code&gt;baseName&lt;/code&gt;) y una ubicación (&lt;code&gt;location&lt;/code&gt;). Es útil cuando se desea especificar manualmente el nombre y la ubicación de los recursos de localización.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  HomeEndpoint
&lt;/h3&gt;

&lt;p&gt;En el siguiente ejemplo, se demuestra cómo cualquier clase puede tener su propia localización, similar a cómo funciona en ASP.NET Core de forma predeterminada.&lt;/p&gt;

&lt;p&gt;El endpoint que se presenta a continuación simplemente retorna la traducción del texto &lt;code&gt;Hello, World!&lt;/code&gt;. Este enfoque sigue el mismo estilo de ASP.NET Core, permitiendo desarrollar aplicaciones sin preocuparse inicialmente por la localización. Los textos se proporcionan como claves en un idioma predeterminado, generalmente el inglés.&lt;/p&gt;

&lt;p&gt;Este estilo es altamente flexible, ya que no es necesario realizar ninguna configuración adicional durante el desarrollo. Solo en producción se deben proporcionar las traducciones correspondientes. Si una traducción no existe, se devolverá la clave original, que representa el texto en el idioma predeterminado.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="nn"&gt;Microsoft.Extensions.Localization&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;namespace&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="nn"&gt;CustomLocalization.Endpoints&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="nc"&gt;HomeEndpoint&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt;    &lt;/span&gt;&lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;IResult&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IStringLocalizer&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;HomeEndpoint&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;localizer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="err"&gt;    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;Results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;localizer&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"Hello, World!"&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="err"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;IStringLocalizer&lt;/code&gt; se puede utilizar en cualquier parte de la aplicación, como en un Controller, una vista de Razor, Blazor Server, entre otros.&lt;/p&gt;

&lt;h3&gt;
  
  
  Program
&lt;/h3&gt;

&lt;p&gt;En esta sección configuramos las dependencias necesarias.&lt;/p&gt;

&lt;p&gt;Como podrás observar, no estamos agregando directamente el &lt;code&gt;DatabaseStringLocalizer&lt;/code&gt; al contenedor de dependencias. En su lugar, estamos utilizando la implementación predeterminada de ASP.NET Core llamada &lt;code&gt;StringLocalizer&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Esta clase utiliza internamente el &lt;code&gt;IStringLocalizerFactory&lt;/code&gt;, que en nuestro caso es &lt;code&gt;StringLocalizationFactory&lt;/code&gt;. Aunque este nombre podría cambiarse a algo más descriptivo que haga referencia a la base de datos 😅, lo que hace esta fábrica es crear instancias del &lt;code&gt;DatabaseStringLocalizer&lt;/code&gt;, lo que sigue un patrón de fábrica (Factory Pattern). Este patrón permite una abstracción clara entre la lógica de creación de objetos y su uso.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;WebApplication&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Add services to the container.&lt;/span&gt;
&lt;span class="c1"&gt;// other services...&lt;/span&gt;

&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddSingleton&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IStringLocalizerFactory&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;StringLocalizationFactory&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddTransient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IStringLocalizer&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&amp;gt;),&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;StringLocalizer&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&amp;gt;));&lt;/span&gt;

&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddDbContext&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;LocalizationDbContext&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;opts&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;opts&lt;/span&gt;
&lt;span class="err"&gt;    &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseSqlServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Configuration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetConnectionString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"DefaultConnection"&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;
&lt;span class="err"&gt;    &lt;/span&gt;
&lt;span class="c1"&gt;// other services...&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Localización de cada Request va primero que todo&lt;/span&gt;
&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseRequestLocalization&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;RequestLocalizationOptions&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt;    &lt;/span&gt;&lt;span class="n"&gt;DefaultRequestCulture&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="nf"&gt;RequestCulture&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"en-US"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="err"&gt;    &lt;/span&gt;&lt;span class="n"&gt;SupportedCultures&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;CultureInfo&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="err"&gt;    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt;        &lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"en-US"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="err"&gt;        &lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"es-MX"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="err"&gt;        &lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"de-DE"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="err"&gt;    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="err"&gt;    &lt;/span&gt;&lt;span class="n"&gt;SupportedUICultures&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;CultureInfo&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="err"&gt;    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt;        &lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"en-US"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="err"&gt;        &lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"es-MX"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="err"&gt;        &lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"de-DE"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="err"&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;// Configure the HTTP request pipeline.&lt;/span&gt;
&lt;span class="c1"&gt;// other middelwares...&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MapGet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/api"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;HomeEndpoint&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Index&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// stuff...&lt;/span&gt;

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  Explicación de &lt;code&gt;UseRequestLocalization&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;El middleware &lt;code&gt;UseRequestLocalization&lt;/code&gt; se encarga de configurar la localización para cada request. Aquí se define la cultura predeterminada (&lt;code&gt;en-US&lt;/code&gt;) y las culturas que la aplicación soporta (&lt;code&gt;en-US&lt;/code&gt;, &lt;code&gt;es-MX&lt;/code&gt; y &lt;code&gt;de-DE&lt;/code&gt;). Este middleware se ejecuta antes que cualquier otro, asegurando que la cultura esté correctamente establecida para el procesamiento de la solicitud.&lt;/p&gt;

&lt;p&gt;Si en la base de datos existe la localización adecuada, la aplicación retornará el texto configurado según la cultura del sistema.&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%2Fj71fs1z6tiq4880iq9q7.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%2Fj71fs1z6tiq4880iq9q7.png" alt="Image description" width="728" height="100"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Al realizar una solicitud al endpoint y establecer el encabezado HTTP &lt;code&gt;Accept-Language&lt;/code&gt;, obtendremos el siguiente resultado. Para más información, puedes consultar la &lt;a href="https://learn.microsoft.com/en-us/aspnet/core/fundamentals/localization/select-language-culture?view=aspnetcore-8.0" rel="noopener noreferrer"&gt;documentación oficial&lt;/a&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%2Fgbkw07kgsfqzin08wul5.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%2Fgbkw07kgsfqzin08wul5.png" alt="Image description" width="686" height="474"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;La localización es una característica clave en el desarrollo de aplicaciones globales, y ASP.NET Core ofrece una solución incorporada que es suficiente para la mayoría de los proyectos. Sin embargo, en situaciones donde se requiere un mayor control o flexibilidad, como en el manejo de grandes volúmenes de traducciones o la necesidad de actualizar idiomas sin redeploys, una localización personalizada puede ser una excelente opción.&lt;/p&gt;

&lt;p&gt;En este artículo, hemos explorado cómo implementar una solución de localización basada en bases de datos, utilizando &lt;code&gt;IStringLocalizer&lt;/code&gt; y &lt;code&gt;IStringLocalizerFactory&lt;/code&gt; personalizados con Entity Framework Core. Este enfoque permite desacoplar los recursos localizados del código fuente, facilitando la gestión de traducciones y la adición de nuevos idiomas de manera más dinámica.&lt;/p&gt;

&lt;p&gt;Es importante destacar que este es solo un ejemplo de cómo se puede personalizar la localización en ASP.NET Core. Las posibilidades son prácticamente ilimitadas: podrías extender esta idea para almacenar traducciones en archivos JSON, consumirlas desde una API externa, o incluso combinar varios métodos según las necesidades específicas de tu proyecto.&lt;/p&gt;

&lt;p&gt;Aunque la solución incorporada de ASP.NET Core es suficiente en muchos casos, tener la capacidad de personalizar y extender la localización abre la puerta a nuevas oportunidades para crear aplicaciones más flexibles y adaptables. Espero que este artículo te haya dado una perspectiva valiosa sobre cómo puedes llevar la localización en tus proyectos al siguiente nivel.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>aspnetcore</category>
      <category>csharp</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Mejorando la Observabilidad en ASP.NET Core con OpenTelemetry y Aspire</title>
      <dc:creator>Isaac Ojeda</dc:creator>
      <pubDate>Mon, 01 Apr 2024 15:01:08 +0000</pubDate>
      <link>https://forem.com/isaacojeda/mejorando-la-observabilidad-en-aspnet-core-con-opentelemetry-y-aspire-n3h</link>
      <guid>https://forem.com/isaacojeda/mejorando-la-observabilidad-en-aspnet-core-con-opentelemetry-y-aspire-n3h</guid>
      <description>&lt;h2&gt;
  
  
  Introducción
&lt;/h2&gt;

&lt;p&gt;En el ámbito del desarrollo de aplicaciones modernas, la observabilidad se ha vuelto crucial para garantizar el rendimiento y la fiabilidad de nuestros sistemas distribuidos. OpenTelemetry ha surgido como un estándar para recopilar datos de telemetría en tiempo real. En este artículo, exploraremos cómo integrar OpenTelemetry en una aplicación ASP.NET Core con Blazor, con un enfoque central en el uso de Aspire. Aspire ofrece un Dashboard potente que nos permite visualizar y analizar datos de telemetría, facilitando la monitorización y el diagnóstico del comportamiento de nuestras aplicaciones en tiempo real. Utilizaremos un ejemplo práctico para demostrar su implementación y cómo puede potenciar nuestra capacidad de observabilidad.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dashboard standalone de Aspire
&lt;/h2&gt;

&lt;p&gt;.NET Aspire está emergiendo como una herramienta sumamente poderosa para el desarrollo de aplicaciones &lt;em&gt;cloud native&lt;/em&gt; en el ecosistema .NET, además de otras plataformas.&lt;/p&gt;

&lt;p&gt;Es fascinante observar lo sencillo que resulta comenzar a desarrollar con Aspire, abordando muchos aspectos que suelen ser tediosos o complicados de manejar. Sin embargo, ¿qué sucede con las aplicaciones ya existentes? No siempre es viable cambiar por completo el enfoque y adoptar Aspire como orquestador en nuestro entorno de desarrollo. Sin embargo, resulta tentador aprovechar las características que ofrece su dashboard.&lt;/p&gt;

&lt;p&gt;Es posible que en aplicaciones ya establecidas, ya sean distribuidas o no, ya hayamos resuelto la forma de ejecutarlas. No obstante, Aspire y su dashboard nos brindan una herramienta sumamente valiosa durante la fase de desarrollo.&lt;/p&gt;

&lt;p&gt;Contar con un panel de control que nos proporcione logs, métricas y traces es de gran ayuda para comprender nuestra aplicación y cómo se comporta al interactuar con servicios, bases de datos u otros componentes.&lt;/p&gt;

&lt;p&gt;Por eso, el hecho de que el dashboard pueda utilizarse con un receptor OTLP (OpenTelemetry Protocol) y visualizar todos estos datos de manera integrada fue lo que realmente me convenció para explorar su uso.&lt;/p&gt;

&lt;p&gt;Para ejecutar el Dashboard de Aspire, podemos utilizar el siguiente comando de Docker:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; 18888:18888 &lt;span class="nt"&gt;-p&lt;/span&gt; 4317:18889 &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; aspire-dashboard mcr.microsoft.com/dotnet/nightly/aspire-dashboard:8.0.0-preview.4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Este comando creará y ejecutará un contenedor Docker llamado &lt;code&gt;aspire-dashboard&lt;/code&gt;, que aloja el Dashboard de Aspire. El Dashboard estará disponible en el puerto &lt;code&gt;18888&lt;/code&gt;, mientras que el puerto para el OTLP será el &lt;code&gt;4317&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Una vez que el contenedor esté en ejecución, puedes acceder al Dashboard navegando a &lt;code&gt;http://localhost:18888/&lt;/code&gt; en tu navegador web. Ten en cuenta que inicialmente el Dashboard no mostrará datos hasta que una aplicación comience a enviar información a través del protocolo OTLP.&lt;/p&gt;

&lt;p&gt;El Dashboard de Aspire es una herramienta versátil que puede utilizarse con cualquier aplicación que exporte sus datos utilizando el protocolo OTLP. Esto significa que no importa la plataforma en la que estés desarrollando (ya sea .NET, Java, Python, Go, etc.), siempre y cuando puedas configurar tu aplicación para enviar datos mediante OTLP, podrás visualizar y analizar esa información en el Dashboard de Aspire.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Nota 💡: Como costumbre, el código fuente: &lt;a href="https://github.com/isaacOjeda/DevToPosts/tree/main/AspireDashboard/OpenTelemetryExample" rel="noopener noreferrer"&gt;DevToPosts/AspireDashboard/OpenTelemetryExample · isaacOjeda/DevToPosts (github.com)&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Ejemplo de Aplicación Web
&lt;/h3&gt;

&lt;p&gt;En este ejemplo, crearemos una aplicación web utilizando Blazor Server Side Rendering, utilizando la configuración por defecto que proporciona ASP.NET Core, incluyendo páginas de ejemplo y demás. Luego, agregaremos los siguientes paquetes necesarios para integrar OpenTelemetry:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;PackageReference&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"OpenTelemetry.Exporter.OpenTelemetryProtocol"&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="na"&gt;Version=&lt;/span&gt;&lt;span class="s"&gt;"1.7.0"&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt; &lt;span class="nt"&gt;&amp;lt;PackageReference&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"OpenTelemetry.Extensions.Hosting"&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="na"&gt;Version=&lt;/span&gt;&lt;span class="s"&gt;"1.7.0"&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt; &lt;span class="nt"&gt;&amp;lt;PackageReference&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"OpenTelemetry.Instrumentation.AspNetCore"&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="na"&gt;Version=&lt;/span&gt;&lt;span class="s"&gt;"1.7.1"&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt; &lt;span class="nt"&gt;&amp;lt;PackageReference&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"OpenTelemetry.Instrumentation.Http"&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="na"&gt;Version=&lt;/span&gt;&lt;span class="s"&gt;"1.7.1"&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt; &lt;span class="nt"&gt;&amp;lt;PackageReference&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"OpenTelemetry.Instrumentation.Runtime"&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="na"&gt;Version=&lt;/span&gt;&lt;span class="s"&gt;"1.7.0"&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Es importante destacar que todo lo que exploraremos en este artículo es aplicable a cualquier tipo de aplicación desarrollada en ASP.NET Core.&lt;/p&gt;

&lt;p&gt;Si deseas comprender primero cómo funciona OpenTelemetry, ya he escrito un artículo al respecto disponible en &lt;a href="https://dev.to/isaacojeda/aspnet-core-monitoreo-con-opentelemetry-y-grafana-57m9"&gt;este enlace&lt;/a&gt;. Además, su &lt;a href="https://opentelemetry.io/docs/languages/net/" rel="noopener noreferrer"&gt;documentación oficial&lt;/a&gt; es bastante detallada y útil para obtener una comprensión más profunda.&lt;/p&gt;

&lt;h3&gt;
  
  
  Consumiendo una API externa
&lt;/h3&gt;

&lt;p&gt;Para ilustrar un ejemplo claro de traces, vamos a consumir una API pública y gratuita de Pokémon (¡yeei!).&lt;/p&gt;

&lt;p&gt;El procedimiento es simple: crearemos un servicio llamado &lt;code&gt;PokemonService&lt;/code&gt; para consultar todos los Pokémon disponibles utilizando paginación. Luego, utilizaremos este servicio para mostrar los datos consultados en un componente de Blazor.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="nn"&gt;System.Diagnostics&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;namespace&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="nn"&gt;OpenTelemetryExample&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="nc"&gt;PokemonService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ILogger&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;PokemonService&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;HttpClient&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt;    &lt;/span&gt;&lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;ActivitySourceName&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="s"&gt;"DownloadPokemon"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="err"&gt;    &lt;/span&gt;&lt;span class="k"&gt;private&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="k"&gt;readonly&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;ActivitySource&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;ActivitySource&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ActivitySourceName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="err"&gt;    &lt;/span&gt;&lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;PokemonList&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="nf"&gt;GetPokemonAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;pageSize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;currentPage&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="err"&gt;    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt;        &lt;/span&gt;&lt;span class="k"&gt;using&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="nn"&gt;var&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;activity&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;ActivitySource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;StartActivity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"GetPokemon"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;ActivityKind&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Producer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="err"&gt;        &lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="s"&gt;$"https://pokeapi.co/api/v2/pokemon?limit=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;pageSize&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;&amp;amp;offset=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;currentPage&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="p"&gt;*&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;pageSize&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="err"&gt;        &lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;list&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetFromJsonAsync&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;PokemonList&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="err"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;list&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="p"&gt;??&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;PokemonList&lt;/span&gt;
&lt;span class="err"&gt;        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt;            &lt;/span&gt;&lt;span class="n"&gt;Next&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="err"&gt;            &lt;/span&gt;&lt;span class="n"&gt;Previous&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="err"&gt;            &lt;/span&gt;&lt;span class="n"&gt;Results&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="err"&gt;        &lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="err"&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;public&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Pokemon&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt;    &lt;/span&gt;&lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;required&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="k"&gt;init&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="err"&gt;    &lt;/span&gt;&lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;required&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;Url&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="k"&gt;init&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="err"&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;public&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="nc"&gt;PokemonList&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt;    &lt;/span&gt;&lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="k"&gt;init&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="err"&gt;    &lt;/span&gt;&lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;required&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;Next&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="k"&gt;init&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="err"&gt;    &lt;/span&gt;&lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;required&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;Previous&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="k"&gt;init&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="err"&gt;    &lt;/span&gt;&lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;required&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Pokemon&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;Results&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="k"&gt;init&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="err"&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 clase &lt;code&gt;ActivitySource&lt;/code&gt; se utiliza para generar actividades de traces, lo que nos permite registrar eventos relacionados con la descarga de datos de la API de Pokémon. Estos traces pueden ser útiles para entender el rendimiento de la aplicación y depurar posibles problemas.&lt;/p&gt;

&lt;p&gt;Dentro del método &lt;code&gt;GetPokemonAsync&lt;/code&gt;, iniciamos una actividad del trace llamada "GetPokemon" utilizando &lt;code&gt;ActivitySource.StartActivity&lt;/code&gt;. Esto nos permite registrar el inicio y la finalización de la operación de obtención de datos. Luego, hacemos una solicitud HTTP a la API de Pokémon para obtener una lista de Pokémon, y devolvemos los resultados obtenidos.&lt;/p&gt;

&lt;p&gt;Te invito a revisar el código fuente para explorar el componente de Blazor que utiliza el servicio &lt;code&gt;PokemonService&lt;/code&gt;. Sin embargo, por cuestiones de relevancia para este post, he omitido incluirlo directamente.&lt;/p&gt;

&lt;p&gt;Es importante destacar que este ejemplo es solo una muestra y puedes implementar cualquier funcionalidad para probar. Incluso un Endpoint de Minimal API podría ser suficiente para demostrar el funcionamiento del sistema. En este caso particular, el objetivo era demostrar la consulta a un servicio externo y cómo integrarlo en una aplicación Blazor.&lt;/p&gt;

&lt;h3&gt;
  
  
  Program.cs
&lt;/h3&gt;

&lt;p&gt;Ahora que hemos configurado el servicio y la vista de ejemplo, el siguiente paso es ajustar el programa principal para configurar OpenTelemetry y todos los exportadores necesarios (de logs, métricas y traces).&lt;/p&gt;

&lt;p&gt;Es importante destacar que en este punto no estamos limitados a utilizar exclusivamente Aspire. Dado que todo el sistema es agnóstico en cuanto a la implementación, podríamos optar por utilizar servicios compatibles con el Protocolo OpenTelemetry, como Application Insights o Datadog, según nuestras necesidades y preferencias.&lt;/p&gt;

&lt;p&gt;El enfoque que seguiremos es utilizar Aspire en modo desarrollo y, en producción, cualquier opción que esté lista para producción. Esta flexibilidad es una de las características más destacadas, ya que nos permite adaptarnos a diferentes entornos sin estar ligados a una sola solución.&lt;/p&gt;

&lt;p&gt;A continuación, presentamos cómo quedará configurado el archivo &lt;code&gt;Program.cs&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;WebApplication&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nf"&gt;ConfigureOpenTelemetry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddHttpClient&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;PokemonService&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddRazorComponents&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="err"&gt;    &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddInteractiveServerComponents&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Environment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IsDevelopment&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt;    &lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseExceptionHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/Error"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;createScopeForErrors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="err"&gt;    &lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseHsts&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseHttpsRedirection&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseStaticFiles&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseAntiforgery&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MapRazorComponents&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;App&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
&lt;span class="err"&gt;    &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddInteractiveServerRenderMode&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="n"&gt;app&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;El método &lt;code&gt;ConfigureOpenTelemetry&lt;/code&gt; es el siguiente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="nf"&gt;ConfigureOpenTelemetry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IHostApplicationBuilder&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt;    &lt;/span&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddOpenTelemetry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="err"&gt;    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt;        &lt;/span&gt;&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IncludeFormattedMessage&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="err"&gt;        &lt;/span&gt;&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IncludeScopes&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="err"&gt;    &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="err"&gt;    &lt;/span&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddOpenTelemetry&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="err"&gt;        &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ConfigureResource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"OpenTelemetryExample"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="err"&gt;        &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithMetrics&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;metrics&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="err"&gt;        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt;            &lt;/span&gt;&lt;span class="n"&gt;metrics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddHttpClientInstrumentation&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="err"&gt;                &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddRuntimeInstrumentation&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="err"&gt;        &lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="err"&gt;        &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithTracing&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tracing&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="err"&gt;        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt;            &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Environment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IsDevelopment&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="err"&gt;            &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt;                &lt;/span&gt;&lt;span class="c1"&gt;// We want to view all traces in development&lt;/span&gt;
&lt;span class="err"&gt;                &lt;/span&gt;&lt;span class="n"&gt;tracing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetSampler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="nf"&gt;AlwaysOnSampler&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="err"&gt;            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="err"&gt;            &lt;/span&gt;&lt;span class="n"&gt;tracing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddAspNetCoreInstrumentation&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="err"&gt;            &lt;/span&gt;&lt;span class="n"&gt;tracing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddHttpClientInstrumentation&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="err"&gt;            &lt;/span&gt;&lt;span class="n"&gt;tracing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PokemonService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ActivitySourceName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="err"&gt;        &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="err"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Use the OTLP exporter if the endpoint is configured.&lt;/span&gt;
&lt;span class="err"&gt;    &lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;useOtlpExporter&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IsNullOrWhiteSpace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Configuration&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"OTEL_EXPORTER_OTLP_ENDPOINT"&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="err"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;useOtlpExporter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="err"&gt;    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt;        &lt;/span&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Configure&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;OpenTelemetryLoggerOptions&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddOtlpExporter&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="err"&gt;        &lt;/span&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ConfigureOpenTelemetryMeterProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;metrics&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;metrics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddOtlpExporter&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="err"&gt;        &lt;/span&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ConfigureOpenTelemetryTracerProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tracing&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;tracing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddOtlpExporter&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="err"&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 método, definimos cómo queremos gestionar los logs, traces y metrics utilizando las bibliotecas que hemos agregado previamente.&lt;/p&gt;

&lt;p&gt;En primer lugar, configuramos los registros (logs) para incluir el mensaje formateado y los ámbitos. Esto nos permite capturar información detallada sobre el estado de la aplicación y los eventos ocurridos durante su ejecución.&lt;/p&gt;

&lt;p&gt;Luego, configuramos el sistema de métricas para recopilar datos sobre el rendimiento de la aplicación, como el tiempo de respuesta de las solicitudes HTTP y el rendimiento del tiempo de ejecución. Esto nos ayuda a monitorear y diagnosticar el comportamiento de nuestra aplicación en tiempo real.&lt;/p&gt;

&lt;p&gt;Para los traces, establecemos un muestreador que determina qué traces se deben capturar y enviar. En entornos de desarrollo, configuramos un muestreador que captura todas los traces para facilitar la depuración y el análisis. Además, agregamos instrumentación específica para HttpClient y para nuestra clase &lt;code&gt;PokemonService&lt;/code&gt;, lo que nos permite rastrear las solicitudes HTTP y las operaciones realizadas en nuestro servicio.&lt;/p&gt;

&lt;p&gt;Finalmente, configuramos los exportadores OTLP (OpenTelemetry Protocol) para enviar los datos recopilados a un destino específico. Si se ha configurado un endpoint OTLP, utilizamos los exportadores OTLP para enviar registros, métricas y traces. Esto nos proporciona la flexibilidad para dirigir los datos de OpenTelemetry a diferentes sistemas de análisis y monitoreo, como Prometheus, Zipkin o Loki, siguiendo el estándar OTLP.&lt;/p&gt;

&lt;p&gt;En resumen, el método &lt;code&gt;ConfigureOpenTelemetry&lt;/code&gt; establece la base para la instrumentación y observabilidad de nuestra aplicación, permitiéndonos recopilar datos valiosos sobre su rendimiento y comportamiento en tiempo real, y enviarlos a destinos de análisis externos según nuestras necesidades y preferencias.&lt;/p&gt;

&lt;h3&gt;
  
  
  Probando la Solución
&lt;/h3&gt;

&lt;p&gt;Ya podemos correr la aplicación de Blazor y Aspire juntos, y empezaremos a ver los logs de la aplicación:&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%2Fk65w50xd4jlafiw2k23w.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%2Fk65w50xd4jlafiw2k23w.png" alt="Image description" width="800" height="395"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Nota 💡: Si revisas el código fuente, yo estoy utilizando &lt;code&gt;docker-compose&lt;/code&gt; por medio de visual studio, es fácil de usar, pero por eso salen por default esos warnings y errores en el startup.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Utilicemos la aplicación para comenzar a generar métricas:&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%2F3v8cdpd8x6hb6c5yl9bl.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%2F3v8cdpd8x6hb6c5yl9bl.png" alt="Image description" width="800" height="569"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Veremos la consulta de los Pokemons, pero en Aspire nos vamos a los Traces y veremos en efecto el flujo de lo que acaba de suceder:&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%2F5yffvgoouqqwrdhb0kf3.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%2F5yffvgoouqqwrdhb0kf3.png" alt="Image description" width="800" height="394"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ejemplo de las métricas:&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%2Fo49j4mojv3ab97xvsvrp.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%2Fo49j4mojv3ab97xvsvrp.png" alt="Image description" width="800" height="394"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Aquí otro ejemplo de como se vería con un endpoints de Minimal API:&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%2Fkwjffc7rwtqqae1i0t3c.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%2Fkwjffc7rwtqqae1i0t3c.png" alt="Image description" width="800" height="402"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;En este artículo, hemos explorado cómo integrar OpenTelemetry en una aplicación ASP.NET Core utilizando Blazor, destacando el papel central de Aspire en nuestro sistema de observabilidad. Utilizando un enfoque práctico, demostramos cómo configurar OpenTelemetry, consumir una API externa y visualizar los datos recopilados en el potente Dashboard de Aspire. Esta plataforma nos ofrece una interfaz intuitiva para monitorear y analizar datos de telemetría en tiempo real, lo que nos permite identificar y resolver problemas de manera proactiva. Con la combinación de OpenTelemetry y Aspire, podemos mejorar significativamente la observabilidad de nuestras aplicaciones, optimizar su rendimiento y proporcionar una experiencia excepcional a los usuarios finales.&lt;/p&gt;

&lt;h2&gt;
  
  
  Referencias
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/asimmon/net-aspire-dashboard-is-the-best-tool-to-visualize-your-opentelemetry-data-during-local-development-9dl"&gt;.NET Aspire dashboard is the best tool to visualize your OpenTelemetry data during local development - DEV Community&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/samples/dotnet/aspire-samples/aspire-standalone-dashboard/" rel="noopener noreferrer"&gt;Standalone Aspire dashboard sample app - Code Samples | Microsoft Learn&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://opentelemetry.io/docs/languages/net/getting-started/" rel="noopener noreferrer"&gt;Getting Started | OpenTelemetry&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>opentelemetry</category>
      <category>dotnet</category>
      <category>aspnetcore</category>
      <category>csharp</category>
    </item>
  </channel>
</rss>
