ReactJS Training: Creando tu primer juego con React y TypeScript

Mariano Vazquez
16 min readJan 20, 2020

--

If you want to read the English version of this post, click here.

Ahora que hemos aprendido los conceptos básicos, es hora de hacer cosas reales. En este ejercicio, configuraremos una aplicación hecha en React desde cero, y luego implementaremos el código del juego Connect Four sobre ella.

Este es el juego que vamos a implementar en este post: Connect Four

Siguiendo este tutorial paso a paso, aprenderemos sobre React y TypeScript mientras programamos una aplicación de la vida real. Estas preparada/o? 👾

Configuracion Inicial

Nota: Si quieres empezar con el código del juego directamente, puedes omitir esta sección y abrir la aplicación inicial (begin) de este Ejercicio, la cual se encuentra en el repositorio de GitHub de este training. Recuerda ejecutar npm install antes de arrancar la app connpm start.

Como hemos visto en el post anterior, ni TypeScript ni JSX se ejecutan en los navegadores. Y dado que vamos a escribir código utilizando estos lenguajes, necesitamos transpilarlo antes de ejecutar nuestra aplicación. Para hacer esto, tenemos dos opciones:

  1. Presentar, explicar, analizar y configurar varias herramientas (Webpack/Rollup, Babel/tsconfig, CSS Modules, etc.)
  2. Utilizar “scaffolders” (también llamados integrated toolchains), que son aplicaciones preconfiguradas que no requieren ningún tipo de setup para ejecutarse, dejando que nos enfoquemos sólo en nuestro código.

En este post, vamos a instalar la aplicación de Facebook Create React app, la herramienta de facto para construir una aplicación React. Y es muy fácil de instalar:

En tu terminal, corre el comando npx create-react-app connect-four --typescript. Este comando creará una aplicacion con React y Typescript dentro de la carpeta connect-four.

Espera que el proceso termine de ejecutarse. Verás un mensaje similar a este:

Mensaje de instalación exitosa de Create React App

Nota: Si npx no funciona, proba con npm i -g create-react-app seguido de create-react-app connect-four --typescript.

Navegue hacia la carpeta connect-four, que se recién acabas de crear, y tomate un minuto o dos para analizar la estructura de carpetas. Estás viendo una aplicación totalmente funcional con lógica de negocios dentro de la carpeta src:

connect-four
├── node_modules
│ ├── ...
├── public
│ ├── favicon.ico
│ ├── index.html
│ └── manifest.json
│ └── ...
├── src
│ ├── App.css
│ ├── App.tsx
│ ├── index.css
│ ├── index.tsx
│ └── ...
├── package.json
├── tsconfig.json
└── ...

Ahora, ejecute npm start. Este comando inicializa la aplicación en “modo de desarrollo”, proporcionando una recarga automática al realizar cambios en el código de la app (a.k.a. Hot Module replacement), entre otras cosas.

Navegue a http://localhost:3000 para visualizar la aplicación corriendo en el browser.

Página Home inicial de Create React app

Felicitaciones! Haz creado tu primera aplicación con React y TypeScript👏 💃 🕺 👏

Investiguemos un poco cómo está hecha. Abra la aplicación con VSCode (o el IDE de tu preferencia) y navegue al archivo src/App.tsx.

Archivo App.tsx autogenerado por Create React App

Nota: Pro tip! Puedes abrir VSCode usando la terminal ejecutando code . en la carpeta donde se encuentre el codigo. Del mismo modo, puedes hacer lo mismo para Atom con atom .. Y para Sublime, ejecuta subl ..

Tomémonos un par de minutos para analizar el código de este archivo:

  • En la parte superior, tiene declaraciones import. Esta es la forma en que JavaScript (ES6) importa módulos a un archivo. El valor importado (puede ser una función, una clase, un objeto, una constante, etc.) se almacena dentro de una variable para su uso posterior en el archivo. Puedes obtener más información sobre los imports de ES6 aquí.
  • La línea 5 define un React Function component llamado App. Retorna código JSX que luego será dibujado/renderizado por el navegador (después de transpilar). Los componentes React nos ayudan a dividir nuestro código en partes pequeñas, siguiendo el principio de única responsabilidad (SRP).
  • Entre las líneas 6 y 23 se encuentra el código JSX que representa lo que vimos antes renderizado en el navegador. Note que es casi idéntico a HTML, excepto la linea 9 que configura la prop src utilizando una referencia de variable de JavaScript (<img src = {logo} .. />)
  • Por último, la linea 26 exporta nuestra función <App /> para que pueda ser importada desde otros archivos.

Con la aplicación ejecutándose localmente (si la detuviste, ejecuta npm start en la terminal), modifica el código eliminando la línea 17 que contiene la etiqueta de cierre</div> y guarda tus cambios.

Observa que VSCode inmediatamente muestra un error:

Mensaje de error que muestra VSCode

Y que el browser muestra también un error de compilación:

Mensaje de error en el browser

Soluciona el error deshaciendo lo que haz hecho (eliminar la etiqueta de cierre </div>), guarda los cambios y espera a que el navegador actualice la aplicación.

Ahora, abra el archivo src/index.tsx. Este es el punto de inicio de la aplicación:

import React from “react”;
import ReactDOM from “react-dom”;
import App from “./App”;
ReactDOM.render(<App />, document.getElementById(“root”));

En estas pocas líneas, vemos que el código:

  • Importa el componente <App /> , además de las librerías React y ReactDOM.
  • Ejecuta el método ReactDOM.render(), el cuál recibe como parámetros al componente <App /> y una referencia al elemento del documento (HTML) con "root" como ID.
  • El archivo public/index.html el HTML que el browser rendereará cuando la aplicación inicie. En la línea 31, se encuentra el elemento <div id="root">, actualmente sin contenído. Aquí es donde tu aplicación será "montada", lo que significa que es aquí donde el código de tu aplicación, que se encuentra en los métodos render(), será injectado como HTML.

Este archivo tiene exactamente el mismo contenido que los ejemplos básicos que hemos analizado en el post anterior. Este es el poder de React: sin importar la complejidad de tu aplicación, el código utilizado para renderizarla es siempre el mismo.

Resumiendo

En este rápido tutorial paso-a-paso del setup inicial, hicimos lo siguiente:

  1. Creamos una aplicación web completamente funcional con un solo comando (npm) en la Terminal.
  2. Ejecutamos la aplicación web localmente y la mostramos en el navegador.
  3. Analizamos el código React + TypeScript de la aplicación, enfocándonos en el componente principal y el punto de inicio, junto a su archivo HTML asociado.

Por último, no te olvides que los browsers/navegadores sólo entienden HTML, JS y CSS, no entienden TS y JSX. Esta aplicación scaffoldeada tiene un proceso build que generará archivos JS y CSS dentro de la carpeta dist, los cualres serán referenciandos en el archivo index.html (también generado). Y estos archivos autogenerado serán analizados, leídos e interpretados por el browser/navegador.

Note: Si te interesaría leer una explicación profunda sobre el proceso de transpilación, y cómo configurar librerías para hacerlo, escribe un comentario en este post y pídemelo!

Agregando la lógica del juego en la aplicación

Ahora que entendemos las base fundacionales de nuestra aplicación React, es hora de agregar la lógica del juego. Como explicamos en el post anterior, las aplicaciones React dividen la lógica de negocios en diferentes componentes. Pero hay diferentes responsabilidades en una aplicación: la lógica que decide quién ganó (y si alguien lo hizo), la lógica que selecciona los elementos a dibujar (y cómo), la lógica que determina de qué jugador es el turno, etc. ¿Cómo podemos separar estas responsabilidades de manera consistente y repetible?

Utilizaremos un patrón ampliamente conocido, Presentational and Container components, para organizar nuestros componentes en una estructura simple pero potente:

En React, los datos fluyen desde “arriba hacia abajo”, mientras que los eventos mueven la información “desde abajo hacia arriba”

Esta técnica propone encapsular toda la lógica de negocio y el estado en componentes padres (Container o Smart) y usar a sus componentes hijos, usualmente las hojas, para renderizar la UI y manejar la interacción del usuario (Presentational o Dumb).

Los componentes Container envian los datos y las funciones a sus hijos a través de las props. Los componentes Presentational utilizan esos datos para decidir qué y cómo dibujar. Y ejecutan las funciones cuándo el usuario interactúa con ellos, usualmente enviando información extra por parámetro.

Nota: dado que la aplicación terminará enviándo la información “en cascada” de arriba hacia abajo, esta técnica es adecuada para aplicaciones de tamaño pequeño o mediano. Grandes aplicaciones compuestas de una jerarquía profundamente anidada requieren un enfoque diferente. Hablaremos sobre esto en el siguiente post.

Siguiendo esta técnica, podemos identificar las siguientes entidades en nuestro juego:

  • Un componente App, encargado de almacenar el estado de la aplicación. Y de calcular quién es el ganador. Es el componente “padre”/”container”/”smart”.
  • Un componente Board, responsable de dibujar los elementos del juego. El Board (tablero) está compuesto de múltiples Columns (columnas) compuestas de diferentes Tiles (ranuras), las cuales a su vez pueden tener o no una Chip (ficha). Estos son los componentes “hijo”/”presentational”/”dumb”.
  • Cuando se hace click en una Column (columna), se agrega una nueva Chip (ficha) en la última Tile (ranura) vacía de la parte inferior. Esto es parte de la lógica de negocios de la aplicación.
Divide la lógica de negocio en componentes pequeños

Por supuesto, los componentes que decidas usar puede variar, dependiendo de tu preferencia ¿Puedes pensar una manera diferente de organizar tu código?

Creando el componente Tile

Crea una nueva carpeta llamada components dentro de la carpeta src.

Dentro de esta carpeta, crea una nueva carpeta llamada Tile, y dentro de esta última agregue los siguientes archivos (por ahora vacíos):

  • Un archivo Tile.module.css para almacenar el código CSS
  • Un archivo Tile.tsx para la lógica de negocio en React
  • Y un archivo types.ts para definir los tipos de TypeScript de tu componente.

Abra el archivo src/components/Tile/types.ts y agregue el siguiente código. Este código define la interfaz (o contrato) de nuestro componente tipando sus props.

export interface Props {
id: string;
chipType?: string;
onClick: (id: string) => any;
}

Al tipar las props del componente Tile, estamos definiendo su interfaz, o contrato. Este archivo le informa al consumidor del componente que:

  1. Tiene que proveer un id, a través de las props del componente.
  2. Puede enviar unchipType al componente. Como definimos anteriormente, un Tile puede tener un Chip o no.
  3. Tiene que proveer una función a través de la proponClick, la cual será ejecutada cuando el usuario hace click en el componente Tile.

Luego, abra el archivo src/components/Tile.tsx y agregue el siguiente código:

import React from "react";
import classNames from "classnames";
import styles from "./Tile.module.css";
import { Props } from "./types";
export default class Tile extends React.PureComponent<Props> { render() {
const { id, chipType, onClick = () => {} } = this.props;
const chipCssClass = classNames(styles.chip, chipType === "red" ? styles.red : styles.yellow);

return (
<div className={styles.tile} onClick={() => onClick(id)}>
{chipType && <div className={chipCssClass} />}
</div>
);
}
}

Al analizar este código puedes notar que el componente Tile es un componente presentational, encargado de dibujar los tiles (ranuras) del tablero. A su vez, decide si una Chip (ficha) está presente al chequear el valor definido en la prop chipType, y seteando una clase de CSS dependiendo su valor. Finalmente, al ser clickeado, el componente ejecuta la función enviada a través de la prop onClick, enviando el id del Tile como parámetro.

Nota: ¿Has notado que el código vincula a la interfaz Props a la definición deReact.PureComponent? Así es como se tipea una clase de React. El IDE que utilices entenderá esto y te dará información acerca de los tipos de cada una de las props del componente. Puedes probar esto pasando el mouse por encima de la línea que contienethis.props.

Por último, abra el archivo src/components/Tile.module.css y agregue el siguiente código de CSS:

.tile {
width: 75px;
height: 75px;
border: solid 10px #3355ff;
border-radius: 100%;
background-color: white;
}
.chip {
width: 75px;
height: 75px;
border-radius: 100%;
background-color: gray;
}
.yellow {
background-color: #ffff33;
}
.red {
background-color: #ff010b;
}

Nota: Create React app trata los archivos CSS con el formato[nombre].module.css de una manera distinta a un archivo CSS normal, al transpilarlos utilizando la libreria CSS Modules. Uno de los principales beneficios es uno no tiene que preocuparse por la colisión de nombres en las clases de CSS, dado que cada archivo puede ser tratado como un módulo aislado. Esto es posible gracias que, al transpilar, esta librería reemplaza los nombres de las clases de CSS por un nombre “único” que utiliza el formato [filename]_[classname]__[hash].

Para obtener más información sobre esta librería, hace click aquí.

Creando el componente Column

Ahora navegue a la carpeta components y crea una nueva carpeta Column.

Dentro de esta carpeta, crea los siguientes archivos: un archivo Column.module.css para el código de CSS, un archivo Column.tsx para la lógica del componente, y un archivo types.ts para almacenar los tipos de TypeScript types del componente.

Abra el archivo src/components/Column/types.ts y agregue el siguiente código, el cual define las props (contrato) del componente Column.

import { ChipsPositions } from "../App/types";export interface Props {
column: number;
rows: number;
chipsPositions: ChipsPositions;
onTileClick: (id: string) => any;
}

Este código le dice al consumidor del componente que:

  • Necesita proporcionar un número de column. Este valor actúa como el ID del elemento.
  • También necesita decirle al componente cuántas rows tendrá.
  • La propchipsPositions es un objeto que conoce la posición de cada Chip (ficha). Veremos cómo se construye este objeto más adelante. Por ahora, sólo necesitas saber que este objeto puede decirnos si hay un Chip (ficha) dentro de un Tile (ranura) o no.
  • Por último, la función onTileClick se usa para informar al padre cuando el usuario hace click en un Tile (ranura) específico.

Abra el archivo src/components/Column.tsx y agregue el siguiente código:

import React from "react";
import Tile from "../Tile/Tile";
import styles from "./Column.module.css";
import { Props } from "./types";
export default class Column extends React.PureComponent<Props> { render() {
const { column, rows, chipsPositions, onTileClick } = this.props;
const tiles = [];

for (let row = 0; row < rows; row++) {
const tileId = `${row}:${column}`;
const chipType = chipsPositions[tileId];
tiles.push(
<Tile
key={tileId}
id={tileId}
chipType={chipType}
onClick={onTileClick}
/>
);
}
return <div className={styles.column}>{tiles}</div>;
}
}

Este código (también presentational) renderea un elemento<div> el cual contiene tantos componentes Tile como indica el número de rows (filas), enviado via props. Cada Tile (ranura) recibirá unachipTypey la función onTileClick(). Notar también que el valor único tileId es definido aquí al combinar los valores de row y column.

Finalmente, abra el archivo src/components/Column/Column.module.css y agregue el siguiente código de CSS:

.column {
display: flex;
flex-direction: column;
cursor: pointer;
}

Ya estamos ahí! 🙌

Creando el componente Board

Del mismo modo al, navegue a la carpeta components y crea una nueva carpeta Board.

Dentro de esta carpeta, crea los siguientes archivos: un archivo Board.module.css para almacenar el código CSS, un archivo Board.tsx para la lógica de negocio, y un archivo types.ts para tipar al componente usando Typescript.

Nota: ¿Haz notado el patrón común utilizado al crear componentes?

Abra el archivo src/components/Board/types.ts y agregue el siguiente código que define las props (contrato) del componente Board:

import { ChipsPositions } from “../App/types”;export interface Props {
columns: number;
rows: number;
chipsPositions: ChipsPositions;
onTileClick: (id: string) => any;
}

Este código le dice al consumidor del componente que:

  • Tiene que proporcionar el número de columns y rows que tendrá el Board (tablero).
  • Tiene que enviar el objeto chipsPositions. Pero esta información es utilizada por el componente Column, no por el componente Board.
  • Tiene que proporcionar una función onTileClick, que será ejecutada por el componente Tile para notificar que fue clickeado por el usuario.

Luego, abra el archivo src/components/Board.tsx y agregue el siguiente código de tipo presentational:

import React from "react";
import Column from "../Column/Column";
import styles from "./Board.module.css";
import { Props } from "./types";
export default class Board extends React.PureComponent<Props> {

renderColumns() {
const { columns, rows, chipsPositions, onTileClick } = this.props;
const columnsComponents = [];
for (let column = 0; column < columns; column++) {
columnsComponents.push(
<Column
key={column}
column={column}
rows={rows}
chipsPositions={chipsPositions}
onTileClick={onTileClick}
/>
);
}
return <>{columnsComponents}</>;
}
render() {
return <div className={styles.board}>{this.renderColumns()}</div>;
}
}

Este código es similar al del componente Column, pero en lugar de crear Tiles, creamos múltiples columnas, enviándole la información que cada una requiere y luego mostramos el resultado. El método this.renderColumns() encapsula esta lógica.

¿Notaste que también usamos React.Fragment? Probablemente no porque estamos utilizando la abreviado “<> </>”, la cual es un equivalente de “<React.Fragment></React.Fragment>”.

Por último, abra el archivo src/components/Board/Board.module.css y pegue el siguiente código de CSS:

.board {
display: flex;
flex-direction: row;
border: solid 5px #002bff;
border-radius: 5px;
background-color: #3355ff;
}
.columns {
display: flex;
flex-direction: row;
}

Creando el componente App

Ahora vamos a desarrollar la lógica principal de nuestro juego. Presta especial atención a esta sección.

Crea una carpeta llamada App dentro de la carpeta src/components. Dentro de esta carpeta, crea el archivo App.module.css, el archivo App.tsx y el archivo types.ts.

Abra el archivo src/components/App/types.ts y agregue la siguiente información:

export interface ChipsPositions {
[key: string]: Player;
}
export type Player = "red" | "yellow" | "";export interface Props {
columns: number;
rows: number;
}
export interface State {
chipsPositions: ChipsPositions;
gameStatus: string;
playerTurn: Player;
}

Aquí definimos varias cosas importantes:

  • La forma del objeto ChipsPositions: un diccionario que contiene en cada posición uno de estos valores de tipo Player: "red", "yellow" o "" (que representa un estado vacío).
  • Definimos el tipo de los Props y State de la aplicación. El primero nos dice que debemos proporcionar la cantidad de columns y rows para que el componente App se inicialice. Mientras que el último nos dice toda la información que se almacenará en el componente.

Ahora, abra el archivo src/components/App/App.tsx y agregue lo siguiente:

import React from "react";
import Board from "../Board/Board";
import { Props, State, ChipsPositions } from "./types";
import styles from "./App.module.css";
export default class App extends React.PureComponent<Props, State> {
state: State = {
chipsPositions: {},
playerTurn: "red",
gameStatus: "It's red's turn"
};
calculateGameStatus = (playerTurn: string, chipsPositions: ChipsPositions): string => {
// TODO
};
handleTileClick = (tileId: string) => {
// TODO
};
renderBoard() {
const { columns, rows } = this.props;
const { chipsPositions } = this.state;
return (
<Board
columns={columns}
rows={rows}
chipsPositions={chipsPositions}
onTileClick={this.handleTileClick}
/>
);
}
renderStatusMessage() {
const { gameStatus } = this.state;
return <div className={styles.statusMessage}>{gameStatus}</div>;
}
render() {
return (
<div className={styles.app}>
{this.renderBoard()}
{this.renderStatusMessage()}
</div>
);
}
}

Esta es la estructura básica del componente: lógica de tipo presentational para dibujar/renderizar el Board y el mensaje Status, y un estado por defecto para el componente. El código es completamente funcional, pero la app aún no reaccionará cuando el usuario interactúe con el juego. Vamos a codear esta lógica en las próximas líneas.

Implemente el métodohandleTileClick() para que el juego reaccione cuando un usuario cuando el compomnente Tile es clickeado:

handleTileClick = (tileId: string) => {
const { chipsPositions, playerTurn } = this.state;
// Get the last empty tile of the column
const column = parseInt(tileId.split(":")[1]);
let lastEmptyTileId = this.getLastEmptyTile(column);
// If there is no empty tile in the column, do nothing
if (!lastEmptyTileId) {
return;
}
// Add chip to empty tile
const newChipsPositions = {
...chipsPositions,
[lastEmptyTileId]: playerTurn
};
// Change player turn
const newPlayerTurn = playerTurn === "red" ? "yellow" : "red";
// Calculate game status
const gameStatus = this.calculateGameStatus(newPlayerTurn, newChipsPositions);
// Save new state
this.setState({ chipsPositions: newChipsPositions, playerTurn: newPlayerTurn, gameStatus });
};
getLastEmptyTile(column: number) {
const { rows } = this.props;
const { chipsPositions } = this.state;
for (let row = rows - 1; row >= 0; row--) {
const tileId = `${row}:${column}`;

if (!chipsPositions[tileId]) {
return tileId;
}
}
}

Tómese un par de minutos para entender lo que este código está haciendo:

  • Primero, necesita obtener el último Tile vacío de la columna que fue clickeada. Y obtiene el número de columna parseando el tileId.
  • Luego, agrega una ficha al Tile (ranura) dependiendo de qué jugador tiene el turno, conocido solo por el componente App. Y recalcula el estado del juego.
  • Por último, almacena toda la información nueva en el estado del componente, volviendo a renderizar la aplicación completa si algo cambia. React se encargará de decidir esto.

Finalmente, implemente el método calculateGameStatus() utilizando el siguiente código, el cual contiene la lógica para decidir quién es el ganador o quién juega a continuación:

calculateGameStatus = (playerTurn: string, chipsPositions: ChipsPositions): string => {
const { columns, rows } = this.props;
// Check four in a row horizontally
for (let row = 0; row < rows; row++) {
let repetitionCountStatus = { playerChip: "", count: 0 };
for (let column = 0; column < columns; column++) {
const chip = chipsPositions[`${row}:${column}`];

// If there is a chip in that position, and belongs
// to a player, count that chip for that player
// (either increase the count or start over)
if (chip && chip === repetitionCountStatus.playerChip) {
repetitionCountStatus.count++;
} else {
repetitionCountStatus = { playerChip: chip, count: 1 };
}
// If the count for a player is 4, that player won
if (repetitionCountStatus.count === 4) {
return `Player ${repetitionCountStatus.playerChip} won!`;
}
}
}
// Check four in a row vertically
for (let column = 0; column < columns; column++) {
let repetitionCountStatus = { playerChip: "", count: 0 };

for (let row = 0; row < rows; row++) {
const chip = chipsPositions[`${row}:${column}`];
// If there is a chip in that position, and belongs
// to a player, count that chip for that player
// (either increase the count or start over)
if (chip && chip === repetitionCountStatus.playerChip) {
repetitionCountStatus.count++;
} else {
repetitionCountStatus = { playerChip: chip, count: 1 };
}
// If the count for a player is 4, that player won
if (repetitionCountStatus.count === 4) {
return `Player ${repetitionCountStatus.playerChip} won!`;
}
}
}
// TODO: Check four in a row diagonally

return `It's ${playerTurn}'s turn`;
};

Haz notado que este código no chequea que haya cuatro fichas del mismo valor consecutivas en diagonal? Se te ocurre cómo implementar esto? Si es así, envíame la solución via Pull Request!

Inicializando la aplicación the app

Abra el archivo src/index.tsx y reemplace su contenido por el siguiente:

import React from "react";
import ReactDOM from "react-dom";
import App from "./components/App";
import "./index.css";
// Initialize the app with 7 columns and 6 rows
ReactDOM.render(
<App columns={7} rows={6} />,
document.getElementById("root")
);

Inicie la aplicación ejecutando npm start en una terminal.

En la ventana del browser recién abierta, abra la Developer Console y luego haga click en la pestaña Components. Verá aquí el árbol de jerarquías de su aplicación React, compuesto por los componentes que recién ha creado:

Árbol jerárquico de componentes de la app Connect Four app hierarchy en la Developer Console

Juegue un poco, agregue varias fichas en el tablero, y despues compruebe el valor de los diferentes Tiles del tablero. Note que las propiedades recibidas en los componentes cambiarán luego de interactuar con el juego.

Cuando haces click en un Tile vacío, la prop chipType cambia

Nota: Puedes cambiar una prop directamente modificando su valor en el panel derecho. Pruébelo usted mismo cambiando el tipo de chip de un Tile de "red" o undefined a "yellow"

¡Felicidades! Has creado tu primer juego con React y TypeScript 💪💪💪

Resumiendo

En este ejercicio, hemos aprendimos lo siguiente:

  • Cómo crear una aplicación desde cero usando React y TypeScript.
  • Cómo dividir la lógica de negocio de una aplicación en pequeños componentes.
  • Cómo enviar información y notificar eventos de usuarios a través de props.
  • Cómo usar las React Developer Tools para visualizar el árbol de componentes de su aplicación y su estado.

🎉🎉

Recuerde que puede encontrar el training completo en este repositorio de GitHub.

--

--