-
Notifications
You must be signed in to change notification settings - Fork 0
Home
Propietario: daniel alvarez Etiquetas: Guías y procesos Fecha: 20 de junio de 2024 GitHub: https://github.com/danipoal/Bootcamp
tail
cat
grep -i 'mundo$' *O buscar con el Ctrl + f*
-i = Insensitive para buscar ya sea mayus o minus
$ = para que esa palabra sea la ultima de a linea
^\d = Empezar por un digito
\s = espacios en blanco
\n = saltos de linea
ñ.* = Cogeria la ñ y todo lo que va despues
[**set](https://www.notion.so/40598087a8f5447eb746acdd9e32d45b?pvs=21) :** [oj] = Que tenga la o o la j pero no juntas
[^o] = Sombrerito dentro de un set, para buscar todo lo que No contiene la o
^[oj] = Todo lo que empieza por o o j
[a-zA-ZñÑ] = Coge todo el abecedario y en mayus, y la ñ porque no esta incluida
o.o = Coge todo lo que sea o_o. Ese _ es cualquiera
\d = [0-9]
\D = [^0-9] Lo que no sean numeros
\b = Para que la palabra se termine justo donde se ponga ese \b
Quantifiers:
* + ?
a{2} = 2 veces a
s.*o = stackoverflow -> Codicioso
Cajas: () Son los parentesis, y luego en el replace con el dolar $1 $2 moviendolos se puede cambiar el orden de esos grupos
(\w+)(\s\w+) -> La primera cajua es $1 y la segunda el 2. 
Table of contents -> Para hacer un index
# Intro
This is my first **HTML project**, where i __try my best at CSS__ without having any model to copy, only my imagination and my, not very devoloped creativity.
The CSS is totaly improvised.
> Una cita
- Una lista
- Un [link](google.com "Texto si te pones encima") en una lista

| Columna1 |Columna2 |
|:---- |:---- |
|A|B| ~ = /c/Users/user *Ese gusanito significa que estamos en esa ruta*
git config --global user.name "Daniel Alvarez" *Para identificarte*
user.mail "" Si lo pones sin --global sale el user/mail que pusiste
git init / [UNTRACKED]
git add -> Se mueve al STAGING AREA [INDEX]
git commit -> Se mueve al repo LOCAL [HEAD] git commit -m "Mensaje"
git push -> Se mueve al repo REMOTO
*code. -> Abre VSC*
Moverse ebtre commits.
git log --oneline -> Para ver los logs pero ordenados.
HEAD -> Es donde esta apuntando la version
Origin -> Es el repo remoto en gitHug
git log --all --decorate --oneline --graph -> Para ver las ramas
**GIT ADOG**
git checkout -> Para moverte entre commits
checkout main -> Para volver BIEN del todo donde estabamos
git show -> Para ver lo que cambia o no como en github
RAMAS:
git branch rama1 -> Para crear una rama. // **git checkout -b rama02** -> Crea la rama y te posiciona. 2x1
git merge -> Para unir ramas // git merge --no-ff ramaX -m"Mensaje"
Tipos de MERGE
-Fast FORWARD o no. Con ff se pasa toda una rama como tal al main al hacer el merge.
Sin ff queda mas limpio y el el graph se veran las ramas una vez mergeado pero peor para moverte
git rebase -> Una manera de juntar el trabajo pero colocando la rama despues de la del main

El head y el origin. Si el origin estuviera en el de abajo, significa que le debemos un push.

- Elementos BLOCK → H1….
- Seria un
display block, que ocupa todo hasta el final horitzontal
- Seria un
- Elementos LINE

<wbr> -> Seria como el salto de linea responsive cuando se hace pequeña la ventanaTemario de David interesante sobre redes
Api útil para ver los forms en mail
Api útil para ver los forms en mail
Juegos Para aprender selectores
Selectores
[href]{} -> Por atributo. Todo lo que tenga href se selecciona
[href^=https]{} -> Todos los que empiecen por https. Se puede con $
[class|= heading-]{} -> Todo lo que contenga en la clase heading-
main > div > h2{} -> Seria escoger a los h2 que estan en un div que estan en un main
main > div > .azul -> Igual que antes pero en vez de h2 los que tengan clase .azul
main + div -> Que esten en el mismo nivel (hermano) Pero coge los div que van despues de un main
main ~ div -> Todos los hermanos h2
Pseudo Clases
:hover
::first line -> Coge lolo la primera linea
div > div > span + code:not(.foo)-> Un code que este dentro de dos div y vaya detras de un span, que no sea de clase foo
:last-of-type :only-of-type, nth-of-type(),
:last-child, :nth-last-child(), ...

Column-rule: !important herencia(initial, inherit)
transform: translateX(50px); → Coge el contenido y lo mueve a la derecha del div como un padding
1em = 16px
margin/border/padding/content
**Para pintar un padding:**
h1{
--padding: 20px; -> Al hacer -- se guarda la variable
padding: var(--padding);
box-shadow: inset 0 0 var(--padding)
}
border: 1px solid blue; -> border-width, border-style, border-color (son tambien shorthands)
Elementos inline se puede margin lateral pero no arriba abajo
width y height -> Content (por defecto un box-sizing: content-box)
Box-sizing: border-box; -> Cambia el heigh i width de content a border+padding+contentoverflow:
Si tu a un div le das height y width, si el contenido se sale se produce un overflow.
Margenes colapsados:
Los margenes cuando chocan siempre se cogera el mas grande
Display:
inline -> ***En el inline, si le pones margin solo afecta los laterales!***
block ->
inline-block -> Sigue inline, pero hace caso arriba y abajp
flex ->
None ->
Outline:
Un borde por fuera del border. No desplaza nada. outline-offset: Lo hace para dentro
Aunque empieza en el border.
outline-offset: Distancia del borde al outline
box-shadow: Se pueden poner varios poniendo *comas*
Position:
static: Valor por defecto. El top, bottom... no funcionan. Todos los demas valores de position no son static
relative: Relativo al posicionamiento inicial. Usar top, bottom.. Cuando este se mueve, nadie ocupa su posicion inicial
absolute: Si se mueve ocupan su posicion inicial, tmbn relativo al padre pero a la izq arriba
Tambien crea por defecto un display block
sticky: Hace falta un top o algoFloat/clear *(No se usa ahora, pero esta bien saberlo x si lo encuentras)*
float: right;
clear: both;
Units:
Absolutas -> CM/PX/.. No cambian segun dispositivos o pantallas
Relativas-> %/ Si cambian
EM -> Se coge el tamaño relativo de el padre directo en cascada
REM -> Se coge el font-size de el navegador, predeterminado a 16px
% -> Coge width pero NO height
ViewPort -> Como el % pero en funcion de la ventana visible del navegador
Height: 100vh - Width :100vw
Mirar vmax Vmin
Color:
Transparent:
Media-Querry:
@media (max-width) and (250px <= width <= 400px)
-
Webs Front Colores/estilos
https://htmlcolorcodes.com/es/

Container:
**display**: flex;
justify content
align-items
flex-direction -> Column / row(default)
gap -> Espacio entre los hijos
wrap -> noWrap(default) / wrap -> No se hacen pequeños al modificar pantalla y se mueve debajo
reverse -> Sube pero arriba
flex-flow = [flex-direction, flex-wrap]
align-content: Como items pero para cuando hay mas de una fila
Child:
ORDER -> order: -1; (Ese iria delante de todos). Si empata el order, va el del html
align-self -> Como el items pero va en el hijo y para ese objecto en concretoContainer:
display: grid;
grid-template-columns: 1fr 2fr 1fr; == 25% 50% 25% de la cuadricula 3 columnas
grid-template-rows: 1fr 50px 10vh 2fr; = 4 columnas
: repeat(5, 1fr)
grit-template: template-rows / template-columns
grid-row-gap:;
Los elementos se van posicionando en la cuadricula. Para posicionarlos menualmente:
Child:
grid-column-start: 1; grid-column-end: 3; Se hace el shorthand debajo/Empieza 1 acaba 3(no incluido)
== grid-column: 1 / 3; == grid-column: 1 / *span* 2; -> El span es no que acabe en x sino que ocupe X
Si pones -1 -> Se selecciona hasta la ultima column/row
GRID-AREA: row-start/column-start / row-end/column-end;
order -> tambien se puede escoger el order de los elementos
0fr -> Hace un fit-contentFrontend Mentor | Front-end coding challenges using a real-life workflow
Web para mejorar el front con ejemplos muy buenos
Hay que tener node y despues npm i -g sass
A continuación tener un archivo .scss en el que puedes anidar selectores tal que:}
div{
.container{
}
}
Entonces instalas un plugin para preveer sass.
Se escribe en el archivo scss. Despues compilas el archivo y automaticamente se introduce
en el archivo css original con:
sass archivo.scss style.css
Si quieres que se compile cada vez que haga un cambio automaticamente
sass -watch archivo.scssPor ejemplo:
<div **id="cerrar-popular"** class="popular-container">
<a class="popular-close" **href="#cerrar-popular"**>
<img style="height: 15px;" src="./images/close.png" alt="Error">
</a>
</div>
#cerrar:target{
display: none;
}Hay que poner siempre el script en la parte inferior de el body para que carguen primero los elementos. Sino se pueden usar defer o async .
console.log(`${variable1} es una variable`);-
String
-
Number
-
Boolean
-
Null → Alguien tiene que ponerlo explícitamente x = null; x= {};
-
Undefined → Una variable sin valor
-
Symbol → const x = symbol(”Hola”);
-
Object → key: value,
- Arrays → let arr = [1,2];
function funcion(){}
Arrow function->
const function = (params = defaultParam) => {}
*//Para poder poner todos los parametros que quieras, con los 3 puntos*
const Multiples params = (**...params**)=> {}
IF EN UNA LINEA:
n1 > n2 ? console.log(n1): console.log(n2);
Operadores:
== -> Compara valores aunque sean string o boolean "true" = true
=== -> Compara todo true != "true"
Para pasar de string a number -> + string
Para pasar de number a string -> "" + number
++numero != numero++ -> Uno suma antes y otro suma despues, si tiene un log se imprime diferente
FOR OF -> Arrays, colecciones...
FOR IN -> propiedades, atributos...
MAP vs FOREACH -> Map devuelve un dato directamente mientras que each simplemente es un forconst array = ["azul", 22, false];
function miFuncion(color : string, edad : number, hombre : boolean){
}
miFuncion(***...array***);Con esto podemos meter un objeto de array directamente en el parámetro y que los elementos de este ocupen sin problema los elementos de 1 en 1 de la función.



array**.filter**(elementos ⇒ condicion) → Se coloca el array, y se generara uno nuevo si se cumple la condición que pongamos, no hace falta poner el if para la condición. No itera.
array**.map**(elem,index,array ⇒ función) → Recorre el array y ejecuta la función para cada elemento sacando luego un return automático. El resultado se guarda en otro array
array**.forEach**(elem,index,array ⇒ función) → Recorre el array como el map, pero no devuelve nada, es un for normal y corriente
numbers**.reduce**( (accumulator, currentValue) => accumulator + currentValue , 0); → Recorre el array y hace la función que le digas para todos los números. En cada iteración se coloca en current y después el acumulador es el total. En este caso se suma y el 0 es el valor inicial de acumulador.
Para importar jQuery, hay que descargar el archivo .js que seria como una librería, e importarlo en el index .html
Si pones document en el console, te coge da el html
DOM / BOM
JS
Seleccionar elementos en js -> querrySelector() / getElementBy()
const parafo = document.querrySelector(".java / #id"); //Pero solo coge una cosa, si hay mas hay q susar qS..All
Editar el texto de HTML con js -> textContent / innerHTML - *Van sin ()*
parafo.innerHTML = "<h1>Añadimos html a saco</h1>";
parafo.textContent = array;
Obtener valores ->
De un input -> input.value *Es su atributo .value
TOGGLE ->* Para añadir clases a elementos de el html *No hay toggle directo, se hace a mano*
myButton.classList.contains('activo') ? myButton.classList.remove('activo') : myButton.classList.add('activo');
-> myButton**.classList.toggle**('nombreClase') - *Asi si que se podria directamente*
**Para cambiar css desde JS** -> **elemento.style*.propiedadCSS*** = x
-> *main.style.backgroundColor = color.value -* Se cambia a traves de el valor de un input
JQUERY
Seleccionar elementos en js -> $()
const articles = $('article'); -> Por tag name
$('#id_XX');
$('.clase');
Editar el texto de HTML con jQuery -> html() / text() - *Van con parentesis*
articles.html("El articulo x es como..."); Cambia el contenido
articles.text("El texto es este");
parrafo.style.backgroundColor("red");
Obtener valores
De un input -> .val()
Creando elementos:
document.createElement - .createTextNode
Para añadir/quitar una clase
.toggleClass('clase')
element**.addEventListener**(event, function, options) → Se añade al elemento un escuchador en el que para el evento {’click’ o ‘mouseover’ o ‘’}, ejecutará la función. En la parte de la función abrir { } y el options es opcional. *En jQuerry es **.on()***
input.addEventListener('keydown', (***evento***) ⇒{}) → También se puede hacer un Listener cuando pulsamos una tecla. En este caso, habrá que señalar posteriormente que tecla ha sido event.key
Para interactuar con la API de Pokemon:
Viene a ser un JS pero con tipado fuerte. Se tiene que compilar a JS para que lo ejecute el navegador.
Para instalar → npm i -g typescript
Comprobar npm root -g → ruta
Para ver versión compilador → tsc -v
Compilar → tsc archivo.ts
Configuración ts → tsc --init
Compilación constante → tsc --watch
Cuando cogemos los elementos de un querySelector, tenemos que definirlos de el tipo que son para evitar luego problemas en tiempo de programación.
Ejemplos:
let consoleOutput = document.getElementById('output') as **HTMLParagraphElement**;
let consoleInput = document.getElementById('input') as **HTMLInputElement**;
const canvas = document.getElementById('canvas') as **HTMLDivElement**;También en las funciones hay que especificar el tipo de cada parámetro y cada return .
const randomString = (frases : string) : **number** => {
let numero = +"12"; //Con el operador + se comvierte un string en un numero
return numero;
}Como añadir propiedades nuevas a los elementos del DOM o a objetos:
Los elementos como HTMLImageElement tienen propiedades propias como que tienen valores por defecto o se pueden crear nuevas propiedades en función de las necesidades.
Propiedades ya creadas:
p.textContent = "x";
Propiedades personalizadas:
*// Definimos una interfaz que extienda HTMLImageElement para incluir isSelected como atributo*
**interface SelectableImageElement extends HTMLImageElement {**
**isSelected**: boolean;}
const cartas = document.querySelectorAll(".carta");
cartas.forEach((carta){
const selectableCarta = carta as SelectableImageElement; *//Aplicamos la interfaz para que disponga de isSelected*
selectableCarta.isSelected = false; *//Decimos que inicialmente es false*Hay que descargar postman y revisar que el get da el json correctamente, en este iremos a la sección de codeSnippets para obtener el código mas sencillo que nos devuelve dicha información. Hay opciones para todos los lenguajes
Código básico de fetch ts:
*//La interfaz es opcional de typescript, pero asi es la estructura de lo que devuelve la API*
interface JokeResponse {
categories: string[];
created_at: string;
icon_url: string;
id: string;
updated_at: string;
url: string;
value: string;
}
let jsonResponse: JokeResponse | null = null; *//Declaramos el objeto de la interfaz con todo null de momento*
**const requestOptions: RequestInit = {
method: "GET",
redirect: "follow" as RequestRedirect};
fetch("https://api.chucknorris.io/jokes/random", requestOptions)
.then((response) => response.json())
.then((result: JokeResponse) => saveResult(result))
.catch((error) => console.error(error));**
const saveResult = (json : JokeResponse) => {
jsonResponse = json;
console.log(jsonResponse);}-
requestOptions: Es un objeto de configuración que contiene las opciones que se utilizarán en la solicitudfetch. Se coloca en el segundo parámetro de este. -
RequestInit: Se declara explícitamente comoRequestInit, que es una interfaz en TypeScript que define las opciones para una solicitud defetch. -
method: "GET": Especifica que el método HTTP de la solicitud seráGET. Esto significa que estamos solicitando datos del servidor sin enviar ningún dato en el cuerpo de la solicitud. -
redirect: "follow" as RequestRedirect: Especifica cómo se deben manejar las redirecciones. El valor"follow"indica quefetchseguirá automáticamente cualquier redirección. Se está utilizandoas RequestRedirectpara asegurar que el valor es de tipoRequestRedirect.
-
fetch(url, options): Se llama a la funciónfetchcon dos argumentos:-
url: La URL del recurso al que se quiere acceder, en este caso,"https://api.chucknorris.io/jokes/random". -
options: El objetorequestOptionsque contiene las configuraciones de la solicitud. Es opcional.
-
-
then((response) => response.json()):fetchdevuelve una promesa que se resuelve con un objetoResponse. Aquí, estamos utilizando el método.json()del objetoResponsepara convertir la respuesta en formato JSON. Esto también devuelve una promesa que se resuelve con el contenido del JSON. -
then((result: JokeResponse) => saveResult(result)): Una vez que la promesa de.json()se resuelve, se llama a esta funciónthen, que recibe el resultado del JSON (de tipoJokeResponse). Luego, pasa este resultado a la funciónsaveResult.
Al ser un ejemplo tan sencillo, he colocado el console.log() dentro de la función, ya que si se coloca fuera este dará undefined. Esto es debido a que el fetch tarda mas de lo que se tarda en llamar al log y se ejecuta antes de que se haya guardado el valor.
-
Funciones y utilidades
.thenes la manera de hacer una respuesta asíncrona, y que cuando termine la función que va delante, haga lo que hay en su callback function. Se pueden encadenar, y cada .then recoge el valor que retorna el anterior.
Bulma → Moderno y sencillo
Materialize → Estilos google
Foundation → Plantillas
Tailwind → Templates + CSS en clases
**Bootstrap** → Componentes
Nes.css → Pixel Art
-
Set up en local npm
npm init -y→ Para generar el json de dependenciasnpm i bootstrap→ Para generar el node_modules - Hay que referenciar en.gitIgnoreLas dependencias estarán en los archivos
package.jsony ellock. En este cuando en una versión salga ^, significa que esa no se actualizará.En el HTML → importar
<link rel="stylesheet" href="./style.css"> <script src="../../node_modules/bootstrap/dist/js/bootstrap.bundle.js"></script>
En el SCSS → Si quieres editar el propio contenido Bootstrap
@import '../../node_modules/bootstrap/scss/bootstrap.scss'; //Aqui modificaremos el codigo scss y se procesara para los cambios en css @import '../../node_modules/bootstrap/scss/functions'; @import '../../node_modules/bootstrap/scss/variables'; @import '../../node_modules/bootstrap/scss/mixins';
-
Set Up CDN
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous"> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
Media Queries mixins
//Uso :
@include media-breakpoint-only(sm){ -> Aplica solo en este punto
body{
background-color: red;
}
}
media-breakpoint-beetwen(md, lg) -> Entre estos dos tamaños
media-breakpoint-up(md) -> A partir de el breackpoint hasta infinito
media-breakpoint-down(md) -> Hasta este punto inclusive

Como podemos ver, si definimos un sm, este va desde los 566 hasta el infinito si no subimos

Hay containers que ocuparan casi todo su padre menos algunos pixeles y container-fluid que ocupa 100%
La paleta de colores se puede modificar ya que los valores que tienen son !default, osea que si no se asignan toman el valor que le daremos.
CLASES PREDETERMINADAS:
text-muted -> Da como una pequeña opacidad
display-1 -> Como si hucieras un h1 sin ponerlo
text-center -> align-text
list-unestyled -> style:none en la lista
opacity-25 -> Opacidas
bg-primary - bg-warning - bg-danger -
me-1 ms-1 mx/my ... pe/ps/px/py ->
align-self-center -> (Hay que darle tamaño al **row**)
jsutify-content-center -> La row no debe estar ocupada al completo Grid System
- Hacemos un
.container - div con
row - div con
col→ Se dividen en 12. Si se declaran 2 pues cada una ocupa 6/12
Borders:
Hay que añadir border
Después puedes usar los demás elementos : *border-3 border-warning rounded*
<div class="container">
<div class="row">
<div class="col">Ocupa 1/12</div>
<div class="col">Ocupa 1/12</div>
<div class="col-xl-10 col-2">Ocupa 2/12 y en mediaQuery xl 10/12</div>
<div class="col-2 me-1 mx-1">Ocupa 2/12 con 1 columna de **margin** en end y start</div>
<div class="col">Ocupa 1/12</div>
</div>
</div>
Si tiene tamaño como col-2 y le añades margin, es probable que haga wrap y desborde
Si solo tiene col sin definir lo que ocupa, no hace wrap
- col-xl-10
- col-auto → *Ocupa todo el espacio que necesite el contenido*
- order-1
- offset-4 -> Ese elemento dejara delante un espacio de 4 **vacio**
- Gutter = *gy gx con valores de 0 a 5 y deja espaciado como gap [Rarete no va mu bien]*Dimensiones
- h-25 - h-50 → Ocupa el % del padre
- w-50 → Igual pero se hace en container
Imágenes
Con container se hace responsive, con container-fluid se centra + responsive
img-fluid img-thumbnail
float
table table-info table-striped table-hover table-active table-border
d-none d-sm-block
Se trata de un botón o que abre una ventana emergente ya sea para login o algún formulario.
<!-- Button trigger modal -->
<button type="button" class="btn btn-primary" data-bs-toggle="modal" **data-bs-target="#exampleModal**">
Launch demo modal
</button>
//El ID tiene que coincidir
<!-- Modal -->
<div class="modal fade" **id="exampleModal"** tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="exampleModalLabel">Modal title</h1>
<button type="button" class="btn-**close**" data-bs-dismiss="modal" aria-label="Close"></button>
</div> //ahi esta el boton para cerrar, en el mismo div que el titulo
<div class="modal-body">
...
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary">Save changes</button>
</div>
</div>
</div>
</div>-
Ejemplo de validación
<form class="row g-3 needs-validation" novalidate> <div class="col-md-4"> <label for="validationCustom01" class="form-label">First name</label> <input type="text" class="form-control" id="validationCustom01" value="Mark" required> <div class="valid-feedback"> Looks good! </div> </div> <div class="col-md-4"> <label for="validationCustom02" class="form-label">Last name</label> <input type="text" class="form-control" id="validationCustom02" value="Otto" required> <div class="valid-feedback"> Looks good! </div> </div> <div class="col-md-4"> <label for="validationCustomUsername" class="form-label">Username</label> <div class="input-group has-validation"> <span class="input-group-text" id="inputGroupPrepend">@</span> <input type="text" class="form-control" id="validationCustomUsername" aria-describedby="inputGroupPrepend" required> <div class="invalid-feedback"> Please choose a username. </div> </div> </div> <div class="col-md-6"> <label for="validationCustom03" class="form-label">City</label> <input type="text" class="form-control" id="validationCustom03" required> <div class="invalid-feedback"> Please provide a valid city. </div> </div> <div class="col-md-3"> <label for="validationCustom04" class="form-label">State</label> <select class="form-select" id="validationCustom04" required> <option selected disabled value="">Choose...</option> <option>...</option> </select> <div class="invalid-feedback"> Please select a valid state. </div> </div> <div class="col-md-3"> <label for="validationCustom05" class="form-label">Zip</label> <input type="text" class="form-control" id="validationCustom05" required> <div class="invalid-feedback"> Please provide a valid zip. </div> </div> <div class="col-12"> <div class="form-check"> <input class="form-check-input" type="checkbox" value="" id="invalidCheck" required> <label class="form-check-label" for="invalidCheck"> Agree to terms and conditions </label> <div class="invalid-feedback"> You must agree before submitting. </div> </div> </div> <div class="col-12"> <button class="btn btn-primary" type="submit">Submit form</button> </div> </form>
Generador de datos → https://generatedata.com/
root admin
Reverse engineer → Para ver el UML
Unsigned → Quita todos los valores negativos y los coloca encima como positivos, duplica los enteros
DESCRIBE tableName → Para ver todo lo que contiene SHOW COLUMNS FROM tableName
MaxSize → SHOW VARIABLES LIKE ‘max_allowed_packet’ El máximo de peticiones
| Categoría | Comandos Principales |
|---|---|
| DDL (Data Definition Language) | CREATE, ALTER, DROP, TRUNCATE, RENAME |
| DML (Data Manipulation Language) | SELECT, INSERT, UPDATE, DELETE |
| DCL (Data Control Language) | GRANT, REVOKE |
| TCL (Transaction Control Language) | COMMIT, ROLLBACK, SAVEPOINT, SET TRANSACTION |
Queries
INSERT INTO mydb.tableName VALUES (1, 'Adrian', DEFAULT);
-> Sabes que atacaras 100% a mydb, te curas en salud
INSERT INTO mydb.tableName(nombre) VALUES ('Adrian');
-> Si id AUTOINCREMENT y DEFAULT es Spain, adrian tendra esos campos igual
ALIAS:
SELECT columnaName AS **columnNombre** FROM mydb
-> En la vista se vera el **alias**, se puede hacer sin el AS
TRUNCATE:
-> Como el delete pero resetea los id de a tabla
OPERADORES:
SELECT * FROM mydb WHERE columnName **LIKE** 'Dan%l';
-> Encuentra lo que empiece por DAN y acabe por l
SELECT * FROM mydb WHERE columnId **BEETWEEN** 100 AND 200;
IN:
-> Seria como el or pero puede ir en parentesis un array de posibilidades.
SELECT * FROM mydb WHERE columnId **IN** (200,120); *200 OR 120*
SUBQUERY:
SELECT * FROM myTable WHERE id **IN** (SELECT id FROM myTable WHERE country = 'Germany' OR country = 'Turkey');
-> Encuentra todo where el id solo tenga los paises de alemania...
**ORDER BY** -> LIMIT y OFFSET *'El offset es para especificar donde empieza a contar'*
**GROUP BY** -> Necesita una operación en el select.
SELECT COUNT(personas_Name) FROM tabla GROUP BY oficios;Dentro de un select se pueden poner operaciones entre (id + 200).
Relations
JOIN:
-> Va antes de el where y contiene un on para matchear las columnas de diferent tables
INNER-> SELECT * FROM table1 **INNER JOIN table2 ON table1.id = table2.fk_Id**
-> Para juntar dos tablas relacionadas con fk
OUTER *(LEFT, RIGHT, FULL)*-> TableA y TableB : (LEFT junta en la A), (RIGHT en la B), (FULL junta todo)
-> Se mantienen las columnas en la que se junta
SELECT * FROM employees **LEFT JOIN** Buildings ON Employees.building = Buildings.Building_name WHERE Employees.name NOT NULL;
UPDATE:
-> Actualizar registros.
UPDATE tabla SET nColumna = 'valor' WHERE nColumna = 'Paco'
UNION: Permite unir tablas que no tienen ninguna relaciónTransactions
SHOW VARIABLE WHERE Variable_name = 'autocommit'; *Ademas es uno de los botones de arriba*
-> SI esta en ON: se commitea todo lo que se hace
-> Si esta en OFF o 0: con el COMMIT despues de un Begin
BEGIN
-- operaciones (un delete)
**COMMIT o ROLLBACK**byte→ 256 valores posibles = {-128, 127} - 8 bits en binario
short → {-32.768, 32.767}
long → Se pone una L al final de el valor
continue → Saca de esa iteración pero solo esa y sigue iterando las demás
break → Te saca y no hace ninguna iteración restante
Lo mismo pasa con &&. Si el primero es false ya no se evalúa nada mas.
Los Wrappers son clases que envuelven a los datos primitivos que nos permiten tratar a estos como objetos y por tanto usar sus funciones.
Clases abstractas → clase base/padre que no se puede instanciar.
Enum → Para constantes
public enum Futbolista{
DEFENSA,
DELANTERO,
PORTERO
} //Se podria usar para un checkbox que tiene 4 valores
Futbolista ramos = Futbolista.DEFENSA;Maps
Map keySet → Objeto como un [] infinito que contiene objetos clave-valor.
public String allNames(@RequestParam Map<String, String> parametros) {
String output = "";
for(String key : parametros.keySet()) {
output += "Clave: " + key + " Valor: " + parametros.get(key) + "<br>";
}Sabemos que la clave y valor son Strings porque se define <String,String>
Para crear → Map<String, String> map = new HashMap<>();
Para recorrer → parametros.keySet() y después para el valor objeto.get(key)
Este for se trata de un forEach en java: (llave: llaves)
Creamos la conexión con la BD
Para interactuar con ella siempre hay que crear un Statement a partir de la conexión.
JFrame → La ventana en la que meteremos todo.
Es invisible y sin tamaño, así que hay que darle dichas características.
setVisible, setSize, setTitle, setLocation, setBounds, setResizable, …
Layout:
Frame → .setLayout(new BorderLayout()); + frame.add(objeto, BorderLayout.NORTH)
Capex gasto de golpe
Opex gasto de suscripción
TCO total cost ownership, lo que cuesta mantener un negocio
-
Set Up TODO
Spring Boot tools para eclipse en el market place, y puedes usar un initialzr o directamente en el propio eclipse.
Hay que descargar el archivo binario de maven y guardar el maven home
Después en eclipse → Windows/Preferences/Maven/Instalations → Añadir el directorio de el binario descargado para que sea un maven general.
En el mismo sitio, en Maven/User settings → Hay la ruta de el archivo donde se guardan las dependencias de maven .m2/dependencies.
Loggers
private static final Logger LOGGER = LoggerFactory.getLogger(IniciadoAquiApplication.class);
LOGGER.info("Mensaje");En application.properties →
logging.level.root = OFF / TRACE / INFO
logging.level.com.example = x
logging.file.name = “C:/ruta/archivo.log” → Se llena ese archivo con todo un log
logging.pattern.console = “” → Para identificar como quieres que se vean los logs .console o .file
server.port = 1212Swagger → https://swagger.io/
JSON Crack → 🌴 Para ver el esquema árbol de la información de un json

CheatSheet de Revel para Spring Annotations
@SpringBootAplication → 3x1
El ComponentScan analiza el nombre de los paquetes→ com.example.nombrePaquete
Lo primero que se inicia es el DispatcherServlet después el controller que hagamos
Para el controlador:
La clase la hacemos con la anotación del controlador que escojamos.
@Controller / @RestController (+ResponseBody)
A continuación, se hará una función que devuelva un String con la anotación de el request del endpoint que decidamos. Se podría usar modelAndView pero es antiguo, se podría poner un http estatus manualmente.
**@Controller**
public class HomeController {
@RequestMapping("/")
public String index() {
return "index.html"; //La web estaría en resources/static
}
**@ResponseBody**
**@RequestMapping**(path = "/json", produces= "application/json")
public String indexJson() {
return "{\"name\": \"Dani\"}";
}
}En el RestController, cuando devuelve un ResponseBody, significa que no devolverá una pagina web, sino un body con un json u otra información. Este se puede poner en la clase o en la función.
JSON → En el return hay que usar la ** para escapar las comillas e indicar que es un json con un produces. También se puede hacer un objeto envolvente
-
JSON a partir de objeto encapsulado
Creamos la clase y luego la introducimos en la función de el controlador
@ResponseBody @RequestMapping(path = "/tx") public TextToJson textJson() { return new TextToJson("aa"); }
package com.example.pojos; public class TextToJson { public TextToJson(String message) { this.message = message; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } private String message; }
Para mapear el Json se puede hacer a través de: Jackson, Gson, Json-b. (json formatter en el navegador en Chrome Marketplace)
Para especificar el status code se hace a través de un ResponseEntity . Esto se puede hacer con una función que retorne o directamente con una anotación.
-
Código ResponseEntity
@RestController @GetMapping("/") public ResponseEntity<TextToJson>(){ TextToJson recurso = new TextToJson("mensaje"); return ResponseEntity.status(HttpStatus.I_AM_A_TEAPOT).body("recurso"); } //Devuelve un 418 con el body de el json que le hemos pasado ***//or (+ las anotaciones de antes)*** **@ResponseStatus**(HttpStatus.ACCEPTED) public String respuesta(){ return "web.html" }
En el RequestMapping puedes poner dos valores o mas y servirán para esos 2 endpoint . De manera que → RequestMapping(value = {”/junio”, “/agosto”})
Parámetros
Usamos la anotación @RequestParams y estas se pueden recibir a través de un GET o POST.
@RequestMapping("/welcome")
@ResponseBody
public String getWelcome(
**@RequestParam**(required = false , defaultValue = "Desconocido") String name)
{
return "Hola " + name;
}Cuando llegue una url con localhost/8080/welcome?name=algo → retornara el “hola algo”
Para que se redirija a esta url, lo mas sencillo es usar formularios, en este caso usar uno con método GET y con el input name=”nombre parámetro función”. Finalmente un botón submit.
-
Ejemplo POST
<form action="/login" method="post"> <label for="user">User</label> <input id="user" name="user" type="text"> <label for="password">Password</label> <input type="text" name="password" id="password"> <button type="submit">Enviar</button> </form>
@RequestMapping("/login") @ResponseBody public String login(@RequestParam String user, @RequestParam String password) { return "Hola " + user + " Password: " + password; } }

En postman parece que se pasan los parámetros como un get, pero en el navegador se pasa internamente
-
Parámetros infinitos
Se trata de poder pasar tantos parámetros como quieras
**@GetMapping**("/allNames") **@ResponseBody** public String allNames(**@RequestParam** Map<String, String> parametros) { String output = ""; for(String key : parametros.keySet()) { output += "Clave: " + key + " Valor: " + parametros.get(key) + "<br>"; } return output; }
Después tenemos @PathVariable que se usaría para dar una URL dinámica en función de cada caso específico. Este también se pasa por parámetro.
@GetMapping("/users/{usuario}")
@ResponseBody
public String userHomePage(**@PathVariable** String usuario) {
return "Hola " + usuario;
}Esta anotación también puede ser usada así @PathVariable(name = id) Optional<Integer>id
Para trabajar con ello tenemos que instalar las dependencias de → MySQL Driver, Spring Data JDBC y Spring Web
Hay que asignar la URL a la DB con user y password para que se pueda iniciar lo aplicación, sino nos dará un error.
spring.datasource.url = jdbc:mysql://localhost/nombre_db
spring.datasource.username = root
spring.datasource.password = admin
spring.datasource.driver-class-name = com.mysql.cj.jdbc.Driver
Posteriormente usaremos un objeto JdbcTemplate para interactuar con la información de la BD SQL.
-
Crear la clase con JdbcTemplate y su constructor
@RestController public class AgendaController { @Autowired JdbcTemplate jdbcTemplate; // Sin el autowired seria: JdbcTemplate jdbcTemplate = new JdbcTemplate() public AgendaController(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } }
-
Iniciar conexión ejecutando la querry
@GetMapping("/allContacts") public List<String> getAllContacts() { final String QUERRY = "SELECT * FROM AGENDA;"; List<Map<String, Object>> allContacts = jdbcTemplate.queryForList(QUERRY); return null; }
-
Iteración del ResultSet para convertirlo en algo printeable
List<String> stringList = new ArrayList<String>(); ***//Aqui pasaremos items de una lista List<Map<String, Object>> a List<String>*** for(Map<String, Object> stringItem : allContacts) { stringList.add(stringItem.toString()); }

Será como un array de Strings, por lo que no puede ser accedida como un json
Ante este problema, surge la necesidad de usar un pojo para que el output sea un JSON
-
Iteración a JSON usando POJOs
-
BD usada
CREATE SCHEMA Agenda; USE Agenda; CREATE TABLE IF NOT EXISTS Agenda.agenda ( id_contacto INTEGER UNSIGNED AUTO_INCREMENT PRIMARY KEY, nombre VARCHAR(20), apellido VARCHAR(50), telefono INTEGER);
-
Pojo usado
package com.example.entity; import java.io.Serializable; public class Contacto implements Serializable{ private Long id; private String nombre; private String apellidos; private int telefono; private static final long serialVersionUID = 1L; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getNombre() { return nombre; } public void setNombre(String nombre) { this.nombre = nombre; } public String getApellidos() { return apellidos; } public void setApellidos(String apellidos) { this.apellidos = apellidos; } public int getTelefono() { return telefono; } public void setTelefono(int telefono) { this.telefono = telefono; } public Contacto(int id, String nombre, String apellidos, int telefono) { super(); this.id = id; this.nombre = nombre; this.apellidos = apellidos; this.telefono = telefono; }}
*// Hacer que salga como objeto por el pojo que creara Json* List<Contacto> **contactList** = new ArrayList<Contacto>(); for(Map<String, Object> row : allContacts) { Contacto contacto = new Contacto(); //Para id int id =(Long) row.get("id_contacto"); contacto.setId(id); //Para los demas contacto.setNombre((String) row.get("nombre")); contacto.setApellidos((String) row.get("apellido")); contacto.setTelefono((int) row.get("telefono")); *//Añadimos el objeto seteado a la lista creada anteriormente* contactList.add(contacto); }

Esta vez tenemos el output en JSON para poder fetchearlo correctamente en el frontEnd, y así comprobar que es lo que tenemos en la BD.
-
POJOS
Un Bean es un pojo pero un pojo no es un bean. Este es mas específico.
Debe implementar la interfaz Serializable, la cual dará un ID de versión para cada Bean.
En caso de que no devuelva nada se puede devolver el ResponseEntity con noContent
-
LOMBOK → Se usa para no tener que escribir getters/setters… Se hace automático
Pasos de instalación:
- Añadir dependencia que se encuentra en mvnrepository en Project Lombok
- Una vez añadido al pom.xml, buscar la carpeta /user/.m2/lombok
- Ejecutar el archivo .jar con
java -jar .\lombok-1.18.34.jare instalar - Reiniciar Eclipse y en
Project/UpdateMavenProject→ force
Ahora al crear un pojo/entidad/bean no será necesario escribir todos los getters, etc.. Simplemente con las anotaciones de Lombok se omiten.
-
@NonNull @Cleanup @Getter/@Setter @ToString @EqualsAndHashCode @NoArgsConstructor @RequiredArgsConstructor @AllArgsConstructor @Data @Value @Builder @SneakyThrows @Synchronized @Locked @With @Getter(lazy=true) @Log
Ejemplo: Este código sustituiría al Pojo que hay mas arriba
@Data public class Contacto implements Serializable{ private Long id; private String nombre; private String apellidos; private int telefono; private static final long serialVersionUID = 1L; }
Se añaden las dependencias en el pom.xml
Hay 3 pasos → Se crean ENTIDAD - REPOSITORIO - CONTROLADOR
-
La Entidad
Creamos lo que vendría siendo los elementos del SQL.
**@Data @Entity** **@Table**(name = "Agenda") public class ContactoJPA implements Serializable{ private static final long serialVersionUID = 1L; **@Id @GeneratedValue**(strategy = GenerationType.IDENTITY) **@Column**(name = "id") //No obligatorio, solo si el atributo se llama diff. public int id; }
-
El repositorio → Consta de la interfaz y el servicio
Interfaz:
public interface ContactoRepositoryJPA extends JpaRepository<ContactoJPA, Integer>{}
Servicio: Consta de todos los procesos CRUD prehechos
@Repository public class ContactService { private ContactoRepositoryJPA contactoRepositoryJPA; }
-
Controlador
@RestController public class AgendaControllerJPA { //Se llama al service final ContactService contactService; //Constructor que tiene el service public AgendaControllerJPA(ContactService contactService) { this.contactService = contactService; } //TODO Hay que hacer los metodos de el controlador en JPA }
DTO data transfer object
Seria como hacer un pojo pero con lo que quieres de una entidad, o de mas de una entidad
modelMapper es una dependenciay con un DTO
Se hace una lista en el service de dtos que quieres u para ir añadiéndolo se hace el for in automáticamente modelMapper.map()
El nombre de atributo de el DTO tiene que llamarse exactamente igual que el de la Entidad
Relaciones
@ManyToMany → Así se saca en el Json información de una tabla y otras.
Microservicios
Vamos a implementar la funcionalidad de registro de usuario con la estructura típica en una aplicación Spring Boot: modelo (entidad), repositorio, servicio y controlador.
-
Capa de Modelo (Entidad):
- Representa la tabla de la base de datos como una clase Java.
- Contiene los atributos del usuario y sus relaciones con otras tablas.
- Usaremos JPA para mapear automáticamente la entidad a la base de datos.
- Con Lombok, simplificamos el código eliminando getters, setters y constructores manuales.
-
Capa de Repositorio:
- Es la interfaz que conecta con la base de datos para operaciones CRUD (crear, leer, actualizar, eliminar).
- JPA simplifica las consultas comunes y permite personalizar otras más complejas.
- Con esta capa, no necesitamos escribir código SQL directamente.
-
Capa de Servicio:
- Contiene la lógica de negocio. Aquí validamos los datos, verificamos condiciones (como que el correo no exista) y gestionamos tareas complejas antes de interactuar con el repositorio.
-
Capa de Controlador:
- Expone los endpoints que el cliente (frontend, Postman, etc.) utiliza para interactuar con la aplicación.
- Recibe las solicitudes HTTP, llama al servicio y devuelve la respuesta al cliente.
-
Lombok:
-
Reduce código repetitivo (getters, setters, constructores, etc.).
-
Mejora la legibilidad del código.
-
Ejemplo:Esto genera automáticamente los métodos
getName(),setName(String), y un constructor vacío.java Copiar código @Getter @Setter @NoArgsConstructor public class User { private String name; private String email; }
-
-
JPA:
-
Se encarga de mapear las entidades a tablas en la base de datos.
-
Permite realizar operaciones CRUD sin escribir SQL.
-
Ejemplo:Esto crea automáticamente la tabla
Useren la base de datos con columnasid,name, yemail.java Copiar código @Entity public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; private String email; }
-
-
Crea la clase
Useren el paqueteEntities:java Copiar código package com.hackathon.finservice.Entities; import jakarta.persistence.*; import lombok.*; @Entity @Table(name = "users") // Nombre de la tabla en la base de datos @Getter @Setter @NoArgsConstructor @AllArgsConstructor @Builder public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) // Genera ID automáticamente private Long id; @Column(nullable = false) // Campo obligatorio private String name; @Column(nullable = false, unique = true) // Debe ser único private String email; @Column(nullable = false) // Se almacenará la contraseña hasheada private String password; @Column(nullable = false, unique = true) // Debe ser único private String accountNumber; @Column(nullable = false) private String accountType = "Main"; // Tipo de cuenta por defecto }
-
@Entity: Marca la clase como una tabla en la base de datos. -
@Table(name = "users"): Opcionalmente da un nombre específico a la tabla. -
@Idy@GeneratedValue: Declaran la columnaidcomo clave primaria y generan valores automáticamente. -
@Column: Configura los atributos como columnas, marcando restricciones comonullableounique.
-
@Gettery@Setter: Generan automáticamente los getters y setters. -
@NoArgsConstructory@AllArgsConstructor: Generan constructores vacío y con todos los argumentos. -
@Builder: Permite crear objetos con un estilo fluido:java Copiar código User user = User.builder() .name("Nuwe Test") .email("nuwe@nuwe.com") .password("hashedPassword") .accountNumber("19b332") .build();
-
Crea la interfaz
UserRepositoryen el paqueteRepositories:java Copiar código package com.hackathon.finservice.Repositories; import com.hackathon.finservice.Entities.User; import org.springframework.data.jpa.repository.JpaRepository; import java.util.Optional; public interface UserRepository extends JpaRepository<User, Long> { Optional<User> findByEmail(String email); // Busca usuario por email }
-
JpaRepository: Proporciona métodos comosave,findById,findAll, ydeleteautomáticamente. - El método personalizado
findByEmailusa la convención de nombres de JPA para generar automáticamente la consulta.
-
Crea la clase
UserServiceen el paqueteService:java Copiar código package com.hackathon.finservice.Service; import com.hackathon.finservice.Entities.User; import com.hackathon.finservice.Repositories.UserRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.UUID; @Service public class UserService { @Autowired private UserRepository userRepository; public User registerUser(User user) { // Validar que el correo no exista if (userRepository.findByEmail(user.getEmail()).isPresent()) { throw new IllegalArgumentException("El correo ya está registrado"); } // Generar número de cuenta único user.setAccountNumber(UUID.randomUUID().toString().substring(0, 6)); // Guardar usuario return userRepository.save(user); } }
- Contiene la lógica de negocio: Validación y generación del número de cuenta.
- Interactúa con el repositorio para guardar al usuario.
-
Crea la clase
UserControlleren el paqueteControllers:java Copiar código package com.hackathon.finservice.Controllers; import com.hackathon.finservice.Entities.User; import com.hackathon.finservice.Service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/api/users") public class UserController { @Autowired private UserService userService; @PostMapping("/register") public ResponseEntity<?> registerUser(@RequestBody User user) { User registeredUser = userService.registerUser(user); return ResponseEntity.ok(registeredUser); } }
- Define el endpoint
/api/users/register. - Recibe el cuerpo de la solicitud HTTP (
User) y llama al servicio para registrar al usuario. - Devuelve la respuesta al cliente.
-
Ejecuta tu aplicación Spring Boot.
-
Usa Postman o
curlpara probar el endpoint:bash Copiar código curl -X POST http://localhost:3000/api/users/register \ -H "Content-Type: application/json" \ -d '{"name":"Nuwe Test","email":"nuwe@nuwe.com","password":"NuweTest1$"}' -
Verifica que el usuario se registre correctamente en la base de datos.
Con esto tienes listo el registro básico de usuarios. ¿Alguna duda o necesitas ayuda con la validación o el hashing de contraseñas? 😊
Enunciado: Hacer que esta estructura de app se ejecute
-
Estructura
caixabank-backend-java-bankingapp/ ├── docker-compose.yml ├── Dockerfile ├── mvnw ├── mvnw.cmd ├── pom.xml ├── [README.md](http://readme.md/) └── src ├── main │ ├── java │ │ └── com │ │ └── hackathon │ │ └── finservice │ │ ├── Config │ │ │ └── CorsConfig.java │ │ ├── Controllers │ │ │ └── HealthCheckController.java │ │ ├── DTO │ │ ├── Entities │ │ │ ├── Account.java │ │ │ ├── Token.java │ │ │ ├── Transaction.java │ │ │ ├── TransactionStatus.java │ │ │ ├── TransactionType.java │ │ │ └── User.java │ │ ├── Exception │ │ ├── FinserviceApplication.java │ │ ├── Repositories │ │ ├── Security │ │ ├── Service │ │ └── Util │ │ └── JsonUtil.java │ └── resources │ └── application.properties └── test └── java └── com └── hackathon └── finservice
-
Docker-compose
version: '3.8' services: app: build: context: . dockerfile: Dockerfile ports: - "3000:3000" depends_on: - mysql networks: - finservice_network restart: always mysql: image: mysql:8.0 environment: MYSQL_DATABASE: bankingapp MYSQL_PASSWORD: root MYSQL_ROOT_PASSWORD: root ports: - "3306:3306" networks: - finservice_network restart: always networks: finservice_network: driver: bridge -
DockerFile
# Usa una imagen base de Maven para compilar el proyecto FROM maven:3.8.5-openjdk-17 AS build # Establece el directorio de trabajo dentro del contenedor WORKDIR /app # Copia el archivo pom.xml y descarga las dependencias (mejora la caché de Docker) COPY pom.xml . RUN mvn dependency:go-offline -B # Copia el resto de los archivos del proyecto COPY src ./src # Construye el proyecto RUN mvn clean package -DskipTests # Usa una imagen base más ligera para ejecutar la aplicación FROM openjdk:17-jdk-slim # Establece el directorio de trabajo dentro del contenedor WORKDIR /app # Copia el archivo .jar generado en la etapa anterior COPY --from=build /app/target/*.jar app.jar # Expone el puerto 3000 EXPOSE 3000 # Comando para ejecutar la aplicación ENTRYPOINT ["java", "-jar", "app.jar"]
En este caso se usará con JPA y LOMBOK para no tener que escribir el sql a mano desde la app de java
-
Output Chatgpt bastante bueno para este caso
Vamos a implementar la funcionalidad de registro de usuario con la estructura típica en una aplicación Spring Boot: modelo (entidad), repositorio, servicio y controlador.
-
Capa de Modelo (Entidad):
- Representa la tabla de la base de datos como una clase Java.
- Contiene los atributos del usuario y sus relaciones con otras tablas.
- Usaremos JPA para mapear automáticamente la entidad a la base de datos.
- Con Lombok, simplificamos el código eliminando getters, setters y constructores manuales.
-
Capa de Repositorio:
- Es la interfaz que conecta con la base de datos para operaciones CRUD (crear, leer, actualizar, eliminar).
- JPA simplifica las consultas comunes y permite personalizar otras más complejas.
- Con esta capa, no necesitamos escribir código SQL directamente.
-
Capa de Servicio:
- Contiene la lógica de negocio. Aquí validamos los datos, verificamos condiciones (como que el correo no exista) y gestionamos tareas complejas antes de interactuar con el repositorio.
-
Capa de Controlador:
- Expone los endpoints que el cliente (frontend, Postman, etc.) utiliza para interactuar con la aplicación.
- Recibe las solicitudes HTTP, llama al servicio y devuelve la respuesta al cliente.
-
Lombok:
-
Reduce código repetitivo (getters, setters, constructores, etc.).
-
Mejora la legibilidad del código.
-
Ejemplo:Esto genera automáticamente los métodos
getName(),setName(String), y un constructor vacío.java Copiar código @Getter @Setter @NoArgsConstructor public class User { private String name; private String email; }
-
-
JPA:
-
Se encarga de mapear las entidades a tablas en la base de datos.
-
Permite realizar operaciones CRUD sin escribir SQL.
-
Ejemplo:Esto crea automáticamente la tabla
Useren la base de datos con columnasid,name, yemail.java Copiar código @Entity public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; private String email; }
-
-
Crea la clase
Useren el paqueteEntities:java Copiar código package com.hackathon.finservice.Entities; import jakarta.persistence.*; import lombok.*; @Entity @Table(name = "users") // Nombre de la tabla en la base de datos @Getter @Setter @NoArgsConstructor @AllArgsConstructor @Builder public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) // Genera ID automáticamente private Long id; @Column(nullable = false) // Campo obligatorio private String name; @Column(nullable = false, unique = true) // Debe ser único private String email; @Column(nullable = false) // Se almacenará la contraseña hasheada private String password; @Column(nullable = false, unique = true) // Debe ser único private String accountNumber; @Column(nullable = false) private String accountType = "Main"; // Tipo de cuenta por defecto }
-
@Entity: Marca la clase como una tabla en la base de datos. -
@Table(name = "users"): Opcionalmente da un nombre específico a la tabla. -
@Idy@GeneratedValue: Declaran la columnaidcomo clave primaria y generan valores automáticamente. -
@Column: Configura los atributos como columnas, marcando restricciones comonullableounique.
-
@Gettery@Setter: Generan automáticamente los getters y setters. -
@NoArgsConstructory@AllArgsConstructor: Generan constructores vacío y con todos los argumentos. -
@Builder: Permite crear objetos con un estilo fluido:java Copiar código User user = User.builder() .name("Nuwe Test") .email("nuwe@nuwe.com") .password("hashedPassword") .accountNumber("19b332") .build();
-
Crea la interfaz
UserRepositoryen el paqueteRepositories:java Copiar código package com.hackathon.finservice.Repositories; import com.hackathon.finservice.Entities.User; import org.springframework.data.jpa.repository.JpaRepository; import java.util.Optional; public interface UserRepository extends JpaRepository<User, Long> { Optional<User> findByEmail(String email); // Busca usuario por email }
-
JpaRepository: Proporciona métodos comosave,findById,findAll, ydeleteautomáticamente. - El método personalizado
findByEmailusa la convención de nombres de JPA para generar automáticamente la consulta.
-
Crea la clase
UserServiceen el paqueteService:java Copiar código package com.hackathon.finservice.Service; import com.hackathon.finservice.Entities.User; import com.hackathon.finservice.Repositories.UserRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.UUID; @Service public class UserService { @Autowired private UserRepository userRepository; public User registerUser(User user) { // Validar que el correo no exista if (userRepository.findByEmail(user.getEmail()).isPresent()) { throw new IllegalArgumentException("El correo ya está registrado"); } // Generar número de cuenta único user.setAccountNumber(UUID.randomUUID().toString().substring(0, 6)); // Guardar usuario return userRepository.save(user); } }
- Contiene la lógica de negocio: Validación y generación del número de cuenta.
- Interactúa con el repositorio para guardar al usuario.
-
Crea la clase
UserControlleren el paqueteControllers:java Copiar código package com.hackathon.finservice.Controllers; import com.hackathon.finservice.Entities.User; import com.hackathon.finservice.Service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/api/users") public class UserController { @Autowired private UserService userService; @PostMapping("/register") public ResponseEntity<?> registerUser(@RequestBody User user) { User registeredUser = userService.registerUser(user); return ResponseEntity.ok(registeredUser); } }
- Define el endpoint
/api/users/register. - Recibe el cuerpo de la solicitud HTTP (
User) y llama al servicio para registrar al usuario. - Devuelve la respuesta al cliente.
-
Ejecuta tu aplicación Spring Boot.
-
Usa Postman o
curlpara probar el endpoint:bash Copiar código curl -X POST http://localhost:3000/api/users/register \ -H "Content-Type: application/json" \ -d '{"name":"Nuwe Test","email":"nuwe@nuwe.com","password":"NuweTest1$"}' -
Verifica que el usuario se registre correctamente en la base de datos.
Con esto tienes listo el registro básico de usuarios. ¿Alguna duda o necesitas ayuda con la validación o el hashing de contraseñas? 😊
-
Capa de Modelo (Entidad):
-
DEFINICIONES

Entidad/Modelo
Sirve para crear el objeto literal de la tabla sql en java. Tendra atributos de la tabla y conexiones con otras.
Lombok → Evita codigo repetitivo. Se generan automaticamente
@Getter @Setterde cada campo. Tambien genera el constructorJPA → 2 funciones
- Analiza el codigo del objeto creado y lo crea en la BD automaticamente. No hace falta ir creando la bd a mano en el MySql
- Crea automaticamente todo el CRUD para no tener que ir escribiendo sql en tu back.
Repositorio
Conecta el CRUD con la BD directamente
Servicio
Es la lógica de negocios, pondremos las verificaciones extra que querramos etc etc
Controlador
Define los endpoints. Reciber solicitudes http y consulta al servicio.
-
IMPLEMENTACIÓN Entidad - Repositorio - Servicio - Controlador
Se nos pide que usuario pueda devolver json en el que el mas completo tenga los campos: name, email, password, accountNumber, accountType.
Modelo/Entidad
(Esto a continuación son anotaciones de JPA)
Clase/tabla →
JPA
@Entity→ Se marca para que la clase se entienda como una tabla de BD@Table(name =””)→ Si la tabla se tiene que llamar diferente a la de la clase, se especificaLombok
@Getter@Setter@AllArgsConstructor@NoArgsConstructor@Builder→ Permite crear objetos con estilo fluido (Syntaxis)-
Función de un Builder
Al haber esa concatenación de . y tabulaciones el codigo queda mas claro
// Constructor largo new Persona("Juan", 30, "Calle Principal 123"); // Con Builder Persona.builder() .nombre("Juan") .edad(30) .direccion("Calle Principal 123") .build();
Id→ Se añade
@Idy@GeneratedValuepara que se establezca la clave primaria al generar la tabla y ademas los valores se generen automaticamente.Atributos → Marcaremos que son columnas y por parametros especificamos nullable or not. Se añadem
@Column(nullable = false, unique = true)-
Codigo completo Entidad
package com.hackathon.finservice.Entities; import jakarta.persistence.*; import lombok.*; @Entity @Table(name = "users") // Nombre de la tabla en la base de datos @Getter @Setter @NoArgsConstructor @AllArgsConstructor @Builder public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) // Genera ID automáticamente private Long id; @Column(nullable = false) // Campo obligatorio private String name; @Column(nullable = false, unique = true) // Debe ser único private String email; @Column(nullable = false) // Se almacenará la contraseña hasheada private String password; @Column(nullable = false, unique = true) // Debe ser único private String accountNumber; @Column(nullable = false) private String accountType = "Main"; // Tipo de cuenta por defecto }
Repositorio
Importamos el Modelo/Entidad
Es la clase que tiene los metodos del CRUD y los hereda de JpaRepository
- Metodos Default
-
save(T entity): Guarda o actualiza una entidad en la base de datos. -
findById(ID id): Busca una entidad por su clave primaria. -
findAll(): Recupera todas las entidades de la base de datos. -
delete(T entity): Elimina una entidad específica. -
deleteById(ID id): Elimina una entidad usando su clave primaria.
-
- Metodos Personalizados
*Optional<User>* **findByEmail***(String email);*- Tambien se pueden generar metodos personalizados añadiendolos a la clase
- Si se sigue la convencion findBy + atributo, no hace falta definir el metodo, ya que JPA lo crea automaticamente para que haga →
*SELECT * FROM user WHERE email = ?;* - Optional es para poder gestionar los valores nulos, que nos devolveran Optional.empty() que es mas seguro
- En este caso, como el
JpaRepositoryestá parametrizado con<User, Long>, significa:-
User: Es la entidad que este repositorio gestionará. -
Long: Es el tipo de la clave primaria (ID) de la entidadUser. -
Codigo Completo Repositorio
package com.hackathon.finservice.Repositories; import com.hackathon.finservice.Entities.User; import org.springframework.data.jpa.repository.JpaRepository; import java.util.Optional; public interface UserRepository extends JpaRepository<User, Long> { Optional<User> findByEmail(String email); // Busca usuario por email }
-
Servicio
Importa de → Entidad/Modelo y repositorio
Tiene
Atributo de repositorio inyectado
Metodos para gestionar logica
Es la capa de lógica de negocio. Aqui comprobaremos que el correo sea correcto, que el UUID se genera bien….
-
Requsitos para este caso → No puede haber campos vacios, email valido y que no exista en la bbdd.
-
Anotaciones
-
@Service→ Indica que tiene la logica, y se gestiona como un bean por Spring -
@Autowired→ Permite a Spring inyectar el UserRepository como dependencia
-
-
Libreria extra UUID → java.utils.UUID
- Permite generar UUIDs
-
Codigo Completo Service
package com.hackathon.finservice.Service; import com.hackathon.finservice.Entities.User; import com.hackathon.finservice.Repositories.UserRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.UUID; @Service public class UserService { @Autowired private UserRepository userRepository; public User registerUser(User user) { // Validar que el correo no exista if (userRepository.findByEmail(user.getEmail()).isPresent()) { throw new IllegalArgumentException("El correo ya está registrado"); } // Generar número de cuenta único user.setAccountNumber(UUID.randomUUID().toString().substring(0, 6)); // Guardar usuario return userRepository.save(user); } }
Controlador
Importa de → Servicio y Entidad
Usa
ResponseEntitypara usar los codigos predefinidos que se devuelven como html.@RestController → Asigna la clase como controlador MVC
→ Maneja Http y devuelve Json
→ Controller + ResponseBody
@RequestMapping → Define la ruta base
- Se crea un UserService atributo con @Autowired
- Metodo
- Devuelve un ResponseEntity<> → 200 OK..
- Parametro RequestBody → User user, un json del usuario
- Se llama al servicio y aplica la logica, devuelve un objeto user si esta todo ok
@PostMapping("/register") public ResponseEntity<?> registerUser(@RequestBody User user) { User registeredUser = userService.registerUser(user); return ResponseEntity.ok(registeredUser); } == return new ResponseEntity<>(registeredUser, HttpStatus.OK);
Si el usuario no acepta la lógica de negocio y no se registra
Entonces en el servicio devolviamos una excepcion, que deberemos controlar en el controlador tal que:
try { User registeredUser = userService.registerUser(user); return ResponseEntity.ok(registeredUser); } catch (IllegalArgumentException e) { return ResponseEntity.badRequest().body(e.getMessage()); // Devuelve un 400 con el mensaje de error. }
-
Manejo Centralizado Errores
Se puede crear una clase reutilizable para los errores que siempre muestre el mensaje del error.
@RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(IllegalArgumentException.class) public ResponseEntity<?> handleIllegalArgumentException(IllegalArgumentException e) { return ResponseEntity.badRequest().body(e.getMessage()); } } //DEVUELVE: { "status": 400, "error": "Bad Request", "message": "El correo ya está registrado", "timestamp": "2024-11-23T10:00:00Z" }
-
Codigo completo Controller
package com.hackathon.finservice.Controllers; import com.hackathon.finservice.Entities.User; import com.hackathon.finservice.Service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/api/users") public class UserController { @Autowired private UserService userService; @PostMapping("/register") public ResponseEntity<?> registerUser(@RequestBody User user) { User registeredUser = userService.registerUser(user); return ResponseEntity.ok(registeredUser); } }
-
-
Logica de negocio
Para sacar un usuario a partir de un id o de algun elemento.
Optional<User> optionalUser = userRepository.findByEmail(email);- Se te guarda automaticamente en la variable Optional un User, sino hay nada, se comprueba con un
.isEmpty() - Si esta →
User user = optionalUser.get()y ahi si lo guardas bene
- Se te guarda automaticamente en la variable Optional un User, sino hay nada, se comprueba con un
-
DTO
Sirven para devolver un json personalizado, con mas campos de los que habitualmente se tienen
package com.hackathon.finservice.Dto; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; @Data // Genera getters, setters, toString, equals, y hashCode @Builder // Genera un patrón de builder para crear objetos @AllArgsConstructor // Genera un constructor con todos los argumentos public class UserResponse { private String name; private String email; private String accountNumber; private String accountType; private String hashedPassword; }
-
JWT
-
CREAR TOKEN
-
subject: Identificador único del usuario (en este caso,accountNumber). -
issuedAt: Fecha de emisión del token. -
expiration: Fecha de expiración del token. -
signWith: Firma el token con el algoritmoHS512y la clave secreta.
@Component public class JwtUtil { private final String SECRET_KEY = "my_secret_key"; // Cambiar por una clave más segura public String generateToken(String subject) { return Jwts.builder() .setSubject(subject) // El identificador principal (número de cuenta) .setIssuedAt(new Date()) // Fecha de emisión .setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 10)) // 10 horas .signWith(SignatureAlgorithm.HS512, SECRET_KEY) // Algoritmo y clave secreta .compact(); } }
En el UserService si todo esta correcto, en la funcion de login() pondriamos
return jwtUtil.generateToken(user.getAccountNumber());Esto nos devolvera un token único si el login es 200 OK.
-
-
USAR EL TOKEN postLogin
Ahora hay que validar el Token para que el usuario pueda obtener la información
-
En SecurityConfig → Añadir sessionManagement y el filtro que crearemos
-
Codigo de SecurityConfig
@Configuration public class SecurityConfig { @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http, JwtAuthenticationFilter jwtAuthenticationFilter) throws Exception { http .authorizeHttpRequests(authorize -> authorize .requestMatchers("/api/users/login", "/api/users/register", "/health").permitAll() .anyRequest().authenticated() ) **.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class) // Agrega el filtro aquí** .csrf().disable() **.sessionManagement().disable(); // Sin estado, ideal para JWT** return http.build(); } }
-
-
Crear archivo JwtAuthenticationFilter en el paquete/carpeta Security
-
Se marca como Component, significa que se gestiona como bean y se podra Autowirear cuando se declare. Extends from OncePerRequestFilter que solo permite una validacion por request http
-
Usamos jwtUtil porque queremos extraer el AccountNumber UUID del token
-
Obtiene el token de el header http “Authorization” e inicializa jwt y accountNumber
final String authHeader = request.getHeader("Authorization"); String jwt = null; String accountNumber = null;
-
Verifica si el encabezado tiene un token
if (authHeader != null && authHeader.startsWith("Bearer ")) { jwt = authHeader.substring(7); accountNumber = jwtUtil.extractSubject(jwt); // Extraer el accountNumber del token }
-
Valida el token y gestiona la autentificación
-
Codigo completo Auth
package com.hackathon.finservice.Security; @Component public class JwtAuthenticationFilter extends OncePerRequestFilter { @Autowired private JwtUtil jwtUtil; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { final String authHeader = request.getHeader("Authorization"); String jwt = null; String accountNumber = null; // Verificar si el encabezado contiene el token if (authHeader != null && authHeader.startsWith("Bearer ")) { jwt = authHeader.substring(7); accountNumber = jwtUtil.extractSubject(jwt); // Extraer el accountNumber del token } // Si el token es válido, configura la autenticación if (accountNumber != null && SecurityContextHolder.getContext().getAuthentication() == null) { if (jwtUtil.validateToken(jwt)) { UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken( accountNumber, null, null); authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(authentication); } } chain.doFilter(request, response); } }
-
-
Actualizar JwtUtil con los metodos
extractSubjectyvalidateTokenHemos usado estos metodos en el Auth para gestionar la info del token, así que hay que implementarlos
-
extractSubject → Viene primero de extractAll
// Método interno para extraer todos los claims del token private Claims extractAllClaims(String token) { return Jwts.parser() .setSigningKey(SECRET_KEY) // Configura la clave secreta para validar la firma .parseClaimsJws(token) // Analiza el token JWT .getBody(); // Extrae los claims del token } // Extrae el sujeto del token (el número de cuenta) public String extractSubject(String token) { return extractAllClaims(token).getSubject(); }
-
Validate
// Valida el token verificando su firma y si ha expirado public boolean validateToken(String token) { try { extractAllClaims(token); // Intenta extraer los claims para validar return true; // Si no lanza una excepción, el token es válido } catch (ExpiredJwtException | MalformedJwtException | SignatureException | UnsupportedJwtException | IllegalArgumentException e) { System.out.println("Token inválido: " + e.getMessage()); return false; } }
-
-
Crear el controlador para esta acción
Logica de controlador
-
Codigo
package com.hackathon.finservice.Controllers; @RestController @RequestMapping("/api/dashboard") public class DashboardController { @Autowired private UserRepository userRepository; /** * Endpoint para obtener información del usuario principal */ @GetMapping("/user") public ResponseEntity<?> getUserInfo() { // Obtener el accountNumber del token JWT String accountNumber = (String) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); // Recuperar el usuario desde la base de datos User user = userRepository.findByAccountNumber(accountNumber) .orElseThrow(() -> new IllegalArgumentException("User not found for account number: " + accountNumber)); // Crear respuesta personalizada (opcional, si no quieres enviar la entidad completa) return ResponseEntity.ok(Map.of( "name", user.getName(), "email", user.getEmail(), "accountNumber", user.getAccountNumber(), "accountType", "Main", // Suponemos que siempre se devolverá "Main" para el tipo principal "hashedPassword", user.getPassword() )); } }
Finalmente es PostMan, usar el Bearer Token y introducir el token sin nada mas
-
-
-
Entidad - Repositorio - Controlador SIN SERVICIO
No siempre es necesario logica de negocio, de manera que nos podemos evitar esa clase. Pero lo correcto seria siempre crearla para encapsular el codigo.
Pondre un ejemplo simple para entender el funcionamiento de estos 3 juntos
Modelo
@Entity @Table(name = "accounts") @Getter @Setter @AllArgsConstructor @NoArgsConstructor @Builder public class Account{ @Id @GeneratedValue private long id; @Column(nullable = false, unique = true) private String accountNumber; @Column(nullable = false) private Double balance; @Column(nullable = false) private String accountType = "Main"; }
Repositorio
public interface AccountRepository extends JpaRepository<Account, Long> { Optional<Account> findByAccountNumber(String accountNumber); }
Controlador
@RestController @RequestMapping("api/dashboard") public class DashboardController { @Autowired private AccountRepository accountRepository; // Este get Mapping es estatico, pero lo dinamico viene a partir // de token asi que no se maneja por url @GetMapping("/account") public ResponseEntity<?> getAccountInfo(){ // Obtener el accountNumber del token JWT String accountNumber = (String) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); Account account = accountRepository.findByAccountNumber(accountNumber) .orElseThrow(() -> new IllegalArgumentException("User not found for account number: " + accountNumber)); // Crear respuesta personalizada return ResponseEntity.ok(Map.of( "accountNumber", account.getAccountNumber(), "balance", account.getBalance(), "accountType", account.getAccountType() )); }
TODO → Refactorizar entities para que contengan → Usuario una lista de accounts
→ Accounts un usuario
Despues arreglar los controllers y servicios para que se mapeen correctamente
Generar DTOs para las respuestas
JPA
Funciones útiles de JPA para interaccionar con BD
save() → Crea una nueva fila en SQL de la entidad que le pasemos si esa entidad no tiene asignado un id. Si lo tiene asignado, hara un update.
@returns el id del creado.
-
npm node_modules
Se puede trabajar a través de CDN(deprecated)
El npm install que usaremos será en appData/Roaming/node_modules, que es como mas global, o sino se puede meter en la propia carpeta del proyecto pero es peor realmente.
Si te lo bajaras de git, habría que hacer un npm i ya que no tendrás node modules de React.
Una cosa es el node modules para crear el proyecto y el otro para que funcione la app
-
Set up vanilla
npm i -g create-react-appSeria para guardar el node modules en global y en la carpeta que estés pues iniciar el proyecto.npx create-react-app nombre-proyectonpx seria para cogerlo de internet o algo así y después unnpm start
Descargar EsLint (plugin VSC) para que de mas info.
-
Componente simple
const React = require("react"); class Comp extends React.Component{ render(){ return React.createElement( "div", null, "Texto dentro de el componente" ); } } export default Comp;
const Comp = () => { return ( <div>Componente en JSX</div> )}
Babel → transpilador interno de jsx
React Fragment para pasar mas de un elemento o un componente, se puede usar directamente sin nada <> </>
Opciones css en JSX
Para js es un bloque de escape {} para css serian 2.
Css en el mismo componente con el escape de llaves →
-
Para el css puedes usar una constante que usaras luego en la función de el componente, todo en el mismo archivo.
-
Puedes hacer archivos de estilos externos que importaras después como si fuera un objeto.
-
O también usar module.nombreArchivo, entonces lo importas como styles, y luego aplicas styles al style y solo cogerá el que tenga el mismo nombre
//En el archivo css tienes: .title {background-color: 'red';} //En el jsx del componente import styles from module.NombreArchivo.css <div className= {styles.title}> Elementos </div>
Props
→ Se trata de pasar por parámetros a los componentes información. Padre pasa props a el hijo y hijo pasa eventos al padre.
Hooks y Eventos
-
Crear Proyecto con Vite
npm i -g vite create-vitenpm create vite@latestnpm installynpm run dev
React Router → Hay que instalarlo
npm i react-router-dom bundlephovia(web para ver la composición de los paquetes)
import BrowserRouter from 'react-router-dom'
<BrowserRouter>
<App />
</BrowserRouter>import {Routes, Route, Link, UseParams} from 'react-router-dom'
function App() {
return(
<>
<Routes>
<**Route** path="/ruta1" element = {"<Componente />"}/> *o pagina*
</Routes>
</>
)
}Para que refresque solamente lo que actualizas, se trabaja con Link en vez de
<Link to=”/pagina”> Go to pagina</Link>
Single page application SPA/MPA
useParams → para coger el nombre de la ruta
React toastify, sweetalert, sonner , mui