m3

Descargar como pdf o txt
Descargar como pdf o txt
Está en la página 1de 168

Modulo 3

Modulo 3 .................................................................................................................................... 1
Actualizar paquetes ................................................................................................................... 8
TypeScript 1 ............................................................................................................................... 8
Configuración de entorno ........................................................................................................ 12
ESLint .................................................................................................................................... 13
En resumen, muy resumido .................................................................................................. 16
Tipos de datos básicos .......................................................................................................... 17
En conclusión... ..................................................................................................................... 17
Homework ............................................................................................................................ 18
INSTRUCCIONES ................................................................................................................ 18
TypeScritp 2 ............................................................................................................................. 20
Funciones y tipado ................................................................................................................ 21
¿Cómo tipar funciones? .................................................................................................... 21
Interfaces y tipos personalizados ......................................................................................... 22
¿Qué es una interfaz?........................................................................................................ 22
Definición de tipos personalizados ....................................................................................... 23
Interfaces | Casos de uso .................................................................................................. 23
Tipos | Casos de uso ............................................................................................................. 24
UNION TYPES ........................................................................................................................ 24
ALIAS ..................................................................................................................................... 25
Tipos vs Interfaces (en objetos) ............................................................................................ 25
En conclusión ........................................................................................................................ 27
Homework................................................................................................................................ 27
Express y TypeScript ................................................................................................................ 28
Express & TypeScript ............................................................................................................ 29
EXPRESS ............................................................................................................................. 29
TYPESCRIPT ........................................................................................................................ 30
Estructura de proyecto ......................................................................................................... 32
Inicialización de proyecto .................................................................................................. 32
Archivos y carpetas ........................................................................................................... 32
NOTAS ESTRUCTURA DEL PROYECTO de instalación del servidor y entorno para el
proyecto ............................................................................................................................ 33
Manejo de rutas ................................................................................................................... 34

1
Creación y tipado de rutas ................................................................................................ 34
Inserción de recursos ........................................................................................................ 36
CRUD de recursos .............................................................................................................. 36
Lectura de recursos ........................................................................................................... 36
Actualización de recursos .................................................................................................. 36
Eliminación de recursos .................................................................................................... 36
AHORA RESUMO EL PROCESO Y CÓDIGO SEGÚN EL CICLO (PONDRÉ CÓDIGO CON LAS
EXPLICACIONES COMENTADAS. ........................................................................................ 36
Middlewares ............................................................................................................................ 39
¿Qué era un middleware? .................................................................................................... 39
Tipado de middlewares ........................................................................................................ 39
Cierre .................................................................................................................................... 40
Homework ............................................................................................................................ 40
SQL Fundamentals ................................................................................................................... 41
Bases de datos relacionales .................................................................................................. 42
Relacional .......................................................................................................................... 42
PRODUCTOS.......................................................................................................................... 43
UNO A UNO (1:1) ............................................................................................................... 46
UNO A MUCHOS (1:N) ....................................................................................................... 46
MUCHOS A MUCHOS (N:M) .............................................................................................. 47
¿Qué es SQL? ........................................................................................................................ 48
PostgreSQL............................................................................................................................ 48
¿Qué es PostgreSQL? ........................................................................................................ 48
Creación de tablas y constraints ........................................................................................... 49
Create Table ...................................................................................................................... 50
Constraints y tipos de datos .............................................................................................. 50
Estructura de una query ....................................................................................................... 51
Interacción con las tablas .................................................................................................. 51
Relaciones en SQL ............................................................................................................. 51
Join .................................................................................................................................... 52
Filtrado y ordenamiento de datos ........................................................................................ 55
Operadores de comparación ............................................................................................. 55
Operadores lógicos............................................................................................................ 56
Nuestro chat con IA ........................................................................................................... 57
Cierre .................................................................................................................................... 57
Homework ............................................................................................................................ 58

2
PostgreSQL............................................................................................................................ 60
CREAR TABLAS: .................................................................................................................. 60
INTEGRIDAD REFERENCIAL Y CASCADAS DE ELIMINACIÓN Y ACTUALIZACIÓN ................ 61
INSERTAR INFORMACIÓN:................................................................................................. 61
ACTUALIZACIONES: ........................................................................................................... 62
CONSULTAS: ...................................................................................................................... 63
ELIMINACIÓN: ................................................................................................................... 64
DELETE FROM peliculas WHERE id = 3; ............................................................................. 64
Modelo Entidad Relación .................................................................................................. 64
TypeORM ................................................................................................................................. 64
¿Que es TypeORM? .............................................................................................................. 65
Instalar TypeORM .................................................................................................................... 65
Definición de modelos .......................................................................................................... 66
Creación de entidades y modelos ..................................................................................... 66
Operaciones básicas ............................................................................................................. 68
Inserción de datos ............................................................................................................. 69
Relaciones entre entidades .................................................................................................. 69
Cierre .................................................................................................................................... 71
TypeORM 2............................................................................................................................... 71
Introducción.......................................................................................................................... 72
Transacciones ....................................................................................................................... 72
Users ...................................................................................................................... 72
Rollback y manejo de errores ............................................................................................... 73
ELIMINACIÓN Y ACTUALIZACIÓN EN CASCADA ................................................................ 78
Manejo de repositorios ........................................................................................................ 80
Repositorios vs EntityManager ......................................................................................... 80
Repositorios personalizados ............................................................................................. 80
CODIGO DE PERSONALIZACIÓN DE LOS REPOSITORIOS Y EJEMPLOS............................... 81
En conclusión... ..................................................................................................................... 82
React ........................................................................................................................................ 83
¿De dónde viene react? ........................................................................................................ 83
React + vite ........................................................................................................................... 83
Vite .................................................................................................................................... 83
Estructura de un componente .............................................................................................. 85
Renderizado ................................................................................................................. 85

3
Estilos .................................................................................................................................... 86
Técnicas de estilo .............................................................................................................. 86
Legacy ................................................................................................................................ 86
Módules ............................................................................................................................. 88
Cierre .................................................................................................................................... 90
Homework ............................................................................................................................ 90
REQUISITOS ....................................................................................................................... 90
INSTRUCCIONES ................................................................................................................ 90
Instalación: ........................................................................................................................ 91
React Data Flow ....................................................................................................................... 92
Introducción.......................................................................................................................... 92
Flujo de datos ....................................................................................................................... 92
Direccionalidad .................................................................................................................. 92
Estados .................................................................................................................................. 93
Estado de un componente ................................................................................................ 93
Hooks .................................................................................................................................... 94
Casos de uso ......................................................................................................................... 94
Props ..................................................................................................................................... 98
¿Qué son las props? .......................................................................................................... 98
Lifting state up .................................................................................................................... 100
Información vs eventos ................................................................................................... 100
Ejemplo práctico .............................................................................................................. 104
Cierre .................................................................................................................................. 111
Homework .......................................................................................................................... 111
INSTRUCCIONES .............................................................................................................. 111
React Lifecycle........................................................................................................................ 113
Introducción........................................................................................................................ 113
Ciclo de vida ........................................................................................................................ 113
Componentes y sus ciclos ............................................................................................... 113
Componentes de clase .................................................................................................... 113
Ciclos................................................................................................................................ 114
useEffect ............................................................................................................................. 115
Hook useEffect ................................................................................................................ 115
Casos de uso .................................................................................................................... 116
Wrap up .............................................................................................................................. 117

4
Practiquemos en una demo ............................................................................................ 117
Homework .......................................................................................................................... 117
INSTRUCCIONES .............................................................................................................. 117
React Forms ........................................................................................................................... 119
Introducción........................................................................................................................ 119
Formularios ......................................................................................................................... 119
Formularios controlados .................................................................................................... 122
Validación de datos ............................................................................................................ 122
Expresiones regulares ..................................................................................................... 122
EJERCICIO .................................................................................................................. 123
TAKE AGAIN ..................................................................................................................... 123
Formik ................................................................................................................................. 124
React forms vs Formik ..................................................................................................... 125
En conclusión... ................................................................................................................... 126
Homework .......................................................................................................................... 126
INSTRUCCIONES .............................................................................................................. 126
React Routing ......................................................................................................................... 128
Introducción........................................................................................................................ 128
Routing ................................................................................................................................... 128
Enrutamiento ...................................................................................................................... 128
React router dom................................................................................................................ 130
Instalación y configuración ................................................................................................. 131
Starter Pack......................................................................................................................... 132
Configuración React Router ................................................................................................ 134
<Routes> y <Route> ........................................................................................................ 134
Rutas Dinámicas.................................................................................................................. 135
Redirección en eventos ...................................................................................................... 138
Nuevos hooks ..................................................................................................................... 138
Manejo de errores .............................................................................................................. 139
Componente Error........................................................................................................... 139
Cierre .................................................................................................................................. 140
En conclusión................................................................................................................... 140
Homework .......................................................................................................................... 141
INSTRUCCIONES .............................................................................................................. 141
React Redux ........................................................................................................................... 142

5
Introducción........................................................................................................................ 142
Redux .................................................................................................................................. 142
Estados | Globales vs Locales .......................................................................................... 142
¿Qué es redux? ................................................................................................................ 143
Los 3 principios de redux ................................................................................................. 143
Flujo de información ....................................................................................................... 144
Redux Core.......................................................................................................................... 144
ACTIONS & ACTION CREATORS ....................................................................................... 145
REDUCER ......................................................................................................................... 145
STORE .............................................................................................................................. 145
Redux Toolkit ...................................................................................................................... 145
CreateSlice ....................................................................................................................... 146
React-Redux ........................................................................................................................ 147
Configuración .................................................................................................................. 147
useSelector ...................................................................................................................... 147
useDispatch ..................................................................................................................... 147
Cierre .................................................................................................................................. 148
En conclusión................................................................................................................... 148
Frontend Advanced ................................................................................................................ 149
Introducción........................................................................................................................ 149
Custom hooks ..................................................................................................................... 150
Personalización de hooks ................................................................................................ 150
Lazy loading & Suspense..................................................................................................... 151
Características ................................................................................................................. 151
REACT.LAZY...................................................................................................................... 151
SUSPENSE ........................................................................................................................ 151
Implementación de servicios .............................................................................................. 152
Definamos un servicio para nuestra aplicación .............................................................. 153
Cierre .................................................................................................................................. 153
En conclusión................................................................................................................... 154
Homework .......................................................................................................................... 154
JavaScript Advanced II ........................................................................................................... 154
Introducción........................................................................................................................ 154
Time & space complexity .................................................................................................... 155
Algoritmos .......................................................................................................................... 156

6
¿Qué es un algoritmo? .................................................................................................... 156
Big O' Notation ................................................................................................................ 156
Algoritmos en código ...................................................................................................... 159
Algoritmos de búsqueda y ordenamiento .......................................................................... 161
Algoritmos de búsqueda ................................................................................................. 161
Algoritmos de ordenamiento .......................................................................................... 162
Eficiencia de algoritmos con ChatGPT ................................................................................ 163
Empleo de IA ................................................................................................................... 163
Cierre .................................................................................................................................. 168
En conclusión................................................................................................................... 168

7
Actualizar paquetes
Listar paquetes instalados con node
$ npm list -g -depth 0

Actualizar npm
$ sudo npm install -g npm@latest

Actualizar node y quitar versiones antiguas


$ sudo n latest
$ sudo n prune

Actualizar typescript
$ tsc -v
$ npm install -g typescript@latest

TypeScript 1
TypeScript es un lenguaje de programación de código abierto fuertemente tipado que
actúa como una extensión de JavaScript. En otras palabras, es JavaScript con esteroides.

¿Fuertemente tipado?
Pero, ¿Qué significa que sea un lenguaje "fuertemente tipado"? Recordemos que dentro de
las características básicas de JavaScript, se menciona que es un lenguaje de programación
"débilmente tipado". Esto significa que las variables declaradas en JavaScript pueden
cambiar de tipo de dato durante la ejecución de un programa.
Por ejemplo, podríamos definir una variable x que sea inicializada con un valor numérico, y
en algún otro lugar del código decidir cambiarla a un string.

En TypeScript esto no sería posible ya que el tipo de dato es estático, lo que significa que
está asociado a la variable en el momento de su creación y no permite que sea modificado
nunca más, y el tratar de modificarlo conllevará a un error.

8
¿Por qué utilizar Typescript?
En realidad, TypeScript nos da las mismas funcionalidades de JavaScript, pero con una capa
extra de seguridad gracias a su sistema de tipado. Por esto se dice que son lenguajes
“primos”. Esto quiere decir que podemos trabajar código tal como lo haríamos con
JavaScript, pero con la ventaja de que podemos supervisar la consistencia en los tipos de
datos utilizados para prevenir comportamientos inesperados en el código o bugs.

Consideremos el siguiente caso. Imagina que dentro de un archivo index.js típico existe una
función que utiliza como argumento un string para imprimir algo en consola. Al trabajar con
JavaScript estamos asumiendo que efectivamente ese argumento recibido será siempre un
string.

En el mejor escenario esto va a funcionar sin problemas pero, ¿qué ocurre si en lugar de
recibir un string la función recibe un número, un array o un objeto?:

9
En efecto, obtenemos un error dado que el método toUpperCase solo está definido para
strings. Este tipo de errores son más frecuentes de lo que creemos.
Ejemplo real

Cuando recibimos información de una API asumimos que la información vendrá de


determinada manera, pero muchas veces viene con otro formato. Estos errores serían
detectados únicamente al momento de ejecutar el código. TypeScript nos permite
ahorrarnos estos errores.

Este lenguaje hace una verificación en tiempo de compilación, ayudándonos a detectar


errores mientras escribimos el código y no al ejecutarlo.

10
Continuando con el ejemplo anterior, vamos a hacer una prueba de esto. Primero
cambiaremos la extensión del archivo de index.js a index.ts. Luego vamos a agregarle a la
variable user el tipado (user: string). Al hacer esto nos daremos cuenta que inmediatamente
podemos ver los errores en la función sayHello() con los distintos argumentos.

Te invitamos a replicar este ejemplo en tu Visual Studio Code. Recuerda cambiar la


extensión del archivo a .ts.

Además de brindarnos información sobre los errores de forma rápida, TypeScript incluye
una herramienta nativa de autocompletado de funciones en el editor de texto, lo que lo
vuelve más preciso dando opciones compatibles con el tipo de dato asociado.

Es necesario hacer la observación de que los entornos de ejecución como node o los
navegadores web no tienen ni idea de qué es TypeScript ni su sintaxis para tipar
estáticamente, pues solo trabajan con JavaScript. Si intentamos ejecutar el archivo index.ts
con node recibiremos errores…

11
Para poder ejecutar este script es necesario que sea previamente compilado (traducido) a
JavaScript. Para esto debemos realizar algunas configuraciones en nuestro programa. A
continuación exploraremos de qué se trata esto.

Configuración de entorno
Para poder utilizar TypeScript es necesario instalarlo dentro de nuestro proyecto o de forma
global en nuestra computadora. Esto lo podemos hacer con estos comandos…

Con este comando podrás instalar Este comando creará automáticamente un


TypeScript de forma global en tu proyecto local de node con todas las
computadora y utilizarlo sin problemas en dependencias de este lenguaje ya instaladas.
cualquier proyecto.

Te recomendamos que NO instales TypeScript globalmente, ya que puede traer problemas


de compatibilidad de versiones más adelante.

12
Una vez hecho esto, podremos compilar el código TS a JS utilizando el comando npx tsc
index.ts.

Al hacerlo, nos daremos cuenta que dentro de nuestro proyecto se crea un archivo de .js.
¡Es un archivo de JavaScript básico! Veamos ahora cómo funciona el compilador.

Luego crear el fichero de configuración tsconfig.json con $ tsc --init


Que indicará cómo se comportará TypeScript en el proyecto.
Los ficheros creados al final.
Esta configuración permite además sólo tener que escribir $ tsc, para ejecutarlo y sin
tener que dar $ tsc index.ts (por ejemplo).

ESLint
Antes de empezar con las bases de TypeScript vamos a aprender a cómo configurar un
"analizador de código".

Por analizador de código nos referimos a una herramienta que permite al lenguaje detectar
e informar los errores conforme se escriben líneas de código. Si bien el análisis
proporcionado nos permite trabajar en la mayoría de casos, hay ocasiones en las que las
necesidades del proyecto requieren opciones más personalizables y de mayor alcance. Para
ello, haremos uso de ESLint.

ESLint es una herramienta para análisis de código de JavaScript, puede ser implementada
con TypeScript mediante la adición de algunos plugins con características específicas. Para
incorporarlo a nuestro proyecto, es necesario instalar dependencias que serán utilizadas
durante el proceso de desarrollo, a través del comando…

13
De esta forma podemos utilizar a ESLint para analizar y darle formato al código, aunque
debemos hacer algunas configuraciones. En primera instancia, hay que crear un archivo de
configuración llamado .eslintrc.js (nótese el punto del inicio) dentro del cual configuraremos
los plugins necesarios.

Por último, solo tendrás que asignar en el package.json un nuevo script llamado lint con el
valor de la imagen. Profundicemos esto en el siguiente video....

¡Ahora sí! Estamos listos para aprender los fundamentos de TypeScript.

Resumen de Instalación:
$ npm install -g typescript
$ tsc --init
$ npm install eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin prettier --
save-dev

Si instalando typescript local da error como esta captura debes ejecutar:

14
$ sudo chown -R 502:20 "/Users/schmilo/.npm"

Package.json
Agregar scripts y los valores iniciales, importante el main: index.js.
Ejemplo: $ npm run build
{
"name": "pm3-fundamentos",
"version": "1.0.0",
"description": "First typescript test area",
"main": "index.js",
"scripts": {
"lint": "eslint . --ext .ts",
"build": "tsc"
},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^7.2.0",
"@typescript-eslint/parser": "^7.2.0",
"eslint": "^8.57.0",
"prettier": "^3.2.5"
}
}

.eslintrc.js
// eslintrc.js
module.exports = {
parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint'],
extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended'],
rules: {
// Puedes añadir reglas personalizadas acá
},
};

tsconfig.json
Es como el fichero de configuración de Apache o de Tomcat, las Opciones de Compilación
están casi todas comentadas y lo que vaya necesitando se activa quitándole el comentario.

Descomentamos outDir y agregamos “./dist” en lugar del valor por defecto “./”. Esto indica
el directorio de salida para todos los ficheros emitidos.
"outDir": "./dist"

"noImplicitAny": true, /* Enable error reporting for


expressions and declarations with an implied 'any' type. */

Agregar al final de "compilerOptions", luego de la llave

15
"skipLibCheck": true /* Skip type checking all
.d.ts files. */
},
"files": ["index.ts"]
}
Esto permite que sólo se centre en la lista de ficheros TypeScript que le indique esta
opción y con los otros .ts ya veremos cómo hacemos más adelante.

Descomentamos “noImplicitAny”: true. No dejar que tengamos variables o parámetros donde no


se tenga claro que tipo de dato es, que no lo infiera y no mareque error cuando se indique
el tipo en los parámetros de entrada de la función.
const num1:number = 5;
const num2:number = 10;
const sumar = (a: number, b: number) => a + b;
console.log(sumar(num1, num2));
Como por ejemplo, para variables y parámetros, que es el tipo básico number y no el objeto
Number que permite hacer operaciones como por ejemplo:

number:
let age: number = 30;
function add(a: number, b: number): number {
//ese último number es lo que debe retornar la función, un number
return a + b;
}

Number:
const x: Number = new Number(10);
const y: Number = new Number(20);
const z: number = x.valueOf() + y.valueOf();
console.log(z); // 30

Generalmente se recomienda utilizar el tipo de número en lugar del objeto Número en


TypeScript, ya que es una forma más ligera y eficiente de trabajar con valores numéricos.

En resumen, muy resumido


Todos los pasos iniciales para tener configurado ambiente TypeScript.

$ npm install -g typescript


$ npm init -y
$ tsc --init
$ npm install eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin prettier --
save-dev

package.json
{
"name": "pm3-fundamentos",
"version": "1.0.0",
"description": "First typescript test area",
"main": "index.js",
"scripts": {
"lint": "eslint . --ext .ts",
"build": "tsc",
"start": "node ./dist/index.js"
},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^7.2.0",
"@typescript-eslint/parser": "^7.2.0",
"eslint": "^8.57.0",
"prettier": "^3.2.5"

16
}
}

.eslintrc.js
module.exports = {
parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint'],
extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended'],
rules: {
// Puedes añadir reglas personalizadas acá
},
};

tsconfig.json

"outDir": "./dist"
"noImplicitAny": true, /* Enable error reporting for
expressions and declarations with an implied 'any' type. */
},
"files": ["index.ts"]
}

Tipos de datos básicos


Comenzaremos con los tipos de datos primitivos en TypeScript. El tipado para las
variables que contienen estos datos, no necesariamente deben definirse de forma manual,
puesto que este lenguaje puede inferir el tipo de forma automática.

Este es un buen ejemplo de cuándo utilizar la asignación de tipos. En la práctica se hace una
combinación de la asignación e inferencia de datos para generar código más legible y al
mismo tiempo seguro. Veremos más casos de uso conforme avancemos con las clases.

Inferir el tipo de dato (TypeScript sabe, por el valor asignado el tipo de dato que
mantendrá), en la inferencia no se le indica el tipo de dato, lo deduce del valor asignado (al
ser 5, entonces será del tipo de dato básico number).
const num1 = 5;
Explicitar el tipo de dato
const num1:number = 5;
Any permite que el tipo de info contenida mute en el código ( peligroso).
const num1:any = 5;
num1 = “Pedro”;
Definición de un array, por ejemplo de números
let arregloNumeros: number[] = [1, 2, 3];
let arregloStrings: string[] = ["Uno", "Dos", "Tres"];
Definición en tipo de datos en un arreglo (array).
const sumarNumeros = (arr: number[]): number => {
let suma: number = 0;
for (const num of arr) suma += num;
return suma;
}

En conclusión...

17
En esta clase aprendimos un nuevo lenguaje: TypeScript. Vimos cuáles son las ventajas de
hacer código utilizando el tipado estático y cómo configurar nuestro entorno de desarrollo
para que nuestros proyectos puedan ser más seguros.
Descubrimos que TypeScript provee una especie de "asistente de desarrollo" que está al
tanto de nuestros posibles errores al momento de escribir código, compilando nuestros
scripts con extensión .ts en archivos .js tradicionales.
Finalmente, conocimos de qué manera hacer uso de los tipos de datos primitivos, así como
a determinar en qué situaciones es beneficioso realizar el tipado estático manualmente y
cuándo dejar que este sea inferido.

Homework
En esta lección encontrarás una introducción al proyecto/homework que deberás
desarrollar hoy. Mira el siguiente video con atención y luego sigue las consignas. ¡Buena
suerte!

INSTRUCCIONES
Habilidades y competencias que desarrollarás...
• Planificación de estructura de proyecto Full Stack.
• Planificación de estructura de base de datos.

[REQUISITOS]:
• Haber redactado las historias de usuario para la aplicación de gestión de turnos que
vamos a desarrollar.
• Haber planteado la estructura de entidades y atributos para la aplicación. La misma
debe contar con al menos las siguientes
entidades: Usuarios, Turnos y Credenciales (usuario y contraseña de cada usuario).

ACTIVIDAD 1
PUNTOS A TENER EN CUENTA
¡Bienvenidos al Proyecto Integrador del módulo 3!
Notarás que este proyecto será mucho más robusto y desafiante que los anteriores, por lo
cual será clave más que nunca contar con una correcta PLANIFICACIÓN.

18
Para iniciar, pasamos en limpio lo que queremos lograr: una aplicación para la gestión de
turnos. El usuario deberá poder, interactuando con el FrontEnd, agendar su turno en una
fecha y hora determinados para asistir a ser atendido a un determinado lugar.
Para encarar este proyecto partiremos de algunas pautas y simplificaciones:
• Tú decides de qué lugar se trata: un banco, una peluquería, un restaurant, un
consultorio médico, etc. Tendrás libertad para esta elección y luego la temática
deberá verse reflejada en el diseño de tu aplicación de Frontend.
• Un usuario siempre debe estar autenticado para poder reservar un turno. No se
agendará turnos a anónimos o invitados.
• Los turnos deberán ser agendados siempre dentro del horario de atención del
establecimiento, el cual también estará en tus manos decidir cuál es. También
deberás tener en cuenta los fines de semana como días no laborables.
• Asumimos que el establecimiento cuenta con “infinitos” recursos para atender a sus
clientes. Es decir, si 10, 20, 50 o 100 usuarios agendaron un turno para las 10:00hs
del día 11/12/24, asumimos que el lugar cuenta con capacidad para poder
atenderlos a todos al mismo tiempo en este horario.
• Los turnos reservados por los usuarios pueden ser cancelados hasta el día anterior al
día de la reserva. No implementaremos la función de reprogramar.

Asimismo, definiremos una serie de EXTRA-CREDITS, los cuales podrás incluir en tu proyecto
en las cantidades y tiempos que estén dentro de tus posibilidades:
• Envío de confirmación vía email al usuario luego de reservar un turno o cancelarlo.
• Posibilidad de que el usuario “suba” una foto perfil a su cuenta a través de un
archivo de imagen (.jpg, .png, etc).

ACTIVIDAD 2
Ahora que tenemos estas pautas sobre la mesa, pasemos a la actividad del día: ¡a planificar!
1. Redactar las “user stories” de tu proyecto. En el video que acompaña a esta consigna
te explicamos qué son estos puntos y algunas estrategias para desarrollarlos.
2. Define un primer esquema de tu base de datos. Probablemente luego encuentres
que hay cosas que modificar o mejorar, pero al menos establece un punto de
partida: entidades involucradas, relación entre las entidades, atributos que
queremos describir y, muy importante, los tipos de datos de cada atributo.

TIPS
• Notarás que en el proceso de desarrollo de este proyecto te tocará tomar muchas
decisiones y planificar varias cosas. Debes tener en cuenta que en el desarrollo
escribir código no lo es todo. Por eso este paso de planificación resultará vital.
• Los extra credits son desafíos que requerirán de investigación adicional para poder
completarlos, por eso te recomendamos que intentes alcanzar al menos uno de
ellos. Esta práctica será muy valiosa de cara a los proyectos que encaramos luego de
este módulo.

Homework – Users Stories.

Reservas de un restaurante.

19
Esta aplicación está diseñada para llevar las reservas de un restaurante, con mesas infinitas
y un cliente sólo podrá hacer dos reservas por día. Para poder reservar el cliente tiene que
estar registrado.

Historia del restaurante.


1. Como administrador de la plataforma quiero que los clientes se registren para poder
hacer reservas.
2. Como administrador quiero que los clientes puedan reservar mesa y que sean
notificados del éxito de su reserva.
3. Como administrador quiero que la plataforma limite a los clientes a máximo dos
reservas diarias.
4. Como administrador quiero que los potenciales clientes, sin registrar o registrados,
tengan conocimiento de mi restaurante, su menú y ventajas, como: buena comida,
atención y un menú exquisito, oferta de degustación para más de 5 personas, zona
envuelta por parques que aportan belleza al lugar, parking propio, servicio VIP a
domicilio, ambiente elegante y tranquilo, etc.
5.
Historia del usuario. Al usuario sin registrar se le llamará futuro cliente, y al usuario
registrado se le llamará cliente.
1. Como futuro cliente quiero ver la oferta del restaurante, ubicación y forma de
contacto.
2. Como futuro cliente quiero poder registrarme, creando una cuenta nueva.
3. Como futuro cliente quiero ser notificado por correo electrónico que he sido
registrado con éxito en la plataforma.
4. Como cliente registrado quiero poder iniciar sesión con las credenciales que me
registré.
5. Como cliente registrado quiero poder reservar una mesa y recibir un correo
electrónico (extra credit) de que el hecho ocurrió correctamente.
6. Como cliente registrado quiero poder cancelar una reserva y recibir un correo
electrónico (extra credit) que indique que la cancelación a ocurrido con éxito.
7. Como cliente registrado quiero ver el histórico de reservas, viendo primero las que
están pendientes de asistencias, como las que ya he asistido.
8. Como cliente registrado quiero ordenar por fecha de cita el histórico de reservas.
9. Como cliente registrado quiero filtrar la lista de reservas por pendientes o vencidas.
10. Como cliente registrado quiero poder cerrar sesión.
11. Como cliente registrado quiero poder cambiar mis datos personales.
12. Como cliente registrado quiero poder cambiar mi correo electrónico.
13. Como cliente registrado quiero poder cambiar mi nombre de usuario.
14. Como cliente de usuario quiero poder subir mi foto de perfil (extra credit).
15. Como cliente registrado quiero poder eliminar mi cuenta de cliente, y que eliminen
toda mi información con base a la ley orgánica de protección de datos (LOPD).

TypeScritp 2
Segunda clase de TypeScript... aquí es donde se pone buena la cosa. En la clase anterior
aprendimos los fundamentos de este lenguaje, la declaración de variables a un nivel básico y

20
la configuración del entorno del proyecto que vamos a desarrollar. En el siguiente video
haremos un overview sobre cómo continuaremos.

Revisemos cuáles son los objetivos de esta clase


1. Comprender y aplicar el concepto de funciones y tipado en TypeScript.
2. Dominar la técnica de emplear parámetros tipados en funciones para mejorar la
integridad del código.
3. Utilizar tipos de retorno en funciones para asegurar la consistencia en el manejo de
datos.
4. Explorar el uso de interfaces y tipos personalizados.

Funciones y tipado
¿Cómo tipar funciones?
¿Recuerdas la clase anterior? Uno de los puntos que habíamos visto es la importancia de
implementar tipados estáticos en nuestras variables. Esto nos permitía definir qué tipo de
dato vamos a guardar en una variable. También exploramos el tipado en tipos de datos
primitivos y de qué manera TypeScript nos proporciona una inferencia sobre estos.
Ahora vamos a explorar cómo darle un tipado a nuestras funciones, lo que permitirá
conocer los posibles errores en el tiempo de compilación. Revisemos esto en el siguiente
video.
¿Cuál es el error en el siguiente código?

Se quiere sumar un string con un número. Esto no se puede.


Se quiere retornar un número, pero la función explicita que es de tipo void.
La función Number no está declarada, por lo que no existe.
SUBMIT

TAKE AGAIN
Ahora que entendemos esto, ¿Qué sucede si los parámetros que le pasamos a una función
poseen un tipo de dato más complejo? Como por ejemplo arrays u objetos. Para abordar
este tema vamos a hablar sobre interfaces.

21
Interfaces y tipos personalizados
¿Qué es una interfaz?
Las interfaces permiten definir la forma que debe tener los tipos de datos más complejos.

En el caso de los objetos, por ejemplo, especifican qué propiedades deben contener, así
como los tipos de datos asociados a sus valores.

No proporcionan una implementación real. Es decir, solo establece las reglas que deben
seguir. Como una especie de "contrato", para que el objeto sea compatible con la interfaz.
Veamos un ejemplo...
Imagina que deseas construir algo con bloques y cada uno de estos es diferente a los demás.
Algunos son cuadrados, otros rectangulares y otros circulares. También tienen propiedades
como tamaño y color.
Una interfaz sería una especie de plantilla que define cómo deben ser los bloques de cada
tipo. Por ejemplo, la interfaz “Cuadrado” podría tener una propiedad “lado” de tipo
número, mientras la interfaz “Rectángulo” tendría propiedades “ancho” y “largo” ambas de
tipo número.

22
Las interfaces ayudan a garantizar la consistencia y facilitan la comunicación entre
diferentes partes del programa. Pero mejor veamos esto en ejemplos de código.
Ahora sabemos utilizar parámetros según el tipo de dato: strings, numbers o booleanos, e
incluso cómo construir aquellos más complejos, como los objetos, a partir de las interfaces.
Pero, ya que conocemos las interfaces, vamos a trabajar ahora con una nueva herramienta
similar: tipos personalizados.

Definición de tipos personalizados


Los tipos (types), similares a las interfaces, proporcionan reglas que nos permiten definir
tipos de datos como objetos, arrays, funciones, etc. Veamos esto en el siguiente video.

Interfaces | Casos de uso


Tanto las interfaces como los types permiten ser extendidos por otras interfaces o types.
Esto quiere decir que pueden heredar información para usarla en sus propias estructuras.
Sin embargo, lo más común es utilizar interfaces, debido a su legibilidad y mejor
visualización de errores en compilación.
Veamos un ejemplo haciendo uso de la palabra clave extends. En este caso, la
interfaz IEmpleado podrá utilizar las propiedades de ITrabajo para definir sus propios
objetos.

23
Otra característica particular es que cuando creamos dos interfaces con el mismo nombre
en distintas partes del código, ambas se comportan como una sola con toda la información.
Aquí vemos que, a pesar que la interfaz se reescribió dos veces y con el mismo nombre, se
comporta al final como una única con la información de ambas.

Tipos | Casos de uso


Hay dos características muy comunes a las que se les da uso en types: union types y alias.

UNION TYPES
Estos permiten describir valores que pueden ser uno de varios tipos posibles, ya sean
primitivos o complejos.

Por ejemplo, podemos definir un tipo con las tallas de camisas para una tienda virtual, con
las opciones "S", "M", "L" y “XL”, de manera que únicamente pueda tomar estos valores y
valide, por ejemplo, si hay disponibles o no en stock. Para indicar que este será un nuevo
tipo de dato debemos inicializarlo con el indicador type.

24
Al utilizar unión types se proporciona una forma clara y segura de manejar casos en los que
una variable puede tener distintos valores.

ALIAS
Los alias de tipos son una característica en TypeScript que permiten asignar un nombre
personalizado a un tipo existente o complejo. Esto facilita la creación de tipos reutilizables.

En este caso, Coordenada es un alias para un array de dos números. Al utilizar este alias,
estamos haciendo que el código sea más expresivo y fácil de entender.

Tipos vs Interfaces (en objetos)


Tanto las interfaces como los tipos son herramientas que nos permiten definir la
estructura de los tipos de datos en el código. Pero, aunque su funcionamiento sea similar,
¿Cuándo debemos utilizar uno u otro? Respondamos esto en el siguiente video.
¿Mucha información hasta ahora verdad?

25
Para que no nos perdamos entre tantas reglas y conceptos, en la siguiente lección vamos a
repasar un poco todo lo que vimos hasta el momento dentro de una demo. ¡Vamos!

Interface: Siempre que haya que describir las propiedades de un objeto.

Demo (integración)
Hagamos un repaso
Con todo lo que vimos en mente, vamos a trabajar en una demo que nos permita integrar
todos estos conceptos. Vamos a crear interfaces, y cada una de ellas nos servirá como base
para definir objetos con una estructura particular.

Interesante el tema de un atributo que pueda ser de varios tipos y que pueda ser resumido
con una herencia porque todos son Itrack

interface ITrack {
title: string;
}
interface ISong extends ITrack {
artist: string;
duration: number;
}
interface IPodcast extends ITrack {
host: string;
episode: number;
}
interface IAudiobook extends ITrack {
author: string;
duration: number;
}
/* donde por ejemplo, la herencia y en el caso de ISong,
no solamente hereda las propiedades sino que también
es un track, por eso ITrack funcionará perfectamente en los 3 casos. */
interface IPLaylist {
name: string;
playlist: ITrack[];
}
// Primera forma de tener varios tipos en un atributo
// interface IPLaylist {
// name: string;
// playlist: (ISong | IPodcast | IAudiobook)[];
// }

const song1: ISong = {


title: 'By the way',
artist: 'Red Hot Chili Peppers',
duration: 100,
};
const podcastl: IPodcast = {
title: 'Cuentos desde la cripta',
host: 'Una calavera',

26
episode: 100,
};
const audiobook1: IAudiobook = {
title: 'El Principito',
author: 'Antoine de Saint-Exupéry',
duration: 100,
};
const myPlaylist: IPLaylist = {
name: 'Mi playlist',
playlist: [song1, podcastl, audiobook1],
};

Preguntas:
- ¿Puedo tener una pista que sólo sea el título y eso desvirtuaría el uso del resto?
- Si hago una consulta de la playlist, cómo obtengo el tipo (canción, podcast o audiolibro)
¿typeOf me traería el tipo de interface concreto, o me diría que es una función o un objeto?
- ¿Le puedo crear un enum de los tipos para para que con las key obtenga un nombre más
amable, porque por lo pronto y con el último ejemplo todos sería Itrack?

En conclusión
En esta clase conocimos conceptos más avanzados, como el tipado de
funciones en, interfaces y el tipado personalizado. Comprendimos cómo tipificar funciones,
tanto sus parámetros como el valor que retornan, a partir de tipos de datos primitivos y
complejos. Si nuestras funciones no retornan valores, TypeScript podrá inferirlo sin
necesidad de escribirlo manualmente.

Exploramos la creación y uso de interfaces y tipos para estructurar objetos. Vimos que, a
pesar de que su comportamiento fuera similar, tenían casos de uso puntuales que los
diferenciaba uno del otro, haciendo énfasis en la herencia, los unión types y los alias.

Homework
En esta lección encontrarás una introducción al proyecto/homework que deberás
desarrollar hoy. Mira el siguiente video con atención y luego sigue las consignas. ¡Buena
suerte!

27
INSTRUCCIONES
Habilidades y competencias que desarrollarás...
• Configuración de entorno de desarrollo con TypeScript.

ACTIVIDAD 01
En esta actividad nos centraremos en configurar nuestro entorno de desarrollo con
TypeScript. Para ello haremos lo siguiente: Generar el archivo package.json con el comando
correspondiente.
ACTIVIDAD 02
Instalar TypeScript . Recuerda que te sugerimos hacer esta instalación de forma LOCAL.
ACTIVIDAD 03
Generar el archivo tsconfig.json con el comando correspondiente.
ACTIVIDAD 04
Ajustar en tsconfig.json las configuraciones que hemos visto en la clase.
ACTIVIDAD 05
Configurar el comando build para que ejecute el compilador de TypeScript y el comando
start para que ejecute dicho build.

ACTIVIDAD 06
Crear la carpeta src del proyecto y el módulo index.ts.
ACTIVIDAD 07
Realizar pruebas de variables, objetos, funciones y demás características de TypeScript
vistas en clase dentro de este módulo index.ts.

TIPS
• Si bien esta actividad puede parecer sencilla, la configuración de TypeScript requiere
de mucha atención y cuidado en cada una de las propiedades involucradas. Recrea
paso a paso lo visto en las clases para que tu módulo index.ts se pueda “buildear”
correctamente y luego ser ejecutado desde el build.
• Aprovechar el index.ts a modo de “sandbox”. Has todas las pruebas que quieras con
las características de TypeScript para ir familiarizándote con ellas.
• No te preocupes por las importaciones y exportaciones de módulos. Si intentas
trabajar con varios módulos probablemente te encuentres con algunos errores. No
te apresures. En la próxima clase veremos cómo realizar este paso.

[REQUISITOS]:
• Haber creado correctamente el archivo package.json.
• Haber creado correctamente el archivo tsconfig.json.
• Haber creado correctamente la carpeta src y el módulo index.ts dentro de la carpeta.
• Haber configurado correctamente los comandos build y start para que funcionen de
acuerdo a lo esperado en las consignas.

Express y TypeScript
Introducción

28
¡Alto! Hasta el momento hemos aprendido a utilizar una herramienta super útil para la
construcción de un servidor: express. Además, en las clases anteriores le dimos un level up a
nuestro lenguaje de desarrollo: TypeScript.
Como ya delata el nombre de esta clase, veremos cómo integrar ambas herramientas en un
solo proyecto para construir un servidor profesional. En el siguiente video veremos cómo lo
llevaremos a cabo.

Revisemos cuáles son los objetivos de esta clase


1. Repasar en qué consisten express y TypeScript.
2. Aprender a inicializar un proyecto con NPM y TypeScript.
3. Crear un CRUD básico y mejorar la seguridad del código.
4. Comprender de qué manera los middlewares con express y TypeScript nos permiten
hacer más eficiente el proceso de desarrollo.

Express & TypeScript


Breve repaso de herramientas
Antes de ver de qué manera ambas tecnologías se conectan para trabajar y desarrollar un
proyecto más potente, recordemos un poco en qué consiste cada una de ellas.

EXPRESS
Express es un framework minimalista para node, ideal para construir aplicaciones web y
APIs. Su sencillez y flexibilidad lo convierten en una opción popular para el desarrollo
backend con JavaScript o TypeScript.

Hemos visto también que pueden agregarse middlewares y servicios, pero por ahora solo
nos centraremos en lo básico de un servidor. Un ejemplo sencillo de como crear un servidor
básico luce así.

29
TYPESCRIPT
TypeScrip es un lenguaje de programación que potencia las funcionalidades de JavaScript y
que nos permite definir tipados estáticos, los cuales nos ayudarán a reducir
comportamientos inesperados en la ejecución del código y que podemos prever en tiempo
de compilación.

Por ejemplo, al construir una función dejamos asignado explícitamente el tipo de datos que
recibirá como argumentos y que retornará, en caso que devuelva algo:

¿Cómo se complementan?
La combinación de ambas tecnologías implica la creación de aplicaciones del lado servidor
más mantenibles, escalables y eficientes. Podremos dar uso a los tipos estáticos de TS
dentro de los controladores y las rutas para detectar posibles errores y prevenir
comportamientos inesperados en nuestros proyectos.

En este ejemplo vemos que la variable PORT recibe un valor string en lugar de uno
numérico.

Así que TSC nos notifica que es preciso realizar la corrección para poder levantar el servidor.

30
En este otro ejemplo validamos la estructura de un nuevo usuario a partir de una
interfaz Usuario, logrando ver errores en tiempo de compilación.

En este caso, la propiedad extra no hace parte de la interfaz y de allí el error.


Estos ejemplos nos muestran cómo TypeScript y Express trabajan juntos para darnos un
desarrollo más completo y complementario. Pasemos ahora a ver formalmente cómo
integrar express y TypeScript en nuestros proyectos de backend.

31
Estructura de proyecto
Inicialización de proyecto
Para avanzar, vamos a construir una pequeña API Rest para realizar solicitudes básicas de
CRUD. Esto ya lo hemos realizado antes...
1. Lo primero será crear un nuevo directorio con el comando mkdir mi-proyectoe
ingresar al directorio del proyecto con cd mi-proyecto.
2. Una vez dentro, inicializamos el proyecto utilizando npm init -y.
3. Para instalar TypeScript, ingresa el comando npm install --save-dev typescript
@types/express @types/node nodemon ts-node express.

Archivos y carpetas
Comencemos añadiendo estratégicamente los diferentes archivos y directorios que
usaremos en el proyecto, con el fin de trabajar de manera organizada. Puedes ver esto en el
siguiente video.

Antes de continuar, analicemos algunas cuestiones importantes del video anterior.


• Los archivos de TS a compilar estarán ubicados en la carpeta src y el resultado de la
compilación estará en una carpeta dist que se creará en su momento.
• El target señala que se compilará para la versión ES5 y se utilizará el sistema
de módulos CommonJS.
• Con strict indicamos que se harán restricciones más rigurosas del código, lo que
implica mayores comprobaciones por parte de TSC.
• Se incluirá en la compilación todos aquellos archivos dentro de src y sus directorios
internos con extensión .ts y se omite la carpeta de node_modules para la
compilación.

Solo nos resta configurar el script para ejecutar el proyecto. Como node no puede ejecutar
código de TypeScript, primero debe realizar la compilación. Para esto, dentro del archivo
package.json agregaremos un script build que ejecutará a TSC. Esto nos permitirá realizar la
compilación al ejecutar el comando npm run build.

En el script start agregamos nodemon, pero esto sería muy poco práctico ya que cada vez
que realicemos cambios en algún archivo de TS, deberíamos detener el servidor,
ejecutar npm run build y volver a ejecutar npm start.

Para evitar este proceso instalamos la dependencia ts-node con el comando que nos
permite ejecutar archivos .ts directamente en node, sin necesidad de compilar previamente.
Finalmente, usamos nodemon para reiniciar automáticamente nuestro servidor. Recuerda
instalar también dotenv para la gestión de las variables de entorno (npm install dotenv).
Ahora que hemos comprobado que la compilación y el servidor funcionan, llegó el momento
de crear nuestras primeras rutas.

32
NOTAS ESTRUCTURA DEL PROYECTO de instalación del servidor y
entorno para el proyecto
$ npm install dotenv
En el fichero, a nivel de back, el fichero .env poner las variables de ambiente que creamos
necesitar,
por ejemplo:
.env
__PORT__=3000

$ npm i express
$ npm i -D morgan
$ npm i -D nodemon

nodemon.js: a nivel de back, crear el fichero nodemon.json le agregaremos 3 propiedades:


{
"watch": ["src"],
"ext": "ts",
"exec": "ts-node src/index.ts"
}

Que lo que dice es: Vigila los cambios en los ficheros ".ts" dentro de la carpeta "src" y al
haber cambios vuelve a ejecutar con ts-node el fichero index.tx dentro de la carpeta "src".

package.json cambiar el script:


"start": "nodemon src/index.ts"
POR ESTE (porque ya el nodemon se le dieron las instrucciones en el fichero de
configuración nodemon.js)
"start": "nodemon"
Nota: Por eso es que con la ejecución de "npm start" esto funcionará.

Las siguientes 2, para no tener que indicar tipos de datos complicados como router, req, res,
etc.

$ npm i -D @types/node
$ npm i -D @types/express

Paquete de herramientas para hacer más sencillo trabajar con TypeScript dentro del
entorno Node, y que nodemon funcione sin problemas (cosas sencillas funcionan, pero al
complicarse el código… la cosa podría cambiar si no se instala el TypeScript for Node).
Además de no tener que pasar por el proceso de build y ejecución, build y ejecución, hasta
el infinito (…y más allá).

$ npm i -D ts-node

Generar tsconfig.json
$ tsc --init
tsconfig.json
"target": "es2016"
"module": "commonjs"
"outDir": "./dist" // Ficheros compilados

33
"rootDir": "./src" // Código fuente de la aplicación, los ficheros fuentes
"strict": true // activar chequeo de tipos

AL FINAL de tsconfig.json
"include": ["src/**/*.ts"],
"exclude": ["node_modules"]

include: indica que cuando ts haga el build para hacer la carpeta dist, cuales son los ficheros
que tienen que buscar.
"src/**/*.ts" significa que: busque dentro de la carpeta src, o cualquier subcarpeta (**),
todos los ficheros *.ts, para compilar.

exclude: es una carpeta muy grande y que TS no tiene por qué compilar.

En resumen el fichero quedaría de la siguiente manera:


{
"compilerOptions": {
"target": "es2016",
"module": "commonjs",
"rootDir": "./src",
"outDir": "./dist",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitAny": true,
"skipLibCheck": true
},
"include": ["src/**/*.ts"],
"exclude": ["node_modules"]
}

PARA PROBAR RÁPIDAMENTE TODA LA CONFIGURACIÓN HECHA HASTA AHORA:

env/index.ts
const express = require('express');
require('dotenv').config();
const port = process.env.__PORT__;
const server = express();
server.listen(port, () => {
console.log(`Server listening on port ${port}`);
});

En terminal:
$ npm start

En consola tendremos que ver el puerto, ejecutándose y que cuando hayan cambios se
relance el servidor ts-node.

Manejo de rutas
Creación y tipado de rutas
¿Recuerdas qué significa CRUD? Se refiere a las
operaciones create, read, update y delete que podemos realizar en un servidor. Con todo lo

34
que hemos aprendido hasta el momento sobre TypeScript, nuestro siguiente paso será
construir rutas y controladores estáticamente tipadas.
Lo primero que haremos será crear una interfaz llamada Recurso. Esta interfaz estará
definida en un archivo llamado recursos.ts dentro de una nueva carpeta llamada routes en
el directorio src.
Esto nos permitirá realizar validaciones dentro de las rutas que haremos en su momento.

Recuerda anteponer la letra i mayúscula en el nombre para resaltar el hecho de que es


interfaz.

35
En este mismo archivo, definiremos el router con cada uno de los métodos HTTP, por lo que
deberemos importarlo junto con las interfaces Request y Response para manejar la solicitud
y la respuesta a las peticiones, respectivamente.

Inserción de recursos
Lo primero que vamos a hacer es crear una ruta de tipo POST. Como sabemos, esta ruta va a
recibir como primer valor un endpoint y como segundo una función de callback
(controlador) que recibe como argumentos la request y response.
🫨Utilizaremos la interfaz de IRecurso para el tipado en la solicitud con Request<IRecurso>, lo
cual significa que esperamos recibir por body un objeto con esta estructura.

CRUD de recursos
Ya hemos creado nuestro primer recurso. Puedes seguir agregando todos los que desees.
¿Qué utilizaremos en los siguientes ejemplos?

Lectura de recursos
Para la lectura de recursos crearemos una ruta GET que ejecutará un controlador para
obtener todos los recursos. Luego responderemos con la lista completa de estos.

Actualización de recursos
Crearemos una ruta PUT para actualizar recursos existentes. En el controlador de la solicitud
podremos asignar no sólo el tipo del recurso, sino también el valor recibido por params.
Luego de esto, actualizamos y respondemos con el recurso actualizado. Si no existe,
devolvemos un mensaje de error.

Eliminación de recursos
En este ejemplo, añadiremos una ruta DELETE para eliminar recursos. En este caso, solo
necesitamos validar el tipo de dato recibido por params, ya que no construiremos un nuevo
recurso, por lo que la interfaz no será necesaria. Luego verificamos. Si el recurso existe, lo
eliminamos y respondemos con el recurso eliminado. Si este no existe, devolvemos un
mensaje de error.

¡Ahora si! Teniendo en claro el contexto anterior, podemos continuar con la revisión del
ejemplo en el siguiente video.
Este CRUD básico de express y TypeScript muestra cómo gestionar recursos de manera
estructurada. La implementación de estas operaciones proporciona una base sólida para el
desarrollo de aplicaciones más complejas. Veamos ahora, cómo podemos
definir middlewares con este lenguaje.

AHORA RESUMO EL PROCESO Y CÓDIGO SEGÚN EL CICLO (PONDRÉ


CÓDIGO CON LAS EXPLICACIONES COMENTADAS.
Ciclo:

36
1. src/index.ts: Es el punto de entrada, en él se ejecuta el servidor a escuchar en el
puerto de la variable de ambiente.
2. src/server.ts: Es la instanciación del servidor express que será llamado por
src/index.ts. A este servidor se le incluirá sus funciones de: comunicación json en
req y res, que use routes/index. como gestor de rutas (no se incluye index.ts en
los import porque se infiere, pero se podría incluir).
3. src/routes/index.ts: contiene los endpoints a ejecutar según la llamada
(solicitudes del front o simuladas con thunder client o similar). Te dice que
controlador deberá ejecutarse según el endpoint y tipo de llamada (get, post,
delete, etc.). Esto finalmente deberá dividrse en módulos y no descargar toda la
funcionalidad en index.ts (ej. usersRoutes.ts, citasRoutes.ts, etc.)
4. src/controllers/usersController.ts: serán los metodos ejecutados desde las rutas,
que indican qué hay qué hacer, el cómo hay que hacer, son los services y serán
ejecutados desde acá. En este punto ya comenzamos a necesitar definir la estructura
del usuario, un interfaz IUser.
5. src/services/userServices.ts: es el momento que la ruta llamada, con los parámetros
recibidos, cobran vida al ejecutar la acción que se necesita realmente: crear
efectivamente un usuario, listar los usuarios, eliminar un usuario según su id.
Para comunicarse con los servicios se necesita un Interfaz especial, porque sería
la de IUser pero difiere ligeramente, es la UserDTO (Data Transfer Object, para
traducirlo que explique mejor: Objeto de transferencia de datos, pero se entiende
mejor si le digo Objeto para pasar datos – a quién a quien necesite recibirlo y que
difiera ligeramente de su definición original).

src/index.ts
import server from './server';
import { PORT } from './config/envs';
server.listen(PORT, () => {
console.log(`Server listening on port ${PORT}`);
});
src/server.ts
import express from 'express';
import router from './routes';

const server = express();


server.use(express.json()); // si no se pone esto, no se podrá leer del body
objetos json por ejemplo en los controladores OJITO PONER LOS PARENTESIS A json()
server.use(router);

// exportar única y exclusivamente server, nada más.


export default server;

src/routes/index.ts
import { Router } from 'express';
import { getUsers, createUser, deleteUser } from '../controllers/usersController';

const router: Router = Router();


// El tipado de router como :Router es facilitado por la instalación
// de $npm i -D @types/express (Siendo en realidad una interfaz).

router.post('/users', createUser);
export default router;

src/controllers/userController.ts
// Nombre Fichero: También se usa como convención User.Controller.ts
import { Request, Response } from 'express'; //al ser mostrados por IntelliSense ha
de escogerse los que indiquen express como objeto que este import sea correcto

37
import { createUserService, getUsersService, deleteUserService } from
'../services/usersServices';
import IUser from '../interfaces/IUser';

export const createUser = async (req: Request, res: Response) => {


const { name, email, active } = req.body;
const newUser: IUser = await createUserService({ name, email, active });
res.status(201).json(newUser);
};

src/interfaces/IUser.ts
interface IUser {
id: number;
name: string;
email: string;
active: boolean;
}
export default IUser;

src/dto/UserDTO.ts
// DATA TRANSFER OBJECT
// Interfaz especial cada vez que necesito llamar a una función y sé que tengo que
pasarle un objeto
// poder describir como es ese objeto, este trabajo lo hará el Data Transfer
Object.
// Por ejemplo: Una función del controlador se la pasa a un servicio un objeto con
información,
// hay que poder describir esa información.
// Aunque sea una interfaz de usuario, no se ajusta estrictamente a la interfaz de
usuario IUser,
// por ejemplo esta no tiene ID
interface UserDto {
name: string;
email: string;
active: boolean;
}
export default UserDto;

src/services/usersServices.ts
import UserDto from '../dto/UserDto';
import IUser from '../interfaces/IUser';

// Sin base de datos, delegamos por ahora los usuarios


// a un arreglo DE TIPO IUser, en principio vacío
let users: IUser[] = [];
let id: number = 1;

export const createUserService = async (userData: UserDto): Promise<IUser> => {


// recibir los datos del usuario
// crear un nuevo usuario
// incluir nuevo usuario en el arreglo
// retornar el objeto usuario creado como respuesta,
// pero que en realidad devuelve una promesa que se devuelve a IUser,

38
// por eso Promise<IUser> como tipo devuelt en la función

const newUser: IUser = {


id,
name: userData.name,
email: userData.email,
active: userData.active,
};
users.push(newUser);
id++;
return newUser;
};

Middlewares
¿Qué era un middleware?
Los middlewares son funciones que juegan un papel importante en el manejo de solicitudes
y respuestas en express. Estas funciones “intermediarias” realizan una tarea concreta antes
de que la request llegue a su destino. Teniendo en cuenta esto, vamos a ver la creación de
middlewares con TypeScript.

Tipado de middlewares
Vamos a crear una nueva carpeta llamada middlewares dentro de src en la cual
agregaremos un archivo de nombre autenticación.ts. En este archivo vamos a definir un
middleware para verificar si un usuario tiene o no autorización para acceder a cierto
contenido de la API.
La estructura base de un middleware es una función que recibe como argumentos
a request, response y next, que nos permiten tomar la solicitud, analizarla y continuar al
endpoint designado por la ruta. Para ello, importamos las
interfaces Request, Response y NextFunction.

También vamos a enviar en esta función un token para saber que el usuario está
autenticado y que tiene permiso para acceder a la información devuelta por el método GET.
Esta información se envía a través de algo llamado "headers" de la petición.

39
Un header es información adicional que acompaña a una solicitud y puede especificar, por
ejemplo, el tipo de contenido que se envía (JSON, texto plano, etc.), la longitud del
contenido y, en particular, datos de autenticación, como en nuestro caso.

Recibiremos el token a través de una propiedad headers del objeto request y


validaremos si este coincide con la palabra “autenticado”. De ser así, la función next nos
permitirá continuar con la petición. Caso contrario, se enviará un mensaje diciendo que no
está autorizado.
¡Excelente! Has construido tu primer middleware utilizando TypeScript. Como podemos ver,
los middlewares se siguen comportando tal cual los conocemos con JavaScript, solo que con
TypeScript crean una capa más de seguridad a partir de su tipado.

Cierre
En esta clase exploramos la sinergia entre TypeScript y express, como la ventaja de unir
estas tecnologías hasta la creación de middlewares con tipado estático. Aprendimos cómo
estructurar nuestros proyectos a partir de un esquema organizado de archivos y directorios.

También vimos de qué manera configurar nuestro entorno de trabajo con node y TypeScript
para generar adecuadamente un servidor y definir las posibles rutas. También
aprovechamos el tipado estático para prevenir errores al momento de definir controladores
y middlewares, cuyo comportamiento es el ya conocido en JavaScript.

Homework
En esta lección encontrarás una introducción al proyecto/homework que deberás
desarrollar hoy. Mira el siguiente video con atención y luego sigue las consignas. ¡Buena
suerte!

INSTRUCCIONES
Habilidades y competencias que desarrollarás...
• Creación de servidor HTTP con express & TypeScript.

ACTIVIDAD 01
En esta actividad iremos paso a paso sobre los contenidos visto en la clase para la creación
del servidor HTTP con express y TypeScript.

Si bien el paso a paso está disponible en la clase, podrás notar que el proceso es bastante
extenso, por lo cual marcaremos para esta actividad los siguiente puntos a alcanzar:

1. Instalación de todas las dependencias necesarias para generar correctamente


nuestro entorno.
2. Configuración de nodemon.json y los comandos del package.json para poder iniciar
nuestro servidor con nodemon al ejecutar el comando correspondiente.
3. Creación del scaffolding del proyecto.

40
4. Configuración de la variable de entorno PORT en su módulo correspondiente.
5. Creación de la instancia del servidor y ejecución en el módulo correspondiente.

ACTIVIDAD 02
A partir de este punto deberías tener tu servidor funcionando y esperando peticiones en el
puerto indicado. Ahora empezaremos a crear muestreo flujo. Ten en cuenta que las
entidades involucradas en nuestra aplicación serán:
• User: el usuario que se registra y utiliza nuestra aplicación.
• Appointment: el turno que puede reservar o cancelar el usuario.
• Credential: el par de “username” y “password” que registra cada usuario.

Rutas / endpoints a crear:


• /users
o GET /users => Obtener el listado de todos los usuarios.
o GET /users/:id => Obtener el detalle de un usuario específico.
o POST /users/register => Registro de un nuevo usuario.
o POST /users/login => Login del usuario a la aplicación.
• /appointments
o GET /appointments => Obtener el listado de todos los turnos de todos los
usuarios.
o GET /appointment => Obtener el detalle de un turno específico.
o POST /appointment/schedule => Agendar un nuevo turno.
o PUT /appointment/cancel => Cambiar el estatus de un turno a “cancelled”.

Para cada uno de los endpoints, crearemos su correspondiente controlador, el cual por el
momento únicamente deberá responder al cliente con un mensaje en texto que indica cuál
será la funcionalidad a ejecutar en cada caso.

TIPS
• En el video de la clase tienes toda la información y pasos para poder realizar la
actividad. Revisa la clase cuantas veces sea necesario, toma nota, y avanza paso a
paso con la actividad.
• Apóyate en el uso de ThunderClient, Insomnia o Postman. Te servirán para probar tu
aplicación en tiempo real.

[REQUISITOS]:
• Haber configurado correctamente la ejecución del proyecto.
• Haber configurado correctamente las variables de entorno.
• Haber creado un enrutador principal y dos enrutadores para users y appointments.
• Haber definido en cada enrutador los endpoints correspondientes a cada entidad.
• Haber implementado el controlador correspondiente para cada endpoint, los cuales
deben responder con un mensaje en texto indicando la operación que se realizará en
cada caso.

SQL Fundamentals

41
Seguro recuerdas cuando mencionamos a SQL, pero... ¿Sabes de que se trata?
Descubramos qué aspectos fundamentales del lenguaje vamos a revisar en el siguiente
video.

Revisemos cuáles son los objetivos de esta clase


1. Aprender qué son las base de datos relacionales.
2. Realizar la configuración inicial de PostgreSQL.
3. Conocer los fundamentos de SQL.
4. Realizar las operaciones básicas de consulta, creación, eliminación y actualización de
datos mediante SQL.
5. Entender el modelado de solicitudes mediante cláusulas y operadores de SQL.

Bases de datos relacionales


Relacional
Hasta ahora sabemos que las bases de datos son sistemas que nos permiten gestionar la
información de una aplicación de manera ordenada para manipular y registrar datos.
Hemos trabajado hasta el día de hoy con bases de datos no relacionales (MongoDB), las
cuales organizan la información en forma de colecciones y documentos (objetos) que
pueden ser referenciados entre ellos mismos y poseen una estructura bastante flexible.

Por otro lado, las bases de datos relaciones cumplen la misma función que las no
relacionales, pero su principal diferencia es que la información se almacena en estructuras
tabulares (tablas) compuestas por filas y columnas, en lugar de colecciones y documentos.
Consideremos dos escenarios concretos para ilustrar mejor esta diferencia.
El primero de ellos, será un sistema de gestión de pedidos en un comercio electrónico. En
este tendremos tablas que representan usuarios, productos y pedidos.

Podemos considerar que la tabla Usuariosguardará el mismo tipo de información para cada
registro: nombre, email y contraseña.

42
PRODUCTOS
En la tabla Productos, cada uno de estos tendrá un nombre y un código de identificación.
Finalmente, la tabla Pedidos va a contener el nombre del usuario, los productos adquiridos
y la fecha de compra.

43
Nuestro segundo escenario, será una plataforma de redes sociales en la que cada usuario
tiene su propio perfil.

Como podemos ver, las tablas en una base de datos relacional mantienen una estructura
estática para la información que guardan en ellas.
Cada usuario tiene las mismas características, así como los productos y pedidos.

Los perfiles basados en una base de datos no relacional, no tienen esta condición.
Por su parte, poseen un esquema flexible y pueden contener diferentes datos según las
necesidades del proyecto.

44
En el modelo relacional, una entidad se representa como una tabla que encapsula las
características de un objeto, como el usuario del ejemplo anterior.

Cada columna de la tabla representa los atributos o propiedades de esa entidad (por
ejemplo, un identificador, nombre del usuario, email y teléfono) y las filas contienen los
registros o instancias particulares de esa entidad (cada usuario individual).

Cabe resaltar que una entidad puede ser cualquier concepto del mundo real, en el que
necesites almacenar y gestionar datos.

45
Para mantener esta integridad se especifica la cardinalidad entre tablas relacionadas. La
cardinalidad nos indica cuántos registros de una tabla están asociados con cuántos de otra.
Por ejemplo, si tenemos dos entidades, Estudiantes y Cursos, estas podrían tener registros
relacionados entre sí:

Existen tres tipos posibles de cardinalidad dentro del modelo relacional:

UNO A UNO (1:1)


Cada fila en una tabla está relacionada a lo sumo con una fila en la otra tabla, y viceversa.

En este caso, podemos definir, por ejemplo, que una entidad Persona se relacione con una
entidad Area (nos referimos al "área" de una empresa), bajo la relación “dirigir”.

En palabras simples, diremos que “una persona dirige una sola área y, a su vez, un área es
dirigido por una sola persona”.

UNO A MUCHOS (1:N)


Cada fila en la tabla "A" puede estar relacionada con varias filas en la tabla "B", pero cada
fila en la tabla "B" solo puede estar relacionada con una fila en la tabla "A".

46
Continuando con las entidades del ejemplo anterior, diremos que, bajo la relación
“trabajar”, podemos considerar que “muchas personas trabajan en una sola área y, de la
misma manera, en una sola área trabajan muchas personas”.

MUCHOS A MUCHOS (N:M)


Varias filas en una tabla pueden estar relacionadas con varias filas en la otra tabla.

Para este caso y considerando dos entidades, Actor y Película, bajo la relación “actuar”, es
pertinente decir que “cada actor puede actuar en muchas películas y, de forma análoga, en
cada película actúan muchos actores”.

47
¿Qué es SQL?
SQL (Structured Query Language) es un lenguaje estandarizado que permite realizar queries
de información a una base de datos relacional. Estas queries pueden ser consultas,
actualizaciones, inserciones o eliminación de datos. Es un lenguaje declarativo, es decir, el
usuario indica qué operación y qué resultado desea obtener y el sistema de gestión se
encarga del procesamiento de la solicitud.

Imagina que tienes una lista de mil usuarios y quieres felicitar a quienes cumplen años el día
de hoy.

No harás el proceso manualmente, ya que te llevaría mucho tiempo. Lo que harás es,
mediante comandos SQL, señalar cuáles son las condiciones y la acción (en este caso,
felicitara quienes cumplen años hoy).
Ahora que entendemos la diferencia entre ambos paradigmas vamos a enfocarnos en
trabajar con una base de datos relacional mediante SQL. Las bases de datos relacionales
más utilizadas son estas...

1. PostgreSQL ----> esta es la que utilizaremos.


2. MySQL
3. MariaDB
4. SQlite

PostgreSQL
¿Qué es PostgreSQL?
PostgreSQL es una base de datos de código abierto y de alto rendimiento. Es hasta la fecha
el sistema de gestión más utilizado en la industria. Podemos mencionar que cuenta con
soporte completo para las consultas SQL e incorpora una amplia gama de tipos de datos
propios y personalizados que facilitan la gestión de la información.

¿Veamos cómo instalar y configurar PostgreSQL en nuestra computadora?

48
Ahora que tienes tu terminal shell abierta (recuerda ingresar tus credenciales), te invitamos
a crear una base de datos con el nombre empleados.

Importante
Todos los comandos que escribas dentro de esta terminal deben terminar en punto y
coma (;) para que sean ejecutados correctamente.
Para poder visualizar nuestras bases de datos dentro de PostgreSQL debemos ejecutar el
comando \l en la línea de comandos. Nos encontraremos un listado incluyendo la que
acabamos de crear.

Para conectarnos a empleados y trabajar sobre ella debemos utilizar el


comando \cespecificando el nombre de la BD. Si la conexión se realiza con éxito verás el
mensaje de la imagen.

Es hora de empezar a llenar de información esta base de datos.

Veamos cómo construir las distintas tablas que la componen.

Creación de tablas y constraints

49
Create Table
El primer paso será crear las tablas correspondientes a las entidades que queremos definir
dentro de nuestra base de datos. Para ello trabajaremos sobre el siguiente modelo entidad
relación que representa la base de datos de los empleados de una empresa.

¡No te abrumes con el diagrama! Lo veremos detalladamente de aquí en adelante.

Ya que estamos conectados a nuestra base de datos estamos listos para crear nuestras
tablas. Utiliza el comando de la imagen, en el que las propiedades indican las características
que tendrá cada columna de la tabla.

Antes de ejecutar el comando anterior, conozcamos de qué manera podemos definir


estas propiedades.
Constraints y tipos de datos
Las propiedades (columnas) de cada entidad deben tener asociado un tipo de dato que nos
permite asegurar que la información de todas las instancias o registros de dicha tabla sea
estandarizada, limitando la información que puede ser almacenada en ella. Algunos
ejemplos de estos tipos de datos son:

Character varying - varchar(n)


Para strings que contengan entre 1 y n caracteres.

Integer - int

50
Integers de 4 bytes, es decir, un número tan grande como 2 a la potencia 32.

Date
Fechas de calendario con estructura año, mes, dia.

Data Types: En la documentación oficial podrás encontrar todos los tipos de datos posibles.
https://www.postgresql.org/docs/current/datatype.html

En el siguiente video revisaremos paso a paso los comandos que hemos visto y le daremos
forma a nuestras primeras tablas.

Por defecto, cuando creamos una tabla siempre estará vacía. Veamos de qué manera
podemos agregarles información y trabajar con ellas.

Estructura de una query


Interacción con las tablas
Cuando hablamos de trabajar con tablas nos referimos a trabajar con información; añadirla,
modificarla, eliminarla, etc. Para lograr este tipo de interacciones utilizaremos
las querys que nos permite crear SQL.

Comencemos viendo de qué manera podemos agregar información a nuestras tablas.


Hasta ahora hemos logrado crear tablas y realizar solicitudes operaciones de
información. Todo esto dentro de tablas independientes. Pero, ¿Qué sucede si trabajamos
con tablas que están relacionadas entre sí como el diagrama que habíamos visto
anteriormente?

Relaciones en SQL
Middlewares con express y ts
Como mencionamos anteriormente, es imprescindible planificar de antemano las relaciones
que habrá entre nuestras tablas. Estas relaciones tendrán una representación de la
cardinalidad. Tomemos nuestro diagrama, por ejemplo, junto a la tabla EMPLEADOS como
base.
Al crear esta tabla, tendremos que indicar las relaciones con el resto de las entidades.
Debemos considerar que la tabla departamentos es la única que se relaciona con la
tabla localidades, así que es necesario observar de qué manera lo hacen a partir de
la cardinalidad vista al inicio de clase.

51
Join
Cuando realizamos solicitudes lo más común es que esa solicitud dependa de la información
de dos o más tablas en función de la relación entre ellas. Para poder crear estas
querys, SQL nos permite utilizar la cláusula llamada JOIN para unir la información entre
tablas.

Por ejemplo, si al realizar una solicitud en vez de visualizar las claves foráneas de cada tabla

52
deseamos conocer el valor que representa, debemos definir el punto de uniónentre ambas
tablas. ¿Parece un trabalenguas🫣? Veamos la explicación de esto en el sigueinte video.

53
54
Ya puedes operar de forma básica dentro de una base de datos relacional. Ahora demos el
siguiente paso y hagamos solicitudes con mayor nivel de complejidad.

Filtrado y ordenamiento de datos


Operadores de comparación
SQL integra también herramientas para filtrar y comparar datos de nuestras consultas a
partir de operadores. Estos pueden ser utilizados para buscar registros con características
muy particulares respecto a sus valores.
Para crear las querys que nos permiten realizar estas solicitudes tenemos mútiples
herramientas que nos ayudan a definir condiciones de filtro u ordenamiento. Una de ellas
son los operadores de comparación. Estos operadores nos permiten utilizar cláusulas
como WHERE o JOIN para comparar dos valores y devolver un resultado booleano que nos
permitirá filtrar la información.
Algunos de estos operadores son....
• Igual a (=)
• Diferente de (!=)
• Mayor que (>)
• Menor que (<)
• Mayor o igual que (>=)
• Menor o igual que (<=)
Veamos cómo crear una query que nos permita traer todos aquellos empleados que
tengan un sueldo mayor a 250k.

55
Este es un caso muy simple en el cual utilizamos un operador de comparación para realizar
una consulta de información.
Ahora, vamos a hacer un aumento de sueldo del 10% a los empleados que ganen menos de
250k. En este caso, el operador está siendo utilizado para definir en qué registros dentro de
la tabla EMPLEADOS deben ser actualizados.

Operadores lógicos
Estos operadores permiten combinar/modificar condiciones en las querys con cláusulas
como WHERE, y sirven para realizar comparaciones lógicas entre expresiones.

1. AND. Deben cumplirse ambas condiciones


2. OR. Debe cumplirse al menos una condición
3. NOT. Invierte el valor de una condición

Un ejemplo básico es utilizar estos operadores para unir dos condiciones de filtrado
generadas por un operador de comparación. Por ejemplo, tratamos de filtrar a los
empleados que coincidan con dos nombres específicos...

56
Otro ejemplo podría ser que queramos actualizar el sueldo de los empleados exceptuando a
uno en específico. Ambos operadores pueden ser utilizados de formas combinadas para
lograr diferentes resultados…

Finalmente, abordaremos un último tema que nos permitirá ejecutar consultas aún más
complejas con operaciones que combinan la información de las tablas relacionadas.

Nuestro chat con IA


En este enlace te compartimos el chat con GPT que hemos utilizado a lo largo de la clase.
https://chat.openai.com/share/fedf71ca-7c00-4e74-9526-6c76d10864fd

Cierre
Hemos visto los fundamentos de las relaciones en una base de datos y de SQL, así como sus
principales características y uso. Comparamos el paradigma SQL vs NoSQLpara resaltar las
razones por las cuales es importante conocer y trabajar también con bases de datos
relacionales.

También exploramos cómo organizar, manipular y consultar datos


utilizando PostgreSQL como base de datos. Diseñamos esquemas, creamos tablas que
representan entidades y se relacionan unas con otras, conocimos las
distintas cardinalidades entre dichas relaciones y a ejecutar consultas complejas utilizando
distintas cláusulas, así como operadores lógicos y de comparación.

Por último, tuvimos un vistazo general de los joins que permiten crear queries más
complejas y obtener información explícita entre las tablas que se relacionan.

57
Homework
En esta lección encontrarás una introducción al proyecto/homework que deberás
desarrollar hoy. Mira el siguiente video con atención y luego sigue las consignas. ¡Buena
suerte!

INSTRUCCIONES
Habilidades y competencias que desarrollarás...
• Creación de servidor HTTP con express & TypeScript .

ACTIVIDAD 1
En esta actividad continuaremos con la creación de nuestro servidor, avanzando con la
implementación de los controladores y servicios.

Implementar las interfaces de las entidades con las que estaremos trabajando.
• User
o id: ID numérico que identifica al usuario.
o name: nombre completo del usuario.
o email: dirección de email del usuario.
o birthdate: fecha de nacimiento.
o nDni: número de DNI o identificación.
o credentialsId: ID de las credenciales, referencia al par de credenciales que
posee el usuario.
• Appointment:
o id: ID numérico que identifica al turno.
o date: fecha para la cual fue reservado el turno.
o time: hora para la cual fue reservado el turno.
o userId: ID del usuario que agendó el turno, referencia al usuario
o status: status actual del turno, que puede ser “active” o “cancelled”.
• Credential:
o id: ID numérico que identifica al par de credenciales.
o username:username del usuario que posee las credenciales.
o password: password del usuario que posee las credenciales.

58
ACTIVIDAD 2

Ahora trabajaremos sobre las funciones de servicio. Recuerda en cada servicio crear, por el
momento, un arreglo de elementos que se ajusten a las interfaces creadas que te servirán
como “precarga” de datos.

En el servicio de credenciales:
• Implementar una función que reciba username y password y cree un nuevo par de
credenciales con estos datos. Debe retornar el ID del par de credenciales creado.
• Implementar una función que recibirá username y password, y deberá chequear si el
nombre de usuario existe entre los datos disponibles y, si es así, si el password es
correcto. En caso de que la validación sea exitosa, deberá retornar el ID de las
credenciales.

En el servicio de usuarios:
• Implementar una función que pueda retornar el arreglo completo de usuarios.
• Implementar una función que pueda retornar un elemento del arreglo que haya sido
identificado por id.
• Implementar una función que pueda crear un nuevo usuario dentro del arreglo PERO
ten en cuenta que al momento de crear el usuario, debe crear su correspondiente
par de credenciales llamando a la función correspondiente del servicio de
credenciales. Al recibir de esta función el id de las credenciales, debe guardar el dato
en la propiedad credentialsId.

En el servicio de turnos:
• Implementar una función que pueda retornar el arreglo completo de turnos.
• Implementar una función que pueda obtener el detalle de un turno por ID.
• Implementar una función que pueda crear un nuevo turno, siempre guardando,
además, el ID del usuario que ha creado dicho turno. NO PUEDE HABER UN TURNO
SIN ID DE USUARIO.
• Implementar una función que reciba el id de un turno específico y una vez
identificado el turno correspondiente, cambiar su estado a “cancelled”.

ACTIVIDAD 3

El siguiente paso será implementar tus controladores ahora en su versión completa,


utilizando cada uno de ellos las funciones de servicio que creas convenientes para cada
caso. Quizás algunas funciones de servicio no sean utilizadas por el momento, pero tendrán
su aparición más adelante.

TIPS
• En el video de la clase tienes toda la información y pasos para poder realizar la
actividad. Revisa la clase cuantas veces sea necesario, toma nota, y avanza paso a
paso con la actividad.
• Apóyate en el uso de ThunderClient, Insomnia o Postman. Te servirán para probar tu
aplicación en tiempo real.

59
[REQUISITOS]:
• Haber configurado correctamente la ejecución del proyecto.
• Haber configurado correctamente las variables de entorno.
• Haber creado un enrutador principal y dos enrutadores para users y appointments.
• Haber definido en cada enrutador los endpoints correspondientes a cada entidad.
• Haber implementado el controlador correspondiente para cada endpoint, los cuales
deben responder con un mensaje en texto indicando la operación que se realizará en
cada caso.

PostgreSQL
Explicación sencilla de Entidad Relación
https://support.microsoft.com/es-es/office/vídeo-introducción-a-las-relaciones-de-tabla-
728d53ff-f332-4ac6-9382-574ee271500a

En el shell de PostgreSQL:

Listar bases de datos: \l


Cambiar de base de datos: \c demo_sql
Listar las tablas: \dt ó \dt+
Describir estructura de una tabla: \d ó \d+ directores

------------------------

CREAR TABLAS:
CREATE TABLE peliculas (
id SERIAL PRIMARY KEY,
title VARCHAR(255),
year INTEGER,
duration INTEGER
);

ALTER TABLE peliculas


ADD COLUMN director_id INTEGER;

CREATE TABLE directores (


id SERIAL PRIMARY KEY,
name VARCHAR(255),
since DATE,
nationality VARCHAR(100)
);

CREATE TABLE generos (


id SERIAL PRIMARY KEY,
nombre VARCHAR(100) NOT NULL
);

CREATE TABLE pelicula_genero (


pelicula_id INTEGER REFERENCES peliculas(id),
genero_id INTEGER REFERENCES generos(id),
PRIMARY KEY (pelicula_id, genero_id)
);

60
INTEGRIDAD REFERENCIAL Y CASCADAS DE ELIMINACIÓN Y
ACTUALIZACIÓN
Agregar integridad referencial y eliminación en cascada en la tabla peliculas
Con esta sentencia, estamos estableciendo que cuando se elimine un director de la tabla
directores, se eliminarán automáticamente las películas asociadas a ese director de la tabla
peliculas, manteniendo así la integridad referencial. Sin embargo, la eliminación de una
película no afectará al director.

Versión básica de la constraint


ALTER TABLE peliculas
ADD CONSTRAINT fk_director
FOREIGN KEY (director_id)
REFERENCES directores(id);

Versión que además de exigencia de integridad referencial incluya actualización y


eliminación en cascada, explicado arriba.
ALTER TABLE peliculas
ADD CONSTRAINT fk_director
FOREIGN KEY (director_id)
REFERENCES directores(id)
ON DELETE CASCADE
ON UPDATE CASCADE;

Agregar integridad referencial y eliminación en cascada en la tabla pelicula_genero


Con estas sentencias, estamos estableciendo que al eliminar un género o una película se
eliminarán automáticamente los registros correspondientes de la tabla pelicula_genero,
manteniendo así la integridad referencial. Sin embargo, la eliminación de una película no
afectará al género y la eliminación de un género no afectará a la película.

ALTER TABLE pelicula_genero


ADD CONSTRAINT fk_pelicula
FOREIGN KEY (pelicula_id)
REFERENCES peliculas(id)
ON DELETE CASCADE
ON UPDATE CASCADE;

ALTER TABLE pelicula_genero


ADD CONSTRAINT fk_genero
FOREIGN KEY (genero_id)
REFERENCES generos(id)
ON DELETE CASCADE
ON UPDATE CASCADE;

INSERTAR INFORMACIÓN:
INSERT INTO peliculas (title, year, duration)
VALUES
('Titanic', 1997, 195),
('Inception', 2010, 148),
('The Dark Knight', 2008, 152),
('The Shawshank Redemption', 1994, 142),
('Forrest Gump', 1994, 142),
('The Matrix', 1999, 136),
('Interstellar', 2014, 169),

61
('Pulp Fiction', 1994, 154),
('The Godfather', 1972, 175),
('The Lord of the Rings: The Fellowship of the Ring', 2001, 178),
('The Lord of the Rings: The Return of the King', 2003, 201);

INSERT INTO directores (name, since, nationality)


VALUES
('James Cameron', '1981-01-01', 'Canadian'),
('Christopher Nolan', '2000-01-01', 'British'),
('Frank Darabont', '1990-01-01', 'American'),
('Robert Zemeckis', '1980-01-01', 'American'),
('Lana Wachowski', '1990-01-01', 'American'),
('Quentin Tarantino', '1990-01-01', 'American'),
('Francis Ford Coppola', '1960-01-01', 'American'),
('Peter Jackson', '1980-01-01', 'New Zealand');

INSERT INTO generos (nombre) VALUES


('Acción'),
('Comedia'),
('Drama'),
('Ciencia ficción'),
('Fantasía'),
('Thriller'),
('Romance'),
('Animación'),
('Aventura'),
('Documental');

INSERT INTO pelicula_genero (pelicula_id, genero_id)


VALUES
(1, 5), -- Titanic - Romance
(2, 6), -- Inception - Thriller
(3, 1), -- The Dark Knight - Acción
(4, 3), -- The Shawshank Redemption - Drama
(5, 3), -- Forrest Gump - Drama
(6, 4), -- The Matrix - Ciencia ficción
(7, 6), -- Interstellar - Thriller
(8, 6), -- Pulp Fiction - Thriller
(9, 3), -- The Godfather - Drama
(10, 5), -- The Lord of the Rings: The Fellowship of the Ring - Romance
(11, 5); -- The Lord of the Rings: The Return of the King - Romance

ACTUALIZACIONES:
Esta actualización está hecha así a propósito para que no dependa de los ID de las tablas,
por eso se hacen subcosnultas a los directores.

UPDATE peliculas
SET director_id =
CASE
WHEN title = 'Titanic' THEN (SELECT id FROM directores WHERE name = 'James
Cameron')
WHEN title = 'Inception' THEN (SELECT id FROM directores WHERE name = 'Christopher
Nolan')
WHEN title = 'The Dark Knight' THEN (SELECT id FROM directores WHERE name =
'Christopher Nolan')
WHEN title = 'The Shawshank Redemption' THEN (SELECT id FROM directores WHERE name
= 'Frank Darabont')
WHEN title = 'Forrest Gump' THEN (SELECT id FROM directores WHERE name = 'Robert
Zemeckis')
WHEN title = 'The Matrix' THEN (SELECT id FROM directores WHERE name = 'Lana
Wachowski')

62
WHEN title = 'Interstellar' THEN (SELECT id FROM directores WHERE name =
'Christopher Nolan')
WHEN title = 'Pulp Fiction' THEN (SELECT id FROM directores WHERE name = 'Quentin
Tarantino')
WHEN title = 'The Godfather' THEN (SELECT id FROM directores WHERE name = 'Francis
Ford Coppola')
WHEN title = 'The Lord of the Rings: The Fellowship of the Ring' THEN (SELECT id
FROM directores WHERE name = 'Peter Jackson')
WHEN title = 'The Lord of the Rings: The Return of the King' THEN (SELECT id FROM
directores WHERE name = 'Peter Jackson')
END;

CONSULTAS:
La próxima consulta utilizará las claves primarias y extranjeras definidas en las tablas para
unir los registros correctamente y devolverá el título de la película, el nombre del director y
el género de cada película.

SELECT
p.title AS "Título de la Película",
d.name AS "Director",
g.nombre AS "Género"
FROM
peliculas p
JOIN
directores d ON p.director_id = d.id
JOIN
pelicula_genero pg ON p.id = pg.pelicula_id
JOIN
generos g ON pg.genero_id = g.id;

La próxima consulta utiliza subconsultas correlacionadas para obtener el nombre del


director y el género de cada película sin necesidad de JOINs explícitos. Esto puede ayudar a
optimizar el rendimiento de la consulta, especialmente en casos donde las tablas
involucradas son grandes.

Esto no es nada eficiente, porque hace una consulta por cada campo, interesante cuando
sólo tienes que traer un campo de una tabla adicional, pero tampoco lo haría así, pero PARA
RECORDARME QUE ESTA COSA EXISTE.

SELECT
p.title AS "Título de la Película",
(
SELECT name
FROM directores d
WHERE p.director_id = d.id
) AS "Director",
(
SELECT nombre
FROM generos g
JOIN pelicula_genero pg ON g.id = pg.genero_id
WHERE p.id = pg.pelicula_id
LIMIT 1
) AS "Género"
FROM
peliculas p;

Consulta con concatenación de géneros, separados por coma.

63
SELECT p.title AS pelicula,
d.name AS director,
COALESCE(STRING_AGG(g.nombre, ', '), 'Sin género') AS generos
FROM peliculas p
LEFT JOIN directores d ON p.director_id = d.id
LEFT JOIN pelicula_genero pg ON p.id = pg.pelicula_id
LEFT JOIN generos g ON pg.genero_id = g.id
GROUP BY p.id, p.title, d.name;

ELIMINACIÓN:
Esto debería automáticamente eliminar el renglón de relación pelicula_genero entre la
película eliminada y el género asignado, según indicamos eliminar el registro de
película_género.

DELETE FROM peliculas WHERE id = 3;

Modelo Entidad Relación


+----------------+ +----------------+
| peliculas | | directores |
+----------------+ +----------------+
| id PK |<<------|-| id PK |
| title | | name |
| year | | since |
| duration | | nationality |
| director_id FK | +----------------+
+----------------+
|
-
|
|
|
|
| +-----------------+
| | pelicula_genero |
| +-----------------+
| | pelicula_id |
+------------------>>| genero_id |
+-----------------+
^
^
|
-
|
+--------------+
| generos |
+--------------+
| id |
| nombre |
+--------------+

TypeORM

64
¿Que es TypeORM?
Para comprender qué es TypeORM, comprendamos primero qué es una ORM.
Un ORM (Object Relational Mapping) es un tipo de herramienta que permite
la interacción entre una base de datos relacional y código estructurado en objetos de
JavaScript.
¿Recuerdas el concepto de ODM cuando hablábamos de Mongoose(opens in a new
tab)? Su funcionamiento sería similar a cómo trabaja la ODM con bases de datos no
relacionales. Veamos un ejemplo sencillo.

TypeORM es una biblioteca para trabajar en diferentes entornos de ejecución. En nuestro


caso, el de node con enfoque en TypeScript y JavaScript nos proporciona una interfaz que
permite transformar objetos a tablas de una base de datos relacional.

¿Por qué utilizar TypeORM y qué ventajas ofrece?


A pesar de que existen múltiples opciones para incorporar como ORM a un proyecto que
trabaja con bases de datos relacionales (como Hibernate, Entity, o Sequelize), TypeORM ha
adquirido mucha más popularidad ya que proporciona una mayor simplificación del proceso
de desarrollo y el mapeo de información.

Es compatible con varios gestores de bases de datos relacionales, como el ya


conocido PostgreSQL, y otros como MySQL, SQLite y SQL Server, brindando flexibilidad
según la necesidad del proyecto. Además, se integra con facilidad a frameworks
como express.

Instalación y Setup en un proyecto


Para trabajar con TypeORM es necesario instalar la librería junto con los complementos
correspondientes a la base de datos con la cual trabaja nuestra aplicación ( PostgreSQL).
El proceso de instalación se logra con este comando... (esto es el punto 6 de la instalación
real según el vídeo, todavía no hay que escribirlo).

Instalar TypeORM
En el terminal de VSC, estando en el directorio /back.

Ir a la página https://typeorm.io que explican bien el paso a paso, pero resumiendo:


1. Install the npm package:
$ npm install typeorm --save
2. You need to install reflect-metadata shim:
$ npm install reflect-metadata –save

65
and import it somewhere in the global place of your app (for example in app.ts)
import "reflect-metadata".
En mi caso back/src/index.ts
import server from './server';
import { PORT } from './config/envs';
import 'reflect-metadata';
ESTAS DOS LÍNEAS SE AÑADEN DESPUÉS DE CUMPLIR EL PUNTO 7 create data-source.ts
import { AppDataSource } from './config/appDataSource';
AppDataSource.initialize().then((res) => {
console.log('Connection to database server successfully achieved');
server.listen(PORT, () => {
console.log(`Server listening on port ${PORT}`);
});
});
3. You may need to install node typings (instalada previamente):
$ npm install @types/node --save-dev
4. Install a database driver:
$ npm install pg --save
5. In tsconfig.ts
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
6. To create a (only) new project using CLI, run the following command:
$ npx typeorm init --name MyProject --database postgres
7. Create src/config/data-source.ts
export const AppDataSource = new DataSource({
type: "postgres",
host: "localhost",
port: 5432,
username: "test",
password: "test",
database: "test",
synchronize: true,
logging: true,
entities: [Post, Category],
subscribers: [],
migrations: [],
})

Definición de modelos
Creación de entidades y modelos
Las entidades representan objetos reales con propiedades y relaciones. Puedes tener, por
ejemplo, una entidad “Usuario” con propiedades como nombre, edad e email.

Un modelo, por su parte, corresponde a una clase de TypeScript que define cómo se verán
y se comportarán las entidades en tu aplicación. Estos modelos se usan para crear, leer,
actualizar y eliminar datos. Veamos esto en el siguiente video.

Hasta ahora, hemos logrado crear nuestra tabla Users, por lo que ya podemos comenzar a
realizar operaciones con ella.

1. Configuración inicial para entidades. Para las entidades de PostgreSQL y que las
propiedades de una clase puedan ser declaradas como TypeORM las necesita (ej. Sin

66
constructor, etc.), en el fichero tsconfig.json hay que descomentar y poner en false el
atributo json: “strictPropertyInitialization”: false, de lo contrario tendremos un error por
cada atributo de la clase que diga, por ejemplo: Error: Property ‘name’ has no initializer
and is not definitely assigned in the constructor.

/back/tsconfig.json
"compilerOptions": {

"strictPropertyInitialization": false /* Check for class properties that are
declared but not set in the constructor. */,

}

2. Definir las entidades. En una carpeta /back/src/entities para que TypeORM cree las
entidades en la base de datos.
/back/src/entities/User.ts
export class User {
id: number;
credential_id: number;
name: string;
surname: string;
email: string;
phone_number: string;
active: boolean;
}

3. Decoradores. Esta clase creada no tiene un constructor haciendo su trabajo, por tanto
hay que hacer pasar las clases por lo que en TypeScript se denomina un decorador. Debo
recordar que para el tsconfig.json habilité dos propiedades que pedía la instalación de
TypeORM, estas son: "experimentalDecorators": true y "emitDecoratorMetadata": true.

Un decorador es una función especial que recibe un objeto, una clase, una función y
convertirla en un patrón de diseño estructural que permite añadir dinámicamente
nuevos comportamientos a objetos colocándolos dentro de objetos especiales que los
envuelven (_wrappers_) y pueden hacer más cosas.

Para este caso usaremos el decorador @Entity que viene con TypeORM, que en realidad
es una función.
De esta manera convertimos una clase común y corriente en una Entidad.

También, y para que cada una de las propiedades sean columnas de la tabla de base de
datos, usaremos el decorador @Column(). A las columnas le podemos indicar el tipo en
base de datos, por ejemplo que nombre, apellido, email y teléfono sean varchar(50), que
activo sea boolean y por defecto (al ser creado el registro).

Para indicar que id es una clave primaria (Primary Key), utilizaremos el decorador
@PrimaryColumn, pero mejor aún, si usamos el decorador @PrimaryGeneratedColumn
la clave primaria será autogenerada.

Alerta Para evitar problemas con el nombre User que es una tabla de PostgreSQL
que contiene un los usuarios y roles con permisos de la administración de base de datos,

67
el decorador @Entity permite definir el nombre que tendrá la entidad/tabla en la base
de datos, y le pondremos users en plural.

/back/src/entities/User.ts
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
@Entity({
name: 'users',
}) // users en plural
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column()
credential_id: number;
@Column({ length: 50 }) // VARCHAR(50)
name: string;
@Column({ length: 50 }) // VARCHAR(50)
surname: string;
@Column({ length: 50 }) // VARCHAR(50)
email: string;
@Column({ length: 20 }) // VARCHAR(20)
phone_number: string;
@Column({ type: 'boolean', default: true })
active: boolean;
}

4. Crear la tabla. Para que sean creadas las entidades hay que indicarle en
/back/src/config/data-source.js las entidades que contendrá la base de datos, entonces
el data-source se encarga de su gestión directamente, y la primera vez en su creación. Al
iniciar el servidro con $ npm start podremos ver cómo ejecuta el SQL que crea la tabla
Users, con el nombre en plural, como indicamos en la clase/entidad. (CREATE TABLE
“users”…)

import { User } from '../entities/User';


export const AppDataSource = new DataSource({
type: 'postgres',
host: DBURI,
port: DBPORT,
username: DBUSER,
password: DBPSW,
database: DBNAME,
synchronize: true,
logging: true,
entities: [User],
subscribers: [],
migrations: [],
});

Operaciones básicas

68
Inserción de datos
Este paso tiene una característica muy importante. Debemos tener en cuenta la integridad
de los datos. Es decir, que la información que queramos añadir a la tabla sea
consistente con los respectivos campos que en ella están definidos.
Conozcamos de qué manera TypeORM interactúa con la base de datos permitiéndonos
agregar registros a la tabla Users en el siguiente video.

OJO: los modelos (AppDataSource.getRepository(Tabla)) deben ir siempre en data-source.ts,


jamás en las clases.
Eliminando el uso de interfaces (igual al enviar DTO, ya no haría falta), sólo que los datos a
enviar al servicio deben contener los datos de password y usuario para la creación.

Relaciones entre entidades


En la bases de datos relacionales trabajamos con diversas tablas las cuales pueden estar
relacionadas entre sí mediante diferentes tipos de cardinalidad. Dentro de TypeORM la
definición del tipo de relación es muy sencilla gracias al uso de decoradores.

1. Uno a uno mediante @OneToOne.


2. Muchos a uno mediante @ManyToOne.
3. Uno a muchos mediante @OneToMany.
4. Muchos a muchos mediante @ManyToMany.

Ilustremos este concepto con un ejemplo de la vida cotidiana. Imagina que tienes
una entidad que representa comidas, cuya tabla contiene distintas recetas de estas y
hay otra entidad de personas donde cada una de ellas tiene una propiedad (arreglo) de sus
comidas favoritas.

69
Si estas tablas estuvieran relacionadas, en lugar de hacer dos peticiones GET (una por tabla)
para obtener toda la información, podremos solo hacer una peticióna personas y
automáticamente obtener también el array con sus comidas favoritas.

USUARIO
import { Entity, Column, PrimaryGeneratedColumn, OneToOne, JoinColumn, CreateDateColumn, UpdateDateColumn,
OneToMany } from 'typeorm';
import { Credential } from './Credential';
import { Appointment } from './Appointment';

export enum userRole {


ADMIN,
EMPLOYEE,
USER,
GUEST,
}

@Entity({ name: 'users' }) // users en plural


export class User {
@PrimaryGeneratedColumn()
id: number;

@Column({ length: 50 }) // VARCHAR(50)


name: string;

@Column({ length: 50 }) // VARCHAR(50)


surname: string;

@Column({ length: 50 }) // VARCHAR(50)


email: string;

@Column({ length: 20 }) // VARCHAR(20)


phone_number: string;

@Column('boolean', { default: true })


active: boolean;

@Column({
type: 'enum',
enum: userRole,
default: userRole.USER,
})
user_role: userRole;

// 6 FOR MICROSECONDS
@CreateDateColumn({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP(6)' })
created_at?: Date;

@UpdateDateColumn({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP(6)', onUpdate: 'CURRENT_TIMESTAMP(6)'


})
updated_at?: Date;

@OneToOne((type) => Credential, (credential) => credential.user, { cascade: true, onDelete: 'CASCADE' })
@JoinColumn()
credential: Credential;

@OneToMany(() => Appointment, (appointment) => appointment.user, { cascade: true, onDelete: 'CASCADE' })
appointments: Appointment[];
}

CREDENCIALES
import { Entity, Column, PrimaryGeneratedColumn, Unique, CreateDateColumn, UpdateDateColumn, OneToOne } from
'typeorm';
import { User } from './User';
// var passwordHash = require('password-hash');

@Entity({
name: 'credentials',
}) // users en plural
@Unique(['username'])
export class Credential {
@PrimaryGeneratedColumn()
id: number;

@Column({ length: 20 })

70
username: string;

@Column({ length: 20 })
passwordHash: string;

// 6 FOR MICROSECONDS
@CreateDateColumn({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP(6)' })
created_at?: Date;

@UpdateDateColumn({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP(6)', onUpdate: 'CURRENT_TIMESTAMP(6)'


})
updated_at?: Date;

@OneToOne((type) => User, (user) => user.credential, { onDelete: 'CASCADE' })


user: User;
}

RESERVACIONES
import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn, ManyToOne, JoinColumn } from
'typeorm';
import { User } from './User';

@Entity({ name: 'appointments' }) // users en plural


export class Appointment {
@PrimaryGeneratedColumn()
id: number;

@Column({ type: 'timestamp' }) // VARCHAR(50)


appointmentDate: Date;

@Column('boolean', { default: true })


status: boolean;

// 6 FOR MICROSECONDS
@CreateDateColumn({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP(6)' })
created_at?: Date;

@UpdateDateColumn({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP(6)', onUpdate: 'CURRENT_TIMESTAMP(6)'


})
updated_at?: Date;

// !OJO: al plural de user.appointmentS en una relación muchos a uno (muchas citas/reservas a un usuario)
@ManyToOne(() => User, (user) => user.appointments, { onDelete: 'CASCADE' })
user: User;
}

Cierre
Aprendimos a definir modelos y entidades, que se traducirán en las distintas tablas, a
definir los atributos y sus tipos para cada modelo, a agregar y consultar registros de las
tablas y a conocer las diferentes maneras de relacionarlas a partir de su cardinalidad.

TypeORM 2

71
Introducción
Segunda clase de TypeORM. En esta clase aprenderás técnicas para optimizar tu código
con modularización de tus consultas. Veámoslo en el siguiente video.

Revisemos cuáles son los objetivos de esta clase


1. Conocer qué son y cómo se manejan las transacciones.
2. Entender cómo utilizar un Repositorio en TypeORM.
3. Aprender qué son y cómo funcionan los listeners y subscribers.

Transacciones
¿Qué son?
Las transacciones son secuencias de operaciones que se ejecutan como una unidad
lógica y atómica. Esto quiere decir que todas las operaciones deben ser ejecutadas con éxito
para que el resto se lleven a cabo.
Pensemos en un ejemplo teórico muy sencillo para entender este concepto. Supongamos

que debemos realizar una transferencia bancaria de la cuenta A a la B. Los pasos serían los
siguientes.

Intuitivo, ¿no? Pero, ¿qué ocurriría si la cuenta A no tiene saldo suficiente o si el número de
la cuenta B no existe? En cualquiera de los casos la transacción tendría que detenerse. Pues
bien, las transacciones en las bases de datos funcionan de esta forma.

🧑‍💻 Users
Vamos a retomar el proyecto que trabajamos la clase anterior para ejemplificar una
transacción. Agregaremos una nueva propiedad llamada inventory al modelo User.

Este número de inventario será un dato secuencial que dependerá del último dato
registrado. Para simplificar el ejemplo, supongamos que únicamente va a incrementar en 2
unidades el valor de inventario.
Podríamos pensar en una solución muy simple la cual sería que, al momento de crear un
usuario, se haga la búsqueda del último registro almacenado, tomar su valor de inventario,
sumarle 2 unidades y asignar el valor resultante al usuario que tratamos de crear.
A pesar de que esta sería una solución bastante buena, imaginemos que no somos el
único utilizando este servidor, sino que hay miles de usuarios y, casualmente, llegan dos
solicitudes para creación de usuarios al mismo tiempo.

72
Ambas van a pedir el valor del último registro a la vez y por consiguiente, asignan
el mismo valor al usuario creado y esto traería problemas.
En lugar de eso implementaremos esta búsqueda como una transacción. La finalidadde esta
transacción será asegurarse que cada solicitud sea tratada como una unidad. Es decir, hasta
que una de las dos solicitudes no sea completada la siguiente no será procesada.
En este ejemplo utilizaremos el método transaction() del
objeto AppDataSource.manager que ya conocemos de la clase anterior. Este método

es asíncrono, por tanto debe llevar antepuesta la palabra await.

Ahora bien, para continuar debemos definir el valor de la propiedad inventory. Para ello,
necesitamos acceder a la información de la DB utilizando
la transactionalEntityManager para buscar el último valor de inventario y almacenar su
valor.
Esta posee un método llamado createQueryBuilder() el cual, a su vez, cuenta con diferentes
métodos para formar querys de SQL a partir de funciones y sus argumentos. Profundicemos

sobre esto en el siguiente video.


Inicio y confirmación de una transacción
Al realizar la solicitud POST podremos ver en nuestra terminal que efectivamente se inicia
una transacción la cual, al ser realizada de forma exitosa, es confirmada por medio
de COMMIT, indicando que se ha finalizado con éxito el registro y continúa con el resto de
las transacciones.

Si agregamos un usuario, veremos que... el valor de inventario es asociado con


éxitotanto para el primer como segundo registro. Pero, ¿qué sucede si la transacción no
puede realizarse con éxito? Esto nos conllevaría a cometer errores en nuestras
implementaciones.

Rollback y manejo de errores


Una transacción puede no completarse debido a situaciones como:
• Violación a las restricciones de la base de datos (clave primaria duplicada, registro
repetido, campos obligatorios vacios, etc).
• El servidor deja de escuchar en un puerto específico por errores de red.

73
• Excepciones en la lógica del código.
Para esto necesitamos mecanismos llamados rollback que permitan manejar errores y
revertir los cambios que se realicen a un estado previo si la transacción no se completa con
éxito.

Para ilustrar este concepto de rollback, vamos a modificar la query para forzar un error,
usando el nombre de una columna inexistente.

La transacción es inicializada con éxito, pero no se resuelve de igual manera: los cambios no
son confirmados y en lugar de ejecutarse el COMMIT, se ejecuta un ROLLBACK,
que revertirá todos los cambios previamente definidos en el inicio de la transacción para
que el resto de estas que están en cola, puedan tomar la información correcta de la base de
datos.

Al presentarse un error nos daremos cuenta que el servidor se “cae” y es


necesario reiniciarlo para poder continuar.

En la práctica esto no es lo ideal, ya que el servidor debe seguir realizando sus funciones
independientemente de una solicitud errónea. Debe reportar también al cliente el error
obtenido de la petición. Esto podemos solucionarlo mediante el manejo de errores.

Cuando trabajamos con controladores asincrónicos, debemos envolver nuestro código en


los bloques try/catch para gestionar la resolución de la promesa creada por la solicitud. Esto
nos permitirá devolver una respuesta al cliente ya sea en caso de éxito o rechazo.
En nuestro ejemplo, todo el código que hicimos hasta este momento corresponde al código
del bloque try, ya que solo considera el caso de éxito: la transacción se complete.
Añadiremos al bloque catch una respuesta que devuelva al cliente el error recibido y
permita que el servidor continúe su función...
A continuación vamos a agregar manejo de errores al resto de los controladores y
aprovecharemos para hacer un cambio que ayuda a la legibilidad del código a partir de algo
llamado Repositorio (y no, no es el de GitHub).

Para lograr hacer lo necesario para tirar de QueryRunner, hay que hacer algunas cosas
primero:

74
import { UserModel, CredentialModel, AppointmentModel, AppDataSource } from '../config/data-soruce';
………
export const deleteUserService = async (id: number): Promise<void> => {
const queryRunner = AppDataSource.createQueryRunner();
await queryRunner.connect();
await queryRunner.startTransaction();

Por otro lado, y para el manejo de errores, si yo me consigo por ejemplo, que un usuario
que no existe tenemos que el manejo de errores para operaciones asíncronas es muy
diferente, más aún esta operación asíncrona y dentro de un for await, en el caso de la
lecture TypeORM II (Rollback y manejo de errores), que no hay try/catch que lo pueda
tomar, por tanto hay que transformar nuestro array de vehículos en un array de promesas, y
en este caso de delete tendría que ser una promesa. En el caso de delete, ya lo que
devuelve es una promesa.

Completo el caso del servicio delete:


// Eliminar un usuario por su identificador, debe eliminar
// las credenciales y las citas.
export const deleteUserService = async (id: number): Promise<void> => {
const queryRunner = AppDataSource.createQueryRunner();
await queryRunner.connect();
await queryRunner.startTransaction();

try {
// DEBO USAR relations para traer las credenciales y citas, y eliminar en cascada
// QueryRunner espera una entidad, se puede especificar directamente la entidad
// User o extraerlo del modelo usando UserModel.target
const user: User | null = await queryRunner.manager.findOne(User, { where: { id }, relations: { credential:
true, appointments: true } });

if (!user) {
throw new Error(`User Id=${id} doesn't exist.`);
}

// Delete dependencies using cascade relationships


await queryRunner.manager.remove(user.credential);
await queryRunner.manager.remove(user.appointments);
// Delete the user itself
await queryRunner.manager.remove(user);

await queryRunner.commitTransaction();
} catch (err: any) {
await queryRunner.rollbackTransaction();
throw err;
} finally {
await queryRunner.release();
}
// ELIMINACIÓN CON transactionalEntityManager
// await AppDataSource.manager.transaction(async (transactionalEntityManager) => {
// const user: User | null = await getUserByIdService(Number(id));
// if (user) {
// await CredentialModel.delete({ id: user.credential.id });
// await AppointmentModel.delete({ user: { id } });
// await UserModel.delete({ id });
// } else {
// throw new Error(`User Id=${id} doesn't exist.`);
// }
// });
};

Pero siguiendo con el ejemplo de la creación de vehículos y asignación a los usuarios, Jorge
Vega lo ha resuelto de la siguiente manera, convirtiendo el arreglo de objetos vehicle en un
array de promesas, al hacer que en lugar de tener objetos en el array tenemos otro a partir
del original pero que sería lo que tendría que ser la promesa, en este caso sería crear el
vehículo, guardar el vehículo, buscar el usuario con el userId que tiene cada objeto del
arreglo de vehículos, si no existe lanzar el error y lo que haría sería rechazar esa promesa,

75
continua y asigna el usuario encontrado al vehículo (newVehicle.user = user; //el
encontrado) y volver a guardar el vehículo.

const preloadVehicles = [{brand: "“Ford”", model: "“Fiesta”", year: 2020, color: "“Red”", userId: 1 }];
export const preloadvehiclesData = async () => {
const queryRunner = AppDataSource.createQueryRunner();
await queryRunner.connect();
// el async antes de la función callback es lo que hace que map
// cree un array de promesas a partir de los objetos que componen al array de vehículos

// CREAMOS LAS PROMESAS


const promises = preloadVehicles.map(async (vehicle) => {
const newVehicle = await VehicleModel.create(vehicle);
await queryRunner.manager.save(newVehicle);
const user = await UserModel.findOneBy({id: vehicle.userId });
if (!user) throw Error("Usuario inexistente");
newVehicle.user = user; // se asocia el usuario al usuario del vehículo
queryRunner.manager.save(newVehicle);
});
// COMENZAMOS LA TRANSACCIÓN
await queryRunner.startTransaction();
// EJECUTAMOS EL PROMISE.ALL
await Promise.all(promises);
console.log("“Precarga exitosa”");
// AHORA NOSOTROS PODEMOS HACER ESTO
await queryRunner.commitTransaction();
}

ENTONCES, CONVERTIMOS EL OBJETO EN UNA PROMESA QUE HACE LA OPERACIÓN,


asignándolo a una constante, lo que se busca es el tratamiento de los errores para rechazar
la promesa sin reventar el servidor, y USAR Promise.all(promises)

El método Promise. all(iterable) devuelve una promesa que termina correctamente cuando
todas las promesas en el argumento iterable han sido concluidas con éxito, o bien rechaza la
petición con el motivo pasado por la primera promesa que es rechazada.

PERO ESTE ES EL CAMINO FELIZ, TENEMOS QUE MANEJAR LOS ERRORES APROVECHANDO
QUE TENEMOS EL CONTROL DE LAS OPERACIONES COMMIT, ROLLBACK, RELEASE.

const preloadVehicles = [{ brand: '“Ford”', model: '“Fiesta”', year: 2020, color: '“Red”', userId: 1 }];
export const preloadvehiclesData = async () => {
const queryRunner = AppDataSource.createQueryRunner();
await queryRunner.connect();
// el async antes de la función callback es lo que hace que map
// cree un array de promesas a partir de los objetos que componen al array de vehículos

// CREAMOS LAS PROMESAS, QUE NO ESTALLA PORQUE SON PROMESAS PERO GUARDA SI HUBO UN ERROR EN ALGUNA
const promises = preloadVehicles.map(async (vehicle) => {
const newVehicle = await VehicleModel.create(vehicle);
await queryRunner.manager.save(newVehicle);
const user = await UserModel.findOneBy({ id: vehicle.userId });
if (!user) throw Error('Usuario(s) inexistente(s');
newVehicle.user = user; // se asocia el usuario al usuario del vehículo
queryRunner.manager.save(newVehicle);
});

try {
// COMENZAMOS LA TRANSACCIÓN
await queryRunner.startTransaction();
// EJECUTAMOS EL PROMISE.ALL
await Promise.all(promises);
console.log('Precarga exitosa');
// APLICAR LAS OPERACIONES DE LA TRANSACCIÓN
await queryRunner.commitTransaction();
} catch (err: any) {
// RETROCEDEMOS LA TRANSACCIÓN
console.log(`Error al intentar crear vehículos: ${err.message}`);
await queryRunner.rollbackTransaction();
} finally {
// LIBERAMOS LA TRANSACCIÓN
console.log('Finalizada precarga de vehículos');

76
await queryRunner.release();
}
};

CONTROLADOR
export const createUser = async (req: Request, res: Response) => {
try {
const { username, passwordHash, name, surname, email, phone_number, active } = req.body;
const user_role: userRole = userRole.USER;
const newUser: User | void = await createUserService({
username,
passwordHash,
name,
surname,
email,
phone_number,
active,
user_role,
});
console.log(newUser);

res.status(201).json(newUser);
} catch (err: any) {
console.error('Error creating a user: ' + err.message);
res.status(400).json({ error: `Error creating a user: ${err.message}` });
}
};

SERVICIO
// CREAR USUARIO Y CREDENCIAL
export const createUserService = async (userData: ICreateUserDTO): Promise<User | void> => {
// TODO: Averiguar manejo errores en TypeScript
// TODO: con función de orden superior
// TODO: para centralizar el catchAsync como
// TODO: si fuese un wrapper (LIKE MODULE 2)

const { username, passwordHash, name, surname, email, phone_number, active, user_role } = userData;

const queryRunner = AppDataSource.createQueryRunner();


await queryRunner.connect();
try {
await queryRunner.startTransaction();

let userCredential: Credential | null = await findCredentialByUsername(username); //search credential by


username
if (userCredential) {
console.log(`Username ${username} is already taken`);
throw new Error(`Username ${username} is already taken`);
}
// create credential and user
const credential = await queryRunner.manager.save(CredentialModel.create({ username, passwordHash }));
if (!credential) {
console.log(`Unexpected error saving user credentials for ${username}: ${name}, ${surname}`);
throw new Error(`Unexpected error saving user credentials for ${username}: ${name}, ${surname}`);
}

const user: User = UserModel.create({


name,
surname,
email,
phone_number,
active: active !== undefined ? active : true,
user_role: user_role !== undefined ? user_role : userRole.USER,
appointments: [],
credential,
});
const newUser = await queryRunner.manager.save(user);
console.log(newUser);

await queryRunner.commitTransaction();
return newUser;
} catch (err: any) {
console.log(`Error: ${err.message}`);
await queryRunner.rollbackTransaction();
throw Error(err.message);
} finally {
console.log('queryRunnerLiberado');

77
await queryRunner.release();
}
};

ELIMINACIÓN Y ACTUALIZACIÓN EN CASCADA


Las entidades:
import { Entity, Column, PrimaryGeneratedColumn, OneToOne, JoinColumn,
CreateDateColumn, UpdateDateColumn, OneToMany } from 'typeorm';
import { Credential } from './Credential';
import { Appointment } from './Appointment';

export enum userRole {


ADMIN,
EMPLOYEE,
USER,
GUEST,
}

@Entity({ name: 'users' }) // users en plural


export class User {
@PrimaryGeneratedColumn()
id: number;

@Column({ length: 50 }) // VARCHAR(50)


name: string;

@Column({ length: 50 }) // VARCHAR(50)


surname: string;

@Column({ length: 50 }) // VARCHAR(50)


email: string;

@Column({ length: 20 }) // VARCHAR(20)


phone_number: string;

@Column('boolean', { default: true })


active: boolean;

@Column({
type: 'enum',
enum: userRole,
default: userRole.USER,
})
user_role: userRole;

// 6 FOR MICROSECONDS
@CreateDateColumn({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP(6)' })
created_at?: Date;

@UpdateDateColumn({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP(6)', onUpdate: 'CURRENT_TIMESTAMP(6)'


})
updated_at?: Date;

@OneToOne((type) => Credential, (credential) => credential.user, { cascade: true, onDelete: 'CASCADE' })
@JoinColumn()
credential: Credential;

@OneToMany(() => Appointment, (appointment) => appointment.user, { cascade: true, onDelete: 'CASCADE' })
appointments: Appointment[];
}

import { Entity, Column, PrimaryGeneratedColumn, Unique, CreateDateColumn, UpdateDateColumn, OneToOne } from


'typeorm';
import { User } from './User';

@Entity({
name: 'credentials',
}) // users en plural
@Unique(['username'])
export class Credential {
@PrimaryGeneratedColumn()
id: number;

@Column({ length: 20 })
username: string;

78
@Column({ length: 20 })
passwordHash: string;

// 6 FOR MICROSECONDS
@CreateDateColumn({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP(6)' })
created_at?: Date;

@UpdateDateColumn({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP(6)', onUpdate: 'CURRENT_TIMESTAMP(6)'


})
updated_at?: Date;

@OneToOne((type) => User, (user) => user.credential, { onDelete: 'CASCADE' })


user: User;
}

import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn, ManyToOne, JoinColumn } from


'typeorm';
import { User } from './User';

@Entity({ name: 'appointments' }) // users en plural


export class Appointment {
@PrimaryGeneratedColumn()
id: number;
@Column({ type: 'timestamp' }) // VARCHAR(50)
appointmentDate: Date;
@Column('boolean', { default: true })
status: boolean;
// 6 FOR MICROSECONDS
@CreateDateColumn({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP(6)' })
created_at?: Date;
@UpdateDateColumn({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP(6)', onUpdate: 'CURRENT_TIMESTAMP(6)'
})
updated_at?: Date;
// !OJO: al plural de user.appointmentS en una relación muchos a uno (muchas citas/reservas a un usuario)
@ManyToOne(() => User, (user) => user.appointments, { onDelete: 'CASCADE' })
user: User;
}

Aprovechar la cascada, consultando con las relaciones incluidas y sólo llamar la eliminación
con el objeto de cada uno, por ejemplo: eliminar el usuario, sería eliminar las credenciales
haciendo referencia a credential de la relación y a appointments (plural) también de la
relación establecida con los usuarios (entidad-relación).

// Eliminar un usuario por su identificador, debe eliminar


// las credenciales y las citas.
export const deleteUserService = async (id: number): Promise<void> => {
const queryRunner = AppDataSource.createQueryRunner();
await queryRunner.connect();
await queryRunner.startTransaction();

try {
// DEBO USAR relations para traer las credenciales y citas, y eliminar en cascada
// QueryRunner espera una entidad, se puede especificar directamente la entidad
// User o extraerlo del modelo usando UserRepository.target
const user: User | null = await queryRunner.manager.findOne(User, { where: { id }, relations: {
credential: true, appointments: true } });

if (!user) {
throw new Error(`User Id=${id} doesn't exist.`);
}

// Delete dependencies using cascade relationships


await queryRunner.manager.remove(user.credential);
await queryRunner.manager.remove(user.appointments);
// Delete the user itself
await queryRunner.manager.remove(user);

await queryRunner.commitTransaction();
} catch (err: any) {
await queryRunner.rollbackTransaction();
throw err;
} finally {
await queryRunner.release();
}

79
};

Manejo de repositorios
Repositorios vs EntityManager
Por ahora hemos trabajado el CRUD por medio del objeto manager de AppDataSourcea
partir de consultas a una colección que contiene todas las entidades de nuestra aplicación.
Si bien, trabajar utilizando esta herramienta es correcto, existe una opción que es mucho
más específica: los repositorios.

A grandes rasgos, un repositorio tiene la misma función que manager. Sin embargo, un
repositorio limita su acción a una entidad en concreto y no sobre la colección general que
las contiene. Veamos a qué nos referimos.
Empecemos por modificar los controladores que utilizan a manager para que hagan uso
de Repository. Lo primero que debemos hacer es generar un repositorio para cada
entidad mediante el método getRepository() de AppDataSource.

Este método recibe como argumento la entidad a la cual queremos acceder y la asocia
al repositorio que creamos como una constante.

Ahora, hagamos los cambios correspondientes en cada controlador y aprovechemos para


agregar el bloque try/catch para el manejo de errores.
De esta forma todos nuestros controladores trabajan con un repositorio particular y
podemos incluso personalizarlos para potenciar sus funciones. Veamos de qué manera
hacer esto.

Repositorios personalizados
El manejar un repositorio que sea exclusivo de una entidad nos permite agregar métodos
que únicamente sean aplicables a este repositorio y que desempeñen una tarea particular
dentro de él. Por ejemplo, supongamos que en diferentes rutas y controladores, vamos a
tener que realizar la búsqueda por nombre.
En lugar de repetir una y otra vez la lógica de búsqueda, podríamos agregar esta
funcionalidad a usersRepository y utilizarla desde allí.
Vamos a crear una nueva carpeta llamada repositories dentro de src. En esta carpeta
crearemos un archivo index.ts, que va a contener todos los repositorios que creamos
anteriormente, por tanto, debemos traer este fragmento de código desde el archivo
de controladores.
No olvides realizar las importaciones y exportaciones correspondientes.

80
Ahora... ¿Cómo logramos darnos cuenta de que un usuario realiza una petición a nuestro
servidor o que nuestra base de datos se ha modificado? Conozcamos dosherramientas
que nos servirán para hacer esta gestión de la información.

POR EJEMPLO, PODEMOS AÑADIR EXTENDIENDO Y CREANDO TODO DENTRO DE LOS


PARÉNTESIS Y CUANDO SE DICE this. SE ESTÁ HABLANDO DEL PROPIO REPOSITORIO.

De data-source.js se eliminan
// Para no tener que repetir tantas veces la llamada
// al objeto en base de datos, lo creamos acá.
// export const UserModel = AppDataSource.getRepository(User);
// export const CredentialModel = AppDataSource.getRepository(Credential);
// export const AppointmentModel = AppDataSource.getRepository(Appointment);

Se crea el directorio /back/src/repositories y dentro de él cada repositorio: UserRepository,


CredentialRepository y AppointmentRepository, y cada uno se parece mucho a:
import { AppDataSource } from '../config/data-soruce';
import { User } from '../entities/User';
const UserRepository = AppDataSource.getRepository(User);
export default UserRepository;

En los servicios y preLoad se sustituyen los modelos creados por las entidades, tanto en la
importación como en el código.
import UserRepository from '../repositories/UserRepository';

// !FIXME: VER O PREGUNTAR CÓMO SOLUCIONAR EL TEMA DE LA GESTIÓN DE ERRORES CON FUNCIONES DE ORDEN SUPERIOR QUE LAS
ENCAPSULA.
// !ENCONTRAR CREDENCIAL POR username, retorna el índice en el array de usuarios
const findCredentialByUsername = async (usernameToFind: string): Promise<Credential | null> => {
try {
const userCredential: Credential | null = await CredentialRepository .findOne({
where: { username: usernameToFind },
relations: { user: true },
});

CODIGO DE PERSONALIZACIÓN DE LOS REPOSITORIOS Y EJEMPLOS


UserRepository.ts
import { AppDataSource } from '../config/data-soruce';
import { User } from '../entities/User';

const UserRepository = AppDataSource.getRepository(User).extend({


// LA VENTAJA DE ESTA FUNCIÓN ES QUE: De una vez busca el usuario
// si NO existe explota con un error o retorna el usuario
// porque que retorne null no sirve, si una búsqueda por Id
// no encuentra nada debe decírmelo con un error, sin null,
// además de que sería un tratamiento de respuesta null
// en lugar del manejo correcto de erroers y que lleguen al front
// para que muestre el mensaje concreto.
findById: async function (id: number): Promise<User> {
const user = await this.findOneBy({ id });
if (!user) throw new Error(`User ${id} not found.`);
return user;
},

checkById: async function (id: number): Promise<boolean> {


const user = await this.findById(id);
// if (user) return true;
// else return false;
//Para que retorne el boolean del valor real de si existe
//si no tiene admiración devolvería el objeto
//un signo de exclamación devolvería el booleano pero lo contrario a si existe
//la doble negación devolvería el boolean del valor real de si existe.
return !!user;
},
});

export default UserRepository;

81
CredentialRepository.ts
import { AppDataSource } from '../config/data-soruce';
import { Credential } from '../entities/Credential';

const CredentialRepository = AppDataSource.getRepository(Credential).extend({


// LA VENTAJA DE ESTA FUNCIÓN ES QUE: De una vez busca el usuario
// si NO existe explota con un error o retorna el usuario
// porque que retorne null no sirve, si una búsqueda por Id
// no encuentra nada debe decírmelo con un error, sin null,
// además de que sería un tratamiento de respuesta null
// en lugar del manejo correcto de erroers y que lleguen al front
// para que muestre el mensaje concreto.
checkUsernameNotExist: async function (username: string): Promise<void> {
const credential = await this.findOneBy({ username });
if (credential) throw new Error(`${username} is already taken`);
},

fetchByUserName: async function (username: string): Promise<Credential> {


const credential = await this.findOne({
where: { username },
relations: { user: true },
});
if (!credential) throw new Error(`${username} not found.`);
return credential;
},
});

export default CredentialRepository;

AppointmetRepository.ts
import { AppDataSource } from '../config/data-soruce';
import { Appointment } from '../entities/Appointment';

const AppointmentRepository = AppDataSource.getRepository(Appointment);

export default AppointmentRepository;

En conclusión...
A lo largo de la lecture, hemos comprendido la importancia de
los repositorios como interfaces para interactuar con nuestras entidades directamente,
permitiendo así una separación clara entre la lógica personalizada de cada entidad y el
manejo de los controladores.
Además, hemos aprendido cómo utilizar transacciones para garantizar la consistenciade
nuestros datos al realizar operaciones complejas que requieren múltiples cambios, así como
a manejar los distintos errores que puedan ocurrir a lo largo del proceso.
Finalmente, hemos explorado los conceptos de listeners y subscribers, que nos permiten
capturar y responder a eventos específicos que ocurran al trabajar con nuestras entidades y
la conexión a la base de datos, añadiendo flexibilidad y extensibilidad a nuestras
aplicaciones.

82
React
¿De dónde viene react?
Hasta el momento, hemos aprendido a crear vistas web para los usuarios utilizando HTML,
JavaScript y CSS, lo cual fue el estándar durante muchos años.
Sin embargo, la industria del desarrollo web comenzaba a necesitar una mejor
herramienta para poder generar contenido de forma más rápida y eficiente y así mantener
el ritmo de la demanda y exigencias del mercado.
History Fact
En el año 2013, Meta desarrolló una librería llamada React, para crear páginas web de una
manera más ágil. Tanto Facebook, como Instagram, como WhatsApp, y muchas otras
aplicaciones, están desarrolladas en base a esta librería.

¿Qué es react?
React es una librería desarrollada específicamente para la creación de interfaces de
usuario utilizando pequeñas piezas independientes y reutilizables conocidas
como componentes, que en conjunto dan forma a una aplicación web en su totalidad.

En pocas palabras, un componente es un bloque visual que define la presentación de una


parte específica de una aplicación web.
Fíjate en Youtube por ejemplo. El recuadro violeta es un componente. Mira como ese
componente se repite una y otra vez a su lado. Lo que cambia es simplemente la
información que lleva internamente.

React + vite
Vite
Para trabajar con react es necesario instalar la librería, y, aunque es posible realizar esto de
forma manual con npm, la documentación nos alienta a utilizar alguna herramienta externa
que nos permita construir una base optimizada de un proyecto con una pre-configuración.

83
Documentación
Te sugerimos tener a manola documentación oficial de react.
LINK WEB
Podemos trabajar con diferentes frameworks, como Gatsby o NextJS para hacer los
cimientos de nuestro proyecto. Por el momento vamos a utilizar uno llamado Vite. En pocas
palabras, esta herramienta nos facilitará el acceso y configuración de react.
¿Por qué Vite?
Las páginas web se han vuelto cada vez más complejas debido a la cantidad de información
que contienen y a la exigencia de las interfaces de usuario, lo cual conlleva a tener una
mayor cantidad de módulos que deben ser unificados para poder trabajar en conjunto.

Este proceso de unificación llamado bundle-build consiste


en consolidar y empaquetarvarios archivos y recursos en un único paquete. Pero con el
aumento en la complejidad de los proyectos fue perdiendo eficiencia en los tiempos de
carga de las aplicaciones web.
Vite permite desarrollar más rápido, dado que carga módulos sobre la marcha.
Básicamente, carga lo que necesitas cuando lo necesitas, lo cual mejora el rendimiento y la
experiencia de desarrollo.

Veamos como crear un entorno de trabajo con esta herramienta en el siguiente video.
Ahora que logramos iniciar un proyecto de react utilizando Vite, es momento de
entender mejor cómo funcionan los componentes de react.

1.- crear carpeta para proyectos vite y entrar desde el terminal


2.- npm create vite@latest
Mostrará una serie de preguntas:
• Nombre del proyecto, que además crea la carpeta con el nombre del proyecto (vite-
react).
• Select a framework: React
• Select a variant: Javascript (no TypeScript y ni SWC).
3.- entramos a la carpeta cd vite-react

84
4.- ejectuamos $npm install (instala dependencias)
5.- ejecutamos $npm run dev (levanta el proyecto y lo podemos ver en le navegador – por
ejemplo – http://localhost:5173)

Puesta a punto de un proyecto REACT en una estructura de proyecto.


1.- Nos ponemos en la carpeta “front” y abrimos el terminal.
2.- $ npm create vite@latest
• Nombre del proyecto: poner el que querramos (ej. vitem3, dejaremos vite-project)
• Framework: React
• Variant: Javascript
3.- Nos movemos a la carpeta vite-project, y ahora sí:
• $npm install (para que se instalen todas las dependencias).
• $npm run dev

Estructura de un componente
Un componente de react, también llamado componente funcional, es una función de
JavaScript que devuelve elementos que representan la interfaz del usuario. Podemos
entenderlos como pequeños bloques de construcción.

Veamos ahora si de forma práctica de qué son los componentes y cómo funcionan.

🧐Renderizado
Pero entonces, ¿Cómo se muestran los componentes de una página web en la pantalla?
React se encarga de construir el DOM virtual utilizando los elementos que retorna cada
componente y genera una versión del DOM real agregando los elementos definidos en
nuestra app.
Por ejemplo, si das click derecho y presionas inspeccionar, encontrarás todo los recursos
básicos de HTML y estilos con los que la página web moldea su contenido.

85
Cada elemento HTML ha sido generado por componentes y se agregan al DOM a partir del
elemento div cuyo id es “root”.
En el siguiente video aprenderemos una técnica de renderizado para nuestros
componentes. Veremos cómo definir cuándo se mostrarán en pantalla y cuándo no, a partir
de una condición.
Hasta ahora, hemos aprendido a renderizar componentes, así como a condicionar la
forma en la que estos se visualizan en el navegador, aunque todo se ve bastante simple.
Veamos ahora cómo darle estilos a nuestra página web.

Estilos
Técnicas de estilo
Al trabajar con react existen diversas formas de agregar estilos a los componentes que
dan forma a nuestra página. Comenzaremos mencionando una de ellas: CSS Legacy.

Legacy
En main.jsx
import "@fontsource/roboto";
import "@fontsource/roboto-mono";
import "@fontsource/roboto/300.css";
import "@fontsource/roboto/400.css";
import "@fontsource/roboto/500.css";
import "@fontsource/roboto/700.css";
import "./styles/App.css";
import App from "./App.jsx";

En HomeTextPresent.jsx

86
const HomeTextPresent = () => {
return (
<>
<Container maxWidth="sm">
<hr />

<Typography variant="h4" className="homeTitle">


Módulo 3
</Typography>

<Typography variant="h6" className="homeSubtitle">


Restaurate
</Typography>

<Typography variant="body1" className="paragraph">


El restaurante abrió sus puertas a finales de 1990 con el nombre de La
Terraza del Módulo. Ubicado en la planta alta del
emblemático NH Collection Real Casino de Madrid, en pleno corazón de la
capital en la calle Alcalá, desde el año 2000 está
dirigido por el chef madrileño Paco Roncero. El cambio de nombre se
produjo a finales del año 2019 siguiendo la evolución natural
de la relación entre el chef y director gastronómico, Paco Roncero, y
la compañía NH porque esta es, desde hace más de 30 años, la
casa de Paco Roncero, su refugio y su lugar de reflexión y creación.
</Typography>
</Container>
...
);
};

export default HomeTextPresent;

En App.css
body {
margin: 0;
font-family: "Roboto", Arial, Helvetica, sans-serif;
color: #444;
font-weight: 100;
background-color: antiquewhite;
}

h1,h2,h3,h4,h5,h6 {
font-weight: 100 !important;
}

.homeTitle {
padding-top: 20px;
padding-bottom: 20px;
align-content: center;
text-align: center;
}

.homeSubtitle {
align-content: center;
text-align: center;
letter-spacing: 0.244em !important;

87
}

.homeFootTitle {
padding-top: 20px;
align-content: center;
text-align: center;
letter-spacing: 0.244em !important;
}

.homePresentImg {
padding-top: 80px;
width: auto;
}

.awardImg {
max-height: 40px;
height: 40px;
margin: 20px;
}

.paragraph {
padding-top: 20px;
text-align: justify;
}

hr {
width: 100px;
border: none;
height: 2px;
background-color: #444;
}

Módules
Veamos ahora qué es y cómo trabaja CSS Modules. Esta es otra técnica que nos
permitirá darle diseño y estilos a nuestra página.

En CSS Modules debe crearse con el mismo nombre y con la palabra module. Por ejemplo:
Para App.jsx se crea App.module.css

88
App.module.css
.container {
background-color: #ffc107 !important;
color: #444 !important;
}

.link {
color: #444 !important;
text-decoration: none !important;
}

.h6 {
font-family: "Roboto Mono" !important;
font-weight: 400 !important;
letter-spacing: 0.03rem !important;
flex-grow: 1 !important;
text-decoration: none !important;
color: #444;
}

En NavBar.jsx
import styles from "../styles/NavBar.module.css";
...
<AppBar position="static" className={styles.container}>
...
<Typography className={styles.h6}
...
{pages.map((page) => (
<Button className={styles.link} key={page.title} id={page.title}
onClick={handleCloseNavMenu} sx={{ my: 2 }}>
{page.title}
</Button>
))}

Styled Components
Por último, veremos una opción más para agregar estilos que, a pesar de ser un poco más
compleja, es un buen ejemplo de cómo utilizar librerías externas para la incorporación de
estilos. Hablemos de Styled Components.

Se crea un fichero .js y se agrega como etiqueta donde se vaya a utilizar (requiere instalar el
módulo $ npm install styled-components.

Ejemplo con CustomLogo.js


import { styled } from "styled-components";

export const CustomLogo = styled.div`


&:hover {
background-color: #444 !important;
color: #ffc107 !important;
border: none !important;
border-radius: 3px !important;
}
`;

Uso en NavBar.jsx
import { CustomLogo } from "../styles/CustomLogo";
<CustomLogo>

89
<FoodBankOutlinedIcon sx={{ display: { xs: "none", md: "flex" } }} />
</CustomLogo>

¡Excelente trabajo! tal vez en este momento no podamos ver la diferencia y el poder
real de los styled components, pero es una herramienta que nos ayudará a generar estilos
dinámicos muy interesantes en el futuro. Por el momento esta base es lo que necesitamos
para empezar a darle forma a una página.

Cierre
En esta clase introductoria de react exploramos los fundamentos de esta biblioteca de
JavaScript. Hemos comprendido la importancia de los componentes como bloques de
construcción modulares, que permiten organizar y reutilizar nuestro código.
También hemos explorado los componentes funcionales, una forma concisa de definir
nuestros elementos que harán parte de la interfaz del usuario.
Además, hemos experimentado con técnicas de estilos, desde enfoques tradicionales
como CSS legacy hasta soluciones más modernas y modulares como los CSS
Modules y Styled Components, todo esto contenido en el entorno de desarrollo Vite.

Homework
REQUISITOS
Haber creado correctamente un nuevo proyecto de react con Vite el lugar correspondiente
del repositorio del proyecto integrador.
Haber implementado el componente Home en la carpeta views.
Haber implementado el componente Navbar en la carpeta components.
Mostrar correctamente el componente Navbar en el componente Home, y este a su vez en
el componente App.
INSTRUCCIONES
Habilidades y competencias que desarrollarás.
• Creación de proyecto de react.
• Creación de componentes funcionales.
• Aplicación de estilos a componentes funcionales.

90
En esta lección encontrarás una introducción al proyecto/homework que deberás
desarrollar hoy. Mira el siguiente video con atención y luego sigue las consignas. ¡Buena
suerte!

ACTIVIDAD 01
Inicializar en la carpeta front del repositorio un nuevo proyecto de react con Vite siguiendo
los mismos pasos vistos en clase.

ACTIVIDAD 02
Instalar las dependencias y ejecutar el proyecto para probar su funcionamiento.

ACTIVIDAD 03
“Limpiar” el módulo App.jsx, eliminando el código innecesario que viene escrito dentro del
componente App y eliminar los imports innecesarios.

ACTIVIDAD 04
Dentro de la carpeta src del nuevo proyecto, crear las carpetas views y components.

ACTIVIDAD 05
Dentro de la carpeta views crea el componente Home. Puedes mostrar en este componente
un tag <h1> con el título “HOME”. Luego haremos más modificaciones.

ACTIVIDAD 06
Exportar el componente Home, importarlo en el componente App y renderizarlo allí. En este
punto, si no tienes errores, deberías poder ver tu componente Home al iniciar la aplicación.

ACTIVIDAD 07
Siguiendo la misma lógica, crearemos en la carpeta components un componente Navbar.
Exporta este nuevo componente para incluirlo en el componente Home. Ten en cuenta que
a su vez el componente Navbar puede requerir de otros componentes más pequeños.
Siéntete libre de crear todos los componentes que creas conveniente.

ACTIVIDAD 08
Finalmente, aplica estilos a los componentes que has creado. Puedes utilizar cualquiera de
los enfoques vistos en la clase para aplicar estos estilos.

Instalación:
• Vamos a la carpeta front del proyecto.
• $ npm create vite@latest
• Nombre del proyecto: vite-project.
• Framework: React
• Variante: Javascript (NO USAR Javascript + SWC).
• Vamos a la carpeta del proyecto $cd vite-project
• Instalar dependencias: $ npm install
• Debe aparecer la carpeta de node_modules
• Las carpetas public para objetos de acceso directo, src para el scaffolding del
proyecto.

91
• Meter en ignore el fichero README.md del proyecto porque ya tengo el del proyecto
completo en raíz.
• Limpiamos los ficheros legado de la instalación de Vite+React para comenzar a
desarrollar el front. App.jsx, App.css.
• Crear carpetas views (para los componentes como páginas principales) y
components (elementos más pequeños y reutilizables).

React Data Flow


Introducción
En esta clase vamos a trabajar mucho con el flujo y uso de información dentro de react.
Esto puede parecer una tarea sencilla, pero requiere entender algunos tecnisismos
relacionados a los componentes. Pero mejor veamos esto en el siguiente video.

Revisemos cuáles son los objetivos de esta clase


1. Entender el flujo de información en react.
2. Aprender qué es y cómo generar un estado dentro de un componente.
3. Comprender cómo se transmite la información entre componentes.
4. Conocer el concepto de levantamiento de estado.

REACT SOLO REACCIONA A CAMBIOS DE ESTADO, no de variables.

Flujo de datos
Vamos a retomar uno de los principios básicos de React que vimos en la clase pasada:
el Flujo unidireccional. Al mismo tiempo, conoceremos qué son y cómo funcionan las
propiedades comúnmente llamadas props de los componentes

Direccionalidad
El manejo de la información como un flujo unidireccional es uno de los principios en los que
se centra react. Se refiere a que los datos, dentro de una aplicación, siguen un único orden
al momento de ser transferidos entre componentes.

92
Para aclarar mejor esta idea, definamos dos conceptos auxiliares: diremos que un
componente es un componente padre si este contiene y engloba a otro componente. Es
decir, lo retorna. Mientras que un componente hijo sería aquel que está contenido en un
padre y es devuelto por él.

Antes de conocer de qué manera se lleva a cabo esta comunicación, veamos un concepto
que será muy importante de aquí en adelante: el estado de un componente.

Estados
Estado de un componente
Cuando hablamos del estado de un componente, nos referimos a un objeto que permite
almacenar y gestionar información, así como cambiar su apariencia y comportamiento a
partir de la interacción de un usuario.
Imagina que tu habitación es un componente y que el aire acondicionado es el estado...

Este estado te permite conocer la información de tu habitación: temperatura, humedad en


el aire, entre otros.

93
Si interactúas con el aire acondicionado y lo ajustas según tu necesidad, verás que el
ambiente o comportamiento en tu habitación será diferente y estos datos representativos
tendrán valores nuevos.
Los estados nos permiten determinar cuándo renderizar (o actualizar) los componentes de
nuestra página. Esto quiere decir que: siempre que actualicemos el estado de un
componente, el componente actualizará de forma inmediata su información.
Para que podamos crear nuestro primer estado deberemos entender antes otro concepto
fundamental: hooks. Esta es la herramienta actual que se utiliza para la creación de estados.

Hooks
Los hooks son funciones especiales que permiten que un componente utilice características
de react. Estos son siempre y únicamente empleados dentro de un componente.

⌛Estos hooks se encuentran integrados a la librería de react, aunque también los podremos
encontrar en otras librerías (que veremos más adelante) o incluso crear hooks personalizados
nosotros mismos.

Los hooks son importantes porque permitirán simplificar distintas funciones específicas que
vienen a solucionar un problema. Por ejemplo, los estados. Si nos ponemos a analizar,
podríamos crear estados sin utilizar hooks, pero esto nos llevaría muchas líneas de código,
lógica, y pérdida de eficiencia en nuestro proyecto.

Entonces… como dice el dicho: “¿Para qué inventar la rueda?”. Si ya sabemos que
los hooks nos facilitan el trabajo, aprendamos a utilizarlos.

Casos de uso

94
Lo primero que debemos hacer antes de construir un estado, es importar el
hook useState dentro del componente y en la parte superior del archivo.

Dentro del componente funcional, utilizamos el hook useState para declarar el estado. Este
tomará un argumento (el valor inicial del estado) y devuelve un array con dos elementos:
la variable de estado y una función para actualizar ese estado.

Podemos utilizar la variable de estado (miEstado) en cualquier parte del componente donde
se necesite acceder o mostrar el valor actual del estado.

95
La función proporcionada por useState(setMiEstado) nos permitirá actualizar el valor del
estado cuando sea necesario.

Esto generalmente se hace en respuesta a algún evento, como un clic de botón o un cambio
en un formulario.

Recuerda
NO hay otra manera de cambiar el valor del estado si no es a través de esta función
(setMiEstado, en este caso).
🤓 Algunos casos comunes en los que se utiliza este hook...

• Contado
Ayuda a llevar la cuenta de un valor y actualizar el número.

• Entrada de usuario
Usado para gestionar el valor de un campo de entrada de usuario.

96
Control de visibilidad
Permite cambiar la visibilidad de un elemento de la interfaz del usuario.

Datos de un formulario
Se utiliza para almacenar y actualizar la información proveniente de un formulario.

Preguntémonos ahora... ¿Qué tienen entonces de especiales los estados si al final de


cuentas son variables? Para responder esta pregunta, hagamos un poco de código.

97
Ahora sí, veamos de qué manera se comunica un componente padre con un
componente hijo según el flujo unidireccional.

Props
¿Qué son las props?
La forma de transferir la información entre componentes es mediante el uso
de propiedades (props). Estas propiedades son datos que guardaremos dentro de un
objeto, y que lo pasaremos como un argumento al componente hijo.

98
¿Recuerdas que dijimos que los componentes son funciones? Si te fijas, estos pueden
recibir parámetros y devolver valores. En el caso de react, los componentes devuelven un
contenido que renderizaremos en la pantalla.

Conozcamos ahora una característica importante al momento de trabajar con estados y


props.

Para que el objeto sea único en el renderizado, la propiedad key se pone en el ciclo map.

Primera forma de hacerlo

Segunda forma de hacerlo (desestructurando el parámetro con llaves, sacándolo a su vez de


props el objeto character en el propio pase de parámetros).

99
Tercera forma de hacerlo Destructuring del destructuring y tener en las props de character
el name y house.

Lifting state up
Información vs eventos
Hemos mencionado a lo largo de la clase que el flujo de la información viaja de
un componente padre a un componente hijo, y solo en esa dirección.
Sin embargo, cuando trabajamos con eventos en React (como por ejemplo, hacer clic sobre
elementos, escribir contenido en un campo, “submitear” un formulario, presionar una tecla

100
o mover el ratón, entre otros), estos se propagan en sentido inverso al flujo de la
información.
Es decir, se originan en componentes hijos y son manejados por funciones pasadas como
props desde componentes padres.

Veamos un ejemplo que nos permita seguir ilustrando este comportamiento.

En el caso de User1 y User2 está explicado en el código que se envía una función para que
se actualice la información que debe recibir la función en el padre View pero que los
hermanos no se pueden hablar entre ellos, pero más interesante es el caso de
ParentComponent y ComponentA, Componente B, donde los hermanos A y B no se hablan
pero es el padre que les envía la información que deben decir a través del estado y la
función de actualización.

Como en las empresas se tiene componente SMART (padre) que tienen la lógica y
componentes (DUMP) que simplemente recibe la información y la muestra.

App.jsx

101
// src/App.jsx
import "./App.css";
import View from "./View";
import ParentComponent from "./ParentComponent";

function App() {
return (
<>
<View />
<ParentComponent />
</>
);
}

export default App;

View.jsx
// src/View.jsx
import User from "./User";
import User2 from "./User2";
const View = () => {
const handleUserButtonClick = (info) => {
console.log(info);
};

return (
<>
<User handleUserButtonClick={handleUserButtonClick} />
{/* Como los hermanos no pueden hablarse entre si
debo pasar el estado al view que es el padre
y sacarlo de User a View*
Pero en su lugar lo haré con Parent, Component A y B */}
<User2 handleUserButtonClick={handleUserButtonClick} />
</>
);
};

export default View;

User y User2
// src/User.jsx
import { useState } from "react";
const User = ({ handleUserButtonClick }) => {
const [updateData, setUpdateData] = useState("Editando...");

const handleUpdateData = () => {


if (updateData === "Editando...") {
setUpdateData("Actualizado...");
} else {
setUpdateData("Editando...");
}
handleUserButtonClick(`Se actualizó el perfil: ${updateData}`);
};

return (
<div>
<h4>User</h4>

102
<button onClick={handleUpdateData}>Actualizar Perfil</button>
<p>Estado del perfil: "{updateData}"</p>
</div>
);
};

export default User;

// src/User2.jsx
import { useState } from "react";

const User2 = ({ handleUserButtonClick }) => {


const [updateData, setUpdateData] = useState("Editando...");

const handleUpdateData = () => {


if (updateData === "Editando...") {
setUpdateData("Actualizado...");
} else {
setUpdateData("Editando...");
}
handleUserButtonClick(`Se actualizó el perfil: ${updateData}`);
};

return (
<div>
<h4>User 2</h4>
<button onClick={handleUpdateData}>Actualizar Perfil</button>
<p>Estado del perfil: "{updateData}"</p>
</div>
);
};

export default User2;

ParentComponente.jsx
// src/View.jsx
import User from "./User";
import User2 from "./User2";
const View = () => {
const handleUserButtonClick = (info) => {
console.log(info);
};

return (
<>
<User handleUserButtonClick={handleUserButtonClick} />
{/* Como los hermanos no pueden hablarse entre si
debo pasar el estado al view que es el padre
y sacarlo de User a View*
Pero en su lugar lo haré con Parent, Component A y B */}
<User2 handleUserButtonClick={handleUserButtonClick} />
</>
);
};

export default View;

ComponenteA y ComponenteB

103
// src/ComponentA.jsx
const ComponentA = ({ counter, onIncrement }) => {
return (
<>
{console.log("ComponentA", { counter }, { onIncrement })}
<p>Componente A</p>
<p>{counter}</p>
<button onClick={onIncrement}>Incrementar counter A</button>
</>
);
};

export default ComponentA;

// src/ComponentB.jsx
const ComponentB = ({ counter, onIncrement }) => {
return (
<>
{console.log("ComponentB", { counter }, { onIncrement })}
<p>Componente B</p>
<p>{counter}</p>
<button onClick={onIncrement}>Incrementar counter B</button>
</>
);
};

export default ComponentB;

Ejemplo práctico
En este momento te invitamos a tratar de aplicar los conocimientos adquiridos en esta clase
resolviendo el siguiente ejercicio, el cual consiste en la creación de una aplicación para
almacenar una lista de tareas (to-do list).
El objetivo de esta aplicación será recibir información sobre una tarea (to-do) por parte
del usuario en un campo de texto el cual será renderizado en una lista debajo de este input
al hacer click en un botón de envío.
Además agregaremos un componente que se encargará de llevar una cuenta
del total de tareas en la lista que deberá ser renderizado justo antes de dicha lista. Para
llegar al resultado final te sugerimos seguir los siguientes pasos:
PASO 1
Configuración inicial
• Crea un nuevo proyecto de React/Vite.
• Crea los archivos App.jsx, AddToDo.jsx, ToDoCounter.jsx, ToDoList.js y App.css.
PASO 2
Implementación de AddToDo
• Define un componente de función AddToDo.
• En el componente AddToDo, define una función handleSubmit que capture el valor
del input y lo añada a la lista de tareas utilizando setTodos (Esta función setter será
definida dentro de app junto con el estado).
• Crea un formulario con un input y un botón, y asocia la función handleSubmit al
evento onSubmit del formulario.
PASO 3
Implementación de ToDoCounter

104
• Define un componente de función ToDoCounter.
• En el componente ToDoCounter, renderiza el número total de tareas recibido por
props.
PASO 4
Implementación de ToDoList
• Define un componente de función ToDoList.
• En el componente ToDoList, itera sobre el array de tareas (todos) recibidas por props
y muestra cada tarea como un elemento <li> en una lista <ul>.
• Asigna una key única a cada elemento <li> utilizando el índice de la tarea en el array.
PASO 5
Implementación de App
• Define el estado todos utilizando useState y proporciona un array inicial de tareas.
• Renderiza los componentes AddToDo, ToDoCounter y ToDoList, pasando el estado
ToDos y la función setTodos según sea necesario.
Resolución
Consideremos una app de tareas que nos permita contar el número total de tareas totales,
listar las tareas y agregar tareas a la lista.
Para ello haremos uso de tres componentes
1. ToDoCounter
2. ToDoList
3. AddToDo, respectivamente.
ToDoCounter se encargará de renderizar simplemente la cantidad de tareas que hay en la
lista, así que será un componente estático sin ninguna lógica específica.

105
ToDoList, tendrá un estado local como array que le permitirá almacenar tareas y renderizar
cada una de estas como ítems de una lista desordenada.
AddToDo, por su parte, se encargará de tomar el valor que se introduzca en el input el cual
corresponde a la tarea a agregar.
Por ahora no entraremos en detalles sobre cómo funciona este componente, puesto que lo
veremos más adelante cuando hablemos de formularios.

Importando los tres componentes a App, obtenemos la siguiente vista:

106
Sin embargo, ¿Cómo sabe el componente ToDoCounter cuántas tareas hay? O ¿Cómo
agregamos la nueva tarea que está en AddToDo en el array de ToDoList?
Es aquí donde debemos hacer uso del lifting state up. Dado que App es el componente
padre de todos los demás, este será quien pueda gestionar y compartir la información a los
componentes hijos.
Poniendo en acción todo lo visto hasta el momento, hagamos algunos cambios en el
código:
El estado definido en ToDoList, será llevado a App, de manera que ToDoCounter reciba el
array todos por props y simplemente se encargue de renderizar la longitud de este arreglo.
De manera análoga, pasamos por props el estado todos a ToDoList, para que muestre la
lista en el navegador.

107
108
Finalmente, pasamos por props a AddToDo la función “seteadora” que modifica el estado
todo en App, de manera que cada vez que se escriba algo en el input, tome su valor y lo
agregue al array.
En este caso, la función setTodos usa una callback para que la renderización de la nueva
tarea se haga de forma inmediata.
Dicha callback tomará el estado anterior, hará una copia de sus elementos (tareas) y
agregará al final a la nueva tarea proveniente del formulario.

109
De esta manera, la información queda compartida entre los componentes que la
requieran y la aplicación funciona adecuadamente.

110
Hasta aquí aprendimos cómo levantar estados de componentes en React para
gestionar información a componentes incomunicados entre sí.

Cierre
En conclusión...
En esta clase hemos aprendido el concepto de flujo unidireccional de la información y cómo
este define la estabilidad de una aplicación al momento de compartir datos entre
componentes padres e hijos a través del objeto props.
Además, aprendimos qué es y cómo generar un estado interno en un componente
mediante el uso del hook useState, el cual nos concede el acceso a un objeto que podemos
controlar para determinar cuándo react debe re-renderizar la información en pantalla.
Descubrimos, también, que aparte del flujo de información, está presente el flujo de
eventos que ayuda a comunicar a los componentes hijos con sus padres a partir de la
ocurrencia de eventos en la interfaz del usuario.
Por último, introdujimos el concepto de levantamiento de estados, que exhibe la
información interna de un componente a partir de su estado local y compartirla con los
demás componentes que la necesiten a través de su componente padre, las props y los
eventos.

Homework
En esta lección encontrarás una introducción al proyecto/homework que deberás
desarrollar hoy. Mira el siguiente video con atención y luego sigue las consignas. ¡Buena
suerte!
INSTRUCCIONES
Habilidades y competencias que desarrollarás...
• Manejo de props y estados con react.
ACTIVIDAD 01
En esta actividad crearemos la vista “Mis Turnos”. Para ello…
Crear dentro de la carpeta views el componente MisTurnos. Exportarlo y renderizar en App.
(Recuerda que puedes agregar el tag <h1> de testigo antes de continuar con los demás
elementos).
ACTIVIDAD 02

111
Crear dentro de src la carpeta helpers. Dentro de ella crear un módulo llamado myTurns.js.
En este módulo crear un arreglo de turnos. Estos turnos nos servirán para trabajar en
nuestras aplicación antes de realizar las peticiones al Backend. Ten en cuenta al momento
de crear los objetos, que es MUY IMPORTANTE que tengan las mismas propiedades que
envía tu aplicación de backend.
ACTIVIDAD 03
Dentro del módulo de MisTurnos.jsx importa el arreglo de turnos que acabas de crear. Crea
dentro del componente un estado al que le asignes como valor dicho arreglo.
ACTIVIDAD 04
Crea dentro de la carpeta components un componente que se encargue de representar UN
ÚNICO turno. Para ello ten en cuenta las props que debe recibir y en qué tags del
componente quieres poner dicha información.
ACTIVIDAD 05
Importa en la vista MisTurnos el componente que has creado y mapea el estado que
contiene el arreglo de turnos. Cada iteración del mapeo debe retornar un ejemplar del
componente Turno pasando por props la información necesaria.
[REQUISITOS]:
• Haber creado un arreglo de turnos que simularán los datos de turnos de un usuario.
• Haber creado la vista MisTurnos e importado la misma en App.
• Haber creado un estado dentro del componente MisTurnos asignándole como valor
el arreglo de turnos.
• Haber creado un componente para representar un único turno.
• Haber mapeado correctamente el estado de MisTurnos para mostrar un
componente por cada turno que se encuentra en el estado.

112
React Lifecycle
Introducción
Hasta el momento hemos aprendimos a crear interfaces con componentes mediante la
librería react. Habíamos dicho que estos pueden tener estados internos y recibir
propiedades. En esta clase veremos cómo hace react para detectar y renderizar cambios de
los componentes.

Revisemos cuáles son los objetivos de esta clase


1. Desarrollar qué es el ciclo de vida de un componente.
2. Comprender las diferentes etapas del ciclo de vida.
3. Aplicar de forma integradora los conocimientos en un mini proyecto.

Ciclo de vida
Componentes y sus ciclos
El término ciclo de vida se refiere a las fases y métodos que ocurren desde que
un componente comienza a renderizarse en pantalla hasta que desaparece. Estas fases
permiten que los componentes realicen diferentes tareas.
Aunque actualmente el concepto del ciclo de vida ha cambiado ligeramente gracias a la
introducción de los hooks, es importante conocer las cuáles son estas etapas en su forma
original para entender mejor cómo controlarlos.
Por este motivo, ahora vamos a aprender una nueva forma de crear componentes.

Aunque actualmente el concepto del ciclo de vida ha cambiado ligeramente gracias a la


introducción de los hooks, es importante conocer cuáles son estas etapas en su forma
original para entender mejor cómo controlarlos.
Por este motivo, ahora vamos a aprender una
nueva forma de crear componentes.
Componentes de clase
De momento sabemos crear componentes funcionales, que son con los que hemos
trabajado hasta el momento. Pero antes de que se haya normalizado utilizar este tipo de
componentes, existían de otro tipo: los componentes de clase.
Los veremos de forma breve para comprender cómo funciona el ciclo de vida. Un
componente de clase luce de esta manera...
Como podemos ver, nuestro componente Saludo se extiende de una clase predefinida de
react llamada Component.
Al ser una clase y no una función no podremos retornar ningún valor. Por lo que la
forma de renderizaralgo es utilizando el método render que también viene predefinido.

Dentro de este método si podremos retornar un valor que será el contenido JSX que
queramos mostrar en pantalla.

113
Como podemos observar, la sintaxis es ligeramente distinta a la ya conocida
de los componentes funcionales.
Los componentes de clase pueden recibir props de un componente padre, así como
contener un estado interno para almacenar y gestionar información. Veamos un ejemplo
sencillo de esto...
Este componente de clase recibe una prop llamada nombre. En el constructor se inicializa
un objeto (al que llamamos state).
En este tipo de componentes no se utilizaban los hooksya que no existían. Aunque, de todos
modos, hoy tampoco se podría, ya que los hooks son exclusivos de los componentes
funcionales.
A su vez, el constructor recibe la prop e inicializa el estado con un mensaje personalizado
que incluye el nombre.

Con estas ideas en mente, veamos ahora las etapas del ciclo de vida de un componente.
Ciclos
Observa el siguiente recuadro para complementar lo que has visto en el video anterior.

114
Ahora que comprendemos el ciclo de vida de los componentes, conozcamos un nuevo
hook: useEffect. Este nos permitirá definir un comportamiento similar al ciclo de videa de
componente de clases, pero en los componentes funcionales.

useEffect
Hook useEffect
El ciclo de vida en los componentes funcionales se trabaja de una manera muy distinta a
cómo lo vimos en los componentes de clase. Aquí se incorpora el concepto de efectos. Estos
hacen referencia a acciones secundarias que pueden llevarse a cabo más allá de la propia
renderización. De esta forma, se puede controlar en qué momento del ciclo queremos que
estas acciones ocurran.

Para gestionar estos efectos, react cuenta con un hook integrado llamado useEffect(), que
se encarga de realizar efectos secundarios dentro de los componentes funcionales en las
distintas etapas del ciclo de vida. En otras palabras...

El hook useEffect nos permitirá realizar acciones cuando un componente se monta, se


actualiza o se desmonta.

Estructura
Veamos cómo es la estructura de este nuevo hook a continuación. (Montaje, Desmontaje y
Arreglo de dependencias)

115
1.

2.

3.

Casos de uso
Existen muchos ejemplos de casos de uso de useEffect() en el desarrollo de aplicaciones. Los
más comunes son...
MONTAJE
• Solicitud de datos desde una API o base de datos.
• Realizar suscripciones a eventos del navegador.
• Inicializar recursos externos como, por ejemplo, un reproductor de video.
ACTUALIZACIÓN
• Realizar acciones cada vez que una prop específica cambie.
• Actualizar el título de la página cada vez que el estado del componente se
modifique.
• Gestionar acciones cada vez que una sección específica del estado cambia, por
ejemplo, cuando un usuario inicia sesión.

116
DESMONTAJE
• Limpiar suscripciones a eventos del navegador.
• Limpiar el almacenamiento local del navegador.
• Cancelar peticiones de red o APIs.

¡Veamos al useEffect en acción!

Wrap up
Practiquemos en una demo
Para poner en práctica al hook useEffect y sus casos de uso, haremos una pequeña demo.
Ten presente que también utilizaremos el hook useState que nos permitirá guardar la
información dentro del componente.
Ahora te toca a ti
Ahora que ya has visto en practica este nuevo hook te invitamos a que experimentes por tu
cuenta con el siguiente ejercicio. Te sugerimos eliminar el límite de personajes que vienen
de la API o limitarlo a cerca de 20 de estos para tener más información:
Step 1. Agregar un botón para cada casa
• Agrega botones (por ejemplo, Gryffindor, Slytherin, Hufflepuff, Ravenclaw) encima
de la lista de personajes.
Step 2. Usar useState para el filtro actual
• Crea un estado para almacenar la casa seleccionada actualmente.
Step 3. Usar useEffect para filtrar los personajes
• Cuando la casa seleccionada cambie, utiliza useEffect para filtrar la lista de
personajes según la casa y actualizar la visualización.
Step 4. Mostrar la casa actualmente seleccionada
• Puedes mostrar en algún lugar de la interfaz cuál es la casa que está actualmente
seleccionada.
Step 5. Restablecer el filtro
• Agrega un botón de "Restablecer" para quitar cualquier filtro y mostrar todos los
personajes nuevamente.

Homework
En esta lección encontrarás una introducción al proyecto/homework que deberás
desarrollar hoy. Mira el siguiente video con atención y luego sigue las consignas. ¡Buena
suerte!
INSTRUCCIONES
Habilidades y competencias que desarrollarás...
• Instalación de dependencias en proyecto de react.
• Manejo de ciclo de vida del componente .
• Peticiones HTTP desde aplicación de frontend.
ACTIVIDAD 01

117
Para esta actividad tienes los pasos descritos en detalle en el video, pero haremos un
resumen de cada uno de los pasos:
• Ejecutar AMBAS aplicaciones, frontend y backend, en consolas separadas. Asegurar
que la aplicación de backend recibe correctamente las peticiones utilizando
Postman, Insomnia, etc.
ACTIVIDAD 02
Instalar en nuestra aplicación de backend cors y @types/cors.
En schmilo back %
$ npm install cors
$ npm i --save-dev @types/cors
ACTIVIDAD 03
Instalar axios en nuestra aplicación de frontend.
En schmilo front/vite-project
$ npm install axios
ACTIVIDAD 04
Configurar correctamente el middleware de cors en nuestra aplicación de backend.
ACTIVIDAD 05
En nuestra aplicación de frontend, en la vista MisTurnos, cambiar el valor inicial del estado
de turnos para que sea un arreglo vacío.
ACTIVIDAD 06
Manejar el ciclo del vida del componente MisTurnos mediante el hook useEffect. Al
montarse el componente, realizar con axios la petición HTTP
a http://localhost:3000/turns(opens in a new tab).
ACTIVIDAD 07
Al resolverse la promesa, setear el valor del estado con el arreglo de turnos que recibimos
desde el backend. (Ten en cuenta que aquí estamos utilizando TODOS los turnos, no los
turnos por usuario. Esto será para más adelante).
ACTIVIDAD 08
Si completaste todos los pasos anteriores, deberás ver el listado de todos los turnos
registrados en la Base de Datos. Si quieres, puedes agregar más turnos utilizando las rutas
que tienes creadas desde Postman o Insomnia, para que la lista sea más larga y puedas
visualizarla.
[REQUISITOS]:
• Haber instalados las dependencias axios, cors y @types/cors en los proyectos
correspondientes.
• Haber manejado correctamente el ciclo de vida de MisTurnos a través del hook
useEffect.
• Haber realizado con axios la petición correspondiente para obtener el arreglo de
turnos desde el backend.
• Haber asignado el arreglo de respuesta como estado local del componente
MisTurnos.

118
React Forms
Introducción
En esta clase nos enfocaremos en la gestión de formularios, los cuales desempeñan un
papel fundamental en la entrada y manipulación de datos en las aplicaciones. ¿Cómo
podemos aprovechar al máximo estas herramientas para mejorar la experiencia del usuario
en nuestras páginas?

Revisemos cuáles son los objetivos de esta clase


1. Aprender qué son y cómo construir un formulario.
2. Entender qué son los formularios controlados.
3. Comprender para qué y cómo validar un formulario controlado.
4. Comparar el proceso de generación de formularios con Formik.

Formularios
Los formularios son elementos muy importantes en las páginas web. Sirven como un puente
entre un usuario y la base de datos. A través de los formularios podemos proporcionar
información para que la aplicación pueda trabajar con ella.

¿Qué tipos de formularios se te ocurren? Aquí te dejamos algunos que se pueden encontrar
con gran frecuencia.

• Formularios para registrarse o iniciar sesión.


• Creación de un nuevo producto en una página.
• Postulación a un puesto de trabajo.
• Publicación en una red social.

Nos pongamos técnicos... ¿Qué es un formulario?


Vamos a interpretar a los formularios como fragmentos de código que permiten a
los usuarios enviar datos a un servidor para su procesamiento.
Está compuesto por una serie de campos (por ejemplo, de texto, desplegables, checkbox,
entre muchas otras) los cuales sirven como data-points para la recopilación de información.

En esta imagen podemos ver un ejemplo básico de un formulario con HTML.

119
En este pequeño ejemplo, estamos generando un formulario básico de contacto que es
inicializado con la etiqueta <form> e incluye algunos campos para ingresar datos, además de
un botón de envío.

A continuación vamos a construir un formulario de login que nos permita mostrar las
ventajas de trabajar con react. Para ello, crearemos un proyecto de vite donde
sustituiremos el archivo App.jsx para que devuelva únicamente nuestro formulario de login.

Tómate unos momentos para observar la estructura de este formulario.


Si a este formulario le sumamos algunos estilos, podremos encontrar algo como esto en
nuestra pantalla...

120
Esto nos dará como resultado un pequeño formulario de login listo para trabajar. Ahora
veremos cómo podemos configurar este formulario de manera tal que podamos controlarlo.

121
Pero para esto, veamos qué son los formularios controlados.

Formularios controlados
¿Qué son?
El término formulario controlado se refiere a formularios donde los elementos de entrada
(inputs, textarea, selectores, checkbox, etc) están vinculados al estado del componente y
son gestionados (controlados) por este. Esto nos permite centralizar la información, realizar
validaciones y manejar eventos de forma precisa.
Con lo que aprendimos hasta ahora, conozcamos de qué manera podemos terminar de
darle funcionalidad a nuestro componente.

Validación de datos
La validación de datos nos permite garantizar que la información almacenada en una base
de datos o utilizados en una aplicación sean precisos y consistentes, lo que evita errores en
el funcionamiento del sistema.
Podemos pensar que las validaciones son una capa adicional de seguridad y así evitar que
datos incorrectos o no válidos lleguen al servidor, ahorrando recursos y evitando
procesamientos innecesarios. Existen diferentes formas de validar un formulario.
Podremos validar la información utilizando propiedades HTML, o también utilizando
funciones intermedias (middlewares).

En nuestro caso, vamos a analizar el estado del componente en el que almacenamos la


información ingresada por el formulario, y haremos las validaciones que necesitamos de
forma inmediata.
Expresiones regulares
Las expresiones regulares (regex) son patrones de búsqueda que permiten encontrar y
manipular cadenas de texto. Se utilizan para validar, buscar y manipular datos en texto de
manera eficiente.
Para hacernos a una mejor idea de esto, supongamos que recibes una lista de contactos y
deseas validar que todos los teléfonos realmente sean números y no texto. Con una
expresión regular, puedes buscar un patrón específico.

122
Aplicando esta expresión regular a la lista, te mostrará los números de teléfono que
sigan este patrón. Existen formas de crear patrones de manera manual, pero es más
frecuente el uso de patrones previamente diseñados que pueden ser encontrados en
diversas páginas de internet o haciendo uso de inteligencia artificial.
Ejemplo regex
Por ejemplo, puedes pedirle a ChatGPT que genere una regex para validar un correo
electrónico.
LINK WEB

😁 EJERCICIO
Ahora te proponemos que le pidas a ChatGPT que genere regex para las siguientes
situaciones...
• Validar que un texto es una página web válida.
• Validar un número de teléfono proveniente de Italia.
Junta aquellas expresiones regulares con el tipo de validación que emplean.
• Solo letras /^[a-zA-Z]+$/
• Alfanumérico /^[a-zA-Z0-9]+$/
• Correo electrónico /^.+@.+\..+$/
• Código postal /^\d{5}$/
• Patrón de fecha \d{2,4}-\d{2}-\d{2}
SUBMIT

TAKE AGAIN
Validación de errores
En el siguiente ejemplo vamos a construir una función de JavaScript en la que validaremos si
lo que recibimos es realmente un correo electrónico.
Para esto utilizaremos un condicional y el método test nativo del lenguaje. En caso de no
cumplirse la validación agregará "error" al objeto de errores con el mensaje "username is
invalid".
Al escribir dentro del input de username veremos que la validación devuelve un objeto
con error hasta el momento en que cumplimos con el patrón esperado.

123
Mientras más casos de error contemples, más protegida estará tu aplicación.
Ahora solo resta controlar que, si hay errores, el usuario no pueda enviar la información del
formulario (lógico, ¿verdad?). La forma más sencilla de hacerlo es generar una
condición dentro de una función submitHandler() que evalúe el estado errores.

Si el objeto errors contiene información, entonces no permite el envío.


Caso contrario, puede enviar la data.
En esta lección trabajamos un formulario con dos inputs, pero, ¿qué sucedería si
tuviéramos, en su lugar, unos diez o más campos? Sería un poco difícil y extenso definir el
código para controlarlos.
Para trabajar con formularios complejos lo más frecuente es la incorporación de librerías
que, a pesar de trabajar con la misma lógica, simplifican mucho la creación de código.

Formik
¿Qué es?
Formik es una librería de código abierto enfocada en la creación de formularios que trabaja
de forma declarativa (al igual que react) simplificando el control de los valores y validación
de inputs.

124
Para entender mejor la ventaja que nos trae Formik, vamos a transformar nuestro
formulario de login a uno creado con esta librería.
Primero instalemos esta librería con el comando...

React forms vs Formik


Una vez instalada la herramienta, tendremos acceso a las funciones, elementos y
componentes disponibles en formik. Empecemos por crear un componente llamado
FormikLogin e importemos las dependencias que utilizaremos...
• Formik | Form | Field | ErrorMessage.
Todos estos elementos son componentes de Formik que deberás importar, y que sustituyen
a los tags de HTML que utilizamos para construir el formulario anterior.

• Validar: Función que hemos creado anteriormente para validar inputs.


• Formik: Este es un componente que se importa desde la librería y debe envolver a
todo el formulario, Recibe 3 propiedades.
1. InitialValues: es en donde diremos cuáles son los datos que queremos reunir
en el formulario.
2. Validate: le pasaremos nuestra función validadora.

125
3. OnSubmit: es la función que se ejecutará cuando el formulario se envíe
correctamente y no presente errores.
• Form: Dentro del componente form es en donde construiremos nuestro formulario.
• Field & ErrorMessage: El componente Field será el input per sé en el que el usuario
ingresará sus datos. En cambio, el componente ErrorMessage es simplemente en
donde aparecerá el mensaje de error en el caso de haber un problema con los datos
ingresados.

Este es solo un ejemplo básico de la capacidad de Formik, pero existen muchas otras
herramientas y funcionalidades por descubrir.
Documentación
No dudes en consultar la documentación para aprender más al respecto.
LINK WEB

En conclusión...
En esta clase hemos comprendiendo cómo el manejo y validación
mediante formularios controlados nos brinda un mayor control sobre la información
ingresada por un usuario.
Al adoptar la gestión del estado del formulario a través de react, podemos
crear aplicaciones que validen los campos de manera inmediata y avisen al usuario sobre
posibles errores en la información suministrada.
Además, hemos mencionado a Formik, una herramienta que simplificasignificativamente la
creación y validación de formularios. También mejora la legibilidad y el mantenimiento,
permitiéndonos enfocarnos más en la lógica de la aplicación que en la gestión del estado del
formulario de manera manual.

Homework
En esta lección encontrarás una introducción al proyecto/homework que deberás
desarrollar hoy. Mira el siguiente video con atención y luego sigue las consignas. ¡Buena
suerte!
INSTRUCCIONES
Habilidades y competencias que desarrollarás...

126
• Creación de formularios controlados con react.
ACTIVIDAD 1
Crear en la carpeta views los componentes Register.jsx y Login.jsx. Exportar ambos
componentes e importarlos en App para poder mostrarlos.
ACTIVIDAD 2
Implementar en el componente Register un formulario controlado que se encargará del
registro de usuario.
• Controlar el formulario de manera tal que se pueda validar que todos los datos
necesarios para el registro están completos, al mismo tiempo que los datos de los
inputs son reflejados en el estado local correspondiente y viceversa.
• Una vez completos y validados los datos, se debe poder presionar un botón que
dispare un evento, el cual ejecutará una función que se encargue de realizar la
petición de tipo POST correspondiente al servidor para el registro del usuario
enviando como body el estado que se confeccionó a través del formulario.
• En caso de que el registro sea exitoso, informar al usuario. Del mismo modo,
informar al usuario si ha ocurrido un error.
ACTIVIDAD 3
Implementar en el componente Login un formulario controlado que se encargará del login
del usuario.
• Controlar el formulario de manera tal que se pueda validar que todos los datos
necesarios para el login están completos, al mismo tiempo que los datos de los
inputs son reflejados en el estado local correspondiente y viceversa.
• Una vez completos y validados los datos, se debe poder presionar un botón que
dispare un evento, el cual ejecutará una función que se encargue de realizar la
petición de tipo POST correspondiente al servidor para el login del usuario enviando
como body el estado que se confeccionó a través del formulario.
• En caso de que el login sea exitoso, informar al usuario. Del mismo modo, informar al
usuario si ha ocurrido un error.
¡Bien hecho!
TIPS
• Trabaja primero con el componente Register y LUEGO con el componente Login. De
esta forma podemos luego intentar logearnos con el usuario nuevo que hemos
registrado.
• Recuerda SIEMPRE notificar al usuario sobre el resultado de la operación que ha
realizado.
• Como TIP te recomendamos deshabilitar el botón de registro o login hasta que todos
los datos estén completos y validados. De esta forma puedes evitar envíos de
peticiones incompletas.

[REQUISITOS]:
• Haber implementado el formulario correspondiente en el componente Register. El
mismo debe estar controlado y validado.
• Haber realizado la petición correspondiente desde el formulario de Register para el
registro de usuario. Informar al usuario sobre el resultado de la petición.
• Haber implementado el formulario correspondiente en el componente Logn. El
mismo debe estar controlado y validado.

127
• Haber realizado la petición correspondiente desde el formulario de Login para el
login de usuario. Informar al usuario sobre el resultado de la petición.

React Routing
Introducción
Ya viste que react es una herramienta súper completa y muy útil. En esta clase daremos
un paso más allá y aprenderemos una nueva característica: las rutas. ¡Pero cuidado! No son
las mismas rutas que en el backend.

Revisemos cuáles son los objetivos de esta clase


1 Aprender qué es routing.
2 Conocer la librería de react router dom.
3 Comprender cómo realizar la configuración inicial de react router dom.
4 Entender cómo utilizar algunas de las funcionalidades de react router dom para generar
una página dinámica.

Routing
Enrutamiento
El routing es la gestión de las diferentes vistas de una página web, permitiendo que un
usuario pueda navegar entre ellas. El usuario se podrá mover entre las diferentes vistas sin
la necesidad de recargar la página por completo. De esta forma, definimos qué elementos
serán renderizados en cada ruta.
Por ejemplo, si entras a la página oficial de Henry (o a cualquier otra) y comienzas a
presionar botones verás como puedes navegar en distintas vistas de la página.
De hecho, si miras la URL, verás como cambia a medida que cambias de vista.

128
El término Single Page Application hace referencia a que, en el navegador, se carga solo una
sola página HTML.
Su contenido se actualizará de forma dinámica mientras el usuario interactúa con la
aplicación. Por ende, en lugar de recargar la página una y otra vez cuando se hace un
cambio, lo que hace es alternar la aparición de distintos elementos llamados Views o Pages,
que se renderizan según la ruta en la que esté el usuario.

129
Tomemos otro ejemplo. La página de LinkedIn (que pronto será tu mejor amiga ) tiene
una gran cantidad de vistas dinámicas.
Si presionamos los distintos botones podemos ver cómo cambia la URL. Sin embargo, la
página no se recarga.
De esta manera, denominaremos al cambio de vistas como routing. La navegación entre
vistas es más fluida y rápida que la forma tradicional entre páginas.
¿Cómo podemos implementar esto en nuestras aplicaciones de react? Para lograr esto
existen multiples opciones. En nuestro caso vamos a enfocarnos en conocer una librería
llamada .react-router-dom.

React router dom


Esta es una librería enfocada en el manejo de rutas para aplicaciones del lado del cliente.
Esta herramienta gestiona el proceso de renderizado en una SPA, a partir de las rutas
definidas para los diferentes componentes de nuestra aplicación.
El enrutamiento puede ser representado, así como el DOM, como una especie de árbol. El
componente App será el nodo root y los demás componentes junto a sus respectivas rutas,
harán de nodos del árbol. Veremos esta analogía más adelante.

130
Instalación y configuración
Haremos una pequeña práctica para aprender cómo definir rutas y renderizar componentes
en ellas. Por lo que te pedimos que ejecutes los comandos que ya conoces para crear
un template inicial de vite con react.
Una vez hecho lo anterior vamos a instalar la librería con el comando...

Documentación
Te invitamos a que revises la documentación oficial de esta librería.
LINK WEB

131
Documentación
Te invitamos a que revises la documentación oficial de esta librería.
LINK WEB

El proyecto en el que vamos a trabajar tiene diferentes archivos. Estos estarán agrupados en
dos carpetas principales.
1. Carpeta components: tendrá a todos aquellos componentes que se van a renderizar
dentro de nuestra página web.
2. Carpeta views: estarán todos los componentes que sean piezas estructurales de la
aplicación, pero no son renderizados individualmente al cambiar de ruta.

Starter Pack
En el archivo STARTER podrás encontrar el repositorio base.
DESCARGA

132
Por el momento renderizamos una página estática que muestra únicamente la vista
principal en la cual hay una lista de contactos que provienen de una API al momento del
montaje.
El componente Navbar está también en la ruta principal, pero no tenemos forma de acceder
al resto de las vistas de la aplicación.
Para iniciar lo configuración tendremos que dar acceso al "enrutador
global"(BrowserRouter).
Este es el componente de la librería que nos proveerá de las funcionalidades para crear un
enrutado.
Este debe ubicarse en el nivel más alto de nuestra aplicación. En nuestro caso, dentro del
archivo main.jsx envolviendo al componente App.

133
Recuerda importar esto de la librería.
De esta manera, todos los componentes de la aplicación podrán acceder al enrutado. ¿Te
parece si creamos nuestra primer ruta?

Configuración React Router


<Routes> y <Route>
La librería nos proveerá de dos componentes muy importantes: Routes y Route. Estos se
utilizan para definir las rutas de nuestra aplicación. Veamos cómo utilizarlos...
Hasta ahora, hemos logrado definir rutas donde se renderizan componentes y enlaces que
nos lleven a estas. Pero, ¿Qué sucede si una ruta contiene valores que pueden cambiar y
exhibir información diferente cada vez?
Por ejemplo, piensa en la vista de "Perfil" que existe en Instagram como un solo
componente. Si te fijas, todos los perfiles son idénticos. Lo único que cambia es la
información e imágenes que lleva dentro. ¿Cómo podemos lograr esto?

134
Rutas Dinámicas
Las rutas dinámicas permiten configurar las direcciones web de manera que puedan incluir
segmentos variables. Esto conlleva a que una misma ruta maneje diferentes valores en
ciertas partes de la URL.
El utilizar parámetros en las rutas, posibilita la creación de componentes reutilizables,
facilitando así la construcción de aplicaciones más dinámicas y adaptables.

135
136
137
Redirección en eventos
Nuevos hooks
En muchos sitios web, la experiencia del usuario implica en ocasiones ser redirigido a rutas
específicas para visualizar información. Conozcamos a continuación, dos nuevos hooks que
nos permitirán implementar redirecciones dentro de nuestras aplicaciones.

Hemos visto cómo dirigirnos a distintas vistas mediante el uso del hook useNavigate, así
como renderizar componentes de manera condicional gracias a useLocation. Te invitamos a
probar ingresar a una ruta de la aplicación desde el navegador cambiando la URL, pero a
una ruta que no exista. ¿Qué crees que sucederá?

Veamos de que manera podemos definir un componente para los errores que se puedan
presentar al acceder a rutas sin contenido específico.

138
Manejo de errores
Componente Error
Existen diferentes tipos de errores y formas de manejarlos mediante el enrutamiento. Por
ahora, nos enfocaremos únicamente en el manejo de rutas inexistentes.
Vamos a crear un componente Error que será el que se visualice cuando el usuario ingrese a
una ruta que no exista.
Este componente muestra una cuenta regresiva al montarse, usando un estado local
llamado countdown (inicializado en 5).
Se actualiza cada segundo mediante setInterval y se reinicia al montar el componente para
sincronizar con setTimeout, que llevará al usuario a "/home" después de 5 segundos.
Al desmontarse el componente, reiniciamos el intervalo para mantener la precisión al volver
a ingresar.

139
Para mostrar este componente en cualquier ruta que no sea la existente tendremos que
definirlo.
El path de esta ruta es "*", lo que significa que el componente se renderizará si no se ha
accedido a ninguna de las rutas definidas anteriormente.

Cierre
En conclusión...
Hemos abordado el concepto de routing. Conocimos la librería react-router-dom que
facilita la navegación y el enrutamiento. Aprendimos sus componentes principales, como
el BrowserRouter, Route, Routes y Link.

Descubrimos que el enrutamiento puede estar sujeto a distintos parámetros que


permiten visualizar diferente contenido mediante el uso del hook useParams. También
vimos que la combinación de useLocation, y useNavigate añade flexibilidad y dinamismo a
nuestras rutas, permitiendo renderizar condicionalmente dentro de cada ruta, así como la
redirección.

140
Homework
En esta lección encontrarás una introducción al proyecto/homework que deberás
desarrollar hoy. Mira el siguiente video con atención y luego sigue las consignas. ¡Buena
suerte!
INSTRUCCIONES
Habilidades y competencias que desarrollarás...
• Implementación de enrutado en aplicación de react.

ACTIVIDAD 1
Para esta actividad tendrás que implementar las rutas para poder navegar dentro de tu
aplicación. Para ello vas a:
• Importar y utilizar el componente BrowserRouter correctamente en el módulo
main.jsx.
ACTIVIDAD 2
Importar y utilizar el componente Routes correctamente en el módulo App.jsx.
ACTIVIDAD 3
Importar y utilizar para cada una de las rutas que deseas crear el componente Route.
Recuerda darle las props necesarias (path y element).
ACTIVIDAD 4
Te sugerimos dejar la barra de navegación por fuera del enrutado para que se muestre en
todas las vistas de la navegación.
ACTIVIDAD 5
En el componente Navbar importar y utilizar correctamente el componente Link. Recuerda
aplicarlo a todas las opciones de navegación.

TIPS
• En la vista MisTurnos seguiremos viendo los turnos de todos los usuarios y no
únicamente del usuario logueado. No te preocupes. Arreglaremos esto en la próxima
clase.
REQUISITOS:
• Haber implementado correctamente la navegación dentro de la SPA.
o Haber utilizado BrowserRouter para dar a la aplicación la capacidad de
Navegación.
o Haber utilizados los componentes Routes y Route para enrutar
correctamente la aplicación.
o Haber utilizado el componente Link en Navbar para poder navegar al clickear
en las opciones correspondientes.

141
React Redux
Introducción
En esta clase exploraremos una herramienta para el manejo de estados: redux
toolkit. Pero antes de que te lo preguntes, no. Esto no viene a reemplazar los estados de los
componentes. redux nos facilitará un estado para toda la página.

Revisemos cuáles son los objetivos de esta clase


1. Aprender qué es redux.
2. Entender los principios fundamentales de redux core.
3. Conocer qué es redux toolkit.
4. Integrar a redux en un proyecto.

Redux
Estados | Globales vs Locales
Hasta ahora hemos trabajado con estados locales en los componentes y compartiendo
información por medio de props. Esta forma de manejar información es útil cuando una
aplicación es sencilla y la información almacenada es usada solo por algunos componentes.
Sin embargo, pensemos en una aplicación como una red social del nivel de Instagram.
Allí, la información del usuario o sus contactos, es utilizada en una gran cantidad de
componentes diferentes, los cuales en ocasiones no están siquiera relacionados.

Existen diversas maneras de crear y manejar este estado global. Una de ellas es mediante el
uso de redux.
Por esto es importante tener una herramienta que nos permita facilitar la gestión de la
información a un nivel global. De esta forma podremos acceder a la información desde
cualquier parte de la aplicación.

142
¿Qué es redux?

Redux es una librería independiente de react que fue diseñada para manejar de manera
sencilla el estado global de una aplicación.
• El objetivo general de redux es proporcionar un almacén centralizado para el estado
de la aplicación.
• Facilita el manejo y sincronización de los datos entre las distintas partes que lo
consumen.

Pensémoslo con un ejemplo. Supongamos que compartes con tu roomie una lista de tareas
compartidas de la casa . Redux sería como un tablero central donde tú y tu roomie
apuntan y revisan las tareas, asegurando que ambos estén al tanto de lo que sucede,
evitando confusiones y mejorando la coordinación.

Los 3 principios de redux


En Redux, todo gira en torno a tres principios fundamentales:
1. Fuente única de la verdad: todo lo que necesitas saber sobre la aplicación se
encuentra en un solo lugar, denominado store, que facilita la gestión y comprensión
del estado general.
2. El estado es de solo lectura: solo puedes cambiar el estado emitiendo las llamadas
actions (acciones), lo que hace que las modificaciones sean predecibles y rastreables.
Los estados son considerados inmutables: no pueden ser modificados directamente
después de ser creados.
3. Los cambios son realizados mediante funciones puras: las transformaciones del
estado son manejadas por funciones predecibles y sin efectos secundarios,
conocidas como reducers.

143
Flujo de información
En el siguiente diagrama podemos ver una representación gráfica del flujo que sigue la
información del estado global dentro de una aplicación controlada por Redux.
PASO 1. Cuando pedimos un cambio en un componente, como hacer clic en un botón, la
información se usa para crear una ACTION.
PASO 2. Esta ACTION luego se envía al STORE, para ser pasada junto con el estado global
actual al REDUCER.
PASO 3. En esencia, este último decidirá si el cambio solicitado puede realizarse o no.
PASO 4. Si es así, modifica el estado global con la información de la ACTION y devuelve el
nuevo estado que refleja los cambios en el componente.
¿Qué y cómo es crear una ACTION que puede ser enviada a una STORE como si fuese un
paquete de mensajería para que se pase del STORE la ACTION junto con el Estado Global al
REDUCER?, que allí decide si puede cambiar ese estado global con la información que trae
la ACTION y devuelve ese nuevo estado que refleja los cambios en el componente.

Ahora que entendemos qué es redux y cuál es el flujo de la información, conozcamos


los elementos que lo componen…

Redux Core
¿Qué es Redux de manera sencilla? https://www.youtube.com/watch?v=j-jzI3wkkVk

El Redux Core corresponde a los conceptos y funciones fundamentales que componen la


librería de redux. Estos son...

144
ACTIONS & ACTION CREATORS
Veremos a una action como un objeto que contiene información sobre el cambio
solicitado por un componente de la aplicación.

Este objeto comprende, a lo sumo, dos propiedades básicas:


• Action type: indica el tipo de acción a efectuarse. Actúa como una clave que será
utilizada por el reducer para determinar qué cambio realizar.
• Action payload: contiene la información necesaria enviada por el componente
para modificar el estado. esta información es opcional dependiendo de la acción
a realizar.
REDUCER
El reducer es una función pura que toma dos argumentos: el estado, (por defecto, el
estado inicial de la aplicación) y la action.

Esta función analiza el tipo de acción mediante un switch statement y ejecuta la


función correspondiente. Dentro de cada caso, realiza modificaciones al estado
global según sea necesario utilizando la información del action payload.

El objeto devuelto, que mantiene el estado y solo modifica la propiedad


correspondiente, asegura la inmutabilidad del estado.

Si no corresponde a ningún caso, el estado se devuelve sin cambios como caso


por defecto.

STORE
La store en Redux es un objeto centralizado que almacena el estado global de la
aplicación.

Se encarga de ejecutar el reducer para modificar el estado y notificar a los


componentes sobre los cambios.

Antes de llevar a la práctica el flujo de redux en una aplicación de react, consideremos una
herramienta complementaria que facilitará su uso: Redux Toolkit.

Redux Toolkit
Redux Toolkit y React https://www.youtube.com/watch?v=w2rAP7d6ndg&t=0s

Esta es una librería oficial de redux que fue creada para incorporar la lógica de redux core de
una manera simple y eficiente. Cuenta con métodos y configuraciones que nos ayudan a
simplificar el manejo de las estructuras básicas.

Actualmente la integración de toolkit es la aproximación recomendada por redux para


trabajar en proyectos reales. Veamos cómo integrar redux con toolkit en una aplicación.

145
Te invitamos a generar un proyecto de react con vite nuevo. Para utilizar redux en conjunto
con redux toolkit, es necesario instalarlos mediante el comando...

Allí figuran dos librerías.


1. @reduxjs/toolkit, para instalar los paquetes correspondientes a redux toolkit.
2. react-redux, para integrar react con redux.

CreateSlice
Toolkit reestructura por completo la creación y manejo de los reducers. Deja de tratarlos
como funciones dependientes del switch statement para modificar el estado, para tomarlos
como funciones independientes.
Imagina que el reducer (o estado global) es un pan para hacer sandwiches. Estos reducers
estarán creados a partir de una función llamada createSlice.
Un slice (rebanada) es una colección de fragmentos de lógica correspondiente al reducer.
Cada una de estas porciones se encargará de manejar una sola pieza del estado. Esto
conllevará a que sean manipulados de forma independiente reduciendo así posibles errores.

CreateSlice requiere un objeto con tres propiedades:


• Name: un nombre en string para identificar el slice creado.
• InitialState: el estado inicial.
• Objeto reducers: con una o más funciones reducer que definirán cómo actualizar el
estado.

Supongamos que vamos a crear una ToDoList. Necesitaremos crear un slice que debe
contener un estado inicial con la información de los ToDo. Agregaremos, también, un
nombre y todas las funciones reducer que necesiten actualizar la lista de todos.
Como resultado, el método createSlice nos generará los reducers y también las actions y
action creators correspondientes a cada uno de ellos, sin necesidad de definirlas de forma
manual. Podremos exportar el reducer del slice y cada una de las acciones de forma
independiente.
¡Bien hecho! Hemos hecho la configuración inicial de redux. Pero... ¿Dónde está react y
sus componentes? ¿Conocen ya el estado global? ¡Descubramos de qué manera hacer la
unión react + redux!

146
React-Redux
Configuración
Nuestra app de ToDoList solo agregará y eliminará tareas. Por el momento, esta aplicación
muestra algunas tarjetas definidas manualmente dentro de un estado local en App:
La idea aquí es consumir la información desde el store, que es donde se encuentra el estado
global. Lo primero que debemos hacer es envolver a App dentro del componente Provider.
Este provee a todos sus componentes hijos con la información y funcionalidades de la
store que recibe como atributo.

useSelector
El proceso de suscripción consiste en hacer que un componente se mantenga al tanto de los
cambios realizados en el estado global. Esto le permitirá actualizar la información que
utiliza del estado sin importar en qué parte de la aplicación haya sido modificada.
Para poder suscribir a un componente, utilizamos el hook useSelector de react-redux. Este
selector recibe una función callback que “selecciona” a qué propiedad del estado global
deseamos suscribirnos.

useDispatch
Vamos a hablar de un nuevo método de redux: dispatch. Este hace referencia a una función
encargada de enviar las acciones a los reducers en respuesta a eventos específicos.

Puede ser utilizada con el hook useDispatch de react-redux, el cual toma como argumento
la acción que deseamos despachar al reducer.
CREATE TO DO
Para nuestro ejemplo, vayamos al componente CreateTodo que se encargará de
recibir la información proporcionada por el usuario dentro de un formulario.
Esta información será el payload de una acción encargada de agregar una tarea a
nuestro estado global.
ADD TO DO
Por otro lado, restará manejar la lógica en el reducer addTodo. Tomaremos la propiedad
todos del estado global y modificaremos su valor agregando la tarea que recibimos
por payload.

147
• Se asignará también un valor de id incremental de acuerdo a la cantidad de
elementos dentro del array.
Como mencionamos antes, aunque el estado en Redux es inmutable, Redux
Toolkit utiliza la librería interna para permitirnos escribir código que parece mutar
directamente el estado.
Esto es solo una simplificación sintáctica para mayor legibilidad.
REMOVE TO DO
Para removeTodo, por su parte, filtraremos el estado con el método filter de
JavaScript.
Este método crea un nuevo array con las coincidencias del valor especificado. En
este caso, usaremos el operador "!" para obtener todo excepto el valor definido,
que será el id previamente asignado.

Con esto hemos completado nuestra TodoList App, la cual combina la creación de
interfaces dinámicas de react con gestión de estado global mediante redux toolkit.

Cierre
En conclusión...
En resumen, durante esta clase hemos profundizado en redux y su vinculación con react, así
como en el flujo de trabajo que implica. Exploramos cómo redux facilita la gestión de
un estado global de react.

A través de los conceptos de actions, reducers, store y el flujo de la información, hemos


aprendido a estructurar nuestra lógica de estado de manera modular. La conexión entre
react y redux ofrece una solución robusta para el manejo de estados.

Homework
ATENCIÓN
Esta homework está pensada para ser desarrollada a lo largo de las clases 12, 13 y 14 del
módulo. Tu instructor/a te guiará con ello.
INSTRUCCIONES
Habilidades y competencias que desarrollarás...
• Implementación de estado global de redux en aplicación de react.

148
• Manejo de estado global de usuario.
• Manejo de rutas dinámicas con params.
ACTIVIDAD 1
Esta será la última actividad de este proyecto integrador. Al tratarse de una actividad
extensa, contarás con los días restantes hasta el momento de la entrega para poder
finalizarla.
• Implementar el store de redux y conectarlo con tu aplicación de react.
ACTIVIDAD 2
Sugerimos partir de un estado inicial que posea las propiedades “user” y
“userAppointments”. Puedes crear otros estados si lo encuentras conveniente.
ACTIVIDAD 3
Adaptar la lógica del login para que, si el login es exitoso, la información de usuario se
guarde adecuadamente en el store global.
ACTIVIDAD 4
• Adaptar la lógica del montaje de MisTurnos, de manera tal que:
o Al montarse el componente, si no hay un usuario logueado (guardado en el
store) redirija a Home. Otra posibilidad adicional es que, en caso de no haber
un usuario logueado, el botón de “Mis Turnos” en Navbar esté inhabilitado o
directamente no se muestre.
o Al montarse el componente, si hay un usuario logueado, pida a la aplicación
de backend únicamente los turnos que corresponden a dicho usuario
mediante la action correspondiente que guarde estos datos en el estado
global.
o Utilizar un selector que pida al store los datos de los turnos y los renderice si
hay alguno. En caso de no haber turnos, renderizar algún componente que
indique que aún no hay turnos agendados para este usuario.
ACTIVIDAD 5
• Si aún no lo has hecho, implementar la lógica de cancelación de turnos. Para ello ten
en cuenta que:
o Debes realizar la petición correspondiente a la aplicación de backend.
o Al cambiar el estado del turnos deberás actualizar los datos de turnos en tu
aplicación de frontend, de manera tal que el turno figure cancelado en la UI.
ACTIVIDAD 6
• Implementar el formulario de creación de turno.
o Si no existe un usuario logueado, no debería poder acceder a la vista. Puedes
utilizar la misma lógica que para la sección “Mis Turnos”.
o Implementar y validar el formulario. El mismo debe realizar la solicitud
correspondiente para la creación del turno, RECUERDA ENVIAR EN EL BODY
EL ID DEL USUARIO QUE TIENES EN EL STORE.

Frontend Advanced
Introducción

149
Con todas las habilidades que has aprendido hasta este momento ya eres capaz de
construir una aplicación full stack por completo. Pero, ya que venimos trabajando con react,
en esta clase vamos a ver algunas cuestiones avanzadas sobre frontend.

Revisemos cuáles son los objetivos de esta clase


1. Aprender qué son los custom hooks.
2. Conocer qué son y cómo funcionan el Lazy Loading y Suspense.
3. Mejorar la modularización de una aplicación por medio de la implementación de
servicios.

Custom hooks
Personalización de hooks
Hasta ahora, sabemos que los hooks son funciones que nos permiten desarrollar tareas
específicas dentro del código. Sea que pertenezcan tanto a react como a otras librerías, nos
sirven para manipular estados, realizar enrutamientos o gestionar efectos en los
componentes.
Además de los hooks predefinidos (built in), podemos crear hooks personalizados.
Estos custom hooks encapsulan la lógica de tareas específicas para facilitar su reutilización
en diferentes partes del código.

Al igual que los hooks que conocemos, los custom hooks, por convención, se crean
utilizando el prefijo “use” seguido de un nombre descriptivo que indique su funcionalidad.
Pero, ¿no basta con los ya conocidos? ¿En qué situaciones conviene crear un hook?
Casos de uso
Los custom hooks tienen muchos tipos de implementaciones. Su objetivo simplemente es
generar bloques de código reutilizables que nos permitan aumentar la eficiencia de la
aplicación. Algunos ejemplos pueden ser...
1. Realizar solicitudes HTTP.
2. Verificar el status de una conexión de red.
3. Autenticar usuarios.
4. Gestionar formularios.
5. Etc.

150
Veamos un ejemplo de esto dentro de un proyecto de react + vite previamente
inicializado.
Hemos visto cómo los custom hooks nos permiten encapsular lógica para facilitar su
reutilización en distintos componentes.
Ahora, ya que estamos trabajando con la carga de componentes y recursos, aprovechemos
para introducir dos nuevas características de react, que mejorarán significativamente el
performance de nuestras aplicaciones: Lazy Loading y Suspense.

Lazy loading & Suspense


Características
Lazy Loading y Suspense son características introducidas en react para permitir la carga
diferida (lazy loading) de componentes y la gestión de la espera (suspense) mientras se
cargan los recursos asíncronos. El uso de ambos nos permite mejorar significativamente el
rendimiento de la aplicación, así como la experiencia de usuario.
REACT.LAZY
Lazy Loading es una técnica que consiste en cargar recursos solo cuando son
necesarios, en lugar de cargar todo al iniciar una aplicación. Esto ayuda
considerablemente al rendimiento de nuestras aplicaciones.

Se utiliza especialmente en proyectos grandes con muchos recursos, reduciendo el


tiempo de carga de la página. En el contexto de react, React.lazy nos permite
importar componentes de forma diferida. Esto significa que el componente se carga
solo cuando se necesita, disminuyendo el tamaño del paquete inicial descargado por
el navegador.

SUSPENSE
Suspense, por su parte, maneja la carga asíncrona de recursos.

Brinda indicadores visuales durante la obtención de datos para mejorar la


experiencia del usuario. Además, proporciona retroalimentación visual sobre el
progreso y reduce la sensación de espera.

Dentro de react, Suspense es un componente que envuelve a aquellos que se cargan


de forma diferida y muestra un componente mientras se cargan los recursos
asíncronos.

151
Tomemos la lógica del renderizado condicional de error para encapsularla en un nuevo
componente LazyDataLoader.
Es probable que el componente de carga sea visible solo por una fracción muy pequeña de
segundo, ya que esta solicitud se resuelve casi de manera inmediata. Cuando la información
solicitada es, por ejemplo, imágenes de alta resolución, su duración en pantalla será más
extensa.
Como último tema de esta clase, y para complementar las buenas prácticas de código,
conozcamos qué son y para qué sirven los servicios en react.

Implementación de servicios
La implementación de servicios se refiere al uso de módulos o clases para manejar la lógica
relacionada a la comunicación con agentes externos, como APIs , bases de datos, servicios
en la nube, etc. En otras palabras, todas las solicitudes realizadas desde nuestra app estarán
centralizadas en archivos denominados servicios.

152
Definamos un servicio para nuestra aplicación
En una nueva carpeta services dentro src, agregaremos un archivo llamado apiService.js en
el que desarrollaremos la lógica de todas aquellas funciones que soliciten información a una
API.

En esta ocasión, haremos uso de la librería Axios, en lugar de Fetch, para las solicitudes. No
olvides instalarla utilizando el comando: npm install axios.

En nuestro ejemplo, no es un componente quien solicita la información sino el custom


Hook useFetch. Para avanzar, haremos algunos cambios para que, en vez de realizar la
solicitud, este hook invoque al servicio correspondiente y construya el estado de respuesta
o error.

Como pudiste ver en el video anterior, el funcionamiento de la página se mantiene intacto,


¡por más que hayamos agregado muchas características de react!

Cierre

153
En conclusión...
En esta clase hemos explorado conceptos avanzados que nos permiten mejorar la
modularidad de nuestras aplicaciones. Gracias a los custom hooks, hemos aprendido a
encapsular y reutilizar lógica compleja de manera sencilla, permitiéndonos separar
preocupaciones.
Mediante el lazy loading y suspense, hemos descubierto cómo optimizar el rendimiento de
nuestras aplicaciones al cargar componentes de forma diferida y mostrar indicadores de
carga mientras llegan los recursos asíncronos.
Por último, mediante la implementación de servicios, hemos aprendido a abstraer la lógica
de comunicación con servicios externos, promoviendo una mejor organización del código y
facilitando su mantenimiento y modularización.

Homework
Te recordamos que las consignas de las últimas homeworks de este módulo las
encontrarás en al final de la clase 12 ¡Buena suerte!

JavaScript Advanced II
Introducción
Para cerrar este módulo vamos a ver un tema que puede marcarse como un eje
transversal de cualquier lenguaje de programación: algoritmos. Veamos en el siguiente
video de qué manera abordaremos esta clase.

Revisemos cuáles son los objetivos de esta clase


1. Aprender qué es la eficiencia y la complejidad de un algoritmo.
2. Conocer e implementar algoritmos para comparar su complejidad.
3. Utilizar la inteligencia artificial para mejorar la eficiencia de un código.

154
Time & space complexity
Eficiencia en programación
Hay dos factores claves a la hora de analizar código: funcionalidad y eficiencia. Pensémoslo
de la siguiente manera:

Cuando hablamos de código, podríamos decir que la diferencia entre un mal código y uno
bueno, es que este último es el que funciona. La correcta funcionalidad de los códigos nos
indica que elegiremos el que cumple con éxito el objetivo para el que fue desarrollado.

Ahora, si comparamos dos buenos códigos, ¿Cuál sería la justificación para determinar el
mejor de ellos? Aquí aparece el término de eficiencia.

La eficiencia es la capacidad de que algo genere el efecto deseado con el mínimo esfuerzo
posible.

Entonces, ¿Cómo identificamos código eficiente? Las métricas para determinar esto se
encuentran bien definidas y evalúan principalmente los siguientes recursos:

1. Complejidad temporal: representa la cantidad de tiempo que requiere un programa


para ejecutarse en función del tamaño de su entrada. Este se mide normalmente en
unidades de tiempo como segundos, milisegundos, etc.

2. Complejidad espacial: cantidad adicional de memoria que utiliza un programa


mientras se ejecuta (incluyendo la memoria asignada para variables, estructuras de
datos, pilas de llamadas, entre otros) en función del tamaño de la entrada.

155
Algoritmos
¿Qué es un algoritmo?
Se entiende por algoritmo a una secuencia ordenada y finita de pasos que permiten realizar
una actividad o tarea. Dichos pasos deben ser sucesivos y exactos.
Un algoritmo debe ser preciso, claro y no ambiguo, de manera que cualquier persona que
siga los pasos correctamente pueda obtener el resultado deseado. No sirve de nada tener
un algoritmo muy eficiente, pero que sea extremadamente complejo.
Un algoritmo podría ser, por ejemplo, las instrucciones para preparar un café...
1. Verificar que la cafetera esté enchufada y encendida.
2. Colocar un filtro de papel en la canasta del filtro.
3. Medir la cantidad deseada de café molido y verterlo en el filtro.
4. Llenar el depósito de agua con la cantidad adecuada de agua.
5. Colocar la jarra de vidrio en su lugar debajo del filtro.
6. Presionar el botón de inicio en la cafetera.
7. Esperar a que la cafetera complete el ciclo de preparación del café.
8. Retirar la jarra de vidrio cuando la cafetera haya terminado de preparar el café.
9. Servir el café en una taza.
A partir de lo que vimos anteriormente podemos decir que la eficiencia de un algoritmo se
mide mediante su complejidad algorítmica, evaluando el tiempo y espacio necesarios en
función del tamaño de la entrada. Esta complejidad, a su vez, se expresa comúnmente con
la notación Big O, que destaca la peor situación posible en relación con el tamaño de la
entrada.
Big O' Notation
La notación Big O se emplea en el análisis de algoritmos para comparar su rendimiento a
medida que el tamaño de la entrada crece. Se expresa como "O(f(n))", donde "f(n)" describe
la máxima cantidad de recursos necesarios en función del tamaño de la entrada "n". Esta
función representa típicamente el peor caso en términos de tiempo o espacio del
algoritmo.

156
Piensa en una receta que consiste en 8 pasos. Esta tendría una notación de “O(8)”. Entre
más pasos tenga la receta, más tiempo tardará en completarse, igual que un algoritmo.
Los algoritmos suelen clasificarse según la notación Big'O que representa su complejidad
algorítmica. Esta clasificación del algoritmo se refleja en la siguiente gráfica…

Cuanto menor sea la complejidad en términos de la notación Big O (zona morada y


amarilla), más eficiente será el algoritmo en términos de tiempo y/o espacio. Para
dimensionar la diferencia entre estas categorías veamos el siguiente recuadro donde
comparamos la cantidad de datos que puede procesar una función en un tiempo
determinado.

Aquí de forma individual cada una de las gráficas. Los valores de “# of boxes” (no le busques
un significado de más, es solo un ejemplo) representan la cantidad de datos de entrada.
Debajo de cada función, estará el tiempo aproximado en segundos, minutos, días o años,
que tardará el algoritmo en completarse, según su complejidad.

157
Es evidente que entre mayor sea la cantidad de datos de entrada, más tiempo tomará en
completarse. Igualmente, entre mayor complejidad (más pronunciada es la gráfica), más
tiempo tardará en terminar.

1. O(1) – Constante. Este tipo de complejidad significa que el tiempo de ejecución (o


espacio en memoria) del algoritmo, no depende del tamaño de la entrada. Por
ejemplo, el tiempo que tardas en encender una bombilla específica de tu casa es
constante, independientemente de cuántas bombillas haya allí.
2. O(log n) – Logarítmica. A medida que el tamaño de la entrada aumenta, el tiempo (o
espacio) necesario para completar la ejecución crece de manera proporcional al
logaritmo del tamaño de la entrada.

158
Como ejemplo, podríamos pensar en buscar un contacto en una guía telefónica. A
medida que la guía telefónica crece en tamaño, el tiempo necesario para encontrar
un contacto aumenta, pero no de manera lineal. Cada vez que divides el directorio
por la mitad y decides en qué mitad continuar tu búsqueda, estás siguiendo un
patrón logarítmico. Al avanzar, el número de páginas que debes revisar disminuye a
la mitad en cada paso.
3. O(n log n) - Lineal logarítmica. La complejidad crece en proporción al producto del
tamaño de la entrada y el logaritmo del tamaño de la entrada.
Pensemos en ordenar una baraja de cartas. El tiempo necesario para ordenar la
baraja de cartas, depende de la cantidad de símbolos en la baraja en relación a la
cantidad de cartas por cada símbolo.
4. O(n) – Lineal. A medida que la entrada aumenta, el tiempo (o espacio) necesario
para completar la ejecución también aumenta de manera proporcional.
Por ejemplo, contar el número de personas en una sala. A medida que el número de
personas en la sala aumenta, el tiempo necesario para contarlas lo hará
proporcionalmente también.
5. O(2^n) – Exponencial. A medida que la entrada aumenta, el tiempo (o espacio)
necesario para completar la ejecución aumenta exponencialmente.
En este caso, un buen ejemplo sería encontrar todas las posibles combinaciones de
un candado de combinación de n dígitos probando todas las combinaciones posibles.
El número de combinaciones aumenta exponencialmente con la longitud de la clave.
6. O(n^2) – Cuadrática. A medida que la entrada aumenta, el tiempo (o espacio)
necesario para completar la ejecución aumenta al cuadrado del tamaño de la
entrada.
Por ejemplo, limpiar una habitación cuadrada barriendo en patrones de ida y vuelta.
A medida que aumenta el área de la habitación, el tiempo necesario para limpiarla
aumenta cuadráticamente.

Algoritmos en código
Los ejemplos mencionados ilustran cómo se aplican las diferentes categorías de complejidad
en la notación Big'O utilizando analogías de la vida cotidiana. Veamos algunos ejemplos más
enfocados ahora en código, exclusivamente, así como el tiempo que tardan en completarse.

159
Si pensamos en una función para imprimir en pantalla el primer elemento de un array,
podríamos pensar en una complejidad constante O(1), ya que en este caso el tamaño de
dicho array sería totalmente indiferente. Solo nos interesa el primero de sus elementos.

Mientras que imprimir cada elemento de ese array podría ser considerado un algoritmo de
complejidad lineal O(n), pues el tiempo es directamente proporcional a la cantidad de
elementos del array.

160
Al comparar ambas funciones, veremos que el tiempo de ejecución aumenta (tal vez no
considerablemente), ya que en el segundo caso debe recorrer el array entero. La eficiencia
se verá afectada por el tamaño del array tal como mencionamos anteriormente.

Algoritmos de búsqueda y ordenamiento


Algoritmos de búsqueda
Los algoritmos de búsqueda son métodos sistemáticos que se utilizan para localizar un
elemento particular dentro de un conjunto de datos, como estructuras de datos. A pesar de
que su eficiencia y complejidad pueden variar, los algoritmos de búsqueda son
fundamentales en la resolución de problemas relacionados con la recuperación de
información.
Pensemos en la búsqueda de un número en un array ordenado. Este algoritmo podría tener
dos diferentes aproximaciones: BÚSQUEDA LINEAL y BÚSQUEDA BINARIA.

BÚSQUEDA LINEAL
La primer aproximación se daría a partir de una búsqueda lineal.
Se recorrerá el array de principio a fin comparando cada elemento con el valor objetivo. En
el peor de los casos, este valor coincide con el último elemento del array, haciendo que el
tiempo de resolución sea proporcional al tamaño de dicho arreglo.

Supongamos que buscamos el número 33:

161
BÚSQUEDA BINARIA
La segunda aproximación permite mejorar la eficiencia de esta tarea. Recibe el nombre
de búsqueda binaria y posee una complejidad logarítmica (log n).

Este algoritmo consiste en encontrar el valor intermedio del array ordenado y compararlo
con el valor objetivo. Si es igual, devolvemos el resultado y finalizamos el proceso. De lo
contrario, tendremos dos casos posibles: que el valor objetivo sea mayor o menor al valor
intermedio.
En ambos casos tendremos dos grupos de datos (mayores y menores al objetivo) y
podremos desechar la mitad de los datos según corresponda. Repetiremos esta operación
tomando el nuevo array de datos hasta encontrar el resultado.

De esta manera lograremos reducir el espacio de búsqueda por la mitad en cada iteración
dividiendo el array en dos partes y seleccionando la mitad que podría contener el valor
objetivo. Esto permite mantener una eficiencia constante sin importar la posición del
número dentro del array.

Supongamos que buscamos el número 8...

Empezamos a entender un poco más cómo funciona y cómo podemos mejorar la eficiencia
de nuestro código. Veamos algunos ejemplos más a través de algoritmos de ordenamiento
de datos.

VIDEO

Algoritmos de ordenamiento
El ordenamiento de datos es una práctica frecuente en la programación. Esta puede ser
abordada de múltiples maneras por medio de algoritmos con diferentes niveles de
eficiencia. A pesar de la amplia variedad de algoritmos de ordenamiento, nos centraremos
en los más comúnmente empleados.
Bubble sort
Bubble Sort compara elementos de a pares y los ordena dentro de un array. Este proceso
se repetirá hasta que los datos estén ordenados y no se realice ningún cambio durante un
recorrido completo del arreglo. Aunque su eficiencia varía según el orden inicial de los
elementos en el array, tiende a tener una complejidad cuadrática debido a la cantidad de
recorridos que pueda llegar a ejecutar. Veámoslo en detalle en el siguiente video...
Es un algoritmo sencillo de implementar. Su problema es que, conforme aumenta la
cantidad de elementos del array y se encuentran más desordenados, la cantidad de
recorridos a realizar aumenta de forma cuadrática.

162
Merge sort
La filosofía de este algoritmos gira en torno al concepto de divide and conquer (divide y
vencerás). Esto significa que la mejor aproximación es segmentar un problema en
problemas más pequeños que sean más fáciles de resolver de manera independiente.
Consta de tres etapas.
1. Divide: Divide el array desordenado en dos sublistas de tamaño aproximadamente
igual.
2. Ordena: Ordena recursivamente cada sublista. Este paso se repite hasta que cada
sublista tenga un solo elemento, lo que se considera que está ordenado.
3. Combina(merge): Combina las sublistas ordenadas para producir una lista única y
ordenada.

Si planteamos este algoritmo en código, el primer paso será recibir un array y fragmentarlo
utilizando una función a la que nombraremos dividir.

Este proceso se hará hasta obtener arrays cada uno con un solo elemento del conjunto
original. Los arreglos serán unificados y ordenados de manera recursiva gracias a la
función merge.

Debido a su enfoque recursivo y la división en sub-listas, merge sort tiene una complejidad
temporal garantizada de O(n log n) en todos los casos. Es mucho más eficiente que el
anterior para ordenar grandes conjuntos de datos. Sin embargo, su principal desventaja es
que requiere espacio adicional para almacenar las sub-listas durante el proceso de fusión.

Eficiencia de algoritmos con ChatGPT


Empleo de IA
Supongamos que nos encontramos con el problema de determinar si existe alguna
combinación de valores dentro de un conjunto de datos que nos permita obtener un valor
específico y nos devuelva un booleano como indicador…

163
Evidentemente es una mejora al algoritmo, pero no sabemos si es la opción más eficiente.
En realidad, ni siquiera sabemos su nivel de complejidad. Podríamos acudir a algunas
fórmulas aritméticas para determinarlo de forma manual o seguramente con la experiencia
podemos asumirlo analizando el código.
¿Y si mejor aprovechamos a la tecnología para esta tarea? Iniciemos una nueva
conversación de ChatGPT y consultemos la eficiencia de nuestra función.
Conversación #1
Revisemos la eficiencia de nuestra función
CHAT GPT
Y aquí veremos que una de las principales ventajas de consultar la eficiencia con ChatGPT,
es que nos desglosa de manera detallada el código para determinar su eficiencia.
Finalmente, la función subSetSum es clasificada como un algoritmo de complejidad
O(m*2m) debido a que el arreglo de sumas se recorre una cantidad de veces proporcional a
la longitud del array. Para no detenernos aquí, veamos si ChatGPT puede darnos una
alternativa para llegar al mismo resultado con una mejor eficiencia.

164
Conversación #2
Veamos ahora si hay alguna forma de mejorar la eficiencia de la función.
CHAT GPT
Cabe resaltar, que es muy importante que al hacer esto, entiendas perfectamente el código
resultante. En muchas ocasiones chatGPT hace uso de algoritmos demasiado complejos que
pueden llegar a ser problemáticos al momento de tratar de solucionar problemas en tu
código.

165
166
167
Cierre
En conclusión...
En esta clase exploramos conceptos fundamentales de algoritmos, desde su significado
formal hasta las consideraciones que debemos tener al implementarlos. Comprendimos la
importancia de la complejidad temporal y espacial para determinar qué algoritmo puede
considerarse mejor que otro en términos de recursos y eficiencia.
Analizamos algoritmos de búsqueda y ordenamiento donde visualizamos las distintas
categorías según la complejidad algorítmica determinada por la notación Big O.
Finalmente, destacamos la eficiencia algorítmica como un criterio clave para evaluar y
mejorar nuestros procesos. Además, logramos integrar a chatGPT como herramienta para
determinar y mejorar la eficiencia de un algoritmo.

168

También podría gustarte