<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: IagoLast</title>
    <description>The latest articles on Forem by IagoLast (@iagolast).</description>
    <link>https://forem.com/iagolast</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F607559%2F9c64c4c8-46a7-494d-9959-f7de1b7f6342.png</url>
      <title>Forem: IagoLast</title>
      <link>https://forem.com/iagolast</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/iagolast"/>
    <language>en</language>
    <item>
      <title>Testing de SPAs con Vitest Browser Mode: Velocidad de Unit Tests con Confianza E2E</title>
      <dc:creator>IagoLast</dc:creator>
      <pubDate>Sun, 21 Dec 2025 20:35:40 +0000</pubDate>
      <link>https://forem.com/iagolast/testing-de-spas-con-vitest-browser-mode-velocidad-de-unit-tests-con-confianza-e2e-1be8</link>
      <guid>https://forem.com/iagolast/testing-de-spas-con-vitest-browser-mode-velocidad-de-unit-tests-con-confianza-e2e-1be8</guid>
      <description>&lt;h2&gt;
  
  
  La Revolución del Testing
&lt;/h2&gt;

&lt;p&gt;Kent C. Dodds revolucionó el testing frontend con una idea simple pero poderosa:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;"Cuanto más se parezcan tus tests a la forma en que se usa tu software, más confianza pueden darte."&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Antes de Testing Library, los tests tenían este aspecto:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Probando detalles de implementación ❌&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;wrapper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.primary-focus-default-foo&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;simulate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;change&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;test@test.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;wrapper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;state&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;test@test.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Este enfoque tenía un problema fundamental: &lt;strong&gt;estábamos probando cómo funcionaba el código, no lo que experimentaba el usuario&lt;/strong&gt;. Si renombrábamos una clase CSS o refactorizábamos el estado interno, el test se rompía—incluso si la aplicación seguía funcionando perfectamente.&lt;/p&gt;

&lt;p&gt;Testing Library lo cambió todo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Probando comportamiento ✅&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByLabelText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Email&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;userEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;test@test.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toHaveValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;test@test.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ahora nuestros tests se leen como historias de usuario: "buscar el input de email, escribir un email, verificar que aparece". Si cambia la clase CSS, el test sigue pasando. Si cambiamos el estado de React por una librería de formularios, el test sigue pasando. &lt;strong&gt;Estamos probando comportamiento, no implementación.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  La Era de jsdom: Velocidad a un Coste
&lt;/h2&gt;

&lt;p&gt;Los tests con Testing Library son rápidos—milisegundos, no segundos. Esta velocidad viene de &lt;strong&gt;jsdom&lt;/strong&gt;, una implementación de JavaScript del DOM que se ejecuta en Node.js.&lt;/p&gt;

&lt;p&gt;En lugar de lanzar un navegador real, jsdom simula uno en memoria. Nuestros tests se ejecutan al instante porque no hay arranque del navegador, no hay motor de renderizado, no hay pila de red real.&lt;/p&gt;

&lt;p&gt;Pero jsdom es una simulación. No implementa todo:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No hay renderizado real de CSS ni cálculos de layout&lt;/li&gt;
&lt;li&gt;No hay peticiones de red reales&lt;/li&gt;
&lt;li&gt;Diferencias sutiles en el manejo de eventos&lt;/li&gt;
&lt;li&gt;APIs web que faltan&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Para la mayoría de tests, esto no importa. Pero de vez en cuando, un test pasa en jsdom y falla en un navegador real. O peor: un test pasa en todas partes, pero la funcionalidad está rota en producción.&lt;/p&gt;

&lt;h2&gt;
  
  
  El Problema de los Efectos Secundarios: MSW y server-stubs
&lt;/h2&gt;

&lt;p&gt;Incluso con una simulación perfecta del DOM, nuestras aplicaciones no existen aisladas. Hacen peticiones HTTP. Leen cookies. Interactúan con servidores.&lt;/p&gt;

&lt;p&gt;Nuestros tests necesitan manejar estos &lt;strong&gt;efectos secundarios&lt;/strong&gt;. La comunidad se decantó por &lt;a href="https://mswjs.io/" rel="noopener noreferrer"&gt;MSW (Mock Service Worker)&lt;/a&gt; como solución estándar. MSW intercepta las peticiones HTTP a nivel de red—nuestra aplicación no sabe que está siendo testeada.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;HttpResponse&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;msw&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;setupServer&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;msw/browser&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;server&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;setupServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/users&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;HttpResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;John&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Jane&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;]);&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;MSW es potente, pero configurar los handlers puede volverse verboso. Por eso construimos &lt;strong&gt;&lt;a href="https://github.com/frontend-testing/server-stubs" rel="noopener noreferrer"&gt;@frontend-testing/server-stubs&lt;/a&gt;&lt;/strong&gt;—una capa fina sobre MSW que simplifica el stubbing a una sola línea:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;stubJsonResponse&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@frontend-testing/server-stubs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;spy&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;stubJsonResponse&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;*/api/users&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;GET&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;John&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Después de la petición:&lt;/span&gt;
&lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;spy&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toHaveBeenCalledTimes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sin boilerplate de handlers. Spies de peticiones integrados. Respuestas secuenciales para lógica de reintentos. Todos los patrones comunes, simplificados.&lt;/p&gt;

&lt;h2&gt;
  
  
  2025: Vitest Browser Mode lo Cambia Todo
&lt;/h2&gt;

&lt;p&gt;Luego llegó &lt;strong&gt;Vitest 2.0&lt;/strong&gt; con Browser Mode, y el panorama cambió.&lt;/p&gt;

&lt;p&gt;Browser Mode ejecuta tus tests en un &lt;strong&gt;navegador real&lt;/strong&gt;—Chromium, Firefox o WebKit—mientras mantiene la velocidad de Vitest. No más jsdom. No más simulaciones. DOM real, eventos reales, APIs reales del navegador.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// vitest.config.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;playwright&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@vitest/browser-playwright&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineConfig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;vitest/config&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;playwright&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="na"&gt;instances&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;chromium&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tus tests se ejecutan en milisegundos—igual que con jsdom—pero en un navegador real. La brecha de confianza se cierra.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mi Enfoque: Casi E2E, a Velocidad de Unit Test
&lt;/h2&gt;

&lt;p&gt;Con estas piezas en su lugar, propongo una estrategia de testing que te da la confianza de E2E a velocidad de unit test:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Usar Vitest Browser Mode&lt;/strong&gt; — Navegador real, confianza real&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Renderizar la aplicación completa&lt;/strong&gt; — No componentes aislados, la app real&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Navegar a la ruta bajo prueba&lt;/strong&gt; — Usando &lt;code&gt;@frontend-testing/vitest-browser-navigate&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hacer stub de respuestas del servidor&lt;/strong&gt; — Usando server-stubs sobre MSW&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Opcionalmente sembrar estado&lt;/strong&gt; — Cookies, localStorage, tokens de autenticación&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Todo lo demás es real. Routing real. Componentes reales. Interacciones de usuario reales.&lt;/p&gt;

&lt;h3&gt;
  
  
  El Problema de la Navegación
&lt;/h3&gt;

&lt;p&gt;Hay un detalle: en Browser Mode, tu app se renderiza dentro de un iframe. No puedes simplemente cambiar &lt;code&gt;window.location&lt;/code&gt;—necesitas simular la navegación de una SPA.&lt;/p&gt;

&lt;p&gt;Por eso construimos &lt;strong&gt;&lt;a href="https://github.com/frontend-testing/vitest-browser-navigate" rel="noopener noreferrer"&gt;@frontend-testing/vitest-browser-navigate&lt;/a&gt;&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-D&lt;/span&gt; @frontend-testing/vitest-browser-navigate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// vitest.config.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;navigate&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@frontend-testing/vitest-browser-navigate&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;commands&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;navigate&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ahora puedes navegar como un usuario:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;commands&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;vitest/browser&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;commands&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;navigate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/dashboard&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bajo el capó, llama a &lt;code&gt;history.pushState()&lt;/code&gt; y dispara un evento &lt;code&gt;popstate&lt;/code&gt;. Tu router—React Router, Vue Router, el que uses—reacciona exactamente igual que con navegación real.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ejemplo Completo: Testeando un Flujo de Login
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// login-page.spec.tsx&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@frontend-testing/vitest-browser-navigate&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;App&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@/App&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;stubJsonResponse&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@frontend-testing/server-stubs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;waitFor&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@testing-library/react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;test&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;vitest&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;render&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;vitest-browser-react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;commands&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;userEvent&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;vitest/browser&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;LoginPage&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;should login and redirect to dashboard&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// 1. Hacer stub de la respuesta del servidor&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;spy&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;stubJsonResponse&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;*/auth/login&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mock-token&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="c1"&gt;// 2. Navegar a la página de login&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;commands&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;navigate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/login&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// 3. Renderizar la aplicación COMPLETA&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;App&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// 4. Interactuar como un usuario real&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;userEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByLabelText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Email&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;test@test.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;userEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByLabelText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Password&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;password123&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;userEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Login&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// 5. Verificar que la petición se hizo correctamente&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;spy&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toHaveBeenCalledTimes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;spy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;calls&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;test@test.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;password123&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="c1"&gt;// 6. Verificar que la navegación ocurrió&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;waitFor&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pathname&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/dashboard&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fíjate en lo que estamos probando:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ Renderizado en navegador real&lt;/li&gt;
&lt;li&gt;✅ Routing completo de la aplicación&lt;/li&gt;
&lt;li&gt;✅ Interacciones de usuario reales&lt;/li&gt;
&lt;li&gt;✅ Verificación de peticiones HTTP&lt;/li&gt;
&lt;li&gt;✅ Resultados de navegación&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;El único mock es la respuesta del servidor. Todo lo demás es la aplicación real.&lt;/p&gt;

&lt;h2&gt;
  
  
  Por Qué Funciona Este Enfoque
&lt;/h2&gt;

&lt;p&gt;Este enfoque funciona porque combina lo mejor de ambos mundos: la velocidad de los unit tests con la confianza de los tests E2E. Mis tests se ejecutan en un navegador real, así que si pasan, la funcionalidad realmente funciona—no más sorpresas de "funciona en jsdom". Los tests se completan en milisegundos, permitiéndome ejecutar cientos en segundos y mantener mi flujo de desarrollo ágil. Pruebo comportamiento, no implementación, lo que significa que los refactors no rompen tests y los cambios de CSS no rompen tests—solo los cambios de funcionalidad real lo hacen. Y es simple: un patrón para todo. Navegar, interactuar, verificar. Sin infraestructura compleja de E2E, sin servidores separados de Cypress, sin mantener dos suites de tests diferentes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Empezando
&lt;/h2&gt;

&lt;p&gt;Configurar este stack de testing lleva unos 10 minutos. Aquí tienes una guía paso a paso.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Instalar Dependencias
&lt;/h3&gt;

&lt;p&gt;Primero, instala Vitest con soporte para browser mode y nuestras librerías auxiliares:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-D&lt;/span&gt; vitest @vitest/browser playwright
npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-D&lt;/span&gt; @frontend-testing/vitest-browser-navigate
npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-D&lt;/span&gt; @frontend-testing/server-stubs msw
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Si estás usando React, también querrás las utilidades de render compatibles con el navegador:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-D&lt;/span&gt; vitest-browser-react @testing-library/react
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Configurar Vitest
&lt;/h3&gt;

&lt;p&gt;Crea o actualiza tu &lt;code&gt;vitest.config.ts&lt;/code&gt; para habilitar browser mode y registrar el comando navigate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// vitest.config.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;playwright&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@vitest/browser-playwright&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;navigate&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@frontend-testing/vitest-browser-navigate&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineConfig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;vitest/config&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;playwright&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="na"&gt;instances&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;chromium&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
      &lt;span class="na"&gt;commands&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;navigate&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;setupFiles&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./test/setup.ts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Configurar MSW y server-stubs
&lt;/h3&gt;

&lt;p&gt;Crea un archivo de setup de tests que inicialice MSW. El &lt;code&gt;serverManager&lt;/code&gt; de server-stubs desacopla el servidor MSW de tus archivos de test, así que lo configuras una vez y usas stubs en cualquier lugar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// test/setup.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;setupWorker&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;msw/browser&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;serverManager&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@frontend-testing/server-stubs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;beforeAll&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;afterEach&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;afterAll&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;vitest&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;server&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;setupWorker&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;serverManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setDefaultServerLoader&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nf"&gt;beforeAll&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;onUnhandledRequest&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;warn&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}));&lt;/span&gt;
&lt;span class="nf"&gt;afterEach&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resetHandlers&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="nf"&gt;afterAll&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;La opción &lt;code&gt;onUnhandledRequest: "warn"&lt;/code&gt; es útil durante el desarrollo—registra una advertencia cuando tu app hace una petición que no está stubbeada, ayudándote a detectar mocks faltantes.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Escribir Tu Primer Test
&lt;/h3&gt;

&lt;p&gt;Ahora estás listo para escribir tests. Crea un archivo spec e importa todo lo que necesites:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/pages/home.spec.tsx&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;stubJsonResponse&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@frontend-testing/server-stubs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;screen&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@testing-library/react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;test&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;vitest&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;render&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;vitest-browser-react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;commands&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;userEvent&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;vitest/browser&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;App&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./App&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;HomePage&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;displays user data after loading&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Hacer stub de la API&lt;/span&gt;
    &lt;span class="nf"&gt;stubJsonResponse&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;*/api/user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;John Doe&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;john@example.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="c1"&gt;// Navegar y renderizar&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;commands&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;navigate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;App&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Verificar&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;element&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;John Doe&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;toBeVisible&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  5. Ejecutar Tus Tests
&lt;/h3&gt;



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

&lt;/div&gt;



&lt;p&gt;Vitest lanzará un navegador, ejecutará tus tests y mostrará los resultados en la terminal. Los tests se ejecutan en modo watch por defecto, re-ejecutándose cuando guardas cambios.&lt;/p&gt;

&lt;p&gt;Para entornos de CI, usa:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx vitest run
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Eso es todo. Estás testeando en un navegador real, a velocidad de unit test.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;¿Preguntas? Abre un issue en &lt;a href="https://github.com/frontend-testing/vitest-browser-navigate" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;Y si, este post esta escrito por IA, pero revisado por mi :)&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>frontend</category>
      <category>tooling</category>
      <category>testing</category>
      <category>typescript</category>
    </item>
    <item>
      <title>¿Qué podemos hacer con #laligagate?</title>
      <dc:creator>IagoLast</dc:creator>
      <pubDate>Wed, 16 Apr 2025 13:58:19 +0000</pubDate>
      <link>https://forem.com/iagolast/que-podemos-hacer-con-laligagate-26bb</link>
      <guid>https://forem.com/iagolast/que-podemos-hacer-con-laligagate-26bb</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7wrm9g1tj6fdc7u209bb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7wrm9g1tj6fdc7u209bb.png" alt=" " width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ya llevaba tiempo viendo en redes que muchos usuarios se quejaban de que sus webs estaban siendo bloqueadas por LaLiga. Hasta que este fin de semana me tocó a mí.&lt;/p&gt;

&lt;p&gt;Soy una persona honrada y muy trabajadora. Trabajo como un idiota, muchas horas al día. Pago una barbaridad de impuestos, cumplo todas las normativas absurdas que se me imponen, y me esfuerzo por hacer las cosas bien. Me dejo la piel para que mis webs funcionen, tengan visitas, generen ingresos, y poder ganarme la vida dignamente con software. ¿Y con qué me encuentro? Con que, de un día para otro, puede aparecer un mensaje en mi web diciendo: "esta página alberga contenido pirata y ha sido bloqueada". Aunque no tenga nada que ver. Aunque no haya hecho absolutamente nada malo.&lt;/p&gt;

&lt;h2&gt;
  
  
  El problema
&lt;/h2&gt;

&lt;p&gt;En España, como en muchos otros países, hay personas que consumen fútbol de forma ilegal a través de páginas web no autorizadas. Estas webs suelen utilizar servicios de infraestructura completamente legítimos, los mismos que emplean miles de negocios online para operar. El problema no está en el proveedor, sino en el uso que algunos hacen de él. Para entenderlo con una analogía sencilla: una banda criminal puede alquilar un piso para llevar a cabo actividades delictivas. El piso, el edificio, e incluso la compañía eléctrica que les da servicio, no tienen ninguna responsabilidad directa. El problema es lo que ocurre dentro de una unidad concreta.&lt;/p&gt;

&lt;p&gt;La situación, desde el punto de vista técnico, es compleja. Internet funciona sobre capas de protocolos que, por diseño, no permiten identificar fácilmente a qué web pertenece una dirección IP determinada. Además, las nuevas tecnologías que refuerzan la privacidad de los usuarios —como el cifrado del tráfico o el uso compartido de infraestructuras— dificultan aún más esa identificación. Esto provoca que, en la práctica, sea muy difícil aplicar un bloqueo quirúrgico. Es como si quisieras cerrar solo los pisos de tu ciudad donde se cometen delitos, pero no tuvieras forma de saber con precisión en cuáles se está produciendo la actividad ilegal.&lt;/p&gt;

&lt;h2&gt;
  
  
  La demanda
&lt;/h2&gt;

&lt;p&gt;LaLiga lleva tiempo impulsando medidas judiciales agresivas contra plataformas que considera facilitadoras del acceso ilegal a sus contenidos. Hace apenas unos meses consiguió que se bloqueara Telegram en España. La medida se revirtió en pocas horas, pero fue un aviso: no iban a detenerse. Esta vez no han ido a por una aplicación, sino contra la infraestructura misma de internet.&lt;/p&gt;

&lt;p&gt;En diciembre de 2024, LaLiga y Telefónica Audiovisual Digital (Movistar+) interpusieron una demanda contra los principales operadores de internet en España. El truco está en que Telefónica, a través de otras filiales, aparecía también como parte demandada. Es decir, era demandante y demandada al mismo tiempo.&lt;/p&gt;

&lt;p&gt;Y, curiosamente, todas las partes demandadas se allanaron. Es decir, aceptaron sin reservas lo que LaLiga pedía. No hubo oposición. No hubo debate técnico. No se evaluaron alternativas. El juzgado dictó resolución de forma inmediata y sin escuchar a terceros potencialmente afectados.&lt;/p&gt;

&lt;p&gt;Para que nos entendamos: La conclusión de todo esto es que como no sabemos en qué pisos se están cometiendo delitos, optamos por bombardear bloques de viviendas enteros, por si acaso...&lt;/p&gt;

&lt;h3&gt;
  
  
  La sentencia
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.redes-sociales.com/wp-content/uploads/2025/04/sentencia-bloqueos-ips-laliga-SJM_B_309_2024.pdf" rel="noopener noreferrer"&gt;https://www.redes-sociales.com/wp-content/uploads/2025/04/sentencia-bloqueos-ips-laliga-SJM_B_309_2024.pdf&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;La resolución dictada por el Juzgado Mercantil de Barcelona el 18 de diciembre de 2024 se basa en los artículos 138 y 139.1.h) del Texto Refundido de la Ley de Propiedad Intelectual.&lt;/p&gt;

&lt;p&gt;El artículo 138 establece que:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“Tanto las medidas de cesación específicas contempladas en el artículo 139.1.h) como las medidas cautelares previstas en el artículo 141.6 podrán también solicitarse, cuando sean apropiadas, contra los intermediarios a cuyos servicios recurra un tercero para infringir derechos de propiedad intelectual reconocidos en esta ley, aunque los actos de dichos intermediarios no constituyan en sí mismos una infracción, sin perjuicio de lo dispuesto en la Ley 34/2002, de 11 de julio, de servicios de la sociedad de la información y de comercio electrónico. Dichas medidas habrán de ser objetivas, proporcionadas y no discriminatorias.”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Y el artículo 139.1.h), por su parte, dice:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“La suspensión de los servicios prestados por intermediarios a terceros que se valgan de ellos para infringir derechos de propiedad intelectual, sin perjuicio de lo dispuesto en la Ley 34/2002, de 11 de julio, de servicios de la sociedad de la información y de comercio electrónico.”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;La ley permite actuar contra intermediarios, incluso si estos no son infractores directos, pero impone una condición explícita: que las medidas adoptadas sean proporcionadas, objetivas y no discriminatorias. Además, ambas remiten a la LSSI como límite legal que debe respetarse en todo caso.&lt;/p&gt;

&lt;p&gt;En esta sentencia, el juzgado acepta por completo los informes técnicos presentados por LaLiga y Telefónica, en los que se afirma que tecnologías como ECH y Apple Relay impiden aplicar bloqueos selectivos a nivel de dominio. A partir de ahí, se concluye que el único medio efectivo es el bloqueo de direcciones IP completas.&lt;/p&gt;

&lt;p&gt;En ningún momento se plantea si existen alternativas menos lesivas. Tampoco se solicita una verificación técnica externa. Ni se valora cuántas webs o servicios legales pueden verse afectados por esa decisión.&lt;/p&gt;

&lt;p&gt;En el &lt;strong&gt;fundamento segundo&lt;/strong&gt; se afirma expresamente:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“Sin que dicho allanamiento sea contrario a la ley, al orden público ni perjudica a terceros.”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Esa afirmación no va acompañada de ninguna evaluación. Se asume que no hay perjuicio, sin contrastarlo, sin pedir datos ni tener en cuenta la naturaleza compartida de muchas infraestructuras.&lt;/p&gt;

&lt;p&gt;Además, se autoriza a LaLiga y a Telefónica a actualizar dinámicamente las IP bloqueadas enviando nuevas listas cada semana —incluso en fines de semana y festivos— sin intervención judicial previa. El control de la ejecución queda así delegado en la parte actora, sin supervisión externa ni mecanismo de revisión.&lt;/p&gt;

&lt;p&gt;Lo que se permite en esta sentencia no es una acción puntual ni limitada. Es un sistema automatizado de bloqueos, sostenido en el tiempo, y gestionado por los propios interesados. Una medida que debería ser excepcional se convierte en procedimiento ordinario.&lt;/p&gt;

&lt;p&gt;La proporcionalidad no se analiza. El posible perjuicio a terceros no se investiga. La neutralidad de la red no se menciona. Y los principios de la LSSI —como la mínima intervención o el respeto a los servicios legales— quedan, en la práctica, sin garantía efectiva.&lt;/p&gt;

&lt;h2&gt;
  
  
  Qué podemos hacer!?
&lt;/h2&gt;

&lt;p&gt;Aunque la sentencia ya ha sido dictada y los bloqueos están en marcha, no significa que tengamos que aceptarlo como algo inevitable. Hay caminos posibles, aunque no son sencillos, y el más urgente es dejar de actuar cada uno por su cuenta. Necesitamos coordinarnos, organizarnos, y hacernos oír.&lt;/p&gt;

&lt;p&gt;Desde el punto de vista jurídico, algunas empresas y profesionales podrían explorar la vía de actuar como terceros perjudicados. Es una opción legalmente prevista, pero compleja. Requiere demostrar que se ha producido un daño directo —como la interrupción de un servicio legítimo por el bloqueo de una IP compartida—, que ese daño vulnera derechos fundamentales como la libertad de expresión, la libertad de empresa o la tutela judicial efectiva, y que además no se tuvo oportunidad de participar en el proceso judicial. Es una vía exigente, incierta, y que requerirá asesoramiento legal especializado si se decide intentarla.&lt;/p&gt;

&lt;p&gt;También es posible cuestionar cómo se está ejecutando esta sentencia. Aunque el fallo hable de bloquear contenidos ilegales sin afectar a terceros, la realidad es que se están cortando rangos completos de IPs, sin revisión previa, sin transparencia, y afectando a webs perfectamente legales. Si se puede demostrar el perjuicio, se podrían exigir explicaciones o incluso una exclusión del bloqueo, pero tampoco es un camino fácil ni rápido.&lt;/p&gt;

&lt;p&gt;Por eso, más allá de lo judicial, necesitamos actuar en el plano administrativo y político. Deberíamos presentar reclamaciones ante la CNMC, que tiene la obligación de supervisar a los operadores y velar por la neutralidad de la red. También ante el Ministerio de Asuntos Económicos y Transformación Digital (y que por una vez sirva para algo) que debe proteger los derechos digitales de ciudadanos y empresas.&lt;/p&gt;

&lt;p&gt;Por último, deberíamos intentar escalar el tema a Europa, donde todo este asunto podría ir en contra del reglamento de acceso a internet.&lt;/p&gt;

&lt;p&gt;Pero para que algo de esto funcione, lo primero es organizarnos y sobre todo hacerlo visible. Si no basta con decirlo a los medios o a los organismos reguladores, habrá que decirlo también a los patrocinadores de LaLiga. &lt;/p&gt;

&lt;p&gt;Esto no va de fútbol ni de propiedad intelectual.&lt;/p&gt;

&lt;p&gt;Va de cómo se regula internet y de como poco a poco con la excusa de "la seguridad" o del orden vamos perdiendo libertad.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Next.js 14: No hemos aprendido NADA 😡</title>
      <dc:creator>IagoLast</dc:creator>
      <pubDate>Mon, 30 Oct 2023 07:55:47 +0000</pubDate>
      <link>https://forem.com/iagolast/nextjs-14-no-hemos-aprendido-nada-2509</link>
      <guid>https://forem.com/iagolast/nextjs-14-no-hemos-aprendido-nada-2509</guid>
      <description>&lt;p&gt;Hace ya algún tiempo que Next.js está en el foco del desarrollo frontend. Es el framework de moda: rápido, moderno, fantasticoso (fantástico y maravilloso).&lt;/p&gt;

&lt;p&gt;Como ocurre con cada nueva tecnología, miles de desarrolladores aspiran a ser &lt;em&gt;early adopters&lt;/em&gt; y dedican una fortuna para reemplazar tecnologías obsoletas con las más relucientes. Next.js no es una excepción.&lt;/p&gt;

&lt;p&gt;Quiero aclarar que ni soy un experto en Next.js ni soy un detractor. De hecho, &lt;strong&gt;me gusta Next y creo que ofrece ideas y avances muy valiosos para la comunidad&lt;/strong&gt;. Sin embargo, he observado que están cometiendo un error, en mi opinión, GRAVE. Esta semana, tras la conferencia donde se presentó &lt;a href="https://nextjs.org/learn" rel="noopener noreferrer"&gt;next/learn&lt;/a&gt;, decidí que escribiría un artículo al respecto.&lt;/p&gt;

&lt;p&gt;El error es tan fundamental que se puede resumir en una frase:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;El ecosistema de Next.js relega el testing.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A pesar de enfatizar constantemente las ventajas de rendimiento, la rapidez del compilador, la eficiencia del &lt;em&gt;app router&lt;/em&gt; y la revolucionaria naturaleza de los &lt;em&gt;server-side-components&lt;/em&gt;, en toda la documentación ni se menciona el TESTING.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy9af60er021sn4m4agbo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy9af60er021sn4m4agbo.png" alt="La documentación de next sin referencias a testing" width="800" height="558"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;En versiones anteriores al &lt;em&gt;app router (pages)&lt;/em&gt;, al menos contaban con &lt;a href="https://nextjs.org/docs/pages/building-your-application/optimizing/testing" rel="noopener noreferrer"&gt;una sección sobre testing&lt;/a&gt; en el apartado de "optimizaciones". Sin embargo, esta sección ha desaparecido en la documentación actual.&lt;/p&gt;

&lt;p&gt;Es sorprendente, pero parece que en pleno 2023 debemos recordar nuevamente la importancia del testing automático. Afortunadamente, en Vercel ya lo saben, pues el primer punto destacado de Next14 es que cuentan con 5000 tests en su base de código.&lt;/p&gt;

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

&lt;p&gt;Aún así, por razones desconocidas, continúan omitiendo casi por completo toda referencia a testing. Cómo si sus clientes no necesitasen hacer pruebas.&lt;/p&gt;

&lt;p&gt;Yo tengo una opinión bastante radical, creo que los tutoriales y ejemplos deberían seguir la metodología TDD y cada muestra de código debería incluir un test asociado. Aunque se que esto es una utopía que únicamente existe en mi cabeza, creo que al menos sería positivo que desde Next se promoviesen un MÍNIMO estas buenas prácticas y al menos preparasen el terreno para que la comunidad interesada en testing pueda investigar y generar contenido.&lt;/p&gt;

&lt;p&gt;Al iniciar una app desde cero, no te pregunta por el framework de &lt;em&gt;unit testing&lt;/em&gt; que utilizarás ni incluye un script de &lt;em&gt;test&lt;/em&gt; predeterminado.&lt;/p&gt;

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

&lt;p&gt;Ni siquiera incluye un script de "test":&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"dev"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"next dev"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"build"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"next build"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"start"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"next start"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"lint"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"next lint"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No me doy por vencido. Intentaré configurar Jest siguiendo &lt;a href="https://github.com/vercel/next.js/tree/canary/examples/with-jest" rel="noopener noreferrer"&gt;los ejemplos proporcionados en la documentación anterior&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;El tutorial es bastante directo. Instalo las dependencias:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--save-dev&lt;/span&gt; jest jest-environment-jsdom @testing-library/react @testing-library/jest-dom 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Luego, creo el archivo &lt;code&gt;jest.config.mjs&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;nextJest&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;next/jest.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;createJestConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;nextJest&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="c1"&gt;// Provide the path to your Next.js app to load next.config.js and .env files in your test environment&lt;/span&gt;
  &lt;span class="na"&gt;dir&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="c1"&gt;// Add any custom config to be passed to Jest&lt;/span&gt;
&lt;span class="cm"&gt;/** @type {import('jest').Config} */&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Add more setup options before each test is run&lt;/span&gt;
  &lt;span class="c1"&gt;// setupFilesAfterEnv: ['&amp;lt;rootDir&amp;gt;/jest.setup.js'],&lt;/span&gt;

  &lt;span class="na"&gt;testEnvironment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;jest-environment-jsdom&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// createJestConfig is exported this way to ensure that next/jest can load the Next.js config which is async&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;createJestConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Y finalmente realizo un test de prueba. Todo parece funcionar.&lt;/p&gt;

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

&lt;p&gt;Sin embargo, ¿qué ocurre con los &lt;em&gt;server-side components&lt;/em&gt; asíncronos?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getData&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;World&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Home&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getData&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Hello &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Este componente funciona perfectamente en Next, pero al intentar ejecutar los tests nuevamente…&lt;/p&gt;

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

&lt;p&gt;Es evidente: no es posible realizar tests unitarios de páginas SSR. Actualmente, la única opción que conozco es utilizar tests E2E con Playwright o Cypress. &lt;/p&gt;

&lt;p&gt;Pero si queremos probar cosas tan &lt;em&gt;"sencillas"&lt;/em&gt; (o al menos comunes) cómo &lt;em&gt;layouts&lt;/em&gt;, rutas complejas y otros aspectos que anteriormente eran fácilmente testables con Jest + Testing Library estamos totalmente abandonados.&lt;/p&gt;

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

&lt;p&gt;Hoy, resulta complejo (si no imposible) desarrollar una aplicación en Next.js con tests unitarios. Los tests son herramientas esenciales para garantizar la calidad del software y la eficiencia en el desarrollo.&lt;/p&gt;

&lt;p&gt;Lamentablemente, el framework que promete liderar el desarrollo frontend en los próximos años parece desestimar la importancia del testing.&lt;/p&gt;

&lt;p&gt;Me preocupa que, si ya es difícil convencer a los desarrolladores de realizar pruebas, si el framework no ayuda será una batalla perdida.&lt;/p&gt;

&lt;p&gt;Espero que alguien del equipo de Next (preferentemente hispanohablante ;) ) lea esto y tome acciones, pues insisto: es una tecnología impresionante.&lt;/p&gt;

</description>
      <category>testing</category>
      <category>nextjs</category>
      <category>webdev</category>
      <category>programming</category>
    </item>
    <item>
      <title>UTC considered harmful</title>
      <dc:creator>IagoLast</dc:creator>
      <pubDate>Wed, 26 Jul 2023 15:57:09 +0000</pubDate>
      <link>https://forem.com/iagolast/utc-considered-harmful-24i8</link>
      <guid>https://forem.com/iagolast/utc-considered-harmful-24i8</guid>
      <description>&lt;p&gt;Decidí fundar &lt;a href="https://www.timetime.in/" rel="noopener noreferrer"&gt;TimeTime.in&lt;/a&gt; porque, al contrario de lo que mucha gente piensa, trabajar con fechas sigue siendo terriblemente complicado para un desarrollador. (No es casualidad que el &lt;a href="https://mdbootstrap.com/docs/angular/forms/datepicker/" rel="noopener noreferrer"&gt;Datepicker habitualmente sea de pago&lt;/a&gt; en muchas librerías de UI).&lt;/p&gt;

&lt;p&gt;Hace no mucho tiempo, estaba en Twitter y me encontré &lt;a href="https://twitter.com/BenNadel/status/1684171835657662464/photo/1" rel="noopener noreferrer"&gt;un post de Ben Nadel&lt;/a&gt;, un desarrollador nada sospechoso de ser junior, contando un problema que había tenido manejando fechas y enlazando a &lt;a href="https://www.bennadel.com/blog/4492-considering-utc-and-recording-activity-streak-data-in-coldfusion.htm" rel="noopener noreferrer"&gt;un post explicando su solución&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Para mi sorpresa, su solución no es totalmente correcta, así que me he animado a escribir este post para evitar que más desarrolladores caigan en el mismo error.&lt;/p&gt;

&lt;h2&gt;
  
  
  ¡Lo primero de todo!
&lt;/h2&gt;

&lt;p&gt;Como no quiero hacer este post muy largo, voy a empezar introduciendo los conceptos mínimos necesarios para entenderlo.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tiempo Civil vs UTC
&lt;/h3&gt;

&lt;p&gt;Como mucha gente sabe, en Internet la mayoría de las fechas se representan usando tiempo UTC (en realidad se utiliza tiempo UNIX, que no es UTC y tiene bastantes problemas si queremos conseguir algo de seriedad, pero eso es material para otro post). Simplificando, podemos decir que el tiempo UTC representa el número de segundos a partir de un tiempo de referencia.&lt;/p&gt;

&lt;p&gt;Como no es práctico hablar en segundos constantemente, los humanos utilizamos un sistema diferente para representar fechas, llamado tiempo civil. Los humanos no decimos “he nacido en 89891283”, decimos “he nacido el 4 de julio de 1992 en Pontevedra, España”.&lt;/p&gt;

&lt;p&gt;Si hay algo importante en este texto, es que esta diferencia esté clara. &lt;strong&gt;Los humanos hablamos en tiempo civil y las máquinas en tiempo estandarizado&lt;/strong&gt;, y si queremos tener un sistema informático que funcione, tenemos que ser capaces de transformar un formato en el otro de forma efectiva.&lt;/p&gt;

&lt;h3&gt;
  
  
  Husos horarios, zonas horarias y GMT offset
&lt;/h3&gt;

&lt;p&gt;Simplificando, en 1884 se celebró la conferencia del meridiano, donde se acordó dividir la tierra en 24 husos horarios separados por 15º entre sí.&lt;/p&gt;

&lt;p&gt;&lt;u&gt;Los husos horarios son diferentes a las zonas horarias.&lt;/u&gt; Una zona horaria es una región geográfica que comparte una misma hora oficial. Es muy importante entender que &lt;strong&gt;una zona horaria es un concepto de tiempo civil cuyas normas pueden cambiar arbitrariamente.&lt;/strong&gt;&lt;/p&gt;

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

&lt;p&gt;Por último, hay que hablar del offset GMT. El GMT offset es la diferencia en horas y minutos entre la hora de referencia (GMT) y una hora concreta.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(Muchas veces la gente habla de “offset UTC”, aunque cabe recordar que GMT es una zona horaria mientras que UTC es un estándar de tiempo.)&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
   ISO 8601
&lt;/h3&gt;

&lt;p&gt;Es una normativa estándar que especifica la notación para representar, entre otros, fechas y horas. En esta normativa se indica que este es el formato para representar una fecha con horas, minutos, segundos y milisegundos:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;yyyy-mm-ddThh:mm:ss.mmm&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Además, este formato contempla que se pueda indicar la hora utilizando la zona horaria local, añadiendo cuánto desfase (offset) hay respecto a GMT. Si el desfase es 0, se añade una Z al final.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;yyyy-mm-ddThh:mm:ss.mmmZ&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Es decir, el mismo instante de tiempo (UTC) en España se llamará “26/07/2023 15:51:19 de España” y en Portugal, que está en una zona horaria anterior, se llamará “26/07/2023 14:51:19 de Portugal”.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;ISO&lt;/th&gt;
&lt;th&gt;GMT&lt;/th&gt;
&lt;th&gt;UTC (ms)&lt;/th&gt;
&lt;th&gt;Tiempo civil&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;2023-07-26T13:51:19.724Z&lt;/td&gt;
&lt;td&gt;2023-07-26T13:51:19.724Z&lt;/td&gt;
&lt;td&gt;1690379479724&lt;/td&gt;
&lt;td&gt;26/07/2023 15:51:19 de España&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2023-07-26T14:51:19.724-01:00&lt;/td&gt;
&lt;td&gt;2023-07-26T13:51:19.724Z&lt;/td&gt;
&lt;td&gt;1690379479724&lt;/td&gt;
&lt;td&gt;26/07/2023 14:51:19 de Portugal&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h1&gt;
  
  
  Developers
&lt;/h1&gt;

&lt;p&gt;Ahora que hemos visto todo esto, ¿qué implicaciones tiene para nosotros como desarrolladores?&lt;/p&gt;

&lt;h3&gt;
  
  
  Nivel 0: Timestamps
&lt;/h3&gt;

&lt;p&gt;Mucha gente utiliza timestamps para representar fechas. Como hemos visto, los timestamps son un concepto de tiempo absoluto que no tiene en cuenta ni el offset ni la zona horaria.&lt;/p&gt;

&lt;p&gt;Este es el problema que tiene Ben y que explica en su blog. Tiene una aplicación de deportes en la que guarda datos de entrenamientos. Tiene una funcionalidad de “racha de entrenos” donde básicamente indica qué días el usuario ha entrenado.&lt;/p&gt;

&lt;p&gt;En un primer momento, guardaba los entrenamientos como timestamps, el problema es que un usuario en la zona este de EEUU, se encuentra en offset GMT-4, esto quiere decir que si entrena a las 5 de la mañana y a las 10 de la noche ha entrenado dos días diferentes en la zona horaria GMT, pero el mismo día en GMT-4.&lt;/p&gt;

&lt;p&gt;Por ejemplos como este, guardar timestamps es muchas veces insuficiente si queremos conservar la semántica de las fechas.&lt;/p&gt;

&lt;h3&gt;
  
  
  Nivel 1: GMT offset
&lt;/h3&gt;

&lt;p&gt;Una solución habitual es almacenar las fechas incluyendo el offset GMT. De esta forma, al saber el offset de un usuario, sabemos si clasificar los entrenamientos en diferentes días o no. Sin embargo, esta aproximación tiene un problema: Asumir que las zonas horarias son estáticas.&lt;/p&gt;

&lt;p&gt;Como hemos visto previamente, &lt;strong&gt;una zona horaria es diferente a un offset GMT&lt;/strong&gt;. El ejemplo típico que todos conocemos es el cambio entre el horario de verano y el horario de invierno. Además, las propias zonas horarias pueden cambiar debido a decisiones políticas. Sin ir más lejos, &lt;a href="https://www.washingtonpost.com/news/worldviews/wp/2016/12/14/fascism-helped-create-a-time-zone-oddity-in-spain-70-years-later-it-may-finally-be-undone/#:~:text=It%20was%20only%20during%20World,clocks%20were%20never%20changed%20back." rel="noopener noreferrer"&gt;España cambió de huso horario en 1940&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Nivel 2: ISO + Zona Horaria
&lt;/h3&gt;

&lt;p&gt;Sin entrar en muchos detalles aburridos, puedo decir sin miedo a equivocarme que &lt;strong&gt;la forma correcta de almacenar y transmitir fechas suele ser utilizando formato ISO + Zona Horaria.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Pongamos un ejemplo. Supongamos que tengo una aplicación que permite reservar citas médicas. Como en España la sanidad no va todo lo bien que debería, me dan cita para dentro de meses.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Tu cita ha sido programada para el Sábado 20 de julio de 2024 a las 09:00 de la mañana, hora española peninsular.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Esta fecha y hora, están expresadas en tiempo civil.&lt;/p&gt;

&lt;p&gt;Imaginemos que guardamos la fecha en mi base de datos almacenando fecha y GMT offset en formato ISO.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;2024-07-20T09:00:00.000+02:00
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Esta fecha se corresponde con un instante UTC determinado:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;Y a su vez se corresponde con esta fecha en GMT:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Sat, 20 Jul 2024 07:00:00 GMT
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ahora imaginemos que entre medias, &lt;a href="https://www.europarl.europa.eu/legislative-train/theme-union-of-democratic-change/file-discontinuing-seasonal-changes-of-time#:~:text=The%20Parliament%20adopted%20its%20position,wishing%20to%20keep%20summer%20time." rel="noopener noreferrer"&gt;algunos países de Europa, incluido España, deciden eliminar el horario de verano&lt;/a&gt;. Para cuando llegue el día en nuestra base de datos, seguimos teniendo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;2024-07-20T09:00:00.000+02:00 (1721458800000 UTC)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Que transformado al hipotético horario civil español después del cambio de hora será:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;El Sábado 20 de julio de 2024 a las &lt;strong&gt;08:00 de la mañana&lt;/strong&gt;, hora española peninsular.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Sin embargo, ¿es esto lo que queremos? ¿Queremos persistir el instante UTC original al que se correspondía la fecha civil? ¿O queremos conservar la hora inicial a la que el paciente pidió la cita médica?&lt;/p&gt;

&lt;p&gt;En mi opinión, cuando una persona pide cita para el médico a las 9 de la mañana, no le importa el tiempo UTC o GMT ni los cambios horarios que se realicen en su país entre medias. Le importa que su cita sea su hora civil local.&lt;/p&gt;

&lt;p&gt;En mi caso cuando digo &lt;em&gt;“Sábado 20 de julio de 2024 a las 09:00 de la mañana, hora española peninsular”&lt;/em&gt;, no me importa lo que pueda pasar entre medias, ¡quiero mi cita a las 09:00 de la mañana!&lt;/p&gt;

&lt;h2&gt;
  
  
   Conclusiones
&lt;/h2&gt;

&lt;p&gt;En este texto, sólo hemos tocado la superficie de todas las implicaciones y trampas que te puedes encontrar manejando fechas por poner algunas:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hasta 1908 Rusia utilizaba el calendario juliano, causando entre otras cosas, &lt;a href="https://www.si.com/extra-mustard/2013/12/30/the-extra-mustard-trivia-hour-when-a-calendar-defeated-russia-in-the-1908-olympics" rel="noopener noreferrer"&gt;que llegasen 12 días tarde a los Juegos Olímpicos de ese año&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.timeanddate.com/date/february-30.html#:~:text=February%2030%20existed%20from%201930,days%20were%20%E2%80%9Cmonthless%E2%80%9D%20holidays." rel="noopener noreferrer"&gt;Existió el 30 de febrero.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://unix.stackexchange.com/questions/333179/what-harm-does-a-leap-second-cause-on-a-unix-system/334029#334029" rel="noopener noreferrer"&gt;El tiempo UNIX, (utilizado en las fechas javascript) no es exactamente el tiempo UTC.&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;De momento siempre que manejes fechas te recomiendo tener este texto en cuenta y si es necesario persistir la zona horaria junto a la fecha para conservar tanta información como sea posible.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>programming</category>
      <category>beginners</category>
    </item>
    <item>
      <title>UTC considered harmful</title>
      <dc:creator>IagoLast</dc:creator>
      <pubDate>Wed, 26 Jul 2023 15:56:58 +0000</pubDate>
      <link>https://forem.com/iagolast/utc-considered-harmful-5bcc</link>
      <guid>https://forem.com/iagolast/utc-considered-harmful-5bcc</guid>
      <description>&lt;p&gt;I decided to launch &lt;a href="https://www.timetime.in/" rel="noopener noreferrer"&gt;TimeTime.in&lt;/a&gt; because, contrary to popular belief, working with dates is still terribly complicated for developers. (It's no coincidence that the &lt;a href="https://mdbootstrap.com/docs/angular/forms/datepicker/" rel="noopener noreferrer"&gt;Datepicker is often paid&lt;/a&gt; in many UI libraries).&lt;/p&gt;

&lt;p&gt;Not long ago, I was on Twitter and came across &lt;a href="https://twitter.com/BenNadel/status/1684171835657662464/photo/1" rel="noopener noreferrer"&gt;a post by Ben Nadel&lt;/a&gt;, a developer not suspected of being a junior, discussing a problem he faced with dates and linking to &lt;a href="https://www.bennadel.com/blog/4492-considering-utc-and-recording-activity-streak-data-in-coldfusion.htm" rel="noopener noreferrer"&gt;a post explaining his solution&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To my surprise, his solution isn't entirely correct, so I was inspired to write this post to prevent more developers from making the same mistake.&lt;/p&gt;

&lt;h2&gt;
  
  
  First things first!
&lt;/h2&gt;

&lt;p&gt;Not wanting to make this post too long, I'll start by introducing the basic concepts required to understand it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Civil Time vs UTC
&lt;/h3&gt;

&lt;p&gt;As many people know, on the Internet most dates are represented using UTC time (actually UNIX time is used, which isn't UTC and has significant issues if we want to be serious, but that's material for another post). Simplifying, we can say that UTC time represents the number of seconds from a reference time.&lt;/p&gt;

&lt;p&gt;Practically, instead of continuously talking in seconds, humans use a different system to represent dates called civil time. We don't say “I was born at 89891283”, we say “I was born on July 4, 1992, in Pontevedra, Spain”.&lt;/p&gt;

&lt;p&gt;The crucial point here is to understand this difference. &lt;strong&gt;Humans speak in civil time and machines in standardized time&lt;/strong&gt;, and if we want a functioning computer system, we need to effectively transform one format into the other.&lt;/p&gt;

&lt;h3&gt;
  
  
  Time Zones, Area Zones, and GMT Offset
&lt;/h3&gt;

&lt;p&gt;In simple terms, the 1884 meridian conference was held where it was agreed to divide the earth into 24 time zones, each separated by 15º.&lt;/p&gt;

&lt;p&gt;&lt;u&gt;Time zones are different from area zones.&lt;/u&gt; An area zone is a geographical region that shares the same official time. It's essential to understand that &lt;strong&gt;an area zone is a civil time concept whose rules can change arbitrarily.&lt;/strong&gt;&lt;/p&gt;

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

&lt;p&gt;Lastly, we should discuss the GMT offset. The GMT offset is the difference in hours and minutes between the reference time (GMT) and a specific time.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(People often talk about the “UTC offset”, even though it's important to remember that GMT is a time zone, whereas UTC is a time standard.)&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  ISO 8601
&lt;/h3&gt;

&lt;p&gt;It's a standard regulation specifying the notation to represent dates, times, and more. This standard indicates that the format for representing a date with hours, minutes, seconds, and milliseconds is:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;yyyy-mm-ddThh:mm:ss.mmm&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Furthermore, this format allows specifying time using the local time zone, adding how much offset there is from GMT. If the offset is 0, a Z is added at the end.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;yyyy-mm-ddThh:mm:ss.mmmZ&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Meaning, the same moment in time (UTC) in Spain will be called "26/07/2023 15:51:19 in Spain" and in Portugal, which is in a prior time zone, it'll be called "26/07/2023 14:51:19 in Portugal".&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;ISO&lt;/th&gt;
&lt;th&gt;GMT&lt;/th&gt;
&lt;th&gt;UTC (ms)&lt;/th&gt;
&lt;th&gt;Civil Time&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;2023-07-26T13:51:19.724Z&lt;/td&gt;
&lt;td&gt;2023-07-26T13:51:19.724Z&lt;/td&gt;
&lt;td&gt;1690379479724&lt;/td&gt;
&lt;td&gt;26/07/2023 15:51:19 in Spain&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2023-07-26T14:51:19.724-01:00&lt;/td&gt;
&lt;td&gt;2023-07-26T13:51:19.724Z&lt;/td&gt;
&lt;td&gt;1690379479724&lt;/td&gt;
&lt;td&gt;26/07/2023 14:51:19 in Portugal&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h1&gt;
  
  
  Developers
&lt;/h1&gt;

&lt;p&gt;Now that we've covered this, what implications does it have for us as developers?&lt;/p&gt;

&lt;h3&gt;
  
  
  Level 0: Timestamps
&lt;/h3&gt;

&lt;p&gt;Many people use timestamps to represent dates. As we've seen, timestamps are an absolute time concept that doesn't account for the offset or time zone.&lt;/p&gt;

&lt;p&gt;This is the problem Ben faced and explained on his blog. He has a sports app where he saves training data. It has a “training streak” feature which essentially shows which days the user trained.&lt;/p&gt;

&lt;p&gt;Initially, he saved workouts as timestamps. The problem is for a user in the Eastern US, which is in GMT-4 offset. This means if they train at 5 in the morning and 10 at night, they've trained on two different days in GMT time, but the same day in GMT-4.&lt;/p&gt;

&lt;p&gt;Examples like this show that saving timestamps is often inadequate if we want to preserve date semantics.&lt;/p&gt;

&lt;h3&gt;
  
  
  Level 1: GMT Offset
&lt;/h3&gt;

&lt;p&gt;A common solution is to store dates, including the GMT offset. This way, knowing a user's offset, we know whether to classify workouts on different days or not. However, this approach has one problem: Assuming that time zones are static.&lt;/p&gt;

&lt;p&gt;As previously discussed, &lt;strong&gt;a time zone differs from a GMT offset&lt;/strong&gt;. The classic example we all know is the switch between daylight saving time and standard time. Moreover, time zones themselves can change due to political decisions. For instance, &lt;a href="https://www.washingtonpost.com/news/worldviews/wp/2016/12/14/fascism-helped-create-a-time-zone-oddity-in-spain-70-years-later-it-may-finally-be-undone/#:~:text=It%20was%20only%20during%20World,clocks%20were%20never%20changed%20back." rel="noopener noreferrer"&gt;Spain changed its time zone in 1940&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Level 2: ISO + Time Zone
&lt;/h2&gt;

&lt;p&gt;Without delving into many tedious details, I can confidently say that &lt;strong&gt;the correct way to store and transmit dates is usually by using the ISO format + Time Zone.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Let's take an example. Suppose I have an app that allows booking medical appointments. Since healthcare in Spain isn't as good as it should be, they schedule my appointment for months later.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Your appointment is scheduled for Saturday, July 20, 2024, at 09:00 AM, Spanish mainland time.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This date and time are expressed in civil time.&lt;/p&gt;

&lt;p&gt;Imagine that we save this date in my database, storing the date and GMT offset in ISO format.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;2024-07-20T09:00:00.000+02:00
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This date corresponds to a certain UTC moment:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;Which in turn corresponds to this GMT date:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Sat, 20 Jul 2024 07:00:00 GMT
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now let's imagine that in the meantime, &lt;a href="https://www.europarl.europa.eu/legislative-train/theme-union-of-democratic-change/file-discontinuing-seasonal-changes-of-time#:~:text=The%20Parliament%20adopted%20its%20position,wishing%20to%20keep%20summer%20time." rel="noopener noreferrer"&gt;some countries in Europe, including Spain, decide to abolish daylight saving time&lt;/a&gt;. By the time the day comes in our database, we still have:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;2024-07-20T09:00:00.000+02:00 (1721458800000 UTC)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Transformed to the hypothetical Spanish civil time after the time change will be:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Saturday, July 20, 2024, at &lt;strong&gt;08:00 in the morning&lt;/strong&gt;, Peninsular Spanish time.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;However, is this what we want? Do we want to persist the original UTC moment to which the civil date corresponded? Or do we want to preserve the initial time when the patient requested the medical appointment?&lt;/p&gt;

&lt;p&gt;In my opinion, when a person schedules a medical appointment for 9 in the morning, they don't care about UTC or GMT or any daylight saving time changes that occur in their country in the meantime. They care about their appointment being at their local civil time.&lt;/p&gt;

&lt;p&gt;In my case, when I say "Saturday, July 20, 2024, at 09:00 in the morning, Peninsular Spanish time," I don't care about anything that happens in between—I want my appointment at 09:00 in the morning!&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusions
&lt;/h2&gt;

&lt;p&gt;In this text, we have only scratched the surface of all the implications and pitfalls you can encounter when dealing with dates, just to name a few:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Until 1908, Russia used the Julian calendar,&lt;a href="https://www.si.com/extra-mustard/2013/12/30/the-extra-mustard-trivia-hour-when-a-calendar-defeated-russia-in-the-1908-olympics" rel="noopener noreferrer"&gt; causing them to arrive 12 days late to the Olympics that year.&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.timeanddate.com/date/february-30.html#:~:text=February%2030%20existed%20from%201930,days%20were%20%E2%80%9Cmonthless%E2%80%9D%20holidays" rel="noopener noreferrer"&gt;There was a February 30th.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://unix.stackexchange.com/questions/333179/what-harm-does-a-leap-second-cause-on-a-unix-system/334029#334029" rel="noopener noreferrer"&gt;The UNIX time (used in JavaScript dates) is not exactly the UTC time&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For now, whenever you handle dates, I recommend keeping this text in mind, and if necessary, persist the time zone along with the date to preserve as much information as possible.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>D5, La metodología que me funciona.</title>
      <dc:creator>IagoLast</dc:creator>
      <pubDate>Thu, 13 Apr 2023 18:54:52 +0000</pubDate>
      <link>https://forem.com/iagolast/d5-la-metodologia-que-me-funciona-5fo</link>
      <guid>https://forem.com/iagolast/d5-la-metodologia-que-me-funciona-5fo</guid>
      <description>&lt;p&gt;Agile, kanban, waterfall, proces unficiado… son decenas las metodologías que se han propuesto para desarrollar software a lo largo de la historia. Tras más de una década peleándome con diferentes aproximaciones nunca he visto ninguna correctamente implementada y, creo, nunca he visto ninguna funcionar hasta que en mi última empresa decidimos implementar durante una época una metodología que llamamos internamente &lt;strong&gt;D5&lt;/strong&gt;&lt;/p&gt;

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

&lt;p&gt;D5 combina, en mi opinión, todas las buenas ideas que se han propuesto en otras metodologías. Es suficientemente estricta como para que todo el equipo tenga claro que hacer en cada momento pero a la vez es suficientemente flexible como para no ahogar en burocracia y rituales. D5 consiste en &lt;u&gt;identificar problemas y ofrecer soluciones lo antes posible&lt;/u&gt;.&lt;/p&gt;

&lt;p&gt;Los seguidores del &lt;a href="https://es.wikipedia.org/wiki/Lean_manufacturing" rel="noopener noreferrer"&gt;movimiento lean&lt;/a&gt;, o del desarrollo ágil hace años que tienen una máxima clara “desarrolla un MVP lo antes posible, válida tu idea y aprende”.&lt;/p&gt;

&lt;p&gt;D5 divide el desarrollo en 5 fases en las que aunque todo el equipo participa, siempre hay un claro responsable de cada una de ellas. &lt;strong&gt;Discovery, Design, Develop, Deploy, Deliver&lt;/strong&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Discovery
&lt;/h1&gt;

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

&lt;p&gt;&lt;strong&gt;Responsable: Product Owner&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;odo empieza aquí. En esta fase, la persona responsable del producto está constantemente entrevistándose con usuarios y potenciales usuarios detectando “problemas” (pains).&lt;/p&gt;

&lt;p&gt;El objetivo fundamental es escuchar al usuario, entender qué problemas tiene, cómo usa el producto, saber qué alternativas existen y los pros y los contras.&lt;/p&gt;

&lt;p&gt;En este punto, he observado muchos errores que se repiten constantemente en diferentes empresas:&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Ideas felices
&lt;/h2&gt;

&lt;p&gt;La persona responsable de producto tiene ideas felices que añade al backlog sin realizar ningún tipo de discovery:&lt;br&gt;
“Vamos a añadir un chat”. “Creo que si ponemos avatares de colores, la gente nos va a usar más”.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Ingenieros ausentes
&lt;/h2&gt;

&lt;p&gt;Aunque estrictamente la fase de discovery es responsabilidad del equipo de producto, es bueno que el equipo de ingeniería esté al corriente de lo que está pasando. Obligarles a dar soporte o asistir a reuniones con clientes cada cierto tiempo es una manera de recordarles que el software es una herramienta para resolver problemas a otras personas.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Falta de issue tracker
&lt;/h2&gt;

&lt;p&gt;Creo que es fundamental disponer de un issue tracker automatizado donde los clientes puedan reportar errores y sugerencias de mejora, y sacar métricas constantemente. Si el 90% de los errores están relacionados con una funcionalidad concreta, quizá sea hora de darle un lavado de cara.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Soluciones antes de problemas
&lt;/h2&gt;

&lt;p&gt;Lo más importante en esta fase es tener claro que el &lt;strong&gt;objetivo de esta fase es identificar PROBLEMAS&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Os contaré una historia. Hace tiempo trabajaba en una app de chat y videollamada. Uno de los clientes nos pidió un “detector de presencia”, el típico icono verde/rojo que indica si un interlocutor está conectado o desconectado a un chat. Implementar esa funcionalidad nos llevó meses y muchos quebraderos de cabeza. Justo antes de entregarla a alguien, se le ocurrió preguntar para qué querían esa funcionalidad, y resultó que la estaban usando para asignar responsables a un determinado chat.&lt;/p&gt;

&lt;p&gt;Su problema era que, dada una incidencia, querían asignar a una persona concreta para chatear. ¡Y esto lo hacían marcándose como “no disponibles” manualmente!&lt;/p&gt;

&lt;p&gt;Resultó que la asignación de chats era algo que nuestra plataforma ya soportaba, pero no estaba bien documentado. Se solucionó en 1 día, y todo el sistema de presencia quedó en el olvido*.&lt;/p&gt;

&lt;h1&gt;
  
  
  Design
&lt;/h1&gt;

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

&lt;p&gt;&lt;strong&gt;Personas responsables: product owners, product designer, tech lead&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;En esta fase, tenemos un problema correctamente identificado, y el equipo debe analizarlo en busca de una solución.&lt;/p&gt;

&lt;p&gt;Esta solución no debe ser definitiva, debe ser algo que se pueda desarrollar lo antes posible, sin acumular deuda técnica, y que permita validar con el usuario si la solución propuesta aporta valor resolviendo el problema.&lt;/p&gt;

&lt;p&gt;Quizá en esta fase es donde más decisiones críticas se toman, por eso es importante involucrar a todo el equipo.&lt;/p&gt;

&lt;p&gt;Es importante recordar que diseño no solo implica interfaz gráfica. Diseño implica pensar en todas las cosas que pueden salir mal, implica pensar en GDPR, implica hablar con el departamento legal, implica hablar con finanzas, implica no tener miedo a comprar soluciones en lugar de reinventar la rueda.&lt;/p&gt;

&lt;p&gt;De nuevo, en esta fase he identificado varios problemas recurrentes.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Exceso de optimismo
&lt;/h2&gt;

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

&lt;p&gt;Los product owners son demasiado optimistas. En mi experiencia, es frecuente que siempre piensen en el caso “de oro”, pero nunca se paren a pensar en los casos extremos.&lt;/p&gt;

&lt;p&gt;Recuerdo una vez que estábamos implementando una funcionalidad de cash-back.&lt;/p&gt;

&lt;p&gt;“De todas las compras que haga el usuario, se le devolverá un 1% a final de mes.”&lt;/p&gt;

&lt;p&gt;¿Qué es un mes? ¿Un mes UTC? ¿Un mes en la zona horaria del usuario? ¿Qué pasa si el usuario se mueve? ¿Qué pasa si el usuario compra un coche de 30.000€ el día 31 y lo devuelve el 5 del mes siguiente? ¿Qué pasa si el usuario usa su tarjeta para recargar su cuenta de Revolut? ¿Puede crear dinero infinito? ¿Deberíamos añadir todo esto a los términos y condiciones?&lt;/p&gt;

&lt;p&gt;Todo este tipo de cosas y casos raros suelen estar en la cabeza de los ingenieros y rara vez en la cabeza del equipo de producto.&lt;/p&gt;

&lt;p&gt;Por ello, es importante que una persona de ingeniería esté encargada de buscar casos raros en esa fase.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Subestimar esfuerzo
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhhuzboebedamainaqkth.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhhuzboebedamainaqkth.png" alt="Es simplemente poner un botón más" width="800" height="136"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Otro problema que suelo encontrar en esta fase es que el equipo de producto subestima la complejidad de algunas tareas. Por ello, es conveniente siempre tener a personas del equipo de ingeniería involucradas en el proceso de diseño para dar estimaciones realistas del coste de cada propuesta.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. No se recorta el alcance
&lt;/h2&gt;

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

&lt;p&gt;Una vez tenemos una solución pensada, hemos estudiado sus implicaciones, y tenemos una idea del coste de desarrollo, toca hacer lo más difícil: RECORTAR.&lt;/p&gt;

&lt;p&gt;Dedicar un tiempo a pensar bien en un problema y su solución no quiere decir que tengamos que implementar todo desde el primer momento. En muchas ocasiones, se pueden hacer concesiones y reducir el alcance de una funcionalidad, dejando partes para etapas posteriores.&lt;/p&gt;

&lt;p&gt;En este punto, es importante preguntarse: Sabiendo lo que sabemos, ¿qué es lo mínimo que podemos hacer para que el cliente lo utilice y nos dé feedback sin hipotecar nuestro futuro?&lt;/p&gt;

&lt;p&gt;Seguro que poder filtrar por fecha con un datepicker es una idea genial, pero igual podemos usar el input por defecto del navegador en la primera versión. Seguro que crear una herramienta de analíticas en tiempo real es la bomba, pero igual le podemos mandar al usuario un csv por email.&lt;/p&gt;

&lt;p&gt;Al final, se trata de hacer concesiones conociendo los riesgos y beneficios de cada decisión.&lt;/p&gt;

&lt;h2&gt;
  
  
  Develop
&lt;/h2&gt;

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

&lt;p&gt;&lt;strong&gt;Persona responsable: Tech lead&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Llegamos a la fase de desarrollo. En esta fase, hay cientos de metodologías sobre las que podría escribir algún día, pero en este texto me limitaré a dar un simple consejo:&lt;/p&gt;

&lt;h2&gt;
  
  
   1. Ten todo automatizado
&lt;/h2&gt;

&lt;p&gt;Si alguien pregunta: “¿En qué estado está esta tarea?” o si el equipo no tiene claro si un ticket está desplegado, es un claro indicio de que algo va mal.&lt;/p&gt;

&lt;p&gt;Herramientas como conventional commits y convenciones de nombrado de ramas permiten que los tickets se asignen y se muevan por el board automáticamente, sin necesidad de que los desarrolladores abran JIRA.&lt;/p&gt;

&lt;p&gt;De nuevo, puede que escriba sobre esto porque tengo opiniones muy fuertes sobre dinámicas de desarrollo de software.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deploy
&lt;/h2&gt;

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

&lt;p&gt;&lt;strong&gt;Persona responsable: Tech lead&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Personalmente, considero el despliegue como una fase más del proceso de desarrollo. Es más, si todo está automatizado, no debería existir más allá de las herramientas de gestión de tareas. En todo caso, es en esta fase donde los cambios se despliegan, y es importante no confundirla con la última fase (deliver), en la que los cambios son visibles por el cliente.&lt;/p&gt;

&lt;p&gt;Desplegar algo implica tener el código integrado en la rama correspondiente, independientemente de si las funcionalidades implementadas son visibles o no por el usuario final.&lt;/p&gt;

&lt;p&gt;En esta fase, el equipo puede realizar pruebas internas (QA) o incluso probar con clientes reales.&lt;/p&gt;

&lt;p&gt;De nuevo, el mismo consejo: &lt;strong&gt;tener todo automatizado&lt;/strong&gt;. En nuestro caso, cada commit a cada rama &lt;code&gt;dev&lt;/code&gt; y &lt;code&gt;prd&lt;/code&gt; se despliega automáticamente en el entorno que corresponda, y no es necesaria ninguna acción extra por parte del equipo de desarrollo.&lt;/p&gt;

&lt;p&gt;Además, esta automatización mueve los tickets automáticamente por el board para representar el estado actualizado del ticket en cada momento. (&lt;code&gt;IN REVIEW --&amp;gt; DEPLOYED TO STG&lt;/code&gt;)&lt;/p&gt;

&lt;h2&gt;
  
  
  Deliver
&lt;/h2&gt;

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

&lt;p&gt;&lt;strong&gt;Persona responsable: Product Owner&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Aunque parezca increíble, me he encontrado varias veces en equipos donde se desarrolla una funcionalidad y se nos olvida desplegarla para que los clientes puedan usarla.&lt;/p&gt;

&lt;p&gt;Por ello, es importante que la persona responsable sea la misma persona encargada de producto (PO), y por ello es importante que disponga de herramientas para entregar la nueva funcionalidad sin recurrir al equipo de desarrollo.&lt;/p&gt;

&lt;p&gt;De nuevo, se podrían escribir varios artículos al respecto, pero en mi opinión, lo que mejor funciona es utilizar &lt;a href="https://martinfowler.com/articles/feature-toggles.html" rel="noopener noreferrer"&gt;feature flags&lt;/a&gt;, y una vez que la funcionalidad ha sido validada (QA), se puede liberar y entregársela a todos los usuarios.&lt;/p&gt;

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

&lt;p&gt;Personalmente, toda esta metodología me gusta porque puede verse como una cadena de montaje donde los tickets van pasando de una fase a otra, y en cada fase se da libertad al equipo responsable de los detalles de implementación. No se especifica si en la fase de desarrollo se debe usar Kanban, XP o Scrum con sprints de dos semanas.&lt;/p&gt;

&lt;p&gt;Se centra en QUÉ debe hacer un grupo de personas en cada fase y no en el CÓMO, y lo hace poniendo al usuario y sus problemas como el origen de todo el ciclo de desarrollo.&lt;/p&gt;

&lt;p&gt;Si esta metodología se implementa bien, cada equipo se despierta con una serie de tareas bien definidas en su columna "TODO". Como estas tareas han pasado por una etapa de refinamiento previa, estarán bien pensadas de antemano, por lo que el equipo puede centrarse en desarrollarlas y aportar valor en lugar de perder su tiempo aclarando detalles y haciendo preguntas absurdas.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>productivity</category>
      <category>discuss</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Comunicación eficiente en empresas tech.</title>
      <dc:creator>IagoLast</dc:creator>
      <pubDate>Sun, 06 Nov 2022 18:16:41 +0000</pubDate>
      <link>https://forem.com/iagolast/comunicacion-eficiente-en-empresas-tech-3984</link>
      <guid>https://forem.com/iagolast/comunicacion-eficiente-en-empresas-tech-3984</guid>
      <description>&lt;p&gt;Uno de los puntos de mejora recurrentes en las empresas en las que he trabajado es la comunicación. Muchas empresas trabajan en de forma remota pero no trabajan de forma asíncrona pese a que podrían implementar algunos aspectos del trabajo asíncrono con poco esfuerzo.&lt;/p&gt;

&lt;p&gt;En este texto intentaré dar una serie de consejos relacionados con los canales de comunicación y cómo utilizarlos para conseguir una cultura de trabajo asíncrona.&lt;/p&gt;

&lt;h2&gt;
  
  
  Asíncrono vs remoto
&lt;/h2&gt;

&lt;p&gt;Me gustaría empezar recordando algo que para muchos sigue sin estar claro: &lt;strong&gt;trabajar de forma asíncrona es diferente que trabajar en remoto.&lt;/strong&gt; Conseguir una buena cultura de trabajo asíncrono en una empresa requiere disciplina y esfuerzo por parte de los trabajadores y es una forma de trabajar que no tiene por que gustar a todo el mundo. En mi caso no soy imparcial, ya que siempre me he considerado fiel defensor del trabajo mayoritariamente a asíncrono.&lt;/p&gt;

&lt;p&gt;Creo que tener una cultura de trabajo de este tipo ofrece más ventajas que inconvenientes. Empezando por la conciliación laboral ya que por definición la gente pueden establecer sus propios horarios y trabajar cuando se consideran más productivos o simplemente cuando prefieran, aumentando considerablemente la felicidad.&lt;/p&gt;

&lt;p&gt;Una empresa asíncrona no sólo se libra de las barreras físicas que tradicionalmente limitan a las empresas no-remotas. Sino que rompe también con las barreras horarias permitiendo la contratación de cualquier persona en cualquier parte del mundo.&lt;/p&gt;

&lt;p&gt;Otra ventaja a tener en cuenta es la escalabilidad. Un entorno de trabajo asíncrono exige tener documentación sobre casi todos los procesos y decisiones de una empresa de forma que cualquier nuevo trabajador puede familiarizarse con ellos de forma prácticamente autónoma.&lt;/p&gt;

&lt;p&gt;Por último creo que una de las ventajas fundamentales es que la necesidad de documentación mencionada anteriormente obliga al equipo a reflexionar y pensar antes de cada paso y el kaos tiende a crecer más lentamente que con otros entornos.&lt;/p&gt;

&lt;h2&gt;
  
  
  Consejos de comunicación por nivel.
&lt;/h2&gt;

&lt;p&gt;A continuación voy a dar una serie de consejos que en mi opinión son útiles para orientar la empresa hacia una metodología de trabajo y comunicación asíncrona y cuando y para qué se debe usar cada herramienta ordenados por niveles/herramientas comunes en la mayoría de empresas.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  Nivel 0: Reuniones
&lt;/h3&gt;

&lt;p&gt;Las reuniones no gustan a casi nadie. Me incluyo.&lt;/p&gt;

&lt;p&gt;En general son una pérdida de tiempo, hay más gente de la necesaria y solamente una minoría participa en las mismas. En cualquier reunión, en un momento determinado uno habla, N escuchan y sólo M encuentran útil lo que se esta diciendo es terriblemente ineficiente.&lt;/p&gt;

&lt;p&gt;El discurso suele ser improvisado y la conversación se desvía hacia lugares que no estaban previstos.&lt;/p&gt;

&lt;p&gt;Además las barreras linguísticas la timidez o simplemente que no es el momento "adecuado" pueden hacer que personas que participarían de forma activa si se usase otro canal no lo hagan en reuniones perdiendo así parte de la capacidad del equipo.&lt;/p&gt;

&lt;p&gt;Por lo general mi recomendación es evitarlas en la medida de lo posible pero si con todo esto sigues pensando en hacer reuniones aquí van una serie de consejos:&lt;/p&gt;

&lt;h4&gt;
  
  
  1. Invita a la gente con al menos 24h de antelación.
&lt;/h4&gt;

&lt;p&gt;No todos los empleados tienen la misma disponibilidad o los mismos horarios, por lo que es importante planificar las reuniones con antelación para que todos puedan asistir habiendo preparado correctamente el contenido de la misma.&lt;/p&gt;

&lt;h4&gt;
  
  
  2. Escribe una descripción y un objetivo.
&lt;/h4&gt;

&lt;p&gt;¿De que va la reunión? ¿Qué temas se quieren tratar? ¿Qué opciones se barajan? ¿Qué documentos debería leer para tener todos el contexto suficiente?&lt;/p&gt;

&lt;h4&gt;
  
  
  3. Limita la duración.
&lt;/h4&gt;

&lt;p&gt;Las reuniones no deben durar más de lo absolutamente necesario. Si se ha planificado correctamente, una reunión de una hora debería ser suficiente para discutir un único tema. Si la reunión se alarga, es probable que se pierda el foco y la eficacia.&lt;/p&gt;

&lt;p&gt;En mi experiencia a los 40 minutos la gente deja de atender. No hay problema por dividir una reunión en diferentes reuniones más pequeñas.&lt;/p&gt;

&lt;h4&gt;
  
  
  4. Limita el número de personas.
&lt;/h4&gt;

&lt;p&gt;Las reuniones más eficientes son aquellas en las que solo están presentes las personas absolutamente necesarias. Si hay más de 8-10 personas en una reunión, es probable que sea demasiado grande y no se pueda llevar a cabo de manera eficiente.&lt;/p&gt;

&lt;h4&gt;
  
  
  5. Asegúrate de que alguien toma notas.
&lt;/h4&gt;

&lt;p&gt;Durante las reuniones es importante que alguien esté encargado de tomar notas de lo que se discute y se acuerda. De esta forma, se puede tener un registro de lo que se ha hablado y se puede asegurar de que se cumplan los compromisos adquiridos.&lt;/p&gt;

&lt;p&gt;Además nos aseguramos de que al menos una persona presta atención constantemente!&lt;/p&gt;

&lt;h4&gt;
  
  
  6. Establece action items
&lt;/h4&gt;

&lt;p&gt;Para evitar cerrar "en falso" las reuniones es buena idea utilizar los últimos minutos para establecer los pasos a seguir y resumir las conclusiones en una serie de puntos.&lt;/p&gt;

&lt;p&gt;Asegúrate de que todos los participantes tengan claro qué se espera que hagan: ¿Cuál es el siguiente paso? ¿Quién se encargará de qué? ¿Deben pasarse a limpio las notas? ¿Actualizar issues? ¿Quién lo hará?&lt;/p&gt;

&lt;h4&gt;
  
  
  7. Aprovecha sus ventajas
&lt;/h4&gt;

&lt;p&gt;No somos robots. El componente social es importante y a veces es bueno verse las caras y simplemente charlar, quejarse de los problemas y cotillear sobre la empresa con los compañeros. En este sentido las reuniones son el mejor aliado.&lt;/p&gt;

&lt;h3&gt;
  
  
  Nivel 1: Chat
&lt;/h3&gt;

&lt;p&gt;Dado que Slack es la herramienta de trabajo por excelencia en el mundo de las startups los ejemplos y consejos ofrecidos en esta sección irán orientados a esta herramienta. No obstante la mayoría pueden aplicarse independientemente de la plataforma utilizada.&lt;/p&gt;

&lt;h4&gt;
  
  
  1. No utilizar el chat como almacenamiento de información.
&lt;/h4&gt;

&lt;p&gt;Por lo general el chat debe utilizarse para hacer preguntas, comentarios y tener conversaciones o discusiones rutinarias &lt;strong&gt;pero no debe utilizarse para almacenar información.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;He visto bastantes empresas donde los requisitos de un ticket o decisiones importantes se pierden en una conversación por chat. Esto es un error porque por lo general la información en los mismos es difícil de buscar y de encontrar haciendo que se pierda la trazabilidad de las decisiones aumentando el &lt;em&gt;kaos&lt;/em&gt; que tanto odiamos.&lt;/p&gt;

&lt;p&gt;Puedes pedir links a documentos, exponer argumentos y debatir acaloradamente, pero una vez se toma una decisión o se genera conocimiento, este debe pasar a formar parte de la base de conocimiento de la empresa en lugar de quedarse en el chat.&lt;/p&gt;

&lt;h4&gt;
  
  
  2. Utiliza hilos de conversaciones
&lt;/h4&gt;

&lt;p&gt;Para evitar fatigar a los participantes de una conversación es recomendable utilizar hilos de forma que la información del canal se mantenga ordenada.&lt;/p&gt;

&lt;h4&gt;
  
  
  3. Utiliza los estados
&lt;/h4&gt;

&lt;p&gt;Muchas empresas tienen un canal donde los empleados anuncian las vacaciones o avisan de que tienen que salir un momento.&lt;/p&gt;

&lt;p&gt;Esto es una mala idea dado que nadie va a buscar entre ese amasijo de mensajes si alguien escribió que tiene que salir o que está enfermo.&lt;/p&gt;

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

&lt;p&gt;Es mucho más eficiente tener una serie de estados y  habilitarlos o deshabilitarlos en cada momento. De esa forma si alguien quiere decir algo por slack sabe perfectamente que su interlocutor esta comiendo, esta enfermo o de vacaciones sin tener que bucear en un canal con mensajes de toda la empresa diciendo que salen a hacer un recado.&lt;/p&gt;

&lt;h3&gt;
  
  
  Nivel 2: Email
&lt;/h3&gt;

&lt;p&gt;El email puede ser visto como un chat asíncrono. Personalmente recomiendo utilizarlo exclusivamente para notificaciones o comunicaciones fuera de la empresa. Para conversaciones irrelevantes se debería utilizar el chat y para discusiones o generar conocimiento se debería usar otra herramienta.&lt;/p&gt;

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

&lt;p&gt;☝️ En mi caso mantengo mi bandeja de entrada está totalmente vacía. En el pantallazo se ve que lo uso a modo te "TODO LIST", donde cada email, normalmente notificaciones de diferentes aplicaciones, representaría una &lt;em&gt;tarea por hacer&lt;/em&gt;. En este caso tengo unos libros por leer y revisar una transacción de TripActions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Nivel 3: Base de conocimiento
&lt;/h3&gt;

&lt;p&gt;Tener una base de conocimientos organizada es uno de los pilares fundamentales para construir una buena cultura de trabajo asíncrono.&lt;/p&gt;

&lt;p&gt;Una empresa realmente asíncrona tendrá todo por escrito en una base de conocimiento pública. Personalmente distingo dos grandes subtipos: Los "de producto" y los de "procesos". Los "de producto" contienen toda la información necesaria para desarrollar el software: ¿Cómo es la arquitectura? ¿Cómo se comunican los sistemas? ¿Cómo se debe comportar la UX en ciertas situaciones? Los de proceso hacen referencia a tareas de la propia empresa. ¿Cómo solicitar un ordenador nuevo? ¿Cómo pedir vacaciones? ¿Con quien hablo si quiero cambiar de rol?&lt;/p&gt;

&lt;h4&gt;
  
  
  1. Es mejor no tener un documento que tenerlo desactualizado
&lt;/h4&gt;

&lt;p&gt;No hay nada mas frustrante que perder el tiempo leyendo información que ya no es relevante. Por eso, en lugar de esforzarse por mantener una base de conocimientos perfecta y completa, es mejor tener una simple pero que esté constantemente en evolución. De esta forma, los empleados tendrán acceso a la información más actualizada y no perderán el tiempo leyendo documentos obsoletos.&lt;/p&gt;

&lt;p&gt;Cada vez que se crea un artículo hay que definir un responsable de sus actualizaciones y esa persona debe preocuparse de que el contenido de los mismos este siempre actualizado.&lt;/p&gt;

&lt;h4&gt;
  
  
  2. Discusiones abajo, resumen actualizado arriba
&lt;/h4&gt;

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

&lt;p&gt;Es muy común que los requisitos de una tarea evolucionen con el tiempo. Pongamos por caso un ticket: "Actualizar la interfaz de las tarjetas de crédito".&lt;/p&gt;

&lt;p&gt;En la descripción del ticket estará una descripción detallada de la tarea que se quiere realizar. Pero no es raro que la gente haga aportaciones en comentarios y se genere una discusión que alterará los requisitos iniciales:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Developer: ¿Esto es solo para tarjetas de crédito?&lt;/li&gt;
&lt;li&gt;PM: "Es para credito y débito"&lt;/li&gt;
&lt;li&gt;Developer: ¿Cuales son los colores posibles?&lt;/li&gt;
&lt;li&gt;PM: "Azul, verde, dorado y negro"&lt;/li&gt;
&lt;li&gt;PM: "Visa nos acaba de confirmar está prohibido hacer tarjetas de color dorado sin su consentimiento, los colores son: azul verde y negro"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Toda esta evolución de los requisitos &lt;strong&gt;debe reflejarse en el ticket de forma que un developer sólamente necesita leer la descripción para saber qué tiene que hacer&lt;/strong&gt; y opcionalmente podrá leer los comentarios para saber qué proceso llevo a esos requisitos.&lt;/p&gt;

&lt;p&gt;Entender este punto es fundamental porque ahorra muchísimo tiempo y dolores de cabeza.&lt;/p&gt;

&lt;h4&gt;
  
  
  3. Toma meeting notes
&lt;/h4&gt;

&lt;p&gt;Por cada reunión a la que asistas escribe un documento de meeting notes y persístelo en la base de conocimiento.&lt;/p&gt;

&lt;p&gt;Personalmente anoto: Objetivo de la reunión, Participantes, Transcripción y conclusiones.&lt;/p&gt;

&lt;p&gt;De esta forma se puede analizar si lo discutido tiene relación con el objetivo de la reunión y si se ha llegado a alguna conclusión relevante.&lt;/p&gt;

&lt;h1&gt;
  
  
  Resumen
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;Trabajar en remoto es diferente a trabajar de forma asíncrona&lt;/li&gt;
&lt;li&gt;Trabajar de forma asíncrona tiene muchas ventajas&lt;/li&gt;
&lt;li&gt;Para crear una cultura de trabajo asíncrona hay que adaptar la comunicación.

&lt;ul&gt;
&lt;li&gt;Por defecto todo debe quedar por escrito.&lt;/li&gt;
&lt;li&gt;El uso del chat y el email debe reducirse&lt;/li&gt;
&lt;li&gt;Las reuniones tienen que evitarse en la medida de lo posible.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

</description>
      <category>spanish</category>
    </item>
    <item>
      <title>Abstracciones detalles e interfaces.</title>
      <dc:creator>IagoLast</dc:creator>
      <pubDate>Thu, 20 Oct 2022 10:50:29 +0000</pubDate>
      <link>https://forem.com/iagolast/abstracciones-detalles-e-interfaces-3p0i</link>
      <guid>https://forem.com/iagolast/abstracciones-detalles-e-interfaces-3p0i</guid>
      <description>&lt;p&gt;Si tuviese que elegir un primer tema a la hora de hablar de arquitectura de software lo tendría claro: &lt;strong&gt;Inversión de la dependencia.&lt;/strong&gt; Este concepto no es nuevo y sin embargo son pocos los que lo aplican en su código. En este artículo me gustaría explicar este concepto de la forma más sencilla posible y poner ejemplos que faciliten su comprensión.&lt;/p&gt;

&lt;h2&gt;
  
  
  Abstracciones, detalles e interfaces
&lt;/h2&gt;

&lt;p&gt;Antes de entrar en materia es importante definir una serie de conceptos que nos permitirán entender correctamente en que consiste el la inversión de la dependencias.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://es.wikipedia.org/wiki/Abstracci%C3%B3n_(filosof%C3%ADa)"&gt;&lt;strong&gt;Abstracción&lt;/strong&gt;&lt;/a&gt; (del latín abstrahere, 'alejar, sustraer, separar') es una operación mental destinada a aislar conceptualmente una propiedad o función concreta de un objeto, y pensar qué es, ignorando otras propiedades del objeto en cuestión.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Detalle&lt;/strong&gt; es una parte, hecho o circunstancia que contribuye a formar o completar una cosa pero no es indispensable en ella.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Interfaz&lt;/strong&gt; es la conexión funcional entre dos sistemas, programas, dispositivos o componentes de cualquier tipo, que proporciona una comunicación de distintos niveles de abstracción, permitiendo el intercambio de información.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Para entender estos conceptos vamos a poner un pequeño ejemplo aplicándolos en mundo de la automoción. &lt;/p&gt;

&lt;h3&gt;
  
  
  Abstracciones en automoción
&lt;/h3&gt;

&lt;p&gt;Para la mayoría de los conductores el motor de sus coches es una &lt;strong&gt;abstracción&lt;/strong&gt;, un concepto del que no necesitan conocer todos los detalles para poder conducir correctamente. Estoy seguro de que muy pocos sabemos si nuestro coche tiene un motor con &lt;a href="https://es.wikipedia.org/wiki/Motor_en_l%C3%ADnea"&gt;una configuración de cilindros en línea&lt;/a&gt; o una &lt;a href="https://es.wikipedia.org/wiki/Motor_en_V"&gt;configuración en V&lt;/a&gt;. Simplemente necesitamos saber cuatro cosas sencillas como si es diésel o gasolina o cada cuantos kilómetros necesita un cambio de aceite. El coche en sí es una &lt;strong&gt;abstracción&lt;/strong&gt; para los conductores y esta abstracción es posible porque los coches nos ofrecen una serie de &lt;strong&gt;interfaces&lt;/strong&gt; que nos permiten conducirlos sin necesidad de conocer sus &lt;strong&gt;detalles de implementación&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;¿Os imagináis tener que estudiar absolutamente todos los detalles de cada coche para poder conducirlo? Gracias a las abstracciones podemos pasar de tener que conocer todos los detalles de implementación...&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ZjV3ph1q--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://static-resources.imageservice.cloud/5119194/2000-lincoln-continental-2000-lincoln-continental-high-beam.jpg%21%255Bimage%255D%28https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4nggwq6vh9tqrrri5mpo.png%29" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ZjV3ph1q--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://static-resources.imageservice.cloud/5119194/2000-lincoln-continental-2000-lincoln-continental-high-beam.jpg%21%255Bimage%255D%28https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4nggwq6vh9tqrrri5mpo.png%29" alt="" width="654" height="890"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;...a tener una serie de interfaces que nos permiten conducir sin necesidad de conocer los detalles&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--dEi3wfke--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.wikihow.com/images/thumb/1/1d/Drive-Manual-Step-7-Version-6.jpg/v4-460px-Drive-Manual-Step-7-Version-6.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--dEi3wfke--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.wikihow.com/images/thumb/1/1d/Drive-Manual-Step-7-Version-6.jpg/v4-460px-Drive-Manual-Step-7-Version-6.jpg" alt="" width="460" height="345"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;De esta forma podemos conducir cualquier modelo de coche, abstrayéndonos del tipo de motor, del amperaje de la batería, de si es de gasolina o eléctrico o de cuántos cilindros tenga... Es suficiente con conocer la interfaz que nos exponen los fabricantes a los conductores para conducir correctamente el vehículo.&lt;/p&gt;

&lt;h3&gt;
  
  
  Abstracciones en software
&lt;/h3&gt;

&lt;p&gt;De la misma forma que en la automoción, en el mundo del software también se manejan estos tres conceptos. Por poner un ejemplo, las funciones son &lt;strong&gt;abstracciones&lt;/strong&gt; que conociendo su &lt;strong&gt;interfaz&lt;/strong&gt; (parámetros de entrada y valor de retorno) nos permiten realizas tareas complejas obviando los &lt;strong&gt;detalles de implementación&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;Por ejemplo sabemos que &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/btoa"&gt;la función btoa de javascript&lt;/a&gt; recibe como parámetro un string y devuelve su representación en base64 pero no necesitamos conocer &lt;a href="https://datatracker.ietf.org/doc/html/rfc4648"&gt;el RFC donde se define el algoritmo&lt;/a&gt; para utilizarla ya que para nosotros es un detalle de implementación sin importancia.&lt;/p&gt;

&lt;h2&gt;
  
  
  Inversión de la dependencia
&lt;/h2&gt;

&lt;p&gt;¿Qué nos dice el principio de la inversión de la dependencia?&lt;/p&gt;

&lt;p&gt;A grandes rasgos nos dice que &lt;strong&gt;nuestro código debe depender de abstracciones en lugar de depender de detalles&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;En lenguajes como Java este principio suele ser mas sencillo de aplicar porque el propio lenguaje dispone del concepto de interfaz pero en el mundo del frontend su aplicacón suele no ser tan directa.&lt;/p&gt;

&lt;p&gt;Una forma sencilla que me gusta utilizar para que mi código no dependa de detalles es &lt;u&gt;crear módulos intermedios&lt;/u&gt; que sirvan como abstracción de una implementación concreta. &lt;/p&gt;

&lt;p&gt;Pongamos un ejemplo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// LoginPage.tsx&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;ReactGA&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react-ga&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * Componente de react que contiene una página de login
 */&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;LoginPage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="cm"&gt;/**
     * Función de login que se ejecutará cuando el usuario haga click en el botón de "login"
     */&lt;/span&gt;
    &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;login&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FormEvent&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;HTMLFormElement&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="cm"&gt;/**
         * Enviamos eventos a Google Analytics
         */&lt;/span&gt;
        &lt;span class="nx"&gt;ReactGA&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;category&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;User&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;login&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="c1"&gt;// ...&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="cm"&gt;/**
     * Omitimos la UI dado que no es relevante para este ejemplo
     */&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt; &lt;span class="na"&gt;onsubmit&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;login&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; ... &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Imaginemos una página de Login que registra un evento cada vez que el usuario envía el formulario de login al servidor. Esta página utiliza &lt;a href="https://github.com/react-ga/react-ga"&gt;react-ga&lt;/a&gt; (Una librería de google analytics sobre React) para monitorizar los eventos del usuario.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--xfYJSmao--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tnm6dn7f0rvb3jvqkrp4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--xfYJSmao--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tnm6dn7f0rvb3jvqkrp4.png" alt="Screenshot 2021-10-04 at 09.57.07" width="880" height="448"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;El problema de esta aproximación es que los componentes (páginas) están acoplados a google-analytics (react-ga). &lt;/p&gt;

&lt;p&gt;Una forma sencilla de eliminar este acoplamiento sería crear un módulo intermedio llamado &lt;code&gt;analytics&lt;/code&gt; y que sea este módulo quien dependa de google-analytics:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// LoginPage.tsx&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;analytics&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./analytics.ts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// LoginPage solamente depende de analytics&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * Componente de react que contiene una página de login
 */&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;LoginPage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="cm"&gt;/**
     * Función de login que se ejecutará cuando el usuario haga click en el botón de "login"
     */&lt;/span&gt;
    &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;login&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FormEvent&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;HTMLFormElement&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="cm"&gt;/**
         * ¡¡Enviamos eventos a nuestra abstracción de analíticas!!
         */&lt;/span&gt;
        &lt;span class="nx"&gt;analytics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;category&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;User&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;login&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="c1"&gt;// ...&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="cm"&gt;/**
     * Omitimos la UI dado que no es relevante para este ejemplo
     */&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;form&lt;/span&gt; &lt;span class="nx"&gt;onsubmit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;login&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/form&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

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

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// analytics.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;ReactGA&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react-ga&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// analytics.ts depende de google-analytics&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * Exponemos una función que nos abstrae de la implementación concreta.
 */&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;track&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;category&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;ReactGA&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--PKCZCZ1e--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7yrca1cvope41a7a5a59.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--PKCZCZ1e--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7yrca1cvope41a7a5a59.png" alt="Screenshot 2021-10-04 at 10.00.32" width="880" height="339"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;De esta forma el código de los componentes ya no depende directamente de Google analytics, si no que depende de una abstracción llamada analytics cuyo detalle de implementación es desconocido para los componentes.&lt;/p&gt;

&lt;p&gt;Aunque puede parecer una tontería hemos &lt;strong&gt;desacoplado&lt;/strong&gt; la lógica de las analíticas del resto de nuestro código y si dentro de unos meses decidimos migrar a cualquier otro proveedor de analíticas basta con hacer cambios en el archivo &lt;code&gt;analytics&lt;/code&gt; y si estos cambios mantienen la misma interfaz el resto del código funcionará perfectamente. &lt;/p&gt;

&lt;p&gt;Volviendo a la metáfora de los coches, podríamos decir qué mientras los pedales funcionen de la misma forma podríamos llegar a reemplazar el motor por otro diferente de forma totalmente transparente para el conductor.&lt;/p&gt;

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

&lt;p&gt;En este artículo hemos visto en que consiste la inversión de la dependencia, los conceptos de abstracción, detalle de implementación e interfaz y cómo se relacionan entre sí. También hemos visto una forma sencilla de abstraer el código de los detalles utilizando módulos intermedios. &lt;/p&gt;

</description>
      <category>javascript</category>
      <category>programming</category>
      <category>beginners</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Breve introducción a las arquitecturas limpias.</title>
      <dc:creator>IagoLast</dc:creator>
      <pubDate>Sun, 09 Oct 2022 12:54:03 +0000</pubDate>
      <link>https://forem.com/iagolast/breve-introduccion-a-las-arquitecturas-limpias-32fc</link>
      <guid>https://forem.com/iagolast/breve-introduccion-a-las-arquitecturas-limpias-32fc</guid>
      <description>&lt;p&gt;En este artículo trataré de describir de forma muy breve y quizá algo inexacta el concepto de arquitectura limpia en el contexto del software.&lt;/p&gt;

&lt;p&gt;"Arquitectura limpia" es el título del libro de Robert C Martin en el que expone y desarrolla el concepto.&lt;/p&gt;

&lt;h2&gt;
  
  
  Arquitectura de software
&lt;/h2&gt;

&lt;p&gt;Citando a Robert, &lt;em&gt;"La arquitectura de un sistema de software es la forma en la que se organizan los componentes que lo forman y la forma en la que se comunican entre sí"&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;En proyectos pequeños como prácticas de clase la importancia de tener una buena arquitectura puede no ser muy evidente pero a medida que los proyectos crecen se hace más y más claro su importancia.&lt;/p&gt;

&lt;p&gt;Uno de los mejores ejemplos que he visto para ilustrar esta importancia &lt;a href="https://dev.to/huytaquoc/a-different-approach-to-frontend-architecture-38d4"&gt;es la siguiente imagen&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fpracticaldev%2Fimage%2Ffetch%2Fs--uwSQ0rQI--%2Fc_limit%252Cf_auto%252Cfl_progressive%252Cq_auto%252Cw_880%2Fhttps%3A%2F%2Fimages.ctfassets.net%2F1es3ne0caaid%2F4qnEjwkNAIiAmYcms8iKua%2F5173990eceb6223dc08e6607636dcc48%2Fclean-architecture-ex-1.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fpracticaldev%2Fimage%2Ffetch%2Fs--uwSQ0rQI--%2Fc_limit%252Cf_auto%252Cfl_progressive%252Cq_auto%252Cw_880%2Fhttps%3A%2F%2Fimages.ctfassets.net%2F1es3ne0caaid%2F4qnEjwkNAIiAmYcms8iKua%2F5173990eceb6223dc08e6607636dcc48%2Fclean-architecture-ex-1.jpeg" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;¿Qué nudos tienes que deshacer para separar la grapadora del resto de componentes?&lt;/p&gt;

&lt;p&gt;Ahora mira esta imagen:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fpracticaldev%2Fimage%2Ffetch%2Fs--w1qI3JJr--%2Fc_limit%252Cf_auto%252Cfl_progressive%252Cq_auto%252Cw_880%2Fhttps%3A%2F%2Fimages.ctfassets.net%2F1es3ne0caaid%2F2Qd2FVYAqIEowM0GySIMyM%2F5d6acdc87b8149732a6b750fcb09c4b7%2Fclean-architecture-ex-2.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fpracticaldev%2Fimage%2Ffetch%2Fs--w1qI3JJr--%2Fc_limit%252Cf_auto%252Cfl_progressive%252Cq_auto%252Cw_880%2Fhttps%3A%2F%2Fimages.ctfassets.net%2F1es3ne0caaid%2F2Qd2FVYAqIEowM0GySIMyM%2F5d6acdc87b8149732a6b750fcb09c4b7%2Fclean-architecture-ex-2.jpeg" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Pues con el software pasa lo mismo, sin entrar en detalles buscamos reducir **la dependencia **de cada una de las piezas y que cada cosa esté en su lugar.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dependencia
&lt;/h2&gt;

&lt;p&gt;Es muy difícil resumir el concepto de dependencia / acoplamiento de software. Una de las maneras más formales que conozco para medir y cuantificar la dependencia de cada una de las piezas del software es la &lt;a href="https://connascence.io/" rel="noopener noreferrer"&gt;"conasence" o ¿"Conacimiento"?&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Resumiéndolo mucho podemos decir que &lt;em&gt;"una pieza &lt;code&gt;A&lt;/code&gt; depende de una pieza &lt;code&gt;B&lt;/code&gt; si cambios en la pieza B afectan de alguna forma a la pieza A"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;En la primera foto podemos decir que todos los utensilios están acoplados entre si, porque para mover las tijeras tenemos que mover el bolígrafo y para mover el bolígrafo tenemos que mover la grapadora...&lt;/p&gt;

&lt;p&gt;En la segunda foto este acoplamiento se reduce mucho y las tijeras sólo están acopladas al taco de post-it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Arquitecturas limpias
&lt;/h2&gt;

&lt;p&gt;Las arquitecturas limpias buscan reducir el acoplamiento organizando las piezas en 3 grandes bloques: Dominio, Aplicación y Presentación. La dependencias se organizan de forma que las capas más centrales no saben nada de las capas exteriores.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  Dominio
&lt;/h3&gt;

&lt;p&gt;Es donde se definen nuestros &lt;strong&gt;objetos de negocio.&lt;/strong&gt;  Por ejemplo si estamos trabajando en una entidad bancaria, una &lt;code&gt;hipoteca&lt;/code&gt; o un &lt;code&gt;cliente&lt;/code&gt; serían objetos de negocio.&lt;/p&gt;

&lt;p&gt;Estos objetos se pueden representar con clases y no tienen absolutamente ninguna dependencia (no importan otros archivos).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Mortgage&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;years&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;rate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;amount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;years&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;years&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;rate&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Client&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;age&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;age&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;age&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
   Aplication
&lt;/h3&gt;

&lt;p&gt;Por encima del dominio tenemos las reglas de negocio. Estas reglas conocen los objetos de dominio y junto a estos definen nuestra &lt;strong&gt;lógica de negocio&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Por ejemplo &lt;em&gt;"No conceder préstamos a más de 20 años vista a personas mayores de 40 años"&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/**
 * Esta función recibe como parámetro una hipoteca y un cliente y devuelve
 * true o false según se pueda conceder o no esta hipoteca. 
 *
 * Cambios en la clase hipoteca o en la clase Cliente pueden 
 * afectar a esta función y decimos que están acopladas.
 *
 * Siendo estrictos podemos decir que tenemos 
 * [conacimiento de tipo](https://connascence.io/type.html) 
 * en los parámetros
 *
 */&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;checkIfMortageCanBeGranted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mortage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;age&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;40&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;mortage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;years&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// ... Asumimos que habría mas reglas de negocio&lt;/span&gt;

    &lt;span class="c1"&gt;// Finalmente devolvemos true para conceder la hipoteca&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



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

&lt;p&gt;En esta última capa tendremos toda la lógica necesaria para presentar nuestra lógica de negocio al cliente final. Podríamos escribir una aplicación en React, una aplicación en Vue o incluso un asistente de voz escrito en javascript.&lt;/p&gt;

&lt;p&gt;Nuestra lógica de negocio y nuestros objetos de dominio &lt;strong&gt;son iguales independientemente de la tecnología elegida para la vista&lt;/strong&gt; y pueden ser utilizados por una app escrita en Angular, por una app escrita en Vue o por una app escrita en React. Además podemos escribir tests unitarios que comprueben este comportamiento de forma totalmente aislada para verificar que los requisitos son correctos.&lt;/p&gt;

&lt;p&gt;De nuevo, lo importante es que gracias a aplicar una arquitectura limpia &lt;strong&gt;tanto los objetos de dominio no están acoplados a la lógica de negocio y la lógica de negocio a su vez es independiente de la presentación.&lt;/strong&gt;. Esto hace que cuando tengamos que mantener una aplicación grande tengamos muchísimo más claro qué nudos hay que deshacer para reemplazar la grapadora por una más moderna.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enlaces
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html" rel="noopener noreferrer"&gt;https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html" rel="noopener noreferrer"&gt;https://www.amazon.com/Clean-Architecture-Craftsmans-Software-Structure/dp/0134494164&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://leanpub.com/pppsd" rel="noopener noreferrer"&gt;https://leanpub.com/pppsd&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>javascript</category>
      <category>beginners</category>
      <category>webdev</category>
      <category>programming</category>
    </item>
    <item>
      <title>El futuro de la programación: Programar menos.</title>
      <dc:creator>IagoLast</dc:creator>
      <pubDate>Sat, 24 Sep 2022 09:15:12 +0000</pubDate>
      <link>https://forem.com/iagolast/el-futuro-de-la-programacion-programar-menos-14o4</link>
      <guid>https://forem.com/iagolast/el-futuro-de-la-programacion-programar-menos-14o4</guid>
      <description>&lt;h2&gt;
  
  
  Twillio abre la veda
&lt;/h2&gt;

&lt;p&gt;El pasado 21 de septiembre Twillio publicaba la versión 9 de su SDK para Java.&lt;/p&gt;

&lt;p&gt;Además de contener breaking changes, en la release notes se puede leer un párrafo que indica la dirección de la nueva revolución tech.&lt;/p&gt;

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

&lt;blockquote&gt;
&lt;p&gt;Java Helper is now auto-generated via OpenAPI with this release. This enables us to rapidly add new features and enhance consistency across versions and languages. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  OpenAPI
&lt;/h2&gt;

&lt;p&gt;Una de las revoluciones silenciosas en el mundo de la programación ha sido la aparición de OpenAPI (La versión 3 se publicó en 2017).&lt;/p&gt;

&lt;p&gt;OpenAPI es un estándar abierto para describir APIs RESTful  especificando formalmente que define el formato de los datos que se intercambian entre el cliente y el servidor. OpenAPI se basa en JSON Schema, un tema sobre el que probablemente escriba pronto, un estándar para describir datos en formato JSON.&lt;/p&gt;

&lt;p&gt;La principal ventaja de &lt;strong&gt;OpenAPI&lt;/strong&gt; es que &lt;strong&gt;puede autogenerarse&lt;/strong&gt;. Esto significa que podemos tener gratis documentación y el código clientes a partir de nuestro código backend! Además esta documentación y nuestro código se mantienen sincronizados y actualizados automáticamente.&lt;/p&gt;

&lt;p&gt;Herramientas como readme.io son compatibles con OpenAPI y te permiten tener un portal de documentación completo a partir de la especificación de la API en segundos.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq2wexhwapzqo65chwvuc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq2wexhwapzqo65chwvuc.png" alt="Portal de documentación generado con readme.io" width="800" height="523"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Una de las cosas que más me sorprende es que son capaces de autogenerar clientes para un montón de lenguajes!&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Autogenerando clientes Typescript
&lt;/h2&gt;

&lt;p&gt;En el caso de Typescript lo hacen mediante un paquete llamado &lt;a href="https://api.readme.dev/docs" rel="noopener noreferrer"&gt;api&lt;/a&gt; que hace que me explote la cabeza.&lt;/p&gt;

&lt;p&gt;Actualmente están trabajando en la versión 5 y sospecho que va a cambiar la forma de trabajar de muchos.&lt;/p&gt;

&lt;p&gt;Para generar el cliente typescript de una API basta con ejecutar el comando desde npx indicando la url de la spec en OpenAPI (en el momento de escribir estas lineas estaba en la beta de la versión 5.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx api@5.0.0-beta.3 &lt;span class="nb"&gt;install &lt;/span&gt;https://api.timetime.in/v3/api-docs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feg2u4r4hra2l8zvuju8l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feg2u4r4hra2l8zvuju8l.png" alt="Resultado de la ejecución del comando" width="800" height="435"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Esto se descarga la especificación y genera una librería desde cero con todos los métodos de la API incluyendo los tipos de los parámetros de entrada y de los valores retornados. Además la publica en npm dentro de su namespace &lt;code&gt;@api/&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;El único problema es el versionado. A día de hoy no he visto cómo se puede instalar una versión específica del cliente de una API por lo que creo que las builds no serían reproducibles.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiock2meqt09z4j9ia2a1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiock2meqt09z4j9ia2a1.png" alt="El package.json de la librería generada no tiene versión" width="800" height="297"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Evidentemente, al tratarse de una beta no lo he probado en producción pero creo que en unos años será el estado del arte a la hora de comunicar clientes y servidores matando a GraphQL e incluso a alternativas tan interesantes como gRPC.&lt;/p&gt;

&lt;p&gt;En resumen: No perdais el tiempo! Autogenerad la spec de vuestra API a partir del código del servidor, autogenerad la documentación y autogenerad los SDKs y los clientes.&lt;/p&gt;

</description>
      <category>api</category>
      <category>javascript</category>
      <category>programming</category>
      <category>discuss</category>
    </item>
    <item>
      <title>Las cosas por sus nombre</title>
      <dc:creator>IagoLast</dc:creator>
      <pubDate>Sun, 04 Sep 2022 15:22:24 +0000</pubDate>
      <link>https://forem.com/iagolast/las-cosas-por-sus-nombre-30d0</link>
      <guid>https://forem.com/iagolast/las-cosas-por-sus-nombre-30d0</guid>
      <description>&lt;p&gt;Ayer fue la &lt;a href="https://twitter.com/phpulpocon" rel="noopener noreferrer"&gt;#pulpoCon22&lt;/a&gt; que se ha consolidado como como uno de mis eventos favoritos y que creo que dará mucho de que hablar en el futuro.&lt;/p&gt;

&lt;p&gt;Entre otras cosas tuve la oportunidad de desvirtualizar al gran &lt;a href="https://twitter.com/talkingbit1" rel="noopener noreferrer"&gt;@talkingbit1&lt;/a&gt; que me confesó que el secreto del éxito es escribir algo todos los días y pude asistir a una charla sobre testing de &lt;a href="https://twitter.com/nuria_codes" rel="noopener noreferrer"&gt;@nuria_codes&lt;/a&gt; donde entendí que dijo que &lt;em&gt;los nombres de los tests no importan&lt;/em&gt; y que dedicamos demasiado esfuerzo y tiempo a ponerle apodos a las cosas en lugar de reflexionar sobre sus características.&lt;/p&gt;

&lt;p&gt;En parte estoy de acuerdo y me gustaría recomendar &lt;a href="https://www.amazon.es/%C2%BFEst%C3%A1-broma-Feynman-Libros-Singulares/dp/8420684902" rel="noopener noreferrer"&gt;leer las reflexiones del gran Richard Feynman&lt;/a&gt; donde habla entre otras cosas, sobre la diferencia entre nombrar vs conocer.&lt;/p&gt;

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

&lt;p&gt;Es cierto que para hablar con alguien de forma efectiva y eficiente es recomendable disponer de un lenguaje común, pero es importante recordar que al menos en cuanto a testing los nombres son lo de menos. Lo importante es conocer las características, comportamiento y ventajas e inconvenientes de cada tipo de test independientemente del nombre que le demos.&lt;/p&gt;

&lt;p&gt;En particular a la hora de hablar de un test me gusta acotar las siguientes características:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Sensibilidad&lt;/strong&gt;: Probabilidad de fallar si existe un error en el código.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Especificidad&lt;/strong&gt;: Probabilidad de dar por válido un comportamiento correcto del código.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fragilidad&lt;/strong&gt;: Probabilidad de que un test de falsos positivos. Se compone a su vez de:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Estabilidad&lt;/strong&gt;: Probabilidad de que un test de el mismo resultado en diferentes ejecuciones.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Flexibilidad&lt;/strong&gt;: Tolerancia de un test a cambios en la implementación que no afectan al comportamiento.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Precisión&lt;/strong&gt;: Mide cómo de complicado es identificar la fuente del error si el test falla.&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Mantenibilidad&lt;/strong&gt;: Mide cómo de complicado es actualizar o modificar el código del test. &lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Velocidad&lt;/strong&gt;: Mide cuánto tiempo tarda el test en ejecutarse.&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Profundidad&lt;/strong&gt;   : Indica el número de componentes y dependencias que se ejecutan en el test.&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;Con estas características encima de la mesa creo que uno de los debates más frecuentes es la discusión entre qué es un test unitario y qué es un test de integración. Algunos consideran que un test unitario prueba sólo una unidad (profundidad cero) y otros como Fowler &lt;a href="https://martinfowler.com/bliki/UnitTest.html" rel="noopener noreferrer"&gt;introducen el concepto de test unitarios sociables&lt;/a&gt; para poder denominar tests unitarios a tests con profunidad N.&lt;/p&gt;

&lt;p&gt;Este dogmatismo en cuanto a nombres y "buenas prácticas" hace que algunos se obsesionen con cumplir la máxima de &lt;em&gt;"la pirámide de tests dice que tenemos que tener muchos test unitarios"&lt;/em&gt; y llenan la base de código de tests que no sirven absolutamente para nada mientras que jamás probarán un caso de uso completo porque &lt;em&gt;"esos tests son frágiles y es malo que un test sea frágil"&lt;/em&gt; &lt;/p&gt;

&lt;p&gt;En resumen, aunque llamar a las cosas por su nombre es una buena idea para tener una conversación fluida por desgracia todavía no tenemos unos nombres establecidos en la industria.&lt;/p&gt;

&lt;p&gt;Mientras eso no ocurra, si queremos evitar situaciones donde se imponga el fanatismo y el dogmatismo, lo mejor sería que antes de cada discusión nos aseguráramos de que todos los participantes conocen las características de los tipos de tests de los que vamos a hablar para poder centrar la conversación en las ventajas y riesgos que asumimos con cada uno de ellos y poder &lt;strong&gt;usar la cabeza para determinar cuál es la mejor estrategia de testing para nuestro caso particular&lt;/strong&gt; en lugar de seguir recetas y soluciones milagrosas totalmente a ciegas.&lt;/p&gt;

</description>
      <category>testing</category>
      <category>beginners</category>
      <category>programming</category>
    </item>
    <item>
      <title>Muy breve introducción al testing</title>
      <dc:creator>IagoLast</dc:creator>
      <pubDate>Thu, 01 Sep 2022 13:31:30 +0000</pubDate>
      <link>https://forem.com/iagolast/muy-breve-introduccion-al-testing-4e81</link>
      <guid>https://forem.com/iagolast/muy-breve-introduccion-al-testing-4e81</guid>
      <description>&lt;p&gt;Cuanto más programo más convencido estoy de que el testing es una de las herramientas más útiles que tenemos los programadores a nuestra disposición. No conozco a nadie que haga tests correctamente que no este de acuerdo. Los tests automáticos ahorran tiempo, dinero, reducen la cantidad de errores, ayudan a diseñar, a razonar sobre el software, permiten eliminar deuda técnica con mayor seguridad y hasta sirven como especificación formal de los requisitos del software.&lt;/p&gt;

&lt;h2&gt;
  
  
  ¿Qué es un test?
&lt;/h2&gt;

&lt;p&gt;Imagínate que acabas de escribir un fragmento de código...&lt;/p&gt;

&lt;p&gt;¿Cómo sabes que funciona? ¿Cómo sabes que hace lo se espera que haga?&lt;/p&gt;

&lt;p&gt;La mayoría de la gente responderá que lo sabe porque lo ha probado. Porque han abierto el navegador, navegado hacia cierta sección de la app, rellenado un input, han hecho click en un botón y han comprobado que la app reacciona correctamente a esa interacción del usuario.&lt;/p&gt;

&lt;p&gt;Seguramente tú mismo, has puesto un &lt;code&gt;console.log&lt;/code&gt; para comprobar de alguna forma que una función hace lo que esperas. Especialmente si la función no tiene asociada ninguna interfaz gráfica por ejemplo &lt;em&gt;escribir una función que sume dos números&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;En estos casos la mayoría de developers acabe utilizando la consola para comprobar que la función es correcta:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;// 3&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;// 4&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Vamos a analizar estos procesos... tanto en el primero donde el desarrollador prueba manualmente la app desde el navegador como en el de los console logs se repiten unos patrones comunes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Se identifica la funcionalidad que se quiere probar.&lt;/li&gt;
&lt;li&gt;Se ejecuta el software que contiene esa funcionalidad bajo unas condiciones controladas.&lt;/li&gt;
&lt;li&gt;Se comprueba que el resultado obtenido es igual al resultado esperado.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Estoy seguro que más de uno identifica este comportamiento, si es así enhorabuena ya estás haciendo tests, el siguiente paso es &lt;strong&gt;convertir estos tests manuales en tests automáticos.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Tests automáticos
&lt;/h2&gt;

&lt;p&gt;La pregunta del millón es: ¿Podemos escribir un software que compruebe que otro software funciona correctamente?&lt;/p&gt;

&lt;p&gt;En un principio puede parecer una locura escribir código para que pruebe otro código de forma automática. Sin embargo a medida que el software crece, las ventajas que ofrecen los tests automáticos son evidentes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ayudan a detectar errores fácilmente.&lt;/li&gt;
&lt;li&gt;Nos sirven como documentación.&lt;/li&gt;
&lt;li&gt;Ayudan a razonar sobre el código y a mejorar el diseño.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Tests runners
&lt;/h2&gt;

&lt;p&gt;Supongamos que estamos convencidos de hacer tests automáticos. Lo primero que necesitamos es lo que se llama un "test runner", por definirlo de forma sencilla un test runner es una herramienta que nos permite definir y ejecutar nuestros tests automáticos. En el mundo del frontend los más conocidos son &lt;a href="https://jestjs.io/" rel="noopener noreferrer"&gt;jest&lt;/a&gt; y &lt;a href="https://mochajs.org/" rel="noopener noreferrer"&gt;mocha&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Estos frameworks nos ofrecen entre otras cosas una función llamada &lt;a href="https://jestjs.io/docs/api#testname-fn-timeout" rel="noopener noreferrer"&gt;test&lt;/a&gt; que nos permite nombrar, definir y ejecutar tests de forma sencilla o la función &lt;a href="https://jestjs.io/es-ES/docs/expect" rel="noopener noreferrer"&gt;expect&lt;/a&gt; para comprobar que los valores obtenidos cumplen los requisitos esperados.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;LA DESCRIPCION DEL TEST VA AQUI&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

&lt;span class="c1"&gt;// La implementación del test va aquí&lt;/span&gt;

&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Los tests runners también nos ayudan a ejecutar los tests y generan un informe diciendo cuántos tests han tenido el resultado esperado y cuántos de ellos han fallado.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ejemplo sencillo: La función replace
&lt;/h2&gt;

&lt;p&gt;Los strings de javascript heredan una función &lt;a href="https://developer.mozilla.org/es/docs/Web/JavaScript/Reference/Global_Objects/String/replace" rel="noopener noreferrer"&gt;replace&lt;/a&gt; de su prototipo que recibe dos parámetros, un string a reemplazar y el string que lo reemplazará.&lt;/p&gt;

&lt;p&gt;¿Cómo lo probarías?&lt;/p&gt;

&lt;p&gt;La respuesta es siempre la misma: &lt;strong&gt;Haz exactamente lo mismo que harías si tuvieses que probar manualmente.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;En este caso seguramente definirías un string, llamarías al método replace y comprobarías (mediante un console.log) que el string final es el esperado. En el caso de los tests automáticos el proceso es el mismo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Utilizamos la funcion "test" para dar nombre a la prueba e implementarla&lt;/span&gt;
&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;should replace a string with the new given string&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Creamos un string conocido para ejecutar nuestra prueba&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;template&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hello world&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="c1"&gt;// Ejecutamos la función que queremos probar pasándole unos parámetros conocidos&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;actual&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;world&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;testing&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// Comprobamos que el resultado es el esperado&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;actual&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hello testing&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;y eso es todo! Hemos escrito nuestro primer test.&lt;/p&gt;

&lt;p&gt;Evidentemente esto no es mas que una pequeña introducción, pero la esencia es siempre la misma, intentar automatizar el proceso que seguiríamos para probar una funcionalidad manualmente.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusiones
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Inconscientemente los programadores realizan tests manuales. &lt;/li&gt;
&lt;li&gt;Estos tests se pueden llegar a automatizar.&lt;/li&gt;
&lt;li&gt;Los tests automáticos son una de las mejores herramientas disponibles actualmente.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>testing</category>
      <category>javascript</category>
      <category>beginners</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
