miércoles, 1 de mayo de 2013

Primeras pruebas - 2ª parte: node + mongodb + express


En el anterior post (Primeras pruebas: node + mnogodb + express) dejé preparados los datos necesarios para crear un nuevo documento en la colección 'historial', para luego introducir líneas con los pedidos que se van realizando en dicho historial.

La función 'nuevo_historial' recogía los datos enviados por un formulario y mostraba en el navegador los datos necesarios para crear el nuevo historial. Habrá que modificar para que inserte dichos datos:


exports.nuevo_historial = function(req, res){
var x = 0;
var y = new Date();
var usuario = req.body.usuario;
var _mesa = req.body.mesa;

db.historial.find({},{_id:1}).sort({_id:-1}).limit((1), function(err, id){
if(err || !id) res.send("Error o no hay ningun documento");
else {
x = id[0]._id + 1;
db.historial.insert({_id: x, fecha: y, mesa: _mesa}, function(err, result){
if (err) res.send("Algo ha fallado");
else res.send("Mesa: " + _mesa + " abierta con la id: " + x + " por el usuario " + usuario);
});

}
});
};

He cambiado el nombre de la variable 'mesa' por '_mesa', para que no haya problemas con el nombre de la clave. La renovada función debería insertar un nuevo documento en el historial al recibir los para metros del formulario:


Si envío el formulario:


Parece que todo ha ido bien. De todas formas lo comprobaré desde shell:

{ "_id" : 3, "fecha" : ISODate("2013-04-24T17:58:53.989Z"), "mesa" : "2" }

Todo está bien. Se ha insertado el historial con la _id 3 y la mesa 2. El usuario es lo de menos ya que lo incluí de prueba, por enviar algo más a parte de la mesa. Pero en la aplicación real también debe ir en el documento, para saber quién abre la mesa.

El caso es que, para comprobar los historiales en curso, necesito una función que me los devuelva. Así que voy a crearla. Esta vez mostrará los resultados con un formato más legible, en vez de json:

exports.ver_historiales = function(req, res){
var h = "";
db.historial.find({}, function(err, id){
if(err || !id) res.send("Error o no hay ningun documento");
else { id.forEach(function(historiales){
fecha = historiales.fecha.getDate() +"/"+ historiales.fecha.getMonth()+"/"+ historiales.fecha.getYear();
hora = historiales.fecha.getHours() +":"+ historiales.fecha.getMinutes();
 
h = h + "Historial: " + historiales._id + " Mesa: " + historiales.mesa + " Fecha: " + fecha + " " + hora + " <br>"
 
});
res.send(h)
}
});
};

Modifico app.js para que use la nueva función y pruebo:


Antes de hacer que se muestren los pedidos dentro de cada documento, voy  a crear la función que los inserte. El usuario pincha en el artículo y se agrega al historial.

Para hacerlo un poco más amigable lo que hay hecho hasta ahora, voy a modificar los archivos 'articulos.js' y 'mesas.js' para que me muestre botones en vez de json, así será más cómodo y podré ir probando nuevas opciones. Así practico un poco...

La función ver_zonas mostrará las zonas disponibles en nuestro bar para que seleccionemos una:

exports.ver_zonas = function(req, res) {
var html = "<form method=\"post\" action=\"http://localhost:3000/zona\">";
db.mesas.distinct('zona', function(err, zonas) {
 if( err || !zonas) res.send("No hay zonas");
 else{
 zonas.forEach( function(err, zon) {
html = html + "<input type=\"submit\" name=\"zona\" value=\""+ zonas[zon] + "\">";
 });
 html = html + "</form>";
 }
 res.send(html);
});
};



Ahora, al pulsar una zona, llamaremos a 'mesas_x_zonas' para que muestre las mesas bajo esa zona:. 


exports.mesas_x_zona = function(req, res) {

var html = "<form method=\"post\" action=\"http://localhost:3000/familias\">";
var _zona = req.body.zona;
db.mesas.find({zona: _zona}, function(err, mesas) {
 if( err || !mesas) res.send("No hay zonas");
 else{
 mesas.forEach( function(err, mesa) {
html = html + "<input type=\"submit\" name=\"mesa\" value=\""+ mesas[mesa].nombre + "\">";
 });
 html = html + "</form>";
 
 }
 res.send(html);
});
};

No hay que olvidar cambiar get por post en la app y quitar el parámetro ':zona':

app.post('/zona', mesas.mesas_x_zona);

 Si por ejemplo selecciono 'Salon':

Siguiendo el ejemplo, al pulsar una mesa deben aparecer las familias disponibles para escoger un artículo. Para ello, modifico 'ver_familias' en 'articulos.js':

exports.ver_familias = function(req, res) {
var html = "<form method=\"post\" action=\"http://localhost:3000/familia\">";
var _mesa = req.body.mesa;
db.articulos.distinct('familia', function(err, familias){
if(err || !familias) res.send("No hay familias");
else {
html = html + "Mesa: <input type=\"text\" name=\"mesa\" value=\""+ _mesa + "\"><br>";
 familias.forEach( function(err, familia) {
html = html + "<input type=\"submit\" name=\"familia\" value=\""+ familias[familia] + "\">";
 });
 html = html + "</form>";
    }
res.send(html);
});
};:

Si selecciono la mesa 2:



Aparecen las distintas familias. Pero aquí haré un alto en el camino antes de llegar a la función que inserte el pedido.

Necesito comprobar si la mesa ya tiene algún pedido y mostrarlo debajo de las familias. Modificaré la función 'ver_familias' para que lo muestre:


exports.ver_familias = function(req, res) {
var html = "<form method=\"post\" action=\"http://localhost:3000/familia\">";
var _mesa = req.body.mesa;
db.articulos.distinct('familia', function(err, familias){
if(err || !familias) res.send("No hay familias");
else {
html = html + "Mesa: <input type=\"text\" name=\"mesa\" value=\""+ _mesa + "\"><br>";
 familias.forEach( function(err, familia) {
html = html + "<input type=\"submit\" name=\"familia\" value=\""+ familias[familia] + "\">";
 });
 html = html + "</form>";
    }
db.historial.find({mesa: _mesa}, {descrip: 1}, function(err, historial){
if(err || !historial) html = html + "Error o mesa vacía";
else { historial.forEach( function (err, hist){
historial[hist].descrip.forEach( function (err, desc){
html = html + historial[hist].descrip[desc].uds;
html = html + "  " + historial[hist].descrip[desc].articulo;
html = html + "  " + historial[hist].descrip[desc].precio + "<br>";
});
res.send(html);
});
}
});
});
};


He añadido una consulta para que muestre en el resultado los pedidos en esa mesa. El resultado al seleccionar la mesa 5 ( la que tiene más pedidos) sería como sigue:

Aunque el resultado es el correcto y muestra lo que necesito, me gustaría aislar en una función la consulta que muestra los pedidos en la mesa. De este modo 'ver_familias' quedaría algo más limpia y podría reutilizar la nueva función en otras operaciones.

Esta nueva función se llamará 'check_mesa':

function check_mesa(_mesa, callback){
var html = "Pedido: <br>";
db.historial.find({mesa: _mesa}, {descrip: 1}, function(err, historial){
if(err || !historial) html = html + "Error o mesa vacía";
else { 
historial.forEach( function (err, hist){
historial[hist].descrip.forEach( function (err, desc){
html = html + historial[hist].descrip[desc].uds;
html = html + "  " + historial[hist].descrip[desc].articulo;
html = html + "  " + historial[hist].descrip[desc].precio + "<br>";
});
 
});
}
 
callback(html);
});
}

Modifico 'ver_familias' para que llame a la nueva función:

check_mesa(_mesa, function (datos){
res.send(html + datos);}
);

Comprobaré el pedido de la mesa 5:

Todo correcto. Comprobaré la mesa 6, la cual no tiene nada... Todo OK.

Ahora comprobaré la 2, la cual está en 'historial' pero no tiene pedidos:

La app deja de funcionar, arrojando el error:

TypeError: Cannot call method 'forEach' of undefined

Es lógico, ya que existe el historial pero no hay datos con la  clave 'descrip'. Para corregir esto haré algo que debería haber hecho desde un principio. Añadir excepciones (o como quieran llamarlas). De este modo si hay algún error o algún documento o clave no existen, redirijo a donde deba con el mensaje que corresponda.

Modificando la función 'check_mesa', quedaría así:

 function check_mesa(_mesa, callback){
var html = "Pedido: <br>";
db.historial.find({mesa: _mesa}, {descrip: 1}, function(err, historial){
if(err){
html = "Error obteniedo historial.";
}else if(!historial[0]) {
html = "mesa vacía";
}else { 
  if (!historial[0].descrip){
html = "Mesa abierta pero no hay pedidos";
  }else{
historial.forEach( function (err, hist){
historial[hist].descrip.forEach( function (err, desc){
html = html + historial[hist].descrip[desc].uds;
html = html + "  " + historial[hist].descrip[desc].articulo;
html = html + "  " + historial[hist].descrip[desc].precio + "<br>";
});
});
}
}
 
callback(html);
});
}

Voy a comprobarlo con las  distintas mesas.

Mesa 5 tiene pedidos:

La mesa 2 tiene un historial abierto, pero no tiene pedidos:

La mesa 6 no tiene un historial abierto:



Una vez corregido el fallo ya puedo seguir con la tarea de insertar un pedido en la mesa. Lo siguiente entonces será mostrar los artículos al pulsar sobre una familia para poder agregarlo al pedido.

Modifico 'articulos_x_familia' para que muestre los artículos

exports.articulos_x_familia = function(req, res) {
var html = "<form method=\"post\" action=\"http://localhost:3000/inserta_pedido\">";
var _mesa = req.body.mesa;
var _familia = req.body.familia;
db.articulos.find({familia: _familia}, function(err, articulos){
if(err || !articulos) res.send("No hay artículos en la familia " + _familia);
else{
html = html + "Mesa: <input type=\"text\" name=\"mesa\" value=\""+ _mesa + "\"><br>";
html = html + "Familia: <input type=\"text\" name=\"familia\" value=\""+ _familia + "\"><br>";
articulos.forEach( function(err, articulo){
html = html + "<input type=\"submit\" name=\"articulo\" value=\""+ articulos[articulo].nombre + "\">";
});
html = html + "</form>";
check_mesa(_mesa, function (datos){
res.send(html + datos);}
);
}
});
};

Además de modificar la función para que muestre los artículos, también hago uso de 'check_mesa' para seguir mostrando la lista de pedidos. Si selecciono la familia 'Carnes' el resultado sería:


Ahora crearé la función 'inserta_pedido' encargada de añadir el artículo seleccionado al pedido. La crearé dentro del archivo 'historial.js. Como voy a hacer uso de la tabla 'articulos' para obtener el precio y los visores, tengo que añadir la colección a la variable 'collections'. También necesito añadir la colección mesas, ya que tengo que usar el campo 'visores' para insertarlo en el pedido':

var collections = ["historial", "articulos", "mesas"];

Como voy a crear una función en 'articulos.js' que devuelva el articulo pedido, requiero dicho archivo desde historial.js:

var articulos = require('./articulos');

Ahora, en articulos.js, creo la función que me devolverá los datos del artículo:


exports.ver_articulo = function(_articulo, callback){

db.articulos.find({nombre: _articulo}, function(err, articulo){
if (err){
console.log("Error obteniendo articulo.");
}else if(!articulo){
console.log("No existe el artículo.");
}else{
articulo.forEach( function(art){
callback(art);
});

}
});

};

Y la llamaré desde 'inserta_pedido' para ver si obtengo los datos que necesito:


exports.inserta_pedido = function(req, res){
var _mesa = req.body.mesa;
var _articulo = req.body.articulo;

articulos.ver_articulo(_articulo, function(datos){
res.send(datos.nombre + " " + datos.precio + " " + datos.tipo);
});
};

Compruebo:









El siguiente dato que necesito está en la clave 'visores' dentro de la colección 'mesas'. Crearé otra función en 'mesas' para que lo devuelva. Antes hay que requerir el archivo 'mesas.js' desde 'historial':

var mesas = require('./mesas');

Ahí va la función:


exports.ver_mesa = function (_mesa, art_tipo, callback){
var visor;

db.mesas.find({nombre: _mesa}, function(err, datos){
datos.forEach( function(mes){
  if (art_tipo == "comida"){
  visor = mes.visores.comida;
  }else{
visor = mes.visores.bebida;
  }
});
callback(visor);
});
};


De nuevo para comprobar, modifico 'inserta_pedido' para que muestre los datos por pantalla y estar seguro de que todo es correcto:


articulos.ver_articulo(_articulo, function(datos){
mesas.ver_mesa(_mesa, datos.tipo, function(mes){
res.send(datos.nombre + " " + datos.precio + " " + datos.tipo + "<br>" + mes);
});

});

Compruebo en el navegador:









Si selecciono 'Churrasco' me aparece el nombre, su precio (8.60) y el tipo 'comida', por lo que muestra 'cocina y parrilla' que son los visores por los cuales debe verse el artículo.

Comprobaré con una bebida... Marcaré una buena Jarra de Cerveza:








Aparecen los datos correctos. El último dato que falta para finalmente añadir el pedido, es el número de línea. Necesito otra función que lo compruebe. Si no hay pedidos, lo inserta con la línea número 1. Si los hay, obtiene el dato y le suma uno. Si la mesa no tiene un historial abierto, que avise y redirija al sitio para abrir un nuevo historial.


num_linea = function(_mesa, callback){
var num = 0;
db.historial.find({mesa: _mesa}, {descrip:1}, function(err, historial){
if(err){
callback("Error");
}else if (!historial[0]){
callback("La mesa no tiene historial abierto.");
}else{
historial.forEach( function(desc){
if (!desc.descrip){
num = 1;
callback(num);
}else{
desc.descrip.forEach( function(d){
num = d.linea;
});
callback(num+1);
}
});
}
});
};


La función 'num_linea' será la encargada de realizar esta tarea. Para comprobar los datos, 'inserta_pedido' queda más o menos así:

articulos.ver_articulo(_articulo, function(datos){
mesas.ver_mesa(_mesa, datos.tipo, function(mes){
num_linea(_mesa, function(num){
res.send(datos.nombre + " " + datos.precio + " " + datos.tipo + "<br>" + mes + "<br>Num. linea: " + num);
});
});
});


Pruebo con las distintas mesas:
Mesa 5, como ya tiene 4 lineas, me devuelve 5:

Mesa 2, como no tiene ningún pedido, me devuelve 1:


Mesa 3, no esta en historiales y me avisa:

En este último caso, redireccionaría al formulario para su apertura. Pero ahora lo importante es que ya tengo los datos para poder insertar el pedido en una mesa.

Lo primero será modificar 'num_linea' para que devuelva 0 en caso de no estar en ningún historial:

}else if (!historial[0]){
num = 0;
callback(num);



Ahora creo los $set o $push, dependiendo del número de línea. 'inserta_pedido' queda como sigue:

exports.inserta_pedido = function(req, res){
var _mesa = req.body.mesa;
var _articulo = req.body.articulo;
articulos.ver_articulo(_articulo, function(datos){
mesas.ver_mesa(_mesa, datos.tipo, function(mes){
num_linea(_mesa, function(num){
if (num == 0){
res.send("No se puede insertar el pedido en una mesa sin historial.");
}else if(num == 1){
db.historial.update({mesa:_mesa}, {$set:{descrip:[{linea: num, articulo: datos.nombre, precio : datos.precio, uds: 1, visores:mes }]}}, function(err){
 res.send("Insertado");
});
}else{
db.historial.update({mesa:_mesa}, {$push:{descrip:{linea: num, articulo: datos.nombre, precio : datos.precio, uds: 1, visores:mes }}}, function(err){
 res.send("Insertado");
});
}
});
});
});
};




Para probar, borraré todos los historiales desde shell y comenzare desde 0.

Al borrar los documentos de la colección 'historial', si trato de abrir una mesa desde form_mesa.html, me arrojará un error, ya que la función 'nuevo_historial' suma 1 al último. Al no haber ninguno, fallará. Así que lo primero es modificarla:


db.historial.find({},{_id:1}).sort({_id:-1}).limit((1), function(err, id){
if(err){
res.send("Error");
}else if(!id[0]){
x = 1;
}else {
x = id[0]._id + 1;
}
db.historial.insert({_id: x, fecha: y, mesa: _mesa}, function(err, result){
if (err) res.send("Algo ha fallado");
else res.send("Mesa: " + _mesa + " abierta con la id: " + x + " por el usuario " + usuario);
});
});

Ya está. En caso de que no haya ningún historial, devuelve 1.

A probar. Abro mi formulario y abro la mesa 2:









Pulso Enviar para abrir la mesa:







Accedo al principio de la app (localhost:3000/zonas) y selecciono 'Salon' (donde está la mesa 2):








Selecciono la mesa 2... :







... y me aparecen sus datos:










Selecciono la familia Carnes para ver los artículos carnívoros:











Finalmente pulso 'Churrasco' y...







Parece que se ha insertado correctamente... para comprobarlo, vuelvo hacia atrás en el historial del navegador y actualizo la página:












Añadiré varios artículos más al pedido para ver que funciona:

















Pues parece que todo funciona. Pruebo con otra mesa. Abriré la 5:
























Realmente, lo ideal sería que estuviera viendo una línea nueva en pantalla en vez de ver el mensaje 'insertado' cada vez que se añade un artículo (volver hacia atrás y actualizar la página...). Pero para el ejemplo y lo que quiero comprobar, va sobrado.

Si compruebo las colecciones desde shell:


> db.historial.find().pretty();
{
        "_id" : 1,
        "descrip" : [
                {
                        "linea" : 1,
                        "articulo" : "Churrasco",
                        "precio" : 8.6,
                        "uds" : 1,
                        "visores" : [
                                "cocina",
                                "parrilla"
                        ]
                },
                {
                        "linea" : 2,
                        "articulo" : "Kobe",
                        "precio" : 8.6,
                        "uds" : 1,
                        "visores" : [
                                "cocina",
                                "parrilla"
                        ]
                },
                {
                        "linea" : 3,
                        "articulo" : "Solomillo de Ternera",
                        "precio" : 8.6,
                        "uds" : 1,
                        "visores" : [
                                "cocina",
                                "parrilla"
                        ]
                },
                {
                        "linea" : 4,
                        "articulo" : "Jarra Cerveza",
                        "precio" : 3,
                        "uds" : 1,
                        "visores" : [
                                "barra"
                        ]
                },
                {
                        "linea" : 5,
                        "articulo" : "Tanque",
                        "precio" : 1,
                        "uds" : 1,
                        "visores" : [
                                "barra"
                        ]
                }
        ],
        "fecha" : ISODate("2013-05-01T17:30:13.673Z"),
        "mesa" : "2"
}
{
        "_id" : 2,
        "descrip" : [
                {
                        "linea" : 1,
                        "articulo" : "Solomillo de Ternera",
                        "precio" : 8.6,
                        "uds" : 1,
                        "visores" : [
                                "cocina",
                                "parrilla"
                        ]
                },
                {
                        "linea" : 2,
                        "articulo" : "Kobe",
                        "precio" : 8.6,
                        "uds" : 1,
                        "visores" : [
                                "cocina",
                                "parrilla"
                        ]
                },
                {
                        "linea" : 3,
                        "articulo" : "Urta",
                        "precio" : 8.6,
                        "uds" : 1,
                        "visores" : [
                                "cocina",
                                "parrilla"
                        ]
                },
                {
                        "linea" : 4,
                        "articulo" : "Fanta Naranja",
                        "precio" : 1,
                        "uds" : 1,
                        "visores" : [
                                "barra"
                        ]
                },
                {
                        "linea" : 5,
                        "articulo" : "Fanta Limon",
                        "precio" : 1,
                        "uds" : 1,
                        "visores" : [
                                "barra"
                        ]
                }
        ],
        "fecha" : ISODate("2013-05-01T17:46:40.237Z"),
        "mesa" : "5"
}




Todo correcto (al menos hasta donde llego a alcanzar).

Ya tengo un mini-sistema de pedidos funcionando. No puedo decir que sea KomanderTPV pero puedo insertar pedidos en las mesas.Es lo que me interesa para poder ver los pedidos a través de los visores en tiempo real.

Y hablando de tiempo real... Hasta ahora, lo que se ha hecho, no ha sido una comunicación en tiempo real.  Es más, este sistema no me sirve realmente para mis propósitos (al menos por ahora).

Si quisiera ver la información de los pedidos, tendría que estar llamando cada X tiempo para comprobar si hay pedidos nuevos y mostrarlos. Eso no es lo que quiero.

Para lo que yo quiero necesito Socket.io, el cual desconozco, pero permitirá a mi app mantener una comunicación en tiempo real con los clientes.

Así que el siguiente paso será trastear con socket.io desde node (y mongo por su puesto) y comprobar como recogería estos pedidos en los visores.

Cualquier comentario es de agradecer ya que no sé si voy por el buen camino. Puede que me esté liando y no esté haciendo los esquemas o las funciones como debiera.

Hasta entonces, Saludos.

Luismips


lunes, 22 de abril de 2013

Primeras pruebas: node + mongodb + express

El siguiente post es una continuación de una serie de Documentos para las pruebas, donde he creado y manipulado unas colecciones en mongodb para probar node.js con express.

Esto no es un tutorial. Son los datos que voy recopilando y las pruebas que voy haciendo para ver si me merece la pena pasar a este sistema. Así que puede ser que me equivoque y no vaya por el camino correcto (se agradecen los comentarios al respecto... me gustaría saber si voy encaminado)

Por lo que he visto hasta ahora, me gusta y me intriga. Y creo que realmente acabaré pasando a él. Pero prácticamente no he visto nada. Así que al lío.

Comenzaré creando el directorio para la aplicación. En este caso, crearé una carpeta llamada 'bar' en la raíz de mi disco C:

Una vez creada, voy a instalar express en esa carpeta. 

Para ello guardaré en la carpeta de mi aplicación el archivo package.json con el siguiente contenido:

{
"name": "bar",
"description": "Programa para restaurantes",
"version": "0.0.1",
"private": true,
"dependencies": {
"express": "3.x"
}
}

Desde una shell, entro a la carpeta de mi aplicación, 'bar', para instalar express:

npm install

También instalaré MongoJS:

npm install mongojs

Hace falta una carpeta 'routes' para guardar los distintos archivos que serán los encargados de manipular los datos que recibe nuestra app. La estructura de carpetas queda de la siguiente forma:

bar
|-node_modules
|     |-.bin
|     |-express
|     |-mongojs
|-routes


La base para el script que hará las funciones de servidor, app.js:

var express = require('express');
var app = express();

app.listen(3000);
console.log('Servidor escuchando en el puerto 3000...');

La app ahora mismo no hace nada, sólo crea un servidor que escuchará por el puerto 3000.

Para el primer ejemplo, crearé una función que devuelva los artículos existentes en la colección 'articulos'. Pero esto lo definiré en un archivo dentro de la carpeta 'routes', para luego simplemente hacer una llamada desde app y que devuelva lo que necesitamos. El archivo se llamará articulos.js:

var databaseUrl = "km"; // "username:password@example.com/km"
var collections = ["articulos"]
var db = require("mongojs").connect(databaseUrl, collections);

He introducido el código necesario para conectar a mi base de datos 'km', usando la colección 'articulos'. Necesito una función que devuelva los documentos de esa colección:

exports.ver_articulos = function(req, res) {
db.articulos.find({}, function(err, articulos) {
 if( err || !articulos) res.send("No hay articulos");
 else res.send(articulos);
});
};

Modificaré el archivo app para que pueda usar la función ver_articulos del archivo articulos.js:

var express = require('express');
articulos = require('./routes/articulos');
var app = express();

app.get('/articulos', articulos.ver_articulos);

app.listen(3000);
console.log('Servidor escuchando en el puerto 3000...');

Es el momento de ejecutar mi app y para ver si me devuelve los artículos.

node app.js

Abro un navegador y accedo a:

localhost:3000/articulos

A la primera. Esta vez no me ha fallado nada. Ni yo me lo creo. En mi navegador aparecen los artículos tal y como le he pedido.


Pues manos a la obra. Necesito una función que me devuelva un artículo por su '_id'. Así que me voy a articulos.js y la creo:

exports.ver_articulo = function(req, res) {
var id = req.params.id;
db.articulos.findOne({'_id': id}, function(err, articulo) {
 if( err || !articulo) res.send("No existe el artículo con la id: " + id);
 else res.send(articulo);
});
};

Añado la siguiente línea en app.js para  que pueda usar la nueva función:

app.get('/articulo/:id', articulos.ver_articulo);

Y lo pruebo... Este código lo único que me devuelve es:

'No existe el articulo con la id: X'

Para probar si estoy fallando en la consulta o si hay que formatear la variable id de alguna manera, la sustituiré por la id de algún artículo:

db.articulos.findOne({'_id': 1}, function(err, articulo)

Esto SÍ me devuelve el artículo. Por lo tanto, id debe ser formateado de alguna forma.

La solución está en pasar la _id como int, ya que se creó como tal:

db.articulos.findOne({_id: parseInt(id)}, function(err, articulo)

Si apunto mi navegador, por ejemplo al artículo con la id 2:

Ahora que he probado un par de funciones, pensaré sobre como debe aparecer la información al usuario. Para las pruebas que quiero realizar me basta con los siguientes pasos:

1 - Muestra las zonas del bar: el camarero selecciona una zona para seleccionar una mesa
2 - Muestra las mesas: Se selecciona una mesa donde añadiremos los pedidos
3 - Familias: Se muestran las distintas familias para seleccionar una y que aparezcan los artículos.
4 - Artículos: Se muestran los artículos para seleccionar uno y que se añada al pedido
5 - Se inserta el pedido en el historial
6 - Se vuelve a la pantalla familias para volver a seleccionar otro artículo 

Voy a crear una archivo 'mesas.js' en 'routes' para meter ahí las funciones relacionadas con la colección 'mesas':

var databaseUrl = "km"; // "username:password@example.com/km"
var collections = ["mesas"]
var db = require("mongojs").connect(databaseUrl, collections);

exports.ver_mesas = function(req, res) {
db.mesas.find({}, function(err, mesas) {
 if( err || !mesas) res.send("No hay mesas");
 else res.send(mesas);
});
};

Por lo pronto le he añadido una función para ver todas las mesas. También  he modificado el archivo 'app.js' para que pueda usar las funciones de 'mesa.js'.

Pero lo más importante es empezar por el punto 1, mostrar las zonas.

¿Cómo sería la consulta para mostrar las zonas existentes en nuestro restaurante? Probare desde la shell de mongo lo siguiente:

> db.mesas.distinct('zona')
[ "Salon", "Terraza" ]


Eso me devuelve lo que necesito. Sabiendo eso, la función 'ver_zonas' quedaría así:

exports.ver_zonas = function(req, res) {
db.mesas.distinct('zona', function(err, zonas) {
 if( err || !zonas) res.send("No hay zonas");
 else res.send(zonas);
});
};



Modifico 'app.js' para que acceda a la nueva función y pruebo:

Una vez que muestra las zonas, suponiendo que he seleccionado una de ellas, deben aparecer las mesas existentes en dicha zona:

exports.mesas_x_zona = function(req, res) {
var _zona = req.params.zona;
db.mesas.find({zona: _zona}, function(err, mesas) {
 if( err || !mesas) res.send("No hay zonas");
 else res.send(mesas);
});
};

De nuevo modifico 'app.js' para incluir la nueva función y pruebo:


Para el ejemplo, selecciono la mesa '1' y debería aparecer una pantalla con las familias de artículos para que pueda seleccionar uno y añadirlo al pedido, tal y como dije en el punto 3. 

Así que añado la función correspondiente al archivo 'articulos.js':

exports.ver_familias = function(req, res) {
db.articulos.distinct('familia', function(err, familias){
if(err || !familias) res.send("No hay familias");
else res.send(familias);
});
};

Pruebo de nuevo para comprobar si hay algún error:



Seguimos en el punto 4. Al seleccionar una familia aparecen los artículos correspondientes:

exports.articulos_x_familia = function(req, res) {
var _familia = req.params.familia;
db.articulos.find({familia: _familia}, function(err, articulos){
if(err || !articulos) res.send("No hay artículos en la familia " + _familia);
else res.send(articulos);
});
};



Si selecciono 'Carnes':


Hasta aquí bien todo, pero ahora viene lo bueno. Se supone que cuando el usuario pulse un artículo, debe añadirse al pedido, es decir, a la colección historial.

Para ello será necesario crear otro archivo, 'historial.js', que se encargue de crear documentos nuevos para añadir los pedidos.

Este archivo debería contener dos funciones:

- nuevo_historial, crea un nuevo documento en historial pero dependiendo de la _id del último documento.
- inserta_pedido, añade la clave 'descrip' con todos los datos del pedido en el documento creado.

Para nuevo_historial necesito saber cual es la id del último documento. Pruebo desde la shell:

> db.historial.find().sort({_id:-1}).limit(1).pretty();
{
        "_id" : 2,
        "descrip" : [
                {
                        "linea" : "1",
                        "articulo" : "Fanta Naranja",
                        "precio" : 1,
                        "visores" : [
                                "barra"
                        ]
                }
        ],
        "fecha" : ISODate("2013-04-22T16:11:15.500Z"),
        "mesa" : "4"
}


Me devuelve el documento completo. Yo sólo necesito la '_id':

> db.historial.find({},{_id:1}).sort({_id:-1}).limit(1);
{ "_id" : 2 }

Para comprobar (como siempre)  antes de sumar, compruebo que el resultado sea correcto, que me devuelva la última id de documento:

exports.nuevo_historial = function(req, res){
db.historial.find({},{_id:1}).sort({_id:-1}).limit((1), function(err, id){
if(err || !id) res.send("Error o no hay ningun documento");
else res.send(id);
});
};


Sabiendo que el resultado es el deseado, debería asignarlo a una variable y sumarle uno, para prepararlo como la nueva id del documento a crear. La función 'nuevo_historial' quedaría más o menos así:


exports.nuevo_historial = function(req, res){
var x = 0;
db.historial.find({},{_id:1}).sort({_id:-1}).limit((1), function(err, id){
if(err || !id) res.send("Error o no hay ningun documento");
else {
x = id[0]._id + 1;
res.send("Próxima id: " + x);
}
});
};



Hasta donde llego, es la única forma que he encontrado para recoger el valor que necesito. Ya he dicho que soy un novato con esto. Desconozco si hay una forma mejor o más adecuada (por favor, agregar a comentarios).

Una vez que tengo mi próximo indice preparado, el segundo campo que necesito es la fecha. Modifico 'nuevo_historial':


y = new Date();
res.send("Próxima id: " + x + "<br> Fecha: " + y);

El último dato que hace falta para crear el nuevo historial es el nombre de la mesa, el  cual debería venir en el POST desde alguna página que lo envíe.

Para probar a recoger valores desde un POST, voy a crear un pequeño formulario que envíe el número de la mesa elegida, además del usuario que está utilizando el sistema:


<html>
<head>
   <title>Seleccione la mesa</title>
</head>
<body>
<form method="post" action="http://localhost:3000/nuevo_historial">
    Usuario:<input type="text" name="usuario" value="luismips"><br>
    Mesa:<input type="text" name="mesa" value="2"><br>
    <input type="submit" value="Enviar">
</form>
</body>
</html>



En la app, debo configurar express para que use la función bodyParser():


app.configure(function(){
  app.use(express.bodyParser());
});


Modifico la llamada a la función 'nuevo_historial' desde app.js. La cambiaré de get a post:

app.post('/nuevo_historial', historial.nuevo_historial);

La función 'nuevo_historial' también será modificada. Se recogerá el valor del POST y se mostrará en el navegador:


exports.nuevo_historial = function(req, res){
var x = 0;
var usuario = req.body.usuario;
var mesa = req.body.mesa;

db.historial.find({},{_id:1}).sort({_id:-1}).limit((1), function(err, id){
if(err || !id) res.send("Error o no hay ningun documento");
else {
x = id[0]._id + 1;
y = new Date();
res.send("Próxima id: " + x + "<br> Fecha: " + y + "<br>Mesa: " + mesa + "<br>Usuario: " + usuario);
}
});
};


Es el momento de probar si todo envía y recibe correctamente. Abro mi pequeño formulario en el navegador:


Pulso Enviar:










Finalmente tengo los datos que necesito para crear el nuevo documento que contendrá el pedido de la mesa. El dato 'Usuario' no me sirve para las pruebas. Lo he incluido simplemente por probar.

Lo siguiente será crear el nuevo documento e insertar el pedido al pulsar (enviar en este caso) un artículo, para posteriormente, simular el visor de barra o cocina y ver los pedidos que van entrando en tiempo real (que es una de las cosas por las que quiero usar node... ver en tiempo real).

Pero eso en el siguiente post (2ª parte). Este se me ha hecho muy largo, así que continuaré en el próximo.

Hasta entonces, saludos,

Luismips