<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" xml:lang="es-ES"><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://franiglesias.github.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://franiglesias.github.io/" rel="alternate" type="text/html" hreflang="es-ES" /><updated>2026-03-01T08:52:40+00:00</updated><id>https://franiglesias.github.io/feed.xml</id><title type="html">The talking bit</title><subtitle>Personal learning journal about programming, maybe more.</subtitle><entry><title type="html">Rename, para entender mejor</title><link href="https://franiglesias.github.io/rename/" rel="alternate" type="text/html" title="Rename, para entender mejor" /><published>2026-02-28T00:00:00+00:00</published><updated>2026-02-28T00:00:00+00:00</updated><id>https://franiglesias.github.io/rename</id><content type="html" xml:base="https://franiglesias.github.io/rename/"><![CDATA[<p>El código debería expresar su intención. ¿Qué hacer cuando no es así?</p>

<h2 id="código-que-no-dice-lo-que-hace">Código que no dice lo que hace</h2>

<p>Considera el siguiente ejemplo:</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">static</span> <span class="kd">class</span> <span class="nc">C</span> <span class="o">{</span>
    <span class="nc">String</span> <span class="n">u</span><span class="o">;</span>
    <span class="nc">String</span> <span class="n">p</span><span class="o">;</span>
    <span class="nc">String</span> <span class="n">s</span><span class="o">;</span>
    <span class="nc">String</span> <span class="n">e</span><span class="o">;</span>

    <span class="kd">public</span> <span class="nf">C</span><span class="o">(</span><span class="nc">String</span> <span class="n">u</span><span class="o">,</span> <span class="nc">String</span> <span class="n">p</span><span class="o">,</span> <span class="nc">String</span> <span class="n">s</span><span class="o">,</span> <span class="nc">String</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
        <span class="k">this</span><span class="o">.</span><span class="na">u</span> <span class="o">=</span> <span class="n">u</span><span class="o">;</span>
        <span class="k">this</span><span class="o">.</span><span class="na">p</span> <span class="o">=</span> <span class="n">p</span><span class="o">;</span>
        <span class="k">this</span><span class="o">.</span><span class="na">s</span> <span class="o">=</span> <span class="n">s</span><span class="o">;</span>
        <span class="k">this</span><span class="o">.</span><span class="na">e</span> <span class="o">=</span> <span class="n">e</span><span class="o">;</span>
    <span class="o">}</span>

    <span class="kd">public</span> <span class="nc">String</span> <span class="nf">cx</span><span class="o">()</span> <span class="o">{</span>
        <span class="k">return</span> <span class="nc">String</span><span class="o">.</span><span class="na">format</span><span class="o">(</span><span class="s">"%s:%s@%s/%s"</span><span class="o">,</span> <span class="n">u</span><span class="o">,</span> <span class="n">p</span><span class="o">,</span> <span class="n">s</span><span class="o">,</span> <span class="n">e</span><span class="o">);</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<p>¿Qué hace esta clase? Casi nada en ella nos da una pista de cuál es su intención. Puede que la plantilla de formato que tenemos nos resulte familiar y nos indique por donde van los tiros, pero no deja de ser una apuesta.</p>

<p>Para entender el código siempre nos hará falta contexto del dominio en el que opera. Podemos obtener este contexto de muchas formas. Hay un contexto implícito porque trabajamos en un proyecto determinado, dentro de un equipo de desarrollo en una empresa de cierto sector. Quizá estamos mirando este código desde la perspectiva de estar realizando una tarea específica. Esta información nos proporciona, muchas veces, suficiente contexto para entender la responsabilidad de este código.</p>

<p>Pero, ¿si no es suficiente?</p>

<p>Podemos recurrir al contexto de uso:</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">NoAbbreviations</span> <span class="o">{</span>

    <span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="nc">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span>
        <span class="no">C</span> <span class="n">c</span> <span class="o">=</span> <span class="k">new</span> <span class="no">C</span><span class="o">(</span><span class="s">"admin"</span><span class="o">,</span> <span class="s">"secret"</span><span class="o">,</span> <span class="s">"localhost"</span><span class="o">,</span> <span class="s">"prod"</span><span class="o">);</span>
        <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"Connection: "</span> <span class="o">+</span> <span class="n">c</span><span class="o">.</span><span class="na">cx</span><span class="o">());</span>
    <span class="o">}</span>

    <span class="kd">public</span> <span class="kd">static</span> <span class="kd">class</span> <span class="nc">C</span> <span class="o">{</span>
        <span class="nc">String</span> <span class="n">u</span><span class="o">;</span>
        <span class="nc">String</span> <span class="n">p</span><span class="o">;</span>
        <span class="nc">String</span> <span class="n">s</span><span class="o">;</span>
        <span class="nc">String</span> <span class="n">e</span><span class="o">;</span>

        <span class="kd">public</span> <span class="nf">C</span><span class="o">(</span><span class="nc">String</span> <span class="n">u</span><span class="o">,</span> <span class="nc">String</span> <span class="n">p</span><span class="o">,</span> <span class="nc">String</span> <span class="n">s</span><span class="o">,</span> <span class="nc">String</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
            <span class="k">this</span><span class="o">.</span><span class="na">u</span> <span class="o">=</span> <span class="n">u</span><span class="o">;</span>
            <span class="k">this</span><span class="o">.</span><span class="na">p</span> <span class="o">=</span> <span class="n">p</span><span class="o">;</span>
            <span class="k">this</span><span class="o">.</span><span class="na">s</span> <span class="o">=</span> <span class="n">s</span><span class="o">;</span>
            <span class="k">this</span><span class="o">.</span><span class="na">e</span> <span class="o">=</span> <span class="n">e</span><span class="o">;</span>
        <span class="o">}</span>

        <span class="kd">public</span> <span class="nc">String</span> <span class="nf">cx</span><span class="o">()</span> <span class="o">{</span>
            <span class="k">return</span> <span class="nc">String</span><span class="o">.</span><span class="na">format</span><span class="o">(</span><span class="s">"%s:%s@%s/%s"</span><span class="o">,</span> <span class="n">u</span><span class="o">,</span> <span class="n">p</span><span class="o">,</span> <span class="n">s</span><span class="o">,</span> <span class="n">e</span><span class="o">);</span>
        <span class="o">}</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<p>Bien, sigue siendo críptico, pero ahora tenemos señales que nos dicen que <code class="language-plaintext highlighter-rouge">C</code> es posiblemente una clase encargada de gestionar una configuración de conexión a algún servicio. Todavía no sabemos cuál, algo que quizá podríamos averiguar adquiriendo más contexto.</p>

<p>Por lo mismo, parece claro que <code class="language-plaintext highlighter-rouge">u</code> indica un nombre de usuario, <code class="language-plaintext highlighter-rouge">p</code> una contraseña, <code class="language-plaintext highlighter-rouge">s</code> es el nombre de un servidor o servicio y <code class="language-plaintext highlighter-rouge">e</code> un entorno. El método <code class="language-plaintext highlighter-rouge">cx</code> nos da la cadena de conexión.</p>

<h2 id="el-problema-de-los-nombres-crípticos">El problema de los nombres crípticos</h2>

<p>Todo este proceso para averiguar qué significa cada símbolo requiere tiempo de investigación, lo que aumenta el coste de introducir cambios y encarece el desarrollo.</p>

<p>Además, una vez que averiguamos lo que significa cada cosa y sus relaciones, tenemos que mantener ese conocimiento en la cabeza, con el riesgo de perderlo. Esto añade más coste.</p>

<p>Y, además, ¿cómo garantizamos que ese conocimiento se difunde en el equipo actual y futuro?</p>

<h2 id="rename-para-poner-conocimiento-en-el-código">Rename para poner conocimiento en el código</h2>

<p>La mejor forma de abordar este problema es trasladar el conocimiento al código. Por ejemplo, así:</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">static</span> <span class="kd">class</span> <span class="nc">C</span> <span class="o">{</span>
    <span class="cm">/* u: the username */</span>
    <span class="nc">String</span> <span class="n">u</span><span class="o">;</span>
    <span class="cm">/* p: password */</span>
    <span class="nc">String</span> <span class="n">p</span><span class="o">;</span>
    <span class="cm">/* s: server name */</span>
    <span class="nc">String</span> <span class="n">s</span><span class="o">;</span>
    <span class="cm">/* e: environment prod | staging | local | test */</span>
    <span class="nc">String</span> <span class="n">e</span><span class="o">;</span>

    <span class="kd">public</span> <span class="nf">C</span><span class="o">(</span><span class="nc">String</span> <span class="n">u</span><span class="o">,</span> <span class="nc">String</span> <span class="n">p</span><span class="o">,</span> <span class="nc">String</span> <span class="n">s</span><span class="o">,</span> <span class="nc">String</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
        <span class="k">this</span><span class="o">.</span><span class="na">u</span> <span class="o">=</span> <span class="n">u</span><span class="o">;</span>
        <span class="k">this</span><span class="o">.</span><span class="na">p</span> <span class="o">=</span> <span class="n">p</span><span class="o">;</span>
        <span class="k">this</span><span class="o">.</span><span class="na">s</span> <span class="o">=</span> <span class="n">s</span><span class="o">;</span>
        <span class="k">this</span><span class="o">.</span><span class="na">e</span> <span class="o">=</span> <span class="n">e</span><span class="o">;</span>
    <span class="o">}</span>

    <span class="cm">/** 
     * Returns the connection string populated with the configured values
     * */</span>
    <span class="kd">public</span> <span class="nc">String</span> <span class="nf">cx</span><span class="o">()</span> <span class="o">{</span>
        <span class="k">return</span> <span class="nc">String</span><span class="o">.</span><span class="na">format</span><span class="o">(</span><span class="s">"%s:%s@%s/%s"</span><span class="o">,</span> <span class="n">u</span><span class="o">,</span> <span class="n">p</span><span class="o">,</span> <span class="n">s</span><span class="o">,</span> <span class="n">e</span><span class="o">);</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<p>Añadir comentarios es una opción legítima, pero los comentarios tienen algunos riesgos. Nos obligan a prestarles atención y mantenerlos. Si no lo hacemos así, corremos el riesgo de que acaben estando desactualizados, contribuyendo a más confusión.</p>

<p>Lo mejor es que el propio código se autodocumente, indicando su intención:</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">static</span> <span class="kd">class</span> <span class="nc">ConnectionConfig</span> <span class="o">{</span>
    <span class="nc">String</span> <span class="n">username</span><span class="o">;</span>
    <span class="nc">String</span> <span class="n">password</span><span class="o">;</span>
    <span class="nc">String</span> <span class="n">server</span><span class="o">;</span>
    <span class="nc">String</span> <span class="n">environment</span><span class="o">;</span>

    <span class="kd">public</span> <span class="nf">ConnectionConfig</span><span class="o">(</span><span class="nc">String</span> <span class="n">username</span><span class="o">,</span>
                            <span class="nc">String</span> <span class="n">password</span><span class="o">,</span>
                            <span class="nc">String</span> <span class="n">server</span><span class="o">,</span>
                            <span class="nc">String</span> <span class="n">environment</span><span class="o">)</span> <span class="o">{</span>
        <span class="k">this</span><span class="o">.</span><span class="na">username</span> <span class="o">=</span> <span class="n">username</span><span class="o">;</span>
        <span class="k">this</span><span class="o">.</span><span class="na">password</span> <span class="o">=</span> <span class="n">password</span><span class="o">;</span>
        <span class="k">this</span><span class="o">.</span><span class="na">server</span> <span class="o">=</span> <span class="n">server</span><span class="o">;</span>
        <span class="k">this</span><span class="o">.</span><span class="na">environment</span> <span class="o">=</span> <span class="n">environment</span><span class="o">;</span>
    <span class="o">}</span>

    <span class="kd">public</span> <span class="nc">String</span> <span class="nf">connectionString</span><span class="o">()</span> <span class="o">{</span>
        <span class="k">return</span> <span class="nc">String</span><span class="o">.</span><span class="na">format</span><span class="o">(</span><span class="s">"%s:%s@%s/%s"</span><span class="o">,</span> <span class="n">username</span><span class="o">,</span> <span class="n">password</span><span class="o">,</span> <span class="n">server</span><span class="o">,</span> <span class="n">environment</span><span class="o">);</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<p>Al reflejar nuestro recién adquirido conocimiento en el propio código, evitamos la carga cognitiva de asociar identificadores arbitrarios con sus distintos significados. Podemos olvidarnos de esta clase una vez dejemos de trabajar en ella, porque cuando volvamos su intención sigue estando documentada y no tenemos que buscar más lejos para entenderla.</p>

<p>Y, lo más importante, si es otra persona la que tiene que trabajar con ella más adelante, le habremos ahorrado el esfuerzo de investigarlo. O el tiempo de otra persona que tenga que explicárselo.</p>

<h2 id="seguridad-de-rename">Seguridad de <em>rename</em></h2>

<p><em>Rename</em> es, en los buenos entornos de desarrollo, un refactor automático, con un riesgo bastante bajo y que podemos aplicar con seguridad. Al menos dentro del <em>scope</em> de una clase, método o función y, posiblemente, paquete.</p>

<p>Si el ámbito es más general, dependiendo del lenguaje, podría haber algo de incertidumbre. Si la clase o función renombrada tiene muchos usos, son cambios que afectan a muchos archivos.</p>

<p>Lo mejor, como siempre, es separar la aplicación del refactor de otros cambios, de modo que sea el único.</p>

<p>Para ello, hacemos <em>commit</em> de los cambios que podamos tener pendientes y empezamos con un grupo de cambios vacío. Aplicamos el <em>rename</em> y analizamos el resultado para ver si es lo que esperábamos. En caso de problemas no tenemos más que revertir o arreglarlo manualmente.</p>

<p>Una vez confirmado que el código ha cambiado de la forma deseada, hacemos un nuevo <em>commit</em>.</p>

<h2 id="en-resumen">En resumen</h2>

<p>Poner buenos nombres en el código, o aplicar el refactor <em>rename</em> cuando nos encontramos con nombres malos, es una pequeña inversión de tiempo con un gran retorno futuro. El código con buenos nombres tiene un menor coste de mantenimiento.</p>

<p>Y no solo eso, poner buenos nombres es un acto de empatía hacia nuestro equipo hoy y en el futuro.</p>]]></content><author><name></name></author><category term="articles" /><category term="refactoring" /><category term="good-practices" /><summary type="html"><![CDATA[El código debería expresar su intención. ¿Qué hacer cuando no es así?]]></summary></entry><entry><title type="html">Samman Coaching</title><link href="https://franiglesias.github.io/samman-coaching/" rel="alternate" type="text/html" title="Samman Coaching" /><published>2026-02-04T00:00:00+00:00</published><updated>2026-02-04T00:00:00+00:00</updated><id>https://franiglesias.github.io/samman-coaching</id><content type="html" xml:base="https://franiglesias.github.io/samman-coaching/"><![CDATA[<p>Buscando katas para un curso, encontré esta web de la Samman Technical Coaching Society. Es un recurso muy valioso para cualquier persona que se dedique de una forma u otra a la formación técnica, ya sea dentro de equipos de empresas, en coding-dojos, etc.</p>

<blockquote>
  <p>Technical coaches work with software development teams to help them adopt better coding practices. The Samman method
is a concrete approach that many technical coaches use. This website is designed to help people who do technical
coaching and is maintained by the Samman Technical Coaching Society. This site shares materials you can use for Code
Katas and Learning Hours, as well as many supporting materials. We also have official Training courses.</p>
</blockquote>

<p><a href="https://sammancoaching.org">Samman Coaching</a></p>]]></content><author><name></name></author><category term="articles" /><category term="tb-list" /><category term="refactoring" /><category term="tdd" /><category term="good-practices" /><summary type="html"><![CDATA[Buscando katas para un curso, encontré esta web de la Samman Technical Coaching Society. Es un recurso muy valioso para cualquier persona que se dedique de una forma u otra a la formación técnica, ya sea dentro de equipos de empresas, en coding-dojos, etc.]]></summary></entry><entry><title type="html">Review 2025</title><link href="https://franiglesias.github.io/review-2025/" rel="alternate" type="text/html" title="Review 2025" /><published>2025-12-14T00:00:00+00:00</published><updated>2025-12-14T00:00:00+00:00</updated><id>https://franiglesias.github.io/review-2025</id><content type="html" xml:base="https://franiglesias.github.io/review-2025/"><![CDATA[<p>Después de casi una década de contenido, ¿Seguirá <em>The Talking Bit</em> en 2026?</p>

<h2 id="nueve-años-de-turra">Nueve años de turra</h2>

<p>Resulta que el blog acaba de cumplir nueve años de existencia. 2025 es el segundo año más flojo en cuanto a producción, tras 2021. No cuento 2016 porque solo hay cuatro posts de ese año al haber empezado en noviembre a publicar.</p>

<p>Esta es la cantidad de posts por año desde 2016:</p>

<table>
  <thead>
    <tr>
      <th style="text-align: center">Year</th>
      <th style="text-align: right">Posts</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: center">2016</td>
      <td style="text-align: right">4</td>
    </tr>
    <tr>
      <td style="text-align: center">2017</td>
      <td style="text-align: right">43</td>
    </tr>
    <tr>
      <td style="text-align: center">2018</td>
      <td style="text-align: right">51</td>
    </tr>
    <tr>
      <td style="text-align: center">2019</td>
      <td style="text-align: right">61</td>
    </tr>
    <tr>
      <td style="text-align: center">2020</td>
      <td style="text-align: right">37</td>
    </tr>
    <tr>
      <td style="text-align: center">2021</td>
      <td style="text-align: right">19</td>
    </tr>
    <tr>
      <td style="text-align: center">2022</td>
      <td style="text-align: right">35</td>
    </tr>
    <tr>
      <td style="text-align: center">2023</td>
      <td style="text-align: right">36</td>
    </tr>
    <tr>
      <td style="text-align: center">2024</td>
      <td style="text-align: right">43</td>
    </tr>
    <tr>
      <td style="text-align: center">2025</td>
      <td style="text-align: right">29</td>
    </tr>
  </tbody>
</table>

<p>La verdad es que hay algunas cifras bastante altas, porque en algunos casos estamos hablando de una media de más de tres artículos por mes, que se dice pronto.</p>

<p>2019 fue un año atípico porque me propuse el reto del <a href="/tag/blogtober19/">Blogtober</a>, escribiendo un post al día durante un mes, lo que distorsiona un poco los datos. La cifra de actividad <em>real</em> sería de unos 30 artículos, aunque en la serie hay algunos que tendrían cabida en el blog <em>normal</em>.</p>

<p>2021, año post-pandemia, es el que menos artículos tiene. Creo recordar que fue el año del libro de <a href="https://leanpub.com/tddcourse">Aprende Test Driven Development</a>, lo cual explicaría la menor dedicación al blog.</p>

<p>En esta gráfica se puede apreciar que hay una cierta regularidad de publicaciones, con una ligera tendencia a disminuir.</p>

<pre><code class="language-mermaid">---
config:
  theme: 'dark'
  themeVariables:
    background: 'transparent'
---
xychart-beta
    title "Posts by Year"
    x-axis ["2016", "2017", "2018", "2019", "2020", "2021", "2022", "2023", "2024", "2025"]
    y-axis "Number of Posts" 0 --&gt; 70
    bar [4, 43, 51, 61, 37, 19, 35, 36, 43, 27]
</code></pre>

<p>Y, en esta otra gráfica, tenemos la distribución de posts por meses durante este año.</p>

<pre><code class="language-mermaid">---
config:
  theme: 'dark'
  themeVariables:
    background: 'transparent'
---
xychart-beta
    title "2025 Posts by Month"
    x-axis ["01", "02", "03", "04","05", "06", "07","08", "09", "10", "11", "12"]
    y-axis "Number of Posts" 0 --&gt; 8
    bar [ 1, 1, 4, 0, 7, 7, 0, 0, 1, 2, 4, 1]
</code></pre>

<p>Me ha sorprendido contar más de veinte post este año, pues tenía la impresión de que serían menos. Una cifra que no aparece aquí es que tengo varios en borrador que no he llegado a terminar, pero alguno es casi seguro que no llevará a nada.</p>

<p>Algo característico de 2025 ha sido abordar temas con artículos bastante largos, como por ejemplo la serie sobre <a href="/hexagonal-tdd-1/">arquitectura hexagonal y TDD</a>.</p>

<p>También he dedicado un buen tiempo a un rediseño del blog, que falta le hacía, y refrescar así un poco mis habilidades para el front-end, que tampoco es que sean muchas.</p>

<p>Por cierto, que los gráficos de este artículo están hechos con <a href="https://mermaid-js.github.io/mermaid/#/">Mermaid</a>, algo que he empezado a introducir recientemente en los posts.</p>

<h2 id="canal-de-vídeos-parado">Canal de vídeos parado</h2>

<p>El último vídeo publicado en Youtube es de marzo de 2025. No tengo ninguno más en perspectiva y no espero que haya actividad en 2026.</p>

<p>Soy consciente de que el tipo de vídeos que hacía no es el mejor para destacar en Youtube, pero me consta que algunas personas los encuentran útiles para su aprendizaje. Obviamente, no es mi intención convertirme en <em>youtuber</em>,sino tener una base de material que puede ser útil para enseñar.</p>

<p>Hacer un vídeo tiene un gran coste. Por una parte, es más fácil que escribir un artículo, pero por otra, necesitas practicarlo mucho para que salga como quieres y que no resulte muy confuso. Y, personalmente, necesito cierto ambiente a mi alrededor para poder hacerlo.</p>

<p>Por otro lado, la cantidad ingente de publicidad que introduce Youtube últimamente, hace que me plantee si quiero seguir teniendo los vídeos publicados ahí.</p>

<p>De vez en cuando le doy vueltas a la posibilidad de contratar un hosting para quitar todo el contenido generado de las plataformas de terceros y así evitar anuncios y posibles bloqueos de acceso al mismo, etc.</p>

<p>Pero reconozco que me da pereza ponerme a ello.</p>

<h2 id="sin-libros-este-año">Sin libros este año</h2>

<p>Gracias a la <a href="https://pulpocon.es/">PulpoCon</a> tenía un cierto incentivo para intentar sacar un libro cada año, pero en 2025 no lo hice y no tengo un tema en perspectiva que me resulte lo suficientemente interesante como para ponerme a escribir. Así que tampoco espero novedades por esa parte.</p>

<p>En un momento dado moví la publicación de alguno de los libros a Amazon, pero ha resultado un fracaso. Si bien la disponibilidad de los títulos impresos es bastante buena, la calidad de impresión me ha decepcionado mucho y las ventas tampoco han acompañado. No parece que estas temáticas despierten mucho interés tampoco.</p>

<p>Al principio quise poner los libros en papel porque algunas personas me lo habían pedido, pero no acabo de encontrar el tiempo ni el ánimo para hacerlo con todos y hacerlo bien.</p>

<h2 id="sin-redes-sociales">Sin redes sociales</h2>

<p>Hace ya un buen tiempo que dejé de participar en redes sociales, Twitter en particular, y la única de la que no me he borrado es LinkedIn, donde publico los posts del blog con un automatismo, ya que es mi principal via de comunicación con la mayor parte de mis contactos profesionales y me da acceso a las cada día más escasas ofertas de trabajo (apostaría a que mi edad me empieza a pasar factura).</p>

<p>No estar en estas conversaciones hace que sea más difícil saber si el contenido del blog genera algún tipo de interés y si se crea comunidad alrededor. Lo cierto es que, de vez en cuando, aparecen algunos comentarios en el blog, pero no es muy frecuente.</p>

<p>Aunque escribo principalmente para mi, siempre es agradable saber que lo publicado llega a más personas y que tiene algo de difusión.</p>

<p>No me planteo entrar a publicar a través de sistemas como Medium o Substack. Esto es por principios, ya que quiero que el contenido esté siempre abierto y no pretendo que sea monetizable.</p>

<p>Github pages no impone ninguna restricción que afecte a esos principios, pero sigue siendo una plataforma de terceros y, como mencioné más arriba, me planteo ocasionalmente buscar un hosting. Sin embargo, tampoco veo que merezca mucho la pena si no va a haber continuidad en el futuro.</p>

<h2 id="conclusiones-qué-nos-traerá-el-futuro">Conclusiones: ¿Qué nos traerá el futuro?</h2>

<p>Llevo un tiempo con una sensación de bastante cansancio y, francamente, no acabo de encontrar ni el tono ni temáticas que me impulsen a escribir como antes.</p>

<p>Por eso me ha sorprendido un poco haber superado los 25 artículos publicados en 2025, pues pensaba que serían bastante menos. Es verdad que tengo alguno en proceso que quizá salga pronto.</p>

<p>Es cierto que últimamente no me estoy encontrando temas en el trabajo que me lleven a darle vueltas en mi cabeza hasta plasmarlo en un post. Por otro lado, lo que me ronda últimamente tampoco me parece especialmente interesante como para publicar. Y a eso hay que añadir que la energía es menos y el tiempo disponible también.</p>

<p>Me he dado cuenta, y me preocupa un poco, que cada vez que intento explorar algún área de conocimiento (practicar front-end, desarrollo mobile, etc.) me siento como <em>obligado</em> a intentar convertirlo en un post. Esto es algo bastante estúpido y lo que consigo es que no haga ni una cosa ni otra por puro agotamiento.</p>

<p>Así que preveo que 2026 va a ser un año flojo para The Talking Bit y no me sorprendería que el blog acabe aparcado por un tiempo. He de decir que no tengo ningún plan o decisión al respecto, simplemente que no quiero fijar expectativas en un sentido u otro. Esto debería ayudarme a quitar un poco de presión sobre el tema.</p>

<p>Es posible que aún pueda publicar algún artículo sobre Code Smells, pues tengo alguno a medio terminar, pero tras un impulso inicial bastante fuerte mi ánimo ha decaído bastante.</p>

<p>En resumidas cuentas, ya veremos qué pasa.</p>

<p>¡Feliz año nuevo!</p>]]></content><author><name></name></author><category term="articles" /><category term="misc" /><summary type="html"><![CDATA[Después de casi una década de contenido, ¿Seguirá The Talking Bit en 2026?]]></summary></entry><entry><title type="html">Code Smells Catalog</title><link href="https://franiglesias.github.io/code-smells-catalog/" rel="alternate" type="text/html" title="Code Smells Catalog" /><published>2025-11-23T00:00:00+00:00</published><updated>2025-11-23T00:00:00+00:00</updated><id>https://franiglesias.github.io/code-smells-catalog</id><content type="html" xml:base="https://franiglesias.github.io/code-smells-catalog/"><![CDATA[<p>Un amplio catálogo compilado por Marcel Jerzyk. Los code smells aparecen categorizados con una interfaz que permite usar el catálogo de forma interactiva. Cada smell se ilustra con un ejemplo, explicando sus causas y otros smells relacionados. También explica los refactors que pueden ser aplicados para solucionarlos.</p>

<blockquote>
  <p>Code Smells—a concept not fully understood among programmers, crucial to the code quality, and yet unstandardized in the scientific literature.</p>
</blockquote>

<p><a href="https://www.researchgate.net/publication/369544761_Code_Smells_A_Comprehensive_Online_Catalog_and_Taxonomy">Este artículo explica la motivación y metodología de investigación para desarrollar el catálogo</a></p>

<p><a href="https://luzkan.github.io/smells/">Code Smells Catalog</a></p>]]></content><author><name></name></author><category term="articles" /><category term="tb-list" /><category term="software-design" /><summary type="html"><![CDATA[Un amplio catálogo compilado por Marcel Jerzyk. Los code smells aparecen categorizados con una interfaz que permite usar el catálogo de forma interactiva. Cada smell se ilustra con un ejemplo, explicando sus causas y otros smells relacionados. También explica los refactors que pueden ser aplicados para solucionarlos.]]></summary></entry><entry><title type="html">Primitive Obsession</title><link href="https://franiglesias.github.io/primitive-obsession/" rel="alternate" type="text/html" title="Primitive Obsession" /><published>2025-11-16T00:00:00+00:00</published><updated>2025-11-16T00:00:00+00:00</updated><id>https://franiglesias.github.io/primitive-obsession</id><content type="html" xml:base="https://franiglesias.github.io/primitive-obsession/"><![CDATA[<p><em>Primitive Obsession</em> es el último de los <em>bloaters</em>, una categoría de <em>code smells</em> que se caracterizan por hacer que nuestro código sea grande y complejo, introduciendo muchas oportunidades para la aparición de bugs y comportamiento inconsistente.</p>

<h2 id="definición">Definición</h2>

<p>Caemos en <em>primitive obsession</em> cuando los conceptos de dominio se modelan con primitivos, lo que obliga a esparcir reglas de validación, formato, y todo tipo de comportamiento, por todo el código. La consecuencia es que el código se vuelve difícil de seguir y mantener, con niveles de abstracción mezclados y código repetido por todas partes.</p>

<p><em>Primitive Obsession</em> y <a href="/data-clump/"><em>Data Clump</em></a> son muy similares y seguramente aparecerán juntos muchas veces, como es el caso del siguiente ejemplo. Por señalar una diferencia, <em>primitive obsession</em> incide más en valores primitivos que intentan representar conceptos de dominio con validaciones más allá de las genéricas, mientras que <em>data clump</em> apunta a conjuntos de valores que representan un concepto compuesto único.</p>

<h2 id="ejemplo">Ejemplo</h2>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nx">Order</span> <span class="p">{</span>
  <span class="kd">constructor</span><span class="p">(</span>
    <span class="k">private</span> <span class="nx">customerName</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
    <span class="k">private</span> <span class="nx">customerEmail</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
    <span class="k">private</span> <span class="nx">address</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
    <span class="k">private</span> <span class="nx">totalAmount</span><span class="p">:</span> <span class="kr">number</span><span class="p">,</span>
    <span class="k">private</span> <span class="nx">currency</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
  <span class="p">)</span> <span class="p">{</span>
  <span class="p">}</span>

  <span class="nx">sendInvoice</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="k">this</span><span class="p">.</span><span class="nx">customerEmail</span><span class="p">.</span><span class="nx">includes</span><span class="p">(</span><span class="dl">'</span><span class="s1">@</span><span class="dl">'</span><span class="p">))</span> <span class="p">{</span>
      <span class="k">throw</span> <span class="k">new</span> <span class="nb">Error</span><span class="p">(</span><span class="dl">'</span><span class="s1">Email inválido</span><span class="dl">'</span><span class="p">)</span>
    <span class="p">}</span>
    <span class="k">if</span> <span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">totalAmount</span> <span class="o">&lt;=</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
      <span class="k">throw</span> <span class="k">new</span> <span class="nb">Error</span><span class="p">(</span><span class="dl">'</span><span class="s1">El monto debe ser mayor que cero</span><span class="dl">'</span><span class="p">)</span>
    <span class="p">}</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`Factura enviada a </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">customerEmail</span><span class="p">}</span><span class="s2"> por </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">totalAmount</span><span class="p">}</span><span class="s2"> </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">currency</span><span class="p">}</span><span class="s2">`</span><span class="p">)</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="ejercicio">Ejercicio</h2>

<p>Introduce soporte para diferentes monedas y para formatear la dirección en función del país.</p>

<h2 id="problemas-que-encontrarás">Problemas que encontrarás</h2>

<p>Dado que los primitivos no nos permiten garantizar la integridad de sus valores, tendrás que introducir validaciones en muchos lugares, incluso de forma repetida. Algunos datos siempre viajan juntos (Data Clump), por lo que tienes que asegurarte de que permanecen juntos.</p>

<p>Para formatear de forma diferente basándote en algún dato arbitrario tendrás que introducir lógica de decisión en todos aquellos lugares que necesiten utilizar el formato.</p>

<h2 id="solución">Solución</h2>

<h3 id="sin-resolver-el-code-smell">Sin resolver el <em>code smell</em></h3>

<p>Veamos, por ejemplo, qué necesitaríamos hacer para introducir soporte para diferentes monedas. Por ejemplo, cuando usamos dólares es habitual que el símbolo se ponga antes del importe, mientras que con euros, al menos en España, la moneda se indica después:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$100.00
100.00 €
</code></pre></div></div>

<p>Así que tendríamos que añadir algo de código para gestionarlo:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nx">Order</span> <span class="p">{</span>
    <span class="kd">constructor</span><span class="p">(</span>
        <span class="k">private</span> <span class="nx">customerName</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
        <span class="k">private</span> <span class="nx">customerEmail</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
        <span class="k">private</span> <span class="nx">address</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
        <span class="k">private</span> <span class="nx">totalAmount</span><span class="p">:</span> <span class="kr">number</span><span class="p">,</span>
        <span class="k">private</span> <span class="nx">currency</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
    <span class="p">)</span> <span class="p">{</span>
    <span class="p">}</span>

    <span class="nx">sendInvoice</span><span class="p">()</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="k">this</span><span class="p">.</span><span class="nx">customerEmail</span><span class="p">.</span><span class="nx">includes</span><span class="p">(</span><span class="dl">'</span><span class="s1">@</span><span class="dl">'</span><span class="p">))</span> <span class="p">{</span>
            <span class="k">throw</span> <span class="k">new</span> <span class="nb">Error</span><span class="p">(</span><span class="dl">'</span><span class="s1">Email inválido</span><span class="dl">'</span><span class="p">)</span>
        <span class="p">}</span>
        <span class="k">if</span> <span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">totalAmount</span> <span class="o">&lt;=</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
            <span class="k">throw</span> <span class="k">new</span> <span class="nb">Error</span><span class="p">(</span><span class="dl">'</span><span class="s1">El monto debe ser mayor que cero</span><span class="dl">'</span><span class="p">)</span>
        <span class="p">}</span>
        
        <span class="kd">let</span> <span class="nx">amountWithCurrency</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
        <span class="k">if</span> <span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">currency</span> <span class="o">==</span> <span class="dl">'</span><span class="s1">USD</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
            <span class="nx">amountWithCurrency</span> <span class="o">=</span> <span class="s2">`$</span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">totalAmount</span><span class="p">}</span><span class="s2">`</span>
        <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">currency</span> <span class="o">==</span> <span class="dl">'</span><span class="s1">EUR</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
            <span class="nx">amountWithCurrency</span> <span class="o">=</span> <span class="s2">`</span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">totalAmount</span><span class="p">}</span><span class="s2"> €`</span>
        <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
            <span class="k">throw</span> <span class="k">new</span> <span class="nb">Error</span><span class="p">(</span><span class="dl">'</span><span class="s1">Moneda no soportada</span><span class="dl">'</span><span class="p">)</span>
        <span class="p">}</span>

        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`Factura enviada a </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">customerEmail</span><span class="p">}</span><span class="s2"> por </span><span class="p">${</span><span class="nx">amountWithCurrency</span><span class="p">}</span><span class="s2">`</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Podríamos extraer esto a un método. Esto mejora un poco las cosas, pero tal como está hecho no es reutilizable.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nx">Order</span> <span class="p">{</span>
    <span class="kd">constructor</span><span class="p">(</span>
        <span class="k">private</span> <span class="nx">customerName</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
        <span class="k">private</span> <span class="nx">customerEmail</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
        <span class="k">private</span> <span class="nx">address</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
        <span class="k">private</span> <span class="nx">totalAmount</span><span class="p">:</span> <span class="kr">number</span><span class="p">,</span>
        <span class="k">private</span> <span class="nx">currency</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
    <span class="p">)</span> <span class="p">{</span>
    <span class="p">}</span>

    <span class="nx">sendInvoice</span><span class="p">()</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="k">this</span><span class="p">.</span><span class="nx">customerEmail</span><span class="p">.</span><span class="nx">includes</span><span class="p">(</span><span class="dl">'</span><span class="s1">@</span><span class="dl">'</span><span class="p">))</span> <span class="p">{</span>
            <span class="k">throw</span> <span class="k">new</span> <span class="nb">Error</span><span class="p">(</span><span class="dl">'</span><span class="s1">Email inválido</span><span class="dl">'</span><span class="p">)</span>
        <span class="p">}</span>
        <span class="k">if</span> <span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">totalAmount</span> <span class="o">&lt;=</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
            <span class="k">throw</span> <span class="k">new</span> <span class="nb">Error</span><span class="p">(</span><span class="dl">'</span><span class="s1">El monto debe ser mayor que cero</span><span class="dl">'</span><span class="p">)</span>
        <span class="p">}</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`Factura enviada a </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">customerEmail</span><span class="p">}</span><span class="s2"> por </span><span class="p">${(</span><span class="k">this</span><span class="p">.</span><span class="nx">formatAmount</span><span class="p">())}</span><span class="s2">`</span><span class="p">)</span>
    <span class="p">}</span>

    <span class="k">private</span> <span class="nx">formatAmount</span><span class="p">()</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">currency</span> <span class="o">==</span> <span class="dl">'</span><span class="s1">USD</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
            <span class="k">return</span> <span class="s2">`$</span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">totalAmount</span><span class="p">}</span><span class="s2">`</span>
        <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">currency</span> <span class="o">==</span> <span class="dl">'</span><span class="s1">EUR</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
            <span class="k">return</span> <span class="s2">`</span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">totalAmount</span><span class="p">}</span><span class="s2"> €`</span>
        <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
            <span class="k">throw</span> <span class="k">new</span> <span class="nb">Error</span><span class="p">(</span><span class="dl">'</span><span class="s1">Moneda no soportada</span><span class="dl">'</span><span class="p">)</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Para lograr que el código sea reutilizable necesitamos algo de este estilo:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nx">Order</span> <span class="p">{</span>
    <span class="c1">// Code removed for clarity</span>

    <span class="k">private</span> <span class="nx">formatAmount</span><span class="p">()</span> <span class="p">{</span>
        <span class="k">return</span> <span class="nx">formatAmount</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">totalAmount</span><span class="p">,</span> <span class="k">this</span><span class="p">.</span><span class="nx">currency</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="kd">function</span> <span class="nx">formatAmount</span><span class="p">(</span><span class="nx">amount</span><span class="p">:</span> <span class="kr">number</span><span class="p">,</span> <span class="nx">currency</span><span class="p">:</span> <span class="kr">string</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">if</span> <span class="p">(</span><span class="nx">currency</span> <span class="o">==</span> <span class="dl">'</span><span class="s1">USD</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">return</span> <span class="s2">`$</span><span class="p">${</span><span class="nx">amount</span><span class="p">}</span><span class="s2">`</span>
    <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="nx">currency</span> <span class="o">==</span> <span class="dl">'</span><span class="s1">EUR</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">return</span> <span class="s2">`</span><span class="p">${</span><span class="nx">amount</span><span class="p">}</span><span class="s2"> €`</span>
    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
        <span class="k">throw</span> <span class="k">new</span> <span class="nb">Error</span><span class="p">(</span><span class="dl">'</span><span class="s1">Moneda no soportada</span><span class="dl">'</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Bien, ahora tenemos una solución que podemos reutilizar en muchos lugares, pero fíjate que esta función tiene que conocer todas las monedas que se pueden usar y las reglas para formatearlas. Esto hace que sea bastante incómoda de testear y mantener. A la larga, es propensa a errores.</p>

<p>Este refactor nos lleva a un punto un poco mejor, ya que con el map es bastante fácil dar soporte a otras monedas, pues solamente hay que añadir una entrada al mismo.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nx">Order</span> <span class="p">{</span>
    <span class="c1">// Code removed for clarity</span>

    <span class="k">private</span> <span class="nx">formatAmount</span><span class="p">()</span> <span class="p">{</span>
        <span class="k">return</span> <span class="nx">formatAmount</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">totalAmount</span><span class="p">,</span> <span class="k">this</span><span class="p">.</span><span class="nx">currency</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="kd">const</span> <span class="nx">currencyFormatters</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Map</span><span class="o">&lt;</span><span class="kr">string</span><span class="p">,</span> <span class="p">(</span><span class="nx">amount</span><span class="p">:</span> <span class="kr">number</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="kr">string</span><span class="o">&gt;</span><span class="p">([</span>
    <span class="p">[</span><span class="dl">'</span><span class="s1">USD</span><span class="dl">'</span><span class="p">,</span> <span class="p">(</span><span class="nx">amount</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="s2">`$</span><span class="p">${</span><span class="nx">amount</span><span class="p">}</span><span class="s2">`</span><span class="p">],</span>
    <span class="p">[</span><span class="dl">'</span><span class="s1">EUR</span><span class="dl">'</span><span class="p">,</span> <span class="p">(</span><span class="nx">amount</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="s2">`</span><span class="p">${</span><span class="nx">amount</span><span class="p">}</span><span class="s2"> €`</span><span class="p">],</span>
<span class="p">])</span>

<span class="kd">function</span> <span class="nx">formatAmount</span><span class="p">(</span><span class="nx">amount</span><span class="p">:</span> <span class="kr">number</span><span class="p">,</span> <span class="nx">currency</span><span class="p">:</span> <span class="kr">string</span><span class="p">)</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">formatter</span> <span class="o">=</span> <span class="nx">currencyFormatters</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="nx">currency</span><span class="p">)</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">formatter</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">throw</span> <span class="k">new</span> <span class="nb">Error</span><span class="p">(</span><span class="dl">'</span><span class="s1">Moneda no soportada</span><span class="dl">'</span><span class="p">)</span>
    <span class="p">}</span>
    <span class="k">return</span> <span class="nx">formatter</span><span class="p">(</span><span class="nx">amount</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Aun así, tenemos algunos problemas. Por ejemplo, ¿qué ocurre si en vez de usar <code class="language-plaintext highlighter-rouge">USD</code> o <code class="language-plaintext highlighter-rouge">EUR</code> usamos <code class="language-plaintext highlighter-rouge">usd</code> o <code class="language-plaintext highlighter-rouge">eur</code>? O, ¿qué ocurre si en algún punto expresamos las monedas con símbolos diferentes, como <code class="language-plaintext highlighter-rouge">$</code> o <code class="language-plaintext highlighter-rouge">£</code>, pero que serían sinónimos. Es decir, el uso de un <code class="language-plaintext highlighter-rouge">string</code> para representar una moneda no garantiza que lo hagamos de forma consistente y tendríamos que introducir código defensivo en varios lugares para prevenir errores, en un cierto juego del gato y el ratón. O bien duplicar este código auxiliar en cada lugar en el que lo necesitemos.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nx">Order</span> <span class="p">{</span>
<span class="c1">// Code removed for clarity</span>

    <span class="k">private</span> <span class="nx">formatAmount</span><span class="p">()</span> <span class="p">{</span>
        <span class="k">return</span> <span class="nx">formatAmount</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">totalAmount</span><span class="p">,</span> <span class="k">this</span><span class="p">.</span><span class="nx">currency</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="kd">const</span> <span class="nx">currencyFormatters</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Map</span><span class="o">&lt;</span><span class="kr">string</span><span class="p">,</span> <span class="p">(</span><span class="nx">amount</span><span class="p">:</span> <span class="kr">number</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="kr">string</span><span class="o">&gt;</span><span class="p">([</span>
    <span class="p">[</span><span class="dl">'</span><span class="s1">USD</span><span class="dl">'</span><span class="p">,</span> <span class="p">(</span><span class="nx">amount</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="s2">`$</span><span class="p">${</span><span class="nx">amount</span><span class="p">}</span><span class="s2">`</span><span class="p">],</span>
    <span class="p">[</span><span class="dl">'</span><span class="s1">$</span><span class="dl">'</span><span class="p">,</span> <span class="p">(</span><span class="nx">amount</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="s2">`$</span><span class="p">${</span><span class="nx">amount</span><span class="p">}</span><span class="s2">`</span><span class="p">],</span>
    <span class="p">[</span><span class="dl">'</span><span class="s1">EUR</span><span class="dl">'</span><span class="p">,</span> <span class="p">(</span><span class="nx">amount</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="s2">`</span><span class="p">${</span><span class="nx">amount</span><span class="p">}</span><span class="s2"> €`</span><span class="p">],</span>
    <span class="p">[</span><span class="dl">'</span><span class="s1">€</span><span class="dl">'</span><span class="p">,</span> <span class="p">(</span><span class="nx">amount</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="s2">`</span><span class="p">${</span><span class="nx">amount</span><span class="p">}</span><span class="s2"> €`</span><span class="p">],</span>
<span class="p">])</span>

<span class="kd">function</span> <span class="nx">formatAmount</span><span class="p">(</span><span class="nx">amount</span><span class="p">:</span> <span class="kr">number</span><span class="p">,</span> <span class="nx">currency</span><span class="p">:</span> <span class="kr">string</span><span class="p">)</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">formatter</span> <span class="o">=</span> <span class="nx">currencyFormatters</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="nx">currency</span><span class="p">.</span><span class="nx">toUpperCase</span><span class="p">())</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">formatter</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">throw</span> <span class="k">new</span> <span class="nb">Error</span><span class="p">(</span><span class="dl">'</span><span class="s1">Moneda no soportada</span><span class="dl">'</span><span class="p">)</span>
    <span class="p">}</span>
    <span class="k">return</span> <span class="nx">formatter</span><span class="p">(</span><span class="nx">amount</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<p>En este caso hemos podido mantenerlo relativamente aislado, pero tenemos que introducir instrucciones para ocuparnos de cuestiones que van engordando nuestro código en temas no directamente relacionados con el dominio de la pieza en la que trabajamos.</p>

<p>Y exactamente lo mismo nos encontraremos para formatear direcciones, para validar el email, etc. Así que vayamos a ver cómo se resolvería este <em>code smell</em> introduciendo Value Objects.</p>

<h3 id="resolviendo-el-code-smell">Resolviendo el <em>code smell</em></h3>

<p><em>Introduce Value Object</em> es el refactor apropiado para <em>Primitive Obsession</em>. Consiste en encapsular los valores primitivos en objetos que representen conceptos del dominio. Si seguimos las reglas de Domain Driven Design, los value objects representan conceptos de dominio que nos interesan por su valor. Tienen varias propiedades:</p>

<ul>
  <li>Inmutabilidad: sus propiedades se mantienen inalteradas durante su ciclo de vida. Las operaciones que las mutan siempre devuelven una nueva instancia.</li>
  <li>No tienen identidad: los value objects se diferencian entre sí por su valor, por lo que nos da igual la instancia específica.</li>
  <li>Comparación por valor: dos value objects son iguales si tienen el mismo valor de sus propiedades.</li>
  <li>Garantizan su consistencia: no se pueden instanciar con valores no válidos.</li>
  <li>Encapsulación: los value objects atraen el comportamiento relacionado con su valor.</li>
</ul>

<p>Vamos a ver estas cómo algunas de estas propiedades nos benefician en comparación con modelar conceptos con primitivos. Por ejemplo, en el caso de la moneda.</p>

<ul>
  <li>Inmutabilidad: al modelar la moneda (currency) con un string no podemos evitar que algún proceso pueda cambiarla.</li>
  <li>Consistencia: con un string podríamos tener diversas formas equivalentes de expresar una misma moneda (<code class="language-plaintext highlighter-rouge">USD</code>, <code class="language-plaintext highlighter-rouge">US$</code>, <code class="language-plaintext highlighter-rouge">dollars</code>, <code class="language-plaintext highlighter-rouge">$</code>, etc.) que tendríamos que controlar en cada uso. Un Value Object nos proporciona una representación consistente todo el tiempo.</li>
  <li>Encapsulación: al modelar con un string tendríamos que tratar explícitamente con el formato de la moneda en cada lugar donde se use. Con un value object podemos encapsular incluso distintos formatos para usar de forma consistente dependiendo del contexto.</li>
</ul>

<p>Vamos a verlo en acción.</p>

<h4 id="email">Email</h4>

<p>La mayor dificultad para refactorizar introduciendo Value Objects es que tenemos que cambiar firmas de métodos y funciones, pero podemos usar algunas técnicas sencillas para hacerlo sin muchos problemas. Vamos a empezar con el email.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nx">Order</span> <span class="p">{</span>
    <span class="kd">constructor</span><span class="p">(</span>
        <span class="k">private</span> <span class="nx">customerName</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
        <span class="k">private</span> <span class="nx">customerEmail</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
        <span class="k">private</span> <span class="nx">address</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
        <span class="k">private</span> <span class="nx">totalAmount</span><span class="p">:</span> <span class="kr">number</span><span class="p">,</span>
        <span class="k">private</span> <span class="nx">currency</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
    <span class="p">)</span> <span class="p">{</span>
    <span class="p">}</span>

    <span class="nx">sendInvoice</span><span class="p">()</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="k">this</span><span class="p">.</span><span class="nx">customerEmail</span><span class="p">.</span><span class="nx">includes</span><span class="p">(</span><span class="dl">'</span><span class="s1">@</span><span class="dl">'</span><span class="p">))</span> <span class="p">{</span>
            <span class="k">throw</span> <span class="k">new</span> <span class="nb">Error</span><span class="p">(</span><span class="dl">'</span><span class="s1">Email inválido</span><span class="dl">'</span><span class="p">)</span>
        <span class="p">}</span>
        <span class="k">if</span> <span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">totalAmount</span> <span class="o">&lt;=</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
            <span class="k">throw</span> <span class="k">new</span> <span class="nb">Error</span><span class="p">(</span><span class="dl">'</span><span class="s1">El monto debe ser mayor que cero</span><span class="dl">'</span><span class="p">)</span>
        <span class="p">}</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`Factura enviada a </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">customerEmail</span><span class="p">}</span><span class="s2"> por </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">totalAmount</span><span class="p">}</span><span class="s2"> </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">currency</span><span class="p">}</span><span class="s2">`</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Lo primero que nos conviene hacer es cambiar la sintaxis del constructor, pasando de parámetros propiedad a separar parámetros y propiedades.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nx">Order</span> <span class="p">{</span>
    <span class="k">private</span> <span class="nx">customerName</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
    <span class="k">private</span> <span class="nx">customerEmail</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
    <span class="k">private</span> <span class="nx">address</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
    <span class="k">private</span> <span class="nx">totalAmount</span><span class="p">:</span> <span class="kr">number</span><span class="p">;</span>
    <span class="k">private</span> <span class="nx">currency</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>

    <span class="kd">constructor</span><span class="p">(</span>
        <span class="nx">customerName</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
        <span class="nx">customerEmail</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
        <span class="nx">address</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
        <span class="nx">totalAmount</span><span class="p">:</span> <span class="kr">number</span><span class="p">,</span>
        <span class="nx">currency</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
    <span class="p">)</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">currency</span> <span class="o">=</span> <span class="nx">currency</span><span class="p">;</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">totalAmount</span> <span class="o">=</span> <span class="nx">totalAmount</span><span class="p">;</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">address</span> <span class="o">=</span> <span class="nx">address</span><span class="p">;</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">customerEmail</span> <span class="o">=</span> <span class="nx">customerEmail</span><span class="p">;</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">customerName</span> <span class="o">=</span> <span class="nx">customerName</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="nx">sendInvoice</span><span class="p">()</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="k">this</span><span class="p">.</span><span class="nx">customerEmail</span><span class="p">.</span><span class="nx">includes</span><span class="p">(</span><span class="dl">'</span><span class="s1">@</span><span class="dl">'</span><span class="p">))</span> <span class="p">{</span>
            <span class="k">throw</span> <span class="k">new</span> <span class="nb">Error</span><span class="p">(</span><span class="dl">'</span><span class="s1">Email inválido</span><span class="dl">'</span><span class="p">)</span>
        <span class="p">}</span>
        <span class="k">if</span> <span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">totalAmount</span> <span class="o">&lt;=</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
            <span class="k">throw</span> <span class="k">new</span> <span class="nb">Error</span><span class="p">(</span><span class="dl">'</span><span class="s1">El monto debe ser mayor que cero</span><span class="dl">'</span><span class="p">)</span>
        <span class="p">}</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`Factura enviada a </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">customerEmail</span><span class="p">}</span><span class="s2"> por </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">totalAmount</span><span class="p">}</span><span class="s2"> </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">currency</span><span class="p">}</span><span class="s2">`</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>

</code></pre></div></div>

<p>Por otro lado, introducimos un nuevo Value Object para representar el email, encapsulando los comportamientos de validación y formateo que podamos necesitar. Nos basta copiar el código de la clase Order relativo al Email.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nx">Email</span> <span class="p">{</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="nx">email</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>

    <span class="kd">constructor</span><span class="p">(</span><span class="nx">email</span><span class="p">:</span> <span class="kr">string</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">email</span><span class="p">.</span><span class="nx">includes</span><span class="p">(</span><span class="dl">'</span><span class="s1">@</span><span class="dl">'</span><span class="p">))</span> <span class="p">{</span>
            <span class="k">throw</span> <span class="k">new</span> <span class="nb">Error</span><span class="p">(</span><span class="dl">'</span><span class="s1">Email inválido</span><span class="dl">'</span><span class="p">)</span>
        <span class="p">}</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">email</span> <span class="o">=</span> <span class="nx">email</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="nx">toString</span><span class="p">()</span> <span class="p">{</span>
        <span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">email</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Hay algunos autores que señalan que no deberíamos tener validaciones en el constructor, ya que hay situaciones en las que no serían necesarias. No tengo una opinión fuerte sobre ello, pero una alternativa sería:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nx">Email</span> <span class="p">{</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="nx">email</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>

    <span class="k">private</span> <span class="kd">constructor</span><span class="p">(</span><span class="nx">email</span><span class="p">:</span> <span class="kr">string</span><span class="p">)</span> <span class="p">{</span>

        <span class="k">this</span><span class="p">.</span><span class="nx">email</span> <span class="o">=</span> <span class="nx">email</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">static</span> <span class="nx">valid</span><span class="p">(</span><span class="nx">email</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="nx">Email</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">email</span><span class="p">.</span><span class="nx">includes</span><span class="p">(</span><span class="dl">'</span><span class="s1">@</span><span class="dl">'</span><span class="p">))</span> <span class="p">{</span>
            <span class="k">throw</span> <span class="k">new</span> <span class="nb">Error</span><span class="p">(</span><span class="dl">'</span><span class="s1">Email inválido</span><span class="dl">'</span><span class="p">)</span>
        <span class="p">}</span>

        <span class="k">return</span> <span class="k">new</span> <span class="nx">Email</span><span class="p">(</span><span class="nx">email</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="nx">toString</span><span class="p">()</span> <span class="p">{</span>
        <span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">email</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>A continuación, podemos usarlo en la clase Order. En ese caso lo voy a hacer en forma paralela.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nx">Order</span> <span class="p">{</span>
    <span class="k">private</span> <span class="nx">customerName</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
    <span class="k">private</span> <span class="nx">customerEmail</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
    <span class="k">private</span> <span class="nx">address</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
    <span class="k">private</span> <span class="nx">totalAmount</span><span class="p">:</span> <span class="kr">number</span><span class="p">;</span>
    <span class="k">private</span> <span class="nx">currency</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>

    <span class="kd">constructor</span><span class="p">(</span>
        <span class="nx">customerName</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
        <span class="nx">customerEmail</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
        <span class="nx">address</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
        <span class="nx">totalAmount</span><span class="p">:</span> <span class="kr">number</span><span class="p">,</span>
        <span class="nx">currency</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
    <span class="p">)</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">currency</span> <span class="o">=</span> <span class="nx">currency</span><span class="p">;</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">totalAmount</span> <span class="o">=</span> <span class="nx">totalAmount</span><span class="p">;</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">address</span> <span class="o">=</span> <span class="nx">address</span><span class="p">;</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">customerEmail</span> <span class="o">=</span> <span class="nx">customerEmail</span><span class="p">;</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">customerName</span> <span class="o">=</span> <span class="nx">customerName</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="nx">sendInvoice</span><span class="p">()</span> <span class="p">{</span>
        <span class="kd">const</span> <span class="nx">email</span> <span class="o">=</span> <span class="nx">Email</span><span class="p">.</span><span class="nx">valid</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">customerEmail</span><span class="p">);</span>
        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="k">this</span><span class="p">.</span><span class="nx">customerEmail</span><span class="p">.</span><span class="nx">includes</span><span class="p">(</span><span class="dl">'</span><span class="s1">@</span><span class="dl">'</span><span class="p">))</span> <span class="p">{</span>
            <span class="k">throw</span> <span class="k">new</span> <span class="nb">Error</span><span class="p">(</span><span class="dl">'</span><span class="s1">Email inválido</span><span class="dl">'</span><span class="p">)</span>
        <span class="p">}</span>
        <span class="k">if</span> <span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">totalAmount</span> <span class="o">&lt;=</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
            <span class="k">throw</span> <span class="k">new</span> <span class="nb">Error</span><span class="p">(</span><span class="dl">'</span><span class="s1">El monto debe ser mayor que cero</span><span class="dl">'</span><span class="p">)</span>
        <span class="p">}</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`Factura enviada a </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">customerEmail</span><span class="p">}</span><span class="s2"> por </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">totalAmount</span><span class="p">}</span><span class="s2"> </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">currency</span><span class="p">}</span><span class="s2">`</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Fíjate que ahora si el email no es válido <code class="language-plaintext highlighter-rouge">Order</code> fallará igualmente, por lo que podemos quitar el if que valida la propiedad <code class="language-plaintext highlighter-rouge">customerEmail</code>.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nx">Order</span> <span class="p">{</span>
    <span class="k">private</span> <span class="nx">customerName</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
    <span class="k">private</span> <span class="nx">customerEmail</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
    <span class="k">private</span> <span class="nx">address</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
    <span class="k">private</span> <span class="nx">totalAmount</span><span class="p">:</span> <span class="kr">number</span><span class="p">;</span>
    <span class="k">private</span> <span class="nx">currency</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>

    <span class="kd">constructor</span><span class="p">(</span>
        <span class="nx">customerName</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
        <span class="nx">customerEmail</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
        <span class="nx">address</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
        <span class="nx">totalAmount</span><span class="p">:</span> <span class="kr">number</span><span class="p">,</span>
        <span class="nx">currency</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
    <span class="p">)</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">currency</span> <span class="o">=</span> <span class="nx">currency</span><span class="p">;</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">totalAmount</span> <span class="o">=</span> <span class="nx">totalAmount</span><span class="p">;</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">address</span> <span class="o">=</span> <span class="nx">address</span><span class="p">;</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">customerEmail</span> <span class="o">=</span> <span class="nx">customerEmail</span><span class="p">;</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">customerName</span> <span class="o">=</span> <span class="nx">customerName</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="nx">sendInvoice</span><span class="p">()</span> <span class="p">{</span>
        <span class="kd">const</span> <span class="nx">email</span> <span class="o">=</span> <span class="nx">Email</span><span class="p">.</span><span class="nx">valid</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">customerEmail</span><span class="p">);</span>
        <span class="k">if</span> <span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">totalAmount</span> <span class="o">&lt;=</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
            <span class="k">throw</span> <span class="k">new</span> <span class="nb">Error</span><span class="p">(</span><span class="dl">'</span><span class="s1">El monto debe ser mayor que cero</span><span class="dl">'</span><span class="p">)</span>
        <span class="p">}</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`Factura enviada a </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">customerEmail</span><span class="p">}</span><span class="s2"> por </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">totalAmount</span><span class="p">}</span><span class="s2"> </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">currency</span><span class="p">}</span><span class="s2">`</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Y, de hecho, podemos pasar a usar <code class="language-plaintext highlighter-rouge">email</code> en su lugar:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nx">Order</span> <span class="p">{</span>
    <span class="k">private</span> <span class="nx">customerName</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
    <span class="k">private</span> <span class="nx">customerEmail</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
    <span class="k">private</span> <span class="nx">address</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
    <span class="k">private</span> <span class="nx">totalAmount</span><span class="p">:</span> <span class="kr">number</span><span class="p">;</span>
    <span class="k">private</span> <span class="nx">currency</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>

    <span class="kd">constructor</span><span class="p">(</span>
        <span class="nx">customerName</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
        <span class="nx">customerEmail</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
        <span class="nx">address</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
        <span class="nx">totalAmount</span><span class="p">:</span> <span class="kr">number</span><span class="p">,</span>
        <span class="nx">currency</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
    <span class="p">)</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">currency</span> <span class="o">=</span> <span class="nx">currency</span><span class="p">;</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">totalAmount</span> <span class="o">=</span> <span class="nx">totalAmount</span><span class="p">;</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">address</span> <span class="o">=</span> <span class="nx">address</span><span class="p">;</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">customerEmail</span> <span class="o">=</span> <span class="nx">customerEmail</span><span class="p">;</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">customerName</span> <span class="o">=</span> <span class="nx">customerName</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="nx">sendInvoice</span><span class="p">()</span> <span class="p">{</span>
        <span class="kd">const</span> <span class="nx">email</span> <span class="o">=</span> <span class="nx">Email</span><span class="p">.</span><span class="nx">valid</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">customerEmail</span><span class="p">);</span>
        <span class="k">if</span> <span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">totalAmount</span> <span class="o">&lt;=</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
            <span class="k">throw</span> <span class="k">new</span> <span class="nb">Error</span><span class="p">(</span><span class="dl">'</span><span class="s1">El monto debe ser mayor que cero</span><span class="dl">'</span><span class="p">)</span>
        <span class="p">}</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`Factura enviada a </span><span class="p">${</span><span class="nx">email</span><span class="p">.</span><span class="nx">toString</span><span class="p">()}</span><span class="s2"> por </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">totalAmount</span><span class="p">}</span><span class="s2"> </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">currency</span><span class="p">}</span><span class="s2">`</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Una pregunta que te puedes estar haciendo ahora es por qué no estamos construyendo <code class="language-plaintext highlighter-rouge">Order</code> con una propiedad <code class="language-plaintext highlighter-rouge">customerEmail</code> de tipo <code class="language-plaintext highlighter-rouge">Email</code>, ya sea pasándosela directamente, ya sea intanciándola en construcción. Por ejemplo, así:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nx">Order</span> <span class="p">{</span>
    <span class="k">private</span> <span class="nx">customerName</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
    <span class="k">private</span> <span class="nx">customerEmail</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
    <span class="k">private</span> <span class="nx">address</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
    <span class="k">private</span> <span class="nx">totalAmount</span><span class="p">:</span> <span class="kr">number</span><span class="p">;</span>
    <span class="k">private</span> <span class="nx">currency</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
    <span class="k">private</span> <span class="nx">email</span><span class="p">:</span> <span class="nx">Email</span><span class="p">;</span>

    <span class="kd">constructor</span><span class="p">(</span>
        <span class="nx">customerName</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
        <span class="nx">customerEmail</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
        <span class="nx">address</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
        <span class="nx">totalAmount</span><span class="p">:</span> <span class="kr">number</span><span class="p">,</span>
        <span class="nx">currency</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
    <span class="p">)</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">currency</span> <span class="o">=</span> <span class="nx">currency</span><span class="p">;</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">totalAmount</span> <span class="o">=</span> <span class="nx">totalAmount</span><span class="p">;</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">address</span> <span class="o">=</span> <span class="nx">address</span><span class="p">;</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">customerEmail</span> <span class="o">=</span> <span class="nx">customerEmail</span><span class="p">;</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">customerName</span> <span class="o">=</span> <span class="nx">customerName</span><span class="p">;</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">email</span> <span class="o">=</span> <span class="nx">Email</span><span class="p">.</span><span class="nx">valid</span><span class="p">(</span><span class="nx">customerEmail</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="nx">sendInvoice</span><span class="p">()</span> <span class="p">{</span>
        <span class="c1">// Code removed for clarity</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Bien, la principal razón es que ahora la construcción de <code class="language-plaintext highlighter-rouge">Order</code> puede fallar cuando antes no lo hacía, cambiando el comportamiento esperado del constructor. Esto puede ser importante si tenemos que mantener compatibilidad con muchos usos existentes de la clase que contaban con un constructor que no hace validaciones y, por tanto, no esperan que falle y no toman tienen medidas protectoras.</p>

<h4 id="amount-y-currency">Amount y Currency</h4>

<p><code class="language-plaintext highlighter-rouge">Amount</code> y <code class="language-plaintext highlighter-rouge">Currency</code> son dos conceptos que representan un valor monetario, aunque no exactamente de un <code class="language-plaintext highlighter-rouge">Money</code>. <code class="language-plaintext highlighter-rouge">Money</code> representa cualquier cantidad de dinero en una unidad monetaria dada, pero en nuestro ejemplo, <code class="language-plaintext highlighter-rouge">Amount</code> no puede ser cero ni negativo. Podríamos pensar en un <code class="language-plaintext highlighter-rouge">InvoiceAmount</code>, compuesto de <code class="language-plaintext highlighter-rouge">Amount</code> y <code class="language-plaintext highlighter-rouge">Currency</code> en su lugar, cada uno de ellos con su propio Value Object.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nx">Amount</span> <span class="p">{</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="nx">amount</span><span class="p">:</span> <span class="kr">number</span><span class="p">;</span>

    <span class="k">private</span> <span class="kd">constructor</span><span class="p">(</span><span class="nx">amount</span><span class="p">:</span> <span class="kr">number</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">amount</span> <span class="o">=</span> <span class="nx">amount</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">static</span> <span class="nx">valid</span><span class="p">(</span><span class="nx">amount</span><span class="p">:</span> <span class="kr">number</span><span class="p">):</span> <span class="nx">Amount</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="nx">amount</span> <span class="o">&lt;=</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
            <span class="k">throw</span> <span class="k">new</span> <span class="nb">Error</span><span class="p">(</span><span class="dl">'</span><span class="s1">El monto debe ser mayor que cero</span><span class="dl">'</span><span class="p">)</span>
        <span class="p">}</span>

        <span class="k">return</span> <span class="k">new</span> <span class="nx">Amount</span><span class="p">(</span><span class="nx">amount</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="nx">format</span><span class="p">(</span><span class="nx">currency</span><span class="p">:</span> <span class="nx">Currency</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">return</span> <span class="nx">currency</span><span class="p">.</span><span class="nx">format</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">amount</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="kd">class</span> <span class="nx">Currency</span> <span class="p">{</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="nx">currency</span><span class="p">:</span> <span class="kr">string</span>

    <span class="k">private</span> <span class="kd">constructor</span><span class="p">(</span><span class="nx">currency</span><span class="p">:</span> <span class="kr">string</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">currency</span> <span class="o">=</span> <span class="nx">currency</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">static</span> <span class="nx">valid</span><span class="p">(</span><span class="nx">currency</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="nx">Currency</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="p">[</span><span class="dl">'</span><span class="s1">USD</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">EUR</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">GBP</span><span class="dl">'</span><span class="p">].</span><span class="nx">includes</span><span class="p">(</span><span class="nx">currency</span><span class="p">))</span> <span class="p">{</span>
            <span class="k">throw</span> <span class="k">new</span> <span class="nb">Error</span><span class="p">(</span><span class="dl">'</span><span class="s1">Currency not supported</span><span class="dl">'</span><span class="p">)</span>
        <span class="p">}</span>
        <span class="k">if</span> <span class="p">(</span><span class="nx">currency</span><span class="p">.</span><span class="nx">length</span> <span class="o">!==</span> <span class="mi">3</span><span class="p">)</span> <span class="p">{</span>
            <span class="k">throw</span> <span class="k">new</span> <span class="nb">Error</span><span class="p">(</span><span class="dl">'</span><span class="s1">Currency must be 3 characters long</span><span class="dl">'</span><span class="p">)</span>
        <span class="p">}</span>
        <span class="k">return</span> <span class="k">new</span> <span class="nx">Currency</span><span class="p">(</span><span class="nx">currency</span><span class="p">.</span><span class="nx">toUpperCase</span><span class="p">());</span>
    <span class="p">}</span>

    <span class="nx">format</span><span class="p">(</span><span class="nx">amount</span><span class="p">:</span> <span class="kr">number</span><span class="p">):</span> <span class="kr">string</span> <span class="p">{</span>
        <span class="k">return</span> <span class="s2">`</span><span class="p">${</span><span class="nx">amount</span><span class="p">}</span><span class="s2"> </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">currency</span><span class="p">}</span><span class="s2">`</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Fíjate como aplicamos un patrón de <em>Double Dispatch</em> para que ninguno de los Value Objects tenga que conocer las propiedades del otro.</p>

<p>El Value Object compuesto:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nx">InvoiceAmount</span> <span class="p">{</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="nx">amount</span><span class="p">:</span> <span class="nx">Amount</span><span class="p">;</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="nx">currency</span><span class="p">:</span> <span class="nx">Currency</span><span class="p">;</span>

    <span class="k">private</span> <span class="kd">constructor</span><span class="p">(</span><span class="nx">amount</span><span class="p">:</span> <span class="nx">Amount</span><span class="p">,</span> <span class="nx">currency</span><span class="p">:</span> <span class="nx">Currency</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">amount</span> <span class="o">=</span> <span class="nx">amount</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">currency</span> <span class="o">=</span> <span class="nx">currency</span>
    <span class="p">}</span>

    <span class="k">static</span> <span class="nx">valid</span><span class="p">(</span><span class="nx">amount</span><span class="p">:</span> <span class="kr">number</span><span class="p">,</span> <span class="nx">currency</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="nx">InvoiceAmount</span> <span class="p">{</span>
        <span class="k">return</span> <span class="k">new</span> <span class="nx">InvoiceAmount</span><span class="p">(</span><span class="nx">Amount</span><span class="p">.</span><span class="nx">valid</span><span class="p">(</span><span class="nx">amount</span><span class="p">),</span> <span class="nx">Currency</span><span class="p">.</span><span class="nx">valid</span><span class="p">(</span><span class="nx">currency</span><span class="p">))</span>
    <span class="p">}</span>

    <span class="nx">toString</span><span class="p">():</span> <span class="kr">string</span> <span class="p">{</span>
        <span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">amount</span><span class="p">.</span><span class="nx">format</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">currency</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Ahora podemos usarlo en la clase Order:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nx">Order</span> <span class="p">{</span>
    <span class="k">private</span> <span class="nx">customerName</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
    <span class="k">private</span> <span class="nx">customerEmail</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
    <span class="k">private</span> <span class="nx">address</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
    <span class="k">private</span> <span class="nx">totalAmount</span><span class="p">:</span> <span class="kr">number</span><span class="p">;</span>
    <span class="k">private</span> <span class="nx">currency</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
    <span class="k">private</span> <span class="nx">email</span><span class="p">:</span> <span class="nx">Email</span><span class="p">;</span>

    <span class="kd">constructor</span><span class="p">(</span>
        <span class="nx">customerName</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
        <span class="nx">customerEmail</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
        <span class="nx">address</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
        <span class="nx">totalAmount</span><span class="p">:</span> <span class="kr">number</span><span class="p">,</span>
        <span class="nx">currency</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
    <span class="p">)</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">currency</span> <span class="o">=</span> <span class="nx">currency</span><span class="p">;</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">totalAmount</span> <span class="o">=</span> <span class="nx">totalAmount</span><span class="p">;</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">address</span> <span class="o">=</span> <span class="nx">address</span><span class="p">;</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">customerEmail</span> <span class="o">=</span> <span class="nx">customerEmail</span><span class="p">;</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">customerName</span> <span class="o">=</span> <span class="nx">customerName</span><span class="p">;</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">email</span> <span class="o">=</span> <span class="nx">Email</span><span class="p">.</span><span class="nx">valid</span><span class="p">(</span><span class="nx">customerEmail</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="nx">sendInvoice</span><span class="p">()</span> <span class="p">{</span>
        <span class="kd">const</span> <span class="nx">email</span> <span class="o">=</span> <span class="nx">Email</span><span class="p">.</span><span class="nx">valid</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">customerEmail</span><span class="p">);</span>
        <span class="kd">const</span> <span class="nx">invoiceAmount</span> <span class="o">=</span> <span class="nx">InvoiceAmount</span><span class="p">.</span><span class="nx">valid</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">totalAmount</span><span class="p">,</span> <span class="k">this</span><span class="p">.</span><span class="nx">currency</span><span class="p">);</span>

        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`Factura enviada a </span><span class="p">${</span><span class="nx">email</span><span class="p">.</span><span class="nx">toString</span><span class="p">()}</span><span class="s2"> por </span><span class="p">${</span><span class="nx">invoiceAmount</span><span class="p">.</span><span class="nx">toString</span><span class="p">()}</span><span class="s2">`</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Ahora que hemos refactorizado la forma en que se representa Currency podemos pensar en como dar soporte a distintas monedas y sus formatos. Podemos aplicar herencia y hacer de <code class="language-plaintext highlighter-rouge">valid</code> método factoría.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">abstract</span> <span class="kd">class</span> <span class="nx">Currency</span> <span class="p">{</span>
    <span class="k">static</span> <span class="nx">valid</span><span class="p">(</span><span class="nx">currency</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="nx">Currency</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="nx">currency</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">EUR</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
            <span class="k">return</span> <span class="k">new</span> <span class="nx">Euro</span><span class="p">()</span>
        <span class="p">}</span>
        <span class="k">if</span> <span class="p">(</span><span class="nx">currency</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">GBP</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
            <span class="k">return</span> <span class="k">new</span> <span class="nx">Gbp</span><span class="p">()</span>
        <span class="p">}</span>
        <span class="k">if</span> <span class="p">(</span><span class="nx">currency</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">USD</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
            <span class="k">return</span> <span class="k">new</span> <span class="nx">Usd</span><span class="p">()</span>
        <span class="p">}</span>
        <span class="k">throw</span> <span class="k">new</span> <span class="nb">Error</span><span class="p">(</span><span class="dl">'</span><span class="s1">Currency not supported</span><span class="dl">'</span><span class="p">)</span>
    <span class="p">}</span>

    <span class="kd">abstract</span> <span class="nx">format</span><span class="p">(</span><span class="nx">amount</span><span class="p">:</span> <span class="kr">number</span><span class="p">):</span> <span class="kr">string</span>
<span class="p">}</span>

<span class="kd">class</span> <span class="nx">Euro</span> <span class="kd">extends</span> <span class="nx">Currency</span> <span class="p">{</span>
    <span class="nx">format</span><span class="p">(</span><span class="nx">amount</span><span class="p">:</span> <span class="kr">number</span><span class="p">):</span> <span class="kr">string</span> <span class="p">{</span>
        <span class="k">return</span> <span class="s2">`</span><span class="p">${</span><span class="nx">amount</span><span class="p">}</span><span class="s2"> €`</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="kd">class</span> <span class="nx">Gbp</span> <span class="kd">extends</span> <span class="nx">Currency</span> <span class="p">{</span>
    <span class="nx">format</span><span class="p">(</span><span class="nx">amount</span><span class="p">:</span> <span class="kr">number</span><span class="p">):</span> <span class="kr">string</span> <span class="p">{</span>
        <span class="k">return</span> <span class="s2">`£</span><span class="p">${</span><span class="nx">amount</span><span class="p">}</span><span class="s2">`</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="kd">class</span> <span class="nx">Usd</span> <span class="kd">extends</span> <span class="nx">Currency</span> <span class="p">{</span>
    <span class="nx">format</span><span class="p">(</span><span class="nx">amount</span><span class="p">:</span> <span class="kr">number</span><span class="p">):</span> <span class="kr">string</span> <span class="p">{</span>
        <span class="k">return</span> <span class="s2">`$</span><span class="p">${</span><span class="nx">amount</span><span class="p">}</span><span class="s2">`</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>También podemos refactorizar un poco más hasta llegar a esta versión más extensible:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">abstract</span> <span class="kd">class</span> <span class="nx">Currency</span> <span class="p">{</span>
    <span class="k">static</span> <span class="nx">valid</span><span class="p">(</span><span class="nx">currency</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="nx">Currency</span> <span class="p">{</span>
        <span class="c1">// Registry of supported currencies using a map for easy extensibility</span>
        <span class="kd">const</span> <span class="nx">registry</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Map</span><span class="o">&lt;</span><span class="kr">string</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="nx">Currency</span><span class="o">&gt;</span><span class="p">([</span>
            <span class="p">[</span><span class="dl">'</span><span class="s1">EUR</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="k">new</span> <span class="nx">Euro</span><span class="p">()],</span>
            <span class="p">[</span><span class="dl">'</span><span class="s1">GBP</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="k">new</span> <span class="nx">Gbp</span><span class="p">()],</span>
            <span class="p">[</span><span class="dl">'</span><span class="s1">USD</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="k">new</span> <span class="nx">Usd</span><span class="p">()],</span>
        <span class="p">])</span>

        <span class="kd">const</span> <span class="nx">factory</span> <span class="o">=</span> <span class="nx">registry</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="nx">currency</span><span class="p">)</span>
        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">factory</span><span class="p">)</span> <span class="p">{</span>
            <span class="k">throw</span> <span class="k">new</span> <span class="nb">Error</span><span class="p">(</span><span class="dl">'</span><span class="s1">Currency not supported</span><span class="dl">'</span><span class="p">)</span>
        <span class="p">}</span>
        <span class="k">return</span> <span class="nx">factory</span><span class="p">()</span>
    <span class="p">}</span>

    <span class="kd">abstract</span> <span class="nx">format</span><span class="p">(</span><span class="nx">amount</span><span class="p">:</span> <span class="kr">number</span><span class="p">):</span> <span class="kr">string</span>
<span class="p">}</span>

<span class="kd">class</span> <span class="nx">Euro</span> <span class="kd">extends</span> <span class="nx">Currency</span> <span class="p">{</span>
    <span class="nx">format</span><span class="p">(</span><span class="nx">amount</span><span class="p">:</span> <span class="kr">number</span><span class="p">):</span> <span class="kr">string</span> <span class="p">{</span>
        <span class="k">return</span> <span class="s2">`</span><span class="p">${</span><span class="nx">amount</span><span class="p">}</span><span class="s2"> €`</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="kd">class</span> <span class="nx">Gbp</span> <span class="kd">extends</span> <span class="nx">Currency</span> <span class="p">{</span>
    <span class="nx">format</span><span class="p">(</span><span class="nx">amount</span><span class="p">:</span> <span class="kr">number</span><span class="p">):</span> <span class="kr">string</span> <span class="p">{</span>
        <span class="k">return</span> <span class="s2">`£</span><span class="p">${</span><span class="nx">amount</span><span class="p">}</span><span class="s2">`</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="kd">class</span> <span class="nx">Usd</span> <span class="kd">extends</span> <span class="nx">Currency</span> <span class="p">{</span>
    <span class="nx">format</span><span class="p">(</span><span class="nx">amount</span><span class="p">:</span> <span class="kr">number</span><span class="p">):</span> <span class="kr">string</span> <span class="p">{</span>
        <span class="k">return</span> <span class="s2">`$</span><span class="p">${</span><span class="nx">amount</span><span class="p">}</span><span class="s2">`</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Obviamente, podríamos añadir un método para usar el símbolo ISO 4217 de la moneda, etc.</p>

<h4 id="address">Address</h4>

<p>Como ya hemos visto al hablar de <a href="/data-clump/">Data Clump</a> modelar direcciones con un Value Object es una buena idea, ya que nos permite gestionar cada elemento de la misma de forma independiente, lo que nos habilita para introducir fácilmente distintos formatos dependiendo de países, etc. En el artículo enlazado se proponía una posible solución para este caso concreto.</p>

<p>Una cuestión que podemos añadir aquí como ejercicio es la de como usar una dirección que ya tenemos en un string y convertirla en un <em>Value Object compuesto</em>. Básicamente, tendríamos que introducir un parser capaz de descomponer el string en los elementos que necesitamos. Creo que esto se escapa un poco del objetivo del artículo por lo que voy a poner una idea muy simple que puede servir como ilustración de la idea.</p>

<p>En cualquier caso, al movernos a <em>Value Object</em> podríamos encapsular esas transformaciones en el código del objeto.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nx">Address</span> <span class="p">{</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="nx">street</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="nx">city</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="nx">zip</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="nx">country</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>

    <span class="k">private</span> <span class="kd">constructor</span><span class="p">(</span><span class="nx">street</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">city</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">zip</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">country</span><span class="p">:</span> <span class="kr">string</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">street</span> <span class="o">=</span> <span class="nx">street</span><span class="p">;</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">city</span> <span class="o">=</span> <span class="nx">city</span><span class="p">;</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">zip</span> <span class="o">=</span> <span class="nx">zip</span><span class="p">;</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">country</span> <span class="o">=</span> <span class="nx">country</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">static</span> <span class="nx">parse</span><span class="p">(</span><span class="nx">address</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="nx">Address</span> <span class="p">{</span>
        <span class="kd">const</span> <span class="nx">parts</span> <span class="o">=</span> <span class="nx">address</span><span class="p">.</span><span class="nx">split</span><span class="p">(</span><span class="dl">'</span><span class="s1">, </span><span class="dl">'</span><span class="p">);</span>
        <span class="k">if</span> <span class="p">(</span><span class="nx">parts</span><span class="p">.</span><span class="nx">length</span> <span class="o">!==</span> <span class="mi">4</span><span class="p">)</span> <span class="p">{</span>
            <span class="k">throw</span> <span class="k">new</span> <span class="nb">Error</span><span class="p">(</span><span class="dl">'</span><span class="s1">Invalid address format. Expected: street, city, zip, country</span><span class="dl">'</span><span class="p">);</span>
        <span class="p">}</span>
        <span class="kd">const</span> <span class="p">[</span><span class="nx">street</span><span class="p">,</span> <span class="nx">city</span><span class="p">,</span> <span class="nx">zip</span><span class="p">,</span> <span class="nx">country</span><span class="p">]</span> <span class="o">=</span> <span class="nx">parts</span><span class="p">;</span>
        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">street</span> <span class="o">||</span> <span class="o">!</span><span class="nx">city</span> <span class="o">||</span> <span class="o">!</span><span class="nx">zip</span> <span class="o">||</span> <span class="o">!</span><span class="nx">country</span><span class="p">)</span> <span class="p">{</span>
            <span class="k">throw</span> <span class="k">new</span> <span class="nb">Error</span><span class="p">(</span><span class="dl">'</span><span class="s1">All address components are required</span><span class="dl">'</span><span class="p">);</span>
        <span class="p">}</span>
        <span class="k">return</span> <span class="k">new</span> <span class="nx">Address</span><span class="p">(</span><span class="nx">street</span><span class="p">,</span> <span class="nx">city</span><span class="p">,</span> <span class="nx">zip</span><span class="p">,</span> <span class="nx">country</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Adicionalmente, la también podríamos extender <code class="language-plaintext highlighter-rouge">Address</code> para dar soporte a distintos tipos de direcciones según el país.</p>

<h4 id="customername">CustomerName</h4>

<p>Para acabar, podemos introducir un Value Object para representar el nombre del cliente. Por lo general, los nombres de cliente suelen ser conceptos compuestos de, al menos, nombre y apellido, con algunas variaciones dependiendo del país de origen.</p>

<p>Aparte de eso, un nombre de cliente nunca debería estar vacío. De nuevo tenemos una propiedad que un string nunca puede cumplir, ya que un string vacío es perfectamente aceptable. Es justamente este tipo de cuestiones por las que deberíamos dejar de usar tipos primitivos para representar conceptos que requieran una mínima validación para poder ser consistentes con las reglas de negocio.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nx">CustomerName</span> <span class="p">{</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="nx">name</span><span class="p">:</span> <span class="kr">string</span>
    <span class="k">private</span> <span class="kd">constructor</span><span class="p">(</span><span class="nx">name</span><span class="p">:</span> <span class="kr">string</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">name</span> <span class="o">=</span> <span class="nx">name</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="k">static</span> <span class="nx">valid</span><span class="p">(</span><span class="nx">name</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="nx">CustomerName</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">name</span><span class="p">)</span> <span class="p">{</span>
            <span class="k">throw</span> <span class="k">new</span> <span class="nb">Error</span><span class="p">(</span><span class="dl">'</span><span class="s1">Customer name cannot be empty</span><span class="dl">'</span><span class="p">)</span>
        <span class="p">}</span>
        <span class="k">return</span> <span class="k">new</span> <span class="nx">CustomerName</span><span class="p">(</span><span class="nx">name</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="conclusiones">Conclusiones</h2>

<p><em>Primitive Obsession</em> es un <em>code smell</em> muy común en el que es muy fácil caer. Tiene un refactor característico que es <em>Introduce Value Object</em>.</p>

<p>El problema de usar tipos primitivos es que no pueden representar por sí solos los conceptos de dominio que necesitamos. Incluso, en el caso más simple, como podría ser que una cadena de caracteres no puede estar vacía. Los tipos primitivos son completamente genéricos y aunque podemos usarlos para almacenar las propiedades de los conceptos, necesitamos encapsularlos en objetos para poder aplicar reglas de negocio.</p>]]></content><author><name></name></author><category term="articles" /><category term="code-smells" /><category term="refactoring" /><category term="typescript" /><summary type="html"><![CDATA[Primitive Obsession es el último de los bloaters, una categoría de code smells que se caracterizan por hacer que nuestro código sea grande y complejo, introduciendo muchas oportunidades para la aparición de bugs y comportamiento inconsistente.]]></summary></entry><entry><title type="html">Long Parameter List</title><link href="https://franiglesias.github.io/long-parameter-list/" rel="alternate" type="text/html" title="Long Parameter List" /><published>2025-11-11T00:00:00+00:00</published><updated>2025-11-11T00:00:00+00:00</updated><id>https://franiglesias.github.io/long-parameter-list</id><content type="html" xml:base="https://franiglesias.github.io/long-parameter-list/"><![CDATA[<p>Otro <em>smell</em> de la familia de los <em>Bloaters</em>. <em>Long Parameter List</em> ocurre cuando una función o método recibe más de tres ó cuatro parámetros.</p>

<h2 id="definición">Definición</h2>

<p>Una función o método recibe más de tres o cuatro parámetros. Un número alto de parámetros en la firma de una función sobrecarga nuestra memoria de trabajo, dificultando que podamos recordar con precisión cuáles son esos parámetros, su orden o su tipo.</p>

<p>A medida que una función requiere lograr más flexibilidad o contemplar nuevos casos, es posible que necesitemos pasarle más información, por lo que añadimos más parámetros. Sin embargo, al mismo tiempo, añadimos más oportunidades para los errores y dificultamos su mantenimiento en el futuro.</p>

<h2 id="ejemplo">Ejemplo</h2>

<p>El siguiente generador de informes recibe numerosos parámetros, que tienen distintos significados y usos.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nx">ReportGenerator</span> <span class="p">{</span>
  <span class="nx">generateReport</span><span class="p">(</span>
    <span class="nx">title</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
    <span class="nx">startDate</span><span class="p">:</span> <span class="nb">Date</span><span class="p">,</span>
    <span class="nx">endDate</span><span class="p">:</span> <span class="nb">Date</span><span class="p">,</span>
    <span class="nx">includeCharts</span><span class="p">:</span> <span class="nx">boolean</span><span class="p">,</span>
    <span class="nx">includeSummary</span><span class="p">:</span> <span class="nx">boolean</span><span class="p">,</span>
    <span class="nx">authorName</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
    <span class="nx">authorEmail</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
  <span class="p">)</span> <span class="p">{</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`Generando reporte: </span><span class="p">${</span><span class="nx">title</span><span class="p">}</span><span class="s2">`</span><span class="p">)</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`Desde </span><span class="p">${</span><span class="nx">startDate</span><span class="p">.</span><span class="nx">toDateString</span><span class="p">()}</span><span class="s2"> hasta </span><span class="p">${</span><span class="nx">endDate</span><span class="p">.</span><span class="nx">toDateString</span><span class="p">()}</span><span class="s2">`</span><span class="p">)</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`Autor: </span><span class="p">${</span><span class="nx">authorName</span><span class="p">}</span><span class="s2"> (</span><span class="p">${</span><span class="nx">authorEmail</span><span class="p">}</span><span class="s2">)`</span><span class="p">)</span>
    <span class="k">if</span> <span class="p">(</span><span class="nx">includeCharts</span><span class="p">)</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Incluyendo gráficos...</span><span class="dl">'</span><span class="p">)</span>
    <span class="k">if</span> <span class="p">(</span><span class="nx">includeSummary</span><span class="p">)</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Incluyendo resumen...</span><span class="dl">'</span><span class="p">)</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Reporte generado exitosamente.</span><span class="dl">'</span><span class="p">)</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="k">export</span> <span class="kd">function</span> <span class="nx">demoLongParameterList</span><span class="p">():</span> <span class="k">void</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">gen</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">ReportGenerator</span><span class="p">()</span>
  <span class="nx">gen</span><span class="p">.</span><span class="nx">generateReport</span><span class="p">(</span>
    <span class="dl">'</span><span class="s1">Ventas Q1</span><span class="dl">'</span><span class="p">,</span>
    <span class="k">new</span> <span class="nb">Date</span><span class="p">(</span><span class="dl">'</span><span class="s1">2025-01-01</span><span class="dl">'</span><span class="p">),</span>
    <span class="k">new</span> <span class="nb">Date</span><span class="p">(</span><span class="dl">'</span><span class="s1">2025-03-31</span><span class="dl">'</span><span class="p">),</span>
    <span class="kc">true</span><span class="p">,</span>
    <span class="kc">false</span><span class="p">,</span>
    <span class="dl">'</span><span class="s1">Pat Smith</span><span class="dl">'</span><span class="p">,</span>
    <span class="dl">'</span><span class="s1">pat@example.com</span><span class="dl">'</span><span class="p">,</span>
  <span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="ejercicio">Ejercicio</h2>

<p>Supongamos que necesitamos añadir dos opciones más al reporte. Por ejemplo, locale para traducciones y un tamaño de página.</p>

<h2 id="problemas-que-encontrarás">Problemas que encontrarás</h2>

<p>Añadir parámetros es fácil en el momento. El problema viene cuando tenemos que modificar tests o llamadas en diversos puntos del código.</p>

<h2 id="solución">Solución</h2>

<h3 id="sin-resolver-el-smell">Sin resolver el smell</h3>

<p>Pues es tan simple como añadir los parámetros que nos piden y empezar a usarlos.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nx">ReportGenerator</span> <span class="p">{</span>
  <span class="nx">generateReport</span><span class="p">(</span>
    <span class="nx">title</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
    <span class="nx">startDate</span><span class="p">:</span> <span class="nb">Date</span><span class="p">,</span>
    <span class="nx">endDate</span><span class="p">:</span> <span class="nb">Date</span><span class="p">,</span>
    <span class="nx">includeCharts</span><span class="p">:</span> <span class="nx">boolean</span><span class="p">,</span>
    <span class="nx">includeSummary</span><span class="p">:</span> <span class="nx">boolean</span><span class="p">,</span>
    <span class="nx">authorName</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
    <span class="nx">authorEmail</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
    <span class="nx">locale</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
    <span class="nx">pageSize</span><span class="p">:</span> <span class="kr">string</span>
  <span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<p>El problema es que, a estas alturas, ya tenemos nueve parámetros. Y, por otro lado, ¿qué pasa si este método se usa en numerosos lugares de nuestro código?</p>

<p>Una opción es añadir los nuevos parámetros como opcionales, con valores por defecto adecuados. Esto resuelve el problema en el corto plazo, pero no hace más que aumentar el coste de cambio futuro.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">class</span> <span class="nx">ReportGenerator</span> <span class="p">{</span>
    <span class="nx">generateReport</span><span class="p">(</span>
        <span class="nx">title</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
        <span class="nx">startDate</span><span class="p">:</span> <span class="nb">Date</span><span class="p">,</span>
        <span class="nx">endDate</span><span class="p">:</span> <span class="nb">Date</span><span class="p">,</span>
        <span class="nx">includeCharts</span><span class="p">:</span> <span class="nx">boolean</span><span class="p">,</span>
        <span class="nx">includeSummary</span><span class="p">:</span> <span class="nx">boolean</span><span class="p">,</span>
        <span class="nx">authorName</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
        <span class="nx">authorEmail</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
        <span class="nx">locale</span><span class="p">:</span> <span class="kr">string</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">es-ES</span><span class="dl">'</span><span class="p">,</span>
        <span class="nx">pageSize</span><span class="p">:</span> <span class="kr">string</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">A4</span><span class="dl">'</span><span class="p">,</span>
    <span class="p">)</span> <span class="p">{</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`Generando reporte: </span><span class="p">${</span><span class="nx">title</span><span class="p">}</span><span class="s2">`</span><span class="p">)</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`Desde </span><span class="p">${</span><span class="nx">startDate</span><span class="p">.</span><span class="nx">toDateString</span><span class="p">()}</span><span class="s2"> hasta </span><span class="p">${</span><span class="nx">endDate</span><span class="p">.</span><span class="nx">toDateString</span><span class="p">()}</span><span class="s2">`</span><span class="p">)</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`Autor: </span><span class="p">${</span><span class="nx">authorName</span><span class="p">}</span><span class="s2"> (</span><span class="p">${</span><span class="nx">authorEmail</span><span class="p">}</span><span class="s2">)`</span><span class="p">)</span>
        <span class="k">if</span> <span class="p">(</span><span class="nx">includeCharts</span><span class="p">)</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Incluyendo gráficos...</span><span class="dl">'</span><span class="p">)</span>
        <span class="k">if</span> <span class="p">(</span><span class="nx">includeSummary</span><span class="p">)</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Incluyendo resumen...</span><span class="dl">'</span><span class="p">)</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Reporte generado exitosamente.</span><span class="dl">'</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="resolviendo-el-smell">Resolviendo el smell</h3>

<h4 id="testing">Testing</h4>

<p>Empecemos haciendo un test de caracterización del código actual:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="p">{</span><span class="nx">describe</span><span class="p">,</span> <span class="nx">expect</span><span class="p">,</span> <span class="nx">it</span><span class="p">,</span> <span class="nx">vi</span><span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">vitest</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span><span class="nx">ReportGenerator</span><span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./long-parameter-list</span><span class="dl">"</span><span class="p">;</span>

<span class="kd">function</span> <span class="nx">formatConsoleCalls</span><span class="p">(</span><span class="nx">spy</span><span class="p">:</span> <span class="nx">ReturnType</span><span class="o">&lt;</span><span class="k">typeof</span> <span class="nx">vi</span><span class="p">.</span><span class="nx">spyOn</span><span class="o">&gt;</span><span class="p">)</span> <span class="p">{</span>
  <span class="k">return</span> <span class="nx">spy</span><span class="p">.</span><span class="nx">mock</span><span class="p">.</span><span class="nx">calls</span><span class="p">.</span><span class="nx">map</span><span class="p">((</span><span class="nx">call</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">call</span><span class="p">.</span><span class="nx">join</span><span class="p">(</span><span class="dl">'</span><span class="s1"> </span><span class="dl">'</span><span class="p">)).</span><span class="nx">join</span><span class="p">(</span><span class="dl">'</span><span class="se">\n</span><span class="dl">'</span><span class="p">)</span>
<span class="p">}</span>

<span class="nx">describe</span><span class="p">(</span><span class="dl">'</span><span class="s1">Long Parameter List</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="nx">it</span><span class="p">(</span><span class="dl">'</span><span class="s1">Generates a report</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">logSpy</span> <span class="o">=</span> <span class="nx">vi</span><span class="p">.</span><span class="nx">spyOn</span><span class="p">(</span><span class="nx">console</span><span class="p">,</span> <span class="dl">'</span><span class="s1">log</span><span class="dl">'</span><span class="p">)</span>

    <span class="kd">const</span> <span class="nx">gen</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">ReportGenerator</span><span class="p">()</span>
      <span class="nx">gen</span><span class="p">.</span><span class="nx">generateReport</span><span class="p">(</span>
        <span class="dl">'</span><span class="s1">Ventas Q1</span><span class="dl">'</span><span class="p">,</span>
        <span class="k">new</span> <span class="nb">Date</span><span class="p">(</span><span class="dl">'</span><span class="s1">2025-01-01</span><span class="dl">'</span><span class="p">),</span>
        <span class="k">new</span> <span class="nb">Date</span><span class="p">(</span><span class="dl">'</span><span class="s1">2025-03-31</span><span class="dl">'</span><span class="p">),</span>
        <span class="kc">true</span><span class="p">,</span>
        <span class="kc">false</span><span class="p">,</span>
        <span class="dl">'</span><span class="s1">Pat Smith</span><span class="dl">'</span><span class="p">,</span>
        <span class="dl">'</span><span class="s1">pat@example.com</span><span class="dl">'</span><span class="p">,</span>
      <span class="p">)</span>

    <span class="kd">let</span> <span class="nx">output</span> <span class="o">=</span> <span class="nx">formatConsoleCalls</span><span class="p">(</span><span class="nx">logSpy</span><span class="p">)</span>
    <span class="nx">expect</span><span class="p">(</span><span class="nx">output</span><span class="p">).</span><span class="nx">toMatchSnapshot</span><span class="p">()</span>
  <span class="p">})</span>
<span class="p">})</span>
</code></pre></div></div>

<p>Nuestro problema es conseguir reducir la carga cognitiva de tener que usar siete parámetros en la función <code class="language-plaintext highlighter-rouge">generateReport</code> y, posiblemente, aumentarlos a nueve, o puede que incluso más en el futuro. En realidad el refactor básico es bastante fácil, pero lo que puede complicarlo es compatibilizar el cambio de firma con sus usos actuales. Por eso, vamos a usar técnicas que nos permitan una migración fácil, a la vez que introducimos el soporte para nuevas opciones.</p>

<p>En esta ocasión usaremos refactors como <em>Introduce Parameter Object</em> o <em>Introduce Value Object</em>, aplicaremos el patrón <em>Builder</em> con <em>Fluent Interface</em> y para lograrlo usaremos algunas técnicas de cambio en paralelo.</p>

<h4 id="introduce-parameter-object">Introduce Parameter Object</h4>

<p>El refactor Introduce Parameter Object es tan sencillo como crear un tipo de objeto que agrupe todos los parámetros que necesitamos. Ahora bien: ¿hasta qué punto esto nos resuelve un problema o estamos moviendo el problema a otro lugar?</p>

<p>Definimos el tipo. En este caso, lo hacemos mediante una interfaz, que es una forma bastante idiomática de definir tipos en TypeScript.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kr">interface</span> <span class="nx">GenerateReport</span> <span class="p">{</span>
    <span class="nl">title</span><span class="p">:</span> <span class="kr">string</span>
    <span class="nx">startDate</span><span class="p">:</span> <span class="nb">Date</span>
    <span class="nx">endDate</span><span class="p">:</span> <span class="nb">Date</span>
    <span class="nx">includeCharts</span><span class="p">:</span> <span class="nx">boolean</span>
    <span class="nx">includeSummary</span><span class="p">:</span> <span class="nx">boolean</span>
    <span class="nx">authorName</span><span class="p">:</span> <span class="kr">string</span>
    <span class="nx">authorEmail</span><span class="p">:</span> <span class="kr">string</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Y aquí lo instanciamos:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">generateReport</span> <span class="o">=</span> <span class="p">{</span>
    <span class="na">title</span><span class="p">:</span> <span class="dl">'</span><span class="s1">Ventas Q1</span><span class="dl">'</span><span class="p">,</span>
    <span class="na">startDate</span><span class="p">:</span> <span class="k">new</span> <span class="nb">Date</span><span class="p">(</span><span class="dl">'</span><span class="s1">2025-01-01</span><span class="dl">'</span><span class="p">),</span>
    <span class="na">endDate</span><span class="p">:</span> <span class="k">new</span> <span class="nb">Date</span><span class="p">(</span><span class="dl">'</span><span class="s1">2025-03-31</span><span class="dl">'</span><span class="p">),</span>
    <span class="na">includeCharts</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
    <span class="na">includeSummary</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
    <span class="na">authorName</span><span class="p">:</span> <span class="dl">'</span><span class="s1">Pat Smith</span><span class="dl">'</span><span class="p">,</span>
    <span class="na">authorEmail</span><span class="p">:</span> <span class="dl">'</span><span class="s1">pat@example.com</span><span class="dl">'</span>
<span class="p">}</span> <span class="k">as</span> <span class="nx">GenerateReport</span>
</code></pre></div></div>

<p>En principio, la ventaja aquí es que el objeto nos permite despreocuparnos del orden de los parámetros y que tenemos explícito su nombre, lo que facilita entender la información que tenemos que pasar. Nos quedaría incorporar la nueva firma y lo tendríamos listo. Esto puede ser suficiente para una gran mayoría de casos.</p>

<p>Déjame darle una vuelta más:</p>

<h4 id="agrupar-parámetros">Agrupar parámetros</h4>

<p>Si echamos un vistazo al código podemos ver que hay dos tipos de parámetros:</p>

<ul>
  <li>Los que se usan para generar el reporte indicando contenido que debe mostrarse, como: <code class="language-plaintext highlighter-rouge">title</code>, <code class="language-plaintext highlighter-rouge">startDate</code>, <code class="language-plaintext highlighter-rouge">endDate</code>, <code class="language-plaintext highlighter-rouge">authorName</code>, <code class="language-plaintext highlighter-rouge">authorEmail</code>.</li>
  <li>Los que se usan para personalizar el reporte, como opciones que lo configuran, como: <code class="language-plaintext highlighter-rouge">includeCharts</code>, <code class="language-plaintext highlighter-rouge">includeSummary</code>.</li>
</ul>

<p>En el código podemos ver que se usan incluso de distinta forma: los relacionados con el contenido acaban interpolándose, mientras que los de personalización se usan directamente.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nx">ReportGenerator</span> <span class="p">{</span>
  <span class="nx">generateReport</span><span class="p">(</span>
    <span class="nx">title</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
    <span class="nx">startDate</span><span class="p">:</span> <span class="nb">Date</span><span class="p">,</span>
    <span class="nx">endDate</span><span class="p">:</span> <span class="nb">Date</span><span class="p">,</span>
    <span class="nx">includeCharts</span><span class="p">:</span> <span class="nx">boolean</span><span class="p">,</span>
    <span class="nx">includeSummary</span><span class="p">:</span> <span class="nx">boolean</span><span class="p">,</span>
    <span class="nx">authorName</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
    <span class="nx">authorEmail</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
  <span class="p">)</span> <span class="p">{</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`Generando reporte: </span><span class="p">${</span><span class="nx">title</span><span class="p">}</span><span class="s2">`</span><span class="p">)</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`Desde </span><span class="p">${</span><span class="nx">startDate</span><span class="p">.</span><span class="nx">toDateString</span><span class="p">()}</span><span class="s2"> hasta </span><span class="p">${</span><span class="nx">endDate</span><span class="p">.</span><span class="nx">toDateString</span><span class="p">()}</span><span class="s2">`</span><span class="p">)</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`Autor: </span><span class="p">${</span><span class="nx">authorName</span><span class="p">}</span><span class="s2"> (</span><span class="p">${</span><span class="nx">authorEmail</span><span class="p">}</span><span class="s2">)`</span><span class="p">)</span>
    <span class="k">if</span> <span class="p">(</span><span class="nx">includeCharts</span><span class="p">)</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Incluyendo gráficos...</span><span class="dl">'</span><span class="p">)</span>
    <span class="k">if</span> <span class="p">(</span><span class="nx">includeSummary</span><span class="p">)</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Incluyendo resumen...</span><span class="dl">'</span><span class="p">)</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Reporte generado exitosamente.</span><span class="dl">'</span><span class="p">)</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Personalmente, me parece que los de configuración tiene sentido que se pasen en construcción, mientras que los de contenido se pasan en la llamada.</p>

<p>Pero es que, además, algunos de los parámetros están relacionados entre sí. Por ejemplo, <code class="language-plaintext highlighter-rouge">startDate</code> y <code class="language-plaintext highlighter-rouge">endDate</code> definen un rango de fechas, y los datos del autor <code class="language-plaintext highlighter-rouge">authorName</code> y <code class="language-plaintext highlighter-rouge">authorEmail</code> forman también una unidad.</p>

<h4 id="introduce-value-object">Introduce Value Object</h4>

<p>Como vimos al hablar de <a href="/data-clump/">Data Clump</a> datos que viajan siempre juntos suelen formar parte de un mismo concepto, por lo que es frecuente que los podamos agrupar en un Value Object, lo cual nos ayudará tanto a reducir la carga cognitiva de la firma de la función, como a introducir el concepto en nuestra base de código.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">class</span> <span class="nx">DateRange</span> <span class="p">{</span>
  <span class="k">private</span> <span class="nx">startDate</span><span class="p">:</span> <span class="nb">Date</span>
  <span class="k">private</span> <span class="nx">endDate</span><span class="p">:</span> <span class="nb">Date</span>

  <span class="kd">constructor</span><span class="p">(</span>
    <span class="nx">startDate</span><span class="p">:</span> <span class="nb">Date</span><span class="p">,</span>
    <span class="nx">endDate</span><span class="p">:</span> <span class="nb">Date</span><span class="p">,</span>
  <span class="p">)</span> <span class="p">{</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">endDate</span> <span class="o">=</span> <span class="nx">endDate</span><span class="p">;</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">startDate</span> <span class="o">=</span> <span class="nx">startDate</span><span class="p">;</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="k">export</span> <span class="kd">class</span> <span class="nx">Author</span> <span class="p">{</span>
  <span class="k">private</span> <span class="nx">name</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
  <span class="k">private</span> <span class="nx">email</span><span class="p">:</span> <span class="kr">string</span>
  
  <span class="kd">constructor</span><span class="p">(</span><span class="nx">name</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">email</span><span class="p">:</span> <span class="kr">string</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">name</span> <span class="o">=</span> <span class="nx">name</span><span class="p">;</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">email</span> <span class="o">=</span> <span class="nx">email</span><span class="p">;</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h4 id="cambiar-la-firma-de-un-método-sin-romper-la-compatibilidad">Cambiar la firma de un método sin romper la compatibilidad</h4>

<p>Cambiar la firma de un método manteniendo la compatibilidad hacia atrás se hace básicamente… no cambiando la firma del método.</p>

<p>En su lugar, lo que haremos es introducir un nuevo método con la firma deseada y hacer que el viejo lo use. La idea es conservar la firma <em>deprecada</em>, que puede ser llamado desde distintas partes del código, a la vez que ofrecemos la nueva interfaz que podemos empezar a usar progresivamente, sin romper nada.</p>

<p>Veamos como queda la firma de <code class="language-plaintext highlighter-rouge">generateReport</code> con los Value Objects. Introduciremos un nuevo método llamado simplemente <code class="language-plaintext highlighter-rouge">generate</code>, ya que el nombre del original es bastante redundante:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">class</span> <span class="nx">ReportGenerator</span> <span class="p">{</span>
    <span class="nx">generateReport</span><span class="p">(</span>
        <span class="nx">title</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
        <span class="nx">startDate</span><span class="p">:</span> <span class="nb">Date</span><span class="p">,</span>
        <span class="nx">endDate</span><span class="p">:</span> <span class="nb">Date</span><span class="p">,</span>
        <span class="nx">includeCharts</span><span class="p">:</span> <span class="nx">boolean</span><span class="p">,</span>
        <span class="nx">includeSummary</span><span class="p">:</span> <span class="nx">boolean</span><span class="p">,</span>
        <span class="nx">authorName</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
        <span class="nx">authorEmail</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
    <span class="p">)</span> <span class="p">{</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`Generando reporte: </span><span class="p">${</span><span class="nx">title</span><span class="p">}</span><span class="s2">`</span><span class="p">)</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`Desde </span><span class="p">${</span><span class="nx">startDate</span><span class="p">.</span><span class="nx">toDateString</span><span class="p">()}</span><span class="s2"> hasta </span><span class="p">${</span><span class="nx">endDate</span><span class="p">.</span><span class="nx">toDateString</span><span class="p">()}</span><span class="s2">`</span><span class="p">)</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`Autor: </span><span class="p">${</span><span class="nx">authorName</span><span class="p">}</span><span class="s2"> (</span><span class="p">${</span><span class="nx">authorEmail</span><span class="p">}</span><span class="s2">)`</span><span class="p">)</span>
        <span class="k">if</span> <span class="p">(</span><span class="nx">includeCharts</span><span class="p">)</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Incluyendo gráficos...</span><span class="dl">'</span><span class="p">)</span>
        <span class="k">if</span> <span class="p">(</span><span class="nx">includeSummary</span><span class="p">)</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Incluyendo resumen...</span><span class="dl">'</span><span class="p">)</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Reporte generado exitosamente.</span><span class="dl">'</span><span class="p">)</span>
    <span class="p">}</span>

    <span class="nx">generate</span><span class="p">(</span><span class="nx">title</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>  <span class="nx">range</span><span class="p">:</span> <span class="nx">DateRange</span><span class="p">,</span> <span class="nx">author</span><span class="p">:</span> <span class="nx">Author</span><span class="p">,</span> <span class="nx">includeCharts</span><span class="p">:</span> <span class="nx">boolean</span><span class="p">,</span> <span class="nx">includeSummary</span><span class="p">:</span> <span class="nx">boolean</span><span class="p">)</span> <span class="p">{</span>
        <span class="c1">// no-op</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Aquí tenemos el test modificado para usar el nuevo método. Como es de esperar, el nuevo test no pasará.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">describe</span><span class="p">(</span><span class="dl">'</span><span class="s1">Long Parameter List</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="nx">it</span><span class="p">(</span><span class="dl">'</span><span class="s1">Generates a report</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
        <span class="kd">const</span> <span class="nx">logSpy</span> <span class="o">=</span> <span class="nx">vi</span><span class="p">.</span><span class="nx">spyOn</span><span class="p">(</span><span class="nx">console</span><span class="p">,</span> <span class="dl">'</span><span class="s1">log</span><span class="dl">'</span><span class="p">)</span>

        <span class="kd">const</span> <span class="nx">gen</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">ReportGenerator</span><span class="p">()</span>
        <span class="nx">gen</span><span class="p">.</span><span class="nx">generateReport</span><span class="p">(</span>
            <span class="dl">'</span><span class="s1">Ventas Q1</span><span class="dl">'</span><span class="p">,</span>
            <span class="k">new</span> <span class="nb">Date</span><span class="p">(</span><span class="dl">'</span><span class="s1">2025-01-01</span><span class="dl">'</span><span class="p">),</span>
            <span class="k">new</span> <span class="nb">Date</span><span class="p">(</span><span class="dl">'</span><span class="s1">2025-03-31</span><span class="dl">'</span><span class="p">),</span>
            <span class="kc">true</span><span class="p">,</span>
            <span class="kc">false</span><span class="p">,</span>
            <span class="dl">'</span><span class="s1">Pat Smith</span><span class="dl">'</span><span class="p">,</span>
            <span class="dl">'</span><span class="s1">pat@example.com</span><span class="dl">'</span><span class="p">,</span>
        <span class="p">)</span>

        <span class="kd">let</span> <span class="nx">output</span> <span class="o">=</span> <span class="nx">formatConsoleCalls</span><span class="p">(</span><span class="nx">logSpy</span><span class="p">)</span>
        <span class="nx">expect</span><span class="p">(</span><span class="nx">output</span><span class="p">).</span><span class="nx">toMatchSnapshot</span><span class="p">()</span>
    <span class="p">})</span>

    <span class="nx">it</span><span class="p">(</span><span class="dl">'</span><span class="s1">Generates a report with Value Objects</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
        <span class="kd">const</span> <span class="nx">logSpy</span> <span class="o">=</span> <span class="nx">vi</span><span class="p">.</span><span class="nx">spyOn</span><span class="p">(</span><span class="nx">console</span><span class="p">,</span> <span class="dl">'</span><span class="s1">log</span><span class="dl">'</span><span class="p">)</span>

        <span class="kd">const</span> <span class="nx">range</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">DateRange</span><span class="p">(</span>
            <span class="k">new</span> <span class="nb">Date</span><span class="p">(</span><span class="dl">'</span><span class="s1">2025-01-01</span><span class="dl">'</span><span class="p">),</span>
            <span class="k">new</span> <span class="nb">Date</span><span class="p">(</span><span class="dl">'</span><span class="s1">2025-03-31</span><span class="dl">'</span><span class="p">)</span>
        <span class="p">)</span>

        <span class="kd">const</span> <span class="nx">author</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Author</span><span class="p">(</span>
            <span class="dl">'</span><span class="s1">Pat Smith</span><span class="dl">'</span><span class="p">,</span>
            <span class="dl">'</span><span class="s1">pat@example.com</span><span class="dl">'</span><span class="p">,</span>
        <span class="p">)</span>

        <span class="kd">const</span> <span class="nx">gen</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">ReportGenerator</span><span class="p">()</span>
        <span class="nx">gen</span><span class="p">.</span><span class="nx">generate</span><span class="p">(</span>
            <span class="dl">'</span><span class="s1">Ventas Q1</span><span class="dl">'</span><span class="p">,</span>
            <span class="nx">range</span><span class="p">,</span>
            <span class="nx">author</span><span class="p">,</span>
            <span class="kc">true</span><span class="p">,</span>
            <span class="kc">false</span><span class="p">,</span>
        <span class="p">)</span>

        <span class="kd">let</span> <span class="nx">output</span> <span class="o">=</span> <span class="nx">formatConsoleCalls</span><span class="p">(</span><span class="nx">logSpy</span><span class="p">)</span>
        <span class="nx">expect</span><span class="p">(</span><span class="nx">output</span><span class="p">).</span><span class="nx">toMatchSnapshot</span><span class="p">()</span>
    <span class="p">})</span>
<span class="p">})</span>
</code></pre></div></div>

<p>Ahora, vamos a copiar el cuerpo de <code class="language-plaintext highlighter-rouge">generateReport</code> en <code class="language-plaintext highlighter-rouge">generate</code> y cambiaremos lo necesario para que pueda funcionar, ya que no es compatible con la firma actual. Esto require introducir algunos métodos en los Value Objects. Empezamos así:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">class</span> <span class="nx">ReportGenerator</span> <span class="p">{</span>
    <span class="nx">generateReport</span><span class="p">(</span>
        <span class="nx">title</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
        <span class="nx">startDate</span><span class="p">:</span> <span class="nb">Date</span><span class="p">,</span>
        <span class="nx">endDate</span><span class="p">:</span> <span class="nb">Date</span><span class="p">,</span>
        <span class="nx">includeCharts</span><span class="p">:</span> <span class="nx">boolean</span><span class="p">,</span>
        <span class="nx">includeSummary</span><span class="p">:</span> <span class="nx">boolean</span><span class="p">,</span>
        <span class="nx">authorName</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
        <span class="nx">authorEmail</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
    <span class="p">)</span> <span class="p">{</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`Generando reporte: </span><span class="p">${</span><span class="nx">title</span><span class="p">}</span><span class="s2">`</span><span class="p">)</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`Desde </span><span class="p">${</span><span class="nx">startDate</span><span class="p">.</span><span class="nx">toDateString</span><span class="p">()}</span><span class="s2"> hasta </span><span class="p">${</span><span class="nx">endDate</span><span class="p">.</span><span class="nx">toDateString</span><span class="p">()}</span><span class="s2">`</span><span class="p">)</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`Autor: </span><span class="p">${</span><span class="nx">authorName</span><span class="p">}</span><span class="s2"> (</span><span class="p">${</span><span class="nx">authorEmail</span><span class="p">}</span><span class="s2">)`</span><span class="p">)</span>
        <span class="k">if</span> <span class="p">(</span><span class="nx">includeCharts</span><span class="p">)</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Incluyendo gráficos...</span><span class="dl">'</span><span class="p">)</span>
        <span class="k">if</span> <span class="p">(</span><span class="nx">includeSummary</span><span class="p">)</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Incluyendo resumen...</span><span class="dl">'</span><span class="p">)</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Reporte generado exitosamente.</span><span class="dl">'</span><span class="p">)</span>
    <span class="p">}</span>

    <span class="nx">generate</span><span class="p">(</span><span class="nx">title</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>  <span class="nx">range</span><span class="p">:</span> <span class="nx">DateRange</span><span class="p">,</span> <span class="nx">author</span><span class="p">:</span> <span class="nx">Author</span><span class="p">,</span> <span class="nx">includeCharts</span><span class="p">:</span> <span class="nx">boolean</span><span class="p">,</span> <span class="nx">includeSummary</span><span class="p">:</span> <span class="nx">boolean</span><span class="p">)</span> <span class="p">{</span> <span class="p">{</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`Generando reporte: </span><span class="p">${</span><span class="nx">title</span><span class="p">}</span><span class="s2">`</span><span class="p">)</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`Desde </span><span class="p">${</span><span class="nx">startDate</span><span class="p">.</span><span class="nx">toDateString</span><span class="p">()}</span><span class="s2"> hasta </span><span class="p">${</span><span class="nx">endDate</span><span class="p">.</span><span class="nx">toDateString</span><span class="p">()}</span><span class="s2">`</span><span class="p">)</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`Autor: </span><span class="p">${</span><span class="nx">authorName</span><span class="p">}</span><span class="s2"> (</span><span class="p">${</span><span class="nx">authorEmail</span><span class="p">}</span><span class="s2">)`</span><span class="p">)</span>
        <span class="k">if</span> <span class="p">(</span><span class="nx">includeCharts</span><span class="p">)</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Incluyendo gráficos...</span><span class="dl">'</span><span class="p">)</span>
        <span class="k">if</span> <span class="p">(</span><span class="nx">includeSummary</span><span class="p">)</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Incluyendo resumen...</span><span class="dl">'</span><span class="p">)</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Reporte generado exitosamente.</span><span class="dl">'</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Estos cambios en los Value Objects:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">class</span> <span class="nx">DateRange</span> <span class="p">{</span>
    <span class="k">private</span> <span class="nx">startDate</span><span class="p">:</span> <span class="nb">Date</span>
    <span class="k">private</span> <span class="nx">endDate</span><span class="p">:</span> <span class="nb">Date</span>

    <span class="kd">constructor</span><span class="p">(</span>
        <span class="nx">startDate</span><span class="p">:</span> <span class="nb">Date</span><span class="p">,</span>
        <span class="nx">endDate</span><span class="p">:</span> <span class="nb">Date</span><span class="p">,</span>
    <span class="p">)</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">startDate</span> <span class="o">=</span> <span class="nx">startDate</span><span class="p">;</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">endDate</span> <span class="o">=</span> <span class="nx">endDate</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="nx">print</span><span class="p">(</span><span class="nx">template</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="kr">string</span> <span class="p">{</span>
        <span class="k">return</span> <span class="nx">template</span>
            <span class="p">.</span><span class="nx">replace</span><span class="p">(</span><span class="dl">''</span><span class="p">,</span> <span class="k">this</span><span class="p">.</span><span class="nx">startDate</span><span class="p">.</span><span class="nx">toDateString</span><span class="p">())</span>
            <span class="p">.</span><span class="nx">replace</span><span class="p">(</span><span class="dl">''</span><span class="p">,</span> <span class="k">this</span><span class="p">.</span><span class="nx">endDate</span><span class="p">.</span><span class="nx">toDateString</span><span class="p">());</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="k">export</span> <span class="kd">class</span> <span class="nx">Author</span> <span class="p">{</span>
    <span class="k">private</span> <span class="nx">name</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
    <span class="k">private</span> <span class="nx">email</span><span class="p">:</span> <span class="kr">string</span>

    <span class="kd">constructor</span><span class="p">(</span><span class="nx">name</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">email</span><span class="p">:</span> <span class="kr">string</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">name</span> <span class="o">=</span> <span class="nx">name</span><span class="p">;</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">email</span> <span class="o">=</span> <span class="nx">email</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="nx">print</span><span class="p">(</span><span class="nx">template</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="kr">string</span> <span class="p">{</span>
        <span class="k">return</span> <span class="nx">template</span>
            <span class="p">.</span><span class="nx">replace</span><span class="p">(</span><span class="dl">''</span><span class="p">,</span> <span class="k">this</span><span class="p">.</span><span class="nx">name</span><span class="p">)</span>
            <span class="p">.</span><span class="nx">replace</span><span class="p">(</span><span class="dl">''</span><span class="p">,</span> <span class="k">this</span><span class="p">.</span><span class="nx">email</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Y acabamos así:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">class</span> <span class="nx">ReportGenerator</span> <span class="p">{</span>
    <span class="nx">generateReport</span><span class="p">(</span>
        <span class="nx">title</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
        <span class="nx">startDate</span><span class="p">:</span> <span class="nb">Date</span><span class="p">,</span>
        <span class="nx">endDate</span><span class="p">:</span> <span class="nb">Date</span><span class="p">,</span>
        <span class="nx">includeCharts</span><span class="p">:</span> <span class="nx">boolean</span><span class="p">,</span>
        <span class="nx">includeSummary</span><span class="p">:</span> <span class="nx">boolean</span><span class="p">,</span>
        <span class="nx">authorName</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
        <span class="nx">authorEmail</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
    <span class="p">)</span> <span class="p">{</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`Generando reporte: </span><span class="p">${</span><span class="nx">title</span><span class="p">}</span><span class="s2">`</span><span class="p">)</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`Desde </span><span class="p">${</span><span class="nx">startDate</span><span class="p">.</span><span class="nx">toDateString</span><span class="p">()}</span><span class="s2"> hasta </span><span class="p">${</span><span class="nx">endDate</span><span class="p">.</span><span class="nx">toDateString</span><span class="p">()}</span><span class="s2">`</span><span class="p">)</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`Autor: </span><span class="p">${</span><span class="nx">authorName</span><span class="p">}</span><span class="s2"> (</span><span class="p">${</span><span class="nx">authorEmail</span><span class="p">}</span><span class="s2">)`</span><span class="p">)</span>
        <span class="k">if</span> <span class="p">(</span><span class="nx">includeCharts</span><span class="p">)</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Incluyendo gráficos...</span><span class="dl">'</span><span class="p">)</span>
        <span class="k">if</span> <span class="p">(</span><span class="nx">includeSummary</span><span class="p">)</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Incluyendo resumen...</span><span class="dl">'</span><span class="p">)</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Reporte generado exitosamente.</span><span class="dl">'</span><span class="p">)</span>
    <span class="p">}</span>

    <span class="nx">generate</span><span class="p">(</span><span class="nx">title</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>  <span class="nx">range</span><span class="p">:</span> <span class="nx">DateRange</span><span class="p">,</span> <span class="nx">author</span><span class="p">:</span> <span class="nx">Author</span><span class="p">,</span> <span class="nx">includeCharts</span><span class="p">:</span> <span class="nx">boolean</span><span class="p">,</span> <span class="nx">includeSummary</span><span class="p">:</span> <span class="nx">boolean</span><span class="p">)</span> <span class="p">{</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`Generando reporte: </span><span class="p">${</span><span class="nx">title</span><span class="p">}</span><span class="s2">`</span><span class="p">)</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">range</span><span class="p">.</span><span class="nx">print</span><span class="p">(</span><span class="s2">`Desde  hasta `</span><span class="p">))</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">author</span><span class="p">.</span><span class="nx">print</span><span class="p">(</span><span class="s2">`Autor:  ()`</span><span class="p">))</span>
        <span class="k">if</span> <span class="p">(</span><span class="nx">includeCharts</span><span class="p">)</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Incluyendo gráficos...</span><span class="dl">'</span><span class="p">)</span>
        <span class="k">if</span> <span class="p">(</span><span class="nx">includeSummary</span><span class="p">)</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Incluyendo resumen...</span><span class="dl">'</span><span class="p">)</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Reporte generado exitosamente.</span><span class="dl">'</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Con estos cambios, el test pasa y podemos empezar a vaciar el método <code class="language-plaintext highlighter-rouge">generateReport</code> de la clase <code class="language-plaintext highlighter-rouge">ReportGenerator</code>.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">class</span> <span class="nx">ReportGenerator</span> <span class="p">{</span>
    <span class="nx">generateReport</span><span class="p">(</span>
        <span class="nx">title</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
        <span class="nx">startDate</span><span class="p">:</span> <span class="nb">Date</span><span class="p">,</span>
        <span class="nx">endDate</span><span class="p">:</span> <span class="nb">Date</span><span class="p">,</span>
        <span class="nx">includeCharts</span><span class="p">:</span> <span class="nx">boolean</span><span class="p">,</span>
        <span class="nx">includeSummary</span><span class="p">:</span> <span class="nx">boolean</span><span class="p">,</span>
        <span class="nx">authorName</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
        <span class="nx">authorEmail</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
    <span class="p">)</span> <span class="p">{</span>
        <span class="kd">const</span> <span class="nx">range</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">DateRange</span><span class="p">(</span><span class="nx">startDate</span><span class="p">,</span> <span class="nx">endDate</span><span class="p">)</span>
        <span class="kd">const</span> <span class="nx">author</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Author</span><span class="p">(</span><span class="nx">authorName</span><span class="p">,</span> <span class="nx">authorEmail</span><span class="p">)</span>
        <span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">generate</span><span class="p">(</span><span class="nx">title</span><span class="p">,</span> <span class="nx">range</span><span class="p">,</span> <span class="nx">author</span><span class="p">,</span> <span class="nx">includeCharts</span><span class="p">,</span> <span class="nx">includeSummary</span><span class="p">)</span>
    <span class="p">}</span>

    <span class="nx">generate</span><span class="p">(</span><span class="nx">title</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>  <span class="nx">range</span><span class="p">:</span> <span class="nx">DateRange</span><span class="p">,</span> <span class="nx">author</span><span class="p">:</span> <span class="nx">Author</span><span class="p">,</span> <span class="nx">includeCharts</span><span class="p">:</span> <span class="nx">boolean</span><span class="p">,</span> <span class="nx">includeSummary</span><span class="p">:</span> <span class="nx">boolean</span><span class="p">)</span> <span class="p">{</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`Generando reporte: </span><span class="p">${</span><span class="nx">title</span><span class="p">}</span><span class="s2">`</span><span class="p">)</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">range</span><span class="p">.</span><span class="nx">print</span><span class="p">(</span><span class="s2">`Desde  hasta `</span><span class="p">))</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">author</span><span class="p">.</span><span class="nx">print</span><span class="p">(</span><span class="s2">`Autor:  ()`</span><span class="p">))</span>
        <span class="k">if</span> <span class="p">(</span><span class="nx">includeCharts</span><span class="p">)</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Incluyendo gráficos...</span><span class="dl">'</span><span class="p">)</span>
        <span class="k">if</span> <span class="p">(</span><span class="nx">includeSummary</span><span class="p">)</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Incluyendo resumen...</span><span class="dl">'</span><span class="p">)</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Reporte generado exitosamente.</span><span class="dl">'</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Todavía no deberíamos usar el método nuevo, porque su firma no es estable. Demos un paso más:</p>

<p>Antes señalamos que algunos de los parámetros que se pasan a <code class="language-plaintext highlighter-rouge">ReportGenerator</code> parecen tener más sentido como parámetros de configuración del generador y que podrían pasarse en construcción. Por supuesto, a falta de un contexto real, esto no deja de ser nada más que un recurso didáctico para introducir otra forma de pensar sobre el problema.</p>

<p>En este caso, tendríamos que introducir parámetros en la función constructora y guardarlos como propiedades privadas:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">class</span> <span class="nx">ReportGenerator</span> <span class="p">{</span>
    <span class="k">private</span> <span class="nx">includeCharts</span><span class="p">:</span> <span class="nx">boolean</span><span class="p">;</span>
    <span class="k">private</span> <span class="nx">includeSummary</span><span class="p">:</span> <span class="nx">boolean</span><span class="p">;</span>

    <span class="kd">constructor</span><span class="p">(</span><span class="nx">includeCharts</span><span class="p">:</span> <span class="nx">boolean</span> <span class="o">=</span> <span class="kc">true</span><span class="p">,</span> <span class="nx">includeSummary</span><span class="p">:</span> <span class="nx">boolean</span> <span class="o">=</span> <span class="kc">false</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">includeCharts</span> <span class="o">=</span> <span class="nx">includeCharts</span><span class="p">;</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">includeSummary</span> <span class="o">=</span> <span class="nx">includeSummary</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="c1">// Code removed for clarity</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Por supuesto, esto nos va a generar un problema con las instanciaciones actuales que tengamos, ya que ahora requerimos dos parámetros para construir el objeto. Al no tener sobrecarga de constructores en Typescript, lo que nos permitiría introducir uno alternativo, podríamos usar parámetros opcionales:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">class</span> <span class="nx">ReportGenerator</span> <span class="p">{</span>
    <span class="k">private</span> <span class="nx">includeCharts</span><span class="p">:</span> <span class="nx">boolean</span><span class="p">;</span>
    <span class="k">private</span> <span class="nx">includeSummary</span><span class="p">:</span> <span class="nx">boolean</span><span class="p">;</span>

    <span class="kd">constructor</span><span class="p">(</span><span class="nx">includeCharts</span><span class="p">:</span> <span class="nx">boolean</span> <span class="o">=</span> <span class="kc">true</span><span class="p">,</span> <span class="nx">includeSummary</span><span class="p">:</span> <span class="nx">boolean</span> <span class="o">=</span> <span class="kc">false</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">includeCharts</span> <span class="o">=</span> <span class="nx">includeCharts</span><span class="p">;</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">includeSummary</span> <span class="o">=</span> <span class="nx">includeSummary</span><span class="p">;</span>
    <span class="p">}</span>
    
    <span class="c1">// Code removed for clarity</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Esto nos permite prescindir de ambos parámetros en la firma del nuevo método <code class="language-plaintext highlighter-rouge">generate</code>, pero debemos tenerlo en cuenta en <code class="language-plaintext highlighter-rouge">generateReport</code>, método que ahora podríamos deprecar:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">class</span> <span class="nx">ReportGenerator</span> <span class="p">{</span>
    <span class="k">private</span> <span class="nx">includeCharts</span><span class="p">:</span> <span class="nx">boolean</span><span class="p">;</span>
    <span class="k">private</span> <span class="nx">includeSummary</span><span class="p">:</span> <span class="nx">boolean</span><span class="p">;</span>

    <span class="kd">constructor</span><span class="p">(</span><span class="nx">includeCharts</span><span class="p">:</span> <span class="nx">boolean</span> <span class="o">=</span> <span class="kc">true</span><span class="p">,</span> <span class="nx">includeSummary</span><span class="p">:</span> <span class="nx">boolean</span> <span class="o">=</span> <span class="kc">false</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">includeCharts</span> <span class="o">=</span> <span class="nx">includeCharts</span><span class="p">;</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">includeSummary</span> <span class="o">=</span> <span class="nx">includeSummary</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="cm">/** @deprecated Use ReportGenerator.generate instead */</span>
    <span class="nx">generateReport</span><span class="p">(</span>
        <span class="nx">title</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
        <span class="nx">startDate</span><span class="p">:</span> <span class="nb">Date</span><span class="p">,</span>
        <span class="nx">endDate</span><span class="p">:</span> <span class="nb">Date</span><span class="p">,</span>
        <span class="nx">includeCharts</span><span class="p">:</span> <span class="nx">boolean</span><span class="p">,</span>
        <span class="nx">includeSummary</span><span class="p">:</span> <span class="nx">boolean</span><span class="p">,</span>
        <span class="nx">authorName</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
        <span class="nx">authorEmail</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
    <span class="p">)</span> <span class="p">{</span>
        <span class="kd">const</span> <span class="nx">range</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">DateRange</span><span class="p">(</span><span class="nx">startDate</span><span class="p">,</span> <span class="nx">endDate</span><span class="p">)</span>
        <span class="kd">const</span> <span class="nx">author</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Author</span><span class="p">(</span><span class="nx">authorName</span><span class="p">,</span> <span class="nx">authorEmail</span><span class="p">)</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">includeCharts</span> <span class="o">=</span> <span class="nx">includeCharts</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">includeSummary</span> <span class="o">=</span> <span class="nx">includeSummary</span>
        <span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">generate</span><span class="p">(</span><span class="nx">title</span><span class="p">,</span> <span class="nx">range</span><span class="p">,</span> <span class="nx">author</span><span class="p">)</span>
    <span class="p">}</span>

    <span class="nx">generate</span><span class="p">(</span><span class="nx">title</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">range</span><span class="p">:</span> <span class="nx">DateRange</span><span class="p">,</span> <span class="nx">author</span><span class="p">:</span> <span class="nx">Author</span><span class="p">)</span> <span class="p">{</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`Generando reporte: </span><span class="p">${</span><span class="nx">title</span><span class="p">}</span><span class="s2">`</span><span class="p">)</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">range</span><span class="p">.</span><span class="nx">print</span><span class="p">(</span><span class="s2">`Desde  hasta `</span><span class="p">))</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">author</span><span class="p">.</span><span class="nx">print</span><span class="p">(</span><span class="s2">`Autor:  ()`</span><span class="p">))</span>
        <span class="k">if</span> <span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">includeCharts</span><span class="p">)</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Incluyendo gráficos...</span><span class="dl">'</span><span class="p">)</span>
        <span class="k">if</span> <span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">includeSummary</span><span class="p">)</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Incluyendo resumen...</span><span class="dl">'</span><span class="p">)</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Reporte generado exitosamente.</span><span class="dl">'</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h4 id="preparándonos-para-el-siguiente-paso">Preparándonos para el siguiente paso</h4>

<p>Las dos nuevas propiedades que se quieren introducir son <code class="language-plaintext highlighter-rouge">locale</code> y <code class="language-plaintext highlighter-rouge">pageSize</code>. La primera hace más bien referencia al contenido, mientras que la segunda hace referencia al formato del informe. De nuevo, se trata de una decisión didáctica para explicar el problema.</p>

<p>De momento, hemos resuelto el <em>smell</em> para el caso concreto de la firma de <code class="language-plaintext highlighter-rouge">generateReport</code>, pasando de 7 a 3 parámetros, pero no hemos resuelto el problema general. Si tenemos que añadir más parámetros al método, nos pondremos de nuevo en el límite o volveremos a superarlo.</p>

<p>Lo mismo ocurre para el constructor. Tenemos que mantener parámetros opcionales para asegurar compatibilidad hacia atrás. Y si tener tres o más en una firma ya es complicado de leer, cuando son opcionales la cosa se pone peor.</p>

<p>En estos casos nos viene bien utilizar el patrón <code class="language-plaintext highlighter-rouge">Builder</code>. Este patrón nos permite tener un constructor complejo, a la vez que exponemos una interfaz fácil de usar para generar instancias del objeto deseado.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">class</span> <span class="nx">ReportGeneratorBuilder</span> <span class="p">{</span>
    <span class="k">private</span> <span class="nx">includeCharts</span><span class="p">:</span> <span class="nx">boolean</span> <span class="o">=</span> <span class="kc">false</span>
    <span class="k">private</span> <span class="nx">includeSummary</span><span class="p">:</span> <span class="nx">boolean</span> <span class="o">=</span> <span class="kc">false</span>

    <span class="nx">withCharts</span><span class="p">():</span> <span class="nx">ReportGeneratorBuilder</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">includeCharts</span> <span class="o">=</span> <span class="kc">true</span><span class="p">;</span>
        <span class="k">return</span> <span class="k">this</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="nx">withSummary</span><span class="p">():</span> <span class="nx">ReportGeneratorBuilder</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">includeSummary</span> <span class="o">=</span> <span class="kc">true</span><span class="p">;</span>
        <span class="k">return</span> <span class="k">this</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="nx">build</span><span class="p">():</span> <span class="nx">ReportGenerator</span> <span class="p">{</span>
        <span class="k">return</span> <span class="k">new</span> <span class="nx">ReportGenerator</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">includeCharts</span><span class="p">,</span> <span class="k">this</span><span class="p">.</span><span class="nx">includeSummary</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Usaremos el <code class="language-plaintext highlighter-rouge">Builder</code> cuando necesitemos tener una instancia de <code class="language-plaintext highlighter-rouge">ReportGenerator</code>. En este ejemplo, estamos usando opciones por defecto. Dentro de un momento veremos como requerir que ciertos valores se definan de forma obligatoria. Fíjate como usando la interfaz fluída obtenemos una construcción muy expresiva:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">it</span><span class="p">(</span><span class="dl">'</span><span class="s1">Generates a report with Value Objects</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">logSpy</span> <span class="o">=</span> <span class="nx">vi</span><span class="p">.</span><span class="nx">spyOn</span><span class="p">(</span><span class="nx">console</span><span class="p">,</span> <span class="dl">'</span><span class="s1">log</span><span class="dl">'</span><span class="p">)</span>

    <span class="kd">const</span> <span class="nx">range</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">DateRange</span><span class="p">(</span>
        <span class="k">new</span> <span class="nb">Date</span><span class="p">(</span><span class="dl">'</span><span class="s1">2025-01-01</span><span class="dl">'</span><span class="p">),</span>
        <span class="k">new</span> <span class="nb">Date</span><span class="p">(</span><span class="dl">'</span><span class="s1">2025-03-31</span><span class="dl">'</span><span class="p">)</span>
    <span class="p">)</span>

    <span class="kd">const</span> <span class="nx">author</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Author</span><span class="p">(</span>
        <span class="dl">'</span><span class="s1">Pat Smith</span><span class="dl">'</span><span class="p">,</span>
        <span class="dl">'</span><span class="s1">pat@example.com</span><span class="dl">'</span><span class="p">,</span>
    <span class="p">)</span>

    <span class="kd">const</span> <span class="nx">builder</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">ReportGeneratorBuilder</span><span class="p">()</span>
    <span class="kd">const</span> <span class="nx">gen</span> <span class="o">=</span> <span class="nx">builder</span>
        <span class="p">.</span><span class="nx">withCharts</span><span class="p">()</span>
        <span class="p">.</span><span class="nx">build</span><span class="p">()</span>

    <span class="nx">gen</span><span class="p">.</span><span class="nx">generate</span><span class="p">(</span>
        <span class="dl">'</span><span class="s1">Ventas Q1</span><span class="dl">'</span><span class="p">,</span>
        <span class="nx">range</span><span class="p">,</span>
        <span class="nx">author</span><span class="p">,</span>
    <span class="p">)</span>

    <span class="kd">let</span> <span class="nx">output</span> <span class="o">=</span> <span class="nx">formatConsoleCalls</span><span class="p">(</span><span class="nx">logSpy</span><span class="p">)</span>
    <span class="nx">expect</span><span class="p">(</span><span class="nx">output</span><span class="p">).</span><span class="nx">toMatchSnapshot</span><span class="p">()</span>
<span class="p">})</span>
</code></pre></div></div>

<p>Es más, podemos crear todo un vocabulario:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">class</span> <span class="nx">ReportGeneratorBuilder</span> <span class="p">{</span>
    <span class="k">private</span> <span class="nx">includeCharts</span><span class="p">:</span> <span class="nx">boolean</span> <span class="o">=</span> <span class="kc">false</span>
    <span class="k">private</span> <span class="nx">includeSummary</span><span class="p">:</span> <span class="nx">boolean</span> <span class="o">=</span> <span class="kc">false</span>

    <span class="nx">withCharts</span><span class="p">():</span> <span class="nx">ReportGeneratorBuilder</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">includeCharts</span> <span class="o">=</span> <span class="kc">true</span>
        <span class="k">return</span> <span class="k">this</span>
    <span class="p">}</span>

    <span class="nx">withoutCharts</span><span class="p">():</span> <span class="nx">ReportGeneratorBuilder</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">includeCharts</span> <span class="o">=</span> <span class="kc">false</span>
        <span class="k">return</span> <span class="k">this</span>
    <span class="p">}</span>

    <span class="nx">withSummary</span><span class="p">():</span> <span class="nx">ReportGeneratorBuilder</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">includeSummary</span> <span class="o">=</span> <span class="kc">true</span>
        <span class="k">return</span> <span class="k">this</span>
    <span class="p">}</span>

    <span class="nx">withoutSummary</span><span class="p">():</span> <span class="nx">ReportGeneratorBuilder</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">includeSummary</span> <span class="o">=</span> <span class="kc">false</span>
        <span class="k">return</span> <span class="k">this</span>
    <span class="p">}</span>

    <span class="nx">build</span><span class="p">():</span> <span class="nx">ReportGenerator</span> <span class="p">{</span>
        <span class="k">return</span> <span class="k">new</span> <span class="nx">ReportGenerator</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">includeCharts</span><span class="p">,</span> <span class="k">this</span><span class="p">.</span><span class="nx">includeSummary</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>El patrón <em>Builder</em> nos permite ocultar los detalles de construcción del objeto al resto de la aplicación. Así, si queremos que la configuración del reporte sea más escalable, podemos cambiarla sin afectar a la interfaz.</p>

<p>Supongamos que aplicamos <em>Introduce Parameter Object</em> para construir <code class="language-plaintext highlighter-rouge">ReportGenerator</code>.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">interface</span> <span class="nx">ReportConfiguration</span> <span class="p">{</span>
    <span class="nl">includeCharts</span><span class="p">:</span> <span class="nx">boolean</span>
    <span class="nx">includeSummary</span><span class="p">:</span> <span class="nx">boolean</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Cambiamos la clase:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">class</span> <span class="nx">ReportGenerator</span> <span class="p">{</span>
    <span class="k">private</span> <span class="nx">configuration</span><span class="p">:</span> <span class="nx">ReportConfiguration</span>

    <span class="kd">constructor</span><span class="p">(</span><span class="nx">configuration</span><span class="p">:</span> <span class="nx">ReportConfiguration</span> <span class="o">=</span> <span class="p">{</span><span class="na">includeCharts</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span> <span class="na">includeSummary</span><span class="p">:</span> <span class="kc">false</span><span class="p">})</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">configuration</span> <span class="o">=</span> <span class="nx">configuration</span>
    <span class="p">}</span>

    <span class="nx">generateReport</span><span class="p">(</span>
        <span class="nx">title</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
        <span class="nx">startDate</span><span class="p">:</span> <span class="nb">Date</span><span class="p">,</span>
        <span class="nx">endDate</span><span class="p">:</span> <span class="nb">Date</span><span class="p">,</span>
        <span class="nx">includeCharts</span><span class="p">:</span> <span class="nx">boolean</span><span class="p">,</span>
        <span class="nx">includeSummary</span><span class="p">:</span> <span class="nx">boolean</span><span class="p">,</span>
        <span class="nx">authorName</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
        <span class="nx">authorEmail</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
    <span class="p">)</span> <span class="p">{</span>
        <span class="kd">const</span> <span class="nx">range</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">DateRange</span><span class="p">(</span><span class="nx">startDate</span><span class="p">,</span> <span class="nx">endDate</span><span class="p">)</span>
        <span class="kd">const</span> <span class="nx">author</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Author</span><span class="p">(</span><span class="nx">authorName</span><span class="p">,</span> <span class="nx">authorEmail</span><span class="p">)</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">configuration</span> <span class="o">=</span> <span class="p">{</span>
            <span class="na">includeCharts</span><span class="p">:</span> <span class="nx">includeCharts</span><span class="p">,</span>
            <span class="na">includeSummary</span><span class="p">:</span> <span class="nx">includeSummary</span><span class="p">,</span>
        <span class="p">}</span>
        <span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">generate</span><span class="p">(</span><span class="nx">title</span><span class="p">,</span> <span class="nx">range</span><span class="p">,</span> <span class="nx">author</span><span class="p">)</span>
    <span class="p">}</span>

    <span class="nx">generate</span><span class="p">(</span><span class="nx">title</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">range</span><span class="p">:</span> <span class="nx">DateRange</span><span class="p">,</span> <span class="nx">author</span><span class="p">:</span> <span class="nx">Author</span><span class="p">)</span> <span class="p">{</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`Generando reporte: </span><span class="p">${</span><span class="nx">title</span><span class="p">}</span><span class="s2">`</span><span class="p">)</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">range</span><span class="p">.</span><span class="nx">print</span><span class="p">(</span><span class="s2">`Desde  hasta `</span><span class="p">))</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">author</span><span class="p">.</span><span class="nx">print</span><span class="p">(</span><span class="s2">`Autor:  ()`</span><span class="p">))</span>
        <span class="k">if</span> <span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">configuration</span><span class="p">.</span><span class="nx">includeCharts</span><span class="p">)</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Incluyendo gráficos...</span><span class="dl">'</span><span class="p">)</span>
        <span class="k">if</span> <span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">configuration</span><span class="p">.</span><span class="nx">includeSummary</span><span class="p">)</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Incluyendo resumen...</span><span class="dl">'</span><span class="p">)</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Reporte generado exitosamente.</span><span class="dl">'</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Y el Builder:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">class</span> <span class="nx">ReportGeneratorBuilder</span> <span class="p">{</span>
    <span class="k">private</span> <span class="nx">includeCharts</span><span class="p">:</span> <span class="nx">boolean</span> <span class="o">=</span> <span class="kc">false</span>
    <span class="k">private</span> <span class="nx">includeSummary</span><span class="p">:</span> <span class="nx">boolean</span> <span class="o">=</span> <span class="kc">false</span>

    <span class="nx">withCharts</span><span class="p">():</span> <span class="nx">ReportGeneratorBuilder</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">includeCharts</span> <span class="o">=</span> <span class="kc">true</span>
        <span class="k">return</span> <span class="k">this</span>
    <span class="p">}</span>

    <span class="nx">withoutCharts</span><span class="p">():</span> <span class="nx">ReportGeneratorBuilder</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">includeCharts</span> <span class="o">=</span> <span class="kc">false</span>
        <span class="k">return</span> <span class="k">this</span>
    <span class="p">}</span>

    <span class="nx">withSummary</span><span class="p">():</span> <span class="nx">ReportGeneratorBuilder</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">includeSummary</span> <span class="o">=</span> <span class="kc">true</span>
        <span class="k">return</span> <span class="k">this</span>
    <span class="p">}</span>

    <span class="nx">withoutSummary</span><span class="p">():</span> <span class="nx">ReportGeneratorBuilder</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">includeSummary</span> <span class="o">=</span> <span class="kc">false</span>
        <span class="k">return</span> <span class="k">this</span>
    <span class="p">}</span>

    <span class="nx">build</span><span class="p">():</span> <span class="nx">ReportGenerator</span> <span class="p">{</span>
        <span class="kd">const</span> <span class="nx">configuration</span> <span class="o">=</span> <span class="p">{</span>
            <span class="na">includeCharts</span><span class="p">:</span> <span class="k">this</span><span class="p">.</span><span class="nx">includeCharts</span><span class="p">,</span>
            <span class="na">includeSummary</span><span class="p">:</span> <span class="k">this</span><span class="p">.</span><span class="nx">includeSummary</span><span class="p">,</span>
        <span class="p">}</span>
        <span class="k">return</span> <span class="k">new</span> <span class="nx">ReportGenerator</span><span class="p">(</span><span class="nx">configuration</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Pero el test, y todos los posibles consumidores, quedan exactamente igual:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">it</span><span class="p">(</span><span class="dl">'</span><span class="s1">Generates a report with Value Objects</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">logSpy</span> <span class="o">=</span> <span class="nx">vi</span><span class="p">.</span><span class="nx">spyOn</span><span class="p">(</span><span class="nx">console</span><span class="p">,</span> <span class="dl">'</span><span class="s1">log</span><span class="dl">'</span><span class="p">)</span>

    <span class="kd">const</span> <span class="nx">range</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">DateRange</span><span class="p">(</span><span class="k">new</span> <span class="nb">Date</span><span class="p">(</span><span class="dl">'</span><span class="s1">2025-01-01</span><span class="dl">'</span><span class="p">),</span> <span class="k">new</span> <span class="nb">Date</span><span class="p">(</span><span class="dl">'</span><span class="s1">2025-03-31</span><span class="dl">'</span><span class="p">))</span>

    <span class="kd">const</span> <span class="nx">author</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Author</span><span class="p">(</span><span class="dl">'</span><span class="s1">Pat Smith</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">pat@example.com</span><span class="dl">'</span><span class="p">)</span>

    <span class="kd">const</span> <span class="nx">builder</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">ReportGeneratorBuilder</span><span class="p">()</span>
    <span class="kd">const</span> <span class="nx">gen</span> <span class="o">=</span> <span class="nx">builder</span><span class="p">.</span><span class="nx">withCharts</span><span class="p">().</span><span class="nx">build</span><span class="p">()</span>

    <span class="nx">gen</span><span class="p">.</span><span class="nx">generate</span><span class="p">(</span><span class="dl">'</span><span class="s1">Ventas Q1</span><span class="dl">'</span><span class="p">,</span> <span class="nx">range</span><span class="p">,</span> <span class="nx">author</span><span class="p">)</span>

    <span class="kd">let</span> <span class="nx">output</span> <span class="o">=</span> <span class="nx">formatConsoleCalls</span><span class="p">(</span><span class="nx">logSpy</span><span class="p">)</span>
    <span class="nx">expect</span><span class="p">(</span><span class="nx">output</span><span class="p">).</span><span class="nx">toMatchSnapshot</span><span class="p">()</span>
<span class="p">})</span>
</code></pre></div></div>

<h4 id="introduzcamos-nuevos-parámetros-para-construir-reportgenerator">Introduzcamos nuevos parámetros para construir ReportGenerator</h4>

<p>En realidad, ahora es bastante fácil, no tenemos más que introducir un parámetro nuevo en el objeto de configuración:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">interface</span> <span class="nx">ReportConfiguration</span> <span class="p">{</span>
    <span class="nl">includeCharts</span><span class="p">:</span> <span class="nx">boolean</span>
    <span class="nx">includeSummary</span><span class="p">:</span> <span class="nx">boolean</span>
    <span class="nx">pageSize</span><span class="p">:</span> <span class="kr">string</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Y en el <em>Builder</em> podemos añadir incluso validaciones para asegurar que el parámetro se define de forma explícita:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">class</span> <span class="nx">ReportGeneratorBuilder</span> <span class="p">{</span>
    <span class="k">private</span> <span class="nx">includeCharts</span><span class="p">:</span> <span class="nx">boolean</span> <span class="o">=</span> <span class="kc">false</span>
    <span class="k">private</span> <span class="nx">includeSummary</span><span class="p">:</span> <span class="nx">boolean</span> <span class="o">=</span> <span class="kc">false</span>
    <span class="k">private</span> <span class="nx">pageSize</span><span class="p">?:</span> <span class="kr">string</span>

    <span class="nx">withCharts</span><span class="p">():</span> <span class="nx">ReportGeneratorBuilder</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">includeCharts</span> <span class="o">=</span> <span class="kc">true</span>
        <span class="k">return</span> <span class="k">this</span>
    <span class="p">}</span>

    <span class="nx">withoutCharts</span><span class="p">():</span> <span class="nx">ReportGeneratorBuilder</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">includeCharts</span> <span class="o">=</span> <span class="kc">false</span>
        <span class="k">return</span> <span class="k">this</span>
    <span class="p">}</span>

    <span class="nx">withSummary</span><span class="p">():</span> <span class="nx">ReportGeneratorBuilder</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">includeSummary</span> <span class="o">=</span> <span class="kc">true</span>
        <span class="k">return</span> <span class="k">this</span>
    <span class="p">}</span>

    <span class="nx">withoutSummary</span><span class="p">():</span> <span class="nx">ReportGeneratorBuilder</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">includeSummary</span> <span class="o">=</span> <span class="kc">false</span>
        <span class="k">return</span> <span class="k">this</span>
    <span class="p">}</span>

    <span class="nx">withPageSize</span><span class="p">(</span><span class="nx">pageSize</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="nx">ReportGeneratorBuilder</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">pageSize</span> <span class="o">=</span> <span class="nx">pageSize</span>
        <span class="k">return</span> <span class="k">this</span>
    <span class="p">}</span>

    <span class="nx">build</span><span class="p">():</span> <span class="nx">ReportGenerator</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="k">this</span><span class="p">.</span><span class="nx">pageSize</span><span class="p">)</span> <span class="p">{</span>
            <span class="k">throw</span> <span class="k">new</span> <span class="nb">Error</span><span class="p">(</span><span class="dl">'</span><span class="s1">PageSize is required. Use withPageSize(</span><span class="se">\'</span><span class="s1">A4</span><span class="se">\'</span><span class="s1">).</span><span class="dl">'</span><span class="p">)</span>
        <span class="p">}</span>
        <span class="kd">const</span> <span class="nx">configuration</span> <span class="o">=</span> <span class="p">{</span>
            <span class="na">includeCharts</span><span class="p">:</span> <span class="k">this</span><span class="p">.</span><span class="nx">includeCharts</span><span class="p">,</span>
            <span class="na">includeSummary</span><span class="p">:</span> <span class="k">this</span><span class="p">.</span><span class="nx">includeSummary</span><span class="p">,</span>
            <span class="na">pageSize</span><span class="p">:</span> <span class="k">this</span><span class="p">.</span><span class="nx">pageSize</span>
        <span class="p">}</span>
        <span class="k">return</span> <span class="k">new</span> <span class="nx">ReportGenerator</span><span class="p">(</span><span class="nx">configuration</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Usar el parámetro significa que los tests actuales necesitan cambiar, pues se supone que este nuevo parámetro puede cambiar el comportamiento de <code class="language-plaintext highlighter-rouge">ReportGenerator</code>. Si nos interesa mantener el método deprecado <code class="language-plaintext highlighter-rouge">generateReport</code> seguramente querremos mantener el test y el viejo comportamiento. Por tanto, el método <code class="language-plaintext highlighter-rouge">generate</code> que, en último término es el que está siendo ejecutado, debería proporcionarnos alguna protección que asegure cierta protección.</p>

<p>Por ejemplo, ahora mismo si instanciamos directamente <code class="language-plaintext highlighter-rouge">ReportGenerator</code> (sin pasar por el Builder) y ejecutamos <code class="language-plaintext highlighter-rouge">generateReport</code>, el <code class="language-plaintext highlighter-rouge">pageSize</code> por defecto es ‘A4’. Esto sería el comportamiento actual del sistema. Aunque no hemos mencionado nada de esto, podría ser perfectamente que ‘A4’ fuese el valor <em>hardcoded</em> de la implementación deprecada. Así que, en lo tocante al test no tendría ningún efecto.</p>

<p>Podemos hacer esto explícito en el código, aunque sea temporalmente:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">class</span> <span class="nx">ReportGenerator</span> <span class="p">{</span>
    <span class="k">private</span> <span class="nx">configuration</span><span class="p">:</span> <span class="nx">ReportConfiguration</span>

    <span class="kd">constructor</span><span class="p">(</span><span class="nx">configuration</span><span class="p">:</span> <span class="nx">ReportConfiguration</span> <span class="o">=</span> <span class="p">{</span><span class="na">includeCharts</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span> <span class="na">includeSummary</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span> <span class="na">pageSize</span><span class="p">:</span> <span class="dl">'</span><span class="s1">A4</span><span class="dl">'</span><span class="p">})</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">configuration</span> <span class="o">=</span> <span class="nx">configuration</span>
    <span class="p">}</span>

    <span class="c1">// Code removed for clarity</span>

    <span class="nx">generate</span><span class="p">(</span><span class="nx">title</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">range</span><span class="p">:</span> <span class="nx">DateRange</span><span class="p">,</span> <span class="nx">author</span><span class="p">:</span> <span class="nx">Author</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">configuration</span><span class="p">.</span><span class="nx">pageSize</span> <span class="o">!==</span> <span class="dl">'</span><span class="s1">A4</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
            <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`Ajustando página a </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">configuration</span><span class="p">.</span><span class="nx">pageSize</span><span class="p">}</span><span class="s2">...`</span><span class="p">)</span>
        <span class="p">}</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`Generando reporte: </span><span class="p">${</span><span class="nx">title</span><span class="p">}</span><span class="s2">`</span><span class="p">);</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">range</span><span class="p">.</span><span class="nx">print</span><span class="p">(</span><span class="s2">`Desde  hasta `</span><span class="p">))</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">author</span><span class="p">.</span><span class="nx">print</span><span class="p">(</span><span class="s2">`Autor:  ()`</span><span class="p">))</span>
        <span class="k">if</span> <span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">configuration</span><span class="p">.</span><span class="nx">includeCharts</span><span class="p">)</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Incluyendo gráficos...</span><span class="dl">'</span><span class="p">)</span>
        <span class="k">if</span> <span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">configuration</span><span class="p">.</span><span class="nx">includeSummary</span><span class="p">)</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Incluyendo resumen...</span><span class="dl">'</span><span class="p">)</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Reporte generado exitosamente.</span><span class="dl">'</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Y podríamos introducir un nuevo test para verificar pageSize:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="nx">it</span><span class="p">(</span><span class="dl">'</span><span class="s1">Generates a report with custom page Size</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">logSpy</span> <span class="o">=</span> <span class="nx">vi</span><span class="p">.</span><span class="nx">spyOn</span><span class="p">(</span><span class="nx">console</span><span class="p">,</span> <span class="dl">'</span><span class="s1">log</span><span class="dl">'</span><span class="p">)</span>

    <span class="kd">const</span> <span class="nx">range</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">DateRange</span><span class="p">(</span><span class="k">new</span> <span class="nb">Date</span><span class="p">(</span><span class="dl">'</span><span class="s1">2025-01-01</span><span class="dl">'</span><span class="p">),</span> <span class="k">new</span> <span class="nb">Date</span><span class="p">(</span><span class="dl">'</span><span class="s1">2025-03-31</span><span class="dl">'</span><span class="p">))</span>
    <span class="kd">const</span> <span class="nx">author</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Author</span><span class="p">(</span><span class="dl">'</span><span class="s1">Pat Smith</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">pat@example.com</span><span class="dl">'</span><span class="p">)</span>
    <span class="kd">const</span> <span class="nx">builder</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">ReportGeneratorBuilder</span><span class="p">()</span>
    <span class="kd">const</span> <span class="nx">gen</span> <span class="o">=</span> <span class="nx">builder</span><span class="p">.</span><span class="nx">withCharts</span><span class="p">().</span><span class="nx">withPageSize</span><span class="p">(</span><span class="dl">'</span><span class="s1">A5</span><span class="dl">'</span><span class="p">).</span><span class="nx">build</span><span class="p">()</span>

    <span class="nx">gen</span><span class="p">.</span><span class="nx">generate</span><span class="p">(</span><span class="dl">'</span><span class="s1">Ventas Q1</span><span class="dl">'</span><span class="p">,</span> <span class="nx">range</span><span class="p">,</span> <span class="nx">author</span><span class="p">)</span>

    <span class="kd">let</span> <span class="nx">output</span> <span class="o">=</span> <span class="nx">formatConsoleCalls</span><span class="p">(</span><span class="nx">logSpy</span><span class="p">)</span>
    <span class="nx">expect</span><span class="p">(</span><span class="nx">output</span><span class="p">).</span><span class="nx">toMatchSnapshot</span><span class="p">()</span>
  <span class="p">})</span>
</code></pre></div></div>

<p>Que genera este resultado:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Ajustando página a A5...
Generando reporte: Ventas Q1
Desde Wed Jan 01 2025 hasta Mon Mar 31 2025
Autor: Pat Smith (pat@example.com)
Incluyendo gráficos...
Reporte generado exitosamente.
</code></pre></div></div>

<p>Por supuesto, cuando podamos dejar de dar soporte a <code class="language-plaintext highlighter-rouge">generateReport</code>, podríamos eliminar este tratamiento especial.</p>

<h4 id="parameter-object-al-rescate">Parameter Object al rescate</h4>

<p>En nuestro caso, queremos pasar el nuevo parámetro locale al método <code class="language-plaintext highlighter-rouge">generate</code> para poder generar informes en diferentes idiomas.</p>

<p>Como hemos visto, añadir un nuevo parámetro a la firma del método es una promesa de problemas en el futuro, por lo que debemos tomar medidas preventivas. Una vez que hemos agotado la capacidad de los <em>Value Objects</em> para reducir la complejidad de la firma pasaremos a introducir <em>Parameter Objects</em>. Estos son básicamente DTOs en el sentido de que agrupan parámetros dispares para poder pasarlos  en una operación.</p>

<p>Como hicimos antes con <code class="language-plaintext highlighter-rouge">ReportConfiguration</code>, podemos crear un <code class="language-plaintext highlighter-rouge">ReportContent</code>:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kr">interface</span> <span class="nx">ReportContent</span> <span class="p">{</span>
    <span class="nl">title</span><span class="p">:</span> <span class="kr">string</span>
    <span class="nx">range</span><span class="p">:</span> <span class="nx">DateRange</span>
    <span class="nx">author</span><span class="p">:</span> <span class="nx">Author</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Para introducir su uso de forma progresiva, empezaremos pasándolo como parámetro opcional. Si el lenguaje admite sobrecarga de métodos, no tienes más que crear un método con la nueva firma.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">generate</span><span class="p">(</span><span class="nx">title</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">range</span><span class="p">:</span> <span class="nx">DateRange</span><span class="p">,</span> <span class="nx">author</span><span class="p">:</span> <span class="nx">Author</span><span class="p">,</span> <span class="nx">content</span><span class="p">?:</span> <span class="nx">ReportContent</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">if</span> <span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">configuration</span><span class="p">.</span><span class="nx">pageSize</span> <span class="o">!==</span> <span class="dl">'</span><span class="s1">A4</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`Ajustando página a </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">configuration</span><span class="p">.</span><span class="nx">pageSize</span><span class="p">}</span><span class="s2">...`</span><span class="p">)</span>
    <span class="p">}</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`Generando reporte: </span><span class="p">${</span><span class="nx">title</span><span class="p">}</span><span class="s2">`</span><span class="p">);</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">range</span><span class="p">.</span><span class="nx">print</span><span class="p">(</span><span class="s2">`Desde  hasta `</span><span class="p">))</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">author</span><span class="p">.</span><span class="nx">print</span><span class="p">(</span><span class="s2">`Autor:  ()`</span><span class="p">))</span>
    <span class="k">if</span> <span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">configuration</span><span class="p">.</span><span class="nx">includeCharts</span><span class="p">)</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Incluyendo gráficos...</span><span class="dl">'</span><span class="p">)</span>
    <span class="k">if</span> <span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">configuration</span><span class="p">.</span><span class="nx">includeSummary</span><span class="p">)</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Incluyendo resumen...</span><span class="dl">'</span><span class="p">)</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Reporte generado exitosamente.</span><span class="dl">'</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Esto respeta los usos existentes y permite que puedas empezar a usarlo en el futuro. Aquí tenemos un ejemplo de cómo gestionar <code class="language-plaintext highlighter-rouge">title</code>: usaremos el de <code class="language-plaintext highlighter-rouge">content</code>, si existe, o el que se pasa por <code class="language-plaintext highlighter-rouge">title</code> si no existe.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">generate</span><span class="p">(</span><span class="nx">title</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">range</span><span class="p">:</span> <span class="nx">DateRange</span><span class="p">,</span> <span class="nx">author</span><span class="p">:</span> <span class="nx">Author</span><span class="p">,</span> <span class="nx">content</span><span class="p">?:</span> <span class="nx">ReportContent</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">if</span> <span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">configuration</span><span class="p">.</span><span class="nx">pageSize</span> <span class="o">!==</span> <span class="dl">'</span><span class="s1">A4</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`Ajustando página a </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">configuration</span><span class="p">.</span><span class="nx">pageSize</span><span class="p">}</span><span class="s2">...`</span><span class="p">)</span>
    <span class="p">}</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`Generando reporte: </span><span class="p">${</span><span class="nx">content</span><span class="p">?.</span><span class="nx">title</span> <span class="o">??</span> <span class="nx">title</span><span class="p">}</span><span class="s2">`</span><span class="p">);</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">range</span><span class="p">.</span><span class="nx">print</span><span class="p">(</span><span class="s2">`Desde  hasta `</span><span class="p">))</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">author</span><span class="p">.</span><span class="nx">print</span><span class="p">(</span><span class="s2">`Autor:  ()`</span><span class="p">))</span>
    <span class="k">if</span> <span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">configuration</span><span class="p">.</span><span class="nx">includeCharts</span><span class="p">)</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Incluyendo gráficos...</span><span class="dl">'</span><span class="p">)</span>
    <span class="k">if</span> <span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">configuration</span><span class="p">.</span><span class="nx">includeSummary</span><span class="p">)</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Incluyendo resumen...</span><span class="dl">'</span><span class="p">)</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Reporte generado exitosamente.</span><span class="dl">'</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Pero podemos hacerlo de una forma más sólida:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">generate</span><span class="p">(</span><span class="nx">title</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">range</span><span class="p">:</span> <span class="nx">DateRange</span><span class="p">,</span> <span class="nx">author</span><span class="p">:</span> <span class="nx">Author</span><span class="p">,</span> <span class="nx">content</span><span class="p">?:</span> <span class="nx">ReportContent</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">content</span><span class="p">)</span> <span class="p">{</span>
        <span class="nx">content</span> <span class="o">=</span> <span class="p">{</span>
            <span class="na">title</span><span class="p">:</span> <span class="nx">title</span><span class="p">,</span>
            <span class="na">range</span><span class="p">:</span> <span class="nx">range</span><span class="p">,</span>
            <span class="na">author</span><span class="p">:</span> <span class="nx">author</span>
        <span class="p">}</span>
    <span class="p">}</span>
    <span class="k">if</span> <span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">configuration</span><span class="p">.</span><span class="nx">pageSize</span> <span class="o">!==</span> <span class="dl">'</span><span class="s1">A4</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`Ajustando página a </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">configuration</span><span class="p">.</span><span class="nx">pageSize</span><span class="p">}</span><span class="s2">...`</span><span class="p">);</span>
    <span class="p">}</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`Generando reporte: </span><span class="p">${</span><span class="nx">content</span><span class="p">.</span><span class="nx">title</span><span class="p">}</span><span class="s2">`</span><span class="p">);</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">content</span><span class="p">.</span><span class="nx">range</span><span class="p">.</span><span class="nx">print</span><span class="p">(</span><span class="s2">`Desde  hasta `</span><span class="p">))</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">content</span><span class="p">.</span><span class="nx">author</span><span class="p">.</span><span class="nx">print</span><span class="p">(</span><span class="s2">`Autor:  ()`</span><span class="p">))</span>
    <span class="k">if</span> <span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">configuration</span><span class="p">.</span><span class="nx">includeCharts</span><span class="p">)</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Incluyendo gráficos...</span><span class="dl">'</span><span class="p">)</span>
    <span class="k">if</span> <span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">configuration</span><span class="p">.</span><span class="nx">includeSummary</span><span class="p">)</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Incluyendo resumen...</span><span class="dl">'</span><span class="p">)</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Reporte generado exitosamente.</span><span class="dl">'</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Un nuevo test para verificar el comportamiento de <code class="language-plaintext highlighter-rouge">generate</code> con <code class="language-plaintext highlighter-rouge">content</code>:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">it</span><span class="p">(</span><span class="dl">'</span><span class="s1">Generates a report using ReportContent object</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">logSpy</span> <span class="o">=</span> <span class="nx">vi</span><span class="p">.</span><span class="nx">spyOn</span><span class="p">(</span><span class="nx">console</span><span class="p">,</span> <span class="dl">'</span><span class="s1">log</span><span class="dl">'</span><span class="p">)</span>

    <span class="kd">const</span> <span class="nx">range</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">DateRange</span><span class="p">(</span><span class="k">new</span> <span class="nb">Date</span><span class="p">(</span><span class="dl">'</span><span class="s1">2025-01-01</span><span class="dl">'</span><span class="p">),</span> <span class="k">new</span> <span class="nb">Date</span><span class="p">(</span><span class="dl">'</span><span class="s1">2025-03-31</span><span class="dl">'</span><span class="p">))</span>
    <span class="kd">const</span> <span class="nx">author</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Author</span><span class="p">(</span><span class="dl">'</span><span class="s1">Pat Smith</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">pat@example.com</span><span class="dl">'</span><span class="p">)</span>
    <span class="kd">const</span> <span class="nx">builder</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">ReportGeneratorBuilder</span><span class="p">()</span>
    <span class="kd">const</span> <span class="nx">content</span> <span class="o">=</span> <span class="p">{</span>
        <span class="na">title</span><span class="p">:</span> <span class="dl">'</span><span class="s1">Updated Report</span><span class="dl">'</span><span class="p">,</span>
        <span class="na">author</span><span class="p">:</span> <span class="k">new</span> <span class="nx">Author</span><span class="p">(</span><span class="dl">'</span><span class="s1">Jane Doe</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">jane@example.com</span><span class="dl">'</span><span class="p">),</span>
        <span class="na">range</span><span class="p">:</span> <span class="k">new</span> <span class="nx">DateRange</span><span class="p">(</span><span class="k">new</span> <span class="nb">Date</span><span class="p">(</span><span class="dl">'</span><span class="s1">2025-04-01</span><span class="dl">'</span><span class="p">),</span> <span class="k">new</span> <span class="nb">Date</span><span class="p">(</span><span class="dl">'</span><span class="s1">2025-06-31</span><span class="dl">'</span><span class="p">)),</span>
    <span class="p">}</span>
    <span class="kd">const</span> <span class="nx">gen</span> <span class="o">=</span> <span class="nx">builder</span><span class="p">.</span><span class="nx">withCharts</span><span class="p">().</span><span class="nx">withPageSize</span><span class="p">(</span><span class="dl">'</span><span class="s1">A5</span><span class="dl">'</span><span class="p">).</span><span class="nx">build</span><span class="p">()</span>

    <span class="nx">gen</span><span class="p">.</span><span class="nx">generate</span><span class="p">(</span><span class="dl">'</span><span class="s1">Ventas Q1</span><span class="dl">'</span><span class="p">,</span> <span class="nx">range</span><span class="p">,</span> <span class="nx">author</span><span class="p">,</span> <span class="nx">content</span><span class="p">)</span>

    <span class="kd">let</span> <span class="nx">output</span> <span class="o">=</span> <span class="nx">formatConsoleCalls</span><span class="p">(</span><span class="nx">logSpy</span><span class="p">)</span>
    <span class="nx">expect</span><span class="p">(</span><span class="nx">output</span><span class="p">).</span><span class="nx">toMatchSnapshot</span><span class="p">()</span>
<span class="p">})</span>
</code></pre></div></div>

<p>Que genera este snapshot, donde podemos ver que se usan los datos pasados en <code class="language-plaintext highlighter-rouge">content</code>:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Ajustando página a A5...
Generando reporte: Updated Report
Desde Tue Apr 01 2025 hasta Tue Jul 01 2025
Autor: Jane Doe (jane@example.com)
Incluyendo gráficos...
Reporte generado exitosamente.
</code></pre></div></div>

<p>Ahora, extraeremos el cuerpo del método a otro método con la nueva signatura:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">generate</span><span class="p">(</span><span class="nx">title</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">range</span><span class="p">:</span> <span class="nx">DateRange</span><span class="p">,</span> <span class="nx">author</span><span class="p">:</span> <span class="nx">Author</span><span class="p">,</span> <span class="nx">content</span><span class="p">?:</span> <span class="nx">ReportContent</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">content</span><span class="p">)</span> <span class="p">{</span>
        <span class="nx">content</span> <span class="o">=</span> <span class="p">{</span>
            <span class="na">title</span><span class="p">:</span> <span class="nx">title</span><span class="p">,</span>
            <span class="na">range</span><span class="p">:</span> <span class="nx">range</span><span class="p">,</span>
            <span class="na">author</span><span class="p">:</span> <span class="nx">author</span>
        <span class="p">}</span>
    <span class="p">}</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">generateWithContent</span><span class="p">(</span><span class="nx">content</span><span class="p">);</span>
<span class="p">}</span>

<span class="nx">generateWithContent</span><span class="p">(</span><span class="nx">content</span><span class="p">:</span> <span class="nx">ReportContent</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">if</span> <span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">configuration</span><span class="p">.</span><span class="nx">pageSize</span> <span class="o">!==</span> <span class="dl">'</span><span class="s1">A4</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`Ajustando página a </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">configuration</span><span class="p">.</span><span class="nx">pageSize</span><span class="p">}</span><span class="s2">...`</span><span class="p">);</span>
    <span class="p">}</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`Generando reporte: </span><span class="p">${</span><span class="nx">content</span><span class="p">.</span><span class="nx">title</span><span class="p">}</span><span class="s2">`</span><span class="p">);</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">content</span><span class="p">.</span><span class="nx">range</span><span class="p">.</span><span class="nx">print</span><span class="p">(</span><span class="s2">`Desde  hasta `</span><span class="p">))</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">content</span><span class="p">.</span><span class="nx">author</span><span class="p">.</span><span class="nx">print</span><span class="p">(</span><span class="s2">`Autor:  ()`</span><span class="p">))</span>
    <span class="k">if</span> <span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">configuration</span><span class="p">.</span><span class="nx">includeCharts</span><span class="p">)</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Incluyendo gráficos...</span><span class="dl">'</span><span class="p">)</span>
    <span class="k">if</span> <span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">configuration</span><span class="p">.</span><span class="nx">includeSummary</span><span class="p">)</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Incluyendo resumen...</span><span class="dl">'</span><span class="p">)</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Reporte generado exitosamente.</span><span class="dl">'</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<p>De nuevo tenemos dos métodos alternativos, uno deprecable y otro para usar a partir de ahora y para ir refactorizando los usos existentes. Demos algunos retoques y cambiemos el test:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">it</span><span class="p">(</span><span class="dl">'</span><span class="s1">Generates a report using ReportContent object</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">logSpy</span> <span class="o">=</span> <span class="nx">vi</span><span class="p">.</span><span class="nx">spyOn</span><span class="p">(</span><span class="nx">console</span><span class="p">,</span> <span class="dl">'</span><span class="s1">log</span><span class="dl">'</span><span class="p">)</span>

    <span class="kd">const</span> <span class="nx">builder</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">ReportGeneratorBuilder</span><span class="p">()</span>
    <span class="kd">const</span> <span class="nx">content</span> <span class="o">=</span> <span class="p">{</span>
        <span class="na">title</span><span class="p">:</span> <span class="dl">'</span><span class="s1">Updated Report</span><span class="dl">'</span><span class="p">,</span>
        <span class="na">author</span><span class="p">:</span> <span class="k">new</span> <span class="nx">Author</span><span class="p">(</span><span class="dl">'</span><span class="s1">Jane Doe</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">jane@example.com</span><span class="dl">'</span><span class="p">),</span>
        <span class="na">range</span><span class="p">:</span> <span class="k">new</span> <span class="nx">DateRange</span><span class="p">(</span><span class="k">new</span> <span class="nb">Date</span><span class="p">(</span><span class="dl">'</span><span class="s1">2025-04-01</span><span class="dl">'</span><span class="p">),</span> <span class="k">new</span> <span class="nb">Date</span><span class="p">(</span><span class="dl">'</span><span class="s1">2025-06-31</span><span class="dl">'</span><span class="p">)),</span>
    <span class="p">}</span>
    <span class="kd">const</span> <span class="nx">gen</span> <span class="o">=</span> <span class="nx">builder</span><span class="p">.</span><span class="nx">withCharts</span><span class="p">().</span><span class="nx">withPageSize</span><span class="p">(</span><span class="dl">'</span><span class="s1">A5</span><span class="dl">'</span><span class="p">).</span><span class="nx">build</span><span class="p">()</span>

    <span class="nx">gen</span><span class="p">.</span><span class="nx">generateWithContent</span><span class="p">(</span><span class="nx">content</span><span class="p">)</span>

    <span class="kd">let</span> <span class="nx">output</span> <span class="o">=</span> <span class="nx">formatConsoleCalls</span><span class="p">(</span><span class="nx">logSpy</span><span class="p">)</span>
    <span class="nx">expect</span><span class="p">(</span><span class="nx">output</span><span class="p">).</span><span class="nx">toMatchSnapshot</span><span class="p">()</span>
<span class="p">})</span>
</code></pre></div></div>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">class</span> <span class="nx">ReportGenerator</span> <span class="p">{</span>
    <span class="k">private</span> <span class="nx">configuration</span><span class="p">:</span> <span class="nx">ReportConfiguration</span>

    <span class="kd">constructor</span><span class="p">(</span><span class="nx">configuration</span><span class="p">:</span> <span class="nx">ReportConfiguration</span> <span class="o">=</span> <span class="p">{</span><span class="na">includeCharts</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span> <span class="na">includeSummary</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span> <span class="na">pageSize</span><span class="p">:</span> <span class="dl">'</span><span class="s1">A4</span><span class="dl">'</span><span class="p">})</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">configuration</span> <span class="o">=</span> <span class="nx">configuration</span>
    <span class="p">}</span>

    <span class="nx">generateReport</span><span class="p">(</span>
        <span class="nx">title</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
        <span class="nx">startDate</span><span class="p">:</span> <span class="nb">Date</span><span class="p">,</span>
        <span class="nx">endDate</span><span class="p">:</span> <span class="nb">Date</span><span class="p">,</span>
        <span class="nx">includeCharts</span><span class="p">:</span> <span class="nx">boolean</span><span class="p">,</span>
        <span class="nx">includeSummary</span><span class="p">:</span> <span class="nx">boolean</span><span class="p">,</span>
        <span class="nx">authorName</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
        <span class="nx">authorEmail</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
    <span class="p">)</span> <span class="p">{</span>
        <span class="kd">const</span> <span class="nx">range</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">DateRange</span><span class="p">(</span><span class="nx">startDate</span><span class="p">,</span> <span class="nx">endDate</span><span class="p">)</span>
        <span class="kd">const</span> <span class="nx">author</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Author</span><span class="p">(</span><span class="nx">authorName</span><span class="p">,</span> <span class="nx">authorEmail</span><span class="p">)</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">configuration</span> <span class="o">=</span> <span class="p">{</span>
            <span class="na">includeCharts</span><span class="p">:</span> <span class="nx">includeCharts</span><span class="p">,</span>
            <span class="na">includeSummary</span><span class="p">:</span> <span class="nx">includeSummary</span><span class="p">,</span>
            <span class="na">pageSize</span><span class="p">:</span> <span class="k">this</span><span class="p">.</span><span class="nx">configuration</span><span class="p">.</span><span class="nx">pageSize</span><span class="p">,</span>
        <span class="p">}</span>
        <span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">generate</span><span class="p">(</span><span class="nx">title</span><span class="p">,</span> <span class="nx">range</span><span class="p">,</span> <span class="nx">author</span><span class="p">)</span>
    <span class="p">}</span>

    <span class="nx">generate</span><span class="p">(</span><span class="nx">title</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">range</span><span class="p">:</span> <span class="nx">DateRange</span><span class="p">,</span> <span class="nx">author</span><span class="p">:</span> <span class="nx">Author</span><span class="p">,</span> <span class="nx">content</span><span class="p">?:</span> <span class="nx">ReportContent</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">content</span><span class="p">)</span> <span class="p">{</span>
            <span class="nx">content</span> <span class="o">=</span> <span class="p">{</span>
                <span class="na">title</span><span class="p">:</span> <span class="nx">title</span><span class="p">,</span>
                <span class="na">range</span><span class="p">:</span> <span class="nx">range</span><span class="p">,</span>
                <span class="na">author</span><span class="p">:</span> <span class="nx">author</span>
            <span class="p">}</span>
        <span class="p">}</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">generateWithContent</span><span class="p">(</span><span class="nx">content</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="nx">generateWithContent</span><span class="p">(</span><span class="nx">content</span><span class="p">:</span> <span class="nx">ReportContent</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">configuration</span><span class="p">.</span><span class="nx">pageSize</span> <span class="o">!==</span> <span class="dl">'</span><span class="s1">A4</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
            <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`Ajustando página a </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">configuration</span><span class="p">.</span><span class="nx">pageSize</span><span class="p">}</span><span class="s2">...`</span><span class="p">);</span>
        <span class="p">}</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`Generando reporte: </span><span class="p">${</span><span class="nx">content</span><span class="p">.</span><span class="nx">title</span><span class="p">}</span><span class="s2">`</span><span class="p">);</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">content</span><span class="p">.</span><span class="nx">range</span><span class="p">.</span><span class="nx">print</span><span class="p">(</span><span class="s2">`Desde  hasta `</span><span class="p">))</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">content</span><span class="p">.</span><span class="nx">author</span><span class="p">.</span><span class="nx">print</span><span class="p">(</span><span class="s2">`Autor:  ()`</span><span class="p">))</span>
        <span class="k">if</span> <span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">configuration</span><span class="p">.</span><span class="nx">includeCharts</span><span class="p">)</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Incluyendo gráficos...</span><span class="dl">'</span><span class="p">)</span>
        <span class="k">if</span> <span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">configuration</span><span class="p">.</span><span class="nx">includeSummary</span><span class="p">)</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Incluyendo resumen...</span><span class="dl">'</span><span class="p">)</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Reporte generado exitosamente.</span><span class="dl">'</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Para introducir nuevos parámetros no tenemos más que añadir campos al <code class="language-plaintext highlighter-rouge">ReportContent</code>, parámetros que inicialmente pueden ser opcionales.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kr">interface</span> <span class="nx">ReportContent</span> <span class="p">{</span>
    <span class="nl">title</span><span class="p">:</span> <span class="kr">string</span>
    <span class="nx">range</span><span class="p">:</span> <span class="nx">DateRange</span>
    <span class="nx">author</span><span class="p">:</span> <span class="nx">Author</span>
    <span class="nx">locale</span><span class="p">?:</span> <span class="kr">string</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="conclusiones">Conclusiones</h2>

<p><em>Long Parameter List</em> es otro de esos <em>smells</em> en el que vamos cayendo poco a poco hasta que la gestión de nuestro código se complica demasiado. Podemos atacarlo con varios refactors:</p>

<ul>
  <li><em>Introduce Value Object</em> para agrupar parámetros que tienen una relación tal que podríamos considerar un <em>Data Clump</em> y, por tanto, que podrían estar representando un concepto.</li>
  <li><em>Introduce Parameter Object</em> cuando los parámetros ya no tienen esa relación estrecha y necesitamos mantener una firma estable y fácil de gestionar aunque requiera varios parámetros.</li>
  <li>El patrón <em>Builder</em> aplica a la firma de las funciones constructoras, permitiéndonos incluir un lenguaje de construcción expresivo y ocultar los detalles de esa construcción compleja. Incluso aunque finalmente la resolvamos con un <em>Parameter Object</em>.</li>
</ul>

<p>En último término, estos objetos que introducimos nos protegen de futuros cambios de las firmas de los métodos cuando consideramos que son inestables o propensas a cambiar con facilidad.</p>]]></content><author><name></name></author><category term="articles" /><category term="code-smells" /><category term="refactoring" /><category term="typescript" /><summary type="html"><![CDATA[Otro smell de la familia de los Bloaters. Long Parameter List ocurre cuando una función o método recibe más de tres ó cuatro parámetros.]]></summary></entry><entry><title type="html">Long method</title><link href="https://franiglesias.github.io/long-method/" rel="alternate" type="text/html" title="Long method" /><published>2025-11-08T00:00:00+00:00</published><updated>2025-11-08T00:00:00+00:00</updated><id>https://franiglesias.github.io/long-method</id><content type="html" xml:base="https://franiglesias.github.io/long-method/"><![CDATA[<p>Un code smell en el que es fácil caer es <em>Long Method</em>. Añades línes y más líneas a una función o método hasta que empieza a ser difícil de leer y de intervenir. Y un método largo, requiere un artículo largo.</p>

<h2 id="definición">Definición</h2>

<p>Un método en una clase es muy largo. Tiene muchas líneas de código posiblemente está haciendo muchas cosas diferentes.
Seguramente están mezclados distintos niveles de abstracción o distintas responsabilidades.</p>

<h2 id="ejemplo">Ejemplo</h2>

<p>Es muy habitual que los ejemplos de este tipo de <em>smell</em> no sean tan largos como para resultar realistas, pero en esta
ocasión he forzado un poco a la IA a fin de generar este bonito código que, sin ser un ejemplo real, ilustra
perfectamente el problema. El código incluye un par de funciones auxiliares, así como algunos tipos.</p>

<p>El método <code class="language-plaintext highlighter-rouge">process</code> tiene unas 380 líneas.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nx">OrderService</span> <span class="p">{</span>
    <span class="nx">process</span><span class="p">(</span><span class="nx">order</span><span class="p">:</span> <span class="nx">Order</span><span class="p">)</span> <span class="p">{</span>
        <span class="c1">// Validar el pedido</span>
        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">order</span><span class="p">.</span><span class="nx">items</span> <span class="o">||</span> <span class="nx">order</span><span class="p">.</span><span class="nx">items</span><span class="p">.</span><span class="nx">length</span> <span class="o">===</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
            <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">El pedido no tiene productos</span><span class="dl">'</span><span class="p">)</span>
            <span class="k">return</span>
        <span class="p">}</span>

        <span class="c1">// Validar precios y cantidades</span>
        <span class="k">for</span> <span class="p">(</span><span class="kd">const</span> <span class="nx">item</span> <span class="k">of</span> <span class="nx">order</span><span class="p">.</span><span class="nx">items</span><span class="p">)</span> <span class="p">{</span>
            <span class="k">if</span> <span class="p">(</span><span class="nx">item</span><span class="p">.</span><span class="nx">price</span> <span class="o">&lt;</span> <span class="mi">0</span> <span class="o">||</span> <span class="nx">item</span><span class="p">.</span><span class="nx">quantity</span> <span class="o">&lt;=</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
                <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Producto inválido en el pedido</span><span class="dl">'</span><span class="p">)</span>
                <span class="k">return</span>
            <span class="p">}</span>
        <span class="p">}</span>

        <span class="c1">// Constantes de negocio (simples por ahora)</span>
        <span class="kd">const</span> <span class="nx">TAX_RATE</span> <span class="o">=</span> <span class="mf">0.21</span> <span class="c1">// 21% IVA</span>
        <span class="kd">const</span> <span class="nx">FREE_SHIPPING_THRESHOLD</span> <span class="o">=</span> <span class="mi">50</span>
        <span class="kd">const</span> <span class="nx">SHIPPING_FLAT</span> <span class="o">=</span> <span class="mi">5</span>

        <span class="c1">// Calcular subtotal</span>
        <span class="kd">let</span> <span class="nx">subtotal</span> <span class="o">=</span> <span class="mi">0</span>
        <span class="k">for</span> <span class="p">(</span><span class="kd">const</span> <span class="nx">item</span> <span class="k">of</span> <span class="nx">order</span><span class="p">.</span><span class="nx">items</span><span class="p">)</span> <span class="p">{</span>
            <span class="nx">subtotal</span> <span class="o">+=</span> <span class="nx">item</span><span class="p">.</span><span class="nx">price</span> <span class="o">*</span> <span class="nx">item</span><span class="p">.</span><span class="nx">quantity</span>
        <span class="p">}</span>

        <span class="c1">// Descuento por cliente VIP (10% del subtotal)</span>
        <span class="kd">let</span> <span class="nx">discount</span> <span class="o">=</span> <span class="mi">0</span>
        <span class="k">if</span> <span class="p">(</span><span class="nx">order</span><span class="p">.</span><span class="nx">customerType</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">VIP</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
            <span class="nx">discount</span> <span class="o">=</span> <span class="nx">roundMoney</span><span class="p">(</span><span class="nx">subtotal</span> <span class="o">*</span> <span class="mf">0.1</span><span class="p">)</span>
            <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Descuento VIP aplicado</span><span class="dl">'</span><span class="p">)</span>
        <span class="p">}</span>

        <span class="c1">// Base imponible</span>
        <span class="kd">const</span> <span class="nx">taxable</span> <span class="o">=</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">max</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="nx">subtotal</span> <span class="o">-</span> <span class="nx">discount</span><span class="p">)</span>

        <span class="c1">// Impuestos</span>
        <span class="kd">const</span> <span class="nx">tax</span> <span class="o">=</span> <span class="nx">roundMoney</span><span class="p">(</span><span class="nx">taxable</span> <span class="o">*</span> <span class="nx">TAX_RATE</span><span class="p">)</span>

        <span class="c1">// Envío</span>
        <span class="kd">const</span> <span class="nx">shipping</span> <span class="o">=</span> <span class="nx">taxable</span> <span class="o">&gt;=</span> <span class="nx">FREE_SHIPPING_THRESHOLD</span> <span class="p">?</span> <span class="mi">0</span> <span class="p">:</span> <span class="nx">SHIPPING_FLAT</span>

        <span class="c1">// Total</span>
        <span class="kd">const</span> <span class="nx">total</span> <span class="o">=</span> <span class="nx">roundMoney</span><span class="p">(</span><span class="nx">taxable</span> <span class="o">+</span> <span class="nx">tax</span> <span class="o">+</span> <span class="nx">shipping</span><span class="p">)</span>

        <span class="c1">// Actualizar el pedido (side-effects requeridos)</span>
        <span class="nx">order</span><span class="p">.</span><span class="nx">subtotal</span> <span class="o">=</span> <span class="nx">roundMoney</span><span class="p">(</span><span class="nx">subtotal</span><span class="p">)</span>
        <span class="nx">order</span><span class="p">.</span><span class="nx">discount</span> <span class="o">=</span> <span class="nx">discount</span>
        <span class="nx">order</span><span class="p">.</span><span class="nx">tax</span> <span class="o">=</span> <span class="nx">tax</span>
        <span class="nx">order</span><span class="p">.</span><span class="nx">shipping</span> <span class="o">=</span> <span class="nx">shipping</span>
        <span class="nx">order</span><span class="p">.</span><span class="nx">total</span> <span class="o">=</span> <span class="nx">total</span>

        <span class="c1">// Registrar en la base de datos (simulado)</span>
        <span class="c1">// Bloque gigantesco y sobrecargado para simular persistencia con múltiples pasos innecesarios</span>
        <span class="kd">const</span> <span class="nx">dbConnectionString</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">Server=fake.db.local;Database=orders;User=demo;Password=demo</span><span class="dl">'</span>
        <span class="kd">const</span> <span class="nx">dbConnected</span> <span class="o">=</span> <span class="kc">true</span> <span class="c1">// pretendemos que ya está conectado</span>
        <span class="kd">const</span> <span class="nx">dbRetriesMax</span> <span class="o">=</span> <span class="mi">3</span>
        <span class="kd">let</span> <span class="nx">dbRetries</span> <span class="o">=</span> <span class="mi">0</span>
        <span class="kd">const</span> <span class="nx">dbNow</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Date</span><span class="p">()</span>
        <span class="kd">const</span> <span class="nx">dbRecordId</span> <span class="o">=</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">floor</span><span class="p">(</span><span class="nb">Math</span><span class="p">.</span><span class="nx">random</span><span class="p">()</span> <span class="o">*</span> <span class="mi">1000000</span><span class="p">)</span>

        <span class="c1">// Preparar registro a guardar</span>
        <span class="kd">const</span> <span class="nx">dbRecord</span> <span class="o">=</span> <span class="p">{</span>
            <span class="na">id</span><span class="p">:</span> <span class="nx">dbRecordId</span><span class="p">,</span>
            <span class="na">customerEmail</span><span class="p">:</span> <span class="nx">order</span><span class="p">.</span><span class="nx">customerEmail</span><span class="p">,</span>
            <span class="na">customerType</span><span class="p">:</span> <span class="nx">order</span><span class="p">.</span><span class="nx">customerType</span><span class="p">,</span>
            <span class="na">items</span><span class="p">:</span> <span class="nx">order</span><span class="p">.</span><span class="nx">items</span><span class="p">.</span><span class="nx">map</span><span class="p">(</span><span class="nx">i</span> <span class="o">=&gt;</span> <span class="p">({</span><span class="na">name</span><span class="p">:</span> <span class="nx">i</span><span class="p">.</span><span class="nx">name</span><span class="p">,</span> <span class="na">price</span><span class="p">:</span> <span class="nx">i</span><span class="p">.</span><span class="nx">price</span><span class="p">,</span> <span class="na">quantity</span><span class="p">:</span> <span class="nx">i</span><span class="p">.</span><span class="nx">quantity</span><span class="p">})),</span>
            <span class="na">amounts</span><span class="p">:</span> <span class="p">{</span>
                <span class="na">subtotal</span><span class="p">:</span> <span class="nx">order</span><span class="p">.</span><span class="nx">subtotal</span><span class="p">,</span>
                <span class="na">discount</span><span class="p">:</span> <span class="nx">order</span><span class="p">.</span><span class="nx">discount</span><span class="p">,</span>
                <span class="na">tax</span><span class="p">:</span> <span class="nx">order</span><span class="p">.</span><span class="nx">tax</span><span class="p">,</span>
                <span class="na">shipping</span><span class="p">:</span> <span class="nx">order</span><span class="p">.</span><span class="nx">shipping</span><span class="p">,</span>
                <span class="na">total</span><span class="p">:</span> <span class="nx">order</span><span class="p">.</span><span class="nx">total</span><span class="p">,</span>
            <span class="p">},</span>
            <span class="na">status</span><span class="p">:</span> <span class="dl">'</span><span class="s1">PENDING</span><span class="dl">'</span><span class="p">,</span>
            <span class="na">createdAt</span><span class="p">:</span> <span class="nx">dbNow</span><span class="p">.</span><span class="nx">toISOString</span><span class="p">(),</span>
            <span class="na">updatedAt</span><span class="p">:</span> <span class="nx">dbNow</span><span class="p">.</span><span class="nx">toISOString</span><span class="p">(),</span>
            <span class="na">currency</span><span class="p">:</span> <span class="dl">'</span><span class="s1">USD</span><span class="dl">'</span><span class="p">,</span>
        <span class="p">}</span>

        <span class="c1">// Validaciones redundantes antes de guardar</span>
        <span class="kd">const</span> <span class="nx">hasItems</span> <span class="o">=</span> <span class="nb">Array</span><span class="p">.</span><span class="nx">isArray</span><span class="p">(</span><span class="nx">dbRecord</span><span class="p">.</span><span class="nx">items</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="nx">dbRecord</span><span class="p">.</span><span class="nx">items</span><span class="p">.</span><span class="nx">length</span> <span class="o">&gt;</span> <span class="mi">0</span>
        <span class="kd">const</span> <span class="nx">totalsConsistent</span> <span class="o">=</span> <span class="k">typeof</span> <span class="nx">dbRecord</span><span class="p">.</span><span class="nx">amounts</span><span class="p">.</span><span class="nx">total</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">number</span><span class="dl">'</span> <span class="o">&amp;&amp;</span> <span class="nx">dbRecord</span><span class="p">.</span><span class="nx">amounts</span><span class="p">.</span><span class="nx">total</span> <span class="o">&gt;=</span> <span class="mi">0</span>
        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">hasItems</span><span class="p">)</span> <span class="p">{</span>
            <span class="nx">console</span><span class="p">.</span><span class="nx">warn</span><span class="p">(</span><span class="dl">'</span><span class="s1">[DB] No se puede guardar: el pedido no tiene items</span><span class="dl">'</span><span class="p">)</span>
        <span class="p">}</span>
        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">totalsConsistent</span><span class="p">)</span> <span class="p">{</span>
            <span class="nx">console</span><span class="p">.</span><span class="nx">warn</span><span class="p">(</span><span class="dl">'</span><span class="s1">[DB] No se puede guardar: total inconsistente</span><span class="dl">'</span><span class="p">)</span>
        <span class="p">}</span>

        <span class="c1">// Simular transformación/serialización pesada</span>
        <span class="kd">const</span> <span class="nx">serialized</span> <span class="o">=</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">(</span><span class="nx">dbRecord</span><span class="p">,</span> <span class="kc">null</span><span class="p">,</span> <span class="mi">2</span><span class="p">)</span>
        <span class="kd">const</span> <span class="nx">payloadBytes</span> <span class="o">=</span> <span class="nx">Buffer</span><span class="p">.</span><span class="nx">byteLength</span><span class="p">(</span><span class="nx">serialized</span><span class="p">,</span> <span class="dl">'</span><span class="s1">utf8</span><span class="dl">'</span><span class="p">)</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`[DB] Serializando registro </span><span class="p">${</span><span class="nx">dbRecord</span><span class="p">.</span><span class="nx">id</span><span class="p">}</span><span class="s2"> (</span><span class="p">${</span><span class="nx">payloadBytes</span><span class="p">}</span><span class="s2"> bytes) para </span><span class="p">${</span><span class="nx">dbConnectionString</span><span class="p">}</span><span class="s2">`</span><span class="p">)</span>

        <span class="c1">// Simular reintentos de escritura</span>
        <span class="kd">let</span> <span class="nx">dbSaved</span> <span class="o">=</span> <span class="kc">false</span>
        <span class="k">while</span> <span class="p">(</span><span class="o">!</span><span class="nx">dbSaved</span> <span class="o">&amp;&amp;</span> <span class="nx">dbRetries</span> <span class="o">&lt;</span> <span class="nx">dbRetriesMax</span><span class="p">)</span> <span class="p">{</span>
            <span class="nx">dbRetries</span><span class="o">++</span>
            <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">dbConnected</span><span class="p">)</span> <span class="p">{</span>
                <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`[DB] Intento </span><span class="p">${</span><span class="nx">dbRetries</span><span class="p">}</span><span class="s2">/</span><span class="p">${</span><span class="nx">dbRetriesMax</span><span class="p">}</span><span class="s2">: reconectando a la base de datos...`</span><span class="p">)</span>
            <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
                <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`[DB] Intento </span><span class="p">${</span><span class="nx">dbRetries</span><span class="p">}</span><span class="s2">/</span><span class="p">${</span><span class="nx">dbRetriesMax</span><span class="p">}</span><span class="s2">: guardando pedido </span><span class="p">${</span><span class="nx">dbRecord</span><span class="p">.</span><span class="nx">id</span><span class="p">}</span><span class="s2"> con total </span><span class="p">${</span><span class="nx">formatMoney</span><span class="p">(</span><span class="nx">total</span><span class="p">)}</span><span class="s2">`</span><span class="p">)</span>
            <span class="p">}</span>
            <span class="c1">// Resultado aleatorio simulado, pero aquí siempre "exitoso" para no complicar flujos de prueba</span>
            <span class="nx">dbSaved</span> <span class="o">=</span> <span class="kc">true</span>
        <span class="p">}</span>

        <span class="k">if</span> <span class="p">(</span><span class="nx">dbSaved</span><span class="p">)</span> <span class="p">{</span>
            <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`[DB] Pedido </span><span class="p">${</span><span class="nx">dbRecord</span><span class="p">.</span><span class="nx">id</span><span class="p">}</span><span class="s2"> guardado correctamente`</span><span class="p">)</span>
        <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
            <span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">(</span><span class="s2">`[DB] No se pudo guardar el pedido </span><span class="p">${</span><span class="nx">dbRecord</span><span class="p">.</span><span class="nx">id</span><span class="p">}</span><span class="s2"> tras </span><span class="p">${</span><span class="nx">dbRetriesMax</span><span class="p">}</span><span class="s2"> intentos`</span><span class="p">)</span>
        <span class="p">}</span>

        <span class="c1">// Auditoría/bitácora adicional innecesaria</span>
        <span class="kd">const</span> <span class="nx">auditLogEntry</span> <span class="o">=</span> <span class="p">{</span>
            <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">ORDER_SAVED</span><span class="dl">'</span><span class="p">,</span>
            <span class="na">orderId</span><span class="p">:</span> <span class="nx">dbRecord</span><span class="p">.</span><span class="nx">id</span><span class="p">,</span>
            <span class="na">actor</span><span class="p">:</span> <span class="dl">'</span><span class="s1">system</span><span class="dl">'</span><span class="p">,</span>
            <span class="na">at</span><span class="p">:</span> <span class="k">new</span> <span class="nb">Date</span><span class="p">().</span><span class="nx">toISOString</span><span class="p">(),</span>
            <span class="na">metadata</span><span class="p">:</span> <span class="p">{</span>
                <span class="na">ip</span><span class="p">:</span> <span class="dl">'</span><span class="s1">127.0.0.1</span><span class="dl">'</span><span class="p">,</span>
                <span class="na">userAgent</span><span class="p">:</span> <span class="dl">'</span><span class="s1">OrderService/1.0</span><span class="dl">'</span><span class="p">,</span>
            <span class="p">}</span>
        <span class="p">}</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">[AUDIT] Registro:</span><span class="dl">'</span><span class="p">,</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">(</span><span class="nx">auditLogEntry</span><span class="p">))</span>

        <span class="c1">// Enviar correo de confirmación</span>
        <span class="c1">// Bloque gigantesco para simular el envío de un correo con plantillas, adjuntos, y seguimiento</span>
        <span class="kd">const</span> <span class="nx">smtpConfig</span> <span class="o">=</span> <span class="p">{</span>
            <span class="na">host</span><span class="p">:</span> <span class="dl">'</span><span class="s1">smtp.fake.local</span><span class="dl">'</span><span class="p">,</span>
            <span class="na">port</span><span class="p">:</span> <span class="mi">587</span><span class="p">,</span>
            <span class="na">secure</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
            <span class="na">auth</span><span class="p">:</span> <span class="p">{</span><span class="na">user</span><span class="p">:</span> <span class="dl">'</span><span class="s1">notifier</span><span class="dl">'</span><span class="p">,</span> <span class="na">pass</span><span class="p">:</span> <span class="dl">'</span><span class="s1">notifier</span><span class="dl">'</span><span class="p">},</span>
            <span class="na">tls</span><span class="p">:</span> <span class="p">{</span><span class="na">rejectUnauthorized</span><span class="p">:</span> <span class="kc">false</span><span class="p">}</span>
        <span class="p">}</span>
        <span class="kd">const</span> <span class="nx">emailTemplate</span> <span class="o">=</span> <span class="s2">`
      Hola,
      Gracias por tu pedido. Aquí tienes el resumen:\n
      Subtotal: </span><span class="p">${</span><span class="nx">formatMoney</span><span class="p">(</span><span class="nx">order</span><span class="p">.</span><span class="nx">subtotal</span><span class="p">)}</span><span class="s2">\n
      Descuento: </span><span class="p">${</span><span class="nx">order</span><span class="p">.</span><span class="nx">discount</span> <span class="o">&amp;&amp;</span> <span class="nx">order</span><span class="p">.</span><span class="nx">discount</span> <span class="o">&gt;</span> <span class="mi">0</span> <span class="p">?</span> <span class="dl">'</span><span class="s1">-</span><span class="dl">'</span> <span class="o">+</span> <span class="nx">formatMoney</span><span class="p">(</span><span class="nx">order</span><span class="p">.</span><span class="nx">discount</span><span class="p">)</span> <span class="p">:</span> <span class="nx">formatMoney</span><span class="p">(</span><span class="mi">0</span><span class="p">)}</span><span class="s2">\n
      Impuestos: </span><span class="p">${</span><span class="nx">formatMoney</span><span class="p">(</span><span class="nx">order</span><span class="p">.</span><span class="nx">tax</span><span class="p">)}</span><span class="s2">\n
      Envío: </span><span class="p">${</span><span class="nx">formatMoney</span><span class="p">(</span><span class="nx">order</span><span class="p">.</span><span class="nx">shipping</span><span class="p">)}</span><span class="s2">\n
      Total: </span><span class="p">${</span><span class="nx">formatMoney</span><span class="p">(</span><span class="nx">order</span><span class="p">.</span><span class="nx">total</span><span class="p">)}</span><span class="s2">\n

      Nº de pedido: </span><span class="p">${</span><span class="nx">dbRecord</span><span class="p">.</span><span class="nx">id</span><span class="p">}</span><span class="s2">\n
      Fecha: </span><span class="p">${</span><span class="k">new</span> <span class="nb">Date</span><span class="p">().</span><span class="nx">toLocaleString</span><span class="p">()}</span><span class="s2">\n

      Saludos,
      Equipo Demo
    `</span>
        <span class="kd">const</span> <span class="nx">trackingPixelUrl</span> <span class="o">=</span> <span class="s2">`https://tracker.fake.local/pixel?orderId=</span><span class="p">${</span><span class="nx">dbRecord</span><span class="p">.</span><span class="nx">id</span><span class="p">}</span><span class="s2">&amp;t=</span><span class="p">${</span><span class="nb">Date</span><span class="p">.</span><span class="nx">now</span><span class="p">()}</span><span class="s2">`</span>
        <span class="kd">const</span> <span class="nx">emailBodyHtml</span> <span class="o">=</span> <span class="s2">`
      &lt;html&gt;
        &lt;body&gt;
          &lt;p&gt;Hola,&lt;/p&gt;
          &lt;p&gt;Gracias por tu pedido. Aquí tienes el resumen:&lt;/p&gt;
          &lt;ul&gt;
            &lt;li&gt;Subtotal: &lt;strong&gt;</span><span class="p">${</span><span class="nx">formatMoney</span><span class="p">(</span><span class="nx">order</span><span class="p">.</span><span class="nx">subtotal</span><span class="p">)}</span><span class="s2">&lt;/strong&gt;&lt;/li&gt;
            &lt;li&gt;Descuento: &lt;strong&gt;</span><span class="p">${</span><span class="nx">order</span><span class="p">.</span><span class="nx">discount</span> <span class="o">&amp;&amp;</span> <span class="nx">order</span><span class="p">.</span><span class="nx">discount</span> <span class="o">&gt;</span> <span class="mi">0</span> <span class="p">?</span> <span class="dl">'</span><span class="s1">-</span><span class="dl">'</span> <span class="o">+</span> <span class="nx">formatMoney</span><span class="p">(</span><span class="nx">order</span><span class="p">.</span><span class="nx">discount</span><span class="p">)</span> <span class="p">:</span> <span class="nx">formatMoney</span><span class="p">(</span><span class="mi">0</span><span class="p">)}</span><span class="s2">&lt;/strong&gt;&lt;/li&gt;
            &lt;li&gt;Impuestos: &lt;strong&gt;</span><span class="p">${</span><span class="nx">formatMoney</span><span class="p">(</span><span class="nx">order</span><span class="p">.</span><span class="nx">tax</span><span class="p">)}</span><span class="s2">&lt;/strong&gt;&lt;/li&gt;
            &lt;li&gt;Envío: &lt;strong&gt;</span><span class="p">${</span><span class="nx">formatMoney</span><span class="p">(</span><span class="nx">order</span><span class="p">.</span><span class="nx">shipping</span><span class="p">)}</span><span class="s2">&lt;/strong&gt;&lt;/li&gt;
            &lt;li&gt;Total: &lt;strong&gt;</span><span class="p">${</span><span class="nx">formatMoney</span><span class="p">(</span><span class="nx">order</span><span class="p">.</span><span class="nx">total</span><span class="p">)}</span><span class="s2">&lt;/strong&gt;&lt;/li&gt;
          &lt;/ul&gt;
          &lt;p&gt;Nº de pedido: &lt;code&gt;</span><span class="p">${</span><span class="nx">dbRecord</span><span class="p">.</span><span class="nx">id</span><span class="p">}</span><span class="s2">&lt;/code&gt;&lt;/p&gt;
          &lt;p&gt;Fecha: </span><span class="p">${</span><span class="k">new</span> <span class="nb">Date</span><span class="p">().</span><span class="nx">toLocaleString</span><span class="p">()}</span><span class="s2">&lt;/p&gt;
          &lt;img src="</span><span class="p">${</span><span class="nx">trackingPixelUrl</span><span class="p">}</span><span class="s2">" width="1" height="1" alt=""/&gt;
        &lt;/body&gt;
      &lt;/html&gt;
    `</span>

        <span class="kd">const</span> <span class="nx">attachments</span> <span class="o">=</span> <span class="p">[</span>
            <span class="p">{</span><span class="na">filename</span><span class="p">:</span> <span class="s2">`pedido-</span><span class="p">${</span><span class="nx">dbRecord</span><span class="p">.</span><span class="nx">id</span><span class="p">}</span><span class="s2">.json`</span><span class="p">,</span> <span class="na">content</span><span class="p">:</span> <span class="nx">serialized</span><span class="p">,</span> <span class="na">contentType</span><span class="p">:</span> <span class="dl">'</span><span class="s1">application/json</span><span class="dl">'</span><span class="p">},</span>
            <span class="p">{</span><span class="na">filename</span><span class="p">:</span> <span class="dl">'</span><span class="s1">terminos.txt</span><span class="dl">'</span><span class="p">,</span> <span class="na">content</span><span class="p">:</span> <span class="dl">'</span><span class="s1">Términos y condiciones...</span><span class="dl">'</span><span class="p">,</span> <span class="na">contentType</span><span class="p">:</span> <span class="dl">'</span><span class="s1">text/plain</span><span class="dl">'</span><span class="p">}</span>
        <span class="p">]</span>

        <span class="c1">// Simular cálculo de tamaño del correo</span>
        <span class="kd">const</span> <span class="nx">emailSize</span> <span class="o">=</span> <span class="nx">Buffer</span><span class="p">.</span><span class="nx">byteLength</span><span class="p">(</span><span class="nx">emailBodyHtml</span><span class="p">,</span> <span class="dl">'</span><span class="s1">utf8</span><span class="dl">'</span><span class="p">)</span> <span class="o">+</span> <span class="nx">attachments</span><span class="p">.</span><span class="nx">reduce</span><span class="p">((</span><span class="nx">acc</span><span class="p">,</span> <span class="nx">a</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">acc</span> <span class="o">+</span> <span class="nx">Buffer</span><span class="p">.</span><span class="nx">byteLength</span><span class="p">(</span><span class="nx">a</span><span class="p">.</span><span class="nx">content</span><span class="p">,</span> <span class="dl">'</span><span class="s1">utf8</span><span class="dl">'</span><span class="p">),</span> <span class="mi">0</span><span class="p">)</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`[MAIL] Preparando correo (</span><span class="p">${</span><span class="nx">emailSize</span><span class="p">}</span><span class="s2"> bytes) vía </span><span class="p">${</span><span class="nx">smtpConfig</span><span class="p">.</span><span class="nx">host</span><span class="p">}</span><span class="s2">:</span><span class="p">${</span><span class="nx">smtpConfig</span><span class="p">.</span><span class="nx">port</span><span class="p">}</span><span class="s2">`</span><span class="p">)</span>

        <span class="c1">// Simular colas de envío y priorización</span>
        <span class="kd">const</span> <span class="nx">emailPriority</span> <span class="o">=</span> <span class="nx">order</span><span class="p">.</span><span class="nx">customerType</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">VIP</span><span class="dl">'</span> <span class="p">?</span> <span class="dl">'</span><span class="s1">HIGH</span><span class="dl">'</span> <span class="p">:</span> <span class="dl">'</span><span class="s1">NORMAL</span><span class="dl">'</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`[MAIL] Encolando correo (</span><span class="p">${</span><span class="nx">emailPriority</span><span class="p">}</span><span class="s2">) para </span><span class="p">${</span><span class="nx">order</span><span class="p">.</span><span class="nx">customerEmail</span><span class="p">}</span><span class="s2">`</span><span class="p">)</span>

        <span class="c1">// Simular envío con reintentos</span>
        <span class="kd">let</span> <span class="nx">mailAttempts</span> <span class="o">=</span> <span class="mi">0</span>
        <span class="kd">const</span> <span class="nx">mailAttemptsMax</span> <span class="o">=</span> <span class="mi">2</span>
        <span class="kd">let</span> <span class="nx">mailSent</span> <span class="o">=</span> <span class="kc">false</span>
        <span class="k">while</span> <span class="p">(</span><span class="o">!</span><span class="nx">mailSent</span> <span class="o">&amp;&amp;</span> <span class="nx">mailAttempts</span> <span class="o">&lt;</span> <span class="nx">mailAttemptsMax</span><span class="p">)</span> <span class="p">{</span>
            <span class="nx">mailAttempts</span><span class="o">++</span>
            <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`[MAIL] Intento </span><span class="p">${</span><span class="nx">mailAttempts</span><span class="p">}</span><span class="s2">/</span><span class="p">${</span><span class="nx">mailAttemptsMax</span><span class="p">}</span><span class="s2">: enviando correo a </span><span class="p">${</span><span class="nx">order</span><span class="p">.</span><span class="nx">customerEmail</span><span class="p">}</span><span class="s2">`</span><span class="p">)</span>
            <span class="c1">// Simulación simple de éxito</span>
            <span class="nx">mailSent</span> <span class="o">=</span> <span class="kc">true</span>
        <span class="p">}</span>

        <span class="kd">const</span> <span class="nx">messageId</span> <span class="o">=</span> <span class="s2">`msg-</span><span class="p">${</span><span class="nx">dbRecord</span><span class="p">.</span><span class="nx">id</span><span class="p">}</span><span class="s2">-</span><span class="p">${</span><span class="nb">Date</span><span class="p">.</span><span class="nx">now</span><span class="p">()}</span><span class="s2">`</span>
        <span class="k">if</span> <span class="p">(</span><span class="nx">mailSent</span><span class="p">)</span> <span class="p">{</span>
            <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`[MAIL] Correo enviado a </span><span class="p">${</span><span class="nx">order</span><span class="p">.</span><span class="nx">customerEmail</span><span class="p">}</span><span class="s2"> (messageId=</span><span class="p">${</span><span class="nx">messageId</span><span class="p">}</span><span class="s2">)`</span><span class="p">)</span>
        <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
            <span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">(</span><span class="s2">`[MAIL] Fallo al enviar correo a </span><span class="p">${</span><span class="nx">order</span><span class="p">.</span><span class="nx">customerEmail</span><span class="p">}</span><span class="s2"> tras </span><span class="p">${</span><span class="nx">mailAttemptsMax</span><span class="p">}</span><span class="s2"> intentos`</span><span class="p">)</span>
        <span class="p">}</span>

        <span class="c1">// Imprimir resumen -&gt; enviar a impresora</span>
        <span class="kd">const</span> <span class="nx">printJob</span><span class="p">:</span> <span class="nx">PrintJob</span> <span class="o">=</span> <span class="p">{</span>
            <span class="na">title</span><span class="p">:</span> <span class="dl">'</span><span class="s1">Resumen del pedido</span><span class="dl">'</span><span class="p">,</span>
            <span class="na">items</span><span class="p">:</span> <span class="nx">order</span><span class="p">.</span><span class="nx">items</span><span class="p">.</span><span class="nx">map</span><span class="p">(</span><span class="nx">i</span> <span class="o">=&gt;</span> <span class="p">({</span>
                <span class="na">name</span><span class="p">:</span> <span class="nx">i</span><span class="p">.</span><span class="nx">name</span><span class="p">,</span>
                <span class="na">quantity</span><span class="p">:</span> <span class="nx">i</span><span class="p">.</span><span class="nx">quantity</span><span class="p">,</span>
                <span class="na">lineTotal</span><span class="p">:</span> <span class="nx">roundMoney</span><span class="p">(</span><span class="nx">i</span><span class="p">.</span><span class="nx">price</span> <span class="o">*</span> <span class="nx">i</span><span class="p">.</span><span class="nx">quantity</span><span class="p">),</span>
                <span class="na">lineTotalFormatted</span><span class="p">:</span> <span class="nx">formatMoney</span><span class="p">(</span><span class="nx">i</span><span class="p">.</span><span class="nx">price</span> <span class="o">*</span> <span class="nx">i</span><span class="p">.</span><span class="nx">quantity</span><span class="p">),</span>
            <span class="p">})),</span>
            <span class="na">subtotal</span><span class="p">:</span> <span class="nx">order</span><span class="p">.</span><span class="nx">subtotal</span> <span class="o">??</span> <span class="mi">0</span><span class="p">,</span>
            <span class="na">discount</span><span class="p">:</span> <span class="nx">order</span><span class="p">.</span><span class="nx">discount</span> <span class="o">??</span> <span class="mi">0</span><span class="p">,</span>
            <span class="na">tax</span><span class="p">:</span> <span class="nx">order</span><span class="p">.</span><span class="nx">tax</span> <span class="o">??</span> <span class="mi">0</span><span class="p">,</span>
            <span class="na">shipping</span><span class="p">:</span> <span class="nx">order</span><span class="p">.</span><span class="nx">shipping</span> <span class="o">??</span> <span class="mi">0</span><span class="p">,</span>
            <span class="na">total</span><span class="p">:</span> <span class="nx">order</span><span class="p">.</span><span class="nx">total</span> <span class="o">??</span> <span class="mi">0</span><span class="p">,</span>
            <span class="na">currency</span><span class="p">:</span> <span class="dl">'</span><span class="s1">USD</span><span class="dl">'</span><span class="p">,</span>
            <span class="na">formatted</span><span class="p">:</span> <span class="p">{</span>
                <span class="na">subtotal</span><span class="p">:</span> <span class="nx">formatMoney</span><span class="p">(</span><span class="nx">order</span><span class="p">.</span><span class="nx">subtotal</span><span class="p">),</span>
                <span class="na">discount</span><span class="p">:</span> <span class="nx">order</span><span class="p">.</span><span class="nx">discount</span> <span class="o">&amp;&amp;</span> <span class="nx">order</span><span class="p">.</span><span class="nx">discount</span> <span class="o">&gt;</span> <span class="mi">0</span> <span class="p">?</span> <span class="s2">`-</span><span class="p">${</span><span class="nx">formatMoney</span><span class="p">(</span><span class="nx">order</span><span class="p">.</span><span class="nx">discount</span><span class="p">)}</span><span class="s2">`</span> <span class="p">:</span> <span class="nx">formatMoney</span><span class="p">(</span><span class="mi">0</span><span class="p">),</span>
                <span class="na">tax</span><span class="p">:</span> <span class="nx">formatMoney</span><span class="p">(</span><span class="nx">order</span><span class="p">.</span><span class="nx">tax</span><span class="p">),</span>
                <span class="na">shipping</span><span class="p">:</span> <span class="nx">formatMoney</span><span class="p">(</span><span class="nx">order</span><span class="p">.</span><span class="nx">shipping</span><span class="p">),</span>
                <span class="na">total</span><span class="p">:</span> <span class="nx">formatMoney</span><span class="p">(</span><span class="nx">order</span><span class="p">.</span><span class="nx">total</span><span class="p">),</span>
            <span class="p">},</span>
            <span class="na">metadata</span><span class="p">:</span> <span class="p">{</span>
                <span class="na">customerEmail</span><span class="p">:</span> <span class="nx">order</span><span class="p">.</span><span class="nx">customerEmail</span><span class="p">,</span>
                <span class="na">createdAt</span><span class="p">:</span> <span class="k">new</span> <span class="nb">Date</span><span class="p">().</span><span class="nx">toISOString</span><span class="p">(),</span>
            <span class="p">}</span>
        <span class="p">}</span>

        <span class="c1">// Simulación de envío a impresora: bloque deliberadamente grande y sobrecargado</span>
        <span class="c1">// Configuración de impresora (ficticia)</span>
        <span class="kd">const</span> <span class="nx">printerConfig</span> <span class="o">=</span> <span class="p">{</span>
            <span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">Demo Thermal Printer TP-80</span><span class="dl">'</span><span class="p">,</span>
            <span class="na">model</span><span class="p">:</span> <span class="dl">'</span><span class="s1">TP-80</span><span class="dl">'</span><span class="p">,</span>
            <span class="na">dpi</span><span class="p">:</span> <span class="mi">203</span><span class="p">,</span>
            <span class="na">widthMm</span><span class="p">:</span> <span class="mi">80</span><span class="p">,</span>
            <span class="na">maxCharsPerLine</span><span class="p">:</span> <span class="mi">42</span><span class="p">,</span> <span class="c1">// típico en papel de 80mm con fuente estándar</span>
            <span class="na">interface</span><span class="p">:</span> <span class="dl">'</span><span class="s1">USB</span><span class="dl">'</span><span class="p">,</span>
            <span class="na">driver</span><span class="p">:</span> <span class="dl">'</span><span class="s1">ESC/POS</span><span class="dl">'</span><span class="p">,</span>
            <span class="na">location</span><span class="p">:</span> <span class="dl">'</span><span class="s1">Front Desk</span><span class="dl">'</span><span class="p">,</span>
        <span class="p">}</span>

        <span class="c1">// Capabilities detectadas (simuladas)</span>
        <span class="kd">const</span> <span class="nx">printerCaps</span> <span class="o">=</span> <span class="p">{</span>
            <span class="na">supportsBold</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
            <span class="na">supportsUnderline</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
            <span class="na">supportsQr</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
            <span class="na">supportsBarcode</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
            <span class="na">supportsImages</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
            <span class="na">codepage</span><span class="p">:</span> <span class="dl">'</span><span class="s1">cp437</span><span class="dl">'</span>
        <span class="p">}</span>

        <span class="c1">// Conexión (simulada)</span>
        <span class="kd">const</span> <span class="nx">printerConn</span> <span class="o">=</span> <span class="p">{</span><span class="na">connected</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span> <span class="na">retries</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span> <span class="na">maxRetries</span><span class="p">:</span> <span class="mi">2</span><span class="p">}</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`[PRN] Preparando conexión a impresora </span><span class="p">${</span><span class="nx">printerConfig</span><span class="p">.</span><span class="nx">name</span><span class="p">}</span><span class="s2"> (</span><span class="p">${</span><span class="nx">printerConfig</span><span class="p">.</span><span class="kr">interface</span><span class="p">}</span><span class="s2">/</span><span class="p">${</span><span class="nx">printerConfig</span><span class="p">.</span><span class="nx">driver</span><span class="p">}</span><span class="s2">)`</span><span class="p">)</span>

        <span class="c1">// Crear contenido del recibo</span>
        <span class="kd">const</span> <span class="nx">now</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Date</span><span class="p">()</span>
        <span class="kd">const</span> <span class="nx">lineWidth</span> <span class="o">=</span> <span class="nx">printerConfig</span><span class="p">.</span><span class="nx">maxCharsPerLine</span>

        <span class="kd">const</span> <span class="nx">padRight</span> <span class="o">=</span> <span class="p">(</span><span class="nx">text</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">len</span><span class="p">:</span> <span class="kr">number</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">text</span><span class="p">.</span><span class="nx">length</span> <span class="o">&gt;=</span> <span class="nx">len</span> <span class="p">?</span> <span class="nx">text</span><span class="p">.</span><span class="nx">slice</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="nx">len</span><span class="p">)</span> <span class="p">:</span> <span class="nx">text</span> <span class="o">+</span> <span class="dl">'</span><span class="s1"> </span><span class="dl">'</span><span class="p">.</span><span class="nx">repeat</span><span class="p">(</span><span class="nx">len</span> <span class="o">-</span> <span class="nx">text</span><span class="p">.</span><span class="nx">length</span><span class="p">)</span>
        <span class="kd">const</span> <span class="nx">padLeft</span> <span class="o">=</span> <span class="p">(</span><span class="nx">text</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">len</span><span class="p">:</span> <span class="kr">number</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">text</span><span class="p">.</span><span class="nx">length</span> <span class="o">&gt;=</span> <span class="nx">len</span> <span class="p">?</span> <span class="nx">text</span><span class="p">.</span><span class="nx">slice</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="nx">len</span><span class="p">)</span> <span class="p">:</span> <span class="dl">'</span><span class="s1"> </span><span class="dl">'</span><span class="p">.</span><span class="nx">repeat</span><span class="p">(</span><span class="nx">len</span> <span class="o">-</span> <span class="nx">text</span><span class="p">.</span><span class="nx">length</span><span class="p">)</span> <span class="o">+</span> <span class="nx">text</span>
        <span class="kd">const</span> <span class="nx">repeat</span> <span class="o">=</span> <span class="p">(</span><span class="nx">ch</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">n</span><span class="p">:</span> <span class="kr">number</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="k">new</span> <span class="nb">Array</span><span class="p">(</span><span class="nx">n</span> <span class="o">+</span> <span class="mi">1</span><span class="p">).</span><span class="nx">join</span><span class="p">(</span><span class="nx">ch</span><span class="p">)</span>

        <span class="kd">const</span> <span class="nx">formatLine</span> <span class="o">=</span> <span class="p">(</span><span class="nx">left</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">right</span><span class="p">:</span> <span class="kr">string</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
            <span class="kd">const</span> <span class="nx">leftTrim</span> <span class="o">=</span> <span class="nx">left</span> <span class="o">??</span> <span class="dl">''</span>
            <span class="kd">const</span> <span class="nx">rightTrim</span> <span class="o">=</span> <span class="nx">right</span> <span class="o">??</span> <span class="dl">''</span>
            <span class="kd">const</span> <span class="nx">space</span> <span class="o">=</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">max</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="nx">lineWidth</span> <span class="o">-</span> <span class="nx">leftTrim</span><span class="p">.</span><span class="nx">length</span> <span class="o">-</span> <span class="nx">rightTrim</span><span class="p">.</span><span class="nx">length</span><span class="p">)</span>
            <span class="kd">const</span> <span class="nx">spaces</span> <span class="o">=</span> <span class="nx">repeat</span><span class="p">(</span><span class="dl">'</span><span class="s1"> </span><span class="dl">'</span><span class="p">,</span> <span class="nx">space</span><span class="p">)</span>
            <span class="kd">const</span> <span class="nx">tooLong</span> <span class="o">=</span> <span class="nx">leftTrim</span><span class="p">.</span><span class="nx">length</span> <span class="o">+</span> <span class="nx">rightTrim</span><span class="p">.</span><span class="nx">length</span> <span class="o">&gt;</span> <span class="nx">lineWidth</span>
            <span class="k">if</span> <span class="p">(</span><span class="nx">tooLong</span><span class="p">)</span> <span class="p">{</span>
                <span class="c1">// Si no cabe, forzamos salto para la izquierda y mantenemos derecha alineada</span>
                <span class="k">return</span> <span class="nx">leftTrim</span> <span class="o">+</span> <span class="dl">'</span><span class="se">\n</span><span class="dl">'</span> <span class="o">+</span> <span class="nx">padLeft</span><span class="p">(</span><span class="nx">rightTrim</span><span class="p">,</span> <span class="nx">lineWidth</span><span class="p">)</span>
            <span class="p">}</span>
            <span class="k">return</span> <span class="nx">leftTrim</span> <span class="o">+</span> <span class="nx">spaces</span> <span class="o">+</span> <span class="nx">rightTrim</span>
        <span class="p">}</span>

        <span class="c1">// Cabecera</span>
        <span class="kd">const</span> <span class="nx">receiptLines</span><span class="p">:</span> <span class="kr">string</span><span class="p">[]</span> <span class="o">=</span> <span class="p">[]</span>
        <span class="nx">receiptLines</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">repeat</span><span class="p">(</span><span class="dl">'</span><span class="s1">=</span><span class="dl">'</span><span class="p">,</span> <span class="nx">lineWidth</span><span class="p">))</span>
        <span class="nx">receiptLines</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">padRight</span><span class="p">(</span><span class="dl">'</span><span class="s1">RESUMEN DEL PEDIDO</span><span class="dl">'</span><span class="p">,</span> <span class="nx">lineWidth</span><span class="p">))</span>
        <span class="nx">receiptLines</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">padRight</span><span class="p">(</span><span class="nx">now</span><span class="p">.</span><span class="nx">toLocaleString</span><span class="p">(),</span> <span class="nx">lineWidth</span><span class="p">))</span>
        <span class="nx">receiptLines</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">padRight</span><span class="p">(</span><span class="s2">`Cliente: </span><span class="p">${</span><span class="nx">order</span><span class="p">.</span><span class="nx">customerEmail</span><span class="p">}</span><span class="s2">`</span><span class="p">,</span> <span class="nx">lineWidth</span><span class="p">))</span>
        <span class="nx">receiptLines</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">repeat</span><span class="p">(</span><span class="dl">'</span><span class="s1">-</span><span class="dl">'</span><span class="p">,</span> <span class="nx">lineWidth</span><span class="p">))</span>

        <span class="c1">// Items</span>
        <span class="k">for</span> <span class="p">(</span><span class="kd">const</span> <span class="nx">it</span> <span class="k">of</span> <span class="nx">printJob</span><span class="p">.</span><span class="nx">items</span><span class="p">)</span> <span class="p">{</span>
            <span class="kd">const</span> <span class="nx">left</span> <span class="o">=</span> <span class="s2">`</span><span class="p">${</span><span class="nx">it</span><span class="p">.</span><span class="nx">quantity</span><span class="p">}</span><span class="s2"> x </span><span class="p">${</span><span class="nx">it</span><span class="p">.</span><span class="nx">name</span><span class="p">}</span><span class="s2">`</span>
            <span class="kd">const</span> <span class="nx">right</span> <span class="o">=</span> <span class="nx">it</span><span class="p">.</span><span class="nx">lineTotalFormatted</span>
            <span class="nx">receiptLines</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">formatLine</span><span class="p">(</span><span class="nx">left</span><span class="p">,</span> <span class="nx">right</span><span class="p">))</span>
        <span class="p">}</span>

        <span class="c1">// Totales</span>
        <span class="nx">receiptLines</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">repeat</span><span class="p">(</span><span class="dl">'</span><span class="s1">-</span><span class="dl">'</span><span class="p">,</span> <span class="nx">lineWidth</span><span class="p">))</span>
        <span class="nx">receiptLines</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">formatLine</span><span class="p">(</span><span class="dl">'</span><span class="s1">Subtotal</span><span class="dl">'</span><span class="p">,</span> <span class="nx">printJob</span><span class="p">.</span><span class="nx">formatted</span><span class="p">.</span><span class="nx">subtotal</span><span class="p">))</span>
        <span class="k">if</span> <span class="p">((</span><span class="nx">printJob</span><span class="p">.</span><span class="nx">discount</span> <span class="o">??</span> <span class="mi">0</span><span class="p">)</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
            <span class="nx">receiptLines</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">formatLine</span><span class="p">(</span><span class="dl">'</span><span class="s1">Descuento</span><span class="dl">'</span><span class="p">,</span> <span class="s2">`-</span><span class="p">${</span><span class="nx">formatMoney</span><span class="p">(</span><span class="nx">printJob</span><span class="p">.</span><span class="nx">discount</span><span class="p">)}</span><span class="s2">`</span><span class="p">))</span>
        <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
            <span class="nx">receiptLines</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">formatLine</span><span class="p">(</span><span class="dl">'</span><span class="s1">Descuento</span><span class="dl">'</span><span class="p">,</span> <span class="nx">printJob</span><span class="p">.</span><span class="nx">formatted</span><span class="p">.</span><span class="nx">discount</span><span class="p">))</span>
        <span class="p">}</span>
        <span class="nx">receiptLines</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">formatLine</span><span class="p">(</span><span class="dl">'</span><span class="s1">Impuestos</span><span class="dl">'</span><span class="p">,</span> <span class="nx">printJob</span><span class="p">.</span><span class="nx">formatted</span><span class="p">.</span><span class="nx">tax</span><span class="p">))</span>
        <span class="nx">receiptLines</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">formatLine</span><span class="p">(</span><span class="dl">'</span><span class="s1">Envío</span><span class="dl">'</span><span class="p">,</span> <span class="nx">printJob</span><span class="p">.</span><span class="nx">formatted</span><span class="p">.</span><span class="nx">shipping</span><span class="p">))</span>
        <span class="nx">receiptLines</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">formatLine</span><span class="p">(</span><span class="dl">'</span><span class="s1">TOTAL</span><span class="dl">'</span><span class="p">,</span> <span class="nx">printJob</span><span class="p">.</span><span class="nx">formatted</span><span class="p">.</span><span class="nx">total</span><span class="p">))</span>
        <span class="nx">receiptLines</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">repeat</span><span class="p">(</span><span class="dl">'</span><span class="s1">=</span><span class="dl">'</span><span class="p">,</span> <span class="nx">lineWidth</span><span class="p">))</span>

        <span class="c1">// Pie con metadatos</span>
        <span class="nx">receiptLines</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">padRight</span><span class="p">(</span><span class="s2">`Nº pedido: </span><span class="p">${</span><span class="nb">Math</span><span class="p">.</span><span class="nx">abs</span><span class="p">((</span><span class="nx">order</span><span class="p">.</span><span class="nx">total</span> <span class="o">??</span> <span class="mi">0</span><span class="p">)</span> <span class="o">*</span> <span class="mi">1000</span><span class="p">)</span> <span class="o">|</span> <span class="mi">0</span><span class="p">}</span><span class="s2">`</span><span class="p">,</span> <span class="nx">lineWidth</span><span class="p">))</span>
        <span class="nx">receiptLines</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">padRight</span><span class="p">(</span><span class="s2">`Moneda: </span><span class="p">${</span><span class="nx">printJob</span><span class="p">.</span><span class="nx">currency</span><span class="p">}</span><span class="s2">`</span><span class="p">,</span> <span class="nx">lineWidth</span><span class="p">))</span>
        <span class="nx">receiptLines</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">padRight</span><span class="p">(</span><span class="s2">`Creado: </span><span class="p">${</span><span class="nx">printJob</span><span class="p">.</span><span class="nx">metadata</span><span class="p">.</span><span class="nx">createdAt</span><span class="p">}</span><span class="s2">`</span><span class="p">,</span> <span class="nx">lineWidth</span><span class="p">))</span>

        <span class="c1">// Comandos ESC/POS simulados (no operativos, solo logging)</span>
        <span class="kd">const</span> <span class="nx">escposCommands</span> <span class="o">=</span> <span class="p">[</span>
            <span class="dl">'</span><span class="s1">[INIT]</span><span class="dl">'</span><span class="p">,</span>
            <span class="dl">'</span><span class="s1">[ALIGN LEFT]</span><span class="dl">'</span><span class="p">,</span>
            <span class="dl">'</span><span class="s1">[FONT A]</span><span class="dl">'</span><span class="p">,</span>
            <span class="nx">printerCaps</span><span class="p">.</span><span class="nx">supportsBold</span> <span class="p">?</span> <span class="dl">'</span><span class="s1">[BOLD ON]</span><span class="dl">'</span> <span class="p">:</span> <span class="dl">'</span><span class="s1">[BOLD N/A]</span><span class="dl">'</span><span class="p">,</span>
            <span class="dl">'</span><span class="s1">[PRINT LINES]</span><span class="dl">'</span><span class="p">,</span>
            <span class="dl">'</span><span class="s1">[BOLD OFF]</span><span class="dl">'</span><span class="p">,</span>
            <span class="dl">'</span><span class="s1">[CUT PARTIAL]</span><span class="dl">'</span>
        <span class="p">]</span>

        <span class="c1">// Montar payload a imprimir</span>
        <span class="kd">const</span> <span class="nx">textPayload</span> <span class="o">=</span> <span class="nx">receiptLines</span><span class="p">.</span><span class="nx">join</span><span class="p">(</span><span class="dl">'</span><span class="se">\n</span><span class="dl">'</span><span class="p">)</span> <span class="o">+</span> <span class="dl">'</span><span class="se">\n</span><span class="dl">'</span> <span class="o">+</span> <span class="nx">repeat</span><span class="p">(</span><span class="dl">'</span><span class="s1">-</span><span class="dl">'</span><span class="p">,</span> <span class="nx">lineWidth</span><span class="p">)</span> <span class="o">+</span> <span class="dl">'</span><span class="se">\n</span><span class="dl">'</span>
        <span class="kd">const</span> <span class="nx">commandSection</span> <span class="o">=</span> <span class="nx">escposCommands</span><span class="p">.</span><span class="nx">join</span><span class="p">(</span><span class="dl">'</span><span class="s1"> </span><span class="dl">'</span><span class="p">)</span>
        <span class="kd">const</span> <span class="nx">printable</span> <span class="o">=</span> <span class="s2">`\n</span><span class="p">${</span><span class="nx">commandSection</span><span class="p">}</span><span class="s2">\n</span><span class="p">${</span><span class="nx">textPayload</span><span class="p">}</span><span class="s2">`</span>
        <span class="kd">const</span> <span class="nx">spoolBuffer</span> <span class="o">=</span> <span class="nx">Buffer</span><span class="p">.</span><span class="k">from</span><span class="p">(</span><span class="nx">printable</span><span class="p">,</span> <span class="dl">'</span><span class="s1">utf8</span><span class="dl">'</span><span class="p">)</span>
        <span class="kd">const</span> <span class="nx">spoolBytes</span> <span class="o">=</span> <span class="nx">Buffer</span><span class="p">.</span><span class="nx">byteLength</span><span class="p">(</span><span class="nx">printable</span><span class="p">,</span> <span class="dl">'</span><span class="s1">utf8</span><span class="dl">'</span><span class="p">)</span>

        <span class="c1">// Simulación de QR/barcode en el ticket (solo registro)</span>
        <span class="kd">const</span> <span class="nx">qrData</span> <span class="o">=</span> <span class="s2">`ORDER|</span><span class="p">${</span><span class="nx">order</span><span class="p">.</span><span class="nx">customerEmail</span><span class="p">}</span><span class="s2">|</span><span class="p">${</span><span class="nx">printJob</span><span class="p">.</span><span class="nx">total</span><span class="p">}</span><span class="s2">|</span><span class="p">${</span><span class="nx">now</span><span class="p">.</span><span class="nx">getTime</span><span class="p">()}</span><span class="s2">`</span>
        <span class="k">if</span> <span class="p">(</span><span class="nx">printerCaps</span><span class="p">.</span><span class="nx">supportsQr</span><span class="p">)</span> <span class="p">{</span>
            <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`[PRN] Agregando QR con datos: </span><span class="p">${</span><span class="nx">qrData</span><span class="p">}</span><span class="s2">`</span><span class="p">)</span>
        <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="nx">printerCaps</span><span class="p">.</span><span class="nx">supportsBarcode</span><span class="p">)</span> <span class="p">{</span>
            <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`[PRN] Agregando BARCODE con datos: </span><span class="p">${</span><span class="nx">qrData</span><span class="p">.</span><span class="nx">slice</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">12</span><span class="p">)}</span><span class="s2">`</span><span class="p">)</span>
        <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
            <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">[PRN] Sin soporte para QR/BARCODE</span><span class="dl">'</span><span class="p">)</span>
        <span class="p">}</span>

        <span class="c1">// Vista previa ASCII (limitada para no saturar logs)</span>
        <span class="kd">const</span> <span class="nx">preview</span> <span class="o">=</span> <span class="nx">textPayload</span><span class="p">.</span><span class="nx">split</span><span class="p">(</span><span class="dl">'</span><span class="se">\n</span><span class="dl">'</span><span class="p">).</span><span class="nx">slice</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">12</span><span class="p">).</span><span class="nx">join</span><span class="p">(</span><span class="dl">'</span><span class="se">\n</span><span class="dl">'</span><span class="p">)</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">[PRN] Vista previa del recibo:</span><span class="se">\n</span><span class="dl">'</span> <span class="o">+</span> <span class="nx">preview</span> <span class="o">+</span> <span class="p">(</span><span class="nx">receiptLines</span><span class="p">.</span><span class="nx">length</span> <span class="o">&gt;</span> <span class="mi">12</span> <span class="p">?</span> <span class="s2">`\n...(</span><span class="p">${</span><span class="nx">receiptLines</span><span class="p">.</span><span class="nx">length</span> <span class="o">-</span> <span class="mi">12</span><span class="p">}</span><span class="s2"> líneas más)`</span> <span class="p">:</span> <span class="dl">''</span><span class="p">))</span>

        <span class="c1">// Encolado de trabajo de impresión</span>
        <span class="kd">const</span> <span class="nx">printPriority</span> <span class="o">=</span> <span class="nx">order</span><span class="p">.</span><span class="nx">customerType</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">VIP</span><span class="dl">'</span> <span class="p">?</span> <span class="dl">'</span><span class="s1">HIGH</span><span class="dl">'</span> <span class="p">:</span> <span class="dl">'</span><span class="s1">NORMAL</span><span class="dl">'</span>
        <span class="kd">const</span> <span class="nx">printJobId</span> <span class="o">=</span> <span class="s2">`prn-</span><span class="p">${</span><span class="nb">Date</span><span class="p">.</span><span class="nx">now</span><span class="p">()}</span><span class="s2">-</span><span class="p">${</span><span class="nb">Math</span><span class="p">.</span><span class="nx">floor</span><span class="p">(</span><span class="nb">Math</span><span class="p">.</span><span class="nx">random</span><span class="p">()</span> <span class="o">*</span> <span class="mi">1000</span><span class="p">)}</span><span class="s2">`</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`[PRN] Encolando trabajo </span><span class="p">${</span><span class="nx">printJobId</span><span class="p">}</span><span class="s2"> (</span><span class="p">${</span><span class="nx">spoolBytes</span><span class="p">}</span><span class="s2"> bytes, prioridad=</span><span class="p">${</span><span class="nx">printPriority</span><span class="p">}</span><span class="s2">) en </span><span class="p">${</span><span class="nx">printerConfig</span><span class="p">.</span><span class="nx">location</span><span class="p">}</span><span class="s2">`</span><span class="p">)</span>

        <span class="c1">// Envío en trozos (chunking) para simular buffer limitado de la impresora</span>
        <span class="kd">const</span> <span class="nx">chunkSize</span> <span class="o">=</span> <span class="mi">256</span> <span class="c1">// bytes</span>
        <span class="kd">let</span> <span class="nx">sentBytes</span> <span class="o">=</span> <span class="mi">0</span>
        <span class="kd">let</span> <span class="nx">chunkIndex</span> <span class="o">=</span> <span class="mi">0</span>
        <span class="kd">let</span> <span class="nx">sentOk</span> <span class="o">=</span> <span class="kc">true</span>
        <span class="k">while</span> <span class="p">(</span><span class="nx">sentBytes</span> <span class="o">&lt;</span> <span class="nx">spoolBytes</span><span class="p">)</span> <span class="p">{</span>
            <span class="kd">const</span> <span class="nx">remaining</span> <span class="o">=</span> <span class="nx">spoolBytes</span> <span class="o">-</span> <span class="nx">sentBytes</span>
            <span class="kd">const</span> <span class="nx">size</span> <span class="o">=</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">min</span><span class="p">(</span><span class="nx">chunkSize</span><span class="p">,</span> <span class="nx">remaining</span><span class="p">)</span>
            <span class="kd">const</span> <span class="nx">chunk</span> <span class="o">=</span> <span class="nx">spoolBuffer</span><span class="p">.</span><span class="nx">subarray</span><span class="p">(</span><span class="nx">sentBytes</span><span class="p">,</span> <span class="nx">sentBytes</span> <span class="o">+</span> <span class="nx">size</span><span class="p">)</span>
            <span class="c1">// Simular reintentos por chunk</span>
            <span class="kd">let</span> <span class="nx">attempts</span> <span class="o">=</span> <span class="mi">0</span>
            <span class="kd">let</span> <span class="nx">delivered</span> <span class="o">=</span> <span class="kc">false</span>
            <span class="k">while</span> <span class="p">(</span><span class="o">!</span><span class="nx">delivered</span> <span class="o">&amp;&amp;</span> <span class="nx">attempts</span> <span class="o">&lt;</span> <span class="mi">2</span><span class="p">)</span> <span class="p">{</span>
                <span class="nx">attempts</span><span class="o">++</span>
                <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`[PRN] Enviando chunk #</span><span class="p">${</span><span class="nx">chunkIndex</span><span class="p">}</span><span class="s2"> (</span><span class="p">${</span><span class="nx">size</span><span class="p">}</span><span class="s2"> bytes) intento </span><span class="p">${</span><span class="nx">attempts</span><span class="p">}</span><span class="s2">/2`</span><span class="p">)</span>
                <span class="c1">// Éxito simulado</span>
                <span class="nx">delivered</span> <span class="o">=</span> <span class="kc">true</span>
            <span class="p">}</span>
            <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">delivered</span><span class="p">)</span> <span class="p">{</span>
                <span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">(</span><span class="s2">`[PRN] Fallo al enviar chunk #</span><span class="p">${</span><span class="nx">chunkIndex</span><span class="p">}</span><span class="s2">`</span><span class="p">)</span>
                <span class="nx">sentOk</span> <span class="o">=</span> <span class="kc">false</span>
                <span class="k">break</span>
            <span class="p">}</span>
            <span class="nx">sentBytes</span> <span class="o">+=</span> <span class="nx">size</span>
            <span class="nx">chunkIndex</span><span class="o">++</span>
        <span class="p">}</span>

        <span class="c1">// Resultado final de impresión</span>
        <span class="k">if</span> <span class="p">(</span><span class="nx">printerConn</span><span class="p">.</span><span class="nx">connected</span> <span class="o">&amp;&amp;</span> <span class="nx">sentOk</span><span class="p">)</span> <span class="p">{</span>
            <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`[PRN] Trabajo </span><span class="p">${</span><span class="nx">printJobId</span><span class="p">}</span><span class="s2"> impreso correctamente. Total enviado: </span><span class="p">${</span><span class="nx">sentBytes</span><span class="p">}</span><span class="s2"> bytes`</span><span class="p">)</span>
        <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
            <span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">(</span><span class="s2">`[PRN] Error al imprimir trabajo </span><span class="p">${</span><span class="nx">printJobId</span><span class="p">}</span><span class="s2">. Enviado: </span><span class="p">${</span><span class="nx">sentBytes</span><span class="p">}</span><span class="s2">/</span><span class="p">${</span><span class="nx">spoolBytes</span><span class="p">}</span><span class="s2"> bytes`</span><span class="p">)</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="kd">function</span> <span class="nx">roundMoney</span><span class="p">(</span><span class="nx">n</span><span class="p">:</span> <span class="kr">number</span><span class="p">):</span> <span class="kr">number</span> <span class="p">{</span>
    <span class="k">return</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">round</span><span class="p">(</span><span class="nx">n</span> <span class="o">*</span> <span class="mi">100</span><span class="p">)</span> <span class="o">/</span> <span class="mi">100</span>
<span class="p">}</span>

<span class="kd">function</span> <span class="nx">formatMoney</span><span class="p">(</span><span class="nx">n</span><span class="p">:</span> <span class="kr">number</span> <span class="o">|</span> <span class="kc">undefined</span><span class="p">):</span> <span class="kr">string</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">v</span> <span class="o">=</span> <span class="k">typeof</span> <span class="nx">n</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">number</span><span class="dl">'</span> <span class="p">?</span> <span class="nx">n</span> <span class="p">:</span> <span class="mi">0</span>
    <span class="k">return</span> <span class="s2">`$</span><span class="p">${</span><span class="nx">v</span><span class="p">.</span><span class="nx">toFixed</span><span class="p">(</span><span class="mi">2</span><span class="p">)}</span><span class="s2">`</span>
<span class="p">}</span>

<span class="kr">interface</span> <span class="nx">Order</span> <span class="p">{</span>
    <span class="nl">customerEmail</span><span class="p">:</span> <span class="kr">string</span>
    <span class="nx">customerType</span><span class="p">:</span> <span class="dl">'</span><span class="s1">NORMAL</span><span class="dl">'</span> <span class="o">|</span> <span class="dl">'</span><span class="s1">VIP</span><span class="dl">'</span>
    <span class="nx">items</span><span class="p">:</span> <span class="p">{</span> <span class="nl">name</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span> <span class="nl">price</span><span class="p">:</span> <span class="kr">number</span><span class="p">;</span> <span class="nl">quantity</span><span class="p">:</span> <span class="kr">number</span> <span class="p">}[]</span>
    <span class="nx">subtotal</span><span class="p">?:</span> <span class="kr">number</span>
    <span class="nx">discount</span><span class="p">?:</span> <span class="kr">number</span>
    <span class="nx">tax</span><span class="p">?:</span> <span class="kr">number</span>
    <span class="nx">shipping</span><span class="p">?:</span> <span class="kr">number</span>
    <span class="nx">total</span><span class="p">?:</span> <span class="kr">number</span>
<span class="p">}</span>


<span class="kr">interface</span> <span class="nx">PrintJob</span> <span class="p">{</span>
    <span class="nl">title</span><span class="p">:</span> <span class="kr">string</span>
    <span class="nx">items</span><span class="p">:</span> <span class="p">{</span> <span class="nl">name</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span> <span class="nl">quantity</span><span class="p">:</span> <span class="kr">number</span><span class="p">;</span> <span class="nl">lineTotal</span><span class="p">:</span> <span class="kr">number</span><span class="p">;</span> <span class="nl">lineTotalFormatted</span><span class="p">:</span> <span class="kr">string</span> <span class="p">}[]</span>
    <span class="nx">subtotal</span><span class="p">:</span> <span class="kr">number</span>
    <span class="nx">discount</span><span class="p">:</span> <span class="kr">number</span>
    <span class="nx">tax</span><span class="p">:</span> <span class="kr">number</span>
    <span class="nx">shipping</span><span class="p">:</span> <span class="kr">number</span>
    <span class="nx">total</span><span class="p">:</span> <span class="kr">number</span>
    <span class="nx">currency</span><span class="p">:</span> <span class="kr">string</span>
    <span class="nx">formatted</span><span class="p">:</span> <span class="p">{</span>
        <span class="nl">subtotal</span><span class="p">:</span> <span class="kr">string</span>
        <span class="nx">discount</span><span class="p">:</span> <span class="kr">string</span>
        <span class="nx">tax</span><span class="p">:</span> <span class="kr">string</span>
        <span class="nx">shipping</span><span class="p">:</span> <span class="kr">string</span>
        <span class="nx">total</span><span class="p">:</span> <span class="kr">string</span>
    <span class="p">}</span>
    <span class="nl">metadata</span><span class="p">:</span> <span class="p">{</span>
        <span class="na">customerEmail</span><span class="p">:</span> <span class="kr">string</span>
        <span class="na">createdAt</span><span class="p">:</span> <span class="kr">string</span>
    <span class="p">}</span>
<span class="p">}</span>

</code></pre></div></div>

<h2 id="ejercicio">Ejercicio</h2>

<p>Nos gustaría añadir soporte de cupones con expiración y multi‑moneda (USD/EUR), así como diferentes reglas de redondeo
de precios.</p>

<h2 id="problemas-que-encontrarás">Problemas que encontrarás</h2>

<p>381 líneas son muchas líneas y complican tanto entender, como mantener el código. Para poder introducir los cambios
deseados tenemos que desentrañar todos los lugares de este código que podrían verse afectados, que pueden ocultarse
entre los distintos niveles de abstracción y detalle que están entremezclados. Un cambio puede tener efectos no deseados
o simplemente no funcionar como esperamos porque debemos aplicarlo en varios lugares.</p>

<p>Hay varios problemas relacionados con la longitud del método:</p>

<p>En primer lugar, tenemos la mezcla de distintas responsabilidades. Además del procesamiento del pedido como tal, nos
encontramos:</p>

<ul>
  <li>Persistencia de Base de datos, incluyendo detalles de muy bajo nivel, mezclados con otros como validaciones,
serializaciones, etc.</li>
  <li>Envío de notificaciones por email, incluyendo también detalles de implementación del servicio de correo.</li>
  <li>Impresión del pedido, con detalles irrelevantes para el procesamiento del pedido.</li>
</ul>

<p>Pero incluso dentro de lo que consideraríamos el procesamiento del pedido, tenemos varias responsabilidades:</p>

<ul>
  <li>Validación de que el pedido está listo para procesar</li>
  <li>Cálculo de totales</li>
  <li>Aplicación de impuestos y descuentos</li>
</ul>

<h2 id="solución">Solución</h2>

<h2 id="sin-resolver-el-smell">Sin resolver el <em>smell</em></h2>

<p>Al igual que hicimos con <a href="/large-class/">Large Class</a>, no vamos a intentar resolver el ejercicio con el código en su
estado actual. ¿Se podría? Sí, el transpilador lo aguanta todo, y si puede compilar, puede incluso funcionar. Otra cosa
es entender el código o mantenerlo.</p>

<p>Veamos por ejemplo el introducir cupones de descuento. Tenemos un código similar aquí:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Descuento por cliente VIP (10% del subtotal)</span>
<span class="kd">let</span> <span class="nx">discount</span> <span class="o">=</span> <span class="mi">0</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">order</span><span class="p">.</span><span class="nx">customerType</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">VIP</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
    <span class="nx">discount</span> <span class="o">=</span> <span class="nx">roundMoney</span><span class="p">(</span><span class="nx">subtotal</span> <span class="o">*</span> <span class="mf">0.1</span><span class="p">)</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Descuento VIP aplicado</span><span class="dl">'</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Así que podríamos introducir soporte para otros cupones justo después de este bloque, añadiendo otra condicional que
señale la circunstancia a la vez que añadimos complejidad ciclomática, por si no teníamos suficiente:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Descuento por cliente VIP (10% del subtotal)</span>
<span class="kd">let</span> <span class="nx">discount</span> <span class="o">=</span> <span class="mi">0</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">order</span><span class="p">.</span><span class="nx">customerType</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">VIP</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
    <span class="nx">discount</span> <span class="o">=</span> <span class="nx">roundMoney</span><span class="p">(</span><span class="nx">subtotal</span> <span class="o">*</span> <span class="mf">0.1</span><span class="p">)</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Descuento VIP aplicado</span><span class="dl">'</span><span class="p">)</span>
<span class="p">}</span>

<span class="k">if</span> <span class="p">(</span><span class="nx">order</span><span class="p">.</span><span class="nx">coupon</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">DESC15</span><span class="dl">'</span> <span class="o">&amp;&amp;</span> <span class="nx">order</span><span class="p">.</span><span class="nx">couponExpiration</span> <span class="o">&gt;</span> <span class="k">new</span> <span class="nb">Date</span><span class="p">()</span> <span class="p">{</span>
    <span class="nx">discount</span> <span class="o">=</span> <span class="nx">discount</span> <span class="o">+</span> <span class="nx">roundMoney</span><span class="p">(</span><span class="nx">subtotal</span> <span class="o">*</span> <span class="mf">0.15</span><span class="p">)</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Descuento cupón aplicado</span><span class="dl">'</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Para dar soporte a diversas monedas, tendremos que añadirlo en la interface <code class="language-plaintext highlighter-rouge">Order</code> y reemplazar algunos lugares en los
que se usa <code class="language-plaintext highlighter-rouge">USD</code> como moneda.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">interface</span> <span class="nx">Order</span> <span class="p">{</span>
    <span class="nl">customerEmail</span><span class="p">:</span> <span class="kr">string</span>
    <span class="nx">customerType</span><span class="p">:</span> <span class="dl">'</span><span class="s1">NORMAL</span><span class="dl">'</span> <span class="o">|</span> <span class="dl">'</span><span class="s1">VIP</span><span class="dl">'</span>
    <span class="nx">items</span><span class="p">:</span> <span class="p">{</span> <span class="nl">name</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span> <span class="nl">price</span><span class="p">:</span> <span class="kr">number</span><span class="p">;</span> <span class="nl">quantity</span><span class="p">:</span> <span class="kr">number</span> <span class="p">}[]</span>
    <span class="nx">currency</span><span class="p">:</span> <span class="dl">'</span><span class="s1">USD</span><span class="dl">'</span> <span class="o">|</span> <span class="dl">'</span><span class="s1">EUR</span><span class="dl">'</span>
    <span class="nx">subtotal</span><span class="p">?:</span> <span class="kr">number</span>
    <span class="nx">discount</span><span class="p">?:</span> <span class="kr">number</span>
    <span class="nx">tax</span><span class="p">?:</span> <span class="kr">number</span>
    <span class="nx">shipping</span><span class="p">?:</span> <span class="kr">number</span>
    <span class="nx">total</span><span class="p">?:</span> <span class="kr">number</span>
    <span class="nx">coupon</span><span class="p">?:</span> <span class="kr">string</span>
    <span class="nx">couponExpiration</span><span class="p">?:</span> <span class="nb">Date</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Aquí tenemos un ejemplo, pero hay alguno más:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="c1">// Preparar registro a guardar</span>
<span class="kd">const</span> <span class="nx">dbRecord</span> <span class="o">=</span> <span class="p">{</span>
    <span class="na">id</span><span class="p">:</span> <span class="nx">dbRecordId</span><span class="p">,</span>
    <span class="na">customerEmail</span><span class="p">:</span> <span class="nx">order</span><span class="p">.</span><span class="nx">customerEmail</span><span class="p">,</span>
    <span class="na">customerType</span><span class="p">:</span> <span class="nx">order</span><span class="p">.</span><span class="nx">customerType</span><span class="p">,</span>
    <span class="na">items</span><span class="p">:</span> <span class="nx">order</span><span class="p">.</span><span class="nx">items</span><span class="p">.</span><span class="nx">map</span><span class="p">(</span><span class="nx">i</span> <span class="o">=&gt;</span> <span class="p">({</span><span class="na">name</span><span class="p">:</span> <span class="nx">i</span><span class="p">.</span><span class="nx">name</span><span class="p">,</span> <span class="na">price</span><span class="p">:</span> <span class="nx">i</span><span class="p">.</span><span class="nx">price</span><span class="p">,</span> <span class="na">quantity</span><span class="p">:</span> <span class="nx">i</span><span class="p">.</span><span class="nx">quantity</span><span class="p">})),</span>
    <span class="na">amounts</span><span class="p">:</span> <span class="p">{</span>
        <span class="na">subtotal</span><span class="p">:</span> <span class="nx">order</span><span class="p">.</span><span class="nx">subtotal</span><span class="p">,</span>
        <span class="na">discount</span><span class="p">:</span> <span class="nx">order</span><span class="p">.</span><span class="nx">discount</span><span class="p">,</span>
        <span class="na">tax</span><span class="p">:</span> <span class="nx">order</span><span class="p">.</span><span class="nx">tax</span><span class="p">,</span>
        <span class="na">shipping</span><span class="p">:</span> <span class="nx">order</span><span class="p">.</span><span class="nx">shipping</span><span class="p">,</span>
        <span class="na">total</span><span class="p">:</span> <span class="nx">order</span><span class="p">.</span><span class="nx">total</span><span class="p">,</span>
    <span class="p">},</span>
    <span class="na">status</span><span class="p">:</span> <span class="dl">'</span><span class="s1">PENDING</span><span class="dl">'</span><span class="p">,</span>
    <span class="na">createdAt</span><span class="p">:</span> <span class="nx">dbNow</span><span class="p">.</span><span class="nx">toISOString</span><span class="p">(),</span>
    <span class="na">updatedAt</span><span class="p">:</span> <span class="nx">dbNow</span><span class="p">.</span><span class="nx">toISOString</span><span class="p">(),</span>
    <span class="na">currency</span><span class="p">:</span> <span class="nx">order</span><span class="p">.</span><span class="nx">currency</span><span class="p">,</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Al respecto del redondeo la cosa se hace más complicada. Tenemos unas funciones auxiliares que se usan para esta
función, pero el problema está en poder configurar reglas diferentes que podrían aplicarse en distintas circunstancias o
incluso a distintos componentes del precio. Por ejemplo, en el sector de la energía eléctrica, los precios unitarios de
la energía requieren hasta seis decimales, mientras que el importe de la factura se expresa con dos.</p>

<p>Podríamos cambiar <code class="language-plaintext highlighter-rouge">roundMoney</code> para admitir una precisión opcional:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nx">roundMoney</span><span class="p">(</span><span class="nx">n</span><span class="p">:</span> <span class="kr">number</span><span class="p">,</span> <span class="nx">precision</span><span class="p">:</span> <span class="kr">number</span> <span class="o">=</span> <span class="mi">2</span><span class="p">):</span> <span class="kr">number</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">factor</span> <span class="o">=</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">pow</span><span class="p">(</span><span class="mi">10</span><span class="p">,</span> <span class="nx">precision</span><span class="p">)</span>
    <span class="k">return</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">round</span><span class="p">(</span><span class="nx">n</span> <span class="o">*</span> <span class="nx">factor</span><span class="p">)</span> <span class="o">/</span> <span class="nx">factor</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Y luego revisar sus cinco usos para averiguar si tendríamos que cambiar alguno de ellos y hacerlo de manera consistente.</p>

<h2 id="resolviendo-el-smell">Resolviendo el <em>smell</em></h2>

<h3 id="testing">Testing</h3>

<p>Nos vendría bien tener un test de caracterización para poder empezar refactorizar este método largo y convertirlo en un
código manejable. Tenemos algunos desafíos para escribir este test, pero vamos a ver cómo lo hacemos.</p>

<p>Para empezar, este es un ejemplo del output que obtenemos en la consola.</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[DB] Serializando registro 235133 (518 bytes) para Server=fake.db.local;Database=orders;User=demo;Password=demo
[DB] Intento 1/3: guardando pedido 235133 con total $155.18
[DB] Pedido 235133 guardado correctamente
[AUDIT] Registro: {"type":"ORDER_SAVED","orderId":235133,"actor":"system","at":"2025-11-03T16:50:26.107Z","metadata":{"ip":"127.0.0.1","userAgent":"OrderService/1.0"}}
[MAIL] Preparando correo (1211 bytes) vía smtp.fake.local:587
[MAIL] Encolando correo (NORMAL) para customer@example.com
[MAIL] Intento 1/2: enviando correo a customer@example.com
[MAIL] Correo enviado a customer@example.com (messageId=msg-235133-1762188626115)
[PRN] Preparando conexión a impresora Demo Thermal Printer TP-80 (USB/ESC/POS)
[PRN] Agregando QR con datos: ORDER|customer@example.com|155.18|1762188626115
[PRN] Vista previa del recibo:
==========================================
RESUMEN DEL PEDIDO                        
11/3/2025, 5:50:26 PM                     
Cliente: customer@example.com             
------------------------------------------
3 x Product 1                       $36.15
6 x Another                         $92.10
------------------------------------------
Subtotal                           $128.25
Descuento                            $0.00
Impuestos                           $26.93
Envío                                $0.00
...(5 líneas más)
[PRN] Encolando trabajo prn-1762188626115-939 (855 bytes, prioridad=NORMAL) en Front Desk
[PRN] Enviando chunk #0 (256 bytes) intento 1/2
[PRN] Enviando chunk #1 (256 bytes) intento 1/2
[PRN] Enviando chunk #2 (256 bytes) intento 1/2
[PRN] Enviando chunk #3 (87 bytes) intento 1/2
[PRN] Trabajo prn-1762188626115-939 impreso correctamente. Total enviado: 855 bytes
</code></pre></div></div>

<p>Esto nos dice que la mejor forma de poner el código bajo test es mediante <em>snapshot testing</em>, pero eso aún nos deja con
el problema de capturar el output de la consola.</p>

<p>Para esto, debemos recurrir a crear un espía de la consola con las facilidades provistas por <code class="language-plaintext highlighter-rouge">vitest</code>. Este método
funciona bien cuando no podemos crear nuestros propios dobles o espías.</p>

<p>Este es el test inicial que creamos:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">describe</span><span class="p">(</span><span class="dl">'</span><span class="s1">long method</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="nx">it</span><span class="p">(</span><span class="dl">'</span><span class="s1">simple order with NORMAL customer</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
        <span class="kd">const</span> <span class="nx">logSpy</span> <span class="o">=</span> <span class="nx">vi</span><span class="p">.</span><span class="nx">spyOn</span><span class="p">(</span><span class="nx">console</span><span class="p">,</span> <span class="dl">'</span><span class="s1">log</span><span class="dl">'</span><span class="p">)</span>

        <span class="kd">const</span> <span class="nx">order</span> <span class="o">=</span> <span class="p">{</span>
            <span class="na">customerEmail</span><span class="p">:</span> <span class="dl">'</span><span class="s1">customer@example.com</span><span class="dl">'</span><span class="p">,</span>
            <span class="na">customerType</span><span class="p">:</span> <span class="dl">'</span><span class="s1">NORMAL</span><span class="dl">'</span><span class="p">,</span>
            <span class="na">items</span><span class="p">:</span> <span class="p">[</span>
                <span class="p">{</span>
                    <span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">Product 1</span><span class="dl">'</span><span class="p">,</span>
                    <span class="na">price</span><span class="p">:</span> <span class="mf">12.05</span><span class="p">,</span>
                    <span class="na">quantity</span><span class="p">:</span> <span class="mi">3</span>
                <span class="p">},</span>
                <span class="p">{</span>
                    <span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">Another</span><span class="dl">'</span><span class="p">,</span>
                    <span class="na">price</span><span class="p">:</span> <span class="mf">15.35</span><span class="p">,</span>
                    <span class="na">quantity</span><span class="p">:</span> <span class="mi">6</span>
                <span class="p">}</span>
            <span class="p">]</span>
        <span class="p">}</span> <span class="k">as</span> <span class="nx">Order</span>

        <span class="kd">const</span> <span class="nx">orderService</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">OrderService</span><span class="p">();</span>
        <span class="nx">orderService</span><span class="p">.</span><span class="nx">process</span><span class="p">(</span><span class="nx">order</span><span class="p">)</span>

        <span class="kd">let</span> <span class="nx">output</span> <span class="o">=</span> <span class="nx">formatConsoleCalls</span><span class="p">(</span><span class="nx">logSpy</span><span class="p">)</span>
        <span class="nx">expect</span><span class="p">(</span><span class="nx">output</span><span class="p">).</span><span class="nx">toMatchSnapshot</span><span class="p">()</span>

        <span class="nx">logSpy</span><span class="p">.</span><span class="nx">mockRestore</span><span class="p">()</span>
    <span class="p">});</span>
<span class="p">})</span>
</code></pre></div></div>

<p>Nos falta una función auxiliar para poder unir todos los logs que se han producido en la consola y poder usarlos con el
snapshot:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nx">formatConsoleCalls</span><span class="p">(</span><span class="nx">spy</span><span class="p">:</span> <span class="nx">ReturnType</span><span class="o">&lt;</span><span class="k">typeof</span> <span class="nx">vi</span><span class="p">.</span><span class="nx">spyOn</span><span class="o">&gt;</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">return</span> <span class="nx">spy</span><span class="p">.</span><span class="nx">mock</span><span class="p">.</span><span class="nx">calls</span><span class="p">.</span><span class="nx">map</span><span class="p">(</span><span class="nx">call</span> <span class="o">=&gt;</span> <span class="nx">call</span><span class="p">.</span><span class="nx">join</span><span class="p">(</span><span class="dl">'</span><span class="s1"> </span><span class="dl">'</span><span class="p">)).</span><span class="nx">join</span><span class="p">(</span><span class="dl">'</span><span class="se">\n</span><span class="dl">'</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<h4 id="controlando-datos-no-deterministas">Controlando datos no deterministas</h4>

<p>Si ejecutamos el test una vez, se generará el snapshot y el test aparecerá como pasado. Pero al ejecutarlo por segunda
vez, veremos que el test falla. Esto es debido a que hay algunos datos no deterministas:</p>

<ul>
  <li>Identificadores</li>
  <li>Timestamps</li>
  <li>Fechas</li>
  <li>Identificadores de trabajo de impresión</li>
</ul>

<p>Tenemos dos formas principales de resolver esto:</p>

<ul>
  <li>Reemplazar los datos no deterministas antes de generar el snapshot, mediante técnicas buscar y reemplazar patrones.
Esto se denomina <em>scrubbing</em>, pero suele ser tedioso y propenso a generar problems.</li>
  <li>Introducir <em>seams</em> en el código para tener bajo control la generación datos no deterministas.</li>
</ul>

<p>Esta segunda opción es la que vamos a usar y forma parte del arsenal de técnicas de test. Además, nos va a permitir
empezar a entender cómo funciona el código y a introducir algunos colaboradores iniciales.</p>

<p>Básicamente, tenemos que identificar qué datos no deterministas tenemos y aislar su creación en métodos protegidos de la
clase bajo test. Una vez hecho esto, extendemos la clase <code class="language-plaintext highlighter-rouge">OrderService</code> y sobreescribimos esos métodos para que
devuelvan datos fijos.</p>

<p>Así por ejemplo, la primera línea del snapshot en la que podemos encontrar un dato no determinista es:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[DB] Serializando registro 755049 (518 bytes) para Server=fake.db.local;Database=orders;User=demo;Password=demo
</code></pre></div></div>

<p>El número del registro se genera en esta línea que usa la librería <code class="language-plaintext highlighter-rouge">Math</code> para generar un número aleatorio entre 0 y
999999.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">dbRecordId</span> <span class="o">=</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">floor</span><span class="p">(</span><span class="nb">Math</span><span class="p">.</span><span class="nx">random</span><span class="p">()</span> <span class="o">*</span> <span class="mi">1000000</span><span class="p">)</span>
</code></pre></div></div>

<p>Aplicamos el refactor <em>Extract Method</em> para crear el <em>seam</em>. Un seam es un punto del código que podemos cambiar si
afectar a la funcionalidad del programa.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">protected</span>
<span class="nx">generateDbRecordId</span><span class="p">()</span>
<span class="p">{</span>
    <span class="k">return</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">floor</span><span class="p">(</span><span class="nb">Math</span><span class="p">.</span><span class="nx">random</span><span class="p">()</span> <span class="o">*</span> <span class="mi">1000000</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Vamos a ver ahora cómo usaremos este <em>seam</em> en el test. Primero, extendemos la clase <code class="language-plaintext highlighter-rouge">OrderService</code> y declaramos una
clase derivada <code class="language-plaintext highlighter-rouge">TestableOrderService</code> en la que sobreescribimos el método <code class="language-plaintext highlighter-rouge">generateDbRecordId</code> para que devuelva un
valor fijo. De este modo, <code class="language-plaintext highlighter-rouge">OrderService</code> sigue trabajando exactamente igual que antes, pero en el test hay una pequeña
parte de la misma que podemos manipular. Eso es cómo funciona el <em>seam</em>.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nx">TestableOrderService</span> <span class="kd">extends</span> <span class="nx">OrderService</span> <span class="p">{</span>
    <span class="k">protected</span> <span class="nx">generateDbRecordId</span><span class="p">():</span> <span class="kr">number</span> <span class="p">{</span>
        <span class="k">return</span> <span class="mi">67234</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Y empezamos a usar <code class="language-plaintext highlighter-rouge">TestableOrderService</code> en el test:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">orderService</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">TestableOrderService</span><span class="p">();</span>
<span class="nx">orderService</span><span class="p">.</span><span class="nx">process</span><span class="p">(</span><span class="nx">order</span><span class="p">)</span>
</code></pre></div></div>

<p>Ahora, tenemos que actualizar el <em>snapshot</em> para que coincida con el nuevo valor de <code class="language-plaintext highlighter-rouge">dbRecordId</code>. Lo podemos hacer
borrando el archivo de <em>snapshot</em>, o usando las facilidades que nos ofrezca la librería de testing.</p>

<p>Si ejecutamos el test por primera vez (o hemos actualizado el <em>snapshot</em>), veremos que pasa. Al ejecutarlo de nuevo, en
cambio, veremos que falla. Sin embargo, ya falla de forma diferente, puesto que la línea</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[DB] Serializando registro 67234 (517 bytes) para Server=fake.db.local;Database=orders;User=demo;Password=demo
[DB] Intento 1/3: guardando pedido 67234 con total $155.18
</code></pre></div></div>

<p>No solo refleja el valor de <code class="language-plaintext highlighter-rouge">dbRecordId</code> que hemos incrustado en la clase <em>testable</em>, sino que esa línea ya no muestra
diferencias entre el <em>snapshot</em> y el resultado obtenido. Obviamente, otras líneas muestran diferencias:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[AUDIT] Registro: {"type":"ORDER_SAVED","orderId":67234,"actor":"system","at":"2025-11-03T17:21:31.140Z","metadata":{"ip":"127.0.0.1","userAgent":"OrderService/1.0"}}
</code></pre></div></div>

<p>Aquí la diferencia está en la marca de tiempo, que es un valor no determinista, que obtenemos del reloj del sistema.
Este valor es obtenido aquí, concretamente al instanciar un objeto <code class="language-plaintext highlighter-rouge">Date</code>.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">auditLogEntry</span> <span class="o">=</span> <span class="p">{</span>
    <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">ORDER_SAVED</span><span class="dl">'</span><span class="p">,</span>
    <span class="na">orderId</span><span class="p">:</span> <span class="nx">dbRecord</span><span class="p">.</span><span class="nx">id</span><span class="p">,</span>
    <span class="na">actor</span><span class="p">:</span> <span class="dl">'</span><span class="s1">system</span><span class="dl">'</span><span class="p">,</span>
    <span class="na">at</span><span class="p">:</span> <span class="k">new</span> <span class="nb">Date</span><span class="p">().</span><span class="nx">toISOString</span><span class="p">(),</span>
    <span class="na">metadata</span><span class="p">:</span> <span class="p">{</span>
        <span class="na">ip</span><span class="p">:</span> <span class="dl">'</span><span class="s1">127.0.0.1</span><span class="dl">'</span><span class="p">,</span>
        <span class="na">userAgent</span><span class="p">:</span> <span class="dl">'</span><span class="s1">OrderService/1.0</span><span class="dl">'</span><span class="p">,</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Tiene sentido extraer solamente la instanciación de <code class="language-plaintext highlighter-rouge">Date</code>. Esto hace que sea más reutilizable y que la intervención sea
minimalista.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">protected</span>
<span class="nx">getCurrentDate</span><span class="p">()</span>
<span class="p">:</span>
<span class="nb">Date</span>
<span class="p">{</span>
    <span class="k">return</span> <span class="k">new</span> <span class="nb">Date</span><span class="p">();</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Por supuesto, ahora tendremos que sobreescribir el método en <code class="language-plaintext highlighter-rouge">TestableOrderService</code> para que devuelva un valor de fecha
fijo.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nx">TestableOrderService</span> <span class="kd">extends</span> <span class="nx">OrderService</span> <span class="p">{</span>
    <span class="k">protected</span> <span class="nx">generateDbRecordId</span><span class="p">():</span> <span class="kr">number</span> <span class="p">{</span>
        <span class="k">return</span> <span class="mi">67234</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">protected</span> <span class="nx">getCurrentDate</span><span class="p">():</span> <span class="nb">Date</span> <span class="p">{</span>
        <span class="k">return</span> <span class="k">new</span> <span class="nb">Date</span><span class="p">(</span><span class="dl">'</span><span class="s1">2023-05-21T13:35</span><span class="dl">'</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Una vez que hemos hecho esto, podemos actualizar el snapshot y hacer fallar el test de nuevo ejecutándolo dos veces.
Veremos que la fecha del log ya se mantiene constante:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[AUDIT] Registro: {"type":"ORDER_SAVED","orderId":67234,"actor":"system","at":"2023-05-21T11:35:00.000Z","metadata":{"ip":"127.0.0.1","userAgent":"OrderService/1.0"}}
</code></pre></div></div>

<p>Se puede observar una diferencia en la hora, porque no hemos tenido en cuenta la zona horaria al definir la fecha. Sin
embargo, para el propósito del test nos da igual.</p>

<p>La siguiente diferencia que se puede observar está en messageId durante el envío de email, que tiene pinta de ser un
timestamp.</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[MAIL] Correo enviado a customer@example.com (messageId=msg-67234-1762191156326)
</code></pre></div></div>

<p>Veamos en el código:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">messageId</span> <span class="o">=</span> <span class="s2">`msg-</span><span class="p">${</span><span class="nx">dbRecord</span><span class="p">.</span><span class="nx">id</span><span class="p">}</span><span class="s2">-</span><span class="p">${</span><span class="nb">Date</span><span class="p">.</span><span class="nx">now</span><span class="p">()}</span><span class="s2">`</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">mailSent</span><span class="p">)</span> <span class="p">{</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`[MAIL] Correo enviado a </span><span class="p">${</span><span class="nx">order</span><span class="p">.</span><span class="nx">customerEmail</span><span class="p">}</span><span class="s2"> (messageId=</span><span class="p">${</span><span class="nx">messageId</span><span class="p">}</span><span class="s2">)`</span><span class="p">)</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">(</span><span class="s2">`[MAIL] Fallo al enviar correo a </span><span class="p">${</span><span class="nx">order</span><span class="p">.</span><span class="nx">customerEmail</span><span class="p">}</span><span class="s2"> tras </span><span class="p">${</span><span class="nx">mailAttemptsMax</span><span class="p">}</span><span class="s2"> intentos`</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Esto ya lo tenemos cubierto con el <em>seam</em> <code class="language-plaintext highlighter-rouge">getCurrentDate</code>.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">messageId</span> <span class="o">=</span> <span class="s2">`msg-</span><span class="p">${</span><span class="nx">dbRecord</span><span class="p">.</span><span class="nx">id</span><span class="p">}</span><span class="s2">-</span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">getCurrentDate</span><span class="p">().</span><span class="nx">getTime</span><span class="p">()}</span><span class="s2">`</span>
</code></pre></div></div>

<p>Y, efectivamente, ahora conseguimos que esta línea se genere con los mismos datos cada vez que ejecutemos el test. Lo
que nos lleva a la siguiente diferencia:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[PRN] Agregando QR con datos: ORDER|customer@example.com|155.18|1762191531739
</code></pre></div></div>

<p>Se trata de otro timestamp que se obtiene de la variable <code class="language-plaintext highlighter-rouge">now</code>, la cual se puebla en esta línea.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Crear contenido del recibo</span>
<span class="kd">const</span> <span class="nx">now</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Date</span><span class="p">()</span>
</code></pre></div></div>

<p>Aplicamos la misma solución que antes:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">now</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">getCurrentDate</span><span class="p">()</span>
</code></pre></div></div>

<p>Y volvemos a actualizar el snapshot. Por cierto, que en esta ocasión nos encontramos con que se han solucionado dos
diferencias. Primero, la que esperábamos:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[PRN] Agregando QR con datos: ORDER|customer@example.com|155.18|1684668900000
</code></pre></div></div>

<p>Pero el resumen del pedido usa también <code class="language-plaintext highlighter-rouge">now</code> para pintar la fecha del recibo:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>RESUMEN DEL PEDIDO                        
5/21/2023, 1:35:00 PM                     
Cliente: customer@example.com
</code></pre></div></div>

<p>Solo nos queda la siguiente diferencia, al generar el identificador del trabajo de impresión:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[PRN] Encolando trabajo prn-1762191760615-90 (855 bytes, prioridad=NORMAL) en Front Desk
</code></pre></div></div>

<p>El <code class="language-plaintext highlighter-rouge">printJobId</code> combina dos datos no deterministas:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">printJobId</span> <span class="o">=</span> <span class="s2">`prn-</span><span class="p">${</span><span class="nb">Date</span><span class="p">.</span><span class="nx">now</span><span class="p">()}</span><span class="s2">-</span><span class="p">${</span><span class="nb">Math</span><span class="p">.</span><span class="nx">floor</span><span class="p">(</span><span class="nb">Math</span><span class="p">.</span><span class="nx">random</span><span class="p">()</span> <span class="o">*</span> <span class="mi">1000</span><span class="p">)}</span><span class="s2">`</span>
</code></pre></div></div>

<p>Así que lo extraemos a un método protegido para tener un nuevo <em>seam</em>.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">protected</span>
<span class="nx">generatePrintJobId</span><span class="p">()</span>
<span class="p">{</span>
    <span class="k">return</span> <span class="s2">`prn-</span><span class="p">${</span><span class="nb">Date</span><span class="p">.</span><span class="nx">now</span><span class="p">()}</span><span class="s2">-</span><span class="p">${</span><span class="nb">Math</span><span class="p">.</span><span class="nx">floor</span><span class="p">(</span><span class="nb">Math</span><span class="p">.</span><span class="nx">random</span><span class="p">()</span> <span class="o">*</span> <span class="mi">1000</span><span class="p">)}</span><span class="s2">`</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>En este caso, voy a sobreescribir el método usando un valor fijo obtenido del snapshot actual, para simular un
identificador realista.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nx">TestableOrderService</span> <span class="kd">extends</span> <span class="nx">OrderService</span> <span class="p">{</span>
    <span class="k">protected</span> <span class="nx">generateDbRecordId</span><span class="p">():</span> <span class="kr">number</span> <span class="p">{</span>
        <span class="k">return</span> <span class="mi">67234</span><span class="p">;</span>
    <span class="p">}</span>


    <span class="k">protected</span> <span class="nx">getCurrentDate</span><span class="p">():</span> <span class="nb">Date</span> <span class="p">{</span>
        <span class="k">return</span> <span class="k">new</span> <span class="nb">Date</span><span class="p">(</span><span class="dl">'</span><span class="s1">2023-05-21T13:35</span><span class="dl">'</span><span class="p">);</span>
    <span class="p">}</span>


    <span class="k">protected</span> <span class="nx">generatePrintJobId</span><span class="p">():</span> <span class="kr">string</span> <span class="p">{</span>
        <span class="k">return</span> <span class="dl">'</span><span class="s1">prn-1762191762553-125</span><span class="dl">'</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Una vez introducido este cambio y actualizado el snapshot, podemos ver que el test pasa repetidamente. Hemos logrado
eliminar todas las fuentes no deterministas y ya tenemos una buena base con la que trabajar.</p>

<h4 id="golden-master">Golden Master</h4>

<p>Una cuestión interesante es que nuestro código tiene varios flujos de ejecución posibles. Por ejemplo, sie el cliente es
Normal o VIP el cálculo será diferente porque los clientes VIP tienen un descuento. También hay un coste de envío si el
total del pedido es inferior a 50, mientras que es gratis a partir de ese importe. Se hacen también algunas validaciones
para ver si el pedido es procesable, etc.</p>

<p>Por otro lado, los pedidos pueden tener uno o más productos, los cuales se pueden pedir en cualquier número de unidades,
lo que genera la necesidad de cubrir con tests un número de casos representativo.</p>

<p>Aparte de eso, hay una casuística relativa al funcionamiento del sistema de persistencia, de envío de emails y de
impresión, que complica las cosas. En este ejemplo, lo vamos a ignorar y nos vamos a quedar en el happy path. Se trata
de código que estaría funcionando en producción, pero que no vamos a querer tocar en este ejercicio salvo para
extraerlo.</p>

<p>Dado que no conocemos exactamente como funciona el código, nuestra estrategia de testing es generar muchos inputs,
cuantos más mejor, y ver qué genera en cada combinación de ellos. Esta técnica de bombardeo con test nos generará un
Golden Master, es decir, una descripción de como funciona el código en este momento. El objetivo no es hacer un test de
como debería funcionar o para buscar errores.</p>

<p>En su lugar, este test nos va a permitir mantener el comportamiento actual a la vez que refactorizamos poco a poco a un
diseño mejor. Cuando tengamos ese diseño mejor en tanto que más mantenible y fácil de modificar, podremos preparar otros
tests e intervenir en el código para introducir nuevas prestaciones.</p>

<p>Existen algunas librerías para ayudarnos a generar el Golden Master, pero en Typescript con ViTest es bastante fácil.
Podemos usar dos aproximaciones:</p>

<ul>
  <li>Generar combinaciones mediante bucles anidados.</li>
  <li>Generar combinaciones mediante <code class="language-plaintext highlighter-rouge">each</code> anidados, que nos permite proveer datos a los tests.</li>
</ul>

<p>Hay algunas diferencias entre una y otra aproximación. La segunda, nos permite una gran resolución para detectar qué
valores se ven afectados por los cambios, puesto que cada test se ejecuta de forma individual. De la otra forma, se
ejecuta un único test masivo con todas las combinaciones posibles.</p>

<p>Veamos el test aplicando la variación según el tipo de cliente:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">describe</span><span class="p">(</span><span class="dl">'</span><span class="s1">long method</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="nx">describe</span><span class="p">.</span><span class="nx">each</span><span class="p">([</span><span class="dl">'</span><span class="s1">NORMAL</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">VIP</span><span class="dl">'</span><span class="p">])(</span><span class="dl">'</span><span class="s1">Given a %s customer</span><span class="dl">'</span><span class="p">,</span> <span class="p">(</span><span class="na">customerType</span><span class="p">:</span> <span class="kr">string</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
        <span class="nx">it</span><span class="p">(</span><span class="dl">'</span><span class="s1">should process the order</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
            <span class="kd">const</span> <span class="nx">logSpy</span> <span class="o">=</span> <span class="nx">vi</span><span class="p">.</span><span class="nx">spyOn</span><span class="p">(</span><span class="nx">console</span><span class="p">,</span> <span class="dl">'</span><span class="s1">log</span><span class="dl">'</span><span class="p">)</span>

            <span class="kd">const</span> <span class="nx">order</span> <span class="o">=</span> <span class="p">{</span>
                <span class="na">customerEmail</span><span class="p">:</span> <span class="dl">'</span><span class="s1">customer@example.com</span><span class="dl">'</span><span class="p">,</span>
                <span class="na">customerType</span><span class="p">:</span> <span class="nx">customerType</span><span class="p">,</span>
                <span class="na">items</span><span class="p">:</span> <span class="p">[</span>
                    <span class="p">{</span>
                        <span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">Product 1</span><span class="dl">'</span><span class="p">,</span>
                        <span class="na">price</span><span class="p">:</span> <span class="mf">12.05</span><span class="p">,</span>
                        <span class="na">quantity</span><span class="p">:</span> <span class="mi">3</span>
                    <span class="p">},</span>
                    <span class="p">{</span>
                        <span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">Another</span><span class="dl">'</span><span class="p">,</span>
                        <span class="na">price</span><span class="p">:</span> <span class="mf">15.35</span><span class="p">,</span>
                        <span class="na">quantity</span><span class="p">:</span> <span class="mi">6</span>
                    <span class="p">}</span>
                <span class="p">]</span>
            <span class="p">}</span> <span class="k">as</span> <span class="nx">Order</span>

            <span class="kd">const</span> <span class="nx">orderService</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">TestableOrderService</span><span class="p">();</span>
            <span class="nx">orderService</span><span class="p">.</span><span class="nx">process</span><span class="p">(</span><span class="nx">order</span><span class="p">)</span>

            <span class="kd">let</span> <span class="nx">output</span> <span class="o">=</span> <span class="nx">formatConsoleCalls</span><span class="p">(</span><span class="nx">logSpy</span><span class="p">)</span>
            <span class="nx">expect</span><span class="p">(</span><span class="nx">output</span><span class="p">).</span><span class="nx">toMatchSnapshot</span><span class="p">()</span>

            <span class="nx">logSpy</span><span class="p">.</span><span class="nx">mockRestore</span><span class="p">()</span>
        <span class="p">});</span>
    <span class="p">})</span>
<span class="p">})</span>
</code></pre></div></div>

<p>Para obtener pedidos con gastos de envío o no, debemos simular pedidos por importe menor o mayor de 50 euros. Esto viene
dado por los items, su precio y las cantidades de producto.</p>

<p>Aquí tenemos un par de ejemplos:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">hasShippingCosts</span> <span class="o">=</span> <span class="p">[</span>
    <span class="p">{</span>
        <span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">Product 1</span><span class="dl">'</span><span class="p">,</span>
        <span class="na">price</span><span class="p">:</span> <span class="mf">12.05</span><span class="p">,</span>
        <span class="na">quantity</span><span class="p">:</span> <span class="mi">1</span>
    <span class="p">},</span>
    <span class="p">{</span>
        <span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">Another</span><span class="dl">'</span><span class="p">,</span>
        <span class="na">price</span><span class="p">:</span> <span class="mf">15.35</span><span class="p">,</span>
        <span class="na">quantity</span><span class="p">:</span> <span class="mi">1</span>
    <span class="p">}</span>
<span class="p">]</span>

<span class="kd">const</span> <span class="nx">hasFreeShipping</span> <span class="o">=</span> <span class="p">[</span>
    <span class="p">{</span>
        <span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">Product 1</span><span class="dl">'</span><span class="p">,</span>
        <span class="na">price</span><span class="p">:</span> <span class="mf">12.05</span><span class="p">,</span>
        <span class="na">quantity</span><span class="p">:</span> <span class="mi">3</span>
    <span class="p">},</span>
    <span class="p">{</span>
        <span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">Another</span><span class="dl">'</span><span class="p">,</span>
        <span class="na">price</span><span class="p">:</span> <span class="mf">15.35</span><span class="p">,</span>
        <span class="na">quantity</span><span class="p">:</span> <span class="mi">6</span>
    <span class="p">}</span>
<span class="p">]</span>
</code></pre></div></div>

<p>Creo que sería más fácil gestionar los ejemplos si introduzco algunos tipos:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kr">interface</span> <span class="nx">Item</span> <span class="p">{</span>
    <span class="nl">name</span><span class="p">:</span> <span class="kr">string</span>
    <span class="nx">price</span><span class="p">:</span> <span class="kr">number</span>
    <span class="nx">quantity</span><span class="p">:</span> <span class="kr">number</span>
<span class="p">}</span>

<span class="k">export</span> <span class="kd">type</span> <span class="nx">ItemCollection</span> <span class="o">=</span> <span class="nx">Item</span><span class="p">[]</span>
</code></pre></div></div>

<p>Quedaría más o menos así:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">describe</span><span class="p">(</span><span class="dl">'</span><span class="s1">long method</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="nx">describe</span><span class="p">.</span><span class="k">for</span><span class="p">([</span><span class="dl">'</span><span class="s1">NORMAL</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">VIP</span><span class="dl">'</span><span class="p">])(</span><span class="dl">'</span><span class="s1">Given a %s customer</span><span class="dl">'</span><span class="p">,</span> <span class="p">(</span><span class="na">customerType</span><span class="p">:</span> <span class="kr">string</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
        <span class="nx">describe</span><span class="p">.</span><span class="k">for</span><span class="p">([</span>
            <span class="p">{</span><span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">shipping costs</span><span class="dl">'</span><span class="p">,</span> <span class="na">items</span><span class="p">:</span> <span class="nx">hasShippingCosts</span><span class="p">},</span>
            <span class="p">{</span><span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">free shipping</span><span class="dl">'</span><span class="p">,</span> <span class="na">items</span><span class="p">:</span> <span class="nx">hasFreeShipping</span><span class="p">}</span>
        <span class="p">])(</span><span class="dl">'</span><span class="s1">When the order has $name</span><span class="dl">'</span><span class="p">,</span> <span class="p">(</span><span class="na">example</span><span class="p">:</span> <span class="p">{</span> <span class="na">name</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span> <span class="nl">items</span><span class="p">:</span> <span class="nx">ItemCollection</span> <span class="p">})</span> <span class="o">=&gt;</span> <span class="p">{</span>
            <span class="nx">it</span><span class="p">(</span><span class="dl">'</span><span class="s1">should process the order</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
                <span class="kd">const</span> <span class="nx">logSpy</span> <span class="o">=</span> <span class="nx">vi</span><span class="p">.</span><span class="nx">spyOn</span><span class="p">(</span><span class="nx">console</span><span class="p">,</span> <span class="dl">'</span><span class="s1">log</span><span class="dl">'</span><span class="p">)</span>

                <span class="kd">const</span> <span class="nx">order</span> <span class="o">=</span> <span class="p">{</span>
                    <span class="na">customerEmail</span><span class="p">:</span> <span class="dl">'</span><span class="s1">customer@example.com</span><span class="dl">'</span><span class="p">,</span>
                    <span class="na">customerType</span><span class="p">:</span> <span class="nx">customerType</span><span class="p">,</span>
                    <span class="na">items</span><span class="p">:</span> <span class="nx">example</span><span class="p">.</span><span class="nx">items</span><span class="p">,</span>
                <span class="p">}</span> <span class="k">as</span> <span class="nx">Order</span>

                <span class="kd">const</span> <span class="nx">orderService</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">TestableOrderService</span><span class="p">();</span>
                <span class="nx">orderService</span><span class="p">.</span><span class="nx">process</span><span class="p">(</span><span class="nx">order</span><span class="p">)</span>

                <span class="kd">let</span> <span class="nx">output</span> <span class="o">=</span> <span class="nx">formatConsoleCalls</span><span class="p">(</span><span class="nx">logSpy</span><span class="p">)</span>
                <span class="nx">expect</span><span class="p">(</span><span class="nx">output</span><span class="p">).</span><span class="nx">toMatchSnapshot</span><span class="p">()</span>

                <span class="nx">logSpy</span><span class="p">.</span><span class="nx">mockRestore</span><span class="p">()</span>
            <span class="p">});</span>
        <span class="p">})</span>
    <span class="p">})</span>
<span class="p">})</span>
</code></pre></div></div>

<p>Hay otros casos que nos podría interesar cubrir, como pedidos no válidos. Nos quedaría algo así:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">hasShippingCosts</span><span class="p">:</span> <span class="nx">ItemCollection</span> <span class="o">=</span> <span class="p">[</span>
    <span class="p">{</span>
        <span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">Product 1</span><span class="dl">'</span><span class="p">,</span>
        <span class="na">price</span><span class="p">:</span> <span class="mf">12.05</span><span class="p">,</span>
        <span class="na">quantity</span><span class="p">:</span> <span class="mi">1</span>
    <span class="p">},</span>
    <span class="p">{</span>
        <span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">Another</span><span class="dl">'</span><span class="p">,</span>
        <span class="na">price</span><span class="p">:</span> <span class="mf">15.35</span><span class="p">,</span>
        <span class="na">quantity</span><span class="p">:</span> <span class="mi">1</span>
    <span class="p">}</span>
<span class="p">]</span>


<span class="kd">const</span> <span class="nx">hasFreeShipping</span><span class="p">:</span> <span class="nx">ItemCollection</span> <span class="o">=</span> <span class="p">[</span>
    <span class="p">{</span>
        <span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">Product 1</span><span class="dl">'</span><span class="p">,</span>
        <span class="na">price</span><span class="p">:</span> <span class="mf">12.05</span><span class="p">,</span>
        <span class="na">quantity</span><span class="p">:</span> <span class="mi">3</span>
    <span class="p">},</span>
    <span class="p">{</span>
        <span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">Another</span><span class="dl">'</span><span class="p">,</span>
        <span class="na">price</span><span class="p">:</span> <span class="mf">15.35</span><span class="p">,</span>
        <span class="na">quantity</span><span class="p">:</span> <span class="mi">6</span>
    <span class="p">}</span>
<span class="p">]</span>

<span class="kd">const</span> <span class="nx">hasNoItems</span><span class="p">:</span> <span class="nx">ItemCollection</span> <span class="o">=</span> <span class="p">[]</span>

<span class="kd">const</span> <span class="nx">hasInvalidPrice</span><span class="p">:</span> <span class="nx">ItemCollection</span> <span class="o">=</span> <span class="p">[</span>
    <span class="p">{</span>
        <span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">Invalid</span><span class="dl">'</span><span class="p">,</span>
        <span class="na">price</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
        <span class="na">quantity</span><span class="p">:</span> <span class="mi">2</span>
    <span class="p">}</span>
<span class="p">]</span>

<span class="kd">const</span> <span class="nx">hasInvalidQuantity</span><span class="p">:</span> <span class="nx">ItemCollection</span> <span class="o">=</span> <span class="p">[</span>
    <span class="p">{</span>
        <span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">Invalid</span><span class="dl">'</span><span class="p">,</span>
        <span class="na">price</span><span class="p">:</span> <span class="mf">5.45</span><span class="p">,</span>
        <span class="na">quantity</span><span class="p">:</span> <span class="mi">0</span>
    <span class="p">}</span>
<span class="p">]</span>


<span class="nx">describe</span><span class="p">(</span><span class="dl">'</span><span class="s1">long method</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="nx">describe</span><span class="p">.</span><span class="k">for</span><span class="p">([</span><span class="dl">'</span><span class="s1">NORMAL</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">VIP</span><span class="dl">'</span><span class="p">])(</span><span class="dl">'</span><span class="s1">Given a %s customer</span><span class="dl">'</span><span class="p">,</span> <span class="p">(</span><span class="na">customerType</span><span class="p">:</span> <span class="kr">string</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
        <span class="nx">describe</span><span class="p">.</span><span class="k">for</span><span class="p">([</span>
            <span class="p">{</span><span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">shipping costs</span><span class="dl">'</span><span class="p">,</span> <span class="na">items</span><span class="p">:</span> <span class="nx">hasShippingCosts</span><span class="p">},</span>
            <span class="p">{</span><span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">free shipping</span><span class="dl">'</span><span class="p">,</span> <span class="na">items</span><span class="p">:</span> <span class="nx">hasFreeShipping</span><span class="p">},</span>
            <span class="p">{</span><span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">no items</span><span class="dl">'</span><span class="p">,</span> <span class="na">items</span><span class="p">:</span> <span class="nx">hasNoItems</span><span class="p">},</span>
            <span class="p">{</span><span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">invalid price</span><span class="dl">'</span><span class="p">,</span> <span class="na">items</span><span class="p">:</span> <span class="nx">hasInvalidPrice</span><span class="p">},</span>
            <span class="p">{</span><span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">invalid quantity</span><span class="dl">'</span><span class="p">,</span> <span class="na">items</span><span class="p">:</span> <span class="nx">hasInvalidQuantity</span><span class="p">},</span>
        <span class="p">])(</span><span class="dl">'</span><span class="s1">When the order has $name</span><span class="dl">'</span><span class="p">,</span> <span class="p">(</span><span class="na">example</span><span class="p">:</span> <span class="p">{</span> <span class="na">name</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span> <span class="nl">items</span><span class="p">:</span> <span class="nx">ItemCollection</span> <span class="p">})</span> <span class="o">=&gt;</span> <span class="p">{</span>
            <span class="nx">it</span><span class="p">(</span><span class="dl">'</span><span class="s1">should process the order</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
                <span class="kd">const</span> <span class="nx">logSpy</span> <span class="o">=</span> <span class="nx">vi</span><span class="p">.</span><span class="nx">spyOn</span><span class="p">(</span><span class="nx">console</span><span class="p">,</span> <span class="dl">'</span><span class="s1">log</span><span class="dl">'</span><span class="p">)</span>

                <span class="kd">const</span> <span class="nx">order</span> <span class="o">=</span> <span class="p">{</span>
                    <span class="na">customerEmail</span><span class="p">:</span> <span class="dl">'</span><span class="s1">customer@example.com</span><span class="dl">'</span><span class="p">,</span>
                    <span class="na">customerType</span><span class="p">:</span> <span class="nx">customerType</span><span class="p">,</span>
                    <span class="na">items</span><span class="p">:</span> <span class="nx">example</span><span class="p">.</span><span class="nx">items</span><span class="p">,</span>
                <span class="p">}</span> <span class="k">as</span> <span class="nx">Order</span>

                <span class="kd">const</span> <span class="nx">orderService</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">TestableOrderService</span><span class="p">();</span>
                <span class="nx">orderService</span><span class="p">.</span><span class="nx">process</span><span class="p">(</span><span class="nx">order</span><span class="p">)</span>

                <span class="kd">let</span> <span class="nx">output</span> <span class="o">=</span> <span class="nx">formatConsoleCalls</span><span class="p">(</span><span class="nx">logSpy</span><span class="p">)</span>
                <span class="nx">expect</span><span class="p">(</span><span class="nx">output</span><span class="p">).</span><span class="nx">toMatchSnapshot</span><span class="p">()</span>

                <span class="nx">logSpy</span><span class="p">.</span><span class="nx">mockRestore</span><span class="p">()</span>
            <span class="p">});</span>
        <span class="p">})</span>
    <span class="p">})</span>
<span class="p">})</span>
</code></pre></div></div>

<p>Podríamos ampliar la lista de ejemplos con otros casos de pedidos, como podrían ser pedidos con muchos productos o cuyo
importe total sea muy alto, superior a 1000 euros para ver el formato de salida. Sin embargo, para el ejercicio creo que
nos llega con esto.</p>

<p>Esta combinación nos genera 10 tests que, sin ser mucho, ejercita buena parte del código que queremos tratar. Quedan
fuera del coverage algunas líneas cuando hay problemas en la persistencia, la impresión o el envío del correo. Pero,
como hemos señalado antes, no nos interesa cubrir esas partes en este momento.</p>

<h3 id="aislar-responsabilidades-en-métodos-privados">Aislar responsabilidades en métodos privados</h3>

<p>Ahora que tenemos un test que cubre todo el código que nos interesa, vamos con el refactoring en sí.</p>

<p>Para el smell <em>Long Method</em>, el refactor básico que usaremos es <em>Extract Method</em>. El objetivo es separar las distintas
responsabilidades de las que se hace cargo el método en métodos privados separados. Esto nos ayudará también a descubrir
posibles colaboradores para encargarse de ellas. Además, nos servirá para ocultar detalles en el método principal.</p>

<p>Esa primera fase nos despejará el camino para <em>Extract Class</em>. Una vez que hayamos aislado responsabilidades, veremos
que algunas de ellas estarían mejor en clases independientes. <em>Extract Class</em> es el refactor para mover la lógica a
clases nuevas y habilitar la inyección de dependencias, que hará que nuestra clase <code class="language-plaintext highlighter-rouge">OrderService</code> sea más fácil de
entender y probar. Pero seguramente también veremos que son aplicables refactors como <code class="language-plaintext highlighter-rouge">Introduce Value Object</code> y otros.</p>

<p>Nuestro primer paso será identificar las diversas responsabilidades. Para ello, intentaremos agrupar las líneas de
código que son cohesivas, es decir, que colaboran entre ellas para lograr un propósito. Cuando se gestionan varias
responsabilidades en un único método la cohesión total es muy baja, pero hay grupos de líneas que son muy cohesivos. Eso
es lo que tenemos que buscar.</p>

<p>En este ejemplo, buena parte del trabajo está hecho y viene marcado por líneas de comentarios. Esto es algo
relativamente habitual en muchos proyectos. Vamos a ver algunos ejemplos.</p>

<h4 id="validación-del-pedido">Validación del pedido</h4>

<p>Nada más empezar el método se hace una validación del pedido, dependiendo de si tiene items o no, y si estos tienen
valores válidos de precio y cantidad.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="c1">// Validar el pedido</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">order</span><span class="p">.</span><span class="nx">items</span> <span class="o">||</span> <span class="nx">order</span><span class="p">.</span><span class="nx">items</span><span class="p">.</span><span class="nx">length</span> <span class="o">===</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">El pedido no tiene productos</span><span class="dl">'</span><span class="p">)</span>
    <span class="k">return</span>
<span class="p">}</span>

<span class="c1">// Validar precios y cantidades</span>
<span class="k">for</span> <span class="p">(</span><span class="kd">const</span> <span class="nx">item</span> <span class="k">of</span> <span class="nx">order</span><span class="p">.</span><span class="nx">items</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">if</span> <span class="p">(</span><span class="nx">item</span><span class="p">.</span><span class="nx">price</span> <span class="o">&lt;</span> <span class="mi">0</span> <span class="o">||</span> <span class="nx">item</span><span class="p">.</span><span class="nx">quantity</span> <span class="o">&lt;=</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Producto inválido en el pedido</span><span class="dl">'</span><span class="p">)</span>
        <span class="k">return</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Podemos extraer esto a un método, pero necesitamos algo de ayuda para ello. Actualmente, si el pedido no es válido, se
hace un log y no se ejecuta nada. Pero para extraer el método tendríamos que introducir un flag o algo. Con esto, los
tests pasan:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="c1">// Validar el pedido</span>

<span class="kd">let</span> <span class="nx">validOrder</span> <span class="o">=</span> <span class="kc">true</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">order</span><span class="p">.</span><span class="nx">items</span> <span class="o">||</span> <span class="nx">order</span><span class="p">.</span><span class="nx">items</span><span class="p">.</span><span class="nx">length</span> <span class="o">===</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">El pedido no tiene productos</span><span class="dl">'</span><span class="p">)</span>
    <span class="nx">validOrder</span> <span class="o">=</span> <span class="kc">false</span>
<span class="p">}</span>

<span class="c1">// Validar precios y cantidades</span>
<span class="k">for</span> <span class="p">(</span><span class="kd">const</span> <span class="nx">item</span> <span class="k">of</span> <span class="nx">order</span><span class="p">.</span><span class="nx">items</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">if</span> <span class="p">(</span><span class="nx">item</span><span class="p">.</span><span class="nx">price</span> <span class="o">&lt;</span> <span class="mi">0</span> <span class="o">||</span> <span class="nx">item</span><span class="p">.</span><span class="nx">quantity</span> <span class="o">&lt;=</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Producto inválido en el pedido</span><span class="dl">'</span><span class="p">)</span>
        <span class="nx">validOrder</span> <span class="o">=</span> <span class="kc">false</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">validOrder</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">return</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Y ahora podríamos extraer el método:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">private</span>
<span class="nx">validateOrder</span><span class="p">(</span><span class="nx">order</span>
<span class="p">:</span>
<span class="nx">Order</span>
<span class="p">)</span>
<span class="p">{</span>
    <span class="c1">// Validar el pedido</span>

    <span class="kd">let</span> <span class="nx">validOrder</span> <span class="o">=</span> <span class="kc">true</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">order</span><span class="p">.</span><span class="nx">items</span> <span class="o">||</span> <span class="nx">order</span><span class="p">.</span><span class="nx">items</span><span class="p">.</span><span class="nx">length</span> <span class="o">===</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">El pedido no tiene productos</span><span class="dl">'</span><span class="p">)</span>
        <span class="nx">validOrder</span> <span class="o">=</span> <span class="kc">false</span>
    <span class="p">}</span>

    <span class="c1">// Validar precios y cantidades</span>
    <span class="k">for</span> <span class="p">(</span><span class="kd">const</span> <span class="nx">item</span> <span class="k">of</span> <span class="nx">order</span><span class="p">.</span><span class="nx">items</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="nx">item</span><span class="p">.</span><span class="nx">price</span> <span class="o">&lt;</span> <span class="mi">0</span> <span class="o">||</span> <span class="nx">item</span><span class="p">.</span><span class="nx">quantity</span> <span class="o">&lt;=</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
            <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Producto inválido en el pedido</span><span class="dl">'</span><span class="p">)</span>
            <span class="nx">validOrder</span> <span class="o">=</span> <span class="kc">false</span>
        <span class="p">}</span>
    <span class="p">}</span>
    <span class="k">return</span> <span class="nx">validOrder</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Este se usa así:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">let</span> <span class="nx">validOrder</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">validateOrder</span><span class="p">(</span><span class="nx">order</span><span class="p">);</span>

<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">validOrder</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">return</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Todo sigue funcionando como es debido, así que también podríamos refactorizar un poco aquí, librándonos de comentarios y
de una variable temporal:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">private</span>
<span class="nx">validateOrder</span><span class="p">(</span><span class="nx">order</span>
<span class="p">:</span>
<span class="nx">Order</span>
<span class="p">)</span>
<span class="p">{</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">order</span><span class="p">.</span><span class="nx">items</span> <span class="o">||</span> <span class="nx">order</span><span class="p">.</span><span class="nx">items</span><span class="p">.</span><span class="nx">length</span> <span class="o">===</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">El pedido no tiene productos</span><span class="dl">'</span><span class="p">)</span>
        <span class="k">return</span> <span class="kc">false</span>
    <span class="p">}</span>

    <span class="k">for</span> <span class="p">(</span><span class="kd">const</span> <span class="nx">item</span> <span class="k">of</span> <span class="nx">order</span><span class="p">.</span><span class="nx">items</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="nx">item</span><span class="p">.</span><span class="nx">price</span> <span class="o">&lt;</span> <span class="mi">0</span> <span class="o">||</span> <span class="nx">item</span><span class="p">.</span><span class="nx">quantity</span> <span class="o">&lt;=</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
            <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Producto inválido en el pedido</span><span class="dl">'</span><span class="p">)</span>
            <span class="k">return</span> <span class="kc">false</span>
        <span class="p">}</span>
    <span class="p">}</span>
    <span class="k">return</span> <span class="kc">true</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Y podemos hacer lo mismo en el método principal:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="k">this</span><span class="p">.</span><span class="nx">validateOrder</span><span class="p">(</span><span class="nx">order</span><span class="p">))</span> <span class="p">{</span>
    <span class="k">return</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Un método al que le pasamos un objeto y solo opera con ese objeto ya nos estaría indicando que ese comportamiento
pertenece al objeto en cuestión. En otras palabras, <code class="language-plaintext highlighter-rouge">Order</code> bien puede tener un método <code class="language-plaintext highlighter-rouge">validate</code> o<code class="language-plaintext highlighter-rouge">readyForProcessing</code>.
Algo así:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kr">interface</span> <span class="nx">Order</span> <span class="p">{</span>
    <span class="nl">customerEmail</span><span class="p">:</span> <span class="kr">string</span>
    <span class="nx">customerType</span><span class="p">:</span> <span class="dl">'</span><span class="s1">NORMAL</span><span class="dl">'</span> <span class="o">|</span> <span class="dl">'</span><span class="s1">VIP</span><span class="dl">'</span>
    <span class="nx">items</span><span class="p">:</span> <span class="p">{</span> <span class="nl">name</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span> <span class="nl">price</span><span class="p">:</span> <span class="kr">number</span><span class="p">;</span> <span class="nl">quantity</span><span class="p">:</span> <span class="kr">number</span> <span class="p">}[]</span>
    <span class="nx">subtotal</span><span class="p">?:</span> <span class="kr">number</span>
    <span class="nx">discount</span><span class="p">?:</span> <span class="kr">number</span>
    <span class="nx">tax</span><span class="p">?:</span> <span class="kr">number</span>
    <span class="nx">shipping</span><span class="p">?:</span> <span class="kr">number</span>
    <span class="nx">total</span><span class="p">?:</span> <span class="kr">number</span>

    <span class="nx">isReadyForProcessing</span><span class="p">():</span> <span class="nx">boolean</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Claro que <code class="language-plaintext highlighter-rouge">Order</code> es una interfaz y necesitaríamos una clase para poder implementarle comportamientos. Esto nos desvía
un poco de nuestro propósito ahora, por lo que vamos a posponer esta intervención para otro momento más propicio.</p>

<h4 id="más-comportamientos-en-order">Más comportamientos en Order</h4>

<p>A continuación, tenemos un bloque que realiza varios cálculos que se aplican a Order. De hecho, se calculan y asignan
algunas de sus propiedades.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Calcular subtotal</span>
<span class="kd">let</span> <span class="nx">subtotal</span> <span class="o">=</span> <span class="mi">0</span>
<span class="k">for</span> <span class="p">(</span><span class="kd">const</span> <span class="nx">item</span> <span class="k">of</span> <span class="nx">order</span><span class="p">.</span><span class="nx">items</span><span class="p">)</span> <span class="p">{</span>
    <span class="nx">subtotal</span> <span class="o">+=</span> <span class="nx">item</span><span class="p">.</span><span class="nx">price</span> <span class="o">*</span> <span class="nx">item</span><span class="p">.</span><span class="nx">quantity</span>
<span class="p">}</span>

<span class="c1">// Descuento por cliente VIP (10% del subtotal)</span>
<span class="kd">let</span> <span class="nx">discount</span> <span class="o">=</span> <span class="mi">0</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">order</span><span class="p">.</span><span class="nx">customerType</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">VIP</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
    <span class="nx">discount</span> <span class="o">=</span> <span class="nx">roundMoney</span><span class="p">(</span><span class="nx">subtotal</span> <span class="o">*</span> <span class="mf">0.1</span><span class="p">)</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Descuento VIP aplicado</span><span class="dl">'</span><span class="p">)</span>
<span class="p">}</span>

<span class="c1">// Base imponible</span>
<span class="kd">const</span> <span class="nx">taxable</span> <span class="o">=</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">max</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="nx">subtotal</span> <span class="o">-</span> <span class="nx">discount</span><span class="p">)</span>

<span class="c1">// Impuestos</span>
<span class="kd">const</span> <span class="nx">tax</span> <span class="o">=</span> <span class="nx">roundMoney</span><span class="p">(</span><span class="nx">taxable</span> <span class="o">*</span> <span class="nx">TAX_RATE</span><span class="p">)</span>

<span class="c1">// Envío</span>
<span class="kd">const</span> <span class="nx">shipping</span> <span class="o">=</span> <span class="nx">taxable</span> <span class="o">&gt;=</span> <span class="nx">FREE_SHIPPING_THRESHOLD</span> <span class="p">?</span> <span class="mi">0</span> <span class="p">:</span> <span class="nx">SHIPPING_FLAT</span>

<span class="c1">// Total</span>
<span class="kd">const</span> <span class="nx">total</span> <span class="o">=</span> <span class="nx">roundMoney</span><span class="p">(</span><span class="nx">taxable</span> <span class="o">+</span> <span class="nx">tax</span> <span class="o">+</span> <span class="nx">shipping</span><span class="p">)</span>

<span class="c1">// Actualizar el pedido (side-effects requeridos)</span>
<span class="nx">order</span><span class="p">.</span><span class="nx">subtotal</span> <span class="o">=</span> <span class="nx">roundMoney</span><span class="p">(</span><span class="nx">subtotal</span><span class="p">)</span>
<span class="nx">order</span><span class="p">.</span><span class="nx">discount</span> <span class="o">=</span> <span class="nx">discount</span>
<span class="nx">order</span><span class="p">.</span><span class="nx">tax</span> <span class="o">=</span> <span class="nx">tax</span>
<span class="nx">order</span><span class="p">.</span><span class="nx">shipping</span> <span class="o">=</span> <span class="nx">shipping</span>
<span class="nx">order</span><span class="p">.</span><span class="nx">total</span> <span class="o">=</span> <span class="nx">total</span>
</code></pre></div></div>

<p>Estamos en las mismas: operamos con las propiedades de un objeto. Podemos extraer este bloque a un método y así
mantenerlo aislado hasta que nos veamos en condiciones de refactorizarlo y moverlo a la clase <code class="language-plaintext highlighter-rouge">Order</code>.</p>

<p>Al aplicar el refactor automático obtengo lo siguiente:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">private</span>
<span class="nx">calculateOrderTotal</span><span class="p">(</span><span class="nx">order</span>
<span class="p">:</span>
<span class="nx">Order</span><span class="p">,</span> <span class="nx">TAX_RATE</span>
<span class="p">:</span>
<span class="kr">number</span><span class="p">,</span> <span class="nx">FREE_SHIPPING_THRESHOLD</span>
<span class="p">:</span>
<span class="kr">number</span><span class="p">,</span> <span class="nx">SHIPPING_FLAT</span>
<span class="p">:</span>
<span class="kr">number</span>
<span class="p">)</span>
<span class="p">{</span>
    <span class="c1">// Calcular subtotal</span>
    <span class="kd">let</span> <span class="nx">subtotal</span> <span class="o">=</span> <span class="mi">0</span>
    <span class="k">for</span> <span class="p">(</span><span class="kd">const</span> <span class="nx">item</span> <span class="k">of</span> <span class="nx">order</span><span class="p">.</span><span class="nx">items</span><span class="p">)</span> <span class="p">{</span>
        <span class="nx">subtotal</span> <span class="o">+=</span> <span class="nx">item</span><span class="p">.</span><span class="nx">price</span> <span class="o">*</span> <span class="nx">item</span><span class="p">.</span><span class="nx">quantity</span>
    <span class="p">}</span>

    <span class="c1">// Descuento por cliente VIP (10% del subtotal)</span>
    <span class="kd">let</span> <span class="nx">discount</span> <span class="o">=</span> <span class="mi">0</span>
    <span class="k">if</span> <span class="p">(</span><span class="nx">order</span><span class="p">.</span><span class="nx">customerType</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">VIP</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
        <span class="nx">discount</span> <span class="o">=</span> <span class="nx">roundMoney</span><span class="p">(</span><span class="nx">subtotal</span> <span class="o">*</span> <span class="mf">0.1</span><span class="p">)</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Descuento VIP aplicado</span><span class="dl">'</span><span class="p">)</span>
    <span class="p">}</span>

    <span class="c1">// Base imponible</span>
    <span class="kd">const</span> <span class="nx">taxable</span> <span class="o">=</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">max</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="nx">subtotal</span> <span class="o">-</span> <span class="nx">discount</span><span class="p">)</span>

    <span class="c1">// Impuestos</span>
    <span class="kd">const</span> <span class="nx">tax</span> <span class="o">=</span> <span class="nx">roundMoney</span><span class="p">(</span><span class="nx">taxable</span> <span class="o">*</span> <span class="nx">TAX_RATE</span><span class="p">)</span>

    <span class="c1">// Envío</span>
    <span class="kd">const</span> <span class="nx">shipping</span> <span class="o">=</span> <span class="nx">taxable</span> <span class="o">&gt;=</span> <span class="nx">FREE_SHIPPING_THRESHOLD</span> <span class="p">?</span> <span class="mi">0</span> <span class="p">:</span> <span class="nx">SHIPPING_FLAT</span>

    <span class="c1">// Total</span>
    <span class="kd">const</span> <span class="nx">total</span> <span class="o">=</span> <span class="nx">roundMoney</span><span class="p">(</span><span class="nx">taxable</span> <span class="o">+</span> <span class="nx">tax</span> <span class="o">+</span> <span class="nx">shipping</span><span class="p">)</span>

    <span class="c1">// Actualizar el pedido (side-effects requeridos)</span>
    <span class="nx">order</span><span class="p">.</span><span class="nx">subtotal</span> <span class="o">=</span> <span class="nx">roundMoney</span><span class="p">(</span><span class="nx">subtotal</span><span class="p">)</span>
    <span class="nx">order</span><span class="p">.</span><span class="nx">discount</span> <span class="o">=</span> <span class="nx">discount</span>
    <span class="nx">order</span><span class="p">.</span><span class="nx">tax</span> <span class="o">=</span> <span class="nx">tax</span>
    <span class="nx">order</span><span class="p">.</span><span class="nx">shipping</span> <span class="o">=</span> <span class="nx">shipping</span>
    <span class="nx">order</span><span class="p">.</span><span class="nx">total</span> <span class="o">=</span> <span class="nx">total</span>
    <span class="k">return</span> <span class="nx">total</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">total</code> se devuelve porque se va a utilizar en otros lugares del código, concretamente en una línea que pertenece al
bloque de persistencia:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Simular reintentos de escritura</span>
<span class="kd">let</span> <span class="nx">dbSaved</span> <span class="o">=</span> <span class="kc">false</span>
<span class="k">while</span> <span class="p">(</span><span class="o">!</span><span class="nx">dbSaved</span> <span class="o">&amp;&amp;</span> <span class="nx">dbRetries</span> <span class="o">&lt;</span> <span class="nx">dbRetriesMax</span><span class="p">)</span> <span class="p">{</span>
    <span class="nx">dbRetries</span><span class="o">++</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">dbConnected</span><span class="p">)</span> <span class="p">{</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`[DB] Intento </span><span class="p">${</span><span class="nx">dbRetries</span><span class="p">}</span><span class="s2">/</span><span class="p">${</span><span class="nx">dbRetriesMax</span><span class="p">}</span><span class="s2">: reconectando a la base de datos...`</span><span class="p">)</span>
    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`[DB] Intento </span><span class="p">${</span><span class="nx">dbRetries</span><span class="p">}</span><span class="s2">/</span><span class="p">${</span><span class="nx">dbRetriesMax</span><span class="p">}</span><span class="s2">: guardando pedido </span><span class="p">${</span><span class="nx">dbRecord</span><span class="p">.</span><span class="nx">id</span><span class="p">}</span><span class="s2"> con total </span><span class="p">${</span><span class="nx">formatMoney</span><span class="p">(</span><span class="nx">total</span><span class="p">)}</span><span class="s2">`</span><span class="p">)</span>
    <span class="p">}</span>
    <span class="c1">// Resultado aleatorio simulado, pero aquí siempre "exitoso" para no complicar flujos de prueba</span>
    <span class="nx">dbSaved</span> <span class="o">=</span> <span class="kc">true</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Esto ilustra bastante bien un problema que nos podemos encontrar en muchas ocasiones: el código entrelazado aquí y allá
y que hace que grupos de líneas mantengan dependencias que nos impiden separarlos fácilmente.</p>

<p>Así, por ejemplo, <code class="language-plaintext highlighter-rouge">order</code> se usa en este mismo bloque de persistencia, con toda la razón porque estamos procesándola.
Por tanto, cuando intentemos extraer este bloque, necesitaremos pasárselo como parámetro. En principio, deberíamos hacer
lo mismo con <code class="language-plaintext highlighter-rouge">total</code>, pero es fácil ver que no es necesario ya que <code class="language-plaintext highlighter-rouge">total</code> puede ser sustituido por <code class="language-plaintext highlighter-rouge">order.total</code>.</p>

<p>Esto implica básicamente que podríamos dejar de retornar total en esa llamada y reemplazar su uso por <code class="language-plaintext highlighter-rouge">order.total</code> en
donde corresponda, cambio que no afecta al testing:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Simular reintentos de escritura</span>
<span class="kd">let</span> <span class="nx">dbSaved</span> <span class="o">=</span> <span class="kc">false</span>
<span class="k">while</span> <span class="p">(</span><span class="o">!</span><span class="nx">dbSaved</span> <span class="o">&amp;&amp;</span> <span class="nx">dbRetries</span> <span class="o">&lt;</span> <span class="nx">dbRetriesMax</span><span class="p">)</span> <span class="p">{</span>
    <span class="nx">dbRetries</span><span class="o">++</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">dbConnected</span><span class="p">)</span> <span class="p">{</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`[DB] Intento </span><span class="p">${</span><span class="nx">dbRetries</span><span class="p">}</span><span class="s2">/</span><span class="p">${</span><span class="nx">dbRetriesMax</span><span class="p">}</span><span class="s2">: reconectando a la base de datos...`</span><span class="p">)</span>
    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`[DB] Intento </span><span class="p">${</span><span class="nx">dbRetries</span><span class="p">}</span><span class="s2">/</span><span class="p">${</span><span class="nx">dbRetriesMax</span><span class="p">}</span><span class="s2">: guardando pedido </span><span class="p">${</span><span class="nx">dbRecord</span><span class="p">.</span><span class="nx">id</span><span class="p">}</span><span class="s2"> con total </span><span class="p">${</span><span class="nx">formatMoney</span><span class="p">(</span><span class="nx">order</span><span class="p">.</span><span class="nx">total</span><span class="p">)}</span><span class="s2">`</span><span class="p">)</span>
    <span class="p">}</span>
    <span class="c1">// Resultado aleatorio simulado, pero aquí siempre "exitoso" para no complicar flujos de prueba</span>
    <span class="nx">dbSaved</span> <span class="o">=</span> <span class="kc">true</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Por otra parte, las constantes de negocio:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="c1">// Constantes de negocio (simples por ahora)</span>
<span class="kd">const</span> <span class="nx">TAX_RATE</span> <span class="o">=</span> <span class="mf">0.21</span> <span class="c1">// 21% IVA</span>
<span class="kd">const</span> <span class="nx">FREE_SHIPPING_THRESHOLD</span> <span class="o">=</span> <span class="mi">50</span>
<span class="kd">const</span> <span class="nx">SHIPPING_FLAT</span> <span class="o">=</span> <span class="mi">5</span>

<span class="kd">const</span> <span class="nx">total</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">calculateOrderTotal</span><span class="p">(</span><span class="nx">order</span><span class="p">,</span> <span class="nx">TAX_RATE</span><span class="p">,</span> <span class="nx">FREE_SHIPPING_THRESHOLD</span><span class="p">,</span> <span class="nx">SHIPPING_FLAT</span><span class="p">);</span>
</code></pre></div></div>

<p>Se pasan a <code class="language-plaintext highlighter-rouge">calculateOrderTotal</code> y solo se usan allí. La pregunta que me hago es: ¿pertenecen a <code class="language-plaintext highlighter-rouge">OrderService</code> o
pertenecen a <code class="language-plaintext highlighter-rouge">Order</code>?</p>

<p>Por lo general, si las variables se definen y asignan en un lugar y solo se usan una vez es probable que simplemente
podamos aplicar un <em>Inline Variable</em>. O sea: reemplazar la variable por su valor y eliminar la variable temporal. Pero,
en este caso, hacerlo nos conduce a un smell <em>Magic Number</em>, un valor arbitrario en el código cuyo significado de
negocio no es explícito.</p>

<p>Sin embargo, podríamos incluir estas constantes en el método <code class="language-plaintext highlighter-rouge">calculateOrderTotal</code>, que es donde se usan y ya veremos
más adelante como seguir.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">private</span>
<span class="nx">calculateOrderTotal</span><span class="p">(</span><span class="nx">order</span>
<span class="p">:</span>
<span class="nx">Order</span>
<span class="p">):</span>
<span class="k">void</span> <span class="p">{</span>
    <span class="c1">// Constantes de negocio (simples por ahora)</span>
    <span class="kd">const</span> <span class="nx">TAX_RATE</span> <span class="o">=</span> <span class="mf">0.21</span> <span class="c1">// 21% IVA</span>
    <span class="kd">const</span> <span class="nx">FREE_SHIPPING_THRESHOLD</span> <span class="o">=</span> <span class="mi">50</span>
    <span class="kd">const</span> <span class="nx">SHIPPING_FLAT</span> <span class="o">=</span> <span class="mi">5</span>

    <span class="c1">// Calcular subtotal</span>
    <span class="kd">let</span> <span class="nx">subtotal</span> <span class="o">=</span> <span class="mi">0</span>
    <span class="k">for</span><span class="p">(</span><span class="kd">const</span> <span class="nx">item</span> <span class="k">of</span> <span class="nx">order</span><span class="p">.</span><span class="nx">items</span>
<span class="p">)</span>
<span class="p">{</span>
    <span class="nx">subtotal</span> <span class="o">+=</span> <span class="nx">item</span><span class="p">.</span><span class="nx">price</span> <span class="o">*</span> <span class="nx">item</span><span class="p">.</span><span class="nx">quantity</span>
<span class="p">}</span>

<span class="c1">// Descuento por cliente VIP (10% del subtotal)</span>
<span class="kd">let</span> <span class="nx">discount</span> <span class="o">=</span> <span class="mi">0</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">order</span><span class="p">.</span><span class="nx">customerType</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">VIP</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
    <span class="nx">discount</span> <span class="o">=</span> <span class="nx">roundMoney</span><span class="p">(</span><span class="nx">subtotal</span> <span class="o">*</span> <span class="mf">0.1</span><span class="p">)</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Descuento VIP aplicado</span><span class="dl">'</span><span class="p">)</span>
<span class="p">}</span>

<span class="c1">// Base imponible</span>
<span class="kd">const</span> <span class="nx">taxable</span> <span class="o">=</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">max</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="nx">subtotal</span> <span class="o">-</span> <span class="nx">discount</span><span class="p">)</span>

<span class="c1">// Impuestos</span>
<span class="kd">const</span> <span class="nx">tax</span> <span class="o">=</span> <span class="nx">roundMoney</span><span class="p">(</span><span class="nx">taxable</span> <span class="o">*</span> <span class="nx">TAX_RATE</span><span class="p">)</span>

<span class="c1">// Envío</span>
<span class="kd">const</span> <span class="nx">shipping</span> <span class="o">=</span> <span class="nx">taxable</span> <span class="o">&gt;=</span> <span class="nx">FREE_SHIPPING_THRESHOLD</span> <span class="p">?</span> <span class="mi">0</span> <span class="p">:</span> <span class="nx">SHIPPING_FLAT</span>

<span class="c1">// Total</span>
<span class="kd">const</span> <span class="nx">total</span> <span class="o">=</span> <span class="nx">roundMoney</span><span class="p">(</span><span class="nx">taxable</span> <span class="o">+</span> <span class="nx">tax</span> <span class="o">+</span> <span class="nx">shipping</span><span class="p">)</span>

<span class="c1">// Actualizar el pedido (side-effects requeridos)</span>
<span class="nx">order</span><span class="p">.</span><span class="nx">subtotal</span> <span class="o">=</span> <span class="nx">roundMoney</span><span class="p">(</span><span class="nx">subtotal</span><span class="p">)</span>
<span class="nx">order</span><span class="p">.</span><span class="nx">discount</span> <span class="o">=</span> <span class="nx">discount</span>
<span class="nx">order</span><span class="p">.</span><span class="nx">tax</span> <span class="o">=</span> <span class="nx">tax</span>
<span class="nx">order</span><span class="p">.</span><span class="nx">shipping</span> <span class="o">=</span> <span class="nx">shipping</span>
<span class="nx">order</span><span class="p">.</span><span class="nx">total</span> <span class="o">=</span> <span class="nx">total</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Esto nos deja el inicio del método <code class="language-plaintext highlighter-rouge">process</code> así:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">class</span> <span class="nx">OrderService</span> <span class="p">{</span>
    <span class="nx">process</span><span class="p">(</span><span class="nx">order</span><span class="p">:</span> <span class="nx">Order</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="k">this</span><span class="p">.</span><span class="nx">validateOrder</span><span class="p">(</span><span class="nx">order</span><span class="p">))</span> <span class="p">{</span>
            <span class="k">return</span>
        <span class="p">}</span>

        <span class="k">this</span><span class="p">.</span><span class="nx">calculateOrderTotal</span><span class="p">(</span><span class="nx">order</span><span class="p">);</span>

        <span class="c1">// Code removed for clarity</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Vemos claramente que estamos introduciendo métodos que realmente pertenecen a <code class="language-plaintext highlighter-rouge">Order</code>.</p>

<h4 id="persistencia">Persistencia</h4>

<p>El bloque que sigue nos indica que vamos a guardar el pedido en la base de datos. Y es un buen pedazo de bloque con
cerca de 70 líneas.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Registrar en la base de datos (simulado)</span>
<span class="c1">// Bloque gigantesco y sobrecargado para simular persistencia con múltiples pasos innecesarios</span>
<span class="kd">const</span> <span class="nx">dbConnectionString</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">Server=fake.db.local;Database=orders;User=demo;Password=demo</span><span class="dl">'</span>
<span class="kd">const</span> <span class="nx">dbConnected</span> <span class="o">=</span> <span class="kc">true</span> <span class="c1">// pretendemos que ya está conectado</span>
<span class="kd">const</span> <span class="nx">dbRetriesMax</span> <span class="o">=</span> <span class="mi">3</span>
<span class="kd">let</span> <span class="nx">dbRetries</span> <span class="o">=</span> <span class="mi">0</span>
<span class="kd">const</span> <span class="nx">dbNow</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Date</span><span class="p">()</span>
<span class="kd">const</span> <span class="nx">dbRecordId</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">generateDbRecordId</span><span class="p">();</span>

<span class="c1">// Preparar registro a guardar</span>
<span class="kd">const</span> <span class="nx">dbRecord</span> <span class="o">=</span> <span class="p">{</span>
    <span class="na">id</span><span class="p">:</span> <span class="nx">dbRecordId</span><span class="p">,</span>
    <span class="na">customerEmail</span><span class="p">:</span> <span class="nx">order</span><span class="p">.</span><span class="nx">customerEmail</span><span class="p">,</span>
    <span class="na">customerType</span><span class="p">:</span> <span class="nx">order</span><span class="p">.</span><span class="nx">customerType</span><span class="p">,</span>
    <span class="na">items</span><span class="p">:</span> <span class="nx">order</span><span class="p">.</span><span class="nx">items</span><span class="p">.</span><span class="nx">map</span><span class="p">(</span><span class="nx">i</span> <span class="o">=&gt;</span> <span class="p">({</span><span class="na">name</span><span class="p">:</span> <span class="nx">i</span><span class="p">.</span><span class="nx">name</span><span class="p">,</span> <span class="na">price</span><span class="p">:</span> <span class="nx">i</span><span class="p">.</span><span class="nx">price</span><span class="p">,</span> <span class="na">quantity</span><span class="p">:</span> <span class="nx">i</span><span class="p">.</span><span class="nx">quantity</span><span class="p">})),</span>
    <span class="na">amounts</span><span class="p">:</span> <span class="p">{</span>
        <span class="na">subtotal</span><span class="p">:</span> <span class="nx">order</span><span class="p">.</span><span class="nx">subtotal</span><span class="p">,</span>
        <span class="na">discount</span><span class="p">:</span> <span class="nx">order</span><span class="p">.</span><span class="nx">discount</span><span class="p">,</span>
        <span class="na">tax</span><span class="p">:</span> <span class="nx">order</span><span class="p">.</span><span class="nx">tax</span><span class="p">,</span>
        <span class="na">shipping</span><span class="p">:</span> <span class="nx">order</span><span class="p">.</span><span class="nx">shipping</span><span class="p">,</span>
        <span class="na">total</span><span class="p">:</span> <span class="nx">order</span><span class="p">.</span><span class="nx">total</span><span class="p">,</span>
    <span class="p">},</span>
    <span class="na">status</span><span class="p">:</span> <span class="dl">'</span><span class="s1">PENDING</span><span class="dl">'</span><span class="p">,</span>
    <span class="na">createdAt</span><span class="p">:</span> <span class="nx">dbNow</span><span class="p">.</span><span class="nx">toISOString</span><span class="p">(),</span>
    <span class="na">updatedAt</span><span class="p">:</span> <span class="nx">dbNow</span><span class="p">.</span><span class="nx">toISOString</span><span class="p">(),</span>
    <span class="na">currency</span><span class="p">:</span> <span class="dl">'</span><span class="s1">USD</span><span class="dl">'</span><span class="p">,</span>
<span class="p">}</span>

<span class="c1">// Validaciones redundantes antes de guardar</span>
<span class="kd">const</span> <span class="nx">hasItems</span> <span class="o">=</span> <span class="nb">Array</span><span class="p">.</span><span class="nx">isArray</span><span class="p">(</span><span class="nx">dbRecord</span><span class="p">.</span><span class="nx">items</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="nx">dbRecord</span><span class="p">.</span><span class="nx">items</span><span class="p">.</span><span class="nx">length</span> <span class="o">&gt;</span> <span class="mi">0</span>
<span class="kd">const</span> <span class="nx">totalsConsistent</span> <span class="o">=</span> <span class="k">typeof</span> <span class="nx">dbRecord</span><span class="p">.</span><span class="nx">amounts</span><span class="p">.</span><span class="nx">total</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">number</span><span class="dl">'</span> <span class="o">&amp;&amp;</span> <span class="nx">dbRecord</span><span class="p">.</span><span class="nx">amounts</span><span class="p">.</span><span class="nx">total</span> <span class="o">&gt;=</span> <span class="mi">0</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">hasItems</span><span class="p">)</span> <span class="p">{</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">warn</span><span class="p">(</span><span class="dl">'</span><span class="s1">[DB] No se puede guardar: el pedido no tiene items</span><span class="dl">'</span><span class="p">)</span>
<span class="p">}</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">totalsConsistent</span><span class="p">)</span> <span class="p">{</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">warn</span><span class="p">(</span><span class="dl">'</span><span class="s1">[DB] No se puede guardar: total inconsistente</span><span class="dl">'</span><span class="p">)</span>
<span class="p">}</span>

<span class="c1">// Simular transformación/serialización pesada</span>
<span class="kd">const</span> <span class="nx">serialized</span> <span class="o">=</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">(</span><span class="nx">dbRecord</span><span class="p">,</span> <span class="kc">null</span><span class="p">,</span> <span class="mi">2</span><span class="p">)</span>
<span class="kd">const</span> <span class="nx">payloadBytes</span> <span class="o">=</span> <span class="nx">Buffer</span><span class="p">.</span><span class="nx">byteLength</span><span class="p">(</span><span class="nx">serialized</span><span class="p">,</span> <span class="dl">'</span><span class="s1">utf8</span><span class="dl">'</span><span class="p">)</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`[DB] Serializando registro </span><span class="p">${</span><span class="nx">dbRecord</span><span class="p">.</span><span class="nx">id</span><span class="p">}</span><span class="s2"> (</span><span class="p">${</span><span class="nx">payloadBytes</span><span class="p">}</span><span class="s2"> bytes) para </span><span class="p">${</span><span class="nx">dbConnectionString</span><span class="p">}</span><span class="s2">`</span><span class="p">)</span>

<span class="c1">// Simular reintentos de escritura</span>
<span class="kd">let</span> <span class="nx">dbSaved</span> <span class="o">=</span> <span class="kc">false</span>
<span class="k">while</span> <span class="p">(</span><span class="o">!</span><span class="nx">dbSaved</span> <span class="o">&amp;&amp;</span> <span class="nx">dbRetries</span> <span class="o">&lt;</span> <span class="nx">dbRetriesMax</span><span class="p">)</span> <span class="p">{</span>
    <span class="nx">dbRetries</span><span class="o">++</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">dbConnected</span><span class="p">)</span> <span class="p">{</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`[DB] Intento </span><span class="p">${</span><span class="nx">dbRetries</span><span class="p">}</span><span class="s2">/</span><span class="p">${</span><span class="nx">dbRetriesMax</span><span class="p">}</span><span class="s2">: reconectando a la base de datos...`</span><span class="p">)</span>
    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`[DB] Intento </span><span class="p">${</span><span class="nx">dbRetries</span><span class="p">}</span><span class="s2">/</span><span class="p">${</span><span class="nx">dbRetriesMax</span><span class="p">}</span><span class="s2">: guardando pedido </span><span class="p">${</span><span class="nx">dbRecord</span><span class="p">.</span><span class="nx">id</span><span class="p">}</span><span class="s2"> con total </span><span class="p">${</span><span class="nx">formatMoney</span><span class="p">(</span><span class="nx">order</span><span class="p">.</span><span class="nx">total</span><span class="p">)}</span><span class="s2">`</span><span class="p">)</span>
    <span class="p">}</span>
    <span class="c1">// Resultado aleatorio simulado, pero aquí siempre "exitoso" para no complicar flujos de prueba</span>
    <span class="nx">dbSaved</span> <span class="o">=</span> <span class="kc">true</span>
<span class="p">}</span>

<span class="k">if</span> <span class="p">(</span><span class="nx">dbSaved</span><span class="p">)</span> <span class="p">{</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`[DB] Pedido </span><span class="p">${</span><span class="nx">dbRecord</span><span class="p">.</span><span class="nx">id</span><span class="p">}</span><span class="s2"> guardado correctamente`</span><span class="p">)</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">(</span><span class="s2">`[DB] No se pudo guardar el pedido </span><span class="p">${</span><span class="nx">dbRecord</span><span class="p">.</span><span class="nx">id</span><span class="p">}</span><span class="s2"> tras </span><span class="p">${</span><span class="nx">dbRetriesMax</span><span class="p">}</span><span class="s2"> intentos`</span><span class="p">)</span>
<span class="p">}</span>

<span class="c1">// Auditoría/bitácora adicional innecesaria</span>
<span class="kd">const</span> <span class="nx">auditLogEntry</span> <span class="o">=</span> <span class="p">{</span>
    <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">ORDER_SAVED</span><span class="dl">'</span><span class="p">,</span>
    <span class="na">orderId</span><span class="p">:</span> <span class="nx">dbRecord</span><span class="p">.</span><span class="nx">id</span><span class="p">,</span>
    <span class="na">actor</span><span class="p">:</span> <span class="dl">'</span><span class="s1">system</span><span class="dl">'</span><span class="p">,</span>
    <span class="na">at</span><span class="p">:</span> <span class="k">this</span><span class="p">.</span><span class="nx">getCurrentDate</span><span class="p">().</span><span class="nx">toISOString</span><span class="p">(),</span>
    <span class="na">metadata</span><span class="p">:</span> <span class="p">{</span>
        <span class="na">ip</span><span class="p">:</span> <span class="dl">'</span><span class="s1">127.0.0.1</span><span class="dl">'</span><span class="p">,</span>
        <span class="na">userAgent</span><span class="p">:</span> <span class="dl">'</span><span class="s1">OrderService/1.0</span><span class="dl">'</span><span class="p">,</span>
    <span class="p">}</span>
<span class="p">}</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">[AUDIT] Registro:</span><span class="dl">'</span><span class="p">,</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">(</span><span class="nx">auditLogEntry</span><span class="p">))</span>

</code></pre></div></div>

<p>Lo primero que vamos a hacer es extraerlo. Mi expectativa es que solo se tenga que pasar <code class="language-plaintext highlighter-rouge">order</code> y que no haya otra
dependencia entre estas líneas y el resto. Con los refactors automáticos es fácil probar este tipo de cosas.</p>

<p>Y esto es lo que obtenemos:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="p">{</span><span class="nx">dbRecord</span><span class="p">,</span> <span class="nx">serialized</span><span class="p">}</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">persistOrder</span><span class="p">(</span><span class="nx">order</span><span class="p">)</span>
</code></pre></div></div>

<p>El método extraído retorna el <code class="language-plaintext highlighter-rouge">dbRecord</code> y el <code class="language-plaintext highlighter-rouge">payload</code> serializado. ¿Quién más los necesita?</p>

<p>Pues básicamente el bloque de envío de correo. Al obtener una representación del pedido que contiene algunos datos
extra, como el <code class="language-plaintext highlighter-rouge">dbRecordId</code>, que podrían ser de utilidad en otros lugares, la persona que escribió el código del envío
de email quiso aprovecharlo. Lamentablemente, eso genera un acoplamiento entre ambos bloques. Lo ideal es deshacer ese
entrelazado antes de extraer el bloque de persistencia.</p>

<p>Si estudiamos el código de envío de email lo que podemos constatar es que el dato que necesitamos es el número de
pedido. Para todo lo demás se accede directamente a las propiedades de <code class="language-plaintext highlighter-rouge">order</code>. Otro <em>smell</em> ya que <code class="language-plaintext highlighter-rouge">Order</code> ahora mismo
es una <em>Data Class</em>, pero ya nos ocuparemos de ello en otro momento.</p>

<p>Por su parte, el envío de email también necesita de <code class="language-plaintext highlighter-rouge">serialized</code>, otra representación de <code class="language-plaintext highlighter-rouge">Order</code> generada en el bloque
de persistencia.</p>

<p>Tenemos dos cuestiones que resolver:</p>

<ul>
  <li>¿Quién debería generar el número de pedido?</li>
  <li>¿Deben usar la persistencia y el envío de email la misma serialización?</li>
</ul>

<p>En mi opinión, la primera pregunta tiene una respuesta clara: el número de pedido debe generarse fuera de la
persistencia y debería estar en <code class="language-plaintext highlighter-rouge">Order</code> ya que le proporciona identidad. Aun asumiendo que este número de pedido no se
asigna hasta que el pedido es válido y se puede procesar. O, dicho de otra forma, la asignación de número de pedido
forma parte del procesado del mismo. Esto mueve la responsabilidad de generarlo fuera de la persistencia y permite
ponerlo a disposición de cualquier otro componente que maneje <code class="language-plaintext highlighter-rouge">Order</code>.</p>

<p>Mi respuesta para la segunda pregunta es que aunque usen la misma serialización no tienen que usar la misma instancia.
Es decir, cada uno de los componentes ha de serializar la instancia de <code class="language-plaintext highlighter-rouge">Order</code> que reciban, aunque en último término
usan el mismo mecanismo de serialización.</p>

<p>En consecuencia, el bloque de persistencia no debería devolver nada, mientras que Order debería generar su propio número
de pedido y que el bloque de Email debería depender única y exclusivamente de <code class="language-plaintext highlighter-rouge">Order</code>.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">order</span><span class="p">.</span><span class="nx">id</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">generateDbRecordId</span><span class="p">()</span>
</code></pre></div></div>

<p>Necesitamos introducir la propiedad <code class="language-plaintext highlighter-rouge">id</code> en <code class="language-plaintext highlighter-rouge">Order</code>.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kr">interface</span> <span class="nx">Order</span> <span class="p">{</span>
    <span class="nl">id</span><span class="p">:</span> <span class="kr">number</span><span class="p">;</span>
    <span class="nl">customerEmail</span><span class="p">:</span> <span class="kr">string</span>
    <span class="nx">customerType</span><span class="p">:</span> <span class="dl">'</span><span class="s1">NORMAL</span><span class="dl">'</span> <span class="o">|</span> <span class="dl">'</span><span class="s1">VIP</span><span class="dl">'</span>
    <span class="nx">items</span><span class="p">:</span> <span class="p">{</span> <span class="nl">name</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span> <span class="nl">price</span><span class="p">:</span> <span class="kr">number</span><span class="p">;</span> <span class="nl">quantity</span><span class="p">:</span> <span class="kr">number</span> <span class="p">}[]</span>
    <span class="nx">subtotal</span><span class="p">?:</span> <span class="kr">number</span>
    <span class="nx">discount</span><span class="p">?:</span> <span class="kr">number</span>
    <span class="nx">tax</span><span class="p">?:</span> <span class="kr">number</span>
    <span class="nx">shipping</span><span class="p">?:</span> <span class="kr">number</span>
    <span class="nx">total</span><span class="p">?:</span> <span class="kr">number</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Y empezar a usarla en lugar de <code class="language-plaintext highlighter-rouge">dbRecord.id</code> al menos fuera del bloque de persistencia. Dentro de ese bloque no tenemos
inconveniente. De hecho, lo preferiremos.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">dbRecord</span> <span class="o">=</span> <span class="p">{</span>
    <span class="na">id</span><span class="p">:</span> <span class="nx">order</span><span class="p">.</span><span class="nx">id</span><span class="p">,</span> <span class="c1">// Replace dbRecordId with order.id</span>
    <span class="na">customerEmail</span><span class="p">:</span> <span class="nx">order</span><span class="p">.</span><span class="nx">customerEmail</span><span class="p">,</span>
    <span class="na">customerType</span><span class="p">:</span> <span class="nx">order</span><span class="p">.</span><span class="nx">customerType</span><span class="p">,</span>
    <span class="na">items</span><span class="p">:</span> <span class="nx">order</span><span class="p">.</span><span class="nx">items</span><span class="p">.</span><span class="nx">map</span><span class="p">(</span><span class="nx">i</span> <span class="o">=&gt;</span> <span class="p">({</span><span class="na">name</span><span class="p">:</span> <span class="nx">i</span><span class="p">.</span><span class="nx">name</span><span class="p">,</span> <span class="na">price</span><span class="p">:</span> <span class="nx">i</span><span class="p">.</span><span class="nx">price</span><span class="p">,</span> <span class="na">quantity</span><span class="p">:</span> <span class="nx">i</span><span class="p">.</span><span class="nx">quantity</span><span class="p">})),</span>
    <span class="na">amounts</span><span class="p">:</span> <span class="p">{</span>
        <span class="na">subtotal</span><span class="p">:</span> <span class="nx">order</span><span class="p">.</span><span class="nx">subtotal</span><span class="p">,</span>
        <span class="na">discount</span><span class="p">:</span> <span class="nx">order</span><span class="p">.</span><span class="nx">discount</span><span class="p">,</span>
        <span class="na">tax</span><span class="p">:</span> <span class="nx">order</span><span class="p">.</span><span class="nx">tax</span><span class="p">,</span>
        <span class="na">shipping</span><span class="p">:</span> <span class="nx">order</span><span class="p">.</span><span class="nx">shipping</span><span class="p">,</span>
        <span class="na">total</span><span class="p">:</span> <span class="nx">order</span><span class="p">.</span><span class="nx">total</span><span class="p">,</span>
    <span class="p">},</span>
    <span class="na">status</span><span class="p">:</span> <span class="dl">'</span><span class="s1">PENDING</span><span class="dl">'</span><span class="p">,</span>
    <span class="na">createdAt</span><span class="p">:</span> <span class="nx">dbNow</span><span class="p">.</span><span class="nx">toISOString</span><span class="p">(),</span>
    <span class="na">updatedAt</span><span class="p">:</span> <span class="nx">dbNow</span><span class="p">.</span><span class="nx">toISOString</span><span class="p">(),</span>
    <span class="na">currency</span><span class="p">:</span> <span class="dl">'</span><span class="s1">USD</span><span class="dl">'</span><span class="p">,</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Nos quedaría lidiar con la serialización. Básicamente se produce aquí:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Simular transformación/serialización pesada</span>
<span class="kd">const</span> <span class="nx">serialized</span> <span class="o">=</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">(</span><span class="nx">dbRecord</span><span class="p">,</span> <span class="kc">null</span><span class="p">,</span> <span class="mi">2</span><span class="p">)</span>
</code></pre></div></div>

<p>Parte del problema es que serializamos una representación de <code class="language-plaintext highlighter-rouge">Order</code> que no es exactamente <code class="language-plaintext highlighter-rouge">Order</code>, sino <code class="language-plaintext highlighter-rouge">dbRecord</code>. En
mi opinión, la serialización, o la representación que serialicemos, debería partir directamente de <code class="language-plaintext highlighter-rouge">Order</code> ya que el
envío del Email es algo totalmente ajeno a la persistencia. Pero esto provoca problemas en el test, ya que cambia el
resultado, aunque solo sea ligeramente.</p>

<p>Siendo estrictas, no debemos hacer este cambio todavía que estamos refactorizando, por lo que los tests deben mantenerse
pasando siempre e inalterados.</p>

<p>Las opciones serían:</p>

<ul>
  <li>Que el bloque de persistencia devuelve la serialización y pasarla al bloque de envío de email.</li>
  <li>Separar la serialización (y la representación en forma de dbRecord) para poder reutilizarla en varios lugares
diferentes.</li>
</ul>

<p>La idea de esta última opción es que <code class="language-plaintext highlighter-rouge">Order</code> acabará siendo una entidad capaz de generar una representación como
<code class="language-plaintext highlighter-rouge">dbRecord</code> en forma de DTO, que podrá serializarse. Y mientras tanto, lo aislamos en métodos privados de <code class="language-plaintext highlighter-rouge">OrderService</code>.</p>

<p>Por una parte, queremos aislar este código, en el que tenemos otra dependencia del reloj del sistema que no se había
manifestado hasta ahora.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">dbNow</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Date</span><span class="p">()</span>
<span class="c1">// Preparar registro a guardar</span>
<span class="kd">const</span> <span class="nx">dbRecord</span> <span class="o">=</span> <span class="p">{</span>
    <span class="na">id</span><span class="p">:</span> <span class="nx">order</span><span class="p">.</span><span class="nx">id</span><span class="p">,</span>
    <span class="na">customerEmail</span><span class="p">:</span> <span class="nx">order</span><span class="p">.</span><span class="nx">customerEmail</span><span class="p">,</span>
    <span class="na">customerType</span><span class="p">:</span> <span class="nx">order</span><span class="p">.</span><span class="nx">customerType</span><span class="p">,</span>
    <span class="na">items</span><span class="p">:</span> <span class="nx">order</span><span class="p">.</span><span class="nx">items</span><span class="p">.</span><span class="nx">map</span><span class="p">(</span><span class="nx">i</span> <span class="o">=&gt;</span> <span class="p">({</span><span class="na">name</span><span class="p">:</span> <span class="nx">i</span><span class="p">.</span><span class="nx">name</span><span class="p">,</span> <span class="na">price</span><span class="p">:</span> <span class="nx">i</span><span class="p">.</span><span class="nx">price</span><span class="p">,</span> <span class="na">quantity</span><span class="p">:</span> <span class="nx">i</span><span class="p">.</span><span class="nx">quantity</span><span class="p">})),</span>
    <span class="na">amounts</span><span class="p">:</span> <span class="p">{</span>
        <span class="na">subtotal</span><span class="p">:</span> <span class="nx">order</span><span class="p">.</span><span class="nx">subtotal</span><span class="p">,</span>
        <span class="na">discount</span><span class="p">:</span> <span class="nx">order</span><span class="p">.</span><span class="nx">discount</span><span class="p">,</span>
        <span class="na">tax</span><span class="p">:</span> <span class="nx">order</span><span class="p">.</span><span class="nx">tax</span><span class="p">,</span>
        <span class="na">shipping</span><span class="p">:</span> <span class="nx">order</span><span class="p">.</span><span class="nx">shipping</span><span class="p">,</span>
        <span class="na">total</span><span class="p">:</span> <span class="nx">order</span><span class="p">.</span><span class="nx">total</span><span class="p">,</span>
    <span class="p">},</span>
    <span class="na">status</span><span class="p">:</span> <span class="dl">'</span><span class="s1">PENDING</span><span class="dl">'</span><span class="p">,</span>
    <span class="na">createdAt</span><span class="p">:</span> <span class="nx">dbNow</span><span class="p">.</span><span class="nx">toISOString</span><span class="p">(),</span>
    <span class="na">updatedAt</span><span class="p">:</span> <span class="nx">dbNow</span><span class="p">.</span><span class="nx">toISOString</span><span class="p">(),</span>
    <span class="na">currency</span><span class="p">:</span> <span class="dl">'</span><span class="s1">USD</span><span class="dl">'</span><span class="p">,</span>
<span class="p">}</span>
</code></pre></div></div>

<p>En principio, podemos resolverla así:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">dbNow</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">getCurrentDate</span><span class="p">()</span>
</code></pre></div></div>

<p>Y extraer el código a un método privado:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">dbRecord</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">mapOrderToDto</span><span class="p">(</span><span class="nx">order</span><span class="p">);</span>
</code></pre></div></div>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">private</span>
<span class="nx">mapOrderToDto</span><span class="p">(</span><span class="nx">order</span>
<span class="p">:</span>
<span class="nx">Order</span>
<span class="p">)</span>
<span class="p">{</span>
    <span class="kd">const</span> <span class="nx">dbNow</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">getCurrentDate</span><span class="p">()</span>
    <span class="c1">// Preparar registro a guardar</span>
    <span class="k">return</span> <span class="p">{</span>
        <span class="na">id</span><span class="p">:</span> <span class="nx">order</span><span class="p">.</span><span class="nx">id</span><span class="p">,</span>
        <span class="na">customerEmail</span><span class="p">:</span> <span class="nx">order</span><span class="p">.</span><span class="nx">customerEmail</span><span class="p">,</span>
        <span class="na">customerType</span><span class="p">:</span> <span class="nx">order</span><span class="p">.</span><span class="nx">customerType</span><span class="p">,</span>
        <span class="na">items</span><span class="p">:</span> <span class="nx">order</span><span class="p">.</span><span class="nx">items</span><span class="p">.</span><span class="nx">map</span><span class="p">(</span><span class="nx">i</span> <span class="o">=&gt;</span> <span class="p">({</span><span class="na">name</span><span class="p">:</span> <span class="nx">i</span><span class="p">.</span><span class="nx">name</span><span class="p">,</span> <span class="na">price</span><span class="p">:</span> <span class="nx">i</span><span class="p">.</span><span class="nx">price</span><span class="p">,</span> <span class="na">quantity</span><span class="p">:</span> <span class="nx">i</span><span class="p">.</span><span class="nx">quantity</span><span class="p">})),</span>
        <span class="na">amounts</span><span class="p">:</span> <span class="p">{</span>
            <span class="na">subtotal</span><span class="p">:</span> <span class="nx">order</span><span class="p">.</span><span class="nx">subtotal</span><span class="p">,</span>
            <span class="na">discount</span><span class="p">:</span> <span class="nx">order</span><span class="p">.</span><span class="nx">discount</span><span class="p">,</span>
            <span class="na">tax</span><span class="p">:</span> <span class="nx">order</span><span class="p">.</span><span class="nx">tax</span><span class="p">,</span>
            <span class="na">shipping</span><span class="p">:</span> <span class="nx">order</span><span class="p">.</span><span class="nx">shipping</span><span class="p">,</span>
            <span class="na">total</span><span class="p">:</span> <span class="nx">order</span><span class="p">.</span><span class="nx">total</span><span class="p">,</span>
        <span class="p">},</span>
        <span class="na">status</span><span class="p">:</span> <span class="dl">'</span><span class="s1">PENDING</span><span class="dl">'</span><span class="p">,</span>
        <span class="na">createdAt</span><span class="p">:</span> <span class="nx">dbNow</span><span class="p">.</span><span class="nx">toISOString</span><span class="p">(),</span>
        <span class="na">updatedAt</span><span class="p">:</span> <span class="nx">dbNow</span><span class="p">.</span><span class="nx">toISOString</span><span class="p">(),</span>
        <span class="na">currency</span><span class="p">:</span> <span class="dl">'</span><span class="s1">USD</span><span class="dl">'</span><span class="p">,</span>
    <span class="p">};</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Lo siguiente es hacer lo mismo con la serialización:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">serialized</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">serialize</span><span class="p">(</span><span class="nx">dbRecord</span><span class="p">);</span>
</code></pre></div></div>

<p>Esto ocurre al usar el refactor automático:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">private</span>
<span class="nx">serialize</span><span class="p">(</span><span class="nx">dbRecord</span>
<span class="p">:</span>
<span class="p">{</span>
    <span class="nl">id</span><span class="p">:</span> <span class="kr">number</span><span class="p">;</span>
    <span class="nl">customerEmail</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
    <span class="nl">customerType</span><span class="p">:</span> <span class="dl">"</span><span class="s2">NORMAL</span><span class="dl">"</span> <span class="o">|</span> <span class="dl">"</span><span class="s2">VIP</span><span class="dl">"</span><span class="p">;</span>
    <span class="nl">items</span><span class="p">:</span> <span class="p">{</span>
        <span class="na">name</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
        <span class="nl">price</span><span class="p">:</span> <span class="kr">number</span><span class="p">;</span>
        <span class="nl">quantity</span><span class="p">:</span> <span class="kr">number</span>
    <span class="p">}</span>
    <span class="p">[];</span>
    <span class="nl">amounts</span><span class="p">:</span> <span class="p">{</span>
        <span class="na">subtotal</span><span class="p">:</span> <span class="kr">number</span> <span class="o">|</span> <span class="kc">undefined</span><span class="p">;</span>
        <span class="nl">discount</span><span class="p">:</span> <span class="kr">number</span> <span class="o">|</span> <span class="kc">undefined</span><span class="p">;</span>
        <span class="nl">tax</span><span class="p">:</span> <span class="kr">number</span> <span class="o">|</span> <span class="kc">undefined</span><span class="p">;</span>
        <span class="nl">shipping</span><span class="p">:</span> <span class="kr">number</span> <span class="o">|</span> <span class="kc">undefined</span><span class="p">;</span>
        <span class="nl">total</span><span class="p">:</span> <span class="kr">number</span> <span class="o">|</span> <span class="kc">undefined</span>
    <span class="p">}</span>
    <span class="p">;</span>
    <span class="nl">status</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
    <span class="nl">createdAt</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
    <span class="nl">updatedAt</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
    <span class="nl">currency</span><span class="p">:</span> <span class="kr">string</span>
<span class="p">}</span>
<span class="p">)</span>
<span class="p">{</span>
    <span class="c1">// Simular transformación/serialización pesada</span>
    <span class="k">return</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">(</span><span class="nx">dbRecord</span><span class="p">,</span> <span class="kc">null</span><span class="p">,</span> <span class="mi">2</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Ahora podemos usar estos mismos métodos en el bloque de envío de email y desligarlo del bloque de persistencia. Gracias
a eso, aunque repitamos el mapeado a DTO y la serialización, habremos dejado de tener dependencias entre ellos.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">serializedForEmail</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">serialize</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">mapOrderToDto</span><span class="p">(</span><span class="nx">order</span><span class="p">));</span>
<span class="kd">const</span> <span class="nx">attachments</span> <span class="o">=</span> <span class="p">[</span>
    <span class="p">{</span><span class="na">filename</span><span class="p">:</span> <span class="s2">`pedido-</span><span class="p">${</span><span class="nx">order</span><span class="p">.</span><span class="nx">id</span><span class="p">}</span><span class="s2">.json`</span><span class="p">,</span> <span class="na">content</span><span class="p">:</span> <span class="nx">serializedForEmail</span><span class="p">,</span> <span class="na">contentType</span><span class="p">:</span> <span class="dl">'</span><span class="s1">application/json</span><span class="dl">'</span><span class="p">},</span>
    <span class="p">{</span><span class="na">filename</span><span class="p">:</span> <span class="dl">'</span><span class="s1">terminos.txt</span><span class="dl">'</span><span class="p">,</span> <span class="na">content</span><span class="p">:</span> <span class="dl">'</span><span class="s1">Términos y condiciones...</span><span class="dl">'</span><span class="p">,</span> <span class="na">contentType</span><span class="p">:</span> <span class="dl">'</span><span class="s1">text/plain</span><span class="dl">'</span><span class="p">}</span>
<span class="p">]</span>
</code></pre></div></div>

<p>Con esto, debería poder extraer el bloque de persistencia a un método privado sin tener que mantener variables:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">class</span> <span class="nx">OrderService</span> <span class="p">{</span>
    <span class="nx">process</span><span class="p">(</span><span class="nx">order</span><span class="p">:</span> <span class="nx">Order</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="k">this</span><span class="p">.</span><span class="nx">validateOrder</span><span class="p">(</span><span class="nx">order</span><span class="p">))</span> <span class="p">{</span>
            <span class="k">return</span>
        <span class="p">}</span>

        <span class="nx">order</span><span class="p">.</span><span class="nx">id</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">generateDbRecordId</span><span class="p">()</span>

        <span class="k">this</span><span class="p">.</span><span class="nx">calculateOrderTotal</span><span class="p">(</span><span class="nx">order</span><span class="p">);</span>

        <span class="k">this</span><span class="p">.</span><span class="nx">persistOrder</span><span class="p">(</span><span class="nx">order</span><span class="p">);</span>

        <span class="c1">// Code removed for clarity</span>
    <span class="p">}</span>

    <span class="c1">// Code removed for clarity</span>
<span class="p">}</span>
</code></pre></div></div>

<h4 id="envío-de-email">Envío de Email</h4>

<p>El bloque de envío de Emails debería ser algo sencillo y extraerse sin dependencias, pero vamos a comprobarlo.
Efectivamente, la extracción es limpia y no hay variables que nos impidan hacerlo.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">class</span> <span class="nx">OrderService</span> <span class="p">{</span>
    <span class="nx">process</span><span class="p">(</span><span class="nx">order</span><span class="p">:</span> <span class="nx">Order</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="k">this</span><span class="p">.</span><span class="nx">validateOrder</span><span class="p">(</span><span class="nx">order</span><span class="p">))</span> <span class="p">{</span>
            <span class="k">return</span>
        <span class="p">}</span>

        <span class="nx">order</span><span class="p">.</span><span class="nx">id</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">generateDbRecordId</span><span class="p">()</span>

        <span class="k">this</span><span class="p">.</span><span class="nx">calculateOrderTotal</span><span class="p">(</span><span class="nx">order</span><span class="p">);</span>

        <span class="k">this</span><span class="p">.</span><span class="nx">persistOrder</span><span class="p">(</span><span class="nx">order</span><span class="p">);</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">sendOrderConfirmationEmail</span><span class="p">(</span><span class="nx">order</span><span class="p">);</span>
        <span class="c1">// Code removed for clarity</span>
    <span class="p">}</span>

    <span class="c1">// Code removed for clarity</span>
<span class="p">}</span>
</code></pre></div></div>

<h4 id="imprimir-el-pedido">Imprimir el pedido</h4>

<p>Finalmente, solo nos queda el bloque imprime el pedido, que también resulta sencillo:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">class</span> <span class="nx">OrderService</span> <span class="p">{</span>
    <span class="nx">process</span><span class="p">(</span><span class="nx">order</span><span class="p">:</span> <span class="nx">Order</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="k">this</span><span class="p">.</span><span class="nx">validateOrder</span><span class="p">(</span><span class="nx">order</span><span class="p">))</span> <span class="p">{</span>
            <span class="k">return</span>
        <span class="p">}</span>

        <span class="nx">order</span><span class="p">.</span><span class="nx">id</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">generateDbRecordId</span><span class="p">()</span>

        <span class="k">this</span><span class="p">.</span><span class="nx">calculateOrderTotal</span><span class="p">(</span><span class="nx">order</span><span class="p">);</span>

        <span class="k">this</span><span class="p">.</span><span class="nx">persistOrder</span><span class="p">(</span><span class="nx">order</span><span class="p">);</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">sendOrderConfirmationEmail</span><span class="p">(</span><span class="nx">order</span><span class="p">);</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">printOrder</span><span class="p">(</span><span class="nx">order</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="c1">// Code removed for clarity</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Ahora el método principal <code class="language-plaintext highlighter-rouge">process</code> es muy sencillo y limpio: nos muestra los pasos que debe seguir el proceso de un
pedido. Y al haber conseguido romper dependencias entre los tres últimos, podríamos incluso ejecutarlos en distinto
orden, una vez validado y completado el cálculo del total.</p>

<h3 id="introducir-clases-colaboradoras">Introducir clases colaboradoras</h3>

<p>Lo importante ahora es que los tests siguen pasando y tenemos las responsabilidades generales separadas. El trabajo no
está terminado aún, pero ya podemos empezar a introducir clases colaboradoras que se hagan cargo de cada una de ellas.</p>

<p>Para ello introduciremos nuevas clases y nos llevaremos el código necesario.</p>

<h4 id="persistencia-1">Persistencia</h4>

<p>Voy a llamar a esta clase <code class="language-plaintext highlighter-rouge">OrderDatabase</code> para no crear falsas expectativas con conceptos como <code class="language-plaintext highlighter-rouge">OrderRepository</code>.
Simplemente, <code class="language-plaintext highlighter-rouge">OrderDatabase</code> es una clase que se encarga de persistir un pedido en la base de datos.</p>

<p>Inicialmente, voy a crearla y copiar el método <code class="language-plaintext highlighter-rouge">persistOrder</code>, aunque no lo voy a usar todavía, pero sí inyectarla en el
constructor de <code class="language-plaintext highlighter-rouge">OrderService</code>. Tengo que llevarme algunos métodos auxiliares, como <code class="language-plaintext highlighter-rouge">mapOrderToDto</code>, <code class="language-plaintext highlighter-rouge">getCurrentDate</code> y
<code class="language-plaintext highlighter-rouge">serialize</code>.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nx">OrderDatabase</span> <span class="p">{</span>
    <span class="k">public</span> <span class="nx">persist</span><span class="p">(</span><span class="nx">order</span><span class="p">:</span> <span class="nx">Order</span><span class="p">)</span> <span class="p">{</span>
        <span class="c1">// Registrar en la base de datos (simulado)</span>
        <span class="c1">// Bloque gigantesco y sobrecargado para simular persistencia con múltiples pasos innecesarios</span>
        <span class="kd">const</span> <span class="nx">dbConnectionString</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">Server=fake.db.local;Database=orders;User=demo;Password=demo</span><span class="dl">'</span>
        <span class="kd">const</span> <span class="nx">dbConnected</span> <span class="o">=</span> <span class="kc">true</span> <span class="c1">// pretendemos que ya está conectado</span>
        <span class="kd">const</span> <span class="nx">dbRetriesMax</span> <span class="o">=</span> <span class="mi">3</span>
        <span class="kd">let</span> <span class="nx">dbRetries</span> <span class="o">=</span> <span class="mi">0</span>

        <span class="kd">const</span> <span class="nx">dbRecord</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">mapOrderToDto</span><span class="p">(</span><span class="nx">order</span><span class="p">);</span>

        <span class="c1">// Validaciones redundantes antes de guardar</span>
        <span class="kd">const</span> <span class="nx">hasItems</span> <span class="o">=</span> <span class="nb">Array</span><span class="p">.</span><span class="nx">isArray</span><span class="p">(</span><span class="nx">dbRecord</span><span class="p">.</span><span class="nx">items</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="nx">dbRecord</span><span class="p">.</span><span class="nx">items</span><span class="p">.</span><span class="nx">length</span> <span class="o">&gt;</span> <span class="mi">0</span>
        <span class="kd">const</span> <span class="nx">totalsConsistent</span> <span class="o">=</span> <span class="k">typeof</span> <span class="nx">dbRecord</span><span class="p">.</span><span class="nx">amounts</span><span class="p">.</span><span class="nx">total</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">number</span><span class="dl">'</span> <span class="o">&amp;&amp;</span> <span class="nx">dbRecord</span><span class="p">.</span><span class="nx">amounts</span><span class="p">.</span><span class="nx">total</span> <span class="o">&gt;=</span> <span class="mi">0</span>
        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">hasItems</span><span class="p">)</span> <span class="p">{</span>
            <span class="nx">console</span><span class="p">.</span><span class="nx">warn</span><span class="p">(</span><span class="dl">'</span><span class="s1">[DB] No se puede guardar: el pedido no tiene items</span><span class="dl">'</span><span class="p">)</span>
        <span class="p">}</span>
        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">totalsConsistent</span><span class="p">)</span> <span class="p">{</span>
            <span class="nx">console</span><span class="p">.</span><span class="nx">warn</span><span class="p">(</span><span class="dl">'</span><span class="s1">[DB] No se puede guardar: total inconsistente</span><span class="dl">'</span><span class="p">)</span>
        <span class="p">}</span>
        <span class="kd">const</span> <span class="nx">serialized</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">serialize</span><span class="p">(</span><span class="nx">dbRecord</span><span class="p">);</span>
        <span class="kd">const</span> <span class="nx">payloadBytes</span> <span class="o">=</span> <span class="nx">Buffer</span><span class="p">.</span><span class="nx">byteLength</span><span class="p">(</span><span class="nx">serialized</span><span class="p">,</span> <span class="dl">'</span><span class="s1">utf8</span><span class="dl">'</span><span class="p">)</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`[DB] Serializando registro </span><span class="p">${</span><span class="nx">dbRecord</span><span class="p">.</span><span class="nx">id</span><span class="p">}</span><span class="s2"> (</span><span class="p">${</span><span class="nx">payloadBytes</span><span class="p">}</span><span class="s2"> bytes) para </span><span class="p">${</span><span class="nx">dbConnectionString</span><span class="p">}</span><span class="s2">`</span><span class="p">)</span>

        <span class="c1">// Simular reintentos de escritura</span>
        <span class="kd">let</span> <span class="nx">dbSaved</span> <span class="o">=</span> <span class="kc">false</span>
        <span class="k">while</span> <span class="p">(</span><span class="o">!</span><span class="nx">dbSaved</span> <span class="o">&amp;&amp;</span> <span class="nx">dbRetries</span> <span class="o">&lt;</span> <span class="nx">dbRetriesMax</span><span class="p">)</span> <span class="p">{</span>
            <span class="nx">dbRetries</span><span class="o">++</span>
            <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">dbConnected</span><span class="p">)</span> <span class="p">{</span>
                <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`[DB] Intento </span><span class="p">${</span><span class="nx">dbRetries</span><span class="p">}</span><span class="s2">/</span><span class="p">${</span><span class="nx">dbRetriesMax</span><span class="p">}</span><span class="s2">: reconectando a la base de datos...`</span><span class="p">)</span>
            <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
                <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`[DB] Intento </span><span class="p">${</span><span class="nx">dbRetries</span><span class="p">}</span><span class="s2">/</span><span class="p">${</span><span class="nx">dbRetriesMax</span><span class="p">}</span><span class="s2">: guardando pedido </span><span class="p">${</span><span class="nx">dbRecord</span><span class="p">.</span><span class="nx">id</span><span class="p">}</span><span class="s2"> con total </span><span class="p">${</span><span class="nx">formatMoney</span><span class="p">(</span><span class="nx">order</span><span class="p">.</span><span class="nx">total</span><span class="p">)}</span><span class="s2">`</span><span class="p">)</span>
            <span class="p">}</span>
            <span class="c1">// Resultado aleatorio simulado, pero aquí siempre "exitoso" para no complicar flujos de prueba</span>
            <span class="nx">dbSaved</span> <span class="o">=</span> <span class="kc">true</span>
        <span class="p">}</span>

        <span class="k">if</span> <span class="p">(</span><span class="nx">dbSaved</span><span class="p">)</span> <span class="p">{</span>
            <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`[DB] Pedido </span><span class="p">${</span><span class="nx">dbRecord</span><span class="p">.</span><span class="nx">id</span><span class="p">}</span><span class="s2"> guardado correctamente`</span><span class="p">)</span>
        <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
            <span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">(</span><span class="s2">`[DB] No se pudo guardar el pedido </span><span class="p">${</span><span class="nx">dbRecord</span><span class="p">.</span><span class="nx">id</span><span class="p">}</span><span class="s2"> tras </span><span class="p">${</span><span class="nx">dbRetriesMax</span><span class="p">}</span><span class="s2"> intentos`</span><span class="p">)</span>
        <span class="p">}</span>

        <span class="c1">// Auditoría/bitácora adicional innecesaria</span>
        <span class="kd">const</span> <span class="nx">auditLogEntry</span> <span class="o">=</span> <span class="p">{</span>
            <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">ORDER_SAVED</span><span class="dl">'</span><span class="p">,</span>
            <span class="na">orderId</span><span class="p">:</span> <span class="nx">dbRecord</span><span class="p">.</span><span class="nx">id</span><span class="p">,</span>
            <span class="na">actor</span><span class="p">:</span> <span class="dl">'</span><span class="s1">system</span><span class="dl">'</span><span class="p">,</span>
            <span class="na">at</span><span class="p">:</span> <span class="k">this</span><span class="p">.</span><span class="nx">getCurrentDate</span><span class="p">().</span><span class="nx">toISOString</span><span class="p">(),</span>
            <span class="na">metadata</span><span class="p">:</span> <span class="p">{</span>
                <span class="na">ip</span><span class="p">:</span> <span class="dl">'</span><span class="s1">127.0.0.1</span><span class="dl">'</span><span class="p">,</span>
                <span class="na">userAgent</span><span class="p">:</span> <span class="dl">'</span><span class="s1">OrderService/1.0</span><span class="dl">'</span><span class="p">,</span>
            <span class="p">}</span>
        <span class="p">}</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">[AUDIT] Registro:</span><span class="dl">'</span><span class="p">,</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">(</span><span class="nx">auditLogEntry</span><span class="p">))</span>
    <span class="p">}</span>

    <span class="k">protected</span> <span class="nx">getCurrentDate</span><span class="p">():</span> <span class="nb">Date</span> <span class="p">{</span>
        <span class="k">return</span> <span class="k">new</span> <span class="nb">Date</span><span class="p">();</span>
    <span class="p">}</span>

    <span class="k">private</span> <span class="nx">serialize</span><span class="p">(</span><span class="nx">dbRecord</span><span class="p">:</span> <span class="p">{</span>
        <span class="nl">id</span><span class="p">:</span> <span class="kr">number</span><span class="p">;</span>
        <span class="nl">customerEmail</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
        <span class="nl">customerType</span><span class="p">:</span> <span class="dl">"</span><span class="s2">NORMAL</span><span class="dl">"</span> <span class="o">|</span> <span class="dl">"</span><span class="s2">VIP</span><span class="dl">"</span><span class="p">;</span>
        <span class="nl">items</span><span class="p">:</span> <span class="p">{</span> <span class="na">name</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span> <span class="nl">price</span><span class="p">:</span> <span class="kr">number</span><span class="p">;</span> <span class="nl">quantity</span><span class="p">:</span> <span class="kr">number</span> <span class="p">}[];</span>
        <span class="nl">amounts</span><span class="p">:</span> <span class="p">{</span>
            <span class="na">subtotal</span><span class="p">:</span> <span class="kr">number</span> <span class="o">|</span> <span class="kc">undefined</span><span class="p">;</span>
            <span class="nl">discount</span><span class="p">:</span> <span class="kr">number</span> <span class="o">|</span> <span class="kc">undefined</span><span class="p">;</span>
            <span class="nl">tax</span><span class="p">:</span> <span class="kr">number</span> <span class="o">|</span> <span class="kc">undefined</span><span class="p">;</span>
            <span class="nl">shipping</span><span class="p">:</span> <span class="kr">number</span> <span class="o">|</span> <span class="kc">undefined</span><span class="p">;</span>
            <span class="nl">total</span><span class="p">:</span> <span class="kr">number</span> <span class="o">|</span> <span class="kc">undefined</span>
        <span class="p">};</span>
        <span class="nl">status</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
        <span class="nl">createdAt</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
        <span class="nl">updatedAt</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
        <span class="nl">currency</span><span class="p">:</span> <span class="kr">string</span>
    <span class="p">})</span> <span class="p">{</span>
        <span class="c1">// Simular transformación/serialización pesada</span>
        <span class="k">return</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">(</span><span class="nx">dbRecord</span><span class="p">,</span> <span class="kc">null</span><span class="p">,</span> <span class="mi">2</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="k">private</span> <span class="nx">mapOrderToDto</span><span class="p">(</span><span class="nx">order</span><span class="p">:</span> <span class="nx">Order</span><span class="p">)</span> <span class="p">{</span>
        <span class="kd">const</span> <span class="nx">dbNow</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">getCurrentDate</span><span class="p">()</span>
        <span class="c1">// Preparar registro a guardar</span>
        <span class="k">return</span> <span class="p">{</span>
            <span class="na">id</span><span class="p">:</span> <span class="nx">order</span><span class="p">.</span><span class="nx">id</span><span class="p">,</span>
            <span class="na">customerEmail</span><span class="p">:</span> <span class="nx">order</span><span class="p">.</span><span class="nx">customerEmail</span><span class="p">,</span>
            <span class="na">customerType</span><span class="p">:</span> <span class="nx">order</span><span class="p">.</span><span class="nx">customerType</span><span class="p">,</span>
            <span class="na">items</span><span class="p">:</span> <span class="nx">order</span><span class="p">.</span><span class="nx">items</span><span class="p">.</span><span class="nx">map</span><span class="p">(</span><span class="nx">i</span> <span class="o">=&gt;</span> <span class="p">({</span><span class="na">name</span><span class="p">:</span> <span class="nx">i</span><span class="p">.</span><span class="nx">name</span><span class="p">,</span> <span class="na">price</span><span class="p">:</span> <span class="nx">i</span><span class="p">.</span><span class="nx">price</span><span class="p">,</span> <span class="na">quantity</span><span class="p">:</span> <span class="nx">i</span><span class="p">.</span><span class="nx">quantity</span><span class="p">})),</span>
            <span class="na">amounts</span><span class="p">:</span> <span class="p">{</span>
                <span class="na">subtotal</span><span class="p">:</span> <span class="nx">order</span><span class="p">.</span><span class="nx">subtotal</span><span class="p">,</span>
                <span class="na">discount</span><span class="p">:</span> <span class="nx">order</span><span class="p">.</span><span class="nx">discount</span><span class="p">,</span>
                <span class="na">tax</span><span class="p">:</span> <span class="nx">order</span><span class="p">.</span><span class="nx">tax</span><span class="p">,</span>
                <span class="na">shipping</span><span class="p">:</span> <span class="nx">order</span><span class="p">.</span><span class="nx">shipping</span><span class="p">,</span>
                <span class="na">total</span><span class="p">:</span> <span class="nx">order</span><span class="p">.</span><span class="nx">total</span><span class="p">,</span>
            <span class="p">},</span>
            <span class="na">status</span><span class="p">:</span> <span class="dl">'</span><span class="s1">PENDING</span><span class="dl">'</span><span class="p">,</span>
            <span class="na">createdAt</span><span class="p">:</span> <span class="nx">dbNow</span><span class="p">.</span><span class="nx">toISOString</span><span class="p">(),</span>
            <span class="na">updatedAt</span><span class="p">:</span> <span class="nx">dbNow</span><span class="p">.</span><span class="nx">toISOString</span><span class="p">(),</span>
            <span class="na">currency</span><span class="p">:</span> <span class="dl">'</span><span class="s1">USD</span><span class="dl">'</span><span class="p">,</span>
        <span class="p">};</span>
    <span class="p">}</span>
<span class="p">}</span> 
</code></pre></div></div>

<p>Y ahora tenemos que usarlo en <code class="language-plaintext highlighter-rouge">OrderService</code>:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">class</span> <span class="nx">OrderService</span> <span class="p">{</span>
  <span class="k">private</span> <span class="k">readonly</span> <span class="nx">orderDatabase</span><span class="p">:</span> <span class="nx">OrderDatabase</span>

  <span class="kd">constructor</span><span class="p">(</span><span class="nx">orderDatabase</span><span class="p">:</span> <span class="nx">OrderDatabase</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">OrderDatabase</span><span class="p">())</span> <span class="p">{</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">orderDatabase</span> <span class="o">=</span> <span class="nx">orderDatabase</span><span class="p">;</span>
  <span class="p">}</span>
  
  <span class="c1">// Code removed for clarity</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Y, reemplazamos el contenido del método <code class="language-plaintext highlighter-rouge">persistOrder</code> por el siguiente:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">private</span> <span class="nx">persistOrder</span><span class="p">(</span><span class="nx">order</span><span class="p">:</span> <span class="nx">Order</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">orderDatabase</span><span class="p">.</span><span class="nx">persist</span><span class="p">(</span><span class="nx">order</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Esto me genera un problema por las fechas. La nueva clase genera su propia fecha, necesitamos controlar eso. Podríamos aplicar un <em>seam</em>, como hicimos con <code class="language-plaintext highlighter-rouge">OrderService</code>, pero en este caso podemos introducir otra clase colaboradora que se encargue de proporcionar la fecha actual. Así que introducimos la interfaz <code class="language-plaintext highlighter-rouge">Clock</code> y una clase <code class="language-plaintext highlighter-rouge">SystemClock</code> que se encarga de proporcionar la fecha del sistema, más una clase <code class="language-plaintext highlighter-rouge">ClockStub</code> para tests.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">interface</span> <span class="nx">Clock</span> <span class="p">{</span>
    <span class="nx">getCurrentDate</span><span class="p">():</span> <span class="nb">Date</span><span class="p">;</span>
<span class="p">}</span>

<span class="kd">class</span> <span class="nx">SystemClock</span> <span class="k">implements</span> <span class="nx">Clock</span> <span class="p">{</span>
    <span class="nx">getCurrentDate</span><span class="p">():</span> <span class="nb">Date</span> <span class="p">{</span>
        <span class="k">return</span> <span class="k">new</span> <span class="nb">Date</span><span class="p">();</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="kd">class</span> <span class="nx">ClockStub</span> <span class="k">implements</span> <span class="nx">Clock</span> <span class="p">{</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="nx">currentDate</span><span class="p">:</span> <span class="nb">Date</span><span class="p">;</span>

    <span class="kd">constructor</span><span class="p">(</span><span class="nx">currentDate</span><span class="p">:</span> <span class="nb">Date</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">currentDate</span> <span class="o">=</span> <span class="nx">currentDate</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="nx">getCurrentDate</span><span class="p">():</span> <span class="nb">Date</span> <span class="p">{</span>
        <span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">currentDate</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Ahora cambiamos el test para montar <code class="language-plaintext highlighter-rouge">OrderService</code> con todo lo que necsitamos:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">describe</span><span class="p">(</span><span class="dl">'</span><span class="s1">long method</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="nx">describe</span><span class="p">.</span><span class="k">for</span><span class="p">([</span><span class="dl">'</span><span class="s1">NORMAL</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">VIP</span><span class="dl">'</span><span class="p">])(</span><span class="dl">'</span><span class="s1">Given a %s customer</span><span class="dl">'</span><span class="p">,</span> <span class="p">(</span><span class="na">customerType</span><span class="p">:</span> <span class="kr">string</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
        <span class="nx">describe</span><span class="p">.</span><span class="k">for</span><span class="p">([</span>
            <span class="p">{</span><span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">shipping costs</span><span class="dl">'</span><span class="p">,</span> <span class="na">items</span><span class="p">:</span> <span class="nx">hasShippingCosts</span><span class="p">},</span>
            <span class="p">{</span><span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">free shipping</span><span class="dl">'</span><span class="p">,</span> <span class="na">items</span><span class="p">:</span> <span class="nx">hasFreeShipping</span><span class="p">},</span>
            <span class="p">{</span><span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">no items</span><span class="dl">'</span><span class="p">,</span> <span class="na">items</span><span class="p">:</span> <span class="nx">hasNoItems</span><span class="p">},</span>
            <span class="p">{</span><span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">invalid price</span><span class="dl">'</span><span class="p">,</span> <span class="na">items</span><span class="p">:</span> <span class="nx">hasInvalidPrice</span><span class="p">},</span>
            <span class="p">{</span><span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">invalid quantity</span><span class="dl">'</span><span class="p">,</span> <span class="na">items</span><span class="p">:</span> <span class="nx">hasInvalidQuantity</span><span class="p">},</span>
        <span class="p">])(</span><span class="dl">'</span><span class="s1">When the order has $name</span><span class="dl">'</span><span class="p">,</span> <span class="p">(</span><span class="na">example</span><span class="p">:</span> <span class="p">{</span> <span class="na">name</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span> <span class="nl">items</span><span class="p">:</span> <span class="nx">ItemCollection</span> <span class="p">})</span> <span class="o">=&gt;</span> <span class="p">{</span>
            <span class="nx">it</span><span class="p">(</span><span class="dl">'</span><span class="s1">should process the order</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
                <span class="kd">const</span> <span class="nx">logSpy</span> <span class="o">=</span> <span class="nx">vi</span><span class="p">.</span><span class="nx">spyOn</span><span class="p">(</span><span class="nx">console</span><span class="p">,</span> <span class="dl">'</span><span class="s1">log</span><span class="dl">'</span><span class="p">)</span>

                <span class="kd">const</span> <span class="nx">order</span> <span class="o">=</span> <span class="p">{</span>
                    <span class="na">customerEmail</span><span class="p">:</span> <span class="dl">'</span><span class="s1">customer@example.com</span><span class="dl">'</span><span class="p">,</span>
                    <span class="na">customerType</span><span class="p">:</span> <span class="nx">customerType</span><span class="p">,</span>
                    <span class="na">items</span><span class="p">:</span> <span class="nx">example</span><span class="p">.</span><span class="nx">items</span><span class="p">,</span>
                <span class="p">}</span> <span class="k">as</span> <span class="nx">Order</span>

                <span class="kd">const</span> <span class="nx">clock</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">ClockStub</span><span class="p">(</span><span class="k">new</span> <span class="nb">Date</span><span class="p">(</span><span class="dl">'</span><span class="s1">2023-05-21T13:35</span><span class="dl">'</span><span class="p">))</span>
                <span class="kd">const</span> <span class="nx">db</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">OrderDatabase</span><span class="p">(</span><span class="nx">clock</span><span class="p">)</span>
                <span class="kd">const</span> <span class="nx">orderService</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">TestableOrderService</span><span class="p">(</span><span class="nx">db</span><span class="p">);</span>
                <span class="nx">orderService</span><span class="p">.</span><span class="nx">process</span><span class="p">(</span><span class="nx">order</span><span class="p">)</span>

                <span class="kd">let</span> <span class="nx">output</span> <span class="o">=</span> <span class="nx">formatConsoleCalls</span><span class="p">(</span><span class="nx">logSpy</span><span class="p">)</span>
                <span class="nx">expect</span><span class="p">(</span><span class="nx">output</span><span class="p">).</span><span class="nx">toMatchSnapshot</span><span class="p">()</span>

                <span class="nx">logSpy</span><span class="p">.</span><span class="nx">mockRestore</span><span class="p">()</span>
            <span class="p">});</span>
        <span class="p">})</span>
    <span class="p">})</span>
<span class="p">})</span>
</code></pre></div></div>

<p>Ciertamente, podríamos cambiar OrderService para aceptar también un <code class="language-plaintext highlighter-rouge">Clock</code> y usarlo en lugar de sobreescribir el método <code class="language-plaintext highlighter-rouge">getCurrentDate</code>. Es un paso para eliminar la clase <code class="language-plaintext highlighter-rouge">TestableOrderService</code>.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">class</span> <span class="nx">OrderService</span> <span class="p">{</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="nx">orderDatabase</span><span class="p">:</span> <span class="nx">OrderDatabase</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="nx">clock</span><span class="p">:</span> <span class="nx">Clock</span>

    <span class="kd">constructor</span><span class="p">(</span><span class="nx">orderDatabase</span><span class="p">:</span> <span class="nx">OrderDatabase</span><span class="p">,</span> <span class="nx">clock</span><span class="p">:</span> <span class="nx">Clock</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">orderDatabase</span> <span class="o">=</span> <span class="nx">orderDatabase</span><span class="p">;</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">clock</span> <span class="o">=</span> <span class="nx">clock</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">private</span> <span class="nx">getCurrentDate</span><span class="p">():</span> <span class="nb">Date</span> <span class="p">{</span>
        <span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">clock</span><span class="p">.</span><span class="nx">getCurrentDate</span><span class="p">();</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Y ya no necesitamos ese override:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nx">TestableOrderService</span> <span class="kd">extends</span> <span class="nx">OrderService</span> <span class="p">{</span>
    <span class="k">protected</span> <span class="nx">generateDbRecordId</span><span class="p">():</span> <span class="kr">number</span> <span class="p">{</span>
        <span class="k">return</span> <span class="mi">67234</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">protected</span> <span class="nx">generatePrintJobId</span><span class="p">():</span> <span class="kr">string</span> <span class="p">{</span>
        <span class="k">return</span> <span class="dl">'</span><span class="s1">prn-1762191762553-125</span><span class="dl">'</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>El test queda así:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">describe</span><span class="p">(</span><span class="dl">'</span><span class="s1">long method</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="nx">describe</span><span class="p">.</span><span class="k">for</span><span class="p">([</span><span class="dl">'</span><span class="s1">NORMAL</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">VIP</span><span class="dl">'</span><span class="p">])(</span><span class="dl">'</span><span class="s1">Given a %s customer</span><span class="dl">'</span><span class="p">,</span> <span class="p">(</span><span class="na">customerType</span><span class="p">:</span> <span class="kr">string</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
        <span class="nx">describe</span><span class="p">.</span><span class="k">for</span><span class="p">([</span>
            <span class="p">{</span><span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">shipping costs</span><span class="dl">'</span><span class="p">,</span> <span class="na">items</span><span class="p">:</span> <span class="nx">hasShippingCosts</span><span class="p">},</span>
            <span class="p">{</span><span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">free shipping</span><span class="dl">'</span><span class="p">,</span> <span class="na">items</span><span class="p">:</span> <span class="nx">hasFreeShipping</span><span class="p">},</span>
            <span class="p">{</span><span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">no items</span><span class="dl">'</span><span class="p">,</span> <span class="na">items</span><span class="p">:</span> <span class="nx">hasNoItems</span><span class="p">},</span>
            <span class="p">{</span><span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">invalid price</span><span class="dl">'</span><span class="p">,</span> <span class="na">items</span><span class="p">:</span> <span class="nx">hasInvalidPrice</span><span class="p">},</span>
            <span class="p">{</span><span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">invalid quantity</span><span class="dl">'</span><span class="p">,</span> <span class="na">items</span><span class="p">:</span> <span class="nx">hasInvalidQuantity</span><span class="p">},</span>
        <span class="p">])(</span><span class="dl">'</span><span class="s1">When the order has $name</span><span class="dl">'</span><span class="p">,</span> <span class="p">(</span><span class="na">example</span><span class="p">:</span> <span class="p">{</span> <span class="na">name</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span> <span class="nl">items</span><span class="p">:</span> <span class="nx">ItemCollection</span> <span class="p">})</span> <span class="o">=&gt;</span> <span class="p">{</span>
            <span class="nx">it</span><span class="p">(</span><span class="dl">'</span><span class="s1">should process the order</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
                <span class="kd">const</span> <span class="nx">logSpy</span> <span class="o">=</span> <span class="nx">vi</span><span class="p">.</span><span class="nx">spyOn</span><span class="p">(</span><span class="nx">console</span><span class="p">,</span> <span class="dl">'</span><span class="s1">log</span><span class="dl">'</span><span class="p">)</span>

                <span class="kd">const</span> <span class="nx">order</span> <span class="o">=</span> <span class="p">{</span>
                    <span class="na">customerEmail</span><span class="p">:</span> <span class="dl">'</span><span class="s1">customer@example.com</span><span class="dl">'</span><span class="p">,</span>
                    <span class="na">customerType</span><span class="p">:</span> <span class="nx">customerType</span><span class="p">,</span>
                    <span class="na">items</span><span class="p">:</span> <span class="nx">example</span><span class="p">.</span><span class="nx">items</span><span class="p">,</span>
                <span class="p">}</span> <span class="k">as</span> <span class="nx">Order</span>

                <span class="kd">const</span> <span class="nx">clock</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">ClockStub</span><span class="p">(</span><span class="k">new</span> <span class="nb">Date</span><span class="p">(</span><span class="dl">'</span><span class="s1">2023-05-21T13:35</span><span class="dl">'</span><span class="p">))</span>
                <span class="kd">const</span> <span class="nx">db</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">OrderDatabase</span><span class="p">(</span><span class="nx">clock</span><span class="p">)</span>
                <span class="kd">const</span> <span class="nx">orderService</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">TestableOrderService</span><span class="p">(</span><span class="nx">db</span><span class="p">,</span> <span class="nx">clock</span><span class="p">);</span>
                <span class="nx">orderService</span><span class="p">.</span><span class="nx">process</span><span class="p">(</span><span class="nx">order</span><span class="p">)</span>

                <span class="kd">let</span> <span class="nx">output</span> <span class="o">=</span> <span class="nx">formatConsoleCalls</span><span class="p">(</span><span class="nx">logSpy</span><span class="p">)</span>
                <span class="nx">expect</span><span class="p">(</span><span class="nx">output</span><span class="p">).</span><span class="nx">toMatchSnapshot</span><span class="p">()</span>

                <span class="nx">logSpy</span><span class="p">.</span><span class="nx">mockRestore</span><span class="p">()</span>
            <span class="p">});</span>
        <span class="p">})</span>
    <span class="p">})</span>
<span class="p">})</span>
</code></pre></div></div>

<p>Y sigue pasando correctamente.</p>

<p>Para extraer las otras clases que tenemos pendientes haremos un proceso similar. Crear la nueva clase, copiar en ella el método que vamos a sustituir, darle visibilidad pública y cambiar lo necesario para que funcione. Finalmente, la inyectamos en el constructor de <code class="language-plaintext highlighter-rouge">OrderService</code>.</p>

<h4 id="envío-de-emails">Envío de emails</h4>

<p>Aquí tendríamos la clase extraída:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nx">EmailSender</span> <span class="p">{</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="nx">clock</span><span class="p">:</span> <span class="nx">Clock</span>

    <span class="kd">constructor</span><span class="p">(</span><span class="nx">clock</span><span class="p">:</span> <span class="nx">Clock</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">clock</span> <span class="o">=</span> <span class="nx">clock</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">public</span> <span class="nx">sendOrderConfirmationEmail</span><span class="p">(</span><span class="nx">order</span><span class="p">:</span> <span class="nx">Order</span><span class="p">)</span> <span class="p">{</span>
        <span class="c1">// Enviar correo de confirmación</span>
        <span class="c1">// Bloque gigantesco para simular el envío de un correo con plantillas, adjuntos, y seguimiento</span>
        <span class="kd">const</span> <span class="nx">smtpConfig</span> <span class="o">=</span> <span class="p">{</span>
            <span class="na">host</span><span class="p">:</span> <span class="dl">'</span><span class="s1">smtp.fake.local</span><span class="dl">'</span><span class="p">,</span>
            <span class="na">port</span><span class="p">:</span> <span class="mi">587</span><span class="p">,</span>
            <span class="na">secure</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
            <span class="na">auth</span><span class="p">:</span> <span class="p">{</span><span class="na">user</span><span class="p">:</span> <span class="dl">'</span><span class="s1">notifier</span><span class="dl">'</span><span class="p">,</span> <span class="na">pass</span><span class="p">:</span> <span class="dl">'</span><span class="s1">notifier</span><span class="dl">'</span><span class="p">},</span>
            <span class="na">tls</span><span class="p">:</span> <span class="p">{</span><span class="na">rejectUnauthorized</span><span class="p">:</span> <span class="kc">false</span><span class="p">}</span>
        <span class="p">}</span>
        <span class="kd">const</span> <span class="nx">emailTemplate</span> <span class="o">=</span> <span class="s2">`
      Hola,
      Gracias por tu pedido. Aquí tienes el resumen:\n
      Subtotal: </span><span class="p">${</span><span class="nx">formatMoney</span><span class="p">(</span><span class="nx">order</span><span class="p">.</span><span class="nx">subtotal</span><span class="p">)}</span><span class="s2">\n
      Descuento: </span><span class="p">${</span><span class="nx">order</span><span class="p">.</span><span class="nx">discount</span> <span class="o">&amp;&amp;</span> <span class="nx">order</span><span class="p">.</span><span class="nx">discount</span> <span class="o">&gt;</span> <span class="mi">0</span> <span class="p">?</span> <span class="dl">'</span><span class="s1">-</span><span class="dl">'</span> <span class="o">+</span> <span class="nx">formatMoney</span><span class="p">(</span><span class="nx">order</span><span class="p">.</span><span class="nx">discount</span><span class="p">)</span> <span class="p">:</span> <span class="nx">formatMoney</span><span class="p">(</span><span class="mi">0</span><span class="p">)}</span><span class="s2">\n
      Impuestos: </span><span class="p">${</span><span class="nx">formatMoney</span><span class="p">(</span><span class="nx">order</span><span class="p">.</span><span class="nx">tax</span><span class="p">)}</span><span class="s2">\n
      Envío: </span><span class="p">${</span><span class="nx">formatMoney</span><span class="p">(</span><span class="nx">order</span><span class="p">.</span><span class="nx">shipping</span><span class="p">)}</span><span class="s2">\n
      Total: </span><span class="p">${</span><span class="nx">formatMoney</span><span class="p">(</span><span class="nx">order</span><span class="p">.</span><span class="nx">total</span><span class="p">)}</span><span class="s2">\n

      Nº de pedido: </span><span class="p">${</span><span class="nx">order</span><span class="p">.</span><span class="nx">id</span><span class="p">}</span><span class="s2">\n
      Fecha: </span><span class="p">${</span><span class="k">new</span> <span class="nb">Date</span><span class="p">().</span><span class="nx">toLocaleString</span><span class="p">()}</span><span class="s2">\n

      Saludos,
      Equipo Demo
    `</span>
        <span class="kd">const</span> <span class="nx">trackingPixelUrl</span> <span class="o">=</span> <span class="s2">`https://tracker.fake.local/pixel?orderId=</span><span class="p">${</span><span class="nx">order</span><span class="p">.</span><span class="nx">id</span><span class="p">}</span><span class="s2">&amp;t=</span><span class="p">${</span><span class="nb">Date</span><span class="p">.</span><span class="nx">now</span><span class="p">()}</span><span class="s2">`</span>
        <span class="kd">const</span> <span class="nx">emailBodyHtml</span> <span class="o">=</span> <span class="s2">`
      &lt;html&gt;
        &lt;body&gt;
          &lt;p&gt;Hola,&lt;/p&gt;
          &lt;p&gt;Gracias por tu pedido. Aquí tienes el resumen:&lt;/p&gt;
          &lt;ul&gt;
            &lt;li&gt;Subtotal: &lt;strong&gt;</span><span class="p">${</span><span class="nx">formatMoney</span><span class="p">(</span><span class="nx">order</span><span class="p">.</span><span class="nx">subtotal</span><span class="p">)}</span><span class="s2">&lt;/strong&gt;&lt;/li&gt;
            &lt;li&gt;Descuento: &lt;strong&gt;</span><span class="p">${</span><span class="nx">order</span><span class="p">.</span><span class="nx">discount</span> <span class="o">&amp;&amp;</span> <span class="nx">order</span><span class="p">.</span><span class="nx">discount</span> <span class="o">&gt;</span> <span class="mi">0</span> <span class="p">?</span> <span class="dl">'</span><span class="s1">-</span><span class="dl">'</span> <span class="o">+</span> <span class="nx">formatMoney</span><span class="p">(</span><span class="nx">order</span><span class="p">.</span><span class="nx">discount</span><span class="p">)</span> <span class="p">:</span> <span class="nx">formatMoney</span><span class="p">(</span><span class="mi">0</span><span class="p">)}</span><span class="s2">&lt;/strong&gt;&lt;/li&gt;
            &lt;li&gt;Impuestos: &lt;strong&gt;</span><span class="p">${</span><span class="nx">formatMoney</span><span class="p">(</span><span class="nx">order</span><span class="p">.</span><span class="nx">tax</span><span class="p">)}</span><span class="s2">&lt;/strong&gt;&lt;/li&gt;
            &lt;li&gt;Envío: &lt;strong&gt;</span><span class="p">${</span><span class="nx">formatMoney</span><span class="p">(</span><span class="nx">order</span><span class="p">.</span><span class="nx">shipping</span><span class="p">)}</span><span class="s2">&lt;/strong&gt;&lt;/li&gt;
            &lt;li&gt;Total: &lt;strong&gt;</span><span class="p">${</span><span class="nx">formatMoney</span><span class="p">(</span><span class="nx">order</span><span class="p">.</span><span class="nx">total</span><span class="p">)}</span><span class="s2">&lt;/strong&gt;&lt;/li&gt;
          &lt;/ul&gt;
          &lt;p&gt;Nº de pedido: &lt;code&gt;</span><span class="p">${</span><span class="nx">order</span><span class="p">.</span><span class="nx">id</span><span class="p">}</span><span class="s2">&lt;/code&gt;&lt;/p&gt;
          &lt;p&gt;Fecha: </span><span class="p">${</span><span class="k">new</span> <span class="nb">Date</span><span class="p">().</span><span class="nx">toLocaleString</span><span class="p">()}</span><span class="s2">&lt;/p&gt;
          &lt;img src="</span><span class="p">${</span><span class="nx">trackingPixelUrl</span><span class="p">}</span><span class="s2">" width="1" height="1" alt=""/&gt;
        &lt;/body&gt;
      &lt;/html&gt;
    `</span>
        <span class="kd">const</span> <span class="nx">serializedForEmail</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">serialize</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">mapOrderToDto</span><span class="p">(</span><span class="nx">order</span><span class="p">));</span>
        <span class="kd">const</span> <span class="nx">attachments</span> <span class="o">=</span> <span class="p">[</span>
            <span class="p">{</span><span class="na">filename</span><span class="p">:</span> <span class="s2">`pedido-</span><span class="p">${</span><span class="nx">order</span><span class="p">.</span><span class="nx">id</span><span class="p">}</span><span class="s2">.json`</span><span class="p">,</span> <span class="na">content</span><span class="p">:</span> <span class="nx">serializedForEmail</span><span class="p">,</span> <span class="na">contentType</span><span class="p">:</span> <span class="dl">'</span><span class="s1">application/json</span><span class="dl">'</span><span class="p">},</span>
            <span class="p">{</span><span class="na">filename</span><span class="p">:</span> <span class="dl">'</span><span class="s1">terminos.txt</span><span class="dl">'</span><span class="p">,</span> <span class="na">content</span><span class="p">:</span> <span class="dl">'</span><span class="s1">Términos y condiciones...</span><span class="dl">'</span><span class="p">,</span> <span class="na">contentType</span><span class="p">:</span> <span class="dl">'</span><span class="s1">text/plain</span><span class="dl">'</span><span class="p">}</span>
        <span class="p">]</span>

        <span class="c1">// Simular cálculo de tamaño del correo</span>
        <span class="kd">const</span> <span class="nx">emailSize</span> <span class="o">=</span> <span class="nx">Buffer</span><span class="p">.</span><span class="nx">byteLength</span><span class="p">(</span><span class="nx">emailBodyHtml</span><span class="p">,</span> <span class="dl">'</span><span class="s1">utf8</span><span class="dl">'</span><span class="p">)</span> <span class="o">+</span> <span class="nx">attachments</span><span class="p">.</span><span class="nx">reduce</span><span class="p">((</span><span class="nx">acc</span><span class="p">,</span> <span class="nx">a</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">acc</span> <span class="o">+</span> <span class="nx">Buffer</span><span class="p">.</span><span class="nx">byteLength</span><span class="p">(</span><span class="nx">a</span><span class="p">.</span><span class="nx">content</span><span class="p">,</span> <span class="dl">'</span><span class="s1">utf8</span><span class="dl">'</span><span class="p">),</span> <span class="mi">0</span><span class="p">)</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`[MAIL] Preparando correo (</span><span class="p">${</span><span class="nx">emailSize</span><span class="p">}</span><span class="s2"> bytes) vía </span><span class="p">${</span><span class="nx">smtpConfig</span><span class="p">.</span><span class="nx">host</span><span class="p">}</span><span class="s2">:</span><span class="p">${</span><span class="nx">smtpConfig</span><span class="p">.</span><span class="nx">port</span><span class="p">}</span><span class="s2">`</span><span class="p">)</span>

        <span class="c1">// Simular colas de envío y priorización</span>
        <span class="kd">const</span> <span class="nx">emailPriority</span> <span class="o">=</span> <span class="nx">order</span><span class="p">.</span><span class="nx">customerType</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">VIP</span><span class="dl">'</span> <span class="p">?</span> <span class="dl">'</span><span class="s1">HIGH</span><span class="dl">'</span> <span class="p">:</span> <span class="dl">'</span><span class="s1">NORMAL</span><span class="dl">'</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`[MAIL] Encolando correo (</span><span class="p">${</span><span class="nx">emailPriority</span><span class="p">}</span><span class="s2">) para </span><span class="p">${</span><span class="nx">order</span><span class="p">.</span><span class="nx">customerEmail</span><span class="p">}</span><span class="s2">`</span><span class="p">)</span>

        <span class="c1">// Simular envío con reintentos</span>
        <span class="kd">let</span> <span class="nx">mailAttempts</span> <span class="o">=</span> <span class="mi">0</span>
        <span class="kd">const</span> <span class="nx">mailAttemptsMax</span> <span class="o">=</span> <span class="mi">2</span>
        <span class="kd">let</span> <span class="nx">mailSent</span> <span class="o">=</span> <span class="kc">false</span>
        <span class="k">while</span> <span class="p">(</span><span class="o">!</span><span class="nx">mailSent</span> <span class="o">&amp;&amp;</span> <span class="nx">mailAttempts</span> <span class="o">&lt;</span> <span class="nx">mailAttemptsMax</span><span class="p">)</span> <span class="p">{</span>
            <span class="nx">mailAttempts</span><span class="o">++</span>
            <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`[MAIL] Intento </span><span class="p">${</span><span class="nx">mailAttempts</span><span class="p">}</span><span class="s2">/</span><span class="p">${</span><span class="nx">mailAttemptsMax</span><span class="p">}</span><span class="s2">: enviando correo a </span><span class="p">${</span><span class="nx">order</span><span class="p">.</span><span class="nx">customerEmail</span><span class="p">}</span><span class="s2">`</span><span class="p">)</span>
            <span class="c1">// Simulación simple de éxito</span>
            <span class="nx">mailSent</span> <span class="o">=</span> <span class="kc">true</span>
        <span class="p">}</span>

        <span class="kd">const</span> <span class="nx">messageId</span> <span class="o">=</span> <span class="s2">`msg-</span><span class="p">${</span><span class="nx">order</span><span class="p">.</span><span class="nx">id</span><span class="p">}</span><span class="s2">-</span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">getCurrentDate</span><span class="p">().</span><span class="nx">getTime</span><span class="p">()}</span><span class="s2">`</span>
        <span class="k">if</span> <span class="p">(</span><span class="nx">mailSent</span><span class="p">)</span> <span class="p">{</span>
            <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`[MAIL] Correo enviado a </span><span class="p">${</span><span class="nx">order</span><span class="p">.</span><span class="nx">customerEmail</span><span class="p">}</span><span class="s2"> (messageId=</span><span class="p">${</span><span class="nx">messageId</span><span class="p">}</span><span class="s2">)`</span><span class="p">)</span>
        <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
            <span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">(</span><span class="s2">`[MAIL] Fallo al enviar correo a </span><span class="p">${</span><span class="nx">order</span><span class="p">.</span><span class="nx">customerEmail</span><span class="p">}</span><span class="s2"> tras </span><span class="p">${</span><span class="nx">mailAttemptsMax</span><span class="p">}</span><span class="s2"> intentos`</span><span class="p">)</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="k">private</span> <span class="nx">serialize</span><span class="p">(</span><span class="nx">dbRecord</span><span class="p">:</span> <span class="p">{</span> <span class="nl">id</span><span class="p">:</span> <span class="kr">number</span><span class="p">;</span> <span class="nl">customerEmail</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span> <span class="nl">customerType</span><span class="p">:</span> <span class="dl">"</span><span class="s2">NORMAL</span><span class="dl">"</span> <span class="o">|</span> <span class="dl">"</span><span class="s2">VIP</span><span class="dl">"</span><span class="p">;</span> <span class="nl">items</span><span class="p">:</span> <span class="p">{</span> <span class="na">name</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span> <span class="nl">price</span><span class="p">:</span> <span class="kr">number</span><span class="p">;</span> <span class="nl">quantity</span><span class="p">:</span> <span class="kr">number</span> <span class="p">}[];</span> <span class="nl">amounts</span><span class="p">:</span> <span class="p">{</span> <span class="na">subtotal</span><span class="p">:</span> <span class="kr">number</span> <span class="o">|</span> <span class="kc">undefined</span><span class="p">;</span> <span class="nl">discount</span><span class="p">:</span> <span class="kr">number</span> <span class="o">|</span> <span class="kc">undefined</span><span class="p">;</span> <span class="nl">tax</span><span class="p">:</span> <span class="kr">number</span> <span class="o">|</span> <span class="kc">undefined</span><span class="p">;</span> <span class="nl">shipping</span><span class="p">:</span> <span class="kr">number</span> <span class="o">|</span> <span class="kc">undefined</span><span class="p">;</span> <span class="nl">total</span><span class="p">:</span> <span class="kr">number</span> <span class="o">|</span> <span class="kc">undefined</span> <span class="p">};</span> <span class="nl">status</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span> <span class="nl">createdAt</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span> <span class="nl">updatedAt</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span> <span class="nl">currency</span><span class="p">:</span> <span class="kr">string</span> <span class="p">})</span> <span class="p">{</span>
        <span class="c1">// Simular transformación/serialización pesada</span>
        <span class="k">return</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">(</span><span class="nx">dbRecord</span><span class="p">,</span> <span class="kc">null</span><span class="p">,</span> <span class="mi">2</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="k">private</span> <span class="nx">mapOrderToDto</span><span class="p">(</span><span class="nx">order</span><span class="p">:</span> <span class="nx">Order</span><span class="p">)</span> <span class="p">{</span>
        <span class="kd">const</span> <span class="nx">dbNow</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">getCurrentDate</span><span class="p">()</span>
        <span class="c1">// Preparar registro a guardar</span>
        <span class="k">return</span> <span class="p">{</span>
            <span class="na">id</span><span class="p">:</span> <span class="nx">order</span><span class="p">.</span><span class="nx">id</span><span class="p">,</span>
            <span class="na">customerEmail</span><span class="p">:</span> <span class="nx">order</span><span class="p">.</span><span class="nx">customerEmail</span><span class="p">,</span>
            <span class="na">customerType</span><span class="p">:</span> <span class="nx">order</span><span class="p">.</span><span class="nx">customerType</span><span class="p">,</span>
            <span class="na">items</span><span class="p">:</span> <span class="nx">order</span><span class="p">.</span><span class="nx">items</span><span class="p">.</span><span class="nx">map</span><span class="p">(</span><span class="nx">i</span> <span class="o">=&gt;</span> <span class="p">({</span><span class="na">name</span><span class="p">:</span> <span class="nx">i</span><span class="p">.</span><span class="nx">name</span><span class="p">,</span> <span class="na">price</span><span class="p">:</span> <span class="nx">i</span><span class="p">.</span><span class="nx">price</span><span class="p">,</span> <span class="na">quantity</span><span class="p">:</span> <span class="nx">i</span><span class="p">.</span><span class="nx">quantity</span><span class="p">})),</span>
            <span class="na">amounts</span><span class="p">:</span> <span class="p">{</span>
                <span class="na">subtotal</span><span class="p">:</span> <span class="nx">order</span><span class="p">.</span><span class="nx">subtotal</span><span class="p">,</span>
                <span class="na">discount</span><span class="p">:</span> <span class="nx">order</span><span class="p">.</span><span class="nx">discount</span><span class="p">,</span>
                <span class="na">tax</span><span class="p">:</span> <span class="nx">order</span><span class="p">.</span><span class="nx">tax</span><span class="p">,</span>
                <span class="na">shipping</span><span class="p">:</span> <span class="nx">order</span><span class="p">.</span><span class="nx">shipping</span><span class="p">,</span>
                <span class="na">total</span><span class="p">:</span> <span class="nx">order</span><span class="p">.</span><span class="nx">total</span><span class="p">,</span>
            <span class="p">},</span>
            <span class="na">status</span><span class="p">:</span> <span class="dl">'</span><span class="s1">PENDING</span><span class="dl">'</span><span class="p">,</span>
            <span class="na">createdAt</span><span class="p">:</span> <span class="nx">dbNow</span><span class="p">.</span><span class="nx">toISOString</span><span class="p">(),</span>
            <span class="na">updatedAt</span><span class="p">:</span> <span class="nx">dbNow</span><span class="p">.</span><span class="nx">toISOString</span><span class="p">(),</span>
            <span class="na">currency</span><span class="p">:</span> <span class="dl">'</span><span class="s1">USD</span><span class="dl">'</span><span class="p">,</span>
        <span class="p">};</span>
    <span class="p">}</span>


    <span class="k">private</span> <span class="nx">getCurrentDate</span><span class="p">()</span> <span class="p">{</span>
        <span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">clock</span><span class="p">.</span><span class="nx">getCurrentDate</span><span class="p">()</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Podemos ver que hay código que se repite. De momento, no nos vamos a preocupar por eso. Nuestro objetivo a corto plazo es extraer toda esta funcionalidad a clases especializadas. Posteriormente, cada uno requerirá su propio tratamiento.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">class</span> <span class="nx">OrderService</span> <span class="p">{</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="nx">orderDatabase</span><span class="p">:</span> <span class="nx">OrderDatabase</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="nx">clock</span><span class="p">:</span> <span class="nx">Clock</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="nx">emailSender</span><span class="p">:</span> <span class="nx">EmailSender</span>

    <span class="kd">constructor</span><span class="p">(</span><span class="nx">orderDatabase</span><span class="p">:</span> <span class="nx">OrderDatabase</span><span class="p">,</span> <span class="nx">clock</span><span class="p">:</span> <span class="nx">Clock</span><span class="p">,</span> <span class="nx">emailSender</span><span class="p">:</span> <span class="nx">EmailSender</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">orderDatabase</span> <span class="o">=</span> <span class="nx">orderDatabase</span><span class="p">;</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">clock</span> <span class="o">=</span> <span class="nx">clock</span><span class="p">;</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">emailSender</span> <span class="o">=</span> <span class="nx">emailSender</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="nx">process</span><span class="p">(</span><span class="nx">order</span><span class="p">:</span> <span class="nx">Order</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="k">this</span><span class="p">.</span><span class="nx">validateOrder</span><span class="p">(</span><span class="nx">order</span><span class="p">))</span> <span class="p">{</span>
            <span class="k">return</span>
        <span class="p">}</span>

        <span class="nx">order</span><span class="p">.</span><span class="nx">id</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">generateDbRecordId</span><span class="p">()</span>

        <span class="k">this</span><span class="p">.</span><span class="nx">calculateOrderTotal</span><span class="p">(</span><span class="nx">order</span><span class="p">);</span>

        <span class="k">this</span><span class="p">.</span><span class="nx">persistOrder</span><span class="p">(</span><span class="nx">order</span><span class="p">);</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">sendOrderConfirmationEmail</span><span class="p">(</span><span class="nx">order</span><span class="p">);</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">printOrder</span><span class="p">(</span><span class="nx">order</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="k">private</span> <span class="nx">sendOrderConfirmationEmail</span><span class="p">(</span><span class="nx">order</span><span class="p">:</span> <span class="nx">Order</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">emailSender</span><span class="p">.</span><span class="nx">sendOrderConfirmationEmail</span><span class="p">(</span><span class="nx">order</span><span class="p">)</span>
    <span class="p">}</span>
    <span class="c1">// Code removed for clarity</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Y se usa así:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">clock</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">ClockStub</span><span class="p">(</span><span class="k">new</span> <span class="nb">Date</span><span class="p">(</span><span class="dl">'</span><span class="s1">2023-05-21T13:35</span><span class="dl">'</span><span class="p">))</span>
<span class="kd">const</span> <span class="nx">db</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">OrderDatabase</span><span class="p">(</span><span class="nx">clock</span><span class="p">)</span>
<span class="kd">const</span> <span class="nx">emailSender</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">EmailSender</span><span class="p">(</span><span class="nx">clock</span><span class="p">)</span>
<span class="kd">const</span> <span class="nx">orderService</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">TestableOrderService</span><span class="p">(</span><span class="nx">db</span><span class="p">,</span> <span class="nx">clock</span><span class="p">,</span> <span class="nx">emailSender</span><span class="p">);</span>
<span class="nx">orderService</span><span class="p">.</span><span class="nx">process</span><span class="p">(</span><span class="nx">order</span><span class="p">)</span>
</code></pre></div></div>

<p>Al hacer este cambio dejan de usarse los métodos serialize y mapOrderToDto en OrderService, por lo que podemos eliminarlos.</p>

<h4 id="impresión-del-pedido">Impresión del pedido</h4>

<p>Tenemos que seguir exactamente el mismo proceso para extraer la clase <code class="language-plaintext highlighter-rouge">OrderPrinter</code>. Es decir: introducir la nueva clase, mover a ella el código necesario, hacer público el método principal y, finalmente, inyectarla como dependencia en el constructor de <code class="language-plaintext highlighter-rouge">OrderService</code>.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">class</span> <span class="nx">OrderPrinter</span> <span class="p">{</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="nx">clock</span><span class="p">:</span> <span class="nx">Clock</span>

    <span class="kd">constructor</span><span class="p">(</span><span class="nx">clock</span><span class="p">:</span> <span class="nx">Clock</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">clock</span> <span class="o">=</span> <span class="nx">clock</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">public</span> <span class="nx">print</span><span class="p">(</span><span class="nx">order</span><span class="p">:</span> <span class="nx">Order</span><span class="p">)</span> <span class="p">{</span>
        <span class="c1">// Imprimir resumen -&gt; enviar a impresora</span>
        <span class="kd">const</span> <span class="nx">printJob</span><span class="p">:</span> <span class="nx">PrintJob</span> <span class="o">=</span> <span class="p">{</span>
            <span class="na">title</span><span class="p">:</span> <span class="dl">'</span><span class="s1">Resumen del pedido</span><span class="dl">'</span><span class="p">,</span>
            <span class="na">items</span><span class="p">:</span> <span class="nx">order</span><span class="p">.</span><span class="nx">items</span><span class="p">.</span><span class="nx">map</span><span class="p">(</span><span class="nx">i</span> <span class="o">=&gt;</span> <span class="p">({</span>
                <span class="na">name</span><span class="p">:</span> <span class="nx">i</span><span class="p">.</span><span class="nx">name</span><span class="p">,</span>
                <span class="na">quantity</span><span class="p">:</span> <span class="nx">i</span><span class="p">.</span><span class="nx">quantity</span><span class="p">,</span>
                <span class="na">lineTotal</span><span class="p">:</span> <span class="nx">roundMoney</span><span class="p">(</span><span class="nx">i</span><span class="p">.</span><span class="nx">price</span> <span class="o">*</span> <span class="nx">i</span><span class="p">.</span><span class="nx">quantity</span><span class="p">),</span>
                <span class="na">lineTotalFormatted</span><span class="p">:</span> <span class="nx">formatMoney</span><span class="p">(</span><span class="nx">i</span><span class="p">.</span><span class="nx">price</span> <span class="o">*</span> <span class="nx">i</span><span class="p">.</span><span class="nx">quantity</span><span class="p">),</span>
            <span class="p">})),</span>
            <span class="na">subtotal</span><span class="p">:</span> <span class="nx">order</span><span class="p">.</span><span class="nx">subtotal</span> <span class="o">??</span> <span class="mi">0</span><span class="p">,</span>
            <span class="na">discount</span><span class="p">:</span> <span class="nx">order</span><span class="p">.</span><span class="nx">discount</span> <span class="o">??</span> <span class="mi">0</span><span class="p">,</span>
            <span class="na">tax</span><span class="p">:</span> <span class="nx">order</span><span class="p">.</span><span class="nx">tax</span> <span class="o">??</span> <span class="mi">0</span><span class="p">,</span>
            <span class="na">shipping</span><span class="p">:</span> <span class="nx">order</span><span class="p">.</span><span class="nx">shipping</span> <span class="o">??</span> <span class="mi">0</span><span class="p">,</span>
            <span class="na">total</span><span class="p">:</span> <span class="nx">order</span><span class="p">.</span><span class="nx">total</span> <span class="o">??</span> <span class="mi">0</span><span class="p">,</span>
            <span class="na">currency</span><span class="p">:</span> <span class="dl">'</span><span class="s1">USD</span><span class="dl">'</span><span class="p">,</span>
            <span class="na">formatted</span><span class="p">:</span> <span class="p">{</span>
                <span class="na">subtotal</span><span class="p">:</span> <span class="nx">formatMoney</span><span class="p">(</span><span class="nx">order</span><span class="p">.</span><span class="nx">subtotal</span><span class="p">),</span>
                <span class="na">discount</span><span class="p">:</span> <span class="nx">order</span><span class="p">.</span><span class="nx">discount</span> <span class="o">&amp;&amp;</span> <span class="nx">order</span><span class="p">.</span><span class="nx">discount</span> <span class="o">&gt;</span> <span class="mi">0</span> <span class="p">?</span> <span class="s2">`-</span><span class="p">${</span><span class="nx">formatMoney</span><span class="p">(</span><span class="nx">order</span><span class="p">.</span><span class="nx">discount</span><span class="p">)}</span><span class="s2">`</span> <span class="p">:</span> <span class="nx">formatMoney</span><span class="p">(</span><span class="mi">0</span><span class="p">),</span>
                <span class="na">tax</span><span class="p">:</span> <span class="nx">formatMoney</span><span class="p">(</span><span class="nx">order</span><span class="p">.</span><span class="nx">tax</span><span class="p">),</span>
                <span class="na">shipping</span><span class="p">:</span> <span class="nx">formatMoney</span><span class="p">(</span><span class="nx">order</span><span class="p">.</span><span class="nx">shipping</span><span class="p">),</span>
                <span class="na">total</span><span class="p">:</span> <span class="nx">formatMoney</span><span class="p">(</span><span class="nx">order</span><span class="p">.</span><span class="nx">total</span><span class="p">),</span>
            <span class="p">},</span>
            <span class="na">metadata</span><span class="p">:</span> <span class="p">{</span>
                <span class="na">customerEmail</span><span class="p">:</span> <span class="nx">order</span><span class="p">.</span><span class="nx">customerEmail</span><span class="p">,</span>
                <span class="na">createdAt</span><span class="p">:</span> <span class="k">new</span> <span class="nb">Date</span><span class="p">().</span><span class="nx">toISOString</span><span class="p">(),</span>
            <span class="p">}</span>
        <span class="p">}</span>

        <span class="c1">// Simulación de envío a impresora: bloque deliberadamente grande y sobrecargado</span>
        <span class="c1">// Configuración de impresora (ficticia)</span>
        <span class="kd">const</span> <span class="nx">printerConfig</span> <span class="o">=</span> <span class="p">{</span>
            <span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">Demo Thermal Printer TP-80</span><span class="dl">'</span><span class="p">,</span>
            <span class="na">model</span><span class="p">:</span> <span class="dl">'</span><span class="s1">TP-80</span><span class="dl">'</span><span class="p">,</span>
            <span class="na">dpi</span><span class="p">:</span> <span class="mi">203</span><span class="p">,</span>
            <span class="na">widthMm</span><span class="p">:</span> <span class="mi">80</span><span class="p">,</span>
            <span class="na">maxCharsPerLine</span><span class="p">:</span> <span class="mi">42</span><span class="p">,</span> <span class="c1">// típico en papel de 80mm con fuente estándar</span>
            <span class="na">interface</span><span class="p">:</span> <span class="dl">'</span><span class="s1">USB</span><span class="dl">'</span><span class="p">,</span>
            <span class="na">driver</span><span class="p">:</span> <span class="dl">'</span><span class="s1">ESC/POS</span><span class="dl">'</span><span class="p">,</span>
            <span class="na">location</span><span class="p">:</span> <span class="dl">'</span><span class="s1">Front Desk</span><span class="dl">'</span><span class="p">,</span>
        <span class="p">}</span>

        <span class="c1">// Capabilities detectadas (simuladas)</span>
        <span class="kd">const</span> <span class="nx">printerCaps</span> <span class="o">=</span> <span class="p">{</span>
            <span class="na">supportsBold</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
            <span class="na">supportsUnderline</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
            <span class="na">supportsQr</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
            <span class="na">supportsBarcode</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
            <span class="na">supportsImages</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
            <span class="na">codepage</span><span class="p">:</span> <span class="dl">'</span><span class="s1">cp437</span><span class="dl">'</span>
        <span class="p">}</span>

        <span class="c1">// Conexión (simulada)</span>
        <span class="kd">const</span> <span class="nx">printerConn</span> <span class="o">=</span> <span class="p">{</span><span class="na">connected</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span> <span class="na">retries</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span> <span class="na">maxRetries</span><span class="p">:</span> <span class="mi">2</span><span class="p">}</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`[PRN] Preparando conexión a impresora </span><span class="p">${</span><span class="nx">printerConfig</span><span class="p">.</span><span class="nx">name</span><span class="p">}</span><span class="s2"> (</span><span class="p">${</span><span class="nx">printerConfig</span><span class="p">.</span><span class="kr">interface</span><span class="p">}</span><span class="s2">/</span><span class="p">${</span><span class="nx">printerConfig</span><span class="p">.</span><span class="nx">driver</span><span class="p">}</span><span class="s2">)`</span><span class="p">)</span>

        <span class="c1">// Crear contenido del recibo</span>
        <span class="kd">const</span> <span class="nx">now</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">getCurrentDate</span><span class="p">()</span>
        <span class="kd">const</span> <span class="nx">lineWidth</span> <span class="o">=</span> <span class="nx">printerConfig</span><span class="p">.</span><span class="nx">maxCharsPerLine</span>

        <span class="kd">const</span> <span class="nx">padRight</span> <span class="o">=</span> <span class="p">(</span><span class="nx">text</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">len</span><span class="p">:</span> <span class="kr">number</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">text</span><span class="p">.</span><span class="nx">length</span> <span class="o">&gt;=</span> <span class="nx">len</span> <span class="p">?</span> <span class="nx">text</span><span class="p">.</span><span class="nx">slice</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="nx">len</span><span class="p">)</span> <span class="p">:</span> <span class="nx">text</span> <span class="o">+</span> <span class="dl">'</span><span class="s1"> </span><span class="dl">'</span><span class="p">.</span><span class="nx">repeat</span><span class="p">(</span><span class="nx">len</span> <span class="o">-</span> <span class="nx">text</span><span class="p">.</span><span class="nx">length</span><span class="p">)</span>
        <span class="kd">const</span> <span class="nx">padLeft</span> <span class="o">=</span> <span class="p">(</span><span class="nx">text</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">len</span><span class="p">:</span> <span class="kr">number</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">text</span><span class="p">.</span><span class="nx">length</span> <span class="o">&gt;=</span> <span class="nx">len</span> <span class="p">?</span> <span class="nx">text</span><span class="p">.</span><span class="nx">slice</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="nx">len</span><span class="p">)</span> <span class="p">:</span> <span class="dl">'</span><span class="s1"> </span><span class="dl">'</span><span class="p">.</span><span class="nx">repeat</span><span class="p">(</span><span class="nx">len</span> <span class="o">-</span> <span class="nx">text</span><span class="p">.</span><span class="nx">length</span><span class="p">)</span> <span class="o">+</span> <span class="nx">text</span>
        <span class="kd">const</span> <span class="nx">repeat</span> <span class="o">=</span> <span class="p">(</span><span class="nx">ch</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">n</span><span class="p">:</span> <span class="kr">number</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="k">new</span> <span class="nb">Array</span><span class="p">(</span><span class="nx">n</span> <span class="o">+</span> <span class="mi">1</span><span class="p">).</span><span class="nx">join</span><span class="p">(</span><span class="nx">ch</span><span class="p">)</span>

        <span class="kd">const</span> <span class="nx">formatLine</span> <span class="o">=</span> <span class="p">(</span><span class="nx">left</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">right</span><span class="p">:</span> <span class="kr">string</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
            <span class="kd">const</span> <span class="nx">leftTrim</span> <span class="o">=</span> <span class="nx">left</span> <span class="o">??</span> <span class="dl">''</span>
            <span class="kd">const</span> <span class="nx">rightTrim</span> <span class="o">=</span> <span class="nx">right</span> <span class="o">??</span> <span class="dl">''</span>
            <span class="kd">const</span> <span class="nx">space</span> <span class="o">=</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">max</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="nx">lineWidth</span> <span class="o">-</span> <span class="nx">leftTrim</span><span class="p">.</span><span class="nx">length</span> <span class="o">-</span> <span class="nx">rightTrim</span><span class="p">.</span><span class="nx">length</span><span class="p">)</span>
            <span class="kd">const</span> <span class="nx">spaces</span> <span class="o">=</span> <span class="nx">repeat</span><span class="p">(</span><span class="dl">'</span><span class="s1"> </span><span class="dl">'</span><span class="p">,</span> <span class="nx">space</span><span class="p">)</span>
            <span class="kd">const</span> <span class="nx">tooLong</span> <span class="o">=</span> <span class="nx">leftTrim</span><span class="p">.</span><span class="nx">length</span> <span class="o">+</span> <span class="nx">rightTrim</span><span class="p">.</span><span class="nx">length</span> <span class="o">&gt;</span> <span class="nx">lineWidth</span>
            <span class="k">if</span> <span class="p">(</span><span class="nx">tooLong</span><span class="p">)</span> <span class="p">{</span>
                <span class="c1">// Si no cabe, forzamos salto para la izquierda y mantenemos derecha alineada</span>
                <span class="k">return</span> <span class="nx">leftTrim</span> <span class="o">+</span> <span class="dl">'</span><span class="se">\n</span><span class="dl">'</span> <span class="o">+</span> <span class="nx">padLeft</span><span class="p">(</span><span class="nx">rightTrim</span><span class="p">,</span> <span class="nx">lineWidth</span><span class="p">)</span>
            <span class="p">}</span>
            <span class="k">return</span> <span class="nx">leftTrim</span> <span class="o">+</span> <span class="nx">spaces</span> <span class="o">+</span> <span class="nx">rightTrim</span>
        <span class="p">}</span>

        <span class="c1">// Cabecera</span>
        <span class="kd">const</span> <span class="nx">receiptLines</span><span class="p">:</span> <span class="kr">string</span><span class="p">[]</span> <span class="o">=</span> <span class="p">[]</span>
        <span class="nx">receiptLines</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">repeat</span><span class="p">(</span><span class="dl">'</span><span class="s1">=</span><span class="dl">'</span><span class="p">,</span> <span class="nx">lineWidth</span><span class="p">))</span>
        <span class="nx">receiptLines</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">padRight</span><span class="p">(</span><span class="dl">'</span><span class="s1">RESUMEN DEL PEDIDO</span><span class="dl">'</span><span class="p">,</span> <span class="nx">lineWidth</span><span class="p">))</span>
        <span class="nx">receiptLines</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">padRight</span><span class="p">(</span><span class="nx">now</span><span class="p">.</span><span class="nx">toLocaleString</span><span class="p">(),</span> <span class="nx">lineWidth</span><span class="p">))</span>
        <span class="nx">receiptLines</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">padRight</span><span class="p">(</span><span class="s2">`Cliente: </span><span class="p">${</span><span class="nx">order</span><span class="p">.</span><span class="nx">customerEmail</span><span class="p">}</span><span class="s2">`</span><span class="p">,</span> <span class="nx">lineWidth</span><span class="p">))</span>
        <span class="nx">receiptLines</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">repeat</span><span class="p">(</span><span class="dl">'</span><span class="s1">-</span><span class="dl">'</span><span class="p">,</span> <span class="nx">lineWidth</span><span class="p">))</span>

        <span class="c1">// Items</span>
        <span class="k">for</span> <span class="p">(</span><span class="kd">const</span> <span class="nx">it</span> <span class="k">of</span> <span class="nx">printJob</span><span class="p">.</span><span class="nx">items</span><span class="p">)</span> <span class="p">{</span>
            <span class="kd">const</span> <span class="nx">left</span> <span class="o">=</span> <span class="s2">`</span><span class="p">${</span><span class="nx">it</span><span class="p">.</span><span class="nx">quantity</span><span class="p">}</span><span class="s2"> x </span><span class="p">${</span><span class="nx">it</span><span class="p">.</span><span class="nx">name</span><span class="p">}</span><span class="s2">`</span>
            <span class="kd">const</span> <span class="nx">right</span> <span class="o">=</span> <span class="nx">it</span><span class="p">.</span><span class="nx">lineTotalFormatted</span>
            <span class="nx">receiptLines</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">formatLine</span><span class="p">(</span><span class="nx">left</span><span class="p">,</span> <span class="nx">right</span><span class="p">))</span>
        <span class="p">}</span>

        <span class="c1">// Totales</span>
        <span class="nx">receiptLines</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">repeat</span><span class="p">(</span><span class="dl">'</span><span class="s1">-</span><span class="dl">'</span><span class="p">,</span> <span class="nx">lineWidth</span><span class="p">))</span>
        <span class="nx">receiptLines</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">formatLine</span><span class="p">(</span><span class="dl">'</span><span class="s1">Subtotal</span><span class="dl">'</span><span class="p">,</span> <span class="nx">printJob</span><span class="p">.</span><span class="nx">formatted</span><span class="p">.</span><span class="nx">subtotal</span><span class="p">))</span>
        <span class="k">if</span> <span class="p">((</span><span class="nx">printJob</span><span class="p">.</span><span class="nx">discount</span> <span class="o">??</span> <span class="mi">0</span><span class="p">)</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
            <span class="nx">receiptLines</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">formatLine</span><span class="p">(</span><span class="dl">'</span><span class="s1">Descuento</span><span class="dl">'</span><span class="p">,</span> <span class="s2">`-</span><span class="p">${</span><span class="nx">formatMoney</span><span class="p">(</span><span class="nx">printJob</span><span class="p">.</span><span class="nx">discount</span><span class="p">)}</span><span class="s2">`</span><span class="p">))</span>
        <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
            <span class="nx">receiptLines</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">formatLine</span><span class="p">(</span><span class="dl">'</span><span class="s1">Descuento</span><span class="dl">'</span><span class="p">,</span> <span class="nx">printJob</span><span class="p">.</span><span class="nx">formatted</span><span class="p">.</span><span class="nx">discount</span><span class="p">))</span>
        <span class="p">}</span>
        <span class="nx">receiptLines</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">formatLine</span><span class="p">(</span><span class="dl">'</span><span class="s1">Impuestos</span><span class="dl">'</span><span class="p">,</span> <span class="nx">printJob</span><span class="p">.</span><span class="nx">formatted</span><span class="p">.</span><span class="nx">tax</span><span class="p">))</span>
        <span class="nx">receiptLines</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">formatLine</span><span class="p">(</span><span class="dl">'</span><span class="s1">Envío</span><span class="dl">'</span><span class="p">,</span> <span class="nx">printJob</span><span class="p">.</span><span class="nx">formatted</span><span class="p">.</span><span class="nx">shipping</span><span class="p">))</span>
        <span class="nx">receiptLines</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">formatLine</span><span class="p">(</span><span class="dl">'</span><span class="s1">TOTAL</span><span class="dl">'</span><span class="p">,</span> <span class="nx">printJob</span><span class="p">.</span><span class="nx">formatted</span><span class="p">.</span><span class="nx">total</span><span class="p">))</span>
        <span class="nx">receiptLines</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">repeat</span><span class="p">(</span><span class="dl">'</span><span class="s1">=</span><span class="dl">'</span><span class="p">,</span> <span class="nx">lineWidth</span><span class="p">))</span>

        <span class="c1">// Pie con metadatos</span>
        <span class="nx">receiptLines</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">padRight</span><span class="p">(</span><span class="s2">`Nº pedido: </span><span class="p">${</span><span class="nb">Math</span><span class="p">.</span><span class="nx">abs</span><span class="p">((</span><span class="nx">order</span><span class="p">.</span><span class="nx">total</span> <span class="o">??</span> <span class="mi">0</span><span class="p">)</span> <span class="o">*</span> <span class="mi">1000</span><span class="p">)</span> <span class="o">|</span> <span class="mi">0</span><span class="p">}</span><span class="s2">`</span><span class="p">,</span> <span class="nx">lineWidth</span><span class="p">))</span>
        <span class="nx">receiptLines</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">padRight</span><span class="p">(</span><span class="s2">`Moneda: </span><span class="p">${</span><span class="nx">printJob</span><span class="p">.</span><span class="nx">currency</span><span class="p">}</span><span class="s2">`</span><span class="p">,</span> <span class="nx">lineWidth</span><span class="p">))</span>
        <span class="nx">receiptLines</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">padRight</span><span class="p">(</span><span class="s2">`Creado: </span><span class="p">${</span><span class="nx">printJob</span><span class="p">.</span><span class="nx">metadata</span><span class="p">.</span><span class="nx">createdAt</span><span class="p">}</span><span class="s2">`</span><span class="p">,</span> <span class="nx">lineWidth</span><span class="p">))</span>

        <span class="c1">// Comandos ESC/POS simulados (no operativos, solo logging)</span>
        <span class="kd">const</span> <span class="nx">escposCommands</span> <span class="o">=</span> <span class="p">[</span>
            <span class="dl">'</span><span class="s1">[INIT]</span><span class="dl">'</span><span class="p">,</span>
            <span class="dl">'</span><span class="s1">[ALIGN LEFT]</span><span class="dl">'</span><span class="p">,</span>
            <span class="dl">'</span><span class="s1">[FONT A]</span><span class="dl">'</span><span class="p">,</span>
            <span class="nx">printerCaps</span><span class="p">.</span><span class="nx">supportsBold</span> <span class="p">?</span> <span class="dl">'</span><span class="s1">[BOLD ON]</span><span class="dl">'</span> <span class="p">:</span> <span class="dl">'</span><span class="s1">[BOLD N/A]</span><span class="dl">'</span><span class="p">,</span>
            <span class="dl">'</span><span class="s1">[PRINT LINES]</span><span class="dl">'</span><span class="p">,</span>
            <span class="dl">'</span><span class="s1">[BOLD OFF]</span><span class="dl">'</span><span class="p">,</span>
            <span class="dl">'</span><span class="s1">[CUT PARTIAL]</span><span class="dl">'</span>
        <span class="p">]</span>

        <span class="c1">// Montar payload a imprimir</span>
        <span class="kd">const</span> <span class="nx">textPayload</span> <span class="o">=</span> <span class="nx">receiptLines</span><span class="p">.</span><span class="nx">join</span><span class="p">(</span><span class="dl">'</span><span class="se">\n</span><span class="dl">'</span><span class="p">)</span> <span class="o">+</span> <span class="dl">'</span><span class="se">\n</span><span class="dl">'</span> <span class="o">+</span> <span class="nx">repeat</span><span class="p">(</span><span class="dl">'</span><span class="s1">-</span><span class="dl">'</span><span class="p">,</span> <span class="nx">lineWidth</span><span class="p">)</span> <span class="o">+</span> <span class="dl">'</span><span class="se">\n</span><span class="dl">'</span>
        <span class="kd">const</span> <span class="nx">commandSection</span> <span class="o">=</span> <span class="nx">escposCommands</span><span class="p">.</span><span class="nx">join</span><span class="p">(</span><span class="dl">'</span><span class="s1"> </span><span class="dl">'</span><span class="p">)</span>
        <span class="kd">const</span> <span class="nx">printable</span> <span class="o">=</span> <span class="s2">`\n</span><span class="p">${</span><span class="nx">commandSection</span><span class="p">}</span><span class="s2">\n</span><span class="p">${</span><span class="nx">textPayload</span><span class="p">}</span><span class="s2">`</span>
        <span class="kd">const</span> <span class="nx">spoolBuffer</span> <span class="o">=</span> <span class="nx">Buffer</span><span class="p">.</span><span class="k">from</span><span class="p">(</span><span class="nx">printable</span><span class="p">,</span> <span class="dl">'</span><span class="s1">utf8</span><span class="dl">'</span><span class="p">)</span>
        <span class="kd">const</span> <span class="nx">spoolBytes</span> <span class="o">=</span> <span class="nx">Buffer</span><span class="p">.</span><span class="nx">byteLength</span><span class="p">(</span><span class="nx">printable</span><span class="p">,</span> <span class="dl">'</span><span class="s1">utf8</span><span class="dl">'</span><span class="p">)</span>

        <span class="c1">// Simulación de QR/barcode en el ticket (solo registro)</span>
        <span class="kd">const</span> <span class="nx">qrData</span> <span class="o">=</span> <span class="s2">`ORDER|</span><span class="p">${</span><span class="nx">order</span><span class="p">.</span><span class="nx">customerEmail</span><span class="p">}</span><span class="s2">|</span><span class="p">${</span><span class="nx">printJob</span><span class="p">.</span><span class="nx">total</span><span class="p">}</span><span class="s2">|</span><span class="p">${</span><span class="nx">now</span><span class="p">.</span><span class="nx">getTime</span><span class="p">()}</span><span class="s2">`</span>
        <span class="k">if</span> <span class="p">(</span><span class="nx">printerCaps</span><span class="p">.</span><span class="nx">supportsQr</span><span class="p">)</span> <span class="p">{</span>
            <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`[PRN] Agregando QR con datos: </span><span class="p">${</span><span class="nx">qrData</span><span class="p">}</span><span class="s2">`</span><span class="p">)</span>
        <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="nx">printerCaps</span><span class="p">.</span><span class="nx">supportsBarcode</span><span class="p">)</span> <span class="p">{</span>
            <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`[PRN] Agregando BARCODE con datos: </span><span class="p">${</span><span class="nx">qrData</span><span class="p">.</span><span class="nx">slice</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">12</span><span class="p">)}</span><span class="s2">`</span><span class="p">)</span>
        <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
            <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">[PRN] Sin soporte para QR/BARCODE</span><span class="dl">'</span><span class="p">)</span>
        <span class="p">}</span>

        <span class="c1">// Vista previa ASCII (limitada para no saturar logs)</span>
        <span class="kd">const</span> <span class="nx">preview</span> <span class="o">=</span> <span class="nx">textPayload</span><span class="p">.</span><span class="nx">split</span><span class="p">(</span><span class="dl">'</span><span class="se">\n</span><span class="dl">'</span><span class="p">).</span><span class="nx">slice</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">12</span><span class="p">).</span><span class="nx">join</span><span class="p">(</span><span class="dl">'</span><span class="se">\n</span><span class="dl">'</span><span class="p">)</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">[PRN] Vista previa del recibo:</span><span class="se">\n</span><span class="dl">'</span> <span class="o">+</span> <span class="nx">preview</span> <span class="o">+</span> <span class="p">(</span><span class="nx">receiptLines</span><span class="p">.</span><span class="nx">length</span> <span class="o">&gt;</span> <span class="mi">12</span> <span class="p">?</span> <span class="s2">`\n...(</span><span class="p">${</span><span class="nx">receiptLines</span><span class="p">.</span><span class="nx">length</span> <span class="o">-</span> <span class="mi">12</span><span class="p">}</span><span class="s2"> líneas más)`</span> <span class="p">:</span> <span class="dl">''</span><span class="p">))</span>

        <span class="c1">// Encolado de trabajo de impresión</span>
        <span class="kd">const</span> <span class="nx">printPriority</span> <span class="o">=</span> <span class="nx">order</span><span class="p">.</span><span class="nx">customerType</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">VIP</span><span class="dl">'</span> <span class="p">?</span> <span class="dl">'</span><span class="s1">HIGH</span><span class="dl">'</span> <span class="p">:</span> <span class="dl">'</span><span class="s1">NORMAL</span><span class="dl">'</span>
        <span class="kd">const</span> <span class="nx">printJobId</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">generatePrintJobId</span><span class="p">();</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`[PRN] Encolando trabajo </span><span class="p">${</span><span class="nx">printJobId</span><span class="p">}</span><span class="s2"> (</span><span class="p">${</span><span class="nx">spoolBytes</span><span class="p">}</span><span class="s2"> bytes, prioridad=</span><span class="p">${</span><span class="nx">printPriority</span><span class="p">}</span><span class="s2">) en </span><span class="p">${</span><span class="nx">printerConfig</span><span class="p">.</span><span class="nx">location</span><span class="p">}</span><span class="s2">`</span><span class="p">)</span>

        <span class="c1">// Envío en trozos (chunking) para simular buffer limitado de la impresora</span>
        <span class="kd">const</span> <span class="nx">chunkSize</span> <span class="o">=</span> <span class="mi">256</span> <span class="c1">// bytes</span>
        <span class="kd">let</span> <span class="nx">sentBytes</span> <span class="o">=</span> <span class="mi">0</span>
        <span class="kd">let</span> <span class="nx">chunkIndex</span> <span class="o">=</span> <span class="mi">0</span>
        <span class="kd">let</span> <span class="nx">sentOk</span> <span class="o">=</span> <span class="kc">true</span>
        <span class="k">while</span> <span class="p">(</span><span class="nx">sentBytes</span> <span class="o">&lt;</span> <span class="nx">spoolBytes</span><span class="p">)</span> <span class="p">{</span>
            <span class="kd">const</span> <span class="nx">remaining</span> <span class="o">=</span> <span class="nx">spoolBytes</span> <span class="o">-</span> <span class="nx">sentBytes</span>
            <span class="kd">const</span> <span class="nx">size</span> <span class="o">=</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">min</span><span class="p">(</span><span class="nx">chunkSize</span><span class="p">,</span> <span class="nx">remaining</span><span class="p">)</span>
            <span class="kd">const</span> <span class="nx">chunk</span> <span class="o">=</span> <span class="nx">spoolBuffer</span><span class="p">.</span><span class="nx">subarray</span><span class="p">(</span><span class="nx">sentBytes</span><span class="p">,</span> <span class="nx">sentBytes</span> <span class="o">+</span> <span class="nx">size</span><span class="p">)</span>
            <span class="c1">// Simular reintentos por chunk</span>
            <span class="kd">let</span> <span class="nx">attempts</span> <span class="o">=</span> <span class="mi">0</span>
            <span class="kd">let</span> <span class="nx">delivered</span> <span class="o">=</span> <span class="kc">false</span>
            <span class="k">while</span> <span class="p">(</span><span class="o">!</span><span class="nx">delivered</span> <span class="o">&amp;&amp;</span> <span class="nx">attempts</span> <span class="o">&lt;</span> <span class="mi">2</span><span class="p">)</span> <span class="p">{</span>
                <span class="nx">attempts</span><span class="o">++</span>
                <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`[PRN] Enviando chunk #</span><span class="p">${</span><span class="nx">chunkIndex</span><span class="p">}</span><span class="s2"> (</span><span class="p">${</span><span class="nx">size</span><span class="p">}</span><span class="s2"> bytes) intento </span><span class="p">${</span><span class="nx">attempts</span><span class="p">}</span><span class="s2">/2`</span><span class="p">)</span>
                <span class="c1">// Éxito simulado</span>
                <span class="nx">delivered</span> <span class="o">=</span> <span class="kc">true</span>
            <span class="p">}</span>
            <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">delivered</span><span class="p">)</span> <span class="p">{</span>
                <span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">(</span><span class="s2">`[PRN] Fallo al enviar chunk #</span><span class="p">${</span><span class="nx">chunkIndex</span><span class="p">}</span><span class="s2">`</span><span class="p">)</span>
                <span class="nx">sentOk</span> <span class="o">=</span> <span class="kc">false</span>
                <span class="k">break</span>
            <span class="p">}</span>
            <span class="nx">sentBytes</span> <span class="o">+=</span> <span class="nx">size</span>
            <span class="nx">chunkIndex</span><span class="o">++</span>
        <span class="p">}</span>

        <span class="c1">// Resultado final de impresión</span>
        <span class="k">if</span> <span class="p">(</span><span class="nx">printerConn</span><span class="p">.</span><span class="nx">connected</span> <span class="o">&amp;&amp;</span> <span class="nx">sentOk</span><span class="p">)</span> <span class="p">{</span>
            <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`[PRN] Trabajo </span><span class="p">${</span><span class="nx">printJobId</span><span class="p">}</span><span class="s2"> impreso correctamente. Total enviado: </span><span class="p">${</span><span class="nx">sentBytes</span><span class="p">}</span><span class="s2"> bytes`</span><span class="p">)</span>
        <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
            <span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">(</span><span class="s2">`[PRN] Error al imprimir trabajo </span><span class="p">${</span><span class="nx">printJobId</span><span class="p">}</span><span class="s2">. Enviado: </span><span class="p">${</span><span class="nx">sentBytes</span><span class="p">}</span><span class="s2">/</span><span class="p">${</span><span class="nx">spoolBytes</span><span class="p">}</span><span class="s2"> bytes`</span><span class="p">)</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="k">protected</span> <span class="nx">generatePrintJobId</span><span class="p">()</span> <span class="p">{</span>
        <span class="k">return</span> <span class="s2">`prn-</span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">clock</span><span class="p">.</span><span class="nx">getCurrentDate</span><span class="p">().</span><span class="nx">getTime</span><span class="p">()}</span><span class="s2">-</span><span class="p">${</span><span class="nb">Math</span><span class="p">.</span><span class="nx">floor</span><span class="p">(</span><span class="nb">Math</span><span class="p">.</span><span class="nx">random</span><span class="p">()</span> <span class="o">*</span> <span class="mi">1000</span><span class="p">)}</span><span class="s2">`</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">protected</span> <span class="nx">getCurrentDate</span><span class="p">():</span> <span class="nb">Date</span> <span class="p">{</span>
        <span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">clock</span><span class="p">.</span><span class="nx">getCurrentDate</span><span class="p">();</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>En principio, al introducir <code class="language-plaintext highlighter-rouge">OrderPrinter</code> se rompen los tests. Esto es debido al método <code class="language-plaintext highlighter-rouge">generatePrintJobId()</code>, el cual teníamos sobreescrito en el seam de <code class="language-plaintext highlighter-rouge">OrderService</code>, pero que ahora mantiene un comportamiento indeterminista.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">protected</span> <span class="nx">generatePrintJobId</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">return</span> <span class="s2">`prn-</span><span class="p">${</span><span class="nb">Date</span><span class="p">.</span><span class="nx">now</span><span class="p">()}</span><span class="s2">-</span><span class="p">${</span><span class="nb">Math</span><span class="p">.</span><span class="nx">floor</span><span class="p">(</span><span class="nb">Math</span><span class="p">.</span><span class="nx">random</span><span class="p">()</span> <span class="o">*</span> <span class="mi">1000</span><span class="p">)}</span><span class="s2">`</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Podemos solucionarlo con la introducción de una nueva interfaz <code class="language-plaintext highlighter-rouge">PrintJobIdGenerator</code> y sus correspondientes implementaciones para producción y para test:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">interface</span> <span class="nx">PrintJobIdGenerator</span> <span class="p">{</span>
    <span class="nx">generate</span><span class="p">():</span> <span class="kr">string</span>
<span class="p">}</span>

<span class="k">export</span> <span class="kd">class</span> <span class="nx">SystemPrintJobIdGenerator</span> <span class="k">implements</span> <span class="nx">PrintJobIdGenerator</span> <span class="p">{</span>
    <span class="nx">generate</span><span class="p">():</span> <span class="kr">string</span> <span class="p">{</span>
        <span class="k">return</span> <span class="s2">`prn-</span><span class="p">${</span><span class="nb">Date</span><span class="p">.</span><span class="nx">now</span><span class="p">()}</span><span class="s2">-</span><span class="p">${</span><span class="nb">Math</span><span class="p">.</span><span class="nx">floor</span><span class="p">(</span><span class="nb">Math</span><span class="p">.</span><span class="nx">random</span><span class="p">()</span> <span class="o">*</span> <span class="mi">1000</span><span class="p">)}</span><span class="s2">`</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="k">export</span> <span class="kd">class</span> <span class="nx">PrintJobIdGeneratorStub</span> <span class="k">implements</span> <span class="nx">PrintJobIdGenerator</span> <span class="p">{</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="nx">jobId</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>

    <span class="kd">constructor</span><span class="p">(</span><span class="nx">jobId</span><span class="p">:</span> <span class="kr">string</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">jobId</span> <span class="o">=</span> <span class="nx">jobId</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="nx">generate</span><span class="p">():</span> <span class="kr">string</span> <span class="p">{</span>
        <span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">jobId</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">}</span>

</code></pre></div></div>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">class</span> <span class="nx">OrderPrinter</span> <span class="p">{</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="nx">clock</span><span class="p">:</span> <span class="nx">Clock</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="nx">printJobIdGenerator</span><span class="p">:</span> <span class="nx">PrintJobIdGenerator</span><span class="p">;</span>

    <span class="kd">constructor</span><span class="p">(</span><span class="nx">clock</span><span class="p">:</span> <span class="nx">Clock</span><span class="p">,</span> <span class="nx">printJobIdGenerator</span><span class="p">:</span> <span class="nx">PrintJobIdGenerator</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">clock</span> <span class="o">=</span> <span class="nx">clock</span><span class="p">;</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">printJobIdGenerator</span> <span class="o">=</span> <span class="nx">printJobIdGenerator</span>
    <span class="p">}</span>

    <span class="k">public</span> <span class="nx">print</span><span class="p">(</span><span class="nx">order</span><span class="p">:</span> <span class="nx">Order</span><span class="p">)</span> <span class="p">{...}</span>

    <span class="k">protected</span> <span class="nx">generatePrintJobId</span><span class="p">()</span> <span class="p">{</span>
        <span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">printJobIdGenerator</span><span class="p">.</span><span class="nx">generate</span><span class="p">();</span>
    <span class="p">}</span>

    <span class="k">protected</span> <span class="nx">getCurrentDate</span><span class="p">():</span> <span class="nb">Date</span> <span class="p">{</span>
        <span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">clock</span><span class="p">.</span><span class="nx">getCurrentDate</span><span class="p">();</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="descubriendo-colaboradores">Descubriendo colaboradores</h3>

<p>Ahora que hemos movido el código de impresión a <code class="language-plaintext highlighter-rouge">OrderPrinter</code>, vemos que ya no necesitamos tener un Clock en <code class="language-plaintext highlighter-rouge">OrderService</code>, lo que nos permite eliminar esa dependencia.</p>

<p>Tampoco necesitaremos tener un método <code class="language-plaintext highlighter-rouge">generatePrintJobId</code> en <code class="language-plaintext highlighter-rouge">OrderService</code> y podemos eliminar la sobreescritura de <code class="language-plaintext highlighter-rouge">TestableOrderService</code>.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nx">TestableOrderService</span> <span class="kd">extends</span> <span class="nx">OrderService</span> <span class="p">{</span>
    <span class="k">protected</span> <span class="nx">generateDbRecordId</span><span class="p">():</span> <span class="kr">number</span> <span class="p">{</span>
        <span class="k">return</span> <span class="mi">67234</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Esto nos revela otro colaborador que falta, que podría ser un <code class="language-plaintext highlighter-rouge">OrderIdProvider</code>.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">interface</span> <span class="nx">OrderIdProvider</span> <span class="p">{</span>
  <span class="nx">generateId</span><span class="p">():</span> <span class="kr">number</span>
<span class="p">}</span>

<span class="k">export</span> <span class="kd">class</span> <span class="nx">RandomOrderIdProvider</span> <span class="k">implements</span> <span class="nx">OrderIdProvider</span> <span class="p">{</span>
  <span class="nx">generateId</span><span class="p">():</span> <span class="kr">number</span> <span class="p">{</span>
    <span class="k">return</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">floor</span><span class="p">(</span><span class="nb">Math</span><span class="p">.</span><span class="nx">random</span><span class="p">()</span> <span class="o">*</span> <span class="mi">1000000</span><span class="p">);</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="k">export</span> <span class="kd">class</span> <span class="nx">OrderIdProviderStub</span> <span class="k">implements</span> <span class="nx">OrderIdProvider</span> <span class="p">{</span>
  <span class="k">private</span> <span class="k">readonly</span> <span class="nx">id</span><span class="p">:</span> <span class="kr">number</span><span class="p">;</span>
  
  <span class="kd">constructor</span><span class="p">(</span><span class="nx">id</span><span class="p">:</span> <span class="kr">number</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">id</span> <span class="o">=</span> <span class="nx">id</span><span class="p">;</span>
  <span class="p">}</span>

  <span class="nx">generateId</span><span class="p">():</span> <span class="kr">number</span> <span class="p">{</span>
    <span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">id</span><span class="p">;</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Ahora OrderService se construye así, sin necesidad de tener una versión <em>Testable</em>.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">clock</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">ClockStub</span><span class="p">(</span><span class="k">new</span> <span class="nb">Date</span><span class="p">(</span><span class="dl">'</span><span class="s1">2023-05-21T13:35</span><span class="dl">'</span><span class="p">))</span>
<span class="kd">const</span> <span class="nx">db</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">OrderDatabase</span><span class="p">(</span><span class="nx">clock</span><span class="p">)</span>
<span class="kd">const</span> <span class="nx">emailSender</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">EmailSender</span><span class="p">(</span><span class="nx">clock</span><span class="p">)</span>
<span class="kd">const</span> <span class="nx">jobIdGenerator</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">PrintJobIdGeneratorStub</span><span class="p">(</span><span class="dl">'</span><span class="s1">prn-1762191762553-125</span><span class="dl">'</span><span class="p">)</span>
<span class="kd">const</span> <span class="nx">printer</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">OrderPrinter</span><span class="p">(</span><span class="nx">clock</span><span class="p">,</span> <span class="nx">jobIdGenerator</span><span class="p">)</span>
<span class="kd">const</span> <span class="nx">idProvider</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">OrderIdProviderStub</span><span class="p">(</span><span class="mi">67234</span><span class="p">)</span>
<span class="kd">const</span> <span class="nx">orderService</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">OrderService</span><span class="p">(</span><span class="nx">db</span><span class="p">,</span> <span class="nx">emailSender</span><span class="p">,</span> <span class="nx">printer</span><span class="p">,</span> <span class="nx">idProvider</span><span class="p">);</span>
<span class="nx">orderService</span><span class="p">.</span><span class="nx">process</span><span class="p">(</span><span class="nx">order</span><span class="p">)</span>
</code></pre></div></div>

<p>Esto nos pone, finalmente en una situación bastante mejor que la que teníamos al principio. <code class="language-plaintext highlighter-rouge">OrderService</code> delega su trabajo en otras clases. Pero podemos ir un poco más lejos antes de empezar a aplicar las nuevas prestaciones.</p>

<p>El artículo está siendo largo, haciendo honor a su temática. Pero refleja una situación que nos podemos encontrar frecuentemente en el desarrollo de software.</p>

<h3 id="resolviendo-data-class">Resolviendo <em>Data Class</em></h3>

<p>En el código actual de <code class="language-plaintext highlighter-rouge">OrderService</code> es fácil ver que buena parte del comportamiento del servicio pertenece a <code class="language-plaintext highlighter-rouge">Order</code>:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="k">this</span><span class="p">.</span><span class="nx">validateOrder</span><span class="p">(</span><span class="nx">order</span><span class="p">))</span> <span class="p">{</span>
    <span class="k">return</span>
<span class="p">}</span>

<span class="nx">order</span><span class="p">.</span><span class="nx">id</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">generateDbRecordId</span><span class="p">()</span>

<span class="k">this</span><span class="p">.</span><span class="nx">calculateOrderTotal</span><span class="p">(</span><span class="nx">order</span><span class="p">);</span>
</code></pre></div></div>

<p>Los métodos simplemente acceden de forma directa a las propiedades de <code class="language-plaintext highlighter-rouge">Order</code>, para hacer cálculos con ellas e incluso asignarles valores. Este patrón es un buen ejemplo de <em>Data Class</em>. Básicamente, nos está reclamando que movamos comportamientos a <code class="language-plaintext highlighter-rouge">Order</code>.</p>

<p>Se trata de algo relativamente fácil de hacer. Básicamente, es copiar el método de <code class="language-plaintext highlighter-rouge">OrderService</code> en <code class="language-plaintext highlighter-rouge">Order</code> y cambiar lo necesario. Lo único que, en nuestro caso, tendremos que convertir la interfaz en clase o, en todo caso, implementar la interfaz <code class="language-plaintext highlighter-rouge">Order</code> en una clase. Esta última opción podría ofrecernos algunas posibilidades muy interesantes como poder tener clases representando diferentes estados de <code class="language-plaintext highlighter-rouge">Order</code>:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">PendingOrder</code> es una <code class="language-plaintext highlighter-rouge">Order</code> que llega al servicio <code class="language-plaintext highlighter-rouge">OrderService</code> de la cual no tenemos garantía que esté preparada para ser procesada.</li>
  <li><code class="language-plaintext highlighter-rouge">ValidatedOrder</code> es una <code class="language-plaintext highlighter-rouge">Order</code> con los datos validados, pero sin los cálculos realizados a la que se le ha asignado un identificador.</li>
  <li><code class="language-plaintext highlighter-rouge">ProcessedOrder</code> es una <code class="language-plaintext highlighter-rouge">Order</code> con los datos procesados, incluyendo todos los cálculos y totales.</li>
</ul>

<p>Vamos a intentarlo por ahí:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kr">interface</span> <span class="nx">Order</span> <span class="p">{</span>
    <span class="nl">id</span><span class="p">:</span> <span class="kr">number</span><span class="p">;</span>
    <span class="nl">customerEmail</span><span class="p">:</span> <span class="kr">string</span>
    <span class="nx">customerType</span><span class="p">:</span> <span class="dl">'</span><span class="s1">NORMAL</span><span class="dl">'</span> <span class="o">|</span> <span class="dl">'</span><span class="s1">VIP</span><span class="dl">'</span>
    <span class="nx">items</span><span class="p">:</span> <span class="p">{</span> <span class="nl">name</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span> <span class="nl">price</span><span class="p">:</span> <span class="kr">number</span><span class="p">;</span> <span class="nl">quantity</span><span class="p">:</span> <span class="kr">number</span> <span class="p">}[]</span>
    <span class="nx">subtotal</span><span class="p">?:</span> <span class="kr">number</span>
    <span class="nx">discount</span><span class="p">?:</span> <span class="kr">number</span>
    <span class="nx">tax</span><span class="p">?:</span> <span class="kr">number</span>
    <span class="nx">shipping</span><span class="p">?:</span> <span class="kr">number</span>
    <span class="nx">total</span><span class="p">?:</span> <span class="kr">number</span>

    <span class="nx">validate</span><span class="p">(</span><span class="nx">idProvider</span><span class="p">:</span> <span class="nx">OrderIdProvider</span><span class="p">):</span> <span class="nx">ValidatedOrder</span>

    <span class="nx">process</span><span class="p">():</span> <span class="nx">ProcessedOrder</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">PendingOrder</code> solo puede intentar validarse, pero no procesarse. Al validarse devuelve una intancia de <code class="language-plaintext highlighter-rouge">ValidatedOrder</code> que tiene un identificador asignado.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">class</span> <span class="nx">PendingOrder</span> <span class="k">implements</span> <span class="nx">Order</span> <span class="p">{</span>
    <span class="nl">id</span><span class="p">:</span> <span class="kr">number</span>
    <span class="nx">customerEmail</span><span class="p">:</span> <span class="kr">string</span>
    <span class="nx">customerType</span><span class="p">:</span> <span class="dl">'</span><span class="s1">NORMAL</span><span class="dl">'</span> <span class="o">|</span> <span class="dl">'</span><span class="s1">VIP</span><span class="dl">'</span>
    <span class="nx">items</span><span class="p">:</span> <span class="p">{</span> <span class="nl">name</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span> <span class="nl">price</span><span class="p">:</span> <span class="kr">number</span><span class="p">;</span> <span class="nl">quantity</span><span class="p">:</span> <span class="kr">number</span> <span class="p">}[]</span>

    <span class="kd">constructor</span><span class="p">(</span>
        <span class="nx">customerEmail</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
        <span class="nx">customerType</span><span class="p">:</span> <span class="dl">'</span><span class="s1">NORMAL</span><span class="dl">'</span> <span class="o">|</span> <span class="dl">'</span><span class="s1">VIP</span><span class="dl">'</span><span class="p">,</span>
        <span class="nx">items</span><span class="p">:</span> <span class="p">{</span> <span class="nl">name</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span> <span class="nl">price</span><span class="p">:</span> <span class="kr">number</span><span class="p">;</span> <span class="nl">quantity</span><span class="p">:</span> <span class="kr">number</span> <span class="p">}[],</span>
    <span class="p">)</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">id</span> <span class="o">=</span> <span class="mi">0</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">customerEmail</span> <span class="o">=</span> <span class="nx">customerEmail</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">customerType</span> <span class="o">=</span> <span class="nx">customerType</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">items</span> <span class="o">=</span> <span class="nx">items</span>
    <span class="p">}</span>

    <span class="nx">validate</span><span class="p">(</span><span class="nx">idProvider</span><span class="p">:</span> <span class="nx">OrderIdProvider</span><span class="p">):</span> <span class="nx">ValidatedOrder</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="k">this</span><span class="p">.</span><span class="nx">items</span> <span class="o">||</span> <span class="k">this</span><span class="p">.</span><span class="nx">items</span><span class="p">.</span><span class="nx">length</span> <span class="o">===</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
            <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">El pedido no tiene productos</span><span class="dl">'</span><span class="p">)</span>
            <span class="k">throw</span> <span class="k">new</span> <span class="nb">Error</span><span class="p">(</span><span class="dl">'</span><span class="s1">El pedido no tiene productos</span><span class="dl">'</span><span class="p">)</span>
        <span class="p">}</span>

        <span class="k">for</span> <span class="p">(</span><span class="kd">const</span> <span class="nx">item</span> <span class="k">of</span> <span class="k">this</span><span class="p">.</span><span class="nx">items</span><span class="p">)</span> <span class="p">{</span>
            <span class="k">if</span> <span class="p">(</span><span class="nx">item</span><span class="p">.</span><span class="nx">price</span> <span class="o">&lt;</span> <span class="mi">0</span> <span class="o">||</span> <span class="nx">item</span><span class="p">.</span><span class="nx">quantity</span> <span class="o">&lt;=</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
                <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Producto inválido en el pedido</span><span class="dl">'</span><span class="p">)</span>
                <span class="k">throw</span> <span class="k">new</span> <span class="nb">Error</span><span class="p">(</span><span class="dl">'</span><span class="s1">Producto inválido en el pedido</span><span class="dl">'</span><span class="p">)</span>
            <span class="p">}</span>
        <span class="p">}</span>
        <span class="kd">const</span> <span class="nx">id</span> <span class="o">=</span> <span class="nx">idProvider</span><span class="p">.</span><span class="nx">generateId</span><span class="p">()</span>

        <span class="k">return</span> <span class="k">new</span> <span class="nx">ValidatedOrder</span><span class="p">(</span><span class="nx">id</span><span class="p">,</span> <span class="k">this</span><span class="p">.</span><span class="nx">customerEmail</span><span class="p">,</span> <span class="k">this</span><span class="p">.</span><span class="nx">customerType</span><span class="p">,</span> <span class="k">this</span><span class="p">.</span><span class="nx">items</span><span class="p">)</span>
    <span class="p">}</span>

    <span class="nx">process</span><span class="p">():</span> <span class="nx">ProcessedOrder</span> <span class="p">{</span>
        <span class="k">throw</span> <span class="k">new</span> <span class="nb">Error</span><span class="p">(</span><span class="dl">'</span><span class="s1">No se puede procesar un pedido pendiente</span><span class="dl">'</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">ValidatedOrder</code> solo puede procesarse. Si se intenta validar devuelve su propia isntancia.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">class</span> <span class="nx">ValidatedOrder</span> <span class="k">implements</span> <span class="nx">Order</span> <span class="p">{</span>
    <span class="nl">id</span><span class="p">:</span> <span class="kr">number</span>
    <span class="nx">customerEmail</span><span class="p">:</span> <span class="kr">string</span>
    <span class="nx">customerType</span><span class="p">:</span> <span class="dl">'</span><span class="s1">NORMAL</span><span class="dl">'</span> <span class="o">|</span> <span class="dl">'</span><span class="s1">VIP</span><span class="dl">'</span>
    <span class="nx">items</span><span class="p">:</span> <span class="p">{</span> <span class="nl">name</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span> <span class="nl">price</span><span class="p">:</span> <span class="kr">number</span><span class="p">;</span> <span class="nl">quantity</span><span class="p">:</span> <span class="kr">number</span> <span class="p">}[]</span>

    <span class="kd">constructor</span><span class="p">(</span>
        <span class="nx">id</span><span class="p">:</span> <span class="kr">number</span><span class="p">,</span>
        <span class="nx">customerEmail</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
        <span class="nx">customerType</span><span class="p">:</span> <span class="dl">'</span><span class="s1">NORMAL</span><span class="dl">'</span> <span class="o">|</span> <span class="dl">'</span><span class="s1">VIP</span><span class="dl">'</span><span class="p">,</span>
        <span class="nx">items</span><span class="p">:</span> <span class="p">{</span> <span class="nl">name</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span> <span class="nl">price</span><span class="p">:</span> <span class="kr">number</span><span class="p">;</span> <span class="nl">quantity</span><span class="p">:</span> <span class="kr">number</span> <span class="p">}[],</span>
    <span class="p">)</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">id</span> <span class="o">=</span> <span class="nx">id</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">customerEmail</span> <span class="o">=</span> <span class="nx">customerEmail</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">customerType</span> <span class="o">=</span> <span class="nx">customerType</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">items</span> <span class="o">=</span> <span class="nx">items</span>
    <span class="p">}</span>

    <span class="nx">validate</span><span class="p">(</span><span class="nx">_idProvider</span><span class="p">:</span> <span class="nx">OrderIdProvider</span><span class="p">):</span> <span class="nx">ValidatedOrder</span> <span class="p">{</span>
        <span class="k">return</span> <span class="k">this</span>
    <span class="p">}</span>

    <span class="nx">process</span><span class="p">():</span> <span class="nx">ProcessedOrder</span> <span class="p">{</span>
        <span class="c1">// Constantes de negocio (simples por ahora)</span>
        <span class="kd">const</span> <span class="nx">TAX_RATE</span> <span class="o">=</span> <span class="mf">0.21</span> <span class="c1">// 21% IVA</span>
        <span class="kd">const</span> <span class="nx">FREE_SHIPPING_THRESHOLD</span> <span class="o">=</span> <span class="mi">50</span>
        <span class="kd">const</span> <span class="nx">SHIPPING_FLAT</span> <span class="o">=</span> <span class="mi">5</span>

        <span class="c1">// Calcular subtotal</span>
        <span class="kd">let</span> <span class="nx">subtotal</span> <span class="o">=</span> <span class="mi">0</span>
        <span class="k">for</span> <span class="p">(</span><span class="kd">const</span> <span class="nx">item</span> <span class="k">of</span> <span class="k">this</span><span class="p">.</span><span class="nx">items</span><span class="p">)</span> <span class="p">{</span>
            <span class="nx">subtotal</span> <span class="o">+=</span> <span class="nx">item</span><span class="p">.</span><span class="nx">price</span> <span class="o">*</span> <span class="nx">item</span><span class="p">.</span><span class="nx">quantity</span>
        <span class="p">}</span>

        <span class="c1">// Descuento por cliente VIP (10% del subtotal)</span>
        <span class="kd">let</span> <span class="nx">discount</span> <span class="o">=</span> <span class="mi">0</span>
        <span class="k">if</span> <span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">customerType</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">VIP</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
            <span class="nx">discount</span> <span class="o">=</span> <span class="nx">roundMoney</span><span class="p">(</span><span class="nx">subtotal</span> <span class="o">*</span> <span class="mf">0.1</span><span class="p">)</span>
            <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Descuento VIP aplicado</span><span class="dl">'</span><span class="p">)</span>
        <span class="p">}</span>

        <span class="c1">// Base imponible</span>
        <span class="kd">const</span> <span class="nx">taxable</span> <span class="o">=</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">max</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="nx">subtotal</span> <span class="o">-</span> <span class="nx">discount</span><span class="p">)</span>

        <span class="c1">// Impuestos</span>
        <span class="kd">const</span> <span class="nx">tax</span> <span class="o">=</span> <span class="nx">roundMoney</span><span class="p">(</span><span class="nx">taxable</span> <span class="o">*</span> <span class="nx">TAX_RATE</span><span class="p">)</span>

        <span class="c1">// Envío</span>
        <span class="kd">const</span> <span class="nx">shipping</span> <span class="o">=</span> <span class="nx">taxable</span> <span class="o">&gt;=</span> <span class="nx">FREE_SHIPPING_THRESHOLD</span> <span class="p">?</span> <span class="mi">0</span> <span class="p">:</span> <span class="nx">SHIPPING_FLAT</span>

        <span class="c1">// Total</span>
        <span class="kd">const</span> <span class="nx">total</span> <span class="o">=</span> <span class="nx">roundMoney</span><span class="p">(</span><span class="nx">taxable</span> <span class="o">+</span> <span class="nx">tax</span> <span class="o">+</span> <span class="nx">shipping</span><span class="p">)</span>

        <span class="k">return</span> <span class="k">new</span> <span class="nx">ProcessedOrder</span><span class="p">(</span>
            <span class="k">this</span><span class="p">.</span><span class="nx">id</span><span class="p">,</span>
            <span class="k">this</span><span class="p">.</span><span class="nx">customerEmail</span><span class="p">,</span>
            <span class="k">this</span><span class="p">.</span><span class="nx">customerType</span><span class="p">,</span>
            <span class="k">this</span><span class="p">.</span><span class="nx">items</span><span class="p">,</span>
            <span class="nx">discount</span><span class="p">,</span>
            <span class="nx">shipping</span><span class="p">,</span>
            <span class="nx">roundMoney</span><span class="p">(</span><span class="nx">subtotal</span><span class="p">),</span>
            <span class="nx">tax</span><span class="p">,</span>
            <span class="nx">total</span><span class="p">,</span>
        <span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Finalmente, <code class="language-plaintext highlighter-rouge">ProcessedOrder</code> es la clase que representa un pedido procesado.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">class</span> <span class="nx">ProcessedOrder</span> <span class="k">implements</span> <span class="nx">Order</span> <span class="p">{</span>
  <span class="nl">id</span><span class="p">:</span> <span class="kr">number</span>
  <span class="nx">customerEmail</span><span class="p">:</span> <span class="kr">string</span>
  <span class="nx">customerType</span><span class="p">:</span> <span class="dl">'</span><span class="s1">NORMAL</span><span class="dl">'</span> <span class="o">|</span> <span class="dl">'</span><span class="s1">VIP</span><span class="dl">'</span>
  <span class="nx">items</span><span class="p">:</span> <span class="p">{</span> <span class="nl">name</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span> <span class="nl">price</span><span class="p">:</span> <span class="kr">number</span><span class="p">;</span> <span class="nl">quantity</span><span class="p">:</span> <span class="kr">number</span> <span class="p">}[]</span>
  <span class="nx">discount</span><span class="p">:</span> <span class="kr">number</span>
  <span class="nx">shipping</span><span class="p">:</span> <span class="kr">number</span>
  <span class="nx">subtotal</span><span class="p">:</span> <span class="kr">number</span>
  <span class="nx">tax</span><span class="p">:</span> <span class="kr">number</span>
  <span class="nx">total</span><span class="p">:</span> <span class="kr">number</span>

  <span class="kd">constructor</span><span class="p">(</span>
    <span class="nx">id</span><span class="p">:</span> <span class="kr">number</span><span class="p">,</span>
    <span class="nx">customerEmail</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
    <span class="nx">customerType</span><span class="p">:</span> <span class="dl">'</span><span class="s1">NORMAL</span><span class="dl">'</span> <span class="o">|</span> <span class="dl">'</span><span class="s1">VIP</span><span class="dl">'</span><span class="p">,</span>
    <span class="nx">items</span><span class="p">:</span> <span class="p">{</span> <span class="nl">name</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span> <span class="nl">price</span><span class="p">:</span> <span class="kr">number</span><span class="p">;</span> <span class="nl">quantity</span><span class="p">:</span> <span class="kr">number</span> <span class="p">}[],</span>
    <span class="nx">discount</span><span class="p">:</span> <span class="kr">number</span><span class="p">,</span>
    <span class="nx">shipping</span><span class="p">:</span> <span class="kr">number</span><span class="p">,</span>
    <span class="nx">subtotal</span><span class="p">:</span> <span class="kr">number</span><span class="p">,</span>
    <span class="nx">tax</span><span class="p">:</span> <span class="kr">number</span><span class="p">,</span>
    <span class="nx">total</span><span class="p">:</span> <span class="kr">number</span><span class="p">,</span>
  <span class="p">)</span> <span class="p">{</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">id</span> <span class="o">=</span> <span class="nx">id</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">customerEmail</span> <span class="o">=</span> <span class="nx">customerEmail</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">customerType</span> <span class="o">=</span> <span class="nx">customerType</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">items</span> <span class="o">=</span> <span class="nx">items</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">discount</span> <span class="o">=</span> <span class="nx">discount</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">shipping</span> <span class="o">=</span> <span class="nx">shipping</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">subtotal</span> <span class="o">=</span> <span class="nx">subtotal</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">tax</span> <span class="o">=</span> <span class="nx">tax</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">total</span> <span class="o">=</span> <span class="nx">total</span>
  <span class="p">}</span>

  <span class="nx">process</span><span class="p">():</span> <span class="nx">ProcessedOrder</span> <span class="p">{</span>
    <span class="k">return</span> <span class="k">this</span>
  <span class="p">}</span>

  <span class="nx">validate</span><span class="p">(</span><span class="nx">_idProvider</span><span class="p">:</span> <span class="nx">OrderIdProvider</span><span class="p">):</span> <span class="nx">ValidatedOrder</span> <span class="p">{</span>
    <span class="k">throw</span> <span class="k">new</span> <span class="nb">Error</span><span class="p">(</span><span class="dl">'</span><span class="s1">No se puede validar un pedido procesado</span><span class="dl">'</span><span class="p">)</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Así es como queda <code class="language-plaintext highlighter-rouge">OrderService</code> ahora:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">class</span> <span class="nx">OrderService</span> <span class="p">{</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="nx">orderDatabase</span><span class="p">:</span> <span class="nx">OrderDatabase</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="nx">emailSender</span><span class="p">:</span> <span class="nx">EmailSender</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="nx">printer</span><span class="p">:</span> <span class="nx">OrderPrinter</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="nx">idProvider</span><span class="p">:</span> <span class="nx">OrderIdProvider</span>

    <span class="kd">constructor</span><span class="p">(</span>
        <span class="nx">orderDatabase</span><span class="p">:</span> <span class="nx">OrderDatabase</span><span class="p">,</span>
        <span class="nx">emailSender</span><span class="p">:</span> <span class="nx">EmailSender</span><span class="p">,</span>
        <span class="nx">printer</span><span class="p">:</span> <span class="nx">OrderPrinter</span><span class="p">,</span>
        <span class="nx">idProvider</span><span class="p">:</span> <span class="nx">OrderIdProvider</span><span class="p">,</span>
    <span class="p">)</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">orderDatabase</span> <span class="o">=</span> <span class="nx">orderDatabase</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">emailSender</span> <span class="o">=</span> <span class="nx">emailSender</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">printer</span> <span class="o">=</span> <span class="nx">printer</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">idProvider</span> <span class="o">=</span> <span class="nx">idProvider</span>
    <span class="p">}</span>

    <span class="nx">process</span><span class="p">(</span><span class="nx">order</span><span class="p">:</span> <span class="nx">Order</span><span class="p">)</span> <span class="p">{</span>
        <span class="kd">let</span> <span class="nx">validatedOrder</span><span class="p">:</span> <span class="nx">Order</span> <span class="o">|</span> <span class="kc">undefined</span>
        <span class="k">try</span> <span class="p">{</span>
            <span class="nx">validatedOrder</span> <span class="o">=</span> <span class="nx">order</span><span class="p">.</span><span class="nx">validate</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">idProvider</span><span class="p">)</span>
        <span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="p">{</span>
            <span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">(</span><span class="dl">'</span><span class="s1">Error al validar el pedido:</span><span class="dl">'</span><span class="p">,</span> <span class="nx">e</span><span class="p">)</span>
            <span class="k">return</span>
        <span class="p">}</span>

        <span class="kd">const</span> <span class="nx">processedOrder</span> <span class="o">=</span> <span class="nx">validatedOrder</span><span class="p">.</span><span class="nx">process</span><span class="p">()</span>

        <span class="k">this</span><span class="p">.</span><span class="nx">persistOrder</span><span class="p">(</span><span class="nx">processedOrder</span><span class="p">)</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">sendOrderConfirmationEmail</span><span class="p">(</span><span class="nx">processedOrder</span><span class="p">)</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">printOrder</span><span class="p">(</span><span class="nx">processedOrder</span><span class="p">)</span>
    <span class="p">}</span>

    <span class="k">private</span> <span class="nx">printOrder</span><span class="p">(</span><span class="nx">order</span><span class="p">:</span> <span class="nx">Order</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">printer</span><span class="p">.</span><span class="nx">print</span><span class="p">(</span><span class="nx">order</span><span class="p">)</span>
    <span class="p">}</span>

    <span class="k">private</span> <span class="nx">sendOrderConfirmationEmail</span><span class="p">(</span><span class="nx">order</span><span class="p">:</span> <span class="nx">Order</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">emailSender</span><span class="p">.</span><span class="nx">sendOrderConfirmationEmail</span><span class="p">(</span><span class="nx">order</span><span class="p">)</span>
    <span class="p">}</span>

    <span class="k">private</span> <span class="nx">persistOrder</span><span class="p">(</span><span class="nx">order</span><span class="p">:</span> <span class="nx">Order</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">orderDatabase</span><span class="p">.</span><span class="nx">persist</span><span class="p">(</span><span class="nx">order</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h4 id="generar-representaciones-sin-exponer-propiedades-privadas-ni-getters">Generar representaciones sin exponer propiedades privadas ni <em>getters</em></h4>

<p>Tendríamos que seguir trabajando en <code class="language-plaintext highlighter-rouge">Order</code> para hacer privadas sus propiedades y generar representaciones en forma de DTO para su uso en otros componentes, donde justamente ese código se repite.</p>

<p>El mayor consumo directo de las propiedades de Order se produce al generar el DTO para persistencia, envío de email, e impresión. De hecho, el DTO que se genera para impresión es diferente de los otros. Por supuesto, esto tiene sentido: la representación que se genera de la entidad de negocio depende del uso que se le vaya a dar. Por tanto, lo usual sería que se genere un DTO para cada caso de uso.</p>

<p>Hacer esto sin acceder a las propiedades de Order o poblarlo de <em>getters</em> es desafiante. El acceso a las propiedades de una clase es un caso de <em>Inappropriate Intimacy</em>, otro code smell del grupo de los acopladores. La necesidad de mantener los consumidores de <code class="language-plaintext highlighter-rouge">Order</code> en conocimiento de sus propiedades nos impide evolucionar la entidad de negocio.</p>

<p>En otros artículos del blog he tratado este tema en detalle. Para esta ocasión se me ocurre lo siguiente:</p>

<ul>
  <li>Definir un DTO genérico que represente las propiedades de <code class="language-plaintext highlighter-rouge">Order</code> con visibilidad pública y de solo lectura. Este DTO solo se usará dentro de Order y contendrá las propiedades necesarias.</li>
  <li>Crear clases Mapper que usen este DTO para generar su propia representación de <code class="language-plaintext highlighter-rouge">Order</code>.</li>
  <li>Crear una factoría que proporcione una instancia del Mapper adecuado para cada caso de uso.</li>
  <li>Order expondrá un método que tome la factoría y una indicación de la estrategia de mapeo que desea usar, devolviendo la representación deseada.</li>
</ul>

<p>Veamos, por ejemplo, el objeto que se genera para persistencia, el cual no se define de forma explícita en ningún sitio:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span>
    <span class="nl">id</span><span class="p">:</span> <span class="kr">number</span>
    <span class="nx">customerEmail</span><span class="p">:</span> <span class="kr">string</span>
    <span class="nx">customerType</span><span class="p">:</span> <span class="dl">'</span><span class="s1">NORMAL</span><span class="dl">'</span> <span class="o">|</span> <span class="dl">'</span><span class="s1">VIP</span><span class="dl">'</span>
    <span class="nx">items</span><span class="p">:</span> <span class="p">{</span> <span class="nl">name</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span> <span class="nl">price</span><span class="p">:</span> <span class="kr">number</span><span class="p">;</span> <span class="nl">quantity</span><span class="p">:</span> <span class="kr">number</span> <span class="p">}[]</span>
    <span class="nx">amounts</span><span class="p">:</span> <span class="p">{</span>
        <span class="nl">subtotal</span><span class="p">:</span> <span class="kr">number</span> <span class="o">|</span> <span class="kc">undefined</span>
        <span class="nx">discount</span><span class="p">:</span> <span class="kr">number</span> <span class="o">|</span> <span class="kc">undefined</span>
        <span class="nx">tax</span><span class="p">:</span> <span class="kr">number</span> <span class="o">|</span> <span class="kc">undefined</span>
        <span class="nx">shipping</span><span class="p">:</span> <span class="kr">number</span> <span class="o">|</span> <span class="kc">undefined</span>
        <span class="nx">total</span><span class="p">:</span> <span class="kr">number</span> <span class="o">|</span> <span class="kc">undefined</span>
    <span class="p">}</span>
    <span class="nl">status</span><span class="p">:</span> <span class="kr">string</span>
    <span class="nx">createdAt</span><span class="p">:</span> <span class="kr">string</span>
    <span class="nx">updatedAt</span><span class="p">:</span> <span class="kr">string</span>
    <span class="nx">currency</span><span class="p">:</span> <span class="kr">string</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Este es el código que hace el mapeo:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">private</span> <span class="nx">mapOrderToDto</span><span class="p">(</span><span class="nx">order</span><span class="p">:</span> <span class="nx">Order</span><span class="p">)</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">dbNow</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">getCurrentDate</span><span class="p">()</span>
    <span class="c1">// Preparar registro a guardar</span>
    <span class="k">return</span> <span class="p">{</span>
        <span class="na">id</span><span class="p">:</span> <span class="nx">order</span><span class="p">.</span><span class="nx">id</span><span class="p">,</span>
        <span class="na">customerEmail</span><span class="p">:</span> <span class="nx">order</span><span class="p">.</span><span class="nx">customerEmail</span><span class="p">,</span>
        <span class="na">customerType</span><span class="p">:</span> <span class="nx">order</span><span class="p">.</span><span class="nx">customerType</span><span class="p">,</span>
        <span class="na">items</span><span class="p">:</span> <span class="nx">order</span><span class="p">.</span><span class="nx">items</span><span class="p">.</span><span class="nx">map</span><span class="p">((</span><span class="nx">i</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">({</span> <span class="na">name</span><span class="p">:</span> <span class="nx">i</span><span class="p">.</span><span class="nx">name</span><span class="p">,</span> <span class="na">price</span><span class="p">:</span> <span class="nx">i</span><span class="p">.</span><span class="nx">price</span><span class="p">,</span> <span class="na">quantity</span><span class="p">:</span> <span class="nx">i</span><span class="p">.</span><span class="nx">quantity</span> <span class="p">})),</span>
        <span class="na">amounts</span><span class="p">:</span> <span class="p">{</span>
            <span class="na">subtotal</span><span class="p">:</span> <span class="nx">order</span><span class="p">.</span><span class="nx">subtotal</span><span class="p">,</span>
            <span class="na">discount</span><span class="p">:</span> <span class="nx">order</span><span class="p">.</span><span class="nx">discount</span><span class="p">,</span>
            <span class="na">tax</span><span class="p">:</span> <span class="nx">order</span><span class="p">.</span><span class="nx">tax</span><span class="p">,</span>
            <span class="na">shipping</span><span class="p">:</span> <span class="nx">order</span><span class="p">.</span><span class="nx">shipping</span><span class="p">,</span>
            <span class="na">total</span><span class="p">:</span> <span class="nx">order</span><span class="p">.</span><span class="nx">total</span><span class="p">,</span>
        <span class="p">},</span>
        <span class="na">status</span><span class="p">:</span> <span class="dl">'</span><span class="s1">PENDING</span><span class="dl">'</span><span class="p">,</span>
        <span class="na">createdAt</span><span class="p">:</span> <span class="nx">dbNow</span><span class="p">.</span><span class="nx">toISOString</span><span class="p">(),</span>
        <span class="na">updatedAt</span><span class="p">:</span> <span class="nx">dbNow</span><span class="p">.</span><span class="nx">toISOString</span><span class="p">(),</span>
        <span class="na">currency</span><span class="p">:</span> <span class="dl">'</span><span class="s1">USD</span><span class="dl">'</span><span class="p">,</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>En principio necesitamos definir un tipo de DTO equivalente a <code class="language-plaintext highlighter-rouge">Order</code>. Y lo más fácil sería hacerlo como interfaz (en TypeScript).</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kr">interface</span> <span class="nx">Item</span> <span class="p">{</span>
    <span class="nl">name</span><span class="p">:</span> <span class="kr">string</span>
    <span class="nx">price</span><span class="p">:</span> <span class="kr">number</span>
    <span class="nx">quantity</span><span class="p">:</span> <span class="kr">number</span>
<span class="p">}</span>

<span class="k">export</span> <span class="kd">type</span> <span class="nx">ItemCollection</span> <span class="o">=</span> <span class="nx">Item</span><span class="p">[]</span>

<span class="k">export</span> <span class="kr">interface</span> <span class="nx">OrderDTO</span> <span class="p">{</span>
    <span class="nl">id</span><span class="p">:</span> <span class="kr">number</span>
    <span class="nx">customerEmail</span><span class="p">:</span> <span class="kr">string</span>
    <span class="nx">customerType</span><span class="p">:</span> <span class="dl">'</span><span class="s1">NORMAL</span><span class="dl">'</span> <span class="o">|</span> <span class="dl">'</span><span class="s1">VIP</span><span class="dl">'</span>
    <span class="nx">items</span><span class="p">:</span> <span class="nx">Item</span><span class="p">[]</span>
    <span class="nx">subtotal</span><span class="p">?:</span> <span class="kr">number</span>
    <span class="nx">discount</span><span class="p">?:</span> <span class="kr">number</span>
    <span class="nx">tax</span><span class="p">?:</span> <span class="kr">number</span>
    <span class="nx">shipping</span><span class="p">?:</span> <span class="kr">number</span>
    <span class="nx">total</span><span class="p">?:</span> <span class="kr">number</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Por otro lado, definiremos un DTO para la persistencia. Ya está implícito en <code class="language-plaintext highlighter-rouge">OrderDataBase</code> y que comparte algunos elementos con <code class="language-plaintext highlighter-rouge">OrderDTO</code>, por lo que podríamos extenderlo.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kr">interface</span> <span class="nx">OrderRecordDTO</span> <span class="kd">extends</span> <span class="nx">OrderDTO</span><span class="p">{</span>
    <span class="nl">amounts</span><span class="p">:</span> <span class="p">{</span>
        <span class="na">subtotal</span><span class="p">:</span> <span class="kr">number</span> <span class="o">|</span> <span class="kc">undefined</span>
        <span class="na">discount</span><span class="p">:</span> <span class="kr">number</span> <span class="o">|</span> <span class="kc">undefined</span>
        <span class="na">tax</span><span class="p">:</span> <span class="kr">number</span> <span class="o">|</span> <span class="kc">undefined</span>
        <span class="na">shipping</span><span class="p">:</span> <span class="kr">number</span> <span class="o">|</span> <span class="kc">undefined</span>
        <span class="na">total</span><span class="p">:</span> <span class="kr">number</span> <span class="o">|</span> <span class="kc">undefined</span>
    <span class="p">}</span>
    <span class="nl">status</span><span class="p">:</span> <span class="kr">string</span>
    <span class="nx">createdAt</span><span class="p">:</span> <span class="kr">string</span>
    <span class="nx">updatedAt</span><span class="p">:</span> <span class="kr">string</span>
    <span class="nx">currency</span><span class="p">:</span> <span class="kr">string</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Y ahora, creamos un <em>Mapper</em> que lo use para generar el DTO:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">class</span> <span class="nx">OrderToDatabase</span> <span class="p">{</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="nx">clock</span><span class="p">:</span> <span class="nx">Clock</span>

    <span class="kd">constructor</span><span class="p">(</span><span class="nx">clock</span><span class="p">:</span> <span class="nx">Clock</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">clock</span> <span class="o">=</span> <span class="nx">clock</span>
    <span class="p">}</span>

    <span class="nx">map</span><span class="p">(</span><span class="nx">order</span><span class="p">:</span> <span class="nx">OrderData</span><span class="p">):</span> <span class="nx">OrderRecordDTO</span> <span class="p">{</span>
        <span class="kd">const</span> <span class="nx">dbNow</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">getCurrentDate</span><span class="p">()</span>

        <span class="k">return</span> <span class="p">{</span>
            <span class="na">id</span><span class="p">:</span> <span class="nx">order</span><span class="p">.</span><span class="nx">id</span><span class="o">!</span><span class="p">,</span>
            <span class="na">customerEmail</span><span class="p">:</span> <span class="nx">order</span><span class="p">.</span><span class="nx">customerEmail</span><span class="o">!</span><span class="p">,</span>
            <span class="na">customerType</span><span class="p">:</span> <span class="nx">order</span><span class="p">.</span><span class="nx">customerType</span><span class="o">!</span><span class="p">,</span>
            <span class="na">items</span><span class="p">:</span> <span class="nx">order</span><span class="p">.</span><span class="nx">items</span><span class="p">,</span>
            <span class="na">amounts</span><span class="p">:</span> <span class="p">{</span>
                <span class="na">subtotal</span><span class="p">:</span> <span class="nx">order</span><span class="p">.</span><span class="nx">subtotal</span><span class="p">,</span>
                <span class="na">discount</span><span class="p">:</span> <span class="nx">order</span><span class="p">.</span><span class="nx">discount</span><span class="p">,</span>
                <span class="na">tax</span><span class="p">:</span> <span class="nx">order</span><span class="p">.</span><span class="nx">tax</span><span class="p">,</span>
                <span class="na">shipping</span><span class="p">:</span> <span class="nx">order</span><span class="p">.</span><span class="nx">shipping</span><span class="p">,</span>
                <span class="na">total</span><span class="p">:</span> <span class="nx">order</span><span class="p">.</span><span class="nx">total</span><span class="p">,</span>
            <span class="p">},</span>
            <span class="na">status</span><span class="p">:</span> <span class="dl">'</span><span class="s1">PENDING</span><span class="dl">'</span><span class="p">,</span>
            <span class="na">createdAt</span><span class="p">:</span> <span class="nx">dbNow</span><span class="p">.</span><span class="nx">toISOString</span><span class="p">(),</span>
            <span class="na">updatedAt</span><span class="p">:</span> <span class="nx">dbNow</span><span class="p">.</span><span class="nx">toISOString</span><span class="p">(),</span>
            <span class="na">currency</span><span class="p">:</span> <span class="dl">'</span><span class="s1">USD</span><span class="dl">'</span><span class="p">,</span>
        <span class="p">}</span> <span class="k">as</span> <span class="nx">OrderRecordDTO</span>
    <span class="p">}</span>

    <span class="k">protected</span> <span class="nx">getCurrentDate</span><span class="p">():</span> <span class="nb">Date</span> <span class="p">{</span>
        <span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">clock</span><span class="p">.</span><span class="nx">getCurrentDate</span><span class="p">()</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Ahora introduzcamos una factoría de Mappers:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">class</span> <span class="nx">MapperFactory</span> <span class="p">{</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="nx">clock</span><span class="p">:</span> <span class="nx">Clock</span>

    <span class="kd">constructor</span><span class="p">(</span><span class="nx">clock</span><span class="p">:</span> <span class="nx">Clock</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">clock</span> <span class="o">=</span> <span class="nx">clock</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="nx">create</span><span class="p">(</span><span class="nx">strategy</span><span class="p">:</span> <span class="kr">string</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="nx">strategy</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">database</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
            <span class="k">return</span> <span class="k">new</span> <span class="nx">OrderToDatabase</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">clock</span><span class="p">)</span>
        <span class="p">}</span>
        <span class="k">throw</span> <span class="k">new</span> <span class="nb">Error</span><span class="p">(</span><span class="s2">`No se soporta la estrategia </span><span class="p">${</span><span class="nx">strategy</span><span class="p">}</span><span class="s2">`</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>

</code></pre></div></div>

<p>Así que <code class="language-plaintext highlighter-rouge">Order</code> necesita un método que genere la representación:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">representation</span><span class="o">&lt;</span><span class="nx">T</span><span class="o">&gt;</span><span class="p">(</span><span class="nx">factory</span><span class="p">:</span> <span class="nx">MapperFactory</span><span class="p">,</span> <span class="nx">strategy</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="nx">T</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">data</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">OrderData</span><span class="p">(</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">id</span><span class="p">,</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">customerEmail</span><span class="p">,</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">customerType</span><span class="p">,</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">items</span><span class="p">,</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">subtotal</span><span class="p">,</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">discount</span><span class="p">,</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">tax</span><span class="p">,</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">shipping</span><span class="p">,</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">total</span><span class="p">,</span>
    <span class="p">)</span>
    <span class="kd">const</span> <span class="nx">mapper</span> <span class="o">=</span> <span class="nx">factory</span><span class="p">.</span><span class="nx">create</span><span class="p">(</span><span class="nx">strategy</span><span class="p">)</span>
    <span class="k">return</span> <span class="nx">mapper</span><span class="p">.</span><span class="nx">map</span><span class="p">(</span><span class="nx">data</span><span class="p">)</span> <span class="k">as</span> <span class="nx">T</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Hacemos lo mismo para <code class="language-plaintext highlighter-rouge">OrderPrinter</code> y para <code class="language-plaintext highlighter-rouge">EmailSender</code>: introducir su DTO, crear un Mapper y añadirlo a la factoría.</p>

<h2 id="hasta-el-infinito-y-más-allá">Hasta el infinito y más allá</h2>

<p>Podríamos seguir refinando cada pieza mucho más, pero estamos escribiendo uno de los artículos más largos del blog, así que convendría ir parando.</p>

<p>En esencia, todo lo que hemos hecho es mover código desde un lugar en donde estaba todo concentrado hacia clases especializadas, identificando responsabilidades y extrayéndolas. De hecho, hasta ahora hemos seguido trabajando con el test original porque todo nuestro trabajo ha consistido en refactorizar, garantizando con el test que no cambiamos ni el más mínimo aspecto del comportamiento del software.</p>

<p>Para mover este código hemos usado principalmente tres tipos de refactor:</p>

<ul>
  <li><strong>Extract method</strong>: para separar las responsabilidades dentro de la misma clase en métodos privados.</li>
  <li><strong>Extract class</strong>: para llevar esas responsabilidades a clases independientes que luego podemos inyectar.</li>
  <li><strong>Introduce/Move method</strong>: para llevar comportamientos a clases a las que pertenezcan (como entidades y value objects). De hecho, esto es algo que nos ha quedado pendiente.</li>
</ul>

<p>Este test tendrá que desaparecer pronto, para permitirnos introducir nuevas funcionalidades. Recuerda: es un test de caracterización que describe el comportamiento que tiene el software cuando empezamos a trabajar con él.</p>

<p>Este trabajo no lo vamos a hacer en este artículo, pero en este punto, la situación para hacerlo es mucho más favorable. Puede que lo veamos en otro artículo, aunque preferiría moverme a otros <em>smells</em>.</p>]]></content><author><name></name></author><category term="articles" /><category term="code-smells" /><category term="refactoring" /><category term="typescript" /><summary type="html"><![CDATA[Un code smell en el que es fácil caer es Long Method. Añades línes y más líneas a una función o método hasta que empieza a ser difícil de leer y de intervenir. Y un método largo, requiere un artículo largo.]]></summary></entry><entry><title type="html">Large Class</title><link href="https://franiglesias.github.io/large-class/" rel="alternate" type="text/html" title="Large Class" /><published>2025-10-30T00:00:00+00:00</published><updated>2025-10-30T00:00:00+00:00</updated><id>https://franiglesias.github.io/large-class</id><content type="html" xml:base="https://franiglesias.github.io/large-class/"><![CDATA[<p>Otro <em>code smell</em> bastante habitual es tener clases muy grandes, que van acumulando muchas responsabilidades no relacionadas.</p>

<h2 id="definición">Definición</h2>

<p><strong>Large Class</strong> (Clase Grande) ocurre cuando una clase acumula muchas propiedades, muchos métodos o muchas líneas de código, lo que normalmente supone que está intentando manejar muchas responsabilidades no relacionadas o que pueden responder a necesidades diferentes.</p>

<p>Esto ocurre porque muchas veces resulta más fácil colocar nuevas funcionalidades en una clase existente que introducir una nueva. Especialmente cuando se trata de funcionalidades más o menos relacionadas.</p>

<p>Una clase que acumula muchas responsabilidades acaba convirtiéndose en un <em>God Object</em>, otro smell en el que una clase se ocupa de prácticamente todo.</p>

<h2 id="ejemplo">Ejemplo</h2>

<p>La clase <code class="language-plaintext highlighter-rouge">UserAccount</code> se encarga de un montón de tareas relacionadas entre sí por aplicarse, de algún modo, a un usuario.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nx">UserAccount</span> <span class="p">{</span>
  <span class="k">private</span> <span class="nx">name</span><span class="p">:</span> <span class="kr">string</span>
  <span class="k">private</span> <span class="nx">email</span><span class="p">:</span> <span class="kr">string</span>
  <span class="k">private</span> <span class="nx">password</span><span class="p">:</span> <span class="kr">string</span>
  <span class="k">private</span> <span class="nx">lastLogin</span><span class="p">:</span> <span class="nb">Date</span>
  <span class="k">private</span> <span class="nx">loginAttempts</span><span class="p">:</span> <span class="kr">number</span> <span class="o">=</span> <span class="mi">0</span>
  <span class="k">private</span> <span class="nx">notifications</span><span class="p">:</span> <span class="kr">string</span><span class="p">[]</span> <span class="o">=</span> <span class="p">[]</span>
  <span class="k">private</span> <span class="nx">isAdmin</span><span class="p">:</span> <span class="nx">boolean</span>

  <span class="kd">constructor</span><span class="p">(</span><span class="nx">name</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">email</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">password</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">isAdmin</span><span class="p">:</span> <span class="nx">boolean</span> <span class="o">=</span> <span class="kc">false</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">name</span> <span class="o">=</span> <span class="nx">name</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">email</span> <span class="o">=</span> <span class="nx">email</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">password</span> <span class="o">=</span> <span class="nx">password</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">lastLogin</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Date</span><span class="p">()</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">isAdmin</span> <span class="o">=</span> <span class="nx">isAdmin</span>
  <span class="p">}</span>

  <span class="nx">login</span><span class="p">(</span><span class="nx">password</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="nx">boolean</span> <span class="p">{</span>
    <span class="k">if</span> <span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">password</span> <span class="o">===</span> <span class="nx">password</span><span class="p">)</span> <span class="p">{</span>
      <span class="k">this</span><span class="p">.</span><span class="nx">lastLogin</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Date</span><span class="p">()</span>
      <span class="k">this</span><span class="p">.</span><span class="nx">loginAttempts</span> <span class="o">=</span> <span class="mi">0</span>
      <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Inicio de sesión exitoso</span><span class="dl">'</span><span class="p">)</span>
      <span class="k">return</span> <span class="kc">true</span>
    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
      <span class="k">this</span><span class="p">.</span><span class="nx">loginAttempts</span><span class="o">++</span>
      <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Contraseña incorrecta</span><span class="dl">'</span><span class="p">)</span>
      <span class="k">return</span> <span class="kc">false</span>
    <span class="p">}</span>
  <span class="p">}</span>

  <span class="nx">resetPassword</span><span class="p">(</span><span class="nx">newPassword</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="k">void</span> <span class="p">{</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">password</span> <span class="o">=</span> <span class="nx">newPassword</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Contraseña actualizada</span><span class="dl">'</span><span class="p">)</span>
  <span class="p">}</span>

  <span class="nx">updateEmail</span><span class="p">(</span><span class="nx">newEmail</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="k">void</span> <span class="p">{</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">email</span> <span class="o">=</span> <span class="nx">newEmail</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Correo actualizado</span><span class="dl">'</span><span class="p">)</span>
  <span class="p">}</span>

  <span class="nx">updateName</span><span class="p">(</span><span class="nx">newName</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="k">void</span> <span class="p">{</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">name</span> <span class="o">=</span> <span class="nx">newName</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Nombre actualizado</span><span class="dl">'</span><span class="p">)</span>
  <span class="p">}</span>

  <span class="nx">addNotification</span><span class="p">(</span><span class="nx">message</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="k">void</span> <span class="p">{</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">notifications</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">message</span><span class="p">)</span>
  <span class="p">}</span>

  <span class="nx">getNotifications</span><span class="p">():</span> <span class="kr">string</span><span class="p">[]</span> <span class="p">{</span>
    <span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">notifications</span>
  <span class="p">}</span>

  <span class="nx">clearNotifications</span><span class="p">():</span> <span class="k">void</span> <span class="p">{</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">notifications</span> <span class="o">=</span> <span class="p">[]</span>
  <span class="p">}</span>

  <span class="nx">promoteToAdmin</span><span class="p">():</span> <span class="k">void</span> <span class="p">{</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">isAdmin</span> <span class="o">=</span> <span class="kc">true</span>
  <span class="p">}</span>

  <span class="nx">revokeAdmin</span><span class="p">():</span> <span class="k">void</span> <span class="p">{</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">isAdmin</span> <span class="o">=</span> <span class="kc">false</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="ejercicio">Ejercicio</h2>

<p>Queremos añadir soporte para autenticación de dos factores (2FA) y preferencias de notificación.</p>

<h2 id="problemas-que-encontrarás">Problemas que encontrarás</h2>

<p>Para resolver el ejercicio encontrarás el problema de tener que añadir propiedades y métodos dispares a la clase, esto genera problemas de cohesión y de mantenimiento. Si bien, puede ser una forma cómoda de abordar el desarrollo en el corto plazo, el coste de cambio se multiplica en el largo.</p>

<p>Observarás que hay propiedades que están implicadas solo en algunos métodos, siendo innecesarias en otros.</p>

<p>También resulta difícil recordar todo lo que hace la clase, lo que complica intervenir en ella en caso de necesitar aplicar algún cambio.</p>

<p>Se incrementa el riesgo de que los cambios requeridos por una responsabilidad afecten a otras, introduciendo bugs, ya que hay consumidores interesados en distintos comportamientos que posiblemente ignoren las necesidades de otros.</p>

<p>Todos estos problemas empeoran a medida que añadimos funcionalidades.</p>

<h2 id="solución">Solución</h2>

<h3 id="sin-resolver-el-smell">Sin resolver el <em>smell</em></h3>

<p>Con la situación actual, implementar autenticación de dos factores y preferencias de notificación implicaría añadir dos o tres métodos por funcionalidad a la clase <code class="language-plaintext highlighter-rouge">UserAccount</code>. Así, por ejemplo, para gestionar la autenticación de dos factores, <code class="language-plaintext highlighter-rouge">UserAccount</code> tendría que incorporar:</p>

<ul>
  <li>Generar el código de un solo uso</li>
  <li>Enviar el código al usuario</li>
  <li>Validar el código</li>
</ul>

<p>Para habilitar preferencias de notificaciones, habría que implementar:</p>

<ul>
  <li>Registrar un identificador por cada posible medio de notificación</li>
  <li>Dar soporte a un medio de notificación para enviar notificaciones</li>
</ul>

<p>No voy a intentar implementarlo aquí porque creo que se aprecia fácilmente cuál es la dificultad de hacerlo: <code class="language-plaintext highlighter-rouge">UserAccount</code> seguirá creciendo en cantidad de código y métodos, haciéndose más difícil de mantener a cada paso.</p>

<h3 id="resolviendo-el-smell">Resolviendo el <em>smell</em></h3>

<p>Para resolver el <em>smell</em>, debemos separar las funcionalidades de <code class="language-plaintext highlighter-rouge">UserAccount</code> en distintas clases que atiendan a las necesidades de distintos consumidores.</p>

<p>Examinando la clase, podemos identificar las distintas responsabilidades que tiene:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nx">UserAccount</span> <span class="p">{</span>
  <span class="k">private</span> <span class="nx">name</span><span class="p">:</span> <span class="kr">string</span>
  <span class="k">private</span> <span class="nx">email</span><span class="p">:</span> <span class="kr">string</span>
  <span class="k">private</span> <span class="nx">password</span><span class="p">:</span> <span class="kr">string</span>
  <span class="k">private</span> <span class="nx">lastLogin</span><span class="p">:</span> <span class="nb">Date</span>
  <span class="k">private</span> <span class="nx">loginAttempts</span><span class="p">:</span> <span class="kr">number</span> <span class="o">=</span> <span class="mi">0</span>
  <span class="k">private</span> <span class="nx">notifications</span><span class="p">:</span> <span class="kr">string</span><span class="p">[]</span> <span class="o">=</span> <span class="p">[]</span>
  <span class="k">private</span> <span class="nx">isAdmin</span><span class="p">:</span> <span class="nx">boolean</span>

  <span class="kd">constructor</span><span class="p">(</span><span class="nx">name</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">email</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">password</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">isAdmin</span><span class="p">:</span> <span class="nx">boolean</span> <span class="o">=</span> <span class="kc">false</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">name</span> <span class="o">=</span> <span class="nx">name</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">email</span> <span class="o">=</span> <span class="nx">email</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">password</span> <span class="o">=</span> <span class="nx">password</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">lastLogin</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Date</span><span class="p">()</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">isAdmin</span> <span class="o">=</span> <span class="nx">isAdmin</span>
  <span class="p">}</span>

  <span class="c1">// --- Autenticación ---</span>
  <span class="nx">login</span><span class="p">(</span><span class="nx">password</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="nx">boolean</span> <span class="p">{</span>
    <span class="k">if</span> <span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">password</span> <span class="o">===</span> <span class="nx">password</span><span class="p">)</span> <span class="p">{</span>
      <span class="k">this</span><span class="p">.</span><span class="nx">lastLogin</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Date</span><span class="p">()</span>
      <span class="k">this</span><span class="p">.</span><span class="nx">loginAttempts</span> <span class="o">=</span> <span class="mi">0</span>
      <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Inicio de sesión exitoso</span><span class="dl">'</span><span class="p">)</span>
      <span class="k">return</span> <span class="kc">true</span>
    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
      <span class="k">this</span><span class="p">.</span><span class="nx">loginAttempts</span><span class="o">++</span>
      <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Contraseña incorrecta</span><span class="dl">'</span><span class="p">)</span>
      <span class="k">return</span> <span class="kc">false</span>
    <span class="p">}</span>
  <span class="p">}</span>

  <span class="nx">resetPassword</span><span class="p">(</span><span class="nx">newPassword</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="k">void</span> <span class="p">{</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">password</span> <span class="o">=</span> <span class="nx">newPassword</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Contraseña actualizada</span><span class="dl">'</span><span class="p">)</span>
  <span class="p">}</span>

  <span class="c1">// --- Perfil ---</span>
  <span class="nx">updateEmail</span><span class="p">(</span><span class="nx">newEmail</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="k">void</span> <span class="p">{</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">email</span> <span class="o">=</span> <span class="nx">newEmail</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Correo actualizado</span><span class="dl">'</span><span class="p">)</span>
  <span class="p">}</span>

  <span class="nx">updateName</span><span class="p">(</span><span class="nx">newName</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="k">void</span> <span class="p">{</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">name</span> <span class="o">=</span> <span class="nx">newName</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Nombre actualizado</span><span class="dl">'</span><span class="p">)</span>
  <span class="p">}</span>

  <span class="c1">// --- Notificaciones ---</span>
  <span class="nx">addNotification</span><span class="p">(</span><span class="nx">message</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="k">void</span> <span class="p">{</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">notifications</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">message</span><span class="p">)</span>
  <span class="p">}</span>

  <span class="nx">getNotifications</span><span class="p">():</span> <span class="kr">string</span><span class="p">[]</span> <span class="p">{</span>
    <span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">notifications</span>
  <span class="p">}</span>

  <span class="nx">clearNotifications</span><span class="p">():</span> <span class="k">void</span> <span class="p">{</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">notifications</span> <span class="o">=</span> <span class="p">[]</span>
  <span class="p">}</span>

  <span class="c1">// --- Administración ---</span>
  <span class="nx">promoteToAdmin</span><span class="p">():</span> <span class="k">void</span> <span class="p">{</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">isAdmin</span> <span class="o">=</span> <span class="kc">true</span>
  <span class="p">}</span>

  <span class="nx">revokeAdmin</span><span class="p">():</span> <span class="k">void</span> <span class="p">{</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">isAdmin</span> <span class="o">=</span> <span class="kc">false</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Es decir, tenemos:</p>

<ul>
  <li>Autenticación</li>
  <li>Gestión del perfil</li>
  <li>Notificaciones</li>
  <li>Administración de permisos</li>
</ul>

<p>Como se puede ver, los cambios que se nos piden afectan cada uno a un área distinta:</p>

<ul>
  <li>Doble factor: autenticación</li>
  <li>Preferencias de notificaciones: notificaciones</li>
</ul>

<p>El refactor adecuado para resolver el smell es <em>Extraer clase</em>, es decir, crear una nueva clase por cada grupo de funcionalidad. Esto no implica la desaparición de <code class="language-plaintext highlighter-rouge">UserAccount</code>, que proporciona un punto de entrada para los consumidores.</p>

<p>Por otro lado, también puede ser interesante usar <em>Extraer Interface</em>, ya que, en un momento dado, podríamos querer exponer interfaces estrechas orientadas a las necesidades de los consumidores, aplicando el principio de <em>Segregación de Interfaces</em>, de modo que en algún momento se pudiesen cambiar las implementaciones sin afectar a los consumidores.</p>

<h4 id="autenticación">Autenticación</h4>

<p>Este test describe el comportamiento actual de la clase <code class="language-plaintext highlighter-rouge">UserAccount</code> en la responsabilidad de autenticación.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">describe</span><span class="p">(</span><span class="dl">'</span><span class="s1">Authentication</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="nx">describe</span><span class="p">(</span><span class="dl">'</span><span class="s1">When user tries to authenticate</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
        <span class="kd">const</span> <span class="nx">user</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">UserAccount</span><span class="p">(</span>
            <span class="dl">'</span><span class="s1">John Doe</span><span class="dl">'</span><span class="p">,</span> 
            <span class="dl">'</span><span class="s1">john@example.com</span><span class="dl">'</span><span class="p">,</span> 
            <span class="dl">'</span><span class="s1">secret</span><span class="dl">'</span><span class="p">,</span> 
            <span class="kc">false</span>
        <span class="p">)</span>

        <span class="nx">it</span><span class="p">(</span><span class="dl">'</span><span class="s1">should authenticate with the correct password</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
            <span class="nx">expect</span><span class="p">(</span><span class="nx">user</span><span class="p">.</span><span class="nx">login</span><span class="p">(</span><span class="dl">'</span><span class="s1">secret</span><span class="dl">'</span><span class="p">)).</span><span class="nx">toEqual</span><span class="p">(</span><span class="kc">true</span><span class="p">)</span>
        <span class="p">})</span>

        <span class="nx">it</span><span class="p">(</span><span class="dl">'</span><span class="s1">should not authenticate with the wrong password</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
            <span class="nx">expect</span><span class="p">(</span><span class="nx">user</span><span class="p">.</span><span class="nx">login</span><span class="p">(</span><span class="dl">'</span><span class="s1">another</span><span class="dl">'</span><span class="p">)).</span><span class="nx">toEqual</span><span class="p">(</span><span class="kc">false</span><span class="p">)</span>
        <span class="p">})</span>
    <span class="p">})</span>

    <span class="nx">describe</span><span class="p">(</span><span class="dl">'</span><span class="s1">Reset password</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
        <span class="nx">describe</span><span class="p">(</span><span class="dl">'</span><span class="s1">When the user resets the password</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
            <span class="kd">const</span> <span class="nx">user</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">UserAccount</span><span class="p">(</span>
                <span class="dl">'</span><span class="s1">John Doe</span><span class="dl">'</span><span class="p">,</span> 
                <span class="dl">'</span><span class="s1">john@example.com</span><span class="dl">'</span><span class="p">,</span> 
                <span class="dl">'</span><span class="s1">secret</span><span class="dl">'</span><span class="p">,</span> 
                <span class="kc">false</span>
            <span class="p">)</span>
            <span class="nx">user</span><span class="p">.</span><span class="nx">resetPassword</span><span class="p">(</span><span class="dl">'</span><span class="s1">new-secret</span><span class="dl">'</span><span class="p">)</span>
            <span class="nx">it</span><span class="p">(</span><span class="dl">'</span><span class="s1">should not authenticate with the old one</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
                <span class="nx">expect</span><span class="p">(</span><span class="nx">user</span><span class="p">.</span><span class="nx">login</span><span class="p">(</span><span class="dl">'</span><span class="s1">secret</span><span class="dl">'</span><span class="p">)).</span><span class="nx">toEqual</span><span class="p">(</span><span class="kc">false</span><span class="p">)</span>
            <span class="p">})</span>

            <span class="nx">it</span><span class="p">(</span><span class="dl">'</span><span class="s1">should authenticate with the new one</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
                <span class="nx">expect</span><span class="p">(</span><span class="nx">user</span><span class="p">.</span><span class="nx">login</span><span class="p">(</span><span class="dl">'</span><span class="s1">new-secret</span><span class="dl">'</span><span class="p">)).</span><span class="nx">toEqual</span><span class="p">(</span><span class="kc">true</span><span class="p">)</span>
            <span class="p">})</span>
        <span class="p">})</span>
    <span class="p">})</span>
<span class="p">})</span>
</code></pre></div></div>

<p>Por supuesto, mirando el código de <code class="language-plaintext highlighter-rouge">UserAccount</code> podríamos identificar varias cuestiones. Aunque se lleva cuenta de los intentos de login, no se están usando en el comportamiento examinado. Por ejemplo, más de tres intentos podrían provocar que el usuario se bloquee y se vea obligado a resetear la contraseña. No vamos a abordar eso en este ejercicio, al menos por el momento.</p>

<p>Lo que sí podemos ver es que la funcionalidad de autenticación se describe mediante dos métodos: <code class="language-plaintext highlighter-rouge">login</code> y <code class="language-plaintext highlighter-rouge">resetPassword</code>. Esto nos lleva a la posibilidad de aplicar <em>Extraer Interface</em>:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">interface</span> <span class="nx">ManagesAuthentication</span> <span class="p">{</span>
    <span class="nx">login</span><span class="p">(</span><span class="nx">password</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="nx">boolean</span><span class="p">;</span>

    <span class="nx">resetPassword</span><span class="p">(</span><span class="nx">newPassword</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="k">void</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>De modo que <code class="language-plaintext highlighter-rouge">UserAccount</code> se convierte en una clase capaz de ejercer el rol <code class="language-plaintext highlighter-rouge">ManagesAuthentication</code>. Esto último no es estrictamente necesario. Dependiendo del contexto, nos puede interesar hacer desaparecer <code class="language-plaintext highlighter-rouge">UserAccount</code> y reemplazarlo por una clase que implemente esta y las demás interfaces que vamos a extraer.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">class</span> <span class="nx">UserAccount</span> <span class="k">implements</span> <span class="nx">ManagesAuthentication</span> <span class="p">{</span>
    <span class="c1">// Code removed for clarity</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Una consecuencia de haber introducido esta interfaz es que el test anterior se puede aplicar a cualquier clase que la implemente. Y una clase que podría hacerlo es una que extraigamos de <code class="language-plaintext highlighter-rouge">UserAccount</code>. Por ejemplo, <code class="language-plaintext highlighter-rouge">AuthenticateUser</code>. Básicamente, no tenemos más que copiar los métodos y propiedades necesarios de <code class="language-plaintext highlighter-rouge">UserAccount</code> en <code class="language-plaintext highlighter-rouge">AuthenticateUser</code>.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">class</span> <span class="nx">AuthenticateUser</span> <span class="k">implements</span> <span class="nx">ManagesAuthentication</span> <span class="p">{</span>
    <span class="k">private</span> <span class="nx">password</span><span class="p">:</span> <span class="kr">string</span>
    <span class="k">private</span> <span class="nx">lastLogin</span><span class="p">:</span> <span class="nb">Date</span> <span class="o">|</span> <span class="kc">undefined</span>
    <span class="k">private</span> <span class="nx">loginAttempts</span><span class="p">:</span> <span class="kr">number</span> <span class="o">=</span> <span class="mi">0</span>

    <span class="kd">constructor</span><span class="p">(</span><span class="nx">password</span><span class="p">:</span> <span class="kr">string</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">password</span> <span class="o">=</span> <span class="nx">password</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="nx">login</span><span class="p">(</span><span class="nx">password</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="nx">boolean</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">password</span> <span class="o">===</span> <span class="nx">password</span><span class="p">)</span> <span class="p">{</span>
            <span class="k">this</span><span class="p">.</span><span class="nx">lastLogin</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Date</span><span class="p">()</span>
            <span class="k">this</span><span class="p">.</span><span class="nx">loginAttempts</span> <span class="o">=</span> <span class="mi">0</span>
            <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Inicio de sesión exitoso</span><span class="dl">'</span><span class="p">)</span>
            <span class="k">return</span> <span class="kc">true</span>
        <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
            <span class="k">this</span><span class="p">.</span><span class="nx">loginAttempts</span><span class="o">++</span>
            <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Contraseña incorrecta</span><span class="dl">'</span><span class="p">)</span>
            <span class="k">return</span> <span class="kc">false</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="nx">resetPassword</span><span class="p">(</span><span class="nx">newPassword</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="k">void</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">password</span> <span class="o">=</span> <span class="nx">newPassword</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Contraseña actualizada</span><span class="dl">'</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Si usamos esta clase en lugar de <code class="language-plaintext highlighter-rouge">UserAccount</code>, cambiando lo que corresponda, el test sigue pasando:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">describe</span><span class="p">(</span><span class="dl">'</span><span class="s1">ManagesAuthentication</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="nx">describe</span><span class="p">(</span><span class="dl">'</span><span class="s1">When the user tries to authenticate</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
        <span class="kd">const</span> <span class="nx">user</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">AuthenticateUser</span><span class="p">(</span><span class="dl">'</span><span class="s1">secret</span><span class="dl">'</span><span class="p">)</span>

        <span class="nx">it</span><span class="p">(</span><span class="dl">'</span><span class="s1">should authenticate with the correct password</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
            <span class="nx">expect</span><span class="p">(</span><span class="nx">user</span><span class="p">.</span><span class="nx">login</span><span class="p">(</span><span class="dl">'</span><span class="s1">secret</span><span class="dl">'</span><span class="p">)).</span><span class="nx">toEqual</span><span class="p">(</span><span class="kc">true</span><span class="p">)</span>
        <span class="p">})</span>

        <span class="nx">it</span><span class="p">(</span><span class="dl">'</span><span class="s1">should not authenticate with the wrong password</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
            <span class="nx">expect</span><span class="p">(</span><span class="nx">user</span><span class="p">.</span><span class="nx">login</span><span class="p">(</span><span class="dl">'</span><span class="s1">another</span><span class="dl">'</span><span class="p">)).</span><span class="nx">toEqual</span><span class="p">(</span><span class="kc">false</span><span class="p">)</span>
        <span class="p">})</span>
    <span class="p">})</span>

    <span class="nx">describe</span><span class="p">(</span><span class="dl">'</span><span class="s1">Reset password</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
        <span class="nx">describe</span><span class="p">(</span><span class="dl">'</span><span class="s1">When the user resets the password</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
            <span class="kd">const</span> <span class="nx">user</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">AuthenticateUser</span><span class="p">(</span><span class="dl">'</span><span class="s1">secret</span><span class="dl">'</span><span class="p">)</span>
            <span class="nx">user</span><span class="p">.</span><span class="nx">resetPassword</span><span class="p">(</span><span class="dl">'</span><span class="s1">new-secret</span><span class="dl">'</span><span class="p">)</span>
            <span class="nx">it</span><span class="p">(</span><span class="dl">'</span><span class="s1">should not authenticate with the old one</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
                <span class="nx">expect</span><span class="p">(</span><span class="nx">user</span><span class="p">.</span><span class="nx">login</span><span class="p">(</span><span class="dl">'</span><span class="s1">secret</span><span class="dl">'</span><span class="p">)).</span><span class="nx">toEqual</span><span class="p">(</span><span class="kc">false</span><span class="p">)</span>
            <span class="p">})</span>

            <span class="nx">it</span><span class="p">(</span><span class="dl">'</span><span class="s1">should authenticate with the new one</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
                <span class="nx">expect</span><span class="p">(</span><span class="nx">user</span><span class="p">.</span><span class="nx">login</span><span class="p">(</span><span class="dl">'</span><span class="s1">new-secret</span><span class="dl">'</span><span class="p">)).</span><span class="nx">toEqual</span><span class="p">(</span><span class="kc">true</span><span class="p">)</span>
            <span class="p">})</span>
        <span class="p">})</span>
    <span class="p">})</span>
<span class="p">})</span>
</code></pre></div></div>

<p>Nuestro siguiente paso es usar esta clase en <code class="language-plaintext highlighter-rouge">UserAccount</code>, que delega en ella todo lo que tenga que ver con autenticación. De paso, nos libramos de todas las propiedades que correspondan a esta responsabilidad.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">class</span> <span class="nx">UserAccount</span> <span class="k">implements</span> <span class="nx">ManagesAuthentication</span> <span class="p">{</span>
    <span class="k">private</span> <span class="nx">name</span><span class="p">:</span> <span class="kr">string</span>
    <span class="k">private</span> <span class="nx">email</span><span class="p">:</span> <span class="kr">string</span>
    <span class="k">private</span> <span class="nx">notifications</span><span class="p">:</span> <span class="kr">string</span><span class="p">[]</span> <span class="o">=</span> <span class="p">[]</span>
    <span class="k">private</span> <span class="nx">isAdmin</span><span class="p">:</span> <span class="nx">boolean</span>
    <span class="k">private</span> <span class="nx">authenticator</span><span class="p">:</span> <span class="nx">ManagesAuthentication</span>

    <span class="kd">constructor</span><span class="p">(</span><span class="nx">name</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">email</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">password</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">isAdmin</span><span class="p">:</span> <span class="nx">boolean</span> <span class="o">=</span> <span class="kc">false</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">name</span> <span class="o">=</span> <span class="nx">name</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">email</span> <span class="o">=</span> <span class="nx">email</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">isAdmin</span> <span class="o">=</span> <span class="nx">isAdmin</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">authenticator</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">AuthenticateUser</span><span class="p">(</span><span class="nx">password</span><span class="p">)</span>
    <span class="p">}</span>

    <span class="c1">// --- Autenticación ---</span>
    <span class="nx">login</span><span class="p">(</span><span class="nx">password</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="nx">boolean</span> <span class="p">{</span>
        <span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">authenticator</span><span class="p">.</span><span class="nx">login</span><span class="p">(</span><span class="nx">password</span><span class="p">)</span>
    <span class="p">}</span>

    <span class="nx">resetPassword</span><span class="p">(</span><span class="nx">newPassword</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="k">void</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">authenticator</span><span class="p">.</span><span class="nx">resetPassword</span><span class="p">(</span><span class="nx">newPassword</span><span class="p">)</span>
    <span class="p">}</span>

    <span class="c1">// Code removed for clarity</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Lo interesante aquí es que, aunque <code class="language-plaintext highlighter-rouge">UserAccount</code> sigue siendo la clase con la que habla el resto de la aplicación, delega todo ese conocimiento de autenticación. Si hay que aplicar cambios en esa responsabilidad, solo tendremos que hacerlo en <code class="language-plaintext highlighter-rouge">AuthenticateUser</code>, que es la fuente de verdad.</p>

<p>No tenemos más que hacer lo mismo con el resto de las funcionalidades de <code class="language-plaintext highlighter-rouge">UserAccount</code>.</p>

<h4 id="perfil-de-usuario">Perfil de usuario</h4>

<p>Siguiendo el mismo procedimiento, podemos extraer una interfaz para gestionar el perfil de usuario:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">interface</span> <span class="nx">ManagesProfile</span> <span class="p">{</span>
    <span class="nx">updateEmail</span><span class="p">(</span><span class="nx">newEmail</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="k">void</span>
    <span class="nx">updateName</span><span class="p">(</span><span class="nx">newName</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="k">void</span>
<span class="p">}</span>
</code></pre></div></div>

<p>La aplicamos a <code class="language-plaintext highlighter-rouge">UserAccount</code>:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">class</span> <span class="nx">UserAccount</span> <span class="k">implements</span> <span class="nx">ManagesAuthentication</span><span class="p">,</span> <span class="nx">ManagesProfile</span> <span class="p">{</span>
    <span class="c1">// Code removed for clarity</span>
<span class="p">}</span>
</code></pre></div></div>

<p>También escribimos un test para caracterizar el comportamiento de la clase en lo que respecta al perfil. Para ello, introduzco un método <code class="language-plaintext highlighter-rouge">profile</code>, que me permita ver el perfil del usuario y lo añado a la interfaz:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">profile</span><span class="p">():</span> <span class="kr">string</span> <span class="p">{</span>
    <span class="k">return</span> <span class="s2">`</span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">name</span><span class="p">}</span><span class="s2"> &lt;</span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">email</span><span class="p">}</span><span class="s2">&gt;`</span>
<span class="p">}</span>
</code></pre></div></div>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">interface</span> <span class="nx">ManagesProfile</span> <span class="p">{</span>
    <span class="nx">updateEmail</span><span class="p">(</span><span class="nx">newEmail</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="k">void</span>
    <span class="nx">updateName</span><span class="p">(</span><span class="nx">newName</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="k">void</span>
    <span class="nx">profile</span><span class="p">():</span> <span class="kr">string</span>
<span class="p">}</span>
</code></pre></div></div>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">describe</span><span class="p">(</span><span class="dl">'</span><span class="s1">Manages Profile</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="nx">describe</span><span class="p">(</span><span class="dl">'</span><span class="s1">When the user wants to update their profile</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
        <span class="kd">const</span> <span class="nx">user</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">UserAccount</span><span class="p">(</span><span class="dl">'</span><span class="s1">John Doe</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">john@example.com</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">secret</span><span class="dl">'</span><span class="p">,</span> <span class="kc">false</span><span class="p">)</span>

        <span class="nx">it</span><span class="p">(</span><span class="dl">'</span><span class="s1">should be able to change the name</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
            <span class="nx">user</span><span class="p">.</span><span class="nx">updateName</span><span class="p">(</span><span class="dl">'</span><span class="s1">Mark Bezos</span><span class="dl">'</span><span class="p">)</span>
            <span class="nx">expect</span><span class="p">(</span><span class="nx">user</span><span class="p">.</span><span class="nx">profile</span><span class="p">()).</span><span class="nx">toEqual</span><span class="p">(</span><span class="dl">'</span><span class="s1">Mark Bezos &lt;john@example.com&gt;</span><span class="dl">'</span><span class="p">)</span>
        <span class="p">})</span>

        <span class="nx">it</span><span class="p">(</span><span class="dl">'</span><span class="s1">should be able to change the email</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
            <span class="nx">user</span><span class="p">.</span><span class="nx">updateEmail</span><span class="p">(</span><span class="dl">'</span><span class="s1">mark@example.com</span><span class="dl">'</span><span class="p">)</span>
            <span class="nx">expect</span><span class="p">(</span><span class="nx">user</span><span class="p">.</span><span class="nx">profile</span><span class="p">()).</span><span class="nx">toEqual</span><span class="p">(</span><span class="dl">'</span><span class="s1">Mark Bezos &lt;mark@example.com&gt;</span><span class="dl">'</span><span class="p">)</span>
        <span class="p">})</span>
    <span class="p">})</span>
<span class="p">})</span>
</code></pre></div></div>

<p>Con esto, ya tenemos los elementos necesarios para extraer la clase <code class="language-plaintext highlighter-rouge">UserProfile</code>.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">class</span> <span class="nx">UserProfile</span> <span class="k">implements</span> <span class="nx">ManagesProfile</span> <span class="p">{</span>
    <span class="k">private</span> <span class="nx">name</span><span class="p">:</span> <span class="kr">string</span>
    <span class="k">private</span> <span class="nx">email</span><span class="p">:</span> <span class="kr">string</span>

    <span class="kd">constructor</span><span class="p">(</span><span class="nx">name</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">email</span><span class="p">:</span> <span class="kr">string</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">name</span> <span class="o">=</span> <span class="nx">name</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">email</span> <span class="o">=</span> <span class="nx">email</span>
    <span class="p">}</span>

    <span class="nx">updateEmail</span><span class="p">(</span><span class="nx">newEmail</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="k">void</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">email</span> <span class="o">=</span> <span class="nx">newEmail</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Correo actualizado</span><span class="dl">'</span><span class="p">)</span>
    <span class="p">}</span>

    <span class="nx">updateName</span><span class="p">(</span><span class="nx">newName</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="k">void</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">name</span> <span class="o">=</span> <span class="nx">newName</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Nombre actualizado</span><span class="dl">'</span><span class="p">)</span>
    <span class="p">}</span>

    <span class="nx">profile</span><span class="p">():</span> <span class="kr">string</span> <span class="p">{</span>
        <span class="k">return</span> <span class="s2">`</span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">name</span><span class="p">}</span><span class="s2"> &lt;</span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">email</span><span class="p">}</span><span class="s2">&gt;`</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Y no nos queda más que hacer que <code class="language-plaintext highlighter-rouge">UserAccount</code> delegue en <code class="language-plaintext highlighter-rouge">UserProfile</code>:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">class</span> <span class="nx">UserAccount</span> <span class="k">implements</span> <span class="nx">ManagesAuthentication</span><span class="p">,</span> <span class="nx">ManagesProfile</span> <span class="p">{</span>
    <span class="k">private</span> <span class="nx">notifications</span><span class="p">:</span> <span class="kr">string</span><span class="p">[]</span> <span class="o">=</span> <span class="p">[]</span>
    <span class="k">private</span> <span class="nx">isAdmin</span><span class="p">:</span> <span class="nx">boolean</span>
    <span class="k">private</span> <span class="nx">authenticator</span><span class="p">:</span> <span class="nx">ManagesAuthentication</span>
    <span class="k">private</span> <span class="nx">userProfile</span><span class="p">:</span> <span class="nx">ManagesProfile</span>

    <span class="kd">constructor</span><span class="p">(</span><span class="nx">name</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">email</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">password</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">isAdmin</span><span class="p">:</span> <span class="nx">boolean</span> <span class="o">=</span> <span class="kc">false</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">isAdmin</span> <span class="o">=</span> <span class="nx">isAdmin</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">authenticator</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">AuthenticateUser</span><span class="p">(</span><span class="nx">password</span><span class="p">)</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">userProfile</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">UserProfile</span><span class="p">(</span><span class="nx">name</span><span class="p">,</span> <span class="nx">email</span><span class="p">)</span>
    <span class="p">}</span>
    
    <span class="nx">updateEmail</span><span class="p">(</span><span class="nx">newEmail</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="k">void</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">userProfile</span><span class="p">.</span><span class="nx">updateEmail</span><span class="p">(</span><span class="nx">newEmail</span><span class="p">)</span>
    <span class="p">}</span>

    <span class="nx">updateName</span><span class="p">(</span><span class="nx">newName</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="k">void</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">userProfile</span><span class="p">.</span><span class="nx">updateName</span><span class="p">(</span><span class="nx">newName</span><span class="p">)</span>
    <span class="p">}</span>

    <span class="nx">profile</span><span class="p">():</span> <span class="kr">string</span> <span class="p">{</span>
        <span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">userProfile</span><span class="p">.</span><span class="nx">profile</span><span class="p">()</span>
    <span class="p">}</span>
    
    <span class="c1">// Code removed for clarity</span>
<span class="p">}</span>
</code></pre></div></div>

<h4 id="notificaciones">Notificaciones</h4>

<p>El procedimiento es exactamente el mismo que hemos hecho para las otras dos responsabilidades. Empezamos por extraer una interfaz:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">interface</span> <span class="nx">ManagesNotifications</span> <span class="p">{</span>
    <span class="nx">addNotification</span><span class="p">(</span><span class="nx">message</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="k">void</span>
    <span class="nx">getNotifications</span><span class="p">():</span> <span class="kr">string</span><span class="p">[]</span>
    <span class="nx">clearNotifications</span><span class="p">():</span> <span class="k">void</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Y la aplicamos a <code class="language-plaintext highlighter-rouge">UserAccount</code>:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">class</span> <span class="nx">UserAccount</span> <span class="k">implements</span> <span class="nx">ManagesAuthentication</span><span class="p">,</span> <span class="nx">ManagesProfile</span><span class="p">,</span> <span class="nx">ManagesNotifications</span> <span class="p">{</span>
    <span class="c1">// Code removed for clarity</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Escribimos un test para caracterizar el comportamiento de la clase en lo que respecta a las notificaciones:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">describe</span><span class="p">(</span><span class="dl">'</span><span class="s1">Manages notifications</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="nx">describe</span><span class="p">(</span><span class="dl">'</span><span class="s1">When a notification is sent to the user</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
        <span class="kd">const</span> <span class="nx">user</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">UserAccount</span><span class="p">(</span><span class="dl">'</span><span class="s1">John Doe</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">john@example.com</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">secret</span><span class="dl">'</span><span class="p">,</span> <span class="kc">false</span><span class="p">)</span>

        <span class="nx">it</span><span class="p">(</span><span class="dl">'</span><span class="s1">should be registered</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
            <span class="nx">user</span><span class="p">.</span><span class="nx">addNotification</span><span class="p">(</span><span class="dl">'</span><span class="s1">This is a notification</span><span class="dl">'</span><span class="p">)</span>
            <span class="kd">const</span> <span class="nx">expected</span> <span class="o">=</span> <span class="p">[</span><span class="dl">'</span><span class="s1">This is a notification</span><span class="dl">'</span><span class="p">]</span>
            <span class="nx">expect</span><span class="p">(</span><span class="nx">user</span><span class="p">.</span><span class="nx">getNotifications</span><span class="p">()).</span><span class="nx">toEqual</span><span class="p">(</span><span class="nx">expected</span><span class="p">)</span>
        <span class="p">});</span>

        <span class="nx">it</span><span class="p">(</span><span class="dl">'</span><span class="s1">should disappear on clear</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
            <span class="nx">user</span><span class="p">.</span><span class="nx">clearNotifications</span><span class="p">()</span>
            <span class="nx">expect</span><span class="p">(</span><span class="nx">user</span><span class="p">.</span><span class="nx">getNotifications</span><span class="p">()).</span><span class="nx">toEqual</span><span class="p">([])</span>
        <span class="p">});</span>
    <span class="p">})</span>
<span class="p">})</span>
</code></pre></div></div>

<p>Y podemos extraer la clase <code class="language-plaintext highlighter-rouge">UserNotifications</code>, copiando los métodos y propiedades necesarios:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">class</span> <span class="nx">UserNotifications</span> <span class="k">implements</span> <span class="nx">ManagesNotifications</span> <span class="p">{</span>
  <span class="k">private</span> <span class="nx">notifications</span><span class="p">:</span> <span class="kr">string</span><span class="p">[]</span>

  <span class="kd">constructor</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">notifications</span> <span class="o">=</span> <span class="p">[]</span>
  <span class="p">}</span>

  <span class="nx">addNotification</span><span class="p">(</span><span class="nx">message</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="k">void</span> <span class="p">{</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">notifications</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">message</span><span class="p">)</span>
  <span class="p">}</span>

  <span class="nx">getNotifications</span><span class="p">():</span> <span class="kr">string</span><span class="p">[]</span> <span class="p">{</span>
    <span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">notifications</span>
  <span class="p">}</span>

  <span class="nx">clearNotifications</span><span class="p">():</span> <span class="k">void</span> <span class="p">{</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">notifications</span> <span class="o">=</span> <span class="p">[]</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Para, finalmente, delegar en esta última:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">class</span> <span class="nx">UserAccount</span> <span class="k">implements</span> <span class="nx">ManagesAuthentication</span><span class="p">,</span> <span class="nx">ManagesProfile</span><span class="p">,</span> <span class="nx">ManagesNotifications</span> <span class="p">{</span>
    <span class="k">private</span> <span class="nx">isAdmin</span><span class="p">:</span> <span class="nx">boolean</span>
    <span class="k">private</span> <span class="nx">authenticator</span><span class="p">:</span> <span class="nx">ManagesAuthentication</span>
    <span class="k">private</span> <span class="nx">userProfile</span><span class="p">:</span> <span class="nx">ManagesProfile</span>
    <span class="k">private</span> <span class="nx">userNotifications</span><span class="p">:</span> <span class="nx">ManagesNotifications</span>

    <span class="kd">constructor</span><span class="p">(</span><span class="nx">name</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">email</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">password</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">isAdmin</span><span class="p">:</span> <span class="nx">boolean</span> <span class="o">=</span> <span class="kc">false</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">isAdmin</span> <span class="o">=</span> <span class="nx">isAdmin</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">authenticator</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">AuthenticateUser</span><span class="p">(</span><span class="nx">password</span><span class="p">)</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">userProfile</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">UserProfile</span><span class="p">(</span><span class="nx">name</span><span class="p">,</span> <span class="nx">email</span><span class="p">)</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">userNotifications</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">UserNotifications</span><span class="p">()</span>
    <span class="p">}</span>

    <span class="c1">// code removed for clarity</span>
    <span class="nx">addNotification</span><span class="p">(</span><span class="nx">message</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="k">void</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">userNotifications</span><span class="p">.</span><span class="nx">addNotification</span><span class="p">(</span><span class="nx">message</span><span class="p">)</span>
    <span class="p">}</span>

    <span class="nx">getNotifications</span><span class="p">():</span> <span class="kr">string</span><span class="p">[]</span> <span class="p">{</span>
        <span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">userNotifications</span><span class="p">.</span><span class="nx">getNotifications</span><span class="p">()</span>
    <span class="p">}</span>

    <span class="nx">clearNotifications</span><span class="p">():</span> <span class="k">void</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">userNotifications</span><span class="p">.</span><span class="nx">clearNotifications</span><span class="p">()</span>
    <span class="p">}</span>

    <span class="c1">// Code removed for clarity</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Como se puede apreciar, <code class="language-plaintext highlighter-rouge">UserAccount</code> se va haciendo cada vez más pequeña. Aunque mantiene sus responsabilidades, sus métodos se limitan a delegar en las clases que las implementan.</p>

<h4 id="roles-y-permisos">Roles y permisos</h4>

<p>Seguimos el mismo procedimiento para extraer la clase <code class="language-plaintext highlighter-rouge">UserRoles</code>.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">interface</span> <span class="nx">ManagesRoles</span> <span class="p">{</span>
    <span class="nx">promoteToAdmin</span><span class="p">():</span> <span class="k">void</span>
    <span class="nx">revokeAdmin</span><span class="p">():</span> <span class="k">void</span>
    <span class="nx">canDoAnything</span><span class="p">():</span> <span class="nx">boolean</span>
<span class="p">}</span>
</code></pre></div></div>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">class</span> <span class="nx">UserAccount</span> <span class="k">implements</span> <span class="nx">ManagesAuthentication</span><span class="p">,</span> <span class="nx">ManagesProfile</span><span class="p">,</span> <span class="nx">ManagesNotifications</span><span class="p">,</span> <span class="nx">ManagesRoles</span> <span class="p">{</span>
    <span class="c1">// Code removed for clarity</span>
<span class="p">}</span>
</code></pre></div></div>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">describe</span><span class="p">(</span><span class="dl">'</span><span class="s1">Manages roles</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="nx">describe</span><span class="p">(</span><span class="dl">'</span><span class="s1">When a user is a common user</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
        <span class="kd">const</span> <span class="nx">user</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">UserAccount</span><span class="p">(</span><span class="dl">'</span><span class="s1">John Doe</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">john@example.com</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">secret</span><span class="dl">'</span><span class="p">,</span> <span class="kc">false</span><span class="p">)</span>

        <span class="nx">it</span><span class="p">(</span><span class="dl">'</span><span class="s1">should be able to be promoted</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
            <span class="nx">user</span><span class="p">.</span><span class="nx">promoteToAdmin</span><span class="p">()</span>
            <span class="nx">expect</span><span class="p">(</span><span class="nx">user</span><span class="p">.</span><span class="nx">canDoAnything</span><span class="p">()).</span><span class="nx">toEqual</span><span class="p">(</span><span class="kc">true</span><span class="p">)</span>
        <span class="p">})</span>

        <span class="nx">it</span><span class="p">(</span><span class="dl">'</span><span class="s1">should be able to be revoked</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
            <span class="nx">user</span><span class="p">.</span><span class="nx">revokeAdmin</span><span class="p">()</span>
            <span class="nx">expect</span><span class="p">(</span><span class="nx">user</span><span class="p">.</span><span class="nx">canDoAnything</span><span class="p">()).</span><span class="nx">toEqual</span><span class="p">(</span><span class="kc">false</span><span class="p">)</span>
        <span class="p">});</span>

    <span class="p">})</span>
<span class="p">})</span>
</code></pre></div></div>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">class</span> <span class="nx">UserRoles</span> <span class="k">implements</span> <span class="nx">ManagesRoles</span> <span class="p">{</span>
    <span class="k">private</span> <span class="nx">isAdmin</span><span class="p">:</span> <span class="nx">boolean</span>

    <span class="kd">constructor</span><span class="p">(</span><span class="nx">isAdmin</span><span class="p">:</span> <span class="nx">boolean</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">isAdmin</span> <span class="o">=</span> <span class="nx">isAdmin</span>
    <span class="p">}</span>

    <span class="nx">promoteToAdmin</span><span class="p">():</span> <span class="k">void</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">isAdmin</span> <span class="o">=</span> <span class="kc">true</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Usuario promovido a administrador</span><span class="dl">'</span><span class="p">)</span>
    <span class="p">}</span>

    <span class="nx">revokeAdmin</span><span class="p">():</span> <span class="k">void</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">isAdmin</span> <span class="o">=</span> <span class="kc">false</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Usuario revocado de administrador</span><span class="dl">'</span><span class="p">)</span>
    <span class="p">}</span>

    <span class="nx">canDoAnything</span><span class="p">():</span> <span class="nx">boolean</span> <span class="p">{</span>
        <span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">isAdmin</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Una vez aplicados todos los cambios, así es como queda <code class="language-plaintext highlighter-rouge">UserAccount</code>. Podemos ver que, aunque sigue ejerciendo todas las responsabilidades, en realidad simplemente agrega varios colaboradores especializados que las ejecutan. De este modo, mantenemos el punto de entrada que usaban los consumidores de <code class="language-plaintext highlighter-rouge">UserAccount</code>. Dependiendo de nuestro caso, podemos empezar incluso a sustituir <code class="language-plaintext highlighter-rouge">UserAccount</code> por <code class="language-plaintext highlighter-rouge">AuthenticateUser</code>, <code class="language-plaintext highlighter-rouge">UserProfile</code>, <code class="language-plaintext highlighter-rouge">UserNotifications</code> o <code class="language-plaintext highlighter-rouge">UserRoles</code>, cambiando lo necesario para depender de las interfaces.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">class</span> <span class="nx">UserAccount</span> <span class="k">implements</span> <span class="nx">ManagesAuthentication</span><span class="p">,</span> <span class="nx">ManagesProfile</span><span class="p">,</span> <span class="nx">ManagesNotifications</span><span class="p">,</span> <span class="nx">ManagesRoles</span> <span class="p">{</span>
  <span class="k">private</span> <span class="nx">authenticator</span><span class="p">:</span> <span class="nx">ManagesAuthentication</span>
  <span class="k">private</span> <span class="nx">userProfile</span><span class="p">:</span> <span class="nx">ManagesProfile</span>
  <span class="k">private</span> <span class="nx">userNotifications</span><span class="p">:</span> <span class="nx">ManagesNotifications</span>
  <span class="k">private</span> <span class="nx">userRoles</span><span class="p">:</span> <span class="nx">ManagesRoles</span>

  <span class="kd">constructor</span><span class="p">(</span><span class="nx">name</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">email</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">password</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">isAdmin</span><span class="p">:</span> <span class="nx">boolean</span> <span class="o">=</span> <span class="kc">false</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">authenticator</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">AuthenticateUser</span><span class="p">(</span><span class="nx">password</span><span class="p">)</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">userProfile</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">UserProfile</span><span class="p">(</span><span class="nx">name</span><span class="p">,</span> <span class="nx">email</span><span class="p">)</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">userNotifications</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">UserNotifications</span><span class="p">()</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">userRoles</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">UserRoles</span><span class="p">(</span><span class="nx">isAdmin</span><span class="p">)</span>
  <span class="p">}</span>

  <span class="c1">// --- Autenticación ---</span>
  <span class="nx">login</span><span class="p">(</span><span class="nx">password</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="nx">boolean</span> <span class="p">{</span>
    <span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">authenticator</span><span class="p">.</span><span class="nx">login</span><span class="p">(</span><span class="nx">password</span><span class="p">)</span>
  <span class="p">}</span>

  <span class="nx">resetPassword</span><span class="p">(</span><span class="nx">newPassword</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="k">void</span> <span class="p">{</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">authenticator</span><span class="p">.</span><span class="nx">resetPassword</span><span class="p">(</span><span class="nx">newPassword</span><span class="p">)</span>
  <span class="p">}</span>

  <span class="c1">// --- Perfil ---</span>
  <span class="nx">updateEmail</span><span class="p">(</span><span class="nx">newEmail</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="k">void</span> <span class="p">{</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">userProfile</span><span class="p">.</span><span class="nx">updateEmail</span><span class="p">(</span><span class="nx">newEmail</span><span class="p">)</span>
  <span class="p">}</span>

  <span class="nx">updateName</span><span class="p">(</span><span class="nx">newName</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="k">void</span> <span class="p">{</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">userProfile</span><span class="p">.</span><span class="nx">updateName</span><span class="p">(</span><span class="nx">newName</span><span class="p">)</span>
  <span class="p">}</span>

  <span class="nx">profile</span><span class="p">():</span> <span class="kr">string</span> <span class="p">{</span>
    <span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">userProfile</span><span class="p">.</span><span class="nx">profile</span><span class="p">()</span>
  <span class="p">}</span>
  <span class="c1">// --- Notificaciones ---</span>

  <span class="nx">addNotification</span><span class="p">(</span><span class="nx">message</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="k">void</span> <span class="p">{</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">userNotifications</span><span class="p">.</span><span class="nx">addNotification</span><span class="p">(</span><span class="nx">message</span><span class="p">)</span>
  <span class="p">}</span>

  <span class="nx">getNotifications</span><span class="p">():</span> <span class="kr">string</span><span class="p">[]</span> <span class="p">{</span>
    <span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">userNotifications</span><span class="p">.</span><span class="nx">getNotifications</span><span class="p">()</span>
  <span class="p">}</span>

  <span class="nx">clearNotifications</span><span class="p">():</span> <span class="k">void</span> <span class="p">{</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">userNotifications</span><span class="p">.</span><span class="nx">clearNotifications</span><span class="p">()</span>
  <span class="p">}</span>

  <span class="c1">// --- Administración ---</span>

  <span class="nx">promoteToAdmin</span><span class="p">():</span> <span class="k">void</span> <span class="p">{</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">userRoles</span><span class="p">.</span><span class="nx">promoteToAdmin</span><span class="p">()</span>
  <span class="p">}</span>

  <span class="nx">revokeAdmin</span><span class="p">():</span> <span class="k">void</span> <span class="p">{</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">userRoles</span><span class="p">.</span><span class="nx">revokeAdmin</span><span class="p">()</span>
  <span class="p">}</span>

  <span class="nx">canDoAnything</span><span class="p">():</span> <span class="nx">boolean</span> <span class="p">{</span>
    <span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">userRoles</span><span class="p">.</span><span class="nx">canDoAnything</span><span class="p">()</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="resolviendo-el-ejercicio">Resolviendo el ejercicio</h3>

<p>Con todo el proceso anterior hemos eliminado, o al menos puesto bajo control, el code smell <em>Large Class</em> que padecía <code class="language-plaintext highlighter-rouge">UserAccount</code>. Ahora sería el momento de modificar el comportamiento para introducir las nuevas prestaciones. La ventaja que tenemos ahora es que las responsabilidades están separadas y podemos intervenir en ellas de forma independiente.</p>

<h4 id="autenticación-de-doble-factor">Autenticación de doble factor</h4>

<p>La autenticación de doble factor funciona más o menos de la siguiente manera:</p>

<ul>
  <li>El usuario hace login, este login no es definitivo y tiene que confirmarse</li>
  <li>Se genera un código de un solo uso que se notifica al usuario</li>
  <li>El usuario envia de vuelta a este código confirmando el login</li>
</ul>

<p>La idea es que la aplicación genera un código de un solo uso que solo el usuario real podría conocer porque se lo comunicamos por un medio que asumimos está bajo su control: ya sea con un mensaje directo o a través de una aplicación específica.</p>

<p>Sin entrar en muchos detalles, ya que no es el propósito del ejercicio, tenemos que tener en cuenta que si queremos introducir el segundo factor, el login no se produce cuando se detecta que la contraseña aportada coincide con la esperada, sino que se precisa una confirmación.</p>

<p>Nos convendría arreglar algunas cosas en <code class="language-plaintext highlighter-rouge">AuthenticateUser</code> antes de empezar. El código está un poco sucio y, a decir verdad, el método login no debería devolver nada. En su lugar, necesitamos un método que nos permita saber si el usuario está autenticado o no.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">interface</span> <span class="nx">ManagesAuthentication</span> <span class="p">{</span>
    <span class="nx">login</span><span class="p">(</span><span class="nx">password</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="nx">boolean</span><span class="p">;</span>
    <span class="nx">resetPassword</span><span class="p">(</span><span class="nx">newPassword</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="k">void</span><span class="p">;</span>
    <span class="nx">isLoggedIn</span><span class="p">():</span> <span class="nx">boolean</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Para ello, deben cambiarse los tests.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">describe</span><span class="p">(</span><span class="dl">'</span><span class="s1">Manages Authentication</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="nx">describe</span><span class="p">(</span><span class="dl">'</span><span class="s1">When the user tries to authenticate</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
        <span class="kd">const</span> <span class="nx">user</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">UserAccount</span><span class="p">(</span><span class="dl">'</span><span class="s1">John Doe</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">john@example.com</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">secret</span><span class="dl">'</span><span class="p">,</span> <span class="kc">false</span><span class="p">)</span>

        <span class="nx">it</span><span class="p">(</span><span class="dl">'</span><span class="s1">should authenticate with the correct password</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
            <span class="nx">user</span><span class="p">.</span><span class="nx">login</span><span class="p">(</span><span class="dl">'</span><span class="s1">secret</span><span class="dl">'</span><span class="p">)</span>
            <span class="nx">expect</span><span class="p">(</span><span class="nx">user</span><span class="p">.</span><span class="nx">isLoggedIn</span><span class="p">()).</span><span class="nx">toEqual</span><span class="p">(</span><span class="kc">true</span><span class="p">)</span>
        <span class="p">})</span>

        <span class="nx">it</span><span class="p">(</span><span class="dl">'</span><span class="s1">should not authenticate with the wrong password</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
            <span class="nx">user</span><span class="p">.</span><span class="nx">login</span><span class="p">(</span><span class="dl">'</span><span class="s1">wrong</span><span class="dl">'</span><span class="p">)</span>
            <span class="nx">expect</span><span class="p">(</span><span class="nx">user</span><span class="p">.</span><span class="nx">isLoggedIn</span><span class="p">()).</span><span class="nx">toEqual</span><span class="p">(</span><span class="kc">false</span><span class="p">)</span>
        <span class="p">})</span>
    <span class="p">})</span>

    <span class="nx">describe</span><span class="p">(</span><span class="dl">'</span><span class="s1">Reset password</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
        <span class="nx">describe</span><span class="p">(</span><span class="dl">'</span><span class="s1">When the user resets the password</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
            <span class="kd">const</span> <span class="nx">user</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">UserAccount</span><span class="p">(</span><span class="dl">'</span><span class="s1">John Doe</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">john@example.com</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">secret</span><span class="dl">'</span><span class="p">,</span> <span class="kc">false</span><span class="p">)</span>
            <span class="nx">user</span><span class="p">.</span><span class="nx">resetPassword</span><span class="p">(</span><span class="dl">'</span><span class="s1">new-secret</span><span class="dl">'</span><span class="p">)</span>
            <span class="nx">it</span><span class="p">(</span><span class="dl">'</span><span class="s1">should not authenticate with the old one</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
                <span class="nx">user</span><span class="p">.</span><span class="nx">login</span><span class="p">(</span><span class="dl">'</span><span class="s1">secret</span><span class="dl">'</span><span class="p">)</span>
                <span class="nx">expect</span><span class="p">(</span><span class="nx">user</span><span class="p">.</span><span class="nx">isLoggedIn</span><span class="p">()).</span><span class="nx">toEqual</span><span class="p">(</span><span class="kc">false</span><span class="p">)</span>
            <span class="p">})</span>

            <span class="nx">it</span><span class="p">(</span><span class="dl">'</span><span class="s1">should authenticate with the new one</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
                <span class="nx">user</span><span class="p">.</span><span class="nx">login</span><span class="p">(</span><span class="dl">'</span><span class="s1">new-secret</span><span class="dl">'</span><span class="p">)</span>
                <span class="nx">expect</span><span class="p">(</span><span class="nx">user</span><span class="p">.</span><span class="nx">isLoggedIn</span><span class="p">()).</span><span class="nx">toEqual</span><span class="p">(</span><span class="kc">true</span><span class="p">)</span>
            <span class="p">})</span>
        <span class="p">})</span>
    <span class="p">})</span>
<span class="p">})</span>
</code></pre></div></div>

<p>Y este es el código arreglado:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">class</span> <span class="nx">AuthenticateUser</span> <span class="k">implements</span> <span class="nx">ManagesAuthentication</span> <span class="p">{</span>
    <span class="k">private</span> <span class="nx">password</span><span class="p">:</span> <span class="kr">string</span>
    <span class="k">private</span> <span class="nx">lastLogin</span><span class="p">:</span> <span class="nb">Date</span> <span class="o">|</span> <span class="kc">undefined</span>
    <span class="k">private</span> <span class="nx">loginAttempts</span><span class="p">:</span> <span class="kr">number</span> <span class="o">=</span> <span class="mi">0</span>

    <span class="kd">constructor</span><span class="p">(</span><span class="nx">password</span><span class="p">:</span> <span class="kr">string</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">password</span> <span class="o">=</span> <span class="nx">password</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="nx">login</span><span class="p">(</span><span class="nx">password</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="k">void</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">password</span> <span class="o">===</span> <span class="nx">password</span><span class="p">)</span> <span class="p">{</span>
            <span class="k">this</span><span class="p">.</span><span class="nx">successfullyLoggedIn</span><span class="p">();</span>
        <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
            <span class="k">this</span><span class="p">.</span><span class="nx">failedLogin</span><span class="p">();</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="nx">isLoggedIn</span><span class="p">():</span> <span class="nx">boolean</span> <span class="p">{</span>
        <span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">lastLogin</span> <span class="o">!==</span> <span class="kc">undefined</span>
    <span class="p">}</span>

    <span class="nx">resetPassword</span><span class="p">(</span><span class="nx">newPassword</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="k">void</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">password</span> <span class="o">=</span> <span class="nx">newPassword</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Contraseña actualizada</span><span class="dl">'</span><span class="p">)</span>
    <span class="p">}</span>

    <span class="k">private</span> <span class="nx">successfullyLoggedIn</span><span class="p">()</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">lastLogin</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Date</span><span class="p">()</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">loginAttempts</span> <span class="o">=</span> <span class="mi">0</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Inicio de sesión exitoso</span><span class="dl">'</span><span class="p">)</span>
    <span class="p">}</span>

    <span class="k">private</span> <span class="nx">failedLogin</span><span class="p">()</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">lastLogin</span> <span class="o">=</span> <span class="kc">undefined</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">loginAttempts</span><span class="o">++</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Contraseña incorrecta</span><span class="dl">'</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Veamos ahora como introducir el segundo factor en la autenticación. Los cambios que tenemos que implementar son los siguientes:</p>

<ul>
  <li>Si la contraseña es correcta, se debe generar una contraseña de un solo uso (OTP) y comunicarla al usuario. Para esto necesitaremos una forma de generar la OTP y de notificarla al usuario. Esto no requiere cambios de interface.</li>
  <li>Necesitamos un método para validar esta contraseña de un solo uso e invocaremos el método <code class="language-plaintext highlighter-rouge">successfullyLoggedIn</code> en caso de que sea correcta. Esto requiere introducir un nuevo método en la interface.</li>
</ul>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">interface</span> <span class="nx">ManagesAuthentication</span> <span class="p">{</span>
    <span class="nx">login</span><span class="p">(</span><span class="nx">password</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="k">void</span>
    <span class="nx">otpLogin</span><span class="p">(</span><span class="nx">otp</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="k">void</span>
    <span class="nx">resetPassword</span><span class="p">(</span><span class="nx">newPassword</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="k">void</span>
    <span class="nx">isLoggedIn</span><span class="p">():</span> <span class="nx">boolean</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Esta parte me molesta un poco porque genera un caso de <em>Refused Bequest</em>, ya que en la modalidad de autenticación simple no necesitamos este nuevo método.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">class</span> <span class="nx">AuthenticateUser</span> <span class="k">implements</span> <span class="nx">ManagesAuthentication</span> <span class="p">{</span>
    <span class="k">private</span> <span class="nx">password</span><span class="p">:</span> <span class="kr">string</span>
    <span class="k">private</span> <span class="nx">lastLogin</span><span class="p">:</span> <span class="nb">Date</span> <span class="o">|</span> <span class="kc">undefined</span>
    <span class="k">private</span> <span class="nx">loginAttempts</span><span class="p">:</span> <span class="kr">number</span> <span class="o">=</span> <span class="mi">0</span>

    <span class="kd">constructor</span><span class="p">(</span><span class="nx">password</span><span class="p">:</span> <span class="kr">string</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">password</span> <span class="o">=</span> <span class="nx">password</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="nx">login</span><span class="p">(</span><span class="nx">password</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="k">void</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">password</span> <span class="o">===</span> <span class="nx">password</span><span class="p">)</span> <span class="p">{</span>
            <span class="k">this</span><span class="p">.</span><span class="nx">successfullyLoggedIn</span><span class="p">();</span>
        <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
            <span class="k">this</span><span class="p">.</span><span class="nx">failedLogin</span><span class="p">();</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="nx">otpLogin</span><span class="p">(</span><span class="nx">_otp</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="k">void</span> <span class="p">{</span>
        <span class="k">return</span>
    <span class="p">}</span>

    <span class="nx">isLoggedIn</span><span class="p">():</span> <span class="nx">boolean</span> <span class="p">{</span>
        <span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">lastLogin</span> <span class="o">!==</span> <span class="kc">undefined</span>
    <span class="p">}</span>

    <span class="nx">resetPassword</span><span class="p">(</span><span class="nx">newPassword</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="k">void</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">password</span> <span class="o">=</span> <span class="nx">newPassword</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Contraseña actualizada</span><span class="dl">'</span><span class="p">)</span>
    <span class="p">}</span>

    <span class="k">private</span> <span class="nx">successfullyLoggedIn</span><span class="p">()</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">lastLogin</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Date</span><span class="p">()</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">loginAttempts</span> <span class="o">=</span> <span class="mi">0</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Inicio de sesión exitoso</span><span class="dl">'</span><span class="p">)</span>
    <span class="p">}</span>

    <span class="k">private</span> <span class="nx">failedLogin</span><span class="p">()</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">lastLogin</span> <span class="o">=</span> <span class="kc">undefined</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">loginAttempts</span><span class="o">++</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Contraseña incorrecta</span><span class="dl">'</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>En principio, creo que lo mejor sería introducir una nueva implementación de la interfaz <code class="language-plaintext highlighter-rouge">ManagesAuthentication</code>. Introduzcamos un test específico de <code class="language-plaintext highlighter-rouge">ManagesAuthentication</code> de modo que <code class="language-plaintext highlighter-rouge">AuthenticateUser</code> pueda pasarlo. Como invocar otpLogin no tiene efectos en esta implementación el test pasará.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">describe</span><span class="p">(</span><span class="dl">'</span><span class="s1">Manages Authentication with 2nd Factor</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="nx">describe</span><span class="p">(</span><span class="dl">'</span><span class="s1">When the user tries to authenticate</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
        <span class="kd">const</span> <span class="nx">user</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">AuthenticateUser</span><span class="p">(</span><span class="dl">'</span><span class="s1">secret</span><span class="dl">'</span><span class="p">)</span>

        <span class="nx">it</span><span class="p">(</span><span class="dl">'</span><span class="s1">should authenticate with the correct password</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
            <span class="nx">user</span><span class="p">.</span><span class="nx">login</span><span class="p">(</span><span class="dl">'</span><span class="s1">secret</span><span class="dl">'</span><span class="p">)</span>
            <span class="nx">user</span><span class="p">.</span><span class="nx">otpLogin</span><span class="p">(</span><span class="dl">'</span><span class="s1">123456</span><span class="dl">'</span><span class="p">)</span>
            <span class="nx">expect</span><span class="p">(</span><span class="nx">user</span><span class="p">.</span><span class="nx">isLoggedIn</span><span class="p">()).</span><span class="nx">toEqual</span><span class="p">(</span><span class="kc">true</span><span class="p">)</span>
        <span class="p">})</span>

        <span class="nx">it</span><span class="p">(</span><span class="dl">'</span><span class="s1">should not authenticate with the wrong password</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
            <span class="nx">user</span><span class="p">.</span><span class="nx">login</span><span class="p">(</span><span class="dl">'</span><span class="s1">wrong</span><span class="dl">'</span><span class="p">)</span>
            <span class="nx">expect</span><span class="p">(</span><span class="nx">user</span><span class="p">.</span><span class="nx">isLoggedIn</span><span class="p">()).</span><span class="nx">toEqual</span><span class="p">(</span><span class="kc">false</span><span class="p">)</span>
        <span class="p">})</span>
    <span class="p">})</span>
<span class="p">})</span>
</code></pre></div></div>

<p>Hagamos algo similar con una implementación alternativa, que sí verifica el segundo factor.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">describe</span><span class="p">(</span><span class="dl">'</span><span class="s1">Manages Authentication with 2nd Factor</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="nx">describe</span><span class="p">(</span><span class="dl">'</span><span class="s1">When the user tries to authenticate</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
        <span class="kd">const</span> <span class="nx">user</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">AuthenticateUserWithTwoFactor</span><span class="p">(</span><span class="dl">'</span><span class="s1">secret</span><span class="dl">'</span><span class="p">)</span>

        <span class="nx">it</span><span class="p">(</span><span class="dl">'</span><span class="s1">should authenticate with the correct password</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
            <span class="nx">user</span><span class="p">.</span><span class="nx">login</span><span class="p">(</span><span class="dl">'</span><span class="s1">secret</span><span class="dl">'</span><span class="p">)</span>
            <span class="nx">user</span><span class="p">.</span><span class="nx">otpLogin</span><span class="p">(</span><span class="dl">'</span><span class="s1">123456</span><span class="dl">'</span><span class="p">)</span>
            <span class="nx">expect</span><span class="p">(</span><span class="nx">user</span><span class="p">.</span><span class="nx">isLoggedIn</span><span class="p">()).</span><span class="nx">toEqual</span><span class="p">(</span><span class="kc">true</span><span class="p">)</span>
        <span class="p">})</span>

        <span class="nx">it</span><span class="p">(</span><span class="dl">'</span><span class="s1">should not authenticate with the wrong password</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
            <span class="nx">user</span><span class="p">.</span><span class="nx">login</span><span class="p">(</span><span class="dl">'</span><span class="s1">wrong</span><span class="dl">'</span><span class="p">)</span>
            <span class="nx">expect</span><span class="p">(</span><span class="nx">user</span><span class="p">.</span><span class="nx">isLoggedIn</span><span class="p">()).</span><span class="nx">toEqual</span><span class="p">(</span><span class="kc">false</span><span class="p">)</span>
        <span class="p">})</span>

        <span class="nx">it</span><span class="p">(</span><span class="dl">'</span><span class="s1">should not authenticate with the wrong otp</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
            <span class="nx">user</span><span class="p">.</span><span class="nx">login</span><span class="p">(</span><span class="dl">'</span><span class="s1">secret</span><span class="dl">'</span><span class="p">)</span>
            <span class="nx">user</span><span class="p">.</span><span class="nx">otpLogin</span><span class="p">(</span><span class="dl">'</span><span class="s1">wrong</span><span class="dl">'</span><span class="p">)</span>
            <span class="nx">expect</span><span class="p">(</span><span class="nx">user</span><span class="p">.</span><span class="nx">isLoggedIn</span><span class="p">()).</span><span class="nx">toEqual</span><span class="p">(</span><span class="kc">false</span><span class="p">)</span>
        <span class="p">})</span>
    <span class="p">})</span>
<span class="p">})</span>

</code></pre></div></div>

<p>En principio, tendrá la misma funcionalidad que <code class="language-plaintext highlighter-rouge">AuthenticateUser</code>, pero con una interfaz diferente.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">class</span> <span class="nx">AuthenticateUserWithTwoFactor</span> <span class="k">implements</span> <span class="nx">ManagesAuthentication</span> <span class="p">{</span>
    <span class="k">private</span> <span class="nx">password</span><span class="p">:</span> <span class="kr">string</span>
    <span class="k">private</span> <span class="nx">lastLogin</span><span class="p">:</span> <span class="nb">Date</span> <span class="o">|</span> <span class="kc">undefined</span>
    <span class="k">private</span> <span class="nx">loginAttempts</span><span class="p">:</span> <span class="kr">number</span> <span class="o">=</span> <span class="mi">0</span>

    <span class="kd">constructor</span><span class="p">(</span><span class="nx">password</span><span class="p">:</span> <span class="kr">string</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">password</span> <span class="o">=</span> <span class="nx">password</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="nx">login</span><span class="p">(</span><span class="nx">password</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="k">void</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">password</span> <span class="o">===</span> <span class="nx">password</span><span class="p">)</span> <span class="p">{</span>
            <span class="k">this</span><span class="p">.</span><span class="nx">successfullyLoggedIn</span><span class="p">();</span>
        <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
            <span class="k">this</span><span class="p">.</span><span class="nx">failedLogin</span><span class="p">();</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="nx">otpLogin</span><span class="p">(</span><span class="nx">_otp</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="k">void</span> <span class="p">{</span>
        <span class="k">return</span>
    <span class="p">}</span>

    <span class="nx">isLoggedIn</span><span class="p">():</span> <span class="nx">boolean</span> <span class="p">{</span>
        <span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">lastLogin</span> <span class="o">!==</span> <span class="kc">undefined</span>
    <span class="p">}</span>

    <span class="nx">resetPassword</span><span class="p">(</span><span class="nx">newPassword</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="k">void</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">password</span> <span class="o">=</span> <span class="nx">newPassword</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Contraseña actualizada</span><span class="dl">'</span><span class="p">)</span>
    <span class="p">}</span>

    <span class="k">private</span> <span class="nx">successfullyLoggedIn</span><span class="p">()</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">lastLogin</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Date</span><span class="p">()</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">loginAttempts</span> <span class="o">=</span> <span class="mi">0</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Inicio de sesión exitoso</span><span class="dl">'</span><span class="p">)</span>
    <span class="p">}</span>

    <span class="k">private</span> <span class="nx">failedLogin</span><span class="p">()</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">lastLogin</span> <span class="o">=</span> <span class="kc">undefined</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">loginAttempts</span><span class="o">++</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Contraseña incorrecta</span><span class="dl">'</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Hagamos pasar el test:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">class</span> <span class="nx">AuthenticateUserWithTwoFactor</span> <span class="k">implements</span> <span class="nx">ManagesAuthentication</span> <span class="p">{</span>
    <span class="k">private</span> <span class="nx">password</span><span class="p">:</span> <span class="kr">string</span>
    <span class="k">private</span> <span class="nx">otp</span><span class="p">:</span> <span class="kr">string</span>
    <span class="k">private</span> <span class="nx">lastLogin</span><span class="p">:</span> <span class="nb">Date</span> <span class="o">|</span> <span class="kc">undefined</span>
    <span class="k">private</span> <span class="nx">loginAttempts</span><span class="p">:</span> <span class="kr">number</span> <span class="o">=</span> <span class="mi">0</span>

    <span class="kd">constructor</span><span class="p">(</span><span class="nx">password</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">otp</span><span class="p">:</span> <span class="kr">string</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">password</span> <span class="o">=</span> <span class="nx">password</span><span class="p">;</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">otp</span> <span class="o">=</span> <span class="nx">otp</span>
    <span class="p">}</span>

    <span class="nx">login</span><span class="p">(</span><span class="nx">password</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="k">void</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">password</span> <span class="o">===</span> <span class="nx">password</span><span class="p">)</span> <span class="p">{</span>
            <span class="k">this</span><span class="p">.</span><span class="nx">successfullyLoggedIn</span><span class="p">();</span>
        <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
            <span class="k">this</span><span class="p">.</span><span class="nx">failedLogin</span><span class="p">();</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="nx">otpLogin</span><span class="p">(</span><span class="nx">otp</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="k">void</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">otp</span> <span class="o">===</span> <span class="nx">otp</span><span class="p">)</span> <span class="p">{</span>
            <span class="k">this</span><span class="p">.</span><span class="nx">successfullyLoggedIn</span><span class="p">();</span>
        <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
            <span class="k">this</span><span class="p">.</span><span class="nx">failedLogin</span><span class="p">();</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="nx">isLoggedIn</span><span class="p">():</span> <span class="nx">boolean</span> <span class="p">{</span>
        <span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">lastLogin</span> <span class="o">!==</span> <span class="kc">undefined</span>
    <span class="p">}</span>

    <span class="nx">resetPassword</span><span class="p">(</span><span class="nx">newPassword</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="k">void</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">password</span> <span class="o">=</span> <span class="nx">newPassword</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Contraseña actualizada</span><span class="dl">'</span><span class="p">)</span>
    <span class="p">}</span>

    <span class="k">private</span> <span class="nx">successfullyLoggedIn</span><span class="p">()</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">lastLogin</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Date</span><span class="p">()</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">loginAttempts</span> <span class="o">=</span> <span class="mi">0</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Inicio de sesión exitoso</span><span class="dl">'</span><span class="p">)</span>
    <span class="p">}</span>

    <span class="k">private</span> <span class="nx">failedLogin</span><span class="p">()</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">lastLogin</span> <span class="o">=</span> <span class="kc">undefined</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">loginAttempts</span><span class="o">++</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Contraseña incorrecta</span><span class="dl">'</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Esto hace pasar el test. Está claro que <code class="language-plaintext highlighter-rouge">AuthenticateUserWithTwoFactor</code> debería recibir la OTP de algún sitio. En primer lugar, esta contraseña se genera y se comunica si el usuario se ha identificado correctamente. Por otro lado, la OTP proporcionada por el usuario se recibirá en otro momento.</p>

<p>Una posibilidad es introducir un servicio que se encargue de generar la OTP, notificarla y guardarla hasta que el usuario la confirme.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">interface</span> <span class="nx">ManagesOTP</span> <span class="p">{</span>
    <span class="nx">generate</span><span class="p">(</span><span class="nx">user</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="kr">string</span>
    <span class="nx">verify</span><span class="p">(</span><span class="nx">user</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">otp</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="nx">boolean</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Esto me lleva a pensar que a lo mejor necesito otra forma de modelar las credenciales del usuario, ya que voy a necesitar conocer su email para obtener la OTP.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nx">UserCredentials</span> <span class="p">{</span>
    <span class="k">private</span> <span class="nx">email</span><span class="p">:</span> <span class="kr">string</span>
    <span class="k">private</span> <span class="nx">password</span><span class="p">:</span> <span class="kr">string</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Así que hay que hacer algunos cambios en <code class="language-plaintext highlighter-rouge">UserAccount</code>:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">class</span> <span class="nx">UserAccount</span>
    <span class="k">implements</span> <span class="nx">ManagesAuthentication</span><span class="p">,</span> <span class="nx">ManagesProfile</span><span class="p">,</span> <span class="nx">ManagesNotifications</span><span class="p">,</span> <span class="nx">ManagesRoles</span> <span class="p">{</span>
    <span class="k">private</span> <span class="nx">userCredentials</span><span class="p">:</span> <span class="nx">UserCredentials</span>
    <span class="k">private</span> <span class="nx">authenticator</span><span class="p">:</span> <span class="nx">ManagesAuthentication</span>
    <span class="k">private</span> <span class="nx">userProfile</span><span class="p">:</span> <span class="nx">ManagesProfile</span>
    <span class="k">private</span> <span class="nx">userNotifications</span><span class="p">:</span> <span class="nx">ManagesNotifications</span>
    <span class="k">private</span> <span class="nx">userRoles</span><span class="p">:</span> <span class="nx">ManagesRoles</span>

    <span class="kd">constructor</span><span class="p">(</span><span class="nx">name</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">email</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">password</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">isAdmin</span><span class="p">:</span> <span class="nx">boolean</span> <span class="o">=</span> <span class="kc">false</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">userCredentials</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">UserCredentials</span><span class="p">(</span><span class="nx">email</span><span class="p">,</span> <span class="nx">password</span><span class="p">)</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">authenticator</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">AuthenticateUser</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">userCredentials</span><span class="p">)</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">userProfile</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">UserProfile</span><span class="p">(</span><span class="nx">name</span><span class="p">,</span> <span class="nx">email</span><span class="p">)</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">userNotifications</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">UserNotifications</span><span class="p">()</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">userRoles</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">UserRoles</span><span class="p">(</span><span class="nx">isAdmin</span><span class="p">)</span>
    <span class="p">}</span>
    
    <span class="c1">// Code removed for clarity</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Y en <code class="language-plaintext highlighter-rouge">AuthenticateUser</code></p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">class</span> <span class="nx">AuthenticateUser</span> <span class="k">implements</span> <span class="nx">ManagesAuthentication</span> <span class="p">{</span>
  <span class="k">private</span> <span class="nx">credentials</span><span class="p">:</span> <span class="nx">UserCredentials</span>
  <span class="k">private</span> <span class="nx">lastLogin</span><span class="p">:</span> <span class="nb">Date</span> <span class="o">|</span> <span class="kc">undefined</span>
  <span class="k">private</span> <span class="nx">loginAttempts</span><span class="p">:</span> <span class="kr">number</span> <span class="o">=</span> <span class="mi">0</span>

  <span class="kd">constructor</span><span class="p">(</span><span class="nx">credentials</span><span class="p">:</span> <span class="nx">UserCredentials</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">credentials</span> <span class="o">=</span> <span class="nx">credentials</span>
  <span class="p">}</span>

  <span class="nx">login</span><span class="p">(</span><span class="nx">password</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="k">void</span> <span class="p">{</span>
    <span class="k">if</span> <span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">credentials</span><span class="p">.</span><span class="nx">passwordMatch</span><span class="p">(</span><span class="nx">password</span><span class="p">))</span> <span class="p">{</span>
      <span class="k">this</span><span class="p">.</span><span class="nx">successfullyLoggedIn</span><span class="p">()</span>
    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
      <span class="k">this</span><span class="p">.</span><span class="nx">failedLogin</span><span class="p">()</span>
    <span class="p">}</span>
  <span class="p">}</span>

  <span class="nx">otpLogin</span><span class="p">(</span><span class="nx">_otp</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="k">void</span> <span class="p">{</span>
    <span class="k">return</span>
  <span class="p">}</span>

  <span class="nx">isLoggedIn</span><span class="p">():</span> <span class="nx">boolean</span> <span class="p">{</span>
    <span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">lastLogin</span> <span class="o">!==</span> <span class="kc">undefined</span>
  <span class="p">}</span>

  <span class="nx">resetPassword</span><span class="p">(</span><span class="nx">newPassword</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="k">void</span> <span class="p">{</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">credentials</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">credentials</span><span class="p">.</span><span class="nx">reset</span><span class="p">(</span><span class="nx">newPassword</span><span class="p">)</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Contraseña actualizada</span><span class="dl">'</span><span class="p">)</span>
  <span class="p">}</span>

  <span class="k">private</span> <span class="nx">successfullyLoggedIn</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">lastLogin</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Date</span><span class="p">()</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">loginAttempts</span> <span class="o">=</span> <span class="mi">0</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Inicio de sesión exitoso</span><span class="dl">'</span><span class="p">)</span>
  <span class="p">}</span>

  <span class="k">private</span> <span class="nx">failedLogin</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">lastLogin</span> <span class="o">=</span> <span class="kc">undefined</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">loginAttempts</span><span class="o">++</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Contraseña incorrecta</span><span class="dl">'</span><span class="p">)</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Esto va llevando comportamientos a <code class="language-plaintext highlighter-rouge">UserCredentials</code>:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">class</span> <span class="nx">UserCredentials</span> <span class="p">{</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="nx">email</span><span class="p">:</span> <span class="kr">string</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="nx">password</span><span class="p">:</span> <span class="kr">string</span>

    <span class="kd">constructor</span><span class="p">(</span><span class="nx">email</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">password</span><span class="p">:</span> <span class="kr">string</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">email</span> <span class="o">=</span> <span class="nx">email</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">password</span> <span class="o">=</span> <span class="nx">password</span>
    <span class="p">}</span>

    <span class="nx">passwordMatch</span><span class="p">(</span><span class="nx">password</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="nx">boolean</span> <span class="p">{</span>
        <span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">password</span> <span class="o">===</span> <span class="nx">password</span>
    <span class="p">}</span>

    <span class="nx">reset</span><span class="p">(</span><span class="nx">password</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="nx">UserCredentials</span> <span class="p">{</span>
        <span class="k">return</span> <span class="k">new</span> <span class="nx">UserCredentials</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">email</span><span class="p">,</span> <span class="nx">password</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Nos hemos desviado un poco, pero ahora tenemos una forma de que el componente encargado de la autenticación pueda tener información sobre el usuario.</p>

<p>He aquí un test para dirigir el desarrollo de <code class="language-plaintext highlighter-rouge">AuthenticateUserWithTwoFactor</code></p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nx">OTPProviderStub</span> <span class="k">implements</span> <span class="nx">ManagesOTP</span> <span class="p">{</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="nx">otp</span><span class="p">:</span> <span class="kr">string</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="nx">otps</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Map</span><span class="o">&lt;</span><span class="kr">string</span><span class="p">,</span> <span class="kr">string</span><span class="o">&gt;</span><span class="p">()</span>

    <span class="kd">constructor</span><span class="p">(</span><span class="nx">otp</span><span class="p">:</span> <span class="kr">string</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">otp</span> <span class="o">=</span> <span class="nx">otp</span><span class="p">;</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">otps</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Map</span><span class="o">&lt;</span><span class="kr">string</span><span class="p">,</span> <span class="kr">string</span><span class="o">&gt;</span><span class="p">();</span>
    <span class="p">}</span>

    <span class="nx">generate</span><span class="p">(</span><span class="nx">user</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="k">void</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">otps</span><span class="p">.</span><span class="kd">set</span><span class="p">(</span><span class="nx">user</span><span class="p">,</span> <span class="k">this</span><span class="p">.</span><span class="nx">otp</span><span class="p">);</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`OTP: </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">otp</span><span class="p">}</span><span class="s2"> generada para el usuario </span><span class="p">${</span><span class="nx">user</span><span class="p">}</span><span class="s2">`</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="nx">verify</span><span class="p">(</span><span class="nx">user</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">providedOtp</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="nx">boolean</span> <span class="p">{</span>
        <span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">otps</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="nx">user</span><span class="p">)</span> <span class="o">===</span> <span class="nx">providedOtp</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">}</span>


<span class="nx">describe</span><span class="p">(</span><span class="dl">'</span><span class="s1">Manages Authentication with 2nd Factor</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="nx">describe</span><span class="p">(</span><span class="dl">'</span><span class="s1">When the user tries to authenticate</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
        <span class="kd">const</span> <span class="nx">credentials</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">UserCredentials</span><span class="p">(</span><span class="dl">'</span><span class="s1">me@example.com</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">secret</span><span class="dl">'</span><span class="p">)</span>
        <span class="kd">const</span> <span class="nx">otpProvider</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">OTPProviderStub</span><span class="p">(</span><span class="dl">'</span><span class="s1">123456</span><span class="dl">'</span><span class="p">)</span>
        <span class="kd">const</span> <span class="nx">user</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">AuthenticateUserWithTwoFactor</span><span class="p">(</span><span class="nx">credentials</span><span class="p">,</span> <span class="nx">otpProvider</span><span class="p">)</span>

        <span class="nx">it</span><span class="p">(</span><span class="dl">'</span><span class="s1">should authenticate with the correct password</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
            <span class="nx">user</span><span class="p">.</span><span class="nx">login</span><span class="p">(</span><span class="dl">'</span><span class="s1">secret</span><span class="dl">'</span><span class="p">)</span>
            <span class="nx">user</span><span class="p">.</span><span class="nx">otpLogin</span><span class="p">(</span><span class="dl">'</span><span class="s1">123456</span><span class="dl">'</span><span class="p">)</span>
            <span class="nx">expect</span><span class="p">(</span><span class="nx">user</span><span class="p">.</span><span class="nx">isLoggedIn</span><span class="p">()).</span><span class="nx">toEqual</span><span class="p">(</span><span class="kc">true</span><span class="p">)</span>
        <span class="p">})</span>

        <span class="nx">it</span><span class="p">(</span><span class="dl">'</span><span class="s1">should not authenticate with the wrong password</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
            <span class="nx">user</span><span class="p">.</span><span class="nx">login</span><span class="p">(</span><span class="dl">'</span><span class="s1">wrong</span><span class="dl">'</span><span class="p">)</span>
            <span class="nx">expect</span><span class="p">(</span><span class="nx">user</span><span class="p">.</span><span class="nx">isLoggedIn</span><span class="p">()).</span><span class="nx">toEqual</span><span class="p">(</span><span class="kc">false</span><span class="p">)</span>
        <span class="p">})</span>

        <span class="nx">it</span><span class="p">(</span><span class="dl">'</span><span class="s1">should not authenticate with the wrong otp</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
            <span class="nx">user</span><span class="p">.</span><span class="nx">login</span><span class="p">(</span><span class="dl">'</span><span class="s1">secret</span><span class="dl">'</span><span class="p">)</span>
            <span class="nx">user</span><span class="p">.</span><span class="nx">otpLogin</span><span class="p">(</span><span class="dl">'</span><span class="s1">wrong</span><span class="dl">'</span><span class="p">)</span>
            <span class="nx">expect</span><span class="p">(</span><span class="nx">user</span><span class="p">.</span><span class="nx">isLoggedIn</span><span class="p">()).</span><span class="nx">toEqual</span><span class="p">(</span><span class="kc">false</span><span class="p">)</span>
        <span class="p">})</span>
    <span class="p">})</span>
<span class="p">})</span>
</code></pre></div></div>

<p>Así que, después de darle unas vueltas, llego a esto con <code class="language-plaintext highlighter-rouge">AuthenticatorUserWithTwoFactor</code>:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">class</span> <span class="nx">AuthenticateUserWithTwoFactor</span> <span class="k">implements</span> <span class="nx">ManagesAuthentication</span> <span class="p">{</span>
    <span class="k">private</span> <span class="nx">credentials</span><span class="p">:</span> <span class="nx">UserCredentials</span>
    <span class="k">private</span> <span class="nx">otpManager</span><span class="p">:</span> <span class="nx">ManagesOTP</span>
    <span class="k">private</span> <span class="nx">lastLogin</span><span class="p">:</span> <span class="nb">Date</span> <span class="o">|</span> <span class="kc">undefined</span>
    <span class="k">private</span> <span class="nx">loginAttempts</span><span class="p">:</span> <span class="kr">number</span> <span class="o">=</span> <span class="mi">0</span>

    <span class="kd">constructor</span><span class="p">(</span><span class="nx">credentials</span><span class="p">:</span> <span class="nx">UserCredentials</span><span class="p">,</span> <span class="nx">otpManager</span><span class="p">:</span> <span class="nx">ManagesOTP</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">credentials</span> <span class="o">=</span> <span class="nx">credentials</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">otpManager</span> <span class="o">=</span> <span class="nx">otpManager</span>
    <span class="p">}</span>

    <span class="nx">login</span><span class="p">(</span><span class="nx">password</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="k">void</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">credentials</span><span class="p">.</span><span class="nx">passwordMatch</span><span class="p">(</span><span class="nx">password</span><span class="p">))</span> <span class="p">{</span>
            <span class="k">this</span><span class="p">.</span><span class="nx">credentials</span><span class="p">.</span><span class="nx">generateOTP</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">otpManager</span><span class="p">)</span>
        <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
            <span class="k">this</span><span class="p">.</span><span class="nx">failedLogin</span><span class="p">()</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="nx">otpLogin</span><span class="p">(</span><span class="nx">otp</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="k">void</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">credentials</span><span class="p">.</span><span class="nx">verifyOTP</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">otpManager</span><span class="p">,</span> <span class="nx">otp</span><span class="p">))</span> <span class="p">{</span>
            <span class="k">this</span><span class="p">.</span><span class="nx">successfullyLoggedIn</span><span class="p">()</span>
        <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
            <span class="k">this</span><span class="p">.</span><span class="nx">failedLogin</span><span class="p">()</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="nx">isLoggedIn</span><span class="p">():</span> <span class="nx">boolean</span> <span class="p">{</span>
        <span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">lastLogin</span> <span class="o">!==</span> <span class="kc">undefined</span>
    <span class="p">}</span>

    <span class="nx">resetPassword</span><span class="p">(</span><span class="nx">newPassword</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="k">void</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">credentials</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">credentials</span><span class="p">.</span><span class="nx">reset</span><span class="p">(</span><span class="nx">newPassword</span><span class="p">)</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Contraseña actualizada</span><span class="dl">'</span><span class="p">)</span>
    <span class="p">}</span>

    <span class="k">private</span> <span class="nx">successfullyLoggedIn</span><span class="p">()</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">lastLogin</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Date</span><span class="p">()</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">loginAttempts</span> <span class="o">=</span> <span class="mi">0</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Inicio de sesión exitoso</span><span class="dl">'</span><span class="p">)</span>
    <span class="p">}</span>

    <span class="k">private</span> <span class="nx">failedLogin</span><span class="p">()</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">lastLogin</span> <span class="o">=</span> <span class="kc">undefined</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">loginAttempts</span><span class="o">++</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Contraseña incorrecta</span><span class="dl">'</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">UserCredentials</code> sigue atrayendo comportamiento mientras que mantiene la encapsulación gracias al <em>Double Dispatch</em>:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">class</span> <span class="nx">UserCredentials</span> <span class="p">{</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="nx">email</span><span class="p">:</span> <span class="kr">string</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="nx">password</span><span class="p">:</span> <span class="kr">string</span>

    <span class="kd">constructor</span><span class="p">(</span><span class="nx">email</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">password</span><span class="p">:</span> <span class="kr">string</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">email</span> <span class="o">=</span> <span class="nx">email</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">password</span> <span class="o">=</span> <span class="nx">password</span>
    <span class="p">}</span>

    <span class="nx">passwordMatch</span><span class="p">(</span><span class="nx">password</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="nx">boolean</span> <span class="p">{</span>
        <span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">password</span> <span class="o">===</span> <span class="nx">password</span>
    <span class="p">}</span>

    <span class="nx">reset</span><span class="p">(</span><span class="nx">password</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="nx">UserCredentials</span> <span class="p">{</span>
        <span class="k">return</span> <span class="k">new</span> <span class="nx">UserCredentials</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">email</span><span class="p">,</span> <span class="nx">password</span><span class="p">)</span>
    <span class="p">}</span>

    <span class="nx">generateOTP</span><span class="p">(</span><span class="nx">otp</span><span class="p">:</span> <span class="nx">ManagesOTP</span><span class="p">):</span> <span class="k">void</span> <span class="p">{</span>
        <span class="nx">otp</span><span class="p">.</span><span class="nx">generate</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">email</span><span class="p">)</span>
    <span class="p">}</span>

    <span class="nx">verifyOTP</span><span class="p">(</span><span class="nx">otp</span><span class="p">:</span> <span class="nx">ManagesOTP</span><span class="p">,</span> <span class="nx">providedOtp</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span><span class="nx">boolean</span> <span class="p">{</span>
        <span class="k">return</span> <span class="nx">otp</span><span class="p">.</span><span class="nx">verify</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">email</span><span class="p">,</span> <span class="nx">providedOtp</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Se pueden apreciar muchas cosas en el código que vamos generando. Por ejemplo, que resetear la contraseña ya no parece una responsabilidad de <code class="language-plaintext highlighter-rouge">ManagesAuthentication</code>, sino de <code class="language-plaintext highlighter-rouge">UserCredentials</code>. Sin embargo, tal como lo tenemos implementado nos puede generar problemas pues <code class="language-plaintext highlighter-rouge">UserCredentials</code> es inmutable y los autenticadores reciben una instancia específica que tendríamos que actualizar.</p>

<p>A su vez, las clases que implementan <code class="language-plaintext highlighter-rouge">ManagesAuthentication</code> tienen código muy similar y posiblemente podríamos abstraer la funcionalidad común en una clase abstracta y aplicar un patrón <code class="language-plaintext highlighter-rouge">template</code>. De este modo, aseguramos un comportamiento más consistente y eliminamos duplicación innecesaria.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">abstract</span> <span class="kd">class</span> <span class="nx">BaseManagesAuthentication</span> <span class="k">implements</span> <span class="nx">ManagesAuthentication</span> <span class="p">{</span>
    <span class="k">protected</span> <span class="nx">lastLogin</span><span class="p">:</span> <span class="nb">Date</span> <span class="o">|</span> <span class="kc">undefined</span>
    <span class="k">protected</span> <span class="nx">loginAttempts</span><span class="p">:</span> <span class="kr">number</span> <span class="o">=</span> <span class="mi">0</span>
    <span class="k">protected</span> <span class="nx">credentials</span><span class="p">:</span> <span class="nx">UserCredentials</span>

    <span class="k">protected</span> <span class="kd">constructor</span><span class="p">(</span><span class="nx">credentials</span><span class="p">:</span> <span class="nx">UserCredentials</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">credentials</span> <span class="o">=</span> <span class="nx">credentials</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="nx">isLoggedIn</span><span class="p">():</span> <span class="nx">boolean</span> <span class="p">{</span>
        <span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">lastLogin</span> <span class="o">!==</span> <span class="kc">undefined</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="nx">login</span><span class="p">(</span><span class="nx">password</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="k">void</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">credentials</span><span class="p">.</span><span class="nx">passwordMatch</span><span class="p">(</span><span class="nx">password</span><span class="p">))</span> <span class="p">{</span>
            <span class="k">this</span><span class="p">.</span><span class="nx">succeedLogin</span><span class="p">()</span>
        <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
            <span class="k">this</span><span class="p">.</span><span class="nx">failedLogin</span><span class="p">()</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="kd">abstract</span> <span class="nx">succeedLogin</span><span class="p">():</span> <span class="k">void</span>

    <span class="kd">abstract</span> <span class="nx">otpLogin</span><span class="p">(</span><span class="nx">otp</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="k">void</span>

    <span class="nx">resetPassword</span><span class="p">(</span><span class="nx">newPassword</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="k">void</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">credentials</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">credentials</span><span class="p">.</span><span class="nx">reset</span><span class="p">(</span><span class="nx">newPassword</span><span class="p">)</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Contraseña actualizada</span><span class="dl">'</span><span class="p">)</span>
    <span class="p">}</span>

    <span class="k">protected</span> <span class="nx">successfullyLoggedIn</span><span class="p">()</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">lastLogin</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Date</span><span class="p">()</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">loginAttempts</span> <span class="o">=</span> <span class="mi">0</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Inicio de sesión exitoso</span><span class="dl">'</span><span class="p">)</span>
    <span class="p">}</span>

    <span class="k">protected</span> <span class="nx">failedLogin</span><span class="p">()</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">lastLogin</span> <span class="o">=</span> <span class="kc">undefined</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">loginAttempts</span><span class="o">++</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Contraseña incorrecta</span><span class="dl">'</span><span class="p">)</span>
    <span class="p">}</span>

<span class="p">}</span>

<span class="k">export</span> <span class="kd">class</span> <span class="nx">AuthenticateUser</span> <span class="kd">extends</span> <span class="nx">BaseManagesAuthentication</span> <span class="p">{</span>
    <span class="kd">constructor</span><span class="p">(</span><span class="nx">credentials</span><span class="p">:</span> <span class="nx">UserCredentials</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">super</span><span class="p">(</span><span class="nx">credentials</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="nx">succeedLogin</span><span class="p">()</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">successfullyLoggedIn</span><span class="p">()</span>
    <span class="p">}</span>

    <span class="nx">otpLogin</span><span class="p">(</span><span class="nx">_otp</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="k">void</span> <span class="p">{</span>
        <span class="c1">// no op</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="k">export</span> <span class="kd">class</span> <span class="nx">AuthenticateUserWithTwoFactor</span> <span class="kd">extends</span> <span class="nx">BaseManagesAuthentication</span> <span class="p">{</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="nx">otpManager</span><span class="p">:</span> <span class="nx">ManagesOTP</span>

    <span class="kd">constructor</span><span class="p">(</span><span class="nx">credentials</span><span class="p">:</span> <span class="nx">UserCredentials</span><span class="p">,</span> <span class="nx">otpManager</span><span class="p">:</span> <span class="nx">ManagesOTP</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">super</span><span class="p">(</span><span class="nx">credentials</span><span class="p">)</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">otpManager</span> <span class="o">=</span> <span class="nx">otpManager</span>
    <span class="p">}</span>

    <span class="nx">succeedLogin</span><span class="p">():</span> <span class="k">void</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">credentials</span><span class="p">.</span><span class="nx">generateOTP</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">otpManager</span><span class="p">)</span>
    <span class="p">}</span>

    <span class="nx">otpLogin</span><span class="p">(</span><span class="nx">otp</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="k">void</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">credentials</span><span class="p">.</span><span class="nx">verifyOTP</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">otpManager</span><span class="p">,</span> <span class="nx">otp</span><span class="p">))</span> <span class="p">{</span>
            <span class="k">this</span><span class="p">.</span><span class="nx">successfullyLoggedIn</span><span class="p">()</span>
        <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
            <span class="k">this</span><span class="p">.</span><span class="nx">failedLogin</span><span class="p">()</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>En fin, podríamos seguir refinando este código pero el artículo se está haciendo más largo de lo pensando.</p>

<h2 id="conclusiones">Conclusiones</h2>

<p>El <em>code smell Large Class</em> suele venir de una falta de reflexión que nos lleva a añadir código en clases existentes en lugar de introducir otras nuevas. La consecuencia es que la clase acumula responsabilidades que responden a necesidades y stakeholders diferentes.</p>

<p>Esto hace que sea fácil introducir errores cruzados, mientras que el mantenimiento general se complica.</p>

<p>La solución del smell pasa por identificar esas responsabilidades y separarlas en clases independientes, que puedan atender a las necesidades de cada stakeholder sin interferir entre ellas.</p>]]></content><author><name></name></author><category term="articles" /><category term="code-smells" /><category term="refactoring" /><category term="typescript" /><summary type="html"><![CDATA[Otro code smell bastante habitual es tener clases muy grandes, que van acumulando muchas responsabilidades no relacionadas.]]></summary></entry><entry><title type="html">Data Clump</title><link href="https://franiglesias.github.io/data-clump/" rel="alternate" type="text/html" title="Data Clump" /><published>2025-10-27T00:00:00+00:00</published><updated>2025-10-27T00:00:00+00:00</updated><id>https://franiglesias.github.io/data-clump</id><content type="html" xml:base="https://franiglesias.github.io/data-clump/"><![CDATA[<p>Aprovechando material que he preparado para un curso de Refactoring, voy a empezar una serie de artículos sobre code smells. El primero que vamos a tratar es el Data Clump.</p>

<h2 id="definición">Definición</h2>

<p><strong>Data Clump</strong> (Grupo de Datos o Pegote de datos), es un code smells que se caracteriza porque el mismo grupo de campos de datos viaja junto por muchos lugares, lo que sugiere la necesidad de un Value Object y duplicación.</p>

<p>Esos campos que viajan siempre juntos pueden requerir validaciones, formateos, etc., y tendremos que repetirlos en muchos lugares. El problema de esto es la posibilidad de introducir inconsistencias o errores, además de la dificultad de mantenerlos.</p>

<p>Data Clump está relacionado con <em>Primitive Obsession</em>, o sea, el code smell consistente en representar conceptos con primitivos del lenguaje. En este caso, se trataría de conceptos que requieren combinar varios campos.</p>

<h2 id="ejemplo">Ejemplo</h2>

<p>En esta serie haremos un ejercicio por cada <em>code smell</em>. Primero, mostraremos un ejemplo de código ilustrando el <em>smell</em>. Luego, propondremos un ejercicio que pondrá de relieve cómo el <em>code smell</em>, incrementa el coste del cambio de ese código.</p>

<p>Para <strong>data clump</strong> vamos a usar el siguiente ejemplo. Tenemos las clases Invoice y Shipping Label, las cuales utilizan básicamente los mismos campos de datos para representar a un cliente y su dirección postal, aunque su uso es un poco diferente en cada caso.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">class</span> <span class="nx">Invoice</span> <span class="p">{</span>
  <span class="k">private</span> <span class="k">readonly</span> <span class="nx">customerName</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
  <span class="k">private</span> <span class="k">readonly</span> <span class="nx">customerCity</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
  <span class="k">private</span> <span class="k">readonly</span> <span class="nx">customerStreet</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
  <span class="k">private</span> <span class="k">readonly</span> <span class="nx">customerZip</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>

  <span class="kd">constructor</span><span class="p">(</span>
    <span class="nx">customerName</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
    <span class="nx">customerStreet</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
    <span class="nx">customerCity</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
    <span class="nx">customerZip</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
  <span class="p">)</span> <span class="p">{</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">customerZip</span> <span class="o">=</span> <span class="nx">customerZip</span><span class="p">;</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">customerStreet</span> <span class="o">=</span> <span class="nx">customerStreet</span><span class="p">;</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">customerCity</span> <span class="o">=</span> <span class="nx">customerCity</span><span class="p">;</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">customerName</span> <span class="o">=</span> <span class="nx">customerName</span><span class="p">;</span>
  <span class="p">}</span>

  <span class="nx">print</span><span class="p">():</span> <span class="kr">string</span> <span class="p">{</span>
    <span class="k">return</span> <span class="s2">`Factura para: </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">customerName</span><span class="p">}</span><span class="s2">\n`</span> <span class="o">+</span>
      <span class="s2">`Dirección: </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">customerStreet</span><span class="p">}</span><span class="s2">, </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">customerCity</span><span class="p">}</span><span class="s2">, </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">customerZip</span><span class="p">}</span><span class="s2">`</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="k">export</span> <span class="kd">class</span> <span class="nx">ShippingLabel</span> <span class="p">{</span>
  <span class="k">private</span> <span class="k">readonly</span> <span class="nx">customerName</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
  <span class="k">private</span> <span class="k">readonly</span> <span class="nx">customerStreet</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
  <span class="k">private</span> <span class="k">readonly</span> <span class="nx">customerCity</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
  <span class="k">private</span> <span class="k">readonly</span> <span class="nx">customerZip</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>

  <span class="kd">constructor</span><span class="p">(</span>
    <span class="nx">customerName</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
    <span class="nx">customerStreet</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
    <span class="nx">customerCity</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
    <span class="nx">customerZip</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
  <span class="p">)</span> <span class="p">{</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">customerZip</span> <span class="o">=</span> <span class="nx">customerZip</span><span class="p">;</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">customerCity</span> <span class="o">=</span> <span class="nx">customerCity</span><span class="p">;</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">customerStreet</span> <span class="o">=</span> <span class="nx">customerStreet</span><span class="p">;</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">customerName</span> <span class="o">=</span> <span class="nx">customerName</span><span class="p">;</span>
  <span class="p">}</span>

  <span class="nx">print</span><span class="p">():</span> <span class="kr">string</span> <span class="p">{</span>
    <span class="k">return</span> <span class="s2">`Enviar a: </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">customerName</span><span class="p">}</span><span class="s2">\n`</span> <span class="o">+</span> <span class="s2">`</span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">customerStreet</span><span class="p">}</span><span class="s2">, </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">customerCity</span><span class="p">}</span><span class="s2">, </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">customerZip</span><span class="p">}</span><span class="s2">`</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Este código funciona bien. Los code smells no son <em>bugs</em>. Pero, como veremos a continuación, pueden abrir puertas a generar bugs en el futuro.</p>

<h2 id="ejercicio">Ejercicio</h2>

<p>Queremos añadir país y provincia/estado y reglas de formateo internacional de la dirección.</p>

<h2 id="problemas-que-encontrarás">Problemas que encontrarás</h2>

<p>Para resolver el ejercicio, necesitarás modificar constructores, impresores y cualquier lugar que pase estos campos juntos, multiplicando la superficie de cambio. Básicamente, necesitas duplicar toda la lógica y cambios que llegues a introducir, con el riesgo de dejar alguna inconsistencia.</p>

<h2 id="testing-y-solución">Testing y Solución</h2>

<p>Vamos a introducir tests para caracterizar el comportamiento actual de las clases. Una vez hecho, tendremos que modificarlos para introducir los nuevos requisitos. Haremos algo muy sencillo.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="p">{</span> <span class="nx">describe</span><span class="p">,</span> <span class="nx">expect</span><span class="p">,</span> <span class="nx">it</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">vitest</span><span class="dl">'</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">Invoice</span><span class="p">,</span> <span class="nx">ShippingLabel</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./data-clump</span><span class="dl">'</span>

<span class="nx">describe</span><span class="p">(</span><span class="dl">'</span><span class="s1">Data Clump Example</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="nx">it</span><span class="p">(</span><span class="dl">'</span><span class="s1">should generate an Invoice</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">invoice</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Invoice</span><span class="p">(</span><span class="dl">'</span><span class="s1">Jane Doe</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">15, Foo Street</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">ToonTown</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">12345</span><span class="dl">'</span><span class="p">)</span>
    <span class="kd">const</span> <span class="nx">expected</span> <span class="o">=</span> <span class="s2">`Factura para: Jane Doe\nDirección: 15, Foo Street, ToonTown, 12345`</span>

    <span class="nx">expect</span><span class="p">(</span><span class="nx">invoice</span><span class="p">.</span><span class="nx">print</span><span class="p">()).</span><span class="nx">toEqual</span><span class="p">(</span><span class="nx">expected</span><span class="p">)</span>
  <span class="p">})</span>

  <span class="nx">it</span><span class="p">(</span><span class="dl">'</span><span class="s1">should generate a Shipping Label</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">invoice</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">ShippingLabel</span><span class="p">(</span><span class="dl">'</span><span class="s1">Jane Doe</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">15, Foo Street</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">ToonTown</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">12345</span><span class="dl">'</span><span class="p">)</span>
    <span class="kd">const</span> <span class="nx">expected</span> <span class="o">=</span> <span class="s2">`Enviar a: Jane Doe\n15, Foo Street, ToonTown, 12345`</span>

    <span class="nx">expect</span><span class="p">(</span><span class="nx">invoice</span><span class="p">.</span><span class="nx">print</span><span class="p">()).</span><span class="nx">toEqual</span><span class="p">(</span><span class="nx">expected</span><span class="p">)</span>
  <span class="p">})</span>
<span class="p">})</span>
</code></pre></div></div>

<p>El primer cambio es introducir información de provincia (o estado) y país. Por lo tanto, modificaremos el test para utilizar los nuevos campos.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">describe</span><span class="p">(</span><span class="dl">'</span><span class="s1">Data Clump Example With New Fields</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="nx">it</span><span class="p">(</span><span class="dl">'</span><span class="s1">should generate an Invoice</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
        <span class="kd">const</span> <span class="nx">invoice</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Invoice</span><span class="p">(</span>
            <span class="dl">'</span><span class="s1">Jane Doe</span><span class="dl">'</span><span class="p">,</span>
            <span class="dl">'</span><span class="s1">15, Foo Street</span><span class="dl">'</span><span class="p">,</span>
            <span class="dl">'</span><span class="s1">ToonTown</span><span class="dl">'</span><span class="p">,</span>
            <span class="dl">'</span><span class="s1">12345</span><span class="dl">'</span><span class="p">,</span>
            <span class="dl">'</span><span class="s1">CA</span><span class="dl">'</span><span class="p">,</span>
            <span class="dl">'</span><span class="s1">US</span><span class="dl">'</span>
        <span class="p">)</span>
        <span class="kd">const</span> <span class="nx">expected</span> <span class="o">=</span> <span class="s2">`Factura para: Jane Doe\nDirección: 15, Foo Street, ToonTown, 12345, CA, US`</span>

        <span class="nx">expect</span><span class="p">(</span><span class="nx">invoice</span><span class="p">.</span><span class="nx">print</span><span class="p">()).</span><span class="nx">toEqual</span><span class="p">(</span><span class="nx">expected</span><span class="p">)</span>
    <span class="p">})</span>

    <span class="nx">it</span><span class="p">(</span><span class="dl">'</span><span class="s1">should generate a Shipping Label</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
        <span class="kd">const</span> <span class="nx">invoice</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">ShippingLabel</span><span class="p">(</span>
            <span class="dl">'</span><span class="s1">Jane Doe</span><span class="dl">'</span><span class="p">,</span>
            <span class="dl">'</span><span class="s1">15, Foo Street</span><span class="dl">'</span><span class="p">,</span>
            <span class="dl">'</span><span class="s1">ToonTown</span><span class="dl">'</span><span class="p">,</span>
            <span class="dl">'</span><span class="s1">12345</span><span class="dl">'</span><span class="p">,</span>
            <span class="dl">'</span><span class="s1">CA</span><span class="dl">'</span><span class="p">,</span>
            <span class="dl">'</span><span class="s1">US</span><span class="dl">'</span>
        <span class="p">)</span>
        <span class="kd">const</span> <span class="nx">expected</span> <span class="o">=</span> <span class="s2">`Enviar a: Jane Doe\n15, Foo Street, ToonTown, 12345, CA, US`</span>

        <span class="nx">expect</span><span class="p">(</span><span class="nx">invoice</span><span class="p">.</span><span class="nx">print</span><span class="p">()).</span><span class="nx">toEqual</span><span class="p">(</span><span class="nx">expected</span><span class="p">)</span>
    <span class="p">})</span>
<span class="p">})</span>
</code></pre></div></div>

<p>Como se puede apreciar en el nuevo test, hay que hacer un cambio en la firma del constructor de ambas clases, además del cambio del método <code class="language-plaintext highlighter-rouge">print</code>.</p>

<h3 id="sin-resolver-el-data-clump">Sin resolver el Data Clump</h3>

<p>Si no resuelves el Data Clump, tendrás que hacer más o menos los mismos cambios en ambas clases. En este caso, sería algo así para dar soporte a los nuevos campos.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">class</span> <span class="nx">Invoice</span> <span class="p">{</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="nx">customerName</span><span class="p">:</span> <span class="kr">string</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="nx">customerCity</span><span class="p">:</span> <span class="kr">string</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="nx">customerStreet</span><span class="p">:</span> <span class="kr">string</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="nx">customerZip</span><span class="p">:</span> <span class="kr">string</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="nx">customerState</span><span class="p">:</span> <span class="kr">string</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="nx">customerCountry</span><span class="p">:</span> <span class="kr">string</span>

    <span class="kd">constructor</span><span class="p">(</span>
        <span class="nx">customerName</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
        <span class="nx">customerStreet</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
        <span class="nx">customerCity</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
        <span class="nx">customerZip</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
        <span class="nx">customerState</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
        <span class="nx">customerCountry</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
    <span class="p">)</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">customerZip</span> <span class="o">=</span> <span class="nx">customerZip</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">customerStreet</span> <span class="o">=</span> <span class="nx">customerStreet</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">customerCity</span> <span class="o">=</span> <span class="nx">customerCity</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">customerName</span> <span class="o">=</span> <span class="nx">customerName</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">customerState</span> <span class="o">=</span> <span class="nx">customerState</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">customerCountry</span> <span class="o">=</span> <span class="nx">customerCountry</span>
    <span class="p">}</span>

    <span class="nx">print</span><span class="p">():</span> <span class="kr">string</span> <span class="p">{</span>
        <span class="k">return</span> <span class="p">(</span>
            <span class="s2">`Factura para: </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">customerName</span><span class="p">}</span><span class="s2">\n`</span> <span class="o">+</span>
            <span class="s2">`Dirección: </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">customerStreet</span><span class="p">}</span><span class="s2">, </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">customerCity</span><span class="p">}</span><span class="s2">, </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">customerZip</span><span class="p">}</span><span class="s2">, </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">customerState</span><span class="p">}</span><span class="s2">, </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">customerCountry</span><span class="p">}</span><span class="s2">`</span>
        <span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">class</span> <span class="nx">ShippingLabel</span> <span class="p">{</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="nx">customerName</span><span class="p">:</span> <span class="kr">string</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="nx">customerStreet</span><span class="p">:</span> <span class="kr">string</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="nx">customerCity</span><span class="p">:</span> <span class="kr">string</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="nx">customerZip</span><span class="p">:</span> <span class="kr">string</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="nx">customerState</span><span class="p">:</span> <span class="kr">string</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="nx">customerCountry</span><span class="p">:</span> <span class="kr">string</span>

    <span class="kd">constructor</span><span class="p">(</span>
        <span class="nx">customerName</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
        <span class="nx">customerStreet</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
        <span class="nx">customerCity</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
        <span class="nx">customerZip</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
        <span class="nx">customerState</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
        <span class="nx">customerCountry</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
    <span class="p">)</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">customerZip</span> <span class="o">=</span> <span class="nx">customerZip</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">customerCity</span> <span class="o">=</span> <span class="nx">customerCity</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">customerStreet</span> <span class="o">=</span> <span class="nx">customerStreet</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">customerName</span> <span class="o">=</span> <span class="nx">customerName</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">customerState</span> <span class="o">=</span> <span class="nx">customerState</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">customerCountry</span> <span class="o">=</span> <span class="nx">customerCountry</span>
    <span class="p">}</span>

    <span class="nx">print</span><span class="p">():</span> <span class="kr">string</span> <span class="p">{</span>
        <span class="k">return</span> <span class="p">(</span>
            <span class="s2">`Enviar a: </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">customerName</span><span class="p">}</span><span class="s2">\n`</span> <span class="o">+</span>
            <span class="s2">`</span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">customerStreet</span><span class="p">}</span><span class="s2">, </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">customerCity</span><span class="p">}</span><span class="s2">, </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">customerZip</span><span class="p">}</span><span class="s2">, </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">customerState</span><span class="p">}</span><span class="s2">, </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">customerCountry</span><span class="p">}</span><span class="s2">`</span>
        <span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Pero es que además, tenemos que permitir que los formatos de la dirección se adapten a los diferentes países. Definámoslo con un test. Solo pongo el test para Invoice, pero para Shipping Label serían más o menos los mismos.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">describe</span><span class="p">(</span><span class="dl">'</span><span class="s1">Data Clump Example With International Support</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="nx">it</span><span class="p">(</span><span class="dl">'</span><span class="s1">should generate an Invoice for US</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
        <span class="kd">const</span> <span class="nx">invoice</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Invoice</span><span class="p">(</span>
            <span class="dl">'</span><span class="s1">Jane Doe</span><span class="dl">'</span><span class="p">,</span>
            <span class="dl">'</span><span class="s1">15, Foo Street</span><span class="dl">'</span><span class="p">,</span>
            <span class="dl">'</span><span class="s1">ToonTown</span><span class="dl">'</span><span class="p">,</span>
            <span class="dl">'</span><span class="s1">12345</span><span class="dl">'</span><span class="p">,</span>
            <span class="dl">'</span><span class="s1">CA</span><span class="dl">'</span><span class="p">,</span>
            <span class="dl">'</span><span class="s1">US</span><span class="dl">'</span>
        <span class="p">)</span>
        <span class="kd">const</span> <span class="nx">expected</span> <span class="o">=</span> <span class="s2">`Factura para: Jane Doe\nDirección: 15, Foo Street\nToonTown, CA 12345\nUSA`</span>

        <span class="nx">expect</span><span class="p">(</span><span class="nx">invoice</span><span class="p">.</span><span class="nx">print</span><span class="p">()).</span><span class="nx">toEqual</span><span class="p">(</span><span class="nx">expected</span><span class="p">)</span>
    <span class="p">})</span>

    <span class="nx">it</span><span class="p">(</span><span class="dl">'</span><span class="s1">should generate an Invoice for ES</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
        <span class="kd">const</span> <span class="nx">invoice</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Invoice</span><span class="p">(</span>
            <span class="dl">'</span><span class="s1">Pepa Pérez</span><span class="dl">'</span><span class="p">,</span>
            <span class="dl">'</span><span class="s1">Calle Principal, 15</span><span class="dl">'</span><span class="p">,</span>
            <span class="dl">'</span><span class="s1">Vetusta</span><span class="dl">'</span><span class="p">,</span>
            <span class="dl">'</span><span class="s1">12345</span><span class="dl">'</span><span class="p">,</span>
            <span class="dl">'</span><span class="s1">Asturias</span><span class="dl">'</span><span class="p">,</span>
            <span class="dl">'</span><span class="s1">ES</span><span class="dl">'</span>
        <span class="p">)</span>
        <span class="kd">const</span> <span class="nx">expected</span> <span class="o">=</span> <span class="s2">`Factura para: Pepa Pérez\nDirección: Calle Principal, 15\n12345 Vetusta (Asturias)\nEspaña`</span>

        <span class="nx">expect</span><span class="p">(</span><span class="nx">invoice</span><span class="p">.</span><span class="nx">print</span><span class="p">()).</span><span class="nx">toEqual</span><span class="p">(</span><span class="nx">expected</span><span class="p">)</span>
    <span class="p">})</span>
<span class="p">})</span>
</code></pre></div></div>

<p>Para Invoice, el código podría quedar inicialmente así:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">class</span> <span class="nx">Invoice</span> <span class="p">{</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="nx">customerName</span><span class="p">:</span> <span class="kr">string</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="nx">customerCity</span><span class="p">:</span> <span class="kr">string</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="nx">customerStreet</span><span class="p">:</span> <span class="kr">string</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="nx">customerZip</span><span class="p">:</span> <span class="kr">string</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="nx">customerState</span><span class="p">:</span> <span class="kr">string</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="nx">customerCountry</span><span class="p">:</span> <span class="kr">string</span>

    <span class="kd">constructor</span><span class="p">(</span>
        <span class="nx">customerName</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
        <span class="nx">customerStreet</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
        <span class="nx">customerCity</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
        <span class="nx">customerZip</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
        <span class="nx">customerState</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
        <span class="nx">customerCountry</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
    <span class="p">)</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">customerZip</span> <span class="o">=</span> <span class="nx">customerZip</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">customerStreet</span> <span class="o">=</span> <span class="nx">customerStreet</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">customerCity</span> <span class="o">=</span> <span class="nx">customerCity</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">customerName</span> <span class="o">=</span> <span class="nx">customerName</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">customerState</span> <span class="o">=</span> <span class="nx">customerState</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">customerCountry</span> <span class="o">=</span> <span class="nx">customerCountry</span>
    <span class="p">}</span>

    <span class="nx">print</span><span class="p">():</span> <span class="kr">string</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">customerCountry</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">ES</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
            <span class="k">return</span> <span class="p">(</span>
                <span class="s2">`Factura para: </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">customerName</span><span class="p">}</span><span class="s2">\n`</span> <span class="o">+</span>
                <span class="s2">`Dirección: </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">customerStreet</span><span class="p">}</span><span class="s2">\n</span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">customerZip</span><span class="p">}</span><span class="s2"> </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">customerCity</span><span class="p">}</span><span class="s2"> (</span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">customerState</span><span class="p">}</span><span class="s2">)\nEspaña`</span>
            <span class="p">);</span>
        <span class="p">}</span>
        <span class="k">if</span> <span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">customerCountry</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">US</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
            <span class="k">return</span><span class="p">(</span>
                <span class="s2">`Factura para: </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">customerName</span><span class="p">}</span><span class="s2">\n`</span> <span class="o">+</span>
                <span class="s2">`Dirección: </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">customerStreet</span><span class="p">}</span><span class="s2">\n</span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">customerCity</span><span class="p">}</span><span class="s2">, </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">customerState</span><span class="p">}</span><span class="s2"> </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">customerZip</span><span class="p">}</span><span class="s2">\nUSA`</span><span class="p">)</span>
        <span class="p">}</span>
        <span class="k">return</span> <span class="p">(</span>
            <span class="s2">`Factura para: </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">customerName</span><span class="p">}</span><span class="s2">\n`</span> <span class="o">+</span>
            <span class="s2">`Dirección: </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">customerStreet</span><span class="p">}</span><span class="s2">, </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">customerCity</span><span class="p">}</span><span class="s2">, </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">customerZip</span><span class="p">}</span><span class="s2">, </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">customerState</span><span class="p">}</span><span class="s2">, </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">customerCountry</span><span class="p">}</span><span class="s2">`</span>
        <span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Y tendríamos que hacer algo similar para Shipping Label o en cualquier otro lugar en el que se utilice el mismo conjunto de campos para representar la dirección.</p>

<h3 id="resolviendo-el-data-clump">Resolviendo el Data Clump</h3>

<p>Para resolver el ejercicio necesitamos solucionar primero el Data Clump. Recuerda: primero haz que el cambio sea fácil (lo que puede ser difícil), y luego haz el cambio fácil. Así que volvemos a la situación inicial.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">class</span> <span class="nx">Invoice</span> <span class="p">{</span>
  <span class="k">private</span> <span class="k">readonly</span> <span class="nx">customerName</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
  <span class="k">private</span> <span class="k">readonly</span> <span class="nx">customerCity</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
  <span class="k">private</span> <span class="k">readonly</span> <span class="nx">customerStreet</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
  <span class="k">private</span> <span class="k">readonly</span> <span class="nx">customerZip</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>

  <span class="kd">constructor</span><span class="p">(</span>
    <span class="nx">customerName</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
    <span class="nx">customerStreet</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
    <span class="nx">customerCity</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
    <span class="nx">customerZip</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
  <span class="p">)</span> <span class="p">{</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">customerZip</span> <span class="o">=</span> <span class="nx">customerZip</span><span class="p">;</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">customerStreet</span> <span class="o">=</span> <span class="nx">customerStreet</span><span class="p">;</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">customerCity</span> <span class="o">=</span> <span class="nx">customerCity</span><span class="p">;</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">customerName</span> <span class="o">=</span> <span class="nx">customerName</span><span class="p">;</span>
  <span class="p">}</span>

  <span class="nx">print</span><span class="p">():</span> <span class="kr">string</span> <span class="p">{</span>
    <span class="k">return</span> <span class="s2">`Factura para: </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">customerName</span><span class="p">}</span><span class="s2">\n`</span> <span class="o">+</span>
      <span class="s2">`Dirección: </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">customerStreet</span><span class="p">}</span><span class="s2">, </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">customerCity</span><span class="p">}</span><span class="s2">, </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">customerZip</span><span class="p">}</span><span class="s2">`</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="k">export</span> <span class="kd">class</span> <span class="nx">ShippingLabel</span> <span class="p">{</span>
  <span class="k">private</span> <span class="k">readonly</span> <span class="nx">customerName</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
  <span class="k">private</span> <span class="k">readonly</span> <span class="nx">customerStreet</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
  <span class="k">private</span> <span class="k">readonly</span> <span class="nx">customerCity</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
  <span class="k">private</span> <span class="k">readonly</span> <span class="nx">customerZip</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>

  <span class="kd">constructor</span><span class="p">(</span>
    <span class="nx">customerName</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
    <span class="nx">customerStreet</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
    <span class="nx">customerCity</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
    <span class="nx">customerZip</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
  <span class="p">)</span> <span class="p">{</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">customerZip</span> <span class="o">=</span> <span class="nx">customerZip</span><span class="p">;</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">customerCity</span> <span class="o">=</span> <span class="nx">customerCity</span><span class="p">;</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">customerStreet</span> <span class="o">=</span> <span class="nx">customerStreet</span><span class="p">;</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">customerName</span> <span class="o">=</span> <span class="nx">customerName</span><span class="p">;</span>
  <span class="p">}</span>

  <span class="nx">print</span><span class="p">():</span> <span class="kr">string</span> <span class="p">{</span>
    <span class="k">return</span> <span class="s2">`Enviar a: </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">customerName</span><span class="p">}</span><span class="s2">\n`</span> <span class="o">+</span> <span class="s2">`</span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">customerStreet</span><span class="p">}</span><span class="s2">, </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">customerCity</span><span class="p">}</span><span class="s2">, </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">customerZip</span><span class="p">}</span><span class="s2">`</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h4 id="introducir-value-object">Introducir Value Object</h4>

<p>En nuestro caso, lo primero es introducir un <strong>Value Object</strong>. Para nuestro ejercicio, y sin más contexto, el value object podría estar compuesto por todos los campos originales. Estos cuatro campos se refieren a dos conceptos diferentes: el nombre del cliente y la dirección. Dependiendo del contexto concreto podríamos mantenerlos juntos o separarlos. Para el ejemplo, agruparé solo los campos de dirección porque pienso que podría necesitar separar los conceptos de cliente y dirección.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">class</span> <span class="nx">Address</span> <span class="p">{</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="nx">street</span><span class="p">:</span> <span class="kr">string</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="nx">city</span><span class="p">:</span> <span class="kr">string</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="nx">zip</span><span class="p">:</span> <span class="kr">string</span>


    <span class="kd">constructor</span><span class="p">(</span><span class="nx">street</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">city</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">zip</span><span class="p">:</span> <span class="kr">string</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">street</span> <span class="o">=</span> <span class="nx">street</span><span class="p">;</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">city</span> <span class="o">=</span> <span class="nx">city</span><span class="p">;</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">zip</span> <span class="o">=</span> <span class="nx">zip</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Los value objects deberían atraer comportamiento, por lo que sería lógico que Address tuviese un método <code class="language-plaintext highlighter-rouge">print</code> para representarse.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">class</span> <span class="nx">Address</span> <span class="p">{</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="nx">street</span><span class="p">:</span> <span class="kr">string</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="nx">city</span><span class="p">:</span> <span class="kr">string</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="nx">zip</span><span class="p">:</span> <span class="kr">string</span>


    <span class="kd">constructor</span><span class="p">(</span><span class="nx">street</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">city</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">zip</span><span class="p">:</span> <span class="kr">string</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">street</span> <span class="o">=</span> <span class="nx">street</span><span class="p">;</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">city</span> <span class="o">=</span> <span class="nx">city</span><span class="p">;</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">zip</span> <span class="o">=</span> <span class="nx">zip</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="nx">print</span><span class="p">():</span> <span class="kr">string</span> <span class="p">{</span>
        <span class="k">return</span> <span class="s2">`</span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">street</span><span class="p">}</span><span class="s2">, </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">city</span><span class="p">}</span><span class="s2">, </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">zip</span><span class="p">}</span><span class="s2">`</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Básicamente, implementamos <code class="language-plaintext highlighter-rouge">print</code> con el comportamiento que queremos. Fíjate que no añado el prefijo “Dirección” porque quiero que sean los consumidores de <code class="language-plaintext highlighter-rouge">Address</code> quienes lo hagan en la medida en que lo necesiten.</p>

<p>Ahora, podemos modificar las clases para que utilicen <code class="language-plaintext highlighter-rouge">Address</code> en lugar de los campos originales. En lenguajes con sobrecarga es bastante fácil introducir un constructor alternativo que tome un <code class="language-plaintext highlighter-rouge">Address</code>, pero en Typescript lo haremos con un campo opcional para introducir el cambio de forma paralela. Este cambio permite que el primer test siga pasando, mientras que el comportamiento de <code class="language-plaintext highlighter-rouge">print</code> es delegado en <code class="language-plaintext highlighter-rouge">Address</code>.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">class</span> <span class="nx">Invoice</span> <span class="p">{</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="nx">customerName</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="nx">customerCity</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="nx">customerStreet</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="nx">customerZip</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="nx">address</span><span class="p">:</span> <span class="nx">Address</span><span class="p">;</span>

    <span class="kd">constructor</span><span class="p">(</span>
        <span class="nx">customerName</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
        <span class="nx">customerStreet</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
        <span class="nx">customerCity</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
        <span class="nx">customerZip</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
        <span class="nx">address</span><span class="p">?:</span> <span class="nx">Address</span> <span class="o">|</span> <span class="kc">undefined</span><span class="p">,</span>
    <span class="p">)</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">customerZip</span> <span class="o">=</span> <span class="nx">customerZip</span><span class="p">;</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">customerStreet</span> <span class="o">=</span> <span class="nx">customerStreet</span><span class="p">;</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">customerCity</span> <span class="o">=</span> <span class="nx">customerCity</span><span class="p">;</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">customerName</span> <span class="o">=</span> <span class="nx">customerName</span><span class="p">;</span>
        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">address</span><span class="p">)</span> <span class="p">{</span>
            <span class="k">this</span><span class="p">.</span><span class="nx">address</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Address</span><span class="p">(</span><span class="nx">customerStreet</span><span class="p">,</span> <span class="nx">customerCity</span><span class="p">,</span> <span class="nx">customerZip</span><span class="p">);</span>
        <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
            <span class="k">this</span><span class="p">.</span><span class="nx">address</span> <span class="o">=</span> <span class="nx">address</span><span class="p">;</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="nx">print</span><span class="p">():</span> <span class="kr">string</span> <span class="p">{</span>
        <span class="k">return</span> <span class="s2">`Factura para: </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">customerName</span><span class="p">}</span><span class="s2">\n`</span> <span class="o">+</span>
            <span class="s2">`Dirección: </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">address</span><span class="p">.</span><span class="nx">print</span><span class="p">()}</span><span class="s2">`</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Una vez verificado que el test pasa y que estamos pasando <code class="language-plaintext highlighter-rouge">Address</code> en todos los casos en que instanciamos <code class="language-plaintext highlighter-rouge">Invoice</code>, podemos hacer el cambio completo y aplicar el mismo a <code class="language-plaintext highlighter-rouge">ShippingLabel</code>. Quedará así:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">describe</span><span class="p">(</span><span class="dl">'</span><span class="s1">Data Clump Example</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="nx">it</span><span class="p">(</span><span class="dl">'</span><span class="s1">should generate an Invoice</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
        <span class="kd">const</span> <span class="nx">invoice</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Invoice</span><span class="p">(</span>
            <span class="dl">'</span><span class="s1">Jane Doe</span><span class="dl">'</span><span class="p">,</span>
            <span class="k">new</span> <span class="nx">Address</span><span class="p">(</span><span class="dl">'</span><span class="s1">15, Foo Street</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">ToonTown</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">12345</span><span class="dl">'</span><span class="p">)</span>
        <span class="p">)</span>
        <span class="kd">const</span> <span class="nx">expected</span> <span class="o">=</span> <span class="s2">`Factura para: Jane Doe\nDirección: 15, Foo Street, ToonTown, 12345`</span>

        <span class="nx">expect</span><span class="p">(</span><span class="nx">invoice</span><span class="p">.</span><span class="nx">print</span><span class="p">()).</span><span class="nx">toEqual</span><span class="p">(</span><span class="nx">expected</span><span class="p">)</span>
    <span class="p">})</span>

    <span class="nx">it</span><span class="p">(</span><span class="dl">'</span><span class="s1">should generate a Shipping Label</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
        <span class="kd">const</span> <span class="nx">invoice</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">ShippingLabel</span><span class="p">(</span>
            <span class="dl">'</span><span class="s1">Jane Doe</span><span class="dl">'</span><span class="p">,</span>
            <span class="k">new</span> <span class="nx">Address</span><span class="p">(</span><span class="dl">'</span><span class="s1">15, Foo Street</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">ToonTown</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">12345</span><span class="dl">'</span><span class="p">)</span>
        <span class="p">)</span>
        <span class="kd">const</span> <span class="nx">expected</span> <span class="o">=</span> <span class="s2">`Enviar a: Jane Doe\n15, Foo Street, ToonTown, 12345`</span>

        <span class="nx">expect</span><span class="p">(</span><span class="nx">invoice</span><span class="p">.</span><span class="nx">print</span><span class="p">()).</span><span class="nx">toEqual</span><span class="p">(</span><span class="nx">expected</span><span class="p">)</span>
    <span class="p">})</span>
<span class="p">})</span>
</code></pre></div></div>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">class</span> <span class="nx">Invoice</span> <span class="p">{</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="nx">customerName</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="nx">address</span><span class="p">:</span> <span class="nx">Address</span><span class="p">;</span>

    <span class="kd">constructor</span><span class="p">(</span>
        <span class="nx">customerName</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
        <span class="nx">address</span><span class="p">:</span> <span class="nx">Address</span><span class="p">,</span>
    <span class="p">)</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">customerName</span> <span class="o">=</span> <span class="nx">customerName</span><span class="p">;</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">address</span> <span class="o">=</span> <span class="nx">address</span><span class="p">;</span>

    <span class="p">}</span>

    <span class="nx">print</span><span class="p">():</span> <span class="kr">string</span> <span class="p">{</span>
        <span class="k">return</span> <span class="s2">`Factura para: </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">customerName</span><span class="p">}</span><span class="s2">\n`</span> <span class="o">+</span>
            <span class="s2">`Dirección: </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">address</span><span class="p">.</span><span class="nx">print</span><span class="p">()}</span><span class="s2">`</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">class</span> <span class="nx">ShippingLabel</span> <span class="p">{</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="nx">customerName</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="nx">address</span><span class="p">:</span> <span class="nx">Address</span><span class="p">;</span>

    <span class="kd">constructor</span><span class="p">(</span>
        <span class="nx">customerName</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
        <span class="nx">address</span><span class="p">:</span> <span class="nx">Address</span><span class="p">,</span>
    <span class="p">)</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">customerName</span> <span class="o">=</span> <span class="nx">customerName</span><span class="p">;</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">address</span> <span class="o">=</span> <span class="nx">address</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="nx">print</span><span class="p">():</span> <span class="kr">string</span> <span class="p">{</span>
        <span class="k">return</span> <span class="s2">`Enviar a: </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">customerName</span><span class="p">}</span><span class="s2">\n`</span> <span class="o">+</span> <span class="s2">`</span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">address</span><span class="p">.</span><span class="nx">print</span><span class="p">()}</span><span class="s2">`</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">class</span> <span class="nx">Address</span> <span class="p">{</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="nx">street</span><span class="p">:</span> <span class="kr">string</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="nx">city</span><span class="p">:</span> <span class="kr">string</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="nx">zip</span><span class="p">:</span> <span class="kr">string</span>


    <span class="kd">constructor</span><span class="p">(</span><span class="nx">street</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">city</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">zip</span><span class="p">:</span> <span class="kr">string</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">street</span> <span class="o">=</span> <span class="nx">street</span><span class="p">;</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">city</span> <span class="o">=</span> <span class="nx">city</span><span class="p">;</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">zip</span> <span class="o">=</span> <span class="nx">zip</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="nx">print</span><span class="p">():</span> <span class="kr">string</span> <span class="p">{</span>
        <span class="k">return</span> <span class="s2">`</span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">street</span><span class="p">}</span><span class="s2">, </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">city</span><span class="p">}</span><span class="s2">, </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">zip</span><span class="p">}</span><span class="s2">`</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Ahora ya no tenemos el Data Clump. En su lugar disponemos de un Value Object <code class="language-plaintext highlighter-rouge">Address</code> que encapsula ese concepto.</p>

<p>Veamos ahora la primera modificación que se nos pide: introducir provincia y país. He aquí el test modificado:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">describe</span><span class="p">(</span><span class="dl">'</span><span class="s1">Data Clump Example With New Fields</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="nx">it</span><span class="p">(</span><span class="dl">'</span><span class="s1">should generate an Invoice</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
        <span class="kd">const</span> <span class="nx">invoice</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Invoice</span><span class="p">(</span>
            <span class="dl">'</span><span class="s1">Jane Doe</span><span class="dl">'</span><span class="p">,</span>
            <span class="k">new</span> <span class="nx">Address</span><span class="p">(</span>
                <span class="dl">'</span><span class="s1">15, Foo Street</span><span class="dl">'</span><span class="p">,</span>
                <span class="dl">'</span><span class="s1">ToonTown</span><span class="dl">'</span><span class="p">,</span>
                <span class="dl">'</span><span class="s1">12345</span><span class="dl">'</span><span class="p">,</span>
                <span class="dl">'</span><span class="s1">CA</span><span class="dl">'</span><span class="p">,</span>
                <span class="dl">'</span><span class="s1">US</span><span class="dl">'</span>
            <span class="p">)</span>
        <span class="p">)</span>
        <span class="kd">const</span> <span class="nx">expected</span> <span class="o">=</span> <span class="s2">`Factura para: Jane Doe\nDirección: 15, Foo Street, ToonTown, 12345, CA, US`</span>

        <span class="nx">expect</span><span class="p">(</span><span class="nx">invoice</span><span class="p">.</span><span class="nx">print</span><span class="p">()).</span><span class="nx">toEqual</span><span class="p">(</span><span class="nx">expected</span><span class="p">)</span>
    <span class="p">})</span>

    <span class="nx">it</span><span class="p">(</span><span class="dl">'</span><span class="s1">should generate a Shipping Label</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
        <span class="kd">const</span> <span class="nx">invoice</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">ShippingLabel</span><span class="p">(</span>
            <span class="dl">'</span><span class="s1">Jane Doe</span><span class="dl">'</span><span class="p">,</span>
            <span class="k">new</span> <span class="nx">Address</span><span class="p">(</span>
                <span class="dl">'</span><span class="s1">15, Foo Street</span><span class="dl">'</span><span class="p">,</span>
                <span class="dl">'</span><span class="s1">ToonTown</span><span class="dl">'</span><span class="p">,</span>
                <span class="dl">'</span><span class="s1">12345</span><span class="dl">'</span><span class="p">,</span>
                <span class="dl">'</span><span class="s1">CA</span><span class="dl">'</span><span class="p">,</span>
                <span class="dl">'</span><span class="s1">US</span><span class="dl">'</span>
            <span class="p">)</span>
        <span class="p">)</span>
        <span class="kd">const</span> <span class="nx">expected</span> <span class="o">=</span> <span class="s2">`Enviar a: Jane Doe\n15, Foo Street, ToonTown, 12345, CA, US`</span>

        <span class="nx">expect</span><span class="p">(</span><span class="nx">invoice</span><span class="p">.</span><span class="nx">print</span><span class="p">()).</span><span class="nx">toEqual</span><span class="p">(</span><span class="nx">expected</span><span class="p">)</span>
    <span class="p">})</span>
<span class="p">})</span>
</code></pre></div></div>

<p>Este es el cambio que tenemos que aplicar:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">class</span> <span class="nx">Address</span> <span class="p">{</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="nx">street</span><span class="p">:</span> <span class="kr">string</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="nx">city</span><span class="p">:</span> <span class="kr">string</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="nx">zip</span><span class="p">:</span> <span class="kr">string</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="nx">state</span><span class="p">:</span> <span class="kr">string</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="nx">country</span><span class="p">:</span> <span class="kr">string</span>


    <span class="kd">constructor</span><span class="p">(</span><span class="nx">street</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">city</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">zip</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">state</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">country</span><span class="p">:</span> <span class="kr">string</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">street</span> <span class="o">=</span> <span class="nx">street</span><span class="p">;</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">city</span> <span class="o">=</span> <span class="nx">city</span><span class="p">;</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">zip</span> <span class="o">=</span> <span class="nx">zip</span><span class="p">;</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">state</span> <span class="o">=</span> <span class="nx">state</span><span class="p">;</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">country</span> <span class="o">=</span> <span class="nx">country</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="nx">print</span><span class="p">():</span> <span class="kr">string</span> <span class="p">{</span>
        <span class="k">return</span> <span class="s2">`</span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">street</span><span class="p">}</span><span class="s2">, </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">city</span><span class="p">}</span><span class="s2">, </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">zip</span><span class="p">}</span><span class="s2">, </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">state</span><span class="p">}</span><span class="s2">, </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">country</span><span class="p">}</span><span class="s2">`</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Es decir, ahora solo tenemos que modificar el Value Object <code class="language-plaintext highlighter-rouge">Address</code> para que incluya los campos nuevos y modificar la forma en que <code class="language-plaintext highlighter-rouge">print</code> representa la dirección. De hecho, podríamos haber introducido una clase nueva y no modificar <code class="language-plaintext highlighter-rouge">Address</code>.</p>

<p>A destacar: el cambio solo se produce en la clase <code class="language-plaintext highlighter-rouge">Address</code> y no en las clases <code class="language-plaintext highlighter-rouge">Invoice</code> ni <code class="language-plaintext highlighter-rouge">ShippingLabel</code>, ni ningún otro usuario de <code class="language-plaintext highlighter-rouge">Address</code>. Es una aplicación del principio DRY (una sola fuente autoritativa de conocimiento).</p>

<h4 id="introducir-polimorfismo">Introducir polimorfismo</h4>

<p>Para el segundo requisito, introduciremos polimorfismo en la clase <code class="language-plaintext highlighter-rouge">Address</code>. Con esto, podremos dar soporte internacional a las direcciones.</p>

<p>El polimorfismo es la posibilidad de enviar el mismo mensaje a objetos de distintos tipos, sin preocuparnos del tipo de objeto. En nuestro caso, queremos que <code class="language-plaintext highlighter-rouge">Address</code> sea capaz de representar direcciones en diferentes formatos, por lo que haremos subclases especializadas en cada formato. De este modo, todas serán <code class="language-plaintext highlighter-rouge">Address</code>, pero cada clase (<code class="language-plaintext highlighter-rouge">ESAddress</code>, <code class="language-plaintext highlighter-rouge">USAddress</code>…) ejecutará el <code class="language-plaintext highlighter-rouge">print</code> a su manera especial. Esto nos va a requerir tener también una factoría que nos entregue una instancia de la clase adecuada.</p>

<p>Para empezar, extendemos la visibilidad de las propiedades para que puedan ser accesibles por las clases derivadas. Esto no siempre tiene que ser así, pero en este caso los componentes de la dirección son comunes:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">class</span> <span class="nx">Address</span> <span class="p">{</span>
    <span class="k">protected</span> <span class="k">readonly</span> <span class="nx">street</span><span class="p">:</span> <span class="kr">string</span>
    <span class="k">protected</span> <span class="k">readonly</span> <span class="nx">city</span><span class="p">:</span> <span class="kr">string</span>
    <span class="k">protected</span> <span class="k">readonly</span> <span class="nx">zip</span><span class="p">:</span> <span class="kr">string</span>
    <span class="k">protected</span> <span class="k">readonly</span> <span class="nx">state</span><span class="p">:</span> <span class="kr">string</span>
    <span class="k">protected</span> <span class="k">readonly</span> <span class="nx">country</span><span class="p">:</span> <span class="kr">string</span>


    <span class="kd">constructor</span><span class="p">(</span><span class="nx">street</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">city</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">zip</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">state</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">country</span><span class="p">:</span> <span class="kr">string</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">street</span> <span class="o">=</span> <span class="nx">street</span><span class="p">;</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">city</span> <span class="o">=</span> <span class="nx">city</span><span class="p">;</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">zip</span> <span class="o">=</span> <span class="nx">zip</span><span class="p">;</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">state</span> <span class="o">=</span> <span class="nx">state</span><span class="p">;</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">country</span> <span class="o">=</span> <span class="nx">country</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="nx">print</span><span class="p">():</span> <span class="kr">string</span> <span class="p">{</span>
        <span class="k">return</span> <span class="s2">`</span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">street</span><span class="p">}</span><span class="s2">, </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">city</span><span class="p">}</span><span class="s2">, </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">zip</span><span class="p">}</span><span class="s2">, </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">state</span><span class="p">}</span><span class="s2">, </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">country</span><span class="p">}</span><span class="s2">`</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Aquí tenemos una clase derivada para España:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">class</span> <span class="nx">ESAddress</span> <span class="kd">extends</span> <span class="nx">Address</span> <span class="p">{</span>
    <span class="nx">print</span><span class="p">():</span> <span class="kr">string</span> <span class="p">{</span>
        <span class="k">return</span> <span class="s2">`</span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">street</span><span class="p">}</span><span class="s2">\n</span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">zip</span><span class="p">}</span><span class="s2"> </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">city</span><span class="p">}</span><span class="s2"> (</span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">state</span><span class="p">}</span><span class="s2">)\nEspaña`</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Y aquí una para USA:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">class</span> <span class="nx">USAddress</span> <span class="kd">extends</span> <span class="nx">Address</span> <span class="p">{</span>
    <span class="nx">print</span><span class="p">():</span> <span class="kr">string</span> <span class="p">{</span>
        <span class="k">return</span> <span class="s2">`</span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">street</span><span class="p">}</span><span class="s2">\n</span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">city</span><span class="p">}</span><span class="s2">, </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">state</span><span class="p">}</span><span class="s2"> </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">zip</span><span class="p">}</span><span class="s2">\nUSA`</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Como puedes ver, tendríamos que introducir una clase derivada por cada formato de dirección al que queramos dar soporte. Aparte, puede ser adecuado tener un formato por defecto que, en este caso, por simplicidad, nos va a dar <code class="language-plaintext highlighter-rouge">Address</code>.</p>

<p>Nos hace falta una Factoría. Esto es, un objeto que nos entregue una instancia de la clase Address adecuada a partir del país. Podríamos hacer esto de varias formas.</p>

<p><strong>Usando un método factoría</strong>: En <code class="language-plaintext highlighter-rouge">Address</code> podemos añadir un método estático al que pasamos los parámetros y nos devuelve una instancia de la subclase adecuada. Si el país no está soportado, devuelve una instancia de <code class="language-plaintext highlighter-rouge">Address</code>.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">class</span> <span class="nx">Address</span> <span class="p">{</span>
    <span class="k">protected</span> <span class="k">readonly</span> <span class="nx">street</span><span class="p">:</span> <span class="kr">string</span>
    <span class="k">protected</span> <span class="k">readonly</span> <span class="nx">city</span><span class="p">:</span> <span class="kr">string</span>
    <span class="k">protected</span> <span class="k">readonly</span> <span class="nx">zip</span><span class="p">:</span> <span class="kr">string</span>
    <span class="k">protected</span> <span class="k">readonly</span> <span class="nx">state</span><span class="p">:</span> <span class="kr">string</span>
    <span class="k">protected</span> <span class="k">readonly</span> <span class="nx">country</span><span class="p">:</span> <span class="kr">string</span>


    <span class="kd">constructor</span><span class="p">(</span><span class="nx">street</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">city</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">zip</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">state</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">country</span><span class="p">:</span> <span class="kr">string</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">street</span> <span class="o">=</span> <span class="nx">street</span><span class="p">;</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">city</span> <span class="o">=</span> <span class="nx">city</span><span class="p">;</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">zip</span> <span class="o">=</span> <span class="nx">zip</span><span class="p">;</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">state</span> <span class="o">=</span> <span class="nx">state</span><span class="p">;</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">country</span> <span class="o">=</span> <span class="nx">country</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">static</span> <span class="nx">create</span><span class="p">(</span><span class="nx">street</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">city</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">zip</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">state</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">country</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="nx">Address</span> <span class="p">{</span>
        <span class="k">switch</span> <span class="p">(</span><span class="nx">country</span><span class="p">)</span> <span class="p">{</span>
            <span class="k">case</span> <span class="dl">'</span><span class="s1">ES</span><span class="dl">'</span><span class="p">:</span>
                <span class="k">return</span> <span class="k">new</span> <span class="nx">ESAddress</span><span class="p">(</span><span class="nx">street</span><span class="p">,</span> <span class="nx">city</span><span class="p">,</span> <span class="nx">zip</span><span class="p">,</span> <span class="nx">state</span><span class="p">,</span> <span class="nx">country</span><span class="p">)</span>
            <span class="k">case</span> <span class="dl">'</span><span class="s1">US</span><span class="dl">'</span><span class="p">:</span>
                <span class="k">return</span> <span class="k">new</span> <span class="nx">USAddress</span><span class="p">(</span><span class="nx">street</span><span class="p">,</span> <span class="nx">city</span><span class="p">,</span> <span class="nx">zip</span><span class="p">,</span> <span class="nx">state</span><span class="p">,</span> <span class="nx">country</span><span class="p">)</span>
            <span class="k">default</span><span class="p">:</span>
                <span class="k">return</span> <span class="k">new</span> <span class="nx">Address</span><span class="p">(</span><span class="nx">street</span><span class="p">,</span> <span class="nx">city</span><span class="p">,</span> <span class="nx">zip</span><span class="p">,</span> <span class="nx">state</span><span class="p">,</span> <span class="nx">country</span><span class="p">)</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="nx">print</span><span class="p">():</span> <span class="kr">string</span> <span class="p">{</span>
        <span class="k">return</span> <span class="s2">`</span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">street</span><span class="p">}</span><span class="s2">, </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">city</span><span class="p">}</span><span class="s2">, </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">zip</span><span class="p">}</span><span class="s2">, </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">state</span><span class="p">}</span><span class="s2">, </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">country</span><span class="p">}</span><span class="s2">`</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Si bien estos métodos factoría pueden ser una buena solución tienden a dificultar conseguir mantener las clases cerradas a modificación, por lo que una buena opción es introducir un objeto factoría:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">class</span> <span class="nx">AddressFactory</span> <span class="p">{</span>
    <span class="nx">create</span><span class="p">(</span><span class="nx">street</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">city</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">zip</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">state</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">country</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="nx">Address</span> <span class="p">{</span>
        <span class="k">switch</span> <span class="p">(</span><span class="nx">country</span><span class="p">)</span> <span class="p">{</span>
            <span class="k">case</span> <span class="dl">'</span><span class="s1">ES</span><span class="dl">'</span><span class="p">:</span>
                <span class="k">return</span> <span class="k">new</span> <span class="nx">ESAddress</span><span class="p">(</span><span class="nx">street</span><span class="p">,</span> <span class="nx">city</span><span class="p">,</span> <span class="nx">zip</span><span class="p">,</span> <span class="nx">state</span><span class="p">,</span> <span class="nx">country</span><span class="p">)</span>
            <span class="k">case</span> <span class="dl">'</span><span class="s1">US</span><span class="dl">'</span><span class="p">:</span>
                <span class="k">return</span> <span class="k">new</span> <span class="nx">USAddress</span><span class="p">(</span><span class="nx">street</span><span class="p">,</span> <span class="nx">city</span><span class="p">,</span> <span class="nx">zip</span><span class="p">,</span> <span class="nx">state</span><span class="p">,</span> <span class="nx">country</span><span class="p">)</span>
            <span class="k">default</span><span class="p">:</span>
                <span class="k">return</span> <span class="k">new</span> <span class="nx">Address</span><span class="p">(</span><span class="nx">street</span><span class="p">,</span> <span class="nx">city</span><span class="p">,</span> <span class="nx">zip</span><span class="p">,</span> <span class="nx">state</span><span class="p">,</span> <span class="nx">country</span><span class="p">)</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Si me apuras, diría que podríamos combinar los dos patrones en uno, ya que <code class="language-plaintext highlighter-rouge">Address.create()</code> no deja de ser un <em>proxy</em> a la Factoría, que puede ser más fácil de entender.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">class</span> <span class="nx">Address</span> <span class="p">{</span>
    <span class="k">protected</span> <span class="k">readonly</span> <span class="nx">street</span><span class="p">:</span> <span class="kr">string</span>
    <span class="k">protected</span> <span class="k">readonly</span> <span class="nx">city</span><span class="p">:</span> <span class="kr">string</span>
    <span class="k">protected</span> <span class="k">readonly</span> <span class="nx">zip</span><span class="p">:</span> <span class="kr">string</span>
    <span class="k">protected</span> <span class="k">readonly</span> <span class="nx">state</span><span class="p">:</span> <span class="kr">string</span>
    <span class="k">protected</span> <span class="k">readonly</span> <span class="nx">country</span><span class="p">:</span> <span class="kr">string</span>


    <span class="kd">constructor</span><span class="p">(</span><span class="nx">street</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">city</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">zip</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">state</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">country</span><span class="p">:</span> <span class="kr">string</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">street</span> <span class="o">=</span> <span class="nx">street</span><span class="p">;</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">city</span> <span class="o">=</span> <span class="nx">city</span><span class="p">;</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">zip</span> <span class="o">=</span> <span class="nx">zip</span><span class="p">;</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">state</span> <span class="o">=</span> <span class="nx">state</span><span class="p">;</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">country</span> <span class="o">=</span> <span class="nx">country</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">static</span> <span class="nx">create</span><span class="p">(</span><span class="nx">street</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">city</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">zip</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">state</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">country</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="nx">Address</span> <span class="p">{</span>
        <span class="k">return</span> <span class="k">new</span> <span class="nx">AddressFactory</span><span class="p">().</span><span class="nx">create</span><span class="p">(</span><span class="nx">street</span><span class="p">,</span> <span class="nx">city</span><span class="p">,</span> <span class="nx">zip</span><span class="p">,</span> <span class="nx">state</span><span class="p">,</span> <span class="nx">country</span><span class="p">)</span>
    <span class="p">}</span>

    <span class="nx">print</span><span class="p">():</span> <span class="kr">string</span> <span class="p">{</span>
        <span class="k">return</span> <span class="s2">`</span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">street</span><span class="p">}</span><span class="s2">, </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">city</span><span class="p">}</span><span class="s2">, </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">zip</span><span class="p">}</span><span class="s2">, </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">state</span><span class="p">}</span><span class="s2">, </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">country</span><span class="p">}</span><span class="s2">`</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Ahora podemos usar la factoría para instanciar direcciones. En este test, usando tanto la factoría directamente, como a través del método factoría en <code class="language-plaintext highlighter-rouge">Address</code>.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">describe</span><span class="p">(</span><span class="dl">'</span><span class="s1">Data Clump Example With International Support</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="nx">it</span><span class="p">(</span><span class="dl">'</span><span class="s1">should generate an Invoice for US</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
        <span class="kd">const</span> <span class="nx">invoice</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Invoice</span><span class="p">(</span>
            <span class="dl">'</span><span class="s1">Jane Doe</span><span class="dl">'</span><span class="p">,</span>
            <span class="k">new</span> <span class="nx">AddressFactory</span><span class="p">().</span><span class="nx">create</span><span class="p">(</span><span class="dl">'</span><span class="s1">15, Foo Street</span><span class="dl">'</span><span class="p">,</span>
                <span class="dl">'</span><span class="s1">ToonTown</span><span class="dl">'</span><span class="p">,</span>
                <span class="dl">'</span><span class="s1">12345</span><span class="dl">'</span><span class="p">,</span>
                <span class="dl">'</span><span class="s1">CA</span><span class="dl">'</span><span class="p">,</span>
                <span class="dl">'</span><span class="s1">US</span><span class="dl">'</span><span class="p">)</span>
        <span class="p">)</span>
        <span class="kd">const</span> <span class="nx">expected</span> <span class="o">=</span> <span class="s2">`Factura para: Jane Doe\nDirección: 15, Foo Street\nToonTown, CA 12345\nUSA`</span>

        <span class="nx">expect</span><span class="p">(</span><span class="nx">invoice</span><span class="p">.</span><span class="nx">print</span><span class="p">()).</span><span class="nx">toEqual</span><span class="p">(</span><span class="nx">expected</span><span class="p">)</span>
    <span class="p">})</span>

    <span class="nx">it</span><span class="p">(</span><span class="dl">'</span><span class="s1">should generate an Invoice for ES</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
        <span class="kd">const</span> <span class="nx">invoice</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Invoice</span><span class="p">(</span>
            <span class="dl">'</span><span class="s1">Pepa Pérez</span><span class="dl">'</span><span class="p">,</span>
            <span class="nx">Address</span><span class="p">.</span><span class="nx">create</span><span class="p">(</span><span class="dl">'</span><span class="s1">Calle Principal, 15</span><span class="dl">'</span><span class="p">,</span>
                <span class="dl">'</span><span class="s1">Vetusta</span><span class="dl">'</span><span class="p">,</span>
                <span class="dl">'</span><span class="s1">12345</span><span class="dl">'</span><span class="p">,</span>
                <span class="dl">'</span><span class="s1">Asturias</span><span class="dl">'</span><span class="p">,</span>
                <span class="dl">'</span><span class="s1">ES</span><span class="dl">'</span><span class="p">)</span>
        <span class="p">)</span>
        <span class="kd">const</span> <span class="nx">expected</span> <span class="o">=</span> <span class="s2">`Factura para: Pepa Pérez\nDirección: Calle Principal, 15\n12345 Vetusta (Asturias)\nEspaña`</span>

        <span class="nx">expect</span><span class="p">(</span><span class="nx">invoice</span><span class="p">.</span><span class="nx">print</span><span class="p">()).</span><span class="nx">toEqual</span><span class="p">(</span><span class="nx">expected</span><span class="p">)</span>
    <span class="p">})</span>
<span class="p">})</span>
</code></pre></div></div>

<p>En cualquier caso, el conocimiento para fabricar objetos está en <code class="language-plaintext highlighter-rouge">AddressFactory</code>.</p>

<p>Es perfectamente posible evolucionar <code class="language-plaintext highlighter-rouge">AddressFactory</code> para cumplir mejor el principio Abierto/Cerrado, pero es un tema que se aleja del objetivo de este artículo. Sin embargo, en las factorías nos podemos permitir un poco más de flexibilidad.</p>

<h2 id="conclusiones">Conclusiones</h2>

<p>El code smell Data Clump complica innecesariamente el mantenimiento de nuestras aplicaciones al mantener juntos ciertos valores que representan un concepto y obligarnos a repetir el mismo código en varios lugares diferentes, atentando contra el principio DRY.</p>

<p>La solución es introducir Value Objects, que encapsulan los conceptos que representan y sus comportamientos propios, garantizando una única representación autoritativa en el código. Esto facilita el mantenimiento.</p>

<p>Una objeción que puede que tengas es si introducir varias clases nuevas no aumenta la complejidad. Si bien es cierto que introducir varias clases puede hacer parecer que el sistema se vuelve más complejo, lo cierto es que la complejidad disminuye en la medida en las nuevas clases cumplan el principio KISS (Keep it simply stupid).</p>

<p>Veamos un ejemplo sin resolver el data clump. <code class="language-plaintext highlighter-rouge">print</code> tiene una complejidad ciclomática de 3 y crecería con cada nuevo tipo de dirección a la que tengamos que dar soporte.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">class</span> <span class="nx">Invoice</span> <span class="p">{</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="nx">customerName</span><span class="p">:</span> <span class="kr">string</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="nx">customerCity</span><span class="p">:</span> <span class="kr">string</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="nx">customerStreet</span><span class="p">:</span> <span class="kr">string</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="nx">customerZip</span><span class="p">:</span> <span class="kr">string</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="nx">customerState</span><span class="p">:</span> <span class="kr">string</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="nx">customerCountry</span><span class="p">:</span> <span class="kr">string</span>

    <span class="kd">constructor</span><span class="p">(</span>
        <span class="nx">customerName</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
        <span class="nx">customerStreet</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
        <span class="nx">customerCity</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
        <span class="nx">customerZip</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
        <span class="nx">customerState</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
        <span class="nx">customerCountry</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
    <span class="p">)</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">customerZip</span> <span class="o">=</span> <span class="nx">customerZip</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">customerStreet</span> <span class="o">=</span> <span class="nx">customerStreet</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">customerCity</span> <span class="o">=</span> <span class="nx">customerCity</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">customerName</span> <span class="o">=</span> <span class="nx">customerName</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">customerState</span> <span class="o">=</span> <span class="nx">customerState</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">customerCountry</span> <span class="o">=</span> <span class="nx">customerCountry</span>
    <span class="p">}</span>

    <span class="nx">print</span><span class="p">():</span> <span class="kr">string</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">customerCountry</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">ES</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
            <span class="k">return</span> <span class="p">(</span>
                <span class="s2">`Factura para: </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">customerName</span><span class="p">}</span><span class="s2">\n`</span> <span class="o">+</span>
                <span class="s2">`Dirección: </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">customerStreet</span><span class="p">}</span><span class="s2">\n</span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">customerZip</span><span class="p">}</span><span class="s2"> </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">customerCity</span><span class="p">}</span><span class="s2"> (</span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">customerState</span><span class="p">}</span><span class="s2">)\nEspaña`</span>
            <span class="p">);</span>
        <span class="p">}</span>
        <span class="k">if</span> <span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">customerCountry</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">US</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
            <span class="k">return</span><span class="p">(</span>
                <span class="s2">`Factura para: </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">customerName</span><span class="p">}</span><span class="s2">\n`</span> <span class="o">+</span>
                <span class="s2">`Dirección: </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">customerStreet</span><span class="p">}</span><span class="s2">\n</span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">customerCity</span><span class="p">}</span><span class="s2">, </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">customerState</span><span class="p">}</span><span class="s2"> </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">customerZip</span><span class="p">}</span><span class="s2">\nUSA`</span><span class="p">)</span>
        <span class="p">}</span>
        <span class="k">return</span> <span class="p">(</span>
            <span class="s2">`Factura para: </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">customerName</span><span class="p">}</span><span class="s2">\n`</span> <span class="o">+</span>
            <span class="s2">`Dirección: </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">customerStreet</span><span class="p">}</span><span class="s2">, </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">customerCity</span><span class="p">}</span><span class="s2">, </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">customerZip</span><span class="p">}</span><span class="s2">, </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">customerState</span><span class="p">}</span><span class="s2">, </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">customerCountry</span><span class="p">}</span><span class="s2">`</span>
        <span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Pero usando polimorfismo, la complejidad ciclomática del método <code class="language-plaintext highlighter-rouge">print</code> se reduce a 1.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">class</span> <span class="nx">ESAddress</span> <span class="kd">extends</span> <span class="nx">Address</span> <span class="p">{</span>
    <span class="nx">print</span><span class="p">():</span> <span class="kr">string</span> <span class="p">{</span>
        <span class="k">return</span> <span class="s2">`</span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">street</span><span class="p">}</span><span class="s2">\n</span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">zip</span><span class="p">}</span><span class="s2"> </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">city</span><span class="p">}</span><span class="s2"> (</span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">state</span><span class="p">}</span><span class="s2">)\nEspaña`</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Se puede argumentar que <code class="language-plaintext highlighter-rouge">AddressFactory</code> tiene efectivamente una complejidad de 3 y crecería si tenemos que añadir un nuevo tipo:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">class</span> <span class="nx">AddressFactory</span> <span class="p">{</span>
    <span class="nx">create</span><span class="p">(</span><span class="nx">street</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">city</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">zip</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">state</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">country</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="nx">Address</span> <span class="p">{</span>
        <span class="k">switch</span> <span class="p">(</span><span class="nx">country</span><span class="p">)</span> <span class="p">{</span>
            <span class="k">case</span> <span class="dl">'</span><span class="s1">ES</span><span class="dl">'</span><span class="p">:</span>
                <span class="k">return</span> <span class="k">new</span> <span class="nx">ESAddress</span><span class="p">(</span><span class="nx">street</span><span class="p">,</span> <span class="nx">city</span><span class="p">,</span> <span class="nx">zip</span><span class="p">,</span> <span class="nx">state</span><span class="p">,</span> <span class="nx">country</span><span class="p">)</span>
            <span class="k">case</span> <span class="dl">'</span><span class="s1">US</span><span class="dl">'</span><span class="p">:</span>
                <span class="k">return</span> <span class="k">new</span> <span class="nx">USAddress</span><span class="p">(</span><span class="nx">street</span><span class="p">,</span> <span class="nx">city</span><span class="p">,</span> <span class="nx">zip</span><span class="p">,</span> <span class="nx">state</span><span class="p">,</span> <span class="nx">country</span><span class="p">)</span>
            <span class="k">default</span><span class="p">:</span>
                <span class="k">return</span> <span class="k">new</span> <span class="nx">Address</span><span class="p">(</span><span class="nx">street</span><span class="p">,</span> <span class="nx">city</span><span class="p">,</span> <span class="nx">zip</span><span class="p">,</span> <span class="nx">state</span><span class="p">,</span> <span class="nx">country</span><span class="p">)</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Sin embargo, podríamos refactorizarla para que la complejidad disminuya a 1, incluso añadiendo nuevas clases, gracias a user un mapa que nos relaciona el país con la constructora del tipo de dirección adecuado.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">class</span> <span class="nx">AddressFactory</span> <span class="p">{</span>
    <span class="k">private</span> <span class="k">static</span> <span class="k">readonly</span> <span class="nx">addressMap</span><span class="p">:</span> <span class="p">{</span> <span class="p">[</span><span class="nx">key</span><span class="p">:</span> <span class="kr">string</span><span class="p">]:</span> <span class="k">new</span> <span class="p">(</span><span class="nx">street</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">city</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">zip</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">state</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">country</span><span class="p">:</span> <span class="kr">string</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">Address</span> <span class="p">}</span> <span class="o">=</span> <span class="p">{</span>
        <span class="dl">'</span><span class="s1">ES</span><span class="dl">'</span><span class="p">:</span> <span class="nx">ESAddress</span><span class="p">,</span>
        <span class="dl">'</span><span class="s1">US</span><span class="dl">'</span><span class="p">:</span> <span class="nx">USAddress</span><span class="p">,</span>
    <span class="p">};</span>

    <span class="k">static</span> <span class="nx">create</span><span class="p">(</span>
        <span class="nx">street</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
        <span class="nx">city</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
        <span class="nx">zip</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
        <span class="nx">state</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
        <span class="nx">country</span><span class="p">:</span> <span class="kr">string</span>
    <span class="p">):</span> <span class="nx">Address</span> <span class="p">{</span>
        <span class="kd">const</span> <span class="nx">AddressClass</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">addressMap</span><span class="p">[</span><span class="nx">country</span><span class="p">]</span> <span class="o">||</span> <span class="nx">Address</span><span class="p">;</span>
        <span class="k">return</span> <span class="k">new</span> <span class="nx">AddressClass</span><span class="p">(</span><span class="nx">street</span><span class="p">,</span> <span class="nx">city</span><span class="p">,</span> <span class="nx">zip</span><span class="p">,</span> <span class="nx">state</span><span class="p">,</span> <span class="nx">country</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Ahora bien, independientemente de usar una u otra factoría, la dificultad de dar soporte a otros formatos de dirección, corregir posibles errores, o introducir nuevos campos, es muy baja con la solución de Value Objects: solo tenemos que añadir una clase nueva y mapearla en la factoría.</p>

<p>Así que, cuando veas que estás manteniendo grupos de datos juntos en distintas partes del código, es muy probable que tengas un <em>data clump</em> y que puedas resolverlo con Value Objects.</p>]]></content><author><name></name></author><category term="articles" /><category term="code-smells" /><category term="refactoring" /><category term="typescript" /><summary type="html"><![CDATA[Aprovechando material que he preparado para un curso de Refactoring, voy a empezar una serie de artículos sobre code smells. El primero que vamos a tratar es el Data Clump.]]></summary></entry><entry><title type="html">El configurador y la aplicación revisados</title><link href="https://franiglesias.github.io/hexagonal-tdd-8/" rel="alternate" type="text/html" title="El configurador y la aplicación revisados" /><published>2025-09-24T00:00:00+00:00</published><updated>2025-09-24T00:00:00+00:00</updated><id>https://franiglesias.github.io/hexagonal-tdd-8</id><content type="html" xml:base="https://franiglesias.github.io/hexagonal-tdd-8/"><![CDATA[<p>Una vuelta al tema del configurador después de hablar sobre Arquitectura Hexagonal <em>siguiendo el libro</em> en la
PulpoCon25.</p>

<p>Efectivamente, este año hemos estado en la PulpoCon25 explicando el patrón de Arquitectura Hexagonal, tratando de seguir
el libro con el objetivo de conocerlo en su formulación original, bastante olvidada y de la que se cumplen más de 20
años.</p>

<p>Para este taller preparé una nueva versión de Inventory a fin de ilustrar como es el proceso de desarrollo de software
con Arquitectura Hexagonal, y como podría aplicarse a otros modelos de arquitectura, como la más conocida de tres capas
con ley de dependencia, que a veces llamamos <em>arquitectura limpia</em> (y también muy erróneamente arquitectura hexagonal).</p>

<p>Durante las dos sesiones de talleres surgieron comentarios interesantes, tanto sobre el patrón como sobre el ejemplo
propuesto.</p>

<p>Parte de esos comentarios tienen que ver con el quinto elemento: el configurador y distintas formas de montar la
aplicación.</p>

<p>Puedes ver el código al que me refiero en su <a href="https://github.com/franiglesias/api-inventory">repositorio</a></p>

<p>Más específicamente, el código problemático es este:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">function</span> <span class="nx">createInventoryRouter</span><span class="p">(</span><span class="nx">forDispatching</span><span class="p">:</span> <span class="nx">MessageBusAdapter</span><span class="p">)</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">router</span> <span class="o">=</span> <span class="nx">express</span><span class="p">.</span><span class="nx">Router</span><span class="p">()</span>

    <span class="kd">const</span> <span class="nx">forUpdatingStock</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">ForUpdatingStockApiAdapter</span><span class="p">(</span><span class="nx">forDispatching</span><span class="p">)</span>
    <span class="nx">router</span><span class="p">.</span><span class="nx">post</span><span class="p">(</span><span class="dl">'</span><span class="s1">/products/:sku/add</span><span class="dl">'</span><span class="p">,</span> <span class="nx">forUpdatingStock</span><span class="p">.</span><span class="nx">postAddUnits</span><span class="p">.</span><span class="nx">bind</span><span class="p">(</span><span class="nx">forUpdatingStock</span><span class="p">))</span>
    <span class="nx">router</span><span class="p">.</span><span class="nx">post</span><span class="p">(</span><span class="dl">'</span><span class="s1">/products/:sku/remove</span><span class="dl">'</span><span class="p">,</span> <span class="nx">forUpdatingStock</span><span class="p">.</span><span class="nx">postRemoveUnits</span><span class="p">.</span><span class="nx">bind</span><span class="p">(</span><span class="nx">forUpdatingStock</span><span class="p">))</span>

    <span class="kd">const</span> <span class="nx">forRegisteringProducts</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">ForRegisterProductsApiAdapter</span><span class="p">(</span><span class="nx">forDispatching</span><span class="p">)</span>
    <span class="nx">router</span><span class="p">.</span><span class="nx">post</span><span class="p">(</span><span class="dl">'</span><span class="s1">/products</span><span class="dl">'</span><span class="p">,</span> <span class="nx">forRegisteringProducts</span><span class="p">.</span><span class="nx">postProducts</span><span class="p">.</span><span class="nx">bind</span><span class="p">(</span><span class="nx">forRegisteringProducts</span><span class="p">))</span>

    <span class="kd">const</span> <span class="nx">forGettingProducts</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">ForGettingProductsApiAdapter</span><span class="p">(</span><span class="nx">forDispatching</span><span class="p">)</span>
    <span class="nx">router</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="dl">'</span><span class="s1">/products</span><span class="dl">'</span><span class="p">,</span> <span class="nx">forGettingProducts</span><span class="p">.</span><span class="nx">getProducts</span><span class="p">.</span><span class="nx">bind</span><span class="p">(</span><span class="nx">forGettingProducts</span><span class="p">))</span>

    <span class="kd">const</span> <span class="nx">forCheckingHealth</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">ForCheckingHealthApiAdapter</span><span class="p">(</span><span class="nx">forDispatching</span><span class="p">)</span>
    <span class="nx">router</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="dl">'</span><span class="s1">/health</span><span class="dl">'</span><span class="p">,</span> <span class="nx">forCheckingHealth</span><span class="p">.</span><span class="nx">getHealth</span><span class="p">.</span><span class="nx">bind</span><span class="p">(</span><span class="nx">forCheckingHealth</span><span class="p">))</span>

    <span class="nx">router</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="dl">'</span><span class="s1">/</span><span class="dl">'</span><span class="p">,</span> <span class="p">(</span><span class="nx">_request</span><span class="p">,</span> <span class="nx">response</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">response</span><span class="p">.</span><span class="nx">status</span><span class="p">(</span><span class="mi">200</span><span class="p">).</span><span class="nx">send</span><span class="p">(</span><span class="dl">'</span><span class="s1">Hello World</span><span class="dl">'</span><span class="p">))</span>

    <span class="k">return</span> <span class="nx">router</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Y, ¿cuál sería el problema?</p>

<p>Básicamente que estamos inicializando adaptadores primarios, pasándoles directamente adaptadores secundarios, para
montar la aplicación. Ciertamente, es un poco raro, dado que estamos haciendo que el lado primario dependa del lado
secundario de forma directa.</p>

<p>De hecho, ahora mismo caigo en la cuenta de un error de tipado, aunque no impide a la aplicación funcionar.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">function</span> <span class="nx">createInventoryRouter</span><span class="p">(</span><span class="nx">forDispatching</span><span class="p">:</span> <span class="nx">MessageBusAdapter</span><span class="p">)</span>
</code></pre></div></div>

<p>Debería ser:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">function</span> <span class="nx">createInventoryRouter</span><span class="p">(</span><span class="nx">forDispatching</span><span class="p">:</span> <span class="nx">ForDispatchinMessages</span><span class="p">):</span> <span class="nx">Router</span>
</code></pre></div></div>

<p>Solucionado esto, hablemos del problema de fondo. ¿Es una solución correcta? ¿Cuáles son las alternativas?</p>

<p>La objeción principal sería que la interfaz requerida por el puerto secundario no tendría que ser conocida por el puerto
primario.</p>

<p>Pero, por otra parte, podríamos considerar que forma parte de la interfaz <em>provista</em> por el puerto primario. Es decir:
el puerto primario se define por un comando o query que se publica mediante un sistema de despacho de mensajes.</p>

<p>Personalmente, creo que es una solución válida en el contexto de una aplicación sencilla como esta.</p>

<p>Sin embargo, es cierto que podríamos hacerlo mejor. Básicamente, encapsulando algunas operaciones en un objeto que
represente la Aplicación como tal, o incluso varios objetos que representen los distintos puertos primarios.</p>

<p>Veamos a donde nos lleva esto.</p>

<h2 id="un-objeto-aplicación">Un objeto Aplicación</h2>

<p>Dado que el problema es tener dependencias que no queremos que sean directas, lo adecuado es introducir un patrón
Mediator.</p>

<p>Este mediador será un objeto que represente la aplicación, exponiendo un método que pueda ser usado por los adaptadores
primarios para hablar con ella.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">class</span> <span class="nx">InventoryApplication</span> <span class="p">{</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="nx">forDispatchingMessages</span><span class="p">:</span> <span class="nx">ForDispatchingMessages</span><span class="p">;</span>

    <span class="kd">constructor</span><span class="p">(</span><span class="nx">forDispatchingMessages</span><span class="p">:</span> <span class="nx">ForDispatchingMessages</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">forDispatchingMessages</span> <span class="o">=</span> <span class="nx">forDispatchingMessages</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="nx">execute</span><span class="p">(</span><span class="nx">message</span><span class="p">:</span> <span class="nx">Message</span><span class="p">):</span> <span class="nx">unknown</span> <span class="p">{</span>
        <span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">forDispatchingMessages</span><span class="p">.</span><span class="nx">dispatch</span><span class="p">(</span><span class="nx">message</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Ahora tenemos que hacer un cambio un poco más grande, ya que necesitamos pasar la instancia de la aplicación a los
adaptadores primarios. He aquí un ejemplo:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">class</span> <span class="nx">ForCheckingHealthApiAdapter</span> <span class="p">{</span>
    <span class="k">private</span> <span class="nx">application</span><span class="p">:</span> <span class="nx">InventoryApplication</span>

    <span class="kd">constructor</span><span class="p">(</span><span class="nx">forDispatching</span><span class="p">:</span> <span class="nx">InventoryApplication</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">application</span> <span class="o">=</span> <span class="nx">forDispatching</span>
    <span class="p">}</span>

    <span class="k">public</span> <span class="k">async</span> <span class="nx">getHealth</span><span class="p">(</span>
        <span class="nx">req</span><span class="p">:</span> <span class="nx">Request</span><span class="o">&lt;</span><span class="p">{},</span> <span class="kr">any</span><span class="p">,</span> <span class="kr">any</span><span class="p">,</span> <span class="nx">ParsedQs</span><span class="p">,</span> <span class="nb">Record</span><span class="o">&lt;</span><span class="kr">string</span><span class="p">,</span> <span class="kr">any</span><span class="o">&gt;&gt;</span><span class="p">,</span>
        <span class="nx">response</span><span class="p">:</span> <span class="nx">Response</span><span class="o">&lt;</span><span class="kr">any</span><span class="p">,</span> <span class="nb">Record</span><span class="o">&lt;</span><span class="kr">string</span><span class="p">,</span> <span class="kr">any</span><span class="o">&gt;</span><span class="p">,</span> <span class="kr">number</span><span class="o">&gt;</span><span class="p">,</span>
    <span class="p">):</span> <span class="nb">Promise</span><span class="o">&lt;</span><span class="k">void</span><span class="o">&gt;</span> <span class="p">{</span>
        <span class="kd">const</span> <span class="nx">getHealth</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">GetHealth</span><span class="p">()</span>
        <span class="k">if</span> <span class="p">(</span><span class="k">await</span> <span class="k">this</span><span class="p">.</span><span class="nx">application</span><span class="p">.</span><span class="nx">execute</span><span class="p">(</span><span class="nx">getHealth</span><span class="p">))</span> <span class="p">{</span>
            <span class="nx">response</span><span class="p">.</span><span class="nx">status</span><span class="p">(</span><span class="mi">200</span><span class="p">).</span><span class="nx">json</span><span class="p">({</span>
                <span class="na">status</span><span class="p">:</span> <span class="dl">'</span><span class="s1">ok</span><span class="dl">'</span><span class="p">,</span>
            <span class="p">})</span>
            <span class="k">return</span>
        <span class="p">}</span>
        <span class="nx">response</span><span class="p">.</span><span class="nx">status</span><span class="p">(</span><span class="mi">500</span><span class="p">).</span><span class="nx">json</span><span class="p">({</span>
            <span class="na">error</span><span class="p">:</span> <span class="dl">'</span><span class="s1">Internal Server Error. App is not working.</span><span class="dl">'</span><span class="p">,</span>
            <span class="na">code</span><span class="p">:</span> <span class="dl">'</span><span class="s1">500</span><span class="dl">'</span><span class="p">,</span>
        <span class="p">})</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>En resumidas cuentas, ahora los adaptadores primarios interactúan con la Aplicación, sin saber nada de ella, salvo los
comandos que le tienen que pasar.</p>

<p>El punto de entrada queda de esta manera, cambiando las funciones <code class="language-plaintext highlighter-rouge">build*</code> para que reflejen correctamente lo que está
pasando.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">forDispatchingMessages</span> <span class="o">=</span> <span class="nx">buildMessageBus</span><span class="p">()</span>
<span class="kd">const</span> <span class="nx">inventoryApplication</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">InventoryApplication</span><span class="p">(</span><span class="nx">forDispatchingMessages</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">inventoryRouter</span> <span class="o">=</span> <span class="nx">createInventoryRouter</span><span class="p">(</span><span class="nx">inventoryApplication</span><span class="p">)</span>

<span class="kd">const</span> <span class="nx">app</span> <span class="o">=</span> <span class="nx">createApp</span><span class="p">(</span><span class="nx">inventoryRouter</span><span class="p">)</span>

<span class="nx">startServer</span><span class="p">(</span><span class="nx">app</span><span class="p">,</span> <span class="nx">PORT</span><span class="p">)</span>

<span class="k">export</span> <span class="p">{</span><span class="nx">app</span><span class="p">}</span>
</code></pre></div></div>

<h2 id="un-código-más-acorde-con-ah-y-oop">Un código más acorde con AH y OOP</h2>

<p>El resultado es un código más acorde con el principio de Arquitectura Hexagonal, pero también con Orientación a Objetos.</p>

<p><code class="language-plaintext highlighter-rouge">InventoryApplication</code> se construye inyectándole los adaptadores secundarios, ya sea directamente (ForDispatchingMessages) como indirectamente, ya que el bus de mensajes registra los adaptadores secundarios ya montados.</p>

<p>Por otro lado, los adaptadores primarios, que usan la interfaz proporcionada que, en este caso, son los distintos comandos y queries definidos y reciben la instancia de <code class="language-plaintext highlighter-rouge">InventoryApplication</code> y así poder <em>hablar</em> con ella.</p>]]></content><author><name></name></author><category term="articles" /><category term="software-design" /><category term="design-patterns" /><category term="typescript" /><category term="tdd" /><category term="hexagonal" /><summary type="html"><![CDATA[Una vuelta al tema del configurador después de hablar sobre Arquitectura Hexagonal siguiendo el libro en la PulpoCon25.]]></summary></entry></feed>