Posts Tagged ‘Scala’

FactoryPal: New Scala framework for creating objects as test data. Say no to Fixtures and Mocks.

by @mgonto, original article can be found here

Hey everyone! First of all, if you want to read the original article please go to this link

FactoryPal is a scala framework that lets you create objects as test data. All you have to do is define the templates for each of the classes that you want FactoryPal to create objects from. After that, FactoryPal takes care of the rest.

Have you ever heard of factory_girl a super cool Ruby framework? Well, FactoryPal is factory_girl for Scala. It is pretty similar in its use. The difference is that FactoryPal is 100% type safe, which all of us Scala people love.

Here is a link to Github for the anxious https://github.com/mgonto/factory_pal

How do we use this?

FactoryPal is a singleton object where you can register all of the templates. For example, you can define a template as follows:

FactoryPal.register[Person] { person =>
    person.name.mapsTo("gonto") and
    person.age.isRandom
}

In this example, we register a new template for class model. If we try to set a value for a property that Person doesn’t has, your project won’t compile. If you try to set a value to a property that isn’t the type of that property, the project won’t compile either. Pretty cool huh? This was possible thanks to Scala Macros and Dynamic, two features added in the latest Scala 2.10 RC release.

For the time being, there are 3 supported operations on a field template.

  • mapsTo: This sets a certain specific value to that property.
  • isRandom: This sets a random value based on the type of the field. I’ve created some implicit randomizers for common objects (String, Long, Int, Double, etc.) but you can create your own. This is pretty similar to Ordering[T] used in List.
  • isAnotherFactoryModel: You tell FactoryPal that this is an inner object that can be constructed with another template of FactoryPal. For the time being, there can only be one template for each class. I’m going to change this very soon.

After we created the template, we can instantiate objects of that template as follows:

val person = FactoryPal.create[Person]

The create method has another overload that lets you add some field overriders for certain test. For example you can do the following:

val person = FactoryPal.create[Person] { (person : ObjectBuilder[Person]) =>
    person.age.mapsTo(45) alone
}

And that’s it. That’s all you need to know to use this.

How can I add this to my project?

This is an example configuration for Build.scala for your SBT project :). There’re only snapshots for now as Scala 2.10 is not yet final. Once it’s, I’m going to make a release.

import sbt._
import sbt.Keys._
 
object ApplicationBuild extends Build {
 
  lazy val root = Project(
    id = "factory_pal_sample",
    base = file("."),
    settings = Project.defaultSettings ++ Seq(
      name := "factory_pal_sample",
      organization := "ar.com.gonto",
      version := "0.1",
      scalaVersion := "2.10.0-RC3",
      scalacOptions += "",
      licenses      := ("Apache2", new java.net.URL("http://www.apache.org/licenses/LICENSE-2.0.txt")) :: Nil,
      libraryDependencies ++= Seq(
       "org.scala-lang" % "scala-compiler" % "2.10.0-RC3",
       "ar.com.gonto" % "factory_pal_2.10" % "0.1-SNAPSHOT",
       "org.scalatest" % "scalatest_2.10.0-RC3" % "1.8-B1" % "test"
      ),
      resolvers ++= Seq(
         "Typesafe repository" at "http://repo.typesafe.com/typesafe/releases/",
         Resolver.url("Factory Pal Repository", 
          url("http://mgonto.github.com/snapshots/"))(Resolver.ivyStylePatterns)
      )
    )
  )
}

Take a look at the dependency and the repository!

What does it use internally?

Internally, this framework uses Scala Macros, Dynamic and the new Reflection library provided by Scala 2.10.

Next Steps

The next things I want to do are:

  • Add the posibility to have multiple templates for one Class
  • Add template inheritance
  • Add helpers to use this with ScalaTest and Specs2. For the moment, you can create the templates in the before.

For more information or to take a look at the code go to Github

Play! Framework 2 (2.1) Scala with Slick made easy (With example)

by @mgonto, original article can be found here

Hi everyone!

First of all, I want to tell you that the original post is from here (Blogeek).

Today I’ve seen some videos about Slick and Play! Framework 2.1 and I got excited, so I thought, let’s code an app with Slick. What did I realize? It wasn’t that simple. Play! isn’t yet thought to work with Slick very well.

First of all, if you want to see this example complete you can go to

https://github.com/mgonto/slick-play2-example

In Slick, you create table definitions for the class you want to use. However, all of the imports for the Table, the columns, etc. are driver dependant. So, when you change the driver, you don’t want to change the import in the code. You just want to change a configuration in the application.conf from Play! Framework. That’s exactly what I did.

So, let’s see first how it’s implemented. Every Driver (H2, Postgres, etc) extends from a trait called ExtendedProfile. So, I needed my table classes to use some profile that was injected by me. Once I realized that I needed to make some DI I thought about Cake Pattern. For more information about the Cake Pattern please viisit Cake pattern

So, the first thing I implemented was a Trait Profile with an abstract Extended Profile value:

package models

import slick.driver.ExtendedProfile

trait Profile {
  val profile: ExtendedProfile
}

After this, I needed to create the Users table. That Users table needed the profile, so I’d create a UserModule which would need the Profile trait. Then, as I’d mix with a Profile trait, I’d import the things from the abstract ExtendedProfile field and everything would compile. Then I create the users table with an ID and a Name (pretty simple)

package models

case class User(id: Option[Int], name : String)

trait UserComponent {
  this: Profile =>

  import profile.simple._

  object Users extends Table[User]("users") {
    def id = column[Int]("id", O.PrimaryKey)
    def name =  column[String]("name", O.NotNull)
    def * = id.? ~ name <> (User, User.unapply _)

    def add(user: User)(implicit session: Session) = {
      this.insert(user)
    }

    def countByName(name: String)(implicit session: Session) = {
      (for {
        user <- Users
        if (user.name === name)
      } yield(user)).list.size
    }

  }
}

After this, I create a DataAccessLayer class (DAL) which would receive the ExtendedProfile as a constructor parameter and then would include the other traits (Profile and UserModule)

package models

import slick.driver.ExtendedProfile

class DAL(override val profile: ExtendedProfile) extends UserComponent with Profile {

  import profile.simple._

  def create(implicit session: Session): Unit = {
    Users.ddl.create //helper method to create all tables
  }
}

After this, I have everything to create a DAL from a given profile. However, now I needed a way to configure the profile, so I added a new property in the application.conf.

slick.db.driver=scala.slick.driver.H2Driver

Then, I created a trait that would import this driver from the conf and create the database to do the queries and the dal with the corresponding profile. It has two methods, getDb and getDal which will be then used by two other classes.

package models

import slick.session.Database
import play.api.db.DB
import play.api.Application
import slick.driver.ExtendedProfile

trait DBeable {

  val SLICK_DRIVER = "slick.db.driver"
  val DEFAULT_SLICK_DRIVER = "scala.slick.driver.H2Driver"

  def getDal(implicit app : Application) : DAL = {
    val driverClass = app.configuration.getString(SLICK_DRIVER).getOrElse(DEFAULT_SLICK_DRIVER)
    val driver = singleton[ExtendedProfile](driverClass)
    new DAL(driver)
  }

  def getDb(implicit app : Application) = {
    Database.forDataSource(DB.getDataSource())
  }

  private def singleton[T](name : String)(implicit man: Manifest[T]) : T =
    Class.forName(name + "$").getField("MODULE$").get(man.runtimeClass).asInstanceOf[T]

}

The only things left are creating the DB once the app starts and having a singleton object to be able to ask for the dal and the database to do the queries from a controller. This is done as following:

import models.{User, DBeable, AppDB}
import play.api.db.DB
import play.api.GlobalSettings
import play.api.Application
import slick.session.Session

object Global extends GlobalSettings with DBeable {

  override def onStart(app: Application) {
    implicit val application = app
    lazy val database = getDb
    lazy val dal = getDal
    database.withSession {
      implicit session: Session =>
        dal.create
    }
  }
}
package models
import play.api.Play.current
object AppDB extends DBeable {

  lazy val database = getDb
  lazy val dal = getDal

}

And that’s it :). Now you have everything configured to start coding. I’ve tried if everything was working with a test as following.

class UserSpec extends Specification {

  "User" should {

    "be saved" in {
      running(FakeApplication(additionalConfiguration = inMemoryDatabase())) {
        AppDB.database.withSession {
          implicit session: Session =>
            AppDB.dal.Users.add(User(Some(2), "hola"))
            AppDB.dal.Users.countByName("pepe") must beEqualTo(0)
            AppDB.dal.Users.countByName("hola") must beEqualTo(1)

        }
      }
    }
  }
}

And that’s it 🙂 Now you have Play! configured for running with Slick.

Cya people!

Deploying Play Framework 2 apps, with Java AND Scala, to Openshift

Let’s Play! in the cloud

A couple of weeks, Mark Atwood, Jorge Aliss, and me, Sebastián Scarano participated in Red Hat’s webinar LET’S PLAY! IN THE CLOUD: DEVELOPING JAVA WEB APPS ON OPENSHIFT

In the webinar Mark gave a neat introduction to Openshift, Red Hat’s free Platform as a Service:

Then we developed a basic contact manager web application, combining Java and Scala source code in the same Play 2 application, and deployed it on Openshift.

With this quickstart (https://github.com/opensas/play2-openshift-quickstart) you’ll be able to take any Play 2 application and deploy it on openshift. Just follow this instructions.

And here (https://github.com/opensas/play2-contacts-demo) you will find the contact demo app.

In the demo, in spite our internet connection conspiring against us, we managed to cover the following topics:

You can also check this article in which we explain in detail how we took advantage of the new “do-it-yourself” application type on Openshift to achieve native support for Play Framework application on Openshift.

So, if you want to start deploying your play apps on openshift right away, just sign up at openshift.com and enter PLAY!WEBINAR as promotional code, and you’ll get 3 gears, each with 1GB ram and 512 MB data space, for free.

Have fun playing on the cloud!

Play Framework posted values revisited

This is a follow up of palamago’s original tip: how to get a single POST value?

Working with posted values with Play Framework 2.0, without defining a form mapping, might not be so obvious as it was with Play 1.x, that’s why I’m writing this quick cheatsheet.

For this quick sample, let’s define the following view:

app/views/index.scala.html

@(message: String)

message: @message <br />

<h2>Scala form</h2>

<form action="@routes.ScalaPoster.save()" method="POST">
  scala name: <input name="scala_name"> <br />
  scala surname: <input name="scala_surname"> <br />
  <input type="submit" value="save">
</form>

<h2>Java form</h2>

<form action="@routes.JavaPoster.save()" method="POST">
  java name: <input name="java_name"> <br />
  java surname: <input name="java_surname"> <br />
  <input type="submit" value="save">
</form>

And the following routes file:

conf/routes

# Home page
GET     /                         controllers.Application.index

POST    /scala                    controllers.ScalaPoster.save
POST    /java                     controllers.JavaPoster.save

With java, accesing directly the request body:

app/controllers/JavaPoster.java

package controllers;

import play.mvc.*;

import views.html.*;

import java.util.Map;

public class JavaPoster extends Controller {
  
  public static Result save() {

    final Map<String, String[]> values = request().body().asFormUrlEncoded();
    final String name = values.get("java_name")[0];
    final String surname = values.get("java_surname")[0];
    
    return ok(index.render(String.format("You are %s, %s",surname, name)));
  }
  
}

Or using a DynamicForm:

package controllers;

import play.mvc.*;

import views.html.*;

import play.data.DynamicForm;

public class JavaPoster extends Controller {
  
  public static Result save() {

    final DynamicForm form = form().bindFromRequest();
    final String name = form.get("java_name");
    final String surname = form.get("java_surname");
    return ok(index.render(String.format("You are %s, %s",surname, name)));
  }
  
}

Now the scala version, accessing the body:

app/controllers/ScalaPoster.java

package controllers

import play.api.mvc._

object ScalaPoster extends Controller {

  def save = Action { request =>

    def name = request.body.asFormUrlEncoded.get("scala_name")(0)
    def surname = request.body.asFormUrlEncoded.get("scala_surname")(0)

    Ok(views.html.index("You are %s, %s".format(surname, name))) 
  }

}

And defining a form

package controllers

import play.api.mvc._

import play.api.data.Form
import play.api.data.Forms.tuple
import play.api.data.Forms.text

object ScalaPoster extends Controller {

  val form = Form(
    tuple(
      "scala_name" -> text,
      "scala_surname" -> text
    )
  )

  def save = Action { implicit request =>
 
    def values = form.bindFromRequest.data
    def name = values("scala_name")
    def surname = values("scala_surname")

    Ok(views.html.index("You are %s, %s".format(surname, name))) 
  }

}

Notice the implict request in the above sample. You could explicitly pass it to bindFromRequest with

    def values = form.bindFromRequest()(request).data

You can also play with the tuples and issue something like

    val data = form.bindFromRequest.get
    Ok(views.html.index("You are %s, %s".format(data._2, data._1))) 

or

    val (name, surname) = form.bindFromRequest.get
    Ok(views.html.index("You are %s, %s".format(surname, name))) 

And of course, if you just want to read a single posted value you could issue:

    def name = Form("scala_name" -> text).bindFromRequest.get

There are several ways to achieve it. I hope this serves as a useful reminder.

Desplegando aplicaciones de Play Framework 2, con Java Y Scala, en Openshift

Play framework en la nube de Red Hat

Hace un par de semanas, Mark Atwood, Jorge Aliss, y quien les escribe, Sebastián Scarano participamos del webinar LET’S PLAY! IN THE CLOUD: DEVELOPING JAVA WEB APPS ON OPENSHIFT organizado por Red Hat.

En el webinar, Mark dio una completa introducción a Openshift, la plataforma de cloud computing de Red Hat:

Luego junto con Jorge desarrollamos una simple agenda de contactos, usando Play Framework 2.0, combinando en una misma aplicación código fuente en Java y en Scala, y luego la pusimos en producción en Openshift.

Con este quickstart (https://github.com/opensas/play2-openshift-quickstart) podrán desplegar cualquier aplicación de Play 2.0 en Openshift. Simplemente sigan estas instrucciones.

Y aquí (https://github.com/opensas/play2-contacts-demo) encontrarán el código fuente de la agenda de contactos.

En la demo, a pesar de que nuestra conexión a internet nos jugó una mala pasada, logramos abordar los siguientes temas:

También pueden consultar este artículo en el cual analizamos en detalle cómo aprovechar el nuevo tipo de aplicación “do-it-yourself” (hagalo usted mismo) de Openshift para brindar soporte nativo a las aplicaciones de Play.

Si que si desean empezar a jugar con Openshift ya mismo, regístrense en openshift.com ingresen PLAY!WEBINAR como código de promoción, y recibirán 3 gears, cada uno con 1GB de memoria ram y 512 MB de espacio de almacemiento, gratis.

¡Esperamos que les guste!

Play framework 2 quicktip: interactively play with your application from the scala console

Short version for impatients:

path_to_your_play2_app$ play console
scala> new play.core.StaticApplication(new java.io.File("."))

And don’t forget that the tab key is your friend!


When I first started to play with Scala, I was amazed by the Scala interactive interpreter (also known as REPL, read-evaluate-print-loop). It was one of those things that you never expected to find in a statically typed, compiled language like java or scala.

What would you say if we could have it for our play applications?… In scala OR JAVA! And yes, with tab completion and all the bells-and-whistles…

Well, thanks to Peter Hausel (@pk11) from the play dev team, I found out how to do it.

Just open a command prompt and type:

cd <path_to_your_play2_app>
play console

[info] Loading project definition from /home/sas/Dropbox/Public/devel/play/apps/play2/todo/project
[info] Set current project to todo (in build file:/home/sas/Dropbox/Public/devel/play/apps/play2/todo/)
[info] Starting scala interpreter...
[info] 
Welcome to Scala version 2.9.1.final (Java HotSpot(TM) Client VM, Java 1.6.0_31).
Type in expressions to have them evaluated.
Type :help for more information.

That’s it, you are at the scala console, and what’s best, with tab completion enabled!

Right there you can start playing with scala, and also with your application, like this:

scala> val contact = models.Contact(1, "new contact", "new address")
contact: models.Contact = Contact(1,new contact,new address)

You can test your views, and of course you can also issue imports to save yourself quite a few keystrokes

scala> import models._, views.html._
import models._
import views.html._

scala> val contacts = Seq( Contact(1, "@develsas", "Buenos Aires"), Contact(2, "@pk11", "Paris") )
contacts: Seq[models.Contact] = List(Contact(1,@develsas,Buenos Aires), Contact(2,@pk11,Paris))

scala> contact.list(contacts)
res1: play.api.templates.Html = 
<!DOCTYPE html>
<html>
    <head>
        <title>Contact list</title>
        <link rel="styles
[...]
<tr>
	<td>@develsas</td>
	<td>Buenos Aires</td>
</tr>
<tr>
	<td>@pk11</td>
	<td>Paris</td>
</tr>
[...]

But when you try to access your database you’ll get the following error:

scala> val contacts = Contact.all()
java.lang.RuntimeException: There is no started application
	at scala.sys.package$.error(package.scala:27)
[...]

That’s easy, you just have to start your application

scala> import play.core._
scala> new StaticApplication(new java.io.File("."))
[info] play - database [default] connected at jdbc:h2:data/db
[info] play - Application started (Prod)
res1: play.core.StaticApplication = play.core.StaticApplication@10cdd4

And now you can play arround interactively with your running application

scala> val contacts = Contact.all
contacts: Seq[models.Contact] = List(Contact(1,Paul,Boston), Contact(3,Paolo,Roma), Contact(4,Paulain,Paris), Contact(5,Abelardo,San Justo))

scala> val newContact = Contact(6, "new contact", "new address")
newContact: models.Contact = Contact(6,new contact,new address)

scala> Contact.insert(newContact)
res2: Int = 1

scala> val contacts = Contact.all
contacts: Seq[models.Contact] = List(Contact(1,Paul,Boston), Contact(3,Paolo,Roma), Contact(4,Paulain,Paris), Contact(5,Abelardo,San Justo), Contact(6,new contact,new address))

Now go check your database, you’ll see the new contact has been persisted to your db.

One last tip. If you are working against the in-memory database, and you have defined any evolution script, you’ll receive this message when you start the application form play console:

scala> new play.core.StaticApplication(new java.io.File("."))
[info] play - database [default] connected at jdbc:h2:mem:play
[warn] play - Your production database [default] needs evolutions! 

# --- Rev:1,Ups - 74ff2d1
[...]

[warn] play - Run with -DapplyEvolutions.default=true if you want to run them automatically (be careful)
PlayException: Database 'default' needs evolution! [An SQL script need to be run on your database.]
	at play.api.db.evolutions.EvolutionsPlugin$$anonfun$onStart$1.apply(Evolutions.scala:422)
[...]

The solution is quite easy, just start play with

play -DapplyEvolutions.default=true

And your evolution scripts will be automatically applied when you start the application.

Go ahead and give it a try. Grab, for example, the computer-database java demo

$ cd <path_to_your_play2_installation>/samples/java/computer-database
$ play -DapplyEvolutions.default=true

[computer-database] $ console
[info] Starting scala interpreter...
[info] 
Welcome to Scala version 2.9.1.final (Java HotSpot(TM) Client VM, Java 1.7.0_03).
Type in expressions to have them evaluated.
Type :help for more information.


scala> new play.core.StaticApplication(new java.io.File("."))
[info] play - database [default] connected at jdbc:h2:mem:play
[info] play - Application started (Prod)
res0: play.core.StaticApplication = play.core.StaticApplication@129796b

And now, just start playing around with your app

scala> val page = models.Computer.page(1, 10, "name", "asc", "")
page: com.avaje.ebean.Page[models.Computer] = com.avaje.ebeaninternal.server.query.LimitOffsetPage@d386c9

scala> val computerList = page.getList()
computerList: java.util.List[models.Computer] = BeanList size[10] hasMoreRows[true] list[models.Computer@137, models.Computer@20d, models.Computer@12e, models.Computer@1b7, models.Computer@12d, models.Computer@14a, models.Computer@14b, models.Computer@21f, models.Computer@98, models.Computer@1a2]

scala> computerList.get(0).name
res2: java.lang.String = ASCI White

Nevertheless, as soon as you start to play with the db, I faced a couple of problems. I could read from the database but I coudn’t write update nor inserts. I guess it’s the ebean voodoo magic that’s giving me troubles.

With the scala version, I could really go much further, look at this (go ahead, don’t be shy and copy-paste this code)

$ cd <path_to_your_play2_installation>/samples/scala/computer-database
$ play -DapplyEvolutions.default=true

[computer-database] $ console
[info] Starting scala interpreter...
[info] 
Welcome to Scala version 2.9.1.final (Java HotSpot(TM) Client VM, Java 1.7.0_03).
Type in expressions to have them evaluated.
Type :help for more information.

scala> new play.core.StaticApplication(new java.io.File("."))
[info] play - database [default] connected at jdbc:h2:mem:play
[info] play - Application started (Prod)
res0: play.core.StaticApplication = play.core.StaticApplication@1eaf4b3

scala> import models._, anorm._, play.api.db._, play.api.Play.current, anorm.SqlParser._

import models._
import anorm._
import play.api.db._
import play.api.Play.current
import anorm.SqlParser._

scala> // clean-up everything

scala> DB.withConnection { implicit connection => 
     SQL("delete from computer").executeUpdate()
     SQL("delete from company").executeUpdate()
     }
res3: Int = 42

scala> // just checking

scala> DB.withConnection { implicit connection => SQL("select count(*) from computer").as(scalar[Long].single) }
res7: Long = 0

scala> DB.withConnection { implicit connection => SQL("select count(*) from company").as(scalar[Long].single) }
res8: Long = 0

scala> Company.options
res9: Seq[(String, String)] = List()

Ok, let’s create a couple of companies

scala> 
DB.withConnection { implicit connection => 
  Seq((1, "my Company"), (2, "my second company")).map { company =>
    SQL("insert into company values ( %s, '%s')".format(company._1, company._2) ).executeUpdate()
  }
}
res50: Seq[Int] = List(1, 1)

scala> Company.options
res51: Seq[(String, String)] = List((1,my Company), (2,my second company))

And a couple of computers

scala> val newComputer = Computer(NotAssigned, "my computer", None, None, Some(1))
newComputer: models.Computer = Computer(NotAssigned,my computer,None,None,Some(1))

scala> Computer.insert(newComputer)
res67: Int = 1

scala> Computer.insert(Computer(NotAssigned, "my second comuter", None, None, Some(2)))

scala> 
DB.withConnection { implicit connection =>
  SQL("select * from computer").as(Computer.withCompany *)
}
res8: List[(models.Computer, Option[models.Company])] = List((Computer(1000,my computer,None,None,Some(1)),None), (Computer(1001,my second comuter,None,None,Some(2)),None))

We’ll, this example is a little exagerated, but I guess you get an idea of what you can do with the play console, so keep hacking!

Primer Hackaton de Play! framework para Scala y Java en Argentina



Anotate en el primer Hackaton de Play! Framework en Argentina.

  • ¿Cuándo? Sábado 5 de Mayo, a las 9:30 am
  • ¿Dónde? Costa Rica 5546, Buenos Aires, Argentina

Nuestro objetivo en este primer meetup es que todos podamos conocer juntos Play! Framework 2.0, cada uno probando lo que mas le interesa de este framework, en el lenguaje que desea.

La idea es que cada uno que se anote, elija 2 de los 5 tópicos que le interesaría aprender, junto con un nivel de expertise en Play 2.0 y un lenguaje en el cual quiera realizar la experiencia. En base a esto, se crearán parejas para poder realizar Pair Programming durante todo el hackaton.

La dinámica del Hackaton será la siguiente:

  1. Registración
    1. La registración comenzará a las 09:30 AM y continuará hasta las 10. Mientras nos vamos registrando vamos a poder desayunar café con medialunas
  2. Charla inicial de Play! Framework
    1. Se dará una charla inicial de 30 a 45 minutos de Play! Framework 2.0 con ejemplos tanto en Scala como Java. Esta introducción nos permitirá tener a todos una idea básica de que es Play!
  3. División de proyectos
    1. En base a las personas que estamos en el Hackaton y a los resultados de la encuesta, se crearán diversos proyectos pequeños que abarquen estas ideas. Se separarán a todas las personas en estos proyectos de a pares, para poder realizar Pair Programming
  4. Trabajo en los equipos asignados hasta las 1730
  5. Almuerzo
    1. El almuerzo estará incluido. Se proveerán Pizzas con bebidas para que todos podamos comer mientras codeamos.
  6. Conclusiones
    1. Cada grupo debera preparar una mini charla de aproximadamente 10 minutos donde contarán los problemas y soluciones que se encontraron, mostrando un poco del código que hicieron

Para poder participar, es importante que traigas tu notebook y cuentes con:

  • Cuenta en GitHub
  • Conocimientos de Git
  • Conocimientos de Java y/o Scala

Aquí tienen información que pueden ir leyendo para ir informándose del tema:

Los esperamos 😀

%d bloggers like this: