codeAR es un Agregador de Noticias
que intenta capturar los blogs de los desarrolladores Argentinos. Para saber que piensan, que están desarrollando,
que están leyendo, que están imaginando.
Si tenes un blog sobre desarrollo de sistemas o
relacionado al tema, te invitamos a participar de este Planeta,
envianos un mail con la dirección de tu blog.
Inception
Dom Cobb (DiCaprio) es el mejor del mundo en el arte de la extracción: apropiarse de los secretos del subconsciente justo en el momento en que la mente de la víctima es más vulnerable: durante el sueño. El extraño don de Cobb le ha convertido en un hombre muy codiciado en el mundo del espionaje corporativo, pero también le ha condenado a ser un fugitivo internacional costándole todo lo que alguna vez ha querido. Ahora tiene una oportunidad de redimirse y volver a tener la vida normal que anhela, pero para ello él y su equipo tendrán que realizar lo contrario a lo que realizan habitualmente: la incepción, que consiste en implantar una idea en el subconsciente en lugar de sustraerla….
Sucker Punch
Ambientada en los años 50, ‘Sucker Punch’ gira en torno a una chica que es internada por su padrastro en una institución psiquiátrica, donde tienen la intención de practicarle la lobotomía en cinco días. Mientras permanece allí, ella se imagina una realidad alternativa que la aleja de su dramática situación real. En ese mundo de fantasía, la joven comienza a preparar su fuga, para lo cual necesita robar cinco objetos que la ayudarán a escapar antes de que un hombre malvado la desvirgue
Tron Legacy
Secuela del clásico de culto de 1982, que vuelve a contar con Jeff Bridges en el papel de Kevin Flynn
The Expendables
Después de años de corrupción, asesinato de rehenes y de traición de la política exterior, Estados Unidos, con la ayuda de otras naciones, arma en secreto un equipo con su personal militar más capacitado para finalmente derrocar a un dictador que ha causado estragos en algún país de América latina, durante más de 20 años. La misión principal del equipo es terminar con la vida del dictador, pero recibiendo poca ayuda de las naciones, que tratan de mantener el secreto de la misión. Después de darse cuenta de esto, se basan en sus propias fuentes para combatir no sólo el ejército del dictador, sino también a los gobiernos que los respaldan.
Centurion
Bretaña, año 117 de nuestra era. Un grupo de soldados romanos lucha por salvar sus vidas después de que la legión se vea mermada tras un ataque de la guerrilla local. Los valientes soldados componían nada menos que la 9ª legión, la más legendaria por su dureza, pero las cosas no salieron como planeaban. Ahora tan solo quedan unos cuentos supervivientes, liderados por Quintus Dias (Michael Fassbender) y su misión es resistir hasta conseguir llegar a la frontera, antes que las hordas guerreras de la fiera y vengativa Etain (Olga Kurylenko) acabe con todos ellos.
Harry Potter and the Deathly Hallows
Primera parte de la adaptación al cine del último libro de la saga Harry Potter, “Harry Potter y las Reliquias de la Muerte”. La historia continúa desde el punto en el que terminó “El príncipe mestizo”: Una tarea casi imposible cae sobre los hombros de Harry: deberá encontrar y destruir los horrocruxes restantes para dar fin al reinado de Lord Voldemort…
The Last Airbender
Fantasía épica que adapta la serie “Avatar: The Last Airbender”. Aire, Agua, Tierra y Fuego son cuatro naciones enlazadas por el destino cuando la Nación del Fuego declara una brutal guerra a las demás Naciones. Tras un siglo de lucha, no parece haber esperanza de que algo pueda cambiar este entorno de destrucción.
The Sorcerer’s Apprentice
Balthazar Blake (Nicolas Cage) es un alto hechicero en el Manhattan de nuestros días que intenta defender la ciudad de su archi-enemigo Maxim Horvath (Alfed Molina). Balthazar no puede hacerlo solo, así que recluta -a su pesar- a Dave Stutler (Jay Baruchel), un chico normal pero que oculta un gran potencial, para que sea su protegido dándole un curso de inmersión en el arte de la antigua magia.

Sigamos explorando Inversion of Control y Dependency Injection, dentro del Proyecto Hogwarts. Anteriores posts del tema:
Introducción a IoC y DI: Un ejemplo web
Introducción a IoC y DI: Hello, world flexible
Simplemente para ver otro ejemplo sencillo de programación con interfaces, veamos de agregar un objeto presenter en un ejemplo web. La forma de llenar la página con datos (dar datos a la vista) y la forma de atender comandos del usuario (un botón de aceptar en un formulario para grabar lo ingresado), lo separamos del "code behind" y le asignamos esa responsabilidad a un objeto Presenter:
Notemos que la vista que va a consumir no es una clase concreta, sino una interfaz. Eso nos va a permitir cambiar la vista, o probar a nuestro objeto presenter sin necesidad de darle una página web para operar, sino algún otro objeto más adecuado para nuestras pruebas.
El código de este ejemplo se puede bajar de PresenterExample.zip.
Esta vez, la solución tiene dos proyectos de librería de clases, y una aplicación web:
El primer proyecto se basa en nuestro anterior ejemplo: contiene el servicio, ayudado por repositorios.
El segundo proyecto define la interfaz que tiene que cumplir todas las vistas de lista de Customers, ICustomerListView:
namespace Customers.Presenters { public interface ICustomerListView { IEnumerable<Customer> Customers { set; } } }
El Presenter consume tanto a la vista como al servicio:
public class CustomerPresenter { private ICustomerService service; private ICustomerListView view; public CustomerPresenter(ICustomerService service, ICustomerListView view) { this.service = service; this.view = view; } public void Initialize() { this.view.Customers = this.service.GetCustomers(); } }
Vean que el servicio también lo ve como una interfaz. Finalmente, el código de la página arma todos los objetos y se "conecta" con el Presenter para que éste la maneje. En nuestro ejemplo, sólo para llenar sus datos:
public partial class _Default : System.Web.UI.Page, ICustomerListView { protected void Page_Load(object sender, EventArgs e) { if (!this.IsPostBack) { CustomerPresenter presenter = new CustomerPresenter(new CustomerService(new InMemoryCustomerRepository()), this); presenter.Initialize(); } } public IEnumerable<Customer> Customers { set { this.grdCustomers.DataSource = value; this.grdCustomers.DataBind(); } } }
Este ejemplo es un paso más hacia el tema de esta serie de post: Inversion of Control y Dependency Injection, términos que tenemos que definir. Pero, como en el post anterior, hay algo para mejorar: en el código de la página decidimos qué objetos concretos conectamos en un grafo de objetos colaboradores. Lo que quisiéramos mejorar es la definición de esos objetos concretos: tratar de no tenerlo de alguna forma fijo en el código, sino tener alguna otra manera de instanciarlos y de cambiar cuáles son las clases concretas a usar.
Este material será incluido en el curso online del Southworks Professional Improvement Program en http://pip.southworks.net/ (pueden ir ahí para ir explorando el curso de TDD ya publicado).
Nos leemos!
Angel “Java” Lopez
http://www.ajlopez.com
http://twitter.com/ajlopez
Algunos vínculos interesantes que voy coleccionando en Delicious
Algunos vínculos interesantes que voy coleccionando en Delicious
|
Researchers Warn of Geotagging Dangers - Are You Concerned? - http://www.readwriteweb.com/archive...
|
|
Un diario hecho 100% con Youtube - http://www.amphibia.com.ar/un-diar...
|
|
Handy Ruby one liners by David Thomas - http://axonflux.com/handy-r...
|
Bidirectional dict o Injective mapping es una estructura de datos muy útil.
Por lo general cuando usamos un diccionario o tabla hash, tenemos un valor asociado a una clave:
>>> d = {1:'uno', 2:'dos'} >>> d[1] 'uno'
Pero algunas veces también resulta útil indexar por el valor y obtener la clave.
>>> d['uno'] 1
Por ejemplo, en un programa tengo un archivo de configuración que contiene un diccionario dónde las claves son strings de 3 caracteres representando un edificio (‘SFE’, ‘GDP’, …) y las claves números de puerto (5007, 5008, …). En algunas partes el programa requiere conocer el edificio a partir del número de puerto y en otras el puerto a partir del edificio.
Pregunté en StackOverflow y si bien me apuntaron a una implementación, la solución que más me gustó fue esta:
>>> d.update( dict((d[k], k) for k in d))
Así se ve d ahora: {1: 'uno', 2: 'dos', 'uno': 1, 'dos': 2}
Y podemos efectivamente indexar por clave o por valor:
>>> d[1]
'uno'
>>> d['uno']
1
En los diccionarios de Python solo objetos inmutables pueden ser clave de diccionarios, por lo tanto, en nuestro diccionario bidireccional, tanto las claves como los valores deberán serlo.
Otra limitación de este enfoque son las desincronizaciones que puede sufrir al modificar el bidict luego de realizar la transformación; hay que tener cuidado!
Como les contaba antes, yo estaba usando el diccionario original en un archivo de configuración, por lo que no toco la estructura de datos durante la ejecución del programa, así que esta solución compacta y elegante… como dijo el filósofo: me viene al pelo!
Espero a alguien más le sirva.
OWASP have just published the video of my talk in OWASP App Sec Research 2010 in Stockholm.
First talk in English ever.
Fue mi primer charla en inglés.
Slides and text are also avaliable.
Después de haber publicado varios posts de avance sobre el Proyecto Hogwarts, finalmente pudimos hacer pública la versión beta (yo imagino que en beta constante) del material del curso de introducción a TDD, como parte del como parte del Southworks Professional Improvement Program. Pueden registrarse gratuitamente en:

Tienen lecciones en línea, con código, videos, ejercicios, explicaciones de conceptos, bibliografía, enlaces. Como comentaba al principio, para mí está en beta constante. Si hay algo que no se entiende o no queda claro, tienen un foro para plantear dudas. Si tienen una sugerencia, también puede dejarla ahí.
Está soportado en la plataforma open source de Moodle, con PHP y MySQL.
Tenemos otro curso en desarrollo: Inversion of Control y Dependency Injection (van a ver ahí que está dado de alta, incompleto). Próximos cursos a incluir: Mocks (como una continuación de TDD), principios S.O.L.I.D.
Ayer jueves 22, los buenos de @MartinSalias y @sebarenzi dictaron un taller presencial de TDD con máquinas acá en Buenos Aires, basado en el material del curso. La idea es que cada curso también tiene contrapartida de cursos, talleres presenciales. Esperamos redondear la idea, para organizar cómo darlos en otros lugares. Por ejemplo, ahora estamos probando que un instructor de una empresa, dicte el curso de introducción a TDD en su equipo.
Nos leemos!
Angel “Java” Lopez
http://www.ajlopez.com
http://twitter.com/ajlopez
Algunos vínculos interesantes que voy coleccionando en Delicious
La forma más directa de tener pilas y colas en Python es usando listas, una de las poderosas estructuras de datos que vienen con el lenguaje.
Una pila es una estructura de datos secuencial en la que el último elemento insertado es el primero en retirarse (LIFO) y puede implementarse directamente con los métodos append y pop.
Una operación común en las pilas es top, que devuelve el elemento en el tope de la pila, pero sin quitarlo; esta operación se puede emular indexando por -1.
>>> pila = [6,7,8] >>> pila.append(9) >>> pila [6, 7, 8, 9] >>> pila.pop() 9 >>> pila.pop() 8 >>> pila[-1] 7
Implementar una cola requiere algo más. En las colas, a diferencia de las pilas, el primer valor insertado es el primero en quitarse (FIFO).
Un primer intento es implementarla con append y pop(0) que obtiene el primer elemento (o con insert insertando al principio y pop). Pero las listas de Python, si bien están optimizadas para insertar al final y quitar del final, no lo están para insertar al principio o quitar del principio. La solución es utilizar collections.deque que si está implementada con estas operaciones optimizadas.
>>> from collections import deque
>>> cola = deque([1,2,3])
>>> cola.append(4)
>>> cola
deque([1, 2, 3, 4])
>>> cola.popleft()
1
>>> cola.popleft()
2
>>> cola
deque([3, 4])
|
RT @newsycombinator: Flipboard: A social magazine for your iPad http://www.flipboard.com/
|
Sigo posteando material de lo que se está produciendo en el Proyecto Hogwarts. Esta vez, sobre el tema de Inversion of Control y Dependency Injection. Dentro de poco, estará disponible un sitio en línea con un curso de TDD, y otros en construcción. En el anterior post:
Introducción a IoC, DI: Hello, world flexible
había planteado un ejemplo simple, veamos alguno apenas más complejo. Y veamos de plantearla con interfaces.
Lo que vimos en ese ejemplo es que:
- Un objeto A puede necesitar la ayuda de otro objeto B, pero conociendo la interface, sin ligarse a una clase en concreto
- Al objeto A alguien le provee el objeto B
Lo que vamos explorar ahora, es un ejemplo donde al objeto A le damos alguna vez un objeto B1, y otras veces el objeto B2. Para que no sea sólo una aplicación de consola, veamos de levantar el alcance, y hagamos un ejemplo web.
Sea una simple aplicación web, donde tenemos una página para presionar dos enlaces para leer y mostrar los clientes:
La idea es tener DOS implementaciones de una interface que se haga responsable de implementar un repositorio de clientes (digamos, una lista de clientes). Si presionamos en el primer enlace, se usará un repositorio en memoria:
Si presionamos en el segundo enlace, usaremos el repositorio de base de datos:
Pueden bajarse el ejemplo desde mi Skydrive CustomersExample.zip. Contiene una solución .NET y los scripts para crear la base de datos en SQL Server.
En este ejemplo, vamos a usar un servicio que se ayudará de un repositorio para conseguir los objetos que necesita la presentación. Veamos cómo se implementó.
Tenemos entonces, un servicio que necesita consumir un repositorio. Como en el ejemplo de Hello, world, el objeto consumidor no referencia a una clase concreta, sino a una interfaz:
Esa interfaz está definida como:
namespace Customers { public interface ICustomerRepository { Customer GetById(int id); void Insert(Customer customer); void Update(Customer customer); void Delete(Customer customer); IQueryable<Customer> GetAll(); } }
Luego tenemos dos implementaciones de esta interfaz:
Una es la implementación del repositorio en memoria, parte del código:
public class InMemoryCustomerRepository : ICustomerRepository { private IList<Customer> customers = new List<Customer>(); public InMemoryCustomerRepository() { for (int k = 1; k <= 10; k++) { customers.Add(new Customer() { Id = k, Name = string.Format("Customer {0}", k), Address = string.Format("Address {0}", k), Notes = string.Format("Notes {0}", k) }); } } //.... }
La otra implementación de la interfaz accede a una base de datos, código parcial:
public class DatabaseCustomerRepository : ICustomerRepository { private string connectionstring; public DatabaseCustomerRepository(string connectionstring) { this.connectionstring = connectionstring; } public Customer GetById(int id) { SqlConnection conn = new SqlConnection(this.connectionstring); SqlCommand cmd = new SqlCommand("select Id, Name, Address, Notes from Customer where Id = @Id", conn); cmd.Parameters.Add(new SqlParameter("@Id", id)); conn.Open(); SqlDataReader reader = cmd.ExecuteReader(); Customer customer = null; if (reader.Read()) customer = this.GetCustomer(reader); reader.Close(); conn.Close(); return customer; } ///... }
Lo que se usó en este ejemplo es que al crear el objeto servicio le pasamos en los argumentos del constructor cuál implementación de repositorio queremos usar. Así, en la acción de ver por memoria, el código es:
protected void lnkMemory_Click(object sender, EventArgs e) { CustomerService service = new CustomerService(new InMemoryCustomerRepository()); this.grdCustomers.DataSource = service.GetCustomers(); this.grdCustomers.DataBind(); }
La otra opción es por base de datos:
protected void lnkData_Click(object sender, EventArgs e) { CustomerService service = new CustomerService(new DatabaseCustomerRepository("server=.\SQLEXPRESS;database=Customers;integrated security=true")); this.grdCustomers.DataSource = service.GetCustomers(); this.grdCustomers.DataBind(); }
Notemos que al servicio le "inyectamos" su ayudante, esta vez usando un parámetro de su constructor (en el ejemplo Hello World, habiamos inyectado usando propiedades).
Esto nos muestra:
- La capacidad de abstraer lo que necesitamos (la interfaz de repositorio) de cómo lo implementamos
- Nos permite cambiar la implementación y que todo siga funcionando igual
Acá usamos las DOS implementaciones al mismo tiempo. Pero es más común usar solo una.
Podríamos haber codificado el acceso a los clientes en el propio servicio. Pero lo separamos para:
- Poder mejorar el servicio (p.ej., en temas de seguridad) separando esas otras funcionalidades de la más simple de recuperación de clientes.
- Poder cambiar el ayudante, sin necesidad de cambiar el servicio y sus consumidores.
Hasta podemos ir armando una aplicación de demostración, usando durante semanas la implementación del repositorio en memoria. Mientras, en un desarrollo ágil, vamos iterando, mejorando nuestro dominio, SIN tener una base de datos por debajo. El cliente final va viendo nuestro avance, y solamente cuando tenemos algo más definido, vamos definiendo la base de datos subyacente y operando sobre ella.
También, este "approach" nos permite cambiar la implementación de la base de datos. Ahora, la implementación va contra SQL Server, pero si mañana necesitamos ir contra Oracle, cambiamos la implementación, y ni servicio y otros se enteran de ese cambio.
Y por último, veremos, más adelante en otro curso (Mocks con TDD), que podemos usar implementaciones de repositorios que no vayan contra la base, para alivianar nuestros tests del servicio.
Y todo, por haber programado contra la interfaz, y segregado responsabilidades.
Tenemos que avanzar en un sentido: no queremos tener que cambiar el código de creación del servicio, para que una vez use una implementación del repositorio (en memoria) y otra vez use una distinta (en base de datos). Quisiéramos que el cambiar esa relación NO IMPLIQUE tener que cambiar el código. Esa es la idea que subyace en lo que tenemos que definir en los próximos post de este tema: qué es Inversion of Control, qué es Dependency Injection, y contenedores de IoC.
Nos leemos!
Angel “Java” Lopez
http://www.ajlopez.com
http://twitter.com/ajlopez
Algunos vínculos interesantes que voy coleccionando en Delicious
Como algunos de ustedes (sino todos) pueden haber leído en Twitter, desde el viernes soy el feliz poseedor de un Motorola Milestone, un celular con Android al que le tenía ganas hace rato.
Obviamente, de entrada empecé a instalar y probar aplicaciones, entre las que se encontraban algunas de reconocimiento de voz. El problema fue que cuando quería utilizar estas últimas, me salía un aviso diciendo que no tenía instalado Google Voice Recognition. Después de investigar un poco, descubrí que esta utilidad no está disponible en ciertos países, como Alemania, Italia y Argentina (ni por defecto en el celular, ni mediante el market). Por suerte no me tomó mucho tiempo encontrar un foro donde daban la solución.
Lo único que hay que hacer es instalar desde el market una app llamada “Speak n’ send“, la cual al momento de intentar utilizarla nos dirá que no tenemos instalado Google Voice Recognition, luego de lo cual nos dará dos opciones: instalarlo desde el market (cosa que no podemos hacer) o descargarlo directo desde la página. Seleecionamos la segunda opción y tendremos instalado el reconocedor de voz de Google, que funciona muy bien
Espero que les haya servido este “tip” para poder activar el reconocimiento de voz en sus Milestone.
Casi me olvido; una vez que tengan instalaco el voice recognition, no dejen de probar la app TalkToMe Keyboard, les puede facilitar mucho la vida ^^
La transparencia gubernamental es un tema de moda. Fue uno de los pilares de la innovadora campaña electoral de Barack Obama, donde la tecnología jugó un papel central. Una de las formas en las que Obama materializó estas promesas de transparencia es el proyecto data.gov, donde se brinda acceso a más de 40.000 conjuntos de datos “crudos” generados por agencias del gobierno federal. Hay estadísticas de accidentes aéreos o la lista de quienes visitaron la Casa Blanca en los últimos meses. Lo destacable de esta iniciativa, que está siendo implementada por otros países, es que los datos se publican en formatos legibles por computadoras. Esto es, la información puede ser usada en herramientas de análisis de datos (una planilla de cálculo, por ejemplo) o usados para construir mashups, aplicaciones que combinan varias fuentes de información. Un buen ejemplo es OilMoney, que muestra los aportes monetarios que hacen las compañías petroleras a las campañas electorales.
En Argentina, el panorama es bastante desolador. No hay ley de acceso a la información pública; apenas tenemos el decreto 1172/03 que tiene bastantes problemas en su aplicación (ver acá o acá). Algunos organismos que sí publican información en la web, lo hacen en formatos ilegibles para las máquinas. Una imagen que contiene un documento impreso escaneado, como las declaraciones juradas de los funcionarios municipales de Bahía Blanca, es invisible para los buscadores. Esto también es un problema para la transparencia: en esta época de sobreabundancia de información, en la que los filtros son imprescindibles para encontrar lo que se busca, los documentos deben ser publicados en formatos apropiados.
Por suerte, están apareciendo iniciativas particulares en la dirección correcta. La ONG Poder Ciudadano desarrolló dos herramientas interesantes. Una base de datos sobre las pautas publicitarias oficiales y el sitio Dinero y Política. Son un recurso para facilitar la búsqueda y el acceso a datos provenientes de organismos estatales, que no se destacan por producir sitios web de calidad.
La Municipalidad de Bahía Blanca publica en su sitio web las órdenes de compra adjudicadas a proveedores. La herramienta de consulta dista del ideal, pero afortunadamente los datos están publicados en una página web común y silvestre; tienen cierta estructura y por lo tanto, un programa de computadora podrá interpretarlos. La Municipalidad sólo publica los listados de las órdenes de compra: cuándo y a quién se compró tal o cual cosa y a qué repartición fue destinada la compra. Vista así, poco es el aporte de esa información. Entonces, motivado por el interés de probar algunas herramientas informáticas y por la curiosidad acerca de la transparencia y la tecnología, construí Gasto Público Bahiense.
Gasto Público Bahiense facilita el acceso a los datos de compras publicados por la MBB. Permite consultar la lista completa de proveedores y de dependencias municipales, ver las órdenes de compra por repartición o por proveedor, obtener montos totales por período y buscar en el contenido de las órdenes de compra. Puede verse el gasto de la Secretaría de Gobierno en el primer semestre de este año y comparar con el mismo período de la Dirección de Fortalecimiento Humano.
Los gobernantes son responsables de informar sus acciones a los ciudadanos, al fin de cuentas están ahí gracias al voto popular. No pueden desaprovechar la oportunidad que ofrece la web para brindar esa información y mostrarla de manera concisa y clara. Muchas veces, el supuesto costo de este tipo de proyectos es argumento para no implementarlos. La única inversión para construir GPB fue 1 semana de mi tiempo. Tampoco hubo costos de licencias de software; todos los componentes que usé para el desarrollo son software libre o de uso gratuito. A su vez, GPB también es software libre.
Los datos son materia prima para la discusión. Con GPB, tengo la esperanza de que algún bahiense se alarme o se alegre por el dinero invertido en una cosa u otra y que se produzca la discusión que motive acciones transformadoras.
If you run glxinfo and get something around 60FPS (300 in 5 seconds), then you have the vertical sync set to QUALITY.
Open the AMD control center with amdcccle
Go to the “More Settings” item inside the “3D” Page on the left. Change “Wait for vertical refresh” to “Performance”.
Done.
I get this:
buanzo@murray:~$ fgl_glxgears
Using GLX_SGIX_pbuffer
10617 frames in 5.0 seconds = 2123.400 FPS
10988 frames in 5.0 seconds = 2197.600 FPS
10852 frames in 5.0 seconds = 2170.400 FPS
Gracias a la gente de la comunidad ALT.NET Hispano, pude participar de una VAN (des-conferencia) sobre NoSQL, hace ya unas semanas, el 22 de mayo pasado. Había escrito adelantando la presentación en:
ALT.NET Hispano VAN sobre NoSQL
Eso fue un sábado, y ya al lunes siguiente, el video de la VAN estuvo publicado, pueden verlo, junto con unas notas, en:
En estas semanas que pasaron, estuve ocupado con mi mudanza, mi consolidación de libros, y sigo ordenando cajas y volúmenes. Y luego alguna gripe. Pero ya pude hacerme algo de tiempo para pasar en limpio el material que utilicé para la VAN.
La presentación la pueden bajar desde mi Skydrive NoSqlVan2010.pptx
Enlaces sobre NoSQL:
Un panorama de NoSQL en el artículo de la Wikipedia.
A brief history of NoSQL interesante que nombrara a Pick, como hice en la presentación.
Java development 2.0: NoSQL una intro de IBM
NoSQL Architecture
MyNoSQL Blog activo sobre NoSQL
NoSQL Databases – Part 1 – Landscape
NoSQL: scaling to size and scaling to complexity
http://nosql-database.org/
What is NoSQL?
The NoSQL alternative
NoSQL Summer, list of papers
BASE: an Acid alternative
Errors in database systems, eventual consistency and the CAP theorem
Scalable Datastores: comparison nosql, and some scalable RDBMS
NoSQL el movimiento en contra de las bases de datos (yo no lo veo en contra, es complementario)
Diff SQL NoSQL
Choosing a NoSQL data store according to your data set
John P. Wood NoSQL posts
Databases: relational vs object vs graph vs document
NoSQL: no necesitas ACID
NoSQL and SQL anti-patterns
Learning NoSQL from Twitters experience
Eventual Consistency:
http://www.allthingsdistributed.com/2008/12/eventually_consistent.html
http://queue.acm.org/detail.cfm?id=1466448
http://devblog.streamy.com/tag/eventual-consistency
Teorema CAP
http://www.julianbrowne.com/article/viewer/brewers-cap-theorem
http://www.cs.berkeley.edu/~brewer/cs262b-2004/PODC-keynote.pdf
There is no free lunch with distributed data
Enlaces sobre Google BigTable:
http://labs.google.com/papers/bigtable.html
Enlaces sobre Hypertable:
http://hypertable.org/
Hypertable NoSQL (pdf)
Enlaces sobre Amazon Dynamo:
Amazon Dynamo (original paper)
Enlaces sobre Cassandra:
http://cassandra.apache.org/
http://wiki.apache.org/cassandra/ArticlesAndPresentations
Cassandra by example (Twitter-like one)
http://github.com/ericflo/twissandra
WTF is a supercolumn Cassandra model
Cassandra NoSQL Database
Apache Cassandra
NoSQL Live Dynamo derivatives: Cassandra
http://github.com/suguru/cassandra-webconsole
Tutorial: Getting started with Cassandra
NoSQL in Twitter
Twitter, Facebook and Cassandra, and Open Source
Cassandra and Twitter: interview with Ryan King
(Twitter está reviendo si usa NoSQL o no)
Enlaces sobre Voldemort:
http://project-voldemort.com/
Voldemort design
The NoSQL that must not be named
Product project Voldemort Distributed Database
Enlaces sobre CouchDB:
http://couchdb.apache.org
CouchDB Introduction
CouchDB Overview
Enlaces sobre SimpleDB
http://awsdocs.s3.amazonaws.com/SDB/latest/sdb-gsg.pdf
http://awsdocs.s3.amazonaws.com/SDB/latest/sdb-dg.pdf
Enlaces sobre Redis
http://code.google.com/p/redis/
Twitter alike example
Add NoSQL Data Storage to your PHP development with Redis
Nombré en algún momento a Memcached:
Sobre NoSQL tipo graph
InfoQ: Graph NoSQL Neo4j
Graph databases: A special case of document databases
Otros temas relacionados:
Scalability of the Hadoop distributed file system
MapReduce Hadoop algorithms in academic papers
Enlaces sobre MongoDB:
NoSQL with MongoDB, NoRM, and ASP.NET, part 1
NoSQL with MongoDB, NoRM, and ASP.NET, part 2
Master Slave in MongoDB
Choosing a non relational database: Why we migrated from MySQL to MongoDb
MongoHQ - The cloud-based hosted database solution for MongoDB.
http://github.com/azamsharp/SKOOL Ejemplo MongoDB, VS2010, ASP.NET MVC, TDD, BDD
On distributed consistency
El ejemplo que mostré de MongoDB (instalar, levantar el servidor, consumirlo desde .NET con un driver) siguió la línea explicada en el primer artículo de Ted Neward:
Going NoSQL with MongoDB
Going NoSQL with MongoDB (Part 2)
Going NoSQL with MongoDB (Part 3)
Código de ejemplo, ASP.NET MVC con MongoDB (apenas unas páginas) (ver de referenciar al driver que deje compilado en Libraries)
Para esos ejemplos tuve que descargar:
http://github.com/samus/mongodb-csharp
http://www.mongodb.org/display/DOCS/Downloads
Mis enlaces sobre los temas tratados:
http://delicious.com/ajlopez/mysql
http://delicious.com/ajlopez/mongodb
http://delicious.com/ajlopez/cassandra
http://delicious.com/ajlopez/couchdb
En especial, buscar presentaciones, tutoriales, ejemplos y “reviews”, como en:
http://delicious.com/ajlopez/mysql+tutorial
http://delicious.com/ajlopez/mysql+presentation
http://delicious.com/ajlopez/mysql+example
http://delicious.com/ajlopez/mysql+review
Nos leemos!
Angel "Java" Lopez
http://www.ajlopez.com
http://twitter.com/ajlopez
En El monstruo subatómico, un libro de difusión, Isaac Asimov habla sobre Iteligencia Artificial. Hace más o menos dos años que lo leí y tenía la intención de transcribir el artículo para que esté disponible en Internet. Casi lo olvidé, hasta que hoy alguien lo mandó en la lista de PyAr. Copio y pego a continuación.
Capítulo 11
Más pensamientos acerca del pensamiento
Podés leerlo en http://www.librosmaravillosos.com/elmonstruosubatomico/capitulo11.html
Gracias a Diego Avendaño, Patricio Barros y Antonio Bravo.
Se trata de una ley que prohíbe la persecución de contenidos, la discriminación de usuarios y obliga a la transparencia en el servicio. Busca proteger a los consumidores
En este post, generaremos, desde el mismo modelo, archivos de texto para C#, Java, y VB.NET. Anteriores post:
Building An Application Using AjGenesis (Part 1)
Armando una Aplicación usando AjGenesis (Parte 1)
Building An Application Using AjGenesis (Part 2)
Armando una Aplicación usando AjGenesis (Parte 2)
Building an Application Using AjGenesis (Part 3)
Armando una Aplicación usando AjGenesis (Parte 3)
El código de este post puede bajarse desde AppExampleStep04.zip.
Necesitamos los binarios últimos de AjGenesis. Podemos bajarlo desde AjGenesisTrunkBinaries.zip. (el código fuente completo está en el repositorio de AjGenesis en Codeplex). Tienen que agregar el directorio bin al path, para ejecutar los ejemplos de este post.
El ejemplo ahora tiene más estructura:
Projects\AjApp es el folder conteniendo el modelo del ejemplo, en Project.xml:
<Project> <Name>AjApp</Name> <Description>Building an Application using AjGenesis</Description> <Model> <Entities> <Entity Source="Entities/Customer.xml"/> <Entity Source="Entities/Supplier.xml"/> </Entities> </Model> </Project>
Podría, entonces, agregar más carpetas, con otros proyectos, si lo necesitara.
En el directorio inicial, tenemos tres comandos:
GenerateCSharp.cmd
GenerateJava.cmd
GenerateVbNet.cmd
Los tres comandos tienen un contenido similar. Por ejemplo, veamos el de GenerateCSharp.cmd:
AjGenesis.Console Projects\AjApp\Project.xml Projects\AjApp\Technologies\CSharp.xml Tasks\Complete.ajg Tasks\Generate.ajg
Notemos que hay dos modelos que se cargan en modelo: Project.xml es como en los anteriores posts, tiene el modelo independiente de la tecnología. El nuevo, CSharp.xml, describe la tecnología a usar:
<Technology> <Name>CSharp</Name> </Technology>
Por ahora, contiene solo el nombre del lenguaje a usar. Podría extender este modelo, en próximos posts, para que contenga la base de datos a usar, el webserver a usar, etc… Entonces: Project.xml es el modelo abstracto. Technologies\CSharp.xml, Technologies\VbNet.xml, Technologies\Java.xml son los modelos que describen la tecnología a usar. Cada uno de los comandos Generate*.cmd carga el modelo abstracto, Y uno de los tecnológicos.
La tarea Complete.ajg:
' Set Build Directory if not Project.BuildDirectory then Project.BuildDirectory = "Build/${Project.Name}/${Technology.Name}" end if FileManager.CreateDirectory(Project.BuildDirectory) IncludeCode "Tasks/Complete${Technology.Name}.ajg"
Noten el uso de un nuevo truco: incluir el código a ejecutar desde otro archivo, usando un string dinámico. Si Technology.Name == “CSharp”, la tarea de arriba termina ejecutando CompleteCSharp.ajg:
' Some functions ' Name to use for variables function CSharpVariableName(name) firstletter = name.Substring(0,1) return firstletter.ToLower() & name.Substring(1) end function ' Name to use for Classes, Properties.. function CSharpName(name) firstletter = name.Substring(0,1) return firstletter.ToUpper() & name.Substring(1) end function function CSharpType(type) type = type.ToLower() if type="text" then return "string" end if if type="integer" then return "int" end if return type end function ' Set namespace to use in CSharp code if not Project.CSharp.Namespace then Project.CSharp.Namespace = CSharpName(Project.Name) end if ' Complete Entities for each Entity in Project.Model.Entities ' Set the variable name to use for an entity if not Entity.CSharp.VariableName then Entity.CSharp.VariableName = CSharpVariableName(Entity.Name) end if for each Property in Entity.Properties ' Set the CSharp to use in each property if not Property.CSharp.Type then Property.CSharp.Type = CSharpType(Property.Type) end if end for end for
Hay tareas escritas similares a la de arriba, para otras tecnologías: CompleteVbNet.ajg, CompleteJava.ajg. Estas tareas completan el modelo en memoria (asignando namespaces, packages, nombres para usar en las variables y propiedaes, el directorio a usar para dejar lo generado, etc…)
La segunda tarea es Generate.ajg:
IncludeCode "Tasks/Generate${Technology.Name}.ajg"
De nuevo, el viejo truco de un include de código dinámico. Esta es la subtarea GenerateCSharp.ajg:
for each Entity in Project.Model.Entities TransformerManager.Transform("Templates/CSharp/EntityClass.tpl", "${Project.BuildDirectory}/${Entity.Name}.cs", Environment) end for
Compare, con CompleteJava.ajg:
' Some functions ' Name to use for variables function JavaVariableName(name) firstletter = name.Substring(0,1) return firstletter.ToLower() & name.Substring(1) end function ' Name to use for Classes, Properties.. function JavaName(name) firstletter = name.Substring(0,1) return firstletter.ToUpper() & name.Substring(1) end function function JavaType(type) type = type.ToLower() if type="text" then return "String" end if if type="integer" then return "int" end if return type end function ' Set package to use in Java code if not Project.Java.Package then Project.Java.Package = JavaName(Project.Name).ToLower() end if ' Complete Entities for each Entity in Project.Model.Entities ' Set the variable name to use for an entity if not Entity.Java.VariableName then Entity.Java.VariableName = JavaVariableName(Entity.Name) end if for each Property in Entity.Properties if not Property.Java.VariableName then Property.Java.VariableName = JavaVariableName(Property.Name) end if ' Set the Java to use in each property if not Property.Java.Type then Property.Java.Type = JavaType(Property.Type) end if end for end for
y el GenerateJava.ajg:
for each Entity in Project.Model.Entities TransformerManager.Transform("Templates/Java/EntityClass.tpl", "${Project.BuildDirectory}/${Entity.Name}.java", Environment) end for
Hay templates para cada tecnología. Ejemplo, este es el EntityClass.tpl para CSharp:
// Entity Class, generated with AjGenesis (http://ajgenesis.codeplex.com) namespace ${Project.CSharp.Namespace} { public class ${Entity.Name} { <# for each Property in Entity.Properties #> public ${Property.CSharp.Type} ${Property.Name} { get; set; } <# end for #> } }
y éste es el de Java EntityClass.tpl:
// Entity Class, generated with AjGenesis (http://ajgenesis.codeplex.com) package ${Project.Java.Package}; public class ${Entity.Name} { <# for each Property in Entity.Properties #> private ${Property.Java.Type} ${Property.Java.VariableName}; <# end for for each Property in Entity.Properties #> public ${Property.Java.Type} get${Property.Name}() { return this.${Property.Java.VariableName}; } public void set${Property.Name}(${Property.Java.Type} value) { this.${Property.Java.VariableName} = value; } <# end for #> }
Ejecutando los tres comandos Generate*.cmds, se crea el directorio Build, con Build\AjApp, Build\AjApp\CSharp, Build\AjApp\Java, Build\AjApp\VbNet, un directorio por el proyecto, con subdirectorios por tecnología.
Veamos el Customer.cs generado:
// Entity Class, generated with AjGenesis (http://ajgenesis.codeplex.com) namespace AjApp { public class Customer { public string Name { get; set; } public string Address { get; set; } } }
El Customer.vb generado:
' Entity Class, generated with AjGenesis (http://ajgenesis.codeplex.com) Namespace AjApp Public Class Customer Private mName as String Private mAddress as String Public Property Name as String Get return Me.mName End Get Set(value as String) Me.mName = value End Value End Property Public Property Address as String Get return Me.mAddress End Get Set(value as String) Me.mAddress = value End Value End Property End Class End Namespace
Y el Customer.java generado:
// Entity Class, generated with AjGenesis (http://ajgenesis.codeplex.com) package ajapp; public class Customer { private String name; private String address; public String getName() { return this.name; } public void setName(String value) { this.name = value; } public String getAddress() { return this.address; } public void setAddress(String value) { this.address = value; } }
Próximos pasos: generar proyectos C# o VB.NET, listos para cargar en Visual Studio, o un proyecto Eclipse para Java.
Nos leemos!
Angel “Java” Lopez
http://www.ajlopez.com
http://twitter.com/ajlopez
Algunos vínculos interesantes que voy coleccionando en Delicious
"aDb".replace("D","$$")"aDb".replace("D",function() { return "$$" })Si en una terminal tipeamos:
ssh user@domain.com
el cliente ssh intentará contactarse al puerto 22 de domain.com. Cómo cambiamos ese puerto?
ssh -p 2222 user@domain.com
Pero qué pasa si no tenemos acceso a cambiar ese parámetro? Puede pasarnos esto? Sí, por ejemplo si accedemos a un repositorio cvs por ssh; cuando ejecutamos uno de los comandos cvs, el tunel ssh se hace sin que nos demos cuenta. Si el servidor al que nos queremos conectar escucha en un puerto distinto al por defecto, tenemos un problema.
Hoy no me acordaba cómo se configuraba esto y resultó bastante tedioso buscarlo en Internet (todas las respuestas a la búsqueda era sobre como cambiar el puerto en el que un servidor ssh escucha). Lo dejo aquí para futuras referencias.
La forma de configurar un puerto por defecto para un cliente ssh en GNU/Linux es editando el archivo .ssh/config y añadiendo la línea:
Port NNNN
dónde NNNN es el número de puerto.
Supongamos por un momento que tenemos dos clases, y ambas se relacionan 1 a 1, pero sabemos que por ambas se relacionan solo con el mismo objeto, es decir la relación es uno a uno, por ejemplo:
En este caso cada “Persona” es única y está relacionada con un Domicilio de forma tal que un Domicilio solo se asocia con una Persona en particular y viceversa. ¿Se entiende?
Para este tipo de casos es que existe la relación one-to-one, así que vamos a ver un poco el XML:
Ésta es una asociación de clave primaria y como tal no necesitan una columna extra de la tabla. Si dos filas están relacionadas por la asociación entonces las dos filas de tablas comparten el mismo valor de clave principal. Esa es la esencia de este tipo de relaciones.
<hibernate-mapping>
<!-- Defino la clase Padre -->
<class name="modelo.Persona">
<id column="ID_PERSONA" name="oid" type="integer" unsaved-value="null">
<generator class="native"/>
</id>
<property column="NOMBRE" name="nombre" not-null="true" type="string"/>
<property column="APELLIDO" name="apellido" not-null="true" type="string"/>
<property column="EMAIL" name="email" type="string"/>
<!-- Indicamos el tipo de objeto que es y
el nombre del atributo, solo eso -->
<one-to-one class="modelo.Domicilio" name="domicilio"/>
</class>
</hibernate-mapping>
Como vemos la relación es muy simple y con una sola línea se lleva a cabo.
<one-to-one class="modelo.Domicilio" name="domicilio"/>
<hibernate-mapping> <class name="modelo.Domicilio"> <id column="ID_DOMICILIO" name="id" type="integer"> <generator class="foreign"/> </id> <property column="CALLE" name="calle" type="string"/> <property column="NUMERO" name="numero" type="integer"/> <one-to-one name="persona" class="modelo.Persona" constrained="true"/> </class> </hibernate-mapping>
Bueno esto ya pasa a ser la clave de la relación y tenemos dos puntos principales, primero, el one-to-one está en ambas clases. El segundo y más importante, la clase de menor importancia tiene un generador de id del tipo “foreign”. Que si repasamos los generadores, toma el id de la otra tabla y usa el mismo.
En sí, esta relación no es muy complicada, la clase java queda tal cual lo imaginaríamos con la POO:
public class Persona {
private int oid;
private String nombre;
private String apellido;
private String email;
private String cuil;
private Domicilio domicilio;
public Persona() {
}
public Domicilio getDomicilio() {
return domicilio;
}
public void setDomicilio(Domicilio domicilio) {
this.domicilio = domicilio;
}
// ACA SIGUEN LOS GETTERS Y LOS SETTERS ...

|
RT @newsycombinator: Google co-created video of an entire ride of the Trans-Siberian Railway http://www.google.ru/intl...
|
Hibernate proporciona 4 formas de implementar la herencia, yo me voy a referir a una sola que creo que es la mejor, si te interesa conocer las demás te recomiendo que leas la documentación en el “Capítulo 9 – Mapeo de herencias”.
Supongamos que tenemos una clase Persona (que contiene los datos generales) y heredan 2 un Empleado y un Capacitador, para esta estrategia se necesitarían 3 tablas, una para Persona, otra para Empleado y otra para Capacitador. Las dos tablas de subclase tienen asociaciones de clave principal a la tabla de superclase de modo que en el modelo relacional es realmente una asociación uno-a-uno.
El XML se vería así, lo documento sobre la marcha así se entiende mejor:
<hibernate-mapping>
<!-- Defino la clase Padre -->
<class name="modelo.Persona">
<id column="ID" name="oid" type="integer" unsaved-value="null">
<generator class="native"/>
</id>
<property column="NOMBRE" name="nombre" not-null="true" type="string"/>
<property column="APELLIDO" name="apellido" not-null="true" type="string"/>
<!-- …. Sigo con todos los atributos … -->
<!-- AHORA VIENEN LAS HERENCIAS PRIMERO EL EMPLEADO-->
<joined-subclass name="modelo.Empleado" table="EMPLEADO">
<!-- Definio la PK de la tabla Empleado que se va a
emparejar con la de Persona -->
<key column="LEGAJO"/>
<!-- Defino las propiedades del Empleado -->
<property column="FECH_ALTA" name="fechaAlta" not-null="true" type="date"/>
<property column="FECH_BAJA" name="fechaBaja" type="date"/>
</joined-subclass>
<!-- SIGUEN LAS HERENCIAS TURNO DEL CAPACITADOR-->
<joined-subclass name="modelo.Capacitador" table="CAPACITADOR">
<key column="LEGAJO"/>
<!-- Defino las propiedades … -->
</joined-subclass>
</class>
</hibernate-mapping>
Como podemos ver lo que define una clase hija es el tag “joined-subclass” al que se le indica la tabla destino y el tipo de objeto. Para completar se tiene que definir el key que es el que empareja ambas PK en la base de datos siguiendo con los atributos de la clase, está de más aclarar que tenemos UN solo XML para la clase padre e hijas mientras que tenemos varios .java para cada una de las clases.
Como vemos automáticamente creo las referencias entre tablas y las claves primarias para emparejar la herencia.
No hay nada raro en esto si sabes algo de herencia básica de java, pero manos a la obra, primero la clase Persona:
public class Persona {
private int oid;
private String nombre;
private String apellido;
private Date fechadeNac;
private TipoDocumento tipoDoc;
private String nroDoc;
private String email;
private String cuil;
private Domicilio domicilio;
public Persona()
{ }
public int getOid() { return oid; }
public void setOid(int id) { this.oid = id; }
public String getApellido() { return apellido; }
public void setApellido(String apellido) { this.apellido = apellido; }
// .... etc etc ...
}
Ahora seguimos por la clase Empleado, la de capacitador la dejo de lado por considerarla similar:
public class Empleado extends Persona
{
private int legajo;
private String rango;
private EstadoEmpleado estado;
public int getLegajo() { return legajo; }
public void setLegajo(int legajo) { this.legajo = legajo; }
// ... etc etc ...
}
Como vemos la herencia no presenta un problema muy grande en hibernate, y en 5 minutos podemos tener marchando este tipo de relación sin tener dolores agudos de cabeza y tendencias suicidas.

Luego de casi 2 años, slobos.com.ar he actualizado la versión de WordPress (tarea que tenia pendiente desde hace rato, pero no encontraba un momento para hacerlo) y de layout, si bien la disposición del diseño es muy similar al que solia utilizar, encontré este que realmente me agradó por lo limpio que es.
Así mismo notarán que integré algunos plugins como el de Facebook, que espero utilicen , twitter para replicar lo que posteo por ese medio en el sitio mientras no estoy frente a una pc y las ultimas fotitos que cargo a mi flickr para que de alguna forma quienes estan lejos y son medios vagos (lo digo por vos ceci) puedan vernos de algún modo.
No mucho más por decir, queda en ustedes darme su apreciación de que les parece.
Besos, abrazos y apretones de mano según corresponda.
Éste anexo creo que es más importante que todo el manual en sí, ya que lamentablemente, cuando trabajen con hibernate más alla de las clases y los XML, van a trabajar integramente y de la mano con los errores. Sueno pesimista, pero así es, cada error por más chico que sea va a dar una excepción irrecuperable que debemos ser capaces de analizar y de lograr resolver las causas que lo generaron.
Aunque soy algo optimista y espero que cuando se tome más conocimiento de la plataforma, se pueda trabajar sin ver las malditas lineas rojas en el Output.
Este anexo es para no perder tanto tiempo tratando de ver que tenemos mal y dar un par de ideas de que nos puede estar pasando y como solucionarlo de una manera rápida y explicada, o lo que se pueda XD.
java.lang.ClassCastException: org.hibernate.collection.PersistentSet cannot be cast to java.util.HashSet
Tipo de Error: En los .java
A Revisar: Manejo de colecciones
Ignoro qué significa este error, surge de la utilización de una Collection y puede solucionarse así.
Supongamos que tenemos un Empleado “e” que tiene un Set de capacitaciones, La llamada para usar las capacitaciones con error sería:
HashSet listaCaps = (HashSet)e.getCapacitaciones();
Esto da error así que lo tenemos que reemplazarlo por algo como:
HashSet listaCaps = new HashSet(e.getCapacitaciones());
java.sql.SQLException: Excepción de E/S: The Network Adapter could not establish the connection
Tipo de Error: software – hardware
A Revisar: Base de datos y conexiones físicas
Este error es el más simple de todos, se refiere a que Hibernate no pude abrir la conexión con la base de datos, ¿el motivo? Bueno, o la base de datos está caída/apagada, o bien que no se puede llegar al servidor, Empieza por comprobar el estado del servicio de la BD y luego por ver las conexiones, si es localhost mira los firewalls o antivirus por si cortan la conexión.
Could not parse mapping document from resource xxx. hbm.xml
Tipo de Error: En los xml
A Revisar: Revisar semánticamente y conceptualmente los xml
Este es uno de los errores más comunes a la hora de estar armando los XML de mapeo, en otras palabras significa, que tenemos un error en el XML, pero un error de escritura. Por ejemplo puede ser que nos olvidamos de cerrar un tag “/>”, o que tenemos etiquetas que no existen, o está mal escrito un atributo, etc etc. Tienen que verificar el XML en cuestión línea por línea para ver que tienen semánticamente o conceptualmente mal.
Si depuramos un poco más, podemos ver un poco más del detalle, por ejemplo:
Error parsing XML: XML InputStream(14) The element type "property" must be terminated by the matching end-tag "</property>".
Por ejemplo, acá vemos que nos olvidamos de cerrar un tag “property”, la solución es ver cada uno y comprobar que estén correctamente cerrados.
Otro error común es:
XML InputStream(37) The content of element type "set" must match "(meta*,subselect?,cache?,synchronize*,comment?,key, (element|one-to-many|many-to-many|composite-element|many-to-any) ,loader?,sql-insert?,sql-update?,sql-delete?,sql-delete-all?,filter*)".
Vemos que en el xml, en alguno de los Sets, tenemos una tag, que no corresponde, osea tenemos alguna propiedad que no es nativa de los sets y por ello nos da error (Por ejemplo tener declarado un <index column… que es propio de los lists). También puede darse el caso de que nos falte un atributo obligatorio, ver la documentación por si nos estamos olvidando de algo (puede ser que en un list, nos olvidemos de <index column …).

Gracias, mi muchacho, por sorprenderme todos los días. ¡Felices diez años!
Ultima Actualización:
July 31, 2010
Botón para Enlazarnos:
Sitio mantenido por
netFlux

Este trabajo es distribuido bajo una Licencia Creative Commons Atribución-NoComercial 2.5 Argentina.
Construido con Eco v0.1