This commit is contained in:
Alberto Basalo 2015-05-25 18:08:55 +02:00
Родитель 4dd94aa5d5
Коммит 32bb9fb2ab
116 изменённых файлов: 4207 добавлений и 0 удалений

Просмотреть файл

@ -0,0 +1,35 @@
angular.module('controlCajaApp', ['ui.router','ngCookies', 'ngResource']);
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', // declaracion de parametros en rutas
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'
});
});

Просмотреть файл

@ -0,0 +1,23 @@
(function () {
function configuradorInterceptores($httpProvider) {
$httpProvider.interceptors.push(funcionInterceptoraLog);
}
// Esta función se especializa en escribir en la consola
// información sobre llamadas http
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);
}());

Просмотреть файл

@ -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);
}());

Просмотреть файл

@ -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);
}());

Просмотреть файл

@ -0,0 +1,53 @@
<html lang="es" ng-app="controlCajaApp">
<head>
<title>Control de Caja</title>
<link href="http://getbootstrap.com/dist/css/bootstrap.min.css" rel="stylesheet">
<meta charset="utf-8" />
<meta lang="es" />
<meta name="description" content="Ejemplo Control de Caja en AngularJS por Alberto Basalo" />
<meta name="author" content="Alberto Basalo @ Ágora Binaria" />
<meta name="application-name" content="ControlAngularJS" />
<meta name="Keywords" content="AngularJS, ejemplo, tutorial, curso" />
<link rel="author" href="https://plus.google.com/+AlbertoBasalo71" />
<!-- Bootstrap core CSS -->
<link href="http://getbootstrap.com/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<nav class="navbar navbar-default navbar-fixed-top" role="navigation" ng-controller="MenuCtrl as menu">
<div class="navbar-inner">
<div class="container">
<ul class="nav navbar-nav">
<li ng-class="{ active: menu.isActive('total') }">
<a ui-sref="total">Totales</a>
</li>
<li ng-class="{ active: menu.isActive('nuevo') }">
<a ui-sref="nuevo">Nuevo</a>
</li>
<li ng-class="{ active: menu.isActive('lista') }">
<a ui-sref="lista">Lista</a>
</li>
</ul>
</div>
</div>
</nav>
<div class="container text-center" style="padding-top:50px;" ui-view >
</div>
<!-- JavaScript References -->
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.14/angular.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.14/angular-cookies.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.14/angular-resource.min.js"></script>
<script src="http://angular-ui.github.io/ui-router/release/angular-ui-router.min.js"></script>
<script src="app.js"></script>
<!-- Necesitamos colocar delante el configurador del Log, para saber que ocurre pase lo que pase -->
<script src="appHttpLog.js"></script>
<script src="appSecurity.js"></script>
<script src="registroCtrl.js"></script>
<script src="menuCtrl.js"></script>
<script src="cajaCtrl.js"></script>
<!-- El controlador del estado movimiento -->
<script src="movimientoCtrl.js"></script>
<script src="movimientosFactory.js"></script>
<script src="maestrosFactory.js"></script>
</body>
</html>

Просмотреть файл

@ -0,0 +1,31 @@
<section name="Lista">
<p class="lead">Estos son tus movimientos recientes.</p>
<br>
<label class="control-label" for="importe">Filtrar:</label>
<input type="text" name="filtro" placeholder="qué buscas?" class="input" ng-model="valorBuscado">
<table class="table">
<thead>
<tr>
<!-- Nueva columna para el Id-->
<th>Id</th>
<th><a href="" ng-click="campo = 'fecha'; sentido = campo == 'fecha' && !sentido">Fecha</a>
</th>
<th>Tipo</th>
<th>Categoría</th>
<th><a href="" ng-click="campo = 'importe'; sentido = campo == 'importe' && !sentido">Importe</a>
</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="movimiento in caja.movimientos | filter:valorBuscado | orderBy:campo:sentido">
<!-- Nueva columna para el Id, con enlace para ver ese movimiento en otra página-->
<!-- El enlace se formula como un cambio de estado con parámetros-->
<td class="text-left"><a ui-sref="movimiento({id: movimiento.id})">{{movimiento.id }}</a> </td>
<td class="text-left">{{movimiento.fecha | date}}</td>
<td class="text-left">{{movimiento.tipo}}</td>
<td class="text-left">{{movimiento.categoria}}</td>
<td class="text-left" ng-class="{'text-success': movimiento.tipo=='Ingreso', 'text-danger' : movimiento.tipo=='Gasto'}">{{movimiento.importe | number:2}} €</td>
</tr>
</tbody>
</table>
</section>

Просмотреть файл

@ -0,0 +1,9 @@
(function () {
var maestrosFactory =   function ($resource)  {
// Los resursos son totalmente configurables
// Una de sus utilidades de 'fábrica' de una caché simple pero potente
return $resource("/api/pub/maestros/",{},{get: {cache: true}});
};
angular.module('controlCajaApp').factory('maestrosFactory', maestrosFactory);
}());

Просмотреть файл

@ -0,0 +1,8 @@
(function () {
var menuCtrl = function ($state) {
this.isActive = function (estado) {
return $state.is(estado);
}
}
angular.module('controlCajaApp').controller('MenuCtrl',menuCtrl);
}());

Просмотреть файл

@ -0,0 +1,41 @@
<section name="verMovimiento" class="row-fluid">
<form class="form-horizontal text-left">
<fieldset>
<div id="legend">
<legend class="">Ver el movimiento {{ vm.movimiento.id}} </legend>
</div>
<div class="row-fluid">
<div class="col-xs-12 col-sm-6">
<div class="control-group">
<label class="control-label" for="tipo">Tipo</label>
<div class="controls">
<div class="btn-group">
{{ vm.movimiento.tipo}}
</div>
</div>
</div>
<div class="control-group">
<label class="control-label" for="categ">Categoría</label>
<div class="controls">
{{ vm.movimiento.categoria }}
</div>
</div>
</div>
<div class="col-xs-12 col-sm-6">
<div class="control-group">
<label class="control-label" for="fecha">Fecha</label>
<div class="controls">
{{ vm.movimiento.fecha || date }}
</div>
</div>
<div class="control-group">
<label class="control-label" for="importe">Importe</label>
<div class="controls">
{{ vm.movimiento.importe }}
</div>
</div>
</div>
</div>
</fieldset>
</form>
</section>

Просмотреть файл

@ -0,0 +1,12 @@
(function () {
// $stateParams es el servicio de ui-router para acceder a los parámetros de la ruta
var movimientoCtrl = function ($stateParams, movimientosFactory) {
var vm = this;
// El acceso es por nombre de parámetro
var movId = $stateParams.id;
// Se llama al recurso expuesto por la factoría de recursos
// enviándole el objeto que sirve de consulta al método get
vm.movimiento = movimientosFactory.movimientos.get({id:movId});
}
angular.module('controlCajaApp').controller('MovimientoCtrl', movimientoCtrl);
}());

Просмотреть файл

@ -0,0 +1,25 @@
(function () {
var movimientosFactory =   function ($resource)  {
var factory  =   {};
// $resource("/api/priv/movimientos/");
// El uso de parametros en los recursos debe especificarse
// Indicando el nombre local y el remoto
// En la url va el nombre remoto
// En el objeto se repute ese nombre como clave y se dice que propiedad será la que lo lleve
// La propiedad empieza por @
factory.movimientos =  $resource("/api/priv/movimientos/:id", {
id: "@id"
})
factory.total = $resource("/api/priv/total/");
return factory;
};
angular.module('controlCajaApp').factory('movimientosFactory', movimientosFactory);
}());

Просмотреть файл

@ -0,0 +1,3 @@
<section name="total">
<h2>No se ha encontrado lo que buscabas</h2>
</section>

Просмотреть файл

@ -0,0 +1,53 @@
<section name="nuevoMovimiento" class="row-fluid">
<form class="form-horizontal text-left">
<fieldset>
<div id="legend">
<legend class="">Introduce tus movimientos</legend>
</div>
<div class="row-fluid">
<div class="col-xs-12 col-sm-6">
<div class="control-group">
<label class="control-label" for="tipo">Tipo</label>
<div class="controls">
<div class="btn-group">
<button type="button" class="btn btn-success" ng-class="{'active':caja.nuevoMovimiento.esIngreso==1}" ng-click="caja.nuevoMovimiento.esIngreso=1; caja.nuevoMovimiento.esGasto=0">
<span ng-class="{'small':caja.nuevoMovimiento.esIngreso==0}">+ Ingreso</span>
</button>
<button type="button" class="btn btn-danger" ng-class="{'active':caja.nuevoMovimiento.esGasto==1}" ng-click="caja.nuevoMovimiento.esIngreso=0; caja.nuevoMovimiento.esGasto=1">
<span ng-class="{'small':caja.nuevoMovimiento.esGasto==0}">- Gasto</span>
</button>
</div>
</div>
</div>
<div class="control-group">
<label class="control-label" for="categ">Categoría</label>
<div class="controls">
<select ng-show="caja.nuevoMovimiento.esIngreso" name="categoria" ng-model="caja.nuevoMovimiento.categoria" ng-options="categoria for categoria in caja.maestros.categoriasIngresos"></select>
<select ng-show="caja.nuevoMovimiento.esGasto" name="categoria" ng-model="caja.nuevoMovimiento.categoria" ng-options="categoria for categoria in caja.maestros.categoriasGastos"></select>
</div>
</div>
</div>
<div class="col-xs-12 col-sm-6">
<div class="control-group">
<label class="control-label" for="fecha">Fecha</label>
<div class="controls">
<input type="date" name="fecha" placeholder="" class="input" ng-model="caja.nuevoMovimiento.fecha">
</div>
</div>
<div class="control-group">
<label class="control-label" for="importe">Importe</label>
<div class="controls">
<input type="number" name="importe" placeholder="" class="input" ng-model="caja.nuevoMovimiento.importe">
</div>
</div>
</div>
</div>
<div class="text-right">
<button style="margin-top: 20px" type="button" class="btn btn-lg btn-primary" ng-click="caja.guardarMovimiento()">
<span>Guardar {{ caja.tipo(caja.nuevoMovimiento) }}</span>
</button>
</div>
</fieldset>
</form>
{{caja.movimientos}}
</section>

Просмотреть файл

@ -0,0 +1,37 @@
<div class="container">
<div class="row">
<div class="span12">
<form class="form-horizontal">
<fieldset>
<div id="legend">
<legend class="">Login</legend>
</div>
<div class="control-group">
<!-- email -->
<label class="control-label" for="email">Email</label>
<div class="controls">
<input type="email" ng-model="registro.usuario.email" name="email" class="input-xlarge">
</div>
</div>
<div class="control-group">
<!-- Password-->
<label class="control-label" for="password">Password</label>
<div class="controls">
<input type="password" ng-model="registro.usuario.password" name="password" class="input-xlarge">
</div>
</div>
<div class="control-group">
<!-- Button -->
<div class="controls">
<button class="btn btn-primary" ng-click="registro.entrar()">Login</button>
</div>
<!-- Button -->
<div class="controls">
<button class="btn btn-success" ng-click="registro.registrar()">Registro</button>
</div>
</div>
</fieldset>
</form>
</div>
</div>
</div>

Просмотреть файл

@ -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);
}());

Просмотреть файл

@ -0,0 +1,38 @@
<!-- Cada sección tendrá su propia plantilla -->
<!-- No se especifíca el controlador, que será asignado por el enrutador -->
<section name="total">
<!-- Pero se supone un viweModel llamado caja-->
<h1>{{ caja.titulo }}</h1>
<p class="lead">Comprueba de dónde viene y a dónde va tu dinero.</p>
<div class="row-fluid">
<div class="row placeholders">
<div class="col-xs-8 col-sm-4 placeholder">
<h1>
<span class="label label-success">
{{ caja.total.ingresos | number:2 }} €
</span>
</h1>
<h4>Total ingresos</h4>
<span class="text-muted">Acumulado</span>
</div>
<div class="col-xs-8 col-sm-4 placeholder">
<h1>
<span class="label label-danger">
{{ caja.total.gastos | number:2 }} €
</span>
</h1>
<h4>Total gastos</h4>
<span class="text-muted">Acumulado</span>
</div>
<div class="col-xs-8 col-sm-4 placeholder">
<h1>
<span class="label " ng-class="{'label-success': caja.balance()>=0 , 'label-danger' : caja.balance()<0}">
{{ caja.balance() | number:2 }} €
</span>
</h1>
<h4>Balance</h4>
<span class="text-muted">Ingresos-Gastos</span>
</div>
</div>
</div>
</section>

15
18-servicios/package.json Normal file
Просмотреть файл

@ -0,0 +1,15 @@
{
"name": "ControlCashFlow",
"version": "0.0.0",
"description": "Controla tu flujo de caja",
"main": "server.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"
}
}

196
18-servicios/server.js Normal file
Просмотреть файл

@ -0,0 +1,196 @@
var express = require('express');
var bodyParser = require('body-parser');
var app = express();
app.use(bodyParser());
app.use(express.static(__dirname + '/client'));
// Podemos incrustar middelware propio para mostrar por consola todas las llamadas
// Por supuesto hay cientos de paquetes especializados en esta labor
//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');
// Mientras no tengamos base de datos usaremos un contador para ir generando Ids
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));
// Sintaxis mejorada de envío de códigos de estado http
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;
// Avanzamos el contador para asignar un nuevo id al movimiento
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();
});
// Para las rutas paramétircas creamos otro patrón
// El recurso sigue siendo movimientos, y el parámetro se declara con :id
// Obtención a partir de parámetros
app.get('/api/priv/movimientos/:id', function (req, res, next) {
var movId = req.params.id;
// Buscar en el array el movimiento con este 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');

Просмотреть файл

@ -0,0 +1,36 @@
// Incluimos referencia a un módulo propio, con un prefijo propio
angular.module('controlCajaApp', ['ui.router','ngCookies', 'ngResource', 'abFiltros']);
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', // declaracion de parametros en rutas
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'
});
});

Просмотреть файл

@ -0,0 +1,23 @@
(function () {
function configuradorInterceptores($httpProvider) {
$httpProvider.interceptors.push(funcionInterceptoraLog);
}
// Esta función se especializa en escribir en la consola
// información sobre llamadas http
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);
}());

Просмотреть файл

@ -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);
}());

Просмотреть файл

@ -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);
}());

Просмотреть файл

@ -0,0 +1,84 @@
(function () {
// Los filtros se declaran como funciones que a su vez devuelven... funciones
// Esas funciones internas se aplican sobre los valores,
// Tienen al menos un parámetro, que sirve de entrada
// Esta función le quita acentos, guiones bajos, y caracteres raros
// Los sustituye por guiones bajos
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;
}
// Devuelve un trozo de texto y tres puntos suspensivos... para indicar que hay más...
// Demuestra que un filtro puede tener parámetros, y cómo tratar valores por defecto.
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;
}
// Con control de errores y lógica compleja
function rellenarVacios() {
var funcionFiltro = function (cadena) {
try {
if (!cadena || cadena === undefined || cadena.trim() === "") {
return '---';
};
} catch (err) {
return '---';
}
return cadena;
}
return funcionFiltro;
}
// Permite tener filtros predeterminadois en un array
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;
}
// Podemos usar una sintaxis fluida y declarar todo en una solo línea
// Atención a la declaración de un nuevo módulo genérico
angular.module('abFiltros', [])
.filter('abLimpiarCadena', limpiarCadena)
.filter('abRecortar', recortar)
.filter('abRellenarVacios', rellenarVacios)
.filter('abGranImporte', granImporte);
}());

Просмотреть файл

@ -0,0 +1,53 @@
<html lang="es" ng-app="controlCajaApp">
<head>
<title>Control de Caja</title>
<link href="http://getbootstrap.com/dist/css/bootstrap.min.css" rel="stylesheet">
<meta charset="utf-8" />
<meta lang="es" />
<meta name="description" content="Ejemplo Control de Caja en AngularJS por Alberto Basalo" />
<meta name="author" content="Alberto Basalo @ Ágora Binaria" />
<meta name="application-name" content="ControlAngularJS" />
<meta name="Keywords" content="AngularJS, ejemplo, tutorial, curso" />
<link rel="author" href="https://plus.google.com/+AlbertoBasalo71" />
<!-- Bootstrap core CSS -->
<link href="http://getbootstrap.com/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<nav class="navbar navbar-default navbar-fixed-top" role="navigation" ng-controller="MenuCtrl as menu">
<div class="navbar-inner">
<div class="container">
<ul class="nav navbar-nav">
<li ng-class="{ active: menu.isActive('total') }">
<a ui-sref="total">Totales</a>
</li>
<li ng-class="{ active: menu.isActive('nuevo') }">
<a ui-sref="nuevo">Nuevo</a>
</li>
<li ng-class="{ active: menu.isActive('lista') }">
<a ui-sref="lista">Lista</a>
</li>
</ul>
</div>
</div>
</nav>
<div class="container text-center" style="padding-top:50px;" ui-view >
</div>
<!-- JavaScript References -->
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.14/angular.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.14/angular-cookies.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.14/angular-resource.min.js"></script>
<script src="http://angular-ui.github.io/ui-router/release/angular-ui-router.min.js"></script>
<script src="app.js"></script>
<script src="appHttpLog.js"></script>
<script src="appSecurity.js"></script>
<script src="registroCtrl.js"></script>
<script src="menuCtrl.js"></script>
<script src="cajaCtrl.js"></script>
<script src="movimientoCtrl.js"></script>
<script src="movimientosFactory.js"></script>
<script src="maestrosFactory.js"></script>
<!-- El fichero de filtros debe referenciarse -->
<script src="filtros.js"></script>
</body>
</html>

Просмотреть файл

@ -0,0 +1,33 @@
<section name="Lista">
<p class="lead">Estos son tus movimientos recientes.</p>
<br>
<label class="control-label" for="importe">Filtrar:</label>
<input type="text" name="filtro" placeholder="qué buscas?" class="input" ng-model="valorBuscado">
<button class="btn-primary" ng-click="valorCorte=0">Ver todos los movimientos</button>
<button class="btn-danger" ng-click="valorCorte=1000">Sólo grandes movimientos</button>
<table class="table">
<thead>
<tr>
<!-- Nueva columna para el Id-->
<th>Id</th>
<th><a href="" ng-click="campo = 'fecha'; sentido = campo == 'fecha' && !sentido">Fecha</a>
</th>
<th>Tipo</th>
<th>Categoría</th>
<th><a href="" ng-click="campo = 'importe'; sentido = campo == 'importe' && !sentido">Importe</a>
</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="movimiento in caja.movimientos | abGranImporte:valorCorte | filter:valorBuscado | orderBy:campo:sentido">
<!-- Nueva columna para el Id, con enlace para ver ese movimiento en otra página-->
<!-- El enlace se formula como un cambio de estado con parámetros-->
<td class="text-left"><a ui-sref="movimiento({id:movimiento.id})">{{movimiento.id }}</a> </td>
<td class="text-left">{{movimiento.fecha | date}}</td>
<td class="text-left">{{movimiento.tipo}}</td>
<td class="text-left">{{movimiento.categoria}}</td>
<td class="text-left" ng-class="{'text-success': movimiento.tipo=='Ingreso', 'text-danger' : movimiento.tipo=='Gasto'}">{{movimiento.importe | number:2}} €</td>
</tr>
</tbody>
</table>
</section>

Просмотреть файл

@ -0,0 +1,9 @@
(function () {
var maestrosFactory =   function ($resource)  {
// Los resursos son totalmente configurables
// Una de sus utilidades de 'fábrica' de una caché simple pero potente
return $resource("/api/pub/maestros/",{},{get: {cache: true}});
};
angular.module('controlCajaApp').factory('maestrosFactory', maestrosFactory);
}());

Просмотреть файл

@ -0,0 +1,8 @@
(function () {
var menuCtrl = function ($state) {
this.isActive = function (estado) {
return $state.is(estado);
}
}
angular.module('controlCajaApp').controller('MenuCtrl',menuCtrl);
}());

Просмотреть файл

@ -0,0 +1,52 @@
<section name="verMovimiento" class="row-fluid">
<form class="form-horizontal text-left">
<fieldset>
<div id="legend">
<legend class="">Ver el movimiento {{ vm.movimiento.id}} </legend>
</div>
<div class="row-fluid">
<div class="col-xs-12 col-sm-6">
<div class="control-group">
<label class="control-label" for="tipo">Tipo</label>
<div class="controls">
<div class="btn-group">
{{ vm.movimiento.tipo}}
</div>
</div>
</div>
<div class="control-group">
<label class="control-label" for="categ">Categoría</label>
<div class="controls">
{{ vm.movimiento.categoria }}
</div>
</div>
</div>
<div class="col-xs-12 col-sm-6">
<div class="control-group">
<label class="control-label" for="fecha">Fecha</label>
<div class="controls">
{{ vm.movimiento.fecha || date }}
</div>
</div>
<div class="control-group">
<label class="control-label" for="importe">Importe</label>
<div class="controls">
{{ vm.movimiento.importe }}
</div>
</div>
</div>
<div class="col-xs-12 col-sm-6">
<div class="control-group">
<label class="control-label" for="texto">Texto</label>
<div class="controls">
{{ vm.movimiento.texto | abRellenarVacios | abRecortar | abLimpiarCadena }}
</div>
</div>
<div class="control-group">
</div>
</div>
</div>
</fieldset>
</form>
</section>

Просмотреть файл

@ -0,0 +1,10 @@
(function () {
// $stateParams es el servicio de ui-router para acceder a los parámetros de la ruta
var movimientoCtrl = function ($stateParams, movimientosFactory) {
var vm = this;
// El acceso es por nombre de parámetro
var movId = $stateParams.id;
vm.movimiento = movimientosFactory.movimientos.get({id:movId});
}
angular.module('controlCajaApp').controller('MovimientoCtrl', movimientoCtrl);
}());

Просмотреть файл

@ -0,0 +1,25 @@
(function () {
var movimientosFactory =   function ($resource)  {
var factory  =   {};
// $resource("/api/priv/movimientos/");
// El uso de parametros en los recursos debe especificarse
// Indicando el nombre local y el remoto
// En la url va el nombre remoto
// En el objeto se repute ese nombre como clave y se dice que propiedad será la que lo lleve
// La propiedad empieza por @
factory.movimientos =  $resource("/api/priv/movimientos/:id", {
id: "@id"
})
factory.total = $resource("/api/priv/total/");
return factory;
};
angular.module('controlCajaApp').factory('movimientosFactory', movimientosFactory);
}());

Просмотреть файл

@ -0,0 +1,3 @@
<section name="total">
<h2>No se ha encontrado lo que buscabas</h2>
</section>

Просмотреть файл

@ -0,0 +1,65 @@
<section name="nuevoMovimiento" class="row-fluid">
<form class="form-horizontal text-left">
<fieldset>
<div id="legend">
<legend class="">Introduce tus movimientos</legend>
</div>
<div class="row-fluid">
<div class="col-xs-12 col-sm-6">
<div class="control-group">
<label class="control-label" for="tipo">Tipo</label>
<div class="controls">
<div class="btn-group">
<button type="button" class="btn btn-success" ng-class="{'active':caja.nuevoMovimiento.esIngreso==1}" ng-click="caja.nuevoMovimiento.esIngreso=1; caja.nuevoMovimiento.esGasto=0">
<span ng-class="{'small':caja.nuevoMovimiento.esIngreso==0}">+ Ingreso</span>
</button>
<button type="button" class="btn btn-danger" ng-class="{'active':caja.nuevoMovimiento.esGasto==1}" ng-click="caja.nuevoMovimiento.esIngreso=0; caja.nuevoMovimiento.esGasto=1">
<span ng-class="{'small':caja.nuevoMovimiento.esGasto==0}">- Gasto</span>
</button>
</div>
</div>
</div>
<div class="control-group">
<label class="control-label" for="categ">Categoría</label>
<div class="controls">
<select ng-show="caja.nuevoMovimiento.esIngreso" name="categoria" ng-model="caja.nuevoMovimiento.categoria" ng-options="categoria for categoria in caja.maestros.categoriasIngresos"></select>
<select ng-show="caja.nuevoMovimiento.esGasto" name="categoria" ng-model="caja.nuevoMovimiento.categoria" ng-options="categoria for categoria in caja.maestros.categoriasGastos"></select>
</div>
</div>
</div>
<div class="col-xs-12 col-sm-6">
<div class="control-group">
<label class="control-label" for="fecha">Fecha</label>
<div class="controls">
<input type="date" name="fecha" class="input" ng-model="caja.nuevoMovimiento.fecha">
</div>
</div>
<div class="control-group">
<label class="control-label" for="importe">Importe</label>
<div class="controls">
<input type="number" name="importe" class="input" ng-model="caja.nuevoMovimiento.importe">
</div>
</div>
</div>
</div>
<div class="row-fluid">
<div class="col-xs-12 col-sm-6">
<div class="control-group">
<label class="control-label" for="texto">Texto</label>
<div class="controls">
<input type="text" name="texto" class="input" ng-model="caja.nuevoMovimiento.texto">
</div>
</div>
</div>
<div class="col-xs-12 col-sm-6">
<div class="text-right">
<button style="margin-top: 20px" type="button" class="btn btn-lg btn-primary" ng-click="caja.guardarMovimiento()">
<span>Guardar {{ caja.tipo(caja.nuevoMovimiento) }}</span>
</button>
</div>
</div>
</div>
</fieldset>
</form>
</section>

Просмотреть файл

@ -0,0 +1,37 @@
<div class="container">
<div class="row">
<div class="span12">
<form class="form-horizontal">
<fieldset>
<div id="legend">
<legend class="">Login</legend>
</div>
<div class="control-group">
<!-- email -->
<label class="control-label" for="email">Email</label>
<div class="controls">
<input type="email" ng-model="registro.usuario.email" name="email" class="input-xlarge">
</div>
</div>
<div class="control-group">
<!-- Password-->
<label class="control-label" for="password">Password</label>
<div class="controls">
<input type="password" ng-model="registro.usuario.password" name="password" class="input-xlarge">
</div>
</div>
<div class="control-group">
<!-- Button -->
<div class="controls">
<button class="btn btn-primary" ng-click="registro.entrar()">Login</button>
</div>
<!-- Button -->
<div class="controls">
<button class="btn btn-success" ng-click="registro.registrar()">Registro</button>
</div>
</div>
</fieldset>
</form>
</div>
</div>
</div>

Просмотреть файл

@ -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);
}());

Просмотреть файл

@ -0,0 +1,38 @@
<!-- Cada sección tendrá su propia plantilla -->
<!-- No se especifíca el controlador, que será asignado por el enrutador -->
<section name="total">
<!-- Pero se supone un viweModel llamado caja-->
<h1>{{ caja.titulo }}</h1>
<p class="lead">Comprueba de dónde viene y a dónde va tu dinero.</p>
<div class="row-fluid">
<div class="row placeholders">
<div class="col-xs-8 col-sm-4 placeholder">
<h1>
<span class="label label-success">
{{ caja.total.ingresos | number:2 }} €
</span>
</h1>
<h4>Total ingresos</h4>
<span class="text-muted">Acumulado</span>
</div>
<div class="col-xs-8 col-sm-4 placeholder">
<h1>
<span class="label label-danger">
{{ caja.total.gastos | number:2 }} €
</span>
</h1>
<h4>Total gastos</h4>
<span class="text-muted">Acumulado</span>
</div>
<div class="col-xs-8 col-sm-4 placeholder">
<h1>
<span class="label " ng-class="{'label-success': caja.balance()>=0 , 'label-danger' : caja.balance()<0}">
{{ caja.balance() | number:2 }} €
</span>
</h1>
<h4>Balance</h4>
<span class="text-muted">Ingresos-Gastos</span>
</div>
</div>
</div>
</section>

Просмотреть файл

@ -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"
}
}

Просмотреть файл

@ -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');

Просмотреть файл

@ -0,0 +1,36 @@
// Incluimos referencia al módulo de directivas
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', // declaracion de parametros en rutas
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'
});
});

Просмотреть файл

@ -0,0 +1,23 @@
(function () {
function configuradorInterceptores($httpProvider) {
$httpProvider.interceptors.push(funcionInterceptoraLog);
}
// Esta función se especializa en escribir en la consola
// información sobre llamadas http
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);
}());

Просмотреть файл

@ -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);
}());

Просмотреть файл

@ -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);
}());

Просмотреть файл

@ -0,0 +1,89 @@
(function () {
// El uso más simple es crear directivas para usar como código reutilizable
function piePagina() {
return {
restrict: 'AE',
replace: 'true',
template: '<footer class="container"><hr/><p class="text-center">Desarrollado con AngularJS by Google. Por Alberto Basalo - <a href="http://agorabinaria.com">Ágora Binaria SL</a></p></footer>'
};
};
// Dos mejoras, sacar el html a un fichero externo (tpl-directiva)
// Usar Transclude para reutilizar el contenido del usuario y hacer la vista más dinámica
function cabecera() {
return {
restrict: 'AE',
replace: 'true',
transclude: true,
templateUrl: '/tpl-cabecera.html'
};
};
// En este caso el cambio más siginificatico es el scope
// Funciona como un API para la directiva y recibe la info vía atributos
// Por otro lado en este caso hemos tenido que restringir el uso de la directiva
// Los elementos tr dentro de un table requieren definirse explícitamente
function filaMovimiento() {
return {
restrict: 'A',
templateUrl: '/tpl-fila-movimiento.html',
scope: {
movimientoplantilla: "=movimientodirectiva"
}
};
}
// Otro uso de las directivas es extender la funionalidad del DOM sin importar los datos
// Este es el lugar donde manipular el DOM y jamás en los controladores
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');
});
}
};
}
// Ejemplo no funcional de cómo extender un plugin hecho a medida
function plugin() {
return {
link: function (scope, element, attrs) {
// Obtención de parámetros vía atributos
var init = scope.$eval(attrs.ngModel);
var min = scope.$eval(attrs.abSliderMin);
var max = scope.$eval(attrs.abSliderMax);
// Configuración básica
$(element).plugin({
value: init,
min: min,
max: max,
tooltip: 'hide'
});
// Actualizar la vista cuando cambia el modelo
scope.$watch(attrs.ngModel, function (valor) {
$(element).plugin('setValue', valor);
});
// Actualizar el modelo cuando cambia la vista
$(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);
}());

Просмотреть файл

@ -0,0 +1,84 @@
(function () {
// Los filtros se declaran como funciones que a su vez devuelven... funciones
// Esas funciones internas se aplican sobre los valores,
// Tienen al menos un parámetro, que sirve de entrada
// Esta función le quita acentos, guiones bajos, y caracteres raros
// Los sustituye por guiones bajos
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;
}
// Devuelve un trozo de texto y tres puntos suspensivos... para indicar que hay más...
// Demuestra que un filtro puede tener parámetros, y cómo tratar valores por defecto.
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;
}
// Con control de errores y lógica compleja
function rellenarVacios() {
var funcionFiltro = function (cadena) {
try {
if (!cadena || cadena === undefined || cadena.trim() === "") {
return '---';
};
} catch (err) {
return '---';
}
return cadena;
}
return funcionFiltro;
}
// Permite tener filtros predeterminadois en un array
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;
}
// Podemos usar una sintaxis fluida y declarar todo en una solo línea
// Atención a la declaración de un nuevo módulo genérico
angular.module('abFiltros', [])
.filter('abLimpiarCadena', limpiarCadena)
.filter('abRecortar', recortar)
.filter('abRellenarVacios', rellenarVacios)
.filter('abGranImporte', granImporte);
}());

Просмотреть файл

@ -0,0 +1,59 @@
<html lang="es" ng-app="controlCajaApp">
<head>
<title>Control de Caja</title>
<link href="http://getbootstrap.com/dist/css/bootstrap.min.css" rel="stylesheet">
<meta charset="utf-8" />
<meta lang="es" />
<meta name="description" content="Ejemplo Control de Caja en AngularJS por Alberto Basalo" />
<meta name="author" content="Alberto Basalo @ Ágora Binaria" />
<meta name="application-name" content="ControlAngularJS" />
<meta name="Keywords" content="AngularJS, ejemplo, tutorial, curso" />
<link rel="author" href="https://plus.google.com/+AlbertoBasalo71" />
<!-- Bootstrap core CSS -->
<link href="http://getbootstrap.com/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<nav class="navbar navbar-default navbar-fixed-top" name="navbar" role="navigation" ng-controller="MenuCtrl as menu">
<div class="navbar-inner">
<div class="container">
<ul class="nav navbar-nav">
<li ng-class="{ active: menu.isActive('total') }">
<a ui-sref="total" name="menu-total">Totales</a>
</li>
<li ng-class="{ active: menu.isActive('nuevo') }">
<a ui-sref="nuevo" name="menu-nuevo">Nuevo</a>
</li>
<li ng-class="{ active: menu.isActive('lista') }">
<a ui-sref="lista" name="menu-lista">Lista</a>
</li>
</ul>
</div>
</div>
</nav>
<div class="container text-center" style="padding-top:50px;" ui-view>
</div>
<ab-pie-pagina></ab-pie-pagina>
<!-- JavaScript References -->
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.14/angular.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.14/angular-cookies.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.14/angular-resource.min.js"></script>
<script src="http://angular-ui.github.io/ui-router/release/angular-ui-router.min.js"></script>
<script src="app.js"></script>
<script src="appHttpLog.js"></script>
<script src="appSecurity.js"></script>
<script src="registroCtrl.js"></script>
<script src="menuCtrl.js"></script>
<script src="cajaCtrl.js"></script>
<script src="movimientoCtrl.js"></script>
<script src="movimientosFactory.js"></script>
<script src="maestrosFactory.js"></script>
<script src="filtros.js"></script>
<!-- El fichero de directivas debe referenciarse -->
<script src="directivas.js"></script>
<script src="valoracion.js"></script>
</body>
</html>

Просмотреть файл

@ -0,0 +1,39 @@
<section name="Lista">
<!-- <p class="lead">Estos son tus movimientos recientes.</p>-->
<ab-cabecera>Estos son tus movimientos recientes.</ab-cabecera>
<br>
<label class="control-label" for="importe">Filtrar:</label>
<input type="text" name="filtro" placeholder="qué buscas?" class="input" ng-model="valorBuscado">
<button class="btn-primary" ng-click="valorCorte=0">Ver todos los movimientos</button>
<button class="btn-danger" ng-click="valorCorte=1000">Sólo grandes movimientos</button>
<table class="table">
<thead>
<tr>
<th>Id</th>
<th><a href="" ng-click="campo = 'fecha'; sentido = campo == 'fecha' && !sentido">Fecha</a>
</th>
<th>Tipo</th>
<th>Categoría</th>
<th><a href="" ng-click="campo = 'importe'; sentido = campo == 'importe' && !sentido">Importe</a>
</th>
<th>Valoración</th>
</tr>
</thead>
<tbody>
<tr
ng-repeat="movimientoRepeater in caja.movimientos | abGranImporte:valorCorte | filter:valorBuscado | orderBy:campo:sentido"
ab-fila-movimiento movimientodirectiva="movimientoRepeater"
ab-seleccionado >
<!--
</tr>
<tr ng-repeat="movimiento in caja.movimientos | abGranImporte:valorCorte | filter:valorBuscado | orderBy:campo:sentido">
<td class="text-left"><a ui-sref="movimiento({id:movimiento.id})">{{movimiento.id }}</a> </td>
<td class="text-left">{{movimiento.fecha | date}}</td>
<td class="text-left">{{movimiento.tipo}}</td>
<td class="text-left">{{movimiento.categoria}}</td>
<td class="text-left" ng-class="{'text-success': movimiento.tipo=='Ingreso', 'text-danger' : movimiento.tipo=='Gasto'}">{{movimiento.importe | number:2}} €</td>
</tr>
-->
</tbody>
</table>
</section>

Просмотреть файл

@ -0,0 +1,9 @@
(function () {
var maestrosFactory =   function ($resource)  {
// Los resursos son totalmente configurables
// Una de sus utilidades de 'fábrica' de una caché simple pero potente
return $resource("/api/pub/maestros/",{},{get: {cache: true}});
};
angular.module('controlCajaApp').factory('maestrosFactory', maestrosFactory);
}());

Просмотреть файл

@ -0,0 +1,8 @@
(function () {
var menuCtrl = function ($state) {
this.isActive = function (estado) {
return $state.is(estado);
}
}
angular.module('controlCajaApp').controller('MenuCtrl',menuCtrl);
}());

Просмотреть файл

@ -0,0 +1,55 @@
<section name="verMovimiento" class="row-fluid">
<form class="form-horizontal text-left">
<fieldset>
<div id="legend">
<legend class="">Ver el movimiento {{ vm.movimiento.id}} </legend>
</div>
<div class="row-fluid">
<div class="col-xs-12 col-sm-6">
<div class="control-group">
<label class="control-label" for="tipo">Tipo</label>
<div class="controls">
<div class="btn-group">
{{ vm.movimiento.tipo}}
</div>
</div>
</div>
<div class="control-group">
<label class="control-label" for="categ">Categoría</label>
<div class="controls">
{{ vm.movimiento.categoria }}
</div>
</div>
</div>
<div class="col-xs-12 col-sm-6">
<div class="control-group">
<label class="control-label" for="fecha">Fecha</label>
<div class="controls">
{{ vm.movimiento.fecha || date }}
</div>
</div>
<div class="control-group">
<label class="control-label" for="importe">Importe</label>
<div class="controls">
{{ vm.movimiento.importe }}
</div>
</div>
</div>
<div class="col-xs-12 col-sm-6">
<div class="control-group">
<label class="control-label" for="texto">Texto</label>
<div class="controls">
{{ vm.movimiento.texto | abRellenarVacios | abRecortar | abLimpiarCadena }}
</div>
</div>
<div class="control-group">
<label class="control-label" for="valoracion">Valoración</label>
<div class="controls">
<span ab-valoracion valor="caja.nuevoMovimiento.valoracion" max="10" solo-lectura="false"></span>
</div>
</div>
</div>
</div>
</fieldset>
</form>
</section>

Просмотреть файл

@ -0,0 +1,10 @@
(function () {
// $stateParams es el servicio de ui-router para acceder a los parámetros de la ruta
var movimientoCtrl = function ($stateParams, movimientosFactory) {
var vm = this;
// El acceso es por nombre de parámetro
var movId = $stateParams.id;
vm.movimiento = movimientosFactory.movimientos.get({id:movId});
}
angular.module('controlCajaApp').controller('MovimientoCtrl', movimientoCtrl);
}());

Просмотреть файл

@ -0,0 +1,25 @@
(function () {
var movimientosFactory =   function ($resource)  {
var factory  =   {};
// $resource("/api/priv/movimientos/");
// El uso de parametros en los recursos debe especificarse
// Indicando el nombre local y el remoto
// En la url va el nombre remoto
// En el objeto se repute ese nombre como clave y se dice que propiedad será la que lo lleve
// La propiedad empieza por @
factory.movimientos =  $resource("/api/priv/movimientos/:id", {
id: "@id"
})
factory.total = $resource("/api/priv/total/");
return factory;
};
angular.module('controlCajaApp').factory('movimientosFactory', movimientosFactory);
}());

Просмотреть файл

@ -0,0 +1,3 @@
<section name="total">
<h2>No se ha encontrado lo que buscabas</h2>
</section>

Просмотреть файл

@ -0,0 +1,73 @@
<section name="nuevoMovimiento" class="row-fluid">
<ab-cabecera>Introduce tus movimientos.</ab-cabecera>
<form class="form-horizontal text-left">
<fieldset>
<!--
<div id="legend">
<legend class="">Introduce tus movimientos</legend>
</div>
-->
<div class="row-fluid">
<div class="col-xs-12 col-sm-6">
<div class="control-group">
<label class="control-label" for="tipo">Tipo</label>
<div class="controls">
<div class="btn-group">
<button type="button" class="btn btn-success" ng-class="{'active':caja.nuevoMovimiento.esIngreso==1}" ng-click="caja.nuevoMovimiento.esIngreso=1; caja.nuevoMovimiento.esGasto=0">
<span ng-class="{'small':caja.nuevoMovimiento.esIngreso==0}">+ Ingreso</span>
</button>
<button type="button" class="btn btn-danger" ng-class="{'active':caja.nuevoMovimiento.esGasto==1}" ng-click="caja.nuevoMovimiento.esIngreso=0; caja.nuevoMovimiento.esGasto=1">
<span ng-class="{'small':caja.nuevoMovimiento.esGasto==0}">- Gasto</span>
</button>
</div>
</div>
</div>
<div class="control-group">
<label class="control-label" for="categ">Categoría</label>
<div class="controls">
<select ng-show="caja.nuevoMovimiento.esIngreso" name="categoria" ng-model="caja.nuevoMovimiento.categoria" ng-options="categoria for categoria in caja.maestros.categoriasIngresos"></select>
<select ng-show="caja.nuevoMovimiento.esGasto" name="categoria" ng-model="caja.nuevoMovimiento.categoria" ng-options="categoria for categoria in caja.maestros.categoriasGastos"></select>
</div>
</div>
</div>
<div class="col-xs-12 col-sm-6">
<div class="control-group">
<label class="control-label" for="fecha">Fecha</label>
<div class="controls">
<input type="date" name="fecha" class="input" ng-model="caja.nuevoMovimiento.fecha">
</div>
</div>
<div class="control-group">
<label class="control-label" for="importe">Importe</label>
<div class="controls">
<input type="number" name="importe" class="input" ng-model="caja.nuevoMovimiento.importe">
</div>
</div>
</div>
</div>
<div class="row-fluid">
<div class="col-xs-12 col-sm-6">
<div class="control-group">
<label class="control-label" for="texto">Texto</label>
<div class="controls">
<input type="text" name="texto" class="input" ng-model="caja.nuevoMovimiento.texto">
</div>
</div>
</div>
<div class="col-xs-12 col-sm-6">
<div class="control-group">
<label class="control-label" for="valoracion">Valoración</label>
<div class="controls">
<div ab-valoracion valor="caja.nuevoMovimiento.valoracion" max="10" solo-lectura="false"></div>
</div>
</div>
</div>
</div>
<div class="text-right">
<button style="margin-top: 20px" type="button" class="btn btn-lg btn-primary" ng-click="caja.guardarMovimiento()" name="guardar">
<span>Guardar {{ caja.tipo(caja.nuevoMovimiento) }}</span>
</button>
</div>
</fieldset>
</form>
</section>

Просмотреть файл

@ -0,0 +1,37 @@
<div class="container">
<div class="row">
<div class="span12">
<form class="form-horizontal">
<fieldset>
<div id="legend">
<legend class="">Login</legend>
</div>
<div class="control-group">
<!-- email -->
<label class="control-label" for="email">Email</label>
<div class="controls">
<input type="email" ng-model="registro.usuario.email" name="email" class="input-xlarge">
</div>
</div>
<div class="control-group">
<!-- Password-->
<label class="control-label" for="password">Password</label>
<div class="controls">
<input type="password" ng-model="registro.usuario.password" name="password" class="input-xlarge">
</div>
</div>
<div class="control-group">
<!-- Button -->
<div class="controls">
<button class="btn btn-primary" ng-click="registro.entrar()">Login</button>
</div>
<!-- Button -->
<div class="controls">
<button class="btn btn-success" ng-click="registro.registrar()">Registro</button>
</div>
</div>
</fieldset>
</form>
</div>
</div>
</div>

Просмотреть файл

@ -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);
}());

Просмотреть файл

@ -0,0 +1,38 @@
<section name="total">
<!--
<h1>{{ caja.titulo }}</h1>
<p class="lead">Comprueba de dónde viene y a dónde va tu dinero.</p>
-->
<ab-cabecera>Comprueba de dónde viene y a dónde va tu dinero.</ab-cabecera>
<div class="row-fluid">
<div class="row placeholders">
<div class="col-xs-8 col-sm-4 placeholder">
<h1>
<span class="label label-success">
{{ caja.total.ingresos | number:2 }} €
</span>
</h1>
<h4>Total ingresos</h4>
<span class="text-muted">Acumulado</span>
</div>
<div class="col-xs-8 col-sm-4 placeholder">
<h1>
<span class="label label-danger">
{{ caja.total.gastos | number:2 }} €
</span>
</h1>
<h4>Total gastos</h4>
<span class="text-muted">Acumulado</span>
</div>
<div class="col-xs-8 col-sm-4 placeholder">
<h1>
<span class="label " ng-class="{'label-success': caja.balance()>=0 , 'label-danger' : caja.balance()<0}">
{{ caja.balance() | number:2 }} €
</span>
</h1>
<h4>Balance</h4>
<span class="text-muted">Ingresos-Gastos</span>
</div>
</div>
</div>
</section>

Просмотреть файл

@ -0,0 +1,6 @@
<div class="well">
<header>
<h2>Cash Flow</h2>
</header>
<div class="lead" ng-transclude></div>
</div>

Просмотреть файл

@ -0,0 +1,10 @@
<tr>
<td class="text-left"><a ui-sref="movimiento({id:movimientoplantilla.id})">{{movimientoplantilla.id }}</a> </td>
<td class="text-left">{{movimientoplantilla.fecha | date}}</td>
<td class="text-left">{{movimientoplantilla.tipo}}</td>
<td class="text-left">{{movimientoplantilla.categoria}}</td>
<td class="text-left" ng-class="{'text-success': movimientoplantilla.tipo=='Ingreso', 'text-danger' : movimientoplantilla.tipo=='Gasto'}">
{{movimientoplantilla.importe | number:2}} €
</td>
<td class="text-left"><ab-valoracion valor="movimientoplantilla.valoracion" max="10" solo-lectura="true"></ab-valoracion></td>
</tr>

Просмотреть файл

@ -0,0 +1,12 @@
<span>
<span
ng-repeat="estrella in estrellas"
ng-click="marcar($index)" >
<span class="lead"
ng-class=
"{'text-danger h3': estrella.marcada,
'text-muted small': !estrella.marcada}">
<b><u>*</u></b>
</span>
</span>
</span>

Просмотреть файл

@ -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);
}());

Просмотреть файл

@ -0,0 +1,46 @@
Directivas
==========
- Se usan de forma **declarativa** en el HTML
- Su función es extender y enriquecer el HTML encapsulando el **acceso al DOM**
- Se definen como **objetos JS** con propiedades específicas
Propiedades
-----------
### Restrict
Sirve para determinar dónde se puede aplicar la directiva
Valores posibles:
- **A** – Attribute ` <div rating> `
- **E** – Element ` <rating> `
- **C** – Class ` <div class=”rating”> `
- **M** – Comment ` <!– directive: rating –> `
### 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

Просмотреть файл

@ -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"
}
}

Просмотреть файл

@ -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

187
20-directivas/server.js Normal file
Просмотреть файл

@ -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');

Просмотреть файл

@ -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'
});
});

Просмотреть файл

@ -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);
}());

Просмотреть файл

@ -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);
}());

Просмотреть файл

@ -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);
}());

Просмотреть файл

@ -0,0 +1,74 @@
(function () {
function piePagina() {
return {
restrict: 'AE',
replace: 'true',
template: '<footer class="container"><hr/><p class="text-center">Desarrollado con AngularJS by Google. Por Alberto Basalo - <a href="http://agorabinaria.com">Ágora Binaria SL</a></p></footer>'
};
};
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);
}());

Просмотреть файл

@ -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);
}());

Просмотреть файл

@ -0,0 +1,58 @@
<html lang="es" ng-app="controlCajaApp">
<head>
<title>Control de Caja</title>
<link href="http://getbootstrap.com/dist/css/bootstrap.min.css" rel="stylesheet">
<meta charset="utf-8" />
<meta lang="es" />
<meta name="description" content="Ejemplo Control de Caja en AngularJS por Alberto Basalo" />
<meta name="author" content="Alberto Basalo @ Ágora Binaria" />
<meta name="application-name" content="ControlAngularJS" />
<meta name="Keywords" content="AngularJS, ejemplo, tutorial, curso" />
<link rel="author" href="https://plus.google.com/+AlbertoBasalo71" />
<!-- Bootstrap core CSS -->
<link href="http://getbootstrap.com/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<nav class="navbar navbar-default navbar-fixed-top" name="navbar" role="navigation" ng-controller="MenuCtrl as menu">
<div class="navbar-inner">
<div class="container">
<ul class="nav navbar-nav">
<li ng-class="{ active: menu.isActive('total') }">
<a ui-sref="total" name="menu-total">Totales</a>
</li>
<li ng-class="{ active: menu.isActive('nuevo') }">
<a ui-sref="nuevo" name="menu-nuevo">Nuevo</a>
</li>
<li ng-class="{ active: menu.isActive('lista') }">
<a ui-sref="lista" name="menu-lista">Lista</a>
</li>
</ul>
</div>
</div>
</nav>
<div class="container text-center" style="padding-top:50px;" ui-view>
</div>
<ab-pie-pagina></ab-pie-pagina>
<!-- JavaScript References -->
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.14/angular.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.14/angular-cookies.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.14/angular-resource.min.js"></script>
<script src="http://angular-ui.github.io/ui-router/release/angular-ui-router.min.js"></script>
<script src="app.js"></script>
<script src="appHttpLog.js"></script>
<script src="appSecurity.js"></script>
<script src="registroCtrl.js"></script>
<script src="menuCtrl.js"></script>
<script src="cajaCtrl.js"></script>
<script src="movimientoCtrl.js"></script>
<script src="movimientosFactory.js"></script>
<script src="maestrosFactory.js"></script>
<script src="filtros.js"></script>
<script src="directivas.js"></script>
<script src="valoracion.js"></script>
</body>
</html>

Просмотреть файл

@ -0,0 +1,28 @@
<section name="Lista">
<ab-cabecera>Estos son tus movimientos recientes.</ab-cabecera>
<br>
<label class="control-label" for="importe">Filtrar:</label>
<input type="text" name="filtro" placeholder="qué buscas?" class="input" ng-model="valorBuscado">
<button class="btn-primary" ng-click="valorCorte=0">Ver todos los movimientos</button>
<button class="btn-danger" ng-click="valorCorte=1000">Sólo grandes movimientos</button>
<table class="table">
<thead>
<tr>
<th>Id</th>
<th><a href="" ng-click="campo = 'fecha'; sentido = campo == 'fecha' && !sentido">Fecha</a>
</th>
<th>Tipo</th>
<th>Categoría</th>
<th><a href="" ng-click="campo = 'importe'; sentido = campo == 'importe' && !sentido">Importe</a>
</th>
<th>Valoración</th>
</tr>
</thead>
<tbody>
<tr
ng-repeat="movimientoRepeater in caja.movimientos | abGranImporte:valorCorte | filter:valorBuscado | orderBy:campo:sentido"
ab-fila-movimiento movimientodirectiva="movimientoRepeater"
ab-seleccionado >
</tbody>
</table>
</section>

Просмотреть файл

@ -0,0 +1,7 @@
(function () {
var maestrosFactory =   function ($resource)  {
return $resource("/api/pub/maestros/",{},{get: {cache: true}});
};
angular.module('controlCajaApp').factory('maestrosFactory', maestrosFactory);
}());

Просмотреть файл

@ -0,0 +1,8 @@
(function () {
var menuCtrl = function ($state) {
this.isActive = function (estado) {
return $state.is(estado);
}
}
angular.module('controlCajaApp').controller('MenuCtrl',menuCtrl);
}());

Просмотреть файл

@ -0,0 +1,55 @@
<section name="verMovimiento" class="row-fluid">
<form class="form-horizontal text-left">
<fieldset>
<div id="legend">
<legend class="">Ver el movimiento {{ vm.movimiento.id}} </legend>
</div>
<div class="row-fluid">
<div class="col-xs-12 col-sm-6">
<div class="control-group">
<label class="control-label" for="tipo">Tipo</label>
<div class="controls">
<div class="btn-group">
{{ vm.movimiento.tipo}}
</div>
</div>
</div>
<div class="control-group">
<label class="control-label" for="categ">Categoría</label>
<div class="controls">
{{ vm.movimiento.categoria }}
</div>
</div>
</div>
<div class="col-xs-12 col-sm-6">
<div class="control-group">
<label class="control-label" for="fecha">Fecha</label>
<div class="controls">
{{ vm.movimiento.fecha || date }}
</div>
</div>
<div class="control-group">
<label class="control-label" for="importe">Importe</label>
<div class="controls">
{{ vm.movimiento.importe }}
</div>
</div>
</div>
<div class="col-xs-12 col-sm-6">
<div class="control-group">
<label class="control-label" for="texto">Texto</label>
<div class="controls">
{{ vm.movimiento.texto | abRellenarVacios | abRecortar | abLimpiarCadena }}
</div>
</div>
<div class="control-group">
<label class="control-label" for="valoracion">Valoración</label>
<div class="controls">
<span ab-valoracion valor="caja.nuevoMovimiento.valoracion" max="10" solo-lectura="false"></span>
</div>
</div>
</div>
</div>
</fieldset>
</form>
</section>

Просмотреть файл

@ -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);
}());

Просмотреть файл

@ -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);
}());

Просмотреть файл

@ -0,0 +1,3 @@
<section name="total">
<h2>No se ha encontrado lo que buscabas</h2>
</section>

Просмотреть файл

@ -0,0 +1,68 @@
<section name="nuevoMovimiento" class="row-fluid">
<ab-cabecera>Introduce tus movimientos.</ab-cabecera>
<form class="form-horizontal text-left">
<fieldset>
<div class="row-fluid">
<div class="col-xs-12 col-sm-6">
<div class="control-group">
<label class="control-label" for="tipo">Tipo</label>
<div class="controls">
<div class="btn-group">
<button type="button" class="btn btn-success" ng-class="{'active':caja.nuevoMovimiento.esIngreso==1}" ng-click="caja.nuevoMovimiento.esIngreso=1; caja.nuevoMovimiento.esGasto=0">
<span ng-class="{'small':caja.nuevoMovimiento.esIngreso==0}">+ Ingreso</span>
</button>
<button type="button" class="btn btn-danger" ng-class="{'active':caja.nuevoMovimiento.esGasto==1}" ng-click="caja.nuevoMovimiento.esIngreso=0; caja.nuevoMovimiento.esGasto=1">
<span ng-class="{'small':caja.nuevoMovimiento.esGasto==0}">- Gasto</span>
</button>
</div>
</div>
</div>
<div class="control-group">
<label class="control-label" for="categ">Categoría</label>
<div class="controls">
<select ng-show="caja.nuevoMovimiento.esIngreso" name="categoria" ng-model="caja.nuevoMovimiento.categoria" ng-options="categoria for categoria in caja.maestros.categoriasIngresos"></select>
<select ng-show="caja.nuevoMovimiento.esGasto" name="categoria" ng-model="caja.nuevoMovimiento.categoria" ng-options="categoria for categoria in caja.maestros.categoriasGastos"></select>
</div>
</div>
</div>
<div class="col-xs-12 col-sm-6">
<div class="control-group">
<label class="control-label" for="fecha">Fecha</label>
<div class="controls">
<input type="date" name="fecha" class="input" ng-model="caja.nuevoMovimiento.fecha">
</div>
</div>
<div class="control-group">
<label class="control-label" for="importe">Importe</label>
<div class="controls">
<input type="number" name="importe" class="input" ng-model="caja.nuevoMovimiento.importe">
</div>
</div>
</div>
</div>
<div class="row-fluid">
<div class="col-xs-12 col-sm-6">
<div class="control-group">
<label class="control-label" for="texto">Texto</label>
<div class="controls">
<input type="text" name="texto" class="input" ng-model="caja.nuevoMovimiento.texto">
</div>
</div>
</div>
<div class="col-xs-12 col-sm-6">
<div class="control-group">
<label class="control-label" for="valoracion">Valoración</label>
<div class="controls">
<div ab-valoracion valor="caja.nuevoMovimiento.valoracion" max="10" solo-lectura="false"></div>
</div>
</div>
</div>
</div>
<div class="text-right">
<button style="margin-top: 20px" type="button" class="btn btn-lg btn-primary" ng-click="caja.guardarMovimiento()" name="guardar">
<span>Guardar {{ caja.tipo(caja.nuevoMovimiento) }}</span>
</button>
</div>
</fieldset>
</form>
</section>

Просмотреть файл

@ -0,0 +1,37 @@
<div class="container">
<div class="row">
<div class="span12">
<form class="form-horizontal">
<fieldset>
<div id="legend">
<legend class="">Login</legend>
</div>
<div class="control-group">
<!-- email -->
<label class="control-label" for="email">Email</label>
<div class="controls">
<input type="email" ng-model="registro.usuario.email" name="email" class="input-xlarge">
</div>
</div>
<div class="control-group">
<!-- Password-->
<label class="control-label" for="password">Password</label>
<div class="controls">
<input type="password" ng-model="registro.usuario.password" name="password" class="input-xlarge">
</div>
</div>
<div class="control-group">
<!-- Button -->
<div class="controls">
<button class="btn btn-primary" ng-click="registro.entrar()">Login</button>
</div>
<!-- Button -->
<div class="controls">
<button class="btn btn-success" ng-click="registro.registrar()">Registro</button>
</div>
</div>
</fieldset>
</form>
</div>
</div>
</div>

Просмотреть файл

@ -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);
}());

Просмотреть файл

@ -0,0 +1,34 @@
<section name="total">
<ab-cabecera>Comprueba de dónde viene y a dónde va tu dinero.</ab-cabecera>
<div class="row-fluid">
<div class="row placeholders">
<div class="col-xs-8 col-sm-4 placeholder">
<h1>
<span class="label label-success" name="ingresos">
{{ caja.total.ingresos | number:2 }} €
</span>
</h1>
<h4>Total ingresos</h4>
<span class="text-muted">Acumulado</span>
</div>
<div class="col-xs-8 col-sm-4 placeholder">
<h1>
<span class="label label-danger" name="gastos">
{{ caja.total.gastos | number:2 }} €
</span>
</h1>
<h4>Total gastos</h4>
<span class="text-muted">Acumulado</span>
</div>
<div class="col-xs-8 col-sm-4 placeholder">
<h1>
<span class="label " ng-class="{'label-success': caja.balance()>=0 , 'label-danger' : caja.balance()<0}" name="balance">
{{ caja.balance() | number:2 }} €
</span>
</h1>
<h4>Balance</h4>
<span class="text-muted">Ingresos-Gastos</span>
</div>
</div>
</div>
</section>

Просмотреть файл

@ -0,0 +1,6 @@
<div class="well">
<header>
<h2>Cash Flow</h2>
</header>
<div class="lead" ng-transclude></div>
</div>

Просмотреть файл

@ -0,0 +1,10 @@
<tr>
<td class="text-left"><a ui-sref="movimiento({id:movimientoplantilla.id})">{{movimientoplantilla.id }}</a> </td>
<td class="text-left">{{movimientoplantilla.fecha | date}}</td>
<td class="text-left">{{movimientoplantilla.tipo}}</td>
<td class="text-left">{{movimientoplantilla.categoria}}</td>
<td class="text-left" ng-class="{'text-success': movimientoplantilla.tipo=='Ingreso', 'text-danger' : movimientoplantilla.tipo=='Gasto'}">
{{movimientoplantilla.importe | number:2}} €
</td>
<td class="text-left"><ab-valoracion valor="movimientoplantilla.valoracion" max="10" solo-lectura="true"></ab-valoracion></td>
</tr>

Просмотреть файл

@ -0,0 +1,12 @@
<span>
<span
ng-repeat="estrella in estrellas"
ng-click="marcar($index)" >
<span class="lead"
ng-class=
"{'text-danger h3': estrella.marcada,
'text-muted small': !estrella.marcada}">
<b><u>*</u></b>
</span>
</span>
</span>

Просмотреть файл

@ -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);
}());

Просмотреть файл

@ -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"
}
}

Просмотреть файл

@ -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

187
21-organizacion/server.js Normal file
Просмотреть файл

@ -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');

Просмотреть файл

@ -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'
});
});

Просмотреть файл

@ -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);
}());

Просмотреть файл

@ -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);
}());

Просмотреть файл

@ -0,0 +1,8 @@
(function () {
var menuCtrl = function ($state) {
this.isActive = function (estado) {
return $state.is(estado);
}
}
angular.module('controlCajaApp').controller('MenuCtrl',menuCtrl);
}());

Просмотреть файл

@ -0,0 +1,3 @@
<section name="total">
<h2>No se ha encontrado lo que buscabas</h2>
</section>

Просмотреть файл

@ -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);
}());

Просмотреть файл

@ -0,0 +1,28 @@
<section name="Lista">
<ab-cabecera>Estos son tus movimientos recientes.</ab-cabecera>
<br>
<label class="control-label" for="importe">Filtrar:</label>
<input type="text" name="filtro" placeholder="qué buscas?" class="input" ng-model="valorBuscado">
<button class="btn-primary" ng-click="valorCorte=0">Ver todos los movimientos</button>
<button class="btn-danger" ng-click="valorCorte=1000">Sólo grandes movimientos</button>
<table class="table">
<thead>
<tr>
<th>Id</th>
<th><a href="" ng-click="campo = 'fecha'; sentido = campo == 'fecha' && !sentido">Fecha</a>
</th>
<th>Tipo</th>
<th>Categoría</th>
<th><a href="" ng-click="campo = 'importe'; sentido = campo == 'importe' && !sentido">Importe</a>
</th>
<th>Valoración</th>
</tr>
</thead>
<tbody>
<tr
ng-repeat="movimientoRepeater in caja.movimientos | abGranImporte:valorCorte | filter:valorBuscado | orderBy:campo:sentido"
ab-fila-movimiento movimientodirectiva="movimientoRepeater"
ab-seleccionado >
</tbody>
</table>
</section>

Просмотреть файл

@ -0,0 +1,68 @@
<section name="nuevoMovimiento" class="row-fluid">
<ab-cabecera>Introduce tus movimientos.</ab-cabecera>
<form class="form-horizontal text-left">
<fieldset>
<div class="row-fluid">
<div class="col-xs-12 col-sm-6">
<div class="control-group">
<label class="control-label" for="tipo">Tipo</label>
<div class="controls">
<div class="btn-group">
<button type="button" class="btn btn-success" ng-class="{'active':caja.nuevoMovimiento.esIngreso==1}" ng-click="caja.nuevoMovimiento.esIngreso=1; caja.nuevoMovimiento.esGasto=0">
<span ng-class="{'small':caja.nuevoMovimiento.esIngreso==0}">+ Ingreso</span>
</button>
<button type="button" class="btn btn-danger" ng-class="{'active':caja.nuevoMovimiento.esGasto==1}" ng-click="caja.nuevoMovimiento.esIngreso=0; caja.nuevoMovimiento.esGasto=1">
<span ng-class="{'small':caja.nuevoMovimiento.esGasto==0}">- Gasto</span>
</button>
</div>
</div>
</div>
<div class="control-group">
<label class="control-label" for="categ">Categoría</label>
<div class="controls">
<select ng-show="caja.nuevoMovimiento.esIngreso" name="categoria" ng-model="caja.nuevoMovimiento.categoria" ng-options="categoria for categoria in caja.maestros.categoriasIngresos"></select>
<select ng-show="caja.nuevoMovimiento.esGasto" name="categoria" ng-model="caja.nuevoMovimiento.categoria" ng-options="categoria for categoria in caja.maestros.categoriasGastos"></select>
</div>
</div>
</div>
<div class="col-xs-12 col-sm-6">
<div class="control-group">
<label class="control-label" for="fecha">Fecha</label>
<div class="controls">
<input type="date" name="fecha" class="input" ng-model="caja.nuevoMovimiento.fecha">
</div>
</div>
<div class="control-group">
<label class="control-label" for="importe">Importe</label>
<div class="controls">
<input type="number" name="importe" class="input" ng-model="caja.nuevoMovimiento.importe">
</div>
</div>
</div>
</div>
<div class="row-fluid">
<div class="col-xs-12 col-sm-6">
<div class="control-group">
<label class="control-label" for="texto">Texto</label>
<div class="controls">
<input type="text" name="texto" class="input" ng-model="caja.nuevoMovimiento.texto">
</div>
</div>
</div>
<div class="col-xs-12 col-sm-6">
<div class="control-group">
<label class="control-label" for="valoracion">Valoración</label>
<div class="controls">
<div ab-valoracion valor="caja.nuevoMovimiento.valoracion" max="10" solo-lectura="false"></div>
</div>
</div>
</div>
</div>
<div class="text-right">
<button style="margin-top: 20px" type="button" class="btn btn-lg btn-primary" ng-click="caja.guardarMovimiento()" name="guardar">
<span>Guardar {{ caja.tipo(caja.nuevoMovimiento) }}</span>
</button>
</div>
</fieldset>
</form>
</section>

Просмотреть файл

@ -0,0 +1,34 @@
<section name="total">
<ab-cabecera>Comprueba de dónde viene y a dónde va tu dinero.</ab-cabecera>
<div class="row-fluid">
<div class="row placeholders">
<div class="col-xs-8 col-sm-4 placeholder">
<h1>
<span class="label label-success" name="ingresos">
{{ caja.total.ingresos | number:2 }} €
</span>
</h1>
<h4>Total ingresos</h4>
<span class="text-muted">Acumulado</span>
</div>
<div class="col-xs-8 col-sm-4 placeholder">
<h1>
<span class="label label-danger" name="gastos">
{{ caja.total.gastos | number:2 }} €
</span>
</h1>
<h4>Total gastos</h4>
<span class="text-muted">Acumulado</span>
</div>
<div class="col-xs-8 col-sm-4 placeholder">
<h1>
<span class="label " ng-class="{'label-success': caja.balance()>=0 , 'label-danger' : caja.balance()<0}" name="balance">
{{ caja.balance() | number:2 }} €
</span>
</h1>
<h4>Balance</h4>
<span class="text-muted">Ingresos-Gastos</span>
</div>
</div>
</div>
</section>

Просмотреть файл

@ -0,0 +1,7 @@
(function () {
var maestrosFactory =   function ($resource)  {
return $resource("/api/pub/maestros/",{},{get: {cache: true}});
};
angular.module('controlCajaApp').factory('maestrosFactory', maestrosFactory);
}());

Просмотреть файл

@ -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);
}());

Просмотреть файл

@ -0,0 +1,74 @@
(function () {
function piePagina() {
return {
restrict: 'AE',
replace: 'true',
template: '<footer class="container"><hr/><p class="text-center">Desarrollado con AngularJS by Google. Por Alberto Basalo - <a href="http://agorabinaria.com">Ágora Binaria SL</a></p></footer>'
};
};
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);
}());

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше