Posts Tagged ‘BlackBerry OS’


Esta es una continuación de la serie sobre desarrollo en móviles que comencé aquí. Les recomiendo comenzar a escribir el código fuente desde la parte 1 ya que no se publica el código para descarga.

En este post vamos a incluir algunos puntos propios de la plataforma Cordova que no han sido tan obvios hasta ahora y que deben tenerse en cuenta para aplicaciones reales, pero antes hay que planificar.

Planificación:

Quedó pendiente en el post anterior la implementación de un método para ver cuáles noticias eran las más leídas. Algo tan simple nos plantea los siguientes retos:

  1. Al leer una noticia se tendría que agregar un documento indicando que un usuario determinado la ha leído.
  2. El documento agregado debe ser sincronizado solamente con el servidor. No debe ser sincronizada con los demás usuarios
  3. En el servidor debe existir un mecanismo que concentre todos los documentos de lectura y haga la suma para poder ordenarlos y así presentarlos en el móvil.

El punto 1 requiere de insertar documentos en la base de datos. Para eso recordemos que no hay un esquema así que tendríamos que definir uno que tenga al menos la siguiente información: usuario, noticia leída, la fecha. Digamos que algo así:

{
_id:’id del documento’,
newsId: ‘id de la noticia leída’,
fecha: ‘fecha cuando fue leída la noticia’,
tipo: ‘read’,
usuario: ‘usuario’
}

Por ahora este documento cumple con lo que nos hace falta. Y para grabarlo a la base de datos el comando es super simple “db.put(doc)”, ya veremos como usarlo.

Para el punto 2 debemos considerar que al sincronizar, debe haber alguna información en el documento para definir si hace falta ser sincronizado o no. Además, también debemos considerar que hay información que tiene que ir a todos los usuarios. Entonces podríamos usar el campo “usuario” de la siguiente manera:

  • Consideraremos que la información común a todos, será asignada al usuario “all”
  • La información particular a un usuario, será asignada al usuario actual

De esta manera, podríamos definir una regla para que se sincronicen todos los registros donde el campo usuario sea igual a “all” o al “usuario actual”. Lo único que debemos asegurar es que tengamos un mecanismo para generar un id único. También podríamos considerar que no se pasen las noticias muy antiguas, para eso podríamos pasar la fecha, lo haremos al final si sobra tiempo. Todas estas reglas se entienden fácil, pero hace falta que le hagamos entender esto a CouchDB en Cloudant para lo que tendremos que usar una característica llamada “filtered replication” o replicación filtrada.

Para el punto 3, tendremos que usar una vista en el servidor que utilice una característica llamada map/reduce, que no es otra cosa que un proceso de dos pasos, que está optimizado para cuando trabajamos con un software distribuido en muchos servidores, tal como es nuestro caso. Para entenderlo fácil, MAP concentra todos los documentos que deben ser manipulados para que luego REDUCE realice cierta operación sobre los mismos. En nuestro caso, lo que haremos será buscar los documentos donde registramos que una noticia ha sido leída, luego las agrupamos por noticia y contamos las ocurrencias para hacer el ordenamiento.

Ejecución:

Empecemos por un punto 2, la replicación filtrada pues no queremos que los datos viajen por la red sin control para eso tenemos que crear un filtro así que vayamos a la sección de documentos de diseño:

alldesigndocs

Encontraremos el documento por diseño por defecto y para ver su contenido vamos a hacer click en el icono de lápiz:

designdoc

El documento de diseño tiene varias secciones, podrán reconocer la sección “views” donde he definido unas vistas de prueba. Lo que necesitamos hacer es crear una sección nueva para definir nuestro filtro, el cual debe ser una función que se evalúa por cada documento que se envía en una replicación. Tengamos en cuenta que para nuestro caso, la función debe ser verdadera para cuando el campo de usuario sea igual a “all” o al usuario actual. Digamos que el campo usuario lo llamamos “owner” entonces nuestra función de filtro sería:

"filters": {
"leidos": "function(doc,req){ if (doc._deleted){ return false; } if (doc.owner){ if (doc.owner==req.query.user){ return true; }; if (doc.owner==’all’){return true;};} else {if (doc._id==’_design/news’){return true;} else {return false;}}}"
}

Hemos agregado una condición al inicio y se refiere a los documentos que no hayan sido borrados. Por diseño, Couchdb guarda incluso los documentos que borras y los sincroniza también, así que es mejor filtrarlos para que la comunicación sea más fluida y de pasadita ahorramos algo de espacio.

La replicación filtrada nos permite asegurar que los datos que se replican son los necesarios y esto es super importante por dos razones: seguridad y uso de ancho de banda. Es muy común tener apps donde los usuarios tienen información particular que les es exclusiva, como por ejemplo, tu lista de compras, tu lista de reservas, tus preferencias y así. Con este mecanismo aseguramos que cada usuario tiene lo que realmente necesita y de pasadita al dejar de enviar un montón de documentos inútiles ahorramos espacio y ancho de banda.

Y bien, agregado el filtro a nuestro documento de diseño, éste quedaría así:

{
"_id": "_design/news",
"_rev": "41-667b5e312f96dfec37e4480d2c34479d",
"views": {
"topic": {
"map": "function (doc) {\n if (doc.tipo ==\"news\") {\n emit([doc.topic], {_id:doc._id});\n }\n}"
}
},
"filters": {
"leidos": "function(doc,req){ if (doc._deleted){ return false; } if (doc.owner){ if (doc.owner==req.query.user){ return true; }; if (doc.owner==’all’){return true;};} else {if (doc._id==’_design/news’){return true;} else {return false;}}}"
}

Tengan cuidado de poner todos los valores en una sola línea y de no utilizar la comila (“) dentro de cada valor.

Y listo, ahora vamos al código de replicación y pongamos la llamada a la replicación filtrada paso a paso:

Creamos una función para obtener un id único. Para eso, nuevamente usamos ng-Cordova para usar el plugin Device, esta vez en el archivo app.js ya que tenemos que agregar un concepto propio de la plataforma Cordova:

angular.module('starter', ['ionic','controller','service','ngCordova'])
.run(function($ionicPlatform,$cordovaDevice,$cordovaSplashscreen,$state,db) {
 $ionicPlatform.ready(function() {
 // Hide the accessory bar by default (remove this to show the accessory bar above the keyboard
 // for form inputs)
 if(window.cordova && window.cordova.plugins.Keyboard) {
 cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true);
 }
 if(window.StatusBar) {
 StatusBar.styleDefault();
 };
 var id='1231232132';
 if (ionic.Platform.platform()!="win32"){
 id = $cordovaDevice.getUUID();
 };
 db.setUser(id);
 $state.go('app.news',{status:'loading'});
 });
})

Como verán, hemos incluido el módulo ngCordova como una dependencia y llamamos a $cordovaDevice.getUUID() que genera un código único por equipo móvil. Para ser exactos no es tan único, o al menos no tenemos garantía de eso, pero para nuestro ejemplo es suficiente. Quizá lo mas interesante aquí es como y cuando llamamos a esta función.

Si se dan cuenta en la línea 3, llamamos a la función $ionicPlatform.ready. Como les dije en algún momento, ionic está basado en Apache Cordova y pues Apache Cordova se toma un tiempo en estar listo para contestarnos por lo que usamos la línea 3 para “esperar” a que cargue Cordova y en ese momento, ya podemos utilizar los plugins que nos dan acceso a funciones nativas, en nuestro caso $cordovaDevice. Ahora bien, el valor del id del equipo es crítico, al punto que tendremos que hacer que el app espere a que tengamos este valor disponible y eso es lo que se hace en las líneas siguientes:

  • En la línea 16, pasamos el valor del id del equipo al servicio porque nos va a servir para sincronizar la base de datos
  • En la línea 17, cuando ya tenemos todo listo, recién pasamos al estado que muestra las noticias.

Ahora bien, ¿Que hacemos mientras Cordova termina de cargar? pues lo mejor es mostrar un Splash Screen o el logo de nuestra aplicación y para eso tenemos que agregar un nuevo estado


$stateProvider
.state('splash',{
url:"/splash:status",
cache:false,
views:{
"home":{
templateUrl:"templates/splash.html",
controller:"splashController as splash"
}
},
resolve:{
status: function($stateParams){
return $stateParams.status;
}
}
})

Obviamente, tendremos que agregar una vista y un controlador. Aquí va la vista:

<ion-view view-title="">
<ion-content>
<div class="row">
<div class="col"></div>
<div class="col"></div>
<div class="col"></div>
</div>
<div class="row row-center">
<div class="col">
</div>
<div class="col">
<img src="img/ionic.png">
</div>
<div class="col">
</div>
</div>
<div class="row-bottom">
<div class="col"></div>
<div class="col"></div>
<div class="col"></div>
</div>
</ion-content>
</ion-view>

y aquí va el controlador:


.controller('splashController',
['$state',
'status',SplashController])
function SplashController($state,status){
if (status!='loading'){
$state.go('app.news');
}
};

Y verán que hay código de más. El tema es el siguiente: si el splash screen es un estado quiere decir que cuando se muestren las noticias, el usuario podría “regresar” al splash screen con el botón Back en Android o Windows Phone, cosa que nadie quiere. Para eso, en la línea 17 del archivo app.js al ir al estado de noticias, pasamos un parámetro para indicar que llamamos al estado desde el flujo de inicio del programa. Si vamos al estado de noticias mediante el botón Back, llegará sin éste parámetro y haremos que se vaya automáticamente al estado news nuevamente.

Lo bueno de todo esto es que con este mecanismo implementado, estamos 100% seguros que Cordova está listo y todas las llamadas a plugins funcionarán correctamente.

Ahora vamos hasta la método replicate y le tendremos que agregar el usuario como un parámetro, así que también tenemos que actualizar la línea donde llamamos a la replicación en método init. Mostremos todo el archivo services.js porque hay mas cosas que explicar

angular.module('service',[])
.factory('db',['$q','$rootScope',DbService]);
 function DbService($q,$rootScope){
 var key = 'xxxxxxxx';
 var pass = 'xxxxxxxxxx';
 var remote = 'https://'+key+':'+pass+'@server.cloudant.com/news';
 var db;
 var user;
 var initiated=false; 
 function newNews(change){
 if (!change.deleted){
 if (change.doc.tipo=="news")
 $rootScope.$broadcast('db:newNews',
 {newsId:change.id,
 newsTitle:change.doc.titular});
 }
 };
 function put(doc){
 db.put(doc);
 console.log('documento grabado');
 };
 function changed(change){
 console.log('cambios en la base de datos');
 if (!initiated)
 {
 initiated=true;
 db.changes({live: true, since: 'now', include_docs: true})
 .on('change', newNews);
 };
 $rootScope.$broadcast('db:changed',change);
 };
 return {
 setUser:function(userId){
 user = userId;
 },
 init: function(){
 if (!db) db = new PouchDB('news',{adapter:'websql'});
 this.replicate();
 return true;
 },
 replicate: function(){
 if (!db) this.init();
 db.replicate.to(remote,{live:true,
 retry:true,
 filter:'news/leidos',
 query_params:{'user':user}})
 .on('change',function(info){
 console.log('Evento Chage:'+JSON.stringify(info));
 })
 .on('paused',function(){
 console.log('Evento Paused:');
 })
 .on('complete',function(info){
 console.log('Evento Complete:'+JSON.stringify(info));
 })
 .on('active',function(){
 console.log('Evento Active:');
 })
 .on('error',function(err){
 console.log('Evento error:'+JSON.stringify(err));
 })
 .on('denied',function(info){
 console.log('Evento error:'+JSON.stringify(info));
 });
 db.replicate.from(remote,{live:true,
 retry:true,
 filter:'news/leidos',
 query_params:{'user':user}})
 .on('paused',changed);
 },
 get: function(id){
 if (!db) this.init();
 return db.get(id);
 },
 getCats: function(){
 if (!db) this.init();
 return db.allDocs(
 {startkey:'cat_',
 endkey:'cat_\uffff',
 include_docs:true});
 },
 getNews: function(catId){
 if (!db) this.init();
 if (catId)
 return db.query('news/topic',
 {key:[catId],
 include_docs:true,
 descending:true});
 else return db.allDocs({startkey:'news_\uffff',
 endkey:'news_',
 descending: true,
 include_docs:true});
 },
 putRead: function(pnewsId){
 var doc = {
 _id :'read_'+user+'_'+pnewsId,
 newsId: pnewsId,
 fecha:new Date().toString(),
 tipo:'read',
 owner:user 
 };
 put(doc);
 }
 }
 };

Veamos, vayamos en orden:

  • Primero, tenemos que agregar una función para recoger el dato de usuario, para eso hemos agregado una variable interna en la línea 8 y un método público en la línea 33.
  • Seguidamente, necesitamos grabar registros y para eso tenemos un método privado bastante genérico en la línea 18. Pero como necesitamos agregar un documento por lo que también agregamos un método público en la línea 94 donde verán que usamos una plantilla para crear el documento JSON que describimos al comienzo donde solamente cambiamos los valores de los campos para luego llamar al método interno genérico.
  • Finalmente, agregamos un comando de replicación. Los atentos se habrán dado cuenta que el comando de replicación que hemos venido usando es db.replicate.from lo cual significa que la dirección de los datos es del servidor al móvil, así que faltaría agregar la dirección del móvil al servidor. Para hacer un poco más gráfico el proceso de debug, hemos agregado todos los eventos del proceso. NOTA: Hay un comando db.sync que replica en ambos sentidos, pero prefiero separar las dos direcciones porque quiero capturar solamente los eventos solamente cuando “llegan” nuevas noticias del servidor.

Tanto lío y la pregunta que se cae de madura: ¿Ya podemos grabar y sincronizar? Aún no. Falta que la llave que usamos para conectarnos a la base de datos en Cloudant tenga permisos para escribir. Para eso vamos a Cloudant y hacemos click en el icono de candado al costado del nombre de nuestra base de datos:

Security

permissions

En el segundo gráfico podrán verificar que el key que están utilizando tenga el privilegio Writer.

Ahora, agreguemos la grabación cuando una noticia es leída en el controlador:


function DetailController(db,$scope,$state,noticia){
$scope.event= noticia;
db.putRead(noticia._id);
$scope.$on('db:changed',
function(event,changed){
$state.go('.', null, { reload: true });
console.log('Recargando Noticia');
});
};

En la línea 34, hemos agregado la dependencia al servicio db y en la línea 36 hemos agregado la bendita línea para grabar que hemos leído una noticia.

Y con eso ya podemos ver como nuestra aplicación hace lo siguiente:

  • Graba un documento del tipo ‘read’ cada vez que alguien lee una noticia. Como la clave del documento es única, si entramos a una noticia ya leída, la inserción simplemente fallará y eso esta bien porque asegura que sólo tendremos un documento tipo ‘read’ por noticia.
  • Los documentos serán sincronizados con la base de datos en Cloudant de forma automática.
  • Finalmente, manejamos el evento Ready para verificar que Cordova ya está listo y podemos usar plugins que nos den acceso a servicios nativos.

El Post ya se hizo largo, así que en uno próximo haremos un reporte de las noticias más leídas y ya que el código se ha hecho algo grande, vamos a ponerlo en un repositorio Github.

Por mientras, traten de actualizar su código para que tengan la funcionalidad desarrollada hasta el momento. Y sobre todo revisen los eventos que genera la replicación móvil – Servidor.

Advertisements

Esta es una continuación de la serie sobre desarrollo en móviles que comencé aquí. Les recomiendo comenzar a escribir el código fuente desde la parte 1 ya que no se publica el código para descarga.

Me he tomado algo de tiempo para escribir este post por una simple razón: es hora de limpiar el código. Hasta ahora el código que han ido desarrollando es funcional pero tiene cosas que les va a crear problemas cuando quieran hacer algo mas grande. Lo bueno en todo esto es que si han ido cambiado el código desde el post 1, entonces estos cambios les harán mucho sentido. La buena noticia en todo esto, es que puedo publicar todo el código sin problemas.

Comencemos con el archivo app.js:


angular.module('starter', ['ionic','controller','service'])
.run(function($ionicPlatform) {
$ionicPlatform.ready(function() {
// Hide the accessory bar by default (remove this to show the accessory bar above the keyboard
// for form inputs)
if(window.cordova && window.cordova.plugins.Keyboard) {
cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true);
}
if(window.StatusBar) {
StatusBar.styleDefault();
};
});
})
.config(function($stateProvider, $urlRouterProvider) {
$stateProvider
.state('app',{
cache:false,
abstract: true,
views:{
"home":{
templateUrl:"templates/menu.html",
controller:"menuController as menu"
}
},
resolve:{
cats: function(db){
return db.getCats();
}
}
})
.state('app.news', {
cache:false,
url: "/:catId",
views: {
"menuContent":{
templateUrl: "templates/news.html",
controller: "newsController as news"
}
},
resolve:{
noticias:function($stateParams,db){
return db.getNews($stateParams.catId);
}
}
})
.state('app.detail', {
cache:false,
url: "/detail/:id",
views: {
"menuContent":{
templateUrl: "templates/detail.html",
controller: "detailController as detail"
}
},
resolve:{
noticia: function($stateParams,db){
return db.get($stateParams.id);
}
}
});
$urlRouterProvider.otherwise('/');
})

Empecemos poniendo atención en la definición de los estados a partir de la línea 14.

  • Si tenemos un estado que muestra datos que cambian muy seguido en la base de datos, lo mejor es desactivar el cache. Eso lo hacemos mediante el atributo “cache:false” como pueden ver en la definición de cada estado.
  • Los datos es mejor cargarlos en la definición del estado. Esto se traduce en la utilización del atributo “resolve” donde verán que hacemos la llamada al servicio que recupera los datos y los pasa mediante una variable al controlador. Podrán ver que un estado puede recibir parámetros y estos nos sirven para recuperar los datos llamando a los servicios.
  • $ionicPlatform.ready() es muy importante. Conforme nuestro proyecto crezca, notarán que hará falta inicializar algunos componentes o plugins de Cordova. Cordova es el core de todo esto y la noticia es que se demora en cargar, por lo que si tenemos que inicializar algún componente o plugin propio de Cordova tienen que ponerlo dentro de esta función pues se ejecuta cuando ya es un hecho que Cordova ya está cargado.

Ahora nos toca ir a revisar el archivo controller.js


angular.module('controller',['service'])
.controller('menuController',
['$scope',
'$state',
'cats',MenuController])
.controller('newsController',
['$scope',
'$state',
'noticias',NewsController])
.controller('detailController',
['$scope',
'$state',
'noticia',DetailController]);
function MenuController($scope,$state,cats){
$scope.cats = cats.rows;
};
function NewsController($scope,$state,noticias){
$scope.notas=noticias.rows;
$scope.$on('db:changed',
function(event,changed){
$state.go('.', null, { reload: true });
console.log('Recargando noticias');
});
};
function DetailController($scope,$state,noticia){
$scope.event= noticia;
$scope.$on('db:changed',
function(event,changed){
$state.go('.', null, { reload: true });
console.log('Recargando Noticia');
});
};

Aquí si hay varias cosas para revisar. Primero, si bien no hay reglas a la hora de programar, si existen buenas prácticas y la mejor que he encontrado es esta: Angular Style Guide, así que apliquemos algunas reglas.

  • La primera es lo que se llama IIFE. Considerando que el app.js es el archivo principal, tanto controller.js como service.js deben tener todo el código contenido en una estructura como esta:
    • (function(){ … })(); En nuestro caso, hemos incumplido esto para simplificar la explicación, pero pueden agregarlo y verán que el código funciona sin problemas.
  • Verán que las definiciones de cada controlador ahora es algo diferente y es por dos razones:
    • Dependency injection: Esto es casi un formalismo, es decir, la podríamos saltar sin problema pero nos costará cuando usemos herramientas para comprimir nuestro código o para cuando querramos empaquetar nuestro código para evitar copiones. Si se fijan el primer controlador, verán que primero indicamos todos los componentes, entre comillas, que vamos a utilizar dentro del controlador, y luego, tenemos una función como parámetro final sin comillas. Finalmente la función es definida al final incluyendo como parámetros todos los componentes que vamos a usar ya sin comillas.
    • Siguiendo las sugerencias de estilo, el código de cada controlador es ahora puesta en una función aparte a fin de facilitar la lectura del código
  • La base de datos puede cambiar. Para esto tienen que pensar mucho en como diseñar sus estados. En cada controlador he incluido un $scope.$on para recibir el evento $broadcast con el nombre ‘db:changed’ que ya veremos exactamente cuando se genera pero básicamente es que cuando se detecte un cambio en la base de datos debemos refrescar los datos, en nuestro caso, hemos elegido volver a correr el estado mediante la línea $state.go() que vuelve a correr el código.
    • Algo es super importante: Cuando refrescan un estado, el estado padre no se refresca, pero si vemos bien en nuestro caso, el controlador del estado padre no tiene nada de código para refrescar. La clave aquí es que el estado padre se trata de un estado abstracto y estas si se refrescan junto con los estados hijos. Es por eso que basta con refrescar los estados hijos.

Nota final, hay casos en los que su vista no requiera refrescar datos a cada rato, así que evalúen activar el cache o que el refresco de los datos venga de algún evento como un click en algún botón, o un cambio de estado. Refrescar automágicamente cuesta.

Ahora veamos el service.js


angular.module('service',[])
.factory('db',['$q','$rootScope',DbService]);
function DbService($q,$rootScope){
var key = 'bentareadyessharyinessee';
var pass = 'OnEixgKgpt8LyEtl0S5DkAon';
var remote = 'https://'+key+':'+pass+'@supermio.cloudant.com/news';
var db;
function changed(change){
console.log('cambios en la base de datos');
$rootScope.$broadcast('db:changed',change);
};
return {
init: function(){
if (!db) db = new PouchDB('news');
this.replicate();
return true;
},
replicate: function(){
if (!db) this.init();
db.replicate.from(remote,
{live:true,
retry:true})
.on('paused',function(changes){
changed(changes);
});
},
get: function(id){
if (!db) this.init();
return db.get(id);
},
getCats: function(){
if (!db) this.init();
return db.allDocs(
{startkey:'cat_',
endkey:'cat_\uffff',
include_docs:true});
},
getNews: function(catId){
if (!db) this.init();
if (catId)
return db.query('news/topic',
{key:[catId],
include_docs:true,
descending:true});
else return db.allDocs({startkey:'news_\uffff',
endkey:'news_',
descending: true,
include_docs:true});
}
}
};

  • Cuiden la replicación.
    • En la línea 20 verán que iniciamos la replicación  y con el atributo “live” y esto nos garantiza que los cambios se pasan casi instantáneamente pero como todo en la vida, tiene costo, así que consideren tenerlo apagado y encenderlo cuando haga falta. El siguiente atributo “retry” se refiere a que la replicación continúe luego de una falla en la red, tenganlo siempre en true, pero no se confíen.
    • Al encender la replicación podemos especificar eventos. En nuestro caso en la línea 23 encendemos el soporte del evento “paused” que se refiere a que la replicación “live” ya no recibe nuevos cambios, así que podemos decir que la base de datos remota y local son iguales. En nuestro caso, este es un buen momento para refrescar las vistas. Hay otro evento que es “change” que se genera con cada cambio que se recibe, lo cual es bueno pero no tanto porque si refrescamos aquí, tendríamos ejecutando el refresco muchas veces, sobre todo para la corrida inicial y nuestra base de datos local está vacía. Luego utilizaremos estos eventos para crear notificaciones locales.
  • Cuídense de los procesos asíncronos. Todos los métodos en el servicio “db” son iguales en una cosa: todos son asíncronos. Tal como les recomendé, usen los estados en el archivo app.js para llamar a estos métodos y se librarán de la pesadilla de javascript llamada “Promesas”. Ya luego veremos como manejar promesas.

Y listo, todos los demás archivos van iguales y en lineas generales podrán ver que el código está más limpio y ordenado, además, estamos llamando todos los datos directamente desde la base de datos.

Nota final: en lo que se refiere a performance, el consejo básico es que diseñen su app para que no tenga que refrescar automáticamente nada de nada, a menos que sea absolutamente necesario por la simple razón que si bien PouchDB es una buena base de datos, puede volverse bastante pesada si tienen que lidiar con una gran cantidad de datos. Refrescar datos por eventos es la mejor estrategia.

Como podrán ver, no han habido nuevas funciones que hayamos incluido, pero al menos hemos mejorado el código y espero que los consejos y el estilo de programación mostrado aquí les sirva para organizar su código en algo que puedan mantener y entender con facilidad.


Esta es una continuación de la serie sobre desarrollo en móviles que comencé aquí. Les recomiendo comenzar a escribir el código fuente desde la parte 1 ya que no se publica el código para descarga.

En la parte 8 dejamos el app con menus que permiten mostrar las noticias por categorías, pero faltaba cargar las categorías directamente de la base de datos, de tal forma que se vayan mostrando automáticamente las categorías nuevas.

Las categorías serán extraídas de la base de datos en una consulta bastante simple y debemos considerar lo siguiente:

  • Debemos poder establecer el orden en que se muestran las categorías. Para eso deberíamos agregar un campo que nos permita establecer el orden.
  • Debemos poder desactivar las categorías. Para eso también agregaremos un campo que indique el estado

Ok, hagamos esto primero entonces, felizmente tenemos pocas categorías. Para ejemplo, veamos como quedaría “cat_01”:


{
"_id": "cat_01",
"_rev": "2-df694a2ed157b1403baa2ca9214d801b",
"nombre": "Nacional",
"descripcion": "Noticias del país",
"tipo": "topic",
"orden":1,
"estado":1
}

Lo mismo para todas las demás categorías. Con estos cambios, consultaremos las categorías, las ordenamos por el campo orden y las filtramos por el campo estado.

Ahora para hacer la consulta tenemos dos alternativas:

  • Usando la clave principal: Todas las categorías tienen un id que comienza con “cat_” entonces podemos usar la clave por defecto. El orden será el orden de las claves: cat_01 luego cat_02 y así, igual podemos ordenarlo al mostrarlo.
  • Creando una vista: estaremos creando un índice solamente para recuperar categorías, ¿valdrá la pena? pues son pocas categorías así que aún no vale la pena ir por este camino.

Listo, ahora empieza lo complicado. Necesitamos que la lista de categorías sea pasado al estado inicial para que pueda ser mostrado. Recordemos que hemos definido un estado abstracto para incluir el sidemenu. Como en el ejemplo anterior, pasar datos a un estado requiere el uso de “resolve”, eso es fácil, lo  complicado es que tenemos que hacer la consulta a la base de datos para obtener esas categorias. Si revisan el archivo app.js, por ninguna parte hago alguna llamada al factory “db” que contiene todas nuestras operaciones por lo que si llamamos alguna operación de datos en el “app.js” simplemente fallarán porque acuerdense que inicializamos la factory db con el método db.init()  en el controlador “newsController” en la línea 8 del archivo “controllers.js” como pueden ver aquí:


.controller('newsController',function($scope,db){
db.init();
$scope.notas=[];
$scope.$on('refrescar',function(event,news){
$scope.$apply(function(){
$scope.notas = news;
})
})
})

Esto nos funcionó en su momento, y como ya podrán darse cuenta, es una mala práctica inicializar las cosas en un controlador. Incluso llamar a un inicializador puede resultar malo. Así que comenzaremos modificando el archivo “services.js” para dos cosas:

  • Ejecutar la rutina init() cuando haga falta y no tener que llamarla en ningún lugar. De esta manera simplemente llamaremos a las consultas y siempre tendremos la db lista.
  • Agregar el método para consultar las categorias

El primer paso es fácil: simplemente agregamos estas líneas al comienzo de cada método:


if (!db) {
this.init();
}

Simple, preguntamos si la variable interna es nula o no, y ejecutamos el método init() cuando haga falta. Agregamos eso a todos los métodos del factory.

El segundo paso también es simple, al igual que el método “mostrar” usamos el método allDocs así:


getCats: function(){
if (!db) {
this.init();
}
return db.allDocs({startkey:'cat_',endkey:'cat_\uffff',include_docs:true})
}

Con tanto cambio, el archivo services.js. quedará así:


angular.module('services',[])
.factory('db',function($rootScope){
var key = 'bentareadyessharyinessee';
var pass = 'OnEixgKgpt8LyEtl0S5DkAon';
var remote = 'https://'+key+':'+pass+'@supermio.cloudant.com/news';
var db;
var cat;
var mostrar = function(){
db.allDocs({startkey:'news_\uffff',endkey:'news_',descending: true,include_docs:true})
.then(function(result){
$rootScope.$broadcast('refrescar',result.rows);
});
};
var mostrarCat = function(catId){
if (catId)
db.query('news/topic',{key:[catId],include_docs:true,descending:true}).then(function(result){
$rootScope.$broadcast('refrescar',result.rows);});
else mostrar();
};
return {
init: function(){
if (!db) {
db = new PouchDB('news');
}
mostrarCat(cat);
this.replicate();
},
replicate: function(){
if (!db) {
this.init();
}
db.replicate.from(remote,{live:true,retry:true})
.on('paused',function(info){
mostrarCat(cat);
});
},
get: function(id){
if (!db) {
this.init();
}
return db.get(id);
},
setCat: function(id){
if (!db) {
this.init();
}
cat = id;
mostrarCat(cat);
},
getCats: function(){
if (!db) {
this.init();
}
return db.allDocs({startkey:'cat_',endkey:'cat_\uffff',include_docs:true})
}
}
});

Y listo, ahora vamos al archivo “app.js” donde vamos a agregar el resolve al estado abstracto:


.state('app',{
abstract: true,
views:{
"home":{
templateUrl:"templates/menu.html",
controller:"menuController as menu"
}
},
resolve:{
cats: function(db){
return db.getCats();
}
}
})

Agregamos la llamada al nuevo método y la pasamos al controlador a través de la variable “cats”. No se olviden de que, ya que estamos llamando a un factory definido en el modulo “services”, tenemos que agregarlo como dependencia. Así que la primera línea de nuestro “app.js” será ahora así.


angular.module('starter', ['ionic','controllers','services','ui.bootstrap'])

Y listo, terminamos con este archivo. Ya terminamos actualizando el modelo, ahora toca el controlador. Aquí simplemente tenemos que recoger la variable cats con las categorias y quitar cualquier llamada a “db.init()”. El controlador “menuController” quedaría así:


.controller('menuController',function($state,$scope,db,cats){
$scope.cats = cats.rows;
$scope.setCat = function(id){
$state.go('app.news');
db.setCat(id);
};
})

Acuérdense que cuando llamamos a la base de datos obtenemos un objeto y dentro de ese objeto el array “rows” nos trae los registros que queremos.

Ahora así quedaría el controlador “newsController”:


.controller('newsController',function($scope){
$scope.notas=[];
$scope.$on('refrescar',function(event,news){
$scope.$apply(function(){
$scope.notas = news;
})
})
})

Como ven, sólo le hemos quitado la llamada a “db.init()”. Y con esto hemos acabado con el controlador y vamos a arreglar la vista en el archivo “menu.html”. Si se fijan en la línea 25 comienza una lista donde todos los valores son estáticos y va hasta la línea 38. Reemplazamos todo eso por un iterador y quedaría así:


<ion-list>
<ion-item collection-repeat="cat in cats | orderBy: 'doc.orden'| filter:{'doc':{'estado':1}}" nav-clear menu-close ng-click='setCat(cat.id)'>
{{cat.doc.nombre}}
</ion-item>
</ion-list>

Cosas interesantes aqui:

  • Collection-repeat es propio de Ionic y es más eficiente en listas largas
  • El array cats es el que metimos en el $scope desde el controlador y tiene todas nuestras categorías.
  • Mediante “orderBy” las ordenamos por el campo “orden” que agregamos.
  • Y mediante “filter” mostramos solamente los que tienen estado =1

Todo lo demás viene del template anterior, incluso para llamar al método “setCat” llamamos al cat.id.

Y listo!, Si prueban el código hasta aquí funcionará, pero hay algunas limitaciones:

  • Si agregan una categoría en el servidor o se cambia el orden de alguna, no se refresca en la lista del menú automáticamente
  • Al inicio se mostrarán todas las noticias, incluidas las que pertenezcan a categorías inactivas

Esas las veremos en el siguiente post. Lo que si es urgente es corregir el estilo del código, siendo el más urgente el que figura en el controlador “detailController” donde verán que tenemos una llamada a db.get y esperamos por la respuesta. Si se habrán dado cuenta, para las categorías es lo mismo pero no esperamos por respuesta en ningún lado, simplemente esperamos valores y ya, así que usemos ese patrón aquí y corrijamos el controlador para que se vea así:


.controller('detailController',function($scope,db,noticia){
$scope.event=doc;
});

y tendríamos que cambiar el resolve de la ruta en el archivo “app.js” para que se vea asi:


resolve:{
noticia: function($stateParams,db){
return db.get($stateParams.id);
}

Y listo. La explicación para este cambio es que como casi todo en JavaScript es asíncrono, tenemos que usar un modelo llamado “promesas” para esperar por la respuesta y hacer algo con eso. Es por esto que usamos objeto.then() y dentro del then() incluimos una función con una operación. Pues bien, al momento de utilizar estados, el componente detrás de toda esa magia (UI-Router!!!) hace muchas cosas bien, y una de esas cosas es manejar la resolución de las promesas y es tan inteligente que esperará hasta que la promesa sea resuelta y podamos recibir los documentos en el controlador. Con eso nuestro código queda mas limpio.

Promises es todo un tema y siempre es un motivo de confusión así que mejor dejar que sea UI-Router quien las maneje. Así que para que se diviertan notarán que para cargar las noticias para la categoría seleccionada podemos usar el mismo esquema anterior. A ver si pueden completarlo por su cuenta.

NOTA: Ya un lector notó que todo es sobre leer datos en el cliente y tiene una razón. Grabar datos es bien simple pero supone que tengamos mucho cuidado durante el diseño, porque si dejamos escribir a un usuario, debemos tener en cuenta que cualquier usuario podrá hacerlo a menos que pongamos un control. Establecer un control requiere de cierto manejo adicional que aún falta explicar. Para que lo vean en perspectiva: 

  • Cualquiera puede escribir:
    • No hace falta establecer usuario y pass al inicio
    • Se debe tener cuidado de no duplicar el id del documento que se vaya a grabar
    • La replicación debe modificarse, porque si hay muchos usuarios, estaremos recibiendo muchos cambios y datos en nuestra db local.
  • Todos usuarios registrados pueden escribir:
    • Hay que incluir usuario y pass
    • También la replicación se tiene que ajustar para controlar los datos entrantes.
  • Sólo algunos usuarios registrados pueden escribir:
    • Usuario y clave es obligatorio
    • Hay que determinar quien puede escribir y quien no. Podemos integrar la autentication propia de CouchDB para determinar esto, pero esto aplica a todos los documentos, lo cual no necesariamente es bueno.
    • La replicación también se modifica pero ya es más simple

Recuerden que más importante que empezar a construir es sentarse a diseñar.


Esta es una continuación de la serie sobre desarrollo en móviles que comencé aquí. Les recomiendo comenzar a escribir el código fuente desde la parte 1 ya que no se publica el código para descarga.

El mayor desafio que hay al trabajar con NOSQL es consultar los datos pues no hay algo como SQL del mundo relacional que venga y nos simplifique la vida, bueno, si hay pero en cuestión de rendimiento no es lo mismo. CouchDB originalmente viene con funciones Map/Reduce para la creación de índices o vistas, es decir, si queremos recuperar los datos en cierto orden o hacer consultas tipo select * from tabla where campo3=XXX debemos crear un índice para poder buscar por el campo3, el problema es que como no hay tablas estos índices se aplican a todos los documentos en la base de datos y un detalle importante: estos índices se graban en disco lo que los hace muy eficientes. Suena bonito. Veamos como se hace.

Digamos que en nuestra aplicación de noticias queremos agregar secciones tal como lo tienen todos los sitios de noticias: política, espectáculos, deportes y así. Bien, entonces agreguemos los documentos a la base en Cloudant. Empecemos con 3 categorías.


{
 "_id": "cat_01",
 "nombre": "Nacional",
 "descripcion": "Noticias del país",
 "tipo": "topic"
}

{
"_id": "cat_02",
"nombre": "Internacional",
"descripcion": "Noticias del mundo",
"tipo": "topic"
}

{
"_id": "cat_03",
"nombre": "Espectáculos",
"descripcion": "Cine, TV y música",
"tipo": "topic"
}

{
"_id": "cat_04",
"nombre": "Opinión",
"descripcion": "Punto de vista de los editores",
"tipo": "topic"
}

Listo, 4 categorías para comenzar. Recuerden ingresar estos 4 documentos en su página de Cloudant. Seleccionen la base de datos y luego busquen la opción agregar doc.

addNewDoc

Ingresen uno a uno las 4 categorías.

Importante es el campo “tipo” que nos indicará que estos 4 documentos pertenecen a un tipo “topic” que es como identificaremos a esta entidad. Ahora tendremos en la base de datos documentos tipo “topic” y tipo “news”. Ahora lo que tenemos que hacer es vincular los documentos, así que iremos revisando los documentos tipo “news” uno por uno asignándole un topic. Por ejemplo:

{
 "_id": "news_20150724_vpease_001",
 "_rev": "2-6713e069e048d0eeb4d8d826f52454cd",
 "tipo": "news",
 "titular": "Retomando el demo!",
 "resumen": "Regresando a la lector de noticias esta vez con estados",
 "fecha": "2015/07/24",
 "autor": "vpease",
 "topic": "cat_01"
}

En la línea 9 podrán ver que se ha agregado el campo “topic” y el valor que tiene es el id de la categoría 1: “Nacional”.

Ahora necesitaremos una forma de recuperar la lista de categorías para mostrarlas en el app y luego al seleccionar cada una, mostremos las noticias asociadas.Las opciones que tenemos son :

  • Crear una vista: En el servidor buscaremos todos los documentos tipo “topic” y los devolveremos. Como todo índice, va a crear un archivo físico.
  • Usar el índice por defecto: podemos consultar el índice por defecto utilizando el Id. Si vemos el id que hemos fijado, todos los documentos tipo “topic” tienen un Id del tipo “cat_XXX” así que podemos usar esto para recuperarlos. Se reutiliza el índice principal.
  • Cloudant Query. Es el nuevo motor de búsqueda que viene con CouchDB 2.0 que es más fácil de usar que Map/Reduce. Igualmente creará un índice en un archivo físico.

Tengan en cuenta que:

  • Si crean un índice, se crea un archivo. Esto es tanto en el servidor como en el lado del cliente.
  • Ya existe un índice por defecto que nos permite consultar por el id.

Además, gracias al buen artículo publicado por Nolan Lawson en http://pouchdb.com/2014/05/01/secondary-indexes-have-landed-in-pouchdb.html, lo más conveniente es explotar al máximo el Id principal del documento.

Cloudant Query lo guardaremos para el siguiente post, así que por ahora vamos a utilizar la siguiente estrategia:

  • Crear una consulta al Id para recuperar la lista de categorías
  • Crear una vista para recuperar todas las noticias dentro de una categoría.

Empecemos creando la vista. Esto debemos hacerlo en Cloudant y luego será sincronizada automáticamente al cliente en PouchDB. Recuerden que para PouchDB esto se llama índices secundarios. En la pantalla de Cloudant vamos a la opción de crear vistas.

createview

Ahora le damos un nombre a nuestro documento de diseño, para hacerlo simple llamaremos ‘news’ a este documento. Un documento de diseño es donde se definen los indices y otras reglas en CouchDB. Le pondremos el nombre topics a la vista. CUIDADO:  el nombre del documento de diseño es la raíz para cuando quieran llamar a su vista. En nuestro caso, el nombre completo de nuestra vista será ‘news/topics’.

Lo importante es que queremos poder buscar las noticias por la categoría a la que pertenecen, para eso escribimos lo siguiente en la sección Map function:

function (doc) {
if (doc.tipo =="news") {
emit([doc.topic], {_id:doc._id});
}
}

Tremenda ensalada para tan pocos comandos. Lo que hace esto es recuperar todos los documentos tipo news y publicar el índice doc.topic que como ya saben es el campo que tiene la categoría de la noticia, luego le decimos que devuelva todo el documento con la información de la noticia.

La función Reduce es opcional y se usa para cuando queremos realizar una operación sobre los datos obtenidos, en otras palabras, para los nativos SQL es cuando usamos funciones tipo Sum, Count y esas cosas. Para este caso, no nos hace falta.

Y listo, ahora el documento de diseño será sincronizado con PouchDB en el cliente y podremos hacer las consultas, pero antes debemos cambiar el diseño de nuestra aplicación, Para no alargar este post, vamos a hacer un cambio simple, mostraremos solamente las noticias que pertenezcan a la categoría ‘cat_01’ que corresponde a las noticias nacionales.

Primero, comencemos agregando al servicio db un método para consultar la vista que acabamos de crear.


var mostrarCat = function(catId){
db.query('news/topics',{startkey:[catId],include_docs:true,descending:true})
.then(function(result){
$rootScope.$broadcast('refrescar',result.rows);
});
}

El nombre de la vista es ‘news/topics’, noten que comenzamos por incluir el documento de diseño, porque simplemente puede haber más de uno, pero no compliquemos el asunto por ahora. luego están las opciones que hemos incluido:

  • startkey: simple, es el valor que queremos comparar. Como nuestra clave fue definida simplemente por el campo doc.topic entonces es una cadena simple. Es posible definir mas campos en el índice y en ese caso tendríamos que pasar un array. En este parámetro pasamos el id de la categoría que queremos ver.
  • include_docs: Por defecto, CouchDB devuelve solamente el Id del documento asociado, pero como necesitamos mostrar la noticia completa, tenemos que fijar este parámetro en true.
  • descending: indica que el resultado será ordenado por la clave original en modo descendente, recuerden que la clave original tiene la fecha en el formato YYYYMMDD de tal forma que si la ordenamos en forma descendente tendremos las noticias nuevas al comienzo.

Y listo, ahora solo falta indicarle al código del servicio que en lugar de utilizar el método “mostrar()” , donde se recuperan todas las noticias, utilice “mostrarCat(“cat_01″)” donde le decimos que nos muestre solamente los de la categoría “cat_01” que corresponde a las noticias nacionales. Algo tosco pero efectivo. El código del servicio ‘db’ quedará así:


angular.module('services',[])
.factory('db',function($rootScope){
var key = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxx';
var pass = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx';
var remote = 'https://'+key+':'+pass+'@server.cloudant.com/news';
var db;
var mostrar = function(){
db.allDocs({startkey:'news_\uffff',endkey:'news_',descending: true,include_docs:true})
.then(function(result){
$rootScope.$broadcast('refrescar',result.rows);
});
};
var mostrarCat = function(catId){
db.query('news/topic',{startkey:[catId],include_docs:true,descending:true})
.then(function(result){
$rootScope.$broadcast('refrescar',result.rows);
});
}
return {
init: function(){
if (!db) {
db = new PouchDB('news');
};
mostrarCat("cat_01");
this.replicate();
},
replicate: function(){
db.replicate.from(remote,{live:true,retry:true})
.on('paused',function(info){
mostrarCat("cat_01");
});
},
get: function(id){
return db.get(id);
}
}
})

Podrán ver aquí que hemos agregado el método mostrarCat en la línea 13 y luego cambiamos la función para mostrar los datos en la línea 24 y en la 30. Fijense que no hemos tenido que mostrar el Controller para nada.

Al ejecutar este nuevo código notarán que ya no tendrán todas las noticias que solían tener, ahora verán una o dos. Prueben insertando nuevos documentos y cambiando la categoría.

Para la próxima trataremos de insertar el concepto de estados abstractos y sobre todo, pasar datos a un estado, y para arreglar la parte gráfica, agregaremos un bonito menú que nos permita elegir las categorias como cualquier sitio de noticias decente.


Esta es una continuación de la serie sobre desarrollo en móviles que comencé aquí. Les recomiendo comenzar a escribir el código fuente desde la parte 1 ya que no se publica el código para descarga.

Veamos, en la parte 4 vimos como mostrar los datos aún cuando no se tenga conexión a la red. Según algún comentario, la conexión era necesaria para comenzar la aplicación. A fin de asegurar que la aplicación pueda mostrar los datos existentes incluso sin red desde el inicio, hay que verificar lo siguiente:

  • Hay que separar la lógica de mostrar datos de la lógica de la replicación
  • La lógica de la replicación debe refrescar los datos.

Para cumplir con estos dos criterios hay que modificar nuestro archivo services.js. Primero agregamos la lógica de mostrar datos como una función privada:

angular.module('services',[])
.factory('db',function($rootScope){
    var key = 'bentareadyessharyinessee';
	var pass = 'OnEixgKgpt8LyEtl0S5DkAon';
	var remote = 'https://'+key+':'+pass+'@supermio.cloudant.com/news';
	var db;
	var mostrar = function(){
        db.allDocs({startkey:'news_\uffff',endkey:'news_',descending: true,include_docs:true})
                    .then(function(result){
                    $rootScope.$broadcast('refrescar',result.rows);
                });
    };

Hemos agregado la funcion “mostrar” y ademas en la línea 6 podrán ver que hemos dejado la variable db sin inicializar. La función “mostrar” lo que hace es consultar todos los registros y generar el evento refrescar pasando el resultado de la consulta al controlador.

Ahora hacemos la separación de la replicación y la lógica de mostrar en el método “init” de esta manera.

init: function(){
            if (!db) {
                db = new PouchDB('news');
            };
            mostrar();
            this.replicate();
        },

La versión modificada del método “init” lo que hace ahora es inicializar la variable db y seguidamente muestra los datos y en un proceso aparte inicia la replicación. Recuerden que en Javascript estos métodos son asíncronos, así que un método no bloquea al otro.

Finalmente, el método de replicación también debe considerar la lógica de mostrar los datos.

replicate: function(){
            db.replicate.from(remote,{live:true,retry:true})
                .on('paused',function(info){
                mostrar();
            });
        },

Listo, ahora ya cumplimos con los criterios y nuestra aplicación mostrará los datos que tenga localmente incluso trabajando sin red.

Un punto importante es que la lógica de refrescar datos es una función privada llamada mostrar. Se hace en una función privada porque según el modelo MVC solamente el controlador puede actualizar la vista, pero no debe manipular los datos. Todo siempre por separado.


Windows 10 ha sido lanzado y con esto Microsoft promete un nuevo tipo de aplicaciones: “Universal” que asegura su ejecución en todas las plataformas Windows disponibles, desde teléfonos con Windows 10 Mobile, y Pcs. Lo que no nos está diciendo es que se ha redefinido el concepto de PC.

Las PC eran la primera opción para el trabajo tanto para casa como para oficina, con el tradicional monitor y teclado por un lado y el “CPU” por otro. Si nos hacía falta movilidad, no había problema de cambiar a una laptop. Sin embargo, surgieron los smartphones y las tablets y nos dimos cuenta que iban creciendo en potencia y capacidades al punto que muchas personas pueden quedarse con una tablet como computador principal.

Ahora bien, desde el punto de vista funcional, tener una tablet en lugar de una laptop no es nada del otro mundo. Todo está en función de las necesidades de cada usuario.

Para los desarrolladores el problema es desde donde comenzar.

Si vamos por el ala Apple, comenzamos haciendo programas para iPhone/iPod y luego las hacíamos escalar para soportar tablets iPad, cambiando principalmente las pantallas y las resoluciones soportadas. Si queríamos que nuestra app funcione también en Macs ya teníamos que cambiar el código e incluso las gráficas pues el modelo de programación para Mac OSX es diferente al que tiene iOS.

Para el caso de Microsoft, la separación también era clara, si querías móviles te ibas por un rumbo y si querías PC hacías el desarrollo de siempre. El problema es que siempre que hablamos que Windows Phone, o Windows 10 Mobile hasta ahora siempre hemos hablado de teléfonos que nunca llegaron a ser tablets. Cierto que salió Windows RT donde salieron unas tablets pero esa iniciativa nunca tuvo tanto peso comercial. Lo que si parece que viene con fuerza son las tablets pero con Windows 8.1/Windows 10, es decir, el mismo sistema operativo que tenemos en la PC o en la laptop a un precio incluso menor a las tablets disponibles con sistemas operativos móviles consolidados como iOS o Android.

Con el lanzamiento de Windows 10 se viene otra ola que es la “Aplicación Universal” donde se promete que una misma aplicación podrá ser ejecutada tanto en Windows 10 como en Windows 10 mobile. Hasta el momento no se incluía el soporte para teléfonos con Windows Phone 8/8.1 lo que significa que aún no podrá ser tan universal, pero la presencia en el mercado estas tablets con Windows 8/10 abren la posibilidad de una nueva generación de aplicaciones “super inteligentes” ya que no estamos hablando de apps reducidas para entrar en un equipo móvil, sino la misma app que usamos al sentarnos en la oficina. Si podemos tener la misma app empresarial movilizada con una tablet que cuesta 100 usd ¿Para qué molestarnos en hacer una aplicación en un sistema operativo móvil?

En la última versión de Visual Studio (2015), se incluye también el soporte de Apache Cordova, lo que “oficializa” el soporte de Microsoft de tecnologías abiertas para el desarrollo de aplicaciones multiplataforma. Con Cordova, es posible que nuestra app pueda ejecutarse en iOS, Android, Windows y en otros sistemas operativos móviles en forma limitada. Si bien el funcionamiento tiene aún algunos problemitas, la posibilidad de hacer un sólo código justifica la atención.

De hecho, ya en un post anterior, les he descrito mi aplicación Supercomics basada en Cordova que se ejecuta tanto en Windows Phone, iOS y Android. Aún estoy salvando unos problemitas para publicar la versión para Windows, y para ser específico debo decir que en Windows soporto Windows Phone 8 y 8.1, Windows Phone 10, Windows 8/8.1 y Windows 10. Notarán que puedo ir más allá de lo que ofrece una “Aplicación Universal”.

Por supuesto que una “Universal App” puede darnos opción a integrar APIS empresariales  y más funciones en la parte de desarrollo que aún estoy descubriendo. Hasta el momento he visto que las aplicaciones mientras más desconectadas estén, mejor experiencia de uso brindan, así que empezaremos a comparar las ventajas para sugerir la mejor plataforma para nuestras aplicaciones.

Veamos el siguiente cuadro:movilesCon esto vemos un poco más claramente que significa “Aplicación Universal”.

No es mala idea el Universal App, de hecho, es una gran cosa para el mundo empresarial donde las políticas internas normalmente, favorecen el uso de una misma plataforma para sus aplicaciones internas donde ya Microsoft reina. El tema está en el punto débil de Windows en este momento: la falta de aplicaciones móviles, y la inclusión de Apache Cordova significa que Microsoft ha aceptado que ir por la alternativa de HTML5 es lo más eficiente en este momento.

Como siempre, la pregunta que se hace un desarrollador es sobre la velocidad y las funciones que soportará cada opción pues es obvio que una herramienta “nativa” nos ofrecerá un código más rápido y nos permitirá acceso a los recursos de hardware en el terminal. Siempre al inicio de todo desarrollo se tiene que hacer una evaluación de los requerimientos para nuestra aplicación. Si vamos a desarrollar un juego con alto nivel de procesamiento, la opción nativa se impone, pero si queremos una app tipo red social o capturadora de datos con soporte a múltiples dispositivos entonces podremos “sacrificar” la velocidad por la facilidad de desarrollo pues tenemos que considerar que hay otros aspectos como la velocidad de la red o la cantidad de datos, o la integración a otros sistemas que impactan mas fuertemente en el rendimiento de nuestra aplicación.

Ninguna alternativa es completamente a prueba de balas, todo depende de lo que necesitemos. Es una gran opción usar la misma aplicación en todos nuestros dispositivos, pero la movilidad no está en el dispositivo, está en la forma en la que diseñemos la aplicación. Es necesario que se incluyan paradigmas como sincronización, bases de datos locales, autenticación, encriptación en la que se dejemos de lado de una vez por todas el supuesto que todos los recursos estan disponibles todo el tiempo.


Este post lo hago de urgencia pues acabo de experimentar todo lo que no se debe hacer en el tema de sincronización. Para esto les doy un resumen de mi entorno de desarrollo:

  • Servidor: Instancia en Cloudant.com
  • Cliente: PouchDB como base de datos con sincronización al iniciar el app
  • Framework: Ionic

En resumen, se inicia una sincronización entre mi base de datos local en PouchDB y la base remota en Cloudant. Y puede ser todo lindo en Producción, pero como estamos en Desarrollo, el proceso normal es parar e iniciar el app a cada rato. Ahora veamos las consecuencias en desarrollo:

  • Costo: Cloudant te cobra por transacciones y te dice que por transacciones “pesadas” te va a cobrar mas, y pone varias transacciones HTTP y las define como pesadas. Lo que no dice es que hay otras transacciones que se utilizan para la sincronización que también se califican como pesadas, principalmente OPTIONS que se manda a cada rato. Por lo tanto, cada sincronización es bastante intensiva en costo.
  • Tráfico: Para un modelo de desarrollo, no es bueno iniciar conexiones de datos muy seguido. Entonces, el modelo debe saber cuando es bueno iniciar la sincronización sin importar que sea en Desarrollo o Producción.
  • Batería: Como consecuencia de la reducción de tráfico, estaremos también bajando el consumo de energía en el móvil.

No malentiendan, CouchDB/PouchDB es muy bueno, y si existe la necesidad, podemos activar la sincronización “live”, pero como no todos tenemos servidores y ancho de banda de sobra, tenemos que pensar en servicios en demanda en la red, como Cloudant en mi caso. Tengan en cuenta que otros servicios como IrisCouch, también cobran por transacciones.

La salida que encontré depende de como se necesita que fluyan los datos:

  • El recolector de datos: si nuestra app necesita solamente capturar datos y no necesito bajar información desde el servidor. En este caso, lo mejor es implementar un contador que incrementaremos cada vez que se actualice o se inserte un dato nuevo. Lo bueno es que para un entorno NoSQL, insertar o actualizar es el mismo método, así que en ese método podremos insertar el incremento del contador. Luego, al momento antes de sincronizar, verificaremos si es que el contador es mayor a 0, de lo contrario no iniciaremos la sincronización. Sincronizar solamente cuando hayan datos locales.
  • El visualizador de datos: Aquí es totalmente al revés, el app jala información del servidor. En este caso, la primera alternativa es la de fijar un tiempo de sincronización relativamente alto, agregar un visor de la última fecha de sincronización y la opción para forzar la sincronización manualmente.
  • Finalmente, como siempre, en la vida real tendremos una combinación de necesidades, así que la recomendación final es la de utilizar un proceso de sincronizacion para descargar datos y otro para subir datos.

Hay una función que no he mencionado aún: Sincronización filtrada. Esto es super útil y lo voy a desarrollar en otro Post, Básicamente es la de descargar solamente la información que me interesa. Esto es tan importante que todos deben usarlo si es que tenemos que descargar datos.

En conclusión, es muy fácil sincronizar sin límites, pero en la vida real, no hay que pedir más de lo que debemos consumir.


Esta es una continuación de la serie sobre desarrollo en móviles que comencé aquí. Les recomiendo comenzar a escribir el código fuente desde la parte 1 ya que no se publica el código para descarga.

Les recomiendo que la revisen de principio a fin pues es una guía paso a paso de absolutamente todo lo que tienen que hacer para comenzar en este mundo de móviles con javascript. Averiguarlo por cuenta propia es algo que les tomaría muchos meses, se los aseguro. Y si es algo largo, pues vale la pena pues cubre todos los aspectos.

En esta segunda parte voy a centrarme en las capacidades móviles de la plataforma Ionic y en como PouchDB se acomoda a cada una de las capacidades de nuestros equipos para escoger el mejor almacenamiento para los datos. Y de gratis, sus aplicaciones móviles sincronizarán sus datos con Cloudant.

Comenzaremos con el código que dejamos en el post anterior.

Preparando el ambiente

Para poder empezar a generar el código para las diversas plataformas, tenemos primero que instalar el SDK de cada una de ellas. El foco va a estar en Android y Windows Phone 8. Si quieren generar para iOS necesariamente deben estar en Mac (Si no tienen para comprarse aunque sea una Mac mini, aún les queda crear una máquina virtual con VMware). Pueden generar para mas plataformas si quieren: BlackberryOS 10, Firefox OS, incluso pueden generar para Windows 8 o Tizen y otros, pero todas estas plataformas, tienen sus cositas, hay que hacer ciertos trucos  o simplemente son dificiles de conseguir. Según las pruebas que he hecho, si se van con Android, Wp8 y iOS no tendrán casi problemas.

Empecemos con Android:

Revisemos lo que estamos haciendo: El Java SDK es la base de todo, pero para que funcione bien, debemos fijar la variable de entorno porque otras herramientas se fijan en ese valor para funcionar bien. Para crear estas variables tienen que ir a Mi PC – Propiedades – Configuración avanzada del sistema – Variables de entorno  y de preferencia crearlas como variables del sistema. En resumen:

Paquete Variable Por defecto Debe ser Agregar al Path
Java SDK JAVA_HOME C:\Program Files\Java\jdk1.8.0_40 la ruta donde instalen el jdk  nada
ANT ANT_HOME C:\ant\ la ruta donde descompriman el zip de ant  %ANT_HOME%\bin
Android SDK ADT_HOME C:\program files\android-sdk\ la ruta donde instalen el sdk %ADT_HOME%;

%ADT_HOME%\platform-tools;

Con esto ya tienen listo su entorno para Android. Para Windows Phone 8, basta con que instalen Visual Studio Community 2013 update 4 desde aquí .

Finalizo con algunas sugerencias:

  • Para Android: No usen el emulador que viene en el SDK, usen Genymotion
  • Para Windows Phone: usen un equipo real conectado al USB trabajen cargando el proyecto generado en Visual Studio

Plus: Para MAC tendrán que instalar unos paquetes adicionales en NodeJS: ios-sim y ios-deploy y los instalas fácil con el comando npm install -g <paquete>.

Generando los binarios

Ahora si empieza la diversión y para eso nos vamos a una línea de comandos y nos ubicamos en la carpeta donde dejamos el proyecto Superdatos. Aqui empezaremos a ver los potentes comandos de Ionic.

Agregando una versión de Superdatos para Android:

c:\projects\superdatos\>ionic platform android

Y después de unos segundos ya tendremos nuestra versión para android. ¿No me creen? Entonces lancen Genymotion o conecten su Android a la computadora y luego ejecuten:

c:\projects\superdatos\>ionic run android

Con esto verán su aplicación ejecutarse en un emulador o en su móvil tal cual lo vieron en el navegador. Simple y limpio. Y si quieren una versión para Windows Phone 8 el comando es :

c:\projects\superdatos\>ionic platform wp8

Aquí, como les recomendé, lo mejor es abrir el proyecto generado en c:\projects\superdatos\platforms\wp8 con Visual Studio y ejecutar el proyecto en un emulador o en un móvil conectado.

NOTA: En algún momento les dije que Ionic no soportaba WP8 y es cierto, y por lo que han visto, se darán cuenta que el resultado está algo feo ya que no se ven los iconos. Para suerte de ustedes, y después de perder mucho pelo, la solución es simple. En la ruta “www\css” encontrarán el archivo “ionic.app.css” verán esta línea:

src: url("../lib/ionic/fonts/ionicons.eot?v=2.0.1#iefix") format("embedded-opentype"), url("../lib/ionic/fonts/ionicons.ttf?v=2.0.1") format("truetype"), url("../lib/ionic/fonts/ionicons.woff?v=2.0.1") format("woff"), url("../lib/ionic/fonts/ionicons.svg?v=2.0.1#Ionicons") format("svg");

Y la cambian por esta:

src: url("../lib/ionic/fonts/ionicons.eot?v=2.0.1#iefix") format("embedded-opentype"), url("../lib/ionic/fonts/ionicons.ttf") format("truetype"), url("../lib/ionic/fonts/ionicons.woff?v=2.0.1") format("woff"), url("../lib/ionic/fonts/ionicons.svg?v=2.0.1#Ionicons") format("svg");

Recompilan su proyecto y listo. Recuerden hacer esto cada vez que tengan que generar el proyecto. Para los flojos, el cambio está en remover “?v=2.0.2” de la definición ionicons.ttf, en la columna 126 de la misma línea.

Así de simple pueden tener su aplicación para sus dispositivos con Ionic. Es bastante simple pero hay que saber AngularJS, así que para el post que viene revisaremos algo de eso para mejorar nuestro código. Además, veremos como usar plugins para habilitar mas funciones en nuestras apps móviles.


He pasado los últimos meses programando una aplicación para móviles. No es que yo sea el programador más rápido y eficiente que existe pero me divierte mucho hacerlo. Principalmente disfruto la etapa de diseño, así que más que las funciones en el mismo app, me interesa todo el diseño del entorno que tendrá el app, no solo ahora sino también en el futuro.

Los objetivos que me puse para esta aplicación son:

Que sea muy útil. Obvio, aunque no tanto considerando que hay aplicaciones verdaderamente inútiles que tienen mucho éxito.

Que sea fácil de usar. En otras palabras, que no tenga que hacer clicks por todas partes para verlo funcionar. Instalarlo y listo.

Que sea fácil de mantener. Tengo que poder saber cuántos usuarios tengo, a que hora se conectan, que funciones del app están usando mas y cosas por el estilo. Además, que no tenga que estar contratando un super servicio de hosting que me cueste un montón de dinero mensual.

Utilizar un sólo código para muchas plataformas. Ok, Android es el más popular, pero iOS y WP y otros sistemas operativos deben poder usarlo también.

Que se pueda monetizar.

Todo esto me sonó razonable en su momento y aún me lo parece, así que los mantendré.

Ahora, mientras estaba programando, y considerando el último objetivo, me dí cuenta que hacia falta muchas cosas además de elegir hosting, librerías y demás cosas, realmente fue todo un descubrimiento para mi y sobre todo algo que no se puede aprender en ninguna escuela.

En fin, para hacer la historia corta, el proceso que tuve que seguir fue algo así mas o menos:

– Plataformas: 

Ya había escuchado hablar de Phonegap y Cordova desde hace años pero recién les puse atención como debe ser. Lo primero que encontré es que no son una solución completa. Veamos, la idea es que hagas tu app en JavaScript, luego la extiendas con Phonegap/Cordova y finalmente dejarle la generación de las versiones específicas. Todo bien, pero eso de hacer tu app en Javascript no me sonaba bien, además que Jquery no me quedó muy claro, principalmente porque te puedes enredar muy fácil con javascript si no tienes un framework que te ayude a programar. Jquery se orienta a la parte gráfica pero no a construir una estructura de aplicación que se pueda manejar aceptablemente, hay gente que lo hace, pero en mi caso, faltaba algo.

Por suerte encontré Ionic Framework que es una versión de Cordova mas una librería para UI y un framework de aplicaciones con AngularJS. Lo mas importante aquí es la inclusión de AngularJS, que es la forma como siempre debió ser Javascript.

Para ahorrarles algo de trabajo. El mayor problema que tuve con Javascript es que casi todo es asíncrono. Es decir, ejecutas una línea y la respuesta viene en algún momento que no sabes de antemano. Fatal para aprender y para hacer Debug. Además, su organización hace que el manejo de MVC sea muy natural. Finalmente, entiendo la importancia de que una vista no tenga acceso a la capa de datos, y que el controlador sea quien gestione la vista. El resultado es una aplicación donde las vistas con entidades independientes que podrían ser reutilizadas sin problema. Incluso pude dejar de lado la mala costumbre de preocuparme de un estado global de la aplicación gracias a una maravilla llamada UI-Router que viene integrada en Ionic Framework, para resumir, cada vista es un estado y listo.

Con esto solucioné el problema de cuál herramienta de desarrollo utilizar. Si bien tuve que aprender javascript, es un sol esfuerzo para abarcar muchas plataformas así que valía la pena.

– Datos: 

Sobre los datos en una aplicación hay tres cosas que pensar, que los tienes que organizar y tener en algún tipo de base de datos, que los tienes que pasar del servidor al móvil y finalmente que ese paso ocasiona tráfico.Resumiendo, Database, Syncing y Traffic.

En el mundo de móviles no hay muchas opciones y menos aún si queremos un app para varias plataformas. Muchos dirán que SQLite está presente en todas partes, yo me encontraba en ese grupo, pero la noticia mas grave que enfrenté es que no es cierto. SQLite no existe en todas partes y en los lugares donde existe se considera un estándar viejo que puede desaparecer en cualquier momento. Golpe durisimo porque ya empezaba con un problema mas. En fin, la alternativa que ofrece el mundo estándar es IndexedDB que para matar los ánimos, aún no es soportada completamente por todos. Que lío. En fin, como siempre el camino correcto parece siempre estar a la mitad y así fue que encontré PouchDB.

PouchDB es una versión cliente Javascript de CouchDB, que es un tipo de base de datos NOSQL que tiene algo muy bonito: sincronización automática de datos. Lo segundo: sincronización automática suena muy bien, pero lo primero requiere algunas líneas.

NOSQL es un movimiento nuevo de bases de datos que no están basados  en tablas y columnas como los tenemos en Oracle, Sql server y demás. La idea detrás es que sea más fácil de usar en aplicaciones de Internet, por dos motivos: el protocolo es http así que no hay drivers que cargar, y que no exista un modelo de datos, y con eso se eliminan los famosos joins. No es que crea que esta alternativa sea mejor que una base relacional, pero tener sincronización automática me convenció así que pagaré el precio usando NOSQL. El problema principal con estas DB es que hay casos en los que si tengo que usar “joins” para lo cual tengo que usar una pesadilla llamada MAP/REDUCE. En fin, lo bueno es que toda esa complicación me permitió aprender a golpes que hay que ser muy específico cuando se trata de hacer consultas a la base de datos.

– Sincronización y tráfico:

Como ya les dije, Pouch/CouchDB se encarga de sincronizar y lo más importante es que no consume mucho ya que es posible cargar una base de datos inicial, por lo que sólo se transmiten los cambios. Ahora, consideremos que la app que estoy haciendo es principalmente un catálogo con fotos, así que cada nuevo “registro” o mejor dicho “Documento” va a tener un tamaño algo grande, esto es 20K promedio lo que es bastante grande, pero que suena manejable considerando que por semana se crearán un máximo de 5 documentos con 2 documentos en la mayoría de los casos, así que podemos decir problema resuelto: la información estará al día y con un costo de transmisión de datos bastante aceptable. Sobre el consumo de datos, normalmente siempre es un problema la velocidad de datos ya sea por la calidad de la señal o por su costo, lo que hace necesario pensar siempre en transmitir lo mínimo indispensable a fin de que al usuario no le importe mucho usar tu app y por lo tanto, hace necesario que la app pueda funcionar sin datos por completo. Mágicamente, esto se soluciona con PouchDB ya que la base de datos está grabada en el móvil así que si se corta la señal, podremos seguir usando el app sin problemas.

– Hosting:

Una cosa interesante sobre CouchDB es que hay opciones de hosting gratuitos. En mi caso, IrisCouch tiene un servicio que es gratis hasta 5 dólares. Lo interesante es que la sincronización hace que sólo se pague por las diferencias entre la base de datos actual y la que se encuentra en el móvil, así que el tráfico debe ser bastante grande para empezar a pagar.  Un beneficio oculto es que se pueden hacer aplicaciones dentro de CouchDB, lo que simplifica la gestión del hosting. Ya les diré cuando aprenda a usar esa función.

Con esto se cierra lo que es la parte mecánica de la aplicación, es decir todo lo que un programador puede hacer. Ahora faltaba considerar lo que un empresario necesita. En primer lugar: Medir, necesitas saber cuanto se usa tu app y quienes la usan. Identificar: necesitas ahora mas datos de las personas que usan tu app y finalmente (al menos por ahora) Monetizar: generar algún ingreso.

Para la medición opté por Google Analytics porque es bastante completo pero con un giro. Para el cliente escogí una librería que simplifica el registro de los eventos y del uso y que suspuestamente trabaja con todas las plataformas. Cordova tiene un plugin para GA pero no soporta Windows Phone, Angulartics, la librería que escogí, parece que si por lo que estoy cubierto.

Para la identificación hay un gran detalle: los usuarios deben querer ser identificados. No voy a poner un formulario larguisimo para que mis usuarios se registren, lo mas simple es usar alguna red social y Facebook viene al rescate. Mejor aún, es si usamos un plugin para permitir compartir contenido dentro de la app que recibe información de las redes sociales a las que te conectas. En el futuro, si pienso dar la opción a que te registres, pero también seguiré el modelo de vincular la cuenta a alguna red social para simplificar el asunto, para que los usuarios tengan la opción de entrar fácilmente con sus cuentas de Twitter, Facebook o Google Plus y también para dar tiempo a que Windows Phone permita hacer lo mismo que en Android y iOS.

La monetización es algo que no se puede poner tan al principio para evitar que sea un impedimento para el crecimiento de los usuarios. Tampoco voy a dejar el app totalmente libre porque como ya dije, soy muy lento y alguien puede ganarme la jugada. Por el momento, la alternativa son banners y Admob resultó fácil de integrar. Hay un concepto llamado mediación que permite utilizar la misma cuenta Admob para recibir avisos de proveedores de banners que pagan algo mas, así que estaré en esa búsqueda. En el futuro, las opciones de monetización se abren como por ejemplo, cuentas personales que permitan hacer backup de tus datos, opciones para facilitar el intercambio de items entre usuarios en el mundo real y demás.

Adicional a todo esto hay un punto que ya queda casi al límite del tema técnico: Comunicación de los usuarios con el App. Toda app debe tener una identidad “social”. En otras palabras, hay que separar cuentas de Twitter, Facebook, Instagram, Pinterest, Correo, Web y otras por ahi. El app debe poder recibir mensajes y responderlos. Para eso hay que planificar con mucho tiempo porque los Ids en cada red social no esperan. Si se les ocurre un nombre cool, créenlo en la red social de su preferencia y así no lo perderán. Y lo más importante: asignen un Community Manager.

Hay un objetivo que siempre estuvo considerado pero que no lo escribí: que sirva de ejemplo para desarrollar otro tipo de aplicaciones móviles. Quizá esto es lo más importante, porque hasta ahora mi mayor éxito es un diagrama mal dibujado en algún papel, quizá ahora podríamos cambiar eso por una app que se pueda mostrar.

Resumiendo, su próxima aplicación móvil debe considerar las siguientes fases:

– Desarrollo: Plataforma, Datos y Hosting

– Explotación: Medir, Identificar y Monetizar

– Difusión: Planificar

Hay un punto que no he considerado que es la fidelización y ha sido a propósito. Personalmente, creo que la base de la fidelización de usuarios es brindar un buen servicio y satisfacer una necesidad. Veamos a Twitter que es básicamente el mismo servicio desde sus inicios, quiza sean los hash tags y las tendencias lo mas importante en innovación pero nada que altere la paz de los usuarios en escribir y leer tweets. Para el caso de esta aplicación, la estrategia de fidelización está en las nuevas funciones y en las futuras opciones de intercambio de items persona a persona.

¿Sobre que trata la aplicación? Ya lo verán en un par de semanas.


No es un grito de hincha. No es que crea que Blackberry es lo mejor que hay en el mundo móvil. Simplemente es un grito para que un buen producto no muera por la mala gestión de sus managers.

Cuando RIM entró al mercado, nadie le prestó atención, porque era una evolución del hoy difunto sistema de Pagers o Beepers. No parecia amenaza para nadie, hasta que le agregaron los servicios de voz, es decir, evolucionaron para formar un nicho que habia sido visto por otros pero sus soluciones iban mas por darnos una laptop como teléfono, mientras que RIM nos daba un teléfono con correo electrónico. La idea fue ganadora, empezar por abajo significaba bajo costo en el teléfono mediante una gran inversión en infraestructura que ha funcionado como su gran fuerza y también como el punto de críticas: La red Blackberry con su famoso NOC en Canadá que motivó a Francia a vetar el sistema, y que los hindúes quieren monitorear, y los Arabes interceptar.

La evolución fue la clave para que RIM se vuelva en el rey del correo corporativo, y tal fue la fuerza conseguida que el día de hoy no existe en el mercado una alternativa decente a pesar de todas las fallas en el producto Blackberry, una verdadera verguenza para las compañías que se dicen competidoras. Veamos, Microsoft fracasó con Windows Mobile porque el equipo no era fácil de usar ni tenía una batería de duración decente. Lo mismo con Android y Apple no tiene equipo con teclado. Decepción.

Entonces, porque RIM esta cayendo? Veamos si esto es cierto. Primero, no se ve un competidor serio en correo corporativo es verdad, pero el ímpetu de RIM ha sido la de posicionar a Blackberry también como  un equipo para el mercado masivo con relativo éxito. Aún recuerdo el comercial de Tigo Colombia donde un chingado se quejaba que el Operador habia bajado tanto los precios del Blackberry que ya no era “cool” tener uno, deberían darle un premio pues fue eso precisamente lo que pasó, ahora Android o iPhone son “cool”, porque ahora Blackberry lo tiene mucha gente. Cómo Blackberry puede seguir siendo cool? pues obviamente por los teléfonos y eso también tenemos que analizar. Segundo, gracias a la masificación del Exchange, puedo ser cool y aún tener acceso a mi correo. Y tercero otro factor importante es la batería, con Blackberry uno podía ser cool y usar su teléfono por días hasta que salió el Bold 9000 donde la batería empezó a portarse como cualquier otro teléfono, desde ese modelo, todos los demás sufren por lo mismo, pregunten a los que tienen BB 9700 y el 9800 y comprobarán que la batería no dura mas de 1 día. Entonces, si voy a tener mi correo por un día, entonces me da igual elegir un Android o un iPhone que duran lo mismo y aún tengo mi correo. Y listo, fin del reinado de RIM en el corporativo, porque muchos gerentes tienen el celular mas como trofeo que como herramienta de trabajo y ya no queda ningún trofeo apetecible en la cartera de equipos de RIM.

En mercado masivo, el problema es parecido porque mi batería dura igual que los otros smartphones y las funciones son parecidas además que tengo que soportar una interface de usuario orientada al correo por mas que correo no sea mi mayor interés. No hay comparación entre las pantallas de Blackberry contra las de Android ni las de iPhone.

Cuál es el salvavidas de Blackberry? hay 3 cosas principales:

  • La plataforma: El BES y el BIS es lo mejor que hay en el mundo móvil para gestión de móviles y mensajería. Es tan fuerte y bueno que permite una gestión total de los terminales y permite que el usuario se centre en empezar a usar sus servicios sin complicaciones.
  • Los planes de datos: Gracias a la plataforma, los operadores pueden ofrecer planes “casi” ilimitados a precios bajos pues el ahorro de ancho de banda es impresionante. Es el único smartphone en el mercado con plan de datos con nombre propio y además es el más económico. Otro punto para el corporativo es que nadie tiene un plan de roaming de datos tan bueno como Blackberry.
  • BBM: inventaron la mensajería instantánea móvil y crearon el factor de adhesión a la plataforma mas fuerte del mundo móvil. He visto como hay gente que utiliza Blackberry configurados solamente para acceder al BBM, sin cuentas de correo. Es el servicio que sostiene a la gran mayoría de sus usuarios actuales
Ningún competidor tiene estos 3 aspectos pero ya salieron competidores al BBM y los operadores están bajando los precios de los planes de datos. Es en la plataforma donde aún RIM puede respirar tranquilo.
Ahora, cuales son los puntos que jalan a RIM al desastre?. También hay 3:
  • Terminales!!: Esto si que es vergonzoso. Ya en otro post hablé sobre este punto. Los teléfonos de RIM no son buenos y su evolución no ha sido uniforme entre el hardware y el software. Mientras tenemos equipos Android a muy bajo precio y con procesadores monstruosos, RIM sigue sacando equipos mas grandes que no son mas rápidos y con baterías que cada vez duran menos. Bold 9900 con su OS 7 es el Bold 9700 con pantalla táctil, tan novedoso como el Treo Pro que salió hace un par de años y que ya no existe mas. Hace tiempo que vengo gritando para que negocien una alianza con Nokia pero nadie me escucha. Hace falta buenos terminales a precios y con un buen rendimiento, y si les digo que Nokia los tiene por menos de $400 entonces se convencerán.
  • Sistema Operativo: Blackberry OS es un tema perdido a tal punto que RIM ha tenido que pedir a QNX un sistema operativo para operar su tablet pues su propio OS simplemente no da la talla. Y si a eso le agregamos que era amigable con el correo y con nada mas, aún en su versión 6, entonces no queda por donde darle mas oxígeno. ¿Cuál es la salida? pues otra vez veamos a Nokia y encontraremos que han decidido dar de baja a Symbian que corría en equipos muy bajos y muy altos con casi la misma interfase y que permitía servicios de todo tipo con buen rendimiento aún en equipos con procesador y memoria limitados. Y como oferta, se podían llevar una legión de programadores fieles y de gran experiencia que trabajaban proyectos de gran calidad a pesar de que las herramientas eran C++ principalmente, y con compatibilidad con Java lo que permitía una migración natural para los que ya programaban en Blackberry OS.
  • Finalmente, diferenciación: Es decir, si soy de empresa debo tener un teléfono que lo grite y muy fuerte. Ese ha sido el sello de Blackberry por años. Y si soy un usuario común debo tener un teléfono orientado mas al entretenimiento. Es decir, al corporativo dale teclado, buena batería y pantalla grande y al masivo dale touch screen, multimedia y juegos y no al revés (o sea Storm no mas para corporativo). El factor que hará la diferencia además de las funciones será el precio y para eso debo poder comprar equipos solos a precios razonables para renovación pues si por alguna razón alguien sufría la calamidad de perder su BB tenía que pagar un dineral por un equipo mediocre. Si estoy en el corporativo, debo poder subir a equipos mejores fácilmente y si estoy en el masivo debo poder recibir financiamiento. Si todos van por la calle con un Torch, los gerentes se comprarán iPhone. Pero si todos los gerentes usan Torch, yo aspiraré a comprarme un Torch también, mientras tanto tendré un Bold o un Storm. Esa es la idea.

Mi mayor interés aquí es rescatar las buenas ideas y Blackberry lo es. Es un equipo de datos con el costo mas razonable del mercado pero con un costo muy alto por el equipo. Es la mejor implementación de mensajería instantánea móvil pero con una batería que es para llorar, al menos en los modelos altos. Es un equipo fácil de usar aunque en la versión 6 nos complican la existencia. El correo electrónico funciona muy bien aunque como teléfono para llamadas deje mucho que desear. Y así podemos seguir y notaremos que las cosas malas son propias de la tecnología y que las cosas buenas son las que hacen grande a un producto. Si algo hemos aprendido de Apple es que es mejor hacer unas pocas cosas bien que hacer muchas mal, aunque en el caso de Apple, lo que hace bien sea el Marketing.

Blackberry tiene muchos puntos a favor desde el punto de vista de la  ingeniería y ya hace rato que un producto de buena ingeniería no era líder en algo. Ahora falta todo lo demás pero por sobre todo sentido común para pedir ayuda (NOKIAAAA).

 

 

 





%d bloggers like this: