<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="https://arandilopez.me/feed.xml" rel="self" type="application/atom+xml" /><link href="https://arandilopez.me/" rel="alternate" type="text/html" /><updated>2026-08-30T00:03:47+00:00</updated><id>https://arandilopez.me/feed.xml</id><title type="html">Arandi Lopez</title><subtitle>This the personal blog of Arandi Lopez, a software engineer and web developer. I write about web development, programming, and technology.</subtitle><entry><title type="html">Notas sobre construir un orquestador de AI para un proyecto con varios repositorios</title><link href="https://arandilopez.me/spanish/2026/08/26/notas-sobre-construir-un-orquestador-de-ai-para-un-proyecto-con-varios-repositorios.html" rel="alternate" type="text/html" title="Notas sobre construir un orquestador de AI para un proyecto con varios repositorios" /><published>2026-08-26T00:00:00+00:00</published><updated>2026-08-26T00:00:00+00:00</updated><id>https://arandilopez.me/spanish/2026/08/26/notas-sobre-construir-un-orquestador-de-ai-para-un-proyecto-con-varios-repositorios</id><content type="html" xml:base="https://arandilopez.me/spanish/2026/08/26/notas-sobre-construir-un-orquestador-de-ai-para-un-proyecto-con-varios-repositorios.html"><![CDATA[<p>En los últimos meses he usado agentes de AI para trabajar en un proyecto dividido en varios repositorios.
Al principio parecía suficiente abrir una sesión de <a href="https://code.claude.com/docs/en/overview">Claude Code</a>
en cada repositorio en su respectiva rama y pedirle que trabajara en una parte del ticket.
Pero cuando una tarea necesitaba cambios en backend, frontend y otros repositorios, el problema dejó de ser generar código.
El problema era coordinar todo ese trabajo sin perder el contexto y moverse entre ventanas y tabs de terminal.</p>

<!-- more -->

<p>Por eso empecé a construir un pequeño hub de orquestación.
La idea es tener una sesión central de <a href="https://code.claude.com/docs/en/overview">Claude Code</a>,
el agente de AI que uso desde la terminal, que conoce el proyecto, revisa los tickets y envía el trabajo al repositorio correcto.
Esa sesión no escribe código.
Su trabajo es coordinar a los agentes que sí lo hacen.</p>

<p>En este post quiero compartir cómo funciona esta idea, qué partes me han resultado útiles y qué cosas todavía necesitan mejorar.
No creo que sea la única forma de trabajar con agentes de AI, pero hasta ahora me ha ayudado a mantener orden en un proyecto que está dividido en varios repositorios.</p>

<h2 id="el-problema-no-era-generar-código">El problema no era generar código</h2>

<p>Un agente puede leer un repositorio, hacer un plan y modificar archivos bastante bien.
El problema aparece cuando una tarea necesita cambios en varios lugares.
Cada agente tiene su propio contexto y puede tomar decisiones que no coinciden con las decisiones de los demás.
También puede pasar que dos agentes trabajen sobre la misma rama o que una nueva sesión tenga que volver a investigar algo que ya se había discutido.</p>

<p>Mi primera decisión fue separar la coordinación de la implementación.
El orquestador lee el ticket, revisa el contexto que ya existe y pregunta en cuáles repositorios se necesita trabajar.
Después crea una sesión especializada para cada repositorio.</p>

<p>Esto parece una diferencia pequeña, pero me permite tener una sola vista del trabajo sin darle a una sesión demasiado poder.
El orquestador no cambia código, no crea commits y no hace push.
Cada agente trabaja con las reglas, herramientas y pruebas de su propio repositorio.</p>

<h2 id="worktrees-y-memoria-compartida">Worktrees y memoria compartida</h2>

<p>Para aislar el trabajo uso <a href="https://git-scm.com/docs/git-worktree">Git worktrees</a> gestionados por <a href="https://worktrunk.dev">Worktrunk</a>.
Cada ticket obtiene su propio directorio y su propia rama, así que un agente puede trabajar sin modificar el directorio principal y sin interferir con otro ticket.
También puedo abrir ese worktree en <a href="https://neovim.io/">Neovim</a> y revisar los cambios mientras el agente continúa trabajando.</p>

<p>Para monitorear cada tarea uso <a href="https://github.com/motionharvest/herdr">Herdr</a>, un multiplexor de terminal para coordinar agentes, con una pestaña para <a href="https://code.claude.com/docs/en/overview">Claude Code</a>, otra para <a href="https://neovim.io/">Neovim</a> y otra para ver el diff en vivo usando <a href="https://github.com/modem-dev/hunk">Hunk</a>, un visor de diffs de Git para la terminal.
Cada worktree tiene su propio workspace de Herdr, así que puedo ver lo que está haciendo cada agente y revisar los cambios sin mezclar el contexto de un ticket con otro.
No es un sistema automático de aprobación.
Es solamente una forma práctica de tener a la vista lo que está ocurriendo y poder intervenir cuando algo no se ve bien.</p>

<p>El otro problema era la memoria.
Si cada sesión empieza desde cero, una parte importante del tiempo se pierde leyendo el proyecto y reconstruyendo decisiones anteriores.
Para evitarlo uso un vault de <a href="https://obsidian.md/">Obsidian</a> como memoria compartida.
Ahí guardo notas de los tickets, planes y contexto que puede servir para las siguientes tareas.</p>

<p>Antes de enviar un agente, el orquestador consulta esa memoria y le pasa solamente la información que necesita.
De esta forma el vault funciona como una fuente común de contexto, pero el código continúa siendo responsabilidad de cada repositorio.</p>

<h2 id="cómo-se-ve-una-tarea">Cómo se ve una tarea</h2>

<p>El flujo que uso ahora es más o menos el siguiente:</p>

<ol>
  <li>El orquestador lee el ticket y determina conmigo qué partes del proyecto necesitan cambios.</li>
  <li>Consulta las notas existentes para no repetir investigación.</li>
  <li>Crea un worktree y abre un agente especializado por cada repositorio involucrado.</li>
  <li>Los agentes preparan un plan a revisar antes de escribir código.</li>
  <li>Una vez aprobado el plan, los agentes hacen cambios, crean commits y hacen push a sus ramas.</li>
  <li>Después de hacer merge, el orquestador guarda el contexto útil y elimina los worktrees que ya no se necesitan.</li>
</ol>

<p>La revisión humana sigue siendo importante.
En especial para cambios de frontend, prefiero revisar el plan antes de que el agente empiece a modificar archivos. Hago pruebas manuales y capturas de pantalla que sirven de documentación
para el pull request.</p>

<p>Revisar un plan toma poco tiempo y es mucho más simple que corregir una implementación completa basada en una idea equivocada.</p>

<p>También uso el diff en vivo para revisar el trabajo sin esperar un reporte final.
No necesito preguntar constantemente por el estado del ticket.
Puedo ver si el agente está trabajando, si necesita ayuda o si está cambiando algo que no esperaba.</p>

<h2 id="lo-que-todavía-no-funciona-tan-bien">Lo que todavía no funciona tan bien</h2>

<p>El sistema no está libre de problemas.
La memoria compartida usa symlinks en algunos lugares y eso hace que la creación y limpieza de worktrees necesite más cuidado del que me gustaría.
Una mala secuencia puede dejar un enlace roto o hacer que una nota no termine donde esperaba.</p>

<p>La limpieza de ramas también es más complicada de lo que parecía.
No basta con revisar si existe una rama.
El sistema debe confirmar que el cambio ya fue integrado antes de eliminar el worktree y la rama local.
Por eso prefiero que cada repositorio termine su propio flujo de commit, push y pull request, y que el orquestador solamente limpie después.</p>

<p>La otra limitación es encontrar información en el vault.
Guardar notas es sencillo, pero después de varias tareas ya no es práctico buscar archivo por archivo.
Por ahora uso búsqueda por palabras y búsqueda semántica, aunque creo que la memoria necesitará un mejor índice conforme crezca el proyecto.</p>

<p>También he aprendido que guardar todo no significa tener buena memoria.
Las notas deben ser cortas, claras y útiles para la siguiente sesión.
De lo contrario solamente estamos moviendo el problema del contexto a una carpeta con demasiados archivos.</p>

<h3 id="en-conclusión">En conclusión</h3>

<p>Construir este orquestador no hizo que los agentes escribieran mejor código de forma automática.
Lo que mejoró fue la coordinación.
Ahora cada agente tiene un espacio aislado, recibe el contexto necesario y trabaja dentro de las reglas de su repositorio.
Yo mantengo una vista general de los ticket y todavía debo revisar los planes y los cambios antes de aceptarlos.</p>

<p>Lo más útil para mí ha sido separar tres responsabilidades: coordinar el trabajo, implementar los cambios y conservar el conocimiento.
<a href="https://code.claude.com/docs/en/overview">Claude Code</a> se encarga de una parte, <a href="https://git-scm.com/docs/git-worktree">Git worktrees</a> de otra y el vault de <a href="https://obsidian.md/">Obsidian</a> mantiene el contexto entre sesiones.
Tener una visibilidad completa de lo que ocurre en cada worktree con <a href="https://github.com/motionharvest/herdr">Herdr</a> me permite intervenir cuando es necesario.</p>

<p>Aún hay detalles por simplificar, especialmente la sincronización de la memoria y la limpieza de worktrees.
Pero como experimento ha sido una buena forma de trabajar con varios agentes sin perder por completo el control del proyecto.</p>]]></content><author><name></name></author><category term="spanish" /><category term="ai" /><category term="claude" /><category term="orchestration" /><category term="git" /><category term="workflow" /><category term="herdr" /><category term="obsidian" /><category term="neovim" /><category term="hunk" /><category term="worktree" /><summary type="html"><![CDATA[En los últimos meses he usado agentes de AI para trabajar en un proyecto dividido en varios repositorios. Al principio parecía suficiente abrir una sesión de Claude Code en cada repositorio en su respectiva rama y pedirle que trabajara en una parte del ticket. Pero cuando una tarea necesitaba cambios en backend, frontend y otros repositorios, el problema dejó de ser generar código. El problema era coordinar todo ese trabajo sin perder el contexto y moverse entre ventanas y tabs de terminal.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://arandilopez.me/assets/images/og/posts/notas-sobre-construir-un-orquestador-de-ai-para-un-proyecto-con-varios-repositorios.png" /><media:content medium="image" url="https://arandilopez.me/assets/images/og/posts/notas-sobre-construir-un-orquestador-de-ai-para-un-proyecto-con-varios-repositorios.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Vibe Coding mi sitio en Jekyll</title><link href="https://arandilopez.me/spanish/2025/05/25/vibe-coding-mi-sitio-en-jekyll.html" rel="alternate" type="text/html" title="Vibe Coding mi sitio en Jekyll" /><published>2025-05-25T21:03:00+00:00</published><updated>2025-05-25T21:03:00+00:00</updated><id>https://arandilopez.me/spanish/2025/05/25/vibe-coding-mi-sitio-en-jekyll</id><content type="html" xml:base="https://arandilopez.me/spanish/2025/05/25/vibe-coding-mi-sitio-en-jekyll.html"><![CDATA[<p>Esta semana decidí actualizar mi sitio personal. Volví a <a href="https://jekyllrb.com" target="_blank">Jekyll</a>
después de varios años y actualicé las dependencias de mi blog,
<a href="https://tailwindcss.com" target="_blank">TailwindCSS v4</a> y <a href="https://daisyui.com" target="_blank">DaisyUI</a>,
que es muy útil con sus “component classes”. Y decidí darle un <em>chance</em> a la
AI para ayudarme a migrar mi sitio a Jekyll y a una nueva UI.</p>

<!-- MORE -->

<p>Como Ingeniero de Software me es muy interesante todo lo que la comunidad hace
con “Vibe Coding”. Y existen muchas opiniones y muchos comentarios de
hasta donde llegaría el límite de AI como asistente o un reemplazo del Ingeniero
de Software.</p>

<p>De nuevo al tópico de este sitio, al principio en su primera versión creé el sitio
con Jekyll y hosteando en Github Pages. Posteriormente encontré <a href="https://middlemanapp.com" target="_blank">Middleman</a>
y me pareció muy interesante la forma de crear el contenido estático. Ya por
último y desde mi ultima versión migré el sitio de Middleman a
<a href="https://www.bridgetownrb.com" target="_blank">Bridgetown</a>
y usando Netlify como hosting.</p>

<p>Y ahora, de vuelta a Jekyll. Y creo que Jekyll es la mejor plataforma para hacer
un sitio blog como este. Es simple y ha estado en el mercado por muchos años.
Y saltar de framework a framework cada que sale uno nuevo no es algo que quiera
hacer seguido, al menos no con este sitio.  Pero si quiero tenerlo actualizado,
quiero que las dependencias se mantengan actualizadas. Por eso opte por migrar mi
sitio a Jekyll y usar Vite con TailwindCSS y DaisyUI. Espero que estos ultimos no
requieran de mucho esfuerzo para actualizarse en el futuro.</p>

<h2 id="y-que-tiene-que-ver-el-vibe-coding">Y que tiene que ver el Vibe Coding?</h2>

<p>Bueno, quiero enfocarme mas en escribir y quería darle un nuevo diseño a mi sitio
sin tanto problema o este caso sin invertir mucho tiempo.
Y también, probar que tan bueno sería la AI para hacer un diseño.
Un Blog es sencillo, y Tailwind y DaisyUI ya están en el contexto de entrenamiento
de las AI mas modernas, así que no fue tanto trabajo.</p>

<p>Para esto use VSCode, lamentablemente Neovim no tiene soporte avanzado para AI y
agentes o herramientas que editen archivos. Así que simplemente activé el
“Agent-mode” de VSCode y use Gemini 2.5 Pro.
Creé los archivos que necesitaba y solo pedí que haga una UI “mobile-first”.</p>

<p>Luego de varias iteraciones sobre los templates, varios archivos,
y configuraciones obtuve un resultado satisfactorio.
Y no, no fue libre de errores. La paginación de posts no salio bien y ya tuve que
intervenir para arreglarlo. De igual forma en varios detalles visuales y
organización del código HTML. Pero donde si puedo decir que hizo un excelente
trabajo fue cuando le pedí que basado en un CSS que tenia estilos para Rouge
(el highligther default de Jekyll) hiciera uno con el tema <em>catppuccin-frappe</em>
para dark-mode y latte para el sitio en light-mode.
He aquí un ejemplo:</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="c1"># This is an example</span>

<span class="k">def</span> <span class="nf">fibonacci</span><span class="p">(</span><span class="n">n</span><span class="p">)</span>
  <span class="k">return</span> <span class="n">n</span> <span class="k">if</span> <span class="n">n</span> <span class="o">&lt;=</span> <span class="mi">1</span>
  <span class="n">fibonacci</span><span class="p">(</span><span class="n">n</span> <span class="o">-</span> <span class="mi">1</span><span class="p">)</span> <span class="o">+</span> <span class="n">fibonacci</span><span class="p">(</span><span class="n">n</span> <span class="o">-</span> <span class="mi">2</span><span class="p">)</span>
<span class="k">end</span>

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

<h3 id="en-conclusión">En conclusión</h3>

<p>Jekyll es una excelente opción para blogs y sitios estáticos. Tiene excelente
soporte y le apuesto a su longevidad para mi sitio personal. TailwindCSS y
DaisyUI son simples e increíbles, y la AI puede ser una gran ayuda para acelerar
el proceso de diseño y desarrollo, aunque siempre es bueno tener un ojo crítico
y hacer ajustes manuales cuando sea necesario. Para el fin de migrar mi sitio,
le doy un punto a la AI.</p>

<p>Puedes consultar el repositorio de este sitio en <a href="https://github.com/arandilopez/arandilopez.me" target="_blank">Github</a>.</p>]]></content><author><name></name></author><category term="spanish" /><category term="ai" /><category term="vibe-coding" /><category term="jekyllrb" /><summary type="html"><![CDATA[Esta semana decidí actualizar mi sitio personal. Volví a Jekyll después de varios años y actualicé las dependencias de mi blog, TailwindCSS v4 y DaisyUI, que es muy útil con sus “component classes”. Y decidí darle un chance a la AI para ayudarme a migrar mi sitio a Jekyll y a una nueva UI.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://arandilopez.me/assets/images/og/posts/vibe-coding-mi-sitio-en-jekyll.png" /><media:content medium="image" url="https://arandilopez.me/assets/images/og/posts/vibe-coding-mi-sitio-en-jekyll.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Mi camino en Vim</title><link href="https://arandilopez.me/2021/01/16/mi-camino-en-vim.html" rel="alternate" type="text/html" title="Mi camino en Vim" /><published>2021-01-16T00:00:00+00:00</published><updated>2021-01-16T00:00:00+00:00</updated><id>https://arandilopez.me/2021/01/16/mi-camino-en-vim</id><content type="html" xml:base="https://arandilopez.me/2021/01/16/mi-camino-en-vim.html"><![CDATA[<p>En los últimos 2 años he usado <a href="https://vim.org">Vim</a> como mi editor por default en todas las computadoras que tengo (aunque desde el 2020 Emacs se ha vuelto mi elección preferida, pero haré un post para hablar luego de eso). Y todo empezó porque no quiero usar VS Code.</p>

<!-- more -->

<p>Antes que nada, si no conoces o sabes que es Vim te explico de forma sencilla: Vim es un editor de texto cuyos
predecesores existen desde los 80’s (ya unos 40 años), que funciona directamente en la terminal del sistema.
Muchos llaman a Vim como el editor de los programadores y esto es por lo configurable y más que nada <strong>programable</strong> que es Vim.</p>

<h3 id="por-qué-usar-algo-tan-viejo-en-este-siglo-xxi">¿Por qué usar algo tan viejo en este siglo XXI?</h3>

<p>Mi cambio a Vim sucedió por algo inevitable: La pronta muerte de <a href="https://atom.io">Atom</a>. Esto sucederá tarde o temprano,
y aunque no quiera sonar pesimista eso es lo que pasará. ¿Por qué morirá Atom? Es fácil resolver eso, ya no es <em>cool</em> para muchos.</p>

<p>Cuando Atom salió en el 2012 fue increible. Un editor que promovió y evolucionó la tecnología web dando a luz a muchos
proyectos como Electron y que impulsó mucho el poder oculto de javascript fuera de la web,
pero cuyos tiempos se agotan cada vez gracias a la mayor adopción que VS Code de Microsoft tiene ahora.
Y no es culpa de Microsoft (ésta vez), es del trending, el hype que tiene, un algo que lo hace un competidor mejor que Atom.</p>

<p>Pero la gota que derramó el vaso fue la adiquicicíon de Github por Microsoft. Eso creo que declaró la muerte a Atom.
Sin embargo Atom tiene su gente, tiene sus fans y seguramente seguira como un proyecto activo por algunos años más.
Pero eso no lo sabremos.</p>

<h3 id="si-vs-code-es-mejor-que-atom-por-qué-no-usas-vs-code-y-ya">Si VS Code es mejor que Atom, ¿por qué no usas VS Code y ya?</h3>

<p>NO. No puedo. Existe algo que me impide realmente verlo como algo <em>útil</em>. Y jamás he estado a gusto con tecnología de Microsoft.
Simplemente no se puede.</p>

<h3 id="qué-opciones-tengo">¿Qué opciones tengo?</h3>

<p>¿Volver a Sublime? Si, quizás (y amé Sublime). Pero quiero algo <em>open source</em>, quiero algo <em>libre</em>.</p>

<h3 id="neta-vim">¿Neta Vim?</h3>

<p>Si, Vim. ¿Por que no?. Mucha gente lo usa y lo ha usado por casi 40 años. Muchos desarrolladores que admiro lo hacen.
Y si eso sucede es por algo. Y si aún vive en estos tiempos de <strong>hype/trending adopment</strong> es porque algo bien hace. Y es la verdad.
Es un editor increible.</p>

<p>Si ya leiste hasta aquí, lamento decepcionarte que esto no es un tutorial de Vim. Hay muchos por <a href="https://www.google.com/search?q=vim+tutorial">ahi</a>
que podrías revisar. Pero creo que lo importante de aprender ha usar una herramienta, como lo es Vim, es que lo hagas por pasos.
Que lo hagas a tu forma y ya tu ritmo. Podrías obligarte a usar Vim desintalando cualquier otro editor como lo hice yo.
Podrías ir acostumbrandote a sus comandos usando plugins para tu editor favorito, de tal forma que éste emule Vim.
El chiste es que es algo muy personal, pero que sin duda te recomiendo hacer alguna vez.</p>

<h3 id="valió-la-pena">¿Valió la pena?</h3>

<p>Lo valió cada maldito segundo. Por su puesto que me sentí como un completo simio al principio. Habián veces que no sabía que estaba haciendo.
Y en las que al presionar alguna tecla algo cambiaba en el archivo y solo presionaba ESC como loco para ver si no la había cagado en algo.
Me tomó meses de aprender, de crear memoria muscular con los comandos, de interpretar la pantalla y de crear una
<a href="https://github.com/arandilopez/vimfiles">configuración para mi</a>.</p>

<p>Creo que realmente ha mejorado la forma en la que escribo código. Ha cambiado la forma en la que veo y percibo un entorno de desarrollo.</p>

<p>Éste no será el último post sobre Vim. Espero hablar un poco de las configuraciones que tengo. Sin embargo tengo otro contenido que quiero
escribir, pero ahora sobre Emacs.</p>]]></content><author><name></name></author><category term="vim" /><summary type="html"><![CDATA[En los últimos 2 años he usado Vim como mi editor por default en todas las computadoras que tengo (aunque desde el 2020 Emacs se ha vuelto mi elección preferida, pero haré un post para hablar luego de eso). Y todo empezó porque no quiero usar VS Code.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://arandilopez.me/assets/images/og/posts/mi-camino-en-vim.png" /><media:content medium="image" url="https://arandilopez.me/assets/images/og/posts/mi-camino-en-vim.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Install Tensorflow GPU 1.13 on Pop!_OS 18.04</title><link href="https://arandilopez.me/2019/04/09/install-tensorflow-gpu-1-13-on-pop-os-18-04.html" rel="alternate" type="text/html" title="Install Tensorflow GPU 1.13 on Pop!_OS 18.04" /><published>2019-04-09T00:00:00+00:00</published><updated>2019-04-09T00:00:00+00:00</updated><id>https://arandilopez.me/2019/04/09/install-tensorflow-gpu-1-13-on-pop-os-18-04</id><content type="html" xml:base="https://arandilopez.me/2019/04/09/install-tensorflow-gpu-1-13-on-pop-os-18-04.html"><![CDATA[<p>Pop!_OS is an incredible Linux distribution based on Ubuntu. But what’s more amazing about Pop!_OS is its support for the Nvidia and Tensorflow graphics cards, which is a great feature to opt for Pop!_OS as a distro. In this post I will show the steps to get Tensorflow GPU 1.13 with python3.</p>

<!-- more -->

<p>Pop!_OS 18.04 supports the graphics card with <a href="https://system76.com/pop">an ISO image</a> that has the most current proprietary drivers that System76 maintains. The most current driver is version 418, you can check the version installed on your OS with the command <code class="language-plaintext highlighter-rouge">nvidia-smi</code>.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>+--------------------------------------------------------------------------+
| NVIDIA-SMI 418.56       Driver Version: 418.56                           |
|-------------------------------+----------------------+-------------------+
</code></pre></div></div>

<p>Pop!_OS has a <a href="https://support.system76.com/articles/install-tensorflow/">metapackage</a> to natively install Tensorflow and CUDA, however this metapackage installs up to version 1.9 of Tensorflow.</p>

<h2 id="configure-python-3">Configure Python 3</h2>

<p>First we install the necessary packages to start our development with python3, we will install pip and venv to manage the dependencies of our python projects.</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">sudo </span>apt <span class="nb">install </span>python3-dev python3-pip python3-venv
</code></pre></div></div>

<h2 id="install-cuda-100">Install CUDA 10.0</h2>

<p>To install version 1.13, the last stable of Tensorflow, we need to have the Nvidia 418 drivers and install the following dependencies for CUDA and Tensorflow.</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">sudo </span>apt <span class="nb">install </span>autogen autogen-doc cmake cmake-data<span class="se">\</span>
    guile-2.0-libs libblas3 libgc1c2 libgfortran4 libjsoncpp1<span class="se">\</span>
    liblapack3 libnvidia-compute-418 libopts25 libopts25-dev<span class="se">\</span>
    librhash0 libuv1 python3-numpy
</code></pre></div></div>

<p>Now install the necessary CUDA packages.</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">sudo </span>apt <span class="nb">install </span>system76-cuda-10.0 system76-cudnn-10.0
</code></pre></div></div>

<blockquote>
  <p>There are CUDA packages with version 10.1, however they do not have support for Tensorflow 1.13. <a href="Https://github.com/tensorflow/tensorflow/issues/26209#issuecomment-479127128">See here.</a></p>
</blockquote>

<h2 id="install-tensorflow-gpu">Install Tensorflow GPU</h2>

<p>We will create a new folder for our project</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">mkdir</span> ~/test_tensorflow_gpu
<span class="nv">$ </span><span class="nb">cd </span>test_tensorflow_gpu
</code></pre></div></div>

<p>We started a new virtual environment for our dependencies to this project.</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>python3 <span class="nt">-m</span> venv venv
<span class="nv">$ </span><span class="nb">source </span>venv/bin/activate
</code></pre></div></div>

<blockquote>
  <p>Having activated the virtual environment already allows us to run both <code class="language-plaintext highlighter-rouge">python3</code> and <code class="language-plaintext highlighter-rouge">pip3</code> as <code class="language-plaintext highlighter-rouge">python</code> and <code class="language-plaintext highlighter-rouge">pip</code> respectively.</p>
</blockquote>

<p>Install Tensorflow. The correct package to install Tensorflow with CUDA support is <code class="language-plaintext highlighter-rouge">tensorflow-gpu</code>.</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">(</span>venv<span class="o">)</span>
<span class="nv">$ </span>pip <span class="nb">install</span> <span class="nt">--upgrade</span> tensorflow-gpu
</code></pre></div></div>

<p>Test the installation of Tensorflow.</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">(</span>venv<span class="o">)</span>
<span class="nv">$ </span>python <span class="nt">-c</span> <span class="s2">"import tensorflow as tf; print(tf.test.is_built_with_cuda());"</span>
True <span class="c"># &lt;-- expected output</span>
</code></pre></div></div>]]></content><author><name></name></author><category term="tensorflow" /><category term="nvidia" /><category term="cuda" /><category term="ubuntu" /><category term="pop_os" /><category term="python" /><summary type="html"><![CDATA[Pop!_OS is an incredible Linux distribution based on Ubuntu. But what’s more amazing about Pop!_OS is its support for the Nvidia and Tensorflow graphics cards, which is a great feature to opt for Pop!_OS as a distro. In this post I will show the steps to get Tensorflow GPU 1.13 with python3.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://arandilopez.me/assets/images/og/posts/install-tensorflow-gpu-1-13-on-pop-os-18-04.png" /><media:content medium="image" url="https://arandilopez.me/assets/images/og/posts/install-tensorflow-gpu-1-13-on-pop-os-18-04.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Instalar Tensorflow GPU 1.13 en Pop!_OS 18.04</title><link href="https://arandilopez.me/2019/04/05/instalar-tensorflow-gpu-1-13-en-pop-os-18-04.html" rel="alternate" type="text/html" title="Instalar Tensorflow GPU 1.13 en Pop!_OS 18.04" /><published>2019-04-05T00:00:00+00:00</published><updated>2019-04-05T00:00:00+00:00</updated><id>https://arandilopez.me/2019/04/05/instalar-tensorflow-gpu-1-13-en-pop-os-18-04</id><content type="html" xml:base="https://arandilopez.me/2019/04/05/instalar-tensorflow-gpu-1-13-en-pop-os-18-04.html"><![CDATA[<p>Pop!_OS es una increíble distribución de Linux con base en Ubuntu. Pero lo que es más increíble de Pop!_OS es su soporte para las tarjetas gráficas Nvidia y Tensorflow, que es un gran feature para optar por Pop!_OS como distro. En este post mostraré los pasos para conseguir Tensorflow GPU 1.13 con python3.</p>

<!-- more -->

<p>Pop!_OS 18.04 da soporte a la tarjeta gráfica con una <a href="https://system76.com/pop">imagen de ISO</a> que tiene los drivers privativos más actualizados que System76 da mantenimiento. El driver más actual es la version 418,puedes revisar la versión instalada en tu OS con el comando <code class="language-plaintext highlighter-rouge">nvidia-smi</code>.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>+-------------------------------------------------------------------------+
| NVIDIA-SMI 418.56       Driver Version: 418.56                          |
|-------------------------------+----------------------+------------------+
</code></pre></div></div>

<p>Pop!_OS tiene un <a href="https://support.system76.com/articles/install-tensorflow/">metapaquete</a> para instalar nativamente Tensorflow y su soporte a CUDA, sin embargo este metapaquete instala hasta la versión 1.9 de Tensorflow.</p>

<h2 id="configurar-python-3">Configurar Python 3</h2>

<p>Primero instalamos los paquetes necesarios para iniciar nuestro desarrollo con python3, instalaremos pip y venv para manejar las dependencias de nuestros proyectos python.</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">sudo </span>apt <span class="nb">install </span>python3-dev python3-pip python3-venv
</code></pre></div></div>

<h2 id="instalar-cuda-100">Instalar CUDA 10.0</h2>

<p>Para instalar la versión 1.13, la última estable de Tensorflow, Necesitamos tener los drivers Nvidia 418 e instalar las siguientes dependecias para CUDA y Tensorflow.</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">sudo </span>apt <span class="nb">install </span>autogen autogen-doc cmake cmake-data<span class="se">\</span>
    guile-2.0-libs libblas3 libgc1c2 libgfortran4 libjsoncpp1<span class="se">\</span>
    liblapack3 libnvidia-compute-418 libopts25 libopts25-dev<span class="se">\</span>
    librhash0 libuv1 python3-numpy
</code></pre></div></div>

<p>Ahora instalar los paquetes necesarios de Nvidia CUDA.</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">sudo </span>apt <span class="nb">install </span>system76-cuda-10.0 system76-cudnn-10.0
</code></pre></div></div>

<blockquote>
  <p>Existen paquetes de CUDA con versión 10.1, sin embargo no tienen soporte para Tensorflow 1.13. <a href="https://github.com/tensorflow/tensorflow/issues/26209#issuecomment-479127128">Ver aquí.</a></p>
</blockquote>

<h2 id="instalar-tensorflow-gpu">Instalar Tensorflow GPU</h2>

<p>Creamos una nueva carpeta para nuestro proyecto</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">mkdir</span> ~/test_tensorflow_gpu
<span class="nv">$ </span><span class="nb">cd </span>test_tensorflow_gpu
</code></pre></div></div>

<p>Iniciamos un nuevo virtual environment para nuestras dependencias a este proyecto.</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>python3 <span class="nt">-m</span> venv venv
<span class="nv">$ </span><span class="nb">source </span>venv/bin/activate
</code></pre></div></div>

<blockquote>
  <p>Al tener activado el virtual environment ya nos permite ejecutar tanto <code class="language-plaintext highlighter-rouge">python3</code> como <code class="language-plaintext highlighter-rouge">pip3</code> como <code class="language-plaintext highlighter-rouge">python</code> y <code class="language-plaintext highlighter-rouge">pip</code> respectivamente.</p>
</blockquote>

<p>Instala Tensorflow. El paquete correcto para instalar Tensorflow con soporte a CUDA es <code class="language-plaintext highlighter-rouge">tensorflow-gpu</code>.</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">(</span>venv<span class="o">)</span>
<span class="nv">$ </span>pip <span class="nb">install</span> <span class="nt">--upgrade</span> tensorflow-gpu
</code></pre></div></div>

<p>Prueba la instalación de Tensorflow.</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">(</span>venv<span class="o">)</span>
<span class="nv">$ </span>python <span class="nt">-c</span> <span class="s2">"import tensorflow as tf; print(tf.test.is_built_with_cuda());"</span>
True <span class="c"># &lt;-- salida esperada</span>
</code></pre></div></div>]]></content><author><name></name></author><category term="tensorflow" /><category term="nvidia" /><category term="cuda" /><category term="ubuntu" /><category term="pop_os" /><category term="python" /><summary type="html"><![CDATA[Pop!_OS es una increíble distribución de Linux con base en Ubuntu. Pero lo que es más increíble de Pop!_OS es su soporte para las tarjetas gráficas Nvidia y Tensorflow, que es un gran feature para optar por Pop!_OS como distro. En este post mostraré los pasos para conseguir Tensorflow GPU 1.13 con python3.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://arandilopez.me/assets/images/og/posts/instalar-tensorflow-gpu-1-13-en-pop-os-18-04.png" /><media:content medium="image" url="https://arandilopez.me/assets/images/og/posts/instalar-tensorflow-gpu-1-13-en-pop-os-18-04.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry xml:lang="en"><title type="html">Efficent webhook handling with Ruby on Rails</title><link href="https://arandilopez.me/2018/06/03/efficient-webhook-handling-with-ruby-on-rails.html" rel="alternate" type="text/html" title="Efficent webhook handling with Ruby on Rails" /><published>2018-06-03T00:00:00+00:00</published><updated>2018-06-03T00:00:00+00:00</updated><id>https://arandilopez.me/2018/06/03/efficient-webhook-handling-with-ruby-on-rails</id><content type="html" xml:base="https://arandilopez.me/2018/06/03/efficient-webhook-handling-with-ruby-on-rails.html"><![CDATA[<p>When implementing a payment system as Paypal or Stripe, the more tedious part is implementing webhooks. In this post I wil explain you a how you can optimize the webhook and get a clean and light implementation.</p>

<!-- more -->

<h2 id="what-are-the-webhooks-for">What are the Webhooks for?</h2>

<p><a href="https://en.wikipedia.org/wiki/Webhook">Webhooks</a> are endpoints in your application where a service like Paypal or Stripe will inform you about the events happening in them. Paypal and Stripe use them to notify whether a charge was successful or not, for example. This mechanism is necesary given the charges proccess are asyncronous. Ergo, in the moment we create a charge using thier API we won’t know until in the future if the charge was successful or not.</p>

<h2 id="standard-way">Standard way</h2>

<p>The webhook is just an endpoint (route or path) which receives information through <code class="language-plaintext highlighter-rouge">POST</code> method. In Rails a barebone implementation of Stripe webhook could be:</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># controllers/stripe_controller.rb</span>
<span class="c1"># Method responsbile for handling stripe webhooks</span>
<span class="k">def</span> <span class="nf">webhook</span>
  <span class="k">begin</span>
    <span class="n">event</span> <span class="o">=</span> <span class="no">JSON</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="n">request</span><span class="p">.</span><span class="nf">body</span><span class="p">.</span><span class="nf">read</span><span class="p">)</span>
    <span class="k">case</span> <span class="n">event</span><span class="p">[</span><span class="s1">'type'</span><span class="p">]</span>
      <span class="k">when</span> <span class="s1">'invoice.payment_succeeded'</span>
        <span class="c1"># handle success invoice event</span>
      <span class="k">when</span> <span class="s1">'invoice.payment_failed'</span>
        <span class="c1"># handle failure invoice event</span>
      <span class="c1"># More events...</span>
    <span class="k">end</span>
  <span class="k">rescue</span> <span class="no">Exception</span> <span class="o">=&gt;</span> <span class="n">ex</span>
    <span class="n">render</span> <span class="ss">:json</span> <span class="o">=&gt;</span> <span class="p">{</span><span class="ss">:status</span> <span class="o">=&gt;</span> <span class="mi">400</span><span class="p">,</span> <span class="ss">:error</span> <span class="o">=&gt;</span> <span class="s2">"Webhook failed"</span><span class="p">}</span> <span class="ow">and</span> <span class="k">return</span>
  <span class="k">end</span>
  <span class="n">render</span> <span class="ss">:json</span> <span class="o">=&gt;</span> <span class="p">{</span><span class="ss">:status</span> <span class="o">=&gt;</span> <span class="mi">200</span><span class="p">}</span>
<span class="k">end</span>
</code></pre></div></div>

<p>This <code class="language-plaintext highlighter-rouge">webhook</code> method uses a <code class="language-plaintext highlighter-rouge">switch-case</code> in order to determinate, with the event type that was received, which code block execute. Now imagine implement more events, this method will smell and get hard to maintain.</p>

<p>Sure we can write more methods to put the logic of each event and call them into the <code class="language-plaintext highlighter-rouge">switch-case</code>, however we can still improve this webhook method.</p>

<h2 id="cleaner-way">Cleaner way</h2>

<p>The cleanest webhook implementation I’ve seen is in <a href="https://laravel.com/docs/5.6/billing#defining-webhook-event-handlers">Laravel Cashier</a>. Cashier’s Webhook Controller allows you to handle automaticaly by mapping each event to a method. The controller convention says that if you want to handle the event <code class="language-plaintext highlighter-rouge">invoice.payment_succeeded</code> create a method in the controller whose name start with <code class="language-plaintext highlighter-rouge">handle</code> and the <code class="language-plaintext highlighter-rouge">camelCase</code> form of the event type: <code class="language-plaintext highlighter-rouge">handleInvoicePaymentSucceeded</code>.</p>

<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">&lt;?php</span>

<span class="kn">namespace</span> <span class="nn">App\Http\Controllers</span><span class="p">;</span>

<span class="kn">use</span> <span class="nc">Laravel\Cashier\Http\Controllers\WebhookController</span> <span class="k">as</span> <span class="nc">CashierController</span><span class="p">;</span>

<span class="kd">class</span> <span class="nc">WebhookController</span> <span class="kd">extends</span> <span class="nc">CashierController</span>
<span class="p">{</span>
    <span class="cd">/**
     * Handle a Stripe webhook.
     *
     * @param  array  $payload
     * @return Response
     */</span>
    <span class="k">public</span> <span class="k">function</span> <span class="n">handleInvoicePaymentSucceeded</span><span class="p">(</span><span class="nv">$payload</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="c1">// Handle The Event</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="refactoring">Refactoring</h2>

<p>How can we achieve this with Rails?. Easy, thanks to metaprogramming in Ruby. Refactoring the previous method could get like this:</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">webhook</span>
  <span class="k">begin</span>
    <span class="n">event</span> <span class="o">=</span> <span class="no">JSON</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="n">request</span><span class="p">.</span><span class="nf">body</span><span class="p">.</span><span class="nf">read</span><span class="p">)</span>
    <span class="nb">method</span> <span class="o">=</span> <span class="s2">"handle_"</span> <span class="o">+</span> <span class="n">event</span><span class="p">[</span><span class="s1">'type'</span><span class="p">].</span><span class="nf">tr</span><span class="p">(</span><span class="s1">'.'</span><span class="p">,</span> <span class="s1">'_'</span><span class="p">)</span>
    <span class="nb">self</span><span class="p">.</span><span class="nf">send</span> <span class="nb">method</span><span class="p">,</span> <span class="n">event</span>
  <span class="k">rescue</span> <span class="no">JSON</span><span class="o">::</span><span class="no">ParserError</span> <span class="o">=&gt;</span> <span class="n">e</span>
    <span class="n">render</span> <span class="ss">json: </span><span class="p">{</span><span class="ss">:status</span> <span class="o">=&gt;</span> <span class="mi">400</span><span class="p">,</span> <span class="ss">:error</span> <span class="o">=&gt;</span> <span class="s2">"Invalid payload"</span><span class="p">}</span> <span class="ow">and</span> <span class="k">return</span>
  <span class="k">rescue</span> <span class="no">NoMethodError</span> <span class="o">=&gt;</span> <span class="n">e</span>
    <span class="c1"># missing event handler</span>
  <span class="k">end</span>
  <span class="n">render</span> <span class="ss">json: </span><span class="p">{</span><span class="ss">:status</span> <span class="o">=&gt;</span> <span class="mi">200</span><span class="p">}</span>
<span class="k">end</span>

<span class="k">def</span> <span class="nf">handle_invoice_payment_succeeded</span><span class="p">(</span><span class="n">event</span><span class="p">)</span>
  <span class="c1">#handle the event</span>
<span class="k">end</span>

<span class="k">def</span> <span class="nf">handle_invoice_payment_failed</span><span class="p">(</span><span class="n">event</span><span class="p">)</span>
  <span class="c1">#handle the event</span>
<span class="k">end</span>

<span class="c1"># and more...</span>
</code></pre></div></div>

<p>In the webhook method we need to parse the event type to the name of the method which will handle that event.</p>

<ol>
  <li>Translate all <code class="language-plaintext highlighter-rouge">'.'</code> to <code class="language-plaintext highlighter-rouge">'_'</code> and concatenate with <code class="language-plaintext highlighter-rouge">"handle_"</code>.</li>
  <li>Then send the message to call the resultant method with the payload obtained.</li>
  <li>Otherwise, if there’s no method implemented for that event, it will be rescued where we can make another action.</li>
</ol>

<p>Laravel Cashier implementation looks wonderful to handle webhook events. Therefore this way you get a clean and maintainable controller.</p>

<h3 id="protip">ProTip</h3>

<p>As a next step you could write <a href="http://api.rubyonrails.org/classes/ActiveSupport/Concern.html">concerns</a> to group event handlers. Now you have a lighter controller.</p>

<p><em>Do you want to read this article in spanish? Check it <a href="/spanish/2018/06/03/manejo-eficiente-de-webhooks-con-ruby-on-rails.html">here</a></em></p>]]></content><author><name></name></author><category term="webhooks" /><category term="ruby" /><category term="rails" /><category term="ror" /><summary type="html"><![CDATA[When implementing a payment system as Paypal or Stripe, the more tedious part is implementing webhooks. In this post I wil explain you a how you can optimize the webhook and get a clean and light implementation.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://arandilopez.me/assets/images/og/posts/efficient-webhook-handling-with-ruby-on-rails.png" /><media:content medium="image" url="https://arandilopez.me/assets/images/og/posts/efficient-webhook-handling-with-ruby-on-rails.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry xml:lang="es"><title type="html">Manejo eficiente de Webhooks con Ruby on Rails</title><link href="https://arandilopez.me/spanish/2018/06/03/manejo-eficiente-de-webhooks-con-ruby-on-rails.html" rel="alternate" type="text/html" title="Manejo eficiente de Webhooks con Ruby on Rails" /><published>2018-06-03T00:00:00+00:00</published><updated>2018-06-03T00:00:00+00:00</updated><id>https://arandilopez.me/spanish/2018/06/03/manejo-eficiente-de-webhooks-con-ruby-on-rails</id><content type="html" xml:base="https://arandilopez.me/spanish/2018/06/03/manejo-eficiente-de-webhooks-con-ruby-on-rails.html"><![CDATA[<p>Cuando implementas sistemas de pagos como Paypal o Stripe, la parte más tediosa de implementar son los webhooks. En este post te explicaré de que forma puedes optimizar la implementación de tu webhook de forma limpia y ordenada.</p>

<!-- more -->

<h2 id="para-qué-sirven-los-webhooks">¿Para qué sirven los Webhooks?</h2>

<p>Los <a href="https://en.wikipedia.org/wiki/Webhook">webhooks</a> son endpoints en tu aplicación por el cuál un servicio como Paypal o Stripe te informarán sobre los eventos que ocurran dentro. Paypal y Stripe los usan para avisar sobre si un cargo fue exitoso o falló por ejemplo. Este mecanismo es necesario ya que los procesos de cargos, creación o cancelación de pagos se hacen de forma asincróna de nuestra aplicación. Es decir, en el momento que creamos un cargo a traves de la API no sabremos hasta en un futuro si el cargo fue exitoso o no.</p>

<h2 id="implementación-estandard">Implementación estandard</h2>

<p>El webhook es un solo endpoint (ruta) que recibe información mediante el método <code class="language-plaintext highlighter-rouge">POST</code> de HTTP. En ruby on rails una implementación de Webhook de Stripe podría ser la siguiente:</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># controllers/stripe_controller.rb</span>
<span class="c1"># Method responsbile for handling stripe webhooks</span>
<span class="k">def</span> <span class="nf">webhook</span>
  <span class="k">begin</span>
    <span class="n">event</span> <span class="o">=</span> <span class="no">JSON</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="n">request</span><span class="p">.</span><span class="nf">body</span><span class="p">.</span><span class="nf">read</span><span class="p">)</span>
    <span class="k">case</span> <span class="n">event</span><span class="p">[</span><span class="s1">'type'</span><span class="p">]</span>
      <span class="k">when</span> <span class="s1">'invoice.payment_succeeded'</span>
        <span class="c1"># handle success invoice event</span>
      <span class="k">when</span> <span class="s1">'invoice.payment_failed'</span>
        <span class="c1"># handle failure invoice event</span>
      <span class="c1"># More events...</span>
    <span class="k">end</span>
  <span class="k">rescue</span> <span class="no">Exception</span> <span class="o">=&gt;</span> <span class="n">ex</span>
    <span class="n">render</span> <span class="ss">:json</span> <span class="o">=&gt;</span> <span class="p">{</span><span class="ss">:status</span> <span class="o">=&gt;</span> <span class="mi">400</span><span class="p">,</span> <span class="ss">:error</span> <span class="o">=&gt;</span> <span class="s2">"Webhook failed"</span><span class="p">}</span> <span class="ow">and</span> <span class="k">return</span>
  <span class="k">end</span>
  <span class="n">render</span> <span class="ss">:json</span> <span class="o">=&gt;</span> <span class="p">{</span><span class="ss">:status</span> <span class="o">=&gt;</span> <span class="mi">200</span><span class="p">}</span>
<span class="k">end</span>
</code></pre></div></div>

<p>Este método <code class="language-plaintext highlighter-rouge">webhook</code> hace uso de un <code class="language-plaintext highlighter-rouge">switch-case</code> para poder determinar, a través del tipo de evento que se ha recibido, cuál será el bloque de código a ejecutar. Ahora imagina implementar más eventos, este método se volvería muy sucio y difícil de mantener.</p>

<p>Claro que podemos hacer más métodos para colocar la lógica de cada evento e invocar estos métodos dentro del <code class="language-plaintext highlighter-rouge">switch-case</code>, sin embargo aún podemos mejorar el método de webhook.</p>

<h2 id="implementación-limpia">Implementación Limpia</h2>

<p>La implementación más limpia que he visto sobre este tema de webhooks es de <a href="https://laravel.com/docs/5.6/billing#defining-webhook-event-handlers">Cashier de Laravel</a>. El controlador de webhook de Cashier permite manejar los eventos del webhook de forma automática mapeando cada evento a un método. La convención del controlador dice que si quieres manejar el evento <code class="language-plaintext highlighter-rouge">invoice.payment_succeeded</code>, entonces crea un método en el controlador cuyo nombre empiece por <code class="language-plaintext highlighter-rouge">handle</code> y la forma en <code class="language-plaintext highlighter-rouge">camelCase</code> del tipo de evento: <code class="language-plaintext highlighter-rouge">handleInvoicePaymentSucceeded</code>.</p>

<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">&lt;?php</span>

<span class="kn">namespace</span> <span class="nn">App\Http\Controllers</span><span class="p">;</span>

<span class="kn">use</span> <span class="nc">Laravel\Cashier\Http\Controllers\WebhookController</span> <span class="k">as</span> <span class="nc">CashierController</span><span class="p">;</span>

<span class="kd">class</span> <span class="nc">WebhookController</span> <span class="kd">extends</span> <span class="nc">CashierController</span>
<span class="p">{</span>
    <span class="cd">/**
     * Handle a Stripe webhook.
     *
     * @param  array  $payload
     * @return Response
     */</span>
    <span class="k">public</span> <span class="k">function</span> <span class="n">handleInvoicePaymentSucceeded</span><span class="p">(</span><span class="nv">$payload</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="c1">// Handle The Event</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="refactoring">Refactoring</h2>

<p>¿Cómo podríamos conseguir lo mismo con Rails?. Es sencillo gracias a la metaprogramación en Ruby. La implementación refactorizando el método anterior sería más o menos así:</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">webhook</span>
  <span class="k">begin</span>
    <span class="n">event</span> <span class="o">=</span> <span class="no">JSON</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="n">request</span><span class="p">.</span><span class="nf">body</span><span class="p">.</span><span class="nf">read</span><span class="p">)</span>
    <span class="nb">method</span> <span class="o">=</span> <span class="s2">"handle_"</span> <span class="o">+</span> <span class="n">event</span><span class="p">[</span><span class="s1">'type'</span><span class="p">].</span><span class="nf">tr</span><span class="p">(</span><span class="s1">'.'</span><span class="p">,</span> <span class="s1">'_'</span><span class="p">)</span>
    <span class="nb">self</span><span class="p">.</span><span class="nf">send</span> <span class="nb">method</span><span class="p">,</span> <span class="n">event</span>
  <span class="k">rescue</span> <span class="no">JSON</span><span class="o">::</span><span class="no">ParserError</span> <span class="o">=&gt;</span> <span class="n">e</span>
    <span class="n">render</span> <span class="ss">json: </span><span class="p">{</span><span class="ss">:status</span> <span class="o">=&gt;</span> <span class="mi">400</span><span class="p">,</span> <span class="ss">:error</span> <span class="o">=&gt;</span> <span class="s2">"Invalid payload"</span><span class="p">}</span> <span class="ow">and</span> <span class="k">return</span>
  <span class="k">rescue</span> <span class="no">NoMethodError</span> <span class="o">=&gt;</span> <span class="n">e</span>
    <span class="c1"># missing event handler</span>
  <span class="k">end</span>
  <span class="n">render</span> <span class="ss">json: </span><span class="p">{</span><span class="ss">:status</span> <span class="o">=&gt;</span> <span class="mi">200</span><span class="p">}</span>
<span class="k">end</span>

<span class="k">def</span> <span class="nf">handle_invoice_payment_succeeded</span><span class="p">(</span><span class="n">event</span><span class="p">)</span>
  <span class="c1">#handle the event</span>
<span class="k">end</span>

<span class="k">def</span> <span class="nf">handle_invoice_payment_failed</span><span class="p">(</span><span class="n">event</span><span class="p">)</span>
  <span class="c1">#handle the event</span>
<span class="k">end</span>

<span class="c1"># and more...</span>
</code></pre></div></div>

<p>En el método del webhook debemos convertir el tipo de evento al nombre del método que se encargara de manejar ese evento específicamente.</p>

<ol>
  <li>Convertimos los <code class="language-plaintext highlighter-rouge">'.'</code> por <code class="language-plaintext highlighter-rouge">'_'</code> y concatenamos con <code class="language-plaintext highlighter-rouge">"handle_"</code>.</li>
  <li>Lo siguiente es pasar el mensaje de invocar el método resultante con el payload obtenido.</li>
  <li>Posteriormente, si no existe un método implementado para un evento entraría en el <code class="language-plaintext highlighter-rouge">rescue NoMethodError =&gt; e</code> donde podemos realizar otra acción en su caso.</li>
</ol>

<p>La implementación de Laravel Cashier para manejar los eventos de Stripe me pareció increible. Por lo tanto de esta forma obtendrás un controlador mantenible y limpio.</p>

<h3 id="protip">ProTip:</h3>

<p>Como siguiente paso podrías implementar <a href="http://api.rubyonrails.org/classes/ActiveSupport/Concern.html">concerns</a> para agrupar en modulos los eventos que necesites manejar. Así mantienes el controlador del webhook ligero.</p>]]></content><author><name></name></author><category term="spanish" /><category term="webhooks" /><category term="ruby" /><category term="rails" /><category term="ror" /><summary type="html"><![CDATA[Cuando implementas sistemas de pagos como Paypal o Stripe, la parte más tediosa de implementar son los webhooks. En este post te explicaré de que forma puedes optimizar la implementación de tu webhook de forma limpia y ordenada.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://arandilopez.me/assets/images/og/posts/manejo-eficiente-de-webhooks-con-ruby-on-rails.png" /><media:content medium="image" url="https://arandilopez.me/assets/images/og/posts/manejo-eficiente-de-webhooks-con-ruby-on-rails.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">What kind of programmer are you?</title><link href="https://arandilopez.me/2018/01/30/what-kind-of-programmer-are-you.html" rel="alternate" type="text/html" title="What kind of programmer are you?" /><published>2018-01-30T00:00:00+00:00</published><updated>2018-01-30T00:00:00+00:00</updated><id>https://arandilopez.me/2018/01/30/what-kind-of-programmer-are-you</id><content type="html" xml:base="https://arandilopez.me/2018/01/30/what-kind-of-programmer-are-you.html"><![CDATA[<p>A year ago I read an <a href="https://sg.com.mx/revista/53/el-caso-php-y-los-salarios#.WKdlaxIrKRv">article</a> published in the software magazine Software Gurú by <a href="https://sg.com.mx/sgvirtual/speakers/basilio-brice-o">Basilio Briseño</a> where He stated that PHP is one of the least paid languages ​​in Mexico and why. In his article he also published a salary range based for every type of programmer made by the programming group “PHP Mexico”.</p>

<!-- more -->

<p>Although the salary range skill classification is aimed towards PHP programmer, the type of programmer classification
can be applied outside php programming. The following list suggests what a Rookie, Junior, Senior and Master developer skills are expected to get into that classification.</p>

<p>The salary range could be generalized as follows:</p>

<p><strong>Rookie</strong> usually writes code without consistency in indentation or conventions and usually copies and pastes anything found on the internet that seems to do what it was asked to deliver and that, according to the forums, is the solution.</p>

<p><strong>Apprentice</strong> has accepted that he/she needs to improve and decides to learn the best practices and apply them. He/She consults forums not for searching code that he/she could simply copy/paste, instead he/she analyzes it and asks her/his issues after having investigated by his/her own.</p>

<p><strong>Junior</strong> knows what design patterns are and applies them, understands the basics of what MVC, REST, CRUD, ORM, SQL Injection, XSS, I / O Sanitization, etc. are, applies this knowdledge alogside other techniques; He/she is an excellent learner and uses third-party APIs.</p>

<p><strong>Senior</strong> is capable of developing his/her own tools and APIs, is aware of how his/her domain language works internally, is highly interested in the performance of applications beyond its ease of development, he/she is not satisfied with the functioning and design of all third-party tools and APIs as they come, and always seeks to innovate and create things that improve performance, facilitate their work and the speed of their deliverables; contributes with code in different projects and communities.</p>

<p><strong>Master</strong> is capable of developing his own extensions, gems or libraries, and optimizing them for his/her best use; contributes to Open Source projects and he/she is normally dedicated to improving the performance of high performance applications. Knows the domain language its origin and differences with others languages.</p>

<p><strong>Core</strong> contributes to the development of the core of the domain language; contributes in some extensions, gems or libraries and public tools; He/she is a frequent speaker at international conferences and it is usual to see his/her name in the credits of changes of version of his domain language.</p>

<p>So … What kind of programmer are you?</p>

<blockquote>
  <p>Many developers are called Senior after years of work, or experts in some framework or language when they only do tutorials; however, your work and contribution and how it affects other’s people work (not only in your workplace) tells vastly what type of programmer you are.</p>
</blockquote>

<p><em>Do you want to read this article in spanish? Check it <a href="/spanish/2017/02/19/que-tipo-de-programador-eres.html">here</a></em></p>]]></content><author><name></name></author><category term="php" /><category term="senior" /><category term="developer" /><category term="programmer" /><category term="featured" /><summary type="html"><![CDATA[A year ago I read an article published in the software magazine Software Gurú by Basilio Briseño where He stated that PHP is one of the least paid languages ​​in Mexico and why. In his article he also published a salary range based for every type of programmer made by the programming group “PHP Mexico”.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://arandilopez.me/assets/images/og/posts/what-kind-of-programmer-are-you.png" /><media:content medium="image" url="https://arandilopez.me/assets/images/og/posts/what-kind-of-programmer-are-you.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry xml:lang="es"><title type="html">Tips en C - Parte 1</title><link href="https://arandilopez.me/spanish/2018/01/29/tips-en-c-parte-1.html" rel="alternate" type="text/html" title="Tips en C - Parte 1" /><published>2018-01-29T00:00:00+00:00</published><updated>2018-01-29T00:00:00+00:00</updated><id>https://arandilopez.me/spanish/2018/01/29/tips-en-c-parte-1</id><content type="html" xml:base="https://arandilopez.me/spanish/2018/01/29/tips-en-c-parte-1.html"><![CDATA[<p>C es un lenguaje de programación que es considerado como el lenguaje básico a aprender si estudias algún grado en computación. Fue el lenguaje que aprendí en el primer curso de programación de la Universidad, y hoy estoy retomando estos temas en mi maestría.</p>

<!-- more -->

<p>Es por eso que decidí escribir acerca de C, y de cosas que he encontrado de él que no me fueron enseñadas cuando tomé el curso de programación hace años. Espero encuentres útil la siguiente información que comparto.</p>

<hr />

<h2 id="apuntadores">Apuntadores</h2>

<p>La definción más sencilla de mencionar de un apuntador es:</p>

<blockquote>
  <p>Un apuntador es una variable que posee un tipo de dato, él cuál <em>apunta</em> a una dirección de memoria.</p>
</blockquote>

<p>Decir que <em>apunta</em> a una dirección de memoria es que el valor que un apuntador guarda es literalmente el número de la dirección de memoria. La memoria esta direccionada como si fuera un arreglo, desde el 0 hasta, por ejemplo, tus 8GB de memoria RAM.</p>

<p>Tengamos como ejemplo este programa en C.</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include</span> <span class="cpf">&lt;stdio.h&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;stdlib.h&gt;</span><span class="cp">
</span>
<span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">int</span> <span class="n">argc</span><span class="p">,</span> <span class="k">const</span> <span class="kt">char</span> <span class="o">*</span><span class="n">argv</span><span class="p">[])</span>
<span class="p">{</span>
	<span class="kt">int</span> <span class="n">numero</span> <span class="o">=</span> <span class="mi">20</span><span class="p">;</span>
	<span class="kt">int</span> <span class="o">*</span> <span class="n">a_numero</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">numero</span><span class="p">;</span>

	<span class="n">printf</span><span class="p">(</span><span class="s">"El numero es %d</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">numero</span><span class="p">);</span>
	<span class="n">printf</span><span class="p">(</span><span class="s">"El apuntador tiene como memoria %d</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">a_numero</span><span class="p">);</span>
	<span class="n">printf</span><span class="p">(</span><span class="s">"El valor del apuntador es %d</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="o">*</span><span class="n">a_numero</span><span class="p">);</span>

	<span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>En este programa vemos la definición de un apuntador, esta es <code class="language-plaintext highlighter-rouge">int * a_numero = &amp;numero</code>, que significa: definir un apuntador de enteros llamado <code class="language-plaintext highlighter-rouge">a_numero</code>. El valor que se le da al apuntador es <code class="language-plaintext highlighter-rouge">&amp;numero</code>, poner el <code class="language-plaintext highlighter-rouge">&amp;</code> antes del nombre de la variable pide el apuntador (o dirección de memoria) de la variable <code class="language-plaintext highlighter-rouge">numero</code>.</p>

<p>El resultado del código sería lo siguiente</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>El numero es 20
El apuntador tiene como memoria 1598883260
El valor del apuntador es 20
</code></pre></div></div>

<p>Observa el segundo resultado de imprimir el apuntador. Este es el número de memoria en donde se encuentra alojado la variable <code class="language-plaintext highlighter-rouge">numero</code>. El apuntador <code class="language-plaintext highlighter-rouge">a_numero</code> puede obtener su valor al usar el <code class="language-plaintext highlighter-rouge">*</code> al inicio de su nombre, es decir: <code class="language-plaintext highlighter-rouge">*a_numero</code> obtiene el valor de aquella posición de memoria del que <code class="language-plaintext highlighter-rouge">a_numero</code> apunta.</p>

<h3 id="el-apuntador-es-una-referencia">El apuntador es una referencia</h3>

<p>Si, el apuntador <code class="language-plaintext highlighter-rouge">a_numero</code> es una referencia a <code class="language-plaintext highlighter-rouge">numero</code>, asi que cualquier operacion que hagamos sobre <code class="language-plaintext highlighter-rouge">*a_numero</code> será sobre el valor de <code class="language-plaintext highlighter-rouge">numero</code>. La reasignación, por ejemplo, de <code class="language-plaintext highlighter-rouge">*a_numero</code> hará que el valor de <code class="language-plaintext highlighter-rouge">numero</code> también cambie.</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include</span> <span class="cpf">&lt;stdio.h&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;stdlib.h&gt;</span><span class="cp">
</span>
<span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">int</span> <span class="n">argc</span><span class="p">,</span> <span class="k">const</span> <span class="kt">char</span> <span class="o">*</span><span class="n">argv</span><span class="p">[])</span>
<span class="p">{</span>
	<span class="kt">int</span> <span class="n">numero</span> <span class="o">=</span> <span class="mi">20</span><span class="p">;</span>
	<span class="kt">int</span> <span class="o">*</span> <span class="n">a_numero</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">numero</span><span class="p">;</span>

	<span class="n">printf</span><span class="p">(</span><span class="s">"El numero es %d</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">numero</span><span class="p">);</span>
	<span class="n">printf</span><span class="p">(</span><span class="s">"El apuntador tiene como memoria %d</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">a_numero</span><span class="p">);</span>
	<span class="n">printf</span><span class="p">(</span><span class="s">"El valor del apuntador es %d</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="o">*</span><span class="n">a_numero</span><span class="p">);</span>

	<span class="o">*</span><span class="n">a_numero</span> <span class="o">=</span> <span class="mi">40</span><span class="p">;</span>

	<span class="n">printf</span><span class="p">(</span><span class="s">"El numero es %d</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">numero</span><span class="p">);</span>
	<span class="n">printf</span><span class="p">(</span><span class="s">"El valor del apuntador es %d</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="o">*</span><span class="n">a_numero</span><span class="p">);</span>

	<span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>En el ejemplo de código anterior, observa que hacemos una reasignación <code class="language-plaintext highlighter-rouge">*a_numero = 40;</code>.</p>

<p>El resultado de ejecutar el programa es:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>El numero es 20
El apuntador tiene como memoria 1598883260
El valor del apuntador es 20
El numero es 40
El valor del apuntador es 40
</code></pre></div></div>

<p>Entonces al modificar el valor de <code class="language-plaintext highlighter-rouge">*a_numero</code> modificó de igual forma a <code class="language-plaintext highlighter-rouge">numero</code>. Esto es lo que se conoce como <em>referencia</em> en programación. Tener una referencia es tener un apuntador a un valor que ya se encuentra en la memoria y que los cambios que apliquemos sobre ella tomarán efecto como si trabajaramos con la variable real.</p>

<hr />

<p>Espero que encuentres útil ésta información. En la siguiente publicación de esta serie hablaré sobre los <code class="language-plaintext highlighter-rouge">struct</code> en C y cómo hacer nuestros propios tipos de datos complejos para dar a nuestros programas un mejor manejo de la memoria.</p>]]></content><author><name></name></author><category term="spanish" /><category term="c" /><category term="programming" /><category term="software" /><category term="pointers" /><summary type="html"><![CDATA[C es un lenguaje de programación que es considerado como el lenguaje básico a aprender si estudias algún grado en computación. Fue el lenguaje que aprendí en el primer curso de programación de la Universidad, y hoy estoy retomando estos temas en mi maestría.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://arandilopez.me/assets/images/og/posts/tips-en-c-parte-1.png" /><media:content medium="image" url="https://arandilopez.me/assets/images/og/posts/tips-en-c-parte-1.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry xml:lang="es"><title type="html">Cómo hacer que reescriban tu código</title><link href="https://arandilopez.me/spanish/2017/07/05/como-hacer-que-reescriban-tu-codigo.html" rel="alternate" type="text/html" title="Cómo hacer que reescriban tu código" /><published>2017-07-05T00:00:00+00:00</published><updated>2017-07-05T00:00:00+00:00</updated><id>https://arandilopez.me/spanish/2017/07/05/como-hacer-que-reescriban-tu-codigo</id><content type="html" xml:base="https://arandilopez.me/spanish/2017/07/05/como-hacer-que-reescriban-tu-codigo.html"><![CDATA[<p>Esta es una de las grandes habilidades que los “Seniors Developers” hoy en día poseen y aquí listaré aquellos pasos a seguir para que un Ingeniero de Software tome tu código y lo re-escriba por ti.</p>

<!-- more -->

<p><img src="https://static.pexels.com/photos/33535/primate-ape-thinking-mimic.jpg" alt="" /></p>

<h3 id="no-sigas-ninguna-convención">No sigas ninguna convención.</h3>

<p>Eres un Senior Developer, ninguna convención de un grupo gringo de programadores te va a decir como programa. Tu código es hermoso de por sí. ¿Por qué necesitas poner todo bien indentado? No es tu culpa que el IDE de los demás no ponga bien los tabs o spaces.</p>

<h3 id="no-hagas-pruebas-para-tu-código">No hagas pruebas para tu código.</h3>

<p>No las necesitas. Es tu código y es perfecto. Las pruebas sólo son una perdida de tiempo y tu no tienes ese tiempo. Si no te lo enseñaron en el colegio es porque no son importantes. Esos Ingenieros de software son muy desconfiados, tu código no necesita probarse. Además las pruebas no son seguras, no es como que un usuario lo este probando de verdad, ¿no?</p>

<h3 id="no-uses-patrones-de-diseño">No uses patrones de diseño.</h3>

<p>Solo hacen que tu código sea menos entendible. Todos sabemos como funcionan los switches, if’’s y for’s, asi que unos cuantos anidados no le hacen mal a nadie. No tienes porque mejorar eso. Eres un Senior Developer y tu sabes lo que haces.</p>

<h3 id="no-extiendas-librerías-tómalas-y-modifícalas">No extiendas librerías, tómalas y modifícalas.</h3>

<p>Eres un gran Senior Developer, tan grande que podrias tomar jQuery y “mejorar” sus métodos. No tienes porque pluggear nada. Tu puedes tomar la fuente y hacer tus cambios sobre ella. Eres grande.</p>

<h3 id="no-uses-herramientas-automatizadas-ni-minifiques-tus-archivos">No uses herramientas automatizadas, ni minifiques tus archivos.</h3>

<p>Sabemos que hacer las cosas a mano es mejor que automatizar esas tareas repetitivas. Webpack y Gulp son cosas que no necesitas.</p>

<h3 id="no-necesitas-pensar-mucho-en-las-soluciones-de-software">No necesitas pensar mucho en las soluciones de software.</h3>

<p>Eres un gran Senior developer, obsesionate con esa base de datos no relacional y úsala en TODO, no te pongas a investigar cuanto te va a costar, Tú úsala, sabes que “one size fits all”, total ese “relacional” es tan anticuado. Todo lo que tu digas que se debe usar se usará, tanto así que ya eres un Arquitecto de Soluciones.</p>

<hr />

<p>Estos son solo algunos de los pasos a seguir para llegue alguien y tire todo tu código. Un buen Senior Developer debe tener la mente abierta a técnicas que ayuden a incrementar la calidad del producto software en el que trabaja.</p>

<hr />

<p>Las pruebas unitarias y de aceptación son parte de tu mismo código, uno sin el otro no están completos, así que documéntate de los beneficios que tiene seguir metodologías de testing en tus proyectos. así que no ignores a los Juniors developers que tienes ahí. Ellos sólo quieren mejorar el código.</p>]]></content><author><name></name></author><category term="spanish" /><category term="sarcasm" /><summary type="html"><![CDATA[Esta es una de las grandes habilidades que los “Seniors Developers” hoy en día poseen y aquí listaré aquellos pasos a seguir para que un Ingeniero de Software tome tu código y lo re-escriba por ti.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://static.pexels.com/photos/33535/primate-ape-thinking-mimic.jpg" /><media:content medium="image" url="https://static.pexels.com/photos/33535/primate-ape-thinking-mimic.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry></feed>