diff --git a/.eslintrc.json b/.eslintrc.json
index 14152dfcb9ae24e77005322efbbfbe811504ae58..bac0de40f23bdc06babf20e5c715f05b3f8b6c41 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -7,6 +7,8 @@
         "import"
     ],
     "rules": {
+        "jsx-a11y/heading-has-content": [ "off" ],
+        "jsx-a11y/anchor-has-content": [ "off" ],
         "indent": [ "error", 4 ],
         "no-unused-vars": [ "error", { "args": "none" }],
         "no-param-reassign": [ "off" ]
diff --git a/.gitignore b/.gitignore
index 979717eb93b624b88c6749fccd981b0ccb7b2e8d..08baf221d197e203575471053f219672305703c4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
+.vscode
 .DS_Store
 .idea
 lib-cov
@@ -20,3 +21,4 @@ build/*
 
 config.json
 docs/
+.vscode/
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 04cdb1f9f46441a3617636eea51bf6403f1e2ab9..2cfd1adfd30e95fcc86afa8f389450e666b68f85 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -22,6 +22,3 @@ run_tests:
     - gulp test
   tags:
     - node
-  cache:
-    paths:
-      - node_modules/
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a8ebb1348286218dd9ad68d64fcd0bfd0cf58a52..6afe076fb4769bfbb6258a289769c58c8d98103e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,29 @@ All notable changes to this project will be documented in this file.
 The format is based on [Keep a Changelog](http://keepachangelog.com/)
 and this project adheres to [Semantic Versioning](http://semver.org/).
 
+## 1.0.0 - 2018-03-14
+### Added
+- `ReqQueryFields` middleware added to substitute parseParams and do SQL building automagically with the URL params
+- Upgrade to database v2
+- Added user model
+- Passport.js added for user authentication
+- Added routes to create and authenticate a user
+- Added simulation model
+- Added routes to save and retrieve simulations in `/simulation`
+- Add middleware to convert ids to names
+
+### Changed
+- Query middleware throws a 404 when the result is empty
+- Change filters and dimensions names! No more `_id`. **Breaks compability**
+- config.json.example now has values for development, test and production environments
+- Min Node version is 6.8.1
+- Cache is now defined per route
+- Errors from MonetDB are now returned by the query_exec and query middlewares
+- Tests are now in different files
+
+### Removed
+- Parse Params middleware removed
+
 ## 0.1.0 - 2016-10-10
 ### Added
 **Database**
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 8e649c4e017254a3cdc008cbcc2597a4da3fe740..39149f5870bd6f98d5644bedae5dbfe23038692a 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -17,6 +17,8 @@ Bugs are reported and tracked at [simcaq/SCRUM](https://gitlab.c3sl.ufpr.br/simc
 * **development**: default branch
 * **issue/??**: issue branch - a branch created to solve a issue
 * **feature_????**: feature branch - a branch created to add a feature
+* **hotfix_????**: hotfix branch - a branch created to fix a problem or error
+* **release_vM.m.f**: release branch - a branch created to merge with master and set a release. The verion follows the [semantic versioning](http://semver.org)
 
 ## Styleguide
 
diff --git a/config.json.example b/config.json.example
index 136ee928b34e9554c008f7125400286e6ff2ec33..e3d309200d160d47b17dbe57f23dee11cdbccd23 100644
--- a/config.json.example
+++ b/config.json.example
@@ -1,21 +1,139 @@
 {
-    "port": 3000,
-    "ip": "127.0.0.1",
-    "debug" : false,
-    "monetdb": {
-        "host": "simcaqdb1",
-        "port": 50000,
-        "dbname": "simcaq_dev",
-        "user": "monetdb",
-        "password":"monetdb",
-        "nrConnections": "16"
+    "development":
+    {
+        "port": 3000,
+        "ip": "127.0.0.1",
+        "debug" : true,
+        "monetdb": {
+            "host": "simcaqdb3.c3sl.ufpr.br",
+            "port": 50000,
+            "dbname": "simcaq_dev4",
+            "user": "monetdb",
+            "password":"monetdb",
+            "nrConnections": "4"
+        },
+        "cdn" : {
+            "url": "http://simcaqdb3.c3sl.ufpr.br:3000",
+            "download": "https://simcaqdev.c3sl.ufpr.br/download/"
+        },
+        "mongodb" : {
+            "uri": "mongodb://localhost/dev_users"
+        },
+        "monq": {
+            "uri": "mongodb://localhost/dev_monq"
+        },
+        "default": {
+            "api": {
+                "version" : "v1"
+            },
+            "lde": {
+                "url": "http://ldedev.c3sl.ufpr.br/#"
+            },
+            "simcaq": {
+                "url": "http://simcaqdev.c3sl.ufpr.br/#"
+            }
+        },
+        "email": {
+            "port": 25,
+            "host": "mx.c3sl.ufpr.br",
+            "secure": false,
+            "ignoreTLS": true,
+            "from": "\"Laboratório de Dados Educacionais\" <lde@c3sl.ufpr.br>"
+        },
+        "security": {
+            "tokenLife": 3600
+        }
     },
-    "mongodb" : {
-        "uri": "mongodb://localhost/users"
+    "test":
+    {
+        "port": 4000,
+        "ip": "127.0.0.1",
+        "debug" : true,
+        "monetdb": {
+            "host": "simcaqdb3.c3sl.ufpr.br",
+            "port": 50000,
+            "dbname": "simcaq_dev4",
+            "user": "monetdb",
+            "password":"monetdb",
+            "nrConnections": "4"
+        },
+        "cdn" : {
+            "url": "http://simcaqdb3.c3sl.ufpr.br:3000",
+            "download": "https://simcaqdev.c3sl.ufpr.br/download/"
+        },
+        "mongodb" : {
+            "uri": "mongodb://localhost/test_users",
+            "secret": "SimCAQC3SL"
+        },
+        "monq": {
+            "uri": "mongodb://localhost/test_monq"
+        },
+        "default": {
+            "api": {
+                "version" : "v1"
+            },
+            "lde": {
+                "url": "http://ldedev.c3sl.ufpr.br/#"
+            },
+            "simcaq": {
+                "url": "http://simcaqdev.c3sl.ufpr.br/#"
+            }
+        },
+        "email": {
+            "port": 25,
+            "host": "mx.c3sl.ufpr.br",
+            "secure": false,
+            "ignoreTLS": true,
+            "from": "\"Laboratório de Dados Educacionais\" <lde@c3sl.ufpr.br>"
+        },
+        "security": {
+            "tokenLife": 3600
+        }
     },
-    "default": {
-        "api": {
-            "version" : "v1"
+    "production":
+    {
+        "port": 6000,
+        "ip": "127.0.0.1",
+        "debug" : false,
+        "monetdb": {
+            "host": "simcaqdb3.c3sl.ufpr.br",
+            "port": 50000,
+            "dbname": "simcaq_dev4",
+            "user": "monetdb",
+            "password":"monetdb",
+            "nrConnections": "4"
+        },
+        "cdn" : {
+            "url": "http://simcaqdb3.c3sl.ufpr.br:7000",
+            "download": "https://simcaq.c3sl.ufpr.br/download/"
+        },
+        "mongodb" : {
+            "uri": "mongodb://localhost/users",
+            "secret": "SimCAQC3SL"
+        },
+        "monq": {
+            "uri": "mongodb://localhost/monq"
+        },
+        "default": {
+            "api": {
+                "version" : "v1"
+            },
+            "lde": {
+                "url": "http://lde.c3sl.ufpr.br/#"
+            },
+            "simcaq": {
+                "url": "http://simcaq.c3sl.ufpr.br/#"
+            }
+        },
+        "email": {
+            "port": 25,
+            "host": "mx.c3sl.ufpr.br",
+            "secure": false,
+            "ignoreTLS": true,
+            "from": "\"Laboratório de Dados Educacionais\" <lde@c3sl.ufpr.br>"
+        },
+        "security": {
+            "tokenLife": 3600
         }
     }
 }
diff --git a/gulpfile.babel.js b/gulpfile.babel.js
index b2758b0a6abe55329d30fb9658e0547e5fcb150d..11e9416f87c8528c84d0bbb64f5312c8455215b4 100644
--- a/gulpfile.babel.js
+++ b/gulpfile.babel.js
@@ -47,16 +47,17 @@ gulp.task('compile', ['lint'], () => {
     .pipe(babel())      // compile only modified files
     // .pipe(cache.cache())        // cache compiled files
     .pipe(gulp.dest('build'));  // move compiled files to build directory
+});
 
+gulp.task('build', ['compile'], () => {
+    var filesToCopy = [ 'config.json', 'package.json' ];
     // copy configuration file to build directory
-    gulp.src('config.json')
+    gulp.src(filesToCopy)
     .pipe(gulp.dest('build'));
 
     createLogDir();
 });
 
-gulp.task('build', ['compile']);
-
 gulp.task('docco', () => {
     gulp.src('./src/**/*.js')
         .pipe(docco())
@@ -73,12 +74,17 @@ gulp.task('pre-test', () => {
 
 gulp.task('test', ['pre-test'], () => {
     process.chdir('build');
-    gulp.src('test/test.js', {read: false})
-    .pipe(mocha())
+    gulp.src(['test/**/*.js'], {read: false})
+    .pipe(mocha({timeout: 60000}))
     .pipe(istanbul.writeReports())
     .pipe(istanbul.enforceThresholds({
         thresholds: {
-            global: 80 
+            global: {
+                statements: 80,
+                branches: 70,
+                lines: 80,
+                functions: 80
+            }
         }
     }))
     .on('error', () => {
diff --git a/package.json b/package.json
index acc29cd2e3d6ff9a605fde13cfaa6774243b6307..65e8d4c96f3372c0348376b054012ae247d64202 100644
--- a/package.json
+++ b/package.json
@@ -4,30 +4,46 @@
   "author": "C3SL",
   "description": "Simulador custo aluno-qualidade",
   "private": true,
+  "engines": {
+    "node": ">= 6.8.1"
+  },
+  "engineStrict": true,
   "scripts": {
     "start": "cd build && forever start server.js || node server.js",
     "test": "cd build && mocha"
   },
   "dependencies": {
-    "apicache": "0.0.14",
+    "agenda": "^0.9.1",
+    "apicache": "0.7.0",
+    "bcrypt-nodejs": "0.0.3",
     "body-parser": "^1.13.1",
     "chai": "^3.5.0",
     "chai-http": "^3.0.0",
-    "compression": "^1.6.2",
+    "chalk": "^1.1.3",
     "cookie-parser": "^1.3.5",
     "cors": "^2.7.1",
     "csv-express": "^1.1.0",
-    "debug": "~2.0.x",
+    "debug": "~2.3.x",
     "dirty-chai": "^1.2.2",
     "express": "^4.13.0",
-    "faker": "^2.1.5",
     "forever": "^0.15.2",
-    "js2xmlparser": "^1.0.0",
+    "js2xmlparser": "^2.0.2",
+    "jsonexport": "^2.0.9",
+    "jwt-simple": "^0.5.0",
+    "lodash": "^4.17.2",
     "method-override": "^2.3.3",
-    "mocha": "^2.5.3",
+    "mocha": "^3.1.2",
     "monetdb-pool": "0.0.8",
     "mongoose": "^4.6.0",
-    "nconf": "^0.6.x",
+    "nconf": "^0.8.x",
+    "node-uuid": "^1.4.8",
+    "nodemailer": "^4.0.1",
+    "nodemailer-html-to-text": "^2.1.0",
+    "oauth2orize": "^1.8.1",
+    "passport": "^0.3.2",
+    "passport-http-bearer": "^1.0.1",
+    "passport-oauth2-client-password": "^0.1.2",
+    "request": "^2.81.0",
     "squel": "^5.4.2",
     "winston": "^2.2.0"
   },
@@ -41,20 +57,20 @@
     "browserify": "^13.1.0",
     "chai-xml": "^0.3.1",
     "docdash": "^0.4.0",
-    "eslint": "^3.3.1",
-    "eslint-config-airbnb": "^10.0.1",
-    "eslint-plugin-import": "^1.13.0",
-    "eslint-plugin-jsx-a11y": "^2.1.0",
-    "eslint-plugin-react": "^6.1.1",
+    "eslint": "^3.7.1",
+    "eslint-config-airbnb": "^13.0.0",
+    "eslint-plugin-import": "^2.2.0",
+    "eslint-plugin-jsx-a11y": "^2.2.3",
+    "eslint-plugin-react": "^6.4.0",
     "gulp": "^3.9.1",
     "gulp-babel": "^6.1.2",
     "gulp-cli": "^1.2.2",
     "gulp-docco": "0.0.4",
     "gulp-eslint": "^3.0.1",
     "gulp-file-cache": "0.0.1",
-    "gulp-function": "^1.3.6",
+    "gulp-function": "^2.2.0",
     "gulp-istanbul": "^1.1.1",
-    "gulp-jsdoc3": "^0.3.0",
+    "gulp-jsdoc3": "^1.0.1",
     "gulp-mocha": "^3.0.1",
     "gulp-nodemon": "^2.1.0",
     "gulp-plumber": "^1.1.0",
diff --git a/src/libs/app.js b/src/libs/app.js
index f43abfb9732b4793d161c51efaf9b7dc857a674c..6f464ead79b585ef439b825587413177bdc51223 100644
--- a/src/libs/app.js
+++ b/src/libs/app.js
@@ -3,52 +3,84 @@ const cookieParser = require('cookie-parser');
 const bodyParser = require('body-parser');
 const methodOverride = require('method-override');
 const cors = require('cors');
-const compression = require('compression');
 const squel = require('squel');
 
 const libs = `${process.cwd()}/libs`;
 
 const log = require(`${libs}/log`)(module);
 
+process.env.NODE_ENV = process.env.NODE_ENV || 'development';
 const config = require(`${libs}/config`);
-const cache = require('apicache').options({ debug: config.get('debug') }).middleware;
+const cache = require('apicache').options({ debug: config.debug }).middleware;
 
 const app = express();
 
-const api = require(`${libs}/routes/api`);
+const api = require('./routes/api');
+
+const passport = require('passport');
 
 const mongoose = require(`${libs}/db/mongoose`);
 
 const db = mongoose();
 
-app.use(bodyParser.json());
-app.use(bodyParser.urlencoded({ extended: false }));
+require(`${libs}/middlewares/passport`);
+
+app.use(bodyParser.json({limit: '50mb'}));
+app.use(bodyParser.urlencoded({ extended: true, limit: '50mb' }));
 app.use(cookieParser());
 // Enable Cross-Origin Resource Sharing (CORS)
 app.use(cors());
 app.use(methodOverride());
-// Enable cache for 1 day
-app.use(cache('1 day'));
-// Enable maximum compression
-app.use(compression(9));
 // Middleware tha adds the squel object to req
 app.use((req, res, next) => {
-    req.sql = squel.select();
+    req.resetSql = () => {
+        req.sql = squel.select();
+        // HACK to know wich table we are using
+        req.sql.oldFrom = req.sql.from;
+        req.sql.from = (name, alias = null) => {
+            req.sql.oldFrom(name, alias || null);
+            req.sql.tableFrom = name;
+            return req.sql;
+        };
+    };
+    req.resetSql();
+
+    req.sql.oldClone = req.sql.clone;
+    req.sql.clone = () => {
+        let cloned = req.sql.oldClone();
+        cloned.oldFrom = cloned.from;
+        cloned.from = (name, alias = null) => {
+            cloned.oldFrom(name, alias || null);
+            cloned.tableFrom = name;
+            return cloned;
+        };
+        return cloned;
+    };
+    next();
+});
+app.use(passport.initialize());
+app.use(passport.session());
+
+app.use((req, res, next) => {
+    res.setHeader('Last-Modified', (new Date()).toUTCString());
     next();
 });
+// Mounts all API routes under /api/v1
 app.use('/api/v1', api);
 
 // Catch 404 and forward to error handler
 app.use((req, res, next) => {
     res.status(404);
-    log.debug('%s %d %s', req.method, res.statusCode, req.url);
-    res.json({ error: 'Not found' }).end();
+    log.error('%s %d %s', req.method, res.statusCode, req.url);
+    res.json({ error: 'Error 404: Page not found' }).end();
 });
 
-// Error handlers
+// Express' default error handler
 app.use((err, req, res, next) => {
     res.status(err.status || 500);
     log.error('%s %d %s', req.method, res.statusCode, err.message);
+    log.error(`Route: ${req.originalUrl}`);
+    log.error(err);
     res.json({ error: err.message }).end();
 });
 
diff --git a/src/libs/config.js b/src/libs/config.js
index 5aea5a9ca50efd0dac2253dd23690229431236dc..2c07d19e94ba2505205d613a9ac6d5596c388502 100644
--- a/src/libs/config.js
+++ b/src/libs/config.js
@@ -1,8 +1,5 @@
-const nconf = require('nconf');
+let conf = require(`${process.cwd()}/config.json`);
 
-// Exports the config.json as an object with get functions
-nconf.argv()
-    .env()
-    .file({ file: `${process.cwd()}/config.json` });
+conf = conf[process.env.NODE_ENV];
 
-module.exports = nconf;
+module.exports = conf;
diff --git a/src/libs/convert/admDependency.js b/src/libs/convert/admDependency.js
new file mode 100644
index 0000000000000000000000000000000000000000..16017f06634d2d9b69ad1e605c4922c2b29ff243
--- /dev/null
+++ b/src/libs/convert/admDependency.js
@@ -0,0 +1,14 @@
+module.exports = function admDependency(id) {
+    switch (id) {
+        case 1:
+        return 'Federal';
+        case 2:
+        return 'Estadual';
+        case 3:
+        return 'Municipal';
+        case 4:
+        return 'Privada';
+        default:
+        return 'Não classificada';
+    }
+};
diff --git a/src/libs/convert/admDependencyPriv.js b/src/libs/convert/admDependencyPriv.js
new file mode 100644
index 0000000000000000000000000000000000000000..62afaa8354d2ebabe30826e66aceeb2a8c036790
--- /dev/null
+++ b/src/libs/convert/admDependencyPriv.js
@@ -0,0 +1,18 @@
+module.exports = function admDependencyPriv(id) {
+    switch (id) {
+        case 1:
+        return 'Federal';
+        case 2:
+        return 'Estadual';
+        case 3:
+        return 'Municipal';
+        case 4:
+        return 'Privada conveniada';
+        case 5:
+        return 'Privada não conveniada sem fins lucrativos';
+        case 6:
+        return 'Privada não conveniada com fins lucrativos';
+        default:
+        return 'Não classificada';
+    }
+};
diff --git a/src/libs/convert/ageRange.js b/src/libs/convert/ageRange.js
new file mode 100644
index 0000000000000000000000000000000000000000..b24990a1bd9870c90365d0af9b118c1578b1dd3a
--- /dev/null
+++ b/src/libs/convert/ageRange.js
@@ -0,0 +1,18 @@
+module.exports = function ageRange(id) {
+    switch (id) {
+        case 1:
+        return '0-3';
+        case 2:
+        return '4-5';
+        case 3:
+        return '6-10';
+        case 4:
+        return '11-14';
+        case 5:
+        return '15-17';
+        case 6:
+        return '18-24';
+        default:
+        return 'Não declarada';
+    }
+};
diff --git a/src/libs/convert/agreement.js b/src/libs/convert/agreement.js
new file mode 100644
index 0000000000000000000000000000000000000000..e9c5c774b0af69ed5b47d23e20893d7a953b8a4c
--- /dev/null
+++ b/src/libs/convert/agreement.js
@@ -0,0 +1,12 @@
+module.exports = function agreement(id) {
+    switch (id) {
+        case 1:
+        return 'Municipal';
+        case 2:
+        return 'Estadual';
+        case 3:
+        return 'Estadual e Municipal';
+        default:
+        return 'Não declarada';
+    }
+};
diff --git a/src/libs/convert/booleanVariable.js b/src/libs/convert/booleanVariable.js
new file mode 100644
index 0000000000000000000000000000000000000000..8ed8e007e2597049009072955c46319e32891a1c
--- /dev/null
+++ b/src/libs/convert/booleanVariable.js
@@ -0,0 +1,8 @@
+module.exports = function booleanVariable(id) {
+  if (id == null)
+    return 'Não Declarado';
+  else if (id == false)
+    return 'Não';
+  else if (id == true)
+    return 'Sim';
+};
diff --git a/src/libs/convert/citySize.js b/src/libs/convert/citySize.js
new file mode 100644
index 0000000000000000000000000000000000000000..819eb150e8a91e9546cf6935ce7b000b63242789
--- /dev/null
+++ b/src/libs/convert/citySize.js
@@ -0,0 +1,20 @@
+module.exports = function citySize(id) {
+    switch (id) {
+        case 1:
+        return 'até 5000';
+        case 2:
+        return '5001 - 10000';
+        case 3:
+        return '10001 - 20000';
+        case 4:
+        return '20001 - 50000';
+        case 5:
+        return '50001 - 100000';
+        case 6:
+        return '100001 - 500000';
+        case 7:
+        return 'mais que 500000';
+        default:
+        return 'Não classificada';
+    }
+};
diff --git a/src/libs/convert/contractType.js b/src/libs/convert/contractType.js
new file mode 100644
index 0000000000000000000000000000000000000000..6c9167ce6e3758feb8de1d20c7a3b228be1e4f17
--- /dev/null
+++ b/src/libs/convert/contractType.js
@@ -0,0 +1,14 @@
+module.exports = function idhmLevel(id) {
+    switch (id) {
+        case 1:
+        return 'Concursado/Efetivo/Estavél';
+        case 2:
+        return 'Contrato temporário';
+        case 3:
+        return 'Contrato terceirizado';
+        case 4:
+        return 'Contrato CLT';
+        default:
+        return 'Não classificado';
+    }
+};
diff --git a/src/libs/convert/educationLevel.js b/src/libs/convert/educationLevel.js
new file mode 100644
index 0000000000000000000000000000000000000000..e01c8be0f2a5ea682281bca1d95e396d9b2d9068
--- /dev/null
+++ b/src/libs/convert/educationLevel.js
@@ -0,0 +1,108 @@
+module.exports = function educationLevel(id) {
+    switch (id) {
+        case 1:
+        return 'Educação Infantil - Creche';
+        case 2:
+        return 'Educação Infantil - Pré-Escola';
+        case 3:
+        return 'Educação Infantil - Unificada';
+        case 4:
+        return 'Ensino Fundamental de 8 anos - 1ª Série';
+        case 5:
+        return 'Ensino Fundamental de 8 anos - 2ª Série';
+        case 6:
+        return 'Ensino Fundamental de 8 anos - 3ª Série';
+        case 7:
+        return 'Ensino Fundamental de 8 anos - 4ª Série';
+        case 8:
+        return 'Ensino Fundamental de 8 anos - 5ª Série';
+        case 9:
+        return 'Ensino Fundamental de 8 anos - 6ª Série';
+        case 10:
+        return 'Ensino Fundamental de 8 anos - 7ª Série';
+        case 11:
+        return 'Ensino Fundamental de 8 anos - 8ª Série';
+        case 12:
+        return 'Ensino Fundamental de 8 anos - Multi';
+        case 13:
+        return 'Ensino Fundamental de 8 anos - Correção de Fluxo';
+        case 14:
+        return 'Ensino Fundamental de 9 anos - 1º Ano';
+        case 15:
+        return 'Ensino Fundamental de 9 anos - 2º Ano';
+        case 16:
+        return 'Ensino Fundamental de 9 anos - 3º Ano';
+        case 17:
+        return 'Ensino Fundamental de 9 anos - 4º Ano';
+        case 18:
+        return 'Ensino Fundamental de 9 anos - 5º Ano';
+        case 19:
+        return 'Ensino Fundamental de 9 anos - 6º Ano';
+        case 20:
+        return 'Ensino Fundamental de 9 anos - 7º Ano';
+        case 21:
+        return 'Ensino Fundamental de 9 anos - 8º Ano';
+        case 22:
+        return 'Ensino Fundamental de 9 anos - Multi';
+        case 23:
+        return 'Ensino Fundamental de 9 anos - Correção de Fluxo';
+        case 24:
+        return 'Ensino Fundamental de 8 e 9 anos - Multi 8 e 9 anos';
+        case 25:
+        return 'Ensino Médio - 1ª Série';
+        case 26:
+        return 'Ensino Médio - 2ª Série';
+        case 27:
+        return 'Ensino Médio - 3ª Série';
+        case 28:
+        return 'Ensino Médio - 4ª Série';
+        case 29:
+        return 'Ensino Médio - Não Seriada 30 - Curso Técnico Integrado (Ensino Médio Integrado) 1ª Série';
+        case 30:
+        return 'Curso Técnico Integrado (Ensino Médio Integrado) 1ª Série';
+        case 31:
+        return 'Curso Técnico Integrado (Ensino Médio Integrado) 2ª Série';
+        case 32:
+        return 'Curso Técnico Integrado (Ensino Médio Integrado) 3ª Série';
+        case 33:
+        return 'Curso Técnico Integrado (Ensino Médio Integrado) 4ª Série';
+        case 34:
+        return 'Curso Técnico Integrado (Ensino Médio Integrado) Não Seriada';
+        case 35:
+        return 'Ensino Médio - Normal/Magistério 1ª Série';
+        case 36:
+        return 'Ensino Médio - Normal/Magistério 2ª Série';
+        case 37:
+        return 'Ensino Médio - Normal/Magistério 3ª Série';
+        case 38:
+        return 'Ensino Médio - Normal/Magistério 4ª Série';
+        case 39:
+        return 'Curso Técnico - Concomitante';
+        case 40:
+        return 'Curso Técnico - Subsequente';
+        case 41:
+        return 'Ensino Fundamental de 9 anos - 9º Ano';
+        case 56:
+        return 'Educação Infantil e Ensino Fundamental (8 e 9 anos) Multietapa';
+        case 64:
+        return 'Curso Técnico Misto (Concomitante e Subsequente)';
+        case 65:
+        return 'EJA - Ensino Fundamental - Projovem Urbano';
+        case 67:
+        return 'Curso FIC integrado na modalidade EJA - Nível Médio';
+        case 68:
+        return 'Curso FIC Concomitante';
+        case 69:
+        return 'EJA - Ensino Fundamental - Anos Iniciais EJA - Ensino Fundamental - Anos Iniciais';
+        case 70:
+        return 'EJA - Ensino Fundamental - Anos Finais';
+        case 71:
+        return 'EJA - Ensino Médio';
+        case 72:
+        return 'EJA - Ensino Fundamental - Anos Iniciais e Anos Finais';
+        case 73:
+        return 'Curso FIC integrado na modalidade EJA - Nível Fundamental (EJA integrada à Educação Profissional de Nível Fundamental)';
+        case 74:
+        return 'Curso Técnico Integrado na Modalidade EJA (EJA integrada à Educação Profissional de Nível Médio)';
+    }
+};
diff --git a/src/libs/convert/educationLevelBasic.js b/src/libs/convert/educationLevelBasic.js
new file mode 100644
index 0000000000000000000000000000000000000000..d667d09d57aa5cbe0b38d36f5f9a5aaece124fd8
--- /dev/null
+++ b/src/libs/convert/educationLevelBasic.js
@@ -0,0 +1,16 @@
+module.exports = function educationLevelBasic(id) {
+    switch (id) {
+        case 1:
+        return 'Creche';
+        case 2:
+        return 'Pré-Escola';
+        case 4:
+        return 'Ensino Fundamental - anos iniciais';
+        case 5:
+        return 'Ensino Fundamental - anos finais';
+        case 6:
+        return 'Ensino Médio';
+        default:
+        return 'Não classificada';
+    }
+};
diff --git a/src/libs/convert/educationLevelMod.js b/src/libs/convert/educationLevelMod.js
new file mode 100644
index 0000000000000000000000000000000000000000..f3797ee93a25085c444654923fe80d816ff671b3
--- /dev/null
+++ b/src/libs/convert/educationLevelMod.js
@@ -0,0 +1,26 @@
+module.exports = function educationLevelMod(id) {
+    switch (id) {
+        case 1:
+        return 'Creche';
+        case 2:
+        return 'Pré-Escola';
+        case 3:
+        return 'Educação Infantil Unificada';
+        case 4:
+        return 'Ensino Fundamental - anos iniciais';
+        case 5:
+        return 'Ensino Fundamental - anos finais';
+        case 6:
+        return 'Ensino Médio';
+        case 7:
+        return 'Turmas multisseriadas e multieatapas';
+        case 8:
+        return 'EJA - Ensino Fundamental';
+        case 9:
+        return 'EJA - Ensino Médio';
+        case 10:
+        return 'Educação Profissional';
+        default:
+        return 'Não classificada';
+    }
+};
diff --git a/src/libs/convert/educationLevelShort.js b/src/libs/convert/educationLevelShort.js
new file mode 100644
index 0000000000000000000000000000000000000000..ee97e956453c30af7ae7d37d60a6e2708b5dab2c
--- /dev/null
+++ b/src/libs/convert/educationLevelShort.js
@@ -0,0 +1,20 @@
+module.exports = function educationLevelShort(id) {
+    switch (id) {
+        case 1:
+        return 'Creche';
+        case 2:
+        return 'Pré-Escola';
+        case 3:
+        return 'Ensino Fundamental - anos iniciais';
+        case 4:
+        return 'Ensino Fundamental - anos finais';
+        case 5:
+        return 'Ensino Médio';
+        case 6:
+        return 'EJA';
+        case 7:
+        return 'EE exclusiva';
+        default:
+        return 'Não classificada';
+    }
+};
diff --git a/src/libs/convert/educationType.js b/src/libs/convert/educationType.js
new file mode 100644
index 0000000000000000000000000000000000000000..0fffb330c453b0c6f71cbdd9e25078f1ac6e246b
--- /dev/null
+++ b/src/libs/convert/educationType.js
@@ -0,0 +1,22 @@
+module.exports = function educationType(id) {
+    switch (id) {
+        case 1:
+        return 'Ensino Fundamental';
+        case 2:
+        return 'Ensino Médio';
+        case 3:
+        return 'Médio Normal ou Magistério';
+        case 4:
+        return 'Superior';
+        case 5:
+        return 'Superior com licenciatura';
+        case 6:
+        return 'Especialização';
+        case 7:
+        return 'Mestrado';
+        case 8:
+        return 'Doutorado';
+        default:
+        return 'Não definido';
+    }
+};
diff --git a/src/libs/convert/ethnicGroup.js b/src/libs/convert/ethnicGroup.js
new file mode 100644
index 0000000000000000000000000000000000000000..dfa51c810879cdd3655b73939766c2eae29333dd
--- /dev/null
+++ b/src/libs/convert/ethnicGroup.js
@@ -0,0 +1,18 @@
+module.exports = function ethnicGroup(id) {
+    switch (id) {
+        case 0:
+        return 'Não declarada';
+        case 1:
+        return 'Branca';
+        case 2:
+        return 'Preta';
+        case 3:
+        return 'Parda';
+        case 4:
+        return 'Amarela';
+        case 5:
+        return 'Indígena';
+        default:
+        return 'Não declarada';
+    }
+};
diff --git a/src/libs/convert/ethnicGroupPnad.js b/src/libs/convert/ethnicGroupPnad.js
new file mode 100644
index 0000000000000000000000000000000000000000..93428cd4440f4559dda7ff10c429c135e263960a
--- /dev/null
+++ b/src/libs/convert/ethnicGroupPnad.js
@@ -0,0 +1,12 @@
+module.exports = function ethnicGroupPnad(id) {
+    switch (id) {
+        case 0:
+        return 'Indígena';
+        case 1:
+        return 'Branca e amarela';
+        case 2:
+        return 'Preta e parda';
+        default:
+        return 'Sem declaração';
+    }
+};
diff --git a/src/libs/convert/extremesHouseholdIncome.js b/src/libs/convert/extremesHouseholdIncome.js
new file mode 100644
index 0000000000000000000000000000000000000000..67904c4ff2a4418f4dcb1f3516c8552bdabdcac7
--- /dev/null
+++ b/src/libs/convert/extremesHouseholdIncome.js
@@ -0,0 +1,10 @@
+module.exports = function extremesHouseholdIncome(id) {
+    switch (id) {
+        case 1:
+        return '10% menores';
+        case 2:
+        return '10% maiores';
+        default:
+        return 'Sem declaração';
+    }
+};
diff --git a/src/libs/convert/fifthHouseholdIncome.js b/src/libs/convert/fifthHouseholdIncome.js
new file mode 100644
index 0000000000000000000000000000000000000000..ec7669a2a6180ce95d64888f1841fe2c017eeaa5
--- /dev/null
+++ b/src/libs/convert/fifthHouseholdIncome.js
@@ -0,0 +1,16 @@
+module.exports = function fifthHouseholdIncome(id) {
+    switch (id) {
+        case 1:
+        return '20% menores';
+        case 2:
+        return '2o quinto';
+        case 3:
+        return '3o quinto';
+        case 4:
+        return '4o quinto';
+        case 5:
+        return '20% maiores';
+        default:
+        return 'Sem declaração';
+    }
+};
diff --git a/src/libs/convert/gender.js b/src/libs/convert/gender.js
new file mode 100644
index 0000000000000000000000000000000000000000..085eb20e0aa7fa18c2e9c3106fc6c8869b0bd639
--- /dev/null
+++ b/src/libs/convert/gender.js
@@ -0,0 +1,8 @@
+module.exports = function gender(id) {
+    switch(id) {
+        case 1:
+        return 'Masculino';
+        case 2:
+        return 'Feminino';
+    }
+};
diff --git a/src/libs/convert/genderPnad.js b/src/libs/convert/genderPnad.js
new file mode 100644
index 0000000000000000000000000000000000000000..9d5da51e6e8115d4c7de9baacd14b4e77756c11a
--- /dev/null
+++ b/src/libs/convert/genderPnad.js
@@ -0,0 +1,8 @@
+module.exports = function genderPnad(id) {
+    switch(id) {
+        case 2:
+        return 'Masculino';
+        case 4:
+        return 'Feminino';
+    }
+};
diff --git a/src/libs/convert/idhmLevel.js b/src/libs/convert/idhmLevel.js
new file mode 100644
index 0000000000000000000000000000000000000000..c9f1845c6147e4f59bbbcf7aaecd5934ac5c8017
--- /dev/null
+++ b/src/libs/convert/idhmLevel.js
@@ -0,0 +1,16 @@
+module.exports = function idhmLevel(id) {
+    switch (id) {
+        case 1:
+        return 'Muito Baixa';
+        case 2:
+        return 'Baixo';
+        case 3:
+        return 'Médio';
+        case 4:
+        return 'Alto';
+        case 5:
+        return 'Muito Alto';
+        default:
+        return 'Não classificada';
+    }
+};
diff --git a/src/libs/convert/incomeLevel.js b/src/libs/convert/incomeLevel.js
new file mode 100644
index 0000000000000000000000000000000000000000..c39106843027d91d628ec6bdda664507e660aae8
--- /dev/null
+++ b/src/libs/convert/incomeLevel.js
@@ -0,0 +1,16 @@
+module.exports = function citySize(id) {
+    switch (id) {
+        case 1:
+        return '1º quintil – 20% menores';
+        case 2:
+        return '2º quintil';
+        case 3:
+        return '3º quintil';
+        case 4:
+        return '4º quintil';
+        case 5:
+        return '5º quintil – 20% maiores';
+        default:
+        return 'Não classificada';
+    }
+};
diff --git a/src/libs/convert/location.js b/src/libs/convert/location.js
new file mode 100644
index 0000000000000000000000000000000000000000..606e06fc46a87bc5f1d1bf9c61aa8ad5ad06a353
--- /dev/null
+++ b/src/libs/convert/location.js
@@ -0,0 +1,16 @@
+module.exports = function location(id) {
+    switch (id) {
+        case 1:
+        return 'Urbana';
+        case 2:
+        return 'Rural';
+        case 3:
+        return 'Área de assentamento';
+        case 4:
+        return 'Terra indígena';
+        case 5:
+        return 'Área remanescente de quilombos';
+        case 6:
+        return 'Unidade de uso sustentável';
+    }
+};
diff --git a/src/libs/convert/period.js b/src/libs/convert/period.js
new file mode 100644
index 0000000000000000000000000000000000000000..06dbcf588e58ea2739b2e556ceac8a23ba8939d5
--- /dev/null
+++ b/src/libs/convert/period.js
@@ -0,0 +1,12 @@
+module.exports = function period(id) {
+    switch(id) {
+        case 1:
+        return 'Matutino';
+        case 2:
+        return 'Vespertino';
+        case 3:
+        return 'Noturno';
+        default:
+        return 'Indefinido';
+    }
+};
diff --git a/src/libs/convert/ruralLocation.js b/src/libs/convert/ruralLocation.js
new file mode 100644
index 0000000000000000000000000000000000000000..7fa97d527a5001cf8d8ebf2f0a2fac4fcdd6cfe5
--- /dev/null
+++ b/src/libs/convert/ruralLocation.js
@@ -0,0 +1,16 @@
+module.exports = function ruralLocation(id) {
+    switch (id) {
+        case 1:
+        return 'Urbana';
+        case 2:
+        return 'Rural';
+        case 3:
+        return 'Rural - Área de assentamento';
+        case 4:
+        return 'Rural - Terra indígena';
+        case 5:
+        return 'Rural - Área remanescente de quilombos';
+        case 6:
+        return 'Rural - Unidade de uso sustentável';
+    }
+};
diff --git a/src/libs/convert/schoolYear.js b/src/libs/convert/schoolYear.js
new file mode 100644
index 0000000000000000000000000000000000000000..f6b677ccd7c9ac39b8439504b0495e5d0e6dcbf0
--- /dev/null
+++ b/src/libs/convert/schoolYear.js
@@ -0,0 +1,54 @@
+module.exports = function schoolYear(id) {
+    switch(id) {
+        case 11:
+            return 'Creche - Menor de 1 ano';
+        case 12:
+            return 'Creche - 1 ano';
+        case 13:
+            return 'Creche - 2 anos';
+        case 14:
+            return 'Creche - 3 anos';
+        case 21:
+            return 'Pré-escola - 4 anos';
+        case 22:
+            return 'Pré-escola - 5 anos';
+        case 31:
+            return 'Ens. Fundamental - 1º Ano';
+        case 32:
+            return 'Ens. Fundamental - 1ª série/2º ano';
+        case 33:
+            return 'Ens. Fundamental - 2ª série/3º ano';
+        case 34:
+            return 'Ens. Fundamental - 3ª série/4º ano';
+        case 35:
+            return 'Ens. Fundamental - 4ª série/5º Ano';
+        case 41:
+            return 'Ens. Fundamental - 5ª série/6º ano';
+        case 42:
+            return 'Ens. Fundamental - 6ª série/7º ano';
+        case 43:
+            return 'Ens. Fundamental - 7ª série/8º ano';
+        case 44:
+            return 'Ens. Fundamental - 8ª serie/9º ano';
+        case 51:
+            return 'Ens. Médio - 1ª série'; // equivalent to 'EM 1 Série'
+        case 52:
+            return 'Ens. Médio - 2ª série'; // equivalent to 'EM 2 Série'
+        case 53:
+            return 'Ens. Médio - 3ª série'; // equivalent to 'EM 3 Série'
+        case 54:
+            return 'Ens. Médio - 4ª série'; // equivalent to 'EM 4 Série'
+        case 61:
+            return 'EJA - anos iniciais do Ens. Fundamental';
+        case 62:
+            return 'EJA - anos finais do Ens. Fundamental';
+        case 63:
+            return 'EJA - Ensino Médio';
+        case 64:
+            return 'EJA semi-presencial';
+        case 71:
+            return 'Educação Profissional';
+        default:
+            return 'Não classificada';
+    }
+};
diff --git a/src/libs/convert/stateName.js b/src/libs/convert/stateName.js
new file mode 100644
index 0000000000000000000000000000000000000000..3fdfdba4cf8ad67cda3b922a4ffd5f43e1e96011
--- /dev/null
+++ b/src/libs/convert/stateName.js
@@ -0,0 +1,58 @@
+module.exports = function stateName(id) {
+    switch (id) {
+        case 11:
+        return 'Rondônia';
+        case 12:
+        return 'Acre';
+        case 13:
+        return 'Amazonas';
+        case 14:
+        return 'Roraima';
+        case 15:
+        return 'Pará';
+        case 16:
+        return 'Amapá';
+        case 17:
+        return 'Tocantins';
+        case 21:
+        return 'Maranhão';
+        case 22:
+        return'Piauí';
+        case 23:
+        return 'Ceará';
+        case 24:
+        return 'Rio Grande do Norte';
+        case 25:
+        return 'Paraíba';
+        case 26:
+        return 'Pernambuco';
+        case 27:
+        return 'Alagoas';
+        case 28:
+        return 'Sergipe';
+        case 29:
+        return 'Bahia';
+        case 31:
+        return 'Minas Gerais';
+        case 32:
+        return 'Espírito Santo';
+        case 33:
+        return 'Rio de Janeiro ';
+        case 35:
+        return 'São Paulo';
+        case 41:
+        return 'Paraná';
+        case 42:
+        return 'Santa Catarina';
+        case 43:
+        return 'Rio Grande do Sul';
+        case 50:
+        return 'Mato Grosso do Sul';
+        case 51:
+        return 'Mato Grosso';
+        case 52:
+        return 'Goiás';
+        case 53:
+        return 'Distrito Federal';
+    }
+};
diff --git a/src/libs/db/monet.js b/src/libs/db/monet.js
index dd7e620adcf5b6a1dbe29358697fa71a3970ea03..d19fb37dfac5ed68cc15c1762a822818dbbc82da 100644
--- a/src/libs/db/monet.js
+++ b/src/libs/db/monet.js
@@ -6,16 +6,16 @@ const config = require(`${libs}/config`);
 
 // Connection options
 const poolOptions = {
-    nrConnections: config.get('monetdb:nrConnections'),
+    nrConnections: config.monetdb.nrConnections,
 };
 
 // Configuration options
 const options = {
-    host: config.get('monetdb:host'),
-    port: config.get('monetdb:port'),
-    dbname: config.get('monetdb:dbname'),
-    user: config.get('monetdb:user'),
-    password: config.get('monetdb:password'),
+    host: config.monetdb.host,
+    port: config.monetdb.port,
+    dbname: config.monetdb.dbname,
+    user: config.monetdb.user,
+    password: config.monetdb.password,
 };
 
 // Connection singleton
diff --git a/src/libs/db/mongoose.js b/src/libs/db/mongoose.js
index 30b4d4930c203537335c46b14bfb5e2958ace6bb..f9d2ed8ef59767e262f77fa21e2a93d444139145 100644
--- a/src/libs/db/mongoose.js
+++ b/src/libs/db/mongoose.js
@@ -6,9 +6,12 @@ const log = require(`${libs}/log`)(module);
 
 const mongoose = require('mongoose');
 
+mongoose.Promise = global.Promise;
+
 module.exports = () => {
-    const mongoUri = process.env.MONGO_URI || config.get('mongodb:uri');
-    log.debug(`Connecting to MongDB on URI ${mongoUri}`);
+    // Get mongodb URI (ip and port) in config file
+    const mongoUri = process.env.MONGO_URI || config.mongodb.uri;
+    log.info(`Connecting to MongoDB on URI ${mongoUri}`);
     // Connection singleton
     const db = mongoose.connect(mongoUri);
 
diff --git a/src/libs/db/query_exec.js b/src/libs/db/query_exec.js
index 71ae6d9824ac304eb5fd4e4d0e122b16d8d159a2..691117c5672df92ed565c2d4db5e27937b34dfbf 100644
--- a/src/libs/db/query_exec.js
+++ b/src/libs/db/query_exec.js
@@ -19,27 +19,31 @@ function execSqlQuery(sqlQuery, sqlQueryParams = []) {
     log.debug(`Executing SQL query '${sqlQuery}' with params '${sqlQueryParams}'`);
     return new Promise((resolve, reject) => {
         // Prepare statement
-        conn.prepare(sqlQuery, true).then(
-            (dbQuery) => {
-                // Execute query
-                dbQuery.exec(sqlQueryParams).then(
-                    // Success
-                    (dbResult) => {
-                        log.debug(`Query result: ${dbResult.data}`);
-                        log.debug(dbResult.data);
-                        resolve(dbResult.data);
-                    },
-                    // Error
-                    (dbError) => {
-                        log.error(`SQL query execution error: ${dbError.message}`);
-                        reject(new Error(dbError.message));
-                    }
-                );
-                // Release resources allocated for prepared statement
-                conn.release();
-            }
-        );
+        conn.prepare(sqlQuery, true).then((dbQuery) => {
+            // Execute query
+            dbQuery.exec(sqlQueryParams).then((dbResult) => {
+                // release resources allocated for the prepared statement
+                dbQuery.release();
+                resolve(dbResult.data);
+            }).catch((queryError) => {
+                log.error(`SQL query execution error: ${queryError.message}`);
+                log.error(`SQL query: ${sqlQuery} with params: ${sqlQueryParams}`);
+                // release resources allocated for the prepared statement
+                dbQuery.release();
+                reject(new Error(queryError.message));
+            });
+        }).catch((prepError) => {
+            log.error(`SQL prepared statement error: ${prepError.message}`);
+            log.error(`SQL query: ${sqlQuery} with params: ${sqlQueryParams}`);
+            reject(new Error(prepError.message));
+        });
     });
 }
 
-module.exports = execSqlQuery;
+function execMultiQuery(querySet = []) {
+    // Issue all queries concurrently to the database, for every query object in the iterable
+    // NOTE: Array.map() returns a copy of the original array with each object 'mapped'.
+    return querySet.map((qry) => { return execSqlQuery(qry.toString()); });
+}
+
+module.exports = {execSqlQuery, execMultiQuery};
diff --git a/src/libs/log.js b/src/libs/log.js
index fbe7b1394464e06611cbcd91d69e33c022846923..ddf0303f8481514b047aa6678894316500d25a96 100644
--- a/src/libs/log.js
+++ b/src/libs/log.js
@@ -47,7 +47,7 @@ function logger(module) {
         ],
         exitOnError: false,
     });
-    if (!config.get('debug')) {
+    if (!config.debug) {
         log.remove('debug-log');
     }
     return log;
diff --git a/src/libs/middlewares/addMissing.js b/src/libs/middlewares/addMissing.js
new file mode 100644
index 0000000000000000000000000000000000000000..2c6ac526651c9d1f362bd831018003135fe64fdb
--- /dev/null
+++ b/src/libs/middlewares/addMissing.js
@@ -0,0 +1,68 @@
+module.exports = function addMissing(rqf){
+    return (req, res, next) => {
+        let dims = Object.keys(req.dims).filter(i => {return i !== 'size';});
+        // FIXME: No momento, só funciona para duas dimensões (padrão para o LDE)
+        if(dims.length != 2) return next();
+        if(req.filter.min_year != req.filter.max_year) return next();
+        if(req.result.length === 0) return next();
+    
+        let result = req.result;
+        let dimsValues = {};
+        result.forEach((r) => {
+            dims.forEach((dim) => {
+                let rqfName = rqf.fieldValues[dim].resultField;
+                if(typeof dimsValues[rqfName] === 'undefined') {
+                    dimsValues[rqfName] = [];
+                }
+                if(dimsValues[rqfName].indexOf(r[rqfName]) === -1) {
+                    dimsValues[rqfName].push(r[rqfName]);
+                }
+            })
+        });
+    
+        let G = {};
+        let rqfName = rqf.fieldValues[dims[0]].resultField;
+        let rqfName2 = rqf.fieldValues[dims[1]].resultField;
+        G[rqfName] = {};
+        dimsValues[rqfName].forEach((value) => {
+            let secondDim = {};
+            
+            dimsValues[rqfName2].forEach((dValue) => {
+                secondDim[dValue] = false;
+            });
+    
+            G[rqfName][value] = {};
+            G[rqfName][value][rqfName2] = secondDim;
+        });
+        
+        result.forEach((r) => {
+            let resultDim1 = r[rqfName];
+            let resultDim2 = r[rqfName2];
+            
+            G[rqfName][resultDim1][rqfName2][resultDim2] = true;
+        });
+    
+        Object.keys(G[rqfName]).forEach((dim1Value) => {
+            Object.keys(G[rqfName][dim1Value][rqfName2]).forEach((dim2Value) => {
+                let value = G[rqfName][dim1Value][rqfName2][dim2Value];
+                if(!value) {
+                    let newEntry = Object.assign({}, result[0], {[rqfName]: parseInt(dim1Value, 10), [rqfName2]: parseInt(dim2Value, 10), total: 0});
+                    // result.push(newEntry);
+                    let index = 0;
+                    for(let i = 0; i < result.length; ++i) {
+                        let r = result[i];
+                        index = i;
+                        if(r[rqfName] > newEntry[rqfName]) break;
+                        if(r[rqfName] == newEntry[rqfName] && r[rqfName2] > newEntry[rqfName2]) break;
+                    }
+                    let newResult = [...result.slice(0, index), newEntry, ...result.slice(index, result.length)];
+                    result = newResult;
+                }
+            });
+        });
+    
+        req.result = result;
+    
+        next();
+    };
+};
\ No newline at end of file
diff --git a/src/libs/middlewares/checkVersion.js b/src/libs/middlewares/checkVersion.js
new file mode 100644
index 0000000000000000000000000000000000000000..c3187adba9081e2a992d09df36e839efc23dfca0
--- /dev/null
+++ b/src/libs/middlewares/checkVersion.js
@@ -0,0 +1,22 @@
+const curPath = process.cwd();
+const libs = `${process.cwd()}/libs`;
+const log = require(`${libs}/log`)(module);
+const chalk = require('chalk');
+const packageConf = require(`${curPath}/package.json`);
+
+module.exports = () => {
+    // Parse version number from strings such as 'v4.2.0' or `>=4.0.0'
+    function parseVersionNumber(versionString) {
+        return parseFloat(versionString.replace(/[^\d\.]/g, ''));
+    }
+    // Ensure minimum supported node version is used
+    const minNodeVersion = parseVersionNumber(packageConf.engines.node);
+    const currentNodeVersion = parseVersionNumber(process.version);
+    if (minNodeVersion > currentNodeVersion) {
+        log.error(chalk.red(`You must upgrade node to >=${minNodeVersion}.x to use simcaq-node!`));
+        return false;
+    } else {
+        log.info('Node version should work!');
+        return true;
+    }
+};
diff --git a/src/libs/middlewares/downloadDatabase.js b/src/libs/middlewares/downloadDatabase.js
new file mode 100644
index 0000000000000000000000000000000000000000..c4672ff0bf5d925d7d8d0466a260d3025fbc4e09
--- /dev/null
+++ b/src/libs/middlewares/downloadDatabase.js
@@ -0,0 +1,88 @@
+const libs = `${process.cwd()}/libs`;
+
+const log = require(`${libs}/log`)(module);
+
+const execute = require(`${libs}/middlewares/query`).execute;
+
+const request = require(`request`);
+
+const config = require(`${libs}/config`);
+
+const Download = require(`${libs}/models/download`);
+
+module.exports = function download(table, mappingTable) {
+    return (req, res, next) => {
+        // First, query the mapping
+        execute(`SELECT target_name, name FROM ${mappingTable}`, undefined, (err, result) => {
+            if(err) {
+                log.error(err.stack);
+                next(new Error('Request could not be satisfied due to a database error.'));
+            } else {
+                let header = '';
+                req.sql.from(table);
+                result.forEach((field) => {
+                    req.sql.field(`CASE ${table}.${field.name} WHEN true THEN 1 WHEN false THEN 0 ELSE ${table}.${field.name} END AS ${field.target_name}`);
+                    // req.sql.field(table + '.' + field.name, field.target_name);
+                    if(header === '') header += field.target_name;
+                    else header = header + ';' + field.target_name;
+                });
+
+                let form = {
+                    query: req.sql.toString(),
+                    table: req.sql.tableFrom,
+                    name: req.sql.tableFrom,
+                    username: req.user.name,
+                    email: req.user.email,
+                    header
+                };
+
+                request.post(config.cdn.url + '/api/v1/file', {form}, (err, response, body) => {
+                    if(err) {
+                        log.error(err);
+                        return res.json({error: err});
+                    }
+
+                    Download.findOne({query: req.sql.toString()}, (err, download) => {
+                        if(download) {
+                            download.updatedAt = Date.now();
+                            if(download.userId != req.user._id) {
+                                let dl = new Download({
+                                    userId: req.user._id,
+                                    table,
+                                    name: req.query.name,
+                                    mappingTable,
+                                    query: req.sql.toString(),
+                                    status: 'Enviando',
+                                    expired: false
+                                });
+                                console.log(dl);
+                                dl.save((err) => {
+                                    if(err) log.error(err);
+                                });
+                            }
+                        } else {
+                            download = new Download({
+                                userId: req.user._id,
+                                table,
+                                name: req.query.name,
+                                mappingTable,
+                                query: req.sql.toString(),
+                                query: req.sql.toString(),
+                                status: 'Enviando',
+                                expired: false
+                            });
+                            console.log(download);
+                        }
+
+                        download.save((err) => {
+                            if(err) {
+                                log.error(err);
+                            }
+                            res.json({msg: 'Wait for download email', waitForIt: true});
+                        });
+                    });
+                });
+            }
+        });
+    }
+};
\ No newline at end of file
diff --git a/src/libs/middlewares/email.js b/src/libs/middlewares/email.js
new file mode 100644
index 0000000000000000000000000000000000000000..7e0403d423cebd1bd0a8c6c31f57eef5773860f4
--- /dev/null
+++ b/src/libs/middlewares/email.js
@@ -0,0 +1,37 @@
+const libs = `${process.cwd()}/libs`;
+const log = require(`${libs}/log`)(module);
+const config = require(`${libs}/config`);
+const nodemailer = require('nodemailer');
+const htmlToText = require('nodemailer-html-to-text').htmlToText;
+
+let transporter = nodemailer.createTransport({
+    host: config.email.host,
+    port: config.email.port,
+    secure: config.email.secure,
+    ignoreTLS: config.email.ignoreTLS
+});
+
+transporter.use('compile', htmlToText());
+
+// verify connection configuration
+transporter.verify(function(error, success) {
+   if (error) {
+        log.error(error);
+   } else {
+        log.info('Email server is ready to take our messages');
+   }
+});
+
+const mailOptions = {
+    from: config.email.from
+};
+
+module.exports = function send(options, cb) {
+    let opt = Object.assign({}, mailOptions, options);
+    transporter.sendMail(opt, (err, info) => {
+        if(err) {
+            return cb(err);
+        }
+        cb(null, info);
+    });
+};
\ No newline at end of file
diff --git a/src/libs/middlewares/id2str.js b/src/libs/middlewares/id2str.js
new file mode 100644
index 0000000000000000000000000000000000000000..a6c4af2ed6a429baaeffc90d2a0ec220ba4eea58
--- /dev/null
+++ b/src/libs/middlewares/id2str.js
@@ -0,0 +1,128 @@
+const libs = `${process.cwd()}/libs`;
+const gender = require(`${libs}/convert/gender`);
+const period = require(`${libs}/convert/period`);
+const schoolYear = require(`${libs}/convert/schoolYear`);
+const admDependency = require(`${libs}/convert/admDependency`);
+const admDependencyPriv = require(`${libs}/convert/admDependencyPriv`);
+const location = require(`${libs}/convert/location`);
+const ruralLocation = require(`${libs}/convert/ruralLocation`);
+const ethnicGroup = require(`${libs}/convert/ethnicGroup`);
+const agreement = require(`${libs}/convert/agreement`);
+const booleanVariable = require(`${libs}/convert/booleanVariable`);
+const educationLevel = require(`${libs}/convert/educationLevel`);
+const educationLevelMod = require(`${libs}/convert/educationLevelMod`);
+const educationLevelShort = require(`${libs}/convert/educationLevelShort`);
+const educationType = require(`${libs}/convert/educationType`);
+const citySize = require(`${libs}/convert/citySize`);
+const incomeLevel = require(`${libs}/convert/incomeLevel`);
+const idhmLevel = require(`${libs}/convert/idhmLevel`);
+const stateName = require(`${libs}/convert/stateName`);
+const contractType = require(`${libs}/convert/contractType`);
+const ethnicGroupPnad = require(`${libs}/convert/ethnicGroupPnad`);
+const ageRange = require(`${libs}/convert/ageRange`);
+const genderPnad = require(`${libs}/convert/genderPnad`);
+const fifthHouseholdIncome = require(`${libs}/convert/fifthHouseholdIncome`);
+const extremesHouseholdIncome = require(`${libs}/convert/extremesHouseholdIncome`);
+const educationLevelBasic = require(`${libs}/convert/educationLevelBasic`);
+
+const ids = {
+    gender_id: gender,
+    period_id: period,
+    school_year_id: schoolYear,
+    education_level_id: educationLevel,
+    education_level_basic_id: educationLevelBasic,
+    education_level_mod_id: educationLevelMod,
+    education_level_short_id: educationLevelShort,
+    adm_dependency_id: admDependency,
+    adm_dependency_detailed_id: admDependencyPriv,
+    location_id: location,
+    rural_location_id: ruralLocation,
+    location_detailed_id: ruralLocation,
+    ethnic_group_id: ethnicGroup,
+    agreement_id: agreement,
+    integral_time_id: booleanVariable,
+    government_agreement_id: booleanVariable,
+    education_day_care_child_id: booleanVariable,
+    education_preschool_child_id: booleanVariable,
+    education_begin_elementary_school_id: booleanVariable,
+    education_end_elementary_school_id: booleanVariable,
+    education_middle_school_id: booleanVariable,
+    education_professional_id: booleanVariable,
+    education_eja_id: booleanVariable,
+    education_type_id: educationType,
+    income_level_id: incomeLevel,
+    city_size_id: citySize,
+    idhm_level_id: idhmLevel,
+    state_id: stateName,
+    contract_type_id: contractType,
+    ethnic_group_pnad_id: ethnicGroupPnad,
+    age_range_id: ageRange,
+    gender_pnad_id: genderPnad,
+    fifth_household_income_id: fifthHouseholdIncome,
+    extremes_household_income_id: extremesHouseholdIncome
+};
+
+function transform(removeId=false) {
+    return (req, res, next) => {
+        if(req.result.length <= 0) {
+            return next();
+        }
+        // Para cada objeto do resultado
+        req.result.forEach((obj) => {
+            Object.keys(obj).forEach((key) => {
+                // Se não há uma função especificada, retorna
+                if(typeof ids[key] === 'undefined') return;
+                let id = obj[key];
+                obj[key.replace('_id', '_name')] = ids[key](id);
+                if(removeId) delete obj[key];
+            });
+        });
+        next();
+    };
+}
+
+function multitransform(removeId=false) {
+    return (req, res, next) => {
+        Object.keys(req.result[0]).forEach((query) => {
+            req.result[0][query].forEach((obj) => {
+                Object.keys(obj).forEach((key) => {
+                    if(typeof ids[key] === 'undefined') return;
+                    let id = obj[key];
+                    obj[key.replace('_id', '_name')] = ids[key](id);
+                    if(removeId) delete obj[key];
+                });
+            })
+        });
+        next();
+    }
+}
+
+module.exports = {
+    transform,
+    multitransform,
+    gender,
+    period,
+    schoolYear,
+    educationLevel,
+    educationLevelBasic,
+    educationLevelMod,
+    educationLevelShort,
+    admDependency,
+    admDependencyPriv,
+    location,
+    ruralLocation,
+    ethnicGroup,
+    agreement,
+    booleanVariable,
+    educationType,
+    incomeLevel,
+    citySize,
+    idhmLevel,
+    stateName,
+    contractType,
+    ethnicGroupPnad,
+    ageRange,
+    genderPnad,
+    fifthHouseholdIncome,
+    extremesHouseholdIncome
+};
diff --git a/src/libs/middlewares/multiQuery.js b/src/libs/middlewares/multiQuery.js
new file mode 100644
index 0000000000000000000000000000000000000000..7bf37c5d9086f3a97405ef2bfbb61553afd0d2bf
--- /dev/null
+++ b/src/libs/middlewares/multiQuery.js
@@ -0,0 +1,16 @@
+const libs = `${process.cwd()}/libs`;
+const log = require(`${libs}/log`)(module);
+const db = require(`${libs}/db/query_exec`);
+
+// Middleware that executes multiples queries 
+function multiQuery(req, res, next) {
+    Promise.all(db.execMultiQuery(req.querySet)).then((queryResults) => {
+        req.result = queryResults;
+        next();
+    }).catch((err) => {
+        log.error(`[SQL query error] ${err}`);
+        next(err);
+    });
+}
+
+module.exports = multiQuery;
diff --git a/src/libs/middlewares/oauth2.js b/src/libs/middlewares/oauth2.js
new file mode 100644
index 0000000000000000000000000000000000000000..cc9a45ede4a3158bc055166202c6d20fb84a6dff
--- /dev/null
+++ b/src/libs/middlewares/oauth2.js
@@ -0,0 +1,121 @@
+const oauth2orize = require('oauth2orize');
+const passport = require('passport');
+const crypto = require('crypto');
+
+const libs = `${process.cwd()}/libs`;
+
+const config = require(`${libs}/config`);
+const log = require(`${libs}/log`)(module);
+
+const db = require(`${libs}/db/mongoose`);
+const User = require(`${libs}/models/user`);
+const AccessToken = require(`${libs}/models/accessToken`);
+const RefreshToken = require(`${libs}/models/refreshToken`);
+
+// create OAuth 2.0 server
+let aserver = oauth2orize.createServer()
+
+// Generic error handler
+let errFn = (cb, err) => {
+    if (err) {
+        return cb(err)
+    }
+}
+
+// Destroys any old tokens and generates a new access and refresh token
+let generateTokens = (data, done) => {
+    // curries in `done` callback so we don't need to pass it
+    let errorHandler = errFn.bind(undefined, done);
+    let refreshToken;
+    let refreshTokenValue;
+    let token;
+    let tokenValue;
+
+    RefreshToken.remove(data, errorHandler);
+    AccessToken.remove(data, errorHandler);
+
+    tokenValue = crypto.randomBytes(32).toString('hex');
+    refreshTokenValue = crypto.randomBytes(32).toString('hex');
+
+    data.token = tokenValue;
+    token = new AccessToken(data);
+
+    data.token = refreshTokenValue;
+    refreshToken = new RefreshToken(data);
+
+    refreshToken.save(errorHandler);
+
+    token.save((err) => {
+        if (err) {
+            log.error(err);
+            return done(err);
+        }
+        done(null, tokenValue, refreshTokenValue, {
+            'expires_in': config.security.tokenLife
+        });
+    })
+};
+
+// Exchange username & password for access token.
+aserver.exchange(oauth2orize.exchange.password((client, username, password, scope, done) => {
+    User.findOne({ email: username }, (err, user) => {
+        if (err) {
+            return done(err);
+        }
+
+        if (!user || !user.checkPassword(password)) {
+            return done(null, false);
+        }
+
+        var model = {
+            userId: user._id,
+            clientId: client._id
+        };
+        log.info(`Gerando token para usuário ${user.name}`);
+        generateTokens(model, done);
+    })
+
+}));
+
+// Exchange refreshToken for access token.
+aserver.exchange(oauth2orize.exchange.refreshToken((client, refreshToken, scope, done)  =>{
+    RefreshToken.findOne({ token: refreshToken, clientId: client._id }, (err, token) => {
+        if (err) {
+            return done(err);
+        }
+
+        if (!token) {
+            return done(null, false);
+        }
+
+        User.findById(token.userId, (err, user)  => {
+            if (err) { 
+                log.error(err);
+                return done(err);
+            }
+            if (!user) { 
+                return done(null, false); 
+            }
+
+            var model = {
+                userId: user._id,
+                clientId: client._id
+            };
+
+            generateTokens(model, done);
+        })
+    })
+}))
+
+// token endpoint
+//
+// `token` middleware handles client requests to exchange authorization grants
+// for access tokens.  Based on the grant type being exchanged, the above
+// exchange middleware will be invoked to handle the request.  Clients must
+// authenticate when making requests to this endpoint.
+
+exports.token = [
+    passport.authenticate(['oauth2-client-password'], { session: false }),
+    aserver.token(),
+    aserver.errorHandler()
+];
diff --git a/src/libs/middlewares/parseParams.js b/src/libs/middlewares/parseParams.js
deleted file mode 100644
index c74b6b491bc445b22cd480c85d9f232494a139de..0000000000000000000000000000000000000000
--- a/src/libs/middlewares/parseParams.js
+++ /dev/null
@@ -1,66 +0,0 @@
-/**
-* ParseParams middleware
-*
-* EXAMPLE:
-* Use it with no parameters to get all the params specified
-* app.get('/', parseParams('dims'), function(req, res, next){})
-*
-* Use it with an array of accepted values
-* app.get('/', parseParams('filter', ['year', 'location']), function(req, res, next){})
-*
-* Use it globally
-* app.use(parseParams('dims'))
-*/
-
-const libs = `${process.cwd()}/libs`;
-
-const log = require(`${libs}/log`)(module);
-
- // This function returns the intersection of two arrays
-function intersect(a, b) {
-    let t;
-    if (b.length > a.length) {
-        t = b; b = a; a = t;
-    }
-    return a.filter((e) => b.indexOf(e) !== -1);
-}
-
-function parseParams(queryParam, arr) {
-    return (req, res, next) => {
-        req[queryParam] = {};
-        if (req.query[queryParam]) {
-            const params = req.query[queryParam].split(',');
-            // Temporary object to hold the params and it's values
-            const obj = {};
-            for (const param of params) {
-                // Get the key and the value - state:41 is key 'state' whith value 41
-                const kv = param.split(':');
-                // Check if there is a value. If there isn't, assign null
-                obj[kv[0]] = (typeof kv[1] === 'undefined') ? null : kv[1];
-            }
-
-            // If the array exists and is not empty we intersect
-            if (typeof arr !== 'undefined' && arr.length > 0) {
-                // Intersect the keys of the obj with the array arr.
-                // The intersection array is assigned with the keys
-                const intersection = intersect(arr, Object.keys(obj));
-                // This is a bit tricky...
-                // For each key in the intersection array we get it's value in the obj
-                // and assign it to the custom attribute in the req obj.
-                // For example: instersection => ["state"] so
-                // obj[intersection[i]] (with i=0) is obj["state"], that is 41
-                // and req[queryParam]["state"] = 41
-                for (let i = 0; i < intersection.length; ++i) {
-                    req[queryParam][intersection[i]] = obj[intersection[i]];
-                }
-                req[queryParam].size = intersection.length;
-            } else {
-                req[queryParam] = obj;
-                req[queryParam].size = Object.keys(obj).length;
-            }
-        }
-        next();
-    };
-}
-
-module.exports = parseParams;
diff --git a/src/libs/middlewares/passport.js b/src/libs/middlewares/passport.js
new file mode 100644
index 0000000000000000000000000000000000000000..ab895a9604072e3b77a6384ac0c0f9066dcf6753
--- /dev/null
+++ b/src/libs/middlewares/passport.js
@@ -0,0 +1,67 @@
+const passport = require('passport');
+const ClientPasswordStrategy = require('passport-oauth2-client-password');
+const BearerStrategy = require('passport-http-bearer').Strategy;
+
+const libs = `${process.cwd()}/libs`;
+const config = require(`${libs}/config`);
+
+const User = require(`${libs}/models/user`);
+const Client = require(`${libs}/models/client`);
+const AccessToken = require(`${libs}/models/accessToken`);
+const RefreshToken = require(`${libs}/models/refreshToken`);
+const email = require(`${libs}/middlewares/email`);
+
+passport.use(new ClientPasswordStrategy( (clientId, clientSecret, done) => {
+        Client.findOne({ _id: clientId }, (err, client) => {
+            if (err) {
+                return done(err);
+            }
+
+            if (!client) {
+                return done(null, false);
+            }
+
+            if (client.clientSecret !== clientSecret) {
+                return done(null, false);
+            }
+
+            return done(null, client);
+        })
+    }
+));
+
+passport.use(new BearerStrategy( (accessToken, done) => {
+        AccessToken.findOne({ token: accessToken }, (err, token) => {
+            if (err) {
+                return done(err);
+            }
+
+            if (!token) {
+                return done(null, false);
+            }
+
+            if( Math.round((Date.now()-token.created)/1000) > config.security.tokenLife) {
+                AccessToken.remove({ token: accessToken }, (err) => {
+                    if (err) {
+                        return done(err);
+                    }
+                });
+
+                return done(null, false, { msg: 'Token expired' });
+            }
+
+            User.findById(token.userId, function(err, usuario) {
+                if (err) {
+                    return done(err);
+                }
+
+                if (!usuario) {
+                    return done(null, false, { msg: 'Unknown user' });
+                }
+
+                var info = { scope: '*' };
+                done(null, usuario, info);
+            })
+        })
+    }
+));
diff --git a/src/libs/middlewares/query.js b/src/libs/middlewares/query.js
index a4f20e3ba5bdd31cf7dab85a44121b8e8e456fc0..ae002df2fb13727152497dc9ad61c1371d98d194 100644
--- a/src/libs/middlewares/query.js
+++ b/src/libs/middlewares/query.js
@@ -1,18 +1,29 @@
 const libs = `${process.cwd()}/libs`;
 const log = require(`${libs}/log`)(module);
-const execQuery = require(`${libs}/db/query_exec`);
+const db = require(`${libs}/db/query_exec`);
 
  // Middleware that executes a query defined by a squel object in req.sql
 function query(req, res, next) {
     let sql = req.sql.toParam();
-    log.debug(sql);
-    execQuery(sql.text, sql.values).then((result) => {
-        log.debug(result);
-        req.result = result;
-        next();
+    log.info(`Executando query ${req.sql.toString()}`);
+    execute(sql.text, sql.values, (err, result) => {
+        if(err) {
+            log.error(err.stack);
+            next(new Error('Request could not be satisfied due to a database error.'));
+        } else {
+            req.result = result;
+            next();
+        }
+    });
+}
+
+function execute(text, values, cb) {
+    db.execSqlQuery(text, values).then((result) => {
+        cb(null, result);
     }, (error) => {
-        next(error);
+        log.error(error.stack);
+        cb(new Error('Request could not be satisfied due to a database error.'));
     });
 }
 
-module.exports = query;
+module.exports = {query, execute};
diff --git a/src/libs/middlewares/reqQueryFields.js b/src/libs/middlewares/reqQueryFields.js
new file mode 100644
index 0000000000000000000000000000000000000000..5b28567fcf5ded59647c712a2a3dbd17719bab00
--- /dev/null
+++ b/src/libs/middlewares/reqQueryFields.js
@@ -0,0 +1,335 @@
+const libs = `${process.cwd()}/libs`;
+
+const log = require(`${libs}/log`)(module);
+
+const _ = require('lodash');
+
+function parseWhereValue(type, value) {
+    if(type === 'integer') return parseInt(value, 10);
+    if(type === 'double') return parseFloat(value);
+    if(type === 'string') return '%'+value+'%';
+    if(type === 'boolean') {
+        if(value === null || typeof value === 'boolean') {
+            return value;
+        }
+        if(typeof value === 'string') {
+            if(value.toLowerCase() === 'true' || value.toLowerCase() === '1') {
+                return true;
+            }
+            if(value.toLowerCase() === 'null') {
+                return null;
+            }
+        }
+        return false;
+    }
+}
+
+class ReqQueryFields {
+    constructor(fields = {}, fieldValues = {}) {
+        // Exemplo de requisição: `/api/v1/enrollments?dims=state,region,location`
+
+        // Parâmetros no campo query da requisição.
+        // Exemplo de field:
+        // ```js
+        // {
+        //    name: 'dims',
+        //    field: true,
+        //    where: false
+        // }
+        // ```
+        // Array de valores aceitos pelo campo.
+        // Exemplo de valor:
+        // ```
+        // {
+        //     name: 'location',
+        //     table: 'localizacao',
+        //     tableField: 'descricao'
+        //     resultField: 'location_name',
+        //     where: {
+        //         relation: '=',
+        //         type: 'integer',
+        //         field: 'id_localizacao',
+        //         table: 'turma'
+        //     },
+        //     join: {
+        //         primary: 'pk_localizacao_id',
+        //         foreign: 'id_localizacao',
+        //         foreignTable: 'turma'
+        //     }
+        // }
+        // ```
+        this.fields = fields;
+        this.fieldValues = fieldValues;
+    }
+
+    addField(field) {
+        // Parâmetro no campo query da requisição.
+        // Exemplo de field:
+        // ```
+        // {
+        //    name: 'dims',
+        //    field: true,
+        //    where: false,
+        //    fieldValues: {}
+        // }
+        // ```
+        if(typeof this.fields[field.name] === 'undefined') {
+            this.fields[field.name] = field;
+        }
+        return this;
+    }
+
+    addValue(fieldValue) {
+        // Valor aceito por **todos** os campos.
+        // Exemplo de valor:
+        // ```
+        // {
+        //     name: 'location',
+        //     table: 'localizacao',
+        //     tableField: 'descricao'
+        //     resultField: 'location_name',
+        //     where: {
+        //         relation: '=',
+        //         type: 'integer',
+        //         field: 'id_localizacao',
+        //         table: 'turma'
+        //     },
+        //     join: {
+        //         primary: 'pk_localizacao_id',
+        //         foreign: 'id_localizacao',
+        //         foreignTable: 'turma'
+        //     }
+        // }
+        // ```
+        if(typeof this.fieldValues[fieldValue.name] === 'undefined') {
+            this.fieldValues[fieldValue.name] = fieldValue;
+        }
+        return this;
+    }
+
+    addValueToField(fieldValue, field) {
+        // Valor aceito **apenas** pelo campo `field`.
+        if(typeof this.fields[field] === 'undefined') {
+            // Se o campo não existe, lança uma exception
+            throw 'No field with name ' +field+ ' defined';
+        }
+        if(typeof this.fields[field].values === 'undefined') {
+            this.fields[field].values = {};
+        }
+        if(typeof this.fields[field].values[fieldValue.name] === 'undefined') {
+            this.fields[field].values[fieldValue.name] = fieldValue;
+        }
+        return this;
+    }
+
+    parse() {
+        // Faz o parse dos valores que vem na requisição para objetos.
+        return (req, res, next) => {
+            // "Foreach" nos campos aceitos
+            Object.keys(this.fields).map((key, index) => {
+                let params = [];
+                // f é o campo
+                let f = this.fields[key];
+                // Unimos os valores parametros globalmente com os aceitos apenas pelo campo
+                let values = _.merge(this.fieldValues, f.values);
+                // Fazemos um foreach nos parametros aceitos
+                Object.keys(values).map((k, i) => {
+                    let value = values[k];
+                    // Pushamos o parametro
+                    params.push(value.name);
+                });
+                let queryField = f.name;
+                let arrayOfParams = params;
+                // Criamos o atributo com o nome do **campo** no objeto `req` (nome do campo é 'filter', 'dims', 'search', etc)
+                req[queryField] = {};
+                if (req.query[queryField]) {
+                    // Se há mais de um parametro no campo, eles estão separados por vírgula.
+                    // Fazemos o split então para separar os valores
+                    const regex = /,(?=[a-z])/; // Pega as vírgulas que não estão nos atributos multivalorados
+                    const params = req.query[queryField].split(regex);
+                    // Objeto temporário para guardar os parametros e seus valores.
+                    const obj = {};
+                    for (const param of params) {
+                        // O parametro *pode* ter um valor (por exemplo: `state:41`).
+                        // Fazemos o split e temos um array `['state', 41]`
+                        const kv = param.split(':');
+                        // Checa se há um valor. Se não tem, definimos como true.
+                        if ( (typeof kv[1] === 'undefined')) {
+                            obj[kv[0]] = true;
+                        } else {
+                            try {
+                                obj[kv[0]] = JSON.parse(kv[1]);
+                            } catch(err) {
+                                obj[kv[0]] = kv[1];
+                            }
+                        }
+                        // `obj` é agora `{kv[0]: kv[1]}` ou `{kv[0]: true}`.
+                        // No exemplo `{'state': 41}`
+                    }
+
+                    // Se o array existe e não está vazio fazemos a interseção
+                    if (typeof arrayOfParams !== 'undefined' && arrayOfParams.length > 0) {
+                        // Fazemos a interseção das chaves de `obj` com o array `arrayOfParams`.
+                        // O array resultante são as chaves aceitas pelo campo.
+                        const intersection = _.intersection(arrayOfParams, Object.keys(obj));
+                        // Isso é um pouco complicado...
+                        // Para cada chave na interseção pegamos seu valor em `obj`
+                        // e atribuímos para o atributo que definimos no objeto `req`.
+                        // Por exemplo: intersection = `['state']` então
+                        // `obj[intersection[i]]` (com i=0) é `obj['state']`, cujo valor é 41.
+                        // Então fazemos `req[queryField]['state'] = 41`
+                        for (let i = 0; i < intersection.length; ++i) {
+                            req[queryField][intersection[i]] = obj[intersection[i]];
+                        }
+                        req[queryField].size = intersection.length;
+                    } else {
+                        req[queryField] = obj;
+                        req[queryField].size = Object.keys(obj).length;
+                    }
+                }
+            });
+            next();
+        };
+    }
+
+    buildQuery(req, sql) {
+        // "Constrói" o SQL
+        let hasJoined = {};
+        let thisTable = sql.tableFrom;
+        // Foreach no campos
+        Object.keys(this.fields).forEach((key) => {
+            // Campo
+            let field = this.fields[key];
+            // log.debug(field);
+            // `param` aqui é o atributo no objeto `req` (dims, filter, search, ...)
+            let param = req[field.name];
+            // Fazemos um foreach nos parametros dentro do atributo
+            Object.keys(param).forEach((k) => {
+                let values = _.merge(this.fieldValues, field.values);
+                if(typeof values[k] !== 'undefined') {
+                    // Clonamos para não alterar o original
+                    let value = _.clone(values[k]);
+                    if(value.parseOnly) return;
+                    // Checa se não fizemos o join para este valor e se é necessário fazer
+                    if(!hasJoined[value.table] && typeof value.join !== 'undefined') {
+                        let foreignTable = '';
+                        if(value.join.foreignTable) foreignTable = value.join.foreignTable+'.';
+                        if(value.join.foreignTable === '@') foreignTable = thisTable+'.';
+                        // Fazemos o join
+                        let onClause = '';
+                        if(Array.isArray(value.join.primary)) {
+                            // Se é um array, montamos a cláusula ON com mais de uma coluna
+                            value.join.primary.forEach((column, index, arr) => {
+                                onClause += foreignTable+value.join.foreign[index]+'='+value.table+'.'+column;
+                                if(index < arr.length-1) {
+                                    onClause+=' AND ';
+                                }
+                            });
+                        } else {
+                            onClause = foreignTable+value.join.foreign+'='+value.table+'.'+value.join.primary;
+                        }
+                        sql.join(value.table, null, onClause);
+                        // Marcamos o join como feito para não ter problemas
+                        hasJoined[value.table] = true;
+                    }
+                    // Se o valor é um campo a ser incluído no SELECT
+                    if(typeof field.field !== 'undefined' && field.field) {
+                        // log.debug('SELECT');
+                        let table = value.table;
+                        if(table === '@') table = thisTable;
+                        if (Array.isArray(value.resultField)) {
+                            value.tableField.forEach((f, i) => {
+                                sql.field(table+'.'+f, value.resultField[i] || f)
+                                    .group(table+'.'+f)
+                                    .order(table+'.'+f);
+                            })
+                        }else{
+                            sql.field(table+'.'+value.tableField, value.resultField || value.tableField)
+                                .order(table+'.'+value.tableField)
+                                .group(table+'.'+value.tableField);
+                        }
+                    }
+                    // Se o valor é um campo para ser usado no WHERE
+                    if(typeof field.where !== 'undefined' && field.where) {
+                        // log.debug('WHERE');
+                        // Valor do where
+
+                        let whereValue = param[k];
+                        // log.debug('whereValue');
+                        // log.debug(whereValue);
+                        // log.debug(`Where value é array? ${Array.isArray(whereValue)}`);
+
+                        let tbl = value.where.table || value.table;
+                        if (tbl === '@') tbl = thisTable;
+                        // multiple where
+                        if (Array.isArray(value.where.field)) {
+                            let lower = (value.where.type === 'string') ? ' LOWER(?) ' : ' ? ';
+                            let whereField = '';
+                            let whereValues = [];
+                            value.where.field.forEach((f, i, arr) => {
+                                whereField += (value.where.type === 'string') ? 'LOWER(' + tbl + '.' + value.where.field[i] + ')' : tbl + '.' + value.where.field[i];
+                                whereField += ' ' + value.where.relation + ' ?';
+                                if (i < arr.length - 1) {
+                                    whereField += ' ' + value.where.condition + ' ';
+                                }
+
+                                if (Array.isArray(whereValue)) {
+                                    let whereString = '(';
+                                    for(let i = 0; i < whereValue.length; ++i) {
+                                        whereString += whereField;
+                                        whereValues.push(parseWhereValue(value.where.type, whereValue[i]));
+                                        if(i < whereValue.length-1) {
+                                            whereString += ' OR ';
+                                        }
+                                    }
+                                    whereString += ')';
+                                } else {
+                                    whereValues.push(parseWhereValue(value.where.type, whereValue));
+                                }
+                            });
+
+                            sql.where(whereField, ...whereValues);
+                        } else {
+                            let whereField = (value.where.type === 'string') ? 'LOWER(' + tbl + '.' + value.where.field + ')' : tbl + '.' + value.where.field;
+                            let lower = (value.where.type === 'string') ? ' LOWER(?) ' : ' ? ';
+                            if(Array.isArray(whereValue)) {
+                                let whereString = '(';
+                                let arrayWhereValues = [];
+                                for(let i = 0; i < whereValue.length; ++i) {
+                                    whereString += whereField + ' ' + value.where.relation + lower;
+                                    arrayWhereValues.push(parseWhereValue(value.where.type, whereValue[i]));
+                                    if(i < whereValue.length-1) {
+                                        whereString += ' OR ';
+                                    }
+                                }
+                                whereString += ')';
+                                sql.where(whereString, ...arrayWhereValues);
+                            } else {
+                                sql.where(whereField + ' ' + value.where.relation + lower, parseWhereValue(value.where.type, whereValue));
+                            }
+                        }
+                    }
+                }
+            });
+        });
+        return sql;
+    }
+
+    build() {
+        return (req, res, next) => {
+            req.sql = this.buildQuery(req, req.sql);
+            next();
+        };
+    }
+
+    multibuild() {
+        return (req, res, next) => {
+            req.querySet.forEach((query) => {
+                query = this.buildQuery(req, query);
+            });
+            next();
+        };
+    }
+}
+
+module.exports = ReqQueryFields;
diff --git a/src/libs/middlewares/response.js b/src/libs/middlewares/response.js
index 580c16f5bd7470d20c622565954234f873f56b54..bd5009cd1e55ea391231348b30eea07568c6258d 100644
--- a/src/libs/middlewares/response.js
+++ b/src/libs/middlewares/response.js
@@ -7,12 +7,11 @@ const csv = require('csv-express');
  // The function reads the req.query.format param and respond in json, xml or csv
 function response(value) {
     return (req, res, next) => {
-        log.debug(req.query.format);
         if (req.query.format === 'csv') {
             res.attachment(`${value}.csv`);
-            res.csv(req.result);
+            res.csv(req.result, true);
         } else if (req.query.format === 'xml') {
-            res.send(xml('result', JSON.stringify({ [value]: req.result })));
+            res.send(xml.parse('result', { [value]: req.result }));
         } else {
             res.json({ result: req.result });
         }
diff --git a/src/libs/models/accessToken.js b/src/libs/models/accessToken.js
new file mode 100644
index 0000000000000000000000000000000000000000..daab5898a66b952f72359d9685e951b1acf980b7
--- /dev/null
+++ b/src/libs/models/accessToken.js
@@ -0,0 +1,29 @@
+const mongoose = require('mongoose');
+const Schema = mongoose.Schema;
+const libs = `${process.cwd()}/libs`;
+const User = require(`${libs}/models/user`);
+const Client = require(`${libs}/models/client`);
+
+let AccessToken = new Schema({
+    userId: {
+        type: Schema.Types.ObjectId,
+        required: true,
+        ref: 'User'
+    },
+    clientId: {
+        type: Schema.Types.ObjectId,
+        required: true,
+        ref: 'Client'
+    },
+    token: {
+        type: String,
+        unique: true,
+        required: true
+    },
+    createdAt: {
+        type: Date,
+        default: Date.now
+    }
+});
+
+module.exports = mongoose.model('AccessToken', AccessToken);
diff --git a/src/libs/models/client.js b/src/libs/models/client.js
new file mode 100644
index 0000000000000000000000000000000000000000..8ac80d8d158705f63efbbecc72de37d1fef6a3ac
--- /dev/null
+++ b/src/libs/models/client.js
@@ -0,0 +1,17 @@
+const mongoose = require('mongoose');
+const Schema = mongoose.Schema;
+
+let Client = new Schema({
+    name: {
+        type: String,
+        unique: true,
+        required: true
+    },
+    clientSecret: {
+        type: String,
+        required: true,
+        unique: true
+    }
+});
+
+module.exports = mongoose.model('Client', Client);
diff --git a/src/libs/models/download.js b/src/libs/models/download.js
new file mode 100644
index 0000000000000000000000000000000000000000..989896d9a2b6547c11c9b1bc71019db3bb7617a5
--- /dev/null
+++ b/src/libs/models/download.js
@@ -0,0 +1,53 @@
+const mongoose = require('mongoose');
+const Schema = mongoose.Schema;
+const libs = `${process.cwd()}/libs`;
+const log = require(`${libs}/log`)(module);
+const User = require(`${libs}/models/user`);
+
+let Download = new Schema({
+    userId: {
+        type: Schema.Types.ObjectId,
+        required: true,
+        ref: 'User'
+    },
+    table: {
+        type: String,
+        required: true
+    },
+    name: {
+        type: String,
+        required: true
+    },
+    mappingTable: {
+        type: String,
+        required: true
+    },
+    query: {
+        type: String,
+        required: true
+    },
+    createdAt: {
+        type: Date,
+        required: true,
+        default: Date.now
+    },
+    updatedAt: {
+        type: Date,
+        required: true,
+        default: Date.now
+    },
+    status: {
+        type: String
+    },
+    size: {
+        type: Number
+    },
+    expired: {
+        type: Boolean
+    },
+    link: {
+        type: String
+    }
+});
+
+module.exports = mongoose.model('Download', Download);
diff --git a/src/libs/models/pqr.js b/src/libs/models/pqr.js
new file mode 100644
index 0000000000000000000000000000000000000000..f92703188323cef17807f70f1f2198b54a13aa1b
--- /dev/null
+++ b/src/libs/models/pqr.js
@@ -0,0 +1,16 @@
+const mongoose = require('mongoose')
+
+const libs = `${process.cwd()}/libs`;
+const log = require(`${libs}/log`)(module);
+const User = require(`${libs}/models/user`);
+
+const Schema = mongoose.Schema;
+
+let PQRSchema = new Schema({
+    content: {
+        type: String,
+        required: true,
+    }
+});
+
+module.exports = mongoose.model('PQR', PQRSchema);
diff --git a/src/libs/models/refreshToken.js b/src/libs/models/refreshToken.js
new file mode 100644
index 0000000000000000000000000000000000000000..c5f8fd63cdd30733bb60f341e6617420d945b037
--- /dev/null
+++ b/src/libs/models/refreshToken.js
@@ -0,0 +1,30 @@
+const mongoose = require('mongoose');
+const Schema = mongoose.Schema;
+const libs = `${process.cwd()}/libs`;
+const User = require(`${libs}/models/user`);
+const Client = require(`${libs}/models/client`);
+
+let RefreshToken = new Schema({
+    userId: {
+        type: Schema.Types.ObjectId,
+        required: true,
+        ref: 'User'
+    },
+    clientId: {
+        type: Schema.Types.ObjectId,
+        required: true,
+        ref: 'Client'
+    },
+    token: {
+        type: String,
+        unique: true,
+        required: true
+    },
+    createdAt: {
+        type: Date,
+        default: Date.now
+    }
+});
+
+module.exports = mongoose.model('RefreshToken', RefreshToken);
+
diff --git a/src/libs/models/resetToken.js b/src/libs/models/resetToken.js
new file mode 100644
index 0000000000000000000000000000000000000000..322d528188a78525b4e003a65c6355fc0c2039c6
--- /dev/null
+++ b/src/libs/models/resetToken.js
@@ -0,0 +1,46 @@
+const mongoose = require('mongoose');
+const Schema = mongoose.Schema;
+const libs = `${process.cwd()}/libs`;
+const log = require(`${libs}/log`)(module);
+const User = require(`${libs}/models/user`);
+const uuid = require('node-uuid');
+
+let ResetToken = new Schema({
+    userId: {
+        type: Schema.Types.ObjectId,
+        required: true,
+        ref: 'User'
+    },
+    token: {
+        type: String,
+        required: true
+    },
+    reset: {
+        type: Boolean,
+        required: true,
+        default: false
+    },
+    createdAt: {
+        type: Date,
+        required: true,
+        default: Date.now
+    }
+});
+
+ResetToken.methods.createResetToken = function (done) {
+    let resetToken = this;
+    let token = uuid.v4();
+    resetToken.set('token', token);
+    resetToken.reset = false;
+    resetToken.save(function(err) {
+        if (err)
+            return done(err);
+        return done(null, token);
+    })
+}
+ResetToken.methods.hasExpired = function () {
+    var now = new Date();
+    return (now - this.createdAt) > 86400; //Expire if token is 1 day old
+};
+
+module.exports = mongoose.model('ResetToken', ResetToken);
diff --git a/src/libs/models/simulation.js b/src/libs/models/simulation.js
new file mode 100644
index 0000000000000000000000000000000000000000..f1d3b0bd2448fe7640affd57e3d33d330ad7912c
--- /dev/null
+++ b/src/libs/models/simulation.js
@@ -0,0 +1,34 @@
+const mongoose = require('mongoose')
+
+const libs = `${process.cwd()}/libs`;
+const log = require(`${libs}/log`)(module);
+const User = require(`${libs}/models/user`);
+
+const Schema = mongoose.Schema;
+
+let SimulationSchema = new Schema({
+    userId: {
+        type: Schema.Types.ObjectId,
+        required: true,
+        ref: 'User'
+    },
+    content: {
+        type: String,
+        required: true,
+    },
+    name: {
+        type: String
+    },
+    createdAt: {
+        type: Date,
+        required: true,
+        default: Date.now
+    },
+    updatedAt: {
+        type: Date,
+        required: true,
+        default: Date.now
+    }
+});
+
+module.exports = mongoose.model('Simulation', SimulationSchema);
diff --git a/src/libs/models/user.js b/src/libs/models/user.js
new file mode 100644
index 0000000000000000000000000000000000000000..0d54e4175fbf3336c4386bcc252d235dc6b478ce
--- /dev/null
+++ b/src/libs/models/user.js
@@ -0,0 +1,119 @@
+const mongoose = require('mongoose');
+const crypto = require('crypto')
+const libs = `${process.cwd()}/libs`;
+const log = require(`${libs}/log`)(module);
+const Schema = mongoose.Schema;
+
+// set up a mongoose model
+var UserSchema = new Schema({
+    email: {
+        type: String,
+        unique: true,
+        required: [true, 'O campo Email é obrigatório.']
+    },
+    hashedPassword: {
+        type: String,
+        required: [true, 'O campo Senha é obrigatório.']
+    },
+    salt: {
+        type: String,
+        required: true
+    },
+    name: {
+        type: String,
+        required: [true, 'O campo Nome é obrigatório.']
+    },
+    nickname: {
+        type: String,
+        required: [true, 'O campo Apelido é obrigatório.']
+    },
+    cpf:{
+        type: String,
+        unique: true,
+        required: [true, 'O campo CPF é obrigatório.']
+    },
+    cep:{
+        type: String,
+        required: [true, 'O campo CEP é obrigatório.']
+    },
+    schooling: {
+        type: String,
+        required: [true, 'O campo Formação é obrigatório.']
+    },
+    course: {
+        type: String,
+    },
+    complement: {
+        type: String,
+    },
+    address: {
+        type: String,
+    },
+    phone: {
+        type: String,
+    },
+    segment: {
+        type: String,
+        required: [true, 'O campo Segmento é obrigatório.']
+    },
+    role: {
+        type: String,
+        required: [true, 'O campo Função é obrigatório.']
+    },
+    institutionName: {
+        type: String,
+        required: [true, 'O campo Instituição em que trabalha ou estuda é obrigatório.']
+    },
+    state: {
+        type: String,
+        required: [true, 'O campo Estado é obrigatório.']
+    },
+    city: {
+        type: String,
+        required: [true, 'O campo Cidade é obrigatório.']
+    },
+    receiveEmails: {
+        type: Boolean
+    },
+    createdAt: {
+        type: Date,
+        default: Date.now
+    },
+    origin: {
+        type: String,
+        enum: ['LDE', 'SimCAQ'],
+        required: [true, 'O campo origem é obrigatória e aceita apenas os valores "LDE" ou "SimCAQ"']
+    },
+    verified: {
+        type: Boolean,
+        default: false
+    },
+    citesegment: {
+        type: String
+    },
+    citerole: {
+        type: String
+    },
+    admin: {
+        type: Boolean,
+        default: false
+    }
+});
+
+UserSchema.methods.encryptPassword = function(password) {
+    return crypto.pbkdf2Sync(password+'', this.salt, 10000, 512, 'sha512');
+};
+
+UserSchema.virtual('password').set(function(password) {
+    this._plainPassword = password+'';
+    this.salt = crypto.randomBytes(128).toString('hex');
+    this.hashedPassword = this.encryptPassword(password).toString('hex');
+}).get(function() {
+    return this._plainPassword;
+});
+
+UserSchema.methods.checkPassword = function(password) {
+    return this.encryptPassword(password).toString('hex') === this.hashedPassword;
+}
+
+module.exports = mongoose.model('User', UserSchema);
diff --git a/src/libs/models/verificationToken.js b/src/libs/models/verificationToken.js
new file mode 100644
index 0000000000000000000000000000000000000000..ef7e109e16364874ea7d3f4b07b6e19ce74c3eae
--- /dev/null
+++ b/src/libs/models/verificationToken.js
@@ -0,0 +1,41 @@
+const mongoose = require('mongoose');
+const Schema = mongoose.Schema;
+const libs = `${process.cwd()}/libs`;
+const log = require(`${libs}/log`)(module);
+const User = require(`${libs}/models/user`);
+const uuid = require('node-uuid');
+
+let VerificationToken = new Schema({
+    userId: {
+        type: Schema.Types.ObjectId,
+        required: true,
+        ref: 'User'
+    },
+    token: {
+        type: String,
+        required: true
+    },
+    verified: {
+        type: Boolean,
+        required: true,
+        default: false
+    },
+    createdAt: {
+        type: Date,
+        required: true,
+        default: Date.now
+    }
+});
+
+VerificationToken.methods.createVerificationToken = function(done) {
+    let verificationToken = this;
+    let token = uuid.v4();
+    verificationToken.set('token', token);
+    verificationToken.verified = false;
+    verificationToken.save(function(err) {
+        if (err) return done(err);
+        return done(null, token);
+    })
+}
+
+module.exports = mongoose.model('VerificationToken', VerificationToken);
diff --git a/src/libs/routes/api.js b/src/libs/routes/api.js
index 3faaa926c7055bc9849d4457e183e8ea6fd48cf8..2b56919393323db34a82b3f4373f39c1684a28dc 100644
--- a/src/libs/routes/api.js
+++ b/src/libs/routes/api.js
@@ -2,6 +2,12 @@ const express = require('express');
 
 const api = express();
 
+const libs = `${process.cwd()}/libs`;
+
+const config = require(`${libs}/config`);
+
+const classes = require('./class');
+
 const enrollment = require('./enrollment');
 
 const state = require('./state');
@@ -12,15 +18,79 @@ const city = require('./city');
 
 const school = require('./school');
 
+const simulation = require('./simulation');
+
+const user = require('./user');
+
+const classroom = require('./classroom');
+
+const teacher = require('./teacher');
+
+const idhme = require('./idhme');
+
+const pibpercapita = require('./pibpercapita')
+
+const population = require('./population')
+
+const rateSchool = require('./rateSchool')
+
+const glossEnrollmentRatio = require('./glossEnrollmentRatio')
+
+const liquidEnrollmentRatio = require('./liquidEnrollmentRatio')
+
+const idhm = require('./idhm');
+
+const idhmr = require('./idhmr');
+
+const idhml = require('./idhml');
+
+const oauth2 = require(`${libs}/middlewares/oauth2`);
+
+const verifyToken = require(`${libs}/routes/verifyToken`);
+
+const resetToken = require(`${libs}/routes/resetToken`);
+
+const educationYears = require(`${libs}/routes/educationYears`);
+
+const downloads = require(`${libs}/routes/downloads`);
+
+const infrastructure = require(`${libs}/routes/infrastructure`);
+
+const distributionFactor = require(`${libs}/routes/distributionFactor`);
+
+const siope = require(`${libs}/routes/siope`);
+
 api.get('/', (req, res) => {
     res.json({ msg: 'SimCAQ API is running' });
 });
 
 // mount API routes
+api.use('/user', user);
+api.use('/simulation', simulation);
+api.use('/class', classes);
 api.use('/enrollment', enrollment);
 api.use('/state', state);
 api.use('/region', region);
 api.use('/city', city);
 api.use('/school', school);
+api.use('/classroom', classroom);
+api.use('/teacher', teacher);
+api.use('/idhmr', idhmr);
+api.use('/idhm', idhm);
+api.use('/idhme', idhme);
+api.use('/pibpercapita', pibpercapita);
+api.use('/population', population);
+api.use('/rate_school', rateSchool);
+api.use('/gloss_enrollment_ratio', glossEnrollmentRatio);
+api.use('/liquid_enrollment_ratio', liquidEnrollmentRatio);
+api.use('/idhml', idhml);
+api.use('/auth/token', oauth2.token);
+api.use('/verify', verifyToken);
+api.use('/reset', resetToken);
+api.use('/education_years', educationYears);
+api.use('/downloads', downloads);
+api.use('/infrastructure', infrastructure);
+api.use('/distribution_factor', distributionFactor);
+api.use('/siope', siope);
 
 module.exports = api;
diff --git a/src/libs/routes/city.js b/src/libs/routes/city.js
index 733da6f341cc37a7b6b792b8f5383076eae75067..1f6aee12630ba58b48f2f53bb17fc9ab10b4fad1 100644
--- a/src/libs/routes/city.js
+++ b/src/libs/routes/city.js
@@ -6,34 +6,70 @@ const libs = `${process.cwd()}/libs`;
 
 const squel = require('squel');
 
-const query = require(`${libs}/middlewares/query`);
+const query = require(`${libs}/middlewares/query`).query;
 
 const response = require(`${libs}/middlewares/response`);
 
-// Return all cities
-cityApp.get('/', (req, res, next) => {
-    req.sql.from('municipios');
-    next();
-}, query, response('city'));
+const ReqQueryFields = require(`${libs}/middlewares/reqQueryFields`);
 
-// Return a specific city by it's id
-cityApp.get('/:id', (req, res, next) => {
-    req.sql.from('municipios')
-        .where('pk_municipio_id = ?', parseInt(req.params.id, 10));
-    next();
-}, query, response('city'));
+const config = require(`${libs}/config`); 
 
-// Return a specific city by it's IBGE code
-cityApp.get('/ibge/:id', (req, res, next) => {
-    req.sql.from('municipios')
-        .where('codigo_ibge = ?', req.params.id);
-    next();
-}, query, response('city'));
+const cache = require('apicache').options({ debug: config.debug, statusCodes: {include: [200]} }).middleware;
+
+let rqf = new ReqQueryFields();
+
+cityApp.use(cache('15 day'));
 
-// Return all the cities from a specific state
-cityApp.get('/state/:id', (req, res, next) => {
-    req.sql.from('municipios')
-        .where('fk_estado_id = ?', parseInt(req.params.id, 10));
+rqf.addField({
+    name: 'filter',
+    field: false,
+    where: true
+}).addValue({
+    name: 'id',
+    table: 'municipio',
+    tableField: 'id',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'id'
+    }
+}).addValue({
+    name: 'state',
+    table: 'estado',
+    tableField: 'nome',
+    resultField: 'state_name',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'estado_id',
+        table: 'municipio'
+    },
+    join: {
+        primary: 'id',
+        foreign: 'estado_id',
+        foreignTable: 'municipio'
+    }
+}).addField({
+    name: 'search',
+    field: false,
+    where: true
+}).addValueToField({
+    name: 'name',
+    table: 'municipio',
+    tableField: 'nome',
+    where: {
+        relation: 'LIKE',
+        type: 'string',
+        field: 'nome'
+    }
+}, 'search');;
+
+// Return all cities
+cityApp.get('/', rqf.parse(), rqf.build(), (req, res, next) => {
+    req.sql.from('municipio')
+    .field('municipio.nome', 'name')
+    .field('municipio.id')
+    .field('municipio.estado_id', 'state_id');
     next();
 }, query, response('city'));
 
diff --git a/src/libs/routes/class.js b/src/libs/routes/class.js
new file mode 100644
index 0000000000000000000000000000000000000000..dae41b6ee89d8d2e70421f3f9ba0cc38e305fb6b
--- /dev/null
+++ b/src/libs/routes/class.js
@@ -0,0 +1,359 @@
+const express = require('express');
+
+const classApp = express.Router();
+
+const libs = `${process.cwd()}/libs`;
+
+const log = require(`${libs}/log`)(module);
+
+const squel = require('squel');
+
+const query = require(`${libs}/middlewares/query`).query;
+
+const response = require(`${libs}/middlewares/response`);
+
+const ReqQueryFields = require(`${libs}/middlewares/reqQueryFields`);
+
+const id2str = require(`${libs}/middlewares/id2str`);
+
+const config = require(`${libs}/config`); 
+
+const download = require(`${libs}/middlewares/downloadDatabase`);
+
+const addMissing = require(`${libs}/middlewares/addMissing`);
+
+const passport = require('passport');
+
+const cache = require('apicache').options({ debug: config.debug, statusCodes: {include: [200]} }).middleware;
+
+let rqfCount = new ReqQueryFields();
+
+classApp.use(cache('15 day'));
+
+// Complete range of the enrollments dataset.
+// Returns a tuple of start and ending years of the complete enrollments dataset.
+classApp.get('/year_range', (req, res, next) => {
+    req.sql.from('turma')
+    .field('MIN(turma.ano_censo)', 'start_year')
+    .field('MAX(turma.ano_censo)', 'end_year');
+    next();
+}, query, response('range'));
+
+classApp.get('/years', (req, res, next) => {
+    req.sql.from('turma')
+    .field('DISTINCT turma.ano_censo', 'year');
+    next();
+}, query, response('years'));
+
+classApp.get('/source', (req, res, next) => {
+    req.sql.from('fonte')
+    .field('fonte', 'source')
+    .where('tabela = \'turma\'');
+    next();
+}, query, response('source'));
+
+classApp.get('/location', (req, res, next) => {
+    req.result = [];
+    for(let i = 1; i <= 2; ++i) {
+        req.result.push({
+            id: i,
+            name: id2str.location(i)
+        });
+    };
+    next();
+}, response('location'));
+
+classApp.get('/rural_location', (req, res, next) => {
+    req.result = [
+        {id: 1, name: "Urbana"},
+        {id: 2, name: "Rural"},
+        {id: 3, name: "Rural - Área de assentamento"},
+        {id: 4, name: "Rural - Terra indígena"},
+        {id: 5, name: "Rural - Área remanescente de quilombos"},
+        {id: 6, name: "Rural - Unidade de uso sustentável"}
+    ];
+    next();
+}, response('rural_location'));
+
+// Returns all adm dependencies
+classApp.get('/adm_dependency', (req, res, next) => {
+    req.result = [];
+    for(let i = 1; i <= 4; ++i) {
+        req.result.push({
+            id: i,
+            name: id2str.admDependency(i)
+        });
+    };
+    next();
+}, response('adm_dependency'));
+
+classApp.get('/adm_dependency_detailed', (req, res, next) => {
+    req.result = [];
+    for(let i = 1; i <= 6; ++i) {
+        req.result.push({
+            id: i,
+            name: id2str.admDependencyPriv(i)
+        });
+    };
+    next();
+}, response('adm_dependency_detailed'));
+
+// Returns all periods avaible
+classApp.get('/period', (req, res, next) => {
+    req.result = [];
+    for(let i=1; i <= 3; ++i) {
+        req.result.push({
+            id: i,
+            name: id2str.period(i)
+        });
+    }
+    req.result.push({
+        id: 99,
+        name: id2str.period(99)
+    });
+    next();
+}, response('period'));
+
+// Returns integral-time avaible
+classApp.get('/integral_time', (req, res, next) => {
+    req.result = [
+        {id: null, name: 'Não Disponível'},
+        {id: 0, name: 'Não'},
+        {id: 1, name: 'Sim'}
+    ];
+    next();
+}, response('integral_time'));
+
+// Returns all educational levels avaible
+classApp.get('/education_level_mod', (req, res, next) => {
+    req.result = [];
+    for(let i = 1; i <=11; ++i) {
+        req.result.push({
+            id: i,
+            name: id2str.educationLevelMod(i)
+        });
+    }
+    req.result.push({
+        id: 99,
+        name: id2str.educationLevelMod(99)
+    });
+    next();
+}, response('education_level_mod'));
+
+classApp.get('/education_level_short', (req, res, next) => {
+    req.result = [
+        {id: null, name: 'Não classificada'},
+        {id: 1, name: 'Creche'},
+        {id: 2, name: 'Pré-Escola'},
+        {id: 3, name: 'Ensino Fundamental - anos iniciais'},
+        {id: 4, name: 'Ensino Fundamental - anos finais'},
+        {id: 5, name: 'Ensino Médio'},
+        {id: 6, name: 'EJA'},
+        {id: 7, name: 'EE exclusiva'}
+    ];
+    next();
+}, response('education_level_short'));
+
+rqfCount.addField({
+    name: 'filter',
+    field: false,
+    where: true
+}).addField({
+    name: 'dims',
+    field: true,
+    where: false
+}).addValueToField({
+    name: 'city',
+    table: 'municipio',
+    tableField: ['nome', 'id'],
+    resultField: ['city_name', 'city_id'],
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'municipio_id',
+        table: 'turma'
+    },
+    join: {
+        primary: 'id',
+        foreign: 'municipio_id',
+        foreignTable: 'turma'
+    }
+}, 'dims').addValueToField({
+    name: 'city',
+    table: 'municipio',
+    tableField: 'nome',
+    resultField: 'city_name',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'municipio_id',
+        table: 'turma'
+    },
+    join: {
+        primary: 'id',
+        foreign: 'municipio_id',
+        foreignTable: 'turma'
+    }
+}, 'filter').addValue({
+    name: 'state',
+    table: 'estado',
+    tableField: 'nome',
+    resultField: 'state_name',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'estado_id',
+        table: 'turma'
+    },
+    join: {
+        primary: 'id',
+        foreign: 'estado_id',
+        foreignTable: 'turma'
+    }
+}).addValue({
+    name: 'region',
+    table: 'regiao',
+    tableField: 'nome',
+    resultField: 'region_name',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'id'
+    },
+    join: {
+        primary: 'id',
+        foreign: 'regiao_id',
+        foreignTable: 'turma'
+    }
+}).addValue({
+    name: 'min_year',
+    table: 'turma',
+    tableField: 'ano_censo',
+    resultField: 'year',
+    where: {
+        relation: '>=',
+        type: 'integer',
+        field: 'ano_censo'
+    }
+}).addValue({
+    name: 'max_year',
+    table: 'turma',
+    tableField: 'ano_censo',
+    resultField: 'year',
+    where: {
+        relation: '<=',
+        type: 'integer',
+        field: 'ano_censo'
+    }
+}).addValue({
+    name:'adm_dependency',
+    table: 'turma',
+    tableField: 'dependencia_adm_id',
+    resultField: 'adm_dependency_id',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'dependencia_adm_id'
+    }
+}).addValue({
+    name: 'location',
+    table: 'turma',
+    tableField: 'localizacao_id',
+    resultField: 'location_id',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'localizacao_id'
+    }
+}).addValue({
+    name: 'rural_location',
+    table: 'turma',
+    tableField: 'localidade_area_rural',
+    resultField: 'rural_location_id',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'localidade_area_rural'
+    }
+}).addValue({
+  name:'education_level_mod',
+  table: 'turma',
+  tableField: 'etapas_mod_ensino_segmento_id',
+  resultField: 'education_level_mod_id',
+  where: {
+    relation: '=',
+    type: 'integer',
+    field: 'etapas_mod_ensino_segmento_id'
+  }
+}).addValue({
+    name: 'education_level_short',
+    table: 'turma',
+    tableField: 'etapa_resumida',
+    resultField: 'education_level_short_id',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'etapa_resumida'
+    }
+}).addValue({
+    name: 'adm_dependency_detailed',
+    table: 'turma',
+    tableField: 'dependencia_adm_priv',
+    resultField: 'adm_dependency_detailed_id',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'dependencia_adm_priv'
+    }
+}).addValue({
+  name:'period',
+  table: 'turma',
+  tableField: 'turma_turno_id',
+  resultField: 'period_id',
+  where: {
+      relation: '=',
+      type: 'integer',
+      field: 'turma_turno_id'
+  }
+}).addValue({
+  name:'integral_time',
+  table: 'turma',
+  tableField: 'tempo_integral',
+  resultField: 'integral_time_id',
+  where: {
+      relation: '=',
+      type: 'boolean',
+      field: 'tempo_integral'
+  }
+}).addValue({
+    name: 'school',
+    table: 'escola',
+    tableField: 'nome_escola',
+    resultField: 'school_name',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'id'
+    },
+    join: {
+        primary: ['id', 'ano_censo'],
+        foreign: ['escola_id', 'ano_censo'],
+        foreignTable: 'turma'
+    }
+});
+
+
+classApp.get('/', rqfCount.parse(), rqfCount.build(), (req, res, next) => {
+   req.sql.field('COUNT(turma.id)', 'total')
+   .field("'Brasil'", 'name')
+   .field('turma.ano_censo', 'year')
+   .from('turma')
+   .group('turma.ano_censo')
+   .order('turma.ano_censo')
+   .where('turma.tipo_turma_id = 0 OR turma.tipo_turma_id = 1 OR turma.tipo_turma_id = 2 OR turma.tipo_turma_id = 3');
+   next();
+}, query, addMissing(rqfCount), id2str.transform(), response('class'));
+
+classApp.get('/download', passport.authenticate('bearer', { session: false }), rqfCount.parse(), rqfCount.build(), download('turma', 'mapping_turma'));
+
+module.exports = classApp;
diff --git a/src/libs/routes/classroom.js b/src/libs/routes/classroom.js
new file mode 100644
index 0000000000000000000000000000000000000000..246e3d9bc7ae1a54c54a3afcc1104e0190107a13
--- /dev/null
+++ b/src/libs/routes/classroom.js
@@ -0,0 +1,233 @@
+ const express = require('express');
+
+const classroomApp = express.Router();
+
+const libs = `${process.cwd()}/libs`;
+
+const squel = require('squel');
+
+const query = require(`${libs}/middlewares/query`).query;
+
+const response = require(`${libs}/middlewares/response`);
+
+const ReqQueryFields = require(`${libs}/middlewares/reqQueryFields`);
+
+const id2str = require(`${libs}/middlewares/id2str`);
+
+const addMissing = require(`${libs}/middlewares/addMissing`);
+
+const config = require(`${libs}/config`); 
+
+const cache = require('apicache').options({ debug: config.debug, statusCodes: {include: [200]} }).middleware;
+
+let rqf = new ReqQueryFields();
+
+let rqfCount = new ReqQueryFields();
+
+// Complete range of the enrollments dataset.
+// Returns a tuple of start and ending years of the complete enrollments dataset.
+classroomApp.get('/year_range', (req, res, next) => {
+    req.sql.from('escola')
+    .field('MIN(escola.ano_censo)', 'start_year')
+    .field('MAX(escola.ano_censo)', 'end_year');
+    next();
+}, query, response('range'));
+
+classroomApp.get('/years', (req, res, next) => {
+    req.sql.from('escola')
+    .field('DISTINCT escola.ano_censo', 'year');
+    next();
+}, query, response('years'));
+
+classroomApp.get('/source', (req, res, next) => {
+    req.sql.from('fonte')
+    .field('fonte', 'source')
+    .where('tabela = \'escola\'');
+    next();
+}, query, response('source'));
+
+classroomApp.get('/adm_dependency', (req, res, next) => {
+    req.result = [];
+    for(let i = 1; i <= 4; ++i) {
+        req.result.push({
+            id: i,
+            name: id2str.admDependency(i)
+        });
+    };
+    next();
+}, response('adm_dependency'));
+
+classroomApp.get('/adm_dependency_detailed', (req, res, next) => {
+    req.result = [];
+    for(let i = 1; i <= 6; ++i) {
+        req.result.push({
+            id: i,
+            name: id2str.admDependencyPriv(i)
+        });
+    };
+    next();
+}, response('adm_dependency_detailed'));
+
+classroomApp.get('/location', (req, res, next) => {
+    req.result = [
+		{id: 1, name: 'Urbana'},
+		{id: 2, name: 'Rural'}
+	];
+	next();
+}, response('location'));
+
+rqf.addField({
+    name: 'filter',
+    field: false,
+    where: true
+}).addField({
+    name: 'dims',
+    field: true,
+    where: false
+}).addValueToField({
+    name: 'school',
+    table: 'escola',
+    tableField: ['nome_escola', 'id'],
+    resultField: ['school_name', 'school_id'],
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'id'
+    }
+}, 'dims').addValueToField({
+    name: 'school',
+    table: 'escola',
+    tableField: 'nome_escola',
+    resultField: 'school_name',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'id'
+    }
+}, 'filter').addValueToField({
+    name: 'city',
+    table: 'municipio',
+    tableField: ['nome', 'id'],
+    resultField: ['city_name', 'city_id'],
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'municipio_id',
+        table: 'escola'
+    },
+    join: {
+        primary: 'id',
+        foreign: 'municipio_id',
+        foreignTable: 'escola'
+    }
+}, 'dims').addValueToField({
+    name: 'city',
+    table: 'municipio',
+    tableField: 'nome',
+    resultField: 'city_name',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'municipio_id',
+        table: 'escola'
+    },
+    join: {
+        primary: 'id',
+        foreign: 'municipio_id',
+        foreignTable: 'escola'
+    }
+}, 'filter').addValue({
+    name: 'state',
+    table: 'estado',
+    tableField: 'nome',
+    resultField: 'state_name',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'estado_id',
+        table: 'escola'
+    },
+    join: {
+        primary: 'id',
+        foreign: 'estado_id',
+        foreignTable: 'escola'
+    }
+}).addValue({
+    name: 'region',
+    table: 'regiao',
+    tableField: 'nome',
+    resultField: 'region_name',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'id'
+    },
+    join: {
+        primary: 'id',
+        foreign: 'regiao_id',
+        foreignTable: 'escola'
+    }
+}).addValue({
+    name: 'min_year',
+    table: 'escola',
+    tableField: 'ano_censo',
+    resultField: 'year',
+    where: {
+        relation: '>=',
+        type: 'integer',
+        field: 'ano_censo'
+    }
+}).addValue({
+    name: 'max_year',
+    table: 'escola',
+    tableField: 'ano_censo',
+    resultField: 'year',
+    where: {
+        relation: '<=',
+        type: 'integer',
+        field: 'ano_censo'
+    }
+}).addValue({
+    name: 'adm_dependency',
+    table: 'escola',
+    tableField: 'dependencia_adm_id',
+    resultField: 'adm_dependency_id',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'dependencia_adm_id'
+    }
+}).addValue({
+    name: 'adm_dependency_detailed',
+    table: 'escola',
+    tableField: 'dependencia_adm_priv',
+    resultField: 'adm_dependency_detailed_id',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'dependencia_adm_priv'
+    }
+}).addValue({
+    name: 'location',
+    table: 'escola',
+    tableField: 'cod_localizacao',
+    resultField: 'location_id',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'cod_localizacao'
+    }
+});
+
+classroomApp.get('/', cache('15 day'), rqf.parse(), rqf.build(), (req, res, next) => {
+    req.sql.from('escola')
+        .field('SUM(escola.num_salas)', 'total')
+        .field("'Brasil'", 'name')
+        .field('escola.ano_censo', 'year')
+        .group('escola.ano_censo')
+        .order('escola.ano_censo')
+        .where('escola.situacao_de_funcionamento = 1 AND escola.local_func_predio_escolar = 1');
+    next();
+}, query, addMissing(rqf), id2str.transform(), response('classroom'));
+
+module.exports = classroomApp;
diff --git a/src/libs/routes/distributionFactor.js b/src/libs/routes/distributionFactor.js
new file mode 100644
index 0000000000000000000000000000000000000000..df4d84b797884f71f92e55456ed365317d98679a
--- /dev/null
+++ b/src/libs/routes/distributionFactor.js
@@ -0,0 +1,190 @@
+const express = require('express');
+
+const distributionApp = express.Router();
+
+const libs = `${process.cwd()}/libs`;
+
+const squel = require('squel');
+
+const query = require(`${libs}/middlewares/query`).query;
+
+const multiQuery = require(`${libs}/middlewares/multiQuery`);
+
+const response = require(`${libs}/middlewares/response`);
+
+const ReqQueryFields = require(`${libs}/middlewares/reqQueryFields`);
+
+const id2str = require(`${libs}/middlewares/id2str`);
+
+const config = require(`${libs}/config`); 
+
+const cache = require('apicache').options({ debug: config.debug, statusCodes: {include: [200]} }).middleware;
+
+let rqf = new ReqQueryFields();
+
+distributionApp.use(cache('15 day'));
+
+rqf.addField({
+    name: 'dims',
+    field: true,
+    where: false
+}).addField({
+    name: 'filter',
+    field: false,
+    where: true
+}).addValueToField({
+    name: 'city',
+    table: 'municipio',
+    tableField: ['nome', 'id'],
+    resultField: ['city_name', 'city_id'],
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'municipio_id',
+        table: 'fatores_matricula'
+    },
+    join: {
+        primary: 'id',
+        foreign: 'municipio_id',
+        foreignTable: 'fatores_matricula'
+    }
+}, 'dims').addValueToField({
+    name: 'city',
+    table: 'municipio',
+    tableField: 'nome',
+    resultField: 'city_name',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'municipio_id',
+        table: 'fatores_matricula'
+    },
+    join: {
+        primary: 'id',
+        foreign: 'municipio_id',
+        foreignTable: 'fatores_matricula'
+    }
+}, 'filter').addValue({
+    name: 'state',
+    table: 'estado',
+    tableField: 'nome',
+    resultField: 'state_name',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'estado_id',
+        table: 'fatores_matricula'
+    },
+    join: {
+        primary: 'id',
+        foreign: 'estado_id',
+        foreignTable: 'fatores_matricula'
+    }
+}).addValue({
+    name: 'region',
+    table: 'regiao',
+    tableField: 'nome',
+    resultField: 'region_name',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'id'
+    },
+    join: {
+        primary: 'id',
+        foreign: 'regiao_id',
+        foreignTable: 'fatores_matricula'
+    }
+})
+
+// Return all cities
+distributionApp.get('/', rqf.parse(), (req, res, next) => {
+    req.querySet = [];
+    req.queryIndex = {};
+
+    let relation = req.sql.clone();
+    relation.from('relacao_fatores_matricula').field('*');
+    req.queryIndex.relation = req.querySet.push(relation) - 1;
+
+    req.sql.from('fatores_matricula')
+    .field('fatores_matricula.municipio_id', 'municipio_id')
+    .field('fatores_matricula."mais_CRE_0"')
+	.field('fatores_matricula."mais_CRE_1"')
+	.field('fatores_matricula."mais_CRE_2"')
+	.field('fatores_matricula."mais_CRE_3"')
+	.field('fatores_matricula."mais_PRE"')
+	.field('fatores_matricula."mais_EFAI"')
+	.field('fatores_matricula."mais_EFAF"')
+	.field('fatores_matricula."mais_EM"')
+	.field('fatores_matricula."mais_EJA"')
+	.field('fatores_matricula."menos_CRE_0"')
+	.field('fatores_matricula."menos_CRE_1"')
+	.field('fatores_matricula."menos_CRE_2"')
+	.field('fatores_matricula."menos_CRE_3"')
+	.field('fatores_matricula."menos_PRE"')
+	.field('fatores_matricula."menos_EFAI"')
+	.field('fatores_matricula."menos_EFAF"')
+	.field('fatores_matricula."menos_EM"')
+	.field('fatores_matricula."menos_EJA"')
+    .group('fatores_matricula.municipio_id')
+    .group('fatores_matricula."mais_CRE_0"')
+	.group('fatores_matricula."mais_CRE_1"')
+	.group('fatores_matricula."mais_CRE_2"')
+	.group('fatores_matricula."mais_CRE_3"')
+	.group('fatores_matricula."mais_PRE"')
+	.group('fatores_matricula."mais_EFAI"')
+	.group('fatores_matricula."mais_EFAF"')
+	.group('fatores_matricula."mais_EM"')
+	.group('fatores_matricula."mais_EJA"')
+	.group('fatores_matricula."menos_CRE_0"')
+	.group('fatores_matricula."menos_CRE_1"')
+	.group('fatores_matricula."menos_CRE_2"')
+	.group('fatores_matricula."menos_CRE_3"')
+	.group('fatores_matricula."menos_PRE"')
+	.group('fatores_matricula."menos_EFAI"')
+	.group('fatores_matricula."menos_EFAF"')
+	.group('fatores_matricula."menos_EM"')
+	.group('fatores_matricula."menos_EJA"');
+
+    if(typeof req.dims.state !== 'undefined' || typeof req.filter.state !== 'undefined') {
+        req.sql.where('fatores_matricula.nivel = \'UF\'');
+    } else {
+        req.sql.where('fatores_matricula.nivel = \'BR\'');
+    }
+
+    next();
+}, rqf.build(), query, (req, res, next) => {
+    req.enrollmentFactor = req.result;
+    next();
+}, multiQuery, (req, res, next) => {
+    let relation = req.result[req.queryIndex.relation];
+    let result = [];
+    let first = true;
+    req.enrollmentFactor.forEach((city) => {
+        // if(first) console.log(city);
+        let obj = {
+            level: city.nivel,
+            region_id: city.regiao_id,
+            region_name: city.region_name,
+            state_id: city.state_id,
+            state_name: city.state_name,
+            city_id: city.municipio_id,
+            city_name: city.city_name,
+            series: []
+        };
+        // if(first) console.log(obj);
+        first = false;
+        relation.forEach((serie) => {
+            obj.series.push({
+                serie_id: serie.id,
+                distribution_factor_addition: city[serie.fator_adicao],
+                distribution_factor_reduction: city[serie.fator_reducao]
+            });
+        });
+        result.push(obj);
+    });
+    req.result = result;
+    next();
+}, response('ditributionFactor'));
+
+module.exports = distributionApp;
diff --git a/src/libs/routes/downloads.js b/src/libs/routes/downloads.js
new file mode 100644
index 0000000000000000000000000000000000000000..2ec83b9ee7ffa82bbaf3e873c949c842dd6b6ab1
--- /dev/null
+++ b/src/libs/routes/downloads.js
@@ -0,0 +1,54 @@
+const express = require('express');
+
+const downloadApp = express.Router();
+
+const libs = `${process.cwd()}/libs`;
+
+const log = require(`${libs}/log`)(module);
+
+const Download = require(`${libs}/models/download`);
+
+const User = require(`${libs}/models/user`);
+
+const passport = require('passport');
+
+const request = require(`request`);
+
+const config = require(`${libs}/config`);
+
+downloadApp.get('/', passport.authenticate('bearer', {session: false}), (req, res, next) => {
+    request.get(config.cdn.url + '/api/v1/file', (err, response, body) => {
+        let cdn = JSON.parse(body);
+        Download.find({userId: req.user._id}, (err, downloads) => {
+            if (err) {
+                log.error(err);
+                return next(err);
+            }
+            
+            if(!downloads) {
+                res.statusCode = 404;
+                return res.json({msg: 'Nenhum download encontrado'});
+            } else {
+                downloads.forEach((dl) => {
+                    for(let i = 0; i < cdn.length; ++i) {
+                        if(cdn[i].query == dl.query) {
+                            dl.status = cdn[i].expired ? 'Expirado' : 'Enviado';
+                            dl.size = cdn[i].size;
+                            dl.expired = cdn[i].expired;
+                            dl.updatedAt = cdn[i].lastAccess;
+                            dl.link = config.cdn.download + '/' + cdn[i]._id;
+
+                            dl.save((err) => {
+                                if(err) log.error(err);
+                            });
+                            return;
+                        }
+                    }
+                });
+            }
+            res.json(downloads);
+        });
+    });
+});
+
+module.exports = downloadApp;
diff --git a/src/libs/routes/educationYears.js b/src/libs/routes/educationYears.js
new file mode 100644
index 0000000000000000000000000000000000000000..0d103883f53fc53dbd8770fa983dc5fe568564dd
--- /dev/null
+++ b/src/libs/routes/educationYears.js
@@ -0,0 +1,46 @@
+const express = require('express');
+
+const educationYearsApp = express.Router();
+
+const libs = `${process.cwd()}/libs`;
+
+const log = require(`${libs}/log`)(module);
+
+const response = require(`${libs}/middlewares/response`);
+
+const config = require(`${libs}/config`);
+
+const id2str = require(`${libs}/middlewares/id2str`);
+
+const cache = require('apicache').options({ debug: config.debug, statusCodes: {include: [200]} }).middleware;
+
+educationYearsApp.use(cache('15 day'));
+
+educationYearsApp.get('/', (req, res, next) => {
+    req.result = [];
+    for(let i = 1; i <= 7; ++i) {
+        let edLvlShort = {
+            id: i,
+            name: id2str.educationLevelShort(i),
+            schoolYears: []
+        };
+
+        for(let j = i*10; j <= (i*10 + 9); ++j) {
+
+            let schoolYear = {
+                id: j,
+                name: id2str.schoolYear(j)
+            };
+
+            if(schoolYear.name !== id2str.schoolYear(99)) {
+                edLvlShort.schoolYears.push(schoolYear);
+            }
+        }
+        if(edLvlShort.name !== id2str.schoolYear(99)) {
+            req.result.push(edLvlShort);
+        }
+    }
+    next();
+}, response('educationYears'));
+
+module.exports = educationYearsApp;
diff --git a/src/libs/routes/enrollment.js b/src/libs/routes/enrollment.js
index 9fe6e903bdbe9bc10b89181866a920792b832533..a3a25119d7067e08456e71056a3cff02a99b743b 100644
--- a/src/libs/routes/enrollment.js
+++ b/src/libs/routes/enrollment.js
@@ -8,207 +8,463 @@ const log = require(`${libs}/log`)(module);
 
 const squel = require('squel');
 
-const query = require(`${libs}/middlewares/query`);
+const query = require(`${libs}/middlewares/query`).query;
 
 const response = require(`${libs}/middlewares/response`);
 
-const parseParams = require(`${libs}/middlewares/parseParams`);
+const ReqQueryFields = require(`${libs}/middlewares/reqQueryFields`);
 
-// **Temporary** solution to add where clauses that are common to all requests
+const id2str = require(`${libs}/middlewares/id2str`);
 
+const config = require(`${libs}/config`);
+
+const passport = require('passport');
+
+const download = require(`${libs}/middlewares/downloadDatabase`);
+
+const addMissing = require(`${libs}/middlewares/addMissing`);
+
+const cache = require('apicache').options({ debug: config.debug, statusCodes: {include: [200]} }).middleware;
+
+enrollmentApp.use(cache('15 day'));
+
+let rqf = new ReqQueryFields();
 
 // Complete range of the enrollments dataset.
 // Returns a tuple of start and ending years of the complete enrollments dataset.
 enrollmentApp.get('/year_range', (req, res, next) => {
-    req.sql = squel.select()
-        .from('turmas')
-        .field('MIN(turmas.ano_censo)', 'start_year')
-        .field('MAX(turmas.ano_censo)', 'end_year');
-
+    req.sql.from('matricula')
+    .field('MIN(matricula.ano_censo)', 'start_year')
+    .field('MAX(matricula.ano_censo)', 'end_year');
     next();
 }, query, response('range'));
 
-// Returns all educational levels avaible
-enrollmentApp.get('/education_level', (req, res, next) => {
-    req.sql = squel.select()
-        .from('etapa_ensino')
-        .field('pk_etapa_ensino_id', 'id')
-        .field('desc_etapa', 'name');
-
+enrollmentApp.get('/years', (req, res, next) => {
+    req.sql.from('matricula')
+    .field('DISTINCT matricula.ano_censo', 'year');
     next();
-}, query, response('education_level'));
-
-// Returns all adm dependencies
-enrollmentApp.get('/adm_dependency', (req, res, next) => {
-    req.sql = squel.select()
-        .from('dependencia_adms')
-        .field('pk_dependencia_adm_id', 'id')
-        .field('nome', 'name');
+}, query, response('years'));
 
+enrollmentApp.get('/source', (req, res, next) => {
+    req.sql.from('fonte')
+    .field('fonte', 'source')
+    .where('tabela = \'matricula\'');
     next();
-}, query, response('adm_dependency'));
-
-// Parse the filters and dimensions parameter in the query
-enrollmentApp.use('/', parseParams('filter', [
-    'min_year',
-    'max_year',
-    'adm_dependency_id',
-    'location_id',
-    'education_level_id',
-    'region',
-    'state',
-    'city',
-    'school'
-]), parseParams('dims', [
-    'adm_dependency_id',
-    'location_id',
-    'education_level_id',
-    'region',
-    'state',
-    'city',
-    'school'
-]), (req, res, next) => {
-    log.debug(req.filter);
-    log.debug(req.dims);
-
-    // Do the joins
-    if(typeof req.filter.adm_dependency_id !== 'undefined'
-        || typeof req.dims.adm_dependency_id !== 'undefined') {
-        req.sql.join('dependencia_adms', null, 'fk_dependencia_adm_id=dependencia_adms.pk_dependencia_adm_id');
+}, query, response('source'));
+
+enrollmentApp.get('/location', (req, res, next) => {
+    req.result = [];
+    for(let i = 1; i <= 2; ++i) {
+        req.result.push({
+            id: i,
+            name: id2str.location(i)
+        });
+    };
+    next();
+}, response('location'));
+
+enrollmentApp.get('/rural_location', (req, res, next) => {
+    req.result = [
+        {id: 1, name: "Urbana"},
+        {id: 2, name: "Rural"},
+        {id: 3, name: "Rural - Área de assentamento"},
+        {id: 4, name: "Rural - Terra indígena"},
+        {id: 5, name: "Rural - Área remanescente de quilombos"},
+        {id: 6, name: "Rural - Unidade de uso sustentável"}
+    ];
+    next();
+}, response('rural_location'));
+
+// Returns all school years available
+enrollmentApp.get('/school_year', (req, res, next) => {
+    req.result = [];
+    for(let i = 11; i <= 71; ++i) {
+        let obj = {
+            id: i,
+            name: id2str.schoolYear(i)
+        };
+
+        if(obj.name !== id2str.schoolYear(99)) {
+            req.result.push(obj);
+        }
     }
+    req.result.push({
+        id: 99,
+        name: id2str.schoolYear(99)
+    });
+    next();
+}, response('school_year'));
 
-    if(typeof req.filter.education_level_id !== 'undefined'
-        || typeof req.dims.education_level_id !== 'undefined') {
-        req.sql.join('etapa_ensino', null, 'fk_etapa_ensino_id=etapa_ensino.pk_etapa_ensino_id');
+// Returns all school years available
+enrollmentApp.get('/education_level', (req, res, next) => {
+    req.result = [];
+    for(let i = 1; i <= 74; ++i) {
+        let obj = {
+            id: i,
+            name: id2str.educationLevel(i)
+        };
+
+        if(obj.name !== id2str.educationLevel(99)) {
+            req.result.push(obj);
+        }
     }
-
-    if(typeof req.filter.region !== 'undefined'
-        || typeof req.dims.region !== 'undefined') {
-            req.sql.join('municipios', null, 'fk_municipio_id=municipios.pk_municipio_id')
-                .join('estados', null, 'municipios.fk_estado_id=estados.pk_estado_id')
-                .join('regioes', null, 'estados.fk_regiao_id=regioes.pk_regiao_id');
+    req.result.push({
+        id: 99,
+        name: id2str.educationLevel(99)
+    });
+    next();
+}, response('education_level'));
+
+// Returns all school years available
+enrollmentApp.get('/education_level_mod', (req, res, next) => {
+    req.result = [];
+    for(let i = 1; i <= 10; ++i) {
+        req.result.push({
+            id: i,
+            name: id2str.educationLevelMod(i)
+        });
     }
+    req.result.push({
+        id: 99,
+        name: id2str.educationLevelMod(99)
+    });
+    next();
+}, response('education_level_mod'));
+
+enrollmentApp.get('/education_level_short', (req, res, next) => {
+    req.result = [
+        {id: null, name: 'Não classificada'},
+        {id: 1, name: 'Creche'},
+        {id: 2, name: 'Pré-Escola'},
+        {id: 3, name: 'Ensino Fundamental - anos iniciais'},
+        {id: 4, name: 'Ensino Fundamental - anos finais'},
+        {id: 5, name: 'Ensino Médio'},
+        {id: 6, name: 'EJA'},
+        {id: 7, name: 'EE exclusiva'}
+    ];
+    next();
+}, response('education_level_short'));
 
-    if((typeof req.filter.state !== 'undefined'
-        || typeof req.dims.state !== 'undefined')
-        && (typeof req.filter.region === 'undefined'
-        && typeof req.dims.region === 'undefined')) {
-            req.sql.join('municipios', null, 'fk_municipio_id=municipios.pk_municipio_id')
-                .join('estados', null, 'municipios.fk_estado_id=estados.pk_estado_id');
+// Returns all adm dependencies
+enrollmentApp.get('/adm_dependency', (req, res, next) => {
+    req.result = [];
+    for(let i = 1; i <= 4; ++i) {
+        req.result.push({
+            id: i,
+            name: id2str.admDependency(i)
+        });
+    };
+    next();
+}, response('adm_dependency'));
+
+enrollmentApp.get('/adm_dependency_detailed', (req, res, next) => {
+    req.result = [];
+    for(let i = 1; i <= 6; ++i) {
+        req.result.push({
+            id: i,
+            name: id2str.admDependencyPriv(i)
+        });
+    };
+    next();
+}, response('adm_dependency_detailed'));
+
+// Return genders
+enrollmentApp.get('/gender', (req, res, next) => {
+    req.result = [
+        {id: 1, name: 'Masculino'},
+        {id: 2, name: 'Feminino'}
+    ];
+    next();
+}, response('gender'));
+
+// Return ethnic group
+enrollmentApp.get('/ethnic_group', (req, res, next) => {
+    req.result = [];
+    for(let i = 1; i <=5; ++i) {
+        req.result.push({
+            id: i,
+            name: id2str.ethnicGroup(i)
+        });
     }
-
-    if((typeof req.filter.city !== 'undefined'
-        || typeof req.dims.city !== 'undefined')
-        && (typeof req.filter.state === 'undefined'
-        && typeof req.dims.state === 'undefined')
-        && (typeof req.filter.region === 'undefined'
-        && typeof req.dims.region === 'undefined')) {
-        req.sql.join('municipios', null, 'fk_municipio_id=municipios.pk_municipio_id');
+    next();
+}, response('ethnic_group'));
+
+enrollmentApp.get('/period', (req, res, next) => {
+    req.result = [];
+    for(let i = 1; i <= 3; ++i) {
+        req.result.push({
+            id: i,
+            name: id2str.period(i)
+        });
     }
-
-    if(typeof req.dims.school !== 'undefined') {
-        req.sql.join('escolas', null, 'fk_escola_id=escolas.pk_escola_id');
+    req.result.push({
+        id: 99,
+        name: id2str.period(99)
+    });
+    next();
+}, response('period'));
+
+// Returns integral-time avaible
+enrollmentApp.get('/integral_time', (req, res, next) => {
+    req.result = [
+        {id: null, name: 'Não Disponível'},
+        {id: 0, name: 'Não'},
+        {id: 1, name: 'Sim'}
+    ];
+    next();
+}, response('integral_time'));
+
+rqf.addField({
+    name: 'filter',
+    field: false,
+    where: true
+}).addField({
+    name: 'dims',
+    field: true,
+    where: false
+}).addValue({
+    name: 'adm_dependency',
+    table: 'matricula',
+    tableField: 'dependencia_adm_id',
+    resultField: 'adm_dependency_id',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'dependencia_adm_id'
     }
-
-    // Dimensions (add fields)
-
-    if(typeof req.dims.education_level_id !== 'undefined') {
-        req.sql.field('desc_etapa', 'education_level')
-            .group('desc_etapa')
-            .order('desc_etapa');
+}).addValue({
+    name: 'adm_dependency_detailed',
+    table: 'matricula',
+    tableField: 'dependencia_adm_priv',
+    resultField: 'adm_dependency_detailed_id',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'dependencia_adm_priv'
     }
-
-    if(typeof req.dims.region !== 'undefined') {
-        req.sql.field('regioes.nome', 'region_name')
-            .group('regioes.nome')
-            .order('regioes.nome');
+}).addValue({
+    name: 'school_year',
+    table: 'matricula',
+    tableField: 'serie_ano_id',
+    resultField: 'school_year_id',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'serie_ano_id'
     }
-
-    if(typeof req.dims.state !== 'undefined') {
-        req.sql.field('estados.nome', 'state_name')
-            .group('estados.nome')
-            .order('estados.nome');
+}).addValue({
+    name: 'education_level',
+    table: 'matricula',
+    tableField: 'etapa_ensino_id',
+    resultField: 'education_level_id',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'etapa_ensino_id'
     }
-
-    if(typeof req.dims.city !== 'undefined') {
-        req.sql.field('municipios.nome', 'city_name')
-            .group('municipios.nome')
-            .order('municipios.nome');
+}).addValue({
+    name: 'education_level_mod',
+    table: 'matricula',
+    tableField: 'etapas_mod_ensino_segmento_id',
+    resultField: 'education_level_mod_id',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'etapas_mod_ensino_segmento_id'
     }
-
-    if(typeof req.dims.school !== 'undefined') {
-        req.sql.field('escolas.nome_entidade', 'school_name')
-            .group('escolas.nome_entidade')
-            .order('escolas.nome_entidade');
+}).addValue({
+    name: 'education_level_short',
+    table: 'matricula',
+    tableField: 'etapa_resumida',
+    resultField: 'education_level_short_id',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'etapa_resumida'
     }
-
-    if(typeof req.dims.adm_dependency_id !== 'undefined') {
-        req.sql.field('dependencia_adms.nome', 'adm_dependency_name')
-            .group('dependencia_adms.nome')
-            .order('dependencia_adms.nome');
+}).addValue({
+    name: 'region',
+    table: 'regiao',
+    tableField: 'nome',
+    resultField: 'region_name',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'id'
+    },
+    join: {
+        primary: 'id',
+        foreign: 'regiao_id',
+        foreignTable: 'matricula'
     }
-
-    if(typeof req.dims.location_id !== 'undefined') {
-        req.sql.field('turmas.id_localizacao', 'location')
-            .group('turmas.id_localizacao')
-            .order('turmas.id_localizacao');
+}).addValue({
+    name: 'state',
+    table: 'estado',
+    tableField: 'nome',
+    resultField: 'state_name',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'id'
+    },
+    join: {
+        primary: 'id',
+        foreign: 'estado_id',
+        foreignTable: 'matricula'
     }
-
-    if(typeof req.dims.region === 'undefined'
-        && typeof req.dims.state === 'undefined'
-        && typeof req.dims.city === 'undefined'
-        && typeof req.dims.school === 'undefined') {
-        req.sql.field("'Brasil'", 'name');
+}).addValueToField({
+    name: 'city',
+    table: 'municipio',
+    tableField: ['nome', 'id'],
+    resultField: ['city_name', 'city_id'],
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'id'
+    },
+    join: {
+        primary: 'id',
+        foreign: 'municipio_id',
+        foreignTable: 'matricula'
     }
-
-    // Filter (add where)
-
-    if (typeof req.filter.min_year !== 'undefined') {
-        req.sql.where('turmas.ano_censo>=?', parseInt(req.filter.min_year, 10));
+}, 'dims').addValueToField({
+    name: 'city',
+    table: 'municipio',
+    tableField: 'nome',
+    resultField: 'city_name',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'id'
+    },
+    join: {
+        primary: 'id',
+        foreign: 'municipio_id',
+        foreignTable: 'matricula'
     }
-
-    if (typeof req.filter.max_year !== 'undefined') {
-        req.sql.where('turmas.ano_censo<=?', parseInt(req.filter.max_year, 10));
+}, 'filter').addValueToField({
+    name: 'school',
+    table: 'escola',
+    tableField: ['nome_escola', 'id'],
+    resultField: ['school_name', 'school_id'],
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'id'
+    },
+    join: {
+        primary: ['id', 'ano_censo'],
+        foreign: ['escola_id', 'ano_censo'],
+        foreignTable: 'matricula'
     }
-
-    if (typeof req.filter.adm_dependency_id !== 'undefined') {
-        req.sql.where('pk_dependencia_adm_id=?', parseInt(req.filter.adm_dependency_id, 10));
+}, 'dims').addValueToField({
+    name: 'school',
+    table: 'escola',
+    tableField: 'nome_escola',
+    resultField: 'school_name',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'id'
+    },
+    join: {
+        primary: ['id', 'ano_censo'],
+        foreign: ['escola_id', 'ano_censo'],
+        foreignTable: 'matricula'
     }
-
-    if (typeof req.filter.location_id !== 'undefined') {
-        req.sql.where('turmas.id_localizacao=?', parseInt(req.filter.location_id, 10));
+}, 'filter').addValue({
+    name: 'location',
+    table: 'matricula',
+    tableField: 'localizacao_id',
+    resultField: 'location_id',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'localizacao_id'
     }
-
-    if (typeof req.filter.education_level_id !== 'undefined') {
-        req.sql.where('pk_etapa_ensino_id=?', parseInt(req.filter.education_level_id, 10));
+}).addValue({
+    name: 'rural_location',
+    table: 'matricula',
+    tableField: 'localidade_area_rural',
+    resultField: 'rural_location_id',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'localidade_area_rural'
     }
-
-    if (typeof req.filter.region !== 'undefined') {
-        req.sql.where('pk_regiao_id=?', parseInt(req.filter.region, 10));
+}).addValue({
+    name: 'min_year',
+    table: 'matricula',
+    tableField: 'ano_censo',
+    resultField: 'year',
+    where: {
+        relation: '>=',
+        type: 'integer',
+        field: 'ano_censo'
     }
-
-    if (typeof req.filter.state !== 'undefined') {
-        req.sql.where('pk_estado_id=?', parseInt(req.filter.state, 10));
+}).addValue({
+    name: 'max_year',
+    table: 'matricula',
+    tableField: 'ano_censo',
+    resultField: 'year',
+    where: {
+        relation: '<=',
+        type: 'integer',
+        field: 'ano_censo'
     }
-
-    if (typeof req.filter.city !== 'undefined') {
-        req.sql.where('turmas.fk_municipio_id=?', parseInt(req.filter.city, 10));
+}).addValue({
+    name: 'gender',
+    table: 'matricula',
+    tableField: 'sexo',
+    resultField: 'gender_id',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'sexo'
     }
-
-    if (typeof req.filter.school !== 'undefined') {
-        req.sql.where('turmas.fk_escola_id=?', parseInt(req.filter.school, 10));
+}).addValue({
+    name: 'ethnic_group',
+    table: 'matricula',
+    tableField: 'cor_raca_id',
+    resultField: 'ethnic_group_id',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'cor_raca_id'
     }
-    log.debug(req.sql.toParam());
-    next();
+}).addValue({
+    name: 'period',
+    table: 'matricula',
+    tableField: 'turma_turno_id',
+    resultField: 'period_id',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'turma_turno_id'
+    }
+}).addValue({
+  name:'integral_time',
+  table: 'matricula',
+  tableField: 'tempo_integral',
+  resultField: 'integral_time_id',
+  where: {
+      relation: '=',
+      type: 'boolean',
+      field: 'tempo_integral'
+  }
 });
 
-enrollmentApp.get('/', (req, res, next) => {
-    req.sql.field('COALESCE(SUM(num_matriculas), 0)', 'total')
-        .field('turmas.ano_censo', 'year')
-        .from('turmas')
-        .group('turmas.ano_censo')
-        .order('turmas.ano_censo');
+enrollmentApp.get('/', rqf.parse(), rqf.build(), (req, res, next) => {
+    req.sql.field('COUNT(*)', 'total')
+    .field("'Brasil'", 'name')
+    .field('matricula.ano_censo', 'year')
+    .from('matricula')
+    .group('matricula.ano_censo')
+    .order('matricula.ano_censo')
+    .where('matricula.tipo<=3');
     next();
-}, query, response('test'));
+}, query, addMissing(rqf), id2str.transform(false), response('enrollment'));
+
+enrollmentApp.get('/download', passport.authenticate('bearer', { session: false }), rqf.parse(), rqf.build(), download('matricula', 'mapping_matricula'));
 
 module.exports = enrollmentApp;
diff --git a/src/libs/routes/glossEnrollmentRatio.js b/src/libs/routes/glossEnrollmentRatio.js
new file mode 100644
index 0000000000000000000000000000000000000000..0a929ca7727dca5fd5df654183f8d3dea6a96d31
--- /dev/null
+++ b/src/libs/routes/glossEnrollmentRatio.js
@@ -0,0 +1,365 @@
+const express = require('express');
+
+const glossEnrollmentRatioApp = express.Router();
+
+const libs = `${process.cwd()}/libs`;
+
+const log = require(`${libs}/log`)(module);
+
+const squel = require('squel');
+
+const query = require(`${libs}/middlewares/query`).query;
+
+const multiQuery = require(`${libs}/middlewares/multiQuery`);
+
+const response = require(`${libs}/middlewares/response`);
+
+const ReqQueryFields = require(`${libs}/middlewares/reqQueryFields`);
+
+const id2str = require(`${libs}/middlewares/id2str`);
+
+const addMissing = require(`${libs}/middlewares/addMissing`);
+
+const config = require(`${libs}/config`);
+
+const download = require(`${libs}/middlewares/downloadDatabase`);
+
+const passport = require('passport');
+
+const cache = require('apicache').options({ debug: config.debug, statusCodes: {include: [200]} }).middleware;
+
+let rqf = new ReqQueryFields();
+
+glossEnrollmentRatioApp.use(cache('15 day'));
+
+// Complete range of the enrollments dataset.
+// Returns a tuple of start and ending years of the complete enrollments dataset.
+glossEnrollmentRatioApp.get('/year_range', (req, res, next) => {
+    req.sql.from('pnad')
+    .field('DISTINCT pnad.ano_censo', 'year');
+    next();
+}, query, (req, res, next) => {
+    req.oldResult = req.result;
+
+    req.sql = squel.select();
+
+    req.sql.from('matricula')
+    .field('DISTINCT matricula.ano_censo', 'year');
+    next();
+}, query, (req, res, next) => {
+    let distinct_years = [];
+    let new_result = [];
+    for (let i = 0; i < req.oldResult.length; i++) {
+        for (let j = 0; j < req.result.length; j++) {
+            if(req.oldResult[i].year == req.result[j].year) {
+                distinct_years.push(req.oldResult[i]);
+            }
+        }
+    }
+    new_result.push({start_year: distinct_years[distinct_years.length -1].year, end_year: distinct_years[0].year});
+    req.result = new_result;
+    next();
+}, response('range'));
+
+glossEnrollmentRatioApp.get('/years', (req, res, next) => {
+    req.sql.from('pnad')
+    .field('DISTINCT pnad.ano_censo', 'year');
+    next();
+}, query, (req, res, next) => {
+    req.oldResult = req.result;
+
+    req.sql = squel.select();
+
+    req.sql.from('matricula')
+    .field('DISTINCT matricula.ano_censo', 'year');
+    next();
+}, query, (req, res, next) => {
+    let new_result = []
+    for (let i = 0; i < req.oldResult.length; i++) {
+        for (let j = 0; j < req.result.length; j++) {
+            if(req.oldResult[i].year == req.result[j].year) {
+                new_result.push(req.oldResult[i]);
+            }
+        }
+    }
+    req.result = new_result;
+    next();
+}, response('years'));
+
+glossEnrollmentRatioApp.get('/source', (req, res, next) => {
+    req.sql.from('fonte')
+    .field('fonte', 'source')
+    .where('tabela = \'pnad\'');
+    next();
+}, query, response('source'));
+
+glossEnrollmentRatioApp.get('/education_level_basic', (req, res, next) => {
+    req.result = [
+        {id: null, name: 'Não classificada'},
+        {id: 1, name: 'Creche'},
+        {id: 2, name: 'Pré-Escola'},
+        {id: 4, name: 'Ensino Fundamental - anos iniciais'},
+        {id: 5, name: 'Ensino Fundamental - anos finais'},
+        {id: 6, name: 'Ensino Médio'}
+    ];
+    next();
+}, response('education_level_basic'));
+
+glossEnrollmentRatioApp.get('/gender', (req, res, next) => {
+    req.result = [
+        {id: 1, name: 'Masculino'},
+        {id: 2, name: 'Feminino'}
+    ];
+    next();
+}, response('gender'));
+
+glossEnrollmentRatioApp.get('/ethnic_group', (req, res, next) => {
+    req.result = [
+        {id: 0, name: 'Sem declaração'},
+        {id: 1, name: 'Branca'},
+        {id: 2, name: 'Preta'},
+        {id: 3, name: 'Parda'},
+        {id: 4, name: 'Amarela'},
+        {id: 5, name: 'Indígena'}
+    ];
+    next();
+}, response('ethnic_group'));
+
+glossEnrollmentRatioApp.get('/location', (req, res, next) => {
+    req.result = [
+        {id: 1, name: 'Urbana'},
+        {id: 2, name: 'Rural'}
+    ];
+    next();
+}, response('location'));
+
+rqf.addField({
+    name: 'filter',
+    field: false,
+    where: true
+}).addField({
+    name: 'dims',
+    field: true,
+    where: false
+}).addValue({
+    name: 'region',
+    table: 'regiao',
+    tableField: 'nome',
+    resultField: 'region_name',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'id'
+    },
+    join: {
+        primary: 'id',
+        foreign: 'regiao_id',
+        foreignTable: '@'
+    }
+}).addValue({
+    name: 'state',
+    table: 'estado',
+    tableField: 'nome',
+    resultField: 'state_name',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'id'
+    },
+    join: {
+        primary: 'id',
+        foreign: 'estado_id',
+        foreignTable: '@'
+    }
+}).addValue({
+    name: 'ethnic_group',
+    table: '@',
+    tableField: 'cor_raca_id',
+    resultField: 'ethnic_group_id',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'cor_raca_id'
+    }
+}).addValue({
+    name: 'min_year',
+    table: '@',
+    tableField: 'ano_censo',
+    resultField: 'year',
+    where: {
+        relation: '>=',
+        type: 'integer',
+        table: '@',
+        field: 'ano_censo'
+    }
+}).addValue({
+    name: 'max_year',
+    table: '@',
+    tableField: 'ano_censo',
+    resultField: 'year',
+    where: {
+        relation: '<=',
+        type: 'integer',
+        table: '@',
+        field: 'ano_censo'
+    }
+}).addValue({
+    name: 'gender',
+    table: '@',
+    tableField: 'sexo',
+    resultField: 'gender_id',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'sexo'
+    }
+}).addValue({
+    name: 'location',
+    table: '@',
+    tableField: 'localizacao_id',
+    resultField: 'location_id',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'localizacao_id'
+    }
+}).addValue({
+    name: 'education_level_basic',
+    table: 'matricula',
+    tableField: 'etapas_mod_ensino_segmento_id',
+    resultField: 'education_level_basic_id',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'etapas_mod_ensino_segmento_id'
+    }
+});
+
+function matchQueries(queryTotal, queryPartial) {
+    let match = [];
+    queryPartial.forEach((result) => {
+        let newObj = {};
+        let keys = Object.keys(result);
+        keys.forEach((key) => {
+            newObj[key] = result[key];
+        });
+        // console.log('NEW OBJ');
+        // console.log(newObj);
+        // remove total
+        let index = keys.indexOf('total');
+        if(index > -1) keys.splice(index, 1);
+        // remove education_level_basic_id
+        index = keys.indexOf('education_level_basic_id');
+        if(index > -1) keys.splice(index, 1);
+        // remove education_level_basic_name
+        index = keys.indexOf('education_level_basic_name');
+        if(index > -1) keys.splice(index, 1);
+        let objMatch = null;
+
+        for(let i = 0; i < queryTotal.length; ++i) {
+            let total = queryTotal[i];
+            let foundMatch = true;
+            for(let j = 0; j < keys.length; ++j) {
+                let key = keys[j];
+                if(total[key] !== result[key]) {
+                    foundMatch = false;
+                    break;
+                }
+            }
+            if(foundMatch) {
+                objMatch = total;
+                break;
+            }
+        }
+
+        if(objMatch) {
+            // console.log('MATCH!!!!');
+            // console.log(objMatch);
+            newObj.total = (result.total / objMatch.total) * 100;
+            newObj.partial = result.total;
+            newObj.denominator = objMatch.total
+            match.push(newObj);
+        }
+    });
+    // console.log('TAMANHOS');
+    // console.log(queryTotal.length);
+    // console.log(queryPartial.length);
+    // console.log(match.length);
+    return match;
+}
+
+glossEnrollmentRatioApp.get('/', rqf.parse(),(req, res, next) => {
+    req.numerator = {};
+    req.denominator = {};
+    let glossEnrollmentRatioApp = {};
+
+    req.sql.from('matricula')
+    .field('count(*)', 'total')
+    .field('matricula.ano_censo', 'year')
+    .group('matricula.ano_censo')
+    .order('matricula.ano_censo')
+
+    next();
+}, rqf.build(), query, id2str.transform(), (req, res, next) => {
+    req.numerator = req.result;
+    req.resetSql();
+    req.sql.field('sum(peso)', 'total')
+    .field('pnad.ano_censo','year')
+    .from('pnad')
+    .group('pnad.ano_censo')
+    .order('pnad.ano_censo')
+
+    function convert(result) {
+        if (result == 1) {
+            return 'pnad.faixa_etaria_31_03 = 1'
+        } else if (result == 2) {
+            return 'pnad.faixa_etaria_31_03 = 2'
+        } else if (result == 4) {
+            return 'pnad.faixa_etaria_31_03 = 3'
+        } else if (result == 5) {
+            return 'pnad.faixa_etaria_31_03 = 4'
+        } else if (result == 6) {
+            return 'pnad.faixa_etaria_31_03 = 5'
+        }
+    }
+
+    //remove education_level_basic how filter and add faixa_etaria_31_03 in filter
+    if ("education_level_basic" in req.filter) {
+        if (Array.isArray(req.filter.education_level_basic)) {
+            var string_query = '';
+            for(let i = 0; i < req.filter.education_level_basic.length - 1; i++)  {
+                string_query = string_query + convert(req.filter.education_level_basic[i]) + ' OR ';
+            }
+            string_query = string_query + convert(req.filter.education_level_basic[req.filter.education_level_basic.length - 1]);
+            req.sql.where(string_query);
+        } else {
+            req.sql.where(convert(req.filter.education_level_basic));
+        }
+    } else {
+        res.status(400);
+        next({
+            status: 400,
+            message: 'Wrong/No filter specified'
+        });
+    }
+
+    next();
+}, rqf.parse(), (req, res, next) => {
+    if ("education_level_basic" in req.filter) {
+        delete req.filter.education_level_basic;
+    }
+    if ("education_level_basic" in req.dims) {
+        delete req.dims.education_level_basic;
+    }
+    next();
+}, rqf.build(), query, id2str.transform(), (req, res, next) => {
+    req.denominator = req.result;
+
+    req.result = []
+    let glossEnrollment = matchQueries(req.denominator, req.numerator);
+    req.result = glossEnrollment;
+
+    next();
+}, response('glossEnrollmentRatio'));
+
+module.exports = glossEnrollmentRatioApp;
diff --git a/src/libs/routes/idhm.js b/src/libs/routes/idhm.js
new file mode 100644
index 0000000000000000000000000000000000000000..45a193e7750aab100b417a25affae69b0678ea71
--- /dev/null
+++ b/src/libs/routes/idhm.js
@@ -0,0 +1,192 @@
+const express = require('express');
+
+const idhmApp = express.Router();
+
+const libs = `${process.cwd()}/libs`;
+
+const log = require(`${libs}/log`)(module);
+
+const squel = require('squel');
+
+const query = require(`${libs}/middlewares/query`).query;
+
+const response = require(`${libs}/middlewares/response`);
+
+const ReqQueryFields = require(`${libs}/middlewares/reqQueryFields`);
+
+const id2str = require(`${libs}/middlewares/id2str`);
+
+const addMissing = require(`${libs}/middlewares/addMissing`);
+
+const config = require(`${libs}/config`);
+
+const cache = require('apicache').options({ debug: config.debug, statusCodes: {include: [200]} }).middleware;
+
+let rqf = new ReqQueryFields();
+
+idhmApp.use(cache('15 day'));
+
+idhmApp.get('/year_range', (req, res, next) => {
+    req.sql.from('adh_idh')
+    .field('MIN(adh_idh.ano_censo)', 'start_year')
+    .field('MAX(adh_idh.ano_censo)', 'end_year');
+    next();
+}, query, (req, res, next) => {
+    req.sql.from('adh_idh_uf')
+    .field('MIN(adh_idh_uf.ano_censo)', 'start_year')
+    .field('MAX(adh_idh_uf.ano_censo)', 'end_year');
+    req.old_result = req.result;
+    next();
+}, query, (req, res, next) => {
+    if (req.old_result[0].start_year < req.result[0].start_year) {
+        req.result[0].start_year = req.old_result[0].start_year;
+    }
+    if (req.old_result[0].end_year > req.result[0].end_year) {
+        req.result[0].end_year = req.old_result[0].old_result;
+    }
+    next();
+}, query, response('range'));
+
+idhmApp.get('/years', (req, res, next) => {
+    req.sql.from('adh_idh')
+    .field('DISTINCT adh_idh.ano_censo', 'year');
+    next();
+}, query, (req, res, next) => {
+    req.oldResult = req.result;
+
+    req.sql = squel.select();
+
+    req.sql.from('adh_idh_uf')
+    .field('DISTINCT adh_idh_uf.ano_censo', 'year');
+    next();
+}, query, (req, res, next) => {
+    let result = Object.assign(req.oldResult, req.result);
+    req.result = result;
+    next();
+}, response('years'));
+
+idhmApp.get('/source', (req, res, next) => {
+    req.sql.from('fonte')
+    .field('fonte', 'source')
+    .where('tabela = \'adh_idh\'');
+    next();
+}, query, response('source'));
+
+idhmApp.get('/IDHM_level', (req, res, next) => {
+    req.result = [
+        {id: null, name: 'Não classificada'},
+        {id: 1, name: 'Muito Baixa'},
+        {id: 2, name: 'Baixo'},
+        {id: 3, name: 'Médio'},
+        {id: 4, name: 'Alto'},
+        {id: 5, name: 'Muito Alto'}
+    ];
+    next();
+}, response('IDHM_level'));
+
+rqf.addField({
+    name: 'filter',
+    field: false,
+    where: true
+}).addField({
+    name: 'dims',
+    field: true,
+    where: false
+}).addValue({
+    name: 'city',
+    table: 'municipio',
+    tableField: 'nome',
+    resultField: 'city_name',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'municipio_id',
+        table: 'adh_idh'
+    },
+    join: {
+        primary: 'id',
+        foreign: 'municipio_id',
+        foreignTable: 'adh_idh'
+    }
+}).addValue({
+    name: 'state',
+    table: 'estado',
+    tableField: 'nome',
+    resultField: 'state_name',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'estado_id',
+        table: '@'
+    },
+    join: {
+        primary: 'id',
+        foreign: 'estado_id',
+        foreignTable: '@'
+    }
+}).addValue({
+    name: 'min_year',
+    table: '@',
+    tableField: 'ano_censo',
+    resultField: 'year',
+    where: {
+        relation: '>=',
+        type: 'integer',
+        table: '@',
+        field: 'ano_censo'
+    }
+}).addValue({
+    name: 'max_year',
+    table: '@',
+    tableField: 'ano_censo',
+    resultField: 'year',
+    where: {
+        relation: '<=',
+        type: 'integer',
+        table: '@',
+        field: 'ano_censo'
+    }
+}).addValue({
+    name: 'idhm_level',
+    table: '@',
+    tableField: 'idhm_nivel',
+    resultField: 'idhm_level_id',
+    where: {
+        relation: '=',
+        type: 'integer',
+        table: '@',
+        field: 'idhm_nivel'
+    }
+});
+
+
+idhmApp.get('/', rqf.parse(), (req, res, next) => {
+      if (("city" in req.dims) || ("city" in req.filter)) {
+          req.sql.from('adh_idh')
+          .field('adh_idh.idhm', 'total')
+          .field('adh_idh.ano_censo', 'year')
+          .field('adh_idh.municipio_id', 'city_id')
+          .field('adh_idh.estado_id', 'state_id')
+          .group('adh_idh.idhm')
+          .group('adh_idh.ano_censo')
+          .group('adh_idh.municipio_id')
+          .group('adh_idh.estado_id')
+      } else if (("state" in req.filter) || ("state" in req.dims)) {
+          req.sql.from('adh_idh_uf')
+          .field('adh_idh_uf.idhm', 'total')
+          .field('adh_idh_uf.ano_censo', 'year')
+          .field('adh_idh_uf.estado_id', 'state_id')
+          .group('adh_idh_uf.idhm')
+          .group('adh_idh_uf.ano_censo')
+          .group('adh_idh_uf.estado_id')
+        } else {
+            res.status(400);
+            next({
+                status: 400,
+                message: 'Wrong/No filter specified'
+            });
+        }
+        next();
+}, rqf.build(), query, addMissing(rqf), id2str.transform(), response('idhm'));
+
+module.exports = idhmApp;
diff --git a/src/libs/routes/idhme.js b/src/libs/routes/idhme.js
new file mode 100644
index 0000000000000000000000000000000000000000..2a1f4d033fc61b8f96a50d4a42001fca4da52b60
--- /dev/null
+++ b/src/libs/routes/idhme.js
@@ -0,0 +1,165 @@
+const express = require('express');
+
+const idhmeApp = express.Router();
+
+const libs = `${process.cwd()}/libs`;
+
+const squel = require('squel');
+
+const query = require(`${libs}/middlewares/query`).query;
+
+const response = require(`${libs}/middlewares/response`);
+
+const id2str = require(`${libs}/middlewares/id2str`);
+
+const ReqQueryFields = require(`${libs}/middlewares/reqQueryFields`);
+
+const addMissing = require(`${libs}/middlewares/addMissing`);
+
+const config = require(`${libs}/config`); 
+
+const cache = require('apicache').options({ debug: config.debug, statusCodes: {include: [200]} }).middleware;
+
+let rqf = new ReqQueryFields();
+
+idhmeApp.use(cache('15 day'));
+
+idhmeApp.get('/year_range', (req, res, next) => {
+    req.sql.from('adh_idh')
+    .field('MIN(adh_idh.ano_censo)', 'start_year')
+    .field('MAX(adh_idh.ano_censo)', 'end_year');
+    next();
+}, query, (req, res, next) => {
+    req.sql.from('adh_idh_uf')
+    .field('MIN(adh_idh_uf.ano_censo)', 'start_year')
+    .field('MAX(adh_idh_uf.ano_censo)', 'end_year');
+    req.old_result = req.result;
+    next();
+}, query, (req, res, next) => {
+    if (req.old_result[0].start_year < req.result[0].start_year) {
+        req.result[0].start_year = req.old_result[0].start_year;
+    }
+    if (req.old_result[0].end_year > req.result[0].end_year) {
+        req.result[0].end_year = req.old_result[0].old_result;
+    }
+    next();
+}, query, response('range'));
+
+idhmeApp.get('/years', (req, res, next) => {
+    req.sql.from('adh_idh')
+    .field('DISTINCT adh_idh.ano_censo', 'year');
+    next();
+}, query, (req, res, next) => {
+    req.oldResult = req.result;
+
+    req.sql = squel.select();
+
+    req.sql.from('adh_idh_uf')
+    .field('DISTINCT adh_idh_uf.ano_censo', 'year');
+    next();
+}, query, (req, res, next) => {
+    let result = Object.assign(req.oldResult, req.result);
+    req.result = result;
+    next();
+}, response('years'));
+
+idhmeApp.get('/source', (req, res, next) => {
+    req.sql.from('fonte')
+    .field('fonte', 'source')
+    .where('tabela = \'adh_idh\'');
+    next();
+}, query, response('source'));
+
+rqf.addField({
+    name: 'filter',
+    field: false,
+    where: true
+}).addField({
+    name: 'dims',
+    field: true,
+    where: false
+}).addValue({
+    name: 'city',
+    table: 'municipio',
+    tableField: 'nome',
+    resultField: 'city_name',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'municipio_id',
+        table: 'adh_idh'
+    },
+    join: {
+        primary: 'id',
+        foreign: 'municipio_id',
+        foreignTable: 'adh_idh'
+    }
+}).addValue({
+    name: 'min_year',
+    table: '@',
+    tableField: 'ano_censo',
+    resultField: 'year',
+    where: {
+        relation: '>=',
+        type: 'integer',
+        table: '@',
+        field: 'ano_censo'
+    }
+}).addValue({
+    name: 'max_year',
+    table: '@',
+    tableField: 'ano_censo',
+    resultField: 'year',
+    where: {
+        relation: '<=',
+        type: 'integer',
+        table: '@',
+        field: 'ano_censo'
+    }
+}).addValue({
+    name: 'state',
+    table: 'estado',
+    tableField: 'nome',
+    resultField: 'state_name',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'estado_id',
+        table: '@'
+    },
+    join: {
+        primary: 'id',
+        foreign: 'estado_id',
+        foreignTable: '@'
+    }
+});
+
+idhmeApp.get('/', rqf.parse(), (req, res, next) => {
+
+    if (("city" in req.dims) || ("city" in req.filter)) {
+        req.sql.from('adh_idh')
+        .field('adh_idh.idhm_e', 'total')
+        .field('adh_idh.ano_censo', 'year')
+        .field('adh_idh.municipio_id', 'city_id')
+        .group('adh_idh.idhm_e')
+        .group('adh_idh.ano_censo')
+        .group('adh_idh.municipio_id');
+    } else if (("state" in req.filter) || ("state" in req.dims)) {
+        req.sql.from('adh_idh_uf')
+        .field('adh_idh_uf.idhm_e', 'total')
+        .field('adh_idh_uf.ano_censo', 'year')
+        .field('adh_idh_uf.estado_id', 'state_id')
+        .group('adh_idh_uf.idhm_e')
+        .group('adh_idh_uf.ano_censo')
+        .group('adh_idh_uf.estado_id');
+    } else {
+        res.status(400);
+        next({
+            status: 400,
+            message: 'Wrong/No filter specified'
+        });
+    }
+    next();
+}, rqf.build(), query, addMissing(rqf), id2str.transform(), response('idhme'));
+
+module.exports = idhmeApp;
diff --git a/src/libs/routes/idhml.js b/src/libs/routes/idhml.js
new file mode 100644
index 0000000000000000000000000000000000000000..1fddee79146257c2f837a937bb5864fcdbab20af
--- /dev/null
+++ b/src/libs/routes/idhml.js
@@ -0,0 +1,165 @@
+const express = require('express');
+
+const idhmlApp = express.Router();
+
+const libs = `${process.cwd()}/libs`;
+
+const squel = require('squel');
+
+const query = require(`${libs}/middlewares/query`).query;
+
+const response = require(`${libs}/middlewares/response`);
+
+const id2str = require(`${libs}/middlewares/id2str`);
+
+const ReqQueryFields = require(`${libs}/middlewares/reqQueryFields`);
+
+const addMissing = require(`${libs}/middlewares/addMissing`);
+
+const config = require(`${libs}/config`); 
+
+const cache = require('apicache').options({ debug: config.debug, statusCodes: {include: [200]} }).middleware;
+
+let rqf = new ReqQueryFields();
+
+idhmlApp.use(cache('15 day'));
+
+idhmlApp.get('/year_range', (req, res, next) => {
+    req.sql.from('adh_idh')
+    .field('MIN(adh_idh.ano_censo)', 'start_year')
+    .field('MAX(adh_idh.ano_censo)', 'end_year');
+    next();
+}, query, (req, res, next) => {
+    req.sql.from('adh_idh_uf')
+    .field('MIN(adh_idh_uf.ano_censo)', 'start_year')
+    .field('MAX(adh_idh_uf.ano_censo)', 'end_year');
+    req.old_result = req.result;
+    next();
+}, query, (req, res, next) => {
+    if (req.old_result[0].start_year < req.result[0].start_year) {
+        req.result[0].start_year = req.old_result[0].start_year;
+    }
+    if (req.old_result[0].end_year > req.result[0].end_year) {
+        req.result[0].end_year = req.old_result[0].old_result;
+    }
+    next();
+}, query, response('range'));
+
+idhmlApp.get('/years', (req, res, next) => {
+    req.sql.from('adh_idh')
+    .field('DISTINCT adh_idh.ano_censo', 'year');
+    next();
+}, query, (req, res, next) => {
+    req.oldResult = req.result;
+
+    req.sql = squel.select();
+
+    req.sql.from('adh_idh_uf')
+    .field('DISTINCT adh_idh_uf.ano_censo', 'year');
+    next();
+}, query, (req, res, next) => {
+    let result = Object.assign(req.oldResult, req.result);
+    req.result = result;
+    next();
+}, response('years'));
+
+idhmlApp.get('/source', (req, res, next) => {
+    req.sql.from('fonte')
+    .field('fonte', 'source')
+    .where('tabela = \'adh_idh\'');
+    next();
+}, query, response('source'));
+
+rqf.addField({
+    name: 'filter',
+    field: false,
+    where: true
+}).addField({
+    name: 'dims',
+    field: true,
+    where: false
+}).addValue({
+    name: 'city',
+    table: 'municipio',
+    tableField: 'nome',
+    resultField: 'city_name',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'municipio_id',
+        table: 'adh_idh'
+    },
+    join: {
+        primary: 'id',
+        foreign: 'municipio_id',
+        foreignTable: 'adh_idh'
+    }
+}).addValue({
+    name: 'min_year',
+    table: '@',
+    tableField: 'ano_censo',
+    resultField: 'year',
+    where: {
+        relation: '>=',
+        type: 'integer',
+        table: '@',
+        field: 'ano_censo'
+    }
+}).addValue({
+    name: 'max_year',
+    table: '@',
+    tableField: 'ano_censo',
+    resultField: 'year',
+    where: {
+        relation: '<=',
+        type: 'integer',
+        table: '@',
+        field: 'ano_censo'
+    }
+}).addValue({
+    name: 'state',
+    table: 'estado',
+    tableField: 'nome',
+    resultField: 'state_name',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'estado_id',
+        table: '@'
+    },
+    join: {
+        primary: 'id',
+        foreign: 'estado_id',
+        foreignTable: '@'
+    }
+});
+
+idhmlApp.get('/', rqf.parse(), (req, res, next) => {
+
+    if (("city" in req.dims) || ("city" in req.filter)) {
+        req.sql.from('adh_idh')
+        .field('adh_idh.idhm_l', 'total')
+        .field('adh_idh.ano_censo', 'year')
+        .field('adh_idh.municipio_id', 'city_id')
+        .group('adh_idh.idhm_l')
+        .group('adh_idh.ano_censo')
+        .group('adh_idh.municipio_id');
+    } else if (("state" in req.filter) || ("state" in req.dims)) {
+        req.sql.from('adh_idh_uf')
+        .field('adh_idh_uf.idhm_l', 'total')
+        .field('adh_idh_uf.ano_censo', 'year')
+        .field('adh_idh_uf.estado_id', 'state_id')
+        .group('adh_idh_uf.idhm_l')
+        .group('adh_idh_uf.ano_censo')
+        .group('adh_idh_uf.estado_id');
+    } else {
+        res.status(400);
+        next({
+            status: 400,
+            message: 'Wrong/No filter specified'
+        });
+    }
+    next();
+}, rqf.build(), query, addMissing(rqf), id2str.transform(), response('idhme'));
+
+module.exports = idhmlApp;
diff --git a/src/libs/routes/idhmr.js b/src/libs/routes/idhmr.js
new file mode 100644
index 0000000000000000000000000000000000000000..938d58a93db62f00185c08a22a75f34403541c27
--- /dev/null
+++ b/src/libs/routes/idhmr.js
@@ -0,0 +1,168 @@
+const express = require('express');
+
+const idhmrApp = express.Router();
+
+const libs = `${process.cwd()}/libs`;
+
+const log = require(`${libs}/log`)(module);
+
+const squel = require('squel');
+
+const query = require(`${libs}/middlewares/query`).query;
+
+const response = require(`${libs}/middlewares/response`);
+
+const ReqQueryFields = require(`${libs}/middlewares/reqQueryFields`);
+
+const id2str = require(`${libs}/middlewares/id2str`);
+
+const addMissing = require(`${libs}/middlewares/addMissing`);
+
+const config = require(`${libs}/config`);
+
+const cache = require('apicache').options({ debug: config.debug, statusCodes: {include: [200]} }).middleware;
+
+let rqf = new ReqQueryFields();
+
+idhmrApp.use(cache('15 day'));
+
+idhmrApp.get('/year_range', (req, res, next) => {
+    req.sql.from('adh_idh')
+    .field('MIN(adh_idh.ano_censo)', 'start_year')
+    .field('MAX(adh_idh.ano_censo)', 'end_year');
+    next();
+}, query, (req, res, next) => {
+    req.sql.from('adh_idh_uf')
+    .field('MIN(adh_idh_uf.ano_censo)', 'start_year')
+    .field('MAX(adh_idh_uf.ano_censo)', 'end_year');
+    req.old_result = req.result;
+    next();
+}, query, (req, res, next) => {
+    if (req.old_result[0].start_year < req.result[0].start_year) {
+        req.result[0].start_year = req.old_result[0].start_year;
+    }
+    if (req.old_result[0].end_year > req.result[0].end_year) {
+        req.result[0].end_year = req.old_result[0].old_result;
+    }
+    next();
+}, query, response('range'));
+
+idhmrApp.get('/years', (req, res, next) => {
+    req.sql.from('adh_idh')
+    .field('DISTINCT adh_idh.ano_censo', 'year');
+    next();
+}, query, (req, res, next) => {
+    req.oldResult = req.result;
+
+    req.sql = squel.select();
+
+    req.sql.from('adh_idh_uf')
+    .field('DISTINCT adh_idh_uf.ano_censo', 'year');
+    next();
+}, query, (req, res, next) => {
+    let result = Object.assign(req.oldResult, req.result);
+    req.result = result;
+    next();
+}, response('years'));
+
+idhmrApp.get('/source', (req, res, next) => {
+    req.sql.from('fonte')
+    .field('fonte', 'source')
+    .where('tabela = \'adh_idh\'');
+    next();
+}, query, response('source'));
+
+rqf.addField({
+    name: 'filter',
+    field: false,
+    where: true
+}).addField({
+    name: 'dims',
+    field: true,
+    where: false
+}).addValue({
+    name: 'city',
+    table: 'municipio',
+    tableField: 'nome',
+    resultField: 'city_name',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'municipio_id',
+        table: 'adh_idh'
+    },
+    join: {
+        primary: 'id',
+        foreign: 'municipio_id',
+        foreignTable: 'adh_idh'
+    }
+}).addValue({
+    name: 'state',
+    table: 'estado',
+    tableField: 'nome',
+    resultField: 'state_name',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'estado_id',
+        table: '@'
+    },
+    join: {
+        primary: 'id',
+        foreign: 'estado_id',
+        foreignTable: '@'
+    }
+}).addValue({
+    name: 'min_year',
+    table: '@',
+    tableField: 'ano_censo',
+    resultField: 'year',
+    where: {
+        relation: '>=',
+        type: 'integer',
+        table: '@',
+        field: 'ano_censo'
+    }
+}).addValue({
+    name: 'max_year',
+    table: '@',
+    tableField: 'ano_censo',
+    resultField: 'year',
+    where: {
+        relation: '<=',
+        type: 'integer',
+        table: '@',
+        field: 'ano_censo'
+    }
+});
+
+idhmrApp.get('/', rqf.parse(), (req, res, next) => {
+    if (("city" in req.dims) || ("city" in req.filter)) {
+        req.sql.from('adh_idh')
+        .field('adh_idh.idhm_r', 'total')
+        .field('adh_idh.ano_censo', 'year')
+        .field('adh_idh.municipio_id', 'city_id')
+        .field('adh_idh.estado_id', 'state_id')
+        .group('adh_idh.idhm_r')
+        .group('adh_idh.ano_censo')
+        .group('adh_idh.municipio_id')
+        .group('adh_idh.estado_id')
+    } else if (("state" in req.filter) || ("state" in req.dims)) {
+        req.sql.from('adh_idh_uf')
+        .field('adh_idh_uf.idhm_r', 'total')
+        .field('adh_idh_uf.ano_censo', 'year')
+        .field('adh_idh_uf.estado_id', 'state_id')
+        .group('adh_idh_uf.idhm_r')
+        .group('adh_idh_uf.ano_censo')
+        .group('adh_idh_uf.estado_id')
+      } else {
+          res.status(400);
+          next({
+              status: 400,
+              message: 'Wrong/No filter specified'
+          });
+      }
+      next();
+}, rqf.build(), query, addMissing(rqf), id2str.transform(), response('idhmr'));
+
+module.exports = idhmrApp;
diff --git a/src/libs/routes/infrastructure.js b/src/libs/routes/infrastructure.js
new file mode 100644
index 0000000000000000000000000000000000000000..e201e1f41f4156926c04fa76fa78895409507b36
--- /dev/null
+++ b/src/libs/routes/infrastructure.js
@@ -0,0 +1,542 @@
+const express = require('express');
+
+const infrastructureApp = express.Router();
+
+const libs = `${process.cwd()}/libs`;
+
+const log = require(`${libs}/log`)(module);
+
+const squel = require('squel');
+
+const query = require(`${libs}/middlewares/query`).query;
+
+const multiQuery = require(`${libs}/middlewares/multiQuery`);
+
+const response = require(`${libs}/middlewares/response`);
+
+const id2str = require(`${libs}/middlewares/id2str`);
+
+const ReqQueryFields = require(`${libs}/middlewares/reqQueryFields`);
+
+const config = require(`${libs}/config`);
+
+const cache = require('apicache').options({ debug: config.debug, statusCodes: {include: [200]} }).middleware;
+
+let rqf = new ReqQueryFields();
+
+infrastructureApp.use(cache('15 day'));
+
+infrastructureApp.get('/year_range', (req, res, next) => {
+    req.sql.from('escola')
+    .field('MIN(escola.ano_censo)', 'start_year')
+    .field('MAX(escola.ano_censo)', 'end_year');
+    next();
+}, query, response('range'));
+
+infrastructureApp.get('/years', (req, res, next) => {
+    req.sql.from('escola')
+    .field('DISTINCT escola.ano_censo', 'year');
+    next();
+}, query, response('years'));
+
+infrastructureApp.get('/source', (req, res, next) => {
+    req.sql.from('fonte')
+    .field('fonte', 'source')
+    .where('tabela = \'escola\'');
+    next();
+}, query, response('source'));
+
+infrastructureApp.get('/location', (req, res, next) => {
+    req.result = [
+        {id: 1, name: 'Urbana'},
+        {id: 2, name: 'Rural'}
+    ];
+    next();
+}, response('location'));
+
+infrastructureApp.get('/rural_location', (req, res, next) => {
+    req.result = [
+        {id: 1, name: "Urbana"},
+        {id: 2, name: "Rural"},
+        {id: 3, name: "Rural - Área de assentamento"},
+        {id: 4, name: "Rural - Terra indígena"},
+        {id: 5, name: "Rural - Área remanescente de quilombos"},
+        {id: 6, name: "Rural - Unidade de uso sustentável"}
+    ];
+    next();
+}, response('rural_location'));
+
+infrastructureApp.get('/adm_dependency', (req, res, next) => {
+    req.sql.from('dependencia_adm')
+    .field('id')
+    .field('nome', 'name')
+    .where('id <= 4');
+    next();
+}, query, response('adm_dependency'));
+
+infrastructureApp.get('/adm_dependency_detailed', (req, res, next) => {
+    req.sql.from('dependencia_adm_priv')
+    .field('id', 'id')
+    .field('nome', 'name');
+    next();
+}, query, response('adm_dependency_detailed'));
+
+rqf.addField({
+    name: 'filter',
+    field: false,
+    where: true
+}).addField({
+    name: 'dims',
+    field: true,
+    where: false
+}).addValueToField({
+    name: 'city',
+    table: 'municipio',
+    tableField: ['nome', 'id'],
+    resultField: ['city_name', 'city_id'],
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'municipio_id',
+        table: 'escola'
+    },
+    join: {
+        primary: 'id',
+        foreign: 'municipio_id',
+        foreignTable: 'escola'
+    }
+}, 'dims').addValueToField({
+    name: 'city',
+    table: 'municipio',
+    tableField: 'nome',
+    resultField: 'city_name',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'municipio_id',
+        table: 'escola'
+    },
+    join: {
+        primary: 'id',
+        foreign: 'municipio_id',
+        foreignTable: 'escola'
+    }
+}, 'filter').addValue({
+    name: 'state',
+    table: 'estado',
+    tableField: 'nome',
+    resultField: 'state_name',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'estado_id',
+        table: 'escola'
+    },
+    join: {
+        primary: 'id',
+        foreign: 'estado_id',
+        foreignTable: 'escola'
+    }
+}).addValue({
+    name: 'region',
+    table: 'regiao',
+    tableField: 'nome',
+    resultField: 'region_name',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'id'
+    },
+    join: {
+        primary: 'id',
+        foreign: 'regiao_id',
+        foreignTable: 'escola'
+    }
+}).addValue({
+    name: 'location',
+    table: 'escola',
+    tableField: 'cod_localizacao',
+    resultField: 'location_id',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'cod_localizacao'
+    }
+}).addValue({
+    name: 'rural_location',
+    table: 'escola',
+    tableField: 'localidade_area_rural',
+    resultField: 'rural_location_id',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'localidade_area_rural'
+    }
+}).addValue({
+    name: 'adm_dependency',
+    table: 'escola',
+    tableField: 'dependencia_adm_id',
+    resultField: 'adm_dependency_id',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'dependencia_adm_id'
+    }
+}).addValue({
+    name: 'adm_dependency_detailed',
+    table: 'escola',
+    tableField: 'dependencia_adm_priv',
+    resultField: 'adm_dependency_detailed_id',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'dependencia_adm_priv'
+    }
+}).addValue({
+    name: 'min_year',
+    table: 'escola',
+    tableField: 'ano_censo',
+    resultField: 'year',
+    where: {
+        relation: '>=',
+        type: 'integer',
+        field: 'ano_censo'
+    }
+}).addValue({
+    name: 'max_year',
+    table: 'escola',
+    tableField: 'ano_censo',
+    resultField: 'year',
+    where: {
+        relation: '<=',
+        type: 'integer',
+        field: 'ano_censo'
+    }
+});
+
+function matchQueries(queryTotal, queryPartial) {
+    let match = [];
+    queryTotal.forEach((result) => {
+        let newObj = {};
+        let keys = Object.keys(result);
+        keys.forEach((key) => {
+            newObj[key] = result[key];
+        });
+        let index = keys.indexOf('total');
+        if(index > -1) keys.splice(index, 1);
+        let objMatch = null;
+
+        for(let i = 0; i < queryPartial.length; ++i) {
+            let partial = queryPartial[i];
+            let foundMatch = true;
+            for(let j = 0; j < keys.length; ++j) {
+                let key = keys[j];
+                if(partial[key] !== result[key]) {
+                    foundMatch = false;
+                    break;
+                }
+            }
+            if(foundMatch) {
+                objMatch = partial;
+                break;
+            }
+        }
+
+        if(objMatch) {
+            newObj.percentage = (objMatch.total / result.total) * 100;
+            newObj.partial = objMatch.total;
+            newObj.total = result.total
+            match.push(newObj);
+        }
+    });
+
+    return match;
+}
+
+infrastructureApp.get('/', rqf.parse(), rqf.build(), (req, res, next) => {
+    req.querySet = [];
+    req.queryIndex = {};
+
+    // Local de funcionamento
+    let allSchools = req.sql.clone();
+    allSchools.from('escola').field('COUNT(escola.id)', 'total')
+    .field("'Brasil'", 'name')
+    .field('escola.ano_censo', 'year')
+    .group('escola.ano_censo')
+    .where('escola.situacao_de_funcionamento = 1')
+    .order('escola.ano_censo');
+    req.queryIndex.allSchools = req.querySet.push(allSchools) - 1;
+
+    let schoolPlace = allSchools.clone();
+    schoolPlace.where('escola.func_predio_escolar = 1 AND escola.func_salas_empresa = 0 AND escola.func_templo_igreja = 0 AND escola.func_casa_professor = 0 AND escola.func_galpao = 0 AND escola.biblioteca = 1');
+    req.queryIndex.schoolPlace = req.querySet.push(schoolPlace) - 1;
+
+    // Bibliotecas
+    let allLibraries = allSchools.clone();
+    allLibraries.where('escola.func_predio_escolar = 1 AND escola.cod_localizacao = 1');
+    req.queryIndex.allLibraries = req.querySet.push(allLibraries) - 1;
+
+    let haveLibraries = allLibraries.clone();
+    haveLibraries.where('escola.biblioteca = 1');
+    req.queryIndex.haveLibraries = req.querySet.push(haveLibraries) - 1;
+
+    // Bibliotecas/Sala de leitura
+    let allLibrariesReadingRoom = allSchools.clone();
+    allLibrariesReadingRoom.where('escola.func_predio_escolar = 1 AND escola.cod_localizacao = 2');
+    req.queryIndex.allLibrariesReadingRoom = req.querySet.push(allLibrariesReadingRoom) - 1;
+
+    let haveLibrariesReadingRoom = allLibrariesReadingRoom.clone();
+    haveLibrariesReadingRoom.where('escola.sala_leitura = 1');
+    req.queryIndex.haveLibrariesReadingRoom = req.querySet.push(haveLibrariesReadingRoom) - 1;
+
+    // Laboratório de informática
+    let allInfLab = allSchools.clone();
+    allInfLab.where('escola.func_predio_escolar = 1')
+    .where('escola.reg_fund_ai = 1 OR escola.reg_fund_af = 1 OR escola.reg_medio_medio = 1 OR escola.reg_medio_integrado = 1 OR escola.reg_medio_normal = 1 OR escola.ensino_eja_fund = 1 OR escola.ensino_eja_medio = 1 OR escola.ensino_eja_prof = 1');
+    req.queryIndex.allInfLab = req.querySet.push(allInfLab) - 1;
+
+    let haveInfLab = allInfLab.clone();
+    haveInfLab.where('escola.lab_informatica = 1');
+    req.queryIndex.haveInfLab = req.querySet.push(haveInfLab) - 1;
+
+    // Laboratório de ciências
+    let allScienceLab = allInfLab.clone();
+    req.queryIndex.allScienceLab = req.querySet.push(allScienceLab) - 1;
+
+    let haveScienceLab = allScienceLab.clone();
+    haveScienceLab.where('escola.lab_ciencias = 1');
+    req.queryIndex.haveScienceLab = req.querySet.push(haveScienceLab) - 1;
+
+    // Parque infantil
+    let allKidsPark = allSchools.clone();
+    allKidsPark.where('escola.func_predio_escolar = 1')
+    .where('escola.reg_infantil_creche = 1 OR escola.reg_infantil_preescola = 1 OR escola.reg_fund_ai = 1 OR escola.esp_infantil_creche = 1 OR escola.esp_exclusiva_creche = 1 OR escola.reg_esp_exclusiva_fund_ai = 1');
+    req.queryIndex.allKidsPark = req.querySet.push(allKidsPark) - 1;
+
+    let haveKidsPark = allKidsPark.clone();
+    haveKidsPark.where('escola.parque_infantil = 1');
+    req.queryIndex.haveKidsPark = req.querySet.push(haveKidsPark) - 1;
+
+    // Berçário
+    let allCribs = allSchools.clone();
+    allCribs.where('escola.func_predio_escolar = 1')
+    .where('escola.reg_infantil_creche = 1 OR escola.esp_infantil_creche = 1');
+    req.queryIndex.allCribs = req.querySet.push(allCribs) - 1;
+
+    let haveCribs = allCribs.clone();
+    haveCribs.where('escola.bercario = 1');
+    req.queryIndex.haveCribs = req.querySet.push(haveCribs) - 1;
+
+    // Quadra
+    let allSportsCourt = allScienceLab.clone();
+    allSportsCourt.where('escola.cod_localizacao = 1');
+    req.queryIndex.allSportsCourt = req.querySet.push(allSportsCourt) - 1;
+
+    let haveSportsCourt = allSportsCourt.clone();
+    haveSportsCourt.where('escola.quadra_esportes = 1');
+    req.queryIndex.haveSportsCourt = req.querySet.push(haveSportsCourt) - 1;
+
+    // Quadra coberta
+    req.queryIndex.allCoveredSportsCourt = req.queryIndex.allSportsCourt;
+
+    let haveCoveredSportsCourt = allSportsCourt.clone();
+    haveCoveredSportsCourt.where('escola.quadra_esportes_coberta = 1');
+    req.queryIndex.haveCoveredSportsCourt = req.querySet.push(haveCoveredSportsCourt) - 1;
+
+    // Quadra Descoberta
+    let allUncoveredSportsCourt = allSportsCourt.clone();
+    allUncoveredSportsCourt.where('escola.quadra_esportes_coberta = 0');
+    req.queryIndex.allUncoveredSportsCourt = req.querySet.push(allUncoveredSportsCourt) - 1;
+
+    let haveUncoveredSportsCourt = allUncoveredSportsCourt.clone();
+    haveUncoveredSportsCourt.where('escola.quadra_esportes_descoberta = 1');
+    req.queryIndex.haveUncoveredSportsCourt = req.querySet.push(haveUncoveredSportsCourt) - 1;
+
+    // Sala de direção
+    let allDirectorRoom = allSchools.clone();
+    allDirectorRoom.where('escola.func_predio_escolar = 1 AND escola.cod_localizacao = 1');
+    req.queryIndex.allDirectorRoom = req.querySet.push(allDirectorRoom) - 1;
+
+    let haveDirectorRoom = allDirectorRoom.clone();
+    haveDirectorRoom.where('escola.sala_diretoria = 1');
+    req.queryIndex.haveDirectorRoom = req.querySet.push(haveDirectorRoom) - 1;
+
+    // Secretaria
+    let allSecretary = allSchools.clone();
+    allSecretary.where('escola.func_predio_escolar = 1');
+    req.queryIndex.allSecretary = req.querySet.push(allSecretary) - 1;
+
+    let haveSecretary = allSecretary.clone();
+    haveSecretary.where('escola.secretaria = 1');
+    req.queryIndex.haveSecretary = req.querySet.push(haveSecretary) - 1;
+
+    // Sala de professores
+    req.queryIndex.allTeacherRoom = req.queryIndex.allSecretary;
+
+    let haveTeacherRoom = allSecretary.clone();
+    haveTeacherRoom.where('escola.sala_professor = 1');
+    req.queryIndex.haveTeacherRoom = req.querySet.push(haveTeacherRoom) - 1;
+
+    // Cozinha
+    req.queryIndex.allKitchen = req.queryIndex.allSecretary;
+
+    let haveKitchen = allSecretary.clone();
+    haveKitchen.where('escola.cozinha = 1');
+    req.queryIndex.haveKitchen = req.querySet.push(haveKitchen) - 1;
+
+    // Despensa
+    req.queryIndex.allStoreroom = req.queryIndex.allSecretary;
+
+    let haveStoreroom = allSecretary.clone();
+    haveStoreroom.where('escola.despensa = 1');
+    req.queryIndex.haveStoreroom = req.querySet.push(haveStoreroom) - 1;
+
+    // Almoxarifado
+    req.queryIndex.allWarehouse = req.queryIndex.allSecretary;
+
+    let haveWarehouse = allSecretary.clone();
+    haveWarehouse.where('escola.almoxarifado = 1');
+    req.queryIndex.haveWarehouse = req.querySet.push(haveWarehouse) - 1;
+
+    // Internet
+    req.queryIndex.allInternet = req.queryIndex.allLibrariesReadingRoom;
+
+    let haveInternet = allLibrariesReadingRoom.clone();
+    haveInternet.where('escola.internet = 1');
+    req.queryIndex.haveInternet = req.querySet.push(haveInternet) - 1;
+
+    // Internet banda larga
+    req.queryIndex.allBroadbandInternet = req.queryIndex.allLibraries;
+
+    let haveBroadbandInternet = allLibraries.clone();
+    haveBroadbandInternet.where('escola.internet_banda_larga = 1');
+    req.queryIndex.haveBroadbandInternet = req.querySet.push(haveBroadbandInternet) - 1;
+
+    // Banheiro dentro do prédio
+    req.queryIndex.allInsideBathroom = req.queryIndex.allSecretary;
+
+    let haveInsideBathroom = allSecretary.clone();
+    haveInsideBathroom.where('escola.sanitario_dentro_predio = 1');
+    req.queryIndex.haveInsideBathroom = req.querySet.push(haveInsideBathroom) - 1;
+
+    // Banheiro adequado para educação infantil dentro do prédio
+    req.queryIndex.allInsideKidsBathroom = req.queryIndex.allKidsPark;
+
+    let haveInsideKidsBathroom = allKidsPark.clone();
+    haveInsideKidsBathroom.where('escola.sanitario_ei = 1');
+    req.queryIndex.haveInsideKidsBathroom = req.querySet.push(haveInsideKidsBathroom) - 1;
+
+    // Fornecimento de energia
+    req.queryIndex.allEletricEnergy = req.queryIndex.allSecretary;
+
+    let haveEletricEnergy = allSecretary.clone();
+    haveEletricEnergy.where('escola.fornecimento_energia = 1');
+    req.queryIndex.haveEletricEnergy = req.querySet.push(haveEletricEnergy) - 1;
+
+    // Abastecimento de água
+    req.queryIndex.allWaterSupply = req.queryIndex.allSecretary;
+
+    let haveWaterSupply = allSecretary.clone();
+    haveWaterSupply.where('escola.fornecimento_agua = 1');
+    req.queryIndex.haveWaterSupply = req.querySet.push(haveWaterSupply) - 1;
+
+    // Água filtrada
+    req.queryIndex.allFilteredWater = req.queryIndex.allSecretary;
+
+    let haveFilteredWater = allSecretary.clone();
+    haveFilteredWater.where('escola.agua_filtrada = 1');
+    req.queryIndex.haveFilteredWater = req.querySet.push(haveFilteredWater) - 1;
+
+    // Coleta de esgoto
+    req.queryIndex.allSewage = req.queryIndex.allSecretary;
+
+    let haveSewage = allSecretary.clone();
+    haveSewage.where('escola.esgoto_sanitario = 1');
+    req.queryIndex.haveSewage = req.querySet.push(haveSewage) - 1;
+
+    // Sala de recursos multifuncionais para Atendimento Educacional Especializado
+    req.queryIndex.allMultifunctionRoom = req.queryIndex.allSecretary;
+
+    let haveMultifunctionRoom = allSecretary.clone();
+    haveMultifunctionRoom.where('escola.sala_atendimento_especial = 1');
+    req.queryIndex.haveMultifunctionRoom = req.querySet.push(haveMultifunctionRoom) - 1;
+
+    // Banheiros adaptados para pessoas com deficiências
+    req.queryIndex.allSpecialBathroom = req.queryIndex.allSecretary;
+
+    let haveSpecialBathroom = allSecretary.clone();
+    haveSpecialBathroom.where('escola.sanitario_pne = 1');
+    req.queryIndex.haveSpecialBathroom = req.querySet.push(haveSpecialBathroom) - 1;
+
+    // Dependências adaptada para pessoas com deficiências
+    req.queryIndex.allAdaptedBuilding = req.queryIndex.allSecretary;
+
+    let haveAdaptedBuilding = allSecretary.clone();
+    haveAdaptedBuilding.where('escola.dependencias_pne = 1');
+    req.queryIndex.haveAdaptedBuilding = req.querySet.push(haveAdaptedBuilding) - 1;
+
+    next();
+}, multiQuery, (req, res, next) => {
+    // Faz o matching entre os resultados
+    let school_place = matchQueries(req.result[req.queryIndex.allSchools], req.result[req.queryIndex.schoolPlace]);
+    let libraries = matchQueries(req.result[req.queryIndex.allLibraries], req.result[req.queryIndex.haveLibraries]);
+    let libraries_reading_room = matchQueries(req.result[req.queryIndex.allLibrariesReadingRoom], req.result[req.queryIndex.haveLibrariesReadingRoom]);
+    let computer_lab = matchQueries(req.result[req.queryIndex.allInfLab], req.result[req.queryIndex.haveInfLab]);
+    let science_lab = matchQueries(req.result[req.queryIndex.allScienceLab], req.result[req.queryIndex.haveScienceLab]);
+    let kids_park = matchQueries(req.result[req.queryIndex.allKidsPark], req.result[req.queryIndex.haveKidsPark]);
+    let nursery = matchQueries(req.result[req.queryIndex.allCribs], req.result[req.queryIndex.haveCribs]);
+    let sports_court = matchQueries(req.result[req.queryIndex.allSportsCourt], req.result[req.queryIndex.haveSportsCourt]);
+    let covered_sports_court = matchQueries(req.result[req.queryIndex.allCoveredSportsCourt], req.result[req.queryIndex.haveCoveredSportsCourt]);
+    let uncovered_sports_court = matchQueries(req.result[req.queryIndex.allUncoveredSportsCourt], req.result[req.queryIndex.haveUncoveredSportsCourt]);
+    let director_room = matchQueries(req.result[req.queryIndex.allDirectorRoom], req.result[req.queryIndex.haveDirectorRoom]);
+    let secretary = matchQueries(req.result[req.queryIndex.allSecretary], req.result[req.queryIndex.haveSecretary]);
+    let teacher_room = matchQueries(req.result[req.queryIndex.allTeacherRoom], req.result[req.queryIndex.haveTeacherRoom]);
+    let kitchen = matchQueries(req.result[req.queryIndex.allKitchen], req.result[req.queryIndex.haveKitchen]);
+    let storeroom = matchQueries(req.result[req.queryIndex.allStoreroom], req.result[req.queryIndex.haveStoreroom]);
+    let warehouse = matchQueries(req.result[req.queryIndex.allWarehouse], req.result[req.queryIndex.haveWarehouse]);
+    let internet = matchQueries(req.result[req.queryIndex.allInternet], req.result[req.queryIndex.haveInternet]);
+    let broadband_internet = matchQueries(req.result[req.queryIndex.allBroadbandInternet], req.result[req.queryIndex.haveBroadbandInternet]);
+    let inside_bathroom = matchQueries(req.result[req.queryIndex.allInsideBathroom], req.result[req.queryIndex.haveInsideBathroom]);
+    let inside_kids_bathroom = matchQueries(req.result[req.queryIndex.allInsideKidsBathroom], req.result[req.queryIndex.haveInsideKidsBathroom]);
+    let eletric_energy = matchQueries(req.result[req.queryIndex.allEletricEnergy], req.result[req.queryIndex.haveEletricEnergy]);
+    let water_supply = matchQueries(req.result[req.queryIndex.allWaterSupply], req.result[req.queryIndex.haveWaterSupply]);
+    let filtered_water = matchQueries(req.result[req.queryIndex.allFilteredWater], req.result[req.queryIndex.haveFilteredWater]);
+    let sewage_treatment = matchQueries(req.result[req.queryIndex.allSewage], req.result[req.queryIndex.haveSewage]);
+    let special_multifunction_room = matchQueries(req.result[req.queryIndex.allMultifunctionRoom], req.result[req.queryIndex.haveMultifunctionRoom]);
+    let special_bathroom = matchQueries(req.result[req.queryIndex.allSpecialBathroom], req.result[req.queryIndex.haveSpecialBathroom]);
+    let adapted_building = matchQueries(req.result[req.queryIndex.allAdaptedBuilding], req.result[req.queryIndex.haveAdaptedBuilding]);
+
+    req.result = [{
+        school_place,
+        libraries,
+        libraries_reading_room,
+        computer_lab,
+        science_lab,
+        kids_park,
+        nursery,
+        sports_court,
+        covered_sports_court,
+        uncovered_sports_court,
+        director_room,
+        secretary,
+        teacher_room,
+        kitchen,
+        storeroom,
+        warehouse,
+        internet,
+        broadband_internet,
+        inside_bathroom,
+        inside_kids_bathroom,
+        eletric_energy,
+        water_supply,
+        filtered_water,
+        sewage_treatment,
+        special_multifunction_room,
+        special_bathroom,
+        adapted_building
+    }];
+
+    next();
+}, id2str.multitransform(false), response('infrastructure'));
+
+module.exports = infrastructureApp;
diff --git a/src/libs/routes/liquidEnrollmentRatio.js b/src/libs/routes/liquidEnrollmentRatio.js
new file mode 100644
index 0000000000000000000000000000000000000000..1632e06f465152bf235466d3b38893265f7c3eb3
--- /dev/null
+++ b/src/libs/routes/liquidEnrollmentRatio.js
@@ -0,0 +1,392 @@
+const express = require('express');
+
+const liquidEnrollmentRatioApp = express.Router();
+
+const libs = `${process.cwd()}/libs`;
+
+const log = require(`${libs}/log`)(module);
+
+const squel = require('squel');
+
+const query = require(`${libs}/middlewares/query`).query;
+
+const multiQuery = require(`${libs}/middlewares/multiQuery`);
+
+const response = require(`${libs}/middlewares/response`);
+
+const ReqQueryFields = require(`${libs}/middlewares/reqQueryFields`);
+
+const id2str = require(`${libs}/middlewares/id2str`);
+
+const addMissing = require(`${libs}/middlewares/addMissing`);
+
+const config = require(`${libs}/config`);
+
+const download = require(`${libs}/middlewares/downloadDatabase`);
+
+const passport = require('passport');
+
+const cache = require('apicache').options({ debug: config.debug, statusCodes: {include: [200]} }).middleware;
+
+let rqf = new ReqQueryFields();
+
+liquidEnrollmentRatioApp.use(cache('15 day'));
+
+// Complete range of the enrollments dataset.
+// Returns a tuple of start and ending years of the complete enrollments dataset.
+liquidEnrollmentRatioApp.get('/year_range', (req, res, next) => {
+    req.sql.from('pnad')
+    .field('DISTINCT pnad.ano_censo', 'year');
+    next();
+}, query, (req, res, next) => {
+    req.oldResult = req.result;
+
+    req.sql = squel.select();
+
+    req.sql.from('matricula')
+    .field('DISTINCT matricula.ano_censo', 'year');
+    next();
+}, query, (req, res, next) => {
+    let distinct_years = [];
+    let new_result = [];
+    for (let i = 0; i < req.oldResult.length; i++) {
+        for (let j = 0; j < req.result.length; j++) {
+            if(req.oldResult[i].year == req.result[j].year) {
+                distinct_years.push(req.oldResult[i]);
+            }
+        }
+    }
+    new_result.push({start_year: distinct_years[distinct_years.length -1].year, end_year: distinct_years[0].year});
+    req.result = new_result;
+    next();
+}, response('range'));
+
+liquidEnrollmentRatioApp.get('/years', (req, res, next) => {
+    req.sql.from('pnad')
+    .field('DISTINCT pnad.ano_censo', 'year');
+    next();
+}, query, (req, res, next) => {
+    req.oldResult = req.result;
+
+    req.sql = squel.select();
+
+    req.sql.from('matricula')
+    .field('DISTINCT matricula.ano_censo', 'year');
+    next();
+}, query, (req, res, next) => {
+    let new_result = []
+    for (let i = 0; i < req.oldResult.length; i++) {
+        for (let j = 0; j < req.result.length; j++) {
+            if(req.oldResult[i].year == req.result[j].year) {
+                new_result.push(req.oldResult[i]);
+            }
+        }
+    }
+    req.result = new_result;
+    next();
+}, response('years'));
+
+liquidEnrollmentRatioApp.get('/source', (req, res, next) => {
+    req.sql.from('fonte')
+    .field('fonte', 'source')
+    .where('tabela = \'pnad\'');
+    next();
+}, query, response('source'));
+
+liquidEnrollmentRatioApp.get('/education_level_basic', (req, res, next) => {
+    req.result = [
+        {id: null, name: 'Não classificada'},
+        {id: 1, name: 'Creche'},
+        {id: 2, name: 'Pré-Escola'},
+        {id: 4, name: 'Ensino Fundamental - anos iniciais'},
+        {id: 5, name: 'Ensino Fundamental - anos finais'},
+        {id: 6, name: 'Ensino Médio'}
+    ];
+    next();
+}, response('education_level_basic'));
+
+liquidEnrollmentRatioApp.get('/gender', (req, res, next) => {
+    req.result = [
+        {id: 1, name: 'Masculino'},
+        {id: 2, name: 'Feminino'}
+    ];
+    next();
+}, response('gender'));
+
+liquidEnrollmentRatioApp.get('/ethnic_group', (req, res, next) => {
+    req.result = [
+        {id: 0, name: 'Sem declaração'},
+        {id: 1, name: 'Branca'},
+        {id: 2, name: 'Preta'},
+        {id: 3, name: 'Parda'},
+        {id: 4, name: 'Amarela'},
+        {id: 5, name: 'Indígena'}
+    ];
+    next();
+}, response('ethnic_group'));
+
+liquidEnrollmentRatioApp.get('/location', (req, res, next) => {
+    req.result = [
+        {id: 1, name: 'Urbana'},
+        {id: 2, name: 'Rural'}
+    ];
+    next();
+}, response('location'));
+
+rqf.addField({
+    name: 'filter',
+    field: false,
+    where: true
+}).addField({
+    name: 'dims',
+    field: true,
+    where: false
+}).addValue({
+    name: 'region',
+    table: 'regiao',
+    tableField: 'nome',
+    resultField: 'region_name',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'id'
+    },
+    join: {
+        primary: 'id',
+        foreign: 'regiao_id',
+        foreignTable: '@'
+    }
+}).addValue({
+    name: 'state',
+    table: 'estado',
+    tableField: 'nome',
+    resultField: 'state_name',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'id'
+    },
+    join: {
+        primary: 'id',
+        foreign: 'estado_id',
+        foreignTable: '@'
+    }
+}).addValue({
+    name: 'ethnic_group',
+    table: '@',
+    tableField: 'cor_raca_id',
+    resultField: 'ethnic_group_id',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'cor_raca_id'
+    }
+}).addValue({
+    name: 'min_year',
+    table: '@',
+    tableField: 'ano_censo',
+    resultField: 'year',
+    where: {
+        relation: '>=',
+        type: 'integer',
+        table: '@',
+        field: 'ano_censo'
+    }
+}).addValue({
+    name: 'max_year',
+    table: '@',
+    tableField: 'ano_censo',
+    resultField: 'year',
+    where: {
+        relation: '<=',
+        type: 'integer',
+        table: '@',
+        field: 'ano_censo'
+    }
+}).addValue({
+    name: 'gender',
+    table: '@',
+    tableField: 'sexo',
+    resultField: 'gender_id',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'sexo'
+    }
+}).addValue({
+    name: 'location',
+    table: '@',
+    tableField: 'localizacao_id',
+    resultField: 'location_id',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'localizacao_id'
+    }
+}).addValue({
+    name: 'education_level_basic',
+    table: 'matricula',
+    tableField: 'etapas_mod_ensino_segmento_id',
+    resultField: 'education_level_basic_id',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'etapas_mod_ensino_segmento_id'
+    }
+});
+
+function matchQueries(queryTotal, queryPartial) {
+    let match = [];
+    queryPartial.forEach((result) => {
+        let newObj = {};
+        let keys = Object.keys(result);
+        keys.forEach((key) => {
+            newObj[key] = result[key];
+        });
+        // console.log('NEW OBJ');
+        // console.log(newObj);
+        // remove total
+        let index = keys.indexOf('total');
+        if(index > -1) keys.splice(index, 1);
+        // remove education_level_basic_id
+        index = keys.indexOf('education_level_basic_id');
+        if(index > -1) keys.splice(index, 1);
+        // remove education_level_basic_name
+        index = keys.indexOf('education_level_basic_name');
+        if(index > -1) keys.splice(index, 1);
+        let objMatch = null;
+
+        for(let i = 0; i < queryTotal.length; ++i) {
+            let total = queryTotal[i];
+            let foundMatch = true;
+            for(let j = 0; j < keys.length; ++j) {
+                let key = keys[j];
+                if(total[key] !== result[key]) {
+                    foundMatch = false;
+                    break;
+                }
+            }
+            if(foundMatch) {
+                objMatch = total;
+                break;
+            }
+        }
+
+        if(objMatch) {
+            // console.log('MATCH!!!!');
+            // console.log(objMatch);
+            newObj.total = (result.total / objMatch.total) * 100;
+            newObj.partial = result.total;
+            newObj.denominator = objMatch.total
+            match.push(newObj);
+        }
+    });
+    // console.log('TAMANHOS');
+    // console.log(queryTotal.length);
+    // console.log(queryPartial.length);
+    // console.log(match.length);
+    return match;
+}
+
+
+liquidEnrollmentRatioApp.get('/', rqf.parse(),(req, res, next) => {
+    req.numerator = {};
+    req.denominator = {};
+    let liquidEnrollmentRatioApp = {};
+
+    req.sql.from('matricula')
+    .field('count(*)', 'total')
+    .field('matricula.ano_censo', 'year')
+    .group('matricula.ano_censo')
+    .order('matricula.ano_censo')
+
+    function ConvertMatricula(result) {
+        if (result == 1) {
+            return 'matricula.faixa_etaria_31_03 = 1'
+        } else if (result == 2) {
+            return 'matricula.faixa_etaria_31_03 = 2'
+        } else if (result == 4) {
+            return 'matricula.faixa_etaria_31_03 = 3'
+        } else if (result == 5) {
+            return 'matricula.faixa_etaria_31_03 = 4'
+        } else if (result == 6) {
+            return 'matricula.faixa_etaria_31_03 = 5'
+        }
+    }
+    if ("education_level_basic" in req.filter) {
+        if (Array.isArray(req.filter.education_level_basic)) {
+            var string_query_enrollment = '';
+            for(let i = 0; i < req.filter.education_level_basic.length - 1; i++)  {
+                string_query_enrollment = string_query_enrollment + ConvertMatricula(req.filter.education_level_basic[i]) + ' OR ';
+            }
+            string_query_enrollment = string_query_enrollment + ConvertMatricula(req.filter.education_level_basic[req.filter.education_level_basic.length - 1]);
+            req.sql.where(string_query_enrollment);
+        } else {
+            req.sql.where(ConvertMatricula(req.filter.education_level_basic));
+        }
+    } else {
+        res.status(400);
+        next({
+            status: 400,
+            message: 'Wrong/No filter specified'
+        });
+    }
+
+    next();
+}, rqf.build(), query, id2str.transform(),  (req, res, next) => {
+    req.numerator = req.result;
+    req.resetSql();
+    req.sql.field('sum(peso)', 'total')
+    .field('pnad.ano_censo','year')
+    .from('pnad')
+    .group('pnad.ano_censo')
+    .order('pnad.ano_censo')
+
+    function convertPnad(result) {
+        if (result == 1) {
+            return 'pnad.faixa_etaria_31_03 = 1'
+        } else if (result == 2) {
+            return 'pnad.faixa_etaria_31_03 = 2'
+        } else if (result == 4) {
+            return 'pnad.faixa_etaria_31_03 = 3'
+        } else if (result == 5) {
+            return 'pnad.faixa_etaria_31_03 = 4'
+        } else if (result == 6) {
+            return 'pnad.faixa_etaria_31_03 = 5'
+        }
+    }
+
+    //remove education_level_basic how filter and add faixa_etaria_31_03 in filter
+    if ("education_level_basic" in req.filter) {
+        if (Array.isArray(req.filter.education_level_basic)) {
+            var string_query = '';
+            for(let i = 0; i < req.filter.education_level_basic.length - 1; i++)  {
+                string_query = string_query + convertPnad(req.filter.education_level_basic[i]) + ' OR ';
+            }
+            string_query = string_query + convertPnad(req.filter.education_level_basic[req.filter.education_level_basic.length - 1]);
+            req.sql.where(string_query);
+        } else {
+            req.sql.where(convertPnad(req.filter.education_level_basic));
+        }
+    }
+
+    next();
+}, rqf.parse(), (req, res, next) => {
+    if ("education_level_basic" in req.filter) {
+        delete req.filter.education_level_basic;
+    }
+    if ("education_level_basic" in req.dims) {
+        delete req.dims.education_level_basic;
+    }
+    next();
+}, rqf.build(), query, id2str.transform(), (req, res, next) => {
+    req.denominator = req.result;
+
+    //division to generate req.result final
+    req.result = []
+    let liquidEnrollment = matchQueries(req.denominator, req.numerator);
+    req.result = liquidEnrollment;
+    next();
+}, response('liquidEnrollmentRatio'));
+
+module.exports = liquidEnrollmentRatioApp;
diff --git a/src/libs/routes/pibpercapita.js b/src/libs/routes/pibpercapita.js
new file mode 100644
index 0000000000000000000000000000000000000000..10e03b0fa17bb497fe55c1552696411aa1d78302
--- /dev/null
+++ b/src/libs/routes/pibpercapita.js
@@ -0,0 +1,183 @@
+const express = require('express');
+
+const pibpercapitaApp = express.Router();
+
+const libs = `${process.cwd()}/libs`;
+
+const log = require(`${libs}/log`)(module);
+
+const squel = require('squel');
+
+const query = require(`${libs}/middlewares/query`).query;
+
+const response = require(`${libs}/middlewares/response`);
+
+const ReqQueryFields = require(`${libs}/middlewares/reqQueryFields`);
+
+const id2str = require(`${libs}/middlewares/id2str`);
+
+const addMissing = require(`${libs}/middlewares/addMissing`);
+
+const config = require(`${libs}/config`);
+
+const cache = require('apicache').options({ debug: config.debug, statusCodes: {include: [200]} }).middleware;
+
+let rqf = new ReqQueryFields();
+
+pibpercapitaApp.use(cache('15 day'));
+
+pibpercapitaApp.get('/year_range', (req, res, next) => {
+    req.sql.from('ibge_pib')
+    .field('MIN(ibge_pib.ano_censo)', 'start_year')
+    .field('MAX(ibge_pib.ano_censo)', 'end_year')
+    .where('ibge_pib.ano_censo > 2013');
+    next();
+}, query, response('range'));
+
+pibpercapitaApp.get('/years', (req, res, next) => {
+    req.sql.from('ibge_pib').
+    field('DISTINCT ibge_pib.ano_censo', 'year')
+    .where('ibge_pib.ano_censo > 2013');
+    next();
+}, query, response('years'));
+
+pibpercapitaApp.get('/source', (req, res, next) => {
+    req.sql.from('fonte')
+    .field('fonte', 'source')
+    .where('tabela = \'ibge_pib\'');
+    next();
+}, query, response('source'));
+
+pibpercapitaApp.get('/income_level', (req, res, next) => {
+    req.result = [
+        {id: 1, name: "1º quintil – 20% menores"},
+        {id: 2, name: "2º quintil"},
+        {id: 3, name: "3º quintil"},
+        {id: 4, name: "4º quintil"},
+        {id: 5, name: "5º quintil – 20% maiores"},
+    ];
+    next();
+}, response('income_level'));
+
+rqf.addField({
+    name: 'filter',
+    field: false,
+    where: true
+}).addField({
+    name: 'dims',
+    field: true,
+    where: false
+}).addValue({
+    name: 'city',
+    table: 'municipio',
+    tableField: ['nome', 'id'],
+    resultField: ['city_name', 'city_id'],
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'municipio_id',
+        table: 'ibge_pib'
+    },
+    join: {
+        primary: 'id',
+        foreign: 'municipio_id',
+        foreignTable: 'ibge_pib'
+    }
+}).addValue({
+    name: 'state',
+    table: 'estado',
+    tableField: 'nome',
+    resultField: 'state_name',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'estado_id',
+        table: 'ibge_pib'
+    },
+    join: {
+        primary: 'id',
+        foreign: 'estado_id',
+        foreignTable: 'ibge_pib'
+    }
+}).addValue({
+    name: 'region',
+    table: 'regiao',
+    tableField: 'nome',
+    resultField: 'region_name',
+    where: {
+      relation: '=',
+      type: 'integer',
+      field: 'regiao_id',
+      table: 'ibge_pib'
+    },
+    join: {
+        primary: 'id',
+        foreign: 'regiao_id',
+        foreignTable: 'ibge_pib'
+    }
+}).addValue({
+    name: 'min_year',
+    table: 'ibge_pib',
+    tableField: 'ano_censo',
+    resultField: 'year',
+    where: {
+        relation: '>=',
+        type: 'integer',
+        field: 'ano_censo'
+    }
+}).addValue({
+    name: 'max_year',
+    table: 'ibge_pib',
+    tableField: 'ano_censo',
+    resultField: 'year',
+    where: {
+        relation: '<=',
+        type: 'integer',
+        field: 'ano_censo'
+    }
+}).addValue({
+    name: 'income_level',
+    table: 'ibge_pib',
+    tableField: 'nivel_renda_per_capita',
+    resultField: 'income_level_id',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'nivel_renda_per_capita'
+    }
+});
+
+pibpercapitaApp.get('/', rqf.parse(), rqf.build(), (req, res, next) => {
+  if (("city" in req.dims) || ("city" in req.filter)) {
+      req.sql.from('ibge_pib')
+      .field('ibge_pib.pib_per_capita', 'total')
+      .field('ibge_pib.ano_censo', 'year')
+      .group('ibge_pib.ano_censo')
+      .group('ibge_pib.pib_per_capita')
+      .order('ibge_pib.ano_censo')
+  } else  {
+      req.sql.from('ibge_pib')
+      .field('SUM(ibge_pib.pib)/SUM(ibge_pib.populacao)', 'total')
+      .field('ibge_pib.ano_censo', 'year')
+      .group('ibge_pib.ano_censo')
+      .order('ibge_pib.ano_censo')
+   }
+   next();
+}, query, addMissing(rqf), id2str.transform(false), (req, res, next) => {
+     req.result.forEach((i) => {
+         let value = i.total;
+         let res = value.toString().split(".");
+         //rounding decimal.
+         let decimal = Math.round(res[1].toString().substring(0,2) + (".") + res[1].toString().substring(2,3));
+         //case 0 after comma
+         if (res[1].toString().substring(0,1) == 0) {
+              i.total = parseFloat(res[0] + "." + "0" + decimal);
+         } else {
+         i.total = parseFloat(res[0] + "." +  decimal);
+       }
+     });
+     next();
+ }, response("pibpercapita"));
+
+
+module.exports = pibpercapitaApp;
diff --git a/src/libs/routes/population.js b/src/libs/routes/population.js
new file mode 100644
index 0000000000000000000000000000000000000000..cd307cdbcbb70b797c76d92b3550a0e3d9306604
--- /dev/null
+++ b/src/libs/routes/population.js
@@ -0,0 +1,153 @@
+const express = require('express');
+
+const populationApp = express.Router();
+
+const libs = `${process.cwd()}/libs`;
+
+const log = require(`${libs}/log`)(module);
+
+const squel = require('squel');
+
+const query = require(`${libs}/middlewares/query`).query;
+
+const response = require(`${libs}/middlewares/response`);
+
+const ReqQueryFields = require(`${libs}/middlewares/reqQueryFields`);
+
+const id2str = require(`${libs}/middlewares/id2str`);
+
+const addMissing = require(`${libs}/middlewares/addMissing`);
+
+const config = require(`${libs}/config`);
+
+const cache = require('apicache').options({ debug: config.debug, statusCodes: {include: [200]} }).middleware;
+
+let rqf = new ReqQueryFields();
+
+populationApp.use(cache('15 day'));
+
+populationApp.get('/year_range', (req, res, next) => {
+    req.sql.from('ibge_populacao')
+    .field('MIN(ibge_populacao.ano_censo)', 'start_year')
+    .field('MAX(ibge_populacao.ano_censo)', 'end_year');
+    next();
+}, query, response('range'));
+
+populationApp.get('/years', (req, res, next) => {
+    req.sql.from('ibge_populacao').
+    field('DISTINCT ibge_populacao.ano_censo', 'year');
+    next();
+}, query, response('years'));
+
+populationApp.get('/city_size', (req, res, next) => {
+    req.result = [
+        {id: 1, name: "até 5000"},
+        {id: 2, name: "5001 - 10000"},
+        {id: 3, name: "10001 - 20000"},
+        {id: 4, name: "20001 - 50000"},
+        {id: 5, name: "50001 - 100000"},
+        {id: 6, name: "100001 - 500000"},
+        {id: 7, name: "mais que 500000"}
+    ];
+    next();
+}, response('city_size'));
+
+rqf.addField({
+    name: 'filter',
+    field: false,
+    where: true
+}).addField({
+    name: 'dims',
+    field: true,
+    where: false
+}).addValue({
+    name: 'city',
+    table: 'municipio',
+    tableField: ['nome', 'id'],
+    resultField: ['city_name', 'city_id'],
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'municipio_id',
+        table: 'ibge_populacao'
+    },
+    join: {
+        primary: 'id',
+        foreign: 'municipio_id',
+        foreignTable: 'ibge_populacao'
+    }
+}).addValue({
+    name: 'state',
+    table: 'estado',
+    tableField: 'nome',
+    resultField: 'state_name',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'estado_id',
+        table: 'ibge_populacao'
+    },
+    join: {
+        primary: 'id',
+        foreign: 'estado_id',
+        foreignTable: 'ibge_populacao'
+    }
+}).addValue({
+    name: 'region',
+    table: 'regiao',
+    tableField: 'nome',
+    resultField: 'region_name',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'regiao_id',
+        table: 'ibge_populacao'
+    },
+    join: {
+        primary: 'id',
+        foreign: 'regiao_id',
+        foreignTable: 'ibge_populacao'
+    }
+}).addValue({
+    name: 'min_year',
+    table: 'ibge_populacao',
+    tableField: 'ano_censo',
+    resultField: 'year',
+    where: {
+        relation: '>=',
+        type: 'integer',
+        field: 'ano_censo'
+    }
+}).addValue({
+    name: 'max_year',
+    table: 'ibge_populacao',
+    tableField: 'ano_censo',
+    resultField: 'year',
+    where: {
+        relation: '<=',
+        type: 'integer',
+        field: 'ano_censo'
+    }
+}).addValue({
+    name: 'city_size',
+    table: 'ibge_populacao',
+    tableField: 'porte',
+    resultField: 'city_size_id',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'porte'
+    }
+});
+
+populationApp.get('/', rqf.parse(), rqf.build(), (req, res, next) => {
+  req.sql.from('ibge_populacao')
+    .field('SUM(ibge_populacao.populacao)', 'total')
+    .field('ibge_populacao.ano_censo', 'year')
+    .group('ibge_populacao.ano_censo')
+    .order('ibge_populacao.ano_censo')
+
+   next();
+}, query, addMissing(rqf), id2str.transform(false), response('population'));
+
+module.exports = populationApp;
diff --git a/src/libs/routes/rateSchool.js b/src/libs/routes/rateSchool.js
new file mode 100644
index 0000000000000000000000000000000000000000..856013220997193c288c99ebd83dd33ffe8d03a8
--- /dev/null
+++ b/src/libs/routes/rateSchool.js
@@ -0,0 +1,323 @@
+const express = require('express');
+
+const rateSchoolApp = express.Router();
+
+const libs = `${process.cwd()}/libs`;
+
+const log = require(`${libs}/log`)(module);
+
+const squel = require('squel');
+
+const query = require(`${libs}/middlewares/query`).query;
+
+const multiQuery = require(`${libs}/middlewares/multiQuery`);
+
+const response = require(`${libs}/middlewares/response`);
+
+const ReqQueryFields = require(`${libs}/middlewares/reqQueryFields`);
+
+const id2str = require(`${libs}/middlewares/id2str`);
+
+const config = require(`${libs}/config`);
+
+const download = require(`${libs}/middlewares/downloadDatabase`);
+
+const passport = require('passport');
+
+const cache = require('apicache').options({ debug: config.debug, statusCodes: {include: [200]} }).middleware;
+
+rateSchoolApp.use(cache('15 day'));
+
+let rqf = new ReqQueryFields();
+
+// Complete range of the enrollments dataset.
+// Returns a tuple of start and ending years of the complete enrollments dataset.
+rateSchoolApp.get('/year_range', (req, res, next) => {
+    req.sql.from('pnad')
+    .field('MIN(pnad.ano_censo)', 'start_year')
+    .field('MAX(pnad.ano_censo)', 'end_year');
+    next();
+}, query, response('range'));
+
+rateSchoolApp.get('/years', (req, res, next) => {
+    req.sql.from('pnad')
+    .field('DISTINCT pnad.ano_censo', 'year');
+    next();
+}, query, response('years'));
+
+rateSchoolApp.get('/source', (req, res, next) => {
+    req.sql.from('fonte')
+    .field('fonte', 'source')
+    .where('tabela = \'pnad\'');
+    next();
+}, query, response('source'));
+
+rateSchoolApp.get('/ethnic_group_pnad', (req, res, next) => {
+    req.result = [
+        {id: 0, name: 'Indígena'},
+        {id: 1, name: 'Branca e amarela'},
+        {id: 2, name: 'Preta e parda'},
+        {id: 9, name: 'Sem declaração'}
+    ];
+    next();
+}, response('ethnic_group_pnad'));
+
+rateSchoolApp.get('/age_range', (req, res, next) => {
+    req.result = [
+        {id: 1, name: '0-3'},
+        {id: 2, name: '4-5'},
+        {id: 3, name: '6-10'},
+        {id: 4, name: '11-14'},
+        {id: 5, name: '15-17'},
+        {id: 6, name: '18-24'}
+    ];
+    next();
+}, response('age_range'));
+
+rateSchoolApp.get('/gender', (req, res, next) => {
+    req.result = [
+        {id: 1, name: 'Masculino'},
+        {id: 2, name: 'Feminino'}
+    ];
+    next();
+}, response('gender'));
+
+rateSchoolApp.get('/location', (req, res, next) => {
+    req.result = [
+		{id: 1, name: 'Urbana'},
+		{id: 2, name: 'Rural'}
+	];
+    next();
+}, response('location'));
+
+rateSchoolApp.get('/fifth_household_income', (req, res, next) => {
+    req.result = [
+		{id: 1, name: '20% menores'},
+        {id: 2, name: '2o quinto'},
+        {id: 3, name: '3o quinto'},
+        {id: 4, name: '4o quinto'},
+		{id: 5, name: '20% maiores'},
+        {id: -1, name: 'Sem declaração'}
+	];
+    next();
+},response('fifth_household_income'));
+
+rateSchoolApp.get('/extremes_household_income', (req, res, next) => {
+    req.result = [
+		{id: 1, name: '10% menores'},
+        {id: 2, name: '10% maiores'},
+        {id: -1, name: 'Sem declaração'}
+	];
+    next();
+}, response('extremes_household_income'));
+
+rqf.addField({
+    name: 'filter',
+    field: false,
+    where: true
+}).addField({
+    name: 'dims',
+    field: true,
+    where: false
+}).addValue({
+    name: 'region',
+    table: 'regiao',
+    tableField: 'nome',
+    resultField: 'region_name',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'id'
+    },
+    join: {
+        primary: 'id',
+        foreign: 'regiao_id',
+        foreignTable: 'pnad'
+    }
+}).addValue({
+    name: 'state',
+    table: 'estado',
+    tableField: 'nome',
+    resultField: 'state_name',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'id'
+    },
+    join: {
+        primary: 'id',
+        foreign: 'estado_id',
+        foreignTable: 'pnad'
+    }
+}).addValue({
+    name: 'ethnic_group_pnad',
+    table: 'pnad',
+    tableField: 'cor_raca',
+    resultField: 'ethnic_group_pnad_id',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'cor_raca'
+    }
+}).addValue({
+    name: 'min_year',
+    table: 'pnad',
+    tableField: 'ano_censo',
+    resultField: 'year',
+    where: {
+        relation: '>=',
+        type: 'integer',
+        table: 'pnad',
+        field: 'ano_censo'
+    }
+}).addValue({
+    name: 'max_year',
+    table: 'pnad',
+    tableField: 'ano_censo',
+    resultField: 'year',
+    where: {
+        relation: '<=',
+        type: 'integer',
+        table: 'pnad',
+        field: 'ano_censo'
+    }
+}).addValue({
+    name: 'age_range',
+    table: 'pnad',
+    tableField: 'faixa_etaria_31_03',
+    resultField: 'age_range_id',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'faixa_etaria_31_03'
+    }
+}).addValue({
+    name: 'gender',
+    table: 'pnad',
+    tableField: 'sexo',
+    resultField: 'gender_id',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'sexo'
+    }
+}).addValue({
+    name: 'location',
+    table: 'pnad',
+    tableField: 'localizacao_id',
+    resultField: 'location_id',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'localizacao_id'
+    }
+}).addValue({
+    name: 'extremes_household_income',
+    table: 'pnad',
+    tableField: 'extremos_nivel_rendimento',
+    resultField: 'extremes_household_income_id',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'extremos_nivel_rendimento'
+    }
+}).addValue({
+    name: 'fifth_household_income',
+    table: 'pnad',
+    tableField: 'quintil_nivel_rendimento',
+    resultField: 'fifth_household_income_id',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'quintil_nivel_rendimento'
+    }
+});
+
+function matchQueries(queryTotal, queryPartial) {
+    let match = [];
+    queryTotal.forEach((result) => {
+        let newObj = {};
+        let keys = Object.keys(result);
+        keys.forEach((key) => {
+            newObj[key] = result[key];
+        });
+        // console.log('NEW OBJ');
+        // console.log(newObj);
+        let index = keys.indexOf('total');
+        if(index > -1) keys.splice(index, 1);
+        let objMatch = null;
+
+        for(let i = 0; i < queryPartial.length; ++i) {
+            let partial = queryPartial[i];
+            let foundMatch = true;
+            for(let j = 0; j < keys.length; ++j) {
+                let key = keys[j];
+                if(partial[key] !== result[key]) {
+                    foundMatch = false;
+                    break;
+                }
+            }
+            if(foundMatch) {
+                objMatch = partial;
+                break;
+            }
+        }
+
+        if(objMatch) {
+            // console.log(objMatch);
+            newObj.denominator = result.total;
+            newObj.partial = objMatch.total;
+            newObj.total = (objMatch.total / result.total) * 100;
+            match.push(newObj);
+        }
+    });
+
+    return match;
+}
+
+rateSchoolApp.get('/', rqf.parse(), rqf.build(), (req, res, next) => {
+    req.querySet = [];
+    req.queryIndex = {};
+
+    log.debug(req.sql.toParam());
+    if ("age_range" in req.filter || "age_range" in req.dims) {
+        let freq_total = req.sql.clone();
+        freq_total.field('sum(pnad.peso)', 'total')
+        .field('pnad.ano_censo','year')
+        .from('pnad')
+        .group('pnad.ano_censo')
+        .order('pnad.ano_censo')
+        .where('pnad.faixa_etaria_31_03 < 7')
+        req.queryIndex.freq_total = req.querySet.push(freq_total) - 1;
+
+        let freq_nursery = req.sql.clone();
+        freq_nursery.field('sum(pnad.peso)', 'total')
+        .field('pnad.ano_censo','year')
+        .from('pnad')
+        .group('pnad.ano_censo')
+        .order('pnad.ano_censo')
+        .where('pnad.frequenta_escola_creche = 2')
+        .where('pnad.faixa_etaria_31_03 < 7')
+        req.queryIndex.freq_nursery = req.querySet.push(freq_nursery) - 1;
+    }
+     next();
+}, multiQuery, (req, res, next) => {
+    if ("age_range" in req.filter || "age_range" in req.dims) {
+        log.debug(req.result[req.queryIndex.freq_total]);
+        log.debug(req.result[req.queryIndex.freq_nursery])
+        let school_attendance_rate = matchQueries(req.result[req.queryIndex.freq_total], req.result[req.queryIndex.freq_nursery]);
+        req.result = school_attendance_rate;
+    } else {
+        res.status(400);
+        next({
+            status: 400,
+            message: 'Wrong/No filter specified'
+        });
+    }
+    log.debug(req.result)
+    next();
+}, id2str.transform(false), response('rateSchool'));
+
+rateSchoolApp.get('/download', passport.authenticate('bearer', { session: false }), rqf.parse(), rqf.build(), download('pnad', 'mapping_pnad'));
+
+module.exports = rateSchoolApp;
diff --git a/src/libs/routes/region.js b/src/libs/routes/region.js
index 773ad347959049dd2137d96abb255a3bc809a2d4..b1076e6484836b8a4911b8d0ada3631f1304e3c0 100644
--- a/src/libs/routes/region.js
+++ b/src/libs/routes/region.js
@@ -4,23 +4,59 @@ const regionApp = express.Router();
 
 const libs = `${process.cwd()}/libs`;
 
+const log = require(`${libs}/log`)(module);
+
 const squel = require('squel');
 
-const query = require(`${libs}/middlewares/query`);
+const query = require(`${libs}/middlewares/query`).query;
 
 const response = require(`${libs}/middlewares/response`);
 
-// Get all regions
-regionApp.get('/', (req, res, next) => {
-    req.sql.from('regioes');
-    next();
-}, query, response('region'));
-
-// Get a region by it's id
-regionApp.get('/:id', (req, res, next) => {
-    req.sql.from('regioes')
-        .where('pk_regiao_id = ?', parseInt(req.params.id, 10));
+const ReqQueryFields = require(`${libs}/middlewares/reqQueryFields`);
+
+const config = require(`${libs}/config`); 
+
+const cache = require('apicache').options({ debug: config.debug, statusCodes: {include: [200]} }).middleware;
+
+let rqf = new ReqQueryFields();
+
+regionApp.use(cache('15 day'));
+
+rqf.addField({
+    name: 'filter',
+    field: false,
+    where: true
+}).addValue({
+    name: 'id',
+    table: '@',
+    tableField: 'id',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'id',
+        table: '@'
+    }
+}).addField({
+    name: 'search',
+    field: false,
+    where: true
+}).addValueToField({
+    name: 'name',
+    table: '@',
+    tableField: 'nome',
+    where: {
+        relation: 'LIKE',
+        type: 'string',
+        field: 'nome',
+        table: '@'
+    }
+}, 'search');
+
+regionApp.get('/', rqf.parse(), (req, res, next) => {
+    req.sql.from('regiao')
+        .field('id')
+        .field('nome', 'name');
     next();
-}, query, response('region'));
+}, rqf.build(), query, response('region'));
 
 module.exports = regionApp;
diff --git a/src/libs/routes/resetToken.js b/src/libs/routes/resetToken.js
new file mode 100644
index 0000000000000000000000000000000000000000..34ece8455adf7b77208dc200a95641ed04638609
--- /dev/null
+++ b/src/libs/routes/resetToken.js
@@ -0,0 +1,81 @@
+const express = require('express');
+
+const resetTokenApp = express.Router();
+
+const libs = `${process.cwd()}/libs`;
+
+const log = require(`${libs}/log`)(module);
+
+const ResetToken = require(`${libs}/models/resetToken`);
+
+const User = require(`${libs}/models/user`);
+
+resetTokenApp.get('/:token', (req, res, next) => {
+    let token = req.params.token;
+    ResetToken.findOne({token: token}, (err, rToken) => {
+        if(err) {
+            log.error(err);
+            return next(err);
+        }
+        if(!rToken) {
+            // TODO: generate new reset token
+            res.statusCode = 404;
+            return next({msg: 'Token not found', status:404});
+        }
+        if (rToken.hasExpired()) {
+            res.statusCode = 410;
+            ResetToken.remove({token: token}, (err) => {
+                if(err) {
+                    log.error(err);
+                    next(err);
+                }
+            })
+            return next({msg: 'Token expired', status: 410});
+        }
+        User.findById(rToken.userId, (err, user) => {
+            if(err) {
+                log.error(err);
+                next(err);
+            }
+            let u = user.toObject();
+            delete u.salt;
+            delete u.hashedPassword;
+            res.json({user: u});
+        });
+    });
+});
+resetTokenApp.post('/:token', (req, res, next) => {
+    let token = req.params.token;
+    ResetToken.findOne({token: token}, (err, rToken) => {
+        if(err) {
+            log.error(err);
+            return next(err);
+        }
+        if(!rToken) {
+            res.statusCode = 404;
+            return next({msg: 'Token not found', status:404});
+        }
+        User.findById(rToken.userId, (err, user) => {
+            if(err) {
+                log.error(err);
+                next(err);
+            }
+            user.password = req.body.password;
+            user.save((err) => {
+                if(err) {
+                    log.error(err);
+                    next(err);
+                }
+                ResetToken.remove({token: token}, (err) => {
+                    if(err) {
+                        log.error(err);
+                        next(err);
+                    }
+                })
+                res.json({msg: "Senha alterada com sucesso"});
+            })
+        });
+    });
+})
+
+module.exports = resetTokenApp;
diff --git a/src/libs/routes/school.js b/src/libs/routes/school.js
index 6c92430b425d3b9fde8c682216c8f6a10f07b75f..0b3742fe9edb9ba0f701443235e0d1bb5592d8a5 100644
--- a/src/libs/routes/school.js
+++ b/src/libs/routes/school.js
@@ -4,57 +4,486 @@ const schoolApp = express.Router();
 
 const libs = `${process.cwd()}/libs`;
 
+const log = require(`${libs}/log`)(module);
+
 const squel = require('squel');
 
-const query = require(`${libs}/middlewares/query`);
+const query = require(`${libs}/middlewares/query`).query;
 
 const response = require(`${libs}/middlewares/response`);
 
-/**
- * YOU SHALL NOT PASS
- * Esta rota foi desabilitada pois é mais violenta que clube da luta batendo em laranja mecânica
- * A api fica sobrecarregada
- * Pense na cena do elevador de driver mas o elevador é uma bomba de fusão e demora mais que uma luta do DBz
- */
-// schoolApp.get('/', (req, res, next) => {
-//     req.sql = squel.select().from('escolas')
-//         .field('pk_escola_id')
-//         .field('nome_entidade', 'name')
-//         .field('ano_censo', 'year')
-//         .field('fk_cod_estado')
-//         .field('fk_cod_municipio');
-//     next();
-// }, query, response('school'));
-
-// Get a school by it's id
-schoolApp.get('/:id', (req, res, next) => {
-    req.sql.from('escolas')
-        .where('pk_escola_id = ?', parseInt(req.params.id, 10));
+const id2str = require(`${libs}/middlewares/id2str`);
+
+const ReqQueryFields = require(`${libs}/middlewares/reqQueryFields`);
+
+const request = require(`request`);
+
+const config = require(`${libs}/config`);
+
+const passport = require('passport');
+
+const download = require(`${libs}/middlewares/downloadDatabase`);
+
+const addMissing = require(`${libs}/middlewares/addMissing`);
+
+const cache = require('apicache').options({ debug: config.debug, statusCodes: {include: [200]} }).middleware;
+
+let rqf = new ReqQueryFields();
+let rqfCount = new ReqQueryFields();
+
+// Return location
+schoolApp.get('/year_range', cache('15 day'), (req, res, next) => {
+    req.sql.from('escola')
+    .field('MIN(escola.ano_censo)', 'start_year')
+    .field('MAX(escola.ano_censo)', 'end_year')
+    .where('escola.ano_censo > 2014');
     next();
-}, query, response('school'));
+}, query, response('range'));
 
-// Get all schools from a state
-schoolApp.get('/state/:id', (req, res, next) => {
-    req.sql.from('escolas')
-        .field('pk_escola_id')
-        .field('nome_entidade')
-        .field('ano_censo')
-        .field('fk_cod_estado')
-        .field('fk_cod_municipio')
-        .where('fk_cod_estado = ?', parseInt(req.params.id, 10));
+schoolApp.get('/years', cache('15 day'), (req, res, next) => {
+    req.sql.from('escola').
+    field('DISTINCT escola.ano_censo', 'year')
+    .where('escola.ano_censo > 2014');
     next();
-}, query, response('school'));
+}, query, response('years'));
+
+schoolApp.get('/source', (req, res, next) => {
+    req.sql.from('fonte')
+    .field('fonte', 'source')
+    .where('tabela = \'escola\'');
+    next();
+}, query, response('source'));
+
+schoolApp.get('/location', cache('15 day'), (req, res, next) => {
+    req.result = [
+        {id: 1, name: 'Urbana'},
+        {id: 2, name: 'Rural'}
+    ];
+    next();
+}, response('location'));
+
+schoolApp.get('/adm_dependency', (req, res, next) => {
+    req.result = [];
+    for(let i = 1; i <= 4; ++i) {
+        req.result.push({
+            id: i,
+            name: id2str.admDependency(i)
+        });
+    };
+    next();
+}, response('adm_dependency'));
+
+schoolApp.get('/adm_dependency_detailed', cache('15 day'), (req, res, next) => {
+    req.result = [];
+    for(let i = 1; i <= 6; ++i) {
+        req.result.push({
+            id: i,
+            name: id2str.admDependencyPriv(i)
+        });
+    };
+    next();
+}, response('adm_dependency_detailed'));
+
+schoolApp.get('/government_agreement', cache('15 day'), (req, res, next) => {
+    req.result = [
+        {id: null, name: 'Não Declarado'},
+        {id: 0, name: 'Não'},
+        {id: 1, name: 'Sim'}
+    ];
+    next();
+}, response('government_agreement'));
 
-// Get all schools from a city
-schoolApp.get('/city/:id', (req, res, next) => {
-    req.sql.from('escolas')
-        .field('pk_escola_id')
-        .field('nome_entidade')
-        .field('ano_censo')
-        .field('fk_cod_estado')
-        .field('fk_cod_municipio')
-        .where('fk_cod_municipio = ?', parseInt(req.params.id, 10));
+schoolApp.get('/agreement', cache('15 day'), (req, res, next) => {
+    req.result = [
+        {id: 1, name: 'Municipal'},
+        {id: 2, name: 'Estadual'},
+        {id: 3, name: 'Estadual e Municipal'}
+    ];
+    next();
+}, response('agreement'));
+
+schoolApp.get('/education_day_care_child', (req, res, next) => {
+    req.result = [
+        {id: null, name: 'Não Declarado'},
+        {id: 0, name: 'Não'},
+        {id: 1, name: 'Sim'}
+    ];
+    next();
+}, response('education_day_care_child'));
+
+schoolApp.get('/education_preschool_child', cache('15 day'), (req, res, next) => {
+    req.result = [
+        {id: null, name: 'Não Declarado'},
+        {id: 0, name: 'Não'},
+        {id: 1, name: 'Sim'}
+    ];
+    next();
+}, response('education_preschool_child'));
+
+schoolApp.get('/education_begin_elementary_school', cache('15 day'), (req, res, next) => {
+    req.result = [
+        {id: null, name: 'Não Declarado'},
+        {id: 0, name: 'Não'},
+        {id: 1, name: 'Sim'}
+    ];
+    next();
+}, response('education_begin_elementary_school'));
+
+schoolApp.get('/education_end_elementary_school', cache('15 day'), (req, res, next) => {
+    req.result = [
+        {id: null, name: 'Não Declarado'},
+        {id: 0, name: 'Não'},
+        {id: 1, name: 'Sim'}
+    ];
+    next();
+}, response('education_end_elementary_school'));
+
+schoolApp.get('/education_middle_school', cache('15 day'), (req, res, next) => {
+    req.result = [
+        {id: null, name: 'Não Declarado'},
+        {id: 0, name: 'Não'},
+        {id: 1, name: 'Sim'}
+    ];
+    next();
+}, response('education_middle_school'));
+
+schoolApp.get('/education_professional', cache('15 day'), (req, res, next) => {
+    req.result = [
+        {id: null, name: 'Não Declarado'},
+        {id: 0, name: 'Não'},
+        {id: 1, name: 'Sim'}
+    ];
+    next();
+}, response('education_professional'));
+
+schoolApp.get('/education_eja', cache('15 day'), (req, res, next) => {
+    req.result = [
+        {id: null, name: 'Não Declarado'},
+        {id: 0, name: 'Não'},
+        {id: 1, name: 'Sim'}
+    ];
+    next();
+}, response('education_eja'));
+
+rqf.addField({
+    name: 'filter',
+    field: false,
+    where: true
+}).addValue({
+    name: 'id',
+    table: 'escola',
+    tableField: 'id',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'id'
+    }
+}).addValue({
+    name: 'city',
+    table: 'municipio',
+    tableField: 'nome',
+    resultField: 'city_name',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'municipio_id',
+        table: 'escola'
+    },
+    join: {
+        primary: 'id',
+        foreign: 'municipio_id',
+        foreignTable: 'escola'
+    }
+}).addValue({
+    name: 'state',
+    table: 'estado',
+    tableField: 'nome',
+    resultField: 'state_name',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'estado_id',
+        table: 'escola'
+    },
+    join: {
+        primary: 'id',
+        foreign: 'estado_id',
+        foreignTable: 'escola'
+    }
+}).addValue({
+    name: 'year',
+    table: 'escola',
+    tableField: 'ano_censo',
+    resultField: 'year',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'ano_censo',
+        table: 'escola'
+    }
+});
+
+rqfCount.addField({
+    name: 'filter',
+    field: false,
+    where: true
+}).addField({
+    name: 'dims',
+    field: true,
+    where: false
+}).addValue({
+    name: 'id',
+    table: 'escola',
+    tableField: 'id',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'id'
+    }
+}).addValueToField({
+    name: 'city',
+    table: 'municipio',
+    tableField: ['nome', 'id'],
+    resultField: ['city_name', 'city_id'],
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'municipio_id',
+        table: 'escola'
+    },
+    join: {
+        primary: 'id',
+        foreign: 'municipio_id',
+        foreignTable: 'escola'
+    }
+}, 'dims').addValueToField({
+    name: 'city',
+    table: 'municipio',
+    tableField: 'nome',
+    resultField: 'city_name',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'municipio_id',
+        table: 'escola'
+    },
+    join: {
+        primary: 'id',
+        foreign: 'municipio_id',
+        foreignTable: 'escola'
+    }
+}, 'filter').addValue({
+    name: 'state',
+    table: 'estado',
+    tableField: 'nome',
+    resultField: 'state_name',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'estado_id',
+        table: 'escola'
+    },
+    join: {
+        primary: 'id',
+        foreign: 'estado_id',
+        foreignTable: 'escola'
+    }
+}).addValue({
+    name: 'region',
+    table: 'regiao',
+    tableField: 'nome',
+    resultField: 'region_name',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'id'
+    },
+    join: {
+        primary: 'id',
+        foreign: 'regiao_id',
+        foreignTable: 'escola'
+    }
+}).addValue({
+    name: 'year',
+    table: 'escola',
+    tableField: 'ano_censo',
+    resultField: 'year',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'ano_censo',
+        table: 'escola'
+    }
+}).addValue({
+    name: 'location',
+    table: 'escola',
+    tableField: 'cod_localizacao',
+    resultField: 'location_id',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'cod_localizacao'
+    }
+}).addValue({
+    name: 'adm_dependency',
+    table: 'escola',
+    tableField: 'dependencia_adm_id',
+    resultField: 'adm_dependency_id',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'dependencia_adm_id'
+    }
+}).addValue({
+    name: 'adm_dependency_detailed',
+    table: 'escola',
+    tableField: 'dependencia_adm_priv',
+    resultField: 'adm_dependency_detailed_id',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'dependencia_adm_priv'
+    }
+}).addValue({
+    name: 'government_agreement',
+    table: 'escola',
+    tableField: 'conveniada_pp',
+    resultField: 'government_agreement_id',
+    where: {
+        relation: '=',
+        type: 'boolean',
+        field: 'conveniada_pp'
+    }
+}).addValue({
+    name: 'agreement',
+    table: 'escola',
+    tableField: 'tipo_convenio_pp',
+    resultField: 'agreement_id',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'tipo_convenio_pp'
+    }
+}).addValue({
+    name: 'education_day_care_child',
+    table: 'escola',
+    tableField: 'reg_infantil_creche',
+    resultField: 'education_day_care_child_id',
+    where: {
+        relation: '=',
+        type: 'boolean',
+        field: 'reg_infantil_creche'
+    }
+}).addValue({
+    name: 'education_preschool_child',
+    table: 'escola',
+    tableField: 'reg_infantil_preescola',
+    resultField: 'education_preschool_child_id',
+    where: {
+        relation: '=',
+        type: 'boolean',
+        field: 'reg_infantil_preescola'
+    }
+}).addValue({
+    name: 'education_begin_elementary_school',
+    table: 'escola',
+    tableField: 'reg_fund_ai',
+    resultField: 'education_begin_elementary_school_id',
+    where: {
+        relation: '=',
+        type: 'boolean',
+        field: 'reg_fund_ai'
+    }
+}).addValue({
+    name: 'education_end_elementary_school',
+    table: 'escola',
+    tableField: 'reg_fund_af',
+    resultField: 'education_end_elementary_school_id',
+    where: {
+        relation: '=',
+        type: 'boolean',
+        field: 'reg_fund_af'
+    }
+}).addValue({
+    name: 'education_middle_school',
+    table: 'escola',
+    tableField: 'reg_medio_medio',
+    resultField: 'education_middle_school_id',
+    where: {
+        relation: '=',
+        type: 'boolean',
+        field: 'reg_medio_medio'
+    }
+}).addValue({
+    name: 'education_professional',
+    table: 'escola',
+    tableField: 'educacao_profissional',
+    resultField: 'education_professional_id',
+    where: {
+        relation: '=',
+        type: 'boolean',
+        field: 'educacao_profissional'
+    }
+}).addValue({
+    name: 'education_eja',
+    table: 'escola',
+    tableField: 'ensino_eja',
+    resultField: 'education_eja_id',
+    where: {
+        relation: '=',
+        type: 'boolean',
+        field: 'ensino_eja'
+    }
+}).addValue({
+    name: 'min_year',
+    table: 'escola',
+    tableField: 'ano_censo',
+    resultField: 'year',
+    where: {
+        relation: '>=',
+        type: 'integer',
+        field: 'ano_censo'
+    }
+}).addValue({
+    name: 'max_year',
+    table: 'escola',
+    tableField: 'ano_censo',
+    resultField: 'year',
+    where: {
+        relation: '<=',
+        type: 'integer',
+        field: 'ano_censo'
+    }
+});
+schoolApp.get('/', rqf.parse(), rqf.build(), (req, res, next) => {
+    if(typeof req.filter === 'undefined' || Object.keys(req.filter).length === 0) {
+        res.status(400);
+        next({
+            status: 400,
+            message: 'Wrong/No filter specified'
+        });
+    }
+    req.sql.from('escola')
+        .field('escola.id')
+        .field('escola.ano_censo', 'year')
+        .field('escola.nome_escola', 'name')
+        .field('escola.estado_id', 'state_id')
+        .field('escola.municipio_id', 'city_id');
     next();
 }, query, response('school'));
 
+schoolApp.get('/count', cache('15 day'), rqfCount.parse(), rqfCount.build(), (req, res, next) => {
+
+    req.sql.from('escola')
+        .field('COUNT(escola.id)', 'total')
+        .field("'Brasil'", 'name')
+        .field('escola.ano_censo', 'year')
+        .group('escola.ano_censo')
+        .order('escola.ano_censo')
+        .where('escola.situacao_de_funcionamento = 1 AND (escola.ensino_regular = 1 OR escola.ensino_eja=1 or escola.educacao_profissional=1)');
+    next();
+}, query, addMissing(rqfCount), id2str.transform(), response('school'));
+
+schoolApp.get('/count/download', passport.authenticate('bearer', { session: false }), rqfCount.parse(), rqfCount.build(), download('escola', 'mapping_escola'));
+
 module.exports = schoolApp;
diff --git a/src/libs/routes/simulation.js b/src/libs/routes/simulation.js
new file mode 100644
index 0000000000000000000000000000000000000000..4b2e40c33dd1855116353f618a7c87c13078eb84
--- /dev/null
+++ b/src/libs/routes/simulation.js
@@ -0,0 +1,167 @@
+const express = require('express');
+
+const simulationApp = express();
+
+const libs = `${process.cwd()}/libs`;
+
+const log = require(`${libs}/log`)(module);
+
+const squel = require('squel');
+
+const query = require(`${libs}/middlewares/query`).query;
+
+const response = require(`${libs}/middlewares/response`);
+
+const Simulation = require(`${libs}/models/simulation`);
+
+const PQR = require(`${libs}/models/pqr`);
+
+const passport = require('passport');
+
+simulationApp.get('/time', (req, res, next) => {
+    const maxTime = parseInt(req.query.max_time, 10);
+    if(isNaN(maxTime)) {
+        res.status(400);
+        next({
+            status: 400,
+            message: 'Invalid value for mandatory parameter max_time'
+        });
+    }
+    res.json({
+        result: Array.apply(null, {length: maxTime}).map(Number.call, Number).map((i)=>i+1)
+    });
+});
+
+simulationApp.get('/pqr', (req, res) => {
+    PQR.findOne((err, pqr) => {
+        if(err) {
+            log.error(err);
+            return next({err});
+        }
+
+        res.json(pqr);
+    });
+});
+
+simulationApp.put('/pqr', passport.authenticate('bearer', { session: false }), (req, res, next) => {
+    let user = req.user.toObject();
+
+    PQR.findOne((err, pqr) => {
+        if(err) {
+            log.error(err)
+            return next({err});
+        }
+
+        if(!user.admin) {
+            log.info(`Usuário ${user.email} tentou alterar o PQR, mas não tem privilégio`);
+            res.statusCode = 401;
+            return next({err: { msg: 'Unauthorized'}});
+        }
+        pqr.content = req.body.content || pqr.content;
+        pqr.save((err) => {
+            if(err) {
+                log.error(err);
+                return next({err});
+            }
+            res.json({msg: 'PQR updated'})
+        });
+    });
+});
+
+simulationApp.get('/', passport.authenticate('bearer', { session: false }), (req, res) => {
+    let user = req.user.toObject();
+    let query = Simulation.find({userId: user._id}).select('userId name createdAt updatedAt');
+    query.exec((err, simulations) => {
+        if(err) {
+            log.error(err);
+            return next({err});
+        }
+
+        res.json(simulations);
+    });
+
+    // Simulation.find({userId: user._id}, (err, simulations) => {
+    //     if(err) {
+    //         log.error(err);
+    //         return next({err});
+    //     }
+
+    //     res.json(simulations);
+    // });
+});
+
+simulationApp.post('/', passport.authenticate('bearer', { session: false }), (req, res, next) => {
+    let user = req.user.toObject();
+
+    let simulation = new Simulation({
+        userId: user._id,
+        content: req.body.content,
+        name: req.body.name
+    });
+
+    simulation.save((err) => {
+        if(err) {
+            log.error(err);
+            return next({err});
+        }
+
+        res.json({msg: 'Simulation created', simulation});
+    })
+});
+
+simulationApp.get('/:id', passport.authenticate('bearer', { session: false }), (req, res) => {
+    let user = req.user.toObject();
+
+    Simulation.findOne({_id: req.params.id, userId: user._id}, (err, simulation) => {
+        if(err) {
+            log.error(err);
+            return next({err});
+        }
+
+        res.json(simulation);
+    });
+});
+
+simulationApp.put('/:id', passport.authenticate('bearer', { session: false }), (req, res, next) => {
+    let user = req.user.toObject();
+
+    Simulation.findOne({_id: req.params.id, userId: user._id}, (err, simulation) => {
+        if(err) {
+            log.error(err);
+            return next({err});
+        }
+
+        if(!simulation) {
+            res.statusCode = 404;
+            return next({err: { msg: 'Simulation not found'}});
+        }
+
+        simulation.content = req.body.content || simulation.content;
+        simulation.name = req.body.name || simulation.name;
+        simulation.updatedAt = Date.now();
+
+        simulation.save((err) => {
+            if(err) {
+                log.error(err);
+                return next(err);
+            }
+
+            res.json(simulation);
+        });
+    });
+});
+
+simulationApp.delete('/:id', passport.authenticate('bearer', { session: false }), (req, res, next) => {
+    let user = req.user.toObject();
+
+    Simulation.remove({_id: req.params.id, userId: user._id}, (err, simulation) => {
+        if(err) {
+            log.error(err);
+            return next({err});
+        }
+
+        res.json({msg: 'Simulation removed'});
+    });
+});
+
+module.exports = simulationApp;
diff --git a/src/libs/routes/siope.js b/src/libs/routes/siope.js
new file mode 100644
index 0000000000000000000000000000000000000000..51c1c36833b99ff097cf8a85a0c90c20598f737b
--- /dev/null
+++ b/src/libs/routes/siope.js
@@ -0,0 +1,166 @@
+const express = require('express');
+
+const siopeApp = express.Router();
+
+const libs = `${process.cwd()}/libs`;
+
+const log = require(`${libs}/log`)(module);
+
+const squel = require('squel');
+
+const query = require(`${libs}/middlewares/query`).query;
+
+const multiQuery = require(`${libs}/middlewares/multiQuery`);
+
+const response = require(`${libs}/middlewares/response`);
+
+const ReqQueryFields = require(`${libs}/middlewares/reqQueryFields`);
+
+const id2str = require(`${libs}/middlewares/id2str`);
+
+const config = require(`${libs}/config`);
+
+const cache = require('apicache').options({ debug: config.debug, statusCodes: {include: [200]} }).middleware;
+
+let rqf = new ReqQueryFields();
+
+siopeApp.use(cache('15 day'));
+
+siopeApp.get('/years', (req, res, next) => {
+    req.sql.from('siope_mun')
+    .field('DISTINCT siope_mun.ano_censo', 'year');
+    next();
+}, query, (req, res, next) => {
+    req.oldResult = req.result;
+
+    req.sql = squel.select();
+
+    req.sql.from('siope_uf')
+    .field('DISTINCT siope_uf.ano_censo', 'year');
+    next();
+}, query, (req, res, next) => {
+    let result = Object.assign(req.oldResult, req.result);
+    req.result = result;
+    next();
+}, response('years'));
+
+rqf.addField({
+    name: 'filter',
+    field: true,
+    where: true
+}).addField({
+    name: 'dims',
+    field: true,
+    where: false
+}).addValue({
+    name: 'city',
+    table: 'municipio',
+    tableField: 'nome',
+    resultField: 'city_name',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'municipio_id',
+        table: 'siope_mun'
+    },
+    join: {
+        primary: 'id',
+        foreign: 'municipio_id',
+        foreignTable: 'siope_mun'
+    }
+}).addValue({
+    name: 'state',
+    table: 'estado',
+    tableField: ['id','nome','sigla'],
+    resultField: ['state_id','state_name','state_abbreviation'],
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'estado_id',
+        table: '@'
+    },
+    join: {
+        primary: 'id',
+        foreign: 'estado_id',
+        foreignTable: '@'
+    }
+}).addValue({
+    name: 'min_year',
+    table: '@',
+    tableField: 'ano_censo',
+    resultField: 'year',
+    where: {
+        relation: '>=',
+        type: 'integer',
+        table: '@',
+        field: 'ano_censo'
+    }
+}).addValue({
+    name: 'max_year',
+    table: '@',
+    tableField: 'ano_censo',
+    resultField: 'year',
+    where: {
+        relation: '<=',
+        type: 'integer',
+        table: '@',
+        field: 'ano_censo'
+    }
+});
+
+
+
+siopeApp.get('/', rqf.parse(), (req, res, next) => {
+    req.querySet = [];
+    req.queryIndex = {};
+
+    let siopeUf = req.sql.clone();
+    siopeUf.from('siope_uf')
+    .field('siope_uf.ano_censo', 'year')
+    .field('siope_uf.estado_id', 'state_id')
+    .field('siope_uf.fundeb', 'fundeb')
+    .field('siope_uf.total_impostos', 'impostos')
+    .field('siope_uf.total_mde', 'MDE')
+    .group('siope_uf.ano_censo')
+    .group('siope_uf.estado_id')
+    .group('siope_uf.fundeb')
+    .group('siope_uf.total_impostos')
+    .group('siope_uf.total_mde')
+    .order('siope_uf.ano_censo');
+    req.queryIndex.siopeUf = req.querySet.push(siopeUf) - 1;
+
+    let siopeMun = req.sql.clone();
+    siopeMun.from('siope_mun')
+    .field('siope_mun.ano_censo', 'year')
+    .field('siope_mun.estado_id', 'state_id')
+    .field('siope_mun.municipio_id', 'city_id')
+    .field('siope_mun.fundeb', 'fundeb')
+    .field('siope_mun.total_impostos', 'impostos')
+    .field('siope_mun.total_mde', 'MDE')
+    .group('siope_mun.ano_censo')
+    .group('siope_mun.estado_id')
+    .group('siope_mun.municipio_id')
+    .group('siope_mun.fundeb')
+    .group('siope_mun.total_impostos')
+    .group('siope_mun.total_mde')
+    .order('siope_mun.ano_censo');
+    req.queryIndex.siopeMun = req.querySet.push(siopeMun) - 1;
+
+    next();
+}, rqf.multibuild(), multiQuery, (req, res, next) => {
+
+    let result = []
+
+    req.result[req.queryIndex.siopeUf].forEach((item) => {
+        result.push(item)
+    });
+    req.result[req.queryIndex.siopeMun].forEach((item) => {
+        result.push(item)
+    });
+
+    req.result = result;
+    next();
+
+}, response('siope'));
+
+module.exports = siopeApp;
diff --git a/src/libs/routes/spatial.js b/src/libs/routes/spatial.js
new file mode 100644
index 0000000000000000000000000000000000000000..5523de7ad8fa79fa357f16dce588cc53782bc2ff
--- /dev/null
+++ b/src/libs/routes/spatial.js
@@ -0,0 +1,353 @@
+const express = require('express');
+
+const libs = `${process.cwd()}/libs`;
+
+const squel = require('squel');
+
+const log = require(`${libs}/log`)(module);
+
+const query = require(`${libs}/middlewares/query`).query;
+
+const sqlQuery = require(`${libs}/db/query_exec`);
+
+const response = require(`${libs}/middlewares/response`);
+
+const ReqQueryFields = require(`${libs}/middlewares/reqQueryFields`);
+
+const id2str = require(`${libs}/middlewares/id2str`);
+
+const spatialApp = express();
+
+let rqf = new ReqQueryFields();
+
+function processResultSet(querySet, querySetLabels = ["result"], singleResult = false) {
+    const resultMap = new Map();
+    let resultIdx = 0;
+    // loop relies on the fact that Promise.all maintains the order of the original iterable
+    for(let result of querySet) {
+        const resultLbl = querySetLabels[resultIdx];
+        resultMap[resultLbl] = [];
+        if (singleResult) {
+            resultMap[resultLbl] = result[0];
+        } else {
+            for(let row of result) {
+                resultMap[resultLbl].push(row);
+            }
+        }
+        resultIdx++;
+    }
+    return resultMap;
+}
+
+function dbExecAll(querySet = []) {
+    // Issue all queries concurrently to the database, for every query object in the iterable
+    // NOTE: Array.map() returns a copy of the original array with each object 'mapped'.
+    return querySet.map((qry) => { return sqlQuery(qry.toString()); });
+}
+
+rqf.addField({
+    name: 'filter',
+    field: true,
+    where: true
+}).addValue({
+    name: 'region',
+    table: 'regiao',
+    tableField: 'nome',
+    resultField: 'region_name',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'id'
+    },
+    join: {
+        primary: 'id',
+        foreign: 'regiao_id'
+    }
+}).addValue({
+    name: 'state',
+    table: 'estado',
+    tableField: 'nome',
+    resultField: 'state_name',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'id'
+    },
+    join: {
+        primary: 'id',
+        foreign: 'estado_id'
+    }
+}).addValue({
+    name: 'city',
+    table: 'municipio',
+    tableField: 'nome',
+    resultField: 'city_name',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'id'
+    },
+    join: {
+        primary: 'id',
+        foreign: 'municipio_id'
+    }
+}).addValue({
+    name: 'school',
+    table: 'escola',
+    tableField: 'nome_escola',
+    resultField: 'school_name',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'id'
+    }
+});
+
+spatialApp.get('/sociodemographic', rqf.parse(), rqf.build(), (req, res, next) => {
+    const populationYearQry = squel.select()
+        .field('MAX(ibge_populacao.ano_censo)')
+        .from('ibge_populacao');
+
+    const populationQry = req.sql.clone()
+        .field('\'Brasil\'', 'name')
+        .field('SUM(populacao)', 'population')
+        .field('ibge_populacao.ano_censo', 'census_year')
+        .from('ibge_populacao')
+        .where(`ibge_populacao.ano_censo IN (${populationYearQry.toString()})`)
+        .group('ibge_populacao.ano_censo');
+
+    const pibYearQry = squel.select()
+        .field('MAX(ibge_pib.ano_censo)')
+        .from('ibge_pib');
+
+    const pibQry = req.sql.clone()
+        .field('\'Brasil\'', 'name')
+        .field('AVG(ibge_pib.pib_per_capita)', 'gdp_per_capita')
+        .field('ibge_pib.ano_censo', 'census_year')
+        .from('ibge_pib')
+        .where(`ibge_pib.ano_censo IN (${pibYearQry.toString()})`)
+        .group('ibge_pib.ano_censo');
+
+    const idhYearQry = squel.select()
+        .field('MAX(adh_idh.ano_censo)')
+        .from('adh_idh');
+
+    const idhQry = req.sql.clone()
+        .field('\'Brasil\'', 'name')
+        .field('AVG(idhm)', 'idhm')
+        .field('adh_idh.ano_censo', 'census_year')
+        .from('adh_idh')
+        .where(`adh_idh.ano_censo IN (${idhYearQry.toString()})`)
+        .group('adh_idh.ano_censo');
+
+    const analfabYearQry = squel.select()
+        .field('MAX(adh_analfabetismo.ano_censo)')
+        .from('adh_analfabetismo');
+
+    const analfabQry = req.sql.clone()
+        .field('\'Brasil\'', 'name')
+        .field('AVG(t_analf15m)', 'analfabetism')
+        .field('adh_analfabetismo.ano_censo', 'census_year')
+        .from('adh_analfabetismo')
+        .where(`adh_analfabetismo.ano_censo IN (${analfabYearQry.toString()})`)
+        .group('adh_analfabetismo.ano_censo');
+
+    const giniYearQry = squel.select()
+        .field('MAX(adh_gini.ano_censo)')
+        .from('adh_gini');
+
+    const giniQry = req.sql.clone()
+        .field('\'Brasil\'', 'name')
+        .field('AVG(gini)', 'gini')
+        .field('adh_gini.ano_censo', 'census_year')
+        .from('adh_gini')
+        .where(`adh_gini.ano_censo IN (${giniYearQry.toString()})`)
+        .group('adh_gini.ano_censo');
+
+    // map query objects to their respective response labels
+    const queryLabels = [ "population", "gdp", "idh", "analfab", "gini" ];
+    const querySet = [ populationQry, pibQry, idhQry, analfabQry, giniQry ];
+    // wait until all queries finish or one of them fail
+    Promise.all(dbExecAll(querySet)).then((queryResults) => {
+        req.result = processResultSet(queryResults, queryLabels, true);
+        next();
+    }).catch((error) => {
+        log.error(`[SQL query error] ${error}`);
+        next(error);
+    });
+}, response('spatial'));
+
+spatialApp.get('/educational', rqf.parse(), rqf.build(), (req, res, next) => {
+    const censusYearQry = squel.select()
+        .field('MAX(escola.ano_censo)', 'ano_censo')
+        .from('escola')
+        .toString();
+
+    const totalSchoolsQry = req.sql.clone()
+        .field('\'Brasil\'', 'name')
+        .field('\'Total\'', 'location_name')
+        .field('COUNT(DISTINCT(escola.id))', 'total')
+        .field('escola.ano_censo', 'census_year')
+        .from('turma')
+        .from('escola')
+        .where('escola.ano_censo=turma.ano_censo AND escola.id=turma.escola_id')
+        .where(`escola.ano_censo IN (${censusYearQry})`)
+        .where('turma.tipo_turma_id = 0')
+        .group('escola.ano_censo');
+
+    const schoolsPerLocationQry = req.sql.clone()
+        .field('\'Brasil\'', 'name')
+        .field('COUNT(DISTINCT(escola.id))', 'total')
+        .field('escola.ano_censo', 'census_year')
+        .field('localizacao.descricao', 'location_name')
+        .from('localizacao')
+        .from('turma')
+        .from('escola')
+        .where('escola.cod_localizacao=localizacao.id')
+        .where('escola.ano_censo=turma.ano_censo AND escola.id=turma.escola_id')
+        .where(`escola.ano_censo IN (${censusYearQry})`)
+        .where('turma.tipo_turma_id = 0')
+        .group('escola.cod_localizacao')
+        .group('escola.ano_censo')
+        .group('localizacao.descricao')
+        .order('localizacao.descricao');
+
+    const schoolsPerAdmDependencyQry = req.sql.clone()
+        .field('\'Brasil\'', 'name')
+        .field('COUNT(DISTINCT(escola.id))', 'total')
+        .field('escola.ano_censo', 'census_year')
+        .field('dependencia_adm.nome', 'adm_dependency_name')
+        .from('dependencia_adm')
+        .from('escola')
+        .where('escola.dependencia_adm_id=dependencia_adm.id')
+        .where(`escola.ano_censo IN (${censusYearQry})`)
+        .group('escola.ano_censo')
+        .group('dependencia_adm.nome')
+        .order('escola.ano_censo')
+        .order('dependencia_adm.nome');
+
+    const enrollmentsQry = req.sql.clone()
+        .field('\'Brasil\'', 'name')
+        .field('COALESCE(SUM(uc201.matriculas), 0)', 'total')
+        .field('uc201.ano_censo', 'census_year')
+        .from('uc201')
+        .group('uc201.ano_censo');
+
+    const enrollmentsPerAdmDepQry = req.sql.clone()
+        .field('\'Brasil\'', 'name')
+        .field('COALESCE(SUM(uc201.matriculas), 0)', 'total')
+        .field('uc201.ano_censo', 'census_year')
+        .field('dependencia_adm.nome', 'adm_dependency_name')
+        .from('dependencia_adm')
+        .from('uc201')
+        .where('uc201.dependencia_adm_id=dependencia_adm.id')
+        .group('dependencia_adm.nome')
+        .group('uc201.ano_censo');
+
+    const enrollmentsPerSchoolLevelQry = req.sql.clone()
+        .field('\'Brasil\'', 'name')
+        .field('COALESCE(SUM(uc201.matriculas), 0)', 'total')
+        .field('uc201.ano_censo', 'census_year')
+        .field('etapa_ensino.desc_etapa', 'school_level_name')
+        .field('dependencia_adm.nome', 'adm_dependency_name')
+        .from('etapa_ensino')
+        .from('dependencia_adm')
+        .from('uc201')
+        .where('uc201.etapas_mod_ensino_segmento_id=etapa_ensino.id')
+        .where('uc201.dependencia_adm_id=dependencia_adm.id')
+        .group('etapa_ensino.desc_etapa')
+        .group('dependencia_adm.nome')
+        .group('uc201.ano_censo');
+
+    const enrollmentsPerLocationQry = req.sql.clone()
+        .field('\'Brasil\'', 'name')
+        .field('COALESCE(SUM(uc201.matriculas), 0)', 'total')
+        .field('uc201.ano_censo', 'census_year')
+        .field('localizacao.descricao', 'location_name')
+        .from('localizacao')
+        .from('uc201')
+        .where('uc201.localizacao=localizacao.id')
+        .group('localizacao.descricao')
+        .group('uc201.ano_censo');
+
+    const queryLabels = [ "school", "school_per_location", "school_per_adm_dependency", "enrollment", "enrollment_per_adm_dep",
+        "enrollment_per_school_level", "enrollment_per_location" ];
+    const querySet = [ totalSchoolsQry, schoolsPerLocationQry, schoolsPerAdmDependencyQry, enrollmentsQry,
+        enrollmentsPerAdmDepQry, enrollmentsPerSchoolLevelQry, enrollmentsPerLocationQry];
+    // wait until all queries finish or one of them fail
+    Promise.all(dbExecAll(querySet)).then((queryResults) => {
+        req.result = processResultSet(queryResults, queryLabels);
+        next();
+    }).catch((error) => {
+        log.error(`[SQL query error] ${error}`);
+        next(error);
+    });
+}, response('spatial'));
+
+spatialApp.get('/educational/school_level', rqf.parse(), rqf.build(), (req, res, next) => {
+    const enrollmentsPerSchoolLevelYearQry = squel.select()
+        .field('MAX(matricula.ano_censo)', 'census_year')
+        .from('matricula');
+
+    const enrollmentsPerSchoolLevelQry = req.sql.clone()
+        .field('COALESCE(COUNT(matricula.id), 0)', 'total')
+        .field('matricula.ano_censo', 'census_year')
+        .field('matricula.serie_ano_id', 'school_year')
+        .field('etapa_ensino.desc_etapa', 'school_level')
+        .from('etapa_ensino')
+        .from('matricula')
+        .where(`matricula.ano_censo IN (${enrollmentsPerSchoolLevelYearQry.toString()})`)
+        .where('matricula.etapa_ensino_id = etapa_ensino.id')
+        .where('matricula.tipo <= 3')
+        .group('etapa_ensino.desc_etapa')
+        .group('etapa_ensino.id')
+        .group('matricula.serie_ano_id')
+        .group('matricula.ano_censo')
+        .order('etapa_ensino.id')
+        .order('matricula.serie_ano_id')
+        .order('matricula.ano_censo');
+
+    const queryLabels = [ 'enrollment_per_school_level', 'enrollment_census_year' ];
+    const querySet = [ enrollmentsPerSchoolLevelQry, enrollmentsPerSchoolLevelYearQry ];
+    // wait until all queries finish or one of them fail
+    Promise.all(dbExecAll(querySet, enrollmentsPerSchoolLevelYearQry)).then((queryResults) => {
+        const result = queryResults[0];
+        const censusYear = queryResults[1][0]['census_year'];
+
+        let school_levels = {};
+        for(let i = 0; i < result.length; ++i) {
+            const school_year  = id2str.schoolYear(result[i].school_year);
+            const school_level = result[i].school_level;
+            const census_year = result[i].census_year;
+            if (typeof school_levels[school_level] === 'undefined') {
+                school_levels[school_level] = {};
+            }
+            school_levels[school_level][school_year] = parseInt(result[i].total, 10);
+        }
+
+        let response = [];
+        for(let level in school_levels) {
+            if (school_levels.hasOwnProperty(level)) {
+                let sclevel = {};
+                sclevel["degree"] = level;
+                sclevel["census_year"] = parseInt(censusYear, 10);
+                sclevel["table"] = [];
+                for(let school_year in school_levels[level]) {
+                    if (school_levels[level].hasOwnProperty(school_year)) {
+                        let enrollment = { 'title' : school_year,
+                                           'value' : school_levels[level][school_year] };
+                        sclevel["table"].push(enrollment);
+                    }
+                }
+                response.push(sclevel);
+            }
+        }
+        req.result = response;
+        next();
+    }).catch((error) => {
+        log.error(`[SQL query error] ${error}`);
+        next(error);
+    });
+}, response('spatial'));
+
+module.exports = spatialApp;
diff --git a/src/libs/routes/state.js b/src/libs/routes/state.js
index 75e1c0b7835901804ea78e459b1fe0050ec03631..33778869342216fe40e8e5104f327d4f2de804ef 100644
--- a/src/libs/routes/state.js
+++ b/src/libs/routes/state.js
@@ -6,27 +6,74 @@ const libs = `${process.cwd()}/libs`;
 
 const squel = require('squel');
 
-const query = require(`${libs}/middlewares/query`);
+const query = require(`${libs}/middlewares/query`).query;
 
 const response = require(`${libs}/middlewares/response`);
 
-// Get all states
-stateApp.get('/', (req, res, next) => {
-    req.sql.from('estados');
-    next();
-}, query, response('state'));
+const ReqQueryFields = require(`${libs}/middlewares/reqQueryFields`);
 
-// Get a state
-stateApp.get('/:id', (req, res, next) => {
-    req.sql.from('estados')
-        .where('pk_estado_id = ?', parseInt(req.params.id, 10));
-    next();
-}, query, response('state'));
+const config = require(`${libs}/config`); 
+
+const cache = require('apicache').options({ debug: config.debug, statusCodes: {include: [200]} }).middleware;
+
+let rqf = new ReqQueryFields();
+
+stateApp.use(cache('15 day'));
+
+rqf.addField({
+    name: 'filter',
+    field: false,
+    where: true
+}).addValue({
+    name: 'id',
+    table: 'estado',
+    tableField: 'id',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'id'
+    }
+}).addValue({
+    name: 'region',
+    table: 'regiao',
+    tableField: 'nome',
+    resultField: 'region_name',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'regiao_id',
+        table: 'estado'
+    },
+    join: {
+        primary: 'id',
+        foreign: 'regiao_id',
+        foreignTable: 'estado'
+    }
+}).addField({
+    name: 'search',
+    field: false,
+    where: true
+}).addValueToField({
+    name: 'name',
+    table: 'estado',
+    tableField: 'nome',
+    where: {
+        relation: 'LIKE',
+        type: 'string',
+        field: 'nome'
+    }
+}, 'search');
 
-// Get all states from a region
-stateApp.get('/region/:id', (req, res, next) => {
-    req.sql.from('estados')
-        .where('fk_regiao_id = ?', parseInt(req.params.id, 10));
+stateApp.get('/', rqf.parse(), rqf.build(), (req, res, next) => {
+    req.sql.from('estado')
+        .field('estado.id')
+        .group('estado.id')
+        .field('regiao_id', 'region_id')
+        .group('regiao_id')
+        .field('estado.nome', 'name')
+        .group('estado.nome')
+        .field('estado.sigla', 'abbreviation')
+        .group('estado.sigla');
     next();
 }, query, response('state'));
 
diff --git a/src/libs/routes/teacher.js b/src/libs/routes/teacher.js
new file mode 100644
index 0000000000000000000000000000000000000000..a23293de7d7d9343ea435daca95c8016ecb64156
--- /dev/null
+++ b/src/libs/routes/teacher.js
@@ -0,0 +1,429 @@
+const express = require('express');
+
+const teacherApp = express.Router();
+
+const libs = `${process.cwd()}/libs`;
+
+const log = require(`${libs}/log`)(module);
+
+const squel = require('squel');
+
+const query = require(`${libs}/middlewares/query`).query;
+
+const response = require(`${libs}/middlewares/response`);
+
+const ReqQueryFields = require(`${libs}/middlewares/reqQueryFields`);
+
+const id2str = require(`${libs}/middlewares/id2str`);
+
+const config = require(`${libs}/config`);
+
+const passport = require('passport');
+
+const download = require(`${libs}/middlewares/downloadDatabase`);
+
+const addMissing = require(`${libs}/middlewares/addMissing`);
+
+const cache = require('apicache').options({ debug: config.debug, statusCodes: {include: [200]}  }).middleware;
+
+let rqf = new ReqQueryFields();
+
+teacherApp.use(cache('15 day'));
+
+// Returns a tuple of start and ending years of the complete enrollments dataset.
+teacherApp.get('/year_range', (req, res, next) => {
+    req.sql.from('docente')
+    .field('MIN(docente.ano_censo)', 'start_year')
+    .field('MAX(docente.ano_censo)', 'end_year');
+    next();
+}, query, response('range'));
+
+teacherApp.get('/years', (req, res, next) => {
+    req.sql.from('docente').
+    field('DISTINCT docente.ano_censo', 'year');
+    next();
+}, query, response('years'));
+
+teacherApp.get('/source', (req, res, next) => {
+    req.sql.from('fonte')
+    .field('fonte', 'source')
+    .where('tabela = \'docente\'');
+    next();
+}, query, response('source'));
+
+teacherApp.get('/adm_dependency_detailed', (req, res, next) => {
+    req.result = [];
+    for(let i = 1; i <= 6; ++i) {
+        req.result.push({
+            id: i,
+            name: id2str.admDependencyPriv(i)
+        });
+    };
+    next();
+}, response('adm_dependency_detailed'));
+
+teacherApp.get('/adm_dependency', (req, res, next) => {
+    req.result = [];
+    for(let i = 1; i <= 4; ++i) {
+        req.result.push({
+            id: i,
+            name: id2str.admDependency(i)
+        });
+    };
+    next();
+}, response('adm_dependency'));
+
+teacherApp.get('/education_level_mod', (req, res, next) => {
+    req.result = [];
+    for(let i = 1; i <= 11; ++i) {
+        req.result.push({
+            id: i,
+            name: id2str.educationLevelMod(i)
+        });
+    }
+
+    req.result.push({
+        id: 99,
+        name: id2str.educationLevelMod(99)
+    });
+    next();
+}, response('education_level_mod'));
+
+teacherApp.get('/education_level_short', (req, res, next) => {
+    req.result = [
+        {id: null, name: 'Não classificada'},
+        {id: 1, name: 'Creche'},
+        {id: 2, name: 'Pré-Escola'},
+        {id: 3, name: 'Ensino Fundamental - anos iniciais'},
+        {id: 4, name: 'Ensino Fundamental - anos finais'},
+        {id: 5, name: 'Ensino Médio'},
+        {id: 6, name: 'EJA'},
+        {id: 7, name: 'EE exclusiva'}
+    ];
+    next();
+}, response('education_level_short'));
+
+teacherApp.get('/location', (req, res, next) => {
+    req.result = [];
+    for(let i = 1; i <= 2; ++i) {
+        req.result.push({
+            id: i,
+            name: id2str.location(i)
+        });
+    };
+    next();
+}, response('location'));
+
+teacherApp.get('/rural_location', (req, res, next) => {
+    req.result = [
+        {id: 1, name: "Urbana"},
+        {id: 2, name: "Rural"},
+        {id: 3, name: "Rural - Área de assentamento"},
+        {id: 4, name: "Rural - Terra indígena"},
+        {id: 5, name: "Rural - Área remanescente de quilombos"},
+        {id: 6, name: "Rural - Unidade de uso sustentável"}
+    ];
+    next();
+}, response('rural_location'));
+
+teacherApp.get('/education_type', (req, res, next) => {
+    req.sql.from('docente')
+    .field('DISTINCT nivel_tipo_formacao', 'id')
+    .order('id');
+    next();
+}, query, (req, res, next) => {
+    req.result.forEach((result) => {
+        result.name = id2str.educationType(result.id);
+    });
+    next();
+}, response('education_type'));
+
+teacherApp.get('/gender', (req, res, next) => {
+    req.result = [
+        {id: 1, name: 'Masculino'},
+        {id: 2, name: 'Feminino'}
+    ];
+    next();
+}, response('gender'));
+
+
+teacherApp.get('/contract_type', (req, res, next) => {
+    req.result = [
+        {id: 1, name: 'Concursado/Efetivo/Estável'},
+        {id: 2, name: 'Contrato temporário'},
+        {id: 3, name: 'Contrato terceirizado'},
+        {id: 4, name: 'Contrato CLT'}
+    ];
+    next();
+}, response('contract_type'));
+
+teacherApp.get('/ethnic_group', (req, res, next) => {
+    req.result = [];
+    for(let i = 0; i <=5; ++i) {
+        req.result.push({
+            id: i,
+            name: id2str.ethnicGroup(i)
+        });
+    }
+    next();
+}, response('ethnic_group'));
+
+rqf.addField({
+    name: 'filter',
+    field: false,
+    where: true
+}).addField({
+    name: 'dims',
+    field: true,
+    where: false
+}).addValue({
+    name: 'adm_dependency',
+    table: 'docente',
+    tableField: 'dependencia_adm_id',
+    resultField: 'adm_dependency_id',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'dependencia_adm_id'
+    }
+}).addValue({
+    name: 'adm_dependency_detailed',
+    table: 'docente',
+    tableField: 'dependencia_adm_priv',
+    resultField: 'adm_dependency_detailed_id',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'dependencia_adm_priv'
+    }
+}).addValue({
+    name: 'contract_type',
+    table: 'docente',
+    tableField: 'tipo_contratacao',
+    resultField: 'contract_type_id',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'tipo_contratacao'
+    }
+}).addValue({
+    name: 'education_level_mod',
+    table: 'docente',
+    tableField: 'etapas_mod_ensino_segmento_id',
+    resultField: 'education_level_mod_id',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'etapas_mod_ensino_segmento_id'
+    }
+}).addValue({
+    name: 'education_level_short',
+    table: 'docente',
+    tableField: 'etapa_resumida',
+    resultField: 'education_level_short_id',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'etapa_resumida'
+    }
+}).addValue({
+    name: 'education_type',
+    table: 'docente',
+    tableField: 'nivel_tipo_formacao',
+    resultField: 'education_type_id',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'nivel_tipo_formacao'
+    }
+}).addValue({
+    name: 'region',
+    table: 'regiao',
+    tableField: 'nome',
+    resultField: 'region_name',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'id'
+    },
+    join: {
+        primary: 'id',
+        foreign: 'escola_regiao_id',
+        foreignTable: 'docente'
+    }
+}).addValue({
+    name: 'state',
+    table: 'estado',
+    tableField: 'nome',
+    resultField: 'state_name',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'id'
+    },
+    join: {
+        primary: 'id',
+        foreign: 'escola_estado_id',
+        foreignTable: 'docente'
+    }
+}).addValueToField({
+    name: 'city',
+    table: 'municipio',
+    tableField: ['nome', 'id'],
+    resultField: ['city_name', 'city_id'],
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'id'
+    },
+    join: {
+        primary: 'id',
+        foreign: 'escola_municipio_id',
+        foreignTable: 'docente'
+    }
+}, 'dims').addValueToField({
+    name: 'city',
+    table: 'municipio',
+    tableField: 'nome',
+    resultField: 'city_name',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'id'
+    },
+    join: {
+        primary: 'id',
+        foreign: 'escola_municipio_id',
+        foreignTable: 'docente'
+    }
+}, 'filter').addValue({
+    name: 'school',
+    table: 'escola',
+    tableField: 'nome_escola',
+    resultField: 'school_name',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'id'
+    },
+    join: {
+        primary: ['id', 'ano_censo'],
+        foreign: ['escola_id', 'ano_censo'],
+        foreignTable: 'docente'
+    }
+}).addValue({
+    name: 'location',
+    table: 'docente',
+    tableField: 'cod_localizacao',
+    resultField: 'location_id',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'cod_localizacao'
+    }
+}).addValue({
+    name: 'rural_location',
+    table: 'docente',
+    tableField: 'localidade_area_rural',
+    resultField: 'rural_location_id',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'localidade_area_rural'
+    }
+}).addValue({
+    name: 'min_year',
+    table: 'docente',
+    tableField: 'ano_censo',
+    resultField: 'year',
+    where: {
+        relation: '>=',
+        type: 'integer',
+        field: 'ano_censo'
+    }
+}).addValue({
+    name: 'max_year',
+    table: 'docente',
+    tableField: 'ano_censo',
+    resultField: 'year',
+    where: {
+        relation: '<=',
+        type: 'integer',
+        field: 'ano_censo'
+    }
+}).addValue({
+    name: 'gender',
+    table: 'docente',
+    tableField: 'sexo',
+    resultField: 'gender_id',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'sexo'
+    }
+}).addValue({
+    name: 'ethnic_group',
+    table: 'docente',
+    tableField: 'cor_raca',
+    resultField: 'ethnic_group_id',
+    where: {
+        relation: '=',
+        type: 'integer',
+        field: 'cor_raca'
+    }
+});
+
+teacherApp.get('/', rqf.parse(), (req, res, next) => {
+    req.sql.field('COUNT(DISTINCT docente.id)', 'total')
+    .field("'Brasil'", 'name')
+    .field('docente.ano_censo', 'year')
+    .from('docente')
+    .join('turma', null, 'docente.turma_id=turma.id AND docente.ano_censo=turma.ano_censo')
+    .group('docente.ano_censo')
+    .order('docente.ano_censo')
+    .where('(docente.tipo_docente = 1 OR docente.tipo_docente = 5) AND (turma.tipo_turma_id <= 3)');
+
+    if("education_level_mod" in req.dims) {
+        delete req.dims.education_level_mod;
+        req.hadEducationLevelMod = true;
+        req.sql.field('docente.etapas_mod_ensino_segmento_id', 'education_level_mod_id')
+        .where('docente.etapas_mod_ensino_segmento_id < 11')
+        .group('docente.etapas_mod_ensino_segmento_id')
+        .order('docente.etapas_mod_ensino_segmento_id');
+    }
+
+    next();
+}, rqf.build(), query, addMissing(rqf), (req, res, next) => {
+    req.oldResult = req.result;
+    if(req.hadEducationLevelMod) {
+
+        req.sql = squel.select()
+        .field('COUNT(DISTINCT docente.id)', 'total')
+        .field("'Brasil'", 'name')
+        .field('docente.ano_censo', 'year')
+        .from('docente')
+        .join('turma', null, 'docente.turma_id=turma.id AND docente.ano_censo=turma.ano_censo')
+        .group('docente.ano_censo')
+        .order('docente.ano_censo')
+        .where('(docente.tipo_docente = 1 OR docente.tipo_docente = 5) AND (turma.tipo_turma_id <= 3)')
+        .where('docente.profissionalizante = 1');
+
+        rqf.build()(req, res, () => {});
+        query(req, res, next);
+    } else {
+        next();
+    }
+}, (req, res, next) => {
+    if(req.hadEducationLevelMod) {
+        req.result.forEach((result) => {
+            result.education_level_mod_id = 11;
+            req.oldResult.push(result);
+        });
+    }
+    req.result = req.oldResult;
+    next();
+}, id2str.transform(), response('teacher'));
+
+teacherApp.get('/download', passport.authenticate('bearer', { session: false }), rqf.parse(), rqf.build(), download('docente', 'mapping_docente'));
+
+module.exports = teacherApp;
diff --git a/src/libs/routes/user.js b/src/libs/routes/user.js
new file mode 100644
index 0000000000000000000000000000000000000000..af123a588d41a37c1e64fb0cfa20abe3928a84b4
--- /dev/null
+++ b/src/libs/routes/user.js
@@ -0,0 +1,308 @@
+const express = require('express');
+
+const userApp = express();
+
+const libs = `${process.cwd()}/libs`;
+
+const config = require(`${libs}/config`);
+
+const log = require(`${libs}/log`)(module);
+
+const User = require(`${libs}/models/user`);
+
+const VerificationToken = require(`${libs}/models/verificationToken`);
+
+const ResetToken = require(`${libs}/models/resetToken`);
+
+const response = require(`${libs}/middlewares/response`);
+
+const email = require(`${libs}/middlewares/email`);
+
+const passport = require('passport');
+
+function emailSyntax(email) {
+  const regex = /^(([^<>()\[\]\.,;:\s@\"]+(\.[^<>()\[\]\.,;:\s@\"]+)*)|(\".+\"))@(([^<>()[\]\.,;:\s@\"]+\.)+[^<>()[\]\.,;:\s@\"]{2,})$/i;
+  return regex.test(email);
+}
+
+userApp.get('/schooling', (req, res, next) => {
+  req.result = [
+    'Não estudou',
+    'Ensino Fundamental Incompleto',
+    'Ensino Fundamental Completo',
+    'Ensino Médio',
+    'Graduação',
+    'Mestrado',
+    'Doutorado'
+  ];
+  next();
+}, response('schooling'));
+
+userApp.get('/segment', (req, res, next) => {
+  req.result = [
+    'Gestores e equipe gestora das secretarias e ministério da Educação',
+    'Gestores dos órgãos de planejamento e finanças (das três esferas de governo)',
+    'Agentes do poder legislativo',
+    'Agentes dos conselhos de educação',
+    'Profissionais da educação',
+    'Sindicato',
+    'Sociedade civil interessada no financiamento da Educação Básica de qualidade',
+    'Comunidade acadêmica',
+    'Imprensa',
+    'Outro [citar segmento]'
+  ];
+  next();
+}, response('segment'));
+
+userApp.get('/role', (req, res, next) => {
+  req.result = [
+    {"Gestores e equipe gestora das secretarias e ministério da Educação" : ["Dirigente municipal, estadual e federal", "Secretário do MEC", "Servidor da área de planejamento educacional", "Membro de associação de gestores (Ex. Undime, Consed, etc)", "Outro [citar função]"]},
+    {"Gestores dos órgãos de planejamento e finanças (das três esferas de governo)" : ["Equipe gestora dos órgãos de planejamento", "Equipe gestora dos órgãos de finanças", "Outro [citar função]"]},
+    {"Agentes do poder legislativo" : ["Parlamentar", "Assessor/a parlamentar", "Auditor/a dos tribunais de conta", "Conselheiro/a de tribunais de conta.", "Outro [citar função]"]},
+    {"Agentes dos conselhos de educação" : ["Conselheiro/a municipais, estaduais e federais", "Conselheiro/a do Fundeb", "Outro [citar função]"]},
+    {"Profissionais da educação" : ["Professor/a da Educação Básica", "Profissional da educação não-docente", "Outro [citar função]"]},
+    {"Sindicato" : ["Agente de sindicatos"]},
+    {"Sociedade civil interessada no financiamento da Educação Básica de qualidade" : ["Membro de fóruns educacionais", "Membro de ONGs e demais entidades sem fins lucrativos", "Estudante da educação básica e membro de entidades estudantis", "Pais e membros de entidades de pais", "Outro [citar função]"]},
+    {"Comunidade acadêmica" : ["Pesquisador/a", "Estudantes de graduação e pós-graduação", "Representantes de entidades de pesquisa (Ex.: ANPED, ANPAE e FINEDUCA)", "Outro [citar função]"]},
+    {"Imprensa" : ["Jornalista", "Outro [citar função]"]},
+    {"Outro [citar segmento]" : []}
+  ]
+  next();
+}, response('role'));
+
+userApp.get('/', passport.authenticate('bearer', {session: false}), (req, res, next) => {
+  User.find((err, users) => {
+    if(err) {
+      log.error(err);
+      return next(err);
+    }
+
+    let result = [];
+    users.forEach((user) => {
+      let u = user.toObject();
+      delete u.hashedPassword;
+      delete u.salt;
+      result.push(u);
+    });
+    req.result = result;
+    next();
+  });
+}, response('users'));
+
+userApp.get('/me', passport.authenticate('bearer', { session: false }), (req, res, next) => {
+  let user = req.user.toObject();
+  delete user.hashedPassword;
+  delete user.salt;
+  req.result = user;
+  next();
+}, response('user'));
+
+userApp.get('/:id', (req, res, next) => {
+  User.findById(req.params.id, (err, user) => {
+    if(err) {
+      log.error(err);
+      return next(err);
+    }
+    if(!user) {
+      req.statusCode = 404;
+      next({msg: 'User not found'});
+    } else {
+      let u = user.toObject;
+      delete u.hashedPassword;
+      delete u.salt;
+      req.result = u;
+      next();
+    }
+  });
+}, response('user'));
+
+userApp.post('/', (req, res, next) => {
+  let user = new User({
+    email: req.body.email,
+    password: req.body.password,
+    name: req.body.name,
+    nickname: req.body.nickname,
+    cpf: req.body.cpf,
+    cep: req.body.cep,
+    complement: req.body.complement,
+    address: req.body.address,
+    phone: req.body.phone,
+    schooling: req.body.schooling,
+    course: req.body.course,
+    segment: req.body.segment,
+    role: req.body.role,
+    institutionName: req.body.institutionName,
+    state: req.body.state,
+    city: req.body.city,
+    receiveEmails: false || req.body.receiveEmails,
+    origin: req.body.origin,
+    citesegment: req.body.citesegment,
+    citerole: req.body.citerole,
+    admin: false
+  });
+
+  if (typeof req.body.password === 'undefined' || !req.body.password) {
+    res.statusCode = 400;
+    return res.json({errors: ["O campo senha é obrigatório"]});
+  } else {
+    user.save((err) => {
+      if(err) {
+        log.error(err);
+        let errors = [];
+        for(let errName in err.errors) {
+          errors.push(err.errors[errName].message);
+        }
+        log.error(errors);
+        res.statusCode = 400;
+        return res.json({err, errors});
+      }
+
+      // Create verification token
+      let verificationToken = new VerificationToken({
+        userId: user._id
+      });
+
+      verificationToken.createVerificationToken((err, token) => {
+        if(err) {
+          log.error(err);
+          return next(err);
+        }
+        let url = config.default.lde.url + '/verify';
+        let text = `Olá, ${user.name}, seja bem vindo/a ao Laboratório de Dados Educacionais.\n\nClique neste link para confirmar sua conta: ${url}/${token}`;
+        // Send confirmation email
+        let mailOptions = {
+          to: `"${user.name} <${user.email}>"`,
+          subject: "Confirme seu cadastro - Laboratório de Dados Educacionais",
+          text
+        }
+        email(mailOptions, (err, info) => {
+          if(err) {
+            log.error(err);
+            res.json({msg: 'User created'});
+          }
+          if(info) {
+            log.info(`Message ${info.messageId} sent: ${info.response}`);
+            log.info(`Usuário ${user.email} foi criado`);
+          }
+          res.json({msg: 'User created'});
+        });
+      });
+    });
+  }
+
+});
+
+userApp.put('/:id', passport.authenticate('bearer', { session: false }), (req, res, next) => {
+  console.log(req.params.id);
+  console.log(req.user._id);
+  User.findById(req.params.id, (err, user) => {
+    if (err) {
+      log.error(err);
+      return next({err});
+    }
+
+    if(!user) {
+      res.statusCode = 404;
+      return next({err: {
+        message: 'Usuário não encontrado'
+      }});
+    }
+
+    console.log(req.body);
+
+    user.email = req.body.email || user.email;
+    user.name = req.body.name || user.name;
+    user.nickname = req.body.nickname || user.nickname || user.name;
+    user.cep = req.body.cep || user.cep;
+    user.complement = req.body.complement || user.complement;
+    user.address = req.body.address || user.address;
+    user.phone = req.body.phone || user.phone;
+    user.schooling = req.body.schooling || user.schooling;
+    user.course = req.body.course || user.course;
+    user.segment = req.body.segment || user.segment;
+    user.role = req.body.role || user.role;
+    user.institutionName = req.body.institutionName || user.institutionName;
+    user.state = req.body.state || user.state;
+    user.city = req.body.city || user.city;
+    user.receiveEmails = req.body.receiveEmails || user.receiveEmails;
+    user.citesegment = req.body.citesegment || user.citesegment;
+    user.citerole = req.body.citerole || user.citerole;
+
+    // console.log(user.checkPassword(req.body.password));
+    if ((req.body.password) && (req.body.newpassword)) {
+        if (req.body.password != req.body.newpassword) {
+            if (user.checkPassword(req.body.password)) {
+                user.password = req.body.newpassword;
+            } else {
+                res.statusCode = 500;
+                return res.json({error: {
+                    message: 'A senha atual está incorreta'
+                }});
+            }
+        } else {
+            res.statusCode = 500;
+            return res.json({error: {
+                message: 'A nova senha é a mesma da senha atual'
+            }});
+        }
+    }
+
+    user.save(err => {
+      if(err) {
+        log.error(err);
+        return next({message: 'Erro ao atualizar usuário'});
+      }
+      let u = user.toObject();
+      delete u.hashedPassword;
+      delete u.salt;
+      res.json({user: u});
+    })
+  })
+});
+
+userApp.get('/reset/password', (req, res, next) => {
+  let emailAddress = req.query.email;
+  User.findOne({email: emailAddress}, (err, user)=> {
+    if(err) {
+      log.error(err);
+      let errors = [];
+      for(let errName in err.errors) {
+        errors.push(err.errors[errName].message);
+      }
+      res.statusCode = 400;
+      return res.json({err, errors});
+    }
+    if (!user) {
+      res.statusCode = 404;
+      res.json({msg: "O usuário não está cadastrado"});
+    }
+    else {
+      let resetToken = new ResetToken({
+        userId: user._id
+      });
+      resetToken.createResetToken((err, token) => {
+        if (err) {
+          log.error(err);
+          return next(err);
+        }
+        let url = config.default.lde.url + '/reset-password';
+        let text = `Olá, ${user.name}.\n\nRecebemos uma solicitação para redefinir sua senha do Laboratório de Dados Educacionais. Clique neste link para redefinir a sua senha: ${url}/${token}`;
+        let mailOptions = {
+          to: `"${user.name} <${user.email}>"`,
+          subject: "Redefinição de Senha - Laboratório de Dados Educacionais",
+          text
+        }
+        email(mailOptions, (err, info) => {
+          if(err) {
+            log.error(err);
+            res.json({msg: 'Undelivered Reset Password Mail'});
+          }
+          log.info(`Message ${info.messageId} sent: ${info.response}`);
+          res.json({msg: 'Reset Password Mail Successfully Delivered'});
+        });
+      })
+    }
+  })
+})
+
+module.exports = userApp;
diff --git a/src/libs/routes/verifyToken.js b/src/libs/routes/verifyToken.js
new file mode 100644
index 0000000000000000000000000000000000000000..d54f64aa162c767c765784398dbcab455a9d666e
--- /dev/null
+++ b/src/libs/routes/verifyToken.js
@@ -0,0 +1,52 @@
+const express = require('express');
+
+const verifyTokenApp = express.Router();
+
+const libs = `${process.cwd()}/libs`;
+
+const log = require(`${libs}/log`)(module);
+
+const VerificationToken = require(`${libs}/models/verificationToken`);
+
+const User = require(`${libs}/models/user`);
+
+verifyTokenApp.get('/:token', (req, res, next) => {
+    let token = req.params.token;
+    VerificationToken.findOne({token: token}, (err, vToken) => {
+        if(err) {
+            log.error(err);
+            return next(err);
+        }
+        if(!vToken) {
+            // TODO: generate new verification token
+            res.statusCode = 404;
+            return next({msg: 'Token not found', status:404});
+        }
+        User.findById(vToken.userId, (err, user) => {
+            if(err) {
+                log.error(err);
+                next(err);
+            }
+            user.verified = true;
+            user.save((err) => {
+                if(err) {
+                    log.error(err);
+                    next(err);
+                }
+            });
+            let u = user.toObject();
+            delete u.salt;
+            delete u.hashedPassword;
+            vToken.verified = true;
+            vToken.save((err) => {
+                if(err) {
+                    log.error(err);
+                    next(err);
+                }
+            });
+            res.json({msg: 'User verified', user: u});
+        });
+    });
+});
+
+module.exports = verifyTokenApp;
diff --git a/src/server.js b/src/server.js
index 0eef8dfa5fb4764522f0b1c0be02da239603d347..889151c27370bdcce968e3b3c76689288405f689 100644
--- a/src/server.js
+++ b/src/server.js
@@ -1,23 +1,44 @@
-const debug = require('debug')('node-express-base');
-
+const debug = require('debug')('simcaq-api');
 const libs = `${process.cwd()}/libs`;
-
 const config = require(`${libs}/config`);
-
 const log = require(`${libs}/log`)(module);
-
 const app = require(`${libs}/app`);
-
-process.env.NODE_ENV = process.env.NODE_ENV || 'development';
-
-app.set('port', process.env.PORT || config.get('port') || 3000);
-
-// Set default ip: first environment variable IOP, then configuration and last '127.0.0.1'
-app.set('ip', process.env.IP || config.get('ip') || '127.0.0.1');
-
-const server = app.listen(app.get('port'), () => {
-    log.info(`Express server listening on port ${server.address().port}`);
-});
-
-// For testing
-module.exports = server;
+const compatVersion = require(`${libs}/middlewares/checkVersion`);
+const cluster = require('cluster');
+
+// Check if Node version is compatible
+if (!compatVersion()) {
+    process.exit(1);
+}
+
+if(cluster.isMaster) {
+    log.info(`Master ${process.pid} is running`);
+
+    const numCPUs = (process.env.NODE_ENV != 'development') ? require('os').cpus().length : 1;
+    log.info(`Master will create ${numCPUs} workers`);
+    for(let i=0; i < numCPUs; ++i) {
+        cluster.fork();
+    }
+
+    // Caso uma instâcia morra
+    cluster.on('exit',  (worker, code, signal) => {
+        log.info(`Worker ${worker.process.pid} died`);
+        // Revive a instância
+        cluster.fork();
+    });
+} else {
+    // Set default port: first environment variable PORT, then configuration and last 3000
+    app.set('port', process.env.PORT || config.port || 3000);
+    process.env.NODE_ENV = process.env.NODE_ENV || 'development';
+    // Set default ip: first environment variable IOP, then configuration and last '127.0.0.1'
+    app.set('ip', process.env.IP || config.ip || '127.0.0.1');
+
+    const server = app.listen(app.get('port'), () => {
+        log.info(`Express server listening on port ${server.address().port}`);
+    });
+
+    log.info(`Worker ${cluster.worker.id} is running (${process.pid})`);
+
+    // For testing
+    module.exports = server;
+}
diff --git a/src/test/api.js b/src/test/api.js
new file mode 100644
index 0000000000000000000000000000000000000000..b5476ea6352d086cb8d43f8e1f091686bc4f91c4
--- /dev/null
+++ b/src/test/api.js
@@ -0,0 +1,49 @@
+process.env.NODE_ENV = 'test';
+
+const chai = require('chai');
+
+const dirtyChai = require('dirty-chai');
+
+chai.use(dirtyChai);
+
+const chaiXml = require('chai-xml');
+
+chai.use(chaiXml);
+
+const chaiHttp = require('chai-http');
+
+const assert = chai.assert;
+
+const expect = chai.expect;
+
+const should = chai.should(); // actually call the function
+
+const libs = `${process.cwd()}/libs`;
+
+const server = require(`${libs}/app`);
+
+chai.use(chaiHttp);
+
+describe('API is running', () => {
+    it('should respond it\'s running', (done) => {
+        chai.request(server)
+            .get('/api/v1')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('msg');
+                done();
+            })
+    });
+
+    it('should respond with 404 error', (done) => {
+        chai.request(server)
+            .get('/api/v1/thisrouteshouldgivea404')
+            .end((err, res) => {
+                res.should.have.status(404);
+                res.should.be.json;
+                res.body.should.have.property('error');
+                done();
+            })
+    });
+});
diff --git a/src/test/city.js b/src/test/city.js
new file mode 100644
index 0000000000000000000000000000000000000000..1ea61a62f5d31be3c2789df5bc577a137ea50afa
--- /dev/null
+++ b/src/test/city.js
@@ -0,0 +1,84 @@
+process.env.NODE_ENV = 'test';
+
+const chai = require('chai');
+
+const dirtyChai = require('dirty-chai');
+
+chai.use(dirtyChai);
+
+const chaiXml = require('chai-xml');
+
+chai.use(chaiXml);
+
+const chaiHttp = require('chai-http');
+
+const assert = chai.assert;
+
+const expect = chai.expect;
+
+const should = chai.should(); // actually call the function
+
+const libs = `${process.cwd()}/libs`;
+
+const server = require(`${libs}/app`);
+
+chai.use(chaiHttp);
+
+
+describe('request cities', () => {
+    it('should list all cities', (done) => {
+        chai.request(server)
+            .get('/api/v1/city')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('id');
+                res.body.result[0].should.have.property('name');
+                done();
+            });
+    });
+
+    it('should list a city by id', (done) => {
+        chai.request(server)
+            .get('/api/v1/city?filter=id:4106902')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('id');
+                res.body.result[0].should.have.property('name');
+                done();
+            });
+    });
+
+    it('should list all cities from a state', (done) => {
+        chai.request(server)
+            .get('/api/v1/city?filter=state:41')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('id');
+                res.body.result[0].should.have.property('name');
+                done();
+            })
+    })
+
+    it('should search for Curitiba', (done) => {
+        chai.request(server)
+            .get('/api/v1/city?search=name:curitiba')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('id');
+                res.body.result[0].should.have.property('name');
+                done();
+            });
+    });
+});
diff --git a/src/test/class.js b/src/test/class.js
new file mode 100644
index 0000000000000000000000000000000000000000..ef5254f45d847a568c17b31afafc8119273cb50e
--- /dev/null
+++ b/src/test/class.js
@@ -0,0 +1,254 @@
+process.env.NODE_ENV = 'test';
+
+const chai = require('chai');
+
+const dirtyChai = require('dirty-chai');
+
+chai.use(dirtyChai);
+
+const chaiXml = require('chai-xml');
+
+chai.use(chaiXml);
+
+const chaiHttp = require('chai-http');
+
+const assert = chai.assert;
+
+const expect = chai.expect;
+
+const should = chai.should(); // actually call the function
+
+const libs = `${process.cwd()}/libs`;
+
+const server = require(`${libs}/app`);
+
+chai.use(chaiHttp);
+describe('request class', () => {
+    it('should list the locations', (done) => {
+        chai.request(server)
+            .get('/api/v1/class/location')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('id');
+                res.body.result[0].should.have.property('name');
+                done();
+            });
+    });
+
+    it('should list the source', (done) => {
+        chai.request(server)
+            .get('/api/v1/class/source')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('source');
+                done();
+            });
+    });
+
+    it('should list the rural locations', (done) => {
+        chai.request(server)
+            .get('/api/v1/class/rural_location')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('id');
+                res.body.result[0].should.have.property('name');
+                done();
+            });
+    });
+
+    it('should list the education level', (done) => {
+        chai.request(server)
+            .get('/api/v1/class/education_level_mod')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('id');
+                res.body.result[0].should.have.property('name');
+                done();
+            });
+    });
+
+    it('should list the education level short', (done) => {
+        chai.request(server)
+            .get('/api/v1/class/education_level_short')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('id');
+                res.body.result[0].should.have.property('name');
+                done();
+            });
+    });
+
+    it('should list the administrative dependencies', (done) => {
+        chai.request(server)
+            .get('/api/v1/class/adm_dependency_detailed')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('id');
+                res.body.result[0].should.have.property('name');
+                done();
+            });
+    });
+
+    it('should list the administrative dependencies detailed', (done) => {
+        chai.request(server)
+            .get('/api/v1/class/adm_dependency')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('id');
+                res.body.result[0].should.have.property('name');
+                done();
+            });
+    });
+
+    it('should list the periods', (done) => {
+        chai.request(server)
+            .get('/api/v1/class/period')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('id');
+                res.body.result[0].should.have.property('name');
+                done();
+            });
+    });
+
+    it('should list the periods', (done) => {
+        chai.request(server)
+            .get('/api/v1/class/integral_time')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('id');
+                res.body.result[0].should.have.property('name');
+                done();
+            });
+    });
+
+    it('should list class', (done) => {
+        chai.request(server)
+            .get('/api/v1/class')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('name');
+                res.body.result[0].should.have.property('total');
+                done();
+            });
+    });
+
+    it('should list class with valid filters', (done) => {
+        chai.request(server)
+            .get('/api/v1/class?filter=state:41')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('name');
+                res.body.result[0].should.have.property('total');
+                done();
+            });
+    });
+
+    it('should list class with valid dimensions', (done) => {
+        chai.request(server)
+            .get('/api/v1/class?dims=region,state,adm_dependency,location&filter=region:1')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('region_name');
+                res.body.result[0].should.have.property('state_name');
+                res.body.result[0].should.have.property('adm_dependency_name');
+                res.body.result[0].should.have.property('location_name');
+                res.body.result[0].should.have.property('total');
+                done();
+            });
+    });
+
+    it('should list class with invalid dimensions', (done) => {
+        chai.request(server)
+            .get('/api/v1/class?dims=foo,bar')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('name');
+                res.body.result[0].should.have.property('total');
+                done();
+            });
+    });
+
+    it('should list class with valid dimensions and filters', (done) => {
+        chai.request(server)
+            .get('/api/v1/class?dims=region,state,education_level_mod,school,period&filter=min_year:2015,max_year:2015,city:4106902')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('region_name');
+                res.body.result[0].should.have.property('state_name');
+                res.body.result[0].should.have.property('school_name');
+                res.body.result[0].should.have.property('education_level_mod_name');
+                res.body.result[0].should.have.property('total');
+                res.body.result[0].should.have.property('year');
+                done();
+            });
+    });
+
+    it('should list class with dimension rural_location', (done) => {
+        chai.request(server)
+            .get('/api/v1/class?dims=rural_location')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('rural_location_name');
+                done();
+            });
+    });
+
+    it('should list enrollment with dimension education_level_short', (done) => {
+        chai.request(server)
+            .get('/api/v1/class?dims=education_level_short')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('education_level_short_name');
+                done();
+            });
+    });
+});
diff --git a/src/test/classroom.js b/src/test/classroom.js
new file mode 100644
index 0000000000000000000000000000000000000000..9a6fd00a18afe4f1c584bea4f033dd0dcb3a84a6
--- /dev/null
+++ b/src/test/classroom.js
@@ -0,0 +1,291 @@
+process.env.NODE_ENV = 'test';
+
+const chai = require('chai');
+
+const dirtyChai = require('dirty-chai');
+
+chai.use(dirtyChai);
+
+const chaiXml = require('chai-xml');
+
+chai.use(chaiXml);
+
+const chaiHttp = require('chai-http');
+
+const assert = chai.assert;
+
+const expect = chai.expect;
+
+const should = chai.should(); // actually call the function
+
+const libs = `${process.cwd()}/libs`;
+
+const server = require(`${libs}/app`);
+
+chai.use(chaiHttp);
+describe('request classrooms', () => {
+
+    it('should list the locations', (done) => {
+        chai.request(server)
+            .get('/api/v1/classroom/location')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('id');
+                res.body.result[0].should.have.property('name');
+                done();
+            });
+    });
+
+	it('should list the administrative dependencies', (done) => {
+		chai.request(server)
+			.get('/api/v1/classroom/adm_dependency')
+			.end((err, res) => {
+				res.should.have.status(200);
+				res.should.be.json;
+				res.body.should.have.property('result');
+				res.body.result.should.be.a('array');
+				res.body.result[0].should.have.property('id');
+				res.body.result[0].should.have.property('name');
+				done();
+			});
+	});
+
+	it('should list the detailed administrative dependencies', (done) => {
+		chai.request(server)
+			.get('/api/v1/classroom/adm_dependency_detailed')
+			.end((err, res) => {
+				res.should.have.status(200);
+				res.should.be.json;
+				res.body.should.have.property('result');
+				res.body.result.should.be.a('array');
+				res.body.result[0].should.have.property('id');
+				res.body.result[0].should.have.property('name');
+				done();
+			});
+	});
+
+    it('should list classrooms', (done) => {
+        chai.request(server)
+            .get('/api/v1/classroom')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('name');
+                res.body.result[0].should.have.property('total');
+                done();
+            });
+    });
+
+    it('should list classrooms with valid filters', (done) => {
+        chai.request(server)
+            .get('/api/v1/classroom?filter=city:4106902')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('name');
+                res.body.result[0].should.have.property('total');
+                done();
+            });
+    });
+
+    it('should list classrooms with valid filters', (done) => {
+        chai.request(server)
+            .get('/api/v1/classroom?filter=state:41')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('name');
+                res.body.result[0].should.have.property('total');
+                done();
+            });
+    });
+
+    it('should list classrooms with year range', (done) => {
+        chai.request(server)
+            .get('/api/v1/classroom/years')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('year');
+                done();
+            });
+    });
+
+    it('should list classrooms with year range', (done) => {
+        chai.request(server)
+            .get('/api/v1/classroom/source')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('source');
+                done();
+            });
+    });
+
+    it('should list classrooms with valid filters', (done) => {
+        chai.request(server)
+            .get('/api/v1/classroom?filter=region:4')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('name');
+                res.body.result[0].should.have.property('total');
+                done();
+            });
+    });
+
+    it('should list classrooms with valid filters', (done) => {
+        chai.request(server)
+            .get('/api/v1/classroom?filter=adm_dependency:3')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('name');
+                res.body.result[0].should.have.property('total');
+                done();
+            });
+    });
+
+    it('should list classrooms with valid filters', (done) => {
+        chai.request(server)
+            .get('/api/v1/classroom?filter=adm_dependency_detailed:5')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('name');
+                res.body.result[0].should.have.property('total');
+                done();
+            });
+    });
+
+    it('should list classrooms with valid filters', (done) => {
+        chai.request(server)
+            .get('/api/v1/classroom?filter=location:1')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('name');
+                res.body.result[0].should.have.property('total');
+                done();
+            });
+    });
+
+    it('should list classrooms with valid dimensions', (done) => {
+        chai.request(server)
+            .get('/api/v1/classroom?dims=city')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('city_name');
+                res.body.result[0].should.have.property('total');
+                done();
+            });
+    });
+
+    it('should list classrooms with valid dimensions', (done) => {
+        chai.request(server)
+            .get('/api/v1/classroom?dims=state')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('state_name');
+                res.body.result[0].should.have.property('total');
+                done();
+            });
+    });
+
+    it('should list classrooms with valid dimensions', (done) => {
+        chai.request(server)
+            .get('/api/v1/classroom?dims=region')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('region_name');
+                res.body.result[0].should.have.property('total');
+                done();
+            });
+    });
+
+    it('should list classrooms with valid dimensions', (done) => {
+        chai.request(server)
+            .get('/api/v1/classroom?dims=adm_dependency')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('adm_dependency_name');
+                res.body.result[0].should.have.property('total');
+                done();
+            });
+    });
+
+    it('should list classrooms with valid dimensions', (done) => {
+        chai.request(server)
+            .get('/api/v1/classroom?dims=adm_dependency_detailed')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('adm_dependency_detailed_name');
+                res.body.result[0].should.have.property('total');
+                done();
+            });
+    });
+
+    it('should list classrooms with valid dimensions', (done) => {
+        chai.request(server)
+            .get('/api/v1/classroom?dims=location')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('name');
+                res.body.result[0].should.have.property('total');
+                done();
+            });
+    });
+
+    it('should list classrooms with invalid dimensions', (done) => {
+        chai.request(server)
+            .get('/api/v1/classroom?dims=foo,bar')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('name');
+                res.body.result[0].should.have.property('total');
+                done();
+            });
+    });
+});
diff --git a/src/test/distributionFactor.js b/src/test/distributionFactor.js
new file mode 100644
index 0000000000000000000000000000000000000000..e0531c6bfa03ee0d3aba2e49bb3c92e1f3510f13
--- /dev/null
+++ b/src/test/distributionFactor.js
@@ -0,0 +1,74 @@
+process.env.NODE_ENV = 'test';
+
+const chai = require('chai');
+
+const dirtyChai = require('dirty-chai');
+
+chai.use(dirtyChai);
+
+const chaiXml = require('chai-xml');
+
+chai.use(chaiXml);
+
+const chaiHttp = require('chai-http');
+
+const assert = chai.assert;
+
+const expect = chai.expect;
+
+const should = chai.should(); // actually call the function
+
+const libs = `${process.cwd()}/libs`;
+
+const server = require(`${libs}/app`);
+
+chai.use(chaiHttp);
+describe('request distribution factor', () => {
+    it('should list default distribution factor', (done) => {
+        chai.request(server)
+            .get('/api/v1/distribution_factor')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('city_id');
+                res.body.result[0].should.have.property('series');
+                res.body.result[0].series[0].should.have.property('serie_id');
+                res.body.result[0].series[0].should.have.property('distribution_factor_reduction');
+                done();
+            });
+    });
+
+    it('should list distribution factor with valid filters', (done) => {
+        chai.request(server)
+            .get('/api/v1/distribution_factor?filter=state:41,city:4100103')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('city_id');
+                res.body.result[0].should.have.property('series');
+                res.body.result[0].series[0].should.have.property('serie_id');
+                done();
+            });
+    });
+
+    it('should list distribution factor with valid filters', (done) => {
+        chai.request(server)
+            .get('/api/v1/distribution_factor?filter=city:1100023')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('city_id');
+                res.body.result[0].should.have.property('series');
+                res.body.result[0].series[0].should.have.property('serie_id');
+                res.body.result[0].series[0].should.have.property('distribution_factor_addition');
+                done();
+            });
+    });
+
+});
diff --git a/src/test/educationYears.js b/src/test/educationYears.js
new file mode 100644
index 0000000000000000000000000000000000000000..7a47b3079c827bf9e47d1392e08522ce74874402
--- /dev/null
+++ b/src/test/educationYears.js
@@ -0,0 +1,43 @@
+process.env.NODE_ENV = 'test';
+
+const chai = require('chai');
+
+const dirtyChai = require('dirty-chai');
+
+chai.use(dirtyChai);
+
+const chaiXml = require('chai-xml');
+
+chai.use(chaiXml);
+
+const chaiHttp = require('chai-http');
+
+const assert = chai.assert;
+
+const expect = chai.expect;
+
+const should = chai.should(); // actually call the function
+
+const libs = `${process.cwd()}/libs`;
+
+const server = require(`${libs}/app`);
+
+chai.use(chaiHttp);
+describe('request education years', () => {
+    it('should list default education years', (done) => {
+        chai.request(server)
+            .get('/api/v1/education_years')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('id');
+                res.body.result[0].should.have.property('name');
+                res.body.result[0].should.have.property('schoolYears');
+                res.body.result[0].schoolYears.should.be.a('array');
+                res.body.result[0].schoolYears[0].should.have.property('id');
+                done();
+            });
+    });
+});
diff --git a/src/test/enrollment.js b/src/test/enrollment.js
new file mode 100644
index 0000000000000000000000000000000000000000..997e5f70077c2d4b008845d33f452e0b7a9df679
--- /dev/null
+++ b/src/test/enrollment.js
@@ -0,0 +1,478 @@
+process.env.NODE_ENV = 'test';
+
+const chai = require('chai');
+
+const dirtyChai = require('dirty-chai');
+
+chai.use(dirtyChai);
+
+const chaiXml = require('chai-xml');
+
+chai.use(chaiXml);
+
+const chaiHttp = require('chai-http');
+
+const assert = chai.assert;
+
+const expect = chai.expect;
+
+const should = chai.should(); // actually call the function
+
+const libs = `${process.cwd()}/libs`;
+
+const server = require(`${libs}/app`);
+
+chai.use(chaiHttp);
+describe('request enrollments', () => {
+    it('should list the year range', (done) => {
+        chai.request(server)
+            .get('/api/v1/enrollment/year_range')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('start_year');
+                res.body.result[0].should.have.property('end_year');
+                done();
+            });
+    });
+
+    it('should list the source', (done) => {
+        chai.request(server)
+            .get('/api/v1/enrollment/source')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('source');
+                done();
+            });
+    });
+
+    it('should list the years', (done) => {
+        chai.request(server)
+            .get('/api/v1/enrollment/years')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('year');
+                done();
+            });
+    });
+
+    it('should list the locations', (done) => {
+        chai.request(server)
+            .get('/api/v1/enrollment/location')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('id');
+                res.body.result[0].should.have.property('name');
+                done();
+            });
+    });
+
+    it('should list the rural locations', (done) => {
+        chai.request(server)
+            .get('/api/v1/enrollment/rural_location')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('id');
+                res.body.result[0].should.have.property('name');
+                done();
+            });
+    });
+
+    it('should list the school year', (done) => {
+        chai.request(server)
+            .get('/api/v1/enrollment/school_year')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('id');
+                res.body.result[0].should.have.property('name');
+                done();
+            });
+    });
+
+    it('should list the education level', (done) => {
+        chai.request(server)
+            .get('/api/v1/enrollment/education_level')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('id');
+                res.body.result[0].should.have.property('name');
+                done();
+            });
+    });
+
+    it('should list the education level mod', (done) => {
+        chai.request(server)
+            .get('/api/v1/enrollment/education_level_mod')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('id');
+                res.body.result[0].should.have.property('name');
+                done();
+            });
+    });
+
+    it('should list the education level short', (done) => {
+        chai.request(server)
+            .get('/api/v1/enrollment/education_level_short')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('id');
+                res.body.result[0].should.have.property('name');
+                done();
+            });
+    });
+
+    it('should list the administrative dependencies', (done) => {
+        chai.request(server)
+            .get('/api/v1/enrollment/adm_dependency')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('id');
+                res.body.result[0].should.have.property('name');
+                done();
+            });
+    });
+
+    it('should list the administrative dependencies detailed', (done) => {
+        chai.request(server)
+            .get('/api/v1/enrollment/adm_dependency_detailed')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('id');
+                res.body.result[0].should.have.property('name');
+                done();
+            });
+    });
+
+    it('should list genders', (done) => {
+        chai.request(server)
+            .get('/api/v1/enrollment/gender')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('id');
+                res.body.result[0].should.have.property('name');
+                done();
+            });
+    });
+
+    it('should list the ethnic groups', (done) => {
+        chai.request(server)
+            .get('/api/v1/enrollment/ethnic_group')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('id');
+                res.body.result[0].should.have.property('name');
+                done();
+            });
+    });
+
+    it('should list the periods', (done) => {
+        chai.request(server)
+            .get('/api/v1/enrollment/period')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('id');
+                res.body.result[0].should.have.property('name');
+                done();
+            });
+    });
+
+    it('should list the integral time', (done) => {
+        chai.request(server)
+            .get('/api/v1/enrollment/integral_time')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('id');
+                res.body.result[0].should.have.property('name');
+                done();
+            });
+    });
+
+    it('should list enrollments', (done) => {
+        chai.request(server)
+            .get('/api/v1/enrollment')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('name');
+                res.body.result[0].should.have.property('total');
+                done();
+            });
+    });
+
+    it('should list enrollments with valid filters', (done) => {
+        chai.request(server)
+            .get('/api/v1/enrollment?filter=min_year:2014,state:41')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('name');
+                res.body.result[0].should.have.property('total');
+                done();
+            });
+    });
+
+    it('should list enrollments with invalid filters', (done) => {
+        chai.request(server)
+            .get('/api/v1/enrollment?filter=foo:2010,bar:41')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('name');
+                res.body.result[0].should.have.property('total');
+                done();
+            });
+    });
+
+    it('should list enrollments with invalid dimensions', (done) => {
+        chai.request(server)
+            .get('/api/v1/enrollment?dims=foo,bar')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('name');
+                res.body.result[0].should.have.property('total');
+                done();
+            });
+    });
+
+    it('should list enrollments with multivalue filter and single value filter', (done) => {
+        chai.request(server)
+            .get('/api/v1/enrollment?filter=region:[1,2],min_year:2015')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('name');
+                res.body.result[0].should.have.property('total');
+                done();
+            });
+    });
+
+    it('should list enrollments with valid dimensions and filters', (done) => {
+        chai.request(server)
+            .get('/api/v1/enrollment?dims=region,state,school_year,school,gender,period&filter=min_year:2015,max_year:2015,city:4106902')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('region_name');
+                res.body.result[0].should.have.property('state_name');
+                res.body.result[0].should.have.property('school_name');
+                res.body.result[0].should.have.property('school_year_name');
+                res.body.result[0].should.have.property('total');
+                res.body.result[0].should.have.property('year');
+                done();
+            });
+    });
+
+    it('should list enrollment with dimension location', (done) => {
+        chai.request(server)
+            .get('/api/v1/enrollment?dims=location')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('location_name');
+                done();
+            });
+    });
+
+    it('should list enrollment with dimension rural_location', (done) => {
+        chai.request(server)
+            .get('/api/v1/enrollment?dims=rural_location')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('rural_location_name');
+                done();
+            });
+    });
+
+    it('should list enrollment with dimension school year', (done) => {
+        chai.request(server)
+            .get('/api/v1/enrollment?dims=school_year')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('school_year_name');
+                done();
+            });
+    });
+
+    it('should list enrollment with dimension education_level', (done) => {
+        chai.request(server)
+            .get('/api/v1/enrollment?dims=education_level')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('education_level_name');
+                done();
+            });
+    });
+
+    it('should list enrollment with dimension education_level_mod', (done) => {
+        chai.request(server)
+            .get('/api/v1/enrollment?dims=education_level_mod')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('education_level_mod_name');
+                done();
+            });
+    });
+
+    it('should list enrollment with dimension education_level_short', (done) => {
+        chai.request(server)
+            .get('/api/v1/enrollment?dims=education_level_short')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('education_level_short_name');
+                done();
+            });
+    });
+
+    it('should list enrollment with dimension adm_dependency', (done) => {
+        chai.request(server)
+            .get('/api/v1/enrollment?dims=adm_dependency')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('adm_dependency_name');
+                done();
+            });
+    });
+
+    it('should list enrollment with dimension adm_dependency_detailed', (done) => {
+        chai.request(server)
+            .get('/api/v1/enrollment?dims=adm_dependency_detailed')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('adm_dependency_detailed_name');
+                done();
+            });
+    });
+
+    it('should list enrollment with dimension gender', (done) => {
+        chai.request(server)
+            .get('/api/v1/enrollment?dims=gender')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('gender_name');
+                done();
+            });
+    });
+
+    it('should list enrollment with dimension ethnic_group', (done) => {
+        chai.request(server)
+            .get('/api/v1/enrollment?dims=ethnic_group')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('ethnic_group_name');
+                done();
+            });
+    });
+
+    it('should list enrollment with dimension period', (done) => {
+        chai.request(server)
+            .get('/api/v1/enrollment?dims=period')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('period_name');
+                done();
+            });
+    });
+
+    it('should list enrollment with dimension integral_time', (done) => {
+        chai.request(server)
+            .get('/api/v1/enrollment?dims=integral_time')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('integral_time_name');
+                done();
+            });
+    });
+});
diff --git a/src/test/id2str.js b/src/test/id2str.js
new file mode 100644
index 0000000000000000000000000000000000000000..153768988421d52127e8ff7f3f9a9b0d5800d4d2
--- /dev/null
+++ b/src/test/id2str.js
@@ -0,0 +1,100 @@
+process.env.NODE_ENV = 'test';
+
+const chai = require('chai');
+
+const dirtyChai = require('dirty-chai');
+
+chai.use(dirtyChai);
+
+const chaiXml = require('chai-xml');
+
+chai.use(chaiXml);
+
+const chaiHttp = require('chai-http');
+
+const assert = chai.assert;
+
+const expect = chai.expect;
+
+const should = chai.should(); // actually call the function
+
+const libs = `${process.cwd()}/libs`;
+
+const server = require(`${libs}/app`);
+
+const id2str = require(`${libs}/middlewares/id2str`);
+
+chai.use(chaiHttp);
+
+describe('id2str middleware', () => {
+    let req, res;
+    it('should return a function', (done) => {
+        expect(id2str.transform).to.be.a.Function;
+        done();
+    });
+
+    it('should transform a gender id', (done) => {
+        expect(id2str.gender(1)).to.deep.equal('Masculino');
+        done();
+    });
+
+    it('should transform a period id', (done) => {
+        expect(id2str.period(1)).to.deep.equal('Matutino');
+        done();
+    });
+
+    it('should transform a location id', (done) => {
+        expect(id2str.location(6)).to.deep.equal('Unidade de uso sustentável');
+        expect(id2str.location(4)).to.deep.equal('Terra indígena');
+        expect(id2str.location(5)).to.deep.equal('Área remanescente de quilombos');
+        done();
+    });
+
+    it('should transform a school year id', (done) => {
+        expect(id2str.schoolYear(11)).to.deep.equal('Creche - Menor de 1 ano');
+        done();
+    });
+
+
+    it('should transform a income level id', (done) => {
+        expect(id2str.incomeLevel(1)).to.deep.equal('1º quintil – 20% menores');
+        done();
+    });  
+
+    it('should transform a class adm dependency id', (done) => {
+        expect(id2str.admDependency(6)).to.deep.equal('Não classificada');
+        done();
+    });
+
+    it('should transform a class adm dependency priv id', (done) => {
+        expect(id2str.admDependencyPriv(7)).to.deep.equal('Não classificada');
+
+        done();
+    });
+
+    it('should transform a result', (done) => {
+        let req = {
+            result: [{gender_id: 2, period_id: 3, school_year_id: 11}]
+        };
+        id2str.transform(false)(req, {},  (error)=> {
+            if (error) { throw new Error('Expected not to receive an error'); }
+            req.should.have.property('result');
+            req.result.should.not.be.undefined;
+            req.result.should.be.deep.equal([{gender_id: 2, period_id: 3, school_year_id: 11, gender_name: 'Feminino', period_name: 'Noturno', school_year_name: 'Creche - Menor de 1 ano'}]);
+            done();
+        });
+    });
+
+    it('should transform a result and delete the ids', (done) => {
+        let req = {
+            result: [{gender_id: 2, period_id: 3, school_year_id: 11}]
+        };
+        id2str.transform(true)(req, {},  (error)=>{
+            if (error) { throw new Error('Expected not to receive an error'); }
+            req.should.have.property('result');
+            req.result.should.not.be.undefined;
+            req.result.should.be.deep.equal([{gender_name: 'Feminino', period_name: 'Noturno', school_year_name: 'Creche - Menor de 1 ano'}]);
+            done();
+        });
+    });
+});
diff --git a/src/test/idhm.js b/src/test/idhm.js
new file mode 100644
index 0000000000000000000000000000000000000000..cea8893e9a4bbe92b6028615f3c8c841d9abdca1
--- /dev/null
+++ b/src/test/idhm.js
@@ -0,0 +1,194 @@
+process.env.NODE_ENV = 'test';
+
+const chai = require('chai');
+
+const dirtyChai = require('dirty-chai');
+
+chai.use(dirtyChai);
+
+const chaiXml = require('chai-xml');
+
+chai.use(chaiXml);
+
+const chaiHttp = require('chai-http');
+
+const assert = chai.assert;
+
+const expect = chai.expect;
+
+const should = chai.should(); // actually call the function
+
+const libs = `${process.cwd()}/libs`;
+
+const server = require(`${libs}/app`);
+
+chai.use(chaiHttp);
+describe('request idhm', () => {
+    it('should list the year range', (done) => {
+        chai.request(server)
+            .get('/api/v1/idhm/year_range')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('start_year');
+                res.body.result[0].should.have.property('end_year');
+                done();
+            });
+    });
+
+    it('should list the year range', (done) => {
+        chai.request(server)
+            .get('/api/v1/idhm/years')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('year');
+                done();
+            });
+    });
+
+    it('should list the idhm levels', (done) => {
+        chai.request(server)
+            .get('/api/v1/idhm/idhm_level')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('id');
+                res.body.result[0].should.have.property('name');
+                done();
+            });
+    });
+
+    it('should list the source', (done) => {
+        chai.request(server)
+            .get('/api/v1/idhm/source')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('source');
+                done();
+            });
+    });
+
+    it('should return 400 with no filters', function (done) {
+        chai.request(server).get('/api/v1/idhm').end(function (err, res) {
+            res.should.have.status(400);
+            res.should.be.json;
+            res.body.should.have.property('error');
+            res.body.error.should.be.equal('Wrong/No filter specified');
+            done();
+        });
+    });
+
+    it('should list idhm with valid filters', (done) => {
+        chai.request(server)
+            .get('/api/v1/idhm?filter=min_year:2000,state:41')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('total');
+                res.body.result[0].should.have.property('year');
+                res.body.result[0].should.have.property('state_id');
+                res.body.result[0].should.have.property('state_name');
+                done();
+            });
+    });
+
+    it('should list idhm with valid dims', (done) => {
+        chai.request(server)
+            .get('/api/v1/idhm?dims=city')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('total');
+                res.body.result[0].should.have.property('year');
+                res.body.result[0].should.have.property('city_id');
+                res.body.result[0].should.have.property('state_id');
+                res.body.result[0].should.have.property('city_name');
+                res.body.result[0].should.have.property('state_name');
+                done();
+            });
+    });
+
+    it('should list idhm with valid dims', (done) => {
+        chai.request(server)
+            .get('/api/v1/idhm?dims=state')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('total');
+                res.body.result[0].should.have.property('year');
+                res.body.result[0].should.have.property('state_id');
+                res.body.result[0].should.have.property('state_name');
+                done();
+            });
+    });
+
+    it('should list idhm with valid filtes and dims', (done) => {
+        chai.request(server)
+            .get('/api/v1/idhm?filter=state:41&dims=idhm_level')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('total');
+                res.body.result[0].should.have.property('year');
+                res.body.result[0].should.have.property('state_id');
+                res.body.result[0].should.have.property('idhm_level_id');
+                res.body.result[0].should.have.property('state_name');
+                res.body.result[0].should.have.property('idhm_level_name');
+                done();
+            })
+    });
+
+    it('should list idhm with valid filtes and dims', (done) => {
+        chai.request(server)
+            .get('/api/v1/idhm?filter=city:4100202')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('total');
+                res.body.result[0].should.have.property('year');
+                res.body.result[0].should.have.property('city_id');
+                res.body.result[0].should.have.property('state_id');
+                res.body.result[0].should.have.property('state_name');
+                done();
+            })
+    });
+
+    it('should list idhm with valid filtes and dims', (done) => {
+        chai.request(server)
+            .get('/api/v1/idhm?dims=idhm_level,state')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('total');
+                res.body.result[0].should.have.property('year');
+                res.body.result[0].should.have.property('state_id');
+                res.body.result[0].should.have.property('state_name');
+                res.body.result[0].should.have.property('idhm_level_name');
+                res.body.result[0].should.have.property('idhm_level_id');
+                done();
+            })
+    });
+
+});
diff --git a/src/test/idhme.js b/src/test/idhme.js
new file mode 100644
index 0000000000000000000000000000000000000000..dc338d38adbbb0ce8362a0a9f1e950671b49295b
--- /dev/null
+++ b/src/test/idhme.js
@@ -0,0 +1,138 @@
+process.env.NODE_ENV = 'test';
+
+const chai = require('chai');
+
+const dirtyChai = require('dirty-chai');
+
+chai.use(dirtyChai);
+
+const chaiXml = require('chai-xml');
+
+chai.use(chaiXml);
+
+const chaiHttp = require('chai-http');
+
+const assert = chai.assert;
+
+const expect = chai.expect;
+
+const should = chai.should(); // actually call the function
+
+const libs = `${process.cwd()}/libs`;
+
+const server = require(`${libs}/app`);
+
+chai.use(chaiHttp);
+describe('request idhme', () => {
+    it('should list the year range', (done) => {
+        chai.request(server)
+            .get('/api/v1/idhme/year_range')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('start_year');
+                res.body.result[0].should.have.property('end_year');
+                done();
+            });
+    });
+
+    it('should list the available years', (done) => {
+        chai.request(server)
+            .get('/api/v1/idhme/years')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('year');
+                done();
+            });
+    });
+
+    it('should list the source', (done) => {
+        chai.request(server)
+            .get('/api/v1/idhme/source')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('source');
+                done();
+            });
+    });
+
+    it('should list idhme with valid filters', (done) => {
+        chai.request(server)
+            .get('/api/v1/idhme?filter=min_year:2000,state:41')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('total');
+                res.body.result[0].should.have.property('year');
+                res.body.result[0].should.have.property('state_id');
+                done();
+            });
+    });
+
+    it('should list idhme with invalid filters', (done) => {
+        chai.request(server)
+            .get('/api/v1/idhme?filter=foo:2010,bar:41')
+            .end((err, res) => {
+                res.should.have.status(400);
+                res.should.be.json;
+                res.body.should.have.property('error');
+                res.body.error.should.be.equal('Wrong/No filter specified');
+                done();
+            });
+    });
+
+    it('should list idhme with valid dimensions', (done) => {
+        chai.request(server)
+            .get('/api/v1/idhme?dims=state&filter=min_year:2010')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('total');
+                res.body.result[0].should.have.property('year');
+                res.body.result[0].should.have.property('state_id');
+                res.body.result[0].should.have.property('state_name');
+                done();
+            });
+    });
+
+    it('should list idhme with valid filters and dimensions', (done) => {
+        chai.request(server)
+            .get('/api/v1/idhme?filter=state:41,min_year:2010&dims=city')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('total');
+                res.body.result[0].should.have.property('year');
+                res.body.result[0].should.have.property('city_id');
+                res.body.result[0].should.have.property('city_name');
+                done();
+            });
+    });
+
+    it('should return 400 with no filters', (done) => {
+        chai.request(server)
+            .get('/api/v1/idhme')
+            .end((err, res) => {
+                res.should.have.status(400);
+                res.should.be.json;
+                res.body.should.have.property('error');
+                res.body.error.should.be.equal('Wrong/No filter specified');
+                done();
+            });
+    });
+
+});
diff --git a/src/test/idhml.js b/src/test/idhml.js
new file mode 100644
index 0000000000000000000000000000000000000000..90312b0684f5b033d98e4f1b281a5e716a372cbd
--- /dev/null
+++ b/src/test/idhml.js
@@ -0,0 +1,138 @@
+process.env.NODE_ENV = 'test';
+
+const chai = require('chai');
+
+const dirtyChai = require('dirty-chai');
+
+chai.use(dirtyChai);
+
+const chaiXml = require('chai-xml');
+
+chai.use(chaiXml);
+
+const chaiHttp = require('chai-http');
+
+const assert = chai.assert;
+
+const expect = chai.expect;
+
+const should = chai.should(); // actually call the function
+
+const libs = `${process.cwd()}/libs`;
+
+const server = require(`${libs}/app`);
+
+chai.use(chaiHttp);
+describe('request idhml', () => {
+    it('should list the year range', (done) => {
+        chai.request(server)
+            .get('/api/v1/idhml/year_range')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('start_year');
+                res.body.result[0].should.have.property('end_year');
+                done();
+            });
+    });
+
+    it('should list the available years', (done) => {
+        chai.request(server)
+            .get('/api/v1/idhml/years')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('year');
+                done();
+            });
+    });
+
+    it('should list the source', (done) => {
+        chai.request(server)
+            .get('/api/v1/idhml/source')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('source');
+                done();
+            });
+    });
+
+    it('should list idhml with valid filters', (done) => {
+        chai.request(server)
+            .get('/api/v1/idhml?filter=min_year:2000,state:41')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('total');
+                res.body.result[0].should.have.property('year');
+                res.body.result[0].should.have.property('state_id');
+                done();
+            });
+    });
+
+    it('should list idhml with invalid filters', (done) => {
+        chai.request(server)
+            .get('/api/v1/idhml?filter=foo:2010,bar:41')
+            .end((err, res) => {
+                res.should.have.status(400);
+                res.should.be.json;
+                res.body.should.have.property('error');
+                res.body.error.should.be.equal('Wrong/No filter specified');
+                done();
+            });
+    });
+
+    it('should list idhml with valid dimensions', (done) => {
+        chai.request(server)
+        .get('/api/v1/idhml?dims=state&filter=min_year:2010')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('total');
+                res.body.result[0].should.have.property('year');
+                res.body.result[0].should.have.property('state_id');
+                res.body.result[0].should.have.property('state_name');
+                done();
+            });
+    });
+
+    it('should list idhml with valid filters and dimensions', (done) => {
+        chai.request(server)
+            .get('/api/v1/idhml?filter=state:41,min_year:2010&dims=city')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('total');
+                res.body.result[0].should.have.property('year');
+                res.body.result[0].should.have.property('city_id');
+                res.body.result[0].should.have.property('city_name');
+                done();
+            });
+    });
+
+    it('should return 400 with no filters', (done) => {
+        chai.request(server)
+            .get('/api/v1/idhml')
+            .end((err, res) => {
+                res.should.have.status(400);
+                res.should.be.json;
+                res.body.should.have.property('error');
+                res.body.error.should.be.equal('Wrong/No filter specified');
+                done();
+            });
+    });
+
+});
diff --git a/src/test/idhmr.js b/src/test/idhmr.js
new file mode 100644
index 0000000000000000000000000000000000000000..eb06b37d0da0e2c5b03cf245906a4edb4e3c035e
--- /dev/null
+++ b/src/test/idhmr.js
@@ -0,0 +1,138 @@
+process.env.NODE_ENV = 'test';
+
+const chai = require('chai');
+
+const dirtyChai = require('dirty-chai');
+
+chai.use(dirtyChai);
+
+const chaiXml = require('chai-xml');
+
+chai.use(chaiXml);
+
+const chaiHttp = require('chai-http');
+
+const assert = chai.assert;
+
+const expect = chai.expect;
+
+const should = chai.should(); // actually call the function
+
+const libs = `${process.cwd()}/libs`;
+
+const server = require(`${libs}/app`);
+
+chai.use(chaiHttp);
+describe('request idhmr', () => {
+    it('should list the year range', (done) => {
+        chai.request(server)
+            .get('/api/v1/idhmr/year_range')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('start_year');
+                res.body.result[0].should.have.property('end_year');
+                done();
+            });
+    });
+
+    it('should list the source', (done) => {
+        chai.request(server)
+            .get('/api/v1/idhmr/source')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('source');
+                done();
+            });
+    });
+
+    it('should list the available years', (done) => {
+        chai.request(server)
+            .get('/api/v1/idhmr/years')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('year');
+                done();
+            });
+    });
+
+    it('should list idhmr with valid filters', (done) => {
+        chai.request(server)
+            .get('/api/v1/idhmr?filter=min_year:2000,state:41')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('total');
+                res.body.result[0].should.have.property('year');
+                res.body.result[0].should.have.property('state_id');
+                done();
+            });
+    });
+
+    it('should list idhmr with invalid filters', (done) => {
+        chai.request(server)
+            .get('/api/v1/idhmr?filter=foo:2010,bar:41')
+            .end((err, res) => {
+                res.should.have.status(400);
+                res.should.be.json;
+                res.body.should.have.property('error');
+                res.body.error.should.be.equal('Wrong/No filter specified');
+                done();
+            });
+    });
+
+    it('should list idhmr with valid dimensions', (done) => {
+        chai.request(server)
+            .get('/api/v1/idhmr?dims=state&filter=min_year:2010')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('total');
+                res.body.result[0].should.have.property('year');
+                res.body.result[0].should.have.property('state_id');
+                res.body.result[0].should.have.property('state_name');
+                done();
+            });
+    });
+
+    it('should list idhmr with valid filters and dimensions', (done) => {
+        chai.request(server)
+            .get('/api/v1/idhmr?filter=state:41,min_year:2010&dims=city')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('total');
+                res.body.result[0].should.have.property('year');
+                res.body.result[0].should.have.property('city_id');
+                res.body.result[0].should.have.property('city_name');
+                done();
+            });
+    });
+
+    it('should return 400 with no filters', (done) => {
+        chai.request(server)
+            .get('/api/v1/idhmr')
+            .end((err, res) => {
+                res.should.have.status(400);
+                res.should.be.json;
+                res.body.should.have.property('error');
+                res.body.error.should.be.equal('Wrong/No filter specified');
+                done();
+            });
+    });
+
+});
diff --git a/src/test/infrastructure.js b/src/test/infrastructure.js
new file mode 100644
index 0000000000000000000000000000000000000000..c92fda1e1b4ae003beff20c202d852fa997eb115
--- /dev/null
+++ b/src/test/infrastructure.js
@@ -0,0 +1,122 @@
+process.env.NODE_ENV = 'test';
+
+const chai = require('chai');
+
+const dirtyChai = require('dirty-chai');
+
+chai.use(dirtyChai);
+
+const chaiXml = require('chai-xml');
+
+chai.use(chaiXml);
+
+const chaiHttp = require('chai-http');
+
+const assert = chai.assert;
+
+const expect = chai.expect;
+
+const should = chai.should(); // actually call the function
+
+const libs = `${process.cwd()}/libs`;
+
+const server = require(`${libs}/app`);
+
+chai.use(chaiHttp);
+describe('request infrastructure', () => {
+    it('should list default query infrastructure', (done) => {
+        chai.request(server)
+            .get('/api/v1/infrastructure')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('school_place');
+                done();
+            });
+    });
+
+    it('should list the year range', (done) => {
+        chai.request(server)
+            .get('/api/v1/infrastructure/years')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('year');
+                done();
+            });
+    });
+
+    it('should list the source', (done) => {
+        chai.request(server)
+            .get('/api/v1/infrastructure/source')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('source');
+                done();
+            });
+    });
+
+    it('should list the rural locations', (done) => {
+        chai.request(server)
+            .get('/api/v1/infrastructure/rural_location')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('id');
+                res.body.result[0].should.have.property('name');
+                done();
+            });
+    });
+
+    it('should list the locations', (done) => {
+        chai.request(server)
+            .get('/api/v1/infrastructure/location')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('id');
+                res.body.result[0].should.have.property('name');
+                done();
+            });
+    });
+
+    it('should list the source', (done) => {
+        chai.request(server)
+            .get('/api/v1/infrastructure/adm_dependency_detailed')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('id');
+                res.body.result[0].should.have.property('name');
+                done();
+            });
+    });
+
+    it('should list the source', (done) => {
+        chai.request(server)
+            .get('/api/v1/infrastructure/adm_dependency')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('id');
+                res.body.result[0].should.have.property('name');
+                done();
+            });
+    });
+
+});
diff --git a/src/test/pibpercapita.js b/src/test/pibpercapita.js
new file mode 100644
index 0000000000000000000000000000000000000000..e044b1c0ad631c7f236a9a67b9a50c2a111863f8
--- /dev/null
+++ b/src/test/pibpercapita.js
@@ -0,0 +1,128 @@
+process.env.NODE_ENV = 'test';
+
+const chai = require('chai');
+
+const dirtyChai = require('dirty-chai');
+
+chai.use(dirtyChai);
+
+const chaiXml = require('chai-xml');
+
+chai.use(chaiXml);
+
+const chaiHttp = require('chai-http');
+
+const assert = chai.assert;
+
+const expect = chai.expect;
+
+const should = chai.should(); // actually call the function
+
+const libs = `${process.cwd()}/libs`;
+
+const server = require(`${libs}/app`);
+
+chai.use(chaiHttp);
+describe('request pibpercapita', () => {
+    it('should list the year range', (done) => {
+        chai.request(server)
+            .get('/api/v1/pibpercapita/year_range')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('start_year');
+                res.body.result[0].should.have.property('end_year');
+                done();
+            });
+    });
+
+    it('should list the year range', (done) => {
+        chai.request(server)
+            .get('/api/v1/pibpercapita/years')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+
+                res.body.result[0].should.have.property('year');
+                done();
+            });
+    });
+
+    it('should list the source', (done) => {
+        chai.request(server)
+            .get('/api/v1/pibpercapita/source')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('source');
+                done();
+            });
+    })
+
+    it('should list the income level', (done) => {
+        chai.request(server)
+            .get('/api/v1/pibpercapita/income_level')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('id');
+                res.body.result[0].should.have.property('name');
+                done();
+            });
+    })
+
+    it('should list the pib to state', (done) => {
+        chai.request(server)
+            .get('/api/v1/pibpercapita?filter=state:41')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('total');
+                res.body.result[0].should.have.property('year');
+                done();
+            });
+    });
+
+    it('should list the list pib to state and income level', (done) => {
+        chai.request(server)
+            .get('/api/v1/pibpercapita?filter=state:41&dims=income_level')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('total');
+                res.body.result[0].should.have.property('year');
+                res.body.result[0].should.have.property('income_level_name');
+                res.body.result[0].should.have.property('income_level_id');
+                done();
+            });
+    });
+
+    city:4102802
+        it('should list the pib to city', (done) => {
+            chai.request(server)
+                .get('/api/v1/pibpercapita?filter=city:4102802')
+                .end((err, res) => {
+                    res.should.have.status(200);
+                    res.should.be.json;
+                    res.body.should.have.property('result');
+                    res.body.result.should.be.a('array');
+                    res.body.result[0].should.have.property('total');
+                    res.body.result[0].should.have.property('year');
+
+                    done();
+                });
+        });
+        
+});
diff --git a/src/test/population.js b/src/test/population.js
new file mode 100644
index 0000000000000000000000000000000000000000..37be404fceaa84eb4e1b6d6a763096b2d557c675
--- /dev/null
+++ b/src/test/population.js
@@ -0,0 +1,126 @@
+process.env.NODE_ENV = 'test';
+
+const chai = require('chai');
+
+const dirtyChai = require('dirty-chai');
+
+chai.use(dirtyChai);
+
+const chaiXml = require('chai-xml');
+
+chai.use(chaiXml);
+
+const chaiHttp = require('chai-http');
+
+const assert = chai.assert;
+
+const expect = chai.expect;
+
+const should = chai.should(); // actually call the function
+
+const libs = `${process.cwd()}/libs`;
+
+const server = require(`${libs}/app`);
+
+chai.use(chaiHttp);
+describe('request population', () => {
+    it('should list the year range', (done) => {
+        chai.request(server)
+            .get('/api/v1/population/year_range')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('start_year');
+                res.body.result[0].should.have.property('end_year');
+                done();
+            });
+    });
+
+    it('should list the year range', (done) => {
+        chai.request(server)
+            .get('/api/v1/population/years')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('year');
+                done();
+            });
+    });
+
+    it('should list default population', (done) => {
+        chai.request(server)
+            .get('/api/v1/population')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('total');
+                res.body.result[0].should.have.property('year');
+                done();
+            });
+    });
+
+    it('should list default population', (done) => {
+        chai.request(server)
+            .get('/api/v1/population/city_size')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('id');
+                res.body.result[0].should.have.property('name');
+                done();
+            });
+    });
+
+    it('should list population with valid filters', (done) => {
+        chai.request(server)
+            .get('/api/v1/population?filter=state:41')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('total');
+                res.body.result[0].should.have.property('year');
+                done();
+            });
+    });
+
+    it('should list population with valid filters', (done) => {
+        chai.request(server)
+            .get('/api/v1/population?filter=city:4102802')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('total');
+                res.body.result[0].should.have.property('year');
+                done();
+            });
+    });
+
+    it('should list the city size dims', (done) => {
+        chai.request(server)
+            .get('/api/v1/population?dims=city_size')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('total');
+                res.body.result[0].should.have.property('year');
+                res.body.result[0].should.have.property('city_size_id');
+                res.body.result[0].should.have.property('city_size_name');
+                done();
+            });
+    });
+
+});
diff --git a/src/test/query.js b/src/test/query.js
new file mode 100644
index 0000000000000000000000000000000000000000..cad0102ff882c4eb2121cb99c804b3b8b84f0fa4
--- /dev/null
+++ b/src/test/query.js
@@ -0,0 +1,88 @@
+process.env.NODE_ENV = 'test';
+
+const chai = require('chai');
+
+const dirtyChai = require('dirty-chai');
+
+chai.use(dirtyChai);
+
+const chaiXml = require('chai-xml');
+
+chai.use(chaiXml);
+
+const chaiHttp = require('chai-http');
+
+const assert = chai.assert;
+
+const expect = chai.expect;
+
+const should = chai.should(); // actually call the function
+
+const libs = `${process.cwd()}/libs`;
+
+const server = require(`${libs}/app`);
+
+const query = require(`${libs}/middlewares/query`).query;
+
+const squel = require('squel');
+
+chai.use(chaiHttp);
+
+describe('Query middleware', () => {
+    let req, res;
+    it('should return a function', (done) => {
+        expect(query).to.be.a.Function;
+        done();
+    });
+
+    it('should accept three arguments', function(done) {
+      expect(query.length).to.equal(3);
+      done();
+    });
+
+    it('should do a valid query', (done) => {
+        let req = {
+            sql: squel.select().field('1+2')
+        };
+        let res = {};
+        query(req, {},  (error)=>{
+            if (error) { throw new Error('Expected not to receive an error'); }
+            req.should.have.property('result');
+            req.result.should.not.be.undefined;
+            req.result[0].should.have.property('L2');
+            req.result[0].L2.should.be.equal(3);
+            done();
+        });
+    });
+
+    it('should return an error with an invalid query (prepared statement error)', (done) => {
+        let req = {
+            sql: squel.select()
+        };
+        let res = {};
+        query(req, {},  (error)=>{
+            if (error) { done();}
+        });
+    });
+
+    it('should return an error with an invalid query (execution error)', (done) => {
+        let req = {
+            sql: squel.select().from('ibge_pnad').from('ibge_censo')
+        };
+        let res = {};
+        query(req, {},  (error)=>{
+            if (error) { done();}
+        });
+    });
+
+    it('should not return 404 with an empty query result', (done) => {
+        let req = {
+            sql: squel.select().field('*').from('regiao').where('id>6')
+        };
+        let res = {};
+        query(req, {},  (error)=>{
+            req.should.have.property('result');
+            done();
+        });
+    });
+});
diff --git a/src/test/rateSchool.js b/src/test/rateSchool.js
new file mode 100644
index 0000000000000000000000000000000000000000..b029fa42e87e76aca2a65c95bd17400abcd4e40c
--- /dev/null
+++ b/src/test/rateSchool.js
@@ -0,0 +1,247 @@
+process.env.NODE_ENV = 'test';
+
+const chai = require('chai');
+
+const dirtyChai = require('dirty-chai');
+
+chai.use(dirtyChai);
+
+const chaiXml = require('chai-xml');
+
+chai.use(chaiXml);
+
+const chaiHttp = require('chai-http');
+
+const assert = chai.assert;
+
+const expect = chai.expect;
+
+const should = chai.should(); // actually call the function
+
+const libs = `${process.cwd()}/libs`;
+
+const server = require(`${libs}/app`);
+
+chai.use(chaiHttp);
+describe('request rate_school', () => {
+    it('should return 400 with no filters', (done) => {
+        chai.request(server)
+            .get('/api/v1/rate_school')
+            .end((err, res) => {
+                res.should.have.status(400);
+                res.should.be.json;
+                res.body.should.have.property('error');
+                res.body.error.should.be.equal('Wrong/No filter specified');
+                done();
+            })
+    });
+
+    it('should list the year range', (done) => {
+        chai.request(server)
+            .get('/api/v1/rate_school/years')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('year');
+                done();
+            });
+    });
+
+    it('should list the source', (done) => {
+        chai.request(server)
+            .get('/api/v1/rate_school/source')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('source');
+                done();
+            });
+    });
+
+    it('should list the locations', (done) => {
+        chai.request(server)
+            .get('/api/v1/rate_school/location')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('id');
+                res.body.result[0].should.have.property('name');
+                done();
+            });
+    });
+
+    it('should list the ethnic groups', (done) => {
+        chai.request(server)
+            .get('/api/v1/rate_school/ethnic_group_pnad')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('id');
+                res.body.result[0].should.have.property('name');
+                done();
+            });
+    });
+
+    it('should list the age range', (done) => {
+        chai.request(server)
+            .get('/api/v1/rate_school/age_range')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('id');
+                res.body.result[0].should.have.property('name');
+                done();
+            });
+    });
+
+    it('should list the age range', (done) => {
+        chai.request(server)
+            .get('/api/v1/rate_school?dims=age_range')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('age_range_id');
+                res.body.result[0].should.have.property('age_range_name');
+                done();
+            });
+    });
+
+    it('should list the gender', (done) => {
+        chai.request(server)
+            .get('/api/v1/rate_school/gender')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('id');
+                res.body.result[0].should.have.property('name');
+                done();
+            });
+    });
+
+
+    it('should list the dimensions of ethnic groups', (done) => {
+        chai.request(server)
+            .get('/api/v1//rate_school?dims=ethnic_group_pnad&filter=min_year:"2015",max_year:"2015",age_range:["1"]')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('ethnic_group_pnad_id');
+                res.body.result[0].should.have.property('ethnic_group_pnad_name');
+                res.body.result[0].should.have.property('year');
+                res.body.result[0].should.have.property('denominator');
+                res.body.result[0].should.have.property('partial');
+                done();
+            });
+    });
+
+    it('should list the dimensions of gender', (done) => {
+        chai.request(server)
+            .get('/api/v1//rate_school?dims=gender&filter=min_year:"2015",max_year:"2015",age_range:["1"]')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('gender_id');
+                res.body.result[0].should.have.property('gender_name');
+                res.body.result[0].should.have.property('year');
+                res.body.result[0].should.have.property('denominator');
+                res.body.result[0].should.have.property('partial');
+                done();
+            });
+    });
+
+    it('should list fifth household income', (done) => {
+        chai.request(server)
+            .get('/api/v1//rate_school/fifth_household_income')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('id');
+                res.body.result[0].should.have.property('name');
+                done();
+            });
+    });
+
+    it('should list the dimensions of fifth housebold income', (done) => {
+        chai.request(server)
+            .get('/api/v1//rate_school?dims=fifth_household_income&filter=min_year:"2015",max_year:"2015",age_range:["1"]')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('fifth_household_income_id');
+                res.body.result[0].should.have.property('fifth_household_income_name');
+                res.body.result[0].should.have.property('year');
+                res.body.result[0].should.have.property('denominator');
+                res.body.result[0].should.have.property('partial');
+                res.body.result[0].should.have.property('total');
+                done();
+            });
+    });
+
+    it('should list extremes household income', (done) => {
+        chai.request(server)
+            .get('/api/v1//rate_school/extremes_household_income')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('id');
+                res.body.result[0].should.have.property('name');
+                done();
+            });
+    });
+
+    it('should list the dimensions of extremes housebold income', (done) => {
+        chai.request(server)
+            .get('/api/v1//rate_school?dims=extremes_household_income&filter=min_year:"2015",max_year:"2015",age_range:["1"]')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('extremes_household_income_id');
+                res.body.result[0].should.have.property('extremes_household_income_name');
+                res.body.result[0].should.have.property('year');
+                res.body.result[0].should.have.property('denominator');
+                res.body.result[0].should.have.property('partial');
+                res.body.result[0].should.have.property('total');
+                done();
+            });
+    });
+
+    it('should list year range', (done) => {
+        chai.request(server)
+            .get('/api/v1//rate_school/year_range')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('start_year');
+                res.body.result[0].should.have.property('end_year');
+                done();
+            });
+    });
+});
diff --git a/src/test/region.js b/src/test/region.js
new file mode 100644
index 0000000000000000000000000000000000000000..daf45685c7a5298523afdf3e95446a715442429a
--- /dev/null
+++ b/src/test/region.js
@@ -0,0 +1,71 @@
+process.env.NODE_ENV = 'test';
+
+const chai = require('chai');
+
+const dirtyChai = require('dirty-chai');
+
+chai.use(dirtyChai);
+
+const chaiXml = require('chai-xml');
+
+chai.use(chaiXml);
+
+const chaiHttp = require('chai-http');
+
+const assert = chai.assert;
+
+const expect = chai.expect;
+
+const should = chai.should(); // actually call the function
+
+const libs = `${process.cwd()}/libs`;
+
+const server = require(`${libs}/app`);
+
+chai.use(chaiHttp);
+
+describe('request regions', () => {
+    it('should list all regions', (done) => {
+        chai.request(server)
+            .get('/api/v1/region')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('id');
+                res.body.result[0].should.have.property('name');
+                done();
+            });
+    });
+
+    it('should list region by id', (done) => {
+        chai.request(server)
+            .get('/api/v1/region?filter=id:1')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result.should.have.length(1);
+                res.body.result[0].should.have.property('id');
+                res.body.result[0].should.have.property('name');
+                done();
+            });
+    });
+
+    it('shoul search for south region', (done) => {
+        chai.request(server)
+            .get('/api/v1/region?search=name:sul')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result.should.have.length(1);
+                res.body.result[0].should.have.property('id');
+                res.body.result[0].should.have.property('name');
+                done();
+            });
+    });
+});
diff --git a/src/test/response.js b/src/test/response.js
new file mode 100644
index 0000000000000000000000000000000000000000..bdafc9d89eefa01966bc4171ebcbd373924479cf
--- /dev/null
+++ b/src/test/response.js
@@ -0,0 +1,55 @@
+process.env.NODE_ENV = 'test';
+
+const chai = require('chai');
+
+const dirtyChai = require('dirty-chai');
+
+chai.use(dirtyChai);
+
+const chaiXml = require('chai-xml');
+
+chai.use(chaiXml);
+
+const chaiHttp = require('chai-http');
+
+const assert = chai.assert;
+
+const expect = chai.expect;
+
+const should = chai.should(); // actually call the function
+
+const libs = `${process.cwd()}/libs`;
+
+const server = require(`${libs}/app`);
+
+chai.use(chaiHttp);
+
+
+describe('test response', () => {
+    it('should list all regions in json', (done) => {
+        chai.request(server)
+            .get('/api/v1/region')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                done();
+            });
+    });
+    it('should list all regions in xml', (done) => {
+        chai.request(server)
+            .get('/api/v1/region?format=xml')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.xml;
+                done();
+            });
+    });
+    it('should list all regions in csv', (done) => {
+        chai.request(server)
+            .get('/api/v1/region?format=csv')
+            .end((err, res) => {
+                res.should.have.status(200);
+                done();
+            });
+    });
+});
diff --git a/src/test/school.js b/src/test/school.js
new file mode 100644
index 0000000000000000000000000000000000000000..266c542850095f1469d7952174af80e3a35d18e1
--- /dev/null
+++ b/src/test/school.js
@@ -0,0 +1,98 @@
+process.env.NODE_ENV = 'test';
+
+const chai = require('chai');
+
+const dirtyChai = require('dirty-chai');
+
+chai.use(dirtyChai);
+
+const chaiXml = require('chai-xml');
+
+chai.use(chaiXml);
+
+const chaiHttp = require('chai-http');
+
+const assert = chai.assert;
+
+const expect = chai.expect;
+
+const should = chai.should(); // actually call the function
+
+const libs = `${process.cwd()}/libs`;
+
+const server = require(`${libs}/app`);
+
+chai.use(chaiHttp);
+
+describe('request schools', () => {
+    it('should list a school by id', (done) => {
+        chai.request(server)
+            .get('/api/v1/school?filter=id:11000023')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('id');
+                res.body.result[0].should.have.property('year');
+                //res.body.result[0].should.have.property('nome_entidade');
+                done();
+            });
+    });
+
+    it('should list a school by id', (done) => {
+        chai.request(server)
+            .get('/api/v1/school/source')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('source');
+                //res.body.result[0].should.have.property('nome_entidade');
+                done();
+            });
+    });
+
+    it('should list all schools from a state', (done) => {
+        chai.request(server)
+            .get('/api/v1/school?filter=state:41')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('id');
+                res.body.result[0].should.have.property('year');
+                //res.body.result[0].should.have.property('nome_entidade');
+                done();
+            });
+    });
+
+    it('should list all schools from a city', (done) => {
+        chai.request(server)
+            .get('/api/v1/school?filter=city:4102802')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('id');
+                res.body.result[0].should.have.property('year');
+                //res.body.result[0].should.have.property('nome_entidade');
+                done();
+            })
+    });
+
+    it('should return 400 with no filters', (done) => {
+        chai.request(server)
+            .get('/api/v1/school')
+            .end((err, res) => {
+                res.should.have.status(400);
+                res.should.be.json;
+                res.body.should.have.property('error');
+                res.body.error.should.be.equal('Wrong/No filter specified');
+                done();
+            })
+    });
+});
diff --git a/src/test/schoolCount.js b/src/test/schoolCount.js
new file mode 100644
index 0000000000000000000000000000000000000000..305045894b410b0c4215ce2d3d4d85aa065860c2
--- /dev/null
+++ b/src/test/schoolCount.js
@@ -0,0 +1,317 @@
+process.env.NODE_ENV = 'test';
+
+const chai = require('chai');
+
+const dirtyChai = require('dirty-chai');
+
+chai.use(dirtyChai);
+
+const chaiXml = require('chai-xml');
+
+chai.use(chaiXml);
+
+const chaiHttp = require('chai-http');
+
+const assert = chai.assert;
+
+const expect = chai.expect;
+
+const should = chai.should(); // actually call the function
+
+const libs = `${process.cwd()}/libs`;
+
+const server = require(`${libs}/app`);
+
+chai.use(chaiHttp);
+
+describe('request schools count', () => {
+    it('should list the locations', (done) => {
+        chai.request(server)
+            .get('/api/v1/school/location')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('id');
+                res.body.result[0].should.have.property('name');
+                done();
+            });
+    });
+
+    it('should list the administrative dependencies', (done) => {
+        chai.request(server)
+            .get('/api/v1/school/adm_dependency')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('id');
+                res.body.result[0].should.have.property('name');
+                done();
+            });
+    });
+
+    it('should list the administrative dependencies detailed', (done) => {
+        chai.request(server)
+            .get('/api/v1/school/adm_dependency_detailed')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('id');
+                res.body.result[0].should.have.property('name');
+                done();
+            });
+    });
+
+    it('should list the government agreement', (done) => {
+        chai.request(server)
+            .get('/api/v1/school/government_agreement')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('id');
+                res.body.result[0].should.have.property('name');
+                done();
+            });
+    });
+
+    it('should list the agreement', (done) => {
+        chai.request(server)
+            .get('/api/v1/school/agreement')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('id');
+                res.body.result[0].should.have.property('name');
+                done();
+            });
+    });
+
+    it('should list the education day care child', (done) => {
+        chai.request(server)
+            .get('/api/v1/school/education_day_care_child')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('id');
+                res.body.result[0].should.have.property('name');
+                done();
+            });
+    });
+
+    it('should list the education preschool child', (done) => {
+        chai.request(server)
+            .get('/api/v1/school/education_preschool_child')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('id');
+                res.body.result[0].should.have.property('name');
+                done();
+            });
+    });
+
+    it('should list the education begin elementary school', (done) => {
+        chai.request(server)
+            .get('/api/v1/school/education_begin_elementary_school')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('id');
+                res.body.result[0].should.have.property('name');
+                done();
+            });
+    });
+
+    it('should list the education middle school', (done) => {
+        chai.request(server)
+            .get('/api/v1/school/education_middle_school')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('id');
+                res.body.result[0].should.have.property('name');
+                done();
+            });
+    });
+
+    it('should list the education professional', (done) => {
+        chai.request(server)
+            .get('/api/v1/school/education_professional')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('id');
+                res.body.result[0].should.have.property('name');
+                done();
+            });
+    });
+
+    it('should list the education eja', (done) => {
+        chai.request(server)
+            .get('/api/v1/school/education_eja')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('id');
+                res.body.result[0].should.have.property('name');
+                done();
+            });
+    });
+
+    it('should list school with valid dimensions and filters', (done) => {
+        chai.request(server)
+            .get('/api/v1/school/count?dims=location,adm_dependency,government_agreement')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('location_name');
+                res.body.result[0].should.have.property('adm_dependency_name');
+                res.body.result[0].should.have.property('government_agreement_name');
+                res.body.result[0].should.have.property('total');
+                res.body.result[0].should.have.property('year');
+                done();
+            });
+    });
+
+    it('should list school with valid dimensions and filters', (done) => {
+        chai.request(server)
+            .get('/api/v1/school/count?dims=region,state&filter=min_year:2015,max_year:2016,city:4106902')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('region_name');
+                res.body.result[0].should.have.property('state_name');
+                res.body.result[0].should.have.property('total');
+                res.body.result[0].should.have.property('year');
+                done();
+            });
+    });
+
+    it('should list school with no argument dimensions and filters', (done) => {
+        chai.request(server)
+            .get('/api/v1/school/count')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('name');
+                res.body.result[0].should.have.property('total');
+                res.body.result[0].should.have.property('year');
+                done();
+            });
+    });
+
+    it('should list school with valid dimensions and filters of states', (done) => {
+        chai.request(server)
+            .get('/api/v1/school/count?dims=state,education_professional,education_eja&filter=min_year:2015,max_year:2016')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('state_name');
+                res.body.result[0].should.have.property('education_professional_name');
+                res.body.result[0].should.have.property('education_eja_name');
+                res.body.result[0].should.have.property('total');
+                res.body.result[0].should.have.property('year');
+                done();
+            });
+    });
+
+    it('should list school with valid dimensions and filters of states', (done) => {
+        chai.request(server)
+            .get('/api/v1/school/count?dims=state&filter=min_year:2015,max_year:2016')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('state_name');
+                res.body.result[0].should.have.property('total');
+                res.body.result[0].should.have.property('year');
+                done();
+            });
+    });
+
+    it('should list school with valid dimensions and filters of states that have no toilet inside building', (done) => {
+        chai.request(server)
+            .get('/api/v1/school/count?dims=state&filter=min_year:2015,max_year:2016,education_begin_elementary_school:0')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('state_name');
+                res.body.result[0].should.have.property('total');
+                res.body.result[0].should.have.property('year');
+                done();
+            });
+    });
+
+    it('should list school with valid dimensions and filters of states with energy and water', (done) => {
+        chai.request(server)
+            .get('/api/v1/school/count?dims=state&filter=min_year:2015,max_year:2016,government_agreement:1')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('state_name');
+                res.body.result[0].should.have.property('total');
+                res.body.result[0].should.have.property('year');
+                done();
+            });
+    });
+
+    it('should list school with dimension rural_location', (done) => {
+        chai.request(server)
+            .get('/api/v1/school/count?dims=location')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('location_name');
+                done();
+            });
+    });
+
+    it('should list school with dimension agreement', (done) => {
+        chai.request(server)
+            .get('/api/v1/school/count?dims=agreement')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('agreement_name');
+                done();
+            });
+    });
+});
diff --git a/src/test/simulation.js b/src/test/simulation.js
new file mode 100644
index 0000000000000000000000000000000000000000..cd68d68048984fb7c23d22f20a64ce5fe3ab73f2
--- /dev/null
+++ b/src/test/simulation.js
@@ -0,0 +1,433 @@
+process.env.NODE_ENV = 'test';
+
+const chai = require('chai');
+
+const dirtyChai = require('dirty-chai');
+
+chai.use(dirtyChai);
+
+const chaiXml = require('chai-xml');
+
+chai.use(chaiXml);
+
+const chaiHttp = require('chai-http');
+
+const assert = chai.assert;
+
+const expect = chai.expect;
+
+const should = chai.should(); // actually call the function
+
+const libs = `${process.cwd()}/libs`;
+
+const server = require(`${libs}/app`);
+
+const mongoose = require('../libs/db/mongoose');
+const Simulation = require('../libs/models/simulation');
+const User = require('../libs/models/user');
+
+chai.use(chaiHttp);
+
+describe('Requires a simulation', () => {
+    let newSimulation;
+
+    beforeEach(() => {
+        Simulation.remove({}, (err) => {
+            console.log('Test collection purged');
+        });
+    });
+
+    // it('should create a new simulation', (done) => {
+    //     chai.request(server)
+    //         .post('/api/v1/simulation')
+    //         .set('content-type', 'application/x-www-form-urlencoded')
+    //         .set('x-apicache-bypass', 'true')
+    //         .send({ name: 'test_entry' })
+    //         .end((err, res) => {
+    //             res.should.have.status(200);
+    //             res.should.be.json;
+    //             res.body.should.have.property('id');
+    //             res.body.id.should.be.a('string');
+    //             Simulation.findById(res.body.id, (err, simulation) => {
+    //                 simulation.should.have.property('name');
+    //                 simulation.name.should.be.a('string');
+    //                 simulation.name.should.equal('test_entry');
+    //                 done();
+    //             });
+    //         });
+    // });
+    // it('should not create a nameless simulation', (done) => {
+    //     chai.request(server)
+    //         .post('/api/v1/simulation')
+    //         .set('content-type', 'application/x-www-form-urlencoded')
+    //         .set('x-apicache-bypass', 'true')
+    //         .end((err, res) => {
+    //             res.should.have.status(200);
+    //             res.should.be.json;
+    //             res.body.should.not.have.property('id');
+    //             res.body.should.have.property('success');
+    //             res.body.success.should.equal(false);
+    //             Simulation.findById(res.body.id, (err, simulation) => {
+    //                 expect(simulation).to.not.exist;
+    //                 done();
+    //             });
+    //         });
+    // });
+    // it('should find an existing simulation', (done) => {
+    //     newSimulation = new Simulation();
+    //     newSimulation.name = 'test';
+    //     newSimulation.save((err, sim) => {
+    //         let id = sim._id;
+    //         chai.request(server)
+    //             .get(`/api/v1/simulation/${id}`)
+    //             .end((err, res) => {
+    //                 res.should.have.status(200);
+    //                 res.should.be.json;
+    //                 res.body.should.have.property('_id');
+    //                 res.body._id.should.be.a('string');
+    //                 res.body.should.have.property('name');
+    //                 res.body._id.should.be.a('string');
+    //                 done();
+    //             });
+    //     });
+    // });
+    // it('should not find an unexisting simulation', (done) => {
+    //     newSimulation = new Simulation();
+    //     let id = newSimulation._id;
+    //     chai.request(server)
+    //         .get(`/api/v1/simulation/${id}`)
+    //         .end((err, res) => {
+    //             res.should.have.status(200);
+    //             res.should.be.json;
+    //             res.body.should.have.property('success');
+    //             res.body.success.should.equal(false);
+    //             done();
+    //         });
+    // });
+    // it('should update an existing simulation\'s location', (done) => {
+    //     newSimulation = new Simulation();
+    //     newSimulation.name = 'test';
+    //     newSimulation.save((err, sim) => {
+    //         let id = sim._id;
+    //         chai.request(server)
+    //             .post(`/api/v1/simulation/${id}`)
+    //             .send({ location: 5 })
+    //             .end((err, res) => {
+    //                 res.should.have.status(200);
+    //                 res.should.be.json;
+    //                 res.body.should.have.property('id');
+    //                 res.body.id.should.be.a('string');
+    //                 Simulation.findById(res.body.id, (err, simulation) => {
+    //                     simulation.should.have.property('name');
+    //                     simulation.name.should.be.a('string');
+    //                     simulation.name.should.equal('test');
+    //                     simulation.should.have.property('location');
+    //                     simulation.location.should.be.a('number');
+    //                     simulation.location.should.equal(5);
+    //                     done();
+    //                 });
+    //             });
+    //     });
+    // });
+    // it('should update multiple fields on a single request', (done) => {
+    //     newSimulation = new Simulation();
+    //     newSimulation.name = 'test';
+    //     newSimulation.save((err, sim) => {
+    //         let id = sim._id;
+    //         chai.request(server)
+    //             .post(`/api/v1/simulation/${id}`)
+    //             .send({
+    //                 name: 'new_name',
+    //                 location: 5,
+    //                 time: 3,
+    //                 failure_rate: [0.1, 0.2, 0.3],
+    //                 goals_care: [0.3, 0.2, 0.1],
+    //                 goals_inclusion: [0.8, 0.9, 1]
+    //             })
+    //             .end((err, res) => {
+    //                 res.should.have.status(200);
+    //                 res.should.be.json;
+    //                 res.body.should.have.property('id');
+    //                 res.body.id.should.be.a('string');
+    //                 Simulation.findById(res.body.id, (err, simulation) => {
+    //                     simulation.should.have.property('name');
+    //                     simulation.name.should.be.a('string');
+    //                     simulation.name.should.equal('new_name');
+    //                     simulation.should.have.property('location');
+    //                     simulation.location.should.be.a('number');
+    //                     simulation.location.should.equal(5);
+    //                     simulation.should.have.property('time');
+    //                     simulation.time.should.be.a('number');
+    //                     simulation.time.should.equal(3);
+    //                     simulation.should.have.property('failure_rate');
+    //                     simulation.failure_rate.should.be.a('array');
+    //                     simulation.failure_rate.length.should.equal(3);
+    //                     simulation.should.have.property('goals_care');
+    //                     simulation.goals_care.should.be.a('array');
+    //                     simulation.goals_care.length.should.equal(3);
+    //                     simulation.should.have.property('goals_inclusion');
+    //                     simulation.goals_inclusion.should.be.a('array');
+    //                     simulation.goals_inclusion.length.should.equal(3);
+    //                     done();
+    //                 });
+    //             });
+    //     });
+    // });
+    // it('should not update an unexisting simulation', (done) => {
+    //     newSimulation = new Simulation();
+    //     let id = newSimulation._id;
+    //     chai.request(server)
+    //         .post(`/api/v1/simulation/${id}`)
+    //         .send({ location: 5 })
+    //         .end((err, res) => {
+    //             res.should.have.status(200);
+    //             res.should.be.json;
+    //             res.body.should.have.property('success');
+    //             res.body.success.should.equal(false);
+    //             done();
+    //         });
+    // });
+    // it('should update an existing simulation\'s time', (done) => {
+    //     newSimulation = new Simulation();
+    //     newSimulation.name = 'test';
+    //     newSimulation.save((err, sim) => {
+    //         let id = sim._id;
+    //         chai.request(server)
+    //             .post(`/api/v1/simulation/${id}`)
+    //             .send({ time: 5 })
+    //             .end((err, res) => {
+    //                 res.should.have.status(200);
+    //                 res.should.be.json;
+    //                 res.body.should.have.property('id');
+    //                 res.body.id.should.be.a('string');
+    //                 Simulation.findById(res.body.id, (err, simulation) => {
+    //                     simulation.should.have.property('name');
+    //                     simulation.name.should.be.a('string');
+    //                     simulation.should.have.property('time');
+    //                     simulation.time.should.be.a('number');
+    //                     simulation.time.should.equal(5);
+    //                     done();
+    //                 });
+    //             });
+    //     });
+    // });
+    // it('should not change results for empty post requests', (done) => {
+    //     newSimulation = new Simulation();
+    //     newSimulation.name = 'test';
+    //     newSimulation.save((err, sim) => {
+    //         let id = sim._id;
+    //         chai.request(server)
+    //             .post(`/api/v1/simulation/${id}`)
+    //             .end((err, res) => {
+    //                 res.should.have.status(200);
+    //                 res.should.be.json;
+    //                 res.body.should.have.property('success');
+    //                 res.body.success.should.equal(false);
+    //                 done();
+    //             });
+    //     });
+    // });
+    // it('should not update in case of invalid field', (done) => {
+    //     newSimulation = new Simulation();
+    //     newSimulation.name = 'test';
+    //     newSimulation.save((err, sim) => {
+    //         let id = sim._id;
+    //         chai.request(server)
+    //             .post(`/api/v1/simulation/${id}`)
+    //             .send({
+    //                 name: 'other_name',
+    //                 totally_not_valid_value_for_an_entry: 'not hacking this api',
+    //             })
+    //             .end((err, res) => {
+    //                 res.should.have.status(200);
+    //                 res.should.be.json;
+    //                 res.body.should.have.property('success');
+    //                 res.body.success.should.equal(false);
+    //                 Simulation.findById(id, (err, simulation) => {
+    //                     simulation.name.should.equal('test');
+    //                     done();
+    //                 });
+    //             });
+    //     });
+    // });
+    // it('should include consistent enrollment tables', (done) => {
+    //     newSimulation = new Simulation();
+    //     newSimulation.name = 'test';
+    //     newSimulation.save((err, sim) => {
+    //         let id = sim._id;
+    //         chai.request(server)
+    //             .post(`/api/v1/simulation/${id}`)
+    //             .send({
+    //                 time: 5,
+    //                 enrollments: "[[100, 150, 200, 250, 300]]",
+    //             })
+    //             .end((err, res) => {
+    //                 res.should.have.status(200);
+    //                 res.should.be.json;
+    //                 res.body.should.have.property('id');
+    //                 res.body.id.should.be.a('string');
+    //                 Simulation.findById(res.body.id, (err, simulation) => {
+    //                     simulation.should.have.property('name');
+    //                     simulation.name.should.be.a('string');
+    //                     simulation.should.have.property('time');
+    //                     simulation.time.should.be.a('number');
+    //                     simulation.time.should.equal(5);
+    //                     done();
+    //                 });
+    //             });
+    //     });
+    // });
+    // it('should not accept an invalid time', (done) => {
+    //     newSimulation = new Simulation();
+    //     newSimulation.name = 'test';
+    //     newSimulation.save((err, sim) => {
+    //         let id = sim._id;
+    //         chai.request(server)
+    //             .post(`/api/v1/simulation/${id}`)
+    //             .send({
+    //                 time: "I'm an inocent time entry, don't mind me",
+    //             })
+    //             .end((err, res) => {
+    //                 res.should.have.status(200);
+    //                 res.should.be.json;
+    //                 res.body.should.have.property('success');
+    //                 res.body.success.should.equal(false);
+    //                 });
+    //                 done();
+    //             });
+    // });
+    // it('should not accept enrollments table different than provided time', (done) => {
+    //     newSimulation = new Simulation();
+    //     newSimulation.name = 'test';
+    //     newSimulation.save((err, sim) => {
+    //         let id = sim._id;
+    //         chai.request(server)
+    //             .post(`/api/v1/simulation/${id}`)
+    //             .send({
+    //                 time: 5,
+    //                 enrollments: "[[1,2,3]]",
+    //             })
+    //             .end((err, res) => {
+    //                 res.should.have.status(200);
+    //                 res.should.be.json;
+    //                 res.body.should.have.property('success');
+    //                 res.body.success.should.equal(false);
+    //                 done();
+    //             });
+    //     });
+    // });
+    // it('should not include arrays of non arrays as enrollments', (done) => {
+    //     newSimulation = new Simulation();
+    //     newSimulation.name = 'test';
+    //     newSimulation.save((err, sim) => {
+    //         let id = sim._id;
+    //         chai.request(server)
+    //             .post(`/api/v1/simulation/${id}`)
+    //             .send({
+    //                 time: 5,
+    //                 enrollments: "[\"Tomato\"]",
+    //             })
+    //             .end((err, res) => {
+    //                 res.should.have.status(200);
+    //                 res.should.be.json;
+    //                 res.body.should.have.property('success');
+    //                 res.body.success.should.equal(false);
+    //                 done();
+    //             });
+    //     });
+    // });
+    // it('should not accept non array enrollments', (done) => {
+    //     newSimulation = new Simulation();
+    //     newSimulation.name = 'test';
+    //     newSimulation.save((err, sim) => {
+    //         let id = sim._id;
+    //         chai.request(server)
+    //             .post(`/api/v1/simulation/${id}`)
+    //             .send({
+    //                 time: 5,
+    //                 enrollments: "Am I still wanted here?",
+    //             })
+    //             .end((err, res) => {
+    //                 res.should.have.status(200);
+    //                 res.should.be.json;
+    //                 res.body.should.have.property('success');
+    //                 res.body.success.should.equal(false);
+    //                 done();
+    //             });
+    //     });
+    // });
+    // it('should not accept an enrollment with anything other than a number', (done) => {
+    //     newSimulation = new Simulation();
+    //     newSimulation.name = 'test';
+    //     newSimulation.save((err, sim) => {
+    //         let id = sim._id;
+    //         chai.request(server)
+    //             .post(`/api/v1/simulation/${id}`)
+    //             .send({
+    //                 time: 5,
+    //                 enrollments: "[[1,2,\"malicious payload\",4,5]]",
+    //             })
+    //             .end((err, res) => {
+    //                 res.should.have.status(200);
+    //                 res.should.be.json;
+    //                 res.body.should.have.property('success');
+    //                 res.body.success.should.equal(false);
+    //                 done();
+    //             });
+    //     });
+    // });
+    // it('should delete an entry', (done) => {
+    //     newSimulation = new Simulation();
+    //     newSimulation.name = 'test';
+    //     newSimulation.save((err, sim) => {
+    //         let id = sim._id;
+    //         chai.request(server)
+    //             .delete(`/api/v1/simulation/${id}`)
+    //             .end((err, res) => {
+    //                 res.should.have.status(200);
+    //                 res.should.be.json;
+    //                 res.body.should.have.property('success');
+    //                 res.body.success.should.equal(true);
+    //                 done();
+    //             });
+    //     });
+    // });
+    // it('should not delete an unexisting entry', (done) => {
+    //     let sim = new Simulation();
+    //     let id = sim._id;
+    //     chai.request(server)
+    //         .delete(`/api/v1/simulation/${id}`)
+    //         .end((err, res) => {
+    //             res.should.have.status(200);
+    //             res.should.be.json;
+    //             res.body.should.have.property('success');
+    //             res.body.success.should.equal(false);
+    //             done();
+    //         });
+    // });
+
+    // it('should returns an array in simulation/time', (done) => {
+    //     let max_time = 10;
+    //     chai.request(server)
+    //         .get(`/api/v1/simulation/time?max_time=${max_time}`)
+    //         .end((err, res) => {
+    //             res.should.have.status(200);
+    //             res.should.be.json;
+    //             res.body.should.have.property('result');
+    //             res.body.result.should.be.array;
+    //             done();
+    //         });
+    // });
+
+    // it('should return an error when no max_time is specified in simulation/time', (done) => {
+    //     chai.request(server)
+    //         .get(`/api/v1/simulation/time`)
+    //         .end((err, res) => {
+    //             res.should.have.status(400);
+    //             res.should.be.json;
+    //             res.body.should.have.property('error');
+    //             res.body.error.should.equal('Invalid value for mandatory parameter max_time');
+    //             done();
+    //         });
+    // });
+});
diff --git a/src/test/siope.js b/src/test/siope.js
new file mode 100644
index 0000000000000000000000000000000000000000..ac13ead5ba92f76c6294f18fb324b90ed52396fd
--- /dev/null
+++ b/src/test/siope.js
@@ -0,0 +1,87 @@
+process.env.NODE_ENV = 'test';
+
+const chai = require('chai');
+
+const dirtyChai = require('dirty-chai');
+
+chai.use(dirtyChai);
+
+const chaiXml = require('chai-xml');
+
+chai.use(chaiXml);
+
+const chaiHttp = require('chai-http');
+
+const assert = chai.assert;
+
+const expect = chai.expect;
+
+const should = chai.should(); // actually call the function
+
+const libs = `${process.cwd()}/libs`;
+
+const server = require(`${libs}/app`);
+
+chai.use(chaiHttp);
+describe('request siope', () => {
+    it('should list the years', (done) => {
+        chai.request(server)
+            .get('/api/v1/siope/years')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('year');
+                done();
+            });
+    });
+
+    it('should list default siope', (done) => {
+        chai.request(server)
+            .get('/api/v1/siope')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('year');
+                res.body.result[0].should.have.property('impostos');
+                done();
+            });
+    });
+
+    it('should list siope with valid filters', (done) => {
+        chai.request(server)
+            .get('/api/v1/siope?filter=state:41')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('year');
+                res.body.result[0].should.have.property('state_id');
+                res.body.result[0].should.have.property('fundeb');
+                res.body.result[0].should.have.property('impostos');
+                done();
+            });
+    });
+
+    it('should list siope with valid dimensions', (done) => {
+        chai.request(server)
+            .get('/api/v1/siope?dims=state')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('year');
+                res.body.result[0].should.have.property('state_id');
+                res.body.result[0].should.have.property('fundeb');
+                res.body.result[0].should.have.property('impostos');
+                res.body.result[0].should.have.property('state_name');
+                done();
+            });
+    });
+
+});
diff --git a/src/test/state.js b/src/test/state.js
new file mode 100644
index 0000000000000000000000000000000000000000..5cea7a4f69d6169208367df71571ebefa541a13a
--- /dev/null
+++ b/src/test/state.js
@@ -0,0 +1,105 @@
+process.env.NODE_ENV = 'test';
+
+const chai = require('chai');
+
+const dirtyChai = require('dirty-chai');
+
+chai.use(dirtyChai);
+
+const chaiXml = require('chai-xml');
+
+chai.use(chaiXml);
+
+const chaiHttp = require('chai-http');
+
+const assert = chai.assert;
+
+const expect = chai.expect;
+
+const should = chai.should(); // actually call the function
+
+const libs = `${process.cwd()}/libs`;
+
+const server = require(`${libs}/app`);
+
+chai.use(chaiHttp);
+
+
+describe('request states', () => {
+    it('should list all states', (done) => {
+        chai.request(server)
+            .get('/api/v1/state')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('id');
+                res.body.result[0].should.have.property('region_id');
+                res.body.result[0].should.have.property('name');
+                done();
+            });
+    });
+
+    it('should list a state by id', (done) => {
+        chai.request(server)
+            .get('/api/v1/state?filter=id:11')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result.should.have.length(1);
+                res.body.result[0].should.have.property('id');
+                res.body.result[0].should.have.property('region_id');
+                res.body.result[0].should.have.property('name');
+                done();
+            });
+    });
+
+    it('should list states by region id', (done) => {
+        chai.request(server)
+            .get('/api/v1/state?filter=region:1')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('id');
+                res.body.result[0].should.have.property('region_id');
+                res.body.result[0].should.have.property('name');
+                done();
+            });
+    });
+
+    it('should list states by region id', (done) => {
+        chai.request(server)
+            .get('/api/v1/state?filter=region:1,region:2')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('id');
+                res.body.result[0].should.have.property('region_id');
+                res.body.result[0].should.have.property('name');
+                done();
+            });
+    });
+
+    it('should search for Paraná', (done) => {
+        chai.request(server)
+            .get('/api/v1/state?search=name:paran')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result.should.have.length(1);
+                res.body.result[0].should.have.property('id');
+                res.body.result[0].should.have.property('region_id');
+                res.body.result[0].should.have.property('name');
+                done();
+            });
+    });
+});
diff --git a/src/test/test.js b/src/test/teacher.js
similarity index 54%
rename from src/test/test.js
rename to src/test/teacher.js
index 066b7b77c9651dbde50b97cd6ab730c78053d27b..30eddee8a20e6654c2db5f595c44e37f99868a5f 100644
--- a/src/test/test.js
+++ b/src/test/teacher.js
@@ -23,38 +23,64 @@ const libs = `${process.cwd()}/libs`;
 const server = require(`${libs}/app`);
 
 chai.use(chaiHttp);
+describe('request teachers', () => {
+    it('should list the year range', (done) => {
+        chai.request(server)
+            .get('/api/v1/teacher/year_range')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('start_year');
+                res.body.result[0].should.have.property('end_year');
+                done();
+            });
+    });
 
-describe('API is running', () => {
-    it('should respond it\'s running', (done) => {
+    it('should list the source', (done) => {
         chai.request(server)
-            .get('/api/v1')
+            .get('/api/v1/teacher/source')
             .end((err, res) => {
                 res.should.have.status(200);
                 res.should.be.json;
-                res.body.should.have.property('msg');
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('source');
                 done();
-            })
+            });
     });
-});
 
-describe('request enrollments', () => {
-    it('should list the year range', (done) => {
+    it('should list the locations', (done) => {
         chai.request(server)
-            .get('/api/v1/enrollment/year_range')
+            .get('/api/v1/teacher/location')
             .end((err, res) => {
                 res.should.have.status(200);
                 res.should.be.json;
                 res.body.should.have.property('result');
                 res.body.result.should.be.a('array');
-                res.body.result[0].should.have.property('start_year');
-                res.body.result[0].should.have.property('end_year');
+                res.body.result[0].should.have.property('id');
+                res.body.result[0].should.have.property('name');
                 done();
             });
     });
 
-    it('should list the education level', (done) => {
+    it('should list the locations', (done) => {
         chai.request(server)
-            .get('/api/v1/enrollment/education_level')
+            .get('/api/v1/teacher/years')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('year');
+                done();
+            });
+    });
+
+    it('should list the rural locations', (done) => {
+        chai.request(server)
+            .get('/api/v1/teacher/rural_location')
             .end((err, res) => {
                 res.should.have.status(200);
                 res.should.be.json;
@@ -66,9 +92,9 @@ describe('request enrollments', () => {
             });
     });
 
-    it('should list the administrative dependencies', (done) => {
+    it('should list the education level mod', (done) => {
         chai.request(server)
-            .get('/api/v1/enrollment/adm_dependency ')
+            .get('/api/v1/teacher/education_level_mod')
             .end((err, res) => {
                 res.should.have.status(200);
                 res.should.be.json;
@@ -80,318 +106,323 @@ describe('request enrollments', () => {
             });
     });
 
-    it('should list enrollments', (done) => {
+    it('should list the education level short', (done) => {
         chai.request(server)
-            .get('/api/v1/enrollment')
+            .get('/api/v1/teacher/education_level_short')
             .end((err, res) => {
                 res.should.have.status(200);
                 res.should.be.json;
                 res.body.should.have.property('result');
                 res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('id');
                 res.body.result[0].should.have.property('name');
-                res.body.result[0].should.have.property('total');
                 done();
             });
     });
 
-    it('should list enrollments with valid filters', (done) => {
+    it('should list the education type', (done) => {
         chai.request(server)
-            .get('/api/v1/enrollment?filter=min_year:2010,state:41')
+            .get('/api/v1/teacher/education_type')
             .end((err, res) => {
                 res.should.have.status(200);
                 res.should.be.json;
                 res.body.should.have.property('result');
                 res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('id');
                 res.body.result[0].should.have.property('name');
-                res.body.result[0].should.have.property('total');
                 done();
             });
     });
 
-    it('should list enrollments with invalid filters', (done) => {
+    it('should list the contract type', (done) => {
         chai.request(server)
-            .get('/api/v1/enrollment?filter=foo:2010,bar:41')
+            .get('/api/v1/teacher/contract_type')
             .end((err, res) => {
                 res.should.have.status(200);
                 res.should.be.json;
                 res.body.should.have.property('result');
                 res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('id');
                 res.body.result[0].should.have.property('name');
-                res.body.result[0].should.have.property('total');
                 done();
             });
     });
 
-    it('should list enrollments with valid dimensions', (done) => {
+    it('should list the administrative dependencies', (done) => {
         chai.request(server)
-            .get('/api/v1/enrollment?dims=region,state,adm_dependency_id,location_id&filter=min_year:2014,region:4')
+            .get('/api/v1/teacher/adm_dependency')
             .end((err, res) => {
                 res.should.have.status(200);
                 res.should.be.json;
                 res.body.should.have.property('result');
                 res.body.result.should.be.a('array');
-                res.body.result[0].should.have.property('region_name');
-                res.body.result[0].should.have.property('state_name');
-                res.body.result[0].should.have.property('adm_dependency_name');
-                res.body.result[0].should.have.property('total');
+                res.body.result[0].should.have.property('id');
+                res.body.result[0].should.have.property('name');
                 done();
             });
     });
 
-    it('should list enrollments with invalid dimensions', (done) => {
+    it('should list the administrative dependencies detailed', (done) => {
         chai.request(server)
-            .get('/api/v1/enrollment?dims=foo,bar')
+            .get('/api/v1/teacher/adm_dependency_detailed')
             .end((err, res) => {
                 res.should.have.status(200);
                 res.should.be.json;
                 res.body.should.have.property('result');
                 res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('id');
                 res.body.result[0].should.have.property('name');
-                res.body.result[0].should.have.property('total');
                 done();
             });
     });
 
-    it('should list enrollments with valid dimensions and filters', (done) => {
+    it('should list genders', (done) => {
         chai.request(server)
-            .get('/api/v1/enrollment?dims=region,state,education_level_id,school&filter=min_year:2013,max_year:2014,city:3287')
+            .get('/api/v1/teacher/gender')
             .end((err, res) => {
                 res.should.have.status(200);
                 res.should.be.json;
                 res.body.should.have.property('result');
                 res.body.result.should.be.a('array');
-                res.body.result[0].should.have.property('region_name');
-                res.body.result[0].should.have.property('state_name');
-                res.body.result[0].should.have.property('school_name');
-                res.body.result[0].should.have.property('education_level');
-                res.body.result[0].should.have.property('total');
-                res.body.result[0].should.have.property('year');
+                res.body.result[0].should.have.property('id');
+                res.body.result[0].should.have.property('name');
                 done();
             });
     });
 
+    it('should list the ethnic groups', (done) => {
+        chai.request(server)
+            .get('/api/v1/teacher/ethnic_group')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('id');
+                res.body.result[0].should.have.property('name');
+                done();
+            });
+    });
 
-});
 
-describe('request regions', () => {
-    it('should list all regions', (done) => {
+    it('should list teachers count', (done) => {
         chai.request(server)
-            .get('/api/v1/region')
+            .get('/api/v1/teacher?filter=min_year:2016,max_year:2016')
             .end((err, res) => {
                 res.should.have.status(200);
                 res.should.be.json;
                 res.body.should.have.property('result');
                 res.body.result.should.be.a('array');
-                res.body.result[0].should.have.property('pk_regiao_id');
-                res.body.result[0].should.have.property('nome');
+                res.body.result[0].should.have.property('name');
+                res.body.result[0].should.have.property('total');
                 done();
             });
     });
 
-    it('should list region by id', (done) => {
+    it('should list teacher count with valid filters', (done) => {
         chai.request(server)
-            .get('/api/v1/region/1')
+            .get('/api/v1/teacher?filter=min_year:2015,max_year:2015,state:41')
             .end((err, res) => {
                 res.should.have.status(200);
                 res.should.be.json;
                 res.body.should.have.property('result');
                 res.body.result.should.be.a('array');
-                res.body.result.should.have.length(1);
-                res.body.result[0].should.have.property('pk_regiao_id');
-                res.body.result[0].should.have.property('nome');
+                res.body.result[0].should.have.property('name');
+                res.body.result[0].should.have.property('total');
                 done();
             });
     });
-});
 
-describe('request states', () => {
-    it('should list all states', (done) => {
+    it('should list teacher count with invalid filters', (done) => {
         chai.request(server)
-            .get('/api/v1/state')
+            .get('/api/v1/teacher?filter=foo:2010,bar:41,min_year:2016,max_year:2016')
             .end((err, res) => {
                 res.should.have.status(200);
                 res.should.be.json;
                 res.body.should.have.property('result');
                 res.body.result.should.be.a('array');
-                res.body.result[0].should.have.property('pk_estado_id');
-                res.body.result[0].should.have.property('fk_regiao_id');
-                res.body.result[0].should.have.property('nome');
+                res.body.result[0].should.have.property('name');
+                res.body.result[0].should.have.property('total');
                 done();
             });
     });
 
-    it('should list a state by id', (done) => {
+    it('should list teacher count with valid dimensions', (done) => {
         chai.request(server)
-            .get('/api/v1/state/11')
+            .get('/api/v1/teacher?dims=region,state,adm_dependency,location,gender,ethnic_group&filter=min_year:2016,max_year:2016')
             .end((err, res) => {
                 res.should.have.status(200);
                 res.should.be.json;
                 res.body.should.have.property('result');
                 res.body.result.should.be.a('array');
-                res.body.result.should.have.length(1);
-                res.body.result[0].should.have.property('pk_estado_id');
-                res.body.result[0].should.have.property('fk_regiao_id');
-                res.body.result[0].should.have.property('nome');
+                res.body.result[0].should.have.property('region_name');
+                res.body.result[0].should.have.property('state_name');
+                res.body.result[0].should.have.property('adm_dependency_name');
+                res.body.result[0].should.have.property('location_name');
+                res.body.result[0].should.have.property('total');
                 done();
             });
     });
 
-    it('should list states by region id', (done) => {
+    it('should list teacher count with invalid dimensions', (done) => {
         chai.request(server)
-            .get('/api/v1/state/region/1')
+            .get('/api/v1/teacher?dims=foo,bar&filter=min_year:2016,max_year:2016')
             .end((err, res) => {
                 res.should.have.status(200);
                 res.should.be.json;
                 res.body.should.have.property('result');
                 res.body.result.should.be.a('array');
-                res.body.result[0].should.have.property('pk_estado_id');
-                res.body.result[0].should.have.property('fk_regiao_id');
-                res.body.result[0].should.have.property('nome');
+                res.body.result[0].should.have.property('name');
+                res.body.result[0].should.have.property('total');
+                done();
+            });
+    });
+
+    it('should list teacher count with valid dimensions and filters', (done) => {
+        chai.request(server)
+            .get('/api/v1/teacher?dims=region,state,school,gender&filter=min_year:2015,max_year:2015,city:4106902')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('region_name');
+                res.body.result[0].should.have.property('state_name');
+                res.body.result[0].should.have.property('school_name');
+                res.body.result[0].should.have.property('total');
+                res.body.result[0].should.have.property('year');
                 done();
             });
     });
-});
 
-describe('request cities', () => {
-    it('should list all cities', (done) => {
+    it('should list teacher count with dimension location', (done) => {
         chai.request(server)
-            .get('/api/v1/city')
+            .get('/api/v1/teacher?dims=location&filter=min_year:2016,max_year:2016')
             .end((err, res) => {
                 res.should.have.status(200);
                 res.should.be.json;
                 res.body.should.have.property('result');
                 res.body.result.should.be.a('array');
-                res.body.result[0].should.have.property('pk_municipio_id');
-                res.body.result[0].should.have.property('fk_estado_id');
-                res.body.result[0].should.have.property('nome');
-                res.body.result[0].should.have.property('codigo_ibge');
+                res.body.result[0].should.have.property('location_name');
                 done();
             });
     });
 
-    it('should list a city by id', (done) => {
+    it('should list teacher count with dimension rural_location', (done) => {
         chai.request(server)
-            .get('/api/v1/city/1')
+            .get('/api/v1/teacher?dims=rural_location&filter=min_year:2016,max_year:2016')
             .end((err, res) => {
                 res.should.have.status(200);
                 res.should.be.json;
                 res.body.should.have.property('result');
                 res.body.result.should.be.a('array');
-                res.body.result[0].should.have.property('pk_municipio_id');
-                res.body.result[0].should.have.property('fk_estado_id');
-                res.body.result[0].should.have.property('nome');
-                res.body.result[0].should.have.property('codigo_ibge');
+                res.body.result[0].should.have.property('rural_location_name');
                 done();
             });
     });
 
-    it('should list a city by codigo_ibge', (done) => {
+    it('should list teacher count with dimension education_level_mod', (done) => {
         chai.request(server)
-            .get('/api/v1/city/ibge/1200013')
+            .get('/api/v1/teacher?dims=education_level_mod&filter=min_year:2015,max_year:2015')
             .end((err, res) => {
                 res.should.have.status(200);
                 res.should.be.json;
                 res.body.should.have.property('result');
                 res.body.result.should.be.a('array');
-                res.body.result[0].should.have.property('pk_municipio_id');
-                res.body.result[0].should.have.property('fk_estado_id');
-                res.body.result[0].should.have.property('nome');
-                res.body.result[0].should.have.property('codigo_ibge');
+                res.body.result[0].should.have.property('education_level_mod_name');
                 done();
             });
     });
 
-    it('should list all cities from a state', (done) => {
+    it('should list teacher with dimension education_level_short', (done) => {
         chai.request(server)
-            .get('/api/v1/city/state/41')
+            .get('/api/v1/teacher?dims=education_level_short&filter=min_year:2016,max_year:2016')
             .end((err, res) => {
                 res.should.have.status(200);
                 res.should.be.json;
                 res.body.should.have.property('result');
                 res.body.result.should.be.a('array');
-                res.body.result[0].should.have.property('pk_municipio_id');
-                res.body.result[0].should.have.property('fk_estado_id');
-                res.body.result[0].should.have.property('nome');
-                res.body.result[0].should.have.property('codigo_ibge');
+                res.body.result[0].should.have.property('education_level_short_name');
                 done();
-            })
-    })
-});
+            });
+    });
 
-describe('request schools', () => {
-    it('should list a school by id', (done) => {
+    it('should list teacher count with dimension education type', (done) => {
         chai.request(server)
-            .get('/api/v1/school/185588')
+            .get('/api/v1/teacher?dims=education_type&filter=min_year:2016,max_year:2016')
             .end((err, res) => {
                 res.should.have.status(200);
                 res.should.be.json;
                 res.body.should.have.property('result');
                 res.body.result.should.be.a('array');
-                res.body.result[0].should.have.property('pk_escola_id');
-                res.body.result[0].should.have.property('ano_censo');
-                res.body.result[0].should.have.property('cod_entidade');
-                res.body.result[0].should.have.property('nome_entidade');
+                res.body.result[0].should.have.property('education_type_name');
                 done();
             });
     });
 
-    it('should list all schools from a state', (done) => {
+    it('should list teacher count with dimension adm_dependency', (done) => {
         chai.request(server)
-            .get('/api/v1/school/state/41')
+            .get('/api/v1/teacher?dims=adm_dependency&filter=min_year:2016,max_year:2016')
             .end((err, res) => {
                 res.should.have.status(200);
                 res.should.be.json;
                 res.body.should.have.property('result');
                 res.body.result.should.be.a('array');
-                res.body.result[0].should.have.property('pk_escola_id');
-                res.body.result[0].should.have.property('ano_censo');
-                res.body.result[0].should.have.property('nome_entidade');
+                res.body.result[0].should.have.property('adm_dependency_name');
                 done();
             });
     });
 
-    it('should list all schools from a city', (done) => {
+    it('should list teacher count with dimension adm_dependency_detailed', (done) => {
         chai.request(server)
-            .get('/api/v1/school/city/3287')
+            .get('/api/v1/teacher?dims=adm_dependency_detailed&filter=min_year:2016,max_year:2016')
             .end((err, res) => {
                 res.should.have.status(200);
                 res.should.be.json;
                 res.body.should.have.property('result');
                 res.body.result.should.be.a('array');
-                res.body.result[0].should.have.property('pk_escola_id');
-                res.body.result[0].should.have.property('ano_censo');
-                res.body.result[0].should.have.property('nome_entidade');
+                res.body.result[0].should.have.property('adm_dependency_detailed_name');
                 done();
-            })
-    })
-});
+            });
+    });
 
-describe('test response', () => {
-    it('should list all regions in json', (done) => {
+    it('should list teacher count with dimension gender', (done) => {
         chai.request(server)
-            .get('/api/v1/region')
+            .get('/api/v1/teacher?dims=gender&filter=min_year:2016,max_year:2016')
             .end((err, res) => {
                 res.should.have.status(200);
                 res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('gender_name');
                 done();
             });
     });
 
-    it('should list all regions in xml', (done) => {
+    it('should list teacher count with dimension ethnic_group', (done) => {
         chai.request(server)
-            .get('/api/v1/region?format=xml')
+            .get('/api/v1/teacher?dims=ethnic_group&filter=min_year:2016,max_year:2016')
             .end((err, res) => {
                 res.should.have.status(200);
-                res.should.be.xml;
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('ethnic_group_name');
                 done();
             });
     });
 
-    it('should list all regions in csv', (done) => {
+
+
+    it('should list teacher count with dimension contract type', (done) => {
         chai.request(server)
-            .get('/api/v1/region?format=csv')
+            .get('/api/v1/teacher?filter=state:41&dims=contract_type')
             .end((err, res) => {
                 res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('contract_type_name');
                 done();
             });
     });
diff --git a/src/test/user.js b/src/test/user.js
new file mode 100644
index 0000000000000000000000000000000000000000..3109960bd98dfec2727c5ba7bf87b28148046ae7
--- /dev/null
+++ b/src/test/user.js
@@ -0,0 +1,37 @@
+process.env.NODE_ENV = 'test';
+
+const chai = require('chai');
+
+const dirtyChai = require('dirty-chai');
+
+chai.use(dirtyChai);
+
+const chaiXml = require('chai-xml');
+
+chai.use(chaiXml);
+
+const chaiHttp = require('chai-http');
+
+const assert = chai.assert;
+
+const expect = chai.expect;
+
+const should = chai.should(); // actually call the function
+
+const libs = `${process.cwd()}/libs`;
+
+const server = require(`${libs}/app`);
+
+const mongoose = require('../libs/db/mongoose');
+//const Simulation = require('../libs/models/simulation');
+const User = require('../libs/models/user');
+
+chai.use(chaiHttp);
+
+describe('Saves a user', () => {
+    // beforeEach(() => {
+    //     User.remove({}, (err) => {
+    //         console.log('Test collection purged')
+    //     });
+    // });
+});