<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="es">
  <title>Felipe Moyano - Blog en Español</title>
  <subtitle>This site will serve both as a learning experience and as a playground to try out new things and share my thoughts along the way</subtitle>
  <link href="https://gafemoyano.com/es/feed.xml" rel="self" />
  <link href="https://gafemoyano.com/" />
  <updated>2026-01-16T00:00:00Z</updated>
  <id>https://gafemoyano.com/</id>
  <author>
    <name>Felipe Moyano</name>
  </author>
  <entry>
    <title>Día de aprendizajes</title>
    <link href="https://gafemoyano.com/es/posts/dia-de-aprendizajes/" />
    <updated>2026-01-16T00:00:00Z</updated>
    <id>https://gafemoyano.com/es/posts/dia-de-aprendizajes/</id>
    <content type="html">&lt;p&gt;Me desperté en una cama doble. Eso ya era un cambio drástico de las noches en carpa que habíamos pasado las últimas semanas en la Patagonia Chilena. Acabábamos de cruzar la frontera de regreso a Argentina hacía un par de días y aún estábamos acostumbrándonos a los lujos que nos permitía el cambio favorable del dólar. El día anterior había sido largo debido a un pinchazo en una ruta poco transitada y una buena noche de descanso era más que bienvenida.&lt;/p&gt;
&lt;p&gt;Al salir de la casa me encontré a Sergio Andrés trabajando en su moto con su dedicación habitual. Le pregunté si quería algo de desayuno, sabiendo cuál sería la respuesta de antemano. —No, yo estoy bien —contestó—, así que salí a explorar los alrededores del lugar con la esperanza de encontrar algún pan, torta o queso. Sin éxito, volví resignado y, pasando por la entrada, dije: —tocó desayunar mate.&lt;/p&gt;
&lt;p&gt;Una hora después estábamos listos para arrancar, maletas montadas en las motos, cadena lubricada (la de él, yo estaba tomando mate), y ruta planificada. Iríamos en dirección norte con intención de llegar a Bariloche, sin afán, pero con ansias de volver. Sentíamos en el cuerpo el cansancio de los últimos días largos de ruta y necesitábamos descanso, quietud y caras familiares. Sin embargo, Sergio propuso un desvío en Trevelín para recorrer un tramo del parque nacional Los Alerces, hoy sería nuestra última oportunidad de visitarlo.&lt;/p&gt;
 &lt;figure class=&quot;my-8&quot;&gt;
            &lt;img src=&quot;https://gafemoyano.com/assets/img/ey8BHXQMZe-1600.jpeg&quot; alt=&quot;Empezando el día&quot; class=&quot;w-full h-auto rounded-lg shadow-md&quot; loading=&quot;lazy&quot;&gt;
            &lt;figcaption class=&quot;mt-3 text-sm font-sans text-gray-600 italic text-center&quot;&gt;Empezando el día&lt;/figcaption&gt;
          &lt;/figure&gt;
&lt;p&gt;Como una especie de oración al partir, ya que la suerte no nos había acompañado mucho en los últimos días, Sergio nos encomendó al universo para que tuviéramos un día tranquilo.&lt;/p&gt;
&lt;p&gt;El universo tenía otros planes. Veinte minutos después rodábamos con buen clima y un gran paisaje: cielos azules despejados, la cordillera  a nuestra izquierda con sus picos nevados y alguna que otra laguna en el horizonte. Mi apreciación del paisaje fue interrumpida con un ruido y la perdida de tracción de mi llanta de atrás. Se había soltado la cadena. Afortunadamente no se había roto ni enredado. Ligeramente preocupante, en todo caso, pero tampoco inesperado por los terrenos que habíamos atravesado en las últimas semanas. La pusimos nuevamente y quedamos de revisarla cuando llegaramos a Bariloche. —Debería andar con cuidado hasta que la cambie —dijo Sergio.&lt;/p&gt;
&lt;p&gt;Unos 15 minutos después la cadena saltó nuevamente. Ahora sí se había enredado entre el rin y el piñón, causando que uno de los eslabones se doblara casi al punto de romperse. Este sí era momento de preocuparse, ya que Sergio —como me había advertido incontables veces durante el viaje— no tenía la herramienta adecuada para reemplazarlo y yo, si acaso, tenía entre mis herramientas un mate, un destornillador y aceite de oliva.&lt;/p&gt;
&lt;p&gt;Inspeccionamos la gravedad del daño para evaluar nuestras opciones: la moto no podía andar más así. La única esperanza era intentar enderezar el eslabón. Saqué mi navaja de pinzas, que hasta ese día había sido invaluable para cortar quesos y salames, y después de múltiples intentos concluimos que no era la herramienta adecuada para esta tarea, mucho menos en nuestras inexpertas manos uniandinas.&lt;/p&gt;
&lt;p&gt;Afortunadamente, no tardó demasiado en manifestarse la famosa solidaridad argentina hacia los moteros en forma de un par de un camión antiquísimo de transporte que paró a ver si necesitábamos ayuda. Sus dos ocupantes, el copiloto de alrededor 20 años y el piloto de unos 40 venían de dejar una mercancía en la frontera Chilena y estaban disfrutando de una birra dominguera mientras conducían de regreso a casa. Al explicarles la situación, el conductor sacó una pinza gigante de unos 50 cm y con un solo movimiento ajustó el eslabón.&lt;br&gt;
El nuevo plan era andar la moto, lentamente, hasta un lugar que ellos conocían de carga de ganado para poder subir la moto a la altura del camión y así llevarla remolcada hasta el siguiente pueblo donde pudiéramos buscar si un mecánico nos atendía, pues normalmente descansan los domingos.&lt;/p&gt;
&lt;figure class=&quot;my-8&quot;&gt;
        &lt;video controls=&quot;&quot; class=&quot;h-[50vh] w-full rounded-lg shadow-md&quot;&gt;
      &lt;source src=&quot;https://gafemoyano.com/assets/videos/el-camion.mp4&quot; type=&quot;video/mp4&quot;&gt;
    &lt;/video&gt;
        &lt;figcaption class=&quot;mt-3 text-sm font-sans text-gray-600 italic text-center&quot;&gt;El camión gaucho&lt;/figcaption&gt;
      &lt;/figure&gt;
&lt;p&gt;Llegamos a un taller de garaje y el que lo atendía era un bacán, inmediatamente se puso en la tarea de ayudarnos, pues hoy nosotros éramos los viajeros por Argentina pero el día de mañana podría ser él por Colombia. En poco más de una hora logró reemplazar el eslabón dañado, temporalmente, así que igual tendría que manejar con cuidado hasta cambiar la cadena completa. Le entregamos el único efectivo que llevábamos, que tampoco era mucho, y quedamos Sergio y yo, parados a las 3 de la tarde, habiendo recorrido treinta kilómetros frente a las motos con la siguiente pregunta: ¿y ahora qué hacemos?&lt;/p&gt;
&lt;p&gt;Nos miramos un rato mientras cada uno consideraba cuáles eran nuestras opciones en este punto. Bariloche aún estaba a 350k, no habíamos desayunado, no teníamos un centavo, no teníamos señal de teléfono y, además, era domingo. Sergio, siempre determinado, dijo: —Por esa carretera de la izquierda se llega a los Alerces. Agarrando el sentido de sus palabras, suspiré y comencé a alistarme. No estaba enteramente convencido, especialmente porque tendría que manejar despacio, pero sabía lo mucho que él quería hacer esa ruta del parque y, honestamente, yo también.&lt;/p&gt;
&lt;p&gt;Empezamos el ritual de arrancar. Calentar la moto, orinar, chaqueta, cuello, guantes, música, en fin… Pero en vez de arrancar para que yo lo siguiera Sergio simplemente apagó la moto y dijo: —No parce, demasiadas banderas rojas. No tenemos plata, no tenemos señal, y además no sabemos si la carretera está abierta (al parecer había una carrera de cross por esos días). No nos pongamos a mariquear y a tentar el destino, mejor vamos al pueblo. Suspiré nuevamente, esta vez con alivio.&lt;/p&gt;
&lt;p&gt;Nuestra primera parada fue en la oficina de turismo para ver si la carretera estaba abierta. ¿Por qué? No lo sé. Para mi no tenía sentido ya que habíamos dejado atrás la idea de tomar la ruta de Los Alerces. Más que eso, el cansancio y el hambre se estaban convirtiendo en exasperación. Por mi parte, decidí buscar algún lugar para cambiar dólares. Para los que han estado en Argentina, sabrán que esto es mucho más común de lo que suena. Desde una gasolinera hasta una papelería pueden actuar como casas de cambio express. Pero este no era un día en el que la suerte estuviera de nuestro lado. El único lugar que encontré fue un supermercado con un viejo zorro que fue capaz de afirmar, con toda la seriedad del mundo,  que el dólar en Argentina iba a empezar a bajar y que nos cambiaría a un precio más bajo  que el dólar blue, como favor a nosotros.&lt;/p&gt;
&lt;p&gt;Después de recibir este nuevo golpe a nuestros magullados espíritus, Sergio dijo: —Estoy listo para largarme de este hijueputa pueblo. A lo que yo respondí: —Yo de aquí no me voy sin comer algo antes. Esta vez, era el turno de mi compañero para exasperarse conmigo. Muy a regañadientes y con algún madrazo adicional, nos dirigimos a la única estación de servicio que había en el lugar.&lt;/p&gt;
&lt;p&gt;No exagero cuando digo que ese fue el peor sándwich de milanesa que me comí en Argentina de todas las veces que fui. Qué maldita decepción: más caro que el de la YPF, duro, pequeño e insípido. Esa fue mi primera comida del día la cuál consumí vorazmente en un humor negro, con un café del cuál honestamente no me acuerdo, pero estoy seguro de que estaba feo.&lt;br&gt;
Así que no nos quedó más que despejar nuestra mente con lo único decente que tenía ese lugar, WiFi gratis y pasar unos minutos en nuestros respectivos espacios virtuales mientras reuníamos el ánimo necesario para arrancar. Sergio, decidido, no contemplaba la idea de pasar el día en aquel lugar. Sentados en una mesa frente a frente, con la cara agachada en nuestros teléfonos, en silencio, esperando que se nos pasara la exasperación del uno con el otro fue que, inesperadamente, nuestros corazones maltratados estaban a punto de adquirir un nuevo propósito.&lt;br&gt;
—Pille —dije—, mientras le alcanzaba el teléfono con una conversación de Instagram abierta. Sergio tomó el celular para leer el mensaje. Cuando terminó, se enderezó sobre la silla, se quitó las gafas, las puso cuidadosamente en la mesa y, mientras se frotaba fuertemente el puente de la nariz y los ojos, dijo:&lt;br&gt;
—¿Usted hace cuánto sabe esto?&lt;br&gt;
Encogí los hombros y respondí:&lt;br&gt;
—Acabo de ver el mensaje… ahí les contesté que seguíamos en Trevelín.&lt;br&gt;
—¿Me está diciendo que hay tres francesas en Bariloche, con una casa solo para ellas, invitándonos a pasar la noche?&lt;br&gt;
—Ujumm.&lt;br&gt;
—¿Y qué hijueputas estamos haciendo aquí sentados?&lt;/p&gt;
&lt;p&gt;Las tres francesas eran un grupo de chicas que habíamos conocido unas semanas atrás cuando estábamos bajando en dirección sur. Sergio, siempre observador y extrovertido, las había notado en varios puntos a lo largo de la ruta viajando a dedo. Una tarde de un día entero de ruta, decidimos parar en un pueblo, del que no recuerdo el nombre, pero que fácilmente podría ser el escenario de una película de apocalípsis zombie ambientado en la Patagonia. Allí fue que, nuevamente, encontramos a las tres chicas, caminando con sus mochilas por la mitad de la calle. Sergio no pensó dos veces en abordarlas, mientras yo lo esperaba una cuadra más adelante. Después de unas cuantas palabras, caminaron a donde yo me encontraba. Sergio me señaló con el dedo: —Él habla francés —dijo— y, mirándome a mí, agregó: —dígales que busquemos hotel juntos.&lt;/p&gt;
&lt;p&gt;Aquellas francesas que conocimos en el pueblo más recóndito del sur, estaban en Bariloche, en una cabaña invitándonos a pasar la noche allá. Sin pensarlo dos veces y con renovadas energías pusimos en nuestra mente la misión de llegar a Bariloche ese mismo día.  Naturalmente, esto implica comenzar con el ritual de alistar alistar las motos.&lt;br&gt;
En el parqueadero de la estación vimos un grupo de moteros; venían en sentido opuesto —o sea, en dirección sur— y acababan de hacer el mismo tramo que nos preparábamos a recorrer. Se veían cansados; para ellos ya era el final del día, que es lo normal hacia las 4 p. m., cuando empieza a escasear la luz. Les preguntamos cómo estaba la ruta, a lo que respondieron que había estado durísima, que había mucho viento y que los había obligado a parar en un tramo de la carretera a esperar que pasara.&lt;br&gt;
—¿Para dónde van ustedes? —nos preguntaron.&lt;br&gt;
—Para Bariloche —dijimos. Nos miraron con cara de locos.&lt;br&gt;
—¿A esta hora? ¡Ya está! ¡Qué se van a ir a Bariloche ahora!&lt;br&gt;
Pero nuestra decisión ya estaba tomada. Intercambiamos un par de saludos amigables con los demás miembros del grupo y seguimos nuestros preparativos. Justo antes de arrancar, Sergio se quedó mirándome y dijo —No podemos llegar con las manos vacías. Deberíamos llevarles algo. ¿Qué les gustará? —me preguntó.&lt;br&gt;
—Lo que su corazón le dicte —respondí.&lt;br&gt;
Dos minutos después regresó del quiosco de enfrente con una bolsa negra en sus manos. —¿Qué compró? —le pregunté. Sin contestarme, me entregó la bolsa para que la guardara. —Camine —dijo—, y prendió su moto.&lt;/p&gt;
&lt;p&gt;Fue así, dejando que nuestra determinación silenciara el cansancio de las últimas horas, que arrancamos el trayecto de 300 km a Bariloche. El inicio fue bellísimo, pues hay pocos paisajes comparables a la patagonia al atardecer, el sol pareciera que tarda horas en ponerse mientras el azul del horizonte se va transformando de anaranjado a rojo y de rojo a morado. Teníamos que aprovechar las escasas horas de luz para atravesar un tramo de Esquel que se encuentra entre dos montañas y se forma un túnel de viento, así que arrancamos bastante rápido. No tardamos mucho en toparnos con un viento de frente, que no me permitía acelerar a más de 40 km/h. Sergio, al ver lo mucho que me estaba costando avanzar decidió tomar la posición de adelante y alivianar mi paso con su moto más grande.&lt;/p&gt;
&lt;p&gt;No recuerdo en qué momento empezó a llover. Sé que ya estaba oscuro, habíamos dejado atrás el túnel de viento e íbamos camino al Bolsón. La lluvia complicaba un poco la visibilidad, pero aún quedaban destellos de luz para guiarnos. Fue el tramo más fácil del día; disfrutable, incluso, ya que marca un cambio de paisaje donde va quedando atrás el desierto patagónico y se empieza a percibir el verde más profundo que recubre la región de los grandes lagos.&lt;/p&gt;
&lt;p&gt;Llegamos empapados al Bolsón y paramos a cargar gasolina. Estábamos de buen ánimo, habíamos surcado el tramo más exigente y esta sería nuestra última parada antes de llegar a nuestro destino. A unos 100 metros vi la oportunidad de eliminar la última molestia del día que me quedaba: el sabor de aquel terrible sándwich de milanesa de Trevelin. Aprovechando que Sergio no estaba, corrí al carrito y pedí la hamburguesa más grande que tenían. Hice mi mejor cara de inocencia cuando regresé a él con mi pedido en las manos, porque sólo me contestó con su risa. Quisiera recordar que nos la comimos juntos, aunque lo más posible es que ni siquiera la probó. En todo caso, estábamos listos para recorrer el último tramo del día. Serían 120 kms y estos serían en completa oscuridad.&lt;br&gt;
Aún lloviznaba cuando arrancamos y lo primero que pensé al salir del casco urbano es que habían muchas más curvas de las que recordaba en ese tramo. La visibilidad era mala, no podía ver a más de 3 metros en frente mío, y la luz de mi moto modelo 2003 no tenía fuerza suficiente para iluminar a través de la lluvia. Íbamos muy lento para una calle pavimentada, pensé en parar y decirle a Sergio que tomara la delantera con sus exploradoras.&lt;/p&gt;
&lt;p&gt;No lo hice, fue solo un pensamiento. Es difícil describir el estado casi meditativo de manejar una moto por horas. Uno entra en un trance semi meditativo en el que el ser se divide en dos: el que maneja y el que piensa. Como dos ríos paralelos, que a veces se cruzan y se separan de nuevo. En uno viajan  pensamientos que van y vuelven, fluyendo con la corriente direcciones aleatorias, mientras que el otro se mantiene constante, atento, pasivo y alerta al mismo tiempo.&lt;/p&gt;
&lt;p&gt;Mi trance terminó súbitamente, Después de treinta minutos de completa soledad cuando un carro nos adelantó iluminando todo el camino que teníamos por delante. Instintivamente aceleré para mantenernos a pocos metros y poder andar más rápido durante el tiempo que fuera en nuestra misma dirección. Cuando finalmente nos dejó, unos 45 minutos después, ya estábamos respirando con aire de triunfo, faltaba media hora para llegar a Bariloche.&lt;/p&gt;
&lt;p&gt;Paramos unos minutos al lado de la carretera, en la oscuridad intensa de una noche fría: parecía un buen lugar para tener un momento de reflexión. No sería el primero ni el último. Si bien era raro que manejáramos de noche, en un viaje de este estilo los imprevistos están garantizados y habíamos adoptado la costumbre de apreciarlos. Apagamos las motos y prendí un cigarrillo mientras estiramos las piernas.  Hacia arriba no quedaban rastro de las nubes de lluvía que nos azotaron casi todo el camino y en su lugar quedaba un cielo despejado y lleno de estrellas.  A lo lejos podía ver el tenue resplandor de las luces de Bariloche.&lt;br&gt;
Miré mi teléfono por primera vez en horas para buscar la conversación que había desatado esta travesía. —Sergio Andrés —dije—. Yo no les pregunté dónde queda el hospedaje; tengo el nombre, pero sin internet ni idea cómo llegar.&lt;br&gt;
—Usted sí es que es bien marica; allá miramos qué hacemos.&lt;br&gt;
—Ojalá sigan despiertas —dije suspirando.&lt;br&gt;
La última parte del trayecto transcurrió sin inconvenientes. La carretera estaba iluminada y teníamos energía de sobra de saber que la mayor parte del camino estaba atrás de nosotros. Nos costó un par de minutos encontrar la dirección exacta del hospedaje, pero como quedaba sobre la vía principal no fue mucho tiempo.  A pesar de la hora, ellas estaban pendientes de nuestra llegada; salieron a ayudarnos a descargar nuestro equipaje y a acomodarnos en su cabaña.&lt;/p&gt;
&lt;p&gt;Les contamos de nuestra travesía mientras nos ofrecían algo de comida que tenían preparada y una cerveza.  Conversamos un buen rato, ellas en un intento de español y nosotros en un intento de francés. De repente, con un sonido seco, Sergio plantó sobre la mesa la bolsa negra que llevaba en mi equipaje: era una botella de tequila. A riesgo de sonar cursi, la reacción de nuestras amigas no fue nada menos que oh lá lá. Sin embargo, a pesar de esta generosa ofrenda, tras unos minutos más de conversación se despidieron para ir a dormir.  Ellas tres tomarían la cama doble del primer piso; Sergio y yo tomaríamos cada uno una cama sencilla en el segundo junto con nuestra botella.&lt;/p&gt;
&lt;p&gt;Empezamos a tomar sorbos. Tal vez seguíamos cargados de la adrenalina del día, o quizás el tequila nos estaba dando nuevos ánimos. Empezamos a conversar, como muchas noches anteriores. Sobre las sensaciones de ruta, sobre lo que nos esperaba al regreso, sobre los sueños futuros, sobre los amores pasados,presentes y futuros. Nos emborrachamos; no recuerdo el momento en el que me dormí.&lt;/p&gt;
&lt;p&gt;La vista al despertar fue inesperada. Sabía que estábamos cerca al lago, lo había escuchado en la noche, pero en la oscuridad no lo alcancé a percibir. Salí por la ventana para pararme en el techo a recibir la vista y el aire de la mañana. Brillaba un sol resplandeciente con una brisa fresca de otoño. El agua azúl oscuro reflejaba los destellos de luz hacia todos lados. Respiré el aire frío de otoño y reflexioné, nuevamente, sobre una de las lecciones más importante del día anterior:&lt;br&gt;
A las francesas, lo que hay que regalarles es vino.&lt;/p&gt;
 &lt;figure class=&quot;my-8&quot;&gt;
            &lt;img src=&quot;https://gafemoyano.com/assets/img/5B-DPKDDDm-1600.jpeg&quot; alt=&quot;El despertar&quot; class=&quot;w-full h-auto rounded-lg shadow-md&quot; loading=&quot;lazy&quot;&gt;
            &lt;figcaption class=&quot;mt-3 text-sm font-sans text-gray-600 italic text-center&quot;&gt;El despertar&lt;/figcaption&gt;
          &lt;/figure&gt;
</content>
  </entry>
  <entry>
    <title>Caracoles y Milanesas</title>
    <link href="https://gafemoyano.com/es/posts/caracoles-y-milanesas/" />
    <updated>2022-12-26T00:00:00Z</updated>
    <id>https://gafemoyano.com/es/posts/caracoles-y-milanesas/</id>
    <content type="html">&lt;h2 id=&quot;11-de-marzo-de-de-2022&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://gafemoyano.com/es/posts/caracoles-y-milanesas/#11-de-marzo-de-de-2022&quot;&gt;11 de marzo de de 2022&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;De hoy no tengo casi fotos. De por sí es difícil capturar momentos en la ruta. El problema no es que sea difícil detenerse y sacar la cámara. El problema es que es demasiado fácil. Y es que así uno pare cada 100 metros es imposible capturar algunos lugares porque simplemente no caben en una imagen. Lugares Inconmesurables, como llegaría a llamarlos mi compañero Sergio. Pero este momento en particular me fue imposible. Esta foto fue tomada por Sergio y ya que no cuento con otras imágenes para recordarlo bastarán las palabras.&lt;/p&gt;
&lt;p&gt;El día empezó con el sinsabor de una despedida. Nos separábamos de una compañera con la que compartimos las últimas dos semanas de viaje, tal vez menos. Una eternidad en todo caso. Ella seguía su rumbo a Ushuaia y nosotros daríamos media vuelta hacia el norte, buscando cruzar a Chile.&lt;/p&gt;
&lt;p&gt;Pintaba que iba a ser un día de ruta largo. Después de unos cuantos días de quietud, estábamos bien descansados y las motos listas para andar. El plan era salir de Calafate y llagar al menos a Gobernador Gregores. Incluso Bajo Caracoles, si nos iba bien por el ripio de los 73 malditos&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;https://gafemoyano.com/es/posts/caracoles-y-milanesas/#fn1&quot; id=&quot;fnref1&quot;&gt;[1]&lt;/a&gt;&lt;/sup&gt;. Eran kilómetros que ya habíamos recorrido y paisajes que ya habíamos visto, en el sentido contrario. Mi expectativa estaba enfocada en llegar a la estación de Bajo Caracoles y pedir el sánguche de milanesa más salvaje que conozco.&lt;/p&gt;
&lt;p&gt;Saliendo de Calafate fue evidente que el paisaje que tenía en frente no era el mismo que había visto al llegar. El día estaba completamente despejado y se podían ver todos los picos nevados en línea recta en el horizonte hasta el distintivo cuerno del Fitz Roy al norte. Los lagos glaciales brillaban de un color verde esmeralda intenso por el reflejo del sol y vimos volar un cóndor a lo lejos. Con el tiempo es fácil distinguirlos: estatuas inmóviles que se mueven a toda velocidad en el aire. Pasó uno más por encima de la carretera, un macho por su distintivo collar blanco. Me pareció una gran despedida de un lugar al que estuvimos tratando de llegar durante semanas.&lt;/p&gt;
&lt;p&gt;Unos kilómetros más adelante nos adelantaron dos motos con matrícula colombiana, un avistamiento aún más extraño que el anterior por estos lados. Los saludamos en la siguiente parada, más que por colombianos, por moteros. Es común saludarse entre viajeros, pero las motos tienen un magnetismo irresistible que incita a acercarse en cualquier parada, saludar, preguntar de donde vienen, a donde van, que tal el camino de ese lado y desearse buena ruta&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;https://gafemoyano.com/es/posts/caracoles-y-milanesas/#fn2&quot; id=&quot;fnref2&quot;&gt;[2]&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;Se venía el ripio de los 73 malditos y el día no podía ser mejor. Más allá del sol radiante, no había viento. Con viento todo es mas difícil. Esta vez me sentía con confianza, en parte por la seguridad de haberlo cruzado de ida y en parte porque teníamos unas cubiertas chinas nuevas, marca Raigen, que conseguimos en Calafate. En teoría inciertas para andar el asfalto pero seguras para el ripio y esta sería su primera prueba.&lt;/p&gt;
&lt;p&gt;Transitar una carretera de ripio pesado es una experiencia contraintuitiva, donde ser cauteloso es peor que ser determinado, frenar lleva a una caída, acelerar estabiliza, relajarse recupera la estabilidad y tensionarse asegura perder el control. De rodar en asfalto se pasa a navegar en piedra, dando ligeras indicaciones al timón de a donde ir sin forzarla pues, últimamente, la moto quiere seguir rodando. Hay que dejarla hacer lo suyo, confiar en ella.&lt;/p&gt;
&lt;p&gt;Nuevamente tengo la sensación que, a pesar de ser el mismo recorrido, estoy transitando una carretera distinta. Hay paisajes que a pesar de haber visto mil veces es como si aún no los hubiera visto por primera vez. Hacia la mitad del recorrido me recibe un lago gigante que refleja las montañas arenosas que lo rodean. Bajo la velocidad para alargar mi tiempo con la vista y veo a mi izquierda un combo de ñandús, una especie de ave quimérica de mediana altura con cuerpo de pavo y patas de avestruz, comenzar a galopar a mi izquierda en mi misma dirección. Navegando tan lento como puedo andamos juntos unos 70 metros en línea recta antes de que decidieran cruzar la ruta de un lado al otro y seguir su camino hacia donde sea que van los ñandús en días como este.&lt;/p&gt;
&lt;p&gt;Sé que faltan pocos kilómetros y el ripio se siente firme bajo las ruedas, las Raigen están funcionando bien y decido acelerar el último tramo. Coincidencialmente, una ráfaga de viento me golpea y me hace dudar, advirtiéndome que esa amenaza aún existe. Pero no me importa, agarro el acelerador, me paro en los pedales y aprieto a fondo.&lt;/p&gt;
&lt;p&gt;Al terminar el ripio decido bajarme de la moto y sacudirme la adrenalina de los últimos 10 o 15 minutos. Armo un tabaco mientras espero a Sergio, me lo merezco. Lo veo venir a lo lejos y me hago visible a un costado de la carretera pero la velocidad lo hace parar unos metros mas adelante. Camino hacia él para darle un abrazo celebratorio y le pregunto como le fue. - Esa carrera no deberían pavimentarla nunca, responde. Estoy de acuerdo.&lt;/p&gt;
&lt;p&gt;Paramos en Gobernador Gregores a poner nafta. En la estación de servicio llega el momento de tomar una decisión. Paramos o continuamos a Bajo Caracoles. Con el sistema nervioso aún en llamas, concluimos que hemos hecho un buen tiempo hasta este punto y aún tenemos energía para continuar. Además, el día esta espectacular y deberíamos aprovechar que no hay viento para andar el tramo de Las Horquetas. Comemos algo ligero pues el sánguche de milanesa espera y arrancamos.&lt;/p&gt;
&lt;p&gt;Lo delicado de esta ruta es que tiene una recta interminable como de 50kms de pampa, un horizonte infinito de arbustos con ocasionales guanacos y el viento que baja de las montañas pega completamente de frente. Es más bien tediosa y después de unos minutos empezamos a sentir el bajón de adrenalina y el cansancio del día... las ganas cada vez más grandes de llegar me incitan a revolucionar la moto un poco más que de costumbre.&lt;/p&gt;
&lt;p&gt;Tras poco mas de una hora estábamos en el hotel las Horquetas, uno de esos lugares de que asusta más que esté operando que abandonado, marcando la mitad del trayecto. Un momento para estirar las piernas y prepararse para la última sesión del día. Allí encontramos a un par de amigos ciclistas viajando con Dobby, su perrita, que conocimos andando en direcciones opuestas y ahora los alcanzábamos en regresando en el mismo sentido. Es otro viaje, andar en bicicleta. También iban rumbo a Bajo Caracoles pero seguro no llegarían hoy, con suerte harían 30kms más y pasarían la noche acampando al lado de la ruta en algún lugar que los resguarde del viento. Los abrazamos seguramente por última vez y retomamos el rumbo, no sin antes encontrar una trágica sorpresa en mi apoya pies derecho seguido del recuerdo de tres hermosos pajaritos atravesando mi camino, dos de ellos emergiendo del otro lado hacia el desierto.&lt;/p&gt;
&lt;p&gt;10 kms más adelante adelantamos dos nuevos ciclistas luchando contra el viento y se me ocurre que no conozco un lugar más difícil que este, dónde el viento hace la fuerza de una pared en contra, donde no se consigue ni agua por cientos de kilómetros, para viajar en bicicleta. Todo mi respeto. Les doy el acostumbrado saludo alentador y los dejo atrás.&lt;/p&gt;
&lt;p&gt;Tomo una curva de 90 grados que indica el inicio de la segunda recta, igual de interminable, pero con el viento del costado izquierdo. Esta tiene tramos de ripio cada tanto lo que obliga a los camiones, que generalmente andan más rápido que las motos, a detenerse. Adelanto el primero y empiezo a acelerar el paso sintiendo todas las fibras de mi cuerpo anhelar la llegada. Mi compañero de ruta, en un acto que no sé si catalogar como paciencia trascendental o demencia frenética, mantiene una velocidad constante durante todo el trayecto, el muy desquiciado. Por eso que hace varios minutos desapareció de mi retrovisor.&lt;/p&gt;
&lt;p&gt;Con la mirada fija en el horizonte interminable de la pampa, hay una repentina intrusión en mi visión periférica. Algo titila en el tablero de la moto. El indicador del tanque de reserva. Miro la distancia recorrida desde Gobernador Gregores, donde sé que devolví el contador a ceros y leo 130km. Imposible, normalmente se activa sobre los 200km. Empiezo a hacer cuentas rápidas en mi cabeza y trato de recordar la distancia a Bajo Caracoles. ¿Eran 220? ¿230? ¿Cuántos litros tiene mi tanque de reserva? Tres, me parece. Un motero debería saber de corazón la respuesta a todas estas preguntas.&lt;/p&gt;
&lt;p&gt;En ese momento vuelve a mi memoria alguno de estos encuentros magnéticos motoqueros, en el que alguien me dijo, señalando mi moto, que había andado en una de estas 90km con el tanque de reserva hasta que se apagara. Pero yo vengo manejando con el motor a muchas revoluciones y debido al trance relativo/espacial agudo de ruta en el que me encontraba&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;https://gafemoyano.com/es/posts/caracoles-y-milanesas/#fn3&quot; id=&quot;fnref3&quot;&gt;[3]&lt;/a&gt;&lt;/sup&gt; no sé cuánto podría llevar prendido el indicador antes de percatarlo. Hago unas cuentas alegres: si son 220km llegaré con lo justo... si son 230km, ¿quién sabe?. Además el viento se pone más fuerte hacia el final de la tarde, oponiendo mayor resistencia. Da igual, mi única opción es intentar exprimir hasta la última gota de eficiencia de lo que me queda. Relajo la mano del acelerador, observo bajar las revoluciones del motor y empiezo a andar al rededor de 80km/h&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;https://gafemoyano.com/es/posts/caracoles-y-milanesas/#fn4&quot; id=&quot;fnref4&quot;&gt;[4]&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;Después de un tiempo Sergio vuelve a aparecer en mi retrovisor y aprovecha un pedazo de ripio para adelantar un camión al que yo estoy resignado a tener en frente y tragarme el polvo que levanta tras su paso. Lo veo pasar y pienso que ojalá le rinda para que la comida esté lista cuando yo llegue.&lt;/p&gt;
&lt;p&gt;Él, a diferencia mía, no es de adelantarse demasiado y lo encuentro esperándome en una colina. Yo estoy contando cada gota de nafta y no quiero ni parar a decirle qué ocurre, asi que paso despacio por en frente y le señalo el tanque. Es un tema esotérico lo de la comunicación por señas entre transitantes de la ruta, pero con eso bastó para que me entendiera, por lo menos lo suficiente para que decidiera ajustar su velocidad y quedarse detrás mío.&lt;/p&gt;
&lt;p&gt;Andamos a 70km/h ahora y empieza a bajar el sol haciendo evidente que llegaremos más tarde de lo planeado. Usualmente evitamos hacer trayectos si existe el riesgo de que nos alcance la noche antes de llegar al destino. A mi cerebro le parece un buen momento para reflexionar en lo absurdo de rodar la primera mitad del trayecto a 120km/h para terminarlo a unos 70km/h.&lt;/p&gt;
&lt;p&gt;El medidor marca 170km y, aunque lo he buscado atentamente, no encuentro ninguna señal que indique qué tan lejos estamos de la meta. El cielo brilla azul intenso a lo lejos entre unas estiradas nubes blancas. A la izquierda se empieza a teñir un cielo naranja con amarillo intenso entre las montañas desérticas. A la derecha el azul empieza a oscurecerse y transformarse en morado. Vamos tan despacio que una familia de guanacos de todos los tamaño se cruza por la mitad de la carretera sin sobresaltarse de nuestra presencia. Miro hacia atrás para enviarle un reconocimiento telepático a mi compañero de lo absurdo de la escena ante nosotros.&lt;/p&gt;
&lt;p&gt;Los colores se intensifican en el horizonte y la paleta se extiende. El amarillo ahora esta acompañado de carmesí y naranja intenso. El azul se difumina en tonos púrpura y magenta. Las montañas normalmente pálidas reflejan un rojizo contra las pocas nubes blancas aplastadas en el cielo. Una media luna, perfectamente dividida dos mitades, se alinea frente a la carretera. Miro hacia atrás y veo a Sergio rezagado con la cabeza inclinada hacia arriba. Va incluso más lento que yo, andando al son del Bolero de Ravel.&lt;/p&gt;
&lt;p&gt;Los minutos pasan lentamente y nos consumimos en un atardecer imposible de 360 grados. Mis ojos rotan constantemente entre cuatro puntos. A la izquierda rayones salvajes de crayola entre naranja, rojo y salmón, a la derecha las pinceladas gruesas de óleo púrpura y azul, en frente la luna cómo guía de la ruta y abajo una cuenta regresiva que no sabía exactamente cuando iba a acabar.&lt;/p&gt;
&lt;p&gt;El tablero marca 190 kms y aún no hay señales de nada. El éxtasis del paisaje empieza poco a poco a reemplazarse por la angustia inminente de quedarme sin gasolina. Llevo casi 60km con el tanque de reserva, nunca había pasado de 50km. En el mejor de los casos aún puedo andar 30km más.&lt;/p&gt;
&lt;p&gt;A los 200km empiezo a hacerme promesas a mí mismo y a la moto. Le pido disculpas por las veces que la he tratado fuerte y le prometo que no la vuelvo a acelerar de esa forma.&lt;/p&gt;
&lt;p&gt;210km. El cielo, aún un lienzo indescriptible. Pero mis ojos se fijan cada vez más en el medidor y menos en el paisaje. En intervalos cada vez más regulares, le doy unas palmaditas al tanque, acompañadas de unas palabras de ánimo. - Dale, chiquita, ya falta poco. Lo vamos a lograr, son solo unos cuántos kilómetros más. Yo sé que tú puedes, dale, dale.&lt;/p&gt;
&lt;p&gt;Veo una señal azul distante, de esas que indican hospitales, hospedajes, nafta, cualquier cosa. Dice &lt;em&gt;Bajo Caracoles&lt;/em&gt;, nada más. La Puta Madre, dónde está este desgraciado pueblo. - Tranquila, mi amor, ya no falta mucho.&lt;/p&gt;
&lt;p&gt;Marcan los 220km y según mis cuentas alegres debería estarse acabando la reserva, pero seguimos andando. Veo asomarse otra señal de tránsito y siento algo de esperanza en mi, ¿llegamos?. Dice, Bajo Caracoles 10km.&lt;/p&gt;
&lt;p&gt;Inclino la cabeza hacia el cielo espectacular y me río histéricamente entre la euforia y la desesperación. Bueno, 10km más. - Vamos, linda, podemos lograrlo.&lt;/p&gt;
&lt;p&gt;En este punto llevo un monólogo incesante y alentador con la moto, acompañado de golpesitos en el tanque como el cuello de un animal de paso y unas cuantas caricias. Lanzo una que otra promesa, cada tanto.&lt;/p&gt;
&lt;p&gt;La mirada ahora clavada en el horizonte esperando alguna señal, construcción, cerca, animal que indique que hemos llegado. Nunca había hablado tanto ni tan íntimamente con un objeto inanimado.&lt;/p&gt;
&lt;p&gt;Finalmente, veo la sombra de una edificación a lo lejos, un poco más de 1km. ¡ahí está! lo lograremos. Trato de contener la emoción para no aumentar la presión en la manigueta y quemar los pocos mililitros de gasolina que deben quedar[^5].&lt;/p&gt;
&lt;p&gt;Con los últimos rayos de luz, se descubre la vía en piedra que se desvía hacia la estación de servicio. ¡Lo logramos, la re puta madre! Estaciono de cualquier forma y le doy un prolongado abrazo a la moto mientras le repito todas las promesas de los últimos 30 kilómetros.&lt;/p&gt;
&lt;p&gt;Llegamos a Bajo Caracoles. No había sánguches de milanesa.&lt;/p&gt;
&lt;hr class=&quot;footnotes-sep&quot;&gt;
&lt;section class=&quot;footnotes&quot;&gt;
&lt;ol class=&quot;footnotes-list&quot;&gt;
&lt;li id=&quot;fn1&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;Un tramo mítico de la ruta 40 sin pavimentar entre las localidades de Tres Lagos y Gobernador Gregores, particularmente conocido entre motoqueros por su longitud y dificultad en condiciones de viento y lluvia. &lt;a href=&quot;https://gafemoyano.com/es/posts/caracoles-y-milanesas/#fnref1&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn2&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;Alguien con experiencia notaría la exclusión de &lt;em&gt;mecánica&lt;/em&gt; de esa lista. La única cosa que le gusta más a los moteros serios que andar en moto es hablar de mecánica. Así que normalmente huyo del encuentro antes de quedar atrapado durante horas en un vórtice de carburadores, cubiertas, filtros, tanques o, dios me libre, un debate. &lt;a href=&quot;https://gafemoyano.com/es/posts/caracoles-y-milanesas/#fnref2&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn3&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;Los conductores que hayan experimentado este trance seguramente sabrán a qué me refiero. Es como estar en un cuadro en movimiento, el tiempo y el espacio no existen, porque nada cambia, excepto que sí cambia, sólo que, para el observador en el centro de la escena no es perceptible. &lt;a href=&quot;https://gafemoyano.com/es/posts/caracoles-y-milanesas/#fnref3&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn4&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;Una velocidad respetable, considerando las condiciones climáticas. Imposible de lograr por mi XTZ 250 si no fuera, según me hizo notar tiempo después mi compañero, por &lt;a href=&quot;https://youtu.be/u-O5X_KT0sQ?t=296&quot;&gt;un gran viento de cola&lt;/a&gt; que nos acompañó durante el tramo posterior a Las Horquetas. &lt;a href=&quot;https://gafemoyano.com/es/posts/caracoles-y-milanesas/#fnref4&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</content>
  </entry>
  <entry>
    <title>Isla Sigma</title>
    <link href="https://gafemoyano.com/es/posts/isla-sigma/" />
    <updated>2022-09-19T15:24:22Z</updated>
    <id>https://gafemoyano.com/es/posts/isla-sigma/</id>
    <content type="html">&lt;p&gt;La tarde terminó con delfines nadando a nuestro lado mientras corríamos por una playa junto al mar a la que llegamos inesperadamente. Me gustaría decir que nadamos con ellos, y por poco fue así, pero la historia es otra.&lt;/p&gt;
&lt;p&gt;Amanecimos en Puyuhuapi después de una noche que se iba yendo por las ramas. Agotados de un día de caminata en el glaciar Ventisquero Colgante los viajeros del camping se habían reunido a la hora de la cena para comer, conversar y tomar unos tragos hasta bien entrada noche. Fuimos a dormir sin pactar una hora de salida. Es una de esas reglas no escritas de este viaje: arrancamos cuando nos levantemos. No tiene mucho sentido establecer hora de salida cuando no existe una de llegada.&lt;/p&gt;
&lt;p&gt;Tomamos un desayuno sencillo y nos pusimos en la tarea de &lt;em&gt;armar la moto&lt;/em&gt;, ese ritual de encaramar el equipaje encima de la moto de formas infinitamente creativas. En esas se nos acercó Silvi, una de las personas con las que habíamos compartido la caminata del día anterior y que habían rematado la jornada hasta las últimas consecuencias. -¿Ya comieron algo chiquillos? preguntó. -Sí, respondimos. No debimos sonar muy convincentes ya que 15 minutos después regresó para ofrecernos algo de pasta con jurel. Intercambiamos una mirada rápida con Sergio, sabiendo que la respuesta iba a ser afirmativa. No teníamos hambre, en realidad, pero más que la comida ambos sabíamos que no íbamos a rechazar una invitación para compartir.&lt;/p&gt;
&lt;p&gt;Estuvimos listos para salir a las 12:30, tarde, incluso para nuestros laxos estándares motoqueros. Igual teníamos ganas de avanzar más hacia el norte por la carretera austral y el día estaba lindo. Queríamos aprovechar los cada vez más escasos días de sol ya que el otoño se aproximaba amenazante la lluvia. Así que arrancamos con dirección a Chaitén, disfrutando a paso tranquilo los paisajes inexplicables que acompañan la carretera, lienzos verdes que se extienden a lo lejos hasta convertirse en picos rocosos azules cubiertos de nieve.&lt;/p&gt;
&lt;p&gt;Paramos en La Junta a cargar gasolina. Sergio, siempre con el mapa en la cabeza, me dijo -Al salir del pueblo en el cruce de un río hay una ruta que se desprende a la izquierda y bordea un volcán, paremos a ver qué tal está. Llegamos al punto indicado y Sergio bajó de la moto para aproximarse al camino de ripio que se adentraba hacia las montañas. Lo pisó con sus botas, como quien prueba el agua de una piscina para ver la temperatura del agua antes de lanzarse. Me hizo una seña con el pulgar para indicar que el resultado de la prueba era positivo, miré hacia el norte y se asomó un pensamiento -Ya no vamos a llegar a Chaitén. Lo dejé seguir de largo rápidamente y me recordé a mi mismo el mantra de que esto (este viaje, este día) no se trata de llegar al destino final sino de recorrer el camino. Enfoqué toda mi energía y concentración en el camino que tenía en frente y arrancamos hacia hacia el oriente.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://gafemoyano.com/assets/img/isla-sigma-1.jpg&quot; alt=&quot;Camino de ripio patagónico&quot; title=&quot;Camino de ripio patagónico&quot;&gt;&lt;/p&gt;
&lt;p&gt;La ruta nos recibió con un paisaje campestre, múltiples granjas se extendían a lado y lado enmarcadas por la cordillera en el horizonte. Un río verde esmeralda acompañaba la ruta por la derecha, acercándose y alejándose a medida que nos adentrábamos en el camino. Eventualmente el espacio empezó a volverse cada vez más estrecho, el rio y las montañas cerrándose como unas tenazas prensando la ruta en la mitad.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://gafemoyano.com/assets/img/isla-sigma2.jpg&quot; alt=&quot;Rio verde esmeralda&quot; title=&quot;Rio verde esmeralda&quot;&gt;&lt;/p&gt;
&lt;p&gt;Rodamos tranquilamente unos 20 minutos por un terreno llano cuando se nos cruzó un aviso que leía -Cuidado, curvas peligrosas. Pensé para mi mismo --Ahora sí, a lo que vinimos. Repasé una lista mental de ejercicios de manejo que quería practicar. Se había despertado en mi una pasión por el enduro y estaba decidido a aprovechar cada carretera para practicar todo lo que pudiera.&lt;/p&gt;
&lt;p&gt;Me paré en la moto y empecé a fluir con la carretera, probando los límites de mi balance y el de la moto contra el piso resbaloso y las curvas pronunciadas a medida que trepábamos la montaña. Al mismo tiempo pensé en una ecuación ridícula que me permitiera balancear los minutos invertidos en entrenar contra el piso rocoso y desafiante sin perder enteramente la perspectiva del lugar que estaba recorriendo. Eventualmente terminaron las curvas y me encontré ante una recta larga. Bajé la velocidad, me senté y me ví envuelto de repente en una jungla. Hojas verdes del tamaño de personas descansaban a lado y lado de la angosta ruta. La vegetación era tan densa que no había rastros de la montaña que supuestamente estábamos bordeando ni del río que guiaba el camino. Sólo la vegetación tupida emitiendo una humedad amazónica que impregnaba el aire caluroso.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://gafemoyano.com/assets/img/isla-sigma-3.jpg&quot; alt=&quot;Selva Patagónica&quot; title=&quot;Selva Patagónica&quot;&gt;&lt;/p&gt;
&lt;p&gt;Esperé a Sergio unos minutos hasta que lo vi aparecer, mirándome desde dentro del casco con la cara abultada por la sonrisa que ll vana debajo, incrédulo del lugar al que habíamos llegado. Soltamos un par de carcajadas y nos abrazamos para liberar la adrenalina del momento. Sacamos un par de fotos de esas que fallan en capturar la magnitud del lugar y aprovechamos para intercambiar comentarios de la ruta y las motos. --Esto está muy bueno. Sigamos, creo que todavía nos falta la mejor parte. Dijo Sergio.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://gafemoyano.com/assets/img/camacho-dazzled.png&quot; alt=&quot;Hojas del tamaño de personas&quot; title=&quot;Hojas del tamaño de personas&quot;&gt;&lt;/p&gt;
&lt;p&gt;La siguiente hora transitó de una forma similar, períodos de concentración intensa en la ruta y dificultad seguida de momentos de contemplación del espacio que estábamos transitando. El rio, cada vez más grande a nuestra derecha y la montaña mostrándose caprichosamente curvas, siempre sin destaparse enteramente ante nosotros.&lt;/p&gt;
&lt;p&gt;Eventualmente llegamos a un lugar donde había tres o cuatro autos estacionados. El río se había vuelto tan ancho que bloqueaba nuestro camino, marcando el final de la ruta. --¿Y ahora qué hacemos? le pregunté a Sergio. --Devolvernos, contestó. --Ah, pensé que era un circuito, dije. Ante lo que recibí como respuesta esa mirada de desaprobación que indica &amp;quot;&lt;em&gt;este guevón nunca sabe donde está parado&lt;/em&gt;&amp;quot;.&lt;/p&gt;
&lt;p&gt;Descansamos un rato para reponer energía, mientras observábamos de qué se trataba este paradero en el que otras personas esperaban. A lo lejos vimos un ferry empezar a acercarse desde la otra orilla y los carros comenzaron a acomodarse para abordar. Después de que entró el último la tripulación nos hizo una seña preguntándonos si íbamos a subir. Negamos con la cabeza y se marcharon.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://gafemoyano.com/assets/img/isla-sigma-8.png&quot; alt=&quot;Un ferry a lo lejos&quot; title=&quot;Un ferry a lo lejos&quot;&gt;&lt;/p&gt;
&lt;p&gt;Empecé a sentir el cansancio de las últimas horas, las piernas y los brazos agotados del esfuerzo de la manejada. Me senté en un mapa que era major silla que punto informativo, mientras Sergio hablaba con unas personas que acababan de llegar al punto de embarque. Debieron darle algo de información adicional porque cuando volvió el ferry decidió ir a preguntarles cuánto costaba el paso. Por mi mente pasaron rápidamente múltiples escenarios de lo que podía suceder a futuro. ¿Y si no hay nada del otro lado? ¿Y si regresamos muy tarde? ¿Y si no avanzamos más hacia El Chaitén? &lt;br&gt;
&lt;br&gt;
--El paso es gratis, gritó la voz de Sergio a lo lejos, ¿Vamos? &lt;br&gt;
&lt;br&gt;
--Vamos, dijo mi boca antes de que mi cabeza tuviera el chance de interceder.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://gafemoyano.com/assets/img/isla-sigma-9.png&quot; alt=&quot;El mapa muy útil del puerto&quot; title=&quot;El mapa muy útil puerto&quot;&gt;&lt;/p&gt;
&lt;p&gt;5 minutos después desembarcamos del ferry. De ahí, había 10km más al pueblo que recorrimos a paso tranquilo. La vegetación era similar pero el ambiente era mucho más húmedo, lo que se reflejaba en la ruta un tanto más fangosa. Llegamos a las primeras calles del pueblo pero decidimos seguir andando por la ruta principal y explorar el camino. Pasado el pueblo, nos encontramos con nuevos obstáculos. Un charco cubría toda la ruta de lado a lado. Me detuve y miré a Sergio. --¿Y ahora? Se bajó de la moto y repitió el ritual del pie, esta vez sí ante una piscina, pero de lodo. Encogió los hombros como diciendo, está tibia, se subió a la moto y cruzó por un lado.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://gafemoyano.com/assets/img/isla-sigma-10.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Unos metros más adelante encontramos el siguiente obstáculo. --Ahora le toca a usted de primero, indicó Sergio. Pensé que la última forma en la que quería terminar este día agotador era sacando una moto atorada en el barro, al mismo tiempo que soltaba el embrague y me sumergía en el agua, sintiendo la rueda patinar sobre si misma ligeramente, hasta salir del otro lado. Así seguimos unos cuantos minutos más, intercambiando la delantera para avanzar más por este camino desconocido.&lt;/p&gt;
&lt;p&gt;Paramos en un aviso que leía --&lt;em&gt;Solo vehículos 4x4.&lt;/em&gt; Nos encogimos de hombros al unísono de los motores sin detener y continuamos empujando nuestra suerte, pero el camino rápidamente se convirtió en arena fina arenoso y se lo estrechó al punto de que las maletas rozaban contra la vegetación. Fin del camino, hora de dar media vuelta. Hacía un calor, sofocante y húmedo, no sólo del ambiente sino del esfuerzo físico. --Yo no sé usted, pero yo voy a ir a darme un chapuzón en ese claro que pasamos hace unos minutos. Gran idea, pensé yo un tanto aliviado que finalmente habíamos terminado de avanzar.&lt;/p&gt;
&lt;p&gt;Retrocedimos un poco a un claro por donde se entraba a una playa de arena. Corría un río caudaloso con una corriente fuerte, sin embargo el color era mucho más oscuro que el verde esmeralda que nos había acompañado siempre a nuestra derecha. Del otro lado se veían unas montañas y acantilados cubiertos de la misma vegetación intensa y en el fondo se alcanzaba ver una enorme montaña chata cubierta de nieve. Pájaros de distintas especies llenaban el ambiente con sus ruidos, pasando interminablemente hacia arriba y abajo del río. No había rastros de ninguna otra persona.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://gafemoyano.com/assets/img/isla-sigma-11.png&quot; alt=&quot;Una playa, un nevado, una selva&quot; title=&quot;Una playa, un nevado, una selva&quot;&gt;&lt;/p&gt;
&lt;p&gt;Dejamos las motos, la ropa y el kit de manejo colgado en los manubrios como un perchero y nos preparamos para entrar al agua. Yo cargaba una botella de ron escondida hace unos días, esperando el momento indicado para revelársela a mi compañero en el momento preciso. Me acerqué con ella y le brindé un trago del ron re-envasado en botella de plástico a un Sergio Andrés que contemplaba el paisaje al que acabábamos de llegar. -Qué belleza. Estos son ese tipo de lugares a los que uno solo llega en moto. Dijo conmocionado por el lugar.&lt;/p&gt;
&lt;p&gt;Salí corriendo al agua y salté en ella para no alargar la agonía de descubrir qué temperatura tenía, pero lo sorprendente no fue lo fría que estaba, sino el sabor salado que tenía. Es agua de mar? me pregunté. Debíamos de estar muy cerca a la desembocadura. Nos dejamos arrastrar un rato por la corriente fuerte que llevaba, seguramente, al mar. Sergio se adelantó, presumiendo sus dotes de buen nadador. Ojalá no se ahogue ese desgraciado, pensé.&lt;/p&gt;
&lt;p&gt;Me dejé llevar por la corriente hasta que el agua, hace unos minutos refrescante, ahora empezaba a sentirse helada. Sabiendo que mi compañero iba a tardar un rato en terminar su experiencia náutica, decidí salir a calentarme con un poco de ron, un cigarrillo y jugar a capturar con la cámara lo inexplicable del lugar.&lt;/p&gt;
&lt;p&gt;Nos cruzamos unos minutos después caminando la playa en direcciones opuestas. La cara de mi compañero era de éxtasis, como si el lugar le diera energía de vida. --Hay un ruido extraño, de un animal distinto a un pájaro - dijo Sergio. - Sí, he notado un ruido extraño, contesté. --Solo falta que se nos aparezca una ballena, dijo. Me reí por poco tiempo de la hiperbólica idea hasta que me interrumpió un grito --¡Marica un delfín! Sergio señaló con el dedo el lugar, donde no vi más que unas ondas casi indistinguibles sobre el agua. Alisté el teléfono y empecé a grabar anticipadamente, sabiendo que los delfines tienden a mostrarse más de una vez. Pasaron unos segundos hasta que un par de aletas trazaron una medialuna sobre el agua cerca a la orilla, casi en frente de nosotros.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://gafemoyano.com/assets/img/isla-sigma-12.jpg&quot; alt=&quot;Arenita playita&quot; title=&quot;Arenita playita&quot;&gt;&lt;/p&gt;
&lt;p&gt;Sergio empezó a correr rio arriba para perseguirlos, una parte de él quería nadar detrás de ellos a pesar de la futilidad de tratar de perseguir un delfín nadando contra corriente. Yo corrí detrás de él, sintiendo que los estabamos alcanzando a pesar que cada 20 segundos las aletas se mostraban ligeramente más distantes, como si quisieran que los persiguieramos dejando claro que nunca los ibamos a alcanzar. Desistimos después de correr al menos una cancha de fútbol mientras recuperabamos el aire y los veíamos adentrarse en el continente.&lt;/p&gt;
&lt;p&gt;Volvimos al campamento, contemplamos el lugar, brindamos de nuevo, nos abrazamos. Alguno repitió nuestro mantra &lt;em&gt;¿A qué vinimos?&lt;/em&gt;. No recuerdo la respuesta en este momento pero sí tengo muy claro por qué llegamos a este lugar, por qué vivimos este día y por qué lo nombramos Isla Sigma: una parte era filosofía de viaje y la otra un estado mental. Apertura y aceptación. De estar más atentos del camino que del destino, de apreciar que un obstáculo que cierra una puerta puede abrir tres. De arriesgarse a recorrer los caminos que se presentan y dejar ir los que estabamos buscando. De fluir con la inifinta aleatoreidad de la vida en lugar de resistirla. Porque es al final de esos caminos no buscados, de las selvas inesperadas, de los barcos que no sabíamos que no estaban esperando, de los pantanos que cruzan a playas escondidas, que se encuentra Isla Sigma. Y en Isla Sigma, hay delfines.&lt;/p&gt;
&lt;div class=&quot;flex items-center justify-center w-full&quot; style=&quot;margin-top: 6rem;&quot;&gt;
  &lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/O3aWA_oyAbQ&quot; title=&quot;YouTube video player&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;
&lt;/div&gt;</content>
  </entry>
  <entry>
    <title>De Tailwind a Vanilla Extract: la herramienta correcta para un Sistema de Diseño</title>
    <link href="https://gafemoyano.com/es/posts/from-tailwind-to-vanilla-extract/" />
    <updated>2022-06-14T00:00:00Z</updated>
    <id>https://gafemoyano.com/es/posts/from-tailwind-to-vanilla-extract/</id>
    <content type="html">&lt;h2 id=&quot;introducci%C3%B3n&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://gafemoyano.com/es/posts/from-tailwind-to-vanilla-extract/#introducci%C3%B3n&quot;&gt;Introducción&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Recientemente empecé a trabajar en una organización cuyo modelo es un buen fit para implementar su propio Sistema de Diseño. A pesar de que aún está en una etapa temprana del ciclo de producto y que la capacidad del equipo es limitada, decidimos extraer unos componentes de UI base que sirvieran de fundación para un Sistema de Diseño.&lt;/p&gt;
&lt;p&gt;El plan consistió en construir las pantallas de funcionalidades e ir extrayendo componentes comunes de UI a medida que se fueran revelando. Esta aproximación permite tener un buen balance entre el tiempo invertido en extraer componentes reutilizables y al mismo tiempo avanzar en la implementación del MVP.&lt;/p&gt;
&lt;h2 id=&quot;extracci%C3%B3n-de-componentes-de-tailwind&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://gafemoyano.com/es/posts/from-tailwind-to-vanilla-extract/#extracci%C3%B3n-de-componentes-de-tailwind&quot;&gt;Extracción de componentes de Tailwind&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Para este MVP usamos una combinación de &lt;a href=&quot;https://reactjs.org/&quot;&gt;React&lt;/a&gt; (con &lt;a href=&quot;https://nextjs.org/&quot;&gt;Next.js&lt;/a&gt;) y &lt;a href=&quot;https://tailwindcss.com&quot;&gt;Tailwind&lt;/a&gt; para los estilos. En mi experiencia, Tailwind funciona bastante bien con librerías de componentes ya que crean una frontera natural para reutilizar estilos y evita algunas aproximaciones problemáticas como el uso de &lt;code&gt;@apply&lt;/code&gt;. Sin embargo, construir componentes para un sistema de diseño es lo mismo que construir componentes de aplicación. Los primeros tienden a necesitar mucha mayor flexibilidad: pueden implementar temas distintos, incluyen componentes de más bajo nivel para maquetación y espaciado basados en los tokens de diseño. A pesar que el equipo tenía dudas de lograr esta transición efectivamente decidimos seguir adelante con tailwind e ir resolviendo problemas en el camino. Algunas &lt;a href=&quot;https://www.netlify.com/blog/2021/03/23/from-semantic-css-to-tailwind-refactoring-the-netlify-ui-codebase/&quot;&gt;palabras adicionales&lt;/a&gt; del equipo de &lt;a href=&quot;https://www.netlify.com/&quot;&gt;Netlify&lt;/a&gt; nos reafirmaron que la idea no era descabellada después de todo.&lt;/p&gt;
&lt;p&gt;Sin embargo a medida que comenzamos a extraer algunos componentes nos topamos con dificultades relacionadas a la técnica de clases atómicas. Quisiera notar en este punto que Tailwind es una solución perfectamente válida para extraer componentes reutilizables de &lt;strong&gt;aplicación&lt;/strong&gt;, sin embargo en este caso en particular estabamos buscando extraer la base de un Sistema de Diseño para el cual los requerimientos son diferentes. Dicho esto, miremos algunos de los problemas que encontramos.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;No es ergonómico traducir clases de tailwind a propiedades de React&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Una de las primeras decisiones a las que nos enfrentamos es de qué forma exponer propiedades de estilos para los componentes compartidos. Por ejemplo, supongamos que tenemos un component que recibe una propiedad de &lt;em&gt;background&lt;/em&gt; para cambiar su color de fondo. ¿Los consumidores del componentes deberían pasar la clase de tailwind o el nombre del color? El primero se siente extraño pues estaríamos repitiendonos un poco &lt;code&gt;background=&amp;quot;bg-blue-400&amp;quot;&lt;/code&gt;. El segundo le deja al componente la responsabilidad de reconstruir la clase apropiada para aplicar el color de fondo, además de traer otros problemas adicionales que discutiremos en otro punto.&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Esto se siente extraño&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Card&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; background &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;bg-white&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;rest &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;div className&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;background&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;rest&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// Esto necesita trabajo adicional para cada propiedad&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Card&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; background &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;white&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;rest &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; backgroundClass &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;bg-&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;background&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// No type safety&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;div className&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;topSpace&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;rest&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Esto aplica para todas las propiedades de estilos que se quieran exponer del componente y se convierte en una tarea abrumadora rápidamente&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Debes sincronizar manualmente las propiedades de los componentes con el archivo de configuración de Tailwind&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Ya que no existe una integración entre Tailwind y React, en el sentido que ambos coexisten sin la noción del otro, cada vez que se cambie la configuración de Tailwind debemos recordar actualizar las propiedades de los distintos componentes que la consumen. Este paso es fácil de olvidar y puede llevar a estilos y propiedades desactualizados.&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// tailwind.config.js&lt;/span&gt;
module&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;exports &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;extend&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;spacing&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// New values&lt;/span&gt;
      &lt;span class=&quot;token number&quot;&gt;72&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;18rem&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token number&quot;&gt;84&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;21rem&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// Must update types that map to the new values&lt;/span&gt;
type SpacingProps &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// New values&lt;/span&gt;
  &lt;span class=&quot;token number&quot;&gt;72&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; number&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token number&quot;&gt;84&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; number&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Spacer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; top &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;top&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; SpacingProps &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;div className&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;topSpace&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;rest&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Construir clases dinámicamente no permite purgar o generar (con JIT) las clases en producción&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;En un punto anterior comenté que interpolar texto con valores de Tailwind se siente un poco incómodo, sin embargo hay un problema adicional, las clases generadas de esta forma no serán generadas por el JIT y puede que en producción falten algunos estilos esperados. Esto puede ser un poco confuso pero tiene una solución sencilla, incluir un &lt;em&gt;whitelist&lt;/em&gt; de las clases que se deben incluir en el bundle.&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Spacer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; top &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;16&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;rest &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; topSpace &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;mt-&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;top&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// mt-16 no va a estar incluida en el bundle de producción&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;div className&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;topSpace&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;rest&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Deduplicación y especificidad de clases CSS&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Al momento de sobreescribir o modificar las clases base de un componente y asegurarnos que no se incluyan 2 clases que modifiquen una misma propiedad estamos por nuetra cuenta. Generalmente los componentes de un sistema de diseno aplican algunos estilos por defecto y exponen algunas propiedades para modificar su apariencia o comportamiento. Escribir la lógica condicional para hacer funcionar todo esto está en nuetras manos y en algunos componentes complejos puede llegar a ser un poco compleja.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;There&#39;s no good way to break out of tailwind&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Probablemente llegará el momento en el que tengas que implementar alguna funcionalidad más allá de las utilidades de tailwind. La alternativa más común acá es usar clases sencilla de CSS y la utilidad de &lt;code&gt;@apply&lt;/code&gt; para tener acceso a los tokens existentes. Si bien esto funciona en mi experiencia es difícil notar que se está haciendo algo &#39;personalizado&#39; ya que &lt;code&gt;classNames&lt;/code&gt; está sobrecargado de utilidades y es difícil ver que se agregó una clase &#39;custom&#39; que, como implementador, preferiría que fuera algo que se destaca al consultar el código.&lt;/p&gt;
&lt;h2 id=&quot;cambio-a-vanilla-extract&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://gafemoyano.com/es/posts/from-tailwind-to-vanilla-extract/#cambio-a-vanilla-extract&quot;&gt;Cambio a Vanilla Extract&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Dados los puntos anteriores sentimos que estabamos forzando los casos de uso de tailwind y deberíamos cambiar la herramienta base de nuetra creciente librería de componentes. Idealmente buscamos algo que permitiera el uso de tokens de diseno directamente como propiedades de React. Vanilla Extract permite hacer esto a través del aditamento de Sprinkles además de tener un gran soporte de Typescript.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;La transición&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Decidimos hacer un cambio progresivo y convertir un componente a la vez a medida que ibamos necesitando agregar funcionalidad a la librería. Hacer la instalación inicial es relativamente fácil sin embargo fue útil tener las implementaciones de referencia de &lt;a href=&quot;https://github.com/Shopify/foundational-design-system-proto&quot;&gt;Shopify&#39;s Polaris&lt;/a&gt; and &lt;a href=&quot;https://github.com/seek-oss/braid-design-system&quot;&gt;Seek&#39;s Braid&lt;/a&gt; ya que era necesario escribir código adicional para exponer las clases de utilidad como propiedades de React.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Las cosas buenas&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Al ser una librería basada en Typescript los consumidores cuentan con autocompletar y los autores con chequeo de errores de tipo. Esto agrega una capa adicional de confianza al equipo para evolucionar el Sistema de Diseno y evitar errores en componentes de aplicación.&lt;/p&gt;
&lt;p&gt;El modelo mental de Vanilla Extract es diferente a Tailwind ya que este último te guía a construir toda la aplicación a partir de claes de utilidad mientras que el primero te lleva a definir clases atómicas para los estilos más comunes y recurrir a `CSS in TS para complementar los estilos más complejos. En la práctica esta mezcla permite aprovechar el modelo de composición de React y partir de unos componentes base que reciben propiedades de bajo nivel y reutilizarlos para construir componentes más complejos.&lt;/p&gt;
&lt;p&gt;Vanilla Extract nos permitió definir componentes de bajo nivel que se puedan reutilizar para construir unos más complejos. &lt;a href=&quot;https://github.com/TheMightyPenguin/dessert-box&quot;&gt;Dessert Box&lt;/a&gt; fue una gran librería para exponer las variables del tema en un cpomponente genérico &lt;code&gt;Box&lt;/code&gt; o &lt;code&gt;Div&lt;/code&gt;. Con estos bloques base pudimos desarrollar nuevos elementos como &lt;code&gt;Flex&lt;/code&gt;, &lt;code&gt;Grid&lt;/code&gt;, &lt;code&gt;Card&lt;/code&gt;, &lt;code&gt;Button&lt;/code&gt; y otros.&lt;/p&gt;
&lt;p&gt;Estos componentes podían ser utilizados en nuevas funcionalidades o incluso para reimplementar algunos componentes base escritos con Tailwind. Ya que Vanilla Extract tiene soporte para variantes, pudimos simplificar la lógica condicional para aplicar clases según las propiedades del componente. Si el componente es particularmente compleja, los archivos &lt;code&gt;.css.ts&lt;/code&gt; son un lugar natural para alojar la lógica de estilos.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Las cosas no tan buenas&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Anteriormente mencioné el problema de lidiar con PurgeCSS para crear clases dinámicamente. La comparación es un poco injusta, ya que Vanilla Extract no tiene ningún tipo de limpieza de CSS inutilizado. La diferencia es que Vanilla Extract no pretende que &lt;em&gt;toda&lt;/em&gt; la aplicación sea escrita con clases de utilidad sino las propiedades más comunes, por lo que en la práctica es poco probable que el tamaño del CSS atómico sea problemático.&lt;/p&gt;
&lt;p&gt;Aunque una de las propuestas iniciales de valor de Vanilla Extract es que es más cercano a CSS que otras librerías, haciéndolo más accesible para diseñadores (como SASS), todavía es muy temprano para saber si ese será el caso. Hasta ahora los archivos de &lt;code&gt;css.ts&lt;/code&gt; parecen más Typescript que una hoja de estilos. Contar con el poder de un lenguaje de programación es una ventaja para desarrolladores pero idealmente la responsabilidad del sistema de diseño se comparte con el equipo de diseñadores y estos estilos se pueden sentir un poco abrumadores al principio:&lt;/p&gt;
&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Un ejemplo de un archivo css.ts.&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; step &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;styleVariants&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  active&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    color&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;white&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    background&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; vars&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;colors&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;purple-500&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  disabled&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    color&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; vars&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;colors&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;gray-400&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    background&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; vars&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;colors&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;gray-50&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; icon &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;style&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;style&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    transition&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;transform 75ms ease-in&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;atoms&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    height&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;6&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    width&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;6&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; openAnimation &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;style&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  transform&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;rotate(180deg)&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;La restricción más importante de Vanilla Extract hasta el momento es el límite de una variante por propiedad. En tailwind es posible tener varios modificadores para una propiedad, por ejemplo es posible escribir &lt;code&gt;hover:text-lg&lt;/code&gt; y &lt;code&gt;md:text-lg&lt;/code&gt; al mismo tiempo. Con Sprinkles estamos limitados a escoger una de las dos condiciones para las variantes de clases de utilidad. Sin embargo, como mencioné anteriormente, Vanilla Extract no fomenta el uso de utilidades para todos los estilos por lo que la forma de evitar este problema, de nuevo, es usando los archivos &lt;code&gt;css.ts&lt;/code&gt; en estos casos.&lt;/p&gt;
&lt;h2 id=&quot;conclusiones&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://gafemoyano.com/es/posts/from-tailwind-to-vanilla-extract/#conclusiones&quot;&gt;Conclusiones&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Este artículo no pretende ser una crítica contra Tailwind. Aunque sabíamos desde el principio que podía no ser la mejor elección para construir una librería de componentes, nos permitió construir la primera iteración del producto dado el tamaño del equipo y sus habilidades. De todas formas considero valioso escribir acerca de la experiencia de enfrentarse a las limitaciones que se pueden encontrar con estas librerías en el contexto de un producto real ya que muchas veces los artículos comparativos se enfocan en casos de uso superficiales que no alcanzan a dar una foto completa de las ventajas y desventajas de una librería.&lt;/p&gt;
&lt;p&gt;En una nota personal, estoy muy impresionado con Vanilla Extract y considero que la estaré usando como default en proyectos que ameriten una arquitectura de UI basada en componentes, mientras reservo a Tailwind como herramienta por defecto para sitios web que generan el HTML desde el servidor. Siento que Vanilla Extract aún está en sus inicios y me emociona ver como evoluciona a futuro y se integra con nuevos tecnologías como Web Components.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Tailwind: más que un framework de CSS</title>
    <link href="https://gafemoyano.com/es/posts/tailwind-more-than-a-css-library/" />
    <updated>2021-12-20T00:00:00Z</updated>
    <id>https://gafemoyano.com/es/posts/tailwind-more-than-a-css-library/</id>
    <content type="html">&lt;h2 id=&quot;intro&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://gafemoyano.com/es/posts/tailwind-more-than-a-css-library/#intro&quot;&gt;Intro&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Hace un buen tiempo que he sido un fiel usuario de &lt;a href=&quot;https://tailwindcss.com&quot;&gt;Tailwind&lt;/a&gt; y siento que en los últimos días se ha disparado en popularidad en el mundo del desarrollo web. Es una librería excelente y me encanta verla triunfar dado el mucho valor que le he sacado personalmente. Entre tanto se han publicado varios artículos acerca de sus ventajas y desventajas del lado técnico, sin embargo muchas de las razones por las cuales me siento tan adepto (incluso agradecido) a Tailwind son más personales que otra cosa y quisiera compartirlas en este artículo.&lt;/p&gt;
&lt;h2 id=&quot;como-empez%C3%B3-todo&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://gafemoyano.com/es/posts/tailwind-more-than-a-css-library/#como-empez%C3%B3-todo&quot;&gt;Como empezó todo&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;En el 2016 estaba desarrollando una nueva aplicación web para consumidores con mis socios en &lt;a href=&quot;https://savy.co&quot;&gt;Savy&lt;/a&gt;. En ese entonces no tenía mucha idea de como hacer una página visualmente agradable pero sabía que &lt;a href=&quot;https://getbootstrap.com&quot;&gt;Bootstrap&lt;/a&gt; era un buen punto de partida para tener unos textos y colores por defecto junto a algunos componentes de UI. Rápidamente me topé con escenarios en los que quería modificar los estilos por defecto de algunos componentes como la barra de navegación y las tarjetas. Para mi agradable sorpresa, esta versión de Bootstrap incluía algunas clases de utilidad, como &lt;code&gt;mb-5&lt;/code&gt;, &lt;code&gt;px-4&lt;/code&gt; and &lt;code&gt;bg-primary&lt;/code&gt; que permitían personalizar los componentes sin salir del HTML. Sumando a esto otras clases para manejar color, posicionamiento y presentación me dí cuenta que podía lograr el &lt;code&gt;80%&lt;/code&gt; de lo que necesitaba para maquetación sin escribir CSS adicional.&lt;/p&gt;
&lt;p&gt;Desde ese momento quedé enganchado en usar clases de utilidad, pero quería llegar más allá ese &lt;code&gt;80%&lt;/code&gt;, quería poder hacer mucho más con clases atómicas, lo que últimamente me llevó a Tailwind v0.7 (si no estoy mal). Así empezó una de las relaciones más largas que he tenido en mi carrera con una librería, pero el cambio más importante para mi aún estaba por descubrirlo, gracias a Tailwind. El inicio de mi amor por el Diseño Web.&lt;/p&gt;
&lt;h2 id=&quot;m%C3%A1s-que-una-librer%C3%ADa&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://gafemoyano.com/es/posts/tailwind-more-than-a-css-library/#m%C3%A1s-que-una-librer%C3%ADa&quot;&gt;Más que una librería&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Si bien es indiscutible que hay una cantidad inmensa de trabajo detrás de Tailwind, la librería, también es cierto que hay tanto o más trabajo detrás del ecosistema de recursos que construyeron sus creadores. Desde la excelente [documentación de la librería] (&lt;a href=&quot;https://tailwindcss.com&quot;&gt;https://tailwindcss.com&lt;/a&gt;), &lt;a href=&quot;https://www.youtube.com/c/AdamWathan&quot;&gt;los streams&lt;/a&gt; donde construyen UIs complejas con Tailwind, &lt;a href=&quot;https://twitter.com/i/events/879086180909764608?lang=en&quot;&gt;los tips de diseño&lt;/a&gt; en Twitter y hasta un &lt;a href=&quot;https://www.refactoringui.com/book&quot;&gt;libro de diseño para desarrolladores&lt;/a&gt; se encargaron de crear todo un ecosistema de herramientas y materiales de aprendizaje para triunfar no solo en el uso de la herramienta sino en Diseño Web como disciplina.&lt;/p&gt;
&lt;h3 id=&quot;me-ense%C3%B1%C3%B3-las-primitivas-de-css&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://gafemoyano.com/es/posts/tailwind-more-than-a-css-library/#me-ense%C3%B1%C3%B3-las-primitivas-de-css&quot;&gt;Me enseñó las primitivas de CSS&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Para este punto llevaba usando y escribiendo CSS por años, pero creo que nunca lo había &lt;em&gt;entendido&lt;/em&gt; realmente. Debo agradecerle a la documentación de Tailwind finalmente haber entendido &lt;code&gt;flexbox&lt;/code&gt; y todas sus propiedades. Los fragmentos de código y ejemplos visuales me ayudaron tremendamente a crear el modelo mental y retenerlo. No sucedió de un día para otro, pero poco a poco la necesidad de tener la documentación abierta como referencia me obligó a aprender las primitivas necesarias para implementar un diseño.&lt;/p&gt;
&lt;p&gt;Esto fue un punto de quiebre para mi. Tener la habilidad de ver un diseño y descomponer las distintas propiedades necesarias para implementarlo abrió, de repente, todo un mundo de posibilidades de exploración y creatividad.&lt;/p&gt;
&lt;h3 id=&quot;permiti%C3%B3-apropiarme-de-los-estilos-de-mis-aplicaciones&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://gafemoyano.com/es/posts/tailwind-more-than-a-css-library/#permiti%C3%B3-apropiarme-de-los-estilos-de-mis-aplicaciones&quot;&gt;Permitió apropiarme de los estilos de mis aplicaciones&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Anteriormente cambiar los estilos de una aplicación existente se sentía completamente intimidante. No era sólo la falta de conocimiento, hacer cambios en hojas de estilos podía tener resultados muy inesperados. En consecuencia, siempre tuve la sensación de estar adivinando y esperando que no ocurriera nada que afectara el diseño en un lugar sorpresivo. Con Tailwind, a pesar de que en principio puede ser difícil interpretar elementos HTML con muchas clases, tenemos la seguridad que los cambios que se realizan afectan únicamente los elementos que están en la plantilla. Por lo tanto, eres libre de editar todo lo que quieras sin temor de dañar el diseño de la aplicación.&lt;/p&gt;
&lt;h3 id=&quot;me-hizo-m%C3%A1s-productivo&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://gafemoyano.com/es/posts/tailwind-more-than-a-css-library/#me-hizo-m%C3%A1s-productivo&quot;&gt;Me hizo más productivo&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Personalmente valoro mucho el flujo de trabajo de Tailwind y la forma en la que permite implementar diseños sin saltar entre archivos. Tener retroalimentación casi instantánea de los estilos evitando tener que cambiar entre archivos de HTML y CSS es fantástico tanto para experimentar con un nuevo diseño como para implementar uno existente desde una herramienta como figma. También elimina la necesidad de crear nombres para cada clase o componente HTML lo que permite concentrarse únicamente en los estilos. Aunque es cierto que las plantillas se pueden congestionar un poco de clases, Tailwind promueve la idea de extraer parciales/componentes en sus propios archivos lo que permite enfocarse en un elemento a la vez.&lt;/p&gt;
&lt;h3 id=&quot;me-ense%C3%B1%C3%B3-fundamentos-de-dise%C3%B1o&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://gafemoyano.com/es/posts/tailwind-more-than-a-css-library/#me-ense%C3%B1%C3%B3-fundamentos-de-dise%C3%B1o&quot;&gt;Me enseñó fundamentos de diseño&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Esta parte corresponde más al ecosistema de Tailwind que a la librería en sí, pero seguir a sus creadores y promotores iniciales me enseñó muchos conceptos básicos de diseño que jamás había aprendido como desarrollador. Los numerosos tips de Steve en &lt;a href=&quot;https://twitter.com/i/events/994601867987619840&quot;&gt;twitter&lt;/a&gt; me ayudaron a entender los principios de diseño para construir interfaces gráficas funcionales y agradables. Adicionalmente los &lt;em&gt;screencasts&lt;/em&gt; de Adam en &lt;a href=&quot;https://www.youtube.com/c/AdamWathan&quot;&gt;youtube&lt;/a&gt; eran una lección de como implementar un gran diseño en Tailwind y, al ser una herramienta de bajo nivel, en CSS. Estos recursos fueron invaluables en mi trayectoria como desarrollador web y eventualmente como emprendedor.&lt;/p&gt;
&lt;h3 id=&quot;me-mostr%C3%B3-el-valor-de-un-sistema-de-dise%C3%B1o&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://gafemoyano.com/es/posts/tailwind-more-than-a-css-library/#me-mostr%C3%B3-el-valor-de-un-sistema-de-dise%C3%B1o&quot;&gt;Me mostró el valor de un sistema de diseño&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Más o menos, este punto puede sonar un poco forzado. La idea de un Sistema de Diseño va mucho más allá que los tokens, además el framework ha crecido mucho desde los días de la versión 0.7 donde el set limitado de utilidades se podía considerar una ventaja para la consistencia. Sin embargo, la configuración por defecto de Tailwind te encamina en la dirección correcta: tener tokens para los espacios, colores, bordes y tipografías con valores por defecto excelentes son un punto de partida excelente para cualquier aplicación. No considero que Tailwind por sí solo cuente como un sistema de diseño (tampoco es uno de sus objetivos) pero definitivamente es un primer paso.&lt;/p&gt;
&lt;h2 id=&quot;el-futuro&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://gafemoyano.com/es/posts/tailwind-more-than-a-css-library/#el-futuro&quot;&gt;El futuro&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Probablemente seguiré usando Tailwind en mis proyectos personales. La librería sigue empujando los límites de la técnica del CSS atómico y clases de utilidad. Sin embargo, este espacio de CSS está en flujo constante y nuevos paradigmas y herramientas emergen cada año a medida que la plataforma web evoluciona. La adopción de variables CSS por los navegadores principales dieron paso a nuevas librerías como &lt;a href=&quot;https://vanilla-extract.style&quot;&gt;Vanilla Extract&lt;/a&gt; o &lt;a href=&quot;https://stitches.dev/&quot;&gt;Stitches&lt;/a&gt;las cuales se ven prometedoras para el desarrollo de Sistemas de Diseño. Después de todo, sigue siendo una época emocionante para ser un desarrollador web.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>UUIDs con Action Text y Active Storage</title>
    <link href="https://gafemoyano.com/es/posts/uuids-with-action-text-and-active-storage/" />
    <updated>2021-04-11T00:00:00Z</updated>
    <id>https://gafemoyano.com/es/posts/uuids-with-action-text-and-active-storage/</id>
    <content type="html">&lt;p&gt;En este artículo aprenderemos como usar UUIDs por defecto para las llaves primarias de nuestra aplicación Rails usando Postgres, Active Storage y Action Text.&lt;/p&gt;
&lt;p&gt;Hace un tiempo Rails sacudió el mundo del desarrollo web mostrándo el poder de la &amp;quot;convención&amp;quot; sobre &amp;quot;configuración&amp;quot;. Una de estas convenciones fue que cada tabla tendría una columna &lt;code&gt;id&lt;/code&gt; que sería numérica y auto incremental. En ese entonces fue un cambio bienvenido y nos liberó de decisiones de cómo debíamos llamar nuestras columnas de primary keys o si debíamos usar llaves naturales o sustitutas.&lt;/p&gt;
&lt;p&gt;Con el tiempo vale la pena revisar algunas de las convenciones que se adoptaron originalmente, en este caso, la decisión de usar enteros auto incrementales. Si bien siguen siendo una buena decisión para una gran variedad de aplicaciones hoy en día, se puede argumentar que usar UUIDs es un mejor punto de partida dados los siguientes puntos:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;No exponen información interna de tu sistema&lt;/li&gt;
&lt;li&gt;Hacen imposible acceder a modelos&lt;/li&gt;
&lt;li&gt;Le dan la independencia al frontend de crear nuevos IDs de registro sin necesidad de hacer un viaje de ida y vuelta a la base de datos.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;También vale la pena listar algunas de las cosas que se sacrifican al usar UUIDs por default:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;La conveniencia de acceder modelos fácilmente en producción para debugging o tareas esporádicas.&lt;/li&gt;
&lt;li&gt;Lógica de aplicación que maneje casos especiales (no es una buena práctica, pero lo he visto suficientes veces para que valga la pena mencionarlo)&lt;/li&gt;
&lt;li&gt;Asumir una pequeña, pero existente, posibilidad de que se generen conflictos de llaves PKs.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Teniendo en cuenta las consideraciones previas, pienso que vale la pena usar UUIDs por defecto en mis nuevas aplicaciones Rails. Últimamente he estado trabajando en un tablero de empleos en tecnología y quisiera enviar links mágicos que permitan editar los registros sin requerir la creación de cuentas de usuario. Usar una acción de &lt;strong&gt;edit&lt;/strong&gt; por defecto con IDs secuenciales está fuera de consideración por motivos de seguridad, por lo que tendría que generar tokens automáticamente. Sin embargo, usando UUIDs la implementación de esta funcionalidad sería trivial.&lt;/p&gt;
&lt;h2 id=&quot;cambiar-el-default-a-uuids-en-activerecord&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://gafemoyano.com/es/posts/uuids-with-action-text-and-active-storage/#cambiar-el-default-a-uuids-en-activerecord&quot;&gt;Cambiar el default a UUIDs en ActiveRecord&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Ya existe un &lt;a href=&quot;https://pawelurbanek.com/uuid-order-rails&quot;&gt;excelente artículo en inglés&lt;/a&gt; cubriendo los pasos para cambiar una aplicación de Rails 6 y Postgres. Si quieres conocer más en detalle lo que está pasando por detrás para que estos cambios funcionen, recomiendo darle una revisada o si deseas saber cómo migrar tablas existentes en producción a UUIDs. En este artículo, nos enfocaremos en los pasos necesarios para configurar una aplicación nueva.&lt;/p&gt;
&lt;p&gt;Lo primero es habilitar la extensión &lt;code&gt;pgcrypto&lt;/code&gt; de Postgres. Para esto, debemos crear una nueva migración.&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;bin/rails g migration enable_pgcrypto&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;EnablePgcypto&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; ActiveRecord&lt;span class=&quot;token double-colon punctuation&quot;&gt;::&lt;/span&gt;Migration&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;6.0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;change&lt;/span&gt;&lt;/span&gt;
    enable_extension &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&#39;pgcrypto&#39;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A continuación, debemos decirle a Rails que cambie el tipo de llave que se va usar en los generadores de Active Record.&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;Rails&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;application&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;config&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;generators &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;g&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;
  g&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;orm &lt;span class=&quot;token symbol&quot;&gt;:active_record&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;primary_key_type&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:uuid&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;En Rails 6 hay una nueva opción de configuración que nos permite controlar la columna por defecto utilizada para ordenar los registros de nuestras tablas. Normalmente usa los IDs, sin embargo al usar UUIDs obtendríamos resultados distintos cada vez lo cuál podría ser confuso para nuestros usuarios, además de que afectaría el comportamiento de métodos como &lt;code&gt;first&lt;/code&gt; y &lt;code&gt;last&lt;/code&gt;. Para esto, podemos sobre escribir el atributo &lt;code&gt;implicit_order_column&lt;/code&gt; de nuestro &lt;code&gt;ApplicationRecord&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ApplicationRecord&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; ActiveRecord&lt;span class=&quot;token double-colon punctuation&quot;&gt;::&lt;/span&gt;Base
  &lt;span class=&quot;token keyword&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;abstract_class &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;implicit_order_column &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;created_at&quot;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Si todo está funcionando correctamente podríamos generar una nueva migración y validar la salida del comando. Siguiendo el ejemplo de una aplicación de empleos con sus respectivos anuncios:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;bin/rails generate model Listing job:references published:boolean&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;La migración resultante de la operación debería verse algo así&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CreateListings&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; ActiveRecord&lt;span class=&quot;token double-colon punctuation&quot;&gt;::&lt;/span&gt;Migration&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;6.1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;highlight-line&quot;&gt;  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;change&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;highlight-line&quot;&gt;    create_table &lt;span class=&quot;token symbol&quot;&gt;:listings&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:uuid&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;t&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;&lt;/span&gt;
&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt;      t&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;references &lt;span class=&quot;token symbol&quot;&gt;:job&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;foreign_key&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:uuid&lt;/span&gt;&lt;/mark&gt;
&lt;span class=&quot;highlight-line&quot;&gt;      t&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;boolean &lt;span class=&quot;token symbol&quot;&gt;:published&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;highlight-line&quot;&gt;      t&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;timestamps&lt;/span&gt;
&lt;span class=&quot;highlight-line&quot;&gt;    &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;highlight-line&quot;&gt;  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Como podemos observar, es necesario que las migraciones especifiquen el tipo de columna de las llaves primarias y foráneas. Ya que es fácil olvidar agregar estos parámetros adicionales, mi recomendación es usar los generadores de rails tanto como sea posible y así evitar errores manuales.&lt;/p&gt;
&lt;h2 id=&quot;instalar-active-storage&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://gafemoyano.com/es/posts/uuids-with-action-text-and-active-storage/#instalar-active-storage&quot;&gt;Instalar Active Storage&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Ahora que hemos cambiado las opciones por default, podemos continuar con la instalación de Active Storage. Si quieres revisar los detalles completos de configuración puedes revisar la guía oficial &lt;a href=&quot;https://edgeguides.rubyonrails.org/active_storage_overview.html&quot;&gt;aquí&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Para instalar Active Storage ejecutamos el comando:&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;bin/rails active_storage:install&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Este va a generar un par de archivos de migración, los cuales tenemos que ajustar de forma similar al ejemplo de empleos y anuncios, asegurándonos que los &lt;code&gt;id&lt;/code&gt;s y referencias a otra tables incluyan el tipo de dato &lt;code&gt;uuid&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CreateActiveStorageTables&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; ActiveRecord&lt;span class=&quot;token double-colon punctuation&quot;&gt;::&lt;/span&gt;Migration&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;5.2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;highlight-line&quot;&gt;  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;change&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;highlight-line&quot;&gt;    create_table &lt;span class=&quot;token symbol&quot;&gt;:active_storage_blobs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:uuid&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;t&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;&lt;/span&gt;
&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt;      t&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;string   &lt;span class=&quot;token symbol&quot;&gt;:key&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;        &lt;span class=&quot;token symbol&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;/mark&gt;
&lt;span class=&quot;highlight-line&quot;&gt;      t&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;string   &lt;span class=&quot;token symbol&quot;&gt;:filename&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;   &lt;span class=&quot;token symbol&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;highlight-line&quot;&gt;      t&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;string   &lt;span class=&quot;token symbol&quot;&gt;:content_type&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;highlight-line&quot;&gt;      t&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;text     &lt;span class=&quot;token symbol&quot;&gt;:metadata&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;highlight-line&quot;&gt;      t&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;bigint   &lt;span class=&quot;token symbol&quot;&gt;:byte_size&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;  &lt;span class=&quot;token symbol&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;highlight-line&quot;&gt;      t&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;string   &lt;span class=&quot;token symbol&quot;&gt;:checksum&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;   &lt;span class=&quot;token symbol&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;highlight-line&quot;&gt;      t&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;datetime &lt;span class=&quot;token symbol&quot;&gt;:created_at&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;highlight-line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;highlight-line&quot;&gt;      t&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;index &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:key&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;unique&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;highlight-line&quot;&gt;    &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;highlight-line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;highlight-line&quot;&gt;    create_table &lt;span class=&quot;token symbol&quot;&gt;:active_storage_attachments&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:uuid&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;t&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;&lt;/span&gt;
&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt;      t&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;string     &lt;span class=&quot;token symbol&quot;&gt;:name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;     &lt;span class=&quot;token symbol&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;/mark&gt;
&lt;span class=&quot;highlight-line&quot;&gt;      t&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;references &lt;span class=&quot;token symbol&quot;&gt;:record&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;   &lt;span class=&quot;token symbol&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;polymorphic&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;index&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:uuid&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;highlight-line&quot;&gt;      t&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;references &lt;span class=&quot;token symbol&quot;&gt;:blob&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;     &lt;span class=&quot;token symbol&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:uuid&lt;/span&gt;&lt;/span&gt;
&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt;&lt;/mark&gt;
&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt;      t&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;datetime &lt;span class=&quot;token symbol&quot;&gt;:created_at&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;/mark&gt;
&lt;span class=&quot;highlight-line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;highlight-line&quot;&gt;      t&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;index &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:record_type&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:record_id&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:blob_id&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;index_active_storage_attachments_uniqueness&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;unique&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;highlight-line&quot;&gt;      t&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;foreign_key &lt;span class=&quot;token symbol&quot;&gt;:active_storage_blobs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;column&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:blob_id&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;highlight-line&quot;&gt;    &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;highlight-line&quot;&gt;  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;instalar-action-text&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://gafemoyano.com/es/posts/uuids-with-action-text-and-active-storage/#instalar-action-text&quot;&gt;Instalar Action Text&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Para este parte seguimos los mismos pasos del punto anterior. Ejecutamos el comando de instalación y ajustamos las migraciones según corresponda.&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;bin/rails action_text:install&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CreateActionTextTables&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; ActiveRecord&lt;span class=&quot;token double-colon punctuation&quot;&gt;::&lt;/span&gt;Migration&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;6.0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;highlight-line&quot;&gt;  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;change&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;highlight-line&quot;&gt;    create_table &lt;span class=&quot;token symbol&quot;&gt;:action_text_rich_texts&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:uuid&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;t&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;highlight-line&quot;&gt;      t&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;string     &lt;span class=&quot;token symbol&quot;&gt;:name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;highlight-line&quot;&gt;      t&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;text       &lt;span class=&quot;token symbol&quot;&gt;:body&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:long&lt;/span&gt;&lt;/span&gt;
&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt;      t&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;references &lt;span class=&quot;token symbol&quot;&gt;:record&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;polymorphic&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;index&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:uuid&lt;/span&gt;&lt;/mark&gt;
&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt;&lt;/mark&gt;
&lt;span class=&quot;highlight-line&quot;&gt;      t&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;timestamps&lt;/span&gt;
&lt;span class=&quot;highlight-line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;highlight-line&quot;&gt;      t&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;index &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:record_type&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:record_id&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:name&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;index_action_text_rich_texts_uniqueness&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;unique&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;highlight-line&quot;&gt;    &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;highlight-line&quot;&gt;  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Para completar la instalación de Action Text &lt;em&gt;no olvides&lt;/em&gt; completar los demás pasos descritos &lt;a href=&quot;https://edgeguides.rubyonrails.org/action_text_overview.html#installation&quot;&gt;aquí&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;conclusiones&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://gafemoyano.com/es/posts/uuids-with-action-text-and-active-storage/#conclusiones&quot;&gt;Conclusiones&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Espero que esta guía sirva de referencia para lanzar una nueva aplicación de Rails usando UUIDs, Postgres, Action Text y Active Storage. Es un gran momento para iniciar nuevos proyectos en el mundo de Ruby on Rails y prácticamente todas las aplicaciones web que construyo hoy en día usan este par de gemas en algún punto. Si bien algunas configuraciones por defecto de Rails pueden ser debatibles, es bastante fácil sobre escribirlas y reemplazarlas. Como siempre, si hay alguna duda o comentario respecto al artículo y el procedimiento descrito me pueden contactar por &lt;a href=&quot;https://twitter.com/gafemoyano&quot;&gt;twitter&lt;/a&gt; o enviándome un correo a &lt;a href=&quot;mailto:felipe@gafemoyano.com&quot;&gt;felipe@gafemoyano.com&lt;/a&gt;.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>No vale la pena arreglar cada bug</title>
    <link href="https://gafemoyano.com/es/posts/not-every-bug-is-worth-fixing/" />
    <updated>2021-02-27T00:00:00Z</updated>
    <id>https://gafemoyano.com/es/posts/not-every-bug-is-worth-fixing/</id>
    <content type="html">&lt;p&gt;En los primeras días de Savy, dónde aún estábamos construyendo lo que sería el equipo de tecnología y yo aún escribía la mayoría del código, tomé una posición un tanto agresiva en contra de arreglar bugs. Cada vez que uno de mis socios se acercaba a mencionarme sobre algún nuevo error y si podría darle una mirada rápido y arreglarlo, mi respuesta por defecto era &lt;strong&gt;no&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Acto seguido, la conversación tomaría el siguiente curso:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;¿Cómo así que no lo vas a arreglar? un usuario acaba de enviarme esta captura de pantalla del error ¡debemos arreglarlo antes que le suceda a alguien más!&lt;/li&gt;
&lt;li&gt;¿Por qué? respondía yo, sólo le ha ocurrido a esta persona por ahora. Esperemos a verificar que le ocurra a alguien más, o incluso a varias personas. De ahí podemos decidir si &lt;strong&gt;vale la pena&lt;/strong&gt; el esfuerzo de arreglarlo.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Podría afirmar que el 90% de las veces no era urgente. No es que el bug se hubiera desaparecido, por su puesto, simplemente no era &lt;strong&gt;tan&lt;/strong&gt; grave para que tuviera que atenderlo en &lt;em&gt;ese preciso momento&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Como co-fundador estaba en una posición privilegiada para decir &lt;strong&gt;no&lt;/strong&gt; y desafiar la expectativa de arreglar bugs tan pronto como fuera posible. Pero a medida que el equipo fue creciendo, quise extenderles a ellos el mismo poder. Por defecto, los bugs no serían considerados una emergencia. En mi opinión, esa política nos permitió mantenernos enfocados en proyectos grandes y ambiciosos, así eso significara que los bugs iban a tener que esperar un poco en ser arreglados, si es que lograban convertirse en bugs &lt;em&gt;dignos&lt;/em&gt; de arreglarse.&lt;/p&gt;
&lt;p&gt;En mi experiencia previa en la industria de software, los bugs siempre eran tratados como emergencias. La realidad es que son pocos los que merecen algún tipo acción inmediata. En mi opinión, hay dos argumentos para soportar esta afirmación. El primero es el costo de arreglar bugs es alto, mucho más alto de lo que parece a primera vista. El segundo, es que la mayoría de las veces los bugs &lt;strong&gt;no son tan graves&lt;/strong&gt;.&lt;/p&gt;
&lt;h2 id=&quot;bug%3A-una-palabra-incomprendida&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://gafemoyano.com/es/posts/not-every-bug-is-worth-fixing/#bug%3A-una-palabra-incomprendida&quot;&gt;Bug: una palabra incomprendida&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Cuándo desarrollamos un producto digital van a existir bugs. Esto es algo &lt;a href=&quot;https://m.signalvnoise.com/software-has-bugs-this-is-normal/&quot;&gt;natural de todo software&lt;/a&gt;. No obstante, hemos creado expectativas irreales en cuanto a la calidad del mismo. Como consumidores, actuamos con soberbia cada vez que encontramos un error en una aplicación. Y como miembros de la industria, como si cada error fuera un escándalo que debe ser arreglado tan pronto como sea posible, sino antes.&lt;/p&gt;
&lt;p&gt;¿Cómo definir un bug? El problema de esta definición es que no creo que exista una única respuesta y, lo que es peor, el significado seguramente cambia según el rol que desempeñas, por lo que la misma definición no aplica para desarrolladores, analistas de calidad o gerentes de proyecto. En mi opinión, este malentendido viene de la tendencia actual de la industria de crear silos especializados de habilidades técnicas en lugar de tener grupos multidisciplinarios responsables por producto o funcionalidad de este. Es difícil a nivel organizacional ponerse de acuerdo en la severidad de un bug y discutir las ven tajas y desventajas de arreglarlo si cada equipo tiene una definición diferente en mente y, peor aún, métricas de éxito diferentes que no se alineen con el alcance del proyecto.&lt;/p&gt;
&lt;p&gt;Si estás en proceso de construir un nuevo producto seguramente estás desarrollando nuevos features para completar la visión del producto, probando algunas nuevas ideas para validar la viabilidad del mercado. Esto significa que estás escribiendo más código y, por lo tanto, creando nuevos bugs. ¡Y eso no tiene ningún problema! todo lo contrario, es parte del proceso. No tiene ningún sentido pulir una aplicación a la perfección si aún no has validado que existe un mercado para esta. Lo que importa es que estés progresando en el desarrollo del producto y resolviendo las necesidades de tus usuarios. Pero nadie en el equipo debería estar pasando noches en vela intentando mantener limpio un creciente backlog de errores.&lt;/p&gt;
&lt;p&gt;Sí, seguramente llegará una situación en la que un bug terrible aparece y un flujo importante de la aplicación se ve afectado, lo que será una verdadera emergencia de &lt;strong&gt;código rojo&lt;/strong&gt; y todo el mundo tendrá que poner las manos a la obra en solucionarlo. Pero estas situaciones son la &lt;em&gt;excepción&lt;/em&gt; no la regla. No puedes permitir que cada vez que un usuario se encuentra con un error en la aplicación el equipo entre en modo de pánico.&lt;/p&gt;
&lt;p&gt;En resumen, no todos los bugs son iguales. Algunos aparecen en circunstancias muy específicas o a una pequeña cantidad de usuarios. Otros viven en algunos flujos de aplicación poco frecuentados o en funcionalidades de poco impacto. Así que en vez de correr a arreglarlos, es mejor esperar. Revisar los dashboards y los datos de usabilidad. Lo más posible es que la mayoría de los usuarios estén cumpliendo con su objetivo sin ningún problema.&lt;/p&gt;
&lt;h2 id=&quot;el-verdadero-costo-de-arreglar-un-bug&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://gafemoyano.com/es/posts/not-every-bug-is-worth-fixing/#el-verdadero-costo-de-arreglar-un-bug&quot;&gt;El verdadero costo de arreglar un bug&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;En general, existe una tendencia de subestimar el &lt;strong&gt;costo de las interrupciones y el cambio de contexto&lt;/strong&gt;. Esto aplica para reuniones, llamadas, mensajes directos y, por su puesto, reportes de bugs. Hagamos el ejercicio de reflexionar cuánto tiempo tardas en reunir el contexto y la concentración cuando estás trabajando en una tarea que requiera de mucho enfoque. Por ejemplo, escribir un email sensible, codificar o diseñar una interfaz de usuario. Para muchas personas apostaría que toma al menos 30 minutos reunir el contexto necesario de la tarea y retomar donde habías quedado la última vez más otros 30 minutos para entrar en estado de flujo y empezar a progresar. Si a la hora que empiezas a ser productivo, alguien te toca el hombro (sea físicamente o por slack) y te pide que le pongas atención a otra cosa, esa torre de naipes en la mente y el tiempo que tomó construirla no generó ningún avance al final del día. La próxima vez que te sientes que en problema, tendrás que empezar de nuevo. Imagínate que esto suceda dos veces o más a la semana y de repente no te sorprenderías tanto que tu o tu equipo sientan que están constantemente atrasados en sus proyectos.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;¿Por qué deberían los bugs saltarse al frente de la cola?&lt;/strong&gt; Si invertimos una cantidad considerable de tiempo en planear nuestros proyectos, en priorizar las tareas y establecer objetivos ambiciosos, ¿por qué tirarlo todo por la ventana cuando se trata de un bug? ¿Qué los hace más importantes que las otras tareas que están en la lista de prioridades? Al final del día, también son trabajo que va a consumir horas de la persona que los realice. Como tal, deberían ponerse en la balanza con las demás tareas y decidir si, efectivamente, tienen la importancia necesaria en lugar de saltar inmediatamente al frente.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Los bugs pueden conducir a agujeros de conejo&lt;/strong&gt;. Muchas veces la causa de un bug no es fácil ni obvia de encontrar, mucho menos la mejor forma de arreglarlo en caliente. Incluso una vez identificado el problema, puede que no exista una solución fácil para el mismo. Es importante tener esto en cuenta para la próxima vez que sientas el impulso de perseguir un bug. Como mínimo, recomendaría tener una cota de tiempo estricta para hacer el diagnóstico.&lt;/p&gt;
&lt;h2 id=&quot;el-problema-es-cultural%2C-no-t%C3%A9cnico&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://gafemoyano.com/es/posts/not-every-bug-is-worth-fixing/#el-problema-es-cultural%2C-no-t%C3%A9cnico&quot;&gt;El problema es cultural, no técnico&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Cuando se trata de tener buenas dinámicas de equipo y cultura organizacional no existe una &lt;a href=&quot;http://worrydream.com/refs/Brooks-NoSilverBullet.pdf&quot;&gt;receta maestra&lt;/a&gt;. En mi opinión, cada equipo debería evaluar distintas metodologías y ajustarlas según lo que mejor funcione. En consecuencia, muchos de los aprendizajes de este artículo están derivados de mis propias experiencias y experimentos en compañías tipo &lt;em&gt;start up&lt;/em&gt; y seguramente es en donde tendría más sentido aplicarlas. Sin embargo, me at revería a decir que estas lecciones aplican a equipos de ingeniería en cualquier contexto. Si el objetivo es lograr que los equipos sean productivos y logren los resultados esperados a tiempo, deben ser capaces de ejecutar sus tareas sin tener distracciones constantes. Un equipo que está trabajando en problemas retadores y progresando en su misión, seguramente se mantendrá motivado.&lt;/p&gt;
&lt;p&gt;Tener una cultura donde cada bug tiene un temporizador encima para ser resuelto en el menor tiempo posible, impacta negativamente la productividad de un equipo y con el tiempo erosiona la cultura organizacional. Exploremos algunas razones del porqué:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Crea expectativas irreales para los equipos de tecnología.&lt;/strong&gt; En un principio parecerá que no hay mayor problema en lidiar con los errores a medida que van apareciendo. Cuándo una aplicación es relativamente pequeña y aún cabe en la cabeza de una sola persona seguramente será una tarea sencilla y directa. El problema es que no siempre va a ser así y ahora hay un precedente de inmediatez bastante peligroso. A medida que el código aumenta en complejidad e interdependencias, esas pequeñas tareas que tomaban unos pocos minutos se convertirán en tareas de unas cuantas horas. Sin embargo las expectativas de inmediatez y velocidad para el equipo seguirán igual. La mejor forma de prevenir esta situación, es con un &lt;strong&gt;no&lt;/strong&gt; comprensivo, pero firme ante cualquier trabajo con la etiqueta de &lt;em&gt;ahora mismo&lt;/em&gt;. Después de un tiempo, te darás cuenta que la mayoría de las veces no era tan urgente en primer lugar.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Incentiva malas prácticas de comunicación&lt;/strong&gt;. Así como los bugs tienen una tendencia a saltarse la cola de prioridades también tienden a saltarse los protocolos de comunicación. Como líder de equipo, más de una vez me encontré con personas trabajando en bugs porque un líder de otra área se los pidió. Los fundadores tienden a ser particularmente culpables de esta práctica. O tal vez alguien en servicio al cliente les pidió el favor de revisar un problema rápidamente. Personalmente, no me considero alguien que abogue por implementar procesos sobre buena comunicación y sentido común, pero he visto como este tipo de prácticas se salen de control hasta convertirse en un problema. Recuerdo un ejemplo de alguien que se estaba sobrecargando de trabajo porque era demasiado &lt;em&gt;cortés&lt;/em&gt; para decir que no ante estas solicitudes. No estoy diciendo que no se deban reportar bugs, al contrario, incentivaría a todas las áreas de una compañía a tomarse el tiempo de hacerlo apropiadamente. Pero dejemos atrás la expectativa que un reporte significa asignarle esa tarea a alguien y ponerle una fecha límite encima. Los reportes serán atendidos a su debido momento el cual, reitero, no es &lt;em&gt;ahora mismo&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Es desmoralizante para el equipo&lt;/strong&gt;. Consideremos por un minuto la ansiedad que produce tener notificaciones y recordatorios cada vez que aparece un nuevo bug en producción. De por sí en el mundo laboral actual ya estamos sufriendo los efectos psicológicos negativos de tener un flujo constante de notificaciones vía correo o aplicaciones de mensajería instantánea. Agregar bugs a esto puede inducir a sentimientos de culpa e incluso de depresión. Estas notificaciones pueden llegar a ser tan abrumadoras en volumen que algunos equipos terminan ignorándolas del todo. El ratio de señal a ruido es demasiado bajo. Incluso si el equipo aún está logrando controlar este flujo de notificaciones, a menos que existan personas dedicadas exclusivamente a esa tarea, seguramente los está atrasando considerablemente en su día a día. Y en ese caso, la pregunta es ¿por cuánto tiempo podrán sostener ese ritmo? Al final del día, no es una estrategia sostenible al mediano plazo y empezarán a quedarse cortos en sus entregas. Existe una correlación fuerte entre la &lt;a href=&quot;https://www.isixsigma.com/implementation/teams/high-performance-teams-understanding-team-cohesiveness/&quot;&gt;moral&lt;/a&gt; de un equipo y su capacidad de &lt;a href=&quot;https://journals.sagepub.com/doi/10.1177/0730888406290054&quot;&gt;cumplir con sus entregas&lt;/a&gt;. Si el balance se rompe en algún momento, empieza un espiral negativo que termina en un equipo descontento y poco motivado.&lt;/p&gt;
&lt;h2 id=&quot;%C2%BFcomo-garantizar-calidad-entonces%3F&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://gafemoyano.com/es/posts/not-every-bug-is-worth-fixing/#%C2%BFcomo-garantizar-calidad-entonces%3F&quot;&gt;¿Como garantizar calidad entonces?&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;En este punto espero haberte convencido que manejar bugs como una emergencia que debe ser atendida tan pronto como sea posible es una &lt;strong&gt;mala idea&lt;/strong&gt;. Decir que &lt;em&gt;no&lt;/em&gt; a los bugs le devolverá a tu equipo la libertad de enfocarse en resolver los retos que aparezcan durante el ciclo de desarrollo y entregar valor a los usuarios, sin embargo debemos lidiar con ellos en algún momento. Los puntos que resalté anteriormente no son una excusa para ignorarlos para siempre, sin embargo creo que existe una forma más sostenible e inteligente de lidiar con ellos.&lt;/p&gt;
&lt;h3 id=&quot;guardias-t%C3%A9cnicas&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://gafemoyano.com/es/posts/not-every-bug-is-worth-fixing/#guardias-t%C3%A9cnicas&quot;&gt;Guardias técnicas&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Las guardias técnicas son herramientas de tecnología que nos permiten prevenir errores, ayudarnos a tomar decisiones y detectar emergencias oportunamente. Tener la seguridad que los flujos &lt;em&gt;más&lt;/em&gt; importantes de una aplicación están funcionando para la &lt;em&gt;mayoría&lt;/em&gt; de los usuarios evitará una respuesta de pánico y nos dará argumentos para priorizar efectivamente.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Implementa pruebas de integración para los flujos más importantes&lt;/strong&gt;. Si tuviera que escoger sólo una estrategia para implementar de las que voy a describir a continuación, sería esta. Es una inversión que tendrá un retorno de diez veces fácilmente. Las pruebas de integración (o &lt;em&gt;end to end&lt;/em&gt;) no son fáciles ni de implementar ni de mantener. Pero tener la confianza de desplegar aplicaciones a producción sin miedo vale la pena y se convertirá en la última línea de defensa para prevenir que un error llegue a las manos de un usuario. Puede que esto suene obvio, pero la prevención es la forma más rápida y efectiva de lidiar con bugs.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Instala herramientas de monitoreo en el ambiente de producción&lt;/strong&gt;. Hay múltiples herramientas que permiten ver el estado de una aplicación en producción desde la infraestructura, errores, tiempos de respuesta, entre otros. Cada uno de estos tiene su propia utilidad por lo que depende de cada equipo decidir cuáles adoptar. Lo importante, sin embargo, es no obsesionarse con cada métrica o reporte de error, sino mantener la mirada en el panorama general de las cosas y conocer el comportamiento de la aplicación en condiciones normales para poder identificar cuando algo extraordinario suceda. Adicionalmente, ahora contarás con datos históricos para tomar decisiones. Las herramientas de monitoreo de excepciones, por ejemplo, cuentan el número de usuarios e instancias que un error se ha presentado. Esta información es muy valiosa cuando se trata de decidir si se debe actuar inmediatamente o se puede esperar unos cuantos días.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Programa alertas automáticas para emergencias previsibles&lt;/strong&gt;. Generalmente son fáciles de configurar y tienen un propósito doble. Por un lado, una simple notificación puede ayudar a prevenir una futura emergencia, como un servidor llegando al límite de su capacidad. Por otro, aseguran que haya una reacción oportuna por parte del equipo cuando se presente una emergencia y sean los &lt;em&gt;primeros&lt;/em&gt; en enterarse. Esto fomentará una sensación de confianza entre las distintas áreas de negocio de la compañía y le dará espacio al equipo para resolver la situación sin presiones innecesarias.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;procesos-organizacionales&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://gafemoyano.com/es/posts/not-every-bug-is-worth-fixing/#procesos-organizacionales&quot;&gt;Procesos organizacionales&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Las siguientes prácticas no son particulares a tecnología sino a los procesos internos de una empresa de cómo organizamos el trabajo y qué expectativas se imponen a los equipos. Se trata de establecer las líneas de comunicación apropiadas y definir las expectativas correctas en las distintas áreas de la compañía.&lt;/p&gt;
&lt;p&gt;These points deal with working effectively as an organization, and setting up clear boundaries and expectations between the different stake holders.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Define qué es una emergencia y cómo será manejada.&lt;/strong&gt; Como dije anteriormente, 95% de las veces un bug no debería desviar tu atención de tu tarea actual. Pero igual, está el otro 5% restante. ¿Cuáles errores conforman exactamente ese 5% en tu producto? Si hacemos una definición proactiva y preliminar de qué aspectos de la aplicación son &lt;em&gt;críticos&lt;/em&gt; y serán tratados como una &lt;em&gt;emergencia&lt;/em&gt; en caso de que fallen habremos reducido la incertidumbre de todas las personas en la compañía. Los equipos de tecnología no se verán afectados en su día a día cuando no se trate de una emergencia y las áreas directivas tendrán la seguridad de que, cuando algo salga mal, hay un plan de acción.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Tomar un respiro después de los despliegues.&lt;/strong&gt; En el fondo, culpo los ciclos de dos semanas popularizados por Scrum de tener que mencionar este punto. Son tan pequeños que a duras penas se logra hacer algo significativo, sin embargo existe la promesa de desplegar al final. Y de inmediato, en la semana tres hay que estar empezando el nuevo &lt;em&gt;sprint&lt;/em&gt; sin nada de tiempo entre medio. Personalmente, considero que es mucho más saludable tener ciclos más largos de 4 o 6 semanas y dejar una o dos de respiro sin &lt;strong&gt;tareas programadas&lt;/strong&gt;. Este es un buen tiempo para coordinar un despliegue, revisar feedback inmediato y posibles bugs que se escaparon en la etapa de calidad, o otros pendientes que el equipo quiera solucionar.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Tener ciclos exclusivos para trabajar deuda técnica.&lt;/strong&gt; Me alegra ver que esta práctica ha ido aumentando en popularidad dada su efectividad. Después de unos cuantos ciclos de desarrollo de nuevos features, que suelen ser intensos, los equipos generalmente agradecen un ciclo para trabajar en bugs o deuda técnica que hayan identificado en ciclos anteriores. Además sirven para romper un poco la monotonía, cambiar el paso y tienen el beneficio de recortar considerablemente los backlogs de vez en cuando.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Asciende bugs a proyectos.&lt;/strong&gt; Esta es una continuación de la idea que los bugs no son intrínsecamente distintos a otras tareas que hay en la lista des espera. Algunos bugs son síntomas de problemas mucho más grandes en una aplicación, como alguna mala decisión arquitectural del pasado o un servicio externo que no está funcionando correctamente. Este tipo de tareas no deberían realizarse como parte de un ticket, sino convertirse en sus propios proyectos y ponerse en la mesa de toma de decisiones junto con los demás proyectos para considerar su prioridad y las ventajas o desventajas de invertir tiempo en arreglarlo.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;comentarios-finales&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://gafemoyano.com/es/posts/not-every-bug-is-worth-fixing/#comentarios-finales&quot;&gt;Comentarios finales&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;La intención de este post es reflexionar sobre la forma en la que vemos los bugs y las distintas estrategias que usamos para lidiar con ellos. Al final el objetivo de un equipo es construir grandes productos que nuestros usuarios amen y les resuelvan problemas reales. Si tu equipo está logrando esa misión con éxito usando otras técnicas, ¡felicitaciones! continúa haciendo lo que te está funcionando. Pero si sientes que no estás progresando, que semana tras semana lo único que haces es arreglar bugs y que el producto no progresa, entonces considera algunos de los puntos y estrategias descritos acá. De pronto la forma en la que estás manejando los bugs está frenando a tu equipo. En ese caso, la próxima vez que recibas un reporte de un error en lugar de decir &lt;em&gt;dame 15 minutos y lo miro&lt;/em&gt;, podrías responder &lt;em&gt;esperemos un rato,&lt;/em&gt; &lt;em&gt;Asegurémonos que &lt;strong&gt;vale la pena&lt;/strong&gt; arreglar este bug&lt;/em&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;agradecimientos&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://gafemoyano.com/es/posts/not-every-bug-is-worth-fixing/#agradecimientos&quot;&gt;Agradecimientos&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Gracias a mi incondicional amigo Felipe por ser siempre mi primer lector y crítico y a mi hermano David por su disposición y feedback oportuno. Sin la ayuda de ellos, mis pensamientos incoherentes difícilmente llegarían a convertirse en un artículo completo.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Estrategia DRY para errores de validación en Ruby on Rails</title>
    <link href="https://gafemoyano.com/es/posts/dry-input-errors-in-rails/" />
    <updated>2021-01-31T00:00:00Z</updated>
    <id>https://gafemoyano.com/es/posts/dry-input-errors-in-rails/</id>
    <content type="html">&lt;p&gt;Iniciar una nueva aplicación de Ruby on Rails siempre ha sido un placer para mi. Uno puede construir rápidamente el esqueleto de las pantallas que necesita tu app para funcionar, crear los modelos iniciales de base de datos y arrancar a programar la lógica de negocio que, finalmente, es la que genera valor. Siempre he sentido que es una herramienta que &lt;em&gt;facilita&lt;/em&gt; el trabajo. Sin embargo, hay un punto que siempre encuentro difícil, donde las configuraciones por defecto del framework no cumplen del todo con mis expectativas: validación de formularios y manejo de errores. En particular, al mostrar los errores de entrada de los campos.&lt;/p&gt;
&lt;p&gt;Si corremos el generador de &lt;em&gt;scaffold&lt;/em&gt; de Rails (que es una gran forma de arrancar una app pero no está pensado para código de &lt;em&gt;producción&lt;/em&gt;), encontraremos en nuestros formularios el siguiente fragmento:&lt;/p&gt;
&lt;pre class=&quot;language-erb&quot;&gt;&lt;code class=&quot;language-erb&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;error_explanation&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;h2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token erb language-erb&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;&amp;lt;%=&lt;/span&gt;&lt;span class=&quot;token ruby language-ruby&quot;&gt; pluralize&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;model&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;errors&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;count&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;error&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;/span&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;%&gt;&lt;/span&gt;&lt;/span&gt; impidieron guardar este formulario:&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;h2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;

  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;ul&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token erb language-erb&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;&amp;lt;%&lt;/span&gt;&lt;span class=&quot;token ruby language-ruby&quot;&gt; model&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;errors&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;each&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;error&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;/span&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;%&gt;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;li&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token erb language-erb&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;&amp;lt;%=&lt;/span&gt;&lt;span class=&quot;token ruby language-ruby&quot;&gt; error&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;full_message &lt;/span&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;%&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;li&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token erb language-erb&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;&amp;lt;%&lt;/span&gt;&lt;span class=&quot;token ruby language-ruby&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt; &lt;/span&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;%&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;ul&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Lo que esto hace es mostrar en una alerta encima del formulario una lista de los errores encontrados ordenando el nombre del campo, seguido del mensaje de error. Incluso podríamos considerar, ya que esta lógica es bastante genérica, extraerla a una vista parcial aparte y reutilizarla.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://gafemoyano.com/assets/img/articles/lM8pK46.png&quot; alt=&quot;description&quot;&gt;&lt;/p&gt;
&lt;p&gt;Esta técnica funciona decentemente en formularios con dos o tres campos, pero después de eso le traslada la carga cognitiva de relacionar cada error con su campo correspondiente al usuario.&lt;/p&gt;
&lt;p&gt;Sería mucho más claro mostrar los errores de validación junto al campo que falló la validación. Para lograr esto, podríamos incluir un código condicional similar a este para cada campo:&lt;/p&gt;
&lt;pre class=&quot;language-erb&quot;&gt;&lt;code class=&quot;language-erb&quot;&gt;&lt;span class=&quot;token erb language-erb&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;&amp;lt;%=&lt;/span&gt;&lt;span class=&quot;token ruby language-ruby&quot;&gt; form&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;label &lt;span class=&quot;token symbol&quot;&gt;:email&lt;/span&gt; &lt;/span&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;%&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token erb language-erb&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;&amp;lt;%=&lt;/span&gt;&lt;span class=&quot;token ruby language-ruby&quot;&gt; form&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;text_field &lt;span class=&quot;token symbol&quot;&gt;:email&lt;/span&gt; &lt;/span&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;%&gt;&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;&amp;lt;!-- Mostrar los errores de validación de email --&gt;&lt;/span&gt;
&lt;span class=&quot;token erb language-erb&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;&amp;lt;%=&lt;/span&gt;&lt;span class=&quot;token ruby language-ruby&quot;&gt; tag&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;p user&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;errors&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;email&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;join&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;, &quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; user&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;errors&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;email&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;any&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;/span&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;%&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;En principio esto cumple con nuestro requerimiento, sin embargo rápidamente se convierte en algo tedioso de escribir para todos los campos. Si alguna vez has trabajado en una aplicación que usara una gema como &lt;a href=&quot;https://github.com/heartcombo/simple_form&quot;&gt;Simple Form&lt;/a&gt; puedes sentir que estás escribiendo mucho más código en comparación:&lt;/p&gt;
&lt;pre class=&quot;language-erb&quot;&gt;&lt;code class=&quot;language-erb&quot;&gt;&lt;span class=&quot;token erb language-erb&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;&amp;lt;%=&lt;/span&gt;&lt;span class=&quot;token ruby language-ruby&quot;&gt; simple_form_for &lt;span class=&quot;token variable&quot;&gt;@user&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;f&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;/span&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;%&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token erb language-erb&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;&amp;lt;%=&lt;/span&gt;&lt;span class=&quot;token ruby language-ruby&quot;&gt; f&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;input &lt;span class=&quot;token symbol&quot;&gt;:email&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;label&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Your email please&#39;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Email is mandatory, please specify one&#39;&lt;/span&gt;&lt;/span&gt; &lt;/span&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;%&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token erb language-erb&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;&amp;lt;%&lt;/span&gt;&lt;span class=&quot;token ruby language-ruby&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt; &lt;/span&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;%&gt;&lt;/span&gt;&lt;/span&gt;`&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Con esa sóla línea de código, por debajo Simple Form está creando la etiqueta correspondiente, infiriendo el tipo del &lt;code&gt;input&lt;/code&gt; según el campo de base de datos, mostrando un error condicionalmente y seguramente mucho más.&lt;/p&gt;
&lt;p&gt;A pesar de esto tengo dos razones para no incluir Simple Form (o una librería similar) en este proyecto.&lt;/p&gt;
&lt;p&gt;La primera es que para el CSS de este proyecto estoy usando &lt;a href=&quot;https://tailwindcss.com&quot;&gt;tailwind&lt;/a&gt; y los formularios tienen layouts más bien complejos. Al usar un helper como el de simple form, donde las clases del label, input, wrapper se pasan como argumentos adicionales, resulta un poco difícil entender la estructura del html que se va a generar. Adicionalmente, no he logrado que el autocomplete de tailwind funcione en bloques de Ruby:&lt;/p&gt;
&lt;pre class=&quot;language-erb&quot;&gt;&lt;code class=&quot;language-erb&quot;&gt;&lt;span class=&quot;token erb language-erb&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;&amp;lt;%=&lt;/span&gt;&lt;span class=&quot;token ruby language-ruby&quot;&gt; simple_form_for &lt;span class=&quot;token variable&quot;&gt;@user&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;f&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;/span&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;%&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token erb language-erb&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;&amp;lt;%=&lt;/span&gt;&lt;span class=&quot;token ruby language-ruby&quot;&gt; f&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;input &lt;span class=&quot;token symbol&quot;&gt;:email&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
   &lt;span class=&quot;token symbol&quot;&gt;label&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Your email please&#39;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
   &lt;span class=&quot;token symbol&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Email is mandatory, please specify one&#39;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
   &lt;span class=&quot;token symbol&quot;&gt;wrapper_html&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;mt-2&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
   &lt;span class=&quot;token symbol&quot;&gt;label_html&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;font-semibold text-gray-800&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
   &lt;span class=&quot;token symbol&quot;&gt;input_html&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;border border-gray-800 rounded&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
   &lt;/span&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;%&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token erb language-erb&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;&amp;lt;%&lt;/span&gt;&lt;span class=&quot;token ruby language-ruby&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt; &lt;/span&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;%&gt;&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;&amp;lt;!-- vs --&gt;&lt;/span&gt;

&lt;span class=&quot;token erb language-erb&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;&amp;lt;%=&lt;/span&gt;&lt;span class=&quot;token ruby language-ruby&quot;&gt; form_for &lt;span class=&quot;token variable&quot;&gt;@user&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;f&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;/span&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;%&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;mt-2&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;label&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;for&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;user_email&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;font-semibold text-gray-800&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;Email&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;label&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token erb language-erb&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;&amp;lt;%=&lt;/span&gt;&lt;span class=&quot;token ruby language-ruby&quot;&gt; f&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;input &lt;span class=&quot;token symbol&quot;&gt;:email&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;border border-gray-800 rounded&quot;&lt;/span&gt;&lt;/span&gt; &lt;/span&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;%&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token erb language-erb&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;&amp;lt;%&lt;/span&gt;&lt;span class=&quot;token ruby language-ruby&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt; &lt;/span&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;%&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;La segunda, es que como regla auto impuesta si sólo necesito una &lt;em&gt;parte&lt;/em&gt; de la funcionalidad de una gema y yo mismo podría escribirla en un tiempo &lt;em&gt;razonable&lt;/em&gt; seguramente lo haré. En este caso sólo me interesa auto generar los errores.&lt;/p&gt;
&lt;h2 id=&quot;una-estrategia-dry-para-mostrar-los-errores&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://gafemoyano.com/es/posts/dry-input-errors-in-rails/#una-estrategia-dry-para-mostrar-los-errores&quot;&gt;Una estrategia DRY para mostrar los errores&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Con este objetivo en mente salí a buscar mejor forma de incluir los errores automáticamente junto a sus respectivos inputs. Empecé inspeccionando el repositorio de Simple Form, pero rápidamente vi que ellos se &amp;quot;enganchan&amp;quot; de todo el formulario, no sólo de los inputs. Eventualmente me topé con este &lt;a href=&quot;https://www.jorgemanrubia.com/2019/02/16/form-validations-with-html5-and-modern-rails/&quot;&gt;blog post&lt;/a&gt; de Jorge Manrubia. En el artículo jorge nos muestra una forma de extraer la funcionalidad por default del &lt;em&gt;scaffold&lt;/em&gt;, todos los errores agregados en una alerta, en un inicializador de Rails. De ahí, sólo toma un poquito de trabajo adicional modificar el código para que incluya los mensajes de error debajo de los inputs.&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# Place this code in a initializer. E.g: config/initializers/field_error.rb&lt;/span&gt;

