sábado, 29 de diciembre de 2012

Responsive Design - Diseño que Responde

No sabía bien como traducir este término tan especial del inglés. Pero bueno, la idea es la de hacer un diseño (en este caso se mostrarán imágenes de Android pero aplica para todas las plataformas, móviles y PC) que se auto ajuste para diferentes pantallas.

Espacio limitado para recursos

Hay que tener cuidado y no creer que esto de diseñar para diferentes pantallas significa que tenemos que poner recursos gráficos para todos los tamaños. Eso resultaría en una aplicación demasiado grande.

Tampoco se trata de colocar todo el diseño al centro de la pantalla, con un ancho máximo de 320 pixels. Lo único que lograrás es que los usuarios con pantalla grande se pregunten por qué se ve todo tan chiquito si se dispone de un gran ancho de pantalla para usar.

Qué hago entonces?

Ahora más abajo vas a ver algunas capturas que dan ideas. Pero lo principal a tener en cuenta es la distribución de los elementos en la pantalla, según el ancho de la misma. Cuanto más reducida es, los elementos se tienden a apilar uno arriba de otro y a quitar lo que no es tan relevante.

Cuando tienes una pantalla más grande, ya puedes ordenar algunos de tus elementos de forma horizontal en vez de vertical.

Y cuando tienes una pantalla más grande (por ejemplo, una tablet de 10 pulgadas  vista apaisada, o tal vez una PC) entonces usas el mismo diseño que el mencionado antes pero le agrandas los márgenes y colocas elementos de ayuda en pantalla que no suelen entrar en los otros dos espacios.

Aplicación Calendario 
en pantalla pequeña.

Misma aplicación Calendario en una pantalla
mediana (los títulos de los campos van al costado y no arriba)

Misma aplicación Calendario pero en una pantalla todavía más grande.
Es el mismo diseño de una pantalla mediana pero los márgenes son mayores.

Ideas para cambiar diseño

A continuación, pongo algunas capturas de pantalla que muestran como resolver la distribución de la información a medida que cambia la pantalla. Y todo con un mismo código.

Paneles múltiples - Comprimir
La idea es comprimir el tamaño de los paneles si la pantalla es más angosta. Y agrandarlos si hay espacio. No es la idea usar valores fijos sino porcentajes.

Paneles múltiples - Apilar
Esta idea soporta pantallas realmente pequeñas. Y lo que se hace es apilar los elementos a medida que se hace más angosta la pantalla. Y expandir cuando es más grande. 

Paneles múltiples - Expandir / Colapsar.
En este caso la información tiene siempre el mismo tamaño pero es capaz de colapsar a medida que el ancho de la pantalla es menor. Y se expande cuando se hace más grande.

Paneles múltiples - Mostrar / Esconder.
En este caso, decidimos esconder la información que no consideramos tan relevante para el usuario y le dejamos un botón o enlace para que cuando quiera la muestre.

Otros ejemplos...

"Patterns" Application

"Pocket" Application

"TED Talk" Application

Related link on this blog.

UPDATE

Excelente tutorial en español sobre cómo iniciar un diseño usando esta técnica.

Sites with demos on this technique.

Twitter framework for Responsive Design (kind-of).

Adjust automatically Video static image for Youtube, Vimeo and more...




sábado, 15 de diciembre de 2012

¿Cómo funciona la red Tor?


Logo de Tor
Durante estos días, la red Tor ha saltado a la actualidad a raíz de un escándalo político en España. Fue usada para enviar un correo anónimo y muy difícil de rastrear, pero, ¿cómo funciona realmente esta red? ¿Quién lo usa, y para qué propósitos? Genbeta hoy vamos a responder a todas estas preguntas.

Cómo funciona Tor: el “enrutado cebolla”

Cómo funciona Tor
Tor es una red que implementa una técnica llamada Onion Routing (enrutado cebolla en castellano, aunque suena bastante peor), diseñada con vistas a proteger las comunicaciones en la Marina de los Estados Unidos. La ideas es cambiar el modo de enrutado tradicional de Internet para garantizar el anonimato y privacidad de los datos.
El enrutado tradicional que usamos para conectarnos a servidores en Internet es directo. Por ejemplo, si quieres leer Genbeta: tu ordenador se conecta de forma directa a los servidores de Genbeta. La ruta es, a grandes rasgos, sencilla: de tu ordenador a tu router, de ahí a los enrutadores de tu ISP(proveedor de Internet) y después directos a los servidores de Genbeta.
Fácil y sencillo, salvo por el hecho de que si alguien intercepta los paquetes de datos en un punto intermedio sabrá perfectamente de dónde vienen y a dónde van. Incluso aunque se cifren los datos de cada paquete (por ejemplo, visitando una página HTTPS) las cabeceras de este no se cifran, y los campos del remitente y destinatario (entre otros) siguen siendo visibles.
Ahí es donde entra el Onion Routing. Habréis oído que consiste en enviar el paquete por un camino no directo, a través de varios nodos, pero en realidad es algo más complejo que eso.
Primero, el ordenador A, que quiere enviar el mensaje a B, calcula una ruta más o menos aleatoria al destino pasando por varios nodos intermedios. Después, consigue las claves públicas de todos ellos usando un directorio de nodos.
Usando cifrado asimétrico, el ordenador A cifra el mensaje como una cebolla: por capas. Primero cifrará el mensaje con la clave pública del último nodo de la ruta, para que sólo él lo pueda descifrar. Además del mensaje, incluye (también cifradas) instrucciones para llegar al destino, B. Todo este paquete, junto con las instrucciones para llegar al último nodo de la lista, se cifra de nuevo para que sólo lo pueda descifrar el penúltimo nodo de la ruta.
Diagrama de cebollaEl paquete está en el centro, protegido por varias capas (cifrados) para cada uno de los nodos.
El proceso se repite hasta que acabamos con todos los nodos de la ruta. Con esto ya tenemos el paquete de datos listo, así que toca enviarlo. El ordenador A conecta con el primer nodo de la ruta, y le envía el paquete. Este nodo lo descifra, y sigue las instrucciones que ha descifrado para enviar el resto del paquete al nodo siguiente. Éste descifrará de nuevo y volverá a enviar al siguiente, y así sucesivamente. Los datos llegarán finalmente al nodo de salida, que enviará el mensaje a su destino.

Mucho más seguro, privado y anónimo, pero no es infalible

El Onion Routing proporciona muchísima más seguridad y privacidad que el enrutado normal de red. Ninguno de los nodos, salvo el primero y el último, saben de dónde viene o a dónde va el mensaje. Ni siquiera saben qué posición ocupan en la ruta, y mucho menos conocen el contenido del mensaje.
De esta forma, aunque se intercepten las comunicaciones entre dos nodos, es imposible saber qué datos transmite, a dónde van o de dónde vienen. Incluso aunque hubiese un nodo infiltrado, un topo en la red, no tendría nada que hacer con los mensajes que recibe. También tiene la ventaja de que es muy difícil tumbar la red Tor: al estar los nodos distribuidos, habría que tumbar todos y cada uno de ellos para poder parar las comunicaciones.
Por supuesto, y como cualquier sistema, no es infalible. Una forma curiosa de saber quién ha enviado y recibido mensajes es analizar los tiempos. Si el ordenador A ha enviado un paquete a las 18:19:01 y 3 milisegundos, y 300 milisegundos más tarde el ordenador B ha recibido otro paquete, y se repite el patrón de latencia varias veces, es muy probable que A y B estén conectados entre sí.
También hay que tener en cuenta que el nodo final de salida puede leer el mensaje original, así quetambién hay que cifrar el mensaje original. Pero, en general, las redes tipo Tor, de “enrutado cebolla”, son muy seguras y garantizan un nivel de privacidad extremadamente alto.

Tor, la implementación del Onion Routing

Servidor proxy
¿Y qué es Tor? Sencillo: es el software que implementa el Onion Routing de forma sencilla para los usuarios. Al instalarlo en un ordenador, pone en marcha un servidor proxy local. Para los que no lo sepáis, un servidor proxy actúa como intermediario entre dos ordenadores. Como siempre, una imagen vale más que mil palabras y la mejor explicación que he encontrado sobre servidores proxy la tenéis en la imagen de arriba.
Lo peculiar del servidor proxy de Tor es que enruta esas peticiones a través de la red Tor, usando el Onion Routing que comentábamos antes. Usando este servidor como puerta a la red anónima, podemos hacer conexiones privadas desde cualquier programa del ordenador que soporte proxys.
Por supuesto, esta aproximación tiene pegas: puede que el programa que usemos incluya nuestra IP en los datos que envía, y entonces todo el enrutado y los cifrados que hagamos no servirán para nada. Por eso, el proyecto Tor recomienda usar el navegador Tor, ya preparado para conectarse a través de la red y para no enviar ningún dato identificador. Lo mismo con algunos servicios como mensajería instantánea o correo: si no tenemos cuidado, podemos perder toda la privacidad que habíamos ganado por culpa un fallo en un programa.

¿Quién usa las redes Tor, y para qué?

Proyecto Tor
Las redes Tor son ampliamente usadas en todo el mundo por gente que necesita mantener comunicaciones seguras. Desde periodistas que se comunican con fuentes que necesitan permanecer anónimas hasta una rama de la inteligencia de la Marina de los Estados Unidos, pasando por activistas perseguidos en sus países, voluntarios de ONGs o usuarios que quieren acceder a servidores bloqueados por su ISP o por su Gobierno.
La red Tor también se usa para servicios ocultos. Un servicio crea varios puntos de introducción en ciertos nodos de la red, y notifica a una base de datos qué nodos son. Cuando un cliente quiera conectarse, enviará a uno de esos nodos la dirección de un punto de encuentro (al que está conectado) y una clave única. Ese punto de introducción conectará con el servicio oculto, que se conectará al punto de encuentro, estableciendo así una comunicación entre el cliente y el servicio.
La foram en la que están planteados estos servicios ocultos permite conectarnos a servidores de correo o de chat sin saber ni siquiera su dirección exacta, usando intermediarios y circuitos Tor anónimos. A través de uno de estos servidores de correo se envió el famoso email que causó la polémica con la que introducíamos el artículo.
En este caso, es prácticamente imposible rastrear quién envió el correo: ni siquiera el propio servidor de correo sabe con qué ordenador estaba comunicándose.
Esperamos que este artículo os haya aclarado cualquier posible duda sobre lo que es (y lo que no es) Tor, y cómo funciona realmente. Y, como siempre, si tenéis alguna duda poned un comentario e intentaremos ayudaros entre todos.


¿Cómo funciona la red Tor?

jueves, 13 de diciembre de 2012

HTML Flow with Javascript


I've been working today on this page flow and I like it.

The idea is to make things easier for users. They usually are confused when an application takes them from one screen to another.

This example will show how all the screens are piled up (using a simple animation, which must be optimized) as the user goes deeper and deeper through the application.

I did not used jQuery because I wanted to make it as small as possible. And now that I'm writting this, I realize that it'd be better if I give Google's Dart a chance...

Keep reading and be ready for Dart, very soon!

(Just copy and pase the following code into a HTML file and make doble-click to see it working on a browser)

<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<title>My Two parts screen</title>
<style>
body {
font-family:Arial;
font-size:16px;
background:#ddd;
}
/* Decoration */
.box {
position:absolute;
left:5%;
top:5%;
padding:5px;
background:#fff;
border:1px solid #ccc;
}
.box #inner {
padding:1em;
}
</style>
<script>
/* Do after body is fully loaded */
function onBodyComplete()
{
setBoxDiv();
}
/* Get current window's width */
function getWidth() 
{
var myWidth = 0, myHeight = 0;
if( typeof( window.innerWidth ) == 'number' ) {
//Non-IE
myWidth = window.innerWidth;
myHeight = window.innerHeight;
} else if( document.documentElement && ( document.documentElement.clientWidth || document.documentElement.clientHeight ) ) {
//IE 6+ in 'standards compliant mode'
myWidth = document.documentElement.clientWidth;
myHeight = document.documentElement.clientHeight;
} else if( document.body && ( document.body.clientWidth || document.body.clientHeight ) ) {
//IE 4 compatible
myWidth = document.body.clientWidth;
myHeight = document.body.clientHeight;
}
return myWidth;
}
/* Window resizes */
window.onresize = function() {
onBodyComplete();
};
/* Utility functions for the framework*/
function $(divId) {
return document.getElementById(divId);
}
function log(text) {
console.log(text);
}
function doMessage(text)
{
$('message').innerHTML = text;
}
/* Use this function for animating down any DIV element */
function animateDown(divId, pixels, callback)
{
log('animateDown');
var ele = $(divId);
if ( ele == null )
{
console.log('animateDown: element is null!');
return false;
}
var currentMarginTop = ele.style.marginTop;
currentMarginTop = currentMarginTop.replace('px','');
if ( parseInt( currentMarginTop ) >= parseInt( pixels ) ) 
{
log('animateDown: ' + currentMarginTop + ' >= ' + pixels );
ele.style.marginTop = pixels + 'px';
if ( callback != null ) callback();
return true;
}
else
{
moveDown(divId, pixels, callback, currentMarginTop);
}
}
/* This function works along with the animateDown function */
function moveDown(divId, pixels, callback, currentMarginTop)
{
var ele = $(divId);
if ( ele == null )
{
console.log('MoveDown: element is null!');
return false;
}
if ( currentMarginTop == '' ) 
currentMarginTop = 0;
currentMarginTop += 2;
ele.style.marginTop = currentMarginTop + 'px';
setTimeout("animateDown('"+divId+"', '"+pixels+"', "+callback+");", 100);
}
/* Use this function for animating up any DIV element */
function animateUp(divId, pixels, callback)
{
log('animateUp');
var ele = $(divId);
if ( ele == null )
{
console.log('animateUp: element is null!');
return false;
}
var currentMarginTop = ele.style.marginTop;
currentMarginTop = currentMarginTop.replace('px','');
if( typeof( currentMarginTop ) == 'number' )
{
// Nothing
}
else
{
if ( currentMarginTop == '' ) currentMarginTop = 0;
currentMarginTop = parseInt( currentMarginTop );
}

if( typeof( pixels ) == 'number' )
{
// Nothing
}
else
{
pixels = parseInt( pixels );
}
if ( currentMarginTop > 1000 ) currentMarginTop = 500;
if (  currentMarginTop <= pixels ) 
{
ele.style.marginTop = pixels + 'px';
if ( callback != null ) callback();
return true;
}
else
{
moveUp(divId, pixels, callback, currentMarginTop);
}
}
/* This function works along with the animateUp function */
function moveUp(divId, pixels, callback, currentMarginTop)
{
var ele = $(divId);
if ( ele == null )
{
console.log('moveUp: element is null!');
return false;
}
currentMarginTop -= 60;
ele.style.marginTop = currentMarginTop + 'px';
setTimeout("animateUp('"+divId+"', '"+pixels+"', "+callback+");", 0);
}
/* Use this function for showing or hiding a DIV element */
function makeVisible(divId, show)
{
if ( show ) 
$(divId).style.display = 'inline';
else
$(divId).style.display = 'none';
}
/* Use this function for setting height for any DIV element */
function setBoxHeight(boxId, newHeight)
{
$(boxId).style.height = newHeight + 'px';
}
/* 
*
* Utility functions for the application
*
*/
/* Will set width and height for all the components on the screen */
function setBoxDiv() 
{
var percent = '85%';
$('back-box').style.width = percent;
$('back-box').style.height = hForMsg + 'px';
$('login').style.width = percent;
$('login').style.height = hForLogin + 'px';
$('menu').style.width = percent;
$('menu').style.height = hForMenu + 'px';
$('menu-1').style.width = percent;
$('menu-1').style.height = hForMenu1 + 'px';
$('menu-2').style.width = percent;
$('menu-2').style.height = hForMenu1 + 'px';
}
var fDivIdMessageFromMenuDetail;
var fMessageFromMenuDetail;
/* Use this function for showing a message if you are inside a menu option detail */
function sendMessageFromMenuDetail(divId, text)
{
fDivIdMessageFromMenuDetail = divId;
fMessageFromMenuDetail = text;
animateDown(divId, hForMsg, function () 
doMessage(fMessageFromMenuDetail); 
setTimeout("animateUp('" + fDivIdMessageFromMenuDetail + "', 0, null)", 1500);
)
}
/* Use this function for showing an error from the login screen */
function sendErrorLogin(text)
{
fMessageFromMenuDetail = text;
animateDown('login', hForMsg, function () 
doMessage(fMessageFromMenuDetail); 
setTimeout("animateUp('login', 0, null)", 1500);
)
}
/* Use this function when the login is correct */
function sendCorrectLogin()
{
doMessage(''); 
makeVisible('menu', true);
animateDown('login', hForMenu, function () 
writeInfoInYoutLoginBox();
)
}
/* Use this function for showing the logged-in user info on screen */
function writeInfoInYoutLoginBox()
{
$('login').innerHTML = "<div id='inner'>You are logged as: <b>Walter Rodriguez</b>" + 
"<p><input type='button' value='Close Session' onClick='logoff()' /></p></div>";
}
/* Use this function for writting the FORM for making a login */
function writeLoginForm()
{
var text = '<div id="inner"><strong>ABM</strong>' +
'<form>' +
'<p>' +
'<label for="username">Username: </label>' +
'<input type="text" id="username" value="admin" />' + 
'</p>' +
'<p>' + 
'<label for="pass">Password: </label>' + 
'<input type="password" id="pass" value="pass" />' + 
'</p>' + 
'<p>' + 
'<input type="button" value="Simulate login incorrect" onClick="sendErrorLogin(\'Login incorrect. Please try again.\')" />' + 
'<input type="button" value="Simulate login correct" onClick="sendCorrectLogin()" />' + 
'</p>' + 
'</form></div>';
$('login').innerHTML = text;
}
/* This function will accomodate the screen for a logoff scenario */
function logoff()
{
doMessage(''); 
closeAllEnabledMenuDetail();
animateUp('login', 0, function () 
makeVisible('menu', false);
writeLoginForm();
)
}
var fMenuId;
/* Use this function when clicking a menu option */
function showDetailForOptionMenu(menuId)
{
fMenuId = menuId;
doMessage('');
hideAllEnabledMenuDetail();
animateDown('login', (hForMenu + hForMenu1), function () 
animateDown('menu', hForMenu1, function () 
{
makeVisible(fMenuId, true);
)
)
}
/* Use this function for closing the menu window */
function closeMenuDetail(menuId)
{
fMenuId = menuId;
doMessage('');
animateUp(menuId, 0, function ()
{
makeVisible(fMenuId, false);
animateUp('login', hForMenu, function () 
animateUp('menu', 0, function () 
// Nothing
)
)
}
);
}
/* Use this function for closing all the detailed menu option screens available */
function closeAllEnabledMenuDetail()
{
closeMenuDetail('menu-1');
closeMenuDetail('menu-2');
}
/* Use this function for hiding all the detailed menu option screens available */
function hideAllEnabledMenuDetail()
{
makeVisible('menu-1', false);
makeVisible('menu-2', false);
}
var hTiny = 100;
var hSmall = 200;
var hMedium = 300;
var hLarge = 450;
var hForMsg = hTiny;
var hForLogin = hSmall;
var hForMenu = hMedium;
var hForMenu1 = hLarge;
</script>
</head>
<body onLoad="onBodyComplete()">
<div id="wrapper">
<!-- box with error message for all boxes -->
<div id="back-box" class="box">
<div id="inner">
<div id="message"></div>
</div>
</div>

<!-- menu details -->
<div id="menu-1" class="box" style="display:none;">
<div id="inner">
<b>MENU OPTION DETAIL</b>
<p>Bla..</p>
<p>Bla..</p>
<p>Bla..</p>
<p>Bla..</p>
<p>Showing action for option menu 1</p>
<p><input type="button" value="Show message from option detail" 
onClick="sendMessageFromMenuDetail('menu-1','You just fired a message...')" /></p>
<p><input type="button" value="Back to Menu" onClick="closeMenuDetail('menu-1')" /></p>
</div>
</div>

<div id="menu-2" class="box" style="display:none;">
<div id="inner">
<b>MENU OPTION DETAIL</b>
<p>Showing action for option menu 2</p>
<p><input type="button" value="Back to Menu" onClick="closeMenuDetail('menu-2')" /></p>
</div>
</div>
<!-- menu list after login -->
<div id="menu" class="box" style="display:none;">
<div id="inner">
<b>MAIN MENU</b>
<ul>
<li><a href="javascript:showDetailForOptionMenu('menu-1')">Option 1</a></li>
<li><a href="javascript:showDetailForOptionMenu('menu-2')">Option 2</a></li>
<li>Option 3</li>
<li>Option 4</li>
<li>Option 5</li>
<li>Option 6</li>
<li>Option 7</li>
<li>Option 8</li>
<li>Option 9</li>
</ul>
<p><input type='button' value='Close Session' onClick='logoff()' /></p>
</div>
</div>
<!-- login box -->
<div id="login" class="box">
<!-- the Login form will be painted here via javascript -->
</div>
</div>
<script>writeLoginForm()</script>
</body>
</html>

My Two parts screen (with javascript)


Previously, I posted some code for a simple CSS three parts screen template using DIVs.

Now I'd like to save here this code that also works in Chrome, Firefox and iPod Touch/iPhone (thanks to Javascript, it also has a different behavior)


<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<title>My Two parts screen</title>
<style>
body {
font-family:Arial;
font-size:16px;
background:#ddd;
}
#wrapper {
width:auto;
margin:0px;
padding:0px;
border: 1px solid #ccc;
margin-right:5%;
margin-left:5%;
}
#center {
float:left;
width:80%;
background:#444;
}
#right {
float:right;
width:20%;
background:#777;
}
#inner {
padding:10px;
overflow:hidden;
}
</style>

<script>

/* Do after body is fully loaded */
function onBodyComplete()
{
if ( getWidth() <= 320 ) {
var newWidth = ( getWidth() - 48 );
document.getElementById('center').style.width = newWidth + 'px';
document.getElementById('right').style.width = newWidth + 'px';
document.getElementById('mobile-advice').innerHTML = '';
}
}

/* Get current window's width */
function getWidth()
{
var myWidth = 0, myHeight = 0;
if( typeof( window.innerWidth ) == 'number' ) {
//Non-IE
myWidth = window.innerWidth;
myHeight = window.innerHeight;
} else if( document.documentElement && ( document.documentElement.clientWidth || document.documentElement.clientHeight ) ) {
//IE 6+ in 'standards compliant mode'
myWidth = document.documentElement.clientWidth;
myHeight = document.documentElement.clientHeight;
} else if( document.body && ( document.body.clientWidth || document.body.clientHeight ) ) {
//IE 4 compatible
myWidth = document.body.clientWidth;
myHeight = document.body.clientHeight;
}
return myWidth;
}

/* Window resizes */
window.onresize = function() {
onBodyComplete();
};

</script>
</head>
<body onLoad="onBodyComplete()">
<div id="wrapper">

<div id="center">
<div id="inner">This is center</div>
</div>

<div id="right">
<div id="inner">This is right</div>
</div>

</div>

<div id="mobile-advice" style="padding-top:10px;">
<center><small>Check this on your mobile and see how it changes!</small></center>
</div>

</body>
</html>



Three-Parts Screen with DIVs

This is my version for a three parts screens using DIVs with easy.

Tested on Chrome 23.0, Firefox 17.0.1 and iPod Touch / iPhone


<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<title>My three parts screen</title>
<style>
body {
font-family:Arial;
font-size:16px;
background:#ddd;
}
#wrapper {
width:auto;
margin:0px;
padding:0px;
border: 1px solid #ccc;
margin-right:5%;
margin-left:5%;
}
#left {
float:left;
width:20%;
background:#ccc;
}
#center {
float:left;
width:60%;
background:#444;
}
#right {
float:right;
width:20%;
background:#777;
}
#inner {
padding:10px;
overflow:hidden;
}
</style>
</head>
<body>
<div id="wrapper">

<div id="left">
<div id="inner">This is left</div>
</div>

<div id="center">
<div id="inner">This is center</div>
</div>

<div id="right">
<div id="inner">This is right</div>
</div>

</div>
</body>
</html>

miércoles, 5 de diciembre de 2012

Adding maps to your BlackBerry WebWorks Applications - YouTube

Adding maps to your BlackBerry WebWorks Applications - YouTube

THE FUTURE OF DIGITAL - Business Insider

Excelente presentación de lo que significa el mundo tecnológico hasta este año 2012 que finaliza.

En resumen, se ve que Google con Android domina el mercado y que mobile es la plataforma del futuro (pero no solamente mobile)

El buscador más usado es Google y las ventas que hace en publicidad es por el mismo monto que todo USA hace en publicidad escrita.

Estoy prestando atención a las gafas de Google que ahora también tiene competidor (Microsoft) y también van a  ser el punto de pelea en el futuro.

Quién realmente gana? Siempre Google. Por qué? Porque la misión de Google es vender publicidad por Internet y es por eso que le interesa que sea libre y rápida y que todos usemos lo último de la tecnología para poder aprovecharla al máximo. Mientras que Microsoft o Apple venden productos. Y sin saberlo, le están ayudando a Google a facturar más.


THE FUTURE OF DIGITAL [SLIDE DECK] - Business Insider