Patrón de diseño – Composite

El patrón de diseño Composite nos sirve para construir estructuras complejas partiendo de otras estructuras mucho más simples, dicho de otra manera, podemos crear estructuras compuestas las cuales están conformadas por otras estructuras más pequeñas.

Para comprender mejor como funciona este patrón imaginemos una casa de ladrillos, las casas como tal no están hecha de una pieza, si observamos las paredes estas esta echas de pequeñas piezas llamadas ladrillos, entonces, el conjunto de estos ladrillos crea paredes, y un conjunto de paredes crean una casa. este ejemplo puede ser aplicado al patrón Composite, y no digo que vallamos a crear una casa con este patrón, sino más bien nos da una idea de cómo trabaja para poder utilizarlo con otros ejemplos.

Patrón de diseño Composite
Fig. 1: Estructura del patrón de diseño Composite.

El patrón Composite requiere mínimo de tres componentes para poder existir los cuales son Componente, Leaf o Rama y Composite.

Component: Generalmente es una interface o clase abstracta la cual tiene las operaciones mínimas que serán utilizadas, este componente deberá ser extendido por los otros dos componentes Leaf y Composite. En nuestro ejemplo esto podría representar de forma abstracta un ladrillo o toda la casa (Mas adelante comprenderemos porque)

Leaf: El leaf u hoja representa la parte más simple o pequeña de toda la estructura y este extiende o hereda de Component. En nuestro ejemplo, este representaría un ladrillo de nuestra casa.

Composite: Aquí es donde está la magia de este patrón, ya que el composite es una estructura conformada por otros Composite y Leaf, si vemos en la imagen 1, vemos que los Composite tiene los métodos add y remove los cuales nos permiten agregar objetos de tipo Component, Sin embargo como hablamos anteriormente, el Componente es por lo general un Interface o Clase abstracta  por lo que podremos agregamos objetos de tipo Composite o Leaf. Visto desde el punto de vista del ejemplo de la casa el Composite podría representar un conjunto de ladrillos o la casa completa, Esto desde luego sería agregando varias Ladrillo(Leaf) al Composite para crear una Pared.

No te preocupes si no lograste comprender del todo la explicación anterior, para fortalecer la explicación hablaremos de un ejemplo más técnico donde podamos programar un escenario concreto.

Caso de estudio del patrón Composite.

Imaginemos un sistema de punto de venta, en el cual se le pueden vender al cliente una serie de productos, estos productos pueden ser productos simples (Leaf) o paquetes (Composite). El sistema permitirá crear Ordenes de ventas las cuales están compuestas por 1 o muchos productos.

La siguiente imagen ilustra la estructura de un paquete.

Patrón de diseño Composite
Fig. 2: Imagen que muestra de forma gráfica como esta compuesto un paquete, Los paquete están creados a partir de un conjunto de productos simples y otros paquetes por lo que el precio de un paquete esta calculado por el precio de sus hijos de forma recursiva.

La imagen 2 muestra la estructura de una forma conceptual, sin embargo la estructura es un poco más compleja, ya que está formado por una estructura de dato llamada Árbol.

Patrón de diseño Composite
Fig. 3: La imagen muestra un solo paquete es cual esta formado de otros productos simple y compuestos, un compuesto seria otro paquete, el cual tiene dentro mas productos simples. y como vimos en la figura 2, el precio de un paquete es calculado por el precio de todos los hijos de forma recursiva.

Puntos importantes a tomar en cuenta:

  • Un paquete es producto compuesto de varios productos simples y otros paquetes.
  • Los paquetes y productos simples deberán ser tratados de la misma forma, por lo que deberán tener un padre en común.
  • El precio de un paquete es la suma de todos los productos simples que contenga.
  • El sistema deberá mostrar el total de la Orden y los productos que contiene.

Ya conocida la teoría, pasemos a la práctica.

En primero lugar crearemos la clase la cual será nuestro Component

package javamex.patronesdiseño.composite;

/**
 * @author oblancarte
 * 
*/
public abstract class AbstractProduct {

    protected String name;
    protected double price;

    public AbstractProduct(String name, double price) {
        super();
        this.name = name;
        this.price = price;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public double getPrice() {
        return price;
    }

    public void setPrice(double price) {
        this.price = price;
    }
}

Como podemos observar la clase AbstractProduct es una clase abstracta la cual define las características mínimas de un producto, las cuales deben tener todos los productos sin importar que sean productos simples o paquetes. Para este ejemplo, lo minino que un producto debe tener es un nombre y un precio.

En segundo lugar, definimos lo que vendría siendo el Leaf o producto simple.

package javamex.patronesdiseño.composite;

public class SimpleProduct extends AbstractProduct {

    protected String brand;

    public SimpleProduct(String name, double price, String brand) {
        super(name, price);
        this.brand = brand;
    }

    public String getBrand() {
        return brand;
    }

    public void setBrand(String brand) {
        this.brand = brand;
    }
}

Lo primero que observamos es que las clases SimpleProduct extiende de AbstractProduct, luego solo como ejemplo agregamos un atributo que solo un producto simple pueda tener, en este caso, agregamos la marca, aunque en este ejemplo no nos sirve de nada nos da una idea de que podemos personalizar los productos según el tipo.

Otro punto importante es que el método getPrice no lo sobre escribimos por lo que toma el heredado de AbstractProduct el cual solo regresa el precio de la propiedad price.

Para completar el patrón tenemos el Composite, el cual es una agrupación de AbstractProduct.

package javamex.patronesdiseño.composite;

import java.util.ArrayList;
import java.util.List;

public class CompositeProduct extends AbstractProduct {

    private List< AbstractProduct > products = new ArrayList< AbstractProduct >();

    public CompositeProduct(String name) {
        super(name, 0);
    }

    @Override
    public double getPrice() {
        double price = 0d;
        for (AbstractProduct child : products) {
            price += child.getPrice();
        }
        return price;
    }

    @Override
    public void setPrice(double price) {
        throw new UnsupportedOperationException();
    }

    public void addProduct(AbstractProduct product) {
        this.products.add(product);
    }

    public boolean removeProduct(AbstractProduct product) {
        return this.products.remove(product);
    }
}

Lo primero a observar en la clase CompositeProduct es que extiende también la clase AbstractProduct lo cual nos garantiza que tanto el SimpleProduct (Producto Simple) y el CompositeProduct(Paquete) puedan ser tratados de igual manera y nos nos interesa saber en tiempo de ejecución si es un paquete o un producto simple.

En el caso de CompositeProduct veremos que las propiedades que tiene no son las de un producto o paquete, sino que además de tener las que hereda de SimpleProduct, tiene una Lista de AbstractProduct, lo cual nos dice que en esta lista puede tener cualquier objeto que herede de esta clase, y para nuestro ejemplo estas clases serian SimpleProduct y CompositeProduct, Es aquí donde nos damos cuenta que un paquete puede estar compuesto por paquetes y productos simples.

Observemos también el método getPrice y setPrice, el método getPrice es sobre escrito para regresar el precio de todos los productos que contiene y el método setPrice es sobre escrito para que no nos permita establecer un precio, de esta forma cumplimos la regla de que el precio de un paquete es la suma del precio de todos los productos que contiene.

Por último, veamos los métodos addProduct y removeProduct los cuales nos permite agregar y remover productos de un paquete.

Para poder realizar una venta es necesario crear un Orden de venta, por lo que se crear la clase SaleOrder la cual nos permite agregar los productos a vender.

package javamex.patronesdiseño.composite;

import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;

/**
 * @author oblancarte
 * 
*/
public class SaleOrder {

    private long orderId;
    private String customer;
    private Calendar dateTime;
    private List< AbstractProduct > products = new ArrayList< >();

    public SaleOrder(long orderId, String customer) {
        super();
        this.orderId = orderId;
        this.customer = customer;
    }

    public long getOrderId() {
        return orderId;
    }

    public void setOrderId(long orderId) {
        this.orderId = orderId;
    }

    public String getCustomer() {
        return customer;
    }

    public void setCustomer(String customer) {
        this.customer = customer;
    }

    public Calendar getDateTime() {
        return dateTime;
    }

    public void setDateTime(Calendar dateTime) {
        this.dateTime = dateTime;
    }

    public List getProducts() {
        return products;
    }

    public void setProducts(List products) {
        this.products = products;
    }

    public double getPrice() {
        double price = 0d;
        for (AbstractProduct child : products) {
            price += child.getPrice();
        }
        return price;
    }

    public void addProduct(AbstractProduct product) {
        products.add(product);
    }

    public void removeProduct(AbstractProduct product) {
        products.remove(product);
    }

    public void printOrder() {

        NumberFormat formater = new DecimalFormat("###,##0.00");
        System.out.println("\n=============================================nOrden: " + orderId + "nCliente: " + customer + "nProductos:n");
        for (AbstractProduct prod : products) {
            System.out.println(prod.getName() + "ttt$ " + formater.format(prod.getPrice()));

        }
        System.out.println("Total: " + formater.format(getPrice()) + "n=============================================");
    }
}

Veamos que la clase SaleOrden no tiene más que una lista de productos, id, fecha de venta y el nombre del cliente (Datos solo como ejemplo).

Como puntos importantes podemos ver los métodos addProduct y removeProduct, los cuales nos permiten agregar y remover productos a la Orden.

Tenemos el método getPrice, el cual nos regresa el precio total de la Orden.

PrintOrden método que imprime el detalle de la Orden en pantalla.

Ejecución.

Para finalizar este ejemplo veremos la clase Main donde veremos cómo funciona el ejemplo:

package javamex.patronesdiseño.composite;

public class Main {

    public static void main(String[] args) {
        SimpleProduct ram4gb = new SimpleProduct("Memoria RAM 4GB", 750, "KingStone");
        SimpleProduct ram8gb = new SimpleProduct("Memoria RAM 8GB", 1000, "KingStone");

        SimpleProduct disk500gb = new SimpleProduct("Disco Duro 500GB", 1500, "ACME");
        SimpleProduct disk1tb = new SimpleProduct("Disco Duro 1TB", 2000, "ACME");

        SimpleProduct cpuAMD = new SimpleProduct("AMD phenon", 4000, "AMD");
        SimpleProduct cpuIntel = new SimpleProduct("Intel i7", 4500, "Intel");

        SimpleProduct smallCabinete = new SimpleProduct("Gabinete Pequeño", 2000, "ExCom");
        SimpleProduct bigCabinete = new SimpleProduct("Gabinete Grande", 2200, "ExCom");

        SimpleProduct monitor20inch = new SimpleProduct("Monitor 20'", 1500, "HP");
        SimpleProduct monitor30inch = new SimpleProduct("Monitor 30'", 2000, "HP");

        SimpleProduct simpleMouse = new SimpleProduct("Raton Simple", 150, "Genius");
        SimpleProduct gammerMouse = new SimpleProduct("Raton Gammer", 750, "Alien");

//Computadora para Gammer que incluye 8gb de ram,disco de 1tb, procesador Intel i7
//gabinete grande,monitor de 30' y un mouse gammer.
        CompositeProduct gammerPC = new CompositeProduct("Gammer PC");
        gammerPC.addProduct(ram8gb);
        gammerPC.addProduct(disk1tb);
        gammerPC.addProduct(cpuIntel);
        gammerPC.addProduct(bigCabinete);
        gammerPC.addProduct(monitor30inch);
        gammerPC.addProduct(gammerMouse);

//Computadora para Casa que incluye 4gb de ram,disco de 500gb, procesador AMD Phenon
//gabinete chico,monitor de 20' y un mouse simple.
        CompositeProduct homePC = new CompositeProduct("Casa PC");
        homePC.addProduct(ram4gb);//
        homePC.addProduct(disk500gb);
        homePC.addProduct(cpuAMD);
        homePC.addProduct(smallCabinete);
        homePC.addProduct(monitor20inch);
        homePC.addProduct(simpleMouse);

//Paque compuesto de dos paquetes, El paquete Gammer PC y Home PC
        CompositeProduct pc2x1 = new CompositeProduct("Paquete PC Gammer + Casa");
        pc2x1.addProduct(gammerPC);
        pc2x1.addProduct(homePC);

        SaleOrder gammerOrder = new SaleOrder(1, "Juan Perez");
        gammerOrder.addProduct(gammerPC);
        gammerOrder.printOrder();

        SaleOrder homeOrder = new SaleOrder(2, "Marcos Guerra");
        homeOrder.addProduct(homePC);
        homeOrder.printOrder();

        SaleOrder comboOrder = new SaleOrder(3, "Paquete 2x1 en PC");
        comboOrder.addProduct(pc2x1);
        comboOrder.printOrder();

        SaleOrder customOrder = new SaleOrder(4, "Oscar Blancarte");
        customOrder.addProduct(homePC);
        customOrder.addProduct(ram8gb);
        customOrder.addProduct(ram4gb);
        customOrder.addProduct(monitor30inch);
        customOrder.addProduct(gammerMouse);
        customOrder.printOrder();
    }
}

De la linea 5 a la 21 creamos los productos simples. Los cuales únicamente contiene el nombre del producto y el precio de venta.

Estos productos son instancias de SimpleProduct por lo que vendrían siendo el Leaf del patrón de diseño.

En las lineas 23 a la 47 podemos ver como se crean los paquetes o productos compuestos. Estos productos solo son creados con el nombre del paquete y no tiene un precio de venta. Una vez creados se les agregan los productos simples mediante el método addProduct, Si vemos bien los paquetes que creamos, veremos que todos están conformados por productos simples a excepción del paquete pc2x1 el cual es un paquete creado a partir de dos paquetes. Esto provocara que el precio de este paquete sea el precio de los dos paquetes hijos, y el precio de los hijos sera el precio de todos los productos simple que contiene, de esta forma creamos un árbol parecido al que vimos en la figura 3.

Por ultimo de la 48 a la 66 se crean 4 ordenes con productos distintos. En la orden 1 y 2 vemos que solo vendemos un paquete, en la orden 3 vendemos un paquete conformado de dos paquetes y en la orden 4 vendemos un paquete y 4 productos simples.

Resultado de la ejecución:

El resultado de la ejecución del programa nos da el siguiente resultado:

=============================================
Orden: 1
Cliente: Juan Perez
Productos:

Gammer PC $ 12,450.00
Total: 12,450.00
=============================================

=============================================
Orden: 2
Cliente: Marcos Guerra
Productos:

Casa PC $ 9,900.00
Total: 9,900.00
=============================================

=============================================
Orden: 3
Cliente: Paquete 2x1 en PC
Productos:

Paquete PC Gammer + Casa $ 22,350.00
Total: 22,350.00
=============================================

=============================================
Orden: 4
Cliente: Oscar Blancarte
Productos:

Casa PC $ 9,900.00
Memoria RAM 8GB $ 1,000.00
Memoria RAM 4GB $ 750.00
Monitor 30' $ 2,000.00
Raton Gammer $ 750.00
Total: 14,400.00
=============================================
Introducción a los patrones de diseño
¿Quieres aprender más patrones como este? te invito a que vea mi libro.

Análisis: 

Veamos que paso.

En la orden no.1 se vendió un paquete llamado PC Gammer con un precio de 12,450 ,¿pero por qué cuesta eso?, si regresamos a la definición del paquete veremos que se le agregaron 6 productos simples: ram8gb($1,000), disk1tb($2,000), cpuIntel($4,500), bigCabinet(2,200), monitor30inch($2,000), gammerMouse($750), Si sumamos el precio de los 6 productos nos dará el total de 12,450 el mismo precio del paquete.

En la orden no.2 pasa lo mismo, se vende un paquete el cual contiene 6 productos simples pero con un precio menor.

La orden no.3 es un poco distinta, esta orden vende al igual que las anteriores un solo paquete, sin embargo, este paquete pc2x1 es un paquete que se creó a partir de otros dos paquetes. En este caso el total de la orden es de 22,350, pero si vemos el precio del paquete, Gammer PC($12,450) y Home PC($9,900), veremos la suma de estos dos da el precio total del paquete pc2x1($22,350).

Por último, tenemos la orden 4, esta orden explota al máximo el poder del patrón de diseño Composite, ya que nos permite vender paquetes y productos simples sin tener que preocuparnos de que tipo de productos es. ya que de forma polimorfica el programa es capaz de calcular el precio de ambos tipos de productos sin necesidad de programar un trato especial.

Esta orden vende una Home PC($9,900), ram8gb($1,000),ram4gb(750),monitor30inch($2,000),gammerMouse(750), de los cuales Home PC es un paquete y el resto de los productos son productos simples. Si sumamos el precio de los productos veremos que el total es de 14,400.

Como veremos, este patrón es muy flexible con estructuras de datos compuestas, en este caso vimos que teníamos paquetes y productos simples, sin embargo, podríamos crear un tercer tipo de producto que extienda de AbstracProduct y darle el comportamiento que queramos. Otro ejemplo que le podríamos dar, es para agrupar las cuentas de un cliente, de esta forma, podríamos saber el saldo de todas las cuentes de un cliente.

Si te gusto este post, por favor dale me gusta y compártelo ya que esto me ayudara a crear más y mejor material.

Ya está a la venta mi nuevo libro “Introducción a los patrones de diseño“, en donde expongo los 25 patrones de diseño más utilizados y explicados desde un enfoque del mundo real, con ejemplos que pueden ser aplicados en tus proyectos. Sin duda es unos de los libros más completos de patrones de diseño en español.

34 thoughts to “Patrón de diseño – Composite”

  1. Muy buen ejemplo, estaba buscando una web que explicase este patrón de diseño con un ejemplo simple y después de un buen rato he encontrado este magnífica web.

    Sólo una duda, en el constructor de todas las clases llamas al super() siempre, aunque no herede de ninguna clase ¿lo haces por alguna razón especial?

    Muchas gracias 🙂

    1. Hola Juanma, en realidad el super() lo agrega en automáticos el mismo IDE cuando creo el constructor y en los casos donde no se hereda de ninguna clase no tiene mucho sentido pero por costumbre suelo dejarlo. Así que en estos casos, el super lo único que hace es llamar al constructor de Object, el cual es llamado en automático aunque no define la instrucción super().

  2. Profesor Oscar buenas noches,

    Gracias por tan valiosa información, apenas me inicio en la programación con Java por ello le solicito amablemente una breve explicación de la instrucción Child ya que monto el programa en eclipse para verlo funcionar y me saca error en esta línea. Mil gracias

        1. Hola Cesar, lo que vez en la línea 17 se conoce como ForEach, es una forma de recorrer los arreglos sin la necesidad de contador. Fue inicialmente introducido en el Java 1.5 por lo que si esta instrucción te esta marcando error, podría ser por que tengas una versión anterior a la 1.5. Si tienes el error que te arroja el Eclipse te podría ayudar mejor.
          saludos.

  3. Profesor Oscar buenas noches y de nuevo gracias por su ayuda,

    Este es el mensaje de error que me sale al ejecutar el programa:

    Exception in thread “main” java.lang.Error: Unresolved compilation problem:
    Type mismatch: cannot convert from element type Object to AbstractProduct

    at javamex.patronesdiseño.composite.SaleOrder.printOrder(SaleOrder.java:78)
    at javamex.patronesdiseño.composite.Main.main(Main.java:51)

    Saludos.

    1. Hola Cesar, creo que encontre el problema, intenta reemplazar la línea 8 de la clase en questión por lo siguiente:
      private List< AbstractProduct > products = new ArrayList< AbstractProduct >();

      Esto debería de quetarte el error. Si te soluciono el problema, te agradecería me lo comentaras.
      saludos.

      1. buenas noches profesor, lo que pasa es que aun me sigue dando el mismo error, al poner el mouse dice incompatible types object cannot be converted to AbstractProduct, soy principiante en Java y no encuentro solución

  4. Profesor Oscar buenos días,
    Quiero confirmarle que gracias a la modificación de la línea 8 de la clase e cuestión, el programa funcionó perfectamente. Le agradezco mucho por su colaboración.

  5. buenas noches profesor lo que pasa es que sigue saliendo el mismo error E
    xception in thread “main” java.lang.Error: Unresolved compilation problem:
    Type mismatch: cannot convert from element type Object to AbstractProduct
    at javamex.patronesdiseño.composite.SaleOrder.printOrder(SaleOrder.java:78)
    at javamex.patronesdiseño.composite.Main.main(Main.java:51)
    aun con la modificacion de la linea 8, estuve investigando aun soy principiante en java y no encuentro solución, cuando paso el mouse sobre la advertencia dice incompatible types: Object cannot be converted to AbstractProduct espero pueda ayudarme

  6. Excelente articulo, me ayudo a entender este patron de diseño. Pero me surge una duda, ¿por qué la clase ” AbstractProduct” es abstracta sino tiene metodos abstractos?. Estoy empezando en esto de los patrones y estoy un poco confundido.
    De antemano, Muchas gracias!

    1. Hola Luis, antes de contestar tu pregunta quiero aclarar que una clase abstracta no tiene por que tener método abstractos. La idea de las clases abstractas es poder tener una funcionalidad semi-implementada, la cual podamos extender pero nunca instanciar.

      Con esto explicado, la clase AbstractProduct tiene implementadas las propiedades name y price, pues son propiedades que todos los productos deben de tener, luego extendemos la clase AbstractProduct para complementar los datos que debe de tener un producto.

      Otra alternativa es crear solamente una interface.

  7. Excelente, tengo una duda en UML como se relaciona y con que tendria relacion la clase saleOrder, ya que como tal no extiende del componente, en ese caso seria una asociación simple con componente en este caso astradproduct ?

    1. Hola Andres, en realidad la clase SaleOder es una clase que queda fuera del patrón, yo solo la pongo para crear un ejemplo más real de como se crearía una orden de venta con productos compuestos, realmente el patrón composite está limitado a los productos, el resto es solo para darle un enfoque más práctico y lo puedes ver como lo utilizarías en un proyecto real

  8. Bueno días, muy interesante el artículo estoy empezando con el tema de los patrones de diseño y la verdad en este caso me aclaro bastante, referente a los patrones de diseño de creación la verdad es que no tengo claro cuando utilizar cada uno y como se combinan entre ellos, por ejemplo para un supuesto en el que quisiéramos crear varios modelos de vehículos que comparten algo en común, por ejemplo la motorización y transmisión, es la misma para todos, pero unos disponen de unas opiciones y otros de otras en función del modelo y además la mayoría de las opciones son elegidas por el usuario en tiempo de ejecución, ¿que patrón se adecuaria mejor a esta situación?. _Muchas gracias.

    1. Hola Nissrine, no estoy seguro, pues con lo que me cuentas no veo sentido utilizar una composición, digo, podrías, pero creo que por lo simple del problema, creo que te traería más complicaciones que beneficios, además, no veo cual sería la ventaja de utilizarlo, pues no se ventaja tendrías de explotar la composición.

      Quizás, y no estoy seguro por no conocer el problema concreto, podrías utilizar el patrón Builder para ir agregando las partes del carro y finalmente construirlo.

      saludos.

  9. Muchas gracias por tu respuesta Oscar y perdona que no me haya explicado bien, el tema es que quiero modelar una fabrica de coches que actualmente dispone de tres modelos cada uno con un precio base. Los tres modelos disponen de la misma motorizacion y transmision pero difieren en cuanto a sus baterias , dimensiones, numero de plazas y opciones de equipamiento, ¿Como modelo mi coche con estas opciones de equipamiento, puede que sean elegidas o no por el cliente en tiempo de ejecución?, mi modelo final dependera de lo que elija el cliente, no se qué modelo construire de los tres, si se que todos los modelos llavaran la misma motorización y transmision, se que llevaran una bateria pero no se cual si de 80 KWh ó 90 0 100, no se si llevarán la opción de sensor de aparcamientos o no. Además hay opciones que se pueden elegir en todos modelos (el sensor de aparcamiento) y otras solo en algunos (tapiceria deportiva). hay colores que se comparten y otros exclusivos de cada modelo…
    Perdona Oscar y muchas gracias por tu atención he leido mucho sobre le tema pero no lo tengo claro y como tú articulo sobre composite me quedo bastante claro plantee la duda.

    1. Perdona Oscar se me olvido comentarte que posteriormente se pueden realizar pedidos de uno o varios coches, una o varias baterias o asientos, o solicitar repetir un pedido previo. Entiendo que para conseguir el sistema se deberán combinar varios patrones. Muchas gracias Oscar por tu atención.

    1. Hola Felipe, tienes que agregar el tipo genérico AbstractProduct a la lista de productos, lo que pasa es que cuando se renderiza en blog interpreta el generico como una etiqueta HTML y la quita.

      1. podrias poner en un ejemplo donde ponerlo y que hay que poner,
        tengo el mismo problema en los for de salesorder
        y no se como arreglarlo

        muchas gracias

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *