Hoy el post va de perros.He querido mostrar varios aspectos que voy a exponer:

  • Un clase ViewModel. (La clase Dog)
  • Incluir hilos (Task) en la clase
  • Una ventana diseñada con XAML con una sola línea de código InitializeComponent();de modo que el código XAML debe entenderse con la clase (ViewModel) y deben actualizarse sus controles a pesar de que estén corriendo los hilos.
  • Introducción a los Converter

baloo03

Para mostraros esto de un modo más original, he pensado en mi perro Baloo, el cual he pensado muchas veces como actúa, la sencillez con la que toma decisiones y como no, he diseñado un perro con un aparato digestivo de lo más sencillo, (mi perro es bastante más complejo y no defeca u orina en cualquier sitio, mi trabajo me ha costado). Hoy he leido una noticia que comentaba que los perros nos entendían las palabras y frases, por lo que quiero decir que si algún perro lee esto, no se sienta ofendido por mis palabras en las que no vanaglorio su intelectualidad; rompiendo una lanza en favor de los perros, tengo que decir que a veces demuestran más sentido común que los seres humanos, pero como siempre, me centraré en el post!

El diseño consta de dos clases, la clase Dog que hará as funciones de ViewModel, la clase BoolToVisibleConverter que es un converter y un ventana para mostrar la información diseñada integramente con XAML. La aplicación no necesita interacción del usuario, es decir , los parámetros varían con el tiempo tomando algunos de ellos aleatoriamente.

La clase Dog implementa por supuesto la interfaz INotifyPropertyChanged y tiene los siguientes métodos y propiedades:

Baloo01.png

Los métodos de los que consta la clase son las cosas normales que realiza un perro y cada vez que realiza algunas de estas acciones varían las propiedades que dependiendo de su estado, ejecutarán otra acción. Las principales propiedades son Energia, NivelEstomago, NivelVejiga y NivelIntestino.

Según un intervalo, según pasa el tiempo, la energía se va consumiendo, por tanto cuando tiene bajos los niveles de energía, tiene hambre o el nivel estomacal es bajo, come, cuando tiene el estómago lleno, hace la digestión, cuando hace la digestión llena el intestino y aumenta la energía, posteriormente lo vacía haciendo caquitas, cuando tiene el nivel de energía lleno corre, si corre baja la energía más rápido y tiene sed, cuando llena la vejiga hace pipí, si se queda sin energía duerme hasta llenar el nivel de esta y así indefinidamente, es decir un sistema retroalimentado como pueda ser un sistema digestivo. Os muestro un video donde podréis verlo más claro.

Creo que lo de menos es como funciona el sistema, porque lo que importa es que cada método tiene un intervalo propio, (no se tarda lo mismo en hacer la digestión que en hacer pipí) y si no se creara un hilo al iniciar el método, la aplicación quedaría bloqueada y no podríamos moverla o alterar sus controles, así que cada vez que se ejecuta un método, se crea un nuevo Task.Factory.StarNew(()=>Metodo()), pudiendo continuar el sistema digestivo con sus cosas. Usando este código y sin que la clase toque una sola línea de la clase de la ventana, tenemos asegurado un funcionamiento adecuado.

        private void Comer(int cantidad)
        {
            EstaComiendo = true;
            for (int i = 0; i < cantidad; i++)
            {
                NivelEstomago ++;
                Thread.Sleep(IncComer);
                // si el perro está lleno, no come más!
                if (NivelEstomago >= CapacidadEstomago)
                {
                    NivelEstomago = CapacidadEstomago;
                    break;
                }
            }

            Task.Factory.StartNew(() => hacerDigestion());
            EstaComiendo = false;
        }

La clase es instanciada desde el código XAML, así que el constructor inicia el método Vivir y este arrastra al resto de métodos. Para instanciar una clase desde el código XAML, debemos crearla en Window.Resources o en Grid.Resources para que luego el DataContext pueda ser asignado con la key de la clase y además al instanciarlo, podemos asignar el valor a las propiedades públicas, lo muestro

    <Window.Resources>
        <local:BoolToVisibleConverter x:Key="b2vc">
        <local:Dog x:Key="dog" Nombre="Baloo" Raza="Bodeguero">
    <Window.Resources>
    <Grid DataContext="{StaticResource dog}">

Por otra parte, el código XAML, una vez que se ha instaciado la clase, solo queda hacer binding a las propiedades que deseamos actualizar, las cuales deben llamar al evento de cambio de propiedad, cada vez que se produzca un cambio, en caso contrario, no se mostrarán datos

        public int NivelEstomago
        {
            get { return nivelEstomago; }
            set
            {
                nivelEstomago = value < 0 ? 0 : value;
                if (NivelEstomago== CapacidadEstomago)
                {
                    Task.Factory.StartNew(() => hacerDigestion());
                }
                OnPropertyChanged("NivelEstomago");
            }
        }

he aquí un ejemplo del enlace de datos desde la propiedad NivelEstomago al valor del control ProgressBar que mostrará como de lleno esté el estómago 😉

<ProgressBar x:Name="PBEstomago" Grid.Column="2" Value="{Binding NivelEstomago}" Style="{StaticResource progressbarStyle}"/>

Por último, voy a hacer referencia a los converter. WPF tiene multitud de converter, por ejemplo, cuando escribimos True en una propiedad, WPF está convirtiendo este valor en un valor booleano, o cuando le damos un color a la propiedad Background, lo que se plasma en el código XAML es un texto, sin embargo este se convertirá en un objeto Brush o SolidColorBrush o lo que corresponda. Hay veces que necesitaremos convertir valores booleanos en un color, o por ejemplo, como es el caso que voy a exponer, quiero variar la visibilidad de un control mediante un valor booleano como antiguamente, visible=true o false, pero en WPF la visibilidad de un control tiene diferentes valores (por cierto a alguien se le tenía que haber ocurrido antes), bueno pues mediante valores booleano procedentes de las propiedades de nuestra clase Dog, convertiremos un control en visible por true u oculto con false. Para hacer esto, lo primero debemos indicar en los resources es el camino a la clase que contiene el converter, caso que podemos ver en el código de más arriba, cuando instanciábamos la clase y le dimos como valor de key “b2vc”

<local:BoolToVisibleConverter x:Key="b2vc">

en el binding debemos indicar el path desde donde se alimentará el control y la clase conversor. Esta clase debe implementar la interfaz IValueConverter, la cual implementa dos métodos, Convert y ConvertBack el primero es usado para convertir los datos desde la clase al control y el segundo para convertir los datos del control hacia la clase siempre que tengamos habilitada esta opción (ver Mode=TwoWay). Yo he creado dos propiedades en la clase, para poder usar valores booelanos que funcionen al contrario, con true se oculten y con false se muestren. En estos métodos, e pasa el valor a convertir, el tipo, parámetros y la cultura, pero lo normal es que solo se pase valor a convertir, (es bueno tener ases en la manga).

Si tenéis cualquier duda, dejáis un comentario. Espero que hayáis disfrutado con la clase canina. A continuación os dejo el código completo o el proyecto como gustéis.

Saludos

Clase Dog

/* ****************************************************************
* © JOAQUIN MARTINEZ RUS 2016
* PROYECTO:        Sistema digestivo canino
* Archivo:         Dog.cs
* Descripción:     Clase Dog.
 *
* Historial:
*                  1. 31 ago 2016. Creación
*
* Comentarios:
*
*
*******************************************************************/
using System;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
using System.ComponentModel;

namespace DogsNameSpace
{
    public class Dog: INotifyPropertyChanged
    {
        public Dog()
        {
            Task.Factory.StartNew(() => Vivir());
        }

        #region Eventos

        public event PropertyChangedEventHandler PropertyChanged;

        #endregion

        #region Propiedades
        // SOLO LECTURA
        public double MaxEnergia { get; } = 100;
        public int CapacidadEstomago { get; } = 80;
        public int CapacidadVejiga { get; } = 60;
        public int CapacidadInstentinoGrueso { get; } = 80;

        // VARIABLES PRIVADAS
        private int energia = 23;
        private int nivelIntestino = 0;
        private int nivelVejiga=22;
        private int nivelEstomago=25;
        private int incEnergia = 3000;

        // PÚBLICAS
        public string Nombre { get; set; }
        public string Raza { get; set; }
        public bool EnergiaEstaDisminuyendo { get; set; }

        private bool estaComiendo;
        private bool estaHaciendoCaca;
        private bool estaHaciendoPipi;
        private bool estaBebiendo;
        private bool estaHaciendoDigestion;
        private bool estaLadrando;
        private bool estaCorriendo;
        private bool estaDurmiendo;

        public bool EstaDurmiendo
        {
            get { return estaDurmiendo; }
            set
            {
                estaDurmiendo = value;
                OnPropertyChanged("EstaDurmiendo");
            }
        }
        public bool EstaComiendo
        {
            get { return estaComiendo; }
            set
            {
                estaComiendo = value;
                OnPropertyChanged("EstaComiendo");
            }
        }
        public bool EstaHaciendoCaca
        {
            get { return estaHaciendoCaca; }
            set
            {
                estaHaciendoCaca = value;
                OnPropertyChanged("EstaHaciendoCaca");
            }
        }
        public bool EstaHaciendoPipi
        {
            get { return estaHaciendoPipi; }
            set
            {
                estaHaciendoPipi = value;
                OnPropertyChanged("EstaHaciendoPipi");
            }
        }
        public bool EstaBebiendo
        {
            get { return estaBebiendo; }
            set
            {
                estaBebiendo = value;
                OnPropertyChanged("EstaBebiendo");
            }
        }
        public bool EstaHaciendoDigestion
        {
            get { return estaHaciendoDigestion; }
            set
            {
                estaHaciendoDigestion = value;
                OnPropertyChanged("EstaHaciendoDigestion");
            }
        }
        public bool EstaLadrando
        {
            get { return estaLadrando; }
            set
            {
                estaLadrando = value;
                OnPropertyChanged("EstaLadrando");
            }
        }
        public bool EstaCorriendo
        {
            get { return estaCorriendo; }
            set
            {
                estaCorriendo = value;
                OnPropertyChanged("EstaCorriendo");
            }
        }

        public int NivelEstomago
        {
            get { return nivelEstomago; }
            set
            {
                nivelEstomago = value < 0 ? 0 : value;
                if (NivelEstomago== CapacidadEstomago)
                {
                    Task.Factory.StartNew(() => hacerDigestion());
                }
                OnPropertyChanged("NivelEstomago");
            }
        }
        public int Energia
        {
            get { return energia; }
            set
            {
                energia = value < 0 ? 0 : value;

                if (EnergiaEstaDisminuyendo)
                {
                    switch (energia)
                    {
                        case 0:
                            Task.Factory.StartNew(() => Dormir());
                            break;
                        case 20:
                            // comer
                            Ladrar(5);
                            Random rnd = new Random();
                            int cant = rnd.Next(1, 100);
                            Task.Factory.StartNew(() => Comer(cant));

                            break;
                        case 30:
                            // beber
                            Ladrar(2);
                            Random rnd1 = new Random();
                            int cantAgua = rnd1.Next(1, 30);
                            Task.Factory.StartNew(() => Beber(cantAgua));
                            break;

                    }

                }
                else if (energia==100 && !EstaCorriendo)
                {
                    Ladrar(5);
                    Random rnd2 = new Random();
                    int tiempo = rnd2.Next(1, 30);
                    Task.Factory.StartNew(() => Correr(tiempo));
                }

                OnPropertyChanged("Energia");
            }
        }
        public int NivelIntestino
        {
            get { return nivelIntestino; }
            set
            {
                nivelIntestino = value < 0 ? 0 : value;
                if (nivelIntestino >= CapacidadInstentinoGrueso)
                {
                    Task.Factory.StartNew(() => HacerCaca());
                }
                OnPropertyChanged("NivelIntestino");
            }
        }
        public int NivelVejiga        {
            get { return nivelVejiga; }
            set
            {
                nivelVejiga = value<0?0:value;

                if (nivelVejiga>=CapacidadVejiga)
                {
                    Task.Factory.StartNew(() => HacerPipi());
                }
                OnPropertyChanged("NivelVejiga");
            }
        }

        public int IncEnergia
        {
            get { return incEnergia; }
            set
            {
                incEnergia = value;
                OnPropertyChanged("IncEnergia");
            }
        }
        public int IncComer { get { return (int)(IncEnergia * 0.16); } }
        public int IncBeber { get { return (int)(IncEnergia * 0.08); } }
        public int IncHacerCaca { get { return (int)(IncEnergia * 0.03); } }
        public int IncHacerPipi { get { return (int)(IncEnergia * 0.03); } }
        public int IncLadrar { get { return (int)(IncEnergia * 0.015); } }
        public int IncCorrer { get { return (int)(IncEnergia * 0.33); } }
        public int IncDigestion { get { return (int)(IncEnergia * 0.16); } }
        public int IncDormir { get { return (int)(IncEnergia * 0.16); } }

        #endregion

        /// <summary>
        /// Inicia el evento de cambio de propiedad
        /// </summary>
        /// <param name="p_PropertyName">Nombre de la propiedad</param>
        public void OnPropertyChanged(string p_PropertyName)
        {
            if (this.PropertyChanged != null)
            {
                this.PropertyChanged(this, new PropertyChangedEventArgs(p_PropertyName));
            }
        }        

        /// <summary>
        /// Método inicial.
        /// </summary>
        private void Vivir()
        {
            do
            {
                Energia--;
                EnergiaEstaDisminuyendo = true;
                Thread.Sleep(IncEnergia);

            } while (true);
        }

        private void Dormir()
        {
            EnergiaEstaDisminuyendo = false;
            for (int i = 0; i < MaxEnergia; i++)
            {
                Energia++;
                Thread.Sleep(IncDormir);
            }
        }

        /// <summary>
        /// Realiza un número de ladridos determinado
        /// </summary>
        /// <param name="numLadridos">Número de ladridos a realizar</param>
        /// <returns>Retorna una cadena de texto con los ladridos</returns>
        private string Ladrar(int numLadridos)
        {
            StringBuilder ladridos = new StringBuilder();
            EstaLadrando = true;

            for (int i = 0; i < numLadridos; i++)
            {
                ladridos.Append("Guau ");
                Thread.Sleep(IncLadrar);
            }

            EstaLadrando = false;

            return ladridos.ToString();
        }

        /// <summary>
        /// Realiza un ladrido
        /// </summary>
        /// <returns>Retorna una cadena de texto con un ladrido</returns>
        private string Ladrar()
        {
            return Ladrar(1);
        }

        private void Correr(int tiempo)
        {
            EstaCorriendo = true;
            for (int i = 0; i < tiempo; i++)
            {
                Energia--;
                EnergiaEstaDisminuyendo = true;
                Thread.Sleep(IncCorrer);

                // si lleva más de 15 corriendo, bebe agua
                if (i > 15)
                {
                    Beber(30);
                }

            }

            EstaCorriendo = false;
        }

        private void Comer(int cantidad)
        {
            EstaComiendo = true;
            for (int i = 0; i < cantidad; i++)
            {
                NivelEstomago ++;
                Thread.Sleep(IncComer);

                // si el perro está lleno, no come más!
                if (NivelEstomago >= CapacidadEstomago)
                {
                    NivelEstomago = CapacidadEstomago;

                    break;
                }
            }

            Task.Factory.StartNew(() => hacerDigestion());
            EstaComiendo = false;

        }

        private void Beber(int cantidad)
        {
            EstaBebiendo = true;

            int nivelInicial = NivelVejiga;

            for (int i = 0; i < nivelInicial; i++)
            {
                NivelVejiga++;
                Thread.Sleep(IncBeber);
            }
            EstaBebiendo = false;
        }

        private void HacerCaca()
        {
            EstaHaciendoCaca = true;
            // vacia el intestino
            int nivelInicial = NivelIntestino;
            for (int i = 0; i < nivelInicial; i++)
            {
                NivelIntestino--;
                Thread.Sleep(IncHacerCaca);
            }

            Ladrar();

            EstaHaciendoCaca = false;

        }

        private void HacerPipi()
        {
            EstaHaciendoPipi = true;
            int nivelInicial = NivelVejiga;

            for (int i = 0; i < nivelInicial; i++)
            {
                NivelVejiga--;
                Thread.Sleep(IncHacerPipi);
            }
            Ladrar();

            EstaHaciendoPipi = false;

        }

        private void hacerDigestion()
        {
            EstaHaciendoDigestion = true;
            int nivelInicial = NivelEstomago;
            for (int i = 0; i < nivelInicial; i++)
            {
                NivelEstomago--;
                EnergiaEstaDisminuyendo = false;
                if (Energia<100)
                {
                    Energia++;
                }
                else
                {
                    break;
                }
                Thread.Sleep(IncDigestion);
            }

            // Llenar intestino
            double masaConvertida = nivelInicial * .2;
            for (int i = 0; i < (nivelInicial - (int)masaConvertida); i++)
            {
                NivelIntestino++;
                Thread.Sleep(20);
            }

            EstaHaciendoDigestion = false;
        }

    }
}

Clase BoolToVisibleConverter

using System;
using System.Windows;
using System.Windows.Data;

namespace DogsNameSpace
{
    public class BoolToVisibleConverter : IValueConverter
    {
        #region Constructor

        public BoolToVisibleConverter() { }

        #endregion

        #region Propiedades

        public bool Collapse { get; set; }
        public bool Reverse { get; set; }

        #endregion

        #region IValueConverter

        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            bool bValue = (bool)value;

            if (bValue != Reverse)
            {
                return Visibility.Visible;
            }
            else
            {
                if (Collapse)
                    return Visibility.Collapsed;
                else
                    return Visibility.Hidden;
            }
        }

        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            Visibility visibility = (Visibility)value;

            if (visibility == Visibility.Visible)
                return !Reverse;
            else
                return Reverse;
        }

        #endregion
    }
}

Código XAML

<Window x:Class="DogsNameSpace.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:DogsNameSpace"
        mc:Ignorable="d"
        Title="Sistema digestivo canino" Height="600" Width="700" >
    <Window.Resources>
        <local:BoolToVisibleConverter x:Key="b2vc"/>
        <local:Dog x:Key="dog" Nombre="Baloo" Raza="Bodeguero"/>
    </Window.Resources>
    <Grid DataContext="{StaticResource dog}">
        <Grid.RowDefinitions>
            <RowDefinition Height="273"/>
            <RowDefinition Height="41*"/>
            <RowDefinition Height="135*"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="250"/>
            <ColumnDefinition Width="80"/>
            <ColumnDefinition Width="80"/>
            <ColumnDefinition Width="80"/>
            <ColumnDefinition Width="80"/>
            <ColumnDefinition Width="80*"/>
        </Grid.ColumnDefinitions>
        <StackPanel HorizontalAlignment="Left">
            <TextBlock Text="{Binding Nombre}" Style="{StaticResource textblockStyle1}"/>
            <TextBlock Text="{Binding Raza}" Style="{StaticResource textblockStyle1}"/>
        </StackPanel>
        <Image x:Name="BalooRight_png" Source="BalooRight.png" Stretch="Uniform"/>
        <TextBlock Grid.Column="1" Text="{Binding Energia}" Style="{StaticResource textblockPorcStyle1}"/>
        <ProgressBar x:Name="PBEnergia"  Grid.Column="1" Value="{Binding Energia}" Style="{StaticResource progressbarStyle}"/>
        <TextBlock Grid.Column="2" Text="{Binding NivelEstomago}" Style="{StaticResource textblockPorcStyle1}"/>
        <ProgressBar x:Name="PBEstomago" Grid.Column="2" Value="{Binding NivelEstomago}" Style="{StaticResource progressbarStyle}"/>
        <TextBlock Grid.Column="3" Text="{Binding NivelVejiga}" Style="{StaticResource textblockPorcStyle1}"/>
        <ProgressBar x:Name="PBIntestino" Grid.Column="3" Value="{Binding NivelVejiga}" Style="{StaticResource progressbarStyle}"/>
        <TextBlock Grid.Column="4" Text="{Binding NivelIntestino}" Style="{StaticResource textblockPorcStyle1}"/>
        <ProgressBar x:Name="PBVejiga" Grid.Column="4" Value="{Binding NivelIntestino}" Style="{StaticResource progressbarStyle}"/>
        <TextBlock Grid.Column="5" Text="{Binding IncEnergia}" Style="{StaticResource textblockPorcStyle1}"/>

        <TextBlock x:Name="textBlockEnergia" Style="{StaticResource textblockStyle}" Grid.Column="1" Grid.Row="1" Text="Energía" />
        <TextBlock x:Name="textBlockEstomago" Style="{StaticResource textblockStyle}" Grid.Column="2"  Grid.Row="1" Text="Estómago" />
        <TextBlock x:Name="textBlockVejiga" Style="{StaticResource textblockStyle}" Grid.Column="3" Grid.Row="1" Text="Vejiga" />
        <TextBlock x:Name="textBlockIntestino" Style="{StaticResource textblockStyle}" Grid.Column="4" Grid.Row="1" Text="Intestino" />
        <TextBlock x:Name="textBlockTime" Style="{StaticResource textblockStyle}" Grid.Column="5" Grid.Row="1" Text="Intervalo (ms)" />

        <StackPanel Grid.Column="0" Grid.Row="2">

            <TextBlock Text="Durmiendo" Style="{StaticResource textblockStyle1}" Visibility="Collapsed"/>
            <TextBlock Text="Haciendo la digestión" Style="{StaticResource textblockStyle1}" Visibility="{Binding EstaHaciendoDigestion,Converter={StaticResource b2vc}}"/>
            <TextBlock Text="Ladrando" Style="{StaticResource textblockStyle1}" Visibility="{Binding EstaLadrando,Converter={StaticResource b2vc}}"/>
            <TextBlock Text="Comiendo" Style="{StaticResource textblockStyle1}" Visibility="{Binding EstaComiendo,Converter={StaticResource b2vc}}"/>
            <TextBlock Text="Bebiendo" Style="{StaticResource textblockStyle1}" Visibility="{Binding EstaBebiendo,Converter={StaticResource b2vc}}"/>
            <TextBlock Text="Corriendo" Style="{StaticResource textblockStyle1}" Visibility="{Binding EstaCorriendo,Converter={StaticResource b2vc}}"/>
            <TextBlock Text="Haciendo Caca" Style="{StaticResource textblockStyle1}" Visibility="{Binding EstaHaciendoCaca,Converter={StaticResource b2vc}}"/>
            <TextBlock Text="Haciendo Pipí" Style="{StaticResource textblockStyle1}" Visibility="{Binding EstaHaciendoPipi,Converter={StaticResource b2vc}}"/>
            <TextBlock Text="Durmiendo" Style="{StaticResource textblockStyle1}" Visibility="{Binding EstaDurmiendo,Converter={StaticResource b2vc}}"/>
        </StackPanel>
        <Slider x:Name="slider" Grid.Column="5" Height="200"  Value="{Binding IncEnergia}" VerticalAlignment="Bottom" HorizontalAlignment="Center" Width="15" Orientation="Vertical" Minimum="1000" Maximum="5000" SmallChange="100" TickFrequency="500"/>
    </Grid>
</Window>
Anuncios

2 comentarios en “WPF y la clase canina

  1. To determine internal swelling is difficult enough, therefore, consequently, they influence the body for a long time, which threatens the normal fetus. Fluids are dangerous because they break blood circulation. Similar situation leads to worsening feeding and the breath baby, formed hypoxia.
    Fighting such a pathology should be done with the help of correction feeding and special exercises so that fluid does not stay tissues. If the woman is resting, then under the feet better put a cushion or pillow to improve the blood circulation of tired legs. Do not long time to sit or stand, as this leads to stagnation in the body. It is recommended that the knee-elbow position several times a day in order to increase blood flow.
    how to reduce swelling while pregnant

    Me gusta

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión /  Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión /  Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión /  Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión /  Cambiar )

Conectando a %s