Archive | January 2015

Usa módulos del lado cliente con Browserify


Browserify te permite implementar módulos en el lado del cliente muy familiar a lo que hace Node.js. Puedes exportar módulos o requerir de ellos en diversos archivos. Ademas nos permite utilizar módulos del core de Node.js y demás módulos que se encuentra en NPM.

Instalación

Para empezar tenemos que instalar Browserify de manera global:

Código :

npm install -g browserify

Como un pequeño ejemplo, generaremos un archivo suma.js con el siguiente código:

Código :

var suma = function(a,b){  
  return a + b
}
module.exports = suma

Despues crearemos un archivo main.js, que contendrá lo siguiente:

Código :

var suma = require('./suma.js');
console.log(suma(2,3));

En nuestra consola, nos dirigimos a la carpeta de el proyecto en la cual tenemos nuestros archivos y escribiremos lo siguiente

Código :

 browserify main.js > bundle.js

Este comando creara un archivo bundle.js incluyendo el contenido de suma.js y main.js listo para usarse.

Lo que tenemos que hacer es agregar bundel.js a nuestro archivo index.html

Código :

<html>
<head>
<title></title>
<script charset="utf-8" src="bundle.js"></script>
</head>
<body>
</body>
</html>

Abrimos la consola de nuestro navegador y veremos el resultado de la operación.

Agregando como dependencias: Jquery, Underscore y Backbone

Si queremos agregar alguna dependencia como Jquery, Underscore, o Backbone, lo que tenemos que hacer es ir a nuestra terminal e instarlos usando NPM:

Código :

npm install jquery backbone underscore --save

Esto nos deberá de generar una carpeta node_modules donde se encontraran las librerías que bajamos, si queremos agregarlas en el archivo

Código :

main.js

haremos lo siguiente

Código :

var $ = require('jquery');
var _ = require("underscore")
var Backbone = require('backbone');
Backbone.$ = $;

Lo que hace browserify es buscar los módulos en la carpeta de node_modules igual que lo hace Node.js.

Ahora ya tenemos jquery, underscore y backbone en nuestro archivo, solo que tenemos un inconveniente, de esta forma solo están disponibles en nuestro archivomain.js y si quisiéramos utilizarlos en nuestro archivo suma.js o en cualquier otro archivo tendríamos que escribir siempre las mismas 4 lineas para poder hacer uso de ello :shock: , para resolverlo podemos hacer lo siguiente:

Código :

window.$ = require('jquery');
window._ = require("underscore")
window.Backbone = require('backbone');
Backbone.$ = $;

Ahora tenemos accesos a ellas de manera global y si queremos hacer uso de ellas en algun otro archivo solo tendremos que escribir $, _ o BacKbone

Para probarlo haremos un pequeño ejemplo consumiendo un API, creamos un archivo PaisesController.js con el siguiente código:

Código :

module.exports  = Backbone.Collection.extend({
  url: 'http://restcountries.eu/rest/v1/all'
  });

También crearemos un archivo para las vistas que se llamara PaisesView.js con el siguiente código:

Código :

  module.exports = Backbone.View.extend({
    tagName:'p',
    initialize:function() {
      this.template = _.template($("#paisTemplate").html());
      },
      
      render: function() {
        var data = this.model,
        html = this.template(data);
        this.$el.html(html);
      }
      });
      

Y nuestro archivo main.js lucirá así:

Código :

      window.$ = require('jquery');
      window._ = require("underscore")
      window.Backbone = require('backbone');
      window.bluebird = require("bluebird");
      Backbone.$ = $;
      
      var PaisesController = require("./PaisesController.js");
      var PaisesView = require("./PaisesView.js");
      var paises = new PaisesController();
      
      
      paises.fetch()
      .then(function(data){
        data.forEach(function(pais){
          var view = new PaisesView({model:pais})
          view.render();
          view.$el.appendTo("#paises");
          });
          });
          

Como podrán observar, se utiliza la librería bluebird para implementar promesas, pera un no la tenemos en nuestros módulos, para eso haremos lo siguiente en nuestra consola:

Código :

npm install bluebird --save

Ahora modifiquemos nnuestro archivo {b]index.html{/b]

Código :

<html>
            <head>
            <meta charset="UTF-8">
            <title></title>
            </head>
            <body>
            <div id="paises">
            
            </div>
            <script charset="utf-8" src="bundle.js"></script>
            <script type="text/template" id="paisTemplate">
            Pais:<%=name%>, Capital:<%=capital%>
            </script>
            </body>
            </html>

Esto nos presentara una lista de países con su capital.

Ahora para que nuestro archivo bundle.js este listo, tendremos que poner en nuestra consola el comando de browserify:

Código :

browserify main.js > bundle.js
            

Usando Grunt para automatizar Browserify

Cada vez que hagamos un cambio en nuestros archivos tendremos que correr este archivo manualmente :? .Pero no se preocupen, podemos resolverlo utilizando grunt, para eso tendremos que tener instalado grun y ademas instalar: grunt-contrib-watch y grunt-browserify:

Código :

npm install -g grunt-cli

Código :

npm install grunt-contrib-watch --save-dev
            npm install grunt-browserify --save-dev

Y nuestro archivo Gruntfile.js se vera así:

Código :

module.exports = function (grunt) {
              grunt.initConfig({
                watch:{
                  scripts:{
                    files:['./main.js'],
                    tasks: ['browserify'],
                  }
                  },
                  browserify:{
                    client: {
                      src: ['./main.js'],
                      dest: './bundle.js',
                    }
                  }
                  });
                  
                  grunt.loadNpmTasks('grunt-contrib-watch');
                  grunt.loadNpmTasks('grunt-browserify');
                  
                  grunt.registerTask('default',['watch']);
                  }

Ahora solo tendremos que correr en nuestra terminal:

Código :

grunt -v

Y listo, grunt hará el trabajo por nosotros cada vez que hagamos un cambio . :cool:

Bien ahora ya tenemos una aplicación funcionando usando browserify ,jquery , underscore backbone y bluebird y ademas un archivo de grunt corriendo nuestra tarea, espero les sea de ayuda :D

Si quieres consultar el código, este es el repositorio: Github del Ejemplo

Enviar comentario

via Cristalab http://ift.tt/1AJfo5I

Cómo usar la etiqueta template en HTML5

Hasta hace unos años, cuando un desarrollador quería hacer templates de sus páginas , la opción era usar algún lenguaje en el Backbend como PHP, Ruby, Python, etc.

Actualmente el uso de templates es posible del lado del Frontend usando JavaScript. Para manejar dichos los templates tenemos algunos engines como:Jade, Swig, Mustache, Handlebars, etc.

Gracias a la popularidad de estos motores de templates la WhatWG creó una etiqueta nueva en HTML5 poco conocida, que gracias a los Web Components se está volviendo cada vez más popular, la etiqueta <template>.

Soporte

No todos los navegadores soportan la etiqueta <template>. Actualmente es soportado por Firefox +22, Chrome +26, Safari +7.1, Opera +15, iOS Safari +8, Android Browser +4.4, Opera Mobile +24, Chrome for Android +39 y Firefox for Android +33, Internet Explorer no tiene soporte alguno a esta etiqueta.

Para poder detectar si el navegador que el usuario está usando tiene soporte para esta etiqueta simplemente creamos el elemento del DOM con JavaScript y nos fijamos si posee la propiedad .content.

Código :

if ('content' in document.createElement('template')) {
  // Funciona!
} else {
  // Usamos algún motor de templates
}

Creando el template

La etiqueta <template> es esencialmente un elemento clonable del DOM para ser reutilizado en tu sitio o aplicación web. Para crear un template simplemente escribes HTML común y lo colocas dentro de la etiqueta <template>.

Código :

<template id="template">
  <h1></h1>
</template>

Características principales

Envolver contenido dentro de <template> nos da cuatro características importantes.

    1) El contenido es parseado e interpretado por el navegador, pero no renderizado por lo que es invisible para el usuario.

    2) Cualquier contenido dentro del template no tiene efectos secundarios. Los scripts no se ejecutan, las imágenes no cargan, el audio no suena y los vídeos no se reproducen, hasta que el template sea usado.

    3) El contenido del template no es considerado parte del documento, hacer un document.getElementById() o .querySelector() no va a regresar los elementos del template.

    4) El template puede ser colocado en cualquier parte dentro de <head>, <body> o <frameset> y puede contener cualquier tipo de contenido que sea posible usar dentro de estos elementos.

Activando los templates

Para usar un template hay que activarlo, de otra forma nunca va a ser renderizado. La forma más simple es copiar el .content usando document.importNode(). La propiedad .content la representación en JavaScript del contenido del template y desde donde se puede acceder a todo el contenido de este.

Código :

var t = document.querySelector('#template');
var clone = document.importNode(t.content, true);
clone.querySelector('h1').innerHTML = 'Hola Cristalab';
document.body.appendChild(clone);

Luego de colocar el clon del template en nuestra aplicación el contenido es renderizado, en este ejemplo dentro del h1 se coloca el string Hola Cristalab y se renderiza todo el HTML del template.

Como usarlo en Internet Explorer y otros navegadores viejos

Aunque la etiqueta <template> no tiene soporte en Internet Explorer es posible utilizarlo gracias a la librería HTML5Shiv, que por cierto forma parte de de Modernizr por lo que si están usando este, entonces ya tienen HTML5Shiv y ya pueden usar la etiqueta <template>.

Hay sin embargo una pequeña diferencia entre el soporte nativo de la etiqueta y el que permite HTML5Shiv y es que el soporte nativo funciona como está explicado arriba, mientras que HTML5Shiv lo que hace es crear el elemento usando document.createElement() y luego le pone el estilo display: none por defecto, por lo que durante muy poco tiempo es posible que el usuario vea el contenido del template.

Demo

Pueden ver una demo del código de arriba en CodePen en el siguiente link:
http://ift.tt/1yXO4T9[/img]
Hasta hace unos años, cuando un desarrollador quería hacer templates de sus páginas , la opción era usar algún lenguaje en el Backbend como PHP, Ruby, Python, etc.

Actualmente el uso de templates es posible del lado del Frontend usando JavaScript. Para manejar dichos los templates tenemos algunos engines como:Jade, Swig, Mustache, Handlebars, etc.

Gracias a la popularidad de estos motores de templates la WhatWG creó una etiqueta nueva en HTML5 poco conocida, que gracias a los Web Components se está volviendo cada vez más popular, la etiqueta <template>.

Soporte

No todos los navegadores soportan la etiqueta <template>. Actualmente es soportado por Firefox +22, Chrome +26, Safari +7.1, Opera +15, iOS Safari +8, Android Browser +4.4, Opera Mobile +24, Chrome for Android +39 y Firefox for Android +33, Internet Explorer no tiene soporte alguno a esta etiqueta.

Para poder detectar si el navegador que el usuario está usando tiene soporte para esta etiqueta simplemente creamos el elemento del DOM con JavaScript y nos fijamos si posee la propiedad .content.

Código :

if ('content' in document.createElement('template')) {
  // Funciona!
} else {
  // Usamos algún motor de templates
}

Creando el template

La etiqueta <template> es esencialmente un elemento clonable del DOM para ser reutilizado en tu sitio o aplicación web. Para crear un template simplemente escribes HTML común y lo colocas dentro de la etiqueta <template>.

Código :

<template id="template">
  <h1></h1>
</template>

Características principales

Envolver contenido dentro de <template> nos da cuatro características importantes.

    1) El contenido es parseado e interpretado por el navegador, pero no renderizado por lo que es invisible para el usuario.

    2) Cualquier contenido dentro del template no tiene efectos secundarios. Los scripts no se ejecutan, las imágenes no cargan, el audio no suena y los vídeos no se reproducen, hasta que el template sea usado.

    3) El contenido del template no es considerado parte del documento, hacer un document.getElementById() o .querySelector() no va a regresar los elementos del template.

    4) El template puede ser colocado en cualquier parte dentro de <head>, <body> o <frameset> y puede contener cualquier tipo de contenido que sea posible usar dentro de estos elementos.

Activando los templates

Para usar un template hay que activarlo, de otra forma nunca va a ser renderizado. La forma más simple es copiar el .content usando document.importNode(). La propiedad .content la representación en JavaScript del contenido del template y desde donde se puede acceder a todo el contenido de este.

Código :

var t = document.querySelector('#template');
var clone = document.importNode(t.content, true);
clone.querySelector('h1').innerHTML = 'Hola Cristalab';
document.body.appendChild(clone);

Luego de colocar el clon del template en nuestra aplicación el contenido es renderizado, en este ejemplo dentro del h1 se coloca el string Hola Cristalab y se renderiza todo el HTML del template.

Como usarlo en Internet Explorer y otros navegadores viejos

Aunque la etiqueta <template> no tiene soporte en Internet Explorer es posible utilizarlo gracias a la librería HTML5Shiv, que por cierto forma parte de de Modernizr por lo que si están usando este, entonces ya tienen HTML5Shiv y ya pueden usar la etiqueta <template>.

Hay sin embargo una pequeña diferencia entre el soporte nativo de la etiqueta y el que permite HTML5Shiv y es que el soporte nativo funciona como está explicado arriba, mientras que HTML5Shiv lo que hace es crear el elemento usando document.createElement() y luego le pone el estilo display: none por defecto, por lo que durante muy poco tiempo es posible que el usuario vea el contenido del template.

Demo

Pueden ver una demo del código de arriba en CodePen en el siguiente link:
http://ift.tt/1yXO4T9

Enviar comentario

via Cristalab http://ift.tt/1yXO4Ct

Introducción a Android Wear

¿Qué es Android Wear?

Android nació con la filosofia de ser una sola plataforma de software para todo tipo de hardware. Si bien los primeros productos que vimos fueron smartphones y tablets, el sistema operativo móvil de google ahora lidera el mercado e itera sobre otras posibilidades, tal como:

    -Android Wear
    -Android Auto
    -Android TV

Cada una de ellas con diversos contextos de uso, pero que juntos, proporcionan un ecosistema tanto familiar, como lleno de posibilidades.

La intención de este tutorial (y proximos) es el estudiar plataforma pensada especialmente para wearables, sus posibilidades, su alcance y el futuro de la misma.

¿Que es un wearable?

Llevar un smartphone se volvió algo tan cotidiano como vestir, no podemos salir de casa sin él y cuando se termina la bateria, nos sentimos casi desnudos. Su portabilidad y hardware los hacen perfectos a la hora de consumir contenido y realizar algunas tareas del dia a dia. Sin embargo, su funcionalidad decrece de manera considerable si no tenemos total atención sobre el, algo tan simple como responder un mensaje se vuelve casi imposible sin ver la pantalla.

Un wearable es una propuesta de tener información a tu alcance de una manera menos invasiva, presentando como algo que "vestirias" de forma casual. Si el smartphone lo usamos para todo menos para hablar, porque limitar un reloj a solo mostrar la hora. No pensemos por ahora que un wearable sustituye todo lo que puedes hacer con tu smartphone, la comodidad no se puede lograr sin tener unas especificaciones algo simples. Son un complemento para hacer acciones mas puntuales y llevar algunos registros mas precisos. Igual nuestro nuevo compañero requiere un poco mas de datos y batería por parte de tu smartphone.

Diferencias entre Android y Android Wear

¿Que propone Android Wear?

Android wear es una versión ajustada y optimizada para wearables, corriendo Android 5.0 Lollipop. No solo a nivel de código, si no con un equilibrio perfecto entre experiencia de usuario e interfaz de usuario (muy diferente a Glass). Dentro de lo mucho que podemos hacer con Android Wear encontramos:

    -Una nueva manera para visualizar y responder sobre notificaciones
    -Comandos de voz (Multilenguaje) que funcionan muy bien
    -Un SDK para crear aplicaciones propetarias para Wear
    -Sensores optimizados en temas de fitness

¿Que es lo que cambia?

Si vienes del mundo del desarrollo de aplicaciones móviles para Android, las diferencias son minimas, seguimos teniendo Activities, Intents, Views y todo aquello que ya dominas. Si eres nuevo, quizás esto te parezca ajeno y es muy recomendable estudiar la plataforma para móviles primero.

En Android Wear tenemos 3 tipos de aplicaciones:

– Handheld Apps
– Wearable Apps (StandAlone Apps)
– Watch Faces

De igual manera, el uso de un wearable va getionado mediante la aplicación Android Wear ya sea para un dispositivo real o uno emulado:

Handheld Apps

Son aplicaciones instaladas en el smartphone con cierta interacción para tu wearable. La funcionalidad en el wearable es totalmente dependiente de la conexión entre ambos. Como ejemplos tenemos:

Mejores notificaciones, tal como lo hace Whatsapp:

En whatsapp no solo podemos ver un preview de los mensajes, si no que por medio del menú podemos responder el mismo o ignorarlo. A nivel de código, esto es posible con el uso de NotificationCompat API.

Ademas, por ser Handheld, Whatsapp esta limitado a ser usado en el wearable solo mediante notificaciones, no podemos ver ni responder de otro modo.

Compartir diversa información, como el uso de Navegación:

Pedir una dirección wearable significa dos cosas: Internet y GPS, si la mayoria de ellos no cuentan con ellos: ¿Cómo es que se comunican?. Es sencillo, se usa el Wearable Data Layer API. Wearable Data Layer API permite que el intermabio de datos entre el smartphone y el wearable sea mas sencilla.

Wearable Apps (StandAlone Apps)

Son aplicaciones que funcionan en el wearable exista o no conexión con tu smartphone. Sus caracteristicas son algo limitadas ya que se ejecuta directamente en el hardware del mismo. Estas aplicaciones se ejecutan desde el menú inicar:

Como ejemplos tenemos:

Flopsy Droid: un juego que rinde tributo al original Flappy Bird

Aplicaciones conocidas como Duolingo y Tinder

Watch Faces

Son aplicaciones especificamente diseñadas para funcionar como caratulas de reloj, estas se instalan en el wearable y pueden ser tan sencillas como para no requerir conexión entre ambos. Algunos Watch Faces pueden incluir información del clima y status de tu telefono (Cómo bateria, toggles, etc)

Requisitos para Desarrollar en Android Wear

Android Studio

El desarrollo para Android Wear solo es posible en Android Studio, de hecho, ADT pronto dejara de tener soporte. Android Studio es libre y multiplataforma, puede ser descargado en:

Android Developers SDK

Lollipop SDK

Mediante el Android SDK Manager, debemos tener instalado el SDK de Android 5.0 Lollipop, ademas de sus respectivas System Images si quieres emular:

En extras, asegurarnos que tenemos acceso a Google Play Services y Support Libraries:

En caso de no tener un dispositivo con Android Wear:

Para crear un emulador de Android Wear, se recomienda instalar Intel Hardware Accelerated Execution Manager:

Intel Hardware Accelerated Execution Manage

En la siguiente parte, mostrare como crear un AVD de Android Wear.

Hola Mundo de Wearable Apps (StandAlone Apps)

Vamos a crear y entender nuestra primera aplicación en Android Wear.El ejemplo mas sencillo que podemos tener es un Wearable App (StandAlone App).

Lo primero que debemos hacer es abrir Android Studio y crear un nuevo proyecto, en este caso el nombre de la aplicación es WearApp:

En la siguiente pantalla podriamos seleccionar solo una aplicación para Wear, sin embargo, para publicar en el PlayStore es necesario haber crear la aplicación de Wear junto a la de Phone, por lo que lo crearemos en conjunto. Igual de importante tener como versión minima en Phone 4.3 Jelly Bean o superior.

Lo primero que configuraremos es la aplicación Phone, para ello no es necesario hacer alguna modificación, pero una vez que llegamos a la configuración para Wear es importante señalar que tendremos un Blank Wear Activity con sus respectivos Layouts:

Entender cual es nuestra aplicación para los telefonos y cual la del wearable es sencillo, tendremos separadas ambas por paquetes:

Y a nivel de carpetas, la aplicación para wear conserva la misma jerarquia:

A nivel de codigo, el MainActivity.java de la aplicación en Wear se muestra de la siguiente manera:

Código :

  public class MainActivity extends Activity {
 
    private TextView mTextView;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
 
        final WatchViewStub stub = (WatchViewStub) findViewById(R.id.watch_view_stub);
        stub.setOnLayoutInflatedListener(new WatchViewStub.OnLayoutInflatedListener() {
            @Override
            public void onLayoutInflated(WatchViewStub stub) {
                mTextView = (TextView) stub.findViewById(R.id.text);
            }
        });
    }
}

Seguimos tendiendo el mismo ciclo de vida de un Activity, sin embargo, de inmediato debemos "inflar" una vista. ¿Pórque?. Sucede que WatchViewStub es una especie de contenedor que puede ser tanto un Layout Redondo o uno Cuadrado (Los dos tipos de vista que tienen los primeros Android Wear), y por medio de su Listener se muestra el correcto.

Es por eso que en nuestro activity_main.xml solo vemos este Widget, sin embargo se hace referencia a las vistas rectangulares y redondas, estas vistas son un layout independiente, y de hecho, no tienen que ser estrictamente iguales.

Código :

<?xml version="1.0" encoding="utf-8"?>
<android.support.wearable.view.WatchViewStub
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/watch_view_stub"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:rectLayout="@layout/rect_activity_main"
    app:roundLayout="@layout/round_activity_main"
    tools:context=".MainActivity"
    tools:deviceIds="wear">
</android.support.wearable.view.WatchViewStub>

Para notar la diferencia, en este ejemplo modifique el round_activity_main.xml agregando una imagen, pero el rect_activity_main.xml lo deje igual (La mayoria de Widgets son compatibles):

rect_activity_main.xml:

Código :

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity" 
    tools:deviceIds="wear_square">
 
    <TextView android:id="@+id/text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" 
        android:text="@string/hello_square" />    
</LinearLayout>

round_activity_main.xml:

Código :

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#333366"
    tools:context=".MainActivity"
    tools:deviceIds="wear_round">
 
    <TextView android:id="@+id/text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/hello_round"
        android:layout_alignParentTop="true"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="59dp" />
 
    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/mejorandopapel"
        android:id="@+id/imageView"
        android:layout_below="@+id/text"
        android:layout_centerHorizontal="true" />
 
</RelativeLayout>

Lo unico que falta es probar el proyecto, para ello mostrare el ejemplo funcionando sobre un AVD, si tienes un dispositivo real existen dos opciones, depuración USB o Bluetooth. Con USB es de la misma forma que en telefonos, para Bluetooth solo es necesario consultar esta información:

Bluetooth Debugging

Lo primero que hacemos es iniciar nuestro AVD Manager, donde de primera, nos muestra los dispositivos que hemos creado, en este caso vamos a crear uno nuevo:

Nos dirigimos al apartado de Wear, donde tendremos dos configuraciones, yo creare uno tipo Round:

Lo siguiente es la versión del dispositivo, si recordamos los requerimientos esto debe ser Lollipop, de preferencia x86 para que sea mas rapido:

En Actions, es donde ejecutamos la Maquina Virtual, con el boton de Play:

Esperamos un momento a que inicie:

Antes de correr proyecto, nos aseguramos que vamos a compilar el proyecto Wear:

Y listo, tenemos nuestra primera App para Android Wear:

Y aqui en un dispositivo real:

Ya por ultimo, si ejecutamos el ejemplo en una vista rectangular, se ve de la siguiente manera:

Eso sucede ya que no lo modificamos. Estare muy agradecido si me mandas una captura de tu diseño en cuadraro por twitter (@thespianartist), esto es una señal de que querías aprender algo nuevo y me motiva en crear mas material.

El código esta disponible Github

Nos vemos pronto.
Uriel Ramirez @thespianartist – Developer Advocate Mejorando.la – Platzi

Enviar comentario

via Cristalab http://ift.tt/1yyKqPc