portada componentes y databinding guia avanzada

Componentes y databinding en Angular: Guía avanzada – Parte #1

Última actualización:

Los componentes son el núcleo duro de Angular, elementos re-utilizables a lo largo de nuestra app que nos permiten construir proyectos totalmente escalables. Pero para eso debemos entender los componentes en profundidad y saber combinarlos correctamente con databinding.

🤷‍♀️ ¿MÁS PERDID@ QUE UN PULPO EN UN GARAJE? 🐙

Si acabas de aterrizar aquí y estos temas de Angular te suenan a chino, te recomiendo que empieces por este artículo de introducción a Angular con las claves para entenderlo.

Creación de una demo app

Para entender el funcionamiento avanzado de los componentes, vamos a crearnos una demo app sobre la que poder trabajar. Consistirá en una sencilla pantalla donde mostramos al usuario dos campos (en inglés, inputs) para que cree un servidor (en inglés, server) añadiendo un nombre y un contenido.

El hecho de que sea un servidor no tiene nada que ver con el desarrollo backend, ya que simplemente estamos haciendo una simulación de una app que nos permitiría añadir servidores imaginarios. Así que no te hagas la picha un lío. 🙃

Tendremos dos botones. El servidor lo podremos crear como servidor normal o como una plantilla (en inglés, blueprint) según qué botón se pulse. El resultado se mostrará en la misma pantalla, a continuación. El texto será rojo si pulsamos "Add Server" y azul si pulsamos "Add Server Blueprint". Aquí una muestra para que veas de lo que estamos hablando:

estado inicial app

2. Todo el contenido de nuestra app se encuentra inicialmente en el template del AppComponent: 

<div class="container">
  <div class="row">
    <div class="col">
      <p>Add new Servers or blueprints!</p>
      <label>Server Name</label>
      <input type="text" class="form-control" [(ngModel)]="newServerName">
      <label>Server Content</label>
      <input type="text" class="form-control" [(ngModel)]="newServerContent">
      <br>
      <button class="btn btn-primary" (click)="onAddServer()">Add Server</button>
      <button class="btn btn-primary mx-2" (click)="onAddBlueprint()">Add Server Blueprint</button>
    </div>
  </div>
  <hr>
  <div class="row">
    <div class="col">
      <div class="card" *ngFor="let element of serverElements">
        <div class="card-header">{{ element.name }}</div>
        <div class="card-body">
          <p class="card-text">
            <strong *ngIf="element.type === 'server'" style="color: red">{{ element.content }}</strong>
            <em *ngIf="element.type === 'blueprint'">{{ element.content }}</em>
          </p>
        </div>
      </div>
    </div>
  </div>
</div>

Asimismo, toda la lógica de programación también se encuentra en un solo componente, el app.component.ts:

import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  serverElements = [];
  newServerName = '';
  newServerContent = '';

  onAddServer() {
    this.serverElements.push({
      type: 'server',
      name: this.newServerName,
      content: this.newServerContent
    });
  }

  onAddBlueprint() {
    this.serverElements.push({
      type: 'blueprint',
      name: this.newServerName,
      content: this.newServerContent
    });
  }
}

Lo mismo ocurre con el CSS, contenido únicamente en el app.component.css:

.container {
  margin-top: 30px;
}

p {
  color: blue;
}

Esta organización es muy mejorable, ya que podemos dividir este código y colocarlo en diferentes componentes. Así estaremos potenciando la característica de Angular de basarse en componentes re-utilizables, porque tal y como está ahora, poco podemos re-utilizar. 

Separación por componentes

Vamos a dividir nuestra app de la siguiente manera:

  • un componente que haga de "panel de mandos",  conteniendo los inputs para el nombre y el contenido y los botones.
  • un componente que contenga cada nuevo server que creemos pulsando en alguno de los botones.

La estructura de carpetas nos quedaría así:

  • 📂app
    • 📂control-panel
    • 📂server-element

1. Creamos los componentes usando los comandos ng g c control-panel --skipTests ng g c server-element --skipTests.

2. Cortamos el código referente al control-panel del app.component.html y lo pegamos dentro del template del control-panel, sustituyendo el código HTML que traía por defecto.

<div class="row">
  <div class="col">
    <p>Add new Servers or blueprints!</p>
    <label>Server Name</label>
    <input type="text" class="form-control" [(ngModel)]="newServerName">
    <label>Server Content</label>
    <input type="text" class="form-control" [(ngModel)]="newServerContent">
    <br>
    <button class="btn btn-primary" (click)="onAddServer()">Add Server</button>
    <button class="btn btn-primary mx-2" (click)="onAddBlueprint()">Add Server Blueprint</button>
  </div>
</div>

Notarás que ahora los métodos onAddServer onAddBlueprint se marcan como errores en tu IDE. Eso es porque no los tenemos definidos en el archivo TypeScript (en adelante, TS) de ese componente, sino en el archivo TS del AppComponent. Así que los cortamos de ese archivo y los añadimos al control-panel.component.ts.

import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-control-panel',
  templateUrl: './control-panel.component.html',
  styleUrls: ['./control-panel.component.css']
})
export class ControlPanelComponent implements OnInit {

  constructor() { }

  ngOnInit() {
  }

  onAddServer() {
    this.serverElements.push({
      type: 'server',
      name: this.newServerName,
      content: this.newServerContent
    });
  }

  onAddBlueprint() {
    this.serverElements.push({
      type: 'blueprint',
      name: this.newServerName,
      content: this.newServerContent
    });
  }

}

Si guardas, ahora tu app está rota, ya que Angular está buscando propiedades en el control-panel.component.ts, como serverElements, que no existen (porque nos las hemos dejado en el app.component.ts). Pero no podemos trasladar esa propiedad porque la necesitamos para replicar el componente en el app.component.html, en esta parte:

<div class="card" *ngFor="let element of serverElements">
        <div class="card-header">{{ element.name }}</div>
        <div class="card-body">
          <p class="card-text">
            <strong *ngIf="element.type === 'server'" style="color: red">{{ element.content }}</strong>
            <em *ngIf="element.type === 'blueprint'">{{ element.content }}</em>
          </p>
        </div>
      </div>
</div>

Lo que sí que podemos trasladar son las propiedades newServerName newServerContent, pero eso no soluciona el hecho de que la app siga rota. 😅

export class ControlPanelComponent implements OnInit {

  newServerName = '';
  newServerContent = '';

3. Cortamos la parte referente al server-element del app.component.html y la colocamos en el server-element.component.html, sustituyendo el código con el que venía por defecto.

4. Eliminamos el ngFor porque queremos seguir gestionando nuestro array de servidores (serverElements) desde el template del AppComponent.

<div class="card">
  <div class="card-header">{{ element.name }}</div>
  <div class="card-body">
    <p class="card-text">
      <strong *ngIf="element.type === 'server'" style="color: red">{{ element.content }}</strong>
      <em *ngIf="element.type === 'blueprint'">{{ element.content }}</em>
    </p>
  </div>
</div>

5. Añadimos los selectores HTML del control-panel y del server-element en el template del AppComponent.

6. Replicamos el componente server-element usando ngFor.

<div class="container">
  <app-control-panel></app-control-panel>
  <hr>
  <div class="row">
    <div class="col">
      <app-server-element *ngFor="let serverElement of serverElements"></app-server-element>
    </div>
  </div>
</div>

Así conseguimos que la UI de nuestra app vuelva a aparecer, aunque algo distinta, con los botones no funcionando como esperado. Esto es debido a que necesitamos obtener el nuevo servidor que añadamos usando el ControlPanelComponent y añadirlo al array de servidores del app.component.ts. Es decir, necesitamos establecer una comunicación entre componentes para que el AppComponent se entere de que uno de sus componentes hijos (el ControlPanelComponent) cambia cuando el usuario crea un nuevo servidor.

Luego, ese servidor que el usuario añada (cosa que gestiona el ServerElementComponent) será una información que también deberá recibir el AppComponent para poder mostrar ese servidor recién creado. Vamos a ver todo esto en detalle en la siguiente sección.

ControlPanelComponent

crea servidores

ServerElementComponent

gestiona los servidores

AppComponent

muestra los servidores

Nociones generales de property binding event binding

Después de dividir nuestra app en componentes, nos encontramos con el "inconveniente" de que debemos saber cómo pasar información de un componente a otro, es decir, debemos permitirles que se comuniquen entre sí.

Si necesitas conocimientos básicos sobre data binding, puedes consultar esta guía de iniciación. En esa guía mostramos cómo usar data binding  sobre elementos HTML, pero ahora vamos ir un paso más allá, ya que Angular nos permite usar data binding a 3 niveles:

En elementos HTML

En componentes

Cómo vincular nuestras propias propiedades para pasarlas de un componente a otro (custom property binding) 

Para poder seguir sin que nuestra app se rompa, vamos a comentar el código de los métodos del archivo control-panel.component.ts.

  onAddServer() {
    // this.serverElements.push({
    //   type: 'server',
    //   name: this.newServerName,
    //   content: this.newServerContent
    // });
  }

  onAddBlueprint() {
    // this.serverElements.push({
    //   type: 'blueprint',
    //   name: this.newServerName,
    //   content: this.newServerContent
    // });
  }

Si guardas, ya deberías poder ver la UI de tu app en el navegador.

Si observamos la template del ServerComponent, verás que estamos intentando acceder a un element (con element nos referimos a un servidor, sea un server normal o un blueprint. Esto es una clasificación hipotética que nos hemos inventado, como si de verdad tuviésemos un backend con servidores que podemos añadir).

Por eso, debemos crear una propiedad llamada element en el server.component.ts que represente a ese element del template. Dado que TS es un lenguaje de tipos estrictos, definimos el tipo de dato que será esa propiedad. Será un objeto.

Nuestro element tiene a su vez varias propiedades (type, name content), así que podemos especificárselas en su definición.

export class ServerElementComponent implements OnInit {

  element: { type: string; name: string; content: string };
  constructor() { }

  ngOnInit() {
  }

}

A pesar de este cambio, la propiedad element sigue siendo parte únicamente del ServerComponent, ya que no podemos acceder a ella desde fuera (desde otro lugar que no sea ese componente). Sería estupendo que pudiésemos acceder a ella desde el app.component.ts, en el array donde gestionamos los servidores (serverElements).

Es decir,

lo que queremos es pasar esa propiedad desde un componente hijo a un componente padre.

En nuestra app, el AppComponent es el componente padre (en inglés, parent component) del ServerComponent, porque estamos usando su selector dentro de la template del AppComponent.

Para intentar hacer funcionar esto, nos creamos un servidor dentro del array del app.component.ts, hardcoded. Recuerda que un servidor es un objeto de JavaScript (en adelante, JS), así que ese es el formato que debe tener. 

  serverElements = [{ type: 'server', name: 'Rocket Server', content: 'A test to try this server!' }];

Si guardas, verás que en tu navegador te sale el esqueleto de una card de Bootstrap, pero sin ningún contenido. 🤦‍♀️

Desde el app.component.html necesitamos acceder a la propiedad element dentro del selector app-server-element. Aquí llega el momento en el que hacemos property binding sobre un componente (por medio de su selector, app-server-element).

1. Utilizamos la sintaxis del property binding y lo vinculamos a la variable local del loop (serverElement). 

<app-server-element *ngfor="let serverElement of serverElements"
                    [element]="serverElement">
</app-server-element>

Pero verás que eso nos devuelve un error en la consola: 

Uncaught Error: Template parse errors:
Can't bind to 'element' since it isn't a known property of 'app-server-element'...

Este mensaje puede resultar confuso a priori, porque efectivamente, element es una propiedad del ServerElementComponent. Lo que ocurre es que por defecto,

toda propiedad de un componente es accesible únicamente desde ese componente, el acceso desde el exterior está "bloqueado". 🙅‍♀️

Para "desbloquear el acceso", debemos especificar qué propiedades queremos exponer al mundo exterior.

Lo hacemos añadiendo algo delante de la propiedad: el decorador @Input()Es como una función, así que debemos usar lo paréntisis para ejecutarla. No olvides importarlo desde @angular/core.

Exposicion componente al exterior con input

Con estos cambios, ahora cualquier componente que intentase acceder a la propiedad element, como actualmente está intentando el componente AppComponent, podrá tener acceso. 👍

import { Component, OnInit, Input } from '@angular/core';

@Component({
  selector: 'app-server-element',
  templateUrl: './server-element.component.html',
  styleUrls: ['./server-element.component.css']
})
export class ServerElementComponent implements OnInit {

 @Input() element: { type: string; name: string; content: string };

Si guardas, ya deberías ver en tu página el contenido que hemos definido en el app.component.ts.

input decorator uso

Cómo asignar un alias a una propiedad

Hasta ahora hemos vinculado la propiedad element mediante property binding y el decorador @Input. La hemos vinculado tal cual, con el nombre que le hemos puesto (element). Pero a veces no queremos usar el mismo nombre cuando declaramos una propiedad que cuando la utilizamos en otro componente. Es decir, podemos darle un nickname, también llamado alias

1. El alias se lo especificamos entre los paréntesis del @Input('alias aquí').

 @Input('srvElement') element: { type: string; name: string; content: string };

2. Ahora, donde habíamos hecho referencia a la propiedad element, debemos sustituirla por su alias,

porque podemos usar o bien su nombre "real" o bien su alias, pero no ambas cosas, ya que no funcionaría.

Así que cambiamos el nombre element por su alias en el archivo app.component.html, que es el único sitio donde hemos usado esta propiedad por el momento. 

      <app-server-element *ngFor="let serverElement of serverElements" 
                          [srvElement]="serverElement">
      </app-server-element>

Si guardas, deberías ver exactamente el mismo resultado en tu navegador que antes, pero ahora estamos usando un alias. 

Cómo pasar información del child component al parent component emitiendo nuestros propios eventos

Ya sabemos cómo pasar información desde un componente padre hacia un componente hijo (en inglés, child component). Ahora vamos a aprender a hacerlo en la otra dirección. 🔁

¿Cuándo necesitamos hacer este paso? Cuando algo en nuestro componente hijo cambie y queramos informar al componente padre (el componente que hospeda al hijo en su template) de dicho cambio.

Por ejemplo, en nuestro AppComponent estamos usando el componente ControlPanel, que adoptaría el papel de child componentEste child component está hecho para experimentar cambios, ya que dentro tenemos un par de botones destinados a eso.

Nuestro objetivo es que cuando hagamos click en esos botones podamos crear servers y blueprints, cosa que ahora es imposible porque en el archivo control-panel.component.ts tenemos invalidados los métodos que se encargarían de eso. Una vez hecho eso, informaremos al AppComponent de que un nuevo server blueprint ha sido añadido. ¡Vamos a ello! 👩‍💻

1. Vamos al archivo control-panel.component.ts y copiamos los dos métodos que hay, para pegarlos en el archivo app.component.ts. 

2. Les cambiamos el nombre ligeramente, porque queremos reflejar una acción que ocurra al añadir el server/blueprint (es decir, justo después de haber hecho click en los botones).

3. Descomentamos el código de los métodos para poder trabajar sobre él, aunque si ahora guardas, verás que tu aplicación vuelve a estar rota, ya que los métodos hacen referencia a dos propiedades (newServerName y newServerContent) que no están disponibles en el AppComponent. Y esa es exactamente la información que queremos pasar del ControlPanelComponent a su parent component (el AppComponent).

import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  serverElements = [{ type: 'server', name: 'Rocket Server', content: 'A test to try this server!' }];

  onServerAdded() {
    this.serverElements.push({
      type: 'server',
      name: this.newServerName,
      content: this.newServerContent
    });
  }

  onBlueprintAdded() {
    this.serverElements.push({
      type: 'blueprint',
      name: this.newServerName,
      content: this.newServerContent
    });
  }

}

Para eso, tendremos que emitir nuestro propio evento.
4. En el archivo app.component.html, lugar donde utilizamos el selector del ControlPanelComponent, nos creamos un evento propio (en inglés, custom event) al que bautizamos como serverCreated (que por el momento no hará nada)Es el mismo concepto que cuando usamos un click event y lo vinculamos a una función que queremos que se ejecute al hacer click en el elemento HTML al que hayamos añadido ese click event

En este caso, vinculamos el evento serverCreated al método onServerAdded, método que hemos preparado para este fin. Como queremos capturar cierta información emitida por el evento, le pasamos por parámetro $event. Esa información podría ser un objeto de JS con el nombre y el contenido de un servidor, por ejemplo. 

  <app-control-panel (serverCreated)="onServerAdded($event)"></app-control-panel>

Por tanto, ahora el método onServerAdded espera un parámetro, así que se lo especificamos. Con esto configurado, ya podemos asignarlo a las propiedades del método (los key-value pairs), sustituyendo así las propiedades newServerName newServerContent.

  onServerAdded(serverData: { serverName: string, serverContent: string }) {
    this.serverElements.push({
      type: 'server',
      name: serverData.serverName,
      content: serverData.serverContent
    });
  }

5. Replicamos el patrón para el método onBlueprintAdded. Es decir, nos creamos un custom event en el archivo app.component.html, en el selector <app-control-panel> y lo vinculamos a nuestro método onBlueprintAdded, capturando cierta información pasada por parámetro.

 <app-control-panel (serverCreated)="onServerAdded($event)"
                    (blueprintCreated)="onBlueprintAdded($event)">
 </app-control-panel>

6. Al igual que antes, en el app.component.ts definimos por parámetro el tipo de información que esperamos capturar en el método onBlueprintAdded y se la asignamos a las propiedades name content.

  onBlueprintAdded(blueprintData: { blueprintName: string, blueprintContent: string }) {
    this.serverElements.push({
      type: 'blueprint',
      name: blueprintData.blueprintName,
      content: blueprintData.blueprintContent
    });
  }

Dados estos pasos, todavía nos queda emitir nuestro propio evento desde el ControlPanelComponent. 🤯 ¡Vamos a ello!

7. En el archivo control-panel.component.ts nos creamos dos propiedades (serverCreated blueprintCreated). Efectiviwonder, coinciden con los nombres de nuestros custom events del archivo app.component.html.

🧐 Para convertirlas en "eventos" susceptibles de ser emitidos, debemos asignarles un objeto llamado EventEmittere importarlo.

El EventEmitter es un data type genérico, que TS identifica usando los caracteres < >. Entre esos caracteres le pasamos el tipo de información que queremos emitir, que en nuestro caso es simplemente la misma información que esperamos recibir en nuestros métodos del AppComponent, es decir, un objeto de JS con el nombre y el contenido de un servidor. 

No olvidemos llamar al constructor del objeto del EventEmitter: 

new EventEmitter<...>()

8. Repetimos el mismo proceso para la propiedad blueprintCreated.

serverCreated = new EventEmitter<{ serverName: string, serverContent: string }>();
blueprintCreated = new EventEmitter<{ blueprintName: string, blueprintContent: string }>();

9. Ahora, por fin, ya podemos emitir nuestro evento. Para eso, nos vamos a la función onAddServer y borramos el código comentado. Dentro usamos la propiedad serverCreated, a la que le aplicamos un método llamado emit. Esto emitirá un evento del tipo que le hemos especificado al EventEmitter.

10. Al método emit le pasamos el objeto que queremos que se emita (recordemos que se espera un objeto con un serverName  de tipo string y un serverContent de tipo string también). Al crear key-value pairs del objeto, asignamos las keys (serverName serverContent) a las propiedades newServerName newServerContent.

  onAddServer() {
    this.serverCreated.emit({
      serverName: this.newServerName,
      serverContent: this.newServerContent
    });
  }

11. Replicamos el mismo ejercicio en el método onAddBlueprint, con la propiedad blueprintCreatedclaro. 

  onAddBlueprint() {
    this.blueprintCreated.emit({
      blueprintName: this.newServerName,
      blueprintContent: this.newServerContent
    });
  }
Lo único que nos falta es añadir un decorador a nuestras propiedades para que sean susceptibles de ser escuchadas desde el exterior. Esa es la función del "@Output decorator". 

Al igual que el decorador @Input, el @Output debe ser importado desde 'angular/core'.

  import { Component, OnInit, EventEmitter, Output } from '@angular/core';

@Component({
  selector: 'app-control-panel',
  templateUrl: './control-panel.component.html',
  styleUrls: ['./control-panel.component.css']
})
export class ControlPanelComponent implements OnInit {
  @Output() serverCreated = new EventEmitter<{ serverName: string, serverContent: string }>();
  @Output() blueprintCreated = new EventEmitter<{ blueprintName: string, blueprintContent: string }>();

¡Y ya está! 👏 Nuestra demo vuelve a funcionar, con la diferencia de que ahora está todo bien dividido en componentes.

ejemplo event emitter

Puede parecer un proceso un tanto farragoso al principio, pero con un poco de práctica te harás con él. 💪

Cómo asignar un alias a nuestros custom events

Al igual que sucede con las propiedades a las que añadimos el decorador @Input, también podemos asignarle nicknames (alias) a nuestras propiedades que emitimos al exterior gracias al decorador @Output. Por ejemplo, en el archivo control-panel.component.ts, imagina que no queremos utilizar nuestra propiedad blueprintCreated con ese nombre cuando la utilicemos en el exterior. Así que se lo cambiamos:

  @Output('bpCreated') blueprintCreated = new EventEmitter<{ blueprintName: string, blueprintContent: string }>();

Pero no olvides que ahora ése es el nuevo nombre con el que debemos referirnos a dicha propiedad. Ese y ningún otro. Así que se lo cambiamos en los lugares donde estamos usando esa propiedad (únicamente en el AppComponent).

  <app-control-panel (serverCreated)="onServerAdded($event)"
                     (bpCreated)="onBlueprintAdded($event)">

¡Y voilà! Tu app debería funcionar exactamente igual que antes.

Conclusión de esta primera parte

Hasta ahora hemos aprendido a comunicar unos componentes con otros, pero esto es sólo la punta del iceberg, porque para este ejemplo concreto, establecer una comunicación entre parent component child component es lo más lógico, pero ¿qué pasaría si quisiéramos comunicar dos componentes que están uno al lado del otro? 🤔

Por ejemplo, fíjate que en el app.component.html tenemos el componente ControlPanel y el componente ServerElement, posicionados como "hermanos", ya que ninguno está dentro del otro, sino al lado. Si quisiéramos ponerlos en contacto el uno con el otro para pasarse información entre sí, deberíamos construir una compleja (y poco práctica) estructura pasándole datos a su parent component común para luego configurar el recibo de dicha información y el posterior envío. Un planteamiento un tanto caótico, vaya. 

Para ese tipo de casos sería más conveniente utilizar los services, una herramienta que nos ofrece Angular pero que queda fuera del ámbito de este artículo. 

¡BRAVO

Y hasta aquí la parte #1 de esta saga sobre el uso avanzado de los componentes y el databiding en Angular. Si quieres seguir aprendiendo, aquí tienes la parte #2. Si te queda alguna duda, ¡nos vemos en los comentarios!

Sobre la autora de este post

Soy Rocío, una abogada reconvertida en programadora. Soy una apasionada de aprender cosas nuevas y ferviente defensora de que la única manera de ser feliz es alcanzando un equilibrio entre lo que te encanta hacer y lo que te saque de pobre. Mi historia completa, aquí. 

Más recursos de aprendizaje

Aprender Angular a través de tutoriales web y cursos online está genial, pero si necesitas un apoyo en forma de libro, puede que éstos te sirvan de ayuda:

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 Amazon, lo que significa que, si compras alguno de estos libros, yo me llevaré una pequeña comisión y a ti no costará nada extra. Vamos, lo que se dice un win-win 😊.

Otros artículos que pueden interesarte

Días del 353 al 386
Objetivos versus realidad Y nuevamente, llegó otro día clave. Llegó…y pasó. El pasado 4 de marzo este Reto Computer Geek[...]
Angular: Entendiendo la Directiva ngModel
Angular es un framework que nos permite, entre otras cosas, añadir contenido dinámico a nuestros archivos HTML. Una de las formas[...]
Pipes en Angular | Guía completa
¿Qué son los pipes?Los pipes son una herramienta de Angular que nos permite transformar visualmente la información, por ejemplo, cambiar un[...]
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.

Como toda web legal que se precie, utilizamos cookies para asegurar que damos la mejor experiencia al usuario en nuestro sitio web. Si continúas utilizando este sitio asumiremos que estás de acuerdo. más información

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

Cerrar