Elixir Mexico City 29 de Agosto

Este 29 de Agosto estaremos hosteando en Resuelve el meetup de Elixir Mexico City donde dos de nuestros ingenieros serán Speakers, la entrada es gratuita pero reserva en el evento en meetup.com.

Estaremos transmitiendo el evento así que síguenos en twitter o facebook.

¿Quienes estarán?

¿Dónde?
Calz. Gral. Mariano Escobedo 555

Pure Black GmbH
Graslitzer Str. 6
84478 Waldkraiburg
Tel. 08638 6964680
www.pureblack.de

AutoML | Una biblioteca simple para hacer modelos de Machine Learning.

Como científico de datos, siempre intentas obtener el mejor modelo para tu problema. Obtener un gran modelo, entrenarlo y ajustarlo a la perfección es solo una parte de un proyecto más grande. A la medida que vas exponiéndote a algunos proyectos de aprendizaje automático y leyendo algunos de los ciclos de desarrollo de los científicos de datos, descubres que hay un ciclo de desarrollo parecido. En general para nuestro equipo se ve así:

Como muchos en la escena tecnológica, he tenido una experiencia autodidacta en aprendizaje automático. Cuando me estaba introduciendo en esta muy joven práctica, y en todas las palabras de moda asociadas, encontré mucha información, libros, videos, publicaciones, MOOC y tutoriales sobre los algoritmos de creación de modelos. Pero, cuando llegas a construir esos modelos con tus datos reales; datos que no están completos, disponibles, estandarizados, actuales, y una lista de etcéteras que podemos resumir en … datos que no están limpios; te das cuenta de que la mayoría de tu tiempo la pasas limpiando tus conjuntos de datos. Mientras que la creación de tu modelo es algo que toma una fracción de ese tiempo. Adicionalmente, cuando terminas de entrenar esos modelos y deben estar disponibles para clientes/sistemas en producción, resulta que el video tutorial que viste en Coursera / Youtube o el repositorio de Github que te guiaba ya no contaba con esa sección.

De Bernard en StackExchange

No me malinterpreten, creo que encontrar una arquitectura adecuada y ajustar correctamente los hiperparámetros puede mejorar un modelo. Sin embargo, considero que es la calidad y la cantidad de datos lo que más impacta para hacer buenos modelos.

Además, aunque un modelo que solo se puede utilizar en una máquina local es un buen punto de partida, hay muchos pasos a seguir. Pero, como se comentó anteriormente, la limpieza, el aumento de datos y la implementación de un modelo no son temas tan populares como los algoritmos de aprendizaje automático.

Una y otra vez, el equipo y yo descubrimos que estábamos haciendo el mismo proceso de limpieza para nuestros conjuntos de datos, aún haciendo modelos completamente diferentes. Mismos métodos de limpieza para diferentes datos.

Descubrimos también que no era un problema exclusivo de nuestro equipo, sino un problema para muchos científicos de datos:

“The reason data scientists are hired in the first place is to develop algorithms and build machine learning models — and these are typically the parts of the job that they enjoy most. Yet in most companies today, 80 percent of a data scientist’s valuable time is spent simply finding, cleaning and reorganizing huge amounts of data. Without the right cloud tools, this task is insurmountable.” Infoworld

Así que, pensando como ingenieros, decidimos automatizar la parte repetitiva del trabajo, y no solo eso, sino todo lo que pudieramos del ciclo. Ahí fue cuando comenzó la idea de hacer AutoML.


Algunas consideraciones antes de comenzar…

Al momento de iniciar este proyecto, los modelos desarrollados por el equipo eran en su mayoría de datos estructurados. Puede pensarse como datos que pueden estar en una hoja de cálculo simple. Cosas como números, fechas y cadenas de texto. Contrario a los datos no estructurados (imágenes, audio, video, texto). Además, los modelos creados eran para resolver problemas de clasificación.

Photo by Jeremy Bishop on Unsplash

Obtén datos primero

La primera parte fue conseguir los datos con los que vamos a trabajar. Afortunadamente, ya habíamos centralizado la mayor parte de nuestros datos en un solo data warehouse, por lo que solo tuvimos que consultarlo. Entonces, lo primero que necesita la biblioteca para trabajar es la capacidad de hacer consultarlo.

Lavar y enjuagar

Una vez que tenemos los datos que necesitamos, es buena idea deshacerse de las cosas que pueden confundir el algoritmo en la etapa de entrenamiento. Para limpiar los datos utilizamos algunas técnicas realmente simples para excluir variables:

  • Eliminación de constantes
  • Valores faltantes dentro de un umbral de x%
  • variables con más de y categorías, por ejemplo.

Nos aseguramos de que todos los datos que obtengamos estén en el formato y tipo que esperamos. Por ejemplo, nos aseguramos de que las fechas estén en un formato correcto y no como cadenas de texto.

Datos ++

Una vez que hayamos limpiado los datos, avancemos un poco más y aumentemos las variables que tenemos con algunas transformaciones simples:

  • Discretización de variables categóricas
  • Sumas, multiplicaciones y uso de algunas expresiones lógicas
  • Extracciones de fecha: día /día de la semana /mes

Teniendo una buena relación

Ya tenemos una gran cantidad de datos para entrenar, pero para determinar si una variable es útil usamos un análisis de correlación para eliminar las variables que no están tan relacionadas con la variable objetivo (la información que queremos predecir), el factor de inflación de varianza (VIF) para reducir la multicolinealidad e identificamos posibles fugas de datos (data leakage).

Análisis de correlación — este es un método estadístico de evaluación utilizado para estudiar la fuerza de la relación lineal entre dos variables continuas (por ejemplo, altura y peso)

El factor de inflación de varianza (VIF) — es otro método estadístico de evaluación que cuantifica el grado de correlación entre un predictor y los otros predictores en un modelo.

Fuga de datos (data leakage) — cuando una característica está demasiado relacionada con la variable objetivo esta puede ser un mal predictor. Un tipo de data leakage común es el caso en que son datos que no están al momento que usarías tu modelo de predicción. (por ejemplo, tomó_antibiótico= True, cuando se quiere predecir enfermedad. La persona puede estar tomando medicina porque ya sabe que está enferma.)

Es importante realizar este tipo de controles para ofrecer a un modelo (por ejemplo, una red neuronal) una mejor oportunidad de rendir mejor y converger. También crea una pausa saludable para que el científico de datos reflexione sobre las variables seleccionadas y una oportunidad para reducir la dimensionalidad de las entradas para el modelo. Un modelo más simple es más fácil de entender y mantener.

Creación de modelos

Hasta este punto (si todo ha salido bien) deberíamos tener un conjunto de datos limpio con variables aumentadas y relacionadas con nuestra variable objetivo. ¡Genial!
Primero hemos encontrado que es muy útil para hacer un modelo de clasificación muy simple. Solo para tener un ‘modelo base’ que dibuje una línea en la arena como el mínimo estándar de desempeño.
Para buscar un modelo que pueda superar al modelo base, utilizamos tres estrategias:

  • Algoritmos genéticos evolutivos
  • Búsqueda aleatoria
  • Tpot

Sacamos al mejor modelo de cada estrategia y los evaluamos con algunas métricas:

  • AUC (área bajo la curva)
  • F1 score
  • MCC score

¡El que mejor desempeño tenga bajo estos parámetros es el ganador!

Compartamos con el equipo.

Para este momento hemos creado un modelo con los mejores datos, limpios y aumentados; con la mejor arquitectura y parámetros; además lo hemos comparado con con al menos otros tres modelos. ¡Muy bien!
Pero un modelo que se ejecuta en tu computadora no es de mucha ayuda para el equipo. Aún menos útil para la organización. Entonces, deberíamos desplegarlo donde todos puedan utilizarlo.

Actualmente, AutoML puede desplegar un modelo entrenado localmente en el motor de ML de Google, creando un servicio dedicado, auto-escalado y con un endpoint para atender todas las solicitudes que sean necesarias.

Pensamientos y trabajos futuros.

Si bien estamos muy orgullosos del resultado de AutoML, y lo hemos utilizado para desarrollar dos modelos en producción. Encontramos muchas áreas de oportunidad y ‘bugs’ que pueden (y deben) solucionarse para hacer un producto más robusto, más rápido y mejor para todos. Algunas ideas son:

  • Opción de hacer modelos regresivos.
  • Opción para hacer una mayor variedad de modelos (CNN, RNN, Arima, árboles de decisión, etc.)
  • Comprobación de correlación de datos más eficiente
  • Mayor variedad para comprobar correlación de datos
  • Implementación para otros proveedores de la nube
  • Despliegue agnóstico de infraestructura

AutoML es un trabajo en equipo de Francisco Serrano y Dudley Díaz (yo) en el equipo de AI en Resuelve tu Deuda en la Ciudad de México, México.

Descargar archivos csv

¿Qué es un archivo csv?

Un csv es un archivo de valores separados por comas. Se pueden usar con excel o con cualquier otro programa de hoja de cálculo. Se diferencian de otros tipos de archivo de hoja de cálculo en que no puede guardar ningún formato ni puede guardar las fórmulas.

Los archivos con extension csv tienen un comportamiento similar al que tienen los archivos png, jpg, pdf, el navegador los toma como archivos pequeños que puede mostrar en otra pestaña y no los descarga.

¿Cuál es el problema?

Los clientes cuando tratan de descargar un archivo en formato csv el navegador lo abre en una ventana nueva y no lo descarga.

Bien, un issue sencillo, nada complicado, ¿qué tan difícil es descargar un archivo csv?, y si, no es difícil descargar un csv, sin embargo, tuve que hacer una pequeña investigación antes de resolver este issue, y la quiero compartir con ustedes.

En vista de que el navegador no va a descargar el archivo, pero si muestra su contenido, lo primero hay que hacer es descargar el contenido del archivo que en nuestro caso es esta en AWS. Creamos una función que descargue el contenido usando fetch, a continuación con ayuda de la librería Downloadjs descargamos el archivo.

Downloadjs es una librería que permite descargar archivos en diferentes tipos de formatos. Recibe tres parámetros :

  • El archivo que se va a descargar.
  • El nombre del archivo y su extensión(archivo.csv)
  • El MIME type (Es una forma estandarizada de indicar la naturaleza y el formato de un documento) MDN

Este es el método rápido, sencillo y mágico para descargar un archivo csv.

Y así de fácil tienes tu archivo csv.

También puedes descargar un cvs de otra forma un poco mas compleja pero igual de efectiva.

De igual manera que antes descargamos el contenido del archivo con fetch,con new blobconcatenamos la información, le decimos el tipo de archivo que será, creamos una nueva url de la cual vamos a descargar el archivo csv. Creamos una etiqueta (<a></a>)y descargamos el archivo. Posteriormente eliminamos la url creada ya que el navegador no debe guardar la referencia al archivo.

Aqui puedes probar que funciona.

Para finalizar

Si usas el primer método te vas ahorrar código y también vas a poder descargar archivos jpg, png, pdf, txt, gif. De igual manera que con los .csv los puedes descargar muy fácil y rápido. Ten en cuanta que estas agregando una librería mas a tu código con lo que ello implica, aunque al final es una librería pequeña que hace algo puntual.

Si usas el segundo método es un poco mas de código, pero te estas evitando una librería mas en el código. Ahora si sabes que lo único que necesitas es descargar archivos .csv y no necesitas los otros formatos que mencioné anteriormente, pues para que agregar una librería con este método es suficiente.

¿Cuál es la mejor?

Depende de como estén trabajando, del equipo y de que librerías y/o frameworks estén utilizando.

Correcciones, mejoras, comentarios, siempre bien recibidos.

Saludos.

Tipos en JavaScript sin TypeScript/Flow

JavaScript no es un lenguaje que sea estrictamente tipado y muchos proyectos han escalado bastante bien sin usar tipado, pero la realidad es que a medida que crece un proyecto su complejidad aumenta y simplemente existen muchos detalles que ya no podemos tener en mente.

Los tipos nos ayudan a reducir esta complejidad de varias formas, algunas de estas son:

  • Evitar errores comunes, ya que al conocer los input/outputs o interfaces de los módulos que usamos nos ayuda a usarlos como se debería.
  • Documentación, Poder tener claro los tipos de datos que acepta o retorna un módulo sin tener que ir a buscar su definición es bastante útil, más cuando es un proyecto grande.
  • Soporte IDE, la integración y sugerencias de los IDE/Editores es bastante útil cuando programas.
  • Refactorizar, poder modificar partes de tu código sin tener regresiones o infiriendo donde se está usando lo que cambias resulta una de las mejores ventajas que dan los tipos. parámetros Estas son algunas de las razones por las cuales son una gran idea integrar tipos en tus proyectos, te ayudará en general al developer experience y poder prevenir errores antes de que el producto llegue a tus usuarios.

Si no usas una forma de definir tipos está bien, muchos proyectos funcionan sin esto y si no sientes tener el problema no los integres, ya que esto solo va a añadir otra capa de complejidad a tus desarrolladores.

JSDOC + TSC

Las dos alternativas más comunes al integrar tipos en JavaScript es usar Flow o TypeScript, ambas tienen props y contras, pero ambas van a añadir una capa de transpilación a tu código y es posible que integrarlo a tu flujo de desarrollo no resulte tan sencillo.

Toma tiempo y experiencia siendo eficiente con los tipos si tienes background en lenguajes tipados

Una alternativa es usar JSDoc que es una forma estándar de añadir documentación al código en JS, usandolo junto con TSC o TS (solo para chequeo de tipos) podemos tener las mismas ventajas de usar tipado.

Una de las razones por las cuales usar este enfoque es que no requires o require tener un paso más de transpilación, el código que escribes sigue siendo JS y no necesitas migrar o cambiar las herramientas que usas en desarrollo.

También si usas VSCode, este soporta JSDoc para poder usar intelliSense, permitiendo mejorar el autocompletado, información de parámetros, etc.

Configuración

Para activar esto en VSCode puedes hacerlo de dos formas:

La primera es activarlo por defecto en todos los archivos JS, en settings, agrega

"javascript.implicitProjectConfig.checkJs": true

La segunda es agregar en la raíz del proyecto un archivo jsconfig.json, con la siguiente configuración

{
"compilerOptions": {
"target": "es2017",
"allowSyntheticDefaultImports": true,
"jsx": "react",
"noEmit": true,
"strict": true,
"noImplicitThis": true,
}
},
"exclude": ["node_modules", "build"],
}

Puedes leer más en detalle todas las opciones de configuración aquí.

También podrías estar interesado en tener un paso de CI que te permita checar los tipos, para esto basta con añadir un script en el package.json con

"type-lint": "tsc --pretty",

Uno de los proyectos grandes que usan esta forma de revisión de tipos es webpack.

Tipos de librerías de terceros

Una vez inicies a utilizar tipos de esta forma o con TypeScript, vas a encontrar que tus dependencias necesitan ayuda para conocer sus tipos ya que no todas son publicadas con estos, para esto la comunidad de TS, tiene una gran herramienta

qué te ayuda a encontrar los tipos de tus dependencias.

¿Qué tipos puedo usar?

Los tipos básicos son

  • null
  • undefined
  • boolean
  • number
  • string
  • Array or []
  • Object or {}

Para definir el tipo de una variable puedes usar @type

/**
* @type {number}
*/
const age = 1

/**
* @type {string}
*/
const name = "yeison"

Es este caso sería innecesario ya que al asignar un numero a la variable, se infiere el tipo

Por ejemplo en arrays podemos definir el tipo de los elementos que contiene.

/**
* @type {Array<number>}
*/
const randomNumbers = []

Una alternativa es usar la sintaxis @type {number[]}

Con los objetos puedes definir que tipo va a tener cada propiedad.

/**
* @type {{age: number, name: string}}
*/
const person = {age: 1, name: 'yeison'}

Otra alternativa es definir cada propiedad en una línea independiente

/**
* @property {number} age
* @property {string} name
*/
const person = {age: 1, name: 'yeison'}

person.name = 1 // Te va a mostrar un error

Si una propiedad es opcional podemos declararlo usando [] al rededor del nombre de la propiedad.

/** 
* @typedef {Object} Options The Options to use in the function createUser.
* @property {string} firstName The user's first name.
* @property {string} lastName The user's last name.
* @property {number} [age] The user's age.
*/
/**
* @type {Options} opts
*/
const opts = { firstName: 'Joe', lastName: 'Bodoni' } // no va a mostrar error

Definir tipos personalizados

Podemos crear tipos personalizados, esto es una forma para crear tipos personalizados y podemos reutilizarlos.

Para declarar un tipo personalizado usamos @typedef

/**
* @typedef {{age: number, name: string}} Person
*/

/**
* @type {Person}
*/
const person = {age: 1, name: 'yeison'}

/**
*
* @param {Person} person
* @returns {string}
*/
const getUpperName = (person) => person.name.toUpperCase()

Métodos y funciones

Cuando declaramos funciones podemos definir que valores va a recibir y retornar una función, tenemos varias sintaxis que podríamos usar.

Sintaxis estándar de JSDoc

/**
*
* @param {number} a
* @param {number} b
* @returns {boolean}
*/
const gte = (a, b) => a > b

Sintaxis más parecida a TS

/**
* @type {function(number, number): boolean}
*/
const gte = (a, b) => a > b

Sintaxis parecida a Clojure

/**
* @type {(a: number, b: number) => boolean}
*/
const gte = (a, b) => a > b

Generic

Para poder usar valores genéricos podemos usar @template

/**
* @template T
* @param {T} i
* @return {T}
*/
function identity(i) {
return i
}

En este caso identity va a recibir cualquier tipo, pero el tipo que reciba es el que debe retornar.

Importar tipos

También podemos importar tipos entre archivos, de forma estandar JSDoc no permite hacer esto, pero VSCode permite utilizar import para poder lograr importar definiciones de tipos.

/**
* @typedef {import('moment').Moment} initialDate
*/

También puedes importarlos de archivos (no necesitas declarar ninguna clausula de export)

/**
* @typedef {import('../utils').File} File
*/

Intersecciones y uniones

Algo que podríamos querer hacer es extender una definición que ya tenemos, para esto podemos utilizar las intersecciones (&) que nos permite unir varios tipos en uno solo

/**
* @typedef {{name: string, cc: number, tel: number}} Person
* @type {Person & {addres: string}}
*/
const person = { name: 'yeison', cc: 1, tel: 12, addres: 'asd' }

O también podríamos necesitar que un tipo sea uno u otro, para esto podemos utilizar las uniones (|)

/**
* @type {{isValidCitizen: true, cc: number} | {isValidCitizen: false, ce: number}}
*/
const person = { isValidCitizen: true, cc: 1 }

React con JSDoc

Una vez ya sabemos utilizar las partes básicas de JSDOC, podemos aplicar estas en proyectos donde usamos React, en este caso para definir los tipos de los props y el estado de cada componente.

Si un componente que vamos a utilizar tiene definido sus tipos, cuando lo utilizamos el editor nos va a dar información de los props que espera y sus tipos.

import React from 'react'

/**
* @param {{name: string}} Props
*/
const Hello = ({ name }) => (
<h1>Hello {name}</h1>
)

<Hello name={1}> {/* muestra error */}

En los componentes que declaramos con clases, debemos definir sus props y el estado, para esto la clase esta extendiendo de Component, para escribir esto en JSDoc, debemos utilizar @extends

import React, { Component } from 'react'

/**
* @typedef {{ user: string, password: string }} State
* @typedef {{ login: (data: State) => Promise }} Props
* @extends {Component<Props, State>}
*/
class Login extends Component {
state = {
user: '',
password: ''
}

/**
* @param {React.ChangeEvent<HTMLInputElement>} ev
*/
handleChange = (ev) => {
const { value, id } = ev.target

this.setState({[id]: value})
}

/**
* @param {React.FormEvent} ev
*/
handleSubmit = (ev) => {
ev.preventDefault()

this.props.login(this.state)
}

render () {
return (
<form>
<fieldset>
<label htmlFor='user'>
Password
<input type='text' id='user' />
</label>
<label htmlFor='password'>
Password
<input type='password' id='password' />
</label>
</fieldset>
<input type='submit' value='Enviar'/>
</form>
)
}
}

Bonus

En muchos proyectos se usan alias, para poder realizar imports de modulos sin tener que escribir grandes rutas, el problema de esto es que tu editor no va a reconocer estas rutas, para solucionarlo podemos agregar la configuración de los alias en el jsconfig.json

{
"compilerOptions": {
"module": "es2015",
"esModuleInterop": true,
"moduleResolution": "node",
"baseUrl": "./src",
"paths": {
"utils": ["utils/"],
"api": ["api/"],
"actions/*": ["actions/*"],
"stores/*": ["stores/*"],
}
}
}

Palabras finales

Creo personalmente que usar JSDoc en un proyecto es una forma bastante buena y con menos fricción para poder mejorar el developer experiencie y tener las ventajas que ofrece usar tipos sin tener que migrar de una a usar TS/Flow.

Yeison Daza 🍉

Cómo limpiar logs en docker

Después de trabajar un rato con contenedores de docker para desarrollo, me he encontrado con el momento en que los logs, al iniciar el contenedor, cargan y cargan y cargan y parece que no tienen fin. Eso, sumado a que tienes una gran cantidad de logs por request, solo se vuelve más y más largo.

Preguntando a mis compañeros de trabajo, me encontré que la solución más común es simplemente borrar la máquina y volverla a crear. Borrar los logs manualmente había probado ser tedioso y en algunos eventos, hasta ha corrompido el contenedor.

No conforme con esto, (y que tenía el internet bastante limitado), no quería volver a descargar la imagen y volverla a instalar localmente. Así que estuve buscando alguna alternativa. Hasta que encontré esto:

Solo es una pequeña configuración que se pone en el docker-compose.yml justo dentro de la definición del servicio que estás usando para trabajar.

Esta definición va a realizar dos cosas con tus logs la próxima vez que levantes el servicio:

  1. Va a borrar los logs actuales
  2. Los nuevos logs van a estar limitados a 50mb

Efectivamente limpiando los logs todo tu contenedor y teniendo un poco más de paz mental.

Si eres como yo y no confías en lo que haga esta configuración en el servidor de producción (no sé, digamos que usas K8s y subes el docker). Te recomiendo que hagas lo siguiente:

  • Inserta en tu docker-compose.yml la definición
  • Levanta el contenedor y ve cómo ya no hay logs
  • Detén el contenedor
  • Regresa el docker-compose.yml a la normalidad
  • Levanta de nuevo el contenedor

Listo, ahora tienes un contenedor limpio de logs para trabajar y no afectarás los logs en ningún otro lado.

Espero les sirva este pequeño hack. Yo sé que a mí me sirvió xD

Zero out!

Abandoné mi zona de confort. ¿Buena decisión?

Después de 10 años de no cambiar de trabajo, no presentarme a ninguna entrevista o buscar una oportunidad laboral, estar en un trabajo donde me sentía cómodo y estable, pensar muchas veces en cambiar, pero no saber cómo hacer ese cambio. Luego de superar algunos obstáculos y de descubrir mi gusto por la programación: decido aprender a programar.

Pero para aprender a programar debía asistir a la universidad (pensé en ese momento). Pero dinero y principalmente tiempo era lo que no tenía, así que empecé a hacerlo por internet como se hacen muchas cosas actualmente.

Así fue como llegue a Platzi, egghead, udemy, tutoriales, youtube, documentación. etc. Pase aproximadamente 3 años aprendiendo qué es html, js, css, servidores, dominios, back, front. Hasta que front me gustó y decido enfocarme en ese campo.

En el camino un buen amigo me platica de una oportunidad para trabajar como junior en la empresa que él trabaja (Resuelve tu deuda). ¿Es una buena oportunidad? ¿Es el momento ideal para cambiar de trabajo? (pensé es ese momento) luego de pensarlo y con el miedo que se puede sentir después de tanto tiempo de no presentar una entrevista, decido iniciar el proceso.

Primera entrevista, mucho miedo ¿Qué me van a preguntar? ¿Si no contesto bien? Y mil cosas más. Entrevista agradable, con preguntas de parte y parte, ahora pienso que algo normal en la primera entrevista. Me envían la prueba, tardo mucho tiempo en enviarla, solo lo hacía en las noches luego de salir de mi trabajo, finalmente la envío, la revisan, deciden continuar con el proceso, y finalmente me dan la oportunidad.

¿Buena decisión?

Luego de 6 meses aproximadamente debo decir que fue la mejor decisión que he podido tomar, ha sido un a experiencia enriquecedora, de mucho aprendizaje, superar miedos, conocer personas geniales que disfrutan de lo que hacen, enfrentar y superar problemas, de poder asistir a meetups.También darme cuenta que aun me falta mucho por aprender, que hay personas dispuestas a enseñarte, dispuestas crecer y en ese crecimiento quieren que tú crezcas con ellos, de líderes que están siempre dispuestos a enseñarte, a guiarte.

Finalmente para aquellos como yo, que están llenos de temores, que quizá no quieren abandonar su zona de confort, decirles que lo intenten, que se arriesguen, que lo peor que puede pasar es que les digan que no y que muy seguramente alguno les dirá que sí y en unos meses estarán escribiendo esto mismo. También muchas veces pensamos que estamos bien y puede que sea cierto, pero podemos estar mucho mejor si lo intentamos y no dejamos de intentarlo.

Gracias a Yeison Daza, Jordi Adame, Jaime Rodas, Ángel Buzany, y todos los que no mencioné que ya saben quienes son. Y por supuesto a Resuelve tu deuda por darme la oportunidad.

Saludos…

Facebook
Twitter
YouTube