Curso de Desarrollo de software basado en agile y XP (II edición)

https://www.biko2.com/curso-desarrollo-software/

Fechas Duración Plazas Lugar
7sept-21dic 2021 160 h 16 Oficinas de Biko

En Biko hemos creado el curso al que nos hubiera gustado asistir. Un curso en el que no te vamos a enseñar a programar, sino a convertirte en un excelente desarrollador. Y de eso va esto: de aprender a aprender, de dejar atrás el "qué" para centrarnos en el "cómo".

Aprende a desarrollar software (pero de verdad)

Ser desarrollador/a no implica saber desarrollar (buen) software. Y viceversa. Los lenguajes de programación pueden cambiar, pero si conoces los principios, construirás y entregarás valor continuamente.

Esto no es (solo) un curso, esto es otra historia

El Curso de Desarrollo de Software basado en agile y XP es una formación totalmente práctica, alejada de lo que se aprende en cursos tradicionales o en la universidad.

No se trata de enseñarte a tirar tu primera línea de código, sino de que aprendas cómo desarrollar software de valor, trabajar con metodologías ágiles y de mejorar tu potencial.

Al finalizar, obtendrás una titulación oficial homologada por el Ministerio. Y gracias al Servicio Navarro de Empleo, el curso es totalmente gratuito. Suena bien, ¿eh?

Vocabulario tech

UX, mobile, desktop, asistentes de voz Omnicanalidad, mailing, SEO

TDD, testing, testing unitario Patrones de diseño, buenas prácticas de desarrollo, refactoring, pair programming Integración continua, desarrollo incremental, software craftsmanship, deuda técnica

Agile, Scrum, Lean, Kanban [para gestión de cualquier trabajo creativo] XP (Extreme Programming) [marco de trabajo especializado en desarrollo]

Frontend, backend

HTML, CSS, JS React, Angular, Vue Node, PHP, Symphony


"La diferencia entre librería y framework reside en quién tiene el control" – Pablo

  • Librería: tu código llama a la librería. Ej.: ReactJS
  • Framework: el framework llama a tu código. Ej.: Symphony, AngularJS

Drupal, Wordpress, CMS

Cloud, AWS, Lambda, CDN, API, API REST, GraphQL

"GraphQL te da los ingredientes y te montas el bocadillo que quieres. Con API REST tienes que elegir tu bocadillo entre los disponibles de la carta" – Aritz

Git

Conceptos

3 estados: modified, staged y committed

3 áreas de trabajo: git dir (.git/), working dir (copia de una versión) y staging area (próximo commit)

Comandos

  • git stash con nombre: git stash push -m "readme updated"
  • en versiones nuevas de git, git checkout -- . = git restore y git checkout branchB = git switch branchB

Ojocuidao

Detached HEAD: El commit en el que estamos no está en ninguna rama, por lo que si nos movemos de rama este commit se perderá.

git merge: Solo se puede hacer fast-forward si, en el momento de hacer merge, no hay commits añadidos a la rama sobre la que escribimos (ej: master) después de haber empezado nuestra rama (en la que hemos trabajado).

Javascript

Versión ES6

Arrow functions

const d = () => new Date();
const myConcat = (arr, arr2) => arr1.concat(arr2);
console.log(myConcat([1,2],[3,4,5]));

Template literals

`<li>${arr[i]}</li>`

Destructuring

const {name, age} = user;
const bicycle = {
	gear: 2,
	setGear(newGear) {
		this.gear = newGear;
	}
};
bicycle.setGear(3);

Spread operator

arr2 = [...arr1];

Mixin, IIFE

let funModule = (function(){
	return {
		isCuteMixin: function(obj) {
			obj.isCute = function() {
				return true;
			}
		},
		...
	}
})();

Nullish coalescing

const getName = (animal) => { return animal.name ?? 'Toby' };

Typescript

  • Typescript añade tipado estático. Se transpila a Javascript.

  • Las propiedades por lo general siempre privadas. Si hay que devolver su valor, mejor a través de un método.

  • Everyday types:

    • The primitives: string, number and boolean
    • Arrays: Array<string>
    • Any
    • Union Type: let aTextOrNumber: number|string = 5;
    • Generic types: function identity<Type>(arg: Type): Type { return arg; }
  • Interfaz: "el contrato que tenemos que cumplir"

interface Animal {
  type: string;
  name?: string;
}
class Cat implements Animal {
    constructor(public type: string) {}
    ...
}
class Pokemon {
	constructor(private nombre: string) {}
}
/* Encadenamiento opcional u optional chaining (?.)
El operador ?. funciona de manera similar a el operador de encadenamiento .,
excepto que en lugar de causar un error si una referencia es casi-nula (null o undefined),
la expresión hace una evaluación de circuito corto con un valor de retorno de undefined.
Puedes saber más en: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-7.html#optional-chaining */
const getName = (animal?: Animal) => animal?.name;

"Tell, don't ask"

A una clase le tengo que pedir el nombre (getName()), no preguntarle por el nombre (this.name).

NodeJS

Entorno de ejecución de Javascript.

  • Con el motor V8 de Chrome, en C++.
  • Motor de alto rendimiento de Javascript y WebAssembly.
  • Orientado a eventos asíncronos, hilo único.

Gestores de paquetes

npm

npm init # -> package.json
npm install # instala las dependencias de package.json
			# npm ci es más rápido
npm run # ejecuta un script definido en package.json
npm start # ejecuta el script "start" de package.json
npm install --save-dev/-D

yarn

yarn utiliza el registro de paquetes de npm

npx

node package manager

npx create-react-app # = npm install create-react-app

package.json

$ cat package.json
{
  "name": "proj_node",
  "version": "1.0.0",
  "description": "",
  "main": "index.ts",
  "scripts": {
    "start": "ts-node index.ts",
	/*        ^^^ apunta al binario local, que desde node_modules
	   está en .bin/ts-node -> ts-node/dist/bin.js (16KB)
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@types/node": "^16.9.2",
	// ^^^ para tener los tipos de las librerías (ej: http)
    "ts-node": "^10.2.1",
	// ^^^ evita que tengamos que hacer el build de ts a js
    "typescript": "^4.4.3"
	// ^^^ para transpilar
  }
}

Introducción a web

"TCP es como el doble check de Whatsapp" (definición 🔝) – Carlos

¿Cómo se sirve el HTML de la aplicación?

Ficheros estáticos almacenados vs. Ficheros generados dinámicamente, bajo demanda

¿Dónde se genera el HTML de la aplicación?

CSR/SPA (Client-Side Rendering / Single Page Application) vs. SSR/MPA (Server-Side Rendering / Multiple Page Application)


Callback hell

Asynchronous JavaScript, or JavaScript that uses callbacks, is hard to get right intuitively. A lot of code ends up looking like this:

fs.readdir(source, function (err, files) {
  if (err) {
    console.log(err)
  } else {
    files.forEach(function (filename, fileIndex) {
      console.log(filename)
      gm(filename).size(function (err, values) {
        if (err) {
          console.log('err')
        } else {
          console.log(filename + ' : ' + values)
          aspect = (values.width / values.height)
          widths.forEach(function (width, widthIndex) {
             ···
          }.bind(this))
        }
      })
    })
  }
})

See the pyramid shape and all the }) at the end? Eek!

CSS

Son estilos en cascada porque los estilos se aplican de arriba a abajo y, en caso de existir ambigüedad, siguen una serie de normas para resolverla.

Gracias a CSS podemos llevar a cabo el concepto de separación de presentación (diseño, colores, formas - CSS) y contenido (información y datos - HTML).

Espaciado CSS

Así se ve el modelo caja en el inspector de cada navegador (derecha: Chrome, izquierda: Firefox):

  • Padding: entre elemento y elementos internos
  • Margin: entre elemento y elementos externos

Ejemplo:

.warning {
    padding-top: 100px; /* este no aplica,
                           porque el siguiente lo sobreescribe */
    padding: 50px;
}

Los márgenes entre elementos del mismo nivel no se suman, sino que se queda el mayor. Con los paddings sí se suman (son una propiedad interna de la caja).

Recomendación: usar por lo general padding-bottom. Sobre todo mantener un mismo criterio.

Box-sizing

div {
    box-sizing: content-box;
    height: 200px;
    width: 300px; /* width aplica al contenido */
}

Content-box se rompe fácil intentando restar píxeles de margen al % de width.

div {
    box-sizing: border-box;
    height: 200px;
    width: 300px; /* width aplica al margen */
}

Content-box se rompe fácil intentando restar píxeles de margen al % de width.


.page_container {
    margin: 0 auto;
}

Recursos habituales

Cascada y herencia

  • En igualdad de condiciones, gana lo que está escrito después en el CSS.
  • Los hijos heredan estilos del padre. No ocurre con todas las propiedades: No ocurre con border, margins, padding, ... Sí ocurre con color, font, ...
  • Se puede forzar la herencia asignando el valor inherit a la propiedad que queremos que aplique: background-color: inherit;

Clases de utilidad

Nos ayudan a separar responsabilidades (ej.: estilos visuales y estilos de layout).

.margin-bottom-s {
   margin-bottom: 12px;
}

Flexbox

#pond { display: flex; }

Al asignarles el tipo flex a los divs, pasan a ocupar solo lo que su contenido necesita.


Posible solución: usar media queries o:

.card_content {
   width: 300px;
   flex-grow: 1;
}

ReactJS

  • Librería, no framework
  • Componentes en vez de plantillas. Simplifica el trabajo con el DOM.
  • Moderno: ES6, Babel (transpila JS moderno a compatible con todos los navegadores), Webpack (bundelizador, es decir, junta scripts de javascript).

Flujo de datos

  • Se inicia un cambio de estado
  • Se propaga al padre
  • Y se extiende

Las funciones han dejado obsoletas a las clases

import React, {useState} from 'react';
import ReactDom from 'react-dom';

function Counter() { return ...; }

ReactDOM.render(<Counter/>, document.getElementById('root'));

Antes se hacía:

class Welcome extends React.Component {
	render() { return ...; }
}

Hook 1: useState

  1. Coger input (el evento onChange recibe la propiedad event):
const Header = ({ onFilter, onClear, filter }) => (
  <>
    <ComicInput onChange={event => onFilter(event.target.value)} value={filter} />
    <Button marginLeft="base" onClick={onClear}>
      Limpiar búsqueda
    </Button>
  </>
)
  1. Inicializar variables:
export const ComicsList = () => {
  const [filter, setFilter] = React.useState('')
  1. Filtrar en lista:
  const filteredComics = comics.filter(comic => comic.title.toLowerCase().includes(filter.toLowerCase()))
  return (
    <Layout>
	   ...
	  <Header onFilter={setFilter} onClear={() => setFilter('')} filter={filter} />
      <List comics={filteredComics} />
	   ...
    </Layout>
  )
}

ReactJS

Hook 2: useEffect

Es una función que se ejecuta después de useState (ejecuta efectos secundarios).

useEffect( () => {}, [] )
/*            ^^     ^^
              |      └ deps: array de estados de los que depende
              └ effect: función a la que aplica
*/

Si la función es una llamada asíncrona,

const fetchComics = async() => {
	setComics( await api.allComics() )
}
useEffect( () => { fetchComics() }, [] )

useEffect(() => {
	async function fetchComics() {
	  setComics(await getCommonComics(firstSelectedChar, secondSelectedChar))
	  //        ^^^ es igual que:
	  //getCommonComics(...).then( data => {setComics(data)} );
	}
	fetchComics()
}, [firstSelectedChar, secondSelectedChar])

  • Por defecto, los efectos se ejecutan después de cada renderizado completo, pero pueden ejecutarse solo cuando ciertos valores han cambiado (deps).
  • El modelo mental para entenderlo es pensar con qué parte del estado estamos sincronizando este "efecto". No pensemos en cuándo se ejecuta.

Hay más Hooks adicionales, pero son variantes de los básicos: el Hook de estado y el de efecto.


Arquitectura hexagonal (avance)

  • Dominio: tu negocio
  • Infra: externo (ej: AWS)
  • Servicio: coordina tu dominio con tu infra. También se conocen como "casos de uso"

Routing

ui/views/Root.jsx:

import { Route, Switch } from 'react-router-dom'
import { routes } from 'ui/routes'

export const Root = () => (
  <>
    <GlobalStyles />
    <Switch>
      <Route exact path={routes.COMICS} component={ComicsList} />
    </Switch>
  </>
)

ui/routes.js:

export const routes = {
  COMICS: '/'
}

Agile

Planning fallacy

El triángulo de hierro

Valores del manifiesto ágil

Individuos e interacciones sobre procesos y herramientas

Software funcionando sobre documentación extensiva

Colaboración con el cliente sobre negociación contractual

Respuesta ante el cambio sobre seguir un plan

Esto es, aunque valoramos los elementos de la derecha, valoramos más los de la izquierda.

Principios del manifiesto ágil

Elijo algunos de los 12 principios:

  1. El método más eficiente y efectivo de comunicar información entre el equipos y sus miembros es la comunicación cara a cara (mayor Ancho de Banda).
  2. Los procesos Ágiles promueven el desarrollo sostenible -> mantener un ritmo constante de forma indefinida.
  3. Es esencial la simplicidad -> el arte de maximizar la cantidad de trabajo no realizado.
  4. El equipo reflexiona a intervalos regulares para cambiar su comportamiento en base a lo aprendido.

Prácticas y metodologías ágiles

Maximizar el valor

  • Planificación en términos de producto
  • Priorización en base a valor
  • Colaboración con el cliente
  • Visibilidad del proceso
  • Detectar y eliminar desperdicio

Desarrollo iterativo e incremental

Agile != Scrum

  1. 4 people in the room (Cockburn)
  2. Extreme Programming
  3. Kanban
  4. Crystal
  5. Scrum

Scrum

Panel de scrum (artefacto)

To-DoDoingDone

XP

"El problema básico del desarrollo de software es el riesgo." – Kent Beck

  • Evitar riesgos de software
  • Acortar el ciclo de feedback:
    • Con compañeros (programando)
    • Con clientes (features)
    • Con usuarios (a producción)

¿Para qué XP?

  • Calidad

  • Código limpio

  • Profesionalidad

  • Hacer lo correcto

  • Economics

  • Felicidad

Testing

Un test es un bloque de código que ejecuta a otro código (SUT1) para validar de forma automática su correcto funcionamiento.

1

System Under Test

Tipos de tests

  • Unitarios
  • Integración
  • Aceptación o extremo a extremo (end to end, E2E)
  • Regresión visual

Estructura de un test

- Preparación / Ejecución / Aserción
- Arrange     / Act       / Assert   (AAA)

Ejemplo de test escrito con Jest:

describe(name: 'Sum', fun: () => {
//^^^ conjunto de tests

/*
LEGIBILIDAD DE LOS TESTS: más importante en los tests incluso que en código de producción.
- Claridad, simplicidad y densidad. Eliminar detalles innecesarios.
- Utilizar el lenguaje de dominio para describirlos
*/

	it('should sum two numbers', () => {

		// Arrange:
		const num1 = 1;
		const num2 = 2;

		// Act:
		const result = sum(num1, num2);

		// Assert:
		expect(result).toEqual(3);

	})

})

Cualidades de un test

FIRST:

  • Fast
  • Independent
  • Repeteable
  • Self-validating
  • Timely (oportuno: los programamos lo antes posible)

Puntos clave

  • En cada test prueba UNA sola cosa que funciona.
  • Testea el qué, no el cómo.
  • Testea la interfaz pública de cada unidad, no los métodos privados (la interfaz pública solo llamará a los privados).
  • Hay que mantener los tests, deben ser fáciles de entender, y sólo deben fallar cuando aquello que quieres probar no funciona.
  • No testees código trivial, que no tiene lógica (p.ej. getters, setters).
  • Elimina detalles innecesarios (incluso tests que se hayan convertido en innecesarios, una vez han cumplido su labor de ayudarte en el proceso de desarrollo).
  • No escribas tests por buscar el 100% de cobertura. Haz test útiles que te ayuden a comprobar que el sistema funciona y te den fiabilidad a la hora de refactorizar.

TDD

Es una técnica de desarrollo de software que consiste en escribir tests antes de escribir el código.

TDD:

  • No te guía a hacer un buen diseño.
  • No es dogma.
  • No es una técnica de testing.

Beneficios TDD

  • Obliga a pensar en el comportamiento antes de programar.
  • Da feedback rápido.
  • Reduce el miedo a cambiar el código. Los tests actúan como red de seguridad.
  • Guía la documentación. Usamos los tests como documentación viva.

Las tres leyes de TDD

  1. No está permitido escribir código de producción sin tener antes un test unitario que falle.
  2. No está permitido escribir más código de test que el mínimo para hacer que el test falle.
  3. No está permitido escribir más código de producción que el suficiente para que el test que fallaba ahora pase.

Ciclo de TDD: rojo-verde-refactor

flowchart LR
id1(test que falla) --> id2(test que pasa) --> id3(refactorizar test) --> id1

"TDD es un poco ir a romper tu código, porque así ves dónde lo puedes mejorar." – Stefan


La idea de TDD es ir desarrollando a un ritmo constante, sostenible y controlado.

Predicados y reglas

git clone -b predicates-rules-openclose https://github.com/540/fizzbuzz-ts.git

Predicado: función que devuelve un booleano

Si tenemos una torre de ifs, será mejor convertirlo a una tabla (= conjunto de reglas), porque será más mantenible.

import { otherwise, and, contains, isDivisibleBy, or } from './predicate';
import { Rule } from './rule';

export const createFizzBuzz = () => {
    const ruleSet: Rule[] = [
        {predicate: and(isDivisibleBy(3), isDivisibleBy(5)), trans: () => 'FizzBuzz'},
        {predicate: ..., trans: () => ...},
        ...
        {predicate: otherwise, trans: n => n.toString()},
    ];

    return fizzBuzz(ruleSet);
};

export const fizzBuzz = (ruleSet: Rule[]) => (n: number) =>
    ruleSet.find(rule => rule.predicate(n))!.trans(n);
    /*                                     ^ no permite que el find devuelva null,
	                        aunque en este caso tenemos el otherwise en las reglas
							así que no haría falta.
    */
export interface Rule {
    predicate: Predicate
    trans: (n: number) => string
}
export type Predicate = (n: number) => boolean

Test doubles

Como los dobles del cine de acción.

También se conocen como "mocks", aunque estos son un tipo concreto de doble ("mocks aren't stubs").

Tipos de dobles

En el Arrange ("dobles de estado"):

  • Dummy: objeto que se pasa para cumplir una dependencia. No son comunes en sistemas bien diseñados.
  • Stub: reemplaza la respuesta de una llamada a API/BBDD. Siempre devuelve datos.
  • Fake: es un stub hecho a mano, además implementando cierta lógica. Ej.: BBDD en memoria, enrutador con con ciertas rutas predefinidas.

En el Assert ("dobles de interacción", más frágiles):

  • Mock: valida que un comportamiento deseado se ha cumplido. Ej.: "Espero que el método X se haya llamado. Si no, lanzo error".
  • Spy: comprueba que algo ha ocurrido. Hasta que no le preguntas no habla (no falla).

No hay que usar dobles de tests para objetos/métodos simples.

Arquitectura Hexagonal

La arquitectura de software se refiere al conjunto de reglas que decidimos como equipo al definir cómo diseñamos nuestro software.

  • the set of design decisions that must be made early
  • the decisions that you wish you could get right eary
  • the decisions that are hard to change

– Martin Fowler, "Making Architecture Matter"

Design Payoff Line

Complejidad esencial vs. Complejidad accidental

Tipos de arquitecturas

  • Old School ("a batalla", sin arquitectura)
  • MVC
  • Arquitecturas limpias (arquitectura hexagonal)

Arquitecturas limpias

Arquitectura hexagonal

Capa de dominio

A la que pertenece lo relacionado con el problema específico del mundo real que tratamos de resolver desarrollando software

Entidad: Objeto del modelo con identidad propia, distinguible de otros objetos con los mismos atributos (ej.: usuarios).

Value Object: Clase que se identifica por el valor que representa, no por su identidad (ej.: números, fechas, monedas, URLs). Por ejemplo, podrían ser value objects para nosotros dos productos que cuesten 5€, por ser idénticos en cuanto a precio.

Capa de aplicación

Engloba los "casos de uso" o "servicios de aplicación" que representan de forma atómica las funcionalidades del sistema.

Los casos de uso pueden hacer de "barrera transaccional" (o suceden o no suceden, no se quedan a medias) con el sistema de persistencia.

Capa de infraestructura

Es código que cambia en función de decisiones externas (ej.: librerías).

Regla de dependencia

Las capas de fuera conocen lo que hay inmediatamente dentro, pero las capas de dentro no saben nada sobre lo que hay fuera.

Principio de Inversión de Dependencias

Las cosas más importantes (dominio) no deben depender de las cosas menos importantes (infraestructura: frameworks o librerías para el acceso a datos, red, framework de vistas utilizado, etc.).

El principio de inversión de dependencias dice que los módulos de alto nivel no deberían depender de los de bajo nivel, ni las abstracciones de detalles.¹ – Robert C. Martin ("Uncle Bob")

ej.: abstracción: $InvoiceRepository

Queremos conseguir que los Casos de Uso dependan de algo abstracto

ej.: que no dependa de qué base de datos usamos, sino que directamente tengamos un invoiceRepository.save(invoice)

¿Por qué hacer una abstracción cuando solo tienes una implementación? Porque el coste a futuro de cambiar eso es mucho más caro si no hacemos la abstracción de primeras.

Patrón de diseño de Inyección de Dependencias

Un objeto (cliente) recibe otro objeto (servicio) del cual depende.¹ Lo que busca este patrón de diseño es extraer la responsabilidad de creación de instancias de un componente para delegarla en otro. – Martin Fowler

Estructura de carpetas

src/
├── core/
│   ├── domain/
│   ├── infrastructure/
│   └── services/
└── ui/    # ui/ es infraestructura, pero la sacan fuera por ser
	│      # muy grande e incluso desacoplada en cuanto a lenguaje
    ├── components/
    ├── theme/
    └── views/

Naming

⚠️ Desgaste de energía

❌ Nombres acoplados al tipo de variable -> Evitar porque provoca desinformación.

❌ Nombres parecidos (ej.: cust, customer, getList, getLists) -> Hay que hacer que los nombres sean significativamente distintos.

🟢 Nombres pronunciables.

❌ Objetos "dios" (ej: HttpUtils) -> ¿Qué responsabilidad tiene? Es un problema de diseño. Indica que no estamos concretando lo suficiente la responsabilidad de cada método.

"No haces código para ti ni para ahora. Lo haces para el equipo y para el futuro." – Rubén

Porque el mantenimiento del código es el mayor coste.

  • Cuanto hay conceptos de negocio complejos/difíciles de traducir. -> Establecer un diccionario de equipo (lo importante es decidir qué palabra usamos para refernirnos a cada concepto).

  • Ojo Mayúsculas/minúsculas. Ej.: Ficheros en Linux son sensibles a mayúsculas, en Windows no.

  • Reuniones de concerns técnicas (2x30 mins / semana). Después, alguien aplica los cambios retroactivamente.

Biko Insights #5

"Biko Insights es una revista donde recogemos nuestras reflexiones sobre algunas tendencias en tecnología y digitalización."

"Para nosotros es un hito importante haber conseguido publicar esta quinta edición. Jamás imaginamos que compartir nuestra manera de entender este negocio iba a ser tan gratificante."

[Descargar Biko Insights #5]

4 - Una empresa que crea escuela

Un artículo de Aritz Suescun (COO en Biko) y Pablo Albizu (Socio-fundador de 540 y CTO en Biko).

  • Los primeros pasos de lo que llamamos Biko School
  • Lo que nos movió a lanzarnos
    1. Comprobar cuánta gente hay fuera de nuestros muros interesada en profundizar en hacer software bien hecho.
    2. Dar una oportunidad a gente que está empezando en esta profesión para aprender las cosas "bien" desde el principio.
    3. Compartir nuestra experiencia, con sus cosas buenas y sus cosas malas, para que los demás construyan sobre ella y todos mejoremos por el camino.
    4. Si le damos tanta importancia a formarnos continuamente... ¿no tiene sentido que aportemos nuestro granito de arena?
    5. Empezar a enseñar. Probar a enseñar. No sólo individualmente, sino como organización.
  • Una breve historia del primer curso
  • ¿Cuál fue el resultado del curso? Feedback recibido
  • ¿Qué conclusiones sacamos? ¿Qué hemos aprendido?
    • ¿Sabemos enseñar? Sí sabemos
    • La importancia de aprender y de enseñar en nuestro modelo
    • Transformador para la carrera profesional
  • Y ahora ¿dónde nos lleva este camino?
    • Tenemos claro que queremos repetir el curso e incluso hacer más ediciones dentro de un mismo año.
    • Nos gustaría ampliar el público objetivo, enfocándonos también en líderes de equipo, managers y formadores.
    • Vamos a asentar las bases de un espacio de aprendizaje y enseñanza dentro de Biko, de una escuela.