ActionView&lt;span class=&quot;token double-colon punctuation&quot;&gt;::&lt;/span&gt;Base&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;field_error_proc &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Proc&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;html_tag&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; instance_tag&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;
  fragment &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Nokogiri&lt;span class=&quot;token double-colon punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;HTML&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;fragment&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;html_tag&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  field &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; fragment&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;at&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&#39;input,select,textarea&#39;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  html &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; field
           error_message &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; instance_tag&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;error_message&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;join&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&#39;, &#39;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
           field&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&#39;class&#39;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;token content&quot;&gt;field&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&#39;class&#39;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt; border-red-600 border focus:outline-none&quot;&lt;/span&gt;&lt;/span&gt;
           html &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string-literal heredoc-string&quot;&gt;&lt;span class=&quot;token delimiter&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&amp;lt;-&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;HTML&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;
              &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;token content&quot;&gt;fragment&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;to_s&lt;/span&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;
              &amp;lt;p class=&quot;mt-1 text-sm text-red-600&quot;&gt;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;token content&quot;&gt;error_message&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;upcase_first&lt;/span&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&amp;lt;/p&gt;
           &lt;/span&gt;&lt;span class=&quot;token delimiter&quot;&gt;&lt;span class=&quot;token symbol&quot;&gt;HTML&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
           html
         &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt;
           html_tag
         &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

  html&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;html_safe
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Revisemos el código anterior paso a paso para entenderlo mejor. Cuando rails va a mostrar los elementos del formulario, va a llamar el &lt;code&gt;Proc&lt;/code&gt; anterior cada vez que detecte que el modelo tiene un error. Sobre-escribiendo el método anterior, podemos engancharnos y reemplazar el comportamiento por default (que envuelve el input en un &lt;code&gt;div&lt;/code&gt; y le agrega la clase &lt;code&gt;field_error&lt;/code&gt;) con algo distinto.&lt;/p&gt;
&lt;p&gt;Ya que el &lt;code&gt;Proc&lt;/code&gt; es llamado con cualquier elemento dentro del formulario (incluyendo labels) lo primero es revisar si se trata de un input. En ese caso, podemos agregar una etiqueta &lt;code&gt;&amp;lt;p&amp;gt;&lt;/code&gt; que contenga el error de ese campo y los estilos necesarios. Gracias a que &lt;code&gt;instance_tag&lt;/code&gt; expone el método &lt;a href=&quot;https://api.rubyonrails.org/classes/ActionView/Helpers/ActiveModelInstanceTag.html#method-i-error_message&quot;&gt;error_message&lt;/a&gt;) resulta bastante fácil encontrar los errores correspondientes al campo actual. Para todos los demás elementos, retornamos el &lt;code&gt;html&lt;/code&gt; sin ningún cambio.&lt;/p&gt;
&lt;h2 id=&quot;closing-thoughts&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://gafemoyano.com/es/posts/dry-input-errors-in-rails/#closing-thoughts&quot;&gt;Closing thoughts&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Y eso es todo! No olvides reiniciar tu aplicación para que recargue el nuevo archivo de configuración. Con esto, podemos incluir condicionalmente los mensajes de error en línea con los inputs de nuestros formularios sin agregar código adicional. Debo admitir que la solución no me pareció lo más limpio del mundo al principio y tampoco hay mucha documentación al respecto. Pero lo importante es que funciona y es un API público por lo que podemos estar confiados que será estable hacia el futuro. Déjame saber qué te parece esta solución, ¿es una gran idea o es terrible? Cualquier comentario no dudes en escribirme a &lt;a href=&quot;mailto:felipe@gafemoyano.com&quot;&gt;felipe@gafemoyano.com&lt;/a&gt; o en twitter.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Mi lugar feliz</title>
    <link href="https://gafemoyano.com/es/posts/my-happy-place/" />
    <updated>2020-12-08T00:00:00Z</updated>
    <id>https://gafemoyano.com/es/posts/my-happy-place/</id>
    <content type="html">&lt;p&gt;Hace unos días estaba pensando en la frase &amp;quot;Ir a tu lugar feliz&amp;quot;. Ese lugar al que vas en tu imaginación cuando &lt;a href=&quot;https://youtu.be/fzoXalgG7oY&quot;&gt;cierras los ojos e ignoras el resto del mundo&lt;/a&gt;, justo cuando no quieres saber nada más y decides, en vez de enloquecer, cerrar los ojos e ir a otro lugar.&lt;/p&gt;
&lt;p&gt;O por lo menos esa es la idea general, excepto que no he sentido la necesidad de tomar un viaje imaginario a aquel lugar en mucho tiempo.&lt;/p&gt;
&lt;p&gt;Hace un tiempo ya que tengo una deuda conmigo mismo de escribir unas últimas palabras acerca de mi emprendimiento anterior, &lt;a href=&quot;https://savy.co&quot;&gt;Savy&lt;/a&gt;. Después de todo el fin fue tan súbito y poco ceremonioso que no econtré un buen momento para escribirlo. La vida continuó con nuevas oportunidades, el COVID siguió su devastadora curva de contagio y la empresa se disolvió antes de que las palabras correctas llegaran a mi. Hasta que la semana pasada un oportuno algoritmo de Apple&lt;br&gt;
decidió mostrarme en uno de sus recuerdos la foto de portada de este artículo.&lt;/p&gt;
&lt;p&gt;Lo que más me impactó de la foto, fue lo ridículamente feliz que se ven las personas que están en ella. Yo sé que es una foto y se supone que todos sonreímos en ellas, pero hay algo acerca del ambiente y el momento que se tomó este momento que se siente... auténtico, alegre, feliz.&lt;/p&gt;
&lt;p&gt;A pesar de todas las dificultades que vivimos y los retos que enfrentamos, en la mayor parte del tiempo, así se sintió el ambiente de nuestro pequeño lugar.&lt;/p&gt;
&lt;p&gt;Fue recordar esa sensación lo que me motivo, finalmente, a escribir este artículo. Por cerca de tres años tuve el &lt;em&gt;privilegio&lt;/em&gt; de vivir y trabajar en mi lugar feliz. Sin importar qué tan difíciles se llegaron a poner las cosas: si un proyecto no marchaba bien, cuando los números no alcanzaban nuetras espectativas,&lt;br&gt;
cuando tuve que tomar la decisión de despedir a alguien (que es &lt;em&gt;lo peor&lt;/em&gt;); no pensé en un solo momento, en cerrar los ojos y escapar a otro lado. Ni Una. Sola. Vez.&lt;/p&gt;
&lt;p&gt;Un emprendimiento es curioso en este sentido. Uno tiene la oportunidad de experimentar cualquier cantidad de experiencias y oportunidades en un solo lugar: alegría y tristeza, éxito y fracaso, claridad y desespero, orgullo y verguenza... y muchas más. Pero en el día a día las cosas se mueven tan rápido que no hay mucho tiempo para reflexionar sobre todo lo que está sucediendo. En retrospectiva, me es fácil decir que tan solo &lt;em&gt;tener&lt;/em&gt; la oportunidad de vivir &lt;em&gt;todo&lt;/em&gt; esto es, en mi concepto, razón suficiente para hacerlo.&lt;/p&gt;
&lt;p&gt;Desafortunadamente, &lt;a href=&quot;https://youtu.be/s3SO1j06oSA&quot;&gt;todo tiene su final&lt;/a&gt; y el 2020 fue un año despiadado en cuanto a terminar cosas abruptamente. Volviendo un poco a la foto que disparó este artículo, sé que no puedo predecir el futuro. Espero, sinceramente, que todas las personas que estamos en esa foto encontremos, una vez más, el ambiente que fue capturado en ese instante: un ambiente de energía eléctrica y positiva que emana de un equipo que es mucho más que la suma de sus partes. Y que, esta vez, logren mantenerlo un poquito más que lo que pudimos en Savy.&lt;/p&gt;
&lt;p&gt;Soy una persona inreíblemente afortunada por haber tenido la oportunidad de vivir esta experiencia y me siento aún más agradecido hacia todas la personas que participaron de ella y compartieron, junto a mi, los sentimientos que describí anteriormente. Amé cada momento que pasé con este equipo y de haber contribuido mi parte en crear algo que le trajo felicidad y alegría al mundo y a la vida de algunas personas.&lt;/p&gt;
&lt;p&gt;Al equipo de Savy, tanto los que están en la foto como los que no están, por haber compartido esta experiencia conmigo.&lt;/p&gt;
&lt;p&gt;A mis confundadores, por haberme aguantado durante todos estos años.&lt;/p&gt;
&lt;p&gt;A mi familia, mis amigos y especialmente mi novia por haber lidiado con mi ausencia durante tantos meses, días y horas.&lt;/p&gt;
&lt;p&gt;Gracias por haber sido parte de mi lugar feliz.&lt;/p&gt;
&lt;p&gt;Felipe.&lt;/p&gt;
&lt;br&gt;
&lt;br&gt;
&lt;p&gt;&lt;em&gt;P.S. Some other memories&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Allow me, for posterity, to share some other moments that come to memory from looking at this picture:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Los partidos de ping pong y lo competitivo que es Chris de no descansar hasta convertirse en el mejor de todos.&lt;/li&gt;
&lt;li&gt;Los juegos de Mario Kart después de almuerzo para decidir quién lavaba la loza.&lt;/li&gt;
&lt;li&gt;Llegar tarde a todas las reuniones que hubo, para el desespero de mis muy puntuales y franceces co-fundadores.&lt;/li&gt;
&lt;li&gt;El día en el que lanzamos el app al App Store y empezaron a llegar los comentarios positivos.&lt;/li&gt;
&lt;li&gt;Ver a Camilo llegar todos los días a la oficina con una sonrisa gigante en la cara.&lt;/li&gt;
&lt;li&gt;Lo triste que se puso todo el mundo cuando se fue Sebastián.&lt;/li&gt;
&lt;li&gt;Las sesiones de chisme ocasionales con Sandra.&lt;/li&gt;
&lt;li&gt;Cuando Luis cantaba inconcientemente en sus momentos de concentración profunda.&lt;/li&gt;
&lt;li&gt;Nathalia, Juanita y Abraham sentados practicamente uno encima de otro para trabajar en el iMac.&lt;/li&gt;
&lt;li&gt;Mathias 🐕.&lt;/li&gt;
&lt;li&gt;Esa vez que Camila me intentó enseñar a bailar infructuosamente.&lt;/li&gt;
&lt;li&gt;Ruby Conf medellín, donde Luis y yo nos tomamos la mitad de la cerveza de todo el hostal.&lt;/li&gt;
&lt;li&gt;Las largas sesiones de debugging con Camilo.&lt;/li&gt;
&lt;li&gt;Las sesiones de tutoría de HTML y CSS con el jefe.&lt;/li&gt;
&lt;li&gt;Las reuniones de co-founders en la noche, donde nos arrepentimos de nuestra elección de domicilio 9 de cada 10 veces.&lt;/li&gt;
&lt;li&gt;Nuetra cena elegante para celebrar el cierre de la primera ronda.&lt;/li&gt;
&lt;li&gt;Salir de fiesta y darnos cuenta que había una botella de guaro por cada dos personas del grupo&lt;/li&gt;
&lt;li&gt;La ida a jugar tejo, las salidas a rumbear, las cervezas de los viernes, las tardes de cocteles.&lt;/li&gt;
&lt;li&gt;Los discursos.&lt;/li&gt;
&lt;li&gt;Decirle a Gaetan que tenía razón.&lt;/li&gt;
&lt;li&gt;Estrellar la bicicleta de Chris por ir tarde a todo lado. Todavía me duele la mano.&lt;/li&gt;
&lt;li&gt;Practicar el pitch de Shark Tank una y otra vez con el equipo.&lt;/li&gt;
&lt;li&gt;Grabar nuestro primer video de YC en 3 horas.&lt;/li&gt;
&lt;li&gt;Grabar nuetro último video de YC en 3 minutos.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Y tantos más... disculpenme si me faltó alguno importante, seguramente le agregaré algunos más a la lista por tenerlos en algún lado.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Una estrategia simple para traducir rutas en Ruby on Rails</title>
    <link href="https://gafemoyano.com/es/posts/a-simple-strategy-for-translated-routes-in-rails/" />
    <updated>2020-11-02T00:00:00Z</updated>
    <id>https://gafemoyano.com/es/posts/a-simple-strategy-for-translated-routes-in-rails/</id>
    <content type="html">&lt;p&gt;Hace poco empecé un nuevo proyecto personal. Se trata de un listado de trabajos remotos en tecnología. Admito que no es la idea más novedosa que hay, pero no he visto un tablero enfocado en el mercado Colombiano o latino que haya encontrado tracción. Así que decidí hacer el intento y lanzarla yo mismo.&lt;/p&gt;
&lt;p&gt;Escogí Rails ya que me siento bastante cómodo con el framework y estaba seguro que la integración con &lt;a href=&quot;https://edgeguides.rubyonrails.org/active_storage_overview.html&quot;&gt;ActiveStorage&lt;/a&gt; para archivos adjuntos y &lt;a href=&quot;https://edgeguides.rubyonrails.org/action_text_overview.html&quot;&gt;ActionText&lt;/a&gt; para el editor de texto me ahorrarían una buena parte del trabajo.&lt;/p&gt;
&lt;p&gt;Parte de mi hipótesis por la que no existe un buen tablero de trabajos remotos en Latam (a parte de lo complicadísimos que son los sitios existentes) es que están 100% en inglés. Por lo tanto quería que esta app estuviera completamente en español. Si bien Rails tiene un módulo de &lt;a href=&quot;https://guides.rubyonrails.org/i18n.html&quot;&gt;i18n&lt;/a&gt;, siento que está más enfocado en la traducción de textos, sin embargo deja por fuera una parte clave de una app web, la URL.&lt;/p&gt;
&lt;p&gt;Adicionalmente, mi objetivo no es tener una aplicación internacionalizada para incluir otros lenguajes a futuro, simplemente quiero que todo lo que esté de cara al usuario esté en español.&lt;/p&gt;
&lt;p&gt;Históricamente, he tratado la traducción de URLs en rails de las siguientes formas:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Crear los modelos en el lenguaje desado&lt;/strong&gt;. Ya que Rails es un framework que se basa principalmente en convenciones, el nombre de las rutas se puede inferir generalmente del nombre de los modelos. El problema, es que ya que las reglas de &lt;a href=&quot;https://api.rubyonrails.org/classes/ActiveSupport/Inflector.html&quot;&gt;inflexión&lt;/a&gt; están en inglés, tendría que agregar excepciones por cada uno de los modelos de la aplicación y, aún así, hay algunos escenarios en los que las inflexionese no funcionan de la forma que uno esperaría. Finalmente, se me hace confuso tener el código fuente en &lt;strong&gt;spanglish&lt;/strong&gt;, por lo tanto preferiría que el código se mantenga principalmente en inglés.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Usar una gema&lt;/strong&gt; Existen gemas súper completas en el ecosistema para traducciones de rutas. La más notable es &lt;a href=&quot;https://github.com/enriclluelles/route_translator&quot;&gt;route_translator&lt;/a&gt; que incluso permite manejar varias técnicas de internacionalización para sitios multilingues. Esto me permitiría definir las rutas dentro de un bloque &lt;code&gt;localized&lt;/code&gt;, configurar español como lenguaje por defecto y definir el nombre de las rutas en un archivo &lt;code&gt;.yml&lt;/code&gt;. Esta parece una gran opción, sin embargo siento que mi caso de uso es mucho más sencillo. De nuevo, mi idea no es tener un sitio que soporte múltiplese lenguajes y esta gema hace muchísimo más. Me gustaría buscar una solución aún más sencilla.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Dejar las URLs en inglés&lt;/strong&gt;. Honestamente, esta es la opción por la que he optado la mayoría de las veces en el pasado. A pesar de ir encontra de las buenas prácticas de SEO, en mi experiencia no he visto que tener las URLs en inglés tenga un impacto negativo, siempre y cuando los meta tags y el contenido estén configurados correctamente. Por otro lado, los usuarios no tienden a fijarse en las URLs ni en su lenguaje.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Para este proyecto, ninguna de estas opcionese parecía ideal. Pensé que &lt;em&gt;debía&lt;/em&gt; existir una forma más sencilla de lograr traducir URLs. Después de todo, Rails es reconocido por ser un framework modular y flexible (a pesar de que algunas personas no estarían de acuerdo con esta afirmación, en el fondo lo es).&lt;/p&gt;
&lt;p&gt;Resulta que &lt;em&gt;sí&lt;/em&gt; existe una forma más sencilla y siempre estuvo al frente mio. Se trataba de mirar un poco más de cerca la forma en la que rails maneja la definición de rutas. Al definir un recurso con la directiva &lt;code&gt;resource&lt;/code&gt;, esta recibe ciertos &lt;strong&gt;kwargs&lt;/strong&gt; o &lt;strong&gt;keyword arguments&lt;/strong&gt;. Uno de ellos es el argumento &lt;strong&gt;path&lt;/strong&gt; que nos permite cambiar el prefijo de la ruta según&#39;lo descrito en la &lt;a href=&quot;https://api.rubyonrails.org/classes/ActionDispatch/Routing/Mapper/Resources.html#method-i-resources&quot;&gt;documentación&lt;/a&gt;. En un caso hipotético para un recurso &lt;code&gt;jobs&lt;/code&gt; podríamos hacer lo siguiente:&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;resources &lt;span class=&quot;token symbol&quot;&gt;:jobs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&#39;trabajos&#39;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# Generates the following routes:&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# jobs	   GET    /trabajos(.:format)&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;#		   POST   /trabajos(.:format)&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# new_job  GET    /trabajos/new(.:format)&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# edit_job GET    /trabajos/:id/edit(.:format)&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# job 	   GET    /trabajos/:id(.:format)&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;#	       PATCH  /trabajos/:id(.:format)&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;#          PUT    /trabajos/:id(.:format)&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;#	       DELETE /trabajos/:id(.:format)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Excelente, por un lado tenemos nuestras rutas de cara a los usuarios en español (en su mayoría) y por otro podemos mantener las referencias internas al código consistentes en inglés.&lt;/p&gt;
&lt;p&gt;No obstante, aún nos falta una parte para tener las rutas completamente traducidas. &lt;code&gt;new&lt;/code&gt; y &lt;code&gt;edit&lt;/code&gt; todavía están en inglés. Esto lo podemos solucionar fácilmente gracias al argumento &lt;code&gt;path_name&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;resources &lt;span class=&quot;token symbol&quot;&gt;:jobs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&#39;trabajos&#39;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;path_names&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;nuevo&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;edit&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;editar&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Yendo un paso más allá, podemos aplicar &lt;code&gt;path_names&lt;/code&gt; a todo un scope, evitando que tengamos que repetirlo en cada ruta que defina la aplicación.&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;scope&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:path_names&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:new&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&#39;nuevo&#39;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:edit&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&#39;editar&#39;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt;
    resources &lt;span class=&quot;token symbol&quot;&gt;:jobs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&#39;trabajos&#39;&lt;/span&gt;&lt;/span&gt;
    resources &lt;span class=&quot;token symbol&quot;&gt;:tags&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&#39;etiquetas&#39;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# new_job  GET    /trabajos/nuevo(.:format)&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# edit_job GET    /trabajos/:id/editar(.:format)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;¡Y eso es todo! ahora todas las URLs están en el lenguaje que deseamos sin incluir dependencias extenas complejas, sacrificar la legibilidad de nuestro código o perder unos hipotéticos puntos de SEO.&lt;/p&gt;
&lt;p&gt;Espero que este ejemplo haya servido para ilustrar como una solución simple muchas veces puede ser suficiente y recordarnos que a veces debemos mirar &lt;strong&gt;dentro&lt;/strong&gt; de las posibilidades que ofrecen las herramientas que usamos, un lugar de mirar hacia &lt;strong&gt;afuera&lt;/strong&gt; e incluir dependencias que cubren más casos de usos de los que necesitamos.&lt;/p&gt;
</content>
  </entry>
</feed>