Mariano Guerra: Creemos en la Web: Ver o no ver, también es una cuestión

En el capítulo anterior vimos como calcular o no según el resultado de una condición, ahora vamos a ver como cambiar que mostramos en HTML según el valor de condiciones.

Vamos a hacerlo dándole una cara a nuestro despertador.

Empecemos con la versión mas simple, inicializando un despertador con una sola alarma y mostrando si esta prendido o no y a que hora esta configurada la alarma.

<div id="despertador-app-1">
  <p class="alert alert-info">Despertador Prendido? <strong>{{despertadorPrendido}}</strong></p>
  <p class="alert alert-info">Hora Despertador: {{horaDespertador}}</p>
</div>
<script>
  new Vue({
    el: '#despertador-app-1',
    data: {
      despertadorPrendido: false,
      horaDespertador: 8
    }
  })
</script>

El resultado:

Despertador Prendido? {{despertadorPrendido}}

Hora Despertador: {{horaDespertador}}

Repasando un poco vue.js, tenemos un div raíz de nuestra aplicación, el cual tiene que tener un atributo id así le podemos indicar a vue.

Dentro del div tenemos dos párrafos, el primero muestra el valor de la variable despertadorPrendido usando el formato de vue {{variable}} para indicar que queremos que ahí ponga el valor de variable, el segundo muestra el valor de la variable horaDespertador.

Luego en un tag script inicializamos nuestra aplicación con new Vue(...) donde en ... indicamos usando un objeto javascript dos atributos:

el
Id del tag raíz de esta aplicación, para indicarle que es un id tenemos que prefijarlo con un #.
data
Los datos de nuestra aplicación, por ahora las dos variables que usamos en el HTML arriba y que usamos en el capitulo anterior.

Con esto ya mostramos el estado actual de nuestro simple despertador, pero no podemos prenderlo ni cambiar la hora, veamos como hacer eso:

<div id="despertador-app-2">
  <p v-if ="despertadorPrendido" class="alert alert-success">Despertador Prendido</p>
  <p v-else class="alert alert-danger">Despertador Apagado</p>

  <p class="alert alert-info">Hora Despertador: {{horaDespertador}}</p>

  <input v-model.number="horaDespertador" type="number" class="form-control" placeholder="Hora Despertador">

  <div class="mt-3 text-center">
    <button v-if="despertadorPrendido" v-on:click="despertadorPrendido = false" class="btn btn-danger">Apagar</button>
    <button v-else v-on:click="despertadorPrendido = true" class="btn btn-success">Prender</button>
  </div>
</div>

<script>
  new Vue({
    el: '#despertador-app-2',
    data: {
      despertadorPrendido: false,
      horaDespertador: 8
    }
  })
</script>

El resultado, proba cambiando la hora y haciendo click en el botón:

Despertador Prendido

Despertador Apagado

Hora Despertador: {{horaDespertador}}

Algunos cambios con respecto a la versión anterior, notaras que vue tiene su propio if como javascript, en este caso se escribe v-if="condicion" lo usamos para decidir que párrafo mostramos, si despertadorPrendido es true, mostramos:

<p v-if="despertadorPrendido" class="alert alert-success">Despertador Prendido</p>

Sino:

<p v-else class="alert alert-danger">Despertador Apagado</p>

v-else tiene que estar después de un tag con un atributo v-if o v-else-if.

A la hora la seguimos mostrando de la misma forma pero ahora tenemos un campo de texto para poder cambiarlo:

<input v-model.number="horaDespertador" type="number" class="form-control" placeholder="Hora Despertador">

Con v-model le indicamos a vue que queremos que el contenido de este input refleje el valor del campo horaDespertador, es decir que va a mostrar su valor y cuando lo cambiemos va a actualizar su valor.

Como el campo es un numero y no queremos tener un valor de tipo texto con un numero dentro, le indicamos a vue que nos lo convierta a numero con v-model.number.

El resto es HTML estándar.

Finalmente usamos v-if y v-else de nuevo para mostrar el botón Prender o Apagar según el valor de despertadorPrendido:

<button v-if="despertadorPrendido"
    v-on:click="despertadorPrendido = false"
    class="btn btn-danger">Apagar</button>

<button v-else
    v-on:click="despertadorPrendido = true"
    class="btn btn-success">Prender</button>

En cada botón le indicamos a vue con v-on:click que cuando el botón sea clickeado queremos cambiar el valor de despertadorPrendido.

Con esto ya tenemos una aplicación para prender/apagar un despertador y cambiar la hora de la alarma, pero en el capítulo anterior teníamos una alarma con día, veamos como replicar eso en HTML.

Lo primero que vamos a tener que hacer es tener por cada día de la semana un campo para despertadorPrendido y uno para horaDespertador.

Podemos elegir mostrar todos los días o podemos hacerlo mas simple con un selector del día que queremos ver y manipular.

El resto debería ser como hasta ahora.

Veamos como seria eso:

<div id="despertador-app-3">
  <p v-if="dias[diaSeleccionado].despertadorPrendido"
     class="alert alert-success">Despertador Prendido</p>
  <p v-else
     class="alert alert-danger">Despertador Apagado</p>

  <p class="alert alert-info">Hora Despertador:
    {{dias[diaSeleccionado].horaDespertador}}</p>

  <input v-model.number="dias[diaSeleccionado].horaDespertador"
     type="number" class="form-control" placeholder="Hora Despertador">

  <select v-model="diaSeleccionado" class="custom-select mt-3">
    <option value="lunes">Lunes</option>
    <option value="martes">Martes</option>
    <option value="miercoles">Miércoles</option>
    <option value="jueves">Jueves</option>
    <option value="viernes">Viernes</option>
    <option value="sabado">Sábado</option>
    <option value="domingo">Domingo</option>
  </select>

  <div class="mt-3 text-center">
    <button v-if="despertadorPrendido"
        v-on:click="despertadorPrendido = false"
        class="btn btn-danger">Apagar</button>

    <button v-else
        v-on:click="despertadorPrendido = true"
        class="btn btn-success">Prender</button>
  </div>
</div>

<script>
  new Vue({
    el: '#despertador-app-3',
    data: {
      diaSeleccionado: 'lunes',
      dias: {
        lunes: {
          despertadorPrendido: true,
          horaDespertador: 8
        },
        martes: {
          despertadorPrendido: true,
          horaDespertador: 7
        },
        miercoles: {
          despertadorPrendido: true,
          horaDespertador: 8
        },
        jueves: {
          despertadorPrendido: false,
          horaDespertador: 8
        },
        viernes: {
          despertadorPrendido: true,
          horaDespertador: 7
        },
        sabado: {
          despertadorPrendido: false,
          horaDespertador: 8
        },
        domingo: {
          despertadorPrendido: false,
          horaDespertador: 8
        },
      }
    }
  })
</script>

Resultado:

Despertador Prendido

Despertador Apagado

Hora Despertador: {{dias[diaSeleccionado].horaDespertador}}

El HTML es bastante similar, solo que donde antes teníamos despertadorPrendido ahora tenemos dias[diaSeleccionado].despertadorPrendido y donde antes teníamos horaDespertador ahora tenemos dias[diaSeleccionado].horaDespertador, ya que tenemos que mostrar y cambiar los valores del día seleccionado.

También tenemos un tag select con v-model seteado a diaSeleccionado así muestra el día seleccionado y si elegimos otro valor en el select el valor de diaSeleccionado es actualizado.

Si no te gusta repetir mucho las cosas como yo, notaras que dias[diaSeleccionado] esta por todos lados, es molesto escribirlo, podemos cometer un error si lo escribimos mal y si llegamos a renombrar algo vamos a tener que ir a todos los lugares a actualizarlo.

Seria mas fácil si pudiéramos "nombrar" este pedazo de código a algo mas claro y simple.

Otra cosa que podrás haber notado si tenes una tendencia a intentar romper todo lo que te dan es que se puede poner horas incoherentes, por ejemplo 42 en el campo de horaDespertador, estaría bueno poder evitar eso.

Veamos como podemos solucionar estos problemas:

<div id="despertador-app-4">
  <p v-if ="diaActual.despertadorPrendido"
     class="alert alert-success">Despertador Prendido</p>
  <p v-else
     class="alert alert-danger">Despertador Apagado</p>

  <p class="alert alert-info">Hora Despertador:
    {{diaActual.horaDespertador}}</p>

  <div class="input-group mt-3">
    <div class="input-group-prepend">
      <label class="input-group-text" for="inputGroupSelect01">Cambiar Hora</label>
    </div>
  <input v-model.number="diaActual.horaDespertador"
     type="range" min="0" max="23" class="form-control" placeholder="Hora Despertador">
  </div>

  <div class="input-group mt-3">
    <div class="input-group-prepend">
      <label class="input-group-text" for="inputGroupSelect01">Día</label>
    </div>
    <select v-model="diaSeleccionado" class="custom-select">
      <option value="lunes">Lunes</option>
      <option value="martes">Martes</option>
      <option value="miercoles">Miércoles</option>
      <option value="jueves">Jueves</option>
      <option value="viernes">Viernes</option>
      <option value="sabado">Sábado</option>
      <option value="domingo">Domingo</option>
    </select>
  </div>

  <div class="mt-3 text-center">
    <button v-if="diaActual.despertadorPrendido"
            v-on:click="diaActual.despertadorPrendido = false"
            class="btn btn-danger">Apagar</button>
    <button v-else
            v-on:click="diaActual.despertadorPrendido = true"
            class="btn btn-success">Prender</button>
  </div>
</div>

<script>
  new Vue({
    el: '#despertador-app-4',
    computed: {
      diaActual: function () {
        return this.dias[this.diaSeleccionado];
      }
    },
    data: {
      diaSeleccionado: 'lunes',
      dias: {
        lunes: {
          despertadorPrendido: true,
          horaDespertador: 8
        },
        martes: {
          despertadorPrendido: true,
          horaDespertador: 7
        },
        miercoles: {
          despertadorPrendido: true,
          horaDespertador: 8
        },
        jueves: {
          despertadorPrendido: false,
          horaDespertador: 8
        },
        viernes: {
          despertadorPrendido: true,
          horaDespertador: 7
        },
        sabado: {
          despertadorPrendido: false,
          horaDespertador: 8
        },
        domingo: {
          despertadorPrendido: false,
          horaDespertador: 8
        },
      }
    }
  })
</script>

Resulta en:

Despertador Prendido

Despertador Apagado

Hora Despertador: {{diaActual.horaDespertador}}

Notaras que dias[diaSeleccionado] cambio a diaActual, pero como diaActual no es un valor, sino un valor "calculado" (computed en ingles) en base a otros dos valores dias y diaSeleccionado, no podemos ponerlo en el campo data, para estos datos calculados, vue nos permite especificarlos en el atributo computed, donde cada campo es un nombre y una función que devuelve su valor.

En nuestro caso:

computed: {
  diaActual: function () {
    return this.dias[this.diaSeleccionado];
  }

Ahora podemos decir diaActual en nuestro HTML y vue lo va a reemplazar por el valor de dias[diaSeleccionado].

Notaras que a diferencia del código en v-on:click en el HTML acá cada variable empieza con this., esto es porque en el HTML de vue las únicas variables que podemos acceder son las que están en el campo data y computed de nuestra aplicación, por eso vue nos hace el favor de ponerle this. adelante a cada nombre, cuando pasamos ese código a javascript, el navegador no sabe a que nos estamos refiriendo, puede ser una variable en la función actual, una variable global o un atributo de nuestra aplicación vue, para especificar que nos referimos a un nombre dentro de nuestra aplicación, tenemos que prefijar los nombres con this (que en ingles se traduce a "esto/este" y siempre se refiere al objeto al que pertenece la función que estamos ejecutando.

El otro cambio, mas allá de algunas mejoras estéticas usando clases de bootstrap, es que le cambiamos el tipo al input de number a range y le especificamos dos atributos nuevos min y max, de esta manera el navegador en lugar de mostrar un campo de texto donde se puede escribir cualquier cosa, muestra un "slider" donde solo se puede especificar un valor en el rango valido.

Con eso ya tenemos nuestro despertador con soporte para alarmas por día, en el camino repasamos vuejs y aprendimos sobre campos calculados y el tipo range del tag input.

Mariano Guerra: Creemos en la Web: Calcular o no calcular, esa es la cuestión

En capítulos anteriores vimos un tipo de dato llamado bool o boolean que puede tomar dos valores, verdadero (true) o falso (false), los cuales son el resultado de operaciones lógicas y de comparación.

En el siguiente capitulo vimos como hacer cálculos, ahora vamos a unir estos dos conceptos usando el resultado de comparaciones y operaciones lógicas para decidir que calculamos y que no, para eso vamos a introducir una nueva expresión llamada if, pero antes veamos un ejemplo.

Vas a poner el despertador, si "el día es un día de semana", entonces pones el despertador a las 8, sino lo dejas apagado.

Esto lo podríamos expresar en javascript así:

if (esDiaDeSemana) {
    ponerDespertador(8);
}

Esta es la versión mas simple, en la cual escribimos la palabra clave if, luego entre paréntesis la condición que va a evaluar a verdadero o falso, luego entre llaves las expresiones que queremos evaluar si la condición es verdadera.

Ese ejemplo asume que el despertador esta apagado, por lo que no necesitamos hacer nada si esDiaDeSemana es falso.

Pero que pasa si queremos apagarlo por las dudas este prendido, no queremos despertarnos temprano por accidente un sábado!

Entonces podemos usar otra palabra clave, veamos un ejemplo:

if (esDiaDeSemana) {
    ponerDespertador(8);
} else {
    apagarDespertadorSiEstaPrendido();
}

En este caso, después de la llave de cierre escribimos la palabra clave else que significa "sino" y entre llaves las expresiones a evaluar si la condición en el if es falsa.

Podemos imaginar que el código de apagarDespertadorSiEstaPrendido es algo como:

if (despertadorPrendido) {
    apagarDespertador();
}

Con esto podemos hacer muchísimas cosas, pero si tu semana es variada y tenes que despertarte a horas distintas según el día, vas a necesitar tener mas de una condición, veamos como seria eso uniendo las dos palabras claves que ya aprendimos y pongamos todo en una función.

function configurarDespertador(diaDeSemana) {
    if (diaDeSemana === "lunes" || diaDeSemana === "miércoles") {
        ponerDespertador(8);
    } else if (diaDeSemana === "martes" || diaDeSemana === "viernes") {
        ponerDespertador(7);
    } else {
        apagarDespertadorSiEstaPrendido();
    }
}

Antes de describir la función por completo, notar que para tener una segunda condición a comprobar después del primer if, escribimos la palabra clave else seguida de la palabra clave if, esto se leería algo así como:

si (condicion1) entonces
    ejecutar bloque1
sino si (condicion2) entonces
    ejecutar bloque2
sino
    ejecutar bloque3.

Definimos la función configurarDespertador que recibe como parámetro el diaDeSemana, y con el tenemos 3 bloques que podemos evaluar:

  • Si es lunes o miércoles: despertador a las 8
  • Si es martes o viernes: despertador a las 7
  • sino, asegurarse que el despertador este apagado

Si te fijas, el despertador no va a sonar los fines de semana ni el jueves, cuando escribimos condiciones complejas o encadenadas hay que fijarse bien que estamos probando todos los casos.

Para poder probar esto con código completo y ver si tomamos la decisión correcta vamos a aprender dos formas de mostrar al usuario de nuestra aplicación el resultado.

Mostrando texto en la consola de desarrolladores con console.log.

Durante el desarrollo nos puede pasar que queremos saber por donde se ejecuto el código o cual es el valor de una variable, para eso podemos usar el objeto console que tiene algunas funciones útiles, la mas útil de ellas es la función log.

Abrí una pagina cualquiera, abrí las herramientas de desarrollo (usualmente F12 la abre), abrí el tab "Consola" (o nombre similar) si no esta abierto en ese, si ves muchas cosas ahí hace click en el icono del tacho de basura para limpiar la consola y escribí:

console.log("hola");

Y apretá enter, deberías ver que la linea siguiente dice "hola", probemos algunos otros ejemplos:

let numero = 42;
console.log(numero);
console.log("numero: " + numero);
console.log("numero", numero);
console.log("numero", numero, true, null, "hola");

A mi me quedo así:

/galleries/cew/if-else/console-log.png

Podrás notar que console.log recibe tantos parámetros como deseemos y los muestra a todos, no hay necesidad de juntarlos todos en un solo valor de tipo texto.

El objeto console tiene otras funciones útiles, proba alguno de los ejemplos anteriores reemplazando la función log con warn, error, info.

console.warn(numero);
console.error("numero: " + numero);
console.info("numero", numero);

Con esto podemos "simular el despertador", sin tener que escribir todo ahora, solo vamos a imprimir que haría.

Pero antes de ir a eso, vamos a ver otra forma de mostrar información, que si bien es simple y medio molesta es una buena herramienta cuando estamos empezando un proyecto.

Como existe el objeto console, que tiene varias funciones relacionadas a la consola de desarrollo, existe el objeto window que tiene funciones relacionadas a la ventana donde esta nuestra pagina, la cantidad de funciones que tiene es impresionante, pero por ahora nos vamos a enfocar en solo tres, la función alert, que nos permite mostrar un mensaje al usuario, la función confirm que nos permite mostrar un mensaje al usuario y el usuario puede responder el clásico "OK" o "Cancelar" y la función prompt que nos permite preguntar algo y el usuario puede responder con texto o cancelar.

Empecemos por la mas fácil, alert:

window.alert("hola");
let numero = 42;
window.alert("numero: " + numero);
/galleries/cew/if-else/alert.png

A diferencia de console.log, window.alert recibe un solo parámetro, si queremos mostrar el valor de múltiples variables tenemos que juntarlas en un solo valor de tipo texto.

Veamos window.confirm:

let respuesta;
// responde una de las dos opciones
respuesta = window.confirm("Seguir?");
console.log('Respuesta', respuesta);

// responde la otra
respuesta = window.confirm("Seguir?");
console.log('Respuesta', respuesta);
/galleries/cew/if-else/confirm.png

la función devuelve true si se selecciono "OK" y false si se selecciono "Cancel".

Por ultimo window.prompt, el cual recibe dos parámetros, el primero es el mensaje a mostrar, el segundo es el valor por defecto para el campo de texto, si no lo especificamos empieza con el texto vació.

// selecciona "Cancel"
respuesta = window.prompt("Día de Semana", "lunes");
console.log('Respuesta', respuesta);

// selecciona "OK"
respuesta = window.prompt("Día de Semana", "lunes");
console.log('Respuesta', respuesta);
/galleries/cew/if-else/prompt.png

La función devuelve null si se selecciono "Cancel" y el texto en el campo de texto si se selecciono "OK".

Probemos combinándolas un poco:

let r1 = window.confirm("Seguir?");
if (r1) {
    window.alert(":)");
} else {
    window.alert(":(");
}

El código te va a preguntar si querés seguir, si respondes OK va a mostrar :), si respondes cancel va a mostrar :(.

Una nota por si estas probando y te da este error o algo parecido:

SyntaxError: redeclaration of let nombreDeVariableAca

Eso es porque declaraste dos veces la misma variable, las variables se declaran una sola vez por función con let, acá estamos en la consola, así que es como una función eterna que ejecuta cada linea que le damos, así que solo tenemos que declarar la variable una vez, después simplemente se siguen usando, sin tener que declararla de nuevo.

Veamos como usaríamos la función window.prompt para configurar nuestro despertador:

let r2;
r2 = window.prompt("Día", "lunes");
if (r2 === null) {
    window.alert("Acción cancelada");
} else {
    configurarDespertador(r2);
}

Acá estamos llamando a configurarDespertador, que definimos mas arriba solo si el usuario ingreso un día y apretó OK.

La función configurarDespertador llama a un par de funciones que todavía no definimos, así que si probas este código te va a dar un error diciendo que apagarDespertadorSiEstaPrendido o ponerDespertador no están definidas, por ahora vamos a usar window.alert y una variables globales para simular el despertador.

Acá va todo el código junto.

// una variable global (fuera de las funciones) para saber si el despertador
// esta prendido o no, inicializado a false, indicando que esta apagado
let despertadorPrendido = false,
    // variable global para saber a que hora esta puesta la alarma
    horaDespertador = 0;

function preguntarDiaYPonerDespertador() {
    let respuesta = window.prompt("Día", "lunes");

    if (respuesta === null) {
        window.alert("Acción cancelada");
    } else {
        configurarDespertador(respuesta);
    }
}

function configurarDespertador(diaDeSemana) {
    if (diaDeSemana === "lunes" || diaDeSemana === "miércoles") {
        ponerDespertador(8);
    } else if (diaDeSemana === "martes" || diaDeSemana === "viernes") {
        ponerDespertador(7);
    } else {
        apagarDespertadorSiEstaPrendido();
    }
}

function prenderDespertadorSiEstaApagado() {
    // si no esta prendido
    if (!despertadorPrendido) {
        // lo prendemos
        despertadorPrendido = true;
        console.log('despertador prendido');
    } else {
         console.log('despertador ya estaba prendido');
    }
}

function apagarDespertadorSiEstaPrendido() {
    // si esta prendido
    if (despertadorPrendido) {
        // lo apagamos
        despertadorPrendido = false;
        console.log('despertador apagado');
    } else {
        console.log('despertador ya estaba apagado');
    }
}

function ponerDespertador(hora) {
    prenderDespertadorSiEstaApagado();
    console.log('nueva hora para despertador', hora);
    horaDespertador = hora;
}

Te recomiendo que escribas el código en lugar de copiar y pegarlo, de esa forma te vas a ir acostumbrando a escribir los paréntesis, llaves y puntos y coma en su lugar y a entender los errores cuando te equivocaste en algo.

Luego de declarar las variables y funciones llama preguntarDiaYPonerDespertador(); y proba con distintos días varias veces viendo que se imprime en la consola.

Como ejercicio queda comprobar que el día ingresado es un día valido, sino informarlo con window.alert y no intentar configurar el despertador.

Como ayuda, te recomiendo que declares una nueva función esDiaValido, que uses if y else if para comprobar que el día pasado como parámetro es un día valido, si lo es devolvé true (return true;), en el else devolvé false (return false;).

Facundo Batista: PyCon Argentina 2018

Estas últimas semanas vengo a recontrafull con la organización de la PyCon Argentina 2018. Más allá de un comienzo lento hace unos meses, por un problema interno de la organización, se formó un equipo que está laburando un montón.

La conferencia se va a realizar en el Centro Cultural General San Martín, en la Ciudad de Buenos Aires, del jueves 22 al sábado 24 de Noviembre, más alguna actividad social el domingo 25 (nos vamos de Pycnic!).

¡Registrate y vení que es gratis y va a estar buenísima!

El logo de la conferencia

El jueves van a haber varios talleres, entre los que se destaca un Django Girls hecho y derecho. Viernes y sábados es más de "conferencia clásica", pero van a haber otras actividades como espacios abiertos para que cualquiera hable del tema que quiere, zonas de trabajo, charlas rápidas, y algunas cositas más que describo mejor acá abajo.

Con respecto a las charlas, ya están elegidas y confirmadas: en la página principal del sitio ya se pueden ver cuales quedaron. La agenda detallada con los días y horarios de cada una (como así también de los talleres) va a estar en unos días. Destaco que tenemos dos keynotes internacionales: Carol Willing, core developer de Jupyter y ex directora de la PSF, y Brandon Rhodes, programador Python en Dropbox y organizador de la PyCon USA en Portland 2016 y 2017.

También vamos a hacer una muestra de fotografía con las mejores fotos que la gente haya sacado alguna vez en un evento de PyAr (esto es, si la gente propone fotos! Este es el formulario al respecto).

Ya pusimos a la venta las remeras de la conferencia! Las estamos vendiendo al costo, así que tenemos que vender todas las que compramos para no perder plata con eso.. así que comprate una y regalale otra a alguien más :) Y aprovechá que están con descuento por algunos días. Ah, fijate que hay corte hombre y corte mujer, y desde XS a XXXL, ¡no hay excusas!

La remera de la conferencia

Hablando de no tener excusas, no sólo vamos a tener becas para ayudar a que la gente viaje (prontito sale el anuncio) sino que también la Asociación Civil Python Argentina hizo un acuerdo con la Fundación Flechabus y los socios van a poder comprar pasajes con un 40% de descuento.

Los asistentes a la conferencia también van a poder participar de una actividad que realizamos por primera vez (que copiamos de otros eventos): el "speed interview", que es una actividad ideal si estás buscando trabajo, o incluso si estás con ganas de saber qué ofertas hay aunque no tengas nada en mente. En una hora podés charlar con gente de una decena de empresas distintas, a razón de cinco minutos cada una, y si hay algo que te interese puntualmente te queda el contacto para seguir charlando luego.

Finalmente, algo en lo que estamos innovando es un espacio para que madres y padres que tienen que cuidar a sus hijes puedan asistir a las charlas sin mayor preocupación: van a poder dejarles al cuidado de personal especializado en las mismas instalaciones de la conferencia, a pocos metros de donde se desarrollan las charlas y otras actividades, en un ámbito cuidado y con la estructura necesaria para que no haya ningún problema. Hay que anotarse con la mayor anticipación posible, así podemos dimensionarlo correctamente, este es el form con toda la info.

Creo que esas son los principales puntos de la conferencia. ¡Va a estar genial! Ya voy a ir tirando otras novedades en las pocas semanas que falta...

Facundo Batista: Por la educación pública gratuita, laica, y de calidad

Hoy no se hace una revolución tomando por la fuerza, violentamente, el palacio de invierno y derrocando zares, porque, además de que la violencia provoca violencia y a la larga casi nada más, no hay más zares ni existe un poder concentrado en ningún palacio. El cambio social profundo, inclusivo, la revolución del siglo XXI se hace apoderándose del conocimiento, que la elite se empeña en monopolizar. La persona que carece de conocimiento está destinada a ser subalternizada en la sociedad actual, al igual que la nación que carece de desarrollo científico lo está en el concierto mundial. Las elites saben que financiar universidades nacionales es serruchar la rama en que están sentados. Por eso, tienen miedo.

Les recomiendo este genial artículo de opinión: El miedo a la revolución del saber, por Eugenio Raul Zaffaroni, Juez integrante de la Comisión Interamericana de Derechos Humanos, ex integrante de la Corte Suprema de la Nación, y Profesor emérito de la Universidad de Buenos Aires... en general, un tipo al que me encanta leer.

Facundo Batista: Trabajando en Bruselas

La semana pasada estuve de sprint, laburando en Bruselas, Bélgica.

No era la primera vez que iba, pero la vez pasada (que fue medio reunión de trabajo, medio UDS) todo lo que fue hospedaje y evento ocurrió en un hotel en el medio de un bosque lejos del centro. Una hermosura, pero lo que es "conocer Bruselas", no conocí casi nada.

Esta vez nos hospedamos y trabajamos en un hotel céntrico, y como el horario era de 8:30 a 17:30 y estábamos cerrando el verano, se podía salir a pegar una vuelta habiendo todavía luz luego de cada día de trabajo.

Jeanneke Pis

Por otro lado no tuve tiempos largos para ir de visita lejos de la ciudad, ya que llegué el domingo durante la tarde tarde y me fui el sábado pasado el mediodía.

Igual, uno sale, pasea, saca fotos.

El clima también acompañó porque hicieron un par de grados más de lo normal para la época, así que estuvo fresquito lindo, e incluso un día estuve de cortos. El sábado nos llovió un poco a Guillo y a mí justo cuando estábamos pegando una vuelta, pero no tanto como para molestar realmente.

Het Zinneke

La ciudad es linda, limpia, "europea vieja" pero bien. Estaba todo un poco roto porque estaban trabajando en un par de avenidas y eso hacía que la mitad de los caminos estuvieran con cascotes, zonas de construcción y eso, pero nada que moleste demasiado.

Si uno se guía por los locales que encuentra en la ciudad, el 50% del PBI de Bruselas se debe explicar por los chocolates, y el otro 50% por las cervezas... Mucha cervecería, alguna con más onda, otra con menos onda. ¿Recomendaciones? A La Mort Subite (un café muy viejo con toda la onda, comida decente y cervezas locales) y BrewDog (buena cerveza, comida normal para una cervecería (pobre, digamos, aunque la hamburguesa "chipotle chorizo" estaba catorce puntos), y buen ambiente y música).

Nota aparte sobre BrewDog, recién buscando la URL para poner acá vi que historia loca y buena filosofía tiene esta cadena de bares, super interesante, y que seguro algo tiene que ver con la onda que tiene el lugar.

Ah, y traten de probar cervezas trapenses. Todo un estilo, interesante y ricas.

Manneken Pis

A nivel de laburo también estuvo muy bien la semana.

Intra-equipo (somos 16, deberíamos ser más) tenemos una modalidad para estas reuniones mezcla de Trello y Google Docs que funciona muy muy bien. Y en esta oportunidad se formalizó que durante la mañana cada equipo trabajaba entre sí, y luego del almuerzo se metían todas las reuniones "cross-team". Eso da un espacio sin interrupciones que cada equipo (o al menos nosotros) puede aprovechar muy bien.

Lo malo a nivel laboral es que pasamos de un sistema "per-diem", donde te asignan un monto de dinero para que gastes en cenas, almuerzos o desayunos, y después vos gastás más, menos, te preocupás o no, etc, a un sistema "rendir los tickets", con lo cual para cada gasto relacionado con las comidas tenés que acordarte de pedir el (los) tickets, sacarles una foto con el teléfono, meterlos en la app de reportar gastos, etc.

En fin. En general, una semana que estuvo muy bien, con la excepción de Moni en casa que tuvo extra complicado el tema de los horarios de los niños, pero no podía hacer mucho yo por eso. Por suerte, es el último viaje por laburo con Male en el jardín, desde el año que viene es todo más simple.

Facundo Batista: Mi cuerpo también es político

Hace rato que uno viene interesado en la pelea de las mujeres (y otros colectivos que podríamos englobar en un "no-hombres-cis") por estar reconocidos como sujetos de derecho a la par de los hombres cisgénero.

Seguramente habrán estarán al tanto de las marchas #niunamenos o el debate que se dió semanas atrás en el Congreso sobre la despenalización del aborto (lo cual se apoya desde estas lineas). Y si no están al tanto, salgan del termo.

Particularmente, vengo siguiendo estos temas desde hace bastante, antes de que explotara mediáticamente. Como ejemplo basta un botón: hace muchos años que decidí empezar a comprar el Página 12 también los viernes (sólo lo compraba los domingos) solamente porque ese día salen los suplementos Las 12 y Soy.

También vengo empujando desde hace casi una década un movimiento en el grupo de Python Argentina cuya arista más sobresaliente es la de "diversidad dentro del grupo", pero que también versa sobre los derechos de las mujeres en la industria, como cobrar el mismo salario por la misma tarea o destruir el techo de cristal... en general mejorar las condiciones en todos los ámbitos: laboral, obvio, pero también en todos los otros espacios, como conferencias, reuniones, etc.

¿Qué tiene que ver todo esto con mi cuerpo?

Yo, como hombre cisgénero heterosexual, reconozco que me encuentro entre el grupo privilegiado. Dice Malena Pichot en un texto que les recomiendo: "Quienes somos cis heterosexuales, con alguna serie más de privilegios encima, tenemos la absoluta responsabilidad de hacer algo, de no ser cómplices del sistema que nos beneficia a nosotros en detrimento de otros".

Por mi parte, hace rato que tomé la decisión de no tener más hijos. A Felipe y Malena los amo con el alma, los disfruto, los adoro, pero no quiero tener más hijos. Entonces, entra en juego el control de la natalidad. Y acá es donde nos metemos en un terreno que también históricamente fue dominación de los hombres: el cuerpo de las mujeres.

Es que cuando de control de natalidad se habla, siempre se hace hincapié en las mujeres y en formas de que no puedan procrear, desde la ligadura de trompas, pasando por la colocación de un DIU hasta las pastillas anticonceptivas con todos las implicaciones a nivel hormonal que tienen.

Lo importante es tomar la responsabilidad sobre las decisiones. YO no quiero tener más hijos. Entonces, YO tengo que actuar en consecuencia. Lo que corresponde es hacerme una vasectomía. Y me hice una vasectomía.

¿Qué es una vasectomía? Una operación sencillita que se basa en cortar el tubito que lleva los espermatozoides (producidos en la vesícula seminal) al testículo (que produce el resto del semen). Entonces, el semen producido no contiene más espermatozoides! A lo bruto, seguís disparando como siempre pero con balas de salva :p.

Yo pedí turno con un urólogo, me saqué todas las dudas (un poco trata de disuadirte), me mandó a hacer un par de estudios pre-operatorios de rutina, y luego la operación, que es ambulante y bastante rutinaria. Una semana con los testículos hinchados, y otra sin poder hacer ejercicio. Un control un par de semanas después, y otro un par de meses después. Y listo.

Se siente bien poner el cuerpo donde uno tiene la ideología.

De yapa, les dejo un hilo de twitter muy gracioso de Emanuel Rodríguez (a quien yo "sigo desde Cemento") sobre este tema.

Mariano Guerra: Riak Core Tutorial Part 9: Persistent KV with leveled backend

The content of this chapter is in the 07-leveled-kv branch.

https://gitlab.com/marianoguerra/tanodb/tree/07-leveled-kv

Implementing it

Until now we have an in memory key-value store, what do we have to do to make it a persistent one?

We would need to implement a new kv backend, that implements the same API as tanodb_kv_ets but using a library that persists to disk.

For this we are going to use leveled a pure erlang implementation of leveldb.

Being pure erlang means it's easy to build on any platform and easy to understand and contribute since it's all erlang!

The changes will involve making room for configurable KV backends, for that we will keep the backend module in a field called kv_mod in the vnode state:

-record(state, {partition, kv_state, kv_mod}).

On init we will pass an extra field to the KV backend init function with the base path where it can safely store files without clashing with other vnodes in the same node:

init([Partition]) ->
        DataPath = application:get_env(tanodb, data_path, "."),
        KvMod = tanodb_kv_leveled,
        {ok, KvState} = KvMod:new(#{partition => Partition,
                                                                data_path => DataPath}),
        {ok, #state { partition=Partition, kv_state=KvState, kv_mod=KvMod }}.

We are getting the base path to store data from an environment variable (tanodb.data_path), to make it configurable we need to add it to our cuttlefish schema on priv/01-tanodb.schema:

%% @doc base folder where data is stored
{mapping, "paths.data", "tanodb.data_path", [
  {datatype, directory},
  {default, "{{platform_data_dir}}/vnodes"}
]}.

Then we need to replace all the places in tanodb_vnode where we used tanodb_kv_ets to use the value of kv_mod from the state record.

On rebar.config we need to add the leveled dependency, since it doesn't have any release and it's not on hex.pm we will reference the master branch from the github repo:

{deps, [cowboy, jsx, recon,
        {riak_core, {pkg, riak_core_ng}},
        {leveled, {git, "https://github.com/martinsumner/leveled.git", {branch, "master"}}}
]}.

We specify in the release to load leveled and its dependency lz4:

{relx, [{release, { tanodb , "0.1.0"},
                 [tanodb,
                  cuttlefish,
                  cowboy,
                  {leveled, load},
                  {lz4, load},
                  jsx,
                  sasl]},

At this point in time, to be able to compile leveled on Erlang 20.3, we need to add an override to remove the warnings_as_errors option in erl_opts:

{override, leveled,
        [{erl_opts, [{platform_define, "^1[7-8]{1}", old_rand},
                {platform_define, "^R", old_rand},
                {platform_define, "^R", no_sync}]}]}

The code for apps/tanodb/src/tanodb_kv_leveled.erl:

-module(tanodb_kv_leveled).
-export([new/1, get/3, put/4, delete/3, keys/2, dispose/1, delete/1,
                 is_empty/1, foldl/3]).

-include_lib("leveled/include/leveled.hrl").

-record(state, {bookie, base_path}).

new(#{partition := Partition, data_path := DataPath}) ->
        Path = filename:join([DataPath, "leveled", integer_to_list(Partition)]),
        {ok, Bookie} = leveled_bookie:book_start(Path, 2000, 500000000, none),
        State = #state{bookie=Bookie, base_path=Path},
        {ok, State}.

put(State=#state{bookie=Bookie}, Bucket, Key, Value) ->
        R = leveled_bookie:book_put(Bookie, Bucket, Key, Value, []),
        {R, State}.

get(State=#state{bookie=Bookie}, Bucket, Key) ->
        K = {Bucket, Key},
        Res = case leveled_bookie:book_get(Bookie, Bucket, Key) of
                          not_found -> {not_found, K};
                          {ok, Value} -> {found, {K, Value}}
                  end,
        {Res, State}.

delete(State=#state{bookie=Bookie}, Bucket, Key) ->
        R = leveled_bookie:book_delete(Bookie, Bucket, Key, []),
        {ok, State}.

keys(State=#state{bookie=Bookie}, Bucket) ->
        FoldHeadsFun = fun(_B, K, _ProxyV, Acc) -> [K | Acc] end,
        {async, FoldFn} = leveled_bookie:book_returnfolder(Bookie,
                                                        {foldheads_bybucket,
                                                                ?STD_TAG,
                                                                Bucket,
                                                                all,
                                                                FoldHeadsFun,
                                                                true, true, false}),
        Keys = FoldFn(),
        {Keys, State}.

is_empty(State=#state{bookie=Bookie}) ->
        FoldBucketsFun = fun(B, Acc) -> [B | Acc] end,
        {async, FoldFn} = leveled_bookie:book_returnfolder(Bookie,
                                                                                                           {binary_bucketlist,
                                                                                                                ?STD_TAG,
                                                                                                                {FoldBucketsFun, []}}),
        IsEmpty = case FoldFn() of
                                  [] -> true;
                                  _ -> false
                          end,
        {IsEmpty, State}.

dispose(State=#state{bookie=Bookie}) ->
        ok = leveled_bookie:book_close(Bookie),
        {ok, State}.

delete(State=#state{base_path=Path}) ->
        R = remove_path(Path),
        {R, State}.

foldl(Fun, Acc0, State=#state{bookie=Bookie}) ->
        FoldObjectsFun = fun(B, K, V, Acc) -> Fun({{B, K}, V}, Acc) end,
        {async, FoldFn} = leveled_bookie:book_returnfolder(Bookie, {foldobjects_allkeys,
                                                                                                                                ?STD_TAG,
                                                                                                                                {FoldObjectsFun, Acc0},
                                                                                                                                true}),
        AccOut = FoldFn(),
        {AccOut, State}.

% private functions

sub_files(From) ->
        {ok, SubFiles} = file:list_dir(From),
        [filename:join(From, SubFile) || SubFile <- SubFiles].

remove_path(Path) ->
        case filelib:is_dir(Path) of
                false ->
                        file:delete(Path);
                true ->
                        lists:foreach(fun(ChildPath) -> remove_path(ChildPath) end,
                                                  sub_files(Path)),
                        file:del_dir(Path)
        end.

Trying it

From the user perspective nothing changed other than the fact that the data will persist between restarts.

To test it redo the "Trying it" sections from the Handoff and Coverage Calls chapters.

Facundo Batista: Cinéfilo 2018

Otra tanda de películas vistas y las anotadas para ver en un futuro.

No hay grandes descubrimientos pero las cuatro +1 que hay son sólidas.

  • Casi leyendas: -0. Tiene sus partes simpáticas y emotivas, y muchos chistes hacen reir. Pero es demasiado "liviana", casi transparente.
  • Collateral Beauty: +1. Hermosa película sobre la complejidad en algunos casos de lidiar con el tiempo, el amor y la muerte.
  • Dunkirk: -0. No está del todo mal... pero no me gustan las pelis de guerra, no me atrae casi nada, lo desastroso de todo alrededor, conceptualmente, hace que no llegue a ver lo interesante...
  • Ghost of New Orleans: +0. Arranca casi como un cliché caminando, pero después pega algunos giros interesantes que hace que valga la pena.
  • Girl on a Bicycle: +0. Es una comedia romántica tontona, pero linda y divertida, tiene puntos extras por estar hablada todo el tiempo en una mezcla de inglés, alemán, italiano y francés :).
  • Guardians of the Galaxy Vol. 2: -0. Tiene sus momentos, pero hay una cadencia de chistes o gags que no terminan de ser graciosos, y la película no es mucho más que eso...
  • Kiki, Love to Love: +0. Comedia liviana pero divertida con eje en distintas parafilias.
  • Life: +1. Una visión diferente sobre el encuentro de la raza humana con otra vida, en una peli con giros raros, pero no clásicos.
  • Logan: +1. Me gustó mucho, por todo el lado "real" del asunto; no es una peli de superheroes, es una película dramática normal donde un par son mutantes (y obvio, tiene mucha acción y violencia, pero lo que rescato es lo otro).
  • Passage to Mars: +1. Es un documental, un poco lento a veces, pero si te interesa como es la exploración de verdad, imperdible.
  • Personal Shopper: -0. Con partes muy interesantes, y una historia que está bien, pero medio inconexa, muchos agujeros, y le falta como un cierre o propósito.
  • Pirates of the Caribbean: Dead Men Tell No Tales: -0. Me aburrió bastante. Es más de lo mismo, no hay nada nada nuevo :(
  • Spider-Man: Homecoming: +0. Divertida, no mucho más. 100% pochoclera.
  • T2 Trainspotting: +1. Muy buena música, muy buena fotografía. La historia también está muy bien, pero no sé si se entiende todo si no viste la primera.
  • The Red Turtle: -0. Es linda, pero muy lenta! Nos costó llegar hasta el final :/
  • Thor: Ragnarok: +0. La típica de acción de superheroes, pero me divertí bastante, estuvo buena :)
  • Vampyres: -1. No encontré nada nuevo, ni la terminé de ver aburrido de los sinsentidos de la trama y lo malo de las actuaciones.
La desolación, en el Ártico o en Marte

Estas son las nuevas anotadas. Estoy viendo que desde hace un tiempo estoy incorporando bastante películas "no en inglés", pero más que nada porque escucho de ellas! Esto es parte de las mejoras que hablé en algún otro post sobre donde encontrar trailers... que todavía no es perfecto, sin embargo... que me estoy perdiendo trailers (como el de "First man", acá abajo, que me la recomendó Chaghi), pero prefiero perderme trailers de películas mainstream, que luego me entero por otro lado, que de películas menos promocionadas...

  • Camarón: Flamenco y Revolución: (2018; Documentary) - [D: Alexis Morante; A: Camarón de la Isla, Juan Diego]
  • Chavela: (2017; Documentary, Biography, History, Music) Through its lyrical structure, Chavela will take viewers on an evocative, thought-provoking journey through the iconoclastic life of game-changing artist Chavela Vargas. Centered around never before-seen interview footage of Chavela shot 20 years before her death in 2012, and guided by the stories in Chavela's songs, and the myths and tales others have told about her - as well as those she spread about herself - the film weaves an arresting portrait of a woman who dared to dress, speak, sing, and dream her unique life into being.::Aubin Pictures [D: Catherine Gund, Daresha Kyi; A: Pedro Almodóvar, Elena Benarroch, Miguel Bosé]
  • Eva: (2011; Drama, Fantasy, Sci-Fi) Set in 2041, Alex Garel is a well-known robot programmer who after 10 years returns to his home town to work in his old university when his friend Julia brings him a project to create a new line of robot child. There Alex meets his brother David, Lana (Alex's former lover and David's current wife), and Eva, Alex's 10-years-old niece. Looking for inspiration, Alex asks Eva to be the muse of the new robot, watching her attitude and behavior during the time they spend together, making emotional tests to configure its personality. The relationship with his niece gives Alex doubts about finishing the project and awakens old feelings for Lana. At the same time he starts suspecting that perhaps the lovely and imaginative Eva is hiding an important secret about Lana and herself.::Chockys [D: Kike Maíllo; A: Daniel Brühl, Marta Etura, Alberto Ammann]
  • Extraordinary Tales: (2013; Animation, Horror, Mystery) An animated anthology of five tales adapted from Edgar Allan Poe's stories. [D: Raul Garcia; A: Christopher Lee, Bela Lugosi, Julian Sands]
  • First Man: (2018; Biography, Drama, History) A Biopic on the life of the legendary American Astronaut Neil Armstrong from 1961-1969, on his journey to becoming the first human to walk the moon. Exploring the sacrifices and costs on the Nation and Neil himself, during one of the most dangerous missions in the history of space travel.::Matthew Villella [D: Damien Chazelle; A: Claire Foy, Ryan Gosling, Pablo Schreiber]
  • Impulse: (2018; Action, Sci-Fi) 16-year-old Henry Coles is an outsider in her new town of Reston, New York. With a major chip on her shoulder and no friends, she remains withdrawn and isolated, but everything changes when a traumatic encounter with a classmate triggers something deep within Henry- unleashing a power she cannot control. [D: ; A: Maddie Hasson, Sarah Desjardins, Enuka Okuma]
  • The Quietude: (2018; Drama, Thriller) Two sisters, as close as they are different, find themselves after a long separation. [D: Pablo Trapero; A: Martina Gusman, Bérénice Bejo, Edgar Ramírez]
  • Perfect Strangers: (2016; Comedy, Drama) On a warm summer evening, the loving couple of Rocco, a plastic surgeon, and Eva, a therapist, are expecting their good friends to share a pleasant evening over dinner. Everything is in order: The first course is ready, the roast is in the oven, the table is set, and without a doubt, this is going to be a gathering of true friends. Before long, the group will begin the feast; however, in this nice but somewhat ordinary dinner, there's certainly something missing. Perhaps, if everyone placed their mobile phones on the table--and like a dangerous Russian roulette--shared whatever arrived (texts, WhatsApp messages and calls), it would spice things up. Clearly, this uncommon truth-or-dare game has no point among friends who share everything with each other; nevertheless, when the phones start ringing, who will be the one with the sweatiest palms?::Nick Riganas [D: Paolo Genovese; A: Giuseppe Battiston, Anna Foglietta, Marco Giallini]
  • Sicario: (2015; Action, Crime, Drama, Mystery, Thriller) When drug violence worsens on the USA Mexico border, the FBI sends an idealistic agent, Kate Macer (Emily Blunt) on a mission to eradicate a drug cartel responsible for a bomb that had killed members of her team.::Gusde [D: Denis Villeneuve; A: Emily Blunt, Benicio Del Toro, Josh Brolin]
  • Temple Grandin: (2010; Biography, Drama) Biopic of Temple Grandin, an autistic woman who overcame the limitations imposed on her by her condition to become a Ph.D. and expert in the field of animal husbandry. She developed an interest in cattle early in life while spending time at her Aunt and Uncle's ranch. She did not speak until age four and had difficulty right through high school, mostly in dealing with people. Her mother was very supportive as were some of her teachers. She is noted for creating her "hug box", widely recognized today as a way of relieving stress in autistic children, and her humane design for the treatment of cattle in processing plants, which have been the subject of several books and won an award from PETA. Today, she is a professor at Colorado State University and well-known speaker on autism and animal handling.::garykmcd [D: Mick Jackson; A: Claire Danes, Julia Ormond, David Strathairn]
  • Under the Silver Lake: (2018; Comedy, Crime, Drama, Mystery, Thriller) Sam (Andrew Garfield) is a disenchanted 33-year-old who discovers a mysterious woman, Sarah (Riley Keough), frolicking in his apartment's swimming pool. When she vanishes, Sam embarks on a surreal quest across Los Angeles to decode the secret behind her disappearance, leading him into the murkiest depths of mystery, scandal, and conspiracy in the City of Angels.::A24 [D: David Robert Mitchell; A: Topher Grace, Sydney Sweeney, Riley Keough]
  • The House with a Clock in Its Walls: (2018; Comedy, Family, Fantasy, Horror, Mystery, Sci-Fi, Thriller) Lewis Barnavelt, after losing his parents, is sent to Michigan to live with his uncle Jonathan. He discovers his uncle is a warlock, and enters a world of magic and sorcery. But this power is not limited to good people: Lewis learns of Isaac Izard, an evil wizard who wanted to cause the Apocalypse so that he could see what happened afterwards. To do this, he constructed a magical clock with black magic, as long as it exists it will keep ticking, counting down to doomsday. He died before he could finish the clock, but he hid the clock in his house, where Uncle Jonathan now lives. Now Lewis and Jonathan must find the clock before it's too late, and before Isaac's wife, Selena, gets to it. [D: Eli Roth; A: Cate Blanchett, Jack Black, Lorenza Izzo]

Finalmente, el conteo de pendientes por fecha:

(Feb-2013)    2
(Jun-2013)   11   2
(Sep-2013)   16   8
(Dic-2013)   12  12   4
(Abr-2014)    8   8   3
(Jul-2014)   10  10  10   5   1
(Nov-2014)   22  22  22  22   7
(Feb-2015)   13  13  13  13  10
(Jun-2015)       16  16  15  13  11   1
(Dic-2015)           21  19  19  18   6   1
(May-2016)               26  25  23  21   9
(Sep-2016)                   19  19  18  14   1
(Feb-2017)                       26  25  23  21   9
(Jun-2017)                           23  23  21  18
(Dic-2017)                               19  19  18
(May-2018)                                   22  22
(Sep-2018)                                       12
Total:       94  91  89 100  94  97  94  89  84  79