m3
m3
m3
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 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
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.
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…
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.
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....
Resumen de Instalación:
$ npm install -g typescript
$ tsc --init
$ npm install eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin prettier --
save-dev
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"
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.
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
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"]
}
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.
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.
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.
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?
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.
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.
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.
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!
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)[];
// }
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.
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.
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.
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
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".
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.
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.
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.
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';
src/routes/index.ts
import { Router } from 'express';
import { getUsers, createUser, deleteUser } from '../controllers/usersController';
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';
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';
38
// por eso Promise<IUser> como tipo devuelt en la función
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.
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:
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.
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.
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í:
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”.
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”.
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...
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.
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.
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.
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.
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.
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.
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.
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.
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.
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
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:
------------------------
CREAR TABLAS:
CREATE TABLE peliculas (
id SERIAL PRIMARY KEY,
title VARCHAR(255),
year INTEGER,
duration INTEGER
);
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.
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);
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;
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;
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.
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.
Instalar TypeORM
En el terminal de VSC, estando en el directorio /back.
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”…)
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.
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';
@Column({
type: 'enum',
enum: userRole,
default: userRole.USER,
})
user_role: userRole;
// 6 FOR MICROSECONDS
@CreateDateColumn({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP(6)' })
created_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;
RESERVACIONES
import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn, ManyToOne, JoinColumn } from
'typeorm';
import { User } from './User';
// 6 FOR MICROSECONDS
@CreateDateColumn({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP(6)' })
created_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.
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
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
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.
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.
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.
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.`);
}
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
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;
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();
}
};
@Column({
type: 'enum',
enum: userRole,
default: userRole.USER,
})
user_role: userRole;
// 6 FOR MICROSECONDS
@CreateDateColumn({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP(6)' })
created_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[];
}
@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;
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).
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.`);
}
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.
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.
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);
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 },
});
81
CredentialRepository.ts
import { AppDataSource } from '../config/data-soruce';
import { Credential } from '../entities/Credential';
AppointmetRepository.ts
import { AppDataSource } from '../config/data-soruce';
import { Appointment } from '../entities/Appointment';
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.
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.
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.
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)
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 />
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.
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).
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...
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.
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.
Para que el objeto sea único en el renderizado, la propiedad key se pone en el ciclo map.
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.
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 />
</>
);
}
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} />
</>
);
};
User y User2
// src/User.jsx
import { useState } from "react";
const User = ({ handleUserButtonClick }) => {
const [updateData, setUpdateData] = useState("Editando...");
return (
<div>
<h4>User</h4>
102
<button onClick={handleUpdateData}>Actualizar Perfil</button>
<p>Estado del perfil: "{updateData}"</p>
</div>
);
};
// src/User2.jsx
import { useState } from "react";
return (
<div>
<h4>User 2</h4>
<button onClick={handleUpdateData}>Actualizar Perfil</button>
<p>Estado del perfil: "{updateData}"</p>
</div>
);
};
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} />
</>
);
};
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>
</>
);
};
// 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>
</>
);
};
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.
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.
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.
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...
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.
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?
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.
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.
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).
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.
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...
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.
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.
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?
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.
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.
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.
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.
Redux Core
¿Qué es Redux de manera sencilla? https://www.youtube.com/watch?v=j-jzI3wkkVk
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.
STORE
La store en Redux es un objeto centralizado que almacena el estado global de la
aplicación.
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.
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...
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.
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.
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.
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.
SUSPENSE
Suspense, por su parte, maneja la carga asíncrona de recursos.
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.
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.
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:
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…
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.
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.
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.
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.
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.
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