`
+ - **M** – Comment ` `
+
+### Replace
+Indica si el elemento al que se aplica debe ser sustituído por el resultado de la ejecución
+
+### Transclude
+Indica si el elemento al que se aplicaca incluirá el resultado de la ejecución de la directiva en su interior
+
+### Template y template URL
+Cadena de texto o enlace a fichero que contine el HTML que va a devolver la directiva
+
+### Scope
+Intercambio de datos con el elemento que declara la directiva.
+Valores posibles:
+
+ - **@** Interpolating, para enlazar un texto dentro de la directiva. ` scope: { soloLectura: '@' } `
+ - **=** Data bind, un objeto para doble-enlace que la directiva pueda cambiar ` scope: { valor: '=alias' } `
+ - **&** Expression `scope: { onEvaluado: '&' } `
+
+
+### Link
+Una función que enlazará la directiva con el scope
+
+### Compile
+Una función que podrá manipular el DOM creando una función link
+
+### Controller
+Una función que actuará como contrlador para esta directiva
diff --git a/20-directivas/package.json b/20-directivas/package.json
new file mode 100644
index 0000000..6028a5d
--- /dev/null
+++ b/20-directivas/package.json
@@ -0,0 +1,15 @@
+{
+ "name": "ControlCashFlow",
+ "version": "0.0.0",
+ "description": "Controla tu flujo de caja",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "author": "Alberto Basalo",
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "express": "~4.12.1",
+ "body-parser": "~1.0.2"
+ }
+}
diff --git a/20-directivas/recursos.md b/20-directivas/recursos.md
new file mode 100644
index 0000000..678453c
--- /dev/null
+++ b/20-directivas/recursos.md
@@ -0,0 +1,7 @@
+https://angular-ui.github.io/
+http://ngmodules.org/c
+http://angular-js.in/
+http://www.directiv.es/
+
+http://www.tamas.io/node-jsexpress-cors-implementation/
+http://stackoverflow.com/questions/23823010/how-to-enable-cors-in-angularjs
\ No newline at end of file
diff --git a/20-directivas/server.js b/20-directivas/server.js
new file mode 100644
index 0000000..8d3fc97
--- /dev/null
+++ b/20-directivas/server.js
@@ -0,0 +1,187 @@
+var express = require('express');
+var bodyParser = require('body-parser');
+
+var app = express();
+app.use(bodyParser());
+app.use(express.static(__dirname + '/client'));
+
+app.use(function (peticion, respuesta, siguiente) {
+ console.log("recibida petición: " + peticion.url);
+ if (peticion.body && Object.keys(peticion.body).length>0) {
+ console.log("body: " + JSON.stringify(peticion.body));
+ }
+ siguiente();
+ });
+
+console.log('ready');
+
+var maxId = 0;
+var movimientos = [];
+var totales = [];
+
+var usuarios = [];
+var sesiones = [];
+
+
+app.use('/api/priv/', function (req, res, next) {
+ var sessionId = req.get('sessionId');
+ var sesion = getSesion(sessionId);
+ if (sesion) {
+ if (esSesionValida(sesion)) {
+ sesion.timeStamp = new Date();
+ req.usuario = sesion.email;
+ next();
+ } else {
+ console.log('Sesión caducada:' + JSON.stringify(sesion));
+ res.status(419).send('Sesión caducada');
+ }
+ } else {
+ res.status(401).send('Credencial inválida');
+ }
+
+});
+
+
+
+
+
+// API - REST
+// SECURITY
+app.route('/api/usuarios')
+ .get(function (req, res, next) {
+ // Esto devuelve la lista completa de usuarios y contraseñas
+ // PELIGRO: Usar sólo a modo de debug mientras desarrollamos
+ res.json(usuarios);
+ })
+ .post(function (req, res, next) {
+ var usuario = req.body;
+ if (existeUsuario(usuario)) {
+ console.log('email ya registrado:' + usuario.email);
+ res.status(409).send('email ' + usuario.email + ' ya registrado');
+ } else {
+ console.log('registrado:' + usuario.email);
+ usuarios.push(usuario);
+ res.json(newSession(usuario.email));
+ }
+ });
+
+// Gestión de sesiones: listado y login
+app.route('/api/sesiones')
+ .get(function (req, res, next) {
+ // Esto devuelve la lista completa de sesiones
+ // PELIGRO: Usar sólo a modo de debug mientras desarrollamos
+ res.json(sesiones);
+ })
+ .post(function (req, res, next) {
+ var usuario = req.body;
+ if (esUsuarioValido(usuario)) {
+ console.log('aceptado:' + usuario.email);
+ res.json(newSession(usuario.email));
+ } else {
+ console.log('Credencial inválida:' + usuario.email);
+ res.status(401).send('Credencial inválida');
+ }
+ });
+
+function existeUsuario(usuario) {
+ return usuarios.some(function (u) {
+ return u.email == usuario.email;
+ });
+}
+
+function esUsuarioValido(usuario) {
+ return usuarios.filter(function (u) {
+ return u.email == usuario.email && u.password == usuario.password;
+ })[0];
+}
+
+function getSesion(sessionId) {
+ return sesiones.filter(function (s) {
+ return s.sessionId == sessionId;
+ })[0]
+}
+
+function esSesionValida(sesion) {
+ return (new Date() - sesion.timeStamp) < 20 * 60 * 1000;
+}
+
+function newSession(email) {
+ var sessionId = Math.random() * (88888) + 11111;
+ var timeStamp = new Date();
+ sesiones.push({
+ sessionId: sessionId,
+ email: email,
+ timeStamp: timeStamp
+ });
+ return sessionId;
+}
+
+
+// BUSINESS
+app.get('/api/pub/maestros', function (req, res, next) {
+ var maestros = {
+ categoriasIngresos: ['Nómina', 'Ventas', 'Intereses Depósitos'],
+ categoriasGastos: ['Hipotéca', 'Compras', 'Impuestos']
+ };
+ res.json(maestros);
+});
+
+app.route('/api/priv/movimientos')
+ .get(function (req, res, next) {
+ var movimientosUsuario = movimientos.filter(function (m) {
+ return m.usuario = req.usuario;
+ });
+ res.json(movimientosUsuario);
+ })
+ .post(function (req, res, next) {
+ var movimiento = req.body;
+ movimiento.usuario = req.usuario;
+ maxId++;
+ movimiento.id = maxId;
+ movimientos.push(movimiento);
+ var totalUsuario = getTotalUsuario(req.usuario);
+ if (movimiento.tipo == 'Ingreso')
+ totalUsuario.ingresos += movimiento.importe;
+ else
+ totalUsuario.gastos += movimiento.importe;
+ res.status(200).send();
+ });
+
+app.get('/api/priv/movimientos/:id', function (req, res, next) {
+ var movId = req.params.id;
+ movimientoBuscado = getMovimientoById(movId, req.usuario);
+ res.json(movimientoBuscado);
+});
+
+function getMovimientoById(id,usuario) {
+ var movimientoBuscado = movimientos.filter(function (movimiento) {
+ return movimiento.id == id && movimiento.usuario == usuario;
+ })[0];
+ return movimientoBuscado;
+}
+
+app.get('/api/priv/total', function (req, res, next) {
+ var totalUsuario = getTotalUsuario(req.usuario);
+ res.json(totalUsuario);
+});
+
+function getTotalUsuario(usuario) {
+ if(usuario===undefined) return {};
+ var totalUsuario = totales.filter(function (t) {
+ return t.usuario == usuario;
+ })[0];
+ if (totalUsuario===undefined) {
+ totalUsuario = {
+ usuario : usuario,
+ ingresos: 0,
+ gastos: 0
+ };
+ totales.push(totalUsuario);
+ }
+ return totalUsuario;
+}
+
+
+console.log('steady');
+app.listen(3000);
+console.log('go');
diff --git a/21-organizacion/client/app.js b/21-organizacion/client/app.js
new file mode 100644
index 0000000..5d0d216
--- /dev/null
+++ b/21-organizacion/client/app.js
@@ -0,0 +1,35 @@
+angular.module('controlCajaApp', ['ui.router','ngCookies', 'ngResource', 'abFiltros','abDirectivas']);
+
+angular.module('controlCajaApp').config(function ($stateProvider,$locationProvider) {
+ $stateProvider
+ .state('total', {
+ url: '/',
+ controller: 'CajaCtrl as caja',
+ templateUrl: 'total.html'
+ })
+ .state('nuevo', {
+ url: '/nuevo',
+ controller: 'CajaCtrl as caja',
+ templateUrl: 'nuevo.html'
+ })
+ .state('lista', {
+ url: '/lista',
+ controller: 'CajaCtrl as caja',
+ templateUrl: 'lista.html'
+ })
+ .state('movimiento', {
+ url: '/movimiento/:id',
+ controller: 'MovimientoCtrl as vm',
+ templateUrl: 'movimiento.html'
+ })
+ .state('registro', {
+ url: '/registro',
+ controller: 'RegistroCtrl as registro',
+ templateUrl: 'registro.html'
+ })
+ .state('not-found', {
+ url: '*path',
+ controller: 'CajaCtrl as caja',
+ templateUrl: 'total.html'
+ });
+});
diff --git a/21-organizacion/client/appHttpLog.js b/21-organizacion/client/appHttpLog.js
new file mode 100644
index 0000000..0357f95
--- /dev/null
+++ b/21-organizacion/client/appHttpLog.js
@@ -0,0 +1,21 @@
+(function () {
+
+ function configuradorInterceptores($httpProvider) {
+ $httpProvider.interceptors.push(funcionInterceptoraLog);
+ }
+
+ function funcionInterceptoraLog($log) {
+
+ var interceptor = {};
+ interceptor.request = function (request) {
+ $log.info('request:' + request.url);
+ return request ;
+ };
+ interceptor.responseError = function (response) {
+ $log.error("excepción: " + response.status + " de :" + response.config.url);
+ }
+ return interceptor;
+ }
+
+ angular.module('controlCajaApp').config(configuradorInterceptores);
+}());
diff --git a/21-organizacion/client/appSecurity.js b/21-organizacion/client/appSecurity.js
new file mode 100644
index 0000000..5948427
--- /dev/null
+++ b/21-organizacion/client/appSecurity.js
@@ -0,0 +1,29 @@
+(function () {
+ function configuradorInterceptores($httpProvider) {
+ $httpProvider.interceptors.push(funcionInterceptoraSeguridad);
+ }
+
+ function funcionInterceptoraSeguridad($q, $injector, $cookieStore, $rootScope) {
+
+ var interceptor = {};
+ interceptor.request = function (request) {
+ request.headers["sessionId"] = $cookieStore.get("sessionId");
+ return request || $q.when(request);
+ };
+ interceptor.responseError = function (response) {
+ var state = $injector.get('$state');
+ if (response.status === 401) {
+ $rootScope.mensaje = "No hay derecho!!!";
+ state.go('registro');
+ } else if (response.status === 419) {
+ $rootScope.mensaje = "Estoy caduco!!!";
+ $cookieStore.remove("sessionId")
+ state.go('registro');
+ };
+ return $q.reject(response);
+ }
+ return interceptor;
+ }
+
+ angular.module('controlCajaApp').config(configuradorInterceptores);
+}());
diff --git a/21-organizacion/client/cajaCtrl.js b/21-organizacion/client/cajaCtrl.js
new file mode 100644
index 0000000..60ab90e
--- /dev/null
+++ b/21-organizacion/client/cajaCtrl.js
@@ -0,0 +1,34 @@
+(function () {
+ var cajaCtrl = function (maestrosFactory, movimientosFactory) {
+ var vm = this;
+
+ vm.titulo = "Controla tu Cash Flow";
+
+ vm.maestros = maestrosFactory.get();
+
+ vm.nuevoMovimiento = new movimientosFactory.movimientos();
+ vm.nuevoMovimiento.esIngreso = 1;
+ vm.nuevoMovimiento.fecha = new Date();
+
+ vm.movimientos = movimientosFactory.movimientos.query();
+
+ vm.total = movimientosFactory.total.get();
+
+ vm.guardarMovimiento = function () {
+ vm.nuevoMovimiento.tipo = vm.tipo(vm.nuevoMovimiento);
+ vm.nuevoMovimiento.$save()
+ .then(function (postedData) {
+ vm.movimientos = movimientosFactory.movimientos.query();
+ vm.total = movimientosFactory.total.get();
+ vm.nuevoMovimiento.importe = 0;
+ });
+ }
+ vm.balance = function () {
+ return vm.total.ingresos - vm.total.gastos
+ }
+ vm.tipo = function (movimiento) {
+ return movimiento.esIngreso && 'Ingreso' || 'Gasto'
+ }
+ }
+ angular.module('controlCajaApp').controller('CajaCtrl', cajaCtrl);
+}());
diff --git a/21-organizacion/client/directivas.js b/21-organizacion/client/directivas.js
new file mode 100644
index 0000000..617471d
--- /dev/null
+++ b/21-organizacion/client/directivas.js
@@ -0,0 +1,74 @@
+(function () {
+ function piePagina() {
+ return {
+ restrict: 'AE',
+ replace: 'true',
+ template: '
'
+ };
+ };
+
+ function cabecera() {
+ return {
+ restrict: 'AE',
+ replace: 'true',
+ transclude: true,
+ templateUrl: '/tpl-cabecera.html'
+ };
+ };
+
+ function filaMovimiento() {
+ return {
+ restrict: 'A',
+ templateUrl: '/tpl-fila-movimiento.html',
+ scope: {
+ movimientoplantilla: "=movimientodirectiva"
+ }
+ };
+ }
+
+ function seleccionado() {
+ return {
+ link: function ($scope, element, attrs) {
+ element.bind('mouseenter', function () {
+ element.css('background-color', 'lightyellow');
+ });
+ element.bind('mouseleave', function () {
+ element.css('background-color', 'white');
+ });
+ }
+ };
+ }
+
+ function plugin() {
+ return {
+ link: function (scope, element, attrs) {
+ var init = scope.$eval(attrs.ngModel);
+ var min = scope.$eval(attrs.abSliderMin);
+ var max = scope.$eval(attrs.abSliderMax);
+ $(element).plugin({
+ value: init,
+ min: min,
+ max: max,
+ tooltip: 'hide'
+ });
+
+ scope.$watch(attrs.ngModel, function (valor) {
+ $(element).plugin('setValue', valor);
+ });
+
+ $(element).plugin().on('slide', function (evento) {
+ scope.$apply(function () {
+ scope[attrs.ngModel] = evento.value;
+ });
+ });
+ }
+ }
+ }
+
+ angular.module('abDirectivas', [])
+ .directive('abPiePagina', piePagina)
+ .directive('abCabecera', cabecera)
+ .directive('abFilaMovimiento', filaMovimiento)
+ .directive('abSeleccionado', seleccionado)
+ .directive('abPlugin', plugin);
+}());
diff --git a/21-organizacion/client/filtros.js b/21-organizacion/client/filtros.js
new file mode 100644
index 0000000..6398464
--- /dev/null
+++ b/21-organizacion/client/filtros.js
@@ -0,0 +1,71 @@
+(function () {
+
+ function limpiarCadena() {
+ var funcionFiltro = function (cadena) {
+ if (cadena) {
+ var resultado = cadena.toLowerCase();
+ var patron = /[^-A-Za-z0-9]+/g;
+ return resultado.replace(patron, '_');
+ }
+ };
+ return funcionFiltro;
+ }
+
+ function recortar() {
+ var funcionFiltro = function (cadena, largo, quitarInicio) {
+ if (!cadena) {
+ return ''
+ }
+ if (!largo) {
+ largo = 10
+ }
+ if (cadena.length <= largo) {
+ return cadena
+ }
+ if (quitarInicio) {
+ return '...' + cadena.substring(cadena.length - largo)
+ } else {
+ return cadena.substring(0, largo) + '...'
+ }
+ };
+ return funcionFiltro;
+ }
+
+ function rellenarVacios() {
+ var funcionFiltro = function (cadena) {
+ try {
+ if (!cadena || cadena === undefined || cadena.trim() === "") {
+ return '---';
+ };
+ } catch (err) {
+ return '---';
+ }
+ return cadena;
+ }
+ return funcionFiltro;
+ }
+
+ function granImporte() {
+ var funcionFiltro = function (movimientos, valorCorte) {
+ if (valorCorte) {
+ var filtrados = [];
+ for (var i = 0; i < movimientos.length; i++) {
+ var mov = movimientos[i];
+ if (mov.importe >= valorCorte) {
+ filtrados.push(mov);
+ }
+ }
+ return filtrados;
+ } else {
+ return movimientos;
+ }
+ };
+ return funcionFiltro;
+ }
+
+ angular.module('abFiltros', [])
+ .filter('abLimpiarCadena', limpiarCadena)
+ .filter('abRecortar', recortar)
+ .filter('abRellenarVacios', rellenarVacios)
+ .filter('abGranImporte', granImporte);
+}());
diff --git a/21-organizacion/client/index.html b/21-organizacion/client/index.html
new file mode 100644
index 0000000..9e98dc5
--- /dev/null
+++ b/21-organizacion/client/index.html
@@ -0,0 +1,58 @@
+
+
+
+
Control de Caja
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/21-organizacion/client/lista.html b/21-organizacion/client/lista.html
new file mode 100644
index 0000000..b79a6b7
--- /dev/null
+++ b/21-organizacion/client/lista.html
@@ -0,0 +1,28 @@
+
diff --git a/21-organizacion/client/maestrosFactory.js b/21-organizacion/client/maestrosFactory.js
new file mode 100644
index 0000000..2ba429d
--- /dev/null
+++ b/21-organizacion/client/maestrosFactory.js
@@ -0,0 +1,7 @@
+(function () {
+ var maestrosFactory = function ($resource) {
+ return $resource("/api/pub/maestros/",{},{get: {cache: true}});
+ };
+
+ angular.module('controlCajaApp').factory('maestrosFactory', maestrosFactory);
+}());
diff --git a/21-organizacion/client/menuCtrl.js b/21-organizacion/client/menuCtrl.js
new file mode 100644
index 0000000..14ddc17
--- /dev/null
+++ b/21-organizacion/client/menuCtrl.js
@@ -0,0 +1,8 @@
+(function () {
+ var menuCtrl = function ($state) {
+ this.isActive = function (estado) {
+ return $state.is(estado);
+ }
+ }
+ angular.module('controlCajaApp').controller('MenuCtrl',menuCtrl);
+}());
diff --git a/21-organizacion/client/movimiento.html b/21-organizacion/client/movimiento.html
new file mode 100644
index 0000000..dec2ee1
--- /dev/null
+++ b/21-organizacion/client/movimiento.html
@@ -0,0 +1,55 @@
+
diff --git a/21-organizacion/client/movimientoCtrl.js b/21-organizacion/client/movimientoCtrl.js
new file mode 100644
index 0000000..0d966da
--- /dev/null
+++ b/21-organizacion/client/movimientoCtrl.js
@@ -0,0 +1,8 @@
+(function () {
+ var movimientoCtrl = function ($stateParams, movimientosFactory) {
+ var vm = this;
+ var movId = $stateParams.id;
+ vm.movimiento = movimientosFactory.movimientos.get({id:movId});
+ }
+ angular.module('controlCajaApp').controller('MovimientoCtrl', movimientoCtrl);
+}());
diff --git a/21-organizacion/client/movimientosFactory.js b/21-organizacion/client/movimientosFactory.js
new file mode 100644
index 0000000..995a40e
--- /dev/null
+++ b/21-organizacion/client/movimientosFactory.js
@@ -0,0 +1,15 @@
+(function () {
+
+ var movimientosFactory = function ($resource) {
+
+ var factory = {};
+ factory.movimientos = $resource("/api/priv/movimientos/:id", {
+ id: "@id"
+ })
+ factory.total = $resource("/api/priv/total/");
+
+ return factory;
+ };
+
+ angular.module('controlCajaApp').factory('movimientosFactory', movimientosFactory);
+}());
diff --git a/21-organizacion/client/not-found.html b/21-organizacion/client/not-found.html
new file mode 100644
index 0000000..85210c2
--- /dev/null
+++ b/21-organizacion/client/not-found.html
@@ -0,0 +1,3 @@
+
+ No se ha encontrado lo que buscabas
+
diff --git a/21-organizacion/client/nuevo.html b/21-organizacion/client/nuevo.html
new file mode 100644
index 0000000..2b3df9a
--- /dev/null
+++ b/21-organizacion/client/nuevo.html
@@ -0,0 +1,68 @@
+
+ Introduce tus movimientos.
+
+
diff --git a/21-organizacion/client/registro.html b/21-organizacion/client/registro.html
new file mode 100644
index 0000000..6d32793
--- /dev/null
+++ b/21-organizacion/client/registro.html
@@ -0,0 +1,37 @@
+
diff --git a/21-organizacion/client/registroCtrl.js b/21-organizacion/client/registroCtrl.js
new file mode 100644
index 0000000..7344f28
--- /dev/null
+++ b/21-organizacion/client/registroCtrl.js
@@ -0,0 +1,26 @@
+(function () {
+ var registroCtrl = function ($rootScope, $state, $http, $cookieStore) {
+ var urlBase = "http://localhost:3000/api/";
+ var vm = this;
+ vm.usuario = {};
+ vm.entrar = function () {
+ $http.post(urlBase + 'sesiones/', vm.usuario)
+ .success(function (data) {
+ $rootScope.nombre = vm.usuario.email;
+ $rootScope.mensaje = 'recién entrado';
+ $cookieStore.put("sessionId", data);
+ $state.go("total");
+ });
+ }
+ vm.registrar = function () {
+ $http.post(urlBase + 'usuarios/', vm.usuario)
+ .success(function (data) {
+ $rootScope.nombre = vm.usuario.email;
+ $rootScope.mensaje = 'recién creado';
+ $cookieStore.put("sessionId", data);
+ $state.go("total");
+ });
+ }
+ }
+ angular.module('controlCajaApp').controller('RegistroCtrl', registroCtrl);
+}());
diff --git a/21-organizacion/client/total.html b/21-organizacion/client/total.html
new file mode 100644
index 0000000..3f8af9e
--- /dev/null
+++ b/21-organizacion/client/total.html
@@ -0,0 +1,34 @@
+
+ Comprueba de dónde viene y a dónde va tu dinero.
+
+
+
+
+
+ {{ caja.total.ingresos | number:2 }} €
+
+
+ Total ingresos
+ Acumulado
+
+
+
+
+ {{ caja.total.gastos | number:2 }} €
+
+
+ Total gastos
+ Acumulado
+
+
+
+
+ {{ caja.balance() | number:2 }} €
+
+
+ Balance
+ Ingresos-Gastos
+
+
+
+
diff --git a/21-organizacion/client/tpl-cabecera.html b/21-organizacion/client/tpl-cabecera.html
new file mode 100644
index 0000000..5245a7b
--- /dev/null
+++ b/21-organizacion/client/tpl-cabecera.html
@@ -0,0 +1,6 @@
+
diff --git a/21-organizacion/client/tpl-fila-movimiento.html b/21-organizacion/client/tpl-fila-movimiento.html
new file mode 100644
index 0000000..2c3f4c0
--- /dev/null
+++ b/21-organizacion/client/tpl-fila-movimiento.html
@@ -0,0 +1,10 @@
+
+ {{movimientoplantilla.id }}
+ {{movimientoplantilla.fecha | date}}
+ {{movimientoplantilla.tipo}}
+ {{movimientoplantilla.categoria}}
+
+ {{movimientoplantilla.importe | number:2}} €
+
+
+
diff --git a/21-organizacion/client/tpl-valoracion.html b/21-organizacion/client/tpl-valoracion.html
new file mode 100644
index 0000000..63a8bcd
--- /dev/null
+++ b/21-organizacion/client/tpl-valoracion.html
@@ -0,0 +1,12 @@
+
+
+
+ *
+
+
+
diff --git a/21-organizacion/client/valoracion.js b/21-organizacion/client/valoracion.js
new file mode 100644
index 0000000..d055fd8
--- /dev/null
+++ b/21-organizacion/client/valoracion.js
@@ -0,0 +1,38 @@
+(function () {
+ var valoracion = function () {
+ return {
+ restrict: 'AE',
+ templateUrl: '/tpl-valoracion.html',
+ scope: {
+ valor: '=',
+ max: '@',
+ soloLectura: '@'
+ },
+ link: function (scope, elem, attrs) {
+ function actualizar() {
+ if(!scope.valor)scope.valor=1;
+ scope.estrellas = [];
+ for (var i = 0; i < scope.max; i++) {
+ var estrella = {
+ marcada: (i < scope.valor)
+ };
+ scope.estrellas.push(estrella);
+ }
+ };
+
+ scope.marcar = function (indice) {
+ if (scope.soloLectura && scope.soloLectura === 'true') {
+ return;
+ }
+ scope.valor = indice + 1;
+ actualizar();
+ };
+ actualizar();
+ }
+ }
+ }
+
+ angular.module('abDirectivas')
+ .directive('abValoracion', valoracion);
+
+}());
diff --git a/21-organizacion/package.json b/21-organizacion/package.json
new file mode 100644
index 0000000..6028a5d
--- /dev/null
+++ b/21-organizacion/package.json
@@ -0,0 +1,15 @@
+{
+ "name": "ControlCashFlow",
+ "version": "0.0.0",
+ "description": "Controla tu flujo de caja",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "author": "Alberto Basalo",
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "express": "~4.12.1",
+ "body-parser": "~1.0.2"
+ }
+}
diff --git a/21-organizacion/recursos.md b/21-organizacion/recursos.md
new file mode 100644
index 0000000..678453c
--- /dev/null
+++ b/21-organizacion/recursos.md
@@ -0,0 +1,7 @@
+https://angular-ui.github.io/
+http://ngmodules.org/c
+http://angular-js.in/
+http://www.directiv.es/
+
+http://www.tamas.io/node-jsexpress-cors-implementation/
+http://stackoverflow.com/questions/23823010/how-to-enable-cors-in-angularjs
\ No newline at end of file
diff --git a/21-organizacion/server.js b/21-organizacion/server.js
new file mode 100644
index 0000000..8d3fc97
--- /dev/null
+++ b/21-organizacion/server.js
@@ -0,0 +1,187 @@
+var express = require('express');
+var bodyParser = require('body-parser');
+
+var app = express();
+app.use(bodyParser());
+app.use(express.static(__dirname + '/client'));
+
+app.use(function (peticion, respuesta, siguiente) {
+ console.log("recibida petición: " + peticion.url);
+ if (peticion.body && Object.keys(peticion.body).length>0) {
+ console.log("body: " + JSON.stringify(peticion.body));
+ }
+ siguiente();
+ });
+
+console.log('ready');
+
+var maxId = 0;
+var movimientos = [];
+var totales = [];
+
+var usuarios = [];
+var sesiones = [];
+
+
+app.use('/api/priv/', function (req, res, next) {
+ var sessionId = req.get('sessionId');
+ var sesion = getSesion(sessionId);
+ if (sesion) {
+ if (esSesionValida(sesion)) {
+ sesion.timeStamp = new Date();
+ req.usuario = sesion.email;
+ next();
+ } else {
+ console.log('Sesión caducada:' + JSON.stringify(sesion));
+ res.status(419).send('Sesión caducada');
+ }
+ } else {
+ res.status(401).send('Credencial inválida');
+ }
+
+});
+
+
+
+
+
+// API - REST
+// SECURITY
+app.route('/api/usuarios')
+ .get(function (req, res, next) {
+ // Esto devuelve la lista completa de usuarios y contraseñas
+ // PELIGRO: Usar sólo a modo de debug mientras desarrollamos
+ res.json(usuarios);
+ })
+ .post(function (req, res, next) {
+ var usuario = req.body;
+ if (existeUsuario(usuario)) {
+ console.log('email ya registrado:' + usuario.email);
+ res.status(409).send('email ' + usuario.email + ' ya registrado');
+ } else {
+ console.log('registrado:' + usuario.email);
+ usuarios.push(usuario);
+ res.json(newSession(usuario.email));
+ }
+ });
+
+// Gestión de sesiones: listado y login
+app.route('/api/sesiones')
+ .get(function (req, res, next) {
+ // Esto devuelve la lista completa de sesiones
+ // PELIGRO: Usar sólo a modo de debug mientras desarrollamos
+ res.json(sesiones);
+ })
+ .post(function (req, res, next) {
+ var usuario = req.body;
+ if (esUsuarioValido(usuario)) {
+ console.log('aceptado:' + usuario.email);
+ res.json(newSession(usuario.email));
+ } else {
+ console.log('Credencial inválida:' + usuario.email);
+ res.status(401).send('Credencial inválida');
+ }
+ });
+
+function existeUsuario(usuario) {
+ return usuarios.some(function (u) {
+ return u.email == usuario.email;
+ });
+}
+
+function esUsuarioValido(usuario) {
+ return usuarios.filter(function (u) {
+ return u.email == usuario.email && u.password == usuario.password;
+ })[0];
+}
+
+function getSesion(sessionId) {
+ return sesiones.filter(function (s) {
+ return s.sessionId == sessionId;
+ })[0]
+}
+
+function esSesionValida(sesion) {
+ return (new Date() - sesion.timeStamp) < 20 * 60 * 1000;
+}
+
+function newSession(email) {
+ var sessionId = Math.random() * (88888) + 11111;
+ var timeStamp = new Date();
+ sesiones.push({
+ sessionId: sessionId,
+ email: email,
+ timeStamp: timeStamp
+ });
+ return sessionId;
+}
+
+
+// BUSINESS
+app.get('/api/pub/maestros', function (req, res, next) {
+ var maestros = {
+ categoriasIngresos: ['Nómina', 'Ventas', 'Intereses Depósitos'],
+ categoriasGastos: ['Hipotéca', 'Compras', 'Impuestos']
+ };
+ res.json(maestros);
+});
+
+app.route('/api/priv/movimientos')
+ .get(function (req, res, next) {
+ var movimientosUsuario = movimientos.filter(function (m) {
+ return m.usuario = req.usuario;
+ });
+ res.json(movimientosUsuario);
+ })
+ .post(function (req, res, next) {
+ var movimiento = req.body;
+ movimiento.usuario = req.usuario;
+ maxId++;
+ movimiento.id = maxId;
+ movimientos.push(movimiento);
+ var totalUsuario = getTotalUsuario(req.usuario);
+ if (movimiento.tipo == 'Ingreso')
+ totalUsuario.ingresos += movimiento.importe;
+ else
+ totalUsuario.gastos += movimiento.importe;
+ res.status(200).send();
+ });
+
+app.get('/api/priv/movimientos/:id', function (req, res, next) {
+ var movId = req.params.id;
+ movimientoBuscado = getMovimientoById(movId, req.usuario);
+ res.json(movimientoBuscado);
+});
+
+function getMovimientoById(id,usuario) {
+ var movimientoBuscado = movimientos.filter(function (movimiento) {
+ return movimiento.id == id && movimiento.usuario == usuario;
+ })[0];
+ return movimientoBuscado;
+}
+
+app.get('/api/priv/total', function (req, res, next) {
+ var totalUsuario = getTotalUsuario(req.usuario);
+ res.json(totalUsuario);
+});
+
+function getTotalUsuario(usuario) {
+ if(usuario===undefined) return {};
+ var totalUsuario = totales.filter(function (t) {
+ return t.usuario == usuario;
+ })[0];
+ if (totalUsuario===undefined) {
+ totalUsuario = {
+ usuario : usuario,
+ ingresos: 0,
+ gastos: 0
+ };
+ totales.push(totalUsuario);
+ }
+ return totalUsuario;
+}
+
+
+console.log('steady');
+app.listen(3000);
+console.log('go');
diff --git a/21b-organizacion/client/_comun/app.js b/21b-organizacion/client/_comun/app.js
new file mode 100644
index 0000000..453b61e
--- /dev/null
+++ b/21b-organizacion/client/_comun/app.js
@@ -0,0 +1,34 @@
+angular.module('controlCajaApp', ['ui.router','ngCookies', 'ngResource', 'abFiltros','abDirectivas']);
+
+angular.module('controlCajaApp').config(function ($stateProvider,$locationProvider) {
+ $stateProvider
+ .state('total', {
+ url: '/',
+ controller: 'CajaCtrl as caja',
+ templateUrl: 'controlcaja/total.html'
+ })
+ .state('nuevo', {
+ url: '/nuevo',
+ controller: 'CajaCtrl as caja',
+ templateUrl: 'controlcaja/nuevo.html'
+ })
+ .state('lista', {
+ url: '/lista',
+ controller: 'CajaCtrl as caja',
+ templateUrl: 'controlcaja/lista.html'
+ })
+ .state('movimiento', {
+ url: '/movimiento/:id',
+ controller: 'MovimientoCtrl as vm',
+ templateUrl: 'movimiento/movimiento.html'
+ })
+ .state('registro', {
+ url: '/registro',
+ controller: 'RegistroCtrl as registro',
+ templateUrl: 'registro/registro.html'
+ })
+ .state('not-found', {
+ url: '*path',
+ templateUrl: '_comun/not-found.html'
+ });
+});
diff --git a/21b-organizacion/client/_comun/appHttpLog.js b/21b-organizacion/client/_comun/appHttpLog.js
new file mode 100644
index 0000000..0357f95
--- /dev/null
+++ b/21b-organizacion/client/_comun/appHttpLog.js
@@ -0,0 +1,21 @@
+(function () {
+
+ function configuradorInterceptores($httpProvider) {
+ $httpProvider.interceptors.push(funcionInterceptoraLog);
+ }
+
+ function funcionInterceptoraLog($log) {
+
+ var interceptor = {};
+ interceptor.request = function (request) {
+ $log.info('request:' + request.url);
+ return request ;
+ };
+ interceptor.responseError = function (response) {
+ $log.error("excepción: " + response.status + " de :" + response.config.url);
+ }
+ return interceptor;
+ }
+
+ angular.module('controlCajaApp').config(configuradorInterceptores);
+}());
diff --git a/21b-organizacion/client/_comun/appSecurity.js b/21b-organizacion/client/_comun/appSecurity.js
new file mode 100644
index 0000000..5948427
--- /dev/null
+++ b/21b-organizacion/client/_comun/appSecurity.js
@@ -0,0 +1,29 @@
+(function () {
+ function configuradorInterceptores($httpProvider) {
+ $httpProvider.interceptors.push(funcionInterceptoraSeguridad);
+ }
+
+ function funcionInterceptoraSeguridad($q, $injector, $cookieStore, $rootScope) {
+
+ var interceptor = {};
+ interceptor.request = function (request) {
+ request.headers["sessionId"] = $cookieStore.get("sessionId");
+ return request || $q.when(request);
+ };
+ interceptor.responseError = function (response) {
+ var state = $injector.get('$state');
+ if (response.status === 401) {
+ $rootScope.mensaje = "No hay derecho!!!";
+ state.go('registro');
+ } else if (response.status === 419) {
+ $rootScope.mensaje = "Estoy caduco!!!";
+ $cookieStore.remove("sessionId")
+ state.go('registro');
+ };
+ return $q.reject(response);
+ }
+ return interceptor;
+ }
+
+ angular.module('controlCajaApp').config(configuradorInterceptores);
+}());
diff --git a/21b-organizacion/client/_comun/menuCtrl.js b/21b-organizacion/client/_comun/menuCtrl.js
new file mode 100644
index 0000000..14ddc17
--- /dev/null
+++ b/21b-organizacion/client/_comun/menuCtrl.js
@@ -0,0 +1,8 @@
+(function () {
+ var menuCtrl = function ($state) {
+ this.isActive = function (estado) {
+ return $state.is(estado);
+ }
+ }
+ angular.module('controlCajaApp').controller('MenuCtrl',menuCtrl);
+}());
diff --git a/21b-organizacion/client/_comun/not-found.html b/21b-organizacion/client/_comun/not-found.html
new file mode 100644
index 0000000..85210c2
--- /dev/null
+++ b/21b-organizacion/client/_comun/not-found.html
@@ -0,0 +1,3 @@
+
+ No se ha encontrado lo que buscabas
+
diff --git a/21b-organizacion/client/controlcaja/cajaCtrl.js b/21b-organizacion/client/controlcaja/cajaCtrl.js
new file mode 100644
index 0000000..60ab90e
--- /dev/null
+++ b/21b-organizacion/client/controlcaja/cajaCtrl.js
@@ -0,0 +1,34 @@
+(function () {
+ var cajaCtrl = function (maestrosFactory, movimientosFactory) {
+ var vm = this;
+
+ vm.titulo = "Controla tu Cash Flow";
+
+ vm.maestros = maestrosFactory.get();
+
+ vm.nuevoMovimiento = new movimientosFactory.movimientos();
+ vm.nuevoMovimiento.esIngreso = 1;
+ vm.nuevoMovimiento.fecha = new Date();
+
+ vm.movimientos = movimientosFactory.movimientos.query();
+
+ vm.total = movimientosFactory.total.get();
+
+ vm.guardarMovimiento = function () {
+ vm.nuevoMovimiento.tipo = vm.tipo(vm.nuevoMovimiento);
+ vm.nuevoMovimiento.$save()
+ .then(function (postedData) {
+ vm.movimientos = movimientosFactory.movimientos.query();
+ vm.total = movimientosFactory.total.get();
+ vm.nuevoMovimiento.importe = 0;
+ });
+ }
+ vm.balance = function () {
+ return vm.total.ingresos - vm.total.gastos
+ }
+ vm.tipo = function (movimiento) {
+ return movimiento.esIngreso && 'Ingreso' || 'Gasto'
+ }
+ }
+ angular.module('controlCajaApp').controller('CajaCtrl', cajaCtrl);
+}());
diff --git a/21b-organizacion/client/controlcaja/lista.html b/21b-organizacion/client/controlcaja/lista.html
new file mode 100644
index 0000000..b79a6b7
--- /dev/null
+++ b/21b-organizacion/client/controlcaja/lista.html
@@ -0,0 +1,28 @@
+
diff --git a/21b-organizacion/client/controlcaja/nuevo.html b/21b-organizacion/client/controlcaja/nuevo.html
new file mode 100644
index 0000000..2b3df9a
--- /dev/null
+++ b/21b-organizacion/client/controlcaja/nuevo.html
@@ -0,0 +1,68 @@
+
+ Introduce tus movimientos.
+
+
diff --git a/21b-organizacion/client/controlcaja/total.html b/21b-organizacion/client/controlcaja/total.html
new file mode 100644
index 0000000..3f8af9e
--- /dev/null
+++ b/21b-organizacion/client/controlcaja/total.html
@@ -0,0 +1,34 @@
+
+ Comprueba de dónde viene y a dónde va tu dinero.
+
+
+
+
+
+ {{ caja.total.ingresos | number:2 }} €
+
+
+ Total ingresos
+ Acumulado
+
+
+
+
+ {{ caja.total.gastos | number:2 }} €
+
+
+ Total gastos
+ Acumulado
+
+
+
+
+ {{ caja.balance() | number:2 }} €
+
+
+ Balance
+ Ingresos-Gastos
+
+
+
+
diff --git a/21b-organizacion/client/data/maestrosFactory.js b/21b-organizacion/client/data/maestrosFactory.js
new file mode 100644
index 0000000..2ba429d
--- /dev/null
+++ b/21b-organizacion/client/data/maestrosFactory.js
@@ -0,0 +1,7 @@
+(function () {
+ var maestrosFactory = function ($resource) {
+ return $resource("/api/pub/maestros/",{},{get: {cache: true}});
+ };
+
+ angular.module('controlCajaApp').factory('maestrosFactory', maestrosFactory);
+}());
diff --git a/21b-organizacion/client/data/movimientosFactory.js b/21b-organizacion/client/data/movimientosFactory.js
new file mode 100644
index 0000000..995a40e
--- /dev/null
+++ b/21b-organizacion/client/data/movimientosFactory.js
@@ -0,0 +1,15 @@
+(function () {
+
+ var movimientosFactory = function ($resource) {
+
+ var factory = {};
+ factory.movimientos = $resource("/api/priv/movimientos/:id", {
+ id: "@id"
+ })
+ factory.total = $resource("/api/priv/total/");
+
+ return factory;
+ };
+
+ angular.module('controlCajaApp').factory('movimientosFactory', movimientosFactory);
+}());
diff --git a/21b-organizacion/client/directivas/directivas.js b/21b-organizacion/client/directivas/directivas.js
new file mode 100644
index 0000000..42c3c01
--- /dev/null
+++ b/21b-organizacion/client/directivas/directivas.js
@@ -0,0 +1,74 @@
+(function () {
+ function piePagina() {
+ return {
+ restrict: 'AE',
+ replace: 'true',
+ template: '
'
+ };
+ };
+
+ function cabecera() {
+ return {
+ restrict: 'AE',
+ replace: 'true',
+ transclude: true,
+ templateUrl: '/directivas/tpl-cabecera.html'
+ };
+ };
+
+ function filaMovimiento() {
+ return {
+ restrict: 'A',
+ templateUrl: '/directivas/tpl-fila-movimiento.html',
+ scope: {
+ movimientoplantilla: "=movimientodirectiva"
+ }
+ };
+ }
+
+ function seleccionado() {
+ return {
+ link: function ($scope, element, attrs) {
+ element.bind('mouseenter', function () {
+ element.css('background-color', 'lightyellow');
+ });
+ element.bind('mouseleave', function () {
+ element.css('background-color', 'white');
+ });
+ }
+ };
+ }
+
+ function plugin() {
+ return {
+ link: function (scope, element, attrs) {
+ var init = scope.$eval(attrs.ngModel);
+ var min = scope.$eval(attrs.abSliderMin);
+ var max = scope.$eval(attrs.abSliderMax);
+ $(element).plugin({
+ value: init,
+ min: min,
+ max: max,
+ tooltip: 'hide'
+ });
+
+ scope.$watch(attrs.ngModel, function (valor) {
+ $(element).plugin('setValue', valor);
+ });
+
+ $(element).plugin().on('slide', function (evento) {
+ scope.$apply(function () {
+ scope[attrs.ngModel] = evento.value;
+ });
+ });
+ }
+ }
+ }
+
+ angular.module('abDirectivas', [])
+ .directive('abPiePagina', piePagina)
+ .directive('abCabecera', cabecera)
+ .directive('abFilaMovimiento', filaMovimiento)
+ .directive('abSeleccionado', seleccionado)
+ .directive('abPlugin', plugin);
+}());
diff --git a/21b-organizacion/client/directivas/tpl-cabecera.html b/21b-organizacion/client/directivas/tpl-cabecera.html
new file mode 100644
index 0000000..5245a7b
--- /dev/null
+++ b/21b-organizacion/client/directivas/tpl-cabecera.html
@@ -0,0 +1,6 @@
+
diff --git a/21b-organizacion/client/directivas/tpl-fila-movimiento.html b/21b-organizacion/client/directivas/tpl-fila-movimiento.html
new file mode 100644
index 0000000..2c3f4c0
--- /dev/null
+++ b/21b-organizacion/client/directivas/tpl-fila-movimiento.html
@@ -0,0 +1,10 @@
+
+ {{movimientoplantilla.id }}
+ {{movimientoplantilla.fecha | date}}
+ {{movimientoplantilla.tipo}}
+ {{movimientoplantilla.categoria}}
+
+ {{movimientoplantilla.importe | number:2}} €
+
+
+
diff --git a/21b-organizacion/client/directivas/valoracion/tpl-valoracion.html b/21b-organizacion/client/directivas/valoracion/tpl-valoracion.html
new file mode 100644
index 0000000..63a8bcd
--- /dev/null
+++ b/21b-organizacion/client/directivas/valoracion/tpl-valoracion.html
@@ -0,0 +1,12 @@
+
+
+
+ *
+
+
+
diff --git a/21b-organizacion/client/directivas/valoracion/valoracion.js b/21b-organizacion/client/directivas/valoracion/valoracion.js
new file mode 100644
index 0000000..d4ad49d
--- /dev/null
+++ b/21b-organizacion/client/directivas/valoracion/valoracion.js
@@ -0,0 +1,38 @@
+(function () {
+ var valoracion = function () {
+ return {
+ restrict: 'AE',
+ templateUrl: '/directivas/valoracion/tpl-valoracion.html',
+ scope: {
+ valor: '=',
+ max: '@',
+ soloLectura: '@'
+ },
+ link: function (scope, elem, attrs) {
+ function actualizar() {
+ if(!scope.valor)scope.valor=1;
+ scope.estrellas = [];
+ for (var i = 0; i < scope.max; i++) {
+ var estrella = {
+ marcada: (i < scope.valor)
+ };
+ scope.estrellas.push(estrella);
+ }
+ };
+
+ scope.marcar = function (indice) {
+ if (scope.soloLectura && scope.soloLectura === 'true') {
+ return;
+ }
+ scope.valor = indice + 1;
+ actualizar();
+ };
+ actualizar();
+ }
+ }
+ }
+
+ angular.module('abDirectivas')
+ .directive('abValoracion', valoracion);
+
+}());
diff --git a/21b-organizacion/client/filtros/filtros.js b/21b-organizacion/client/filtros/filtros.js
new file mode 100644
index 0000000..6398464
--- /dev/null
+++ b/21b-organizacion/client/filtros/filtros.js
@@ -0,0 +1,71 @@
+(function () {
+
+ function limpiarCadena() {
+ var funcionFiltro = function (cadena) {
+ if (cadena) {
+ var resultado = cadena.toLowerCase();
+ var patron = /[^-A-Za-z0-9]+/g;
+ return resultado.replace(patron, '_');
+ }
+ };
+ return funcionFiltro;
+ }
+
+ function recortar() {
+ var funcionFiltro = function (cadena, largo, quitarInicio) {
+ if (!cadena) {
+ return ''
+ }
+ if (!largo) {
+ largo = 10
+ }
+ if (cadena.length <= largo) {
+ return cadena
+ }
+ if (quitarInicio) {
+ return '...' + cadena.substring(cadena.length - largo)
+ } else {
+ return cadena.substring(0, largo) + '...'
+ }
+ };
+ return funcionFiltro;
+ }
+
+ function rellenarVacios() {
+ var funcionFiltro = function (cadena) {
+ try {
+ if (!cadena || cadena === undefined || cadena.trim() === "") {
+ return '---';
+ };
+ } catch (err) {
+ return '---';
+ }
+ return cadena;
+ }
+ return funcionFiltro;
+ }
+
+ function granImporte() {
+ var funcionFiltro = function (movimientos, valorCorte) {
+ if (valorCorte) {
+ var filtrados = [];
+ for (var i = 0; i < movimientos.length; i++) {
+ var mov = movimientos[i];
+ if (mov.importe >= valorCorte) {
+ filtrados.push(mov);
+ }
+ }
+ return filtrados;
+ } else {
+ return movimientos;
+ }
+ };
+ return funcionFiltro;
+ }
+
+ angular.module('abFiltros', [])
+ .filter('abLimpiarCadena', limpiarCadena)
+ .filter('abRecortar', recortar)
+ .filter('abRellenarVacios', rellenarVacios)
+ .filter('abGranImporte', granImporte);
+}());
diff --git a/21b-organizacion/client/index.html b/21b-organizacion/client/index.html
new file mode 100644
index 0000000..0947174
--- /dev/null
+++ b/21b-organizacion/client/index.html
@@ -0,0 +1,58 @@
+
+
+
+
Control de Caja
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/21b-organizacion/client/movimiento/movimiento.html b/21b-organizacion/client/movimiento/movimiento.html
new file mode 100644
index 0000000..dec2ee1
--- /dev/null
+++ b/21b-organizacion/client/movimiento/movimiento.html
@@ -0,0 +1,55 @@
+
diff --git a/21b-organizacion/client/movimiento/movimientoCtrl.js b/21b-organizacion/client/movimiento/movimientoCtrl.js
new file mode 100644
index 0000000..0d966da
--- /dev/null
+++ b/21b-organizacion/client/movimiento/movimientoCtrl.js
@@ -0,0 +1,8 @@
+(function () {
+ var movimientoCtrl = function ($stateParams, movimientosFactory) {
+ var vm = this;
+ var movId = $stateParams.id;
+ vm.movimiento = movimientosFactory.movimientos.get({id:movId});
+ }
+ angular.module('controlCajaApp').controller('MovimientoCtrl', movimientoCtrl);
+}());
diff --git a/21b-organizacion/client/registro/registro.html b/21b-organizacion/client/registro/registro.html
new file mode 100644
index 0000000..a224951
--- /dev/null
+++ b/21b-organizacion/client/registro/registro.html
@@ -0,0 +1,37 @@
+
diff --git a/21b-organizacion/client/registro/registroCtrl.js b/21b-organizacion/client/registro/registroCtrl.js
new file mode 100644
index 0000000..7344f28
--- /dev/null
+++ b/21b-organizacion/client/registro/registroCtrl.js
@@ -0,0 +1,26 @@
+(function () {
+ var registroCtrl = function ($rootScope, $state, $http, $cookieStore) {
+ var urlBase = "http://localhost:3000/api/";
+ var vm = this;
+ vm.usuario = {};
+ vm.entrar = function () {
+ $http.post(urlBase + 'sesiones/', vm.usuario)
+ .success(function (data) {
+ $rootScope.nombre = vm.usuario.email;
+ $rootScope.mensaje = 'recién entrado';
+ $cookieStore.put("sessionId", data);
+ $state.go("total");
+ });
+ }
+ vm.registrar = function () {
+ $http.post(urlBase + 'usuarios/', vm.usuario)
+ .success(function (data) {
+ $rootScope.nombre = vm.usuario.email;
+ $rootScope.mensaje = 'recién creado';
+ $cookieStore.put("sessionId", data);
+ $state.go("total");
+ });
+ }
+ }
+ angular.module('controlCajaApp').controller('RegistroCtrl', registroCtrl);
+}());
diff --git a/21b-organizacion/package.json b/21b-organizacion/package.json
new file mode 100644
index 0000000..6028a5d
--- /dev/null
+++ b/21b-organizacion/package.json
@@ -0,0 +1,15 @@
+{
+ "name": "ControlCashFlow",
+ "version": "0.0.0",
+ "description": "Controla tu flujo de caja",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "author": "Alberto Basalo",
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "express": "~4.12.1",
+ "body-parser": "~1.0.2"
+ }
+}
diff --git a/21b-organizacion/server.js b/21b-organizacion/server.js
new file mode 100644
index 0000000..73725a9
--- /dev/null
+++ b/21b-organizacion/server.js
@@ -0,0 +1,10 @@
+var app = require('./server/config.js').configApp();
+require('./server/seguridad.js').seguridad(app);
+console.log('ready');
+
+require('./server/maestros.js').routeMaestros(app);
+require('./server/movimientos.js').routeMovimientos(app);
+console.log('steady');
+
+app.listen(3000);
+console.log('go');
diff --git a/21b-organizacion/server/config.js b/21b-organizacion/server/config.js
new file mode 100644
index 0000000..4f70de7
--- /dev/null
+++ b/21b-organizacion/server/config.js
@@ -0,0 +1,21 @@
+module.exports.configApp = function () {
+
+ var express = require('express');
+ var bodyParser = require('body-parser');
+
+ var app = express();
+
+ app.use(bodyParser());
+ app.use(express.static(__dirname + './../client'));
+
+ app.use(function (peticion, respuesta, siguiente) {
+ console.log("recibida petición: " + peticion.url);
+ if (peticion.body && Object.keys(peticion.body).length > 0) {
+ console.log("body: " + JSON.stringify(peticion.body));
+ }
+ siguiente();
+ });
+
+ return app;
+
+}
diff --git a/21b-organizacion/server/maestros.js b/21b-organizacion/server/maestros.js
new file mode 100644
index 0000000..9560cb1
--- /dev/null
+++ b/21b-organizacion/server/maestros.js
@@ -0,0 +1,9 @@
+module.exports.routeMaestros = function (app) {
+ app.get('/api/pub/maestros', function (req, res, next) {
+ var maestros = {
+ categoriasIngresos: ['Nómina', 'Ventas', 'Intereses Depósitos'],
+ categoriasGastos: ['Hipotéca', 'Compras', 'Impuestos']
+ };
+ res.json(maestros);
+ });
+}
diff --git a/21b-organizacion/server/movimientos.js b/21b-organizacion/server/movimientos.js
new file mode 100644
index 0000000..c49af25
--- /dev/null
+++ b/21b-organizacion/server/movimientos.js
@@ -0,0 +1,63 @@
+var maxId = 0;
+var movimientos = [];
+var totales = [];
+
+module.exports.routeMovimientos = function (app) {
+
+
+ app.route('/api/priv/movimientos')
+ .get(function (req, res, next) {
+ var movimientosUsuario = movimientos.filter(function (m) {
+ return m.usuario = req.usuario;
+ });
+ res.json(movimientosUsuario);
+ })
+ .post(function (req, res, next) {
+ var movimiento = req.body;
+ movimiento.usuario = req.usuario;
+ maxId++;
+ movimiento.id = maxId;
+ movimientos.push(movimiento);
+ var totalUsuario = getTotalUsuario(req.usuario);
+ if (movimiento.tipo == 'Ingreso')
+ totalUsuario.ingresos += movimiento.importe;
+ else
+ totalUsuario.gastos += movimiento.importe;
+ res.status(200).send();
+ });
+
+ app.get('/api/priv/movimientos/:id', function (req, res, next) {
+ var movId = req.params.id;
+ movimientoBuscado = getMovimientoById(movId, req.usuario);
+ res.json(movimientoBuscado);
+ });
+
+ function getMovimientoById(id, usuario) {
+ var movimientoBuscado = movimientos.filter(function (movimiento) {
+ return movimiento.id == id && movimiento.usuario == usuario;
+ })[0];
+ return movimientoBuscado;
+ }
+
+ app.get('/api/priv/total', function (req, res, next) {
+ var totalUsuario = getTotalUsuario(req.usuario);
+ res.json(totalUsuario);
+ });
+
+ function getTotalUsuario(usuario) {
+ if (usuario === undefined) return {};
+ var totalUsuario = totales.filter(function (t) {
+ return t.usuario == usuario;
+ })[0];
+ if (totalUsuario === undefined) {
+ totalUsuario = {
+ usuario: usuario,
+ ingresos: 0,
+ gastos: 0
+ };
+ totales.push(totalUsuario);
+ }
+ return totalUsuario;
+ }
+
+}
diff --git a/21b-organizacion/server/seguridad.js b/21b-organizacion/server/seguridad.js
new file mode 100644
index 0000000..3c2e4f9
--- /dev/null
+++ b/21b-organizacion/server/seguridad.js
@@ -0,0 +1,94 @@
+var usuarios = [];
+var sesiones = [];
+
+module.exports.seguridad = function (app) {
+
+ app.use('/api/priv/', function (req, res, next) {
+ var sessionId = req.get('sessionId');
+ var sesion = getSesion(sessionId);
+ if (sesion) {
+ if (esSesionValida(sesion)) {
+ sesion.timeStamp = new Date();
+ req.usuario = sesion.email;
+ next();
+ } else {
+ console.log('Sesión caducada:' + JSON.stringify(sesion));
+ res.status(419).send('Sesión caducada');
+ }
+ } else {
+ res.status(401).send('Credencial inválida');
+ }
+ });
+ // API - REST
+ // SECURITY
+ app.route('/api/usuarios')
+ .get(function (req, res, next) {
+ // Esto devuelve la lista completa de usuarios y contraseñas
+ // PELIGRO: Usar sólo a modo de debug mientras desarrollamos
+ res.json(usuarios);
+ })
+ .post(function (req, res, next) {
+ var usuario = req.body;
+ if (existeUsuario(usuario)) {
+ console.log('email ya registrado:' + usuario.email);
+ res.status(409).send('email ' + usuario.email + ' ya registrado');
+ } else {
+ console.log('registrado:' + usuario.email);
+ usuarios.push(usuario);
+ res.json(newSession(usuario.email));
+ }
+ });
+
+ // Gestión de sesiones: listado y login
+ app.route('/api/sesiones')
+ .get(function (req, res, next) {
+ // Esto devuelve la lista completa de sesiones
+ // PELIGRO: Usar sólo a modo de debug mientras desarrollamos
+ res.json(sesiones);
+ })
+ .post(function (req, res, next) {
+ var usuario = req.body;
+ if (esUsuarioValido(usuario)) {
+ console.log('aceptado:' + usuario.email);
+ res.json(newSession(usuario.email));
+ } else {
+ console.log('Credencial inválida:' + usuario.email);
+ res.status(401).send('Credencial inválida');
+ }
+ });
+
+ function existeUsuario(usuario) {
+ return usuarios.some(function (u) {
+ return u.email == usuario.email;
+ });
+ }
+
+ function esUsuarioValido(usuario) {
+ return usuarios.filter(function (u) {
+ return u.email == usuario.email && u.password == usuario.password;
+ })[0];
+ }
+
+ function getSesion(sessionId) {
+ return sesiones.filter(function (s) {
+ return s.sessionId == sessionId;
+ })[0]
+ }
+
+ function esSesionValida(sesion) {
+ return (new Date() - sesion.timeStamp) < 20 * 60 * 1000;
+ }
+
+ function newSession(email) {
+ var sessionId = Math.random() * (88888) + 11111;
+ var timeStamp = new Date();
+ sesiones.push({
+ sessionId: sessionId,
+ email: email,
+ timeStamp: timeStamp
+ });
+ return sessionId;
+ }
+
+
+}