Construye Minioland: Tu primera aplicación con Angular | Parte #2

NOTA

Esta es la parte #2 de la serie sobre la creación de Minioland, una aplicación sencilla con Angular. Si te perdiste la parte #1, aquí la tienes.

Trabajando con las routes de Angular

Las routes son las herramientas que nos van a permitir navegar de una página a otra de nuestra app sin que nuestro navegador se recargue. 

Por ejemplo, ahora nuestro navegador muestra http://localhost:4200/, pero implementando rutas vamos a conseguir crear direcciones tales como http://localhost:4200/home, http://localhost:4200/about, etc.

1. Creamos un archivo dentro de nuestra carpeta app, llamado app.routes.tsEl .routes no es obligatorio, pero es una convención para que identifiquemos a golpe de vista que es un archivo de rutas. Otra cosa que me ayuda a identificarlo rápidamente es su característico icono (una señal de tráfico mostrando distintas direcciones). Lo puedes conseguir a través de extensiones de vsCode, (Material icon theme). 

El contenido de nuestro archivo app.routes.ts lo podemos generar con la ayuda de un Angular snippet gracias a una extensión de vsCode (busca Angular snippets en la sección de extensiones).

El resultado sería algo así: 

import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { HomeComponent } from './components/home/home.component'; const routes: Routes = [ { path: 'home', component: HomeComponent}, { path: '**', pathMatch: 'full', redirectTo: 'home' }, ]; @NgModule({ imports: [RouterModule.forRoot(routes)], exports: [RouterModule] }) export class AppRoutingModule {}

Como podemos ver, la variable routes es un array de rutas. Cada ruta contiene un path a uno de nuestros componentes y el nombre de dicho componente. 

Debemos dejar siempre al final la ruta path: '**', pues es una ruta especial que se activará si existe algún error en nuestras rutas y Angular no puede localizarlas, redireccionándonos a la ruta que le marquemos en redirectTo. 

En nuestro caso, vamos a redireccionar a nuestra home.​​​​

Es necesario que importemos todos los componentes cuyas rutas vamos a necesitar. 

Por tanto, añadimos nuestras rutas para los componentes de about minions, tal y como hemos hecho con el home component.

2. Avisamos a Angular de que hemos creado un archivo de rutas.

Para ello, vamos al archivo app.module.ts (por cierto, para no ir buscando uno a uno entre nuestra lista casi infinita de archivos de la izquierda, suelo a hacer ctrl+p para buscar un archivo en vsCode), e importamos nuestro archivo de rutas ahí. 

Ahora nuestro archivo app.module.ts tiene este aspecto:

import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; // routes import { AppRoutingModule } from './app.routes'; // components import { AppComponent } from './app.component'; import { NavbarComponent } from './components/shared/navbar/navbar.component'; import { HomeComponent } from './components/home/home.component'; import { AboutComponent } from './components/about/about.component'; import { MinionsComponent } from './components/minions/minions.component'; @NgModule({ declarations: [ AppComponent, NavbarComponent, HomeComponent, AboutComponent, MinionsComponent ], imports: [ BrowserModule, AppRoutingModule ], providers: [], bootstrap: [AppComponent] }) export class AppModule { }

3. Para que nuestras rutas funcionen tenemos que hacer un último paso, ya que Angular todavía no sabe qué ruta debe mostrar cuando nuestra app se cargue por primera vez. 

Para ello, vamos al archivo que Angular utiliza para cargar nuestra app (el app.component.html) e incluimos esta etiqueta:

<router-outlet></router-outlet>

La podemos incluir dentro de un container de bootstrap, quedando nuestro código del app.component.html así:

<app-navbar></app-navbar> <div class="container-fluid"> <router-outlet></router-outlet> </div>

Añadiendo routerLink y routerLinkActive

Seguimos configurando nuestras rutas. 

Desde nuestro navbar vamos a gestionar hacia dónde apuntarán nuestros links. Por ahora solo tenemos el link home y uno que viene por defecto con bootstrap.

1. Eliminamos la clase active del link home y añadimos nuestros links de about minions.

Borramos también el atributo href de nuestros links porque lo vamos a sustituir por una propiedad de Angular, el routerLink. Esta propiedad admite un array de los diferentes niveles de una ruta, donde cada nivel es un elemento del array. Por ejemplo, si queremos una ruta que nos lleve a una página (ficticia) de Sobre Nosotros y ahí dentro existiese un link a otra página llamada Aparaciones en los medios, la sintaxis sería:

[routerLink]="['/sobre-nosotros', '/medios']"

que en el navegador se traduciría en http://localhost:4200/sobre-nosotros/medios

? ¡Ojo! El nombre que le demos en el routerLink debe coincidir con el que el que le hemos dado a la ruta en nuestro archivo app.routes.ts.

¡Y ya está! Ahora deberías poder navegar entre las diferentes páginas de tu app, viendo así su contenido.

2. Reforzamos la visibilidad del link de la página en la que nos encontramos en este momento.

Para ello, al parent element del elemento que tenga la propiedad  [routerLink]="['/nombre de la ruta']", le añadimos routerLinkActive="active"Active es una clase CSS de bootstrap, pero podríamos añadir nuestra propia clase. 

? Cuidado con esto de poner tu clase personalizada, porque existe un conflicto de specificity con las clases de bootstrap. Para solucionarlo, coloca el [routerLink] y el routerLinkActive en el mismo elemento HTML, y define tu clase CSS en el archivo styles.css de la siguiente manera:

.myActiveClass.myActiveClass.myActiveClass.myActiveClass {

 // tu código aquí

}

En este caso no voy a utilizar un clase CSS personalizada, sino la clase active que nos ofrece bootstrap, quedando así nuestro código del archivo navbar.component.html:

<nav class="navbar navbar-expand-lg navbar-dark bg-dark"> <a class="navbar-brand" href="#">Minioland</a> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation"> <span class="navbar-toggler-icon"></span> </button> <div class="collapse navbar-collapse" id="navbarSupportedContent"> <ul class="navbar-nav mr-auto"> <li class="nav-item" routerLinkActive="active"> <a class="nav-link" [routerLink]="['/home']" >Home<span class="sr-only">(current)</span></a> </li> <li class="nav-item" routerLinkActive="active"> <a class="nav-link" [routerLink]="['/minions']" >Minions</a> </li> <li class="nav-item" routerLinkActive="active"> <a class="nav-link" [routerLink]="['/about']" >About</a> </li> </ul> <form class="form-inline my-2 my-lg-0"> <input class="form-control mr-sm-2" type="search" placeholder="Search" aria-label="Search"> <button class="btn btn-outline-success my-2 my-sm-0" type="submit">Search</button> </form> </div> </nav>

Pequeñas animaciones con CSS

En los materiales del proyecto encontrarás un archivo llamado animate. Copia su contenido y vuélcalo en el archivo styles.css, que está al nivel de la carpeta app. 

El archivo es un extracto de animate.css, web muy útil. 

Añadimos las siguientes clases a nuestros componentes about home, en los divs que engloben el componente. Por ejemplo, el home component quedaría así:

<div class="jumbotron jumbotron-fluid animated fadeIn fast"> <div class="container"> <h1 class="display-4">Minioland</h1> <p class="lead">Pon un Minion en tu vida.</p> </div> </div>

Diseñando el componente minions

La estructura de nuestro minions component  se va a basar en los componentes cards que nos ofrece bootstrap 4.

1. Por ahora, vamos a escribir el src del elemento img en modo hardcoded, al igual que el título y el subtítulo de la card. Nos ocuparemos de hacerlo dinámico luego, utilizando una herramienta de Angular llamada service. Un service es un archivo donde guardamos datos que queremos reutilizar, con el objetivo de no repetir código

Por ejemplo, en este caso, podríamos añadir dentro del componente minions.component.html el contenido del nombre del minion y su bio, pero esto no sería eficiente, porque en caso de necesitar esa información en otro componente deberíamos repetir el mismo código en el otro componente. Eso es lo que los services resuelven.

2. También vamos a darle un pequeño estilo a nuestra imagen. Para ello debemos modificar el archivo minions.component.css, quedando de la siguiente manera:

img { width: 50%; position: relative; left: 25%; }

Nuestro archivo minions.component.html nos quedaría así:

<h2 class="mt-4">Minions <small>a mansalva</small></h2>
<hr>

<div class="container">
<div class="card-columns mb-5">
<div class="card">
<img src="../../../assets/img/tu-imagen-aqui.jpg" class="card-img-top" alt="...">
<div class="card-body">
<h5 class="card-title">Card title</h5>
<p class="card-text">This card has supporting text below as a natural lead-in to additional content.</p>
<p class="card-text"><small class="text-muted"> Last updated 3 days ago </small></p>
<button type="button" class="btn btn-outline-warning btn-block">Ver más info</button>
</div>
</div>
</div>
</div>

Como ves, hemos añadido un botón, el cual, una vez configurado, nos deberá llevar a una página donde podremos ver más información sobre el minion que hayamos hecho clic.

Introducción y creación de services en Angular

Como ya adelantamos en el anterior punto, vamos a utilizar un service para almacenar la información (aka data) de nuestros minions.

1. Creamos nuestra carpeta para almacenar nuestros servicios dentro de la carpeta app y la nombramos como queramos, aunque es una convención llamarla services.

Dentro de esta carpeta, creamos un archivo al que llamaremos minions.service.ts. El .service es una convención para poder identificarlo a golpe de vista. 

Puedes utilizar un snippet de la extensión de angular para generar el esqueleto de un service.​​​

Lo que identifica un service como tal es su decorador @Injectable().​​​​

Nuestro código del archivo minions.service.ts quedaría así:

import { Injectable } from '@angular/core'; @Injectable() export class MinionsService { constructor() { console.log('minions service listo para usar, oiga!'); } }

Hemos añadido un console.log para comprobar que el service funciona.

2. Tenemos que avisar a Angular de que hemos creado un servicio nuevo. 

Para ello, vamos a nuestro app.module.ts y lo declaramos ahí, tanto en los imports como en los providers​​​​, quedando nuestro archivo así:

import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; // routes import { AppRoutingModule } from './app.routes'; // services import { MinionsService } from './services/minions.service'; // components import { AppComponent } from './app.component'; import { NavbarComponent } from './components/shared/navbar/navbar.component'; import { HomeComponent } from './components/home/home.component'; import { AboutComponent } from './components/about/about.component'; import { MinionsComponent } from './components/minions/minions.component'; @NgModule({ declarations: [ AppComponent, NavbarComponent, HomeComponent, AboutComponent, MinionsComponent ], imports: [ BrowserModule, AppRoutingModule ], providers: [MinionsService], bootstrap: [AppComponent] }) export class AppModule { }

3. Importamos el service en el componente donde queramos utilizarlo. En nuestro caso, en el minions.component.ts, quedando nuestro código de la siguiente manera: 

import { Component, OnInit } from '@angular/core'; import { MinionsService } from 'src/app/services/minions.service'; @Component({ selector: 'app-minions', templateUrl: './minions.component.html', styleUrls: ['./minions.component.css'] }) export class MinionsComponent implements OnInit { constructor(private minionsService: MinionsService) { } ngOnInit() { } }

Esta es la primera vez que vamos a usar el constructor.

4. Añadimos la palabra reservada private, ya que queremos declarar una propiedad accesible solamente en este componente.

Le damos un alias a nuestra propiedad y la declaramos como tipo MinionsService.​​​​

Al guardar, vamos a nuestra app y deberíamos ver en las dev tools de chrome nuestro console.log('minions service listo para usar, oiga!').

Ten en cuenta que ese console.log solo se ve en la página de Minions. No en el Home ni en el About, ya que sólo estamos usando el service en la página de Minions.

Esto es lo que deberías ver:

minions service listo

5. Rellenamos con datos nuestro MinionsService.

Para ello, vamos a nuestro service (minions.service.ts) donde declaramos una propiedad privada de tipo any[], llamada minions.

Vamos a los materiales del proyecto y abrimos el archivo .txtdonde encontramos un array de objetos. Copiamos toda la información y la pegamos dentro de nuestra propiedad minions recién creada.

6. Ya que hemos declarado nuestra data como una propiedad privada, necesitamos crear un método público para poder acceder a esa propiedad desde fuera de nuestro servicio. Este paso no es estrictamente necesario para esta simple app, pero es una buena práctica por si en el futuro queremos trabajar con APIs. ?

El método lo vamos llamar getMinions(). 

Añadimos también una interface de Angular para fortalecer nuestro código y así evitar errores. Básicamente, con la interface lo que hacemos es declarar que cada minion va a tener un name, bio, img, birth side, de manera que si de ahora en adelante nos equivocamos en alguna de esas características, Angular nos lanzará un error diciendo que cada minion puede tener esas únicas características, ni más ni menos.

El código de nuestro MinionsService quedaría de la siguiente manera:

import { Injectable } from '@angular/core'; @Injectable() export class MinionsService { private minions: Minion[] = [ { name: "Kevin", bio: "Aquí debería haber una biografía de la vida de este minion, pero son gente muy misteriosa, así que tendrás que imaginártela...", img: "assets/img/kevin.jpg", birth: "1951", side:"de los buenos" }, { name: "Josua", bio: "Aquí debería haber una biografía de la vida de este minion, pero son gente muy misteriosa, así que tendrás que imaginártela...", img: "assets/img/Josua.jpg", birth: "1672", side:"malvado" }, { name: "Dave", bio: "Aquí debería haber una biografía de la vida de este minion, pero son gente muy misteriosa, así que tendrás que imaginártela...", img: "assets/img/dave.png", birth: "1723", side: "de los buenos" }, { name: "Mudito", bio: "Aquí debería haber una biografía de la vida de este minion, pero son gente muy misteriosa, así que tendrás que imaginártela...", img: "assets/img/mudito.jpeg", birth: "1379", side:"de los buenos" }, { name: "Llongueras", bio: "Aquí debería haber una biografía de la vida de este minion, pero son gente muy misteriosa, así que tendrás que imaginártela...", img: "assets/img/llongueras.jpg", birth: "1687", side: "malvado" }, { name: "Minioncé", bio: "Le va el cante, dar la nota, ama los karaokes, es el rey y reina de la fiesta. Invítalo a tu fiesta o te arrepentirás.", img: "assets/img/minionce.jpg", birth: "1976", side: "de los buenos" }, { name: "Lobeznion", bio: "No lo enfades, este bichillo tiene muy malas pulgas...aunque sólo mide medio metro y ¡no puede ser más gracioso!", img: "assets/img/lobeznion.jpg", birth: "2017", side: "malvado" }, { name: "Minion Presley", bio: "Aquí debería haber una biografía de la vida de este minion, pero son gente muy misteriosa, así que tendrás que imaginártela...", img: "assets/img/minion-presley.jpg", birth: "2017", side: "malvado" } ]; constructor() { console.log('minions service listo para usar, oiga!'); } getMinions() { return this.minions; } } export interface Minion { name: string; bio: string; img: string; birth: string; side: string; }

7. Y ahora, ¿cómo trasladamos los datos que hemos puesto en nuestro MinionsService para que se vean reflejados en nuestro MinionsComponent? Declarando una propiedad, a la que llamaremos también minions, que será de tipo Minion (esto sale de nuestra interface) en nuestro minions.component.ts y asignándole el valor de la propiedad minions de nuestro archivo minions.service.ts y el método para obtener la información de los minions (getMinions() )

Esto lo vamos a hacer dentro del bloque del ngOnInit() { }.

Y así de sencillo es conectar un servicio con un componente.

Haz un console.log de minions en el ngOnInit() y verás que la lista de minions aparece en tus dev tools.

Aquí el código de minions.component.ts tal y como quedaría:

import { Component, OnInit } from '@angular/core'; import { MinionsService, Minion } from 'src/app/services/minions.service'; @Component({ selector: 'app-minions', templateUrl: './minions.component.html', styleUrls: ['./minions.component.css'] }) export class MinionsComponent implements OnInit { minions: Minion[] = []; constructor(private minionsService: MinionsService) { } ngOnInit() { this.minions = this.minionsService.getMinions(); console.log(this.minions); } }

Aplicando el *ngFor para generar minions

Vamos a usar una herramienta verdaderamente útil que nos proporciona Angular: el *ngForEl *ngFor es una directive de Angular que nos permite realizar loops de manera muy sencilla.

La vamos a usar para generar minions dinámicamente a partir del esqueleto o plantilla base que tenemos definida en nuestra card de bootstrap del minions.component.html: su foto, su nombre, un resumen de su biografía y un botón para ver su información completa.

En nuestro minions.component.ts tenemos nuestra variable local minions. Esa variable la vamos a usar de referencia en el archivo minions.component.html. 

1. La forma de usar este loop es creando una variable local, a la que, siendo lógicos, llamaremos minion y le pasaremos nuestra variable minions del archivo minions.component.ts.

Comprueba por ti mismo la conexión que acabamos de crear poniendo el ratón encima de minions y clicando ctrl+clic izq. Verás que te lleva a tu archivo de typescript (minions.component.ts)? Tachááán!! ?

2. Convertimos las propiedades de nuestros minions en dinámicas, y no hardcoded, como estaban hasta ahora.

Para ello vamos a usar una técnica de Angular llamada property binding, la cual nos permite convertir atributos de un elemento HTML, como el src, en atributos que interactúan con Angular informándole de lo que nosotros le digamos.

En este caso, vinculamos el atributo src del elemento img y utilizamos nuestra variable local minion del *ngFor junto con nuestra propiedad img, creada para cada minion en nuestra interface.

Hacemos lo mismo con el atributo alt de la imagen.

Con el nombre y la bio del minion no podemos hacer property binding, así que utilizamos otra técnica para informar a Angular llamada string interpolation.  

Nuestro código del minions.component.html quedaría así:

<h2 class="mt-4">Minions <small>a mansalva</small></h2>
<hr>

<div class="container">
<div class="card-columns mb-5">
<div class="card" *ngFor="let minion of minions">
<img [src]="minion.img" class="card-img-top" [alt]="minion.name">
<div class="card-body">
<h5 class="card-title"> {{ minion.name }} </h5>
<p class="card-text"> {{ minion.bio }} </p>
<p class="card-text"><small class="text-muted"> {{ minion.birth }} </small></p>
<button type="button" class="btn btn-outline-warning btn-block">Ver más info</button>
</div>
</div>
</div>
</div>

Y ahora nuestra página de Minions se ve así:

minions page

Configurando la navegación entre nuestras páginas

Ya tenemos el componente MinionsComponent, que nos muestra una visión general de todos nuestros minions. Ahora es momento de crear otro componente que será la página donde seremos dirigidos cuando hagamos click en el botón de Ver más info de alguna de las cards  de nuestro componente MinionsComponent. 

1. Creamos el componente (al que llamaremos minion) en app --> components, usando Angular CLI.

ng g c minion --skipTests

2. Actualizamos nuestro archivo de rutas (app.routes.ts), informando así a Angular de que hemos creado una nueva route para poder navegar hasta y desde ella. Así nos queda:

import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { HomeComponent } from './components/home/home.component'; import { AboutComponent } from './components/about/about.component'; import { MinionsComponent } from './components/minions/minions.component'; import { MinionComponent } from './components/minion/minion.component'; const routes: Routes = [ { path: 'home', component: HomeComponent}, { path: 'about', component: AboutComponent}, { path: 'minions', component: MinionsComponent}, { path: 'minion/:id', component: MinionComponent}, { path: '**', pathMatch: 'full', redirectTo: 'home' }, ]; @NgModule({ imports: [RouterModule.forRoot(routes)], exports: [RouterModule] }) export class AppRoutingModule {}

Aquí viene lo nuevo. Cuando le pasemos un path a la ruta, no podemos simplemente llamarlo minion, sino que necesitaremos pasarle un parámetro. Sencillamente porque cuando hagamos clic en el botón de Ver más info de nuestro minion Dave, por ejemplo, Angular nos llevará al componente que acabamos de crear (MinionComponent) pero debemos informarle cuál de todos nuestros minions queremos ver, que deberá coincidir, claro, con el minion que hayamos hecho clic. En este caso, deberá mostrarnos la "página de Dave", por así decirlo. 

Es el mismo concepto que cuando estás navegando por Netflix y seleccionas una serie. Al seleccionarla, Netflix te lleva a la página de esa serie.

Pues lo mismo aquí, mil veces más sencillo ?

A nuestro parámetro lo vamos a llamar id porque ése es el standard y lo lógico: cada uno de nuestros minions es único y por tanto, tiene sentido que cada uno tenga un id distinto.

¿Pero de dónde sacamos ese id, si no le hemos dado ninguno a nuestros minions? ?

Pues del array de minions que tenemos en nuestras dev tools de chrome, que sale de nuestro console.log del minions.component.ts:

array minions dev tools

Así que con esta información del console.log podemos saber qué índice (o id) tiene cada uno de nuestros minions. Por ejemplo, Kevin tiene el índice 0. 

3. A este índice podemos acceder fácilmente desde nuestro código en minions.component.html usando la propiedad index y añadiéndola como parte del *ngFor:

<div class="card" *ngFor="let minion of minions; let i = index;">

4. Nos creamos el método seeMinion() en el archivo de typescript de ese mismo componente (en seguida lo explicaremos) :

seeMinion(idx: number) {

 this.router.navigate(['/minion', idx]);

}

Nuestro código del minions.component.html quedaría de la siguiente manera:

<h2 class="mt-4">Minions <small>a mansalva</small></h2>
<hr>

<div class="container">
<div class="card-columns mb-5">
<div class="card" *ngFor="let minion of minions; let i = index;">
<img [src]="minion.img" class="card-img-top" [alt]="minion.name">
<div class="card-body">
<h5 class="card-title"> {{ minion.name }} </h5>
<p class="card-text"> {{ minion.bio }} </p>
<p class="card-text"><small class="text-muted"> {{ minion.birth }} </small></p>
<button type="button" class="btn btn-outline-warning btn-block" (click)="seeMinion(i)">Ver más info</button>
</div>
</div>
</div>
</div>

Para que nuestro método funcione tenemos que importar el módulo Router de @angular/router en minions.component.ts e inyectarlo en nuestro constructor creando una variable de ese tipo (Router).

Nuestro código del minions.component.ts nos quedaría así:

import { Component, OnInit } from '@angular/core'; import { MinionsService, Minion } from 'src/app/services/minions.service'; import { Router } from '@angular/router'; @Component({ selector: 'app-minions', templateUrl: './minions.component.html', styleUrls: ['./minions.component.css'] }) export class MinionsComponent implements OnInit { minions: Minion[] = []; constructor(private minionsService: MinionsService, private router: Router) { } ngOnInit() { this.minions = this.minionsService.getMinions(); console.log(this.minions); } seeMinion(idx: number) { this.router.navigate(['/minion', idx]); } }

¡Y voilà! Ahora si clicas en cualquier minion desde la página de minions te llevará a su página específica, que de momento debería decir no más que minion works!

Por ejemplo, si clicas en el botón de Ver más info del minion Kevin, verás que tu url ahora es http://localhost:4200/minion/0 , siendo 0 el id que representa a nuestro querido Kevin.

THE END! (POR AHORA...) 

Y hasta aquí la parte #2 de esta serie de posts sobre cómo construir una sencilla aplicación con Angular. Si quieres seguir aprendiendo, aquí tienes la parte #3Y si te perdiste la parte #1, aquí la tienes.

Más recursos de aprendizaje

En mi experiencia, la manera más eficaz para aprender Angular es combinando varias vías de aprendizaje. Uno de mis métodos favoritos son los vídeo-cursos y mi plataforma predilecta para eso es Udemy. He hecho varios cursos pero sólo recomiendo aquellos que verdaderamente me han sido útiles. Aquí van:

angular the complete guide - curso Max S.

  Max Schwarzmüller

curso angular fernando herrera

   Fernando Herrera

Si necesitas apoyo en forma de libro, puede que éstos te sirvan de ayuda:

libro 1 angular
libro 2 angular

La programación es un mundo que evoluciona a una velocidad de vértigo. Los autores de estos libros lo saben, por eso suelen encargarse de actualizar su contenido regularmente. Asegúrate de que así sea antes de adquirirlos ?.

Participo en el programa de afiliados de Udemy y Amazon, lo que significa que, si compras alguno de estos cursos y/o libros, yo me llevaré una pequeña comisión y a ti no costará nada extra. Vamos, lo que se dice un win-win ?.

COMPARTIR ES VIVIR

Si crees que este post puede serle útil a alguien, ¡compártelo!

Si crees que este post puede serle útil a alguien, por favor, ¡compártelo!:

Deja un comentario

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.

Esta web utiliza cookies para asegurar que se da la mejor experiencia al usuario. Si continúas utilizando este sitio se asume que estás de acuerdo. más información

Los ajustes de cookies en esta web están configurados para «permitir las cookies» y ofrecerte la mejor experiencia de navegación posible. Si sigues usando esta web sin cambiar tus ajustes de cookies o haces clic en «Aceptar», estarás dando tu consentimiento a esto.

Cerrar