Archive for the ‘programación’ Category


Cuando quise comprar un TV Led para mi casa, busqué uno que tuviera las funciones mas avanzadas y el tamaño correcto para que me durara una buena cantidad de años, y cuando lo encontré estuve dispuesto a pagar por encima del promedio. En fin, el costo de luchar contra la obsolescencia. A la semana siguiente encontré el mismo TV a mitad de precio. Comparar y verificar que eran las mismas características era simplemente echar sal en la herida.

En tecnología, esto va a suceder siempre, porque su evolución no termina por lo que el costo de obsolescencia exclusivamente puede justificar el paso a la nube.

En fin, ya revisamos la excusa 1 que en el mejor de los casos hará que se reordene el acceso a la información en una empresa. En el segundo caso, imagina que llegas a una empresa que tiene años de operación y encuentras programas que nadie sabe como pero funcionan.

Razón 2: Las cajas negras

Es por todos conocidos que empresas, incluso grandes, tengan por ahí algún pedazo de código que esté hecho en alguna herramientra prehistórica. Para los millenials, será VB 3.0 pero los hardcores sólo nos afecta si hablamos de Cobol o RPG. Simplemente ese código se mantiene en un refrigerador porque si. Este escenario es en extremo común y presenta la oportunidad mas importante.

Primero, la justificación para esto es principalmente por:

  • Es lo único que funciona en el ropero IBM que tiene mil años en la compañía. Reemplaza ropero con hardware que solo funciona con Windows 95 o XP
  • Es un código tan complicado que hacerlo de nuevo no se puede

En el primer caso, hay que hacer una aclaración: Si tu software es para gestionar una máquina local entonces se queda local. Eso pasa con la gestión de impresoras de alta velocidad, o controladores o equipo especializado que tiene que ser alimentado por información de la empresa. Eso no se toca. Todo lo demás si.

Si lo que sucede es que tenemos un programa de planillas que está sólo corre en una computadora Burroughs ( alguién usa eso aún??) , la oportunidad de cambiarlo y subirlo a la nube es gigante, porque o se está pagando un altísimo plan de soporte a alguien o simplemente está ligado a una persona que aún sabe como usarlo. En ambos casos, el costo es inmenso y justifica actualizarlo.

El segundo caso es similar, o lo cambias ahora, o lo pierdes. En algunos casos lo consideran un “Hardware”, es decir, automatizan la entrada y la salida de datos solamente y lo dejan como una caja negra. Es una solución temporal. La sugerencia es ir replicando pedazo por pedazo a una plataforma nueva.

Lo principal es gestionar estas cajas negras, identificando las fuentes de datos y la información que entregan, ya que con los años, se convierten en conocimiento obsoleto que incluso no concuerda con los manuales, ya que es un hecho de que cambiaron en el camino o simplemente se perdió el manual.

Entendamos que el reemplazo o renovación de estos sistemas no es porque sean malos, el tema es que el mantenimiento se hace cada vez mas caro y es posible que en algún momento simplemente dejen de funcionar, o que no consigamos un técnico que aún conozca esas herramientas.

La atención se debe poner a estas tres capas:

  • Datos: Si vas a elegir una base de datos, que sea una que te asegure soporte por largo tiempo
  • Reglas de negocio: En la medida de lo posible separadas de los datos.
  • Front end: todas las formas en la que presentas tu información para manipularla.

Algo viejo pero aún sirve para organizar el software que tenemos, y así podemos enfocar la migración en las capas de Front end y reglas de negocio.

Advertisements

Apple acaba de aprobar SuperComics v2 para iOS con lo que completo las 3 principales plataformas de aplicaciones: Android, Windows Phone y iOS. Finalmente, cumplo con entregar una aplicación que soporta casi todo lo que hay en el mercado con una sola base de código. Aún en el horno está la versión para Windows 10 y para cuando le encuentre la forma de monetizarlo, la versión Web.

La motivación tras este proyecto fue la de realmente darle una mano al mundo Cómic en Perú, pues esta muy bien que salgan muchos a publicar pero para el coleccionista, hace falta un orden y saber, al menos, que hay para comprar en el kiosco, así que inicialmente este proyecto es un catálogo. Y el segundo punto es que sea realmente una aplicación móvil que demuestre que se puede hacer una aplicación multiplataforma con soporte de datos fuera de línea y todo que funcione con recursos limitados. Hasta ahora, me parece que se han cumplido los objetivos y aún hay espacio para mejoras.

Sobre el tema del catálogo de cómics: no soy un experto en el mundo editorial pero por lo poco que me han ido contando las personas que si están en este mundo, me doy cuenta que el detalle común es el super esfuerzo que hacen muchos para publicar, incluso yo diría que lo hacen por un tema de orgullo personal, o simplemente decir que sacaron un cómic por lo tanto, no se les puede pedir planificación ni estrategias de distribución ni de marketing. La suerte es que hay mucho compromiso de gente fanática que ayuda, como por ejemplo las tiendas de cómic que cada día son mas donde los dueños son los más fanáticos. A todos los que piensan publicar o ya están publicando cómics localmente yo les recomendaría estos tips:

  • Creen un roadmap: Si van a sacar un cómic, piensen primero en cuantos cómics serán y el formato, 5 grapas o 2 libros o lo que quieran, y establezcan un cronograma de publicación, cada 15 días, cada mes. Con esto podrán distribuir mejor sus recursos, y por sobre todo, podrán dedicar a alguien a la difusión. ¿Que tan grande debe ser el roadmap? dependerá de cuantas publicaciones quieran hacer: el cielo es el límite.
  • Comuniquen: la idea de comunicar es la de dar a conocer el roadmap al público objetivo con una doble intención: que la gente se entere y lo más importante: MEDIR. Crear un grupo de Facebook no es suficiente, tienen que estar al tanto de que artículo, foto o video ha sido el más visto, o cuantos Likes obtienen o corazones en Twitter, o reproducciones en Vine. Medir les permite establecer el tamaño de su público objetivo y determinar el tiraje de su cómic. Mientras mas tiempo comuniquen y mas tiempo midan, podrán determinar mejor el tamaño de su mercado.
  • Presenten: Si son un cómic nuevo, lo mejor es dar avances. A SpiderMan ya lo conozco, pero si me dicen “Capitan Guachiman” no tendré idea de lo que encontraré al abrir la revista. Otro punto importante es la impresión, no soy fan de las que se imprimen en papel couché y tienen el acabado de fotocopia, prefiero el acabado simple y opaco tipo Frank Miller, lo mismo aplica con los colores, si no coloreas bien, o no tienes plata para la imprenta, ve por los dibujos a tinta china y listo.

Como Bonus: las portadas. SuperComics se basa en mostrar portadas porque es la forma más fácil de identificar un cómic, pero lo principal es saber que números hay, desde cuando están disponibles y todo lo demás. En algunos casos, hay celos de parte de la editora en sacar las portadas antes de la publicación y es comprensible, pero la realidad es que si hablamos de Cómics gringos, todas las portadas ya están en Internet desde hace tiempo. Es mejor salir a comunicar con las portadas, como lo hacía Comics21 al principio, pues te hace más fácil la búsqueda de números atrasados y eso lo digo por experiencia: casí le he hecho aprender Inglés a la señora del Kiosco en Jesús maría donde compro mis revistas, pues yo le pedía “SpiderMan: One More Day issue 2” y la señora se echaba a bucear en la pila de revistas que tenía y pues fácil no resultaba. Ahora sólo le enseño la imagen y ya me saca la revista. Si la editora no quiere soltar las portadas, no hay problema mientras suelte el cronograma, aunque lo ideal es que suelte los dos.

Sobre la aplicación: Cuando decides coleccionar cómics es como cuando juntabas las figuritas de tu álbum, necesitas saber cuantos son, cuáles y cuando los puedes encontrar en tienda. Yo comencé con los cómics de Perú 21 y la cosa no era muy fácil pues no había gente que supiera vender cómics así que encontrabas cómics doblados o escondidos cuando ibas al kiosco, o peor aún, una semana había, y la otra no, fatal. Ahora el tema es más ordenado y si vas a una de las tiendas especializadas, hasta sales con tu bolsicartón gratis. A pesar de la mejora, sigue siendo necesario saber, cuantos, cuáles y donde encontrar tus cómics.

SuperComics utiliza el concepto “Off-line first” para mantener la base de datos de cómics en tu celular para que puedas consultarlos hasta en un sótano o en algún centro comercial caleta bien al fondo. Incluye tanto la portada como la información general del cómic. Además, podrás marcar dos cosas:

  • Nola/Yala: tal cual era con las figuritas, marcas si ya tienes el cómic y te evites de comprar repetidos.
  • Lista de compras: cuando te das el tiempo de revisar tu colección, sueles encontrar que por alguna razón te falta alguno y lo marcas en un papelito que luego se pierde. Ya no más, si lo marcas en compras, tendrás una sección donde verás los cómics que te faltan comprar y de esa manera vayas confiado a la tienda o a la reunión de coleccionistas para hacer tu intercambio.

Finalmente, si tienes la portada te gustaría pasársela a alguien, ya sea porque la quieres cambiar, o simplemente para sacarle cachita a alguien, así que también es posible que compartas la carátula por redes sociales. No todas las redes sociales dejan compartir la misma información, por ahora Twitter es mi favorito, pero si te gusta, también puedes compartir por Whatsapp.

Además de eso, existe un catálogo de Editores, los que pude conseguir hasta ahora, donde encontrarás las colecciones publicadas todas ordenadas para que las busques como te de la gana.

Y como no podía ser de otra manera, incluí una sección con las tiendas especializadas en cómics y su mapa para que las encuentres fácil.

Ahora bien, todo esto no valdría mucho si es que no estuviera al día. Actualmente la base de cómics en la aplicación es de aprox 400 cómics publicados desde el 2012 en adelante, lo que quiere decir que hay como 300 cómics más publicados desde el 2008 que aún no están registrados, aunque es sólo cuestión de tiempo. Para los que ya instalaron el App habrán notado que la primera vez se toma algo de tiempo y esto es porque se indexan todos los cómics localmente. Esto es para que no tengan que estar descargando la info desde Internet a cada rato, así que se les agradece la paciencia.

Sobre las actualizaciones estás son automáticas y van de dos tipos:

  • Mayores: Si son actualizaciones muy grandes (Principalmente cuando suba cómics antiguos) sacaré otra versión del app con toda la información incluida. Es por esto que el instalador debe rondar los 20 mb.
  • Menores: Lo nuevo es pequeño y se va actualizando automáticamente en tu app cada vez que tengas Internet. Para que se hagan una idea, cada cómic nuevo pesa unos 24Kb y en promedio salen 2 por semana, así que casi no te cuesta, y si gorreas Wifi de StarBucks te sale gratis.

Lo que se viene: 

  • Creo que la dinámica que mueve todo el tema de cómics es el intercambio, por lo que estoy trabajando en algún método para que puedas publicar tus repetidos y que los que estén a tu alrededor vean y puedan interactuar.
  • Ahora ya puedes marcar los cómics que ya tienes, pero deberías poder respaldar esos datos. O deberías poder reinstalar el app y recuperar todos tus cómics marcados. Técnicamente, esto ya es posible en la versión actual, pero supone un costo que no puedo asumir, así que sigo trabajando para bajar ese costo,o ver la forma que la gente interesada pague por el servicio.

La decepción:

  • Los códigos de barras: Hubiera sido genial que estos códigos nos ayuden a identificar los cómics, pero la verdad es que no sirven por una simple razón: no son únicos. En el caso de Comics de Perú21, suelen poner un mismo código a toda una colección, o sea hay 4 o 6 cómics con el mismo código, incluso han llegado a poner el mismo código de barras en más de una colección, plop. En Editora Vuk, han sido más cuidadosos y si tienen código único, en realidad tienen dos: un código para la colección y otro más para identificar el cómic. Otros simplemente no lo usan y ya. En conclusión, el código de barras es inútil para identificar el cómic, así que olvídate. Sería genial que todas las editoras usaran un sólo formato de código de barras, mientras tanto no será posible aprovecharlos en el app.

Download_on_the_App_Store_Badge_ES_135x40Spanish_wstore_black_258x67en_generic_rgb_wo_60


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 punto debemos hacer un repaso y considerar algunos puntos que potencian la aplicación.

Primero, comencemos que el código está disponible en Github en la siguiente dirección:

https://github.com/victorpease/SuperDatos

Para que comiencen a verlo funcionar tendrán que seguir los siguientes pasos:

  1. Crear una cuenta en cloudant.com
  2. Crear una base de datos
  3. Crear una key en la sección de permisos y asignarle permisos de Writer
  4. Actualizar la key y la clave en el archivo services.js
  5. Actualizar la url de la base de datos también en el archivo services.js

Con esto ya podrán tener funcionando su propio lector de noticias y verán que cada vez que alguien lea una noticia, un registro será añadido a la base de datos y podrán con esa información hacer reportes sobre las noticias más populares, que luego podremos incluir como un reporte en la misma aplicación.

Bien, ahora los consejos:

  • Hasta ahora hemos considerado la sincronización Live, pero tengan en cuenta que genera mucho tráfico y por lo tanto costo, además, el cobro es por transacción, así que no importa mucho el tamaño de la base de datos. En Cloudant dan gratis un saldo de 50 dólares y eso es bastante, pero para una aplicación seria esto se puede salir de control. Lo mejor es que desactiven Live y que sincronicen manualmente cuando ocurra algún evento relevante. Para que vean la diferencia, Live sincroniza datos cada 25 segundos, así que si cambian que la sincronización sea cada 1 minuto, ya estarían bajando su consumo a la casi la mitad.
  • Si pensamos en este modelo “Offline-First”, es porque no confiamos en el acceso a la red. El mayor impacto es la primera sincronización. Piensen en usar el plugin PouchDB Load, que permite que hagan una carga inicial de datos en el móvil en lugar de esperar los datos de la red. El archivo de texto tiene que ser generado mediante el plugin PouchDB Dump Cli.
  • En el código actual tenemos un estado llamado Splash donde podemos ejecutar todas estas acciones  de inicialización antes de entrar a la aplicación.

Sobre las vistas:

  • Las vistas en PouchDB se generan la primera vez que las llamamos, por lo que pueden demorar un poco mas de lo normal durante la primera vez. Ante esto, pueden controlar que la vista se genere de forma automática sobre todo durante la primera sincronización mediante la siguiente línea:
    pouch.query('vista', {stale: 'update_after'});
  • Hasta ahora hemos usado Map/Reduce para las vistas, y puede ser algo confuso. La alternativa principal es utilizar los índices, no hay nada mas simple y más rápido. La otra alternativa es utilizar PouchDB Find, que es el futuro de las consultas en todo el ecosistema CouchDB. Personalmente, aun me gusta más Map/Reduce aunque sea algo confuso y limitado por una razón: PouchDB Find es simplemente una máscara que hace las cosas fáciles, pero no agrega velocidad.
  • Finalmente, ya les había mencionado que las vistas ocupan sitio

Sobre los límites de espacio:

  • PouchDB maneja varios métodos para almacenar los datos siendo WebSQL (sqlite) y IndexedDB los disponibles en los móviles, estando igualmente sujetos a las restricciones de espacio del navegador del sistema. Aquí pueden revisar los límites . Un truco es utilizar el plugin SQLite que les permite saltarse esos límites y tener un almacenamiento casi ilimitado. Entiendan que esto es solamente recomendado cuando su base de datos esté por los 50 mb. Además, aún tiene algunos problemitas con las vistas.
  • IndexedDB es la alternativa estándar, de hecho WebSQL ya no es mantenido. De hecho es bastante rápido, además que IndexedDB no es tan fácil de abrir que WebSQL, así que les da un nivel de seguridad a los datos.

Sobre la seguridad:

  • Los que utilizan SQLite en Android están acostumbrados a ponerle una clave a la base de datos para protegerla. Esto no está disponible en el modelo Híbrido pero pueden usar el plugin Crypto Pouch que nos permite encriptar los datos.
  • CouchDB tiene usuarios y Cloudant también puede manejar algo parecido. Pueden acceder a esta lista de usuarios mediante el plugin PouchDB Authentication. Recuerden que esto significa que la red es necesaria. Personalmente, prefiero hacer un servicio externo que autentique y que distribuya las key y pass de Cloudant.

Modelamiento:

  • Modelar en NoSQL no es igual que en Relacional, pero hay trucos. Personalmente, prefiero el modelo minimalista de NoSQL pero existe un plugin Relational Pouch que permite utilizar la misma estructura relacional

Finalmente, como verán en algún comentario que hice, pueden tener toda su lógica en la aplicación cliente, siempre y cuando se quieran pegar al modelo “Offline-First”, pero si es que necesitan operaciones en línea, necesitaran de una capa más de lógica.

NOTA Principal: El problema principal con CouchDB y obviamente con Cloudant, es como integrarlo con nuestras bases en SQL. Es un trabajo en progreso en realidad, y no hay una herramienta porque en CouchDB el modelamiento está en función de la velocidad y de la aplicación que va a utilizar los datos, y eso puede ser muy diferente a lo que tenemos en SQL donde se programa para tener consistencia. Hay tantos modelos y tantas transformaciones posibles que una herramienta no puede hasta ahora hacer el trabajo. Felizmente, no es tan complicado porque ya en la base de datos hemos usando db.changes() , Así que el flujo sería:

  • Lenguaje recomendado: Python ( tiene una gran librería de acceso a Cloudant). También pueden usar NodeJS
  • Un script accede a la base de datos SQL y crea los documentos en Cloudant uno por uno según el modelo que hayamos fijado. Podemos cruzar dos tablas para crear documentos Maestro-Detalle siempre que no sean muchos detalles. No es tan rápido y lo ideal es usar BulkDocs para insertar por lotes.
  • Luego se invoca el comando db.changes()  donde cada documento cambiado es actualizado a la base de datos. Otra vez, puede ser como un insert, delete o update, todo depende de como sea su modelo.

Hasta ahora, todo esto es a medida por lo que mejor es que practiquen sus habilidades en NodeJS o Python.


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.


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.

Con la parte 10, podríamos decir que comienza una nueva etapa del código así que debemos empezar con las cosas interesantes. Primero planifiquemos las siguientes funciones:

  • Alertas ante nuevas noticias. Esto ya me lo habían pedido, y recién ahora tiene sentido incluirlo pues ya tenemos todas las herramientas. Según lo que hemos visto, al activar la replicación estamos controlando el evento ‘paused’ para indicar que la sincronización terminó y no hay nada nuevo por el momento, pero no nos dice nada si hubieron noticias nuevas. Para eso debemos controlar otro evento llamado ‘change’ que se genera por cada cambio en la base de datos local, aquí simplemente ubicamos cuando se trate de una nueva noticia y podríamos ya lanzar la notificación, teniendo cuidado pues durante la primera ejecución del programa todas serán noticias nuevas y no queremos una avalancha de alertas.
  • Agregar un mecanismo para registrar las noticias más leídas. Esto supondrá que grabemos en algún lado que hemos leído la noticia. Recordemos que este documento será sincronizado hacia el servidor y a todos los usuarios con la versión actual, lo cual no queremos así que veremos como hacer para que la sincronización sea controlada: que los registros generados por el usuario sean sincronizados con el servidor pero que a los demás usuarios solamente lleguen los documentos con las estadísticas. Igual funcionaría para un mecanismo para registrar “Likes” tipo Facebook.

Comencemos con las alertas para nuevas noticias, para eso necesitamos un plugin para Ionic que nos ayude a manejar las notificaciones locales. Esto se refiere a las mismas alertas que reciben cuando tienen un correo o algún otro cambio relevante. Hay que recalcar que son locales, puesto que los datos ya están en la base de datos; en las apps normalmente se da todo lo contrario, primero llega la notificación y luego se descarga la información, pero como nuestro modelo es “off-line first” no podemos confiarnos en la red.

Para hacer la cosa aún más fácil, instalemos el componente Ng-Cordova que encapsula el código necesario para manejar plugins, mediante el siguiente comando:


bower install ng-Cordova --save

El parámetro –save es para agregar este componente a la lista de dependencias.

Seguidamente, instalemos el plugin:

ionic plugin add https://github.com/katzer/cordova-plugin-local-notifications.git

Y listo, ahora hay que ver donde es el mejor lugar para detectar que se ha insertado una nueva noticia y ese lugar puede ser al detectar un cambio. Recordemos que al momento de replicar ya se genera un evento al detectar el fin de la replicación. Veamos el archivo services.js


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

Es bueno recordar aquí como es esto de los eventos de replicación:

  • Paused: indica solamente que la replicación ha parado. Como hemos establecido el parámetro live en true, significa que ya no hay mas cambios que sincronizar. Esto también significa que la replicación no ha recibido mas registros en un rato largo, lo que sucede a cada rato cuando la velocidad del internet es bastante lenta.
  • Complete: Si es que establecemos live en false, significa que la replicación se hará una sola vez y al terminar se generará el evento Complete.

Noten que si queremos asegurar que la base local está al día, quizá haya que hacer un mecanismo de control como establecer un registro con la fecha de ultima actualización. Además, la replicación manual puede servir si sabemos que los cambios son poco frecuentes o si queremos que sea el usuario final quien controle la actualización.

Bien, entonces regresando al código, cuando la replicación se completa o se para, se ejecuta la función “changed” que está definida así:


function changed(){
console.log('cambios en la base de datos');
if (!initiated)
{
initiated=true;
db.changes({live: true, since: 'now', include_docs: true})
.on('change', newNews);
};

};

Aquí lo que hacemos es verificar que la base de datos está inicializada y usamos una variable “initiated” para controlar que solamente una vez llamemos  a la función db.changes. Con db.changes lo que hacemos es verificar un evento mas: cuando se ha modificado algún registro en la base de datos. Cuando se detecte este cambio se ejecuta la función “newNews”.

NOTA: fijense bien la secuencia, primero se replica y luego se monitorea el evento cambios. Por lo tanto, la secuencia de datos será así: inicias la aplicación, sincronizas, cuando se completa, cada registro nuevo o modificado generará una alerta. De esta manera, las noticias antiguas no generarán alertas.

Ahora veamos la funcion newNews:


function newNews(change){
if (!change.deleted){
if (change.doc.tipo=="news")
$rootScope.$broadcast('db:newNews',{newsId:change.id,
newsTitle:change.doc.titular});
}
}; function newNews(change){
if (!change.deleted){
if (change.doc.tipo=="news")
$rootScope.$broadcast('db:newNews',{newsId:change.id,
newsTitle:change.doc.titular});
}
};

Bien, notarán que esta función toma un parámetro llamado change que es el documento que ha sido afectado. El documento puede haber sido borrado, creado o modificado. Si es borrado la propiedad change.deleted será verdadera. Si el documento es creado o modificado por ahora los trataremos como iguales pues tendríamos que revisar la fecha de creación en alguna propiedad.

En la línea 10 verificamos que no se trate de un documento borrado y seguidamente verificamos que el documento sea del tipo news, porque también hay documentos del tipo cat y esos no queremos que manden notificaciones. Si todo se cumple, mandamos un broadcast para que se genere el evento de generación de la notificacion. Para ver eso vamos al archivo app.js.


.run(function($rootScope,$state,$cordovaLocalNotification){
$rootScope.$on('db:newNews',function(event,args){
var id = new Date().getMilliseconds();
$cordovaLocalNotification.schedule({
id: id,
title: 'Super Datos',
text: args.newsTitle,
data: {
newsId: args.newsId,
newsTitle: args.newstitle
}
}).then(function (result) {
console.log('Notificación registrada');
});

$rootScope.$on('$cordovaLocalNotification:click', function (
event,
notification,
state) {
var datos = JSON.parse(notification.data);
$state.go('app.detail',{id:datos.newsId});
});
});
})

Aquí notarán que hemos agregado una sección .run() para manejar dos eventos en la aplicación: el primero el evento que generamos anteriormente para avisar que hay una nueva noticia y otro mas para el evento $cordovaLocalNotification:click, este último evento es el que se genera cuando un usuario hace click encima de la notificación, es decir, que cuando llegue la notificación, el usuario vera el aviso en su teléfono y al hacer click se abrirá la aplicación y se abrirá en el estado detalle para que pueda leer el contenido. Creo que estamos de acuerdo que mas complicado ha sido encontrar y fijar el evento que lanza la notificación que programarla, pero lo hago así por una razón: trato que todos los eventos sean manejados en el app.js para no tener problema luego para mantenerlos y gestionarlos, incluso trato que los broadcast estén aquí en la medida de lo posible, o en su defecto en el archivo services.js, nunca en un controlador.

Y listo, ya pueden probar su nuevo lector de noticias, y cada vez que tengan una nueva noticia o una noticia antigua ha sido actualizada, recibirán una notificación que les permitirá enterarse e ir directamente al nuevo contenido.

Recalco que esto no es una notificación tradicional, pues el contenido ya está en el teléfono y es la misma aplicación la que genera la notificación. Las apps conectadas que son las mas comunes, mandan la notificación por la red y recién hacen la recuperación de los datos, todo depende de como quieren que su app se comporte.

Finalmente, pueden jugar con más opciones de las notificaciones locales, como agregar un botón para que el app te recuerde leer una noticia dentro de 15 minutos, o poner una notificación cuando el app esté sincronizando y demás cosas. Todo queda en ustedes.


Si eres programador y sobre todo fanático de las herramientas que se instalan y ya funcionan, te habrá pasado alguna vez que sin razón alguna, uno de esos programas deja de funcionar como debe, sin que haya ocurrido problema alguno. Muy probablemente, lo que ha pasado es que se hayan malogrado tus variables de entorno, en especial la variable PATH.

Desde tiempos de DOS, las variables de entorno fueron un tema crítico. Configurar adecuadamente tu autoexec.bat y tu config.sys era básico para todo usuario que empezaba a usar mouse o que tenia la suerte de tener módulos de memoria adicional, querias crear discos virtuales o simplemente querias jugar Accolade o Digger en colores en tu super modern monitor CGA.

Para la gran mayoria de usuarios finales, estos conceptos simplemente son innecesarios porque la evolución de Windows es manejar todo en la parte gráfica y de preferencia en forma automática. Para los programadores la cosa es diferente porque simplemente no todo lo que usamos es visual.

Para tener en cuenta:

  • Las variables de entorno no tienen un máximo de longitud por si mismas, pero si todo el bloque de entorno. Además, ya que no hay ni un autoexec.bat (windows antiguo) o init_profile (Linux) tienes que ir a esta pantalla para organizar tus variables de entorno

environment

  • En este artículo se explica de forma práctica como es eso del límite en las variables de entorno, pero lo más importante es que nos dice que ya que si estamos usando esa pantallita que graba todo en el registro, hay un límite de longitud de 2048 caracteres. ¿Podría ser mas largo? si, teóricamente podríamos llegar hasta 32000 caracteres pero por usar el registro y la pantalla esa, no hay mas.
  • Las variables de entorno pueden llenarse y nadie te va a avisar. Por lo general los nuevos valores se agregan al final, pero he visto que algunos programas agregan valores al inicio. Consecuencia: rutas borradas de la variable
  • PATH es una variable de entorno muy importante, incluso programas “visuales” la siguen usando. Ademas, hay otras como JAVA_HOME,ADT_HOME y otros que afectan increiblemente el funcionamiento de algunos programas y si has llegado hasta aquí es porque eres programador y sabes de lo que hablo.

Con todo esto en la cabeza, pues será muy lógico seguir estos consejos:

  • Guarda un backup del contenido de tus variables de entorno. Si te toca jugar con Java, Perl, Ruby, Python y esas cosas pues te toca cuidar tus variables de entorno como oro.
  • Instala tus programas mas importantes en carpetas con nombres cortos. Hasta ahora odio al cliente de SQL server porque instala sus cosas en rutas larguisimas que van directo al PATH, mal.
  • Si tienes una ruta larga que se repite varias veces, crea una variable de entorno. Por ejemplo:
    • Tiene que agregar las rutas ‘c:\ruta muy larga\sub carpeta mas larga aun\bin’ y ‘c:\ruta muy larga\sub carpeta mas larga aun\tools’.
    • Crea la variable LARGO con valor ‘c:\ruta muy larga\sub carpeta mas larga aun’
    • A la variable PATH agregarás: %LARGO%\bin y %LARGO\tools

¿Porqué pasa todo esto? porque Windows incluso en la version 10, está orientado a la interacción visual, y un programador está orientado a todo tipo de interacción. En los sistemas operativos basados en Linux como Ubuntu o Mac OSX, esto de la configuración del entorno es pan de cada día y directamente a los archivos, en este caso no hay una pantalla tan amigable como la que tiene Windows que sin querer impuso el límite de 2048 caracteres.

Así que ya sabes, si por ahí tu Android SDK no funciona o tu Perl dejó de trabajar, revisa la variable de entorno PATH que seguro algún instalador la modificó.





%d bloggers like this: