Bienvenida al mDevCamp 2019 & Keynote

mDevCamp es uno de los eventos más únicos de la programación mobile, y hemos tenido la posibilidad y la suerte de poder asistir al mismo. Con un entorno vivo y unas charlas tan refrescantes como influyentes, mDevCamp nos ha llenado tanto con su conocimiento como con la energía que ha derrochado cada miembro y participante de la conferencia.

presentación mDevCamp

Michal Šrajer presentando mDevCamp. Twitter

El evento se inició con una keynote dónde el CHO o Chief Happiness Officer y co-fundador del mDevCamp, Michal Šrajer, explicaba los orígenes del evento y cómo ha evolucionado de ser una conferencia de una sola sala en un un local corriente hasta poder reservar el congreso nacional de Praga para sus eventos. Posteriormente dio paso al maestro de ceremonias, Daniel Čech, que pidió que se levantara todo el público para realizar un particular ejercicio, el “warm up” del programador, pidiendo que todos los asistentes realizaran ejercicios como mover el ratón en círculos, escribir en el teclado o quitarse y ponerse los auriculares; muy divertido.

Tras este acto para animar el ambiente se inició el evento con las charlas introductorias de los héroes de la conferencia.

Héroes del mDevCamp

Antes de empezar con las charlas técnicas, varios miembros de comunidades importantes del evento presentaron algunas charlas más inspiradoras, dónde hablaron sobre el impacto social de la tecnología hoy en día y cómo cada uno de ellos ayudan a la sociedad desde sus comunidades.

Jakub Nesetril mDevCamp

Jakub Nesetril hablando sobre la “suerte” de los programadores. Twitter

El primero fue Jakub Nesetril, fundador de Apiary, recientemente adquirida por Oracle y representante de Česko.Digital, que relató como esta comunidad formada por desarrolladores y otros profesionales del sector ayudaban al estado colaborando con organizaciones que no reciben beneficios del gobierno.

presentación comunidad czechitas

Tras esta charla le sucedió Dita Přikrylová, fundadora de Czechitas, que nos introdujo su comunidad, creada con el objetivo de acercar las nuevas tecnologías a niños, adolescentes, mujeres y ancianos del país. Como caso particular del aporte de esta comunidad, mostró cómo personas mayores que habían perdido sus empleos habían podido reintegrarse al mundo laboral a través de los cursos y la ayuda prestada por la comunidad.

Dita Přikrylová represantando Czechitas

Dita Přikrylová represantando Czechitas. Twitter

Por último, Josef Holy presentó su charla Think Broad. Empezó compartiendo su blog, Algocracy News, donde expone cómo la sociedad se está automatizando cada vez más y dónde tu día a día se decide por algoritmos, recordándonos que a pesar de que sigamos creando más sistemas autónomos, también colaboremos entre todos, fomentando las relaciones a través del open source y junto a personas fuera del sector IT, con una perspectiva más global. Por último, nos preguntó cuántos libros no referentes a IT habíamos leído, definiendo nuestra diversity score en libros leídos NonIT divididos entre libros IT.

Charlas del mDevCamp

Después de la keynote, empezaron las charlas técnicas. Se organizaron en tres tracks dando un total de 21 charlas de varios ámbitos en el área mobile (para más información, podéis consultar el calendario). No pudimos asistir a todas, pero hablaremos un poco de aquellas en las que sí participamos:

Level up in Kotlin Pamela Hill (10am)

La primera charla a la que asistimos fue sobre Kotlin. Pamela Hill, Android Engineer en Luno, introdujo la charla haciéndonos una pregunta sobre cuál era la foto más importante de este año, tema que enlazó con la figura de Katie Bouman, pues es quién lidero el desarrollo e investigación que dio tal resultado, dando así visibilidad a las mujeres del sector tecnológico.

Tras esto, habló sobre la singularidad que ha supuesto la aparición de Kotlin en el sector mobile y cómo se ha adoptado como el primer lenguaje de la plataforma android. En su charla habló de algunos tópicos avanzados de Kotlin: Coroutines, Conventions y DSLs.

Respecto a Coroutines nos contó algunos de los conceptos básicos, como que son los lightweight threads y nativos de la plataforma en comparación a RxJava, y también nos mostró algunos de los constructores básicos y sus scopes. El uso del keyword suspend para poder tener funciones que pueden posponerse sin bloquear el hilo principal y cómo combinarlas con otras suspend functions. También mencionó la posibilidad de decidir el hilo donde se ejecutarían a través de sus dispatchers.

En resumen, con coroutines se mejora la performance, la legibilidad de nuestro código y la compatibilidad con Jetpack. Aquí un resumen con las keys.

Posteriormente habló de Conventions, operadores que se usan con ciertas funcionalidades del lenguaje. Su uso nos ayuda a crear un código mucho más funcional y legible que, al compactar estos métodos de una manera aislada, es más fácil de mantener y mayor nuestra capacidad para memorizar dónde se ejecutan.

Explicó distintas de las funciones que hoy usamos en varios contextos: operaciones aritméticas como minus o sum, comparativas como eq o in para sustituir la función contains a la hora de comprobar un listado e incluso operaciones desestructuradas, pudiendo crear n-variables a raíz de una función:

    data class Result(val result: Int, val status: Status)
    fun function(...): Result {
        // computations

        return Result(result, status)
    }

    val (result, status) = function(...)

En resumen, las keys aquí.

Por último, hizo una introducción a las DSLs o domain-specific languages. Nos explicó brevemente el concepto y algunos tips a la hora de crear nuestras DSLs, como por ejemplo: usar extension functions para aumentar la legibilidad, crear lambdas con receptores, usar el operador invoke o usar funciones infix.

What is a DSL?

En su blog tenéis un research más profundo de los temas que mencionó durante su charla.

Live Coding in Swift Chris Eidhof (11am)

A las 11 de la mañana en el hall principal vimos el Live Coding in Swift de Chris Eidhof. Personalmente tenía bastante interés, ya que sigo todo el estupendo contenido que publican junto con su compañero Florian Kugler en su web www.objc.io.

Siguiendo la tónica de sus videos, nos presentó un problema desde cero: hacer un wrapper de una librería antigua en C a Swift. Se trataba de una librería de bajo nivel que permitía dibujar figuras en pantalla.

Primero nos enseñó cómo importar librerías en C a código Swift con XCode.

La primera solución final del problema, es decir, el wrapper acabado escrito en Swift, no tardó en llegar. Pero como decíamos antes, siguiendo la metodología que usan en sus ya habituales videos, fue haciendo iteraciones a cada cual más interesante, hasta obtener una sencilla API de alto nivel con algún genérico que nos permitía hacer uso de la ya desactualizada librería de C.

Por último nos animó a hacer lo mismo con la infinidad de librerías útiles que a día de hoy existen en C y no tienen su pasarela hecha a Swift.

Presentación de Chris Eidhof

Presentación de Chris Eidhof


Lunch (12am – 1:15pm)

Después de las primeras charlas tuvimos un break de aproximadamente una hora y media para poder comer. El catering constaba de comida tradicional muy basada en legumbres y verduras. También nos ofrecieron mucha fruta variada 💚

Fruits!

Fruits!

Después de este break, nos separamos para ir cada uno a las charlas más centradas en nuestras plataformas favoritas:


CHUCHEL, Our Problem Child Jakub Dvorský (1:15pm)

Jakub Dvorský nos explicó en su charla CHUCHEL, Our Problem Child todos los problemas que tuvieron en el desarrollo de su videojuego CHUCHEL. Fue una charla menos técnica, pero no por ello menos interesante.

The Nitty Gritty of Unit Testing in Kotlin Yun Cheng (1.15pm)

Al inicio de su charla, Yun Cheng presentó los proyectos de Asics en los que estaba participando: una aplicación que estaban migrando a Kotlin desde Java y otra que estaban haciendo desde cero.

Empezó su reflexión desde un MVP clásico usando un stack de librerías de terceros clásico en Android: RxJava, Mockito y JUnit junto al común test de presentación cuando recibimos un listado de objetos y gestionar si este viene vacío o no para actualizar nuestra vista debidamente.

Su charla consistió en cómo iteraba en diversas soluciones a los problemas que el compilador le daba en su test.

El primer fallo era que el compilador le lanzaba un error del tipo:

    Mockito cannot mock/spy following:
     final classes
     anonymous classes
     primitive types

Este error ocurre porque la librería de Mockito 1.x no esta optimizada para trabajar en Kotlin y sus final classes, entonces contempló varias soluciones cómo hacer añadir el modificador open a nuestras clases, remarcando que era una mala práctica ya que modificaba las clases de producción. Otra solución era crear interfaces que replicaran el comportamiento de nuestra clase. Prosiguió actualizando la librería a Mockito 2 aplicando adicionalmente una librería llamada mockito-inline (o creando un archivo MockMaker con la línea mock-maker-inline). Tras ese cambio se solucionó el anterior error, pero entonces apareció el siguiente:

    java.lang.IllegalStateException: captor.capture() must not be null

Para solucionar este error siguiendo su camino, recomendó usar este gist para evitar la excepción.

Por último, al usar el método when de la librería de Mockito el test te devuelve un error, porque se nombra igual que el keyword when de Kotlin. La solución es bien sencilla: el método se pone entre comillas, y los tests vuelven a estar verdes.

Este es un caso particular de usar la librería Mockito pero para evitar estos inconvenientes también hay otras librerías de terceros respaldadas por la comunidad como mockK o mockito-kotlin.

Introducing MicroViewController Ken Tominaga (1:55pm)

Ken Tominaga, desarrollador de Mercari, una app de e-commerce presente en Japón y USA, nos presentó su charla de Introducing MicroViewController. Fue un poco polémica, debido a que propuso un cambio bastante drástico en las vistas migrando todas las UIView a ViewControllers.

Una vista normal usando MicroViewControllers

Una vista normal usando MicroViewControllers

Al finalizar la charla se plantearon muchas preguntas sobre posibles caveats que tiene el uso masivo de ViewControllers, como por ejemplo la comunicación en ambos sentidos con elementos enterrados en una jerarquía de vistas. La posible solución que propuso Ken fue usar un DataStore genérico y singleton para acceder directamente a capas más enterradas.

Como ventajas claras del uso de esta arquitectura está la facilidad de que varios programadores puedan trabajar de manera aislada en un ViewController en particular sin pisar el trabajo de los demás. Otra ventaja es que en iOS las UIView no tienen ciclo de vida y los UIViewControllers sí.

Why do we need micro view controllers

Evolution of Test Automation in Spotify Sangsoo Nam (1:55pm)

Esta era una de las charlas más esperadas del evento, y no defraudó. Sangsoo Nam nos explicó cómo han mejorado el testing que se aplica en Spotify a base de distintas iteraciones en función de como escalaban en el producto.

Empezó introduciéndonos la test pyramid que podemos apreciar por ejemplo en este artículo de Martin Fowler que en resumen viene a ser esta imagen:

Test pyramid by Martin Fowler

Tras introducir un ejemplo de test (el Login de una app) nos comentó la importancia de realizar tests de integración porque su principal objetivo era entregar ese contenido específico a cada usuario y que para ello había que extraer información de multiples entidades hasta obtener una playlist dedicada, un autor recomendado, etc… Para ello nos comentó una de las herramientas que utilizan, Beanshell, que genera un script simulando nuestra integración y realizarla de una manera externa a la app.

Al centrarse en esto, aumentando su cantidad de tests de integración, también los tests se volvieron más lentos, lo que retrasaba mucho las entregas. Para ello desarrollaron una herramienta interna, Cassette, con la que adaptando sus antiguos tests consiguieron incrementar hasta 20 veces su velocidad.

Cassette Capabilities

Cassette Capabilities

Con esto y usando Espresso para los UI tests conseguían estabilizar esa pirámide y tener un entorno de test productivo.

Por último, nos presentó un poco la cultura de tests que tienen dentro de la empresa y para ello crearon un manifesto o certificado, llamado TC4C o test certified for client, dónde explican las condiciones y objetivos de sus tests.

TC4C, Test Certified for Client

TC4C, Test Certified for Client

Free Cocoa: hack mobile app and skim the cream Kamil Borzym (2:35pm)

Kamil Borzym explicó lo importante que es prestar atención a la seguridad de nuestras apps.

En su charla nos mostró de una manera interactiva el proceso para conseguir hackear una app de una supuesta cafetería donde la primera vez que hicieses un registro obtuvieras un café gratis.

Gracias a un software open source de ingeniería inversa consigue obtener de una app ya compilada (.ipa/.apk) todas las líneas de código en ensamblador, pero dado que no ofuscaron los nombres de los métodos podemos localizar rápidamente métodos como validateCredentials.

Una vez localizados los métodos que sabemos que tienen que ver con el control de usuario podemos eliminar las líneas críticas de esa app compilada y obtener una nueva aplicación que al ejecutarse se salte literalmente todo tipo de check de seguridad. En el caso expuesto, obtendríamos un café cada vez que abriéramos la app.

Tener en cuenta que esa app modificada no es la del market (AppStore/PlayStore), obviamente, pero gracias a stores de distribución fraudulentas como cydia no sería difícil para un usuario obtener una versión de la aplicación de vuestra empresa totalmente modificada.

Como notas finales recomendó que antes de subir una app al market se pase un proceso de ofuscación donde se cambien todos los nombres de los métodos por nombres aleatorios sin relación lógica con la función que desempeñan, haciendo de este modo más difícil el hackeo. El software para ofuscar que propuso.

Security disclaimer

Security disclaimer

Incorporating Material Theming into Custom Views Nick Rout (2:35pm)

Después de varias charlas más orientadas al negocio de nuestras aplicaciones, tuvimos a Nick Rout, Android Engineer en [Over](https://twitter.com/Over), que nos explicó cómo usar material components en nuestra aplicación Android.

Introdujo la charla con la pregunta “¿Qué es Material Theming?”, enlazándola posteriormente con el sistema ASCII, con el que podemos generar tamaños, tipografías y colores.

Nos contó que desde la incorporación de androidX, la librería que usábamos habitualmente para incorporar elementos de Material Design se había modificado, incluyendo nuevos componentes que podemos configurar a través de estilos y reutilizarlos en nuestra aplicación.

    //old
    implementation com.android.support:design
    //androidX
    implementation com.google.android.material:material

Tras esta introducción dio paso a un Playground donde tenía varios material components que quería modificar de manera homogénea. Para ello empezó incluyendo un nuevo componente personalizado, ColorPickerView. Acto seguido, modificó su hoja de estilos con todos los colores, tamaños y apariencia que quería que tuvieran sus componentes, pero el resultado no era el que esperaba y menos aún cuando usaba un tema oscuro.

Su componente personalizado no aplicaba los cambios, por lo que explicó paso a paso cómo modificar cada una de las vistas internas usando el recurso de la nueva librería en vez del estándar, configurando su estilo para que fuera posible personalizarlo a través del archivo xml dónde se implementa.

Por último, para poder dar soporte a un tema oscuro, comentó la propiedad elevationProviderOverlay, que te permitía aumentar la elevación del componente haciendo que aumente el brillo del fondo para poder resaltar los componentes más pequeños.

Podéis ver las slides de esta charla aquí.

How Artsy Automates Team Culture Ash Furrow (3:30pm)

Ash furrow on stage

Ash furrow on stage

La ponencia de Ash Furrow estaba enfocada en cómo organizan el workflow de su empresa con 33 ingenieros trabajando simultáneamente.

Presentó las herramientas que usan, enfocadas a la automatización de todo tipo de procesos.

La primera de ellas es Danger, una herramienta que corre durante el proceso de la CI y automatiza procesos más comunes de code review. Se definen unas normas previamente y Danger deja escritos en el PR comentarios sobre todo lo que detecta. En la web del framework se pueden ver ejemplos de uso.

Otra herramienta es Peril, un webhook que se enlaza con GitHub y nos permite (una vez más basándonos en unas reglas previamente definidas), por ejemplo, cuando cerramos una issue en GitHub que tiene un ticket de Jira asociado, resolverlo y cerrarlo también.

Aquí podéis encontrar las slides.

Journey to painless releases: Continuous delivery for Philips Hue Android Jeroen Mols (3:30pm)

En esta charla más orientada al producto y no a las plataformas en sí, Jeroen Mols nos habló de cómo ha sido la evolución en Phillips del desarrollo y entrega de su app, Phillips Hue.

Para ello mostró el ciclo de entregas (en adelante releases) que tenían antes de implementar Continuous Delivery, lo que les llevaba aproximadamente unas 10 semanas en tener una versión de producción lista entre desarrollo, testing y beta. Además esto provocaba tener otras versiones en distintas fases, ya que trabajaban en un entorno usando Git Flow con múltiples features en desarrollo.

Las primeras frustraciones que detectó con esta cadena es que:

  • Los problemas eran detectados muy tarde.
  • Los desarrolladores están desconectados de los usuarios, ya que están trabajando en features bastantes avanzadas cuando tiene lugar la release a producción.
  • El contexto varía enormemente entre releases.
  • Hacer una release era muy doloroso.
  • Fricción entre desarrolladores y testers.

Branching model in early phase

Branching model in early phase

Este modelo de ramas que se aplicaba a las releases generaba otras frustraciones adicionales cómo:

  • Rama de desarrollo inestable.
  • Pesadillas en los merges.
  • Revisiones de código pobres.
  • Confusión acerca de dónde resolver bugs.
  • Desarrollo lento.

En resumen, bugs, desarrollo incompleto e insatisfacción del equipo de desarrollo. Por ello se establecieron unas metas, velocidad, calidad y feedback más dinámico entre desarrolladores y usuarios, introduciendo el continuous delivery al producto.

If it hurts, do it more often and bring the pain forward – Jez Humble

Para aumentar la velocidad de las releases propone reducir el tamaño de las pull requests para que sean más rápidas de verificar, a su vez tener actualizadas las librerías de Android, modular en otras soluciones. En CI, usar git hook junto a git cache, precargar el wrapper de Gradle en un contenedor de Docker. Con esto reducieron las builds de 20 minutos a 5.

Se establecieron mas reglas, como que las features no deberían ser superiores a 2 días, asegurando que se integraran lo más rápido posible en master.

También reducir la complejidad eliminando la rama de desarrollo para realizar el merge directamente en master.

Aplicar configuraciones a nivel de Gradle con las distintas versiones de la app, Daily, Test y Production, para que la subida de las versiones más orientadas al test sea lo más liviana posible y optimizadas a través de feature flags. Pasando el proceso de verificación a los propios desarrolladores que a través de un análisis de impacto, determinan si una feature puede provocar alto o bajo impacto en master, para decidir si hay que testear más a fondo y dándole confianza a los desarrolladores.

Impact Analysis

Al final de aplicar estos cambios sus releases son cada dos semanas, pero a su vez la integración de nuevos cambios es diario y con versiones beta más optimizadas. Todo a través de CI/CD, incrementando además la felicidad del equipo y la satisfacción de los usuarios.

Las slides al respecto, podéis verlas aquí.

Improving Developer Experience through tools and techniques Krzysztof Zabłocki (4:30pm)

La última charla de iOS que vimos fue la de Krzysztof Zabłocki, estuvo enfocada al uso de múltiples técnicas y librerías que nos facilitan la vida como desarrolladores.

Algunas de sus aportaciones open source son Sourcery, Playgrounds y Bootstrap, corriendo en más de 30000 apps, algunas tan conocidas como Tinder.

Krzysztof Zabłocki on stage

Krzysztof Zabłocki on stage

RxJava & Coroutines: A Practical Analysis Ashley Davies

La última charla en Android fue introducida por Ashley Davies, Lead Engineer en ImmobilienScout y que estaba basada en el uso de la popular librería RxJava contra las Coroutines propuestas en Kotlin.

En ella nos mostró una breve introducción a los scopes utilizados en coroutines y sus diferentes aplicaciones. 

Nos propuso que las fuéramos implementando, ya que la curva de aprendizaje es sencilla, es una funcionalidad de primera clase nativa (que no depende de ninguna librería de terceros) y por tanto muy ligera.

Tras ello explicó un poco de historia en Android respecto a operaciones asíncronas que fue desde Runnable/Handler, Asynctask, IntentService, WorkManager… hilando todas multitud de opciones con esta viñeta cómica.

How standards proliferate

Después de esta breve reflexión, introdujo RxJava al rescate. Argumentando que cuando salió por primera vez y al ser una librería reactiva, los desarrolladores nos enganchamos rápidamente a ella y esto se sucedió en la creación infinita de librerías usando RxJava.

Rx libraries

Explicó como los desarrolladores estábamos usando RxJava de una manera desorbitada realizando un sin fin de operaciones debido a que la complejidad era muy alta y por tanto la mayoría de los desarrolladores que no eran expertos en su implementación buscaban soluciones a sus operaciones complejas cómo esta:

    Observable
      .fromIterable(resourceDraft.getResources())
      .flatMap(resourceServiceApiClient::createUploadContainer)
      .zipWith(Observable.fromIterable(resourceDraft.getResources()), Pair::create)
      .flatMap(uploadResources())
      .toList()
      .toObservable()
      .flatMapMaybe(resourceCache.getResourceCachedItem())
      .defaultIfEmpty(Resource.getDefaultItem())
      .flatMap(postResource(resourceId, resourceDraft.getText(), currentUser, resourceDraft.getIntent()))
      .observeOn(AndroidSchedulers.mainThread())
      .subscribeOn(Schedulers.io())
      .subscribe(
          resource -> repository.setResource(resourceId, resource, provisionalResourceId),
          resourceUploadError(resourceId, resourceDraft, provisionalResourceId)
      );

Una cadena tan grande que escalando y escalando se puede volver incontrolable, haciendo que los mismos beneficios que buscábamos en la comodidad de usar Rx también nos repercutiese.

“If all you have is a hammer, everything looks like a nail” – Abraham Maslow, The Psychology of Science, 1966

Tras explicar todos los contras de porque abusar de Rx era malo, realizo diversas comparativas de cómo podíamos realizar algunas de las operaciones más simples con Coroutines.

    // RxJava2
    getUser()
      .subscribeOn(Schedulers.io())
      .observeOn(AndroidSchedulers.mainThread())
      .subscribe { /* Do something */ }

    // Coroutines
    launch(Dispatchers.Main) {
      withContext(Dispatchers.IO) {
        val user = getUser()
        /* Do something */  
      }
    }

Por último, resolvió algunas de las preguntas que le pueden surgir a los desarrolladores como si deberían migrar sus proyectos a coroutines o si es un reemplazo de RxJava, respondiendo que no a ambas preguntas, argumentando que si algo no esta roto, no hace falta arreglarlo y que con RxJava se pueden tratar operaciones complejas de una manera más funcional y mas legible.

“If it ain’t broke, don’t fix it” – Bert Lance, Nation’s Business, 1977

Eso sí, recomendó que todo el mundo empezara a usar coroutines en sus nuevas features siempre que no afecte al código en producción o empezando desde cero.

Para más información de la charla, sus slides aquí.

Lightning Talks & Closing Ceremony

Antes de terminar la conferencia, algunos participantes tuvieron la oportunidad de participar en las lightning talks, que eran charlas de aproximadamente 7 minutos para hablar sobre su “segunda vida”, qué les gustaba hacer fuera de su horario de trabajo, una manera interesante para poder desconectar de toda la dosis de información que produjo muchas risas y señales de compresión entre los integrantes.

El primer participante explicó que lo que le apasionaba era hacer triatlón, expresando de una manera muy efusiva cómo trataba de mejorar sus récords en cada intento.

El siguiente participante practicaba slackline, este deporte que consiste en mantener el equilibrio en una cuerda atada a dos puntos fijos y cómo realizarlo le ayudaba a gestionar su mente ya que requiere una gran concentración.

El tercer participante nos vendió el mejor juego de realidad virtual, siendo este LARP, live action role playing, más conocido por interpretar un papel en una secuencia histórica, ficticia o lo que fuera con mucha gente.

El cuarto participante habló de que le gustaba dibujar para finalmente vendernos una app que había creado que ayudaba con la práctica y el último participante, le gustaba ensayar magia y realizó un show frente a la cámara que nos dejó sin palabras.

Finalmente, apareció el CHO para dar las gracias a todos los asistentes, partners, speakers y a todos los participantes que subieron junto a él para despedir el evento.

Closing ceremony

AfterParty 😎

Primer speaker presentando su random slides

Tras terminar la conferencia, se realizaron diversas actividades dónde propiciaban el networking entre todos los asistentes. Primero, hubo un concurso de random slides dónde algunos de los speakers de las anteriores charlas tuvieron que defender productos propuestos por el público como el Google Toilet Paper o el Apple Apple improvisando conforme las slides se sucedían.

Kotlin ketchup

Posteriormente, hubo un quiz por equipos de 30 preguntas basadas en tecnología, programación y series geeks en el cual participamos junto con otros desarrolladores locales.

Quiz team

Sin duda, mDevCamp ha sido una conferencia extraordinaria para los desarrolladores mobile con charlas al día, una organización excelente y un ambiente agradable. Nos hemos ido con una gran experiencia detrás y estaremos esperando al 2020 para ver su próxima edición.