瀏覽代碼

first commit

JeanBenitez 1 年之前
父節點
當前提交
45a4e65c22

+ 39 - 0
src/App.vue

@@ -0,0 +1,39 @@
+<template>
+  <div id="app">
+    <Navbar></Navbar>
+    <router-view />
+    <vueTopprogress ref="topProgress" color="#ed022c" errorColor="#ffbc34" />
+  </div>
+</template>
+<style src="@/assets/css/colors/default-dark.css"></style>
+<style src="@/assets/css/style.css"></style>
+<script>
+import Navbar from "@/components/shared/Navbar.vue";
+import { vueTopprogress } from "vue-top-progress";
+export default {
+  data() {
+    return {
+    
+    }
+  },
+  name: "App",
+  components: {
+    Navbar
+  },
+  created() {
+    this.$router.beforeResolve((to, from, next) => {
+      if (to.name) {
+        this.$refs.topProgress.start();
+      }
+      next();
+    });
+    this.$router.afterEach((to, from) => {
+      // colocar un retardo para leer;
+      setTimeout(() => {
+        this.$refs.topProgress.done();
+      }, 100);
+    });
+  }
+};
+</script>
+ 

+ 166 - 0
src/_mixin/public_mixin.js

@@ -0,0 +1,166 @@
+import moment from "moment";
+import format from "date-fns/format";
+import $ from "jquery";
+import download from "downloadjs";
+export const downloadMixin = {
+    data() {
+        return {
+            buscar: "",
+            downloadingFile: false,
+            idLoadingImage: null,
+            downloadedItem: [],
+            toastCollapse: false
+        }
+    },
+    methods: {
+        slug: function (str) {
+            str = str.replace(/^\s+|\s+$/g, ""); // trim
+            str = str.toLowerCase();
+            // remove accents, swap ñ for n, etc
+            var from = "àáäâèéëêìíïîòóöôùúüûñç·/_,:;";
+            var to = "aaaaeeeeiiiioooouuuunc------";
+            for (var i = 0, l = from.length; i < l; i++) {
+                str = str.replace(new RegExp(from.charAt(i), "g"), to.charAt(i));
+            }
+            str = str
+                .replace(/[^a-z0-9 -]/g, "") // remove invalid chars
+                .replace(/\s+/g, "-") // collapse whitespace and replace by -
+                .replace(/-+/g, "-"); // collapse dashes
+
+            return str;
+        },
+        sendLogDescargarArchivo(datos) {
+            return this.$http
+                .post(this.url_api + "/archivo/descarga", datos, this.config_header)
+                .then(response => {
+                    if (response.data.response) {
+                        // log insertado correctamente
+                    } else {
+                        // error
+                    }
+                })
+                .catch(error => {
+                    this.strError =
+                        "Se ha producido un error al tratar de insertar el log de descarga, error: " + error;
+                });
+        },
+        downloadFile: function (idar, nom) {
+            this.downloadingFile = true;
+            this.launchToast(true);
+            this.downloadedItem.push({
+                idItem: idar,
+                nombre: nom,
+                loading: true
+            });
+            this.$http
+                .get(this.url_api + /archivo/ + idar, this.config_header)
+                .then(response => {
+                    if (response.data.data) {
+                        this.getFile(
+                            response.data.data,
+                            response.data.mime,
+                            response.data.name
+                        );
+                        // completamos del toast cargando...
+                        let index = this.downloadedItem.findIndex(item => {
+                            return item.idItem === idar;
+                        });
+                        this.downloadedItem[index].loading = false;
+                        this.downloadingFile = false;
+                        
+                        // Datos log
+                        let datosLog = {
+                            'IDAR': idar,
+                            'IDUS': this.$store.getters.user.id,
+                            'DISP': 'WEB'
+                        }
+                        this.sendLogDescargarArchivo(datosLog);
+                    } else {
+                        this.strError = response.data.message;
+                    }
+                })
+                .catch(error => {
+                    this.strError = error;
+                });
+        },
+        getFile: function (data, mime, name) {
+            // Transformar el bs64 a un archivo
+            var byteCharacters = atob(data);
+            var byteNumbers = new Array(byteCharacters.length);
+            for (var i = 0; i < byteCharacters.length; i++) {
+                byteNumbers[i] = byteCharacters.charCodeAt(i);
+            }
+            var byteArray = new Uint8Array(byteNumbers);
+            var blob = new Blob([byteArray], { type: mime });
+
+            return download(blob, name, mime);
+        },
+        launchToast: function (show) {
+            $(".collapse").collapse();
+            var x = document.getElementById("toastNew");
+            if (show) {
+                x.className += " animated fadeIn";
+                $(".toast").toast("show");
+            } else {
+                setTimeout(function () {
+                    $(".toast").toast("hide");
+                }, 300);
+            }
+        },
+        scrollBottom() {
+            //Ajustar el scroll
+            var scrollingElement = document.scrollingElement || document.body;
+            $(scrollingElement).animate(
+                {
+                    scrollTop: $("#divSecciones").position().top
+                },
+                1000
+            );
+        },
+        filtrarFecha(tipo) {
+            // Filtramos dependiendo del tipo
+            var filtrarFecha = [];
+            switch (tipo) {
+                case 'est':
+                    filtrarFecha = this.estantesPermitidos;
+                    break;
+                case 'sec':
+                    filtrarFecha = this.seccionesFilter;
+                    break;
+                case 'cate':
+                    filtrarFecha = this.categoriaFilter;
+                    break;
+                default:
+                    filtrarFecha = this.archivosFilter;
+                    break;
+            }
+
+            return filtrarFecha.filter(dato => {
+                let fechaFormato = moment(dato.FECR, "DD-MMM-YY").format("YYYY-MM-DD");
+                // hacemos busqueda entre fechas
+                if(!(moment(fechaFormato).isBefore( this.filtroFecha.fechaInicial ) || 
+                moment(fechaFormato).isAfter( this.filtroFecha.fechaFinal ) ) )
+                {
+                    return dato;
+                }
+                // si no tenemos fechas retornamos mismos datos
+                if (
+                    this.filtroFecha.fechaInicial === "" &&
+                    this.filtroFecha.fechaFinal === ""
+                ) {
+                    return dato;
+                }                
+               
+            });
+          
+        }
+    },
+    filters: {
+        capitalize: function (value) {
+            if (!value) return "";
+            value = value.toString();
+            return value.charAt(0).toUpperCase() + value.slice(1);
+        }
+    }
+}
+

+ 179 - 0
src/_mixin/user_mixin.js

@@ -0,0 +1,179 @@
+import $ from "jquery";
+import moment from "moment";
+import format from "date-fns/format";
+export const token = {
+    data() {
+        return {
+            imagenesData: [],
+            estantes: [],
+            secciones: [],
+            usuarios: [],
+            categorias: [],
+            archivos: [],
+            archivosPublicos: [],
+            config_header: [],
+            strError: '',
+            loading: false,
+            filtroFecha: {
+                filtroSeleccionado: "Cualquier fecha",
+                fechaLimiteCalendario: moment().format("YYYY-MM-DD"),
+                fechaInicial: "",
+                fechaFinal: "",
+                buscarFechaDias: 0,
+                activar: false,
+                verFiltrosBusqueda: false,
+                verModalBusqueda: false
+            },
+            configuracionDragg: {
+                animation: 200,
+                ghostClass: "ghost"
+            },
+            clickedFiltro: {
+                IDES: '',
+                IDSE: '',
+                IDCA: ''
+            }
+
+        }
+    },
+    created() {
+        const token = localStorage.getItem('access_token')
+        if (token) {
+            this.config_header = {
+                headers: {
+                    Authorization: 'Bearer ' + token
+                }
+            }
+        }
+    },
+    methods: {
+        async getImages(tipo) {
+            this.imagenesData = await this.getDataApi(
+                '/imagenes/' + tipo,
+                'Se ha producido un error al cargar las imagenes.'
+            )
+        },
+        async getEstantes() {
+            this.loading = true
+            this.estantes = await this.getDataApi(
+                '/estantes',
+                'Se ha producido un error al cargar los estantes.'
+            )
+            this.loading = false
+        },
+        async getSecciones() {
+            this.loading = true
+            this.secciones = await this.getDataApi(
+                '/secciones',
+                'Se ha producido un error al cargar las secciones.'
+            )
+            this.loading = false
+        },
+        async getCategorias() {
+            this.loading = true
+            this.categorias = await this.getDataApi(
+                '/categorias',
+                'Se ha producido un error al cargar las categorías.'
+            )
+            this.loading = false
+        },
+        async getUsuarios() {
+            const id = this.$store.getters.user.id;
+            this.loading = true
+            this.usuarios = await this.getDataApi(
+                '/usuarios/' + id,
+                'Se ha producido un error al cargar los usuarios.'
+            )
+            this.loading = false
+        },
+        async getArchivos(id, perfil) {
+            this.loading = true
+            this.archivos = await this.getDataApi(
+                '/archivos/' + id + '|' + perfil,
+                'Se ha producido un error al cargar los archivos desde el servidor.'
+            )
+            this.loading = false
+        },
+        async getArchivosPublicos() {
+            this.loading = true
+            this.archivosPublicos = await this.getDataApi(
+                '/archivosPublicos',
+                'Se ha producido un error al cargar los archivos publicos desde el servidor.'
+            )
+            this.loading = false
+        },
+        getPermisos(id) {
+            return this.$http
+                .get(this.url_api + "/usuario/permiso/" + id, this.config_header)
+                .then(response => {
+                    if (response.data.response) {
+                        this.permisosDatos = JSON.parse(response.data.result);
+
+                    } else {
+                        this.strError = response.data.message;
+                    }
+                })
+                .catch(error => {
+                    if (!error.status) {
+                        this.strError =
+                            "Whoops, parece que tenemos problemas al obtener los permisos, Network error...";
+                    } else {
+                        this.strError = msjError;
+                    }
+                });
+        },
+        getDataApi(ruta, msjError) {
+            return new Promise((resolve, reject) => {
+                this.$http
+                    .get(this.url_api + ruta, this.config_header)
+                    .then(response => {
+                        if (response.data.response) {
+                            resolve(response.data.result)
+                        } else {
+                            this.strError = response.data.message
+                        }
+                    })
+                    .catch(error => {
+                        // network error
+                        if (!error.status) {
+                            this.strError = 'Whoops, parece que tenemos problemas al obtener la información, Network error.'
+                        } else {
+                            this.strError = msjError
+
+                        }
+                        // reject(error)
+                        console.log(error);
+                    })
+            })
+        },
+        removeModal: function(nombre) {
+            $("#" + nombre).modal('hide');
+        },
+        showModal: function(nombre) {
+            $("#" + nombre).modal('show');
+        },
+        toFormData(obj) {
+            var formData = new FormData()
+            for (var key in obj) {
+                formData.append(key, obj[key])
+            }
+            return formData
+        },
+        formatoFechaCalendario(fechaInicial, fechaFinal) {
+            let formattedDates = "";
+            if (fechaInicial) {
+                formattedDates = format(fechaInicial, "DD/MMM/YYYY");
+            }
+            if (fechaFinal) {
+                formattedDates += " - " + format(fechaFinal, "DD/MMM/YYYY");
+            }
+            return formattedDates;
+        }
+    },
+    filters: {
+        // Fitrar la fecha en formato 19 jul 2019
+        formatDate(value, fmt = "D MMM YYYY") {
+            return value == null ? "" : moment(value, "DD-MMM-YY").format(fmt);
+        }
+    }
+}

+ 176 - 0
src/_mixin/user_mixinOld.js

@@ -0,0 +1,176 @@
+import $ from "jquery";
+import moment from "moment";
+import format from "date-fns/format";
+export const token = {
+    data() {
+        return {
+            imagenesData: [],
+            estantes: [],
+            secciones: [],
+            usuarios: [],
+            categorias: [],
+            archivos: [],
+            archivosPublicos: [],
+            config_header: [],
+            strError: '',
+            loading: false,
+            filtroFecha: {
+                filtroSeleccionado: "Cualquier fecha",
+                fechaLimiteCalendario: moment().format("YYYY-MM-DD"),
+                fechaInicial: "",
+                fechaFinal: "",
+                buscarFechaDias: 0,
+                activar: false,
+                verFiltrosBusqueda: false,
+                verModalBusqueda: false
+            },
+            configuracionDragg: {
+                animation: 200,
+                ghostClass: "ghost"
+            },
+            clickedFiltro: {
+                IDES: '',
+                IDSE: '',
+                IDCA: ''
+            }
+
+        }
+    },
+    created() {
+        const token = localStorage.getItem('access_token')
+        if (token) {
+            this.config_header = {
+                headers: {
+                    Authorization: 'Bearer ' + token
+                }
+            }
+        }
+    },
+    methods: {
+        async getImages(tipo) {
+            this.imagenesData = await this.getDataApi(
+                '/imagenes/' + tipo,
+                'Se ha producido un error al cargar las imagenes.'
+            )
+        },
+        async getEstantes() {
+            this.loading = true
+            this.estantes = await this.getDataApi(
+                '/estantes',
+                'Se ha producido un error al cargar los estantes.'
+            )
+            this.loading = false
+        },
+        async getSecciones() {
+            this.loading = true
+            this.secciones = await this.getDataApi(
+                '/secciones',
+                'Se ha producido un error al cargar las secciones.'
+            )
+            this.loading = false
+        },
+        async getCategorias() {
+            this.loading = true
+            this.categorias = await this.getDataApi(
+                '/categorias',
+                'Se ha producido un error al cargar las categorías.'
+            )
+            this.loading = false
+        },
+        async getUsuarios() {
+            const id = this.$store.getters.user.id;
+            this.loading = true
+            this.usuarios = await this.getDataApi(
+                '/usuarios/' + id,
+                'Se ha producido un error al cargar los usuarios.'
+            )
+            this.loading = false
+        },
+        async getArchivos(id, perfil) {
+            this.loading = true
+            this.archivos = await this.getDataApi(
+                '/archivos/' + id + '|' + perfil,
+                'Se ha producido un error al cargar los archivos desde el servidor.'
+            )
+            this.loading = false
+        },
+        async getArchivosPublicos() {
+            this.loading = true
+            this.archivosPublicos = await this.getDataApi(
+                '/archivosPublicos',
+                'Se ha producido un error al cargar los archivos publicos desde el servidor.'
+            )
+            this.loading = false
+        },
+        getPermisos(id) {
+            return this.$http
+                .get(this.url_api + "/usuario/permiso/" + id, this.config_header)
+                .then(response => {
+                    if (response.data.response) {
+                        this.permisosDatos = JSON.parse(response.data.result);
+
+                    } else {
+                        this.strError = response.data.message;
+                    }
+                })
+                .catch(error => {
+                    if (!error.status) {
+                        this.strError =
+                            "Whoops, parece que tenemos problemas al obtener los permisos, Network error...";
+                    } else {
+                        this.strError = msjError;
+                    }
+                });
+        },
+        getDataApi(ruta, msjError) {
+            return new Promise((resolve, reject) => {
+                this.$http
+                    .get(this.url_api + ruta, this.config_header)
+                    .then(response => {
+                        if (response.data.response) {
+                            resolve(response.data.result)
+                        } else {
+                            this.strError = response.data.message
+                        }
+                    })
+                    .catch(error => {
+                        // network error
+                        if (!error.status) {
+                            this.strError = 'Whoops, parece que tenemos problemas al obtener la información, Network error.'
+                        } else {
+                            this.strError = msjError
+
+                        }
+                        // reject(error)
+                        console.log(error);
+                    })
+            })
+        },
+        removeModal: function (nombre) {
+            $("#" + nombre).modal('hide');
+        },
+        toFormData(obj) {
+            var formData = new FormData()
+            for (var key in obj) {
+                formData.append(key, obj[key])
+            }
+            return formData
+        },
+        formatoFechaCalendario(fechaInicial, fechaFinal) {
+            let formattedDates = "";
+            if (fechaInicial) {
+                formattedDates = format(fechaInicial, "DD/MMM/YYYY");
+            }
+            if (fechaFinal) {
+                formattedDates += " - " + format(fechaFinal, "DD/MMM/YYYY");
+            }
+            return formattedDates;
+        }
+    },
+    filters: {
+        // Fitrar la fecha en formato 19 jul 2019
+        formatDate(value, fmt = "D MMM YYYY") {
+            return value == null ? "" : moment(value, "DD-MMM-YY").format(fmt);
+        }
+    }
+}

+ 17 - 0
src/components/TopProgress.vue

@@ -0,0 +1,17 @@
+<template>
+  <vue-topprogress ref="topProgress" errorColor="#ed022c"></vue-topprogress>
+</template>
+
+<script>
+import { vueTopprogress } from "vue-top-progress";
+
+export default {
+  mounted() {
+  }, components: {
+    vueTopprogress
+  },
+  props: {
+    errorColor: String
+  }
+};
+</script>

+ 292 - 0
src/components/UploadImage.vue

@@ -0,0 +1,292 @@
+<template>
+  <div>
+    <div class="cargarImagenes">
+      <!-- Botones para cargar la imagen -->
+      <button
+        type="button"
+        class="btn btn-outline-danger"
+        data-toggle="modal"
+        data-target="#cargarImagen"
+        @click="modalActivo=true"
+      >Cargar imagen</button> &nbsp;
+    </div>
+    <!-- Start Upload Image Modal -->
+    <div
+      class="modal animated bounceInDown fast"
+      id="cargarImagen"
+      tabindex="-1"
+      role="dialog"
+      aria-labelledby="cargarImagenLabel"
+      aria-hidden="true"
+      v-if="modalActivo"
+    >
+      <div class="modal-dialog" role="document">
+        <div class="modal-content">
+          <div class="modal-header">
+            <div id="modal-titulo" style="width:100%;text-align: center;">
+              <h5 class="modal-title" id="cargarImagenLabel">Cargar Imagen</h5>
+            </div>
+            <button
+              type="button"
+              class="close"
+              data-dismiss="modal"
+              aria-label="Close"
+              @click="modalActivo=false"
+            >
+              <span aria-hidden="true">&times;</span>
+            </button>
+          </div>
+          <div class="modal-body">
+            <div class="form-row">
+              <div class="form-group col-md-6">
+                <label for="nombre">Nombre</label>
+                <input
+                  class="form-control"
+                  v-validate="'required|regex:^(?:[A-Za-z0-9 _.]*)$'"
+                  :class="{'input': true, 'is-danger': errors.has('nombre') }"
+                  v-model="nombre"
+                  name="nombre"
+                  id="nombre"
+                  placeholder="Nombre de la imagen"
+                />
+                <span
+                  v-show="errors.has('nombre')"
+                  class="invalid-feedback-form"
+                >{{ errors.first('nombre') }}</span>
+              </div>
+              <div class="form-group col-md-6">
+                <label for="alcance">Alcance</label>
+                <select
+                  class="custom-select"
+                  id="alcance"
+                  v-model="alcance"
+                  name="alcance"
+                  v-validate="'required'"
+                  :class="{'input': true, 'is-danger': errors.has('alcance') }"
+                >
+                  <option value>Seleccione una opción</option>
+                  <option value="**ES">Estantes</option>
+                  <option value="ESSE">Estantes y Secciones</option>
+                  <option value="ESCA">Estantes y Categorías</option>
+                  <option value="**SE">Secciones</option>
+                  <option value="SECA">Secciones y Categorías</option>
+                  <option value="**CA">Categorías</option>
+                  <option value="****">Estantes, Secciones y Categorías</option>
+                  <option value="ARCH">Solo archivos</option>
+                </select>
+                <span
+                  v-show="errors.has('alcance')"
+                  class="invalid-feedback-form"
+                >{{ errors.first('alcance') }}</span>
+              </div>
+            </div>
+            <div class="form-check mb-2 mr-sm-2" v-show="alcance =='ARCH'">
+              <input class="form-check-input" type="checkbox" id="filePrede" v-model="imgPred" />
+              <label
+                class="form-check-label"
+                for="filePrede"
+              >¿La imagen será predeterminada para otros archivos?</label>
+            </div>
+            <div v-show="alcance =='ARCH' && imgPred">
+              <!-- Componentent MultiSelect -->
+              <label>Archivos donde aparecerá la imagen</label>
+              <multiselect
+                v-model="extensionesArr"
+                placeholder="Seleccione una extensión de la lista"
+                deselect-label="Presione para borrar"
+                select-label="Presione para seleccionar"
+                label="EXTE"
+                track-by="EXTE"
+                :options="options"
+                :multiple="true"
+                :custom-label="nameWithLang"
+              ></multiselect>
+            </div>
+
+            <div class="input-group mt-2">
+              <label for="file" class="mb-3">Seleccione una imagen con dimensiones 783 x 1021 píxeles con formato JPG o PNG.</label>
+              <input
+                type="file"
+                ref="fileupload"
+                accept="image/*"
+                class="form-control-file"
+                @change="onImageChanged"
+                v-validate="'required'"
+                name="imagen"
+                id="file"
+              />
+              <span
+                v-show="errors.has('imagen')"
+                class="invalid-feedback-form"
+              >{{ errors.first('imagen') }}</span>
+              <img
+                :src="previewImage"
+                class="img-thumbnail mt-2"
+                v-if="fileImage"
+                style="width:50%"
+              />
+            </div>
+          </div>
+          <div class="modal-footer">
+            <button
+              type="button"
+              class="btn btn-light"
+              data-dismiss="modal"
+              aria-label="Close"
+              @click="modalActivo=false"
+            >Cerrar</button>
+            <button
+              class="btn btn-danger btn-sm"
+              @click="onUpload();modalActivo = true"
+              :disabled="isLoading"
+            >
+              <div class="save" v-if="!isLoading">
+                <i class="material-icons icon-align">save</i>
+                Registrar
+              </div>
+              <div v-if="isLoading">
+                <span class="spinner-border spinner-border-sm" role="status" aria-hidden="true" ></span>
+                Loading...
+              </div>
+            </button>
+          </div>
+        </div>
+      </div>
+    </div>
+    <!-- END Upload Image Modal -->
+  </div>
+</template>
+
+<style src="vue-multiselect/dist/vue-multiselect.min.css"></style>
+<style scoped>
+input[type="checkbox"],
+input[type="radio"] {
+  box-shadow: 0 0 1px 0px #6c757d;
+}
+.modal-header {
+  border-bottom: 2px solid #dee2e6 !important;
+}
+.multiselect__tag {
+  background: #fb0b0bb5 !important;
+}
+</style>
+
+<script>
+import { token } from "../_mixin/user_mixin.js";
+import Multiselect from "vue-multiselect";
+export default {
+  name: "UploadImage",
+  mixins: [token],
+  components: {
+    Multiselect
+  },
+  data: () => ({
+    strError: "",
+    modalActivo: false,
+    imageUpdated: false,
+    alcance: "",
+    nombre: "",
+    fileImage: "",
+    previewImage: "",
+    imgPred: "",
+    extensiones: "",
+    extensionesArr: [],
+    options: [],
+    isLoading: false
+  }),
+  mounted: function() {
+    this.getExtensiones();
+  },
+  methods: {
+    onImageChanged: function(event) {
+      // Preview imagen
+      this.fileImage = event.target.files[0];
+      this.previewImage = URL.createObjectURL(this.fileImage);
+    },
+    getExtensiones: function() {
+      this.$http
+        .get(
+          this.url_api+"/extensiones",
+          this.config_header
+        )
+        .then(response => {
+          if (response.data.response) {
+            this.options = response.data.result;
+          }
+        })
+        .catch(error => {
+          this.strError = "Se ha producido un error al cargar las imagenes.";
+        });
+    },
+    onUpload: function() {
+      this.$validator.validateAll().then(result => {
+        if (result) {
+          // upload file
+          if (this.fileImage.length != 0) {
+            // Obtener las extensiones
+            if (this.alcance == "ARCH" && this.imgPred) {
+              this.extensiones = "";
+              // Solamente agregar el ; al final
+              let cont = this.extensionesArr.length;
+              let i = 1;
+              this.extensionesArr.forEach(element => {
+                this.extensiones += element.code;
+                if (i < cont) {
+                  this.extensiones += ";";
+                }
+                i++;
+              });
+            }
+            this.isLoading = true; // Lanzamos cargador
+            let datosImg = {
+              nombre: this.nombre,
+              alcance: this.alcance,
+              extension: this.extensiones,
+              imagen: this.fileImage
+            };
+            var formData = this.toFormData(datosImg);
+            var heads = this.config_header;
+            heads.headers["content-type"] = "multipart/form-data";
+            this.$http
+              .post(
+                this.url_api+"/imagenes",
+                formData,
+                heads
+              )
+              .then(response => {
+                this.imageUpdated = true;
+                this.$emit("imagenUpdatedEvent", this.imageUpdated);
+                this.$toasted.show(response.data.message, this.$toast_success);
+
+                // Limpiamo despues del submit
+                this.isLoading = false; // Lanzamos cargador
+                this.nombre = "";
+                this.alcance = "";
+                this.fileImage = "";
+                this.extensiones = "";
+
+                // Limpiar input tipo file
+                const input = this.$refs.fileupload;
+                input.type = "text";
+                input.type = "file";
+
+                // Reset the validator after the next tick
+                this.$nextTick().then(() => {
+                  this.$validator.reset();
+                  this.errors.clear();
+                });
+              })
+              .catch(error => {
+                this.isLoading = false; // Lanzamos cargador
+                this.$toasted.show(response.data.message, this.$toast_error);
+              });
+          } // Termina if
+        }
+      });
+    },
+    nameWithLang({ DES, EXTE }) {
+      return `${DES} (${EXTE})`;
+    }
+  }
+};
+</script>

+ 222 - 0
src/components/shared/Navbar.vue

@@ -0,0 +1,222 @@
+<template>
+  <header class="topbar fixed-top mb-4">
+    <div id="header">
+      <div id="header_container">
+        <div id="header_inner">
+          <router-link :to="{ name: 'home'}">
+            <img id="imgHeader" :src="require('@/assets/header_logo_1.png')" alt="Soler y Palau" />
+          </router-link>
+        </div>
+        <div class="menuMovil">
+          <div class="row">
+            <div class="mt-2 col-sm-2 col-2">
+              <img
+                id="imagenMovil"
+                class="img-responsive"
+                v-if="path === '/public'"
+                :src="require('@/assets/header_logo_1.png')"
+                alt="Soler y Palau"
+              />
+              <a
+                class="nav-link sidebartoggler"
+                @click="openMenu"
+                style="cursor:pointer"
+                v-show="path === '/admin'"
+                data-toggle="tooltip"
+                :title="[menuIsOpen ? 'Cerrar Menú': 'Abrir Menú']"
+              >
+                <i class="material-icons">{{ menuIsOpen ? 'close': 'reorder'}}</i>
+              </a>
+            </div>
+            <div class="mt-2 col-sm-10 col-10">
+              <div class="float-right mt-2">
+                <b class="font-weight-bold">Bienvenido:</b>
+                {{ user }}
+                <a
+                  href="#"
+                  @click.prevent="logout"
+                  class="link"
+                  data-toggle="tooltip"
+                  title="Salir"
+                >
+                  <i
+                    class="material-icons"
+                    style="font-size:18px;vertical-align:middle;color:#f62d51 "
+                  >exit_to_app</i>
+                </a>
+                <br />
+                <router-link
+                  tag="div"
+                  :to="{ name: 'homeAdmin'}"
+                  v-if="isLoggedIn && path === '/public'"
+                  class="text-muted lbl"
+                  style="cursor:pointer"
+                >Ir a Administrador</router-link>
+                <router-link
+                  :to="{ name: 'home'}"
+                  tag="div"
+                  class="text-muted lbl"
+                  style="cursor:pointer"
+                  v-else
+                >Ir a Inicio</router-link>
+              </div>
+            </div>
+          </div>
+        </div>
+
+        <div id="header_user_container">
+          <div id="header_inner_user_container_table" v-if="isLoggedIn">
+            <b class="font-weight-bold">Bienvenido:</b>
+            {{ user }}
+            <a
+              href="#"
+              @click.prevent="logout"
+              class="link"
+              data-toggle="tooltip"
+              title="Salir"
+            >
+              <i
+                class="material-icons"
+                style="font-size:18px;vertical-align:middle;color:#f62d51 "
+              >exit_to_app</i>
+            </a>
+            <br />
+            <span class="l2">
+              <span class="division">
+                <a href="#" @click.prevent>Cambiar contraseña</a>
+              </span>
+              <span class="division">
+                |
+                <a href="#" @click.prevent>Preguntas frecuentes</a>
+              </span>
+              <span class="division">
+                |
+                <a href="#" @click.prevent>Reportar una falla</a>
+              </span>
+              <span class="division" v-if="revisarPermisos">
+                |
+                <router-link
+                  :to="{ name: 'homeAdmin' }"
+                  v-if="isLoggedIn && path === '/public' "
+                >Administrador</router-link>
+                <router-link :to="{name: 'home'}" v-else>Principal</router-link>
+              </span>
+            </span>
+          </div>
+        </div>
+      </div>
+    </div>
+  </header>
+</template>
+<script>
+import $ from "jquery";
+import { token } from "../../_mixin/user_mixin.js";
+export default {
+  name: "Navbar",
+  mixins: [token],
+  data() {
+    return {
+      verLabelAdmin: false,
+      menuIsOpen: false,
+      role: "",
+      user: "",
+      path: ""
+    };
+  },
+  async mounted() {
+    // Resize Sidebar
+    var i = function() {
+      (window.innerWidth > 0 ? window.innerWidth : this.screen.width) < 1170
+        ? ($("body").addClass("mini-sidebar"),
+          $(".navbar-brand span").hide(),
+          $(".sidebartoggler i").addClass("ti-menu"))
+        : ($("body").removeClass("mini-sidebar"),
+          $(".navbar-brand span").show(),
+          $(".sidebartoggler i").removeClass("ti-menu"));
+    };
+    $(window).ready(i);
+    $(window).on("resize", i);
+
+    /* await this.getPermisos(this.$store.getters.user.id).then(() => {
+      this.revisarPermisos();
+    }); */
+  },
+  methods: {
+    logout: function() {
+      this.$store.dispatch("logout").then(() => {
+        window.open(this.URL_SOLER, "_parent");
+      });
+    },
+    openMenu() {
+      $("body").hasClass("mini-sidebar")
+        ? ($("body").trigger("resize"),
+          $("body").removeClass("mini-sidebar"),
+          $(".navbar-brand span").show(),
+          $(".sidebartoggler i").addClass("ti-menu"))
+        : ($("body").trigger("resize"),
+          $("body").addClass("mini-sidebar"),
+          $(".navbar-brand span").hide(),
+          $(".sidebartoggler i").removeClass("ti-menu"));
+
+      $("body").toggleClass("show-sidebar"),
+        $(".nav-toggler i").toggleClass("ti-menu"),
+        $(".nav-toggler i").addClass("ti-close");
+      $(".sidebartoggler i").toggleClass("ti-menu");
+      this.menuIsOpen = !this.menuIsOpen;
+    }
+  },
+  computed: {
+    revisarPermisos() {
+      if (this.role === "admin" && this.isLoggedIn) {
+        return true;
+      }
+      if ((this.$store.getters.permisos.length !== 0) & this.isLoggedIn) {
+      
+      var permisos = JSON.parse(this.$store.getters.permisos);
+        var estantes = permisos[0].children;
+        var result = estantes.filter(estante => {
+          let arrEstante = estante["text"].split(" | ");
+          let strPermiso = arrEstante[1];
+          return strPermiso === "Ver + Publicar";
+        }).length;
+        return result === 0 ? false : true;
+      
+      }
+      return false;
+    },
+    isLoggedIn() {
+      this.$route.matched.some(name => {
+        return (this.path = name.path === "/admin" ? name.path : "/public");
+      });
+      this.role = this.$store.getters.user.role;
+      this.user = this.$store.getters.user.name;
+
+      return this.$store.getters.isLoggedIn;
+    }
+  }
+};
+</script>
+<style>
+.fix-sidebar.fix-header.mini-sidebar {
+  padding-right: 0 !important;
+}
+.topbar.fixed-top.mb-4 {
+  padding-right: 0 !important;
+}
+body.modal-open {
+  padding-right: 0 !important;
+}
+html {
+  overflow-y: scroll;
+}
+</style>
+<style scoped>
+.lbl {
+  color: #798699;
+  font-size: 14px;
+}
+a:hover {
+  text-decoration: none !important;
+}
+</style>
+

+ 176 - 0
src/components/shared/Sidebar.vue

@@ -0,0 +1,176 @@
+<template>
+  <aside class="fix-sidebar left-sidebar card-no-border">
+    <!-- Sidebar scroll-->
+    <div class="scroll-sidebar">
+      <!-- User profile -->
+      <div class="user-profile">
+        <div class="float-right pb-2">
+          <a
+            class="nav-link sidebartoggler hidden-sm-down text-muted waves-effect waves-red"
+            @click="openMenu"
+            style="cursor:pointer"
+            data-toggle="tooltip"
+            :title="[menuIsOpen ? 'Cerrar Menú': 'Abrir Menú']"
+          >
+            <i class="material-icons">{{ menuIsOpen ? 'reorder': 'arrow_back'}}</i>
+          </a>
+          
+        </div>
+        <!-- User profile image -->
+        <div class="profile-img">
+          <router-link :to="{ name: 'homeAdmin'}">
+            <img :src="require('@/assets/quiosco-icon.png')" alt="Logo Quiosco" />
+          </router-link>
+        </div>
+        <br />
+        <div class="profile-text text-center">
+          <!-- Administrador -->
+          <dt class="font-weight-bold">QUIOSCO</dt>
+          <p>Administrador</p>
+        </div>
+      </div>
+      <nav class="sidebar-nav">
+        <ul class="metismenu" id="sidebarnav">
+          <li class="nav-small-cap">ADMINISTRAR</li>
+          <li v-if="$store.getters.user.role === 'admin'">
+            <a class="has-arrow" href="#quiosco" data-toggle="collapse" aria-expanded="false">
+              <i class="material-icons">library_books</i>
+              <span class="hide-menu">Gestionar Quiosco</span>
+            </a>
+            <ul aria-expanded="false" id="quiosco" class="collapse smooth-enter-active">
+              <li>
+                <router-link :to="{name: 'estantesAdmin' }" active-class="active">Estantes</router-link>
+              </li>
+              <li>
+                <router-link :to="{name: 'seccionesAdmin' }" active-class="active">Secciones</router-link>
+              </li>
+              <li>
+                <router-link :to="{name: 'categoriasAdmin' }" active-class="active">Categorías</router-link>
+              </li>
+            </ul>
+          </li>
+          <li>
+            <router-link :to="{ name: 'archivosAdmin' }" active-class="active">
+              <i class="material-icons">file_copy</i>
+              <span class="hide-menu">Archivos</span>
+            </router-link>
+          </li>
+          <li v-if="$store.getters.user.role === 'admin'">
+            <router-link :to="{ name: 'usuariosAdmin' }" active-class="active">
+              <i class="material-icons">person</i>
+              <span class="hide-menu">Roles</span>
+            </router-link>
+          </li>
+        </ul>
+      </nav>
+      <!-- End Sidebar navigation -->
+    </div>
+    <!-- End Bottom points-->
+  </aside>
+</template>
+
+<script>
+import $ from "jquery";
+export default {
+  name: "Sidebar",
+  data() {
+    return {
+      menuIsOpen: false
+    }
+  },
+  methods: {
+    openMenu() {
+      $("body").hasClass("mini-sidebar")
+        ? ($("body").trigger("resize"),
+          $("body").removeClass("mini-sidebar"),
+          $(".navbar-brand span").show(),
+          $(".sidebartoggler i").addClass("ti-menu"))
+        : ($("body").trigger("resize"),
+          $("body").addClass("mini-sidebar"),
+          $(".navbar-brand span").hide(),
+          $(".sidebartoggler i").removeClass("ti-menu"));
+
+      $("body").toggleClass("show-sidebar"),
+        $(".nav-toggler i").toggleClass("ti-menu"),
+        $(".nav-toggler i").addClass("ti-close");
+      $(".sidebartoggler i").toggleClass("ti-menu");
+      this.menuIsOpen = !this.menuIsOpen;
+    }
+  },
+  
+};
+</script>
+
+<style scoped>
+.smooth-enter-active,
+.smooth-leave-active {
+  transition: height 0.1s;
+  overflow: hidden;
+}
+.smooth-enter,
+.smooth-leave-to {
+  height: 0;
+}
+.mini-sidebar .sidebar-nav {
+  background: transparent !important;
+}
+.mini-sidebar .sidebar-nav ul li.active {
+  background: transparent !important;
+}
+.router-link-exact-active.active {
+  font-weight: bold;
+  background: #f42849;
+}
+.card-no-border .left-sidebar,
+.card-no-border .sidebar-nav {
+  background: #f42849;
+}
+.left-sidebar {
+  background: #f42849;
+  box-shadow: 1px 0px 20px rgba(0, 0, 0, 0.08) !important;
+}
+.sidebar-nav ul li.nav-small-cap {
+  color: #fff;
+}
+.sidebar-nav > ul > li > a i {
+  color: #fff;
+}
+.sidebar-nav ul li a {
+  color: #fff;
+}
+.user-profile .profile-text {
+  color: #000;
+}
+.sidebar-nav > ul > li > a:hover {
+  border-left: 3px solid #fff;
+}
+.sidebar-nav > ul > li.active > a {
+  border-left: 3px solid #fff;
+  color: #fff;
+  font-weight: bold;
+}
+.user-profile {
+  text-align: center;
+  position: relative;
+  background: #fff;
+  margin-top: -25px;
+  padding-top: 30px;
+}
+.sidebar-nav .has-arrow::after {
+  border-color: #fff;
+}
+.mini-sidebar .sidebar-nav #sidebarnav > li > ul {
+  background: #3535359e;
+}
+.mini-sidebar .sidebar-nav #sidebarnav > li:hover > a {
+  background: #3535359e;
+  color: #ffffff;
+  border-color: #fff;
+}
+.sidebar-nav ul li.active {
+  background: #dc2747 !important;
+}
+.card-no-border .sidebar-nav > ul > li > a.active {
+  background: #dc2747 !important;
+}
+</style>

+ 129 - 0
src/main.js

@@ -0,0 +1,129 @@
+
+
+import Vue from 'vue'
+import App from './App.vue'
+import router from './router'
+import store from './store'
+
+import * as Sentry from '@sentry/browser';
+import * as Integrations from '@sentry/integrations';
+
+Vue.config.errorHandler = function (err, vm, info)  {
+  console.log('[Error Global]:  Error: '+ err + ' [información]: ' + info);
+}; 
+
+ Sentry.init({
+  dsn: 'https://ef9f8060706f4e609a23a4643f9b21f3@sentry.io/1807657',
+  integrations: [new Integrations.Vue({Vue, attachProps: true})],
+});
+
+import VueSweetalert2 from 'vue-sweetalert2';
+import "sweetalert2/dist/sweetalert2.css";
+
+import 'bootstrap'
+import 'bootstrap/dist/css/bootstrap.min.css'
+import 'material-icons/iconfont/material-icons.css'
+
+import axios from 'axios'
+import Toasted from 'vue-toasted'
+
+import vueTopprogress from 'vue-top-progress'
+
+import es from 'vee-validate/dist/locale/es'
+import VeeValidate, { Validator } from 'vee-validate'
+
+import AirbnbStyleDatepicker from 'vue-airbnb-style-datepicker'
+import 'vue-airbnb-style-datepicker/dist/vue-airbnb-style-datepicker.min.css'
+
+import VueLazyload from 'vue-lazyload'
+
+const optionsAlert = {
+  confirmButtonColor: '#ff6674',
+  cancelButtonColor: '#c2c2c2',
+};
+Vue.use(VueSweetalert2,optionsAlert);
+
+const datepickerOptions = {
+  colors: {
+    selected: '#dd0302',
+    inRange: '#ff6674',
+    selectedText: '#fff',
+    text: '#565a5c',
+    inRangeBorder: '#fff',
+    disabled: '#fff',
+    hoveredInRange: '#ff6674'
+  },
+  days: ['Lunes', 'Martes', 'Miércoles', 'Jueves', 'Viernes', 'Sábado', 'Domingo'],
+  daysShort: ['Lun', 'Mar', 'Mier', 'Jue', 'Vier', 'Sab', 'Dom'],
+  monthNames: [
+    'Enero',
+    'Febrero',
+    'Marzo',
+    'Abril',
+    'Mayo',
+    'Junio',
+    'Julio',
+    'Agosto',
+    'Septiembre',
+    'Octubre',
+    'Noviembre',
+    'Diciembre'
+  ],
+  texts: {
+    apply: 'Aplicar',
+    cancel: 'Cancelar',
+    keyboardShortcuts: 'Keyboard Shortcuts'
+  }
+
+}
+// Date picker config
+Vue.use(AirbnbStyleDatepicker, datepickerOptions)
+Vue.use(VeeValidate, {
+  inject: true,
+  fieldsBagName: 'veeFields'
+})
+
+Validator.localize('es', es)
+
+Vue.use(vueTopprogress)
+
+Vue.prototype.$http = axios
+Vue.use(Toasted)
+Vue.prototype.$toast_success = {
+  theme: 'bubble',
+  type: 'success',
+  icon: 'check_circle',
+  iconPack: 'material',
+  position: 'top-right',
+  duration: 5000
+}
+Vue.prototype.$toast_error = {
+  theme: 'bubble',
+  type: 'error',
+  icon: 'report_problem',
+  iconPack: 'material',
+  position: 'top-right',
+  duration: null,
+  action: {
+    icon: 'close',
+    onClick: (e, toastObject) => {
+      toastObject.goAway(0);
+    }
+  }
+}
+
+Vue.prototype.URL_SOLER = store.state.url_api+'acceso.php'
+Vue.prototype.url_api = store.state.url_api+'api/v1/quiosco'
+
+Vue.use(VueLazyload, {
+  preLoad: 1.9,
+  error: require('@/assets/images/loading-error.png'),
+  loading: require('@/assets/images/loading2.gif'),
+  attempt: 1
+})
+Vue.config.productionTip = false
+new Vue({
+  router,
+  store,
+  render: function (h) { return h(App) }
+}).$mount('#app')

+ 199 - 0
src/router.js

@@ -0,0 +1,199 @@
+import Vue from 'vue'
+import Router from 'vue-router'
+import store from './store.js'
+// Public
+import Login from './views/public/Login.vue'
+import Protected from './views/public/ProtectedPage.vue'
+import Home from './views/public/Home.vue'
+import Secciones from './views/public/Secciones.vue'
+import Categorias from './views/public/Categorias.vue'
+import CategoriasArchivos from './views/public/CategoriasArchivos.vue'
+import Loading from './views/public/Loading.vue'
+// Admin
+import Admin from './views/admin/Admin.vue'
+import HomeAdmin from './views/admin/HomeAdmin.vue'
+import EstantesAdmin from './views/admin/EstantesAdmin.vue'
+import SeccionesAdmin from './views/admin/SeccionesAdmin.vue'
+import CategoriasAdmin from './views/admin/CategoriasAdmin.vue'
+import UsuariosAdmin from './views/admin/UsuariosAdmin.vue'
+import ArchivosAdmin from './views/admin/ArchivosAdmin.vue'
+
+Vue.use(Router)
+const router = new Router({
+  mode: 'history',
+  //  base: 'QA/quiosco',
+  routes: [
+    {
+      path: '/admin/',
+      component: Admin,
+      children: [
+        {
+          path: '',
+          name: 'homeAdmin',
+          component: HomeAdmin,
+          meta: {
+            title:'Administrador - S&P ',
+            requiresAuth: true,
+            canViewCol: true
+          }
+        },
+        {
+          path: 'estantesAdmin',
+          name: 'estantesAdmin',
+          component: EstantesAdmin,
+          meta: {
+            title:'Administrar Estantes - S&P ',
+            requiresAuth: true,
+            canViewCol: false
+          }
+        },
+        {
+          path: 'seccionesAdmin',
+          name: 'seccionesAdmin',
+          component: SeccionesAdmin,
+          meta: {
+            title:'Administrar Secciones - S&P ',
+            requiresAuth: true,
+            canViewCol: false
+          }
+        },
+        {
+          path: 'categoriasAdmin',
+          name: 'categoriasAdmin',
+          component: CategoriasAdmin,
+          meta: {
+            title:'Administrar Categorías - S&P ',
+            requiresAuth: true,
+            canViewCol: false
+          }
+        },
+        {
+          path: 'usuariosAdmin',
+          name: 'usuariosAdmin',
+          component: UsuariosAdmin,
+          meta: {
+            title:'Administrar Usuarios - S&P ',
+            requiresAuth: true,
+            canViewCol: false
+          }
+        },
+        {
+          path: 'archivosAdmin',
+          name: 'archivosAdmin',
+          component: ArchivosAdmin,
+          meta: {
+            title:'Administrar Archivos - S&P ',
+            requiresAuth: true,
+            canViewCol: true
+          }
+        }
+      ]
+    },
+    {
+      path: '/loading/:datos',
+      name: 'loading',
+      component: Loading,
+      meta: {
+        title:'Cargando... '
+      }
+    },
+    {
+      path: '/login',
+      name: 'login',
+      component: Login,
+      meta: {
+        title:'Iniciar Sesión '
+      }
+    },
+    {
+      path: '/',
+      name: 'home',
+      component: Home,
+      meta: {
+        title:'Estantes - Quiosco S&P ',
+        requiresAuth: true,
+        canViewCol: true
+      }
+    },
+    {
+      path: '/division/:est/',
+      name: 'estantes',
+      component: Secciones,
+      meta: {
+        title:'Secciones - Quiosco S&P ',
+        requiresAuth: true,
+        canViewCol: true
+      }
+    },
+    {
+      path: '/division/:est/:sec',
+      name: 'seccion',
+      component: Categorias,
+      meta: {
+        title:'Categorías - Quiosco S&P ',
+        requiresAuth: true,
+        canViewCol: true
+      }
+    },
+    {
+      path: '/division/:est/:sec/:cat',
+      name: 'categoria',
+      component: CategoriasArchivos,
+      meta: {
+        title:'Archivos - Quiosco S&P ',
+        requiresAuth: true,
+        canViewCol: true
+      }
+    },
+    {
+      path: '/401',
+      name: '401',
+      component: Protected,
+      meta: {
+        title:'Error 404 - Quiosco S&P ',
+        requiresAuth: false,
+        canViewCol: true
+      }
+    },
+    {
+      path: '*',
+      redirect: { name: 'home' },
+      meta: {
+        requiresAuth: true,
+        canViewCol: true
+      }
+    }
+  ]
+})
+router.beforeEach((to, _from, next) => {
+  
+  const requiresAuth = to.matched.some(record => record.meta.requiresAuth)
+  const canViewCol = to.matched.some(record => record.meta.canViewCol)
+  const role = store.getters.user.role
+  const currenUser = store.getters.user.name
+  document.title = to.meta.title
+
+  if (requiresAuth) {
+    // Si no esta auth
+    if (currenUser === '') {
+
+      return next({
+        name: 'loading',
+        params: { datos: 'error' }
+      })
+    }
+    if (role === 'colaborador') {
+      // Si el colaborador no tiene permiso de ver la vista
+      if (!canViewCol) {
+        return next({ name: '401' })
+      }
+      if (to.name === 'homeAdmin') {
+        return next({ name: 'archivosAdmin' })
+      }
+    }
+    return next()
+  } else {
+    return next()
+  }
+})
+export default router

+ 165 - 0
src/store.js

@@ -0,0 +1,165 @@
+import Vue from 'vue'
+import Vuex from 'vuex'
+import axios from 'axios'
+
+Vue.use(Vuex)
+const store = new Vuex.Store({
+  state: {
+    url_api: 'https://smart.sportalsolerpalau.mx/QA/',
+    status: '',
+    token: localStorage.getItem('access_token') || '',
+    user: {
+      name: localStorage.getItem('user') || '',
+      role: localStorage.getItem('role') || '',
+      id: localStorage.getItem('id') || ''
+    },
+    permisos: {
+      datos: localStorage.getItem('permisos') || [],
+      error: false,
+      mensaje: ''
+    },
+    msg: '',
+
+  },
+  mutations: {
+    auth_request(state) {
+      state.status = 'loading'
+    },
+    auth_success(state, token) {
+      state.status = (token.response) ? 'Success' : 'Error'
+      state.token = token.token
+      state.user.name = token.userName
+      state.user.role = token.role
+      state.user.id = token.id
+    },
+    auth_error(state, msg) {
+      state.status = 'error'
+      state.msg = msg
+    },
+    logout(state) {
+      state.status = ''
+      state.token = ''
+      state.user.name = ''
+      state.user.role = ''
+      state.user.id = '',
+      state.permisos = {
+        datos: [],
+        error: false,
+        mensaje: ''
+      }
+    },
+    get_permisos(state, permiso) {
+      state.permisos.datos = permiso.permisosDatos;
+      state.permisos.error = permiso.error;
+      state.permisos.mensaje = permiso.msg;
+
+    }
+  },
+  actions: {
+    getPermisos({ commit, state }, id) {
+      return new Promise((resolve, reject) => {
+        const token = localStorage.getItem('access_token')
+        if (token) {
+          var config_header = {
+            headers: {
+              Authorization: 'Bearer ' + token
+            }
+          }
+        }
+        axios.get(
+          state.url_api + 'api/v1/quiosco/usuario/permiso/' +  state.user.id,
+          config_header,
+        )
+          .then(response => {
+            if (response.data.response) {
+              let permisosDatos =  response.data.result;
+              let error = false;
+              let msg = "Permisos obtenidos correctamente";
+              localStorage.setItem('permisos', permisosDatos )
+
+              commit('get_permisos', { permisosDatos, error, msg })
+              resolve(response)
+
+            } else {
+              var permisosDatos = []
+              var error = true;
+              var msg = "Los permisos no se obtuvieron de la forma esperada."
+              commit('get_permisos', { permisosDatos, error, msg })
+              resolve(response)
+            }
+          })
+          .catch(error => {
+            if (!error.status) {
+              var permisosDatos = [];
+              var error = true
+              var msg = "Whoops, parece que tenemos problemas al obtener los permisos, Network error...";
+              commit('get_permisos', { permisosDatos, error, msg })
+              resolve(response)
+            } else {
+              this.strError = msjError;
+            }
+          });
+      }) // end promise
+    },
+    login({ commit, state, dispatch }, user) {
+      return new Promise((resolve, reject) => {
+        commit('auth_request')
+        axios({
+          url: state.url_api + 'api/v1/login',
+          data: user,
+          method: 'POST'
+        })
+          .then(resp => {
+            const response = resp.data.response
+            const msg = resp.data.message
+            if (response) {
+              const token = resp.data.result['access_token']
+              const userName = resp.data.result['user']
+              const role = (resp.data.result['role'] === 'COLABORADOR') ? 'colaborador' : 'admin'
+              const id = resp.data.result['dinum']
+
+              localStorage.setItem('access_token', token)
+              localStorage.setItem('user', userName)
+              localStorage.setItem('role', role)
+              localStorage.setItem('id', id)
+              commit('auth_success', { token, userName, role, id, response })
+              // Obtenemos permisos del usuario
+              store.dispatch('getPermisos')
+              resolve(response)
+            } else {
+              reject(response)
+              commit('auth_error', msg)
+            }
+          })
+          .catch(err => {
+            reject(err)
+            commit('auth_error', { err })
+            localStorage.removeItem('access_token')
+            localStorage.removeItem('user')
+            localStorage.removeItem('role')
+            localStorage.removeItem('id')
+          })
+      })
+    },
+    logout({ commit }) {
+      return new Promise(resolve => {
+        commit('logout')
+        localStorage.removeItem('access_token')
+        localStorage.removeItem('user')
+        localStorage.removeItem('role')
+        localStorage.removeItem('id')
+        localStorage.removeItem('permisos')
+        resolve()
+      })
+    }
+  },
+  getters: {
+    isLoggedIn: state => !!state.token,
+    authStatus: state => state.status,
+    msgResponse: state => state.msg,
+    user: state => state.user,
+    permisos: state => state.permisos.datos
+  }
+})
+
+export default store

+ 29 - 0
src/views/admin/Admin.vue

@@ -0,0 +1,29 @@
+<template>
+  <div>
+    <Sidebar />
+    <div class="page-wrapper card-no-border mt-5 bg-white">
+      <div class="container-fluid">
+            <router-view  class="mt-3"/>
+      </div>
+    </div>
+  </div>
+</template>
+<style src="@/assets/css/colors/default-dark.css"></style>
+<style src="@/assets/css/style.css"></style>
+<script>
+import Sidebar from "@/components/shared/Sidebar.vue";
+export default {
+  name: "Admin",
+  components: {
+      Sidebar
+    
+  }
+};
+</script>
+
+<style scoped>
+  .container-fluid {
+      min-height: 700px !important;
+  }
+</style>
+ 

+ 1350 - 0
src/views/admin/ArchivosAdmin.vue

@@ -0,0 +1,1350 @@
+<template>
+  <div>
+    <ol class="breadcrumb mb-2">
+      <li class="breadcrumb-item">
+        <router-link :to="{name:'homeAdmin'}">Inicio</router-link>
+      </li>
+      <li class="breadcrumb-item active">Archivos</li>
+    </ol>
+    <div class="row">
+      <div class="col-12">
+        <div class="card">
+          <div class="card-body">
+            <!-- Manejo de error -->
+            <div class="alert alert-danger alert-rounded" v-if="strError.length != 0">
+              <button type="button" class="close" data-dismiss="alert" aria-label="Close"></button>
+              <i class="material-icons icon-align">report_problem</i>
+              {{ strError }}
+            </div>
+            <h4 class="card-title">Archivos</h4>
+            <span class="text-dark">En esta página se pueden visualizar los archivos disponibles.</span>
+            <form class="form-material mt-3" @click.prevent>
+              <input
+                type="text"
+                class="form-control col-sm-4 col-11 float-left"
+                placeholder="Buscar archivo..."
+                v-model="buscar"
+              />
+              <div class="btn-group dropright">
+                <a
+                  href="#"
+                  class="align-middle"
+                  @click.prevent="filtroFecha.verFiltrosBusqueda = !filtroFecha.verFiltrosBusqueda"
+                >
+                  <i
+                    class="material-icons align-middle"
+                    style="color: #f42849;padding-top: 8px;"
+                  >more_vert</i>
+                </a>
+              </div>
+              <button
+                class="btn float-right btn-outline-danger p-1"
+                data-toggle="modal"
+                data-dismis="modal"
+                data-target="#modalArchivos"
+                @click="seleccionarArchivo('','new')"
+              >
+                <i class="material-icons icon-align">add_circle</i> Agregar Archivo
+              </button>
+            </form>
+            <div class="col-sm-12 pt-3">
+              <transition
+                name="custom-classes-transition"
+                enter-active-class="animated fadeInDown"
+                leave-active-class="animated fadeOutUp"
+              >
+                <div class="float-left pb-2" v-show="filtroFecha.verFiltrosBusqueda">
+                  <div class="btn-group">
+                    <input
+                      type="checkbox"
+                      class="form-check-input"
+                      id="check"
+                      v-model="filtroFecha.inputFiltroChecked"
+                      @click="filtroFecha.verModalBusqueda=true;"
+                    />
+                    <label
+                      for="check"
+                      :class="[filtroFecha.clickedFiltro.IDES !== ''
+                     ? 'font-bold':'']"
+                    >Estantes / Secciones / Categorías</label>
+                  </div>
+                  <div class="btn-group">
+                    <a
+                      href="#"
+                      class="dropdown-toggle text-secondary ml-2"
+                      data-toggle="dropdown"
+                      aria-haspopup="true"
+                      aria-expanded="false"
+                      :class="[filtroFecha.buscarFechaDias !== 0 ? 'font-bold':'']"
+                    >Fecha</a>
+                    <div class="dropdown-menu">
+                      <a
+                        class="dropdown-item"
+                        href="#"
+                        @click.prevent="filtroFecha.buscarFechaDias = 0"
+                      >
+                        <i
+                          class="material-icons align-middle"
+                          style="font-weight:bold;margin-left: -30px;"
+                          v-show="filtroFecha.buscarFechaDias==0"
+                        >check</i>
+                        &nbsp; Cualquier fecha
+                      </a>
+                      <a class="dropdown-item" href="#" @click="filtroFecha.buscarFechaDias = 1">
+                        <i
+                          class="material-icons align-middle"
+                          style="font-weight:bold;margin-left: -30px;"
+                          v-show="filtroFecha.buscarFechaDias==1"
+                        >check</i>
+                        &nbsp; Últimas 24 hrs
+                      </a>
+                      <a
+                        class="dropdown-item"
+                        href="#"
+                        @click.prevent="filtroFecha.buscarFechaDias = 7"
+                      >
+                        <i
+                          class="material-icons align-middle"
+                          style="font-weight:bold;margin-left: -30px;"
+                          v-show="filtroFecha.buscarFechaDias==7"
+                        >check</i>
+                        &nbsp; Última semana
+                      </a>
+                      <div class="dropdown-divider"></div>
+                      <a
+                        class="dropdown-item"
+                        href="#"
+                        @click.prevent="filtroFecha.verFiltroFecha=true;filtroFecha.buscarFechaDias=-2"
+                      >
+                        <i
+                          class="material-icons align-middle"
+                          style="font-weight:bold;margin-left: -30px;"
+                          v-show="filtroFecha.verFiltroFecha&&filtroFecha.buscarFechaDias==-2||filtroFecha.buscarFechaDias==-1"
+                        >check</i>
+                        &nbsp; Intervalo personalizado
+                      </a>
+                    </div>
+                  </div>
+                  <div class="btn-group">
+                    <div
+                      class="form-material"
+                      v-show="filtroFecha.verFiltroFecha&&filtroFecha.buscarFechaDias==-2||filtroFecha.buscarFechaDias==-1"
+                    >
+                      <!-- Datepicker Show -->
+                      <div class="datepicker-trigger">
+                        <div class="input-group">
+                          <span
+                            class="input-group-text"
+                            style="background-color: transparent; border: none;"
+                          >
+                            <i class="material-icons align-middle">date_range</i>
+                          </span>
+                          <input
+                            type="text"
+                            class="form-control"
+                            id="datepicker-trigger"
+                            placeholder="Selecciona un rango..."
+                            :value="formatoFechaCalendario(filtroFecha.fechaInicial,filtroFecha.fechaFinal)"
+                          />
+                        </div>
+                        <AirbnbStyleDatepicker
+                          :trigger-element-id="'datepicker-trigger'"
+                          :mode="'range'"
+                          :fullscreen-mobile="false"
+                          :date-one="filtroFecha.fechaInicial"
+                          :date-two="filtroFecha.fechaFinal"
+                          :end-date="filtroFecha.fechaLimiteCalendario"
+                          @date-one-selected="val => { filtroFecha.fechaInicial = val }"
+                          @date-two-selected="val => { filtroFecha.fechaFinal = val }"
+                          @apply="filtroFecha.buscarFechaDias = -1"
+                        />
+                      </div>
+                    </div>
+                  </div>
+                  <div class="btn-group">
+                    <a
+                      href="#"
+                      class="ml-3 text-secondary"
+                      @click="borrarFiltroBusqueda()"
+                      v-if="buscar.length !== 0 || 
+                    filtroFecha.buscarFechaDias !== 0 ||
+                    filtroFecha.clickedFiltro.IDES !== '' "
+                    >Borrar Filtro</a>
+                  </div>
+                </div>
+              </transition>
+            </div>
+            <!--  modal agregar / editar -->
+            <div
+              class="modal animated bounceInDown fast"
+              id="modalArchivos"
+              tabindex="-1"
+              role="dialog"
+              aria-labelledby="modalArchivos"
+            >
+              <div class="modal-dialog" role="document">
+                <div class="modal-content">
+                  <div class="modal-header">
+                    <div style="width:100%">
+                      <h4 class="float-left">{{ mensajeModal }}</h4>
+                      <uploadImage
+                        class="float-right"
+                        v-on:imagenUpdatedEvent="actualizarImagenes"
+                      />
+                    </div>
+                    <button type="button" class="close" data-dismiss="modal" aria-label="Close">
+                      <span aria-hidden="true">&times;</span>
+                    </button>
+                  </div>
+                  <div class="modal-body">
+                    <div class="form-row">
+                      <div class="form-group col-md-6">
+                        <label class="font-bold">Nombre</label>
+                        <input
+                          class="form-control"
+                          v-validate="'required|regex:^(?:[A-Za-z0-9 _.]*)$'"
+                          :class="{'input': true, 'is-danger': errors.has('nombre') }"
+                          v-model="clickedArch.ARCH"
+                          name="nombre"
+                          placeholder="Nombre del archivo"
+                        />
+                        <span
+                          v-show="errors.has('nombre')"
+                          class="invalid-feedback-form"
+                        >{{ errors.first('nombre') }}</span>
+                      </div>
+                      <div class="form-group col-md-6">
+                        <label class="font-bold">Estante</label>
+                        <select
+                          class="custom-select"
+                          v-model="clickedArch.IDES"
+                          name="estante"
+                          v-validate="'required'"
+                          :class="{'input': true, 'is-danger': errors.has('estante') }"
+                          id="estante"
+                          @change="clickedArch.IDSE = ''"
+                        >
+                          <option value>Seleccione una opción</option>
+                          <option
+                            v-for="estantes in permisosEstantes"
+                            :key="estantes.IDES"
+                            :value="estantes.IDES"
+                          >{{estantes.ESTA }}</option>
+                        </select>
+                        <span
+                          v-show="errors.has('estante')"
+                          class="invalid-feedback-form"
+                        >{{ errors.first('estante') }}</span>
+                      </div>
+                    </div>
+                    <div class="form-row">
+                      <div class="form-group col-md-6">
+                        <label class="font-bold">Sección</label>
+                        <select
+                          class="custom-select"
+                          v-model="clickedArch.IDSE"
+                          name="seccion"
+                          v-validate.immediate="clickedArch.IDCA !== '' ? 'required' : '' "
+                          :class="{'input': true, 'is-danger': errors.has('seccion') }"
+                          id="seccion"
+                          @input="clickedArch.IDCA = ''"
+                        >
+                          <option value>Seleccione una opción</option>
+                          <option
+                            v-for="seccion in permisosSecciones"
+                            :key="seccion.IDSE"
+                            :value="seccion.IDSE"
+                          >{{seccion.SECC }}</option>
+                        </select>
+                        <span
+                          v-show="errors.has('seccion')"
+                          class="invalid-feedback-form"
+                        >{{ errors.first('seccion') }}</span>
+                      </div>
+                      <div class="form-group col-md-6">
+                        <label class="font-bold">Categoría</label>
+                        <select class="custom-select" v-model="clickedArch.IDCA">
+                          <option value>Seleccione una opción</option>
+                          <option
+                            v-for="categoria in permisosCategorias"
+                            :key="categoria.IDCA"
+                            :value="categoria.IDCA"
+                          >{{categoria.CATE }}</option>
+                        </select>
+                      </div>
+                    </div>
+                    <div class="form-group" v-if="mensajeButtonModal=='Agregar'">
+                      <label class="font-bold">Archivo</label>
+                      <b-form-file
+                        v-model="file"
+                        placeholder="Seleccione un archivo..."
+                        drop-placeholder="Suelte los archivos aquí..."
+                        accept=".xlsx, .pdf, .docx"
+                        name="archivo"
+                        v-validate="file==null&&mensajeButtonModal=='Agregar' ? 'required' : ''"
+                        :class="{'input': true, 'is-danger': errors.has('archivo') }"
+                      ></b-form-file>
+                      <span
+                        v-show="errors.has('archivo') && mensajeButtonModal=='Agregar'"
+                        class="invalid-feedback-form"
+                      >{{ errors.first('archivo') }}</span>
+                    </div>
+                    <label class="typo__label font-bold">Seleccione una imagen</label>
+                    <multiselect
+                      v-model="imagen"
+                      placeholder="Seleccione una imagen de la lista"
+                      label="title"
+                      track-by="IMAG"
+                      :options="imagenesData"
+                      :option-height="104"
+                      :custom-label="({ title }) => `${title}`"
+                      :show-labels="false"
+                    >
+                      <template slot="singleLabel" slot-scope="props">
+                        <img class="option__image" :src="props.option.URL" style="width:100px" />
+                        <span class="option__desc">
+                          <span class="option__title ml-2">{{ props.option.IMAG }}</span>
+                        </span>
+                      </template>
+                      <template slot="option" slot-scope="props">
+                        <img class="option__image" :src="props.option.URL" style="width:100px" />
+                        <span class="option__title ml-2">{{ props.option.IMAG }}</span>
+                      </template>
+                      <span slot="noOptions">Sin elementos para mostrar.</span>
+                    </multiselect>
+                    <span
+                      v-show="imagenInvalid && imagen.IMAG == 'Seleccione una imagen de la lista'"
+                      class="invalid-feedback-form"
+                    >La imagen es un campo obligatorio</span>
+                  </div>
+                  <div class="modal-footer">
+                    <button
+                      type="button"
+                      class="btn btn-light"
+                      :disabled="isLoading"
+                      data-dismiss="modal"
+                    >Cerrar</button>
+                    <!-- Buttons save and edit -->
+                    <button
+                      type="button"
+                      v-if="mensajeButtonModal=='Editar'"
+                      data-target="#modalCorreo"
+                      @click="validarFormEditar()"
+                      class="btn btn-danger"
+                      :disabled="isLoading"
+                    >
+                      <div class="save" v-if="!isLoading">
+                        <i class="material-icons icon-align">save</i>
+                        {{ mensajeButtonModal }}
+                      </div>
+                      <div class="loading" v-if="isLoading">
+                        <span
+                          class="spinner-border spinner-grow-sm"
+                          role="status"
+                          aria-hidden="true"
+                        ></span> Cargando...
+                      </div>
+                    </button>
+                    
+                    <button
+                      type="button"
+                      v-if="mensajeButtonModal=='Agregar'"
+                      class="btn btn-danger"
+                      data-target="#modalCorreo"
+                      @click="validarFormulario(clickedArch.ARCH,clickedArch.IDES,clickedArch.IDSE,clickedArch.IDCA);"
+                    >
+                      <div class="save" v-if="!isLoading">
+                        <i class="material-icons icon-align">save</i>
+                        {{ mensajeButtonModal }}
+                      </div>
+                      <div class="loading" v-if="isLoading">
+                        <span
+                          class="spinner-border spinner-grow-sm"
+                          role="status"
+                          aria-hidden="true"
+                        ></span> Cargando...
+                      </div>
+                    </button>
+                  </div>
+                </div>
+              </div>
+            </div>
+            <!-- end modal -->
+            
+            <!--  Modal notificar por correo -->
+            <div id="modalCorreo" class="modal animated bounceInDown fast">
+              <div class="modal-dialog">
+                <div class="modal-content">
+                  <form>
+                    <div class="modal-header">
+                      <h4 class="modal-title">¿Desea notificar a los usuarios por correo?</h4>
+                      <button
+                        type="button"
+                        class="close"
+                        data-dismiss="modal"
+                        aria-hidden="true"
+                      >&times;</button>
+                    </div>
+                    <div class="modal-body">
+                      <p>
+                        Si selecciona "Si" todos los usuarios que tengan permisos para descargar el archivo recibirán un correo electrónico con el aviso; si se selecciona "No", sólo se publicará el archivo sin enviar ninguna notificación.
+                      </p>
+                    </div>
+                    <div class="modal-footer">
+                      <input
+                        type="button"
+                        class="btn btn-default"
+                        data-dismiss="modal"
+                        value="No"
+                        @click="notificarCorreo = 'No'; respuestaModalCorreo(textCorreo); "
+                      />
+                      <input
+                        type="submit"
+                        @click="notificarCorreo = 'Si'; respuestaModalCorreo(textCorreo);"
+                        class="btn btn-danger"
+                        value="Sí"
+                        data-dismiss="modal"
+                      />
+                    </div>
+                  </form>
+                </div>
+              </div>
+            </div>
+            <!-- end modal notificar por correo-->
+            
+            <!-- Modal filtro buscar -->
+            <transition name="fade" v-if="filtroFecha.verModalBusqueda ">
+              <div class="modal-mask">
+                <div class="modal-wrapper">
+                  <div class="modal-dialog" role="document">
+                    <div class="modal-content">
+                      <div class="modal-header">
+                        <h6>Filtro avanzado de archivos</h6>
+                        <button
+                          type="button"
+                          class="close"
+                          @click="filtroFecha.verModalBusqueda = false;filtroFecha.inputFiltroChecked = false"
+                          aria-label="Cerrar"
+                        >
+                          <span aria-hidden="true">&times;</span>
+                        </button>
+                      </div>
+                      <div class="modal-body">
+                        <div class="form-group row">
+                          <label for="lbl_estantes" class="col-sm-3 col-form-label">Estantes</label>
+                          <div class="col-sm-9">
+                            <select
+                              class="custom-select"
+                              v-model="filtroFecha.clickedFiltro.IDES"
+                              id="estanteFiltro"
+                              @change="filtroFecha.clickedFiltro.IDSE = ''"
+                            >
+                              <option value>Seleccione una opción</option>
+                              <option
+                                v-for="estantes in permisosEstantes"
+                                :key="estantes.IDES"
+                                :value="estantes.IDES"
+                              >{{estantes.ESTA }}</option>
+                            </select>
+                          </div>
+                        </div>
+                        <div class="form-group row">
+                          <label for="lbl_secciones" class="col-sm-3 col-form-label">Secciones</label>
+                          <div class="col-sm-9">
+                            <select
+                              class="custom-select"
+                              v-model="filtroFecha.clickedFiltro.IDSE"
+                              id="seccionFiltro"
+                            >
+                              <option value>Seleccione una sección</option>
+                              <option
+                                v-for="seccion in seccionesFiltro"
+                                :key="seccion.IDSE"
+                                :value="seccion.IDSE"
+                              >{{seccion.SECC }}</option>
+                            </select>
+                          </div>
+                        </div>
+                        <div class="form-group row">
+                          <label for="lbl_secciones" class="col-sm-3 col-form-label">Categorías</label>
+                          <div class="col-sm-9">
+                            <select
+                              class="custom-select"
+                              v-model="filtroFecha.clickedFiltro.IDCA"
+                              id="seccionFiltro"
+                            >
+                              <option value>Seleccione una sección</option>
+                              <option
+                                v-for="categoria in categoriaFiltro"
+                                :key="categoria.IDCA"
+                                :value="categoria.IDCA"
+                              >{{categoria.CATE }}</option>
+                            </select>
+                          </div>
+                        </div>
+                        <button
+                          class="btn btn-danger float-right mt-3"
+                          @click="filtroFecha.inputFiltroChecked = true;filtroFecha.verModalBusqueda = false;"
+                        >Aplicar</button>
+                      </div>
+                    </div>
+                  </div>
+                </div>
+              </div>
+            </transition>
+
+            <!-- Start delete categoría -->
+            <div id="deleteArchivos" class="modal animated bounceInDown fast">
+              <div class="modal-dialog">
+                <div class="modal-content">
+                  <form>
+                    <div class="modal-header">
+                      <h4 class="modal-title">Borrar Categoría</h4>
+                      <button
+                        type="button"
+                        class="close"
+                        data-dismiss="modal"
+                        aria-hidden="true"
+                      >&times;</button>
+                    </div>
+                    <div class="modal-body">
+                      <p>
+                        Esta a punto de borrar
+                        <b>'{{ clickedArch.ARCH }}'</b>
+                      </p>
+                      <p class="text-danger">Esta acción no se puede deshacer.</p>
+                    </div>
+                    <div class="modal-footer">
+                      <input
+                        type="button"
+                        class="btn btn-default"
+                        data-dismiss="modal"
+                        value="Cerrar"
+                      />
+                      <input
+                        type="submit"
+                        @click="borrarArchivo()"
+                        class="btn btn-danger"
+                        value="Borrar"
+                        data-dismiss="modal"
+                      />
+                    </div>
+                  </form>
+                </div>
+              </div>
+            </div>
+            <!-- End delete categoría -->
+            <div class="table-responsive" v-if="strError.length == 0">
+              <table class="table no-wrap table-hover">
+                <thead>
+                  <tr>
+                    <th>Imagen</th>
+                    <th>Nombre</th>
+                    <th>Estante vinculado</th>
+                    <th>Sección vinculada</th>
+                    <th>Categoría vinculada</th>
+                    <th>Fecha Creación</th>
+                    <th v-if="$store.getters.user.role === 'admin'">Creado Por</th>
+                    <th>Acciones</th>
+                  </tr>
+                </thead>
+                <draggable
+                  v-model="archivos"
+                  tag="tbody"
+                  v-if="archivos.length != 0"
+                  @start="drag=true"
+                  @end="drag = false"
+                  @update="moverArchivos"
+                  v-bind="configuracionDragg"
+                >
+                  <tr v-for="archivos in buscarArchivo" :key="archivos.IDAR">
+                    <td>
+                      <div class="profile-img">
+                        <img
+                          v-lazy="archivos.IDIM"
+                          style="max-width:65px"
+                          alt="user"
+                          class="img-thumbnail"
+                        />
+                      </div>
+                    </td>
+                    <td>{{ archivos.ARCH }}</td>
+                    <td>
+                      <span class="label label-primary">
+                        <i class="material-icons icon-align">dns</i>
+                        {{ archivos.ESTA }}
+                      </span>
+                    </td>
+                    <td>
+                      <span class="label label-info" v-if="archivos.SECC !== null">
+                        <i class="material-icons icon-align">collections_bookmark</i>
+                        {{ archivos.SECC }}
+                      </span>
+                    </td>
+                    <td>
+                      <span class="label label-success" v-if="archivos.CATE !== null">
+                        <i class="material-icons icon-align">book</i>
+                        {{ archivos.CATE }}
+                      </span>
+                    </td>
+                    <td>
+                      <span v-if="archivos.FECR !== null ">
+                        <i class="material-icons icon-align" style="color:red">access_time</i>
+                        {{ archivos.FECR | formatDate }}
+                      </span>
+                    </td>
+                    <td v-if="$store.getters.user.role === 'admin'">
+                      <i class="material-icons icon-align" style="color:red">person</i>
+                      <span>{{ archivos.DINOMBRE }}</span>
+                    </td>
+                    <td>
+                      <!-- Botones de acciones -->
+                      <a
+                        href="#modalArchivos"
+                        class="edit"
+                        data-toggle="modal"
+                        @click="seleccionarArchivo(archivos,'edit')"
+                      >
+                        <i
+                          class="material-icons"
+                          data-toggle="tooltip"
+                          title="Editar"
+                          style="color:#f42849"
+                        >&#xE254;</i>
+                      </a>
+                      <a
+                        href="#deleteArchivos"
+                        @click="seleccionarArchivo(archivos,'delete')"
+                        class="delete"
+                        data-toggle="modal"
+                      >
+                        <i
+                          class="material-icons"
+                          data-toggle="tooltip"
+                          title="Borrar"
+                          style="color:#f42849"
+                        >&#xE872;</i>
+                      </a>
+                    </td>
+                  </tr>
+                </draggable>
+              </table>
+              <!-- Pagination -->
+              <b-pagination
+                v-show="archivos.length != 0 && buscarArchivo.length != 0"
+                v-model="inicioPagina"
+                :total-rows="totalFilas"
+                :per-page="porPagina"
+                align="center"
+              ></b-pagination>
+              <!-- Actualizar orden de los archivos  -->
+              <button
+                v-show="actualizarOrden"
+                class="mr-2 btn btn-sm float-right hidden-sm-down btn-outline-success"
+                @click="actualizarOrdenArchivos()"
+                :disabled="isLoading"
+              >
+                <div class="save" v-if="!isLoading">
+                  <i class="material-icons icon-align">save</i>
+                  Guardar Cambios
+                </div>
+                <div class="loading" v-if="isLoading">
+                  <span class="spinner-border spinner-grow-sm" role="status" aria-hidden="true"></span> Cargando...
+                </div>
+              </button>
+              <button
+                v-show="actualizarOrden"
+                class="mr-2 btn btn-sm float-right hidden-sm-down btn-outline-danger"
+                @click="removerOrden()"
+                :disabled="isLoading"
+              >
+                <i class="material-icons icon-align">refresh</i> Deshacer cambios
+              </button>
+              <div v-if="loading" class="text-center">
+                <p>Cargando...</p>
+                <span
+                  class="spinner-border spinner-grow-sm"
+                  role="status"
+                  aria-hidden="true"
+                  style="color:red"
+                ></span>
+              </div>
+              <div class="text-center">
+                <p
+                  class="mt-3"
+                  v-show="mensajeArchivosVacios"
+                >No se encontraron archivos para mostrar.</p>
+                <p
+                  class="mt-3"
+                  v-show=" archivos.length !== 0 && buscarArchivo.length == 0 && archivos.length != 0 && !loading"
+                >No se encontró ningún archivo en su búsqueda</p>
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+<style src="vue-multiselect/dist/vue-multiselect.min.css"></style>
+<script>
+import uploadImage from "@/components/UploadImage.vue";
+import Multiselect from "vue-multiselect";
+import moment from "moment";
+import draggable from "vuedraggable";
+// Components bootstrap-vue
+import { BPagination, BFormFile } from "bootstrap-vue";
+// Date Pickers
+import format from "date-fns/format";
+import "bootstrap-vue/dist/bootstrap-vue.css";
+import { BFormCheckbox } from "bootstrap-vue";
+import { token } from "../../_mixin/user_mixin.js";
+var _ = require("lodash");
+export default {
+  name: "ArchivosAdmin",
+  mixins: [token],
+  components: {
+    uploadImage,
+    Multiselect,
+    draggable,
+    BPagination,
+    BFormFile,
+    BFormCheckbox
+  },
+  data: () => ({
+    mensajeArchivosVacios: false,
+    estantesPermitidos: [],
+    seccionesPermitidas: [],
+    categoriasPermitidas: [],
+    permisosDatos: [],
+    mensajeModal: "Agregar Archivo",
+    mensajeButtonModal: "Agregar",
+    file: null,
+    clickedArch: {
+      IDES: "",
+      IDSE: "",
+      IDCA: ""
+    },
+    imagen: { IMAG: "Seleccione una imagen de la lista" },
+    buscar: "",
+    totalFilas: 0,
+    porPagina: 25,
+    inicioPagina: 1,
+    actualizarOrden: false,
+    arrOrder: {},
+    isLoading: false,
+    imagenInvalid: false,
+    filtroFecha: {
+      fechaLimiteCalendario: moment().format("YYYY-MM-DD"),
+      verFiltroFecha: false,
+      verFiltrosBusqueda: false,
+      verModalBusqueda: false,
+      inputFiltroChecked: false,
+      fechaInicial: "",
+      fechaFinal: "",
+      buscarFechaDias: 0,
+      clickedFiltro: {
+        IDES: "",
+        IDSE: "",
+        IDCA: ""
+      }
+    },
+    notificarCorreo: 'No',
+    textCorreo: ''
+  }),
+  async mounted() {
+    this.permisosDatos = JSON.parse(this.$store.getters.permisos);
+    var est = await this.getEstantes();
+    var secc = await this.getSecciones();
+    var cate = await this.getCategorias();
+    var img = await this.getImages("AR");
+    var arch = await this.getArchivos(
+      this.$store.getters.user.id,
+      this.$store.getters.user.role
+    );
+  },
+  methods: {
+    editarArchivo: function() {
+      var formData = this.toFormData(this.clickedArch);
+      let IDES = this.clickedArch.IDES;
+      let IDSE = this.clickedArch.IDSE;
+      let IDCA = this.clickedArch.IDCA;
+      this.isLoading = true;
+      this.$http
+        .post(
+          this.url_api + "/archivos/update",
+          formData,
+          this.config_header
+        )
+        .then(response => {
+          this.clickedArch = {};
+          if (response.data.response) {
+            this.$toasted.show(response.data.message, this.$toast_success);
+            if(this.notificarCorreo == 'Si'){
+              this.buscarUsuariosEnvioCorreo(IDES, IDSE, IDCA);
+              this.notificarCorreo = 'No';
+            }
+            this.getArchivos(
+              this.$store.getters.user.id,
+              this.$store.getters.user.role
+            );
+          } else {
+            this.$toasted.show(response.data.message, this.$toast_error);
+          }
+          this.isLoading = false;
+          this.removeModal("modalArchivos");
+        })
+        .catch(error => {
+              this.strError =
+                "Se ha producido un error al actualizar los archivos.";
+              this.removeModal("modalArchivos");
+            });
+        
+    },
+    seleccionarArchivo: function(archivos, accion) {
+      this.clickedArch = {};
+      this.file = null;
+      this.imagen = { IMAG: "Seleccione una imagen de la lista" };
+      this.imagenInvalid = false;
+      // Remove Menssages error
+      this.$nextTick().then(() => {
+        this.$validator.reset();
+        this.errors.clear();
+      });
+      if (accion == "new") {
+        this.clickedArch = { IDES: "", IDSE: "", IDCA: "" };
+        this.mensajeModal = "Agregar Archivo";
+        this.mensajeButtonModal = "Agregar";
+      }
+      // Si estamos editando
+      if (accion == "edit") {
+        let secc = archivos.IDSE !== null ? archivos.IDSE : "";
+        let cate = archivos.IDCA !== null ? archivos.IDCA : "";
+        var datos = {
+          IDAR: archivos.IDAR,
+          ARCH: archivos.ARCH,
+          IDES: archivos.IDES,
+          IDSE: secc,
+          IDCA: cate,
+          IDIM: archivos.IDIM,
+          USMO: this.$store.getters.user.id
+        };
+        this.clickedArch = datos;
+        this.mensajeButtonModal = "Editar";
+        this.mensajeModal = "Editar Archivo";
+      }
+      if (accion == "delete") {
+        this.clickedArch = archivos;
+      }
+    },
+    crearArchivo: function(arch, ides, idse, idca) { 
+      let imagenUrl = this.imagen.URL;
+      var datos = {
+        ARCH: arch,
+        IDES: ides,
+        IDSE: idse,
+        IDCA: idca,
+        IDIM: imagenUrl,
+        USCR: this.$store.getters.user.id,
+        NOMBRE_FILE: this.file.name,
+        file: this.file
+      };
+      var formData = this.toFormData(datos);
+      this.isLoading = true;
+      this.$http
+        .post(this.url_api + "/archivos", formData, this.config_header)
+        .then(response => {
+          if (response.data.response) {
+            this.$toasted.show(response.data.message, this.$toast_success);
+            if(this.notificarCorreo == 'Si'){
+              this.buscarUsuariosEnvioCorreo(ides, idse, idca);
+              this.notificarCorreo = 'No';
+            }
+          } else {
+            this.$toasted.show(response.data.message, this.$toast_error);
+          }
+          this.getArchivos(
+            this.$store.getters.user.id,
+            this.$store.getters.user.role
+          );
+          this.isLoading = false;
+          this.removeModal("modalArchivos");
+        })
+        .catch(error => {
+          this.removeModal("modalArchivos");
+          this.getArchivos(
+            this.$store.getters.user.id,
+            this.$store.getters.user.role
+          );
+          this.strError = "Se ha producido un error al crear el archivo.";
+        });
+    },
+    borrarArchivo: function() {
+      var formData = this.toFormData(this.clickedArch);
+      this.$http
+        .post(this.url_api + "/archivos/delete", formData, this.config_header)
+        .then(response => {
+          if (response.data.response) {
+            this.$toasted.show(response.data.message, this.$toast_success);
+            this.getArchivos(
+              this.$store.getters.user.id,
+              this.$store.getters.user.role
+            );
+          } else {
+            this.$toasted.show(response.data.message, this.$toast_error);
+          }
+        })
+        .catch(error => {
+          this.strError = "Se ha producido un error al borrar el archivo.";
+        });
+    },
+    moverArchivos: function() {
+      this.actualizarOrden = true;
+      var obj = this.archivos;
+      var bodyFormData = new FormData();
+      obj.forEach((element, i) => {
+        i++;
+        bodyFormData.append(element.IDAR, i);
+      });
+      this.arrOrder = bodyFormData;
+    },
+    removerOrden: function() {
+      this.getArchivos(
+        this.$store.getters.user.id,
+        this.$store.getters.user.role
+      );
+      this.actualizarOrden = false;
+    },
+    actualizarOrdenArchivos: function() {
+      var formData = this.arrOrder;
+      this.isLoading = true;
+      this.$http
+        .post(this.url_api + "/archivos/order", formData, this.config_header)
+        .then(response => {
+          this.isLoading = false;
+          this.actualizarOrden = false;
+          if (response.data.response) {
+            this.$toasted.show(response.data.message, this.$toast_success);
+          } else {
+            this.$toasted.show(response.data.message, this.$toast_error);
+          }
+
+          this.getArchivos(
+            this.$store.getters.user.id,
+            this.$store.getters.user.role
+          );
+        })
+        .catch(error => {
+          this.strError = "Se ha producido un error al ordenar el archivo,.";
+        });
+    },
+    paginador: function(archivos) {
+      // Cálcular páginador
+      const indiceInicio = (this.inicioPagina - 1) * this.porPagina;
+      const indiceFinal =
+        indiceInicio + this.porPagina > archivos.length
+          ? archivos.length
+          : indiceInicio + this.porPagina;
+      return archivos.slice(indiceInicio, indiceFinal);
+    },
+    borrarFiltroBusqueda: function() {
+      this.filtroFecha.verFiltroFecha = false;
+      this.filtroFecha.verModalBusqueda = false;
+      this.filtroFecha.inputFiltroChecked = false;
+      this.filtroFecha.fechaInicial = "";
+      this.filtroFecha.fechaFinal = "";
+      this.filtroFecha.buscarFechaDias = 0;
+      this.buscar = "";
+      this.filtroFecha.clickedFiltro.IDES = "";
+      this.filtroFecha.clickedFiltro.IDSE = "";
+      this.filtroFecha.clickedFiltro.IDCA = "";
+    },
+    actualizarImagenes: function(action) {
+      if (action) {
+        this.getImages("AR");
+      }
+    },
+    fachadaSecciones(idEstante) {
+      var secciones = [];
+      if (idEstante !== "") {
+        if (this.permisosDatos.length !== 0) {
+          secciones = this.secciones.filter(secc => secc.IDES === idEstante);
+          // Verificamos el perfil del usuario
+          if (this.permisosDatos[0].children[0].text !== "ADMIN | Ver + Publicar") {
+            let estanteSeleccionado = this.estantes.filter(
+              est => {
+                if(est.IDES == idEstante){
+                  return est;
+                }
+              }
+            );
+
+            let ESTA = estanteSeleccionado[0].ESTA;
+
+            let seccionesInEstante = _.values(
+              _.filter(this.permisosDatos[0].children, perm => {
+                let arrEstanteAndId = perm.text.split(" | ");
+                let strEstante = arrEstanteAndId[0].split(" - ");
+                return strEstante[0] == ESTA;
+              })
+            );
+            
+            // Filtramos solo los permisos de las secciones que tengan permiso = Ver + Publicar
+            let seccionesPermisos = _.filter(
+              seccionesInEstante[0].children,
+              perm => {
+                let permiso = perm.text.split(" | ");
+                return permiso[1] === "Ver + Publicar";
+              }
+            );
+
+            // Buscamos las secciones que coincidan con las secciones permisos para saber a cuales esta permitido entrar
+            let seccionesPermitidas = _.values(
+              _.mapValues(seccionesPermisos, secc => {
+                let seccion = secc.text.split(" | ");
+                let strSeccion = seccion[0].split(" - ");
+                return _.find(secciones, ["SECC", strSeccion[0]]);
+              })
+            );
+
+            if (seccionesPermitidas.length === 0) {
+              // Limpiar categoria
+              this.filtroFecha.clickedFiltro.IDCA = "";
+              this.clickedArch.IDCA = "";
+            }
+
+            secciones = seccionesPermitidas;
+          }
+        }
+        return secciones;
+      }
+    },
+    fachadaCategoria(idEstante, idSeccion) {
+      if (idEstante === "") {
+        this.clickedArch.IDCA = "";
+        this.filtroFecha.clickedFiltro.IDCA = "";
+      }
+      var categorias = [];
+      if (idSeccion !== "") {
+        categorias = this.categorias.filter(cate => cate.IDSE === idSeccion);
+
+        // Verificamos el perfil del usuario
+        if (this.permisosDatos[0].children[0].text !== "ADMIN | Ver + Publicar") {
+          let estanteSeleccionado = this.estantes.filter(
+            est => est.IDES === idEstante
+          );
+          let seccionSeleccionada = this.secciones.filter(
+            secc => secc.IDSE === idSeccion
+          );
+          let SECC = seccionSeleccionada[0].SECC;
+          let ESTA = estanteSeleccionado[0].ESTA;
+
+          let permisosInCategorias = [];
+          // Encontrar los permisos disponibles para cada sección
+          _.filter(this.permisosDatos[0].children, perm => {
+            let arrEstante = perm.text.split(" | ");
+            let strNombre = arrEstante[0].split(" - ");
+            if (strNombre[0] === ESTA) {
+              _.filter(perm.children, permiso => {
+                let seccion = permiso.text.split(" | ");
+                let strSeccion = seccion[0].split(" - ");
+
+                if (strSeccion[0] === SECC) {
+                  permisosInCategorias.push(permiso);
+                }
+              });
+            }
+          });
+
+          // Filtramos solo los permisos de las categorias que tengan permiso = Ver + Publicar
+          let categoriasPermisos = _.filter(
+            permisosInCategorias[0].children,
+            perm => {
+              let permiso = perm.text.split(" | ");
+              return permiso[1] === "Ver + Publicar";
+            }
+          );
+          // Buscamos las categorias que coincidan con el nombre de las secciones con permisos Ver y/o Ver + Publicar
+          let categoriasPermitidas = _.values(
+            _.mapValues(categoriasPermisos, cate => {
+              let categoria = cate.text.split(" | ");
+              let strCategoria = categoria[0].split(" - ");
+
+              return _.find(categorias, ["CATE", strCategoria[0]]);
+            })
+          );
+          categorias = categoriasPermitidas;
+        }
+        return categorias;
+      }
+      if (idSeccion === "") {
+        // Limpiar categoria
+        this.filtroFecha.clickedFiltro.IDCA = "";
+        this.clickedArch.IDCA = "";
+      }
+    },
+    buscarUsuariosEnvioCorreo(ides, idse, idca){
+      let data = {
+        IDES: ides,
+        IDSE: idse,
+        IDCA: idca
+      };
+      data = this.toFormData(data);
+      this.$http
+        .post(this.url_api + "/envioCorreos", data, this.config_header)
+        .then(response => {
+          if (response.data.response) {
+            this.$toasted.show(response.data.message, this.$toast_success);
+          } else {
+            this.$toasted.show(response.data.message, this.$toast_error);
+          }
+          // this.isLoading = false;
+          // this.removeModal("modalArchivos");
+        })
+        .catch(error => {
+          this.strError = "Ha ocurrido un error en el envío de correos.";
+        });
+    },
+    validarFormulario(arch, ides, idse, idca){
+    // Verificar que tengamos imagen seleccionada
+      this.imagenInvalid = false;
+      if (this.imagen.IMAG == "Seleccione una imagen de la lista") {
+        this.imagenInvalid = true;
+      }
+      this.$validator.validateAll().then(result => {
+        if (result && !this.imagenInvalid) {
+          this.showModal('modalCorreo');
+          this.textCorreo = 'Nuevo';
+        } //end Result
+      }); // end validator
+      
+    },
+    validarFormEditar: function() {
+      // Verificar que tengamos imagen seleccionada
+      this.$validator.validateAll().then(errors => {
+        if (errors) {
+          if (this.imagen.IMAG != "Seleccione una imagen de la lista") {
+            let img = this.imagen.URL;
+            this.clickedArch.IDIM = img;
+            
+          }
+          
+          this.showModal('modalCorreo');
+          this.textCorreo = 'Editando';
+        }
+      });
+    },
+    respuestaModalCorreo(text){
+      if(text == 'Nuevo'){
+        this.crearArchivo(
+          this.clickedArch.ARCH, 
+          this.clickedArch.IDES, 
+          this.clickedArch.IDSE,
+          this.clickedArch.IDCA
+          );
+      }else if(text == 'Editando'){
+        this.editarArchivo();
+      }
+     
+    }
+  },
+  computed: {
+    permisosEstantes() {
+      if (this.permisosDatos.length) {
+        if (this.$store.getters.user.role === "colaborador") {
+          let estantesConAcceso = _.filter(
+            this.permisosDatos[0].children,
+            perm => {
+              let permiso = perm.text.split(" | ");
+              return permiso[1] === "Ver + Publicar";
+            }
+          );
+
+          if (this.estantes.length) {
+            let estantesPermitidos = _.values(
+              _.mapValues(estantesConAcceso, est => {
+                let arrEstanteAndId = est.text.split(" | ");
+                let strEstante = arrEstanteAndId[0].split(" - ");
+
+                let resultadoFinal = _.find(this.estantes, [
+                  "ESTA",
+                  strEstante[0]
+                ]);
+                return resultadoFinal;
+              })
+            );
+            return estantesPermitidos;
+          }
+        }
+        return this.estantes;
+      }
+    },
+    permisosSecciones() {
+      let idEstante = this.clickedArch.IDES;
+      return idEstante === "" ? [] : this.fachadaSecciones(idEstante);
+    },
+    permisosCategorias() {
+      let idEstante = this.clickedArch.IDES;
+      let idSeccion = this.clickedArch.IDSE;
+      return idEstante === "" && idSeccion === ""
+        ? []
+        : this.fachadaCategoria(idEstante, idSeccion);
+    },
+    seccionesFiltro() {
+      let idEstante = this.filtroFecha.clickedFiltro.IDES;
+      return idEstante === "" ? [] : this.fachadaSecciones(idEstante);
+    },
+    categoriaFiltro() {
+      let idEstante = this.filtroFecha.clickedFiltro.IDES;
+      let idSeccion = this.filtroFecha.clickedFiltro.IDSE;
+      return idEstante === "" && idSeccion === ""
+        ? []
+        : this.fachadaCategoria(idEstante, idSeccion);
+    },
+    buscarArchivo() {
+       var palabraAbuscar = this.buscar.toLowerCase();
+      // Fecha actual
+      var fechaAhora = moment().format("YYYY-MM-DD");
+      // Arrays Temporales
+      var archivosFiltrados = [];
+      var filtroArchivosModal = [];
+      var filtroArchivosInput = [];
+
+      //Filtro de Modal Búsqueda Avanzada
+      var idEstante = this.filtroFecha.clickedFiltro.IDES;
+      var idSeccion = this.filtroFecha.clickedFiltro.IDSE;
+      var idCategoria = this.filtroFecha.clickedFiltro.IDCA;
+
+      filtroArchivosModal = this.archivos.filter(archivo => {
+        if (idEstante !== "" && idSeccion === "" && idCategoria === "") {
+          return archivo.IDES === idEstante;
+        }
+        if (idEstante !== "" && idSeccion !== "" && idCategoria === "") {
+          return archivo.IDES === idEstante && archivo.IDSE === idSeccion;
+        }
+        if (idEstante !== "" && idSeccion !== "" && idCategoria !== "") {
+          return (
+            archivo.IDES === idEstante &&
+            archivo.IDSE === idSeccion &&
+            archivo.IDCA === idCategoria
+          );
+        }
+        return archivo;
+      });
+
+      // filtra por lo que escriba el usuario en la caja de texto
+      filtroArchivosInput = filtroArchivosModal.filter(arch => {
+        return (
+          arch.ARCH.toLowerCase().match(palabraAbuscar) ||
+          arch.ESTA.toLowerCase().match(palabraAbuscar) ||
+          (arch.SECC !== null &&
+            arch.SECC.toLowerCase().match(palabraAbuscar)) ||
+          (arch.CATE !== null && arch.CATE.toLowerCase().match(palabraAbuscar))
+        );
+      });
+
+      // Filtrar por rango de fecha
+      switch (this.filtroFecha.buscarFechaDias) {
+        case -1:
+          archivosFiltrados = filtroArchivosInput.filter(arch => {
+            let fechaFormato = moment(arch.FECR, "DD-MMM-YY").format(
+              "YYYY-MM-DD"
+            );
+
+            if (
+              !(
+                moment(fechaFormato).isBefore(this.filtroFecha.fechaInicial) ||
+                moment(fechaFormato).isAfter(this.filtroFecha.fechaFinal)
+              )
+            ) {
+              return filtroArchivosInput;
+            }
+          });
+          break;
+        case 1:
+        case 7:
+          // Filtrar ultimos 7 días y 24 hrs
+          var dias = moment()
+            .subtract(this.filtroFecha.buscarFechaDias, "days")
+            .format("YYYY-MM-DD");
+          // filtramos que nuestra fecha esté en el intervalo
+          archivosFiltrados = filtroArchivosInput.filter(arch => {
+            let frm = moment(arch.FECR, "DD-MMM-YY").format("YYYY-MM-DD");
+            if (
+              moment(frm).isBetween(dias, fechaAhora) ||
+              moment(frm).isSame(fechaAhora)
+            ) {
+              return filtroArchivosInput;
+            }
+          });
+          break;
+        default:
+          archivosFiltrados = filtroArchivosInput;
+          break;
+      } // End Switch
+
+      this.totalFilas = archivosFiltrados.length;
+      if (archivosFiltrados.length === 0) {
+        this.mensajeArchivosVacios = true;
+      }
+      return this.paginador(archivosFiltrados);
+    }
+  }
+};
+</script>
+<style scoped>
+.page-titles .breadcrumb .breadcrumb-item + .breadcrumb-item::before {
+  content: "/";
+}
+.uploading-image {
+  display: flex;
+}
+.sortable-chosen {
+  background: #c2dbff;
+}
+.table-responsive {
+  box-shadow: 0px 0px 7px 2px #f2f2f2;
+  padding: 15px;
+}
+.spinner-grow-sm {
+  width: 1.6rem;
+  height: 1.6rem;
+}
+.dropdown-item {
+  padding: 0px 7px 2px 40px;
+}
+.btn-group label {
+  color: #6c757d !important;
+}
+/* Modal Filtro */
+.fade-enter-active,
+.fade-leave-active {
+  transition: opacity 0.5s;
+}
+.fade-enter,
+.fade-leave-to {
+  opacity: 0;
+}
+.modal-mask {
+  position: fixed;
+  z-index: 9998;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  background-color: rgba(0, 0, 0, 0.1);
+  display: table;
+  transition: opacity 0.3s ease;
+}
+.modal-wrapper {
+  display: table-cell;
+  vertical-align: middle;
+}
+.modal-dialog {
+  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2);
+}
+.dropdown-toggle {
+  border: 1px solid#d8d8d8;
+  padding: 3px;
+}
+</style>

+ 1240 - 0
src/views/admin/ArchivosAdminOld.vue

@@ -0,0 +1,1240 @@
+<template>
+  <div>
+    <ol class="breadcrumb mb-2">
+      <li class="breadcrumb-item">
+        <router-link :to="{name:'homeAdmin'}">Inicio</router-link>
+      </li>
+      <li class="breadcrumb-item active">Archivos</li>
+    </ol>
+    <div class="row">
+      <div class="col-12">
+        <div class="card">
+          <div class="card-body">
+            <!-- Manejo de error -->
+            <div class="alert alert-danger alert-rounded" v-if="strError.length != 0">
+              <button type="button" class="close" data-dismiss="alert" aria-label="Close"></button>
+              <i class="material-icons icon-align">report_problem</i>
+              {{ strError }}
+            </div>
+            <h4 class="card-title">Archivos</h4>
+            <span class="text-dark">En esta página se pueden visualizar los archivos disponibles.</span>
+            <form class="form-material mt-3" @click.prevent>
+              <input
+                type="text"
+                class="form-control col-sm-4 col-11 float-left"
+                placeholder="Buscar archivo..."
+                v-model="buscar"
+              />
+              <div class="btn-group dropright">
+                <a
+                  href="#"
+                  class="align-middle"
+                  @click.prevent="filtroFecha.verFiltrosBusqueda = !filtroFecha.verFiltrosBusqueda"
+                >
+                  <i
+                    class="material-icons align-middle"
+                    style="color: #f42849;padding-top: 8px;"
+                  >more_vert</i>
+                </a>
+              </div>
+              <button
+                class="btn float-right btn-outline-danger p-1"
+                data-toggle="modal"
+                data-dismis="modal"
+                data-target="#modalArchivos"
+                @click="seleccionarArchivo('','new')"
+              >
+                <i class="material-icons icon-align">add_circle</i> Agregar Archivo
+              </button>
+            </form>
+            <div class="col-sm-12 pt-3">
+              <transition
+                name="custom-classes-transition"
+                enter-active-class="animated fadeInDown"
+                leave-active-class="animated fadeOutUp"
+              >
+                <div class="float-left pb-2" v-show="filtroFecha.verFiltrosBusqueda">
+                  <div class="btn-group">
+                    <input
+                      type="checkbox"
+                      class="form-check-input"
+                      id="check"
+                      v-model="filtroFecha.inputFiltroChecked"
+                      @click="filtroFecha.verModalBusqueda=true;"
+                    />
+                    <label
+                      for="check"
+                      :class="[filtroFecha.clickedFiltro.IDES !== ''
+                     ? 'font-bold':'']"
+                    >Estantes / Secciones / Categorías</label>
+                  </div>
+                  <div class="btn-group">
+                    <a
+                      href="#"
+                      class="dropdown-toggle text-secondary ml-2"
+                      data-toggle="dropdown"
+                      aria-haspopup="true"
+                      aria-expanded="false"
+                      :class="[filtroFecha.buscarFechaDias !== 0 ? 'font-bold':'']"
+                    >Fecha</a>
+                    <div class="dropdown-menu">
+                      <a
+                        class="dropdown-item"
+                        href="#"
+                        @click.prevent="filtroFecha.buscarFechaDias = 0"
+                      >
+                        <i
+                          class="material-icons align-middle"
+                          style="font-weight:bold;margin-left: -30px;"
+                          v-show="filtroFecha.buscarFechaDias==0"
+                        >check</i>
+                        &nbsp; Cualquier fecha
+                      </a>
+                      <a class="dropdown-item" href="#" @click="filtroFecha.buscarFechaDias = 1">
+                        <i
+                          class="material-icons align-middle"
+                          style="font-weight:bold;margin-left: -30px;"
+                          v-show="filtroFecha.buscarFechaDias==1"
+                        >check</i>
+                        &nbsp; Últimas 24 hrs
+                      </a>
+                      <a
+                        class="dropdown-item"
+                        href="#"
+                        @click.prevent="filtroFecha.buscarFechaDias = 7"
+                      >
+                        <i
+                          class="material-icons align-middle"
+                          style="font-weight:bold;margin-left: -30px;"
+                          v-show="filtroFecha.buscarFechaDias==7"
+                        >check</i>
+                        &nbsp; Última semana
+                      </a>
+                      <div class="dropdown-divider"></div>
+                      <a
+                        class="dropdown-item"
+                        href="#"
+                        @click.prevent="filtroFecha.verFiltroFecha=true;filtroFecha.buscarFechaDias=-2"
+                      >
+                        <i
+                          class="material-icons align-middle"
+                          style="font-weight:bold;margin-left: -30px;"
+                          v-show="filtroFecha.verFiltroFecha&&filtroFecha.buscarFechaDias==-2||filtroFecha.buscarFechaDias==-1"
+                        >check</i>
+                        &nbsp; Intervalo personalizado
+                      </a>
+                    </div>
+                  </div>
+                  <div class="btn-group">
+                    <div
+                      class="form-material"
+                      v-show="filtroFecha.verFiltroFecha&&filtroFecha.buscarFechaDias==-2||filtroFecha.buscarFechaDias==-1"
+                    >
+                      <!-- Datepicker Show -->
+                      <div class="datepicker-trigger">
+                        <div class="input-group">
+                          <span
+                            class="input-group-text"
+                            style="background-color: transparent; border: none;"
+                          >
+                            <i class="material-icons align-middle">date_range</i>
+                          </span>
+                          <input
+                            type="text"
+                            class="form-control"
+                            id="datepicker-trigger"
+                            placeholder="Selecciona un rango..."
+                            :value="formatoFechaCalendario(filtroFecha.fechaInicial,filtroFecha.fechaFinal)"
+                          />
+                        </div>
+                        <AirbnbStyleDatepicker
+                          :trigger-element-id="'datepicker-trigger'"
+                          :mode="'range'"
+                          :fullscreen-mobile="false"
+                          :date-one="filtroFecha.fechaInicial"
+                          :date-two="filtroFecha.fechaFinal"
+                          :end-date="filtroFecha.fechaLimiteCalendario"
+                          @date-one-selected="val => { filtroFecha.fechaInicial = val }"
+                          @date-two-selected="val => { filtroFecha.fechaFinal = val }"
+                          @apply="filtroFecha.buscarFechaDias = -1"
+                        />
+                      </div>
+                    </div>
+                  </div>
+                  <div class="btn-group">
+                    <a
+                      href="#"
+                      class="ml-3 text-secondary"
+                      @click="borrarFiltroBusqueda()"
+                      v-if="buscar.length !== 0 || 
+                    filtroFecha.buscarFechaDias !== 0 ||
+                    filtroFecha.clickedFiltro.IDES !== '' "
+                    >Borrar Filtro</a>
+                  </div>
+                </div>
+              </transition>
+            </div>
+            <!--  modal agregar / editar -->
+            <div
+              class="modal animated bounceInDown fast"
+              id="modalArchivos"
+              tabindex="-1"
+              role="dialog"
+              aria-labelledby="modalArchivos"
+            >
+              <div class="modal-dialog" role="document">
+                <div class="modal-content">
+                  <div class="modal-header">
+                    <div style="width:100%">
+                      <h4 class="float-left">{{ mensajeModal }}</h4>
+                      <uploadImage
+                        class="float-right"
+                        v-on:imagenUpdatedEvent="actualizarImagenes"
+                      />
+                    </div>
+                    <button type="button" class="close" data-dismiss="modal" aria-label="Close">
+                      <span aria-hidden="true">&times;</span>
+                    </button>
+                  </div>
+                  <div class="modal-body">
+                    <div class="form-row">
+                      <div class="form-group col-md-6">
+                        <label class="font-bold">Nombre</label>
+                        <input
+                          class="form-control"
+                          v-validate="'required|regex:^(?:[A-Za-z0-9 _.]*)$'"
+                          :class="{'input': true, 'is-danger': errors.has('nombre') }"
+                          v-model="clickedArch.ARCH"
+                          name="nombre"
+                          placeholder="Nombre del archivo"
+                        />
+                        <span
+                          v-show="errors.has('nombre')"
+                          class="invalid-feedback-form"
+                        >{{ errors.first('nombre') }}</span>
+                      </div>
+                      <div class="form-group col-md-6">
+                        <label class="font-bold">Estante</label>
+                        <select
+                          class="custom-select"
+                          v-model="clickedArch.IDES"
+                          name="estante"
+                          v-validate="'required'"
+                          :class="{'input': true, 'is-danger': errors.has('estante') }"
+                          id="estante"
+                          @change="clickedArch.IDSE = ''"
+                        >
+                          <option value>Seleccione una opción</option>
+                          <option
+                            v-for="estantes in permisosEstantes"
+                            :key="estantes.IDES"
+                            :value="estantes.IDES"
+                          >{{estantes.ESTA }}</option>
+                        </select>
+                        <span
+                          v-show="errors.has('estante')"
+                          class="invalid-feedback-form"
+                        >{{ errors.first('estante') }}</span>
+                      </div>
+                    </div>
+                    <div class="form-row">
+                      <div class="form-group col-md-6">
+                        <label class="font-bold">Sección</label>
+                        <select
+                          class="custom-select"
+                          v-model="clickedArch.IDSE"
+                          name="seccion"
+                          v-validate.immediate="clickedArch.IDCA !== '' ? 'required' : '' "
+                          :class="{'input': true, 'is-danger': errors.has('seccion') }"
+                          id="seccion"
+                          @input="clickedArch.IDCA = ''"
+                        >
+                          <option value>Seleccione una opción</option>
+                          <option
+                            v-for="seccion in permisosSecciones"
+                            :key="seccion.IDSE"
+                            :value="seccion.IDSE"
+                          >{{seccion.SECC }}</option>
+                        </select>
+                        <span
+                          v-show="errors.has('seccion')"
+                          class="invalid-feedback-form"
+                        >{{ errors.first('seccion') }}</span>
+                      </div>
+                      <div class="form-group col-md-6">
+                        <label class="font-bold">Categoría</label>
+                        <select class="custom-select" v-model="clickedArch.IDCA">
+                          <option value>Seleccione una opción</option>
+                          <option
+                            v-for="categoria in permisosCategorias"
+                            :key="categoria.IDCA"
+                            :value="categoria.IDCA"
+                          >{{categoria.CATE }}</option>
+                        </select>
+                      </div>
+                    </div>
+                    <div class="form-group" v-if="mensajeButtonModal=='Agregar'">
+                      <label class="font-bold">Archivo</label>
+                      <b-form-file
+                        v-model="file"
+                        placeholder="Seleccione un archivo..."
+                        drop-placeholder="Suelte los archivos aquí..."
+                        accept=".xlsx, .pdf, .docx"
+                        name="archivo"
+                        v-validate="file==null&&mensajeButtonModal=='Agregar' ? 'required' : ''"
+                        :class="{'input': true, 'is-danger': errors.has('archivo') }"
+                      ></b-form-file>
+                      <span
+                        v-show="errors.has('archivo') && mensajeButtonModal=='Agregar'"
+                        class="invalid-feedback-form"
+                      >{{ errors.first('archivo') }}</span>
+                    </div>
+                    <label class="typo__label font-bold">Seleccione una imagen</label>
+                    <multiselect
+                      v-model="imagen"
+                      placeholder="Seleccione una imagen de la lista"
+                      label="title"
+                      track-by="IMAG"
+                      :options="imagenesData"
+                      :option-height="104"
+                      :custom-label="({ title }) => `${title}`"
+                      :show-labels="false"
+                    >
+                      <template slot="singleLabel" slot-scope="props">
+                        <img class="option__image" :src="props.option.URL" style="width:100px" />
+                        <span class="option__desc">
+                          <span class="option__title ml-2">{{ props.option.IMAG }}</span>
+                        </span>
+                      </template>
+                      <template slot="option" slot-scope="props">
+                        <img class="option__image" :src="props.option.URL" style="width:100px" />
+                        <span class="option__title ml-2">{{ props.option.IMAG }}</span>
+                      </template>
+                      <span slot="noOptions">Sin elementos para mostrar.</span>
+                    </multiselect>
+                    <span
+                      v-show="imagenInvalid && imagen.IMAG == 'Seleccione una imagen de la lista'"
+                      class="invalid-feedback-form"
+                    >La imagen es un campo obligatorio</span>
+                  </div>
+                  <div class="modal-footer">
+                    <button
+                      type="button"
+                      class="btn btn-light"
+                      :disabled="isLoading"
+                      data-dismiss="modal"
+                    >Cerrar</button>
+                    <!-- Buttons save and edit -->
+                    <button
+                      type="button"
+                      v-if="mensajeButtonModal=='Editar'"
+                      @click="editarArchivo()"
+                      class="btn btn-danger"
+                      :disabled="isLoading"
+                    >
+                      <div class="save" v-if="!isLoading">
+                        <i class="material-icons icon-align">save</i>
+                        {{ mensajeButtonModal }}
+                      </div>
+                      <div class="loading" v-if="isLoading">
+                        <span
+                          class="spinner-border spinner-grow-sm"
+                          role="status"
+                          aria-hidden="true"
+                        ></span> Cargando...
+                      </div>
+                    </button>
+                    <button
+                      type="button"
+                      v-if="mensajeButtonModal=='Agregar'"
+                      @click="crearArchivo(clickedArch.ARCH,clickedArch.IDES,clickedArch.IDSE,clickedArch.IDCA)"
+                      class="btn btn-danger"
+                      :disabled="isLoading"
+                    >
+                      <div class="save" v-if="!isLoading">
+                        <i class="material-icons icon-align">save</i>
+                        {{ mensajeButtonModal }}
+                      </div>
+                      <div class="loading" v-if="isLoading">
+                        <span
+                          class="spinner-border spinner-grow-sm"
+                          role="status"
+                          aria-hidden="true"
+                        ></span> Cargando...
+                      </div>
+                    </button>
+                  </div>
+                </div>
+              </div>
+            </div>
+            <!-- end modal -->
+            <!-- Modal filtro buscar -->
+            <transition name="fade" v-if="filtroFecha.verModalBusqueda ">
+              <div class="modal-mask">
+                <div class="modal-wrapper">
+                  <div class="modal-dialog" role="document">
+                    <div class="modal-content">
+                      <div class="modal-header">
+                        <h6>Filtro avanzado de archivos</h6>
+                        <button
+                          type="button"
+                          class="close"
+                          @click="filtroFecha.verModalBusqueda = false;filtroFecha.inputFiltroChecked = false"
+                          aria-label="Cerrar"
+                        >
+                          <span aria-hidden="true">&times;</span>
+                        </button>
+                      </div>
+                      <div class="modal-body">
+                        <div class="form-group row">
+                          <label for="lbl_estantes" class="col-sm-3 col-form-label">Estantes</label>
+                          <div class="col-sm-9">
+                            <select
+                              class="custom-select"
+                              v-model="filtroFecha.clickedFiltro.IDES"
+                              id="estanteFiltro"
+                              @change="filtroFecha.clickedFiltro.IDSE = ''"
+                            >
+                              <option value>Seleccione una opción</option>
+                              <option
+                                v-for="estantes in permisosEstantes"
+                                :key="estantes.IDES"
+                                :value="estantes.IDES"
+                              >{{estantes.ESTA }}</option>
+                            </select>
+                          </div>
+                        </div>
+                        <div class="form-group row">
+                          <label for="lbl_secciones" class="col-sm-3 col-form-label">Secciones</label>
+                          <div class="col-sm-9">
+                            <select
+                              class="custom-select"
+                              v-model="filtroFecha.clickedFiltro.IDSE"
+                              id="seccionFiltro"
+                            >
+                              <option value>Seleccione una sección</option>
+                              <option
+                                v-for="seccion in seccionesFiltro"
+                                :key="seccion.IDSE"
+                                :value="seccion.IDSE"
+                              >{{seccion.SECC }}</option>
+                            </select>
+                          </div>
+                        </div>
+                        <div class="form-group row">
+                          <label for="lbl_secciones" class="col-sm-3 col-form-label">Categorías</label>
+                          <div class="col-sm-9">
+                            <select
+                              class="custom-select"
+                              v-model="filtroFecha.clickedFiltro.IDCA"
+                              id="seccionFiltro"
+                            >
+                              <option value>Seleccione una sección</option>
+                              <option
+                                v-for="categoria in categoriaFiltro"
+                                :key="categoria.IDCA"
+                                :value="categoria.IDCA"
+                              >{{categoria.CATE }}</option>
+                            </select>
+                          </div>
+                        </div>
+                        <button
+                          class="btn btn-danger float-right mt-3"
+                          @click="filtroFecha.inputFiltroChecked = true;filtroFecha.verModalBusqueda = false;"
+                        >Aplicar</button>
+                      </div>
+                    </div>
+                  </div>
+                </div>
+              </div>
+            </transition>
+
+            <!-- Start delete categoría -->
+            <div id="deleteArchivos" class="modal animated bounceInDown fast">
+              <div class="modal-dialog">
+                <div class="modal-content">
+                  <form>
+                    <div class="modal-header">
+                      <h4 class="modal-title">Borrar Categoría</h4>
+                      <button
+                        type="button"
+                        class="close"
+                        data-dismiss="modal"
+                        aria-hidden="true"
+                      >&times;</button>
+                    </div>
+                    <div class="modal-body">
+                      <p>
+                        Esta a punto de borrar
+                        <b>'{{ clickedArch.ARCH }}'</b>
+                      </p>
+                      <p class="text-danger">Esta acción no se puede deshacer.</p>
+                    </div>
+                    <div class="modal-footer">
+                      <input
+                        type="button"
+                        class="btn btn-default"
+                        data-dismiss="modal"
+                        value="Cerrar"
+                      />
+                      <input
+                        type="submit"
+                        @click="borrarArchivo()"
+                        class="btn btn-danger"
+                        value="Borrar"
+                        data-dismiss="modal"
+                      />
+                    </div>
+                  </form>
+                </div>
+              </div>
+            </div>
+            <!-- End delete categoría -->
+            <div class="table-responsive" v-if="strError.length == 0">
+              <table class="table no-wrap table-hover">
+                <thead>
+                  <tr>
+                    <th>Imagen</th>
+                    <th>Nombre</th>
+                    <th>Estante vinculado</th>
+                    <th>Sección vinculada</th>
+                    <th>Categoría vinculada</th>
+                    <th>Fecha Creación</th>
+                    <th v-if="$store.getters.user.role === 'admin'">Creado Por</th>
+                    <th>Acciones</th>
+                  </tr>
+                </thead>
+                <draggable
+                  v-model="archivos"
+                  tag="tbody"
+                  v-if="archivos.length != 0"
+                  @start="drag=true"
+                  @end="drag = false"
+                  @update="moverArchivos"
+                  v-bind="configuracionDragg"
+                >
+                  <tr v-for="archivos in buscarArchivo" :key="archivos.IDAR">
+                    <td>
+                      <div class="profile-img">
+                        <img
+                          v-lazy="archivos.IDIM"
+                          style="max-width:65px"
+                          alt="user"
+                          class="img-thumbnail"
+                        />
+                      </div>
+                    </td>
+                    <td>{{ archivos.ARCH }}</td>
+                    <td>
+                      <span class="label label-primary">
+                        <i class="material-icons icon-align">dns</i>
+                        {{ archivos.ESTA }}
+                      </span>
+                    </td>
+                    <td>
+                      <span class="label label-info" v-if="archivos.SECC !== null">
+                        <i class="material-icons icon-align">collections_bookmark</i>
+                        {{ archivos.SECC }}
+                      </span>
+                    </td>
+                    <td>
+                      <span class="label label-success" v-if="archivos.CATE !== null">
+                        <i class="material-icons icon-align">book</i>
+                        {{ archivos.CATE }}
+                      </span>
+                    </td>
+                    <td>
+                      <span v-if="archivos.FECR !== null ">
+                        <i class="material-icons icon-align" style="color:red">access_time</i>
+                        {{ archivos.FECR | formatDate }}
+                      </span>
+                    </td>
+                    <td v-if="$store.getters.user.role === 'admin'">
+                      <i class="material-icons icon-align" style="color:red">person</i>
+                      <span>{{ archivos.DINOMBRE }}</span>
+                    </td>
+                    <td>
+                      <!-- Botones de acciones -->
+                      <a
+                        href="#modalArchivos"
+                        class="edit"
+                        data-toggle="modal"
+                        @click="seleccionarArchivo(archivos,'edit')"
+                      >
+                        <i
+                          class="material-icons"
+                          data-toggle="tooltip"
+                          title="Editar"
+                          style="color:#f42849"
+                        >&#xE254;</i>
+                      </a>
+                      <a
+                        href="#deleteArchivos"
+                        @click="seleccionarArchivo(archivos,'delete')"
+                        class="delete"
+                        data-toggle="modal"
+                      >
+                        <i
+                          class="material-icons"
+                          data-toggle="tooltip"
+                          title="Borrar"
+                          style="color:#f42849"
+                        >&#xE872;</i>
+                      </a>
+                    </td>
+                  </tr>
+                </draggable>
+              </table>
+              <!-- Pagination -->
+              <b-pagination
+                v-show="archivos.length != 0 && buscarArchivo.length != 0"
+                v-model="inicioPagina"
+                :total-rows="totalFilas"
+                :per-page="porPagina"
+                align="center"
+              ></b-pagination>
+              <!-- Actualizar orden de los archivos  -->
+              <button
+                v-show="actualizarOrden"
+                class="mr-2 btn btn-sm float-right hidden-sm-down btn-outline-success"
+                @click="actualizarOrdenArchivos()"
+                :disabled="isLoading"
+              >
+                <div class="save" v-if="!isLoading">
+                  <i class="material-icons icon-align">save</i>
+                  Guardar Cambios
+                </div>
+                <div class="loading" v-if="isLoading">
+                  <span class="spinner-border spinner-grow-sm" role="status" aria-hidden="true"></span> Cargando...
+                </div>
+              </button>
+              <button
+                v-show="actualizarOrden"
+                class="mr-2 btn btn-sm float-right hidden-sm-down btn-outline-danger"
+                @click="removerOrden()"
+                :disabled="isLoading"
+              >
+                <i class="material-icons icon-align">refresh</i> Deshacer cambios
+              </button>
+              <div v-if="loading" class="text-center">
+                <p>Cargando...</p>
+                <span
+                  class="spinner-border spinner-grow-sm"
+                  role="status"
+                  aria-hidden="true"
+                  style="color:red"
+                ></span>
+              </div>
+              <div class="text-center">
+                <p
+                  class="mt-3"
+                  v-show="mensajeArchivosVacios"
+                >No se encontraron archivos para mostrar.</p>
+                <p
+                  class="mt-3"
+                  v-show=" archivos.length !== 0 && buscarArchivo.length == 0 && archivos.length != 0 && !loading"
+                >No se encontró ningún archivo en su búsqueda</p>
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+<style src="vue-multiselect/dist/vue-multiselect.min.css"></style>
+<script>
+import uploadImage from "@/components/UploadImage.vue";
+import Multiselect from "vue-multiselect";
+import moment from "moment";
+import draggable from "vuedraggable";
+// Components bootstrap-vue
+import { BPagination, BFormFile } from "bootstrap-vue";
+// Date Pickers
+import format from "date-fns/format";
+import { token } from "../../_mixin/user_mixin.js";
+var _ = require("lodash");
+export default {
+  name: "ArchivosAdmin",
+  mixins: [token],
+  components: {
+    uploadImage,
+    Multiselect,
+    draggable,
+    BPagination,
+    BFormFile
+  },
+  data: () => ({
+    mensajeArchivosVacios: false,
+    estantesPermitidos: [],
+    seccionesPermitidas: [],
+    categoriasPermitidas: [],
+    permisosDatos: [],
+    mensajeModal: "Agregar Archivo",
+    mensajeButtonModal: "Agregar",
+    file: null,
+    clickedArch: {
+      IDES: "",
+      IDSE: "",
+      IDCA: ""
+    },
+    imagen: { IMAG: "Seleccione una imagen de la lista" },
+    buscar: "",
+    totalFilas: 0,
+    porPagina: 25,
+    inicioPagina: 1,
+    actualizarOrden: false,
+    arrOrder: {},
+    isLoading: false,
+    imagenInvalid: false,
+    filtroFecha: {
+      fechaLimiteCalendario: moment().format("YYYY-MM-DD"),
+      verFiltroFecha: false,
+      verFiltrosBusqueda: false,
+      verModalBusqueda: false,
+      inputFiltroChecked: false,
+      fechaInicial: "",
+      fechaFinal: "",
+      buscarFechaDias: 0,
+      clickedFiltro: {
+        IDES: "",
+        IDSE: "",
+        IDCA: ""
+      }
+    }
+  }),
+  async mounted() {
+    this.permisosDatos = JSON.parse(this.$store.getters.permisos);
+    var est = await this.getEstantes();
+    var secc = await this.getSecciones();
+    var cate = await this.getCategorias();
+    var img = await this.getImages("AR");
+    var arch = await this.getArchivos(
+      this.$store.getters.user.id,
+      this.$store.getters.user.role
+    );
+  },
+  methods: {
+    editarArchivo: function() {
+      // Verificar que tengamos imagen seleccionada
+      this.$validator.validateAll().then(errors => {
+        if (errors) {
+          if (this.imagen.IMAG != "Seleccione una imagen de la lista") {
+            let img = this.imagen.URL;
+            this.clickedArch.IDIM = img;
+          }
+          var formData = this.toFormData(this.clickedArch);
+          this.isLoading = true;
+          this.$http
+            .post(
+              this.url_api + "/archivos/update",
+              formData,
+              this.config_header
+            )
+            .then(response => {
+              this.clickedArch = {};
+              if (response.data.response) {
+                this.$toasted.show(response.data.message, this.$toast_success);
+                this.getArchivos(
+                  this.$store.getters.user.id,
+                  this.$store.getters.user.role
+                );
+              } else {
+                this.$toasted.show(response.data.message, this.$toast_error);
+              }
+              this.isLoading = false;
+              this.removeModal("modalArchivos");
+            })
+            .catch(error => {
+              this.strError =
+                "Se ha producido un error al actualizar los archivos.";
+              this.removeModal("modalArchivos");
+            });
+        }
+      });
+    },
+    seleccionarArchivo: function(archivos, accion) {
+      this.clickedArch = {};
+      this.file = null;
+      this.imagen = { IMAG: "Seleccione una imagen de la lista" };
+      this.imagenInvalid = false;
+      // Remove Menssages error
+      this.$nextTick().then(() => {
+        this.$validator.reset();
+        this.errors.clear();
+      });
+      if (accion == "new") {
+        this.clickedArch = { IDES: "", IDSE: "", IDCA: "" };
+        this.mensajeModal = "Agregar Archivo";
+        this.mensajeButtonModal = "Agregar";
+      }
+      // Si estamos editando
+      if (accion == "edit") {
+        let secc = archivos.IDSE !== null ? archivos.IDSE : "";
+        let cate = archivos.IDCA !== null ? archivos.IDCA : "";
+        var datos = {
+          IDAR: archivos.IDAR,
+          ARCH: archivos.ARCH,
+          IDES: archivos.IDES,
+          IDSE: secc,
+          IDCA: cate,
+          IDIM: archivos.IDIM,
+          USMO: this.$store.getters.user.id
+        };
+        this.clickedArch = datos;
+        this.mensajeButtonModal = "Editar";
+        this.mensajeModal = "Editar Archivo";
+      }
+      if (accion == "delete") {
+        this.clickedArch = archivos;
+      }
+    },
+    crearArchivo: function(arch, ides, idse, idca) {
+      // Verificar que tengamos imagen seleccionada
+      this.imagenInvalid = false;
+      if (this.imagen.IMAG == "Seleccione una imagen de la lista") {
+        this.imagenInvalid = true;
+      }
+      this.$validator.validateAll().then(result => {
+        if (result && !this.imagenInvalid) {
+          let imagenUrl = this.imagen.URL;
+          var datos = {
+            ARCH: arch,
+            IDES: ides,
+            IDSE: idse,
+            IDCA: idca,
+            IDIM: imagenUrl,
+            USCR: this.$store.getters.user.id,
+            NOMBRE_FILE: this.file.name,
+            file: this.file
+          };
+          var formData = this.toFormData(datos);
+          this.isLoading = true;
+          this.$http
+            .post(this.url_api + "/archivos", formData, this.config_header)
+            .then(response => {
+              if (response.data.response) {
+                this.$toasted.show(response.data.message, this.$toast_success);
+              } else {
+                this.$toasted.show(response.data.message, this.$toast_error);
+              }
+              this.getArchivos(
+                this.$store.getters.user.id,
+                this.$store.getters.user.role
+              );
+              this.isLoading = false;
+              this.removeModal("modalArchivos");
+            })
+            .catch(error => {
+              this.removeModal("modalArchivos");
+              this.getArchivos(
+                this.$store.getters.user.id,
+                this.$store.getters.user.role
+              );
+              this.strError = "Se ha producido un error al crear el archivo.";
+            });
+        } //end Result
+      }); // end validator
+    },
+    borrarArchivo: function() {
+      var formData = this.toFormData(this.clickedArch);
+      this.$http
+        .post(this.url_api + "/archivos/delete", formData, this.config_header)
+        .then(response => {
+          if (response.data.response) {
+            this.$toasted.show(response.data.message, this.$toast_success);
+            this.getArchivos(
+              this.$store.getters.user.id,
+              this.$store.getters.user.role
+            );
+          } else {
+            this.$toasted.show(response.data.message, this.$toast_error);
+          }
+        })
+        .catch(error => {
+          this.strError = "Se ha producido un error al borrar el archivo.";
+        });
+    },
+    moverArchivos: function() {
+      this.actualizarOrden = true;
+      var obj = this.archivos;
+      var bodyFormData = new FormData();
+      obj.forEach((element, i) => {
+        i++;
+        bodyFormData.append(element.IDAR, i);
+      });
+      this.arrOrder = bodyFormData;
+    },
+    removerOrden: function() {
+      this.getArchivos(
+        this.$store.getters.user.id,
+        this.$store.getters.user.role
+      );
+      this.actualizarOrden = false;
+    },
+    actualizarOrdenArchivos: function() {
+      var formData = this.arrOrder;
+      this.isLoading = true;
+      this.$http
+        .post(this.url_api + "/archivos/order", formData, this.config_header)
+        .then(response => {
+          this.isLoading = false;
+          this.actualizarOrden = false;
+          if (response.data.response) {
+            this.$toasted.show(response.data.message, this.$toast_success);
+          } else {
+            this.$toasted.show(response.data.message, this.$toast_error);
+          }
+
+          this.getArchivos(
+            this.$store.getters.user.id,
+            this.$store.getters.user.role
+          );
+        })
+        .catch(error => {
+          this.strError = "Se ha producido un error al ordenar el archivo,.";
+        });
+    },
+    paginador: function(archivos) {
+      // Cálcular páginador
+      const indiceInicio = (this.inicioPagina - 1) * this.porPagina;
+      const indiceFinal =
+        indiceInicio + this.porPagina > archivos.length
+          ? archivos.length
+          : indiceInicio + this.porPagina;
+      return archivos.slice(indiceInicio, indiceFinal);
+    },
+    borrarFiltroBusqueda: function() {
+      this.filtroFecha.verFiltroFecha = false;
+      this.filtroFecha.verModalBusqueda = false;
+      this.filtroFecha.inputFiltroChecked = false;
+      this.filtroFecha.fechaInicial = "";
+      this.filtroFecha.fechaFinal = "";
+      this.filtroFecha.buscarFechaDias = 0;
+      this.buscar = "";
+      this.filtroFecha.clickedFiltro.IDES = "";
+      this.filtroFecha.clickedFiltro.IDSE = "";
+      this.filtroFecha.clickedFiltro.IDCA = "";
+    },
+    actualizarImagenes: function(action) {
+      if (action) {
+        this.getImages("AR");
+      }
+    },
+    fachadaSecciones(idEstante) {
+      var secciones = [];
+      if (idEstante !== "") {
+        if (this.permisosDatos.length !== 0) {
+          secciones = this.secciones.filter(secc => secc.IDES === idEstante);
+          // Verificamos el perfil del usuario
+          if (
+            this.permisosDatos[0].children[0].text !== "ADMIN | Ver + Publicar"
+          ) {
+            let estanteSeleccionado = this.estantes.filter(
+              est => est.IDES === idEstante
+            );
+            let ESTA = estanteSeleccionado[0].ESTA;
+            let seccionesInEstante = _.values(
+              _.filter(this.permisosDatos[0].children, perm => {
+                let arrEstanteAndId = perm.text.split(" | ");
+                let strEstante = arrEstanteAndId[0].split(" - ");
+                return strEstante[0] === ESTA;
+              })
+            );
+
+            // Filtramos solo los permisos de las secciones que tengan permiso = Ver + Publicar
+            let seccionesPermisos = _.filter(
+              seccionesInEstante[0].children,
+              perm => {
+                let permiso = perm.text.split(" | ");
+                return permiso[1] === "Ver + Publicar";
+              }
+            );
+            // Buscamos las secciones que coincidan con las secciones permisos para saber a cuales esta permitido entrar
+            let seccionesPermitidas = _.values(
+              _.mapValues(seccionesPermisos, secc => {
+                let seccion = secc.text.split(" | ");
+                let strSeccion = seccion[0].split(" - ");
+                return _.find(secciones, ["SECC", strSeccion[0]]);
+              })
+            );
+
+            if (seccionesPermitidas.length === 0) {
+              // Limpiar categoria
+              this.filtroFecha.clickedFiltro.IDCA = "";
+              this.clickedArch.IDCA = "";
+            }
+
+            secciones = seccionesPermitidas;
+          }
+        }
+        return secciones;
+      }
+    },
+    fachadaCategoria(idEstante, idSeccion) {
+      if (idEstante === "") {
+        this.clickedArch.IDCA = "";
+        this.filtroFecha.clickedFiltro.IDCA = "";
+      }
+      var categorias = [];
+      if (idSeccion !== "") {
+        categorias = this.categorias.filter(cate => cate.IDSE === idSeccion);
+
+        // Verificamos el perfil del usuario
+        if (
+          this.permisosDatos[0].children[0].text !== "ADMIN | Ver + Publicar"
+        ) {
+          let estanteSeleccionado = this.estantes.filter(
+            est => est.IDES === idEstante
+          );
+          let seccionSeleccionada = this.secciones.filter(
+            secc => secc.IDSE === idSeccion
+          );
+          let SECC = seccionSeleccionada[0].SECC;
+          let ESTA = estanteSeleccionado[0].ESTA;
+
+          let permisosInCategorias = [];
+          // Encontrar los permisos disponibles para cada sección
+          _.filter(this.permisosDatos[0].children, perm => {
+            let arrEstante = perm.text.split(" | ");
+            let strNombre = arrEstante[0].split(" - ");
+            if (strNombre[0] === ESTA) {
+              _.filter(perm.children, permiso => {
+                let seccion = permiso.text.split(" | ");
+                let strSeccion = seccion[0].split(" - ");
+
+                if (strSeccion[0] === SECC) {
+                  permisosInCategorias.push(permiso);
+                }
+              });
+            }
+          });
+
+          // Filtramos solo los permisos de las categorias que tengan permiso = Ver + Publicar
+          let categoriasPermisos = _.filter(
+            permisosInCategorias[0].children,
+            perm => {
+              let permiso = perm.text.split(" | ");
+              return permiso[1] === "Ver + Publicar";
+            }
+          );
+          // Buscamos las categorias que coincidan con el nombre de las secciones con permisos Ver y/o Ver + Publicar
+          let categoriasPermitidas = _.values(
+            _.mapValues(categoriasPermisos, cate => {
+              let categoria = cate.text.split(" | ");
+              let strCategoria = categoria[0].split(" - ");
+
+              return _.find(categorias, ["CATE", strCategoria[0]]);
+            })
+          );
+          categorias = categoriasPermitidas;
+        }
+        return categorias;
+      }
+      if (idSeccion === "") {
+        // Limpiar categoria
+        this.filtroFecha.clickedFiltro.IDCA = "";
+        this.clickedArch.IDCA = "";
+      }
+    }
+  },
+  computed: {
+    permisosEstantes() {
+      if (this.permisosDatos.length) {
+        if (this.$store.getters.user.role === "colaborador") {
+          let estantesConAcceso = _.filter(
+            this.permisosDatos[0].children,
+            perm => {
+              let permiso = perm.text.split(" | ");
+              return permiso[1] === "Ver + Publicar";
+            }
+          );
+
+          if (this.estantes.length) {
+            let estantesPermitidos = _.values(
+              _.mapValues(estantesConAcceso, est => {
+                let arrEstanteAndId = est.text.split(" | ");
+                let strEstante = arrEstanteAndId[0].split(" - ");
+
+                let resultadoFinal = _.find(this.estantes, [
+                  "ESTA",
+                  strEstante[0]
+                ]);
+                return resultadoFinal;
+              })
+            );
+            return estantesPermitidos;
+          }
+        }
+        return this.estantes;
+      }
+    },
+    permisosSecciones() {
+      let idEstante = this.clickedArch.IDES;
+      return idEstante === "" ? [] : this.fachadaSecciones(idEstante);
+    },
+    permisosCategorias() {
+      let idEstante = this.clickedArch.IDES;
+      let idSeccion = this.clickedArch.IDSE;
+      return idEstante === "" && idSeccion === ""
+        ? []
+        : this.fachadaCategoria(idEstante, idSeccion);
+    },
+    seccionesFiltro() {
+      let idEstante = this.filtroFecha.clickedFiltro.IDES;
+      return idEstante === "" ? [] : this.fachadaSecciones(idEstante);
+    },
+    categoriaFiltro() {
+      let idEstante = this.filtroFecha.clickedFiltro.IDES;
+      let idSeccion = this.filtroFecha.clickedFiltro.IDSE;
+      return idEstante === "" && idSeccion === ""
+        ? []
+        : this.fachadaCategoria(idEstante, idSeccion);
+    },
+    buscarArchivo() {
+       var palabraAbuscar = this.buscar.toLowerCase();
+      // Fecha actual
+      var fechaAhora = moment().format("YYYY-MM-DD");
+      // Arrays Temporales
+      var archivosFiltrados = [];
+      var filtroArchivosModal = [];
+      var filtroArchivosInput = [];
+
+      //Filtro de Modal Búsqueda Avanzada
+      var idEstante = this.filtroFecha.clickedFiltro.IDES;
+      var idSeccion = this.filtroFecha.clickedFiltro.IDSE;
+      var idCategoria = this.filtroFecha.clickedFiltro.IDCA;
+
+      filtroArchivosModal = this.archivos.filter(archivo => {
+        if (idEstante !== "" && idSeccion === "" && idCategoria === "") {
+          return archivo.IDES === idEstante;
+        }
+        if (idEstante !== "" && idSeccion !== "" && idCategoria === "") {
+          return archivo.IDES === idEstante && archivo.IDSE === idSeccion;
+        }
+        if (idEstante !== "" && idSeccion !== "" && idCategoria !== "") {
+          return (
+            archivo.IDES === idEstante &&
+            archivo.IDSE === idSeccion &&
+            archivo.IDCA === idCategoria
+          );
+        }
+        return archivo;
+      });
+
+      // filtra por lo que escriba el usuario en la caja de texto
+      filtroArchivosInput = filtroArchivosModal.filter(arch => {
+        return (
+          arch.ARCH.toLowerCase().match(palabraAbuscar) ||
+          arch.ESTA.toLowerCase().match(palabraAbuscar) ||
+          (arch.SECC !== null &&
+            arch.SECC.toLowerCase().match(palabraAbuscar)) ||
+          (arch.CATE !== null && arch.CATE.toLowerCase().match(palabraAbuscar))
+        );
+      });
+
+      // Filtrar por rango de fecha
+      switch (this.filtroFecha.buscarFechaDias) {
+        case -1:
+          archivosFiltrados = filtroArchivosInput.filter(arch => {
+            let fechaFormato = moment(arch.FECR, "DD-MMM-YY").format(
+              "YYYY-MM-DD"
+            );
+
+            if (
+              !(
+                moment(fechaFormato).isBefore(this.filtroFecha.fechaInicial) ||
+                moment(fechaFormato).isAfter(this.filtroFecha.fechaFinal)
+              )
+            ) {
+              return filtroArchivosInput;
+            }
+          });
+          break;
+        case 1:
+        case 7:
+          // Filtrar ultimos 7 días y 24 hrs
+          var dias = moment()
+            .subtract(this.filtroFecha.buscarFechaDias, "days")
+            .format("YYYY-MM-DD");
+          // filtramos que nuestra fecha esté en el intervalo
+          archivosFiltrados = filtroArchivosInput.filter(arch => {
+            let frm = moment(arch.FECR, "DD-MMM-YY").format("YYYY-MM-DD");
+            if (
+              moment(frm).isBetween(dias, fechaAhora) ||
+              moment(frm).isSame(fechaAhora)
+            ) {
+              return filtroArchivosInput;
+            }
+          });
+          break;
+        default:
+          archivosFiltrados = filtroArchivosInput;
+          break;
+      } // End Switch
+
+      this.totalFilas = archivosFiltrados.length;
+      if (archivosFiltrados.length === 0) {
+        this.mensajeArchivosVacios = true;
+      }
+      return this.paginador(archivosFiltrados);
+    }
+  }
+};
+</script>
+<style scoped>
+.page-titles .breadcrumb .breadcrumb-item + .breadcrumb-item::before {
+  content: "/";
+}
+.uploading-image {
+  display: flex;
+}
+.sortable-chosen {
+  background: #c2dbff;
+}
+.table-responsive {
+  box-shadow: 0px 0px 7px 2px #f2f2f2;
+  padding: 15px;
+}
+.spinner-grow-sm {
+  width: 1.6rem;
+  height: 1.6rem;
+}
+.dropdown-item {
+  padding: 0px 7px 2px 40px;
+}
+.btn-group label {
+  color: #6c757d !important;
+}
+/* Modal Filtro */
+.fade-enter-active,
+.fade-leave-active {
+  transition: opacity 0.5s;
+}
+.fade-enter,
+.fade-leave-to {
+  opacity: 0;
+}
+.modal-mask {
+  position: fixed;
+  z-index: 9998;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  background-color: rgba(0, 0, 0, 0.1);
+  display: table;
+  transition: opacity 0.3s ease;
+}
+.modal-wrapper {
+  display: table-cell;
+  vertical-align: middle;
+}
+.modal-dialog {
+  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2);
+}
+.dropdown-toggle {
+  border: 1px solid#d8d8d8;
+  padding: 3px;
+}
+</style>

+ 905 - 0
src/views/admin/CategoriasAdmin.vue

@@ -0,0 +1,905 @@
+<template>
+  <div>
+    <ol class="breadcrumb mb-2">
+      <li class="breadcrumb-item">
+        <router-link :to="{name:'homeAdmin'}">Inicio</router-link>
+      </li>
+      <li class="breadcrumb-item active">Categorías</li>
+    </ol>
+    <div class="row">
+      <div class="col-12">
+        <div class="card">
+          <div class="card-body">
+            <!-- Manejo de errores -->
+            <div class="alert alert-danger alert-rounded" v-if="strError.length != 0">
+              <button type="button" class="close" data-dismiss="alert" aria-label="Close"></button>
+              <i class="material-icons icon-align">report_problem</i>
+              {{ strError }}
+            </div>
+            <h4 class="card-title">Categorías</h4>
+            <span class="text-dark">En esta página se pueden visualizar las categorías disponibles.</span>
+            <div class="col-sm-12">
+              <form class="form-material mt-2">
+                <input
+                  type="text"
+                  class="form-control col-sm-3 mb-2 float-left"
+                  placeholder="Nombre de la categoría"
+                  v-model="buscar"
+                />
+
+                <div class="btn-group dropright">
+                  <a
+                    href="#"
+                    class="align-middle text-danger"
+                    @click.prevent="filtroFecha.verFiltrosBusqueda = !filtroFecha.verFiltrosBusqueda"
+                  >
+                    <i
+                      class="material-icons align-middle"
+                      style="color: #f42849;"
+                      id="busqueda-avanzada"
+                    >more_vert</i>
+                    <b-tooltip target="busqueda-avanzada" placement="bottom">
+                      <span>Búsqueda avanzada</span>
+                    </b-tooltip>
+                  </a>
+                </div>
+                <a
+                  id="filtro-ordenar"
+                  href
+                  @click.prevent="comenzarAMoverElementos(ordenarElementos = !ordenarElementos)"
+                >
+                  <b-tooltip target="filtro-ordenar" placement="bottom">
+                    <span>Ordenar</span>
+                  </b-tooltip>
+                  <i class="material-icons icon-align" :class="[ordenarElementos ? 'text-primary':'text-danger']">filter_list</i>
+                </a>
+                <!-- Botones para odernar -->
+                <button
+                  v-show="updateOrder"
+                  class="ml-2 mr-2 btn btn-sm btn-outline-info"
+                  @click.prevent="getCategorias();updateOrder = false;ordenarElementos = false;"
+                  :disabled="isLoading"
+                >
+                  <i class="material-icons icon-align">refresh</i> Deshacer
+                </button>
+                <button
+                  v-show="updateOrder"
+                  class="mr-2 btn btn-sm btn-outline-success"
+                  @click="actualizarOrdenCategorias()"
+                  :disabled="isLoading"
+                >
+                  <div class="save" v-if="!isLoading">
+                    <i class="material-icons icon-align">save</i>
+                    Guardar Orden
+                  </div>
+                  <div class="loading" v-if="isLoading">
+                    <span class="spinner-border spinner-grow-sm" role="status" aria-hidden="true"></span> Cargando...
+                  </div>
+                </button>
+
+                <button
+                  class="btn float-right btn-outline-danger"
+                  data-toggle="modal"
+                  data-dismis="modal"
+                  data-target="#modalCategoria"
+                  @click.prevent="seleccionarCategoria('','new')"
+                  v-if="strError.length == 0"
+                >
+                  <i class="material-icons icon-align">add_circle</i> Crear Categoría
+                </button>
+              </form>
+            </div>
+            <div class="col-sm-12 float-left mb-2" v-show="filtroFecha.verFiltrosBusqueda">
+              <div class="btn-group">
+                <a
+                  href
+                  class="dropdown-toggle mr-2 text-dark"
+                  :class="[clickedFiltro.IDSE !== ''
+                  ? 'font-bold':'']"
+                  @click.prevent="filtroFecha.verModalBusqueda = true"
+                >Secciones / Categorías</a>
+              </div>
+
+              <div class="btn-group">
+                <a
+                  href="#"
+                  class="dropdown-toggle mr-2 text-dark"
+                  data-toggle="dropdown"
+                  aria-haspopup="true"
+                  aria-expanded="false"
+                  :class="[filtroFecha.buscarFechaDias !== 0 ? 'font-bold':'']"
+                >{{ filtroFecha.filtroSeleccionado }}</a>
+
+                <div class="dropdown-menu">
+                  <a
+                    class="dropdown-item"
+                    href="#"
+                    @click.prevent="filtroFecha.buscarFechaDias = 0;
+                    filtroFecha.filtroSeleccionado = 'Cualquier fecha'"
+                  >
+                    <i
+                      class="material-icons align-middle"
+                      style="font-weight:bold;margin-left: -30px;"
+                      v-show="filtroFecha.buscarFechaDias == 0"
+                    >check</i>
+                    &nbsp; Cualquier fecha
+                  </a>
+                  <a
+                    class="dropdown-item"
+                    href="#"
+                    @click="filtroFecha.buscarFechaDias = 1;
+                  filtroFecha.filtroSeleccionado = 'Últimas 24 hrs'"
+                  >
+                    <i
+                      class="material-icons align-middle"
+                      style="font-weight:bold;margin-left: -30px;"
+                      v-show="filtroFecha.buscarFechaDias == 1"
+                    >check</i>
+                    &nbsp; Últimas 24 hrs
+                  </a>
+                  <a
+                    class="dropdown-item"
+                    href="#"
+                    @click.prevent="filtroFecha.buscarFechaDias = 7
+                    filtroFecha.filtroSeleccionado = 'Última semana'"
+                  >
+                    <i
+                      class="material-icons align-middle"
+                      style="font-weight:bold;margin-left: -30px;"
+                      v-show="filtroFecha.buscarFechaDias == 7"
+                    >check</i>
+                    &nbsp; Última semana
+                  </a>
+                  <div class="dropdown-divider"></div>
+                  <a
+                    class="dropdown-item"
+                    href="#"
+                    @click.prevent="filtroFecha.buscarFechaDias=-1;
+                    filtroFecha.filtroSeleccionado = 'Intervalo personalizado'"
+                  >
+                    <i
+                      class="material-icons align-middle"
+                      style="font-weight:bold;margin-left: -30px;"
+                      v-show="filtroFecha.buscarFechaDias==-1"
+                    >check</i>
+                    &nbsp; Intervalo personalizado
+                  </a>
+                </div>
+              </div>
+
+              <div class="btn-group">
+                <div class="form-material" v-show="filtroFecha.buscarFechaDias == -1">
+                  <!-- Datepicker Show -->
+                  <div class="datepicker-trigger">
+                    <div class="input-group">
+                      <span
+                        class="input-group-text"
+                        style="background-color: transparent; border: none;"
+                      >
+                        <i class="material-icons align-middle">date_range</i>
+                      </span>
+                      <input
+                        type="text"
+                        class="form-control"
+                        id="datepicker-trigger"
+                        placeholder="Selecciona un rango..."
+                        :value="formatoFechaCalendario(filtroFecha.fechaInicial,filtroFecha.fechaFinal)"
+                      />
+                    </div>
+                    <AirbnbStyleDatepicker
+                      :trigger-element-id="'datepicker-trigger'"
+                      :mode="'range'"
+                      :fullscreen-mobile="false"
+                      :date-one="filtroFecha.fechaInicial"
+                      :date-two="filtroFecha.fechaFinal"
+                      :end-date="filtroFecha.fechaLimiteCalendario"
+                      @date-one-selected="val => { filtroFecha.fechaInicial = val }"
+                      @date-two-selected="val => { filtroFecha.fechaFinal = val }"
+                      @apply="filtroFecha.buscarFechaDias = -1"
+                    />
+                  </div>
+                </div>
+              </div>
+
+              <div class="btn-group">
+                <a
+                  href="#"
+                  class="ml-3 text-dark"
+                  style="border: 1px solid #d8d8d8;padding: 3px"
+                  @click="borrarFiltro()"
+                  v-if="buscar.length !== 0 || 
+                    filtroFecha.buscarFechaDias > 0 ||
+                    filtroFecha.fechaInicial !== '' ||
+                    clickedFiltro.IDSE !== ''"
+                >Borrar Filtro</a>
+              </div>
+            </div>
+            <transition name="fade" v-if="filtroFecha.verModalBusqueda ">
+              <div class="modal-mask">
+                <div class="modal-wrapper">
+                  <div class="modal-dialog" role="document">
+                    <div class="modal-content">
+                      <div class="modal-header">
+                        <h6>Filtro avanzado de categorías</h6>
+                        <button
+                          type="button"
+                          class="close"
+                          @click="filtroFecha.verModalBusqueda = false;"
+                          aria-label="Cerrar"
+                        >
+                          <span aria-hidden="true">&times;</span>
+                        </button>
+                      </div>
+                      <div class="modal-body">
+                        <div class="form-group row">
+                          <label for="lbl_estantes" class="col-sm-3 col-form-label">Sección</label>
+                          <div class="col-sm-9">
+                            <select
+                              class="custom-select"
+                              v-model="clickedFiltro.IDSE"
+                              id="estanteFiltro"
+                              @change="clickedFiltro.IDCA = ''"
+                            >
+                              <option value>Seleccione una opción</option>
+                              <option
+                                v-for="seccion in secciones"
+                                :key="seccion.IDSE"
+                                :value="seccion.IDSE"
+                              >{{seccion.SECC }}</option>
+                            </select>
+                          </div>
+                        </div>
+                        <div class="form-group row">
+                          <label for="lbl_secciones" class="col-sm-3 col-form-label">Categoría</label>
+                          <div class="col-sm-9">
+                            <select
+                              class="custom-select"
+                              v-model="clickedFiltro.IDCA"
+                              id="seccionFiltro"
+                            >
+                              <option value>Seleccione una categoría</option>
+                              <option
+                                v-for="categoria in categoriasEnModal"
+                                :key="categoria.IDCA"
+                                :value="categoria.IDCA"
+                              >{{categoria.CATE }}</option>
+                            </select>
+                          </div>
+                        </div>
+                        <button
+                          class="btn btn-danger float-right mt-3"
+                          @click="filtroFecha.verModalBusqueda = false;"
+                        >Aplicar</button>
+                      </div>
+                    </div>
+                  </div>
+                </div>
+              </div>
+            </transition>
+            <!-- add seccion modal -->
+            <div
+              class="modal animated bounceInDown fast"
+              id="modalCategoria"
+              tabindex="-1"
+              role="dialog"
+              aria-labelledby="modalCategoria"
+            >
+              <div class="modal-dialog" role="document">
+                <div class="modal-content">
+                  <div class="modal-header">
+                    <div style="width:100%">
+                      <h4 class="float-left">{{ mensajeModal }}</h4>
+                      <uploadImage
+                        class="float-right"
+                        v-on:imagenUpdatedEvent="actualizarImagenes"
+                      />
+                    </div>
+                    <button type="button" class="close" data-dismiss="modal" aria-label="Close">
+                      <span aria-hidden="true">&times;</span>
+                    </button>
+                  </div>
+                  <div class="modal-body">
+                    <div class="form-group">
+                      <label class="font-bold">Nombre</label>
+                      <input
+                        type="text"
+                        v-model="clickedCate.CATE"
+                        class="form-control"
+                        placeholder="Escriba el nombre de la Categoría"
+                        name="categoria"
+                        v-validate="'required|regex:^(?:[A-Za-z0-9 _.]*)$'"
+                        :class="{'input': true, 'is-danger': errors.has('categoria') }"
+                      />
+                      <span
+                        v-show="errors.has('categoria')"
+                        class="invalid-feedback-form"
+                      >{{ errors.first('categoria') }}</span>
+                    </div>
+                    <div class="form-group">
+                      <label class="font-bold">Sección a vincular</label>
+                      <select
+                        class="custom-select"
+                        v-model="clickedCate.IDSE"
+                        name="seccion"
+                        v-validate="'required'"
+                        :class="{'input': true, 'is-danger': errors.has('seccion') }"
+                      >
+                        <option value>Seleccione una opción</option>
+                        <option
+                          v-for="seccion in secciones"
+                          :key="seccion.IDSE"
+                          :value="seccion.IDSE"
+                        >{{seccion.SECC }}</option>
+                      </select>
+                      <span
+                        v-show="errors.has('seccion')"
+                        class="invalid-feedback-form"
+                      >{{ errors.first('seccion') }}</span>
+                    </div>
+                    <label class="typo__label font-bold">Seleccione una imagen</label>
+                    <multiselect
+                      v-model="imagen"
+                      placeholder="Seleccione una imagen de la lista"
+                      label="title"
+                      track-by="IMAG"
+                      :options="imagenesData"
+                      :option-height="104"
+                      :custom-label="({ title, desc }) => `${title}`"
+                      :show-labels="false"
+                    >
+                      <template slot="singleLabel" slot-scope="props">
+                        <img class="option__image" :src="props.option.URL" style="width:100px" />
+                        <span class="option__desc">
+                          <span class="option__title ml-2">{{ props.option.IMAG }}</span>
+                        </span>
+                      </template>
+                      <template slot="option" slot-scope="props">
+                        <img class="option__image" :src="props.option.URL" style="width:100px" />
+                        <span class="option__title ml-2">{{ props.option.IMAG }}</span>
+                      </template>
+                      <span slot="noOptions">Sin elementos para mostrar.</span>
+                    </multiselect>
+                    <span
+                      v-show="imagenInvalid && imagen.IMAG == 'Seleccione una imagen de la lista'"
+                      class="invalid-feedback-form"
+                    >La imagen es un campo obligatorio</span>
+                  </div>
+                  <div class="modal-footer">
+                    <button type="button" class="btn btn-light" data-dismiss="modal">Cerrar</button>
+                    <!-- Buttons save and edit -->
+                    <button
+                      type="button"
+                      v-if="mensajeButtonModal=='Editar'"
+                      @click="editarCategoria()"
+                      class="btn btn-danger"
+                    >{{ mensajeButtonModal }}</button>
+
+                    <button
+                      type="button"
+                      v-if="mensajeButtonModal=='Agregar'"
+                      @click="crearCategoria(clickedCate.CATE,clickedCate.IDSE)"
+                      class="btn btn-danger"
+                    >{{ mensajeButtonModal }}</button>
+                  </div>
+                </div>
+              </div>
+            </div>
+            <!-- Start delete categoría modal -->
+            <div id="deleteCategoria" class="modal animated bounceInDown fast">
+              <div class="modal-dialog">
+                <div class="modal-content">
+                  <form>
+                    <div class="modal-header">
+                      <h4 class="modal-title">Borrar Categoría</h4>
+                      <button
+                        type="button"
+                        class="close"
+                        data-dismiss="modal"
+                        aria-hidden="true"
+                      >&times;</button>
+                    </div>
+                    <div class="modal-body">
+                      <p>
+                        Esta a punto de borrar
+                        <b>'{{ clickedCate.SECC }}'</b>
+                      </p>
+                      <p class="text-danger">Esta acción no se puede deshacer.</p>
+                    </div>
+                    <div class="modal-footer">
+                      <input
+                        type="button"
+                        class="btn btn-default"
+                        data-dismiss="modal"
+                        value="Cerrar"
+                      />
+                      <input
+                        type="submit"
+                        @click="deleteCategoria = false; borrarCategoria()"
+                        class="btn btn-danger"
+                        value="Borrar"
+                        data-dismiss="modal"
+                      />
+                    </div>
+                  </form>
+                </div>
+              </div>
+            </div>
+            <!-- End delete categoría modal -->
+            <div class="table-responsive mt-2" v-if="strError.length == 0">
+              <table class="table no-wrap table-hover">
+                <thead>
+                  <tr>
+                    <th>Imagen</th>
+                    <th>Nombre</th>
+                    <th>Estante vinculado</th>
+                    <th>Sección vinculada</th>
+                    <th>Fecha Creación</th>
+                    <th>Fecha de Modificación</th>
+                    <th>Acciones</th>
+                  </tr>
+                </thead>
+                <draggable
+                  v-model="categorias"
+                  tag="tbody"
+                  v-if="categorias.length !== 0"
+                  :disabled="!ordenarElementos"
+                  @start="drag = true"
+                  @end="drag = false"
+                  @update="moverCategoria"
+                  v-bind="configuracionDragg"
+                  :style="ordenarElementos==true ?'background: aliceblue;':''"
+                >
+                  <tr v-for="categorias in filtroCategorias" :key="categorias.IDCA">
+                    <td>
+                      <div class="profile-img">
+                        <img
+                          v-lazy="categorias.IDIM"
+                          style="max-width:65px"
+                          alt="user"
+                          class="img-thumbnail"
+                        />
+                      </div>
+                    </td>
+                    <td>{{ categorias.CATE }}</td>
+                    <td>
+                      <span class="label label-primary">
+                        <i class="material-icons icon-align">dns</i>
+                        {{ categorias.ESTA }}
+                      </span>
+                    </td>
+                    <td>
+                      <span class="label label-info">
+                        <i class="material-icons icon-align">collections_bookmark</i>
+                        {{ categorias.SECC }}
+                      </span>
+                    </td>
+                    <td>
+                      <span v-if="categorias.FECR !== null ">
+                        <i class="material-icons icon-align" style="color:red">access_time</i>
+                        {{ categorias.FECR | formatDate }}
+                      </span>
+                    </td>
+                    <td>
+                      <span v-if="categorias.FEMO !== null ">
+                        <i class="material-icons icon-align" style="color:red">access_time</i>
+                        {{ categorias.FEMO | formatDate }}
+                      </span>
+                    </td>
+                    <td>
+                      <!-- Botones de acciones -->
+                      <a
+                        href="#modalCategoria"
+                        @click="modalCategoria = true; seleccionarCategoria(categorias,'edit')"
+                        class="edit"
+                        data-toggle="modal"
+                      >
+                        <i
+                          class="material-icons"
+                          data-toggle="tooltip"
+                          title="Editar"
+                          style="color:#f42849"
+                        >&#xE254;</i>
+                      </a>
+
+                      <a
+                        href="#deleteCategoria"
+                        @click="deleteCategoria = true;seleccionarCategoria(categorias,'delete')"
+                        class="delete"
+                        data-toggle="modal"
+                      >
+                        <i
+                          class="material-icons"
+                          data-toggle="tooltip"
+                          title="Borrar"
+                          style="color:#f42849"
+                        >&#xE872;</i>
+                      </a>
+                    </td>
+                  </tr>
+                </draggable>
+              </table>
+              <!-- Pagination -->
+              <b-pagination
+                v-show="verPaginador && !loading && filtroCategorias.length !== 0"
+                v-model="currentPage"
+                :total-rows="categorias.length"
+                :per-page="perPage"
+                align="center"
+              ></b-pagination>
+
+              <div v-if="loading" class="text-center">
+                <p>Cargando...</p>
+                <span
+                  class="spinner-border spinner-grow-sm"
+                  role="status"
+                  aria-hidden="true"
+                  style="color:red"
+                ></span>
+              </div>
+              <div class="text-center">
+                <p
+                  class="mt-3"
+                  v-if="categorias.length == 0 &&
+                   !loading"
+                >No se encontraron categorías para mostrar.</p>
+                <p
+                  class="mt-3"
+                  v-show="categorias.length !== 0 && 
+                  filtroCategorias.length === 0 && 
+                  (filtroFecha.buscarFechaDias !== 0 || buscar.length !== 0 || clickedFiltro.IDSE !== '') &&
+                  !loading"
+                >No se encontró ninguna categoría en su búsqueda.</p>
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+<style src="vue-multiselect/dist/vue-multiselect.min.css"></style>
+<script>
+import uploadImage from "@/components/UploadImage.vue";
+import Multiselect from "vue-multiselect";
+import moment from "moment";
+import draggable from "vuedraggable";
+import { BPagination, BTooltip } from "bootstrap-vue";
+import { token } from "../../_mixin/user_mixin.js";
+
+export default {
+  name: "CategoriasAdmin",
+  mixins: [token],
+  components: {
+    BTooltip,
+    uploadImage,
+    Multiselect,
+    draggable,
+    BPagination
+  },
+  data: () => ({
+    deleteCategoria: false,
+    mensajeModal: "Agregar categoría",
+    mensajeButtonModal: "Agregar",
+    clickedCate: {},
+    imagen: { IMAG: "Seleccione una imagen de la lista" },
+    buscar: "",
+    perPage: 25,
+    currentPage: 1,
+    verPaginador: true,
+    updateOrder: false,
+    arrOrder: {},
+    isLoading: false,
+    imagenInvalid: false,
+    ordenarElementos: false
+  }),
+  created: async function() {
+    await this.getCategorias();
+    await this.getSecciones();
+    await this.getImages("CA");
+  },
+  methods: {
+    editarCategoria: function() {
+      // Verificar que tengamos imagen seleccionada
+      this.$validator.validateAll().then(errors => {
+        if (errors) {
+          if (this.imagen.IMAG != "Seleccione una imagen de la lista") {
+            let img = this.imagen.URL;
+            this.clickedCate.IDIM = img;
+          }
+          var formData = this.toFormData(this.clickedCate);
+          this.$http
+            .post(
+              this.url_api + "/categorias/update",
+              formData,
+              this.config_header
+            )
+            .then(response => {
+              this.clickedCate = {};
+              if (!response.data.response) {
+                this.$toasted.show(response.data.message, this.$toast_error);
+              } else {
+                this.$toasted.show(response.data.message, this.$toast_success);
+              }
+              this.getCategorias();
+              this.removeModal("modalCategoria");
+            })
+            .catch(error => {
+              this.strError =
+                "Se ha producido un error al actualizar la categoría.";
+            });
+        }
+      });
+    },
+    seleccionarCategoria: function(categoria, accion) {
+      this.clickedCate = {};
+      this.imagen = { IMAG: "Seleccione una imagen de la lista" };
+      this.imagenInvalid = false;
+      // Remove Menssages error
+      this.$nextTick().then(() => {
+        this.$validator.reset();
+        this.errors.clear();
+      });
+      if (accion == "new") {
+        this.clickedCate = { IDSE: "" };
+        this.mensajeModal = "Agregar Categoría";
+        this.mensajeButtonModal = "Agregar";
+      }
+      // Si estamos editando
+      if (accion == "edit") {
+        var datos = {
+          IDCA: categoria.IDCA,
+          IDSE: categoria.IDSE,
+          CATE: categoria.CATE,
+          IDIM: categoria.IDIM,
+          USMO: this.$store.getters.user.id
+        };
+        this.clickedCate = datos;
+        this.mensajeButtonModal = "Editar";
+        this.mensajeModal = "Editar Categoría";
+      }
+      if (accion == "delete") {
+        this.clickedCate = categoria;
+      }
+    },
+    crearCategoria: function(cate, idse) {
+      // Verificar que tengamos imagen seleccionada
+      this.imagenInvalid = false;
+      if (this.imagen.IMAG == "Seleccione una imagen de la lista") {
+        this.imagenInvalid = true;
+      }
+      this.$validator.validateAll().then(result => {
+        if (result && !this.imagenInvalid) {
+          let imagenUrl = this.imagen.URL;
+          var datos = {
+            CATE: cate,
+            IDSE: idse,
+            IDIM: imagenUrl,
+            USCR: this.$store.getters.user.id
+          };
+          var formData = this.toFormData(datos);
+          this.$http
+            .post(this.url_api + "/categorias", formData, this.config_header)
+            .then(response => {
+              if (!response.data.response) {
+                this.$toasted.show(response.data.message, this.$toast_error);
+              } else {
+                this.$toasted.show(response.data.message, this.$toast_success);
+              }
+              this.getCategorias();
+              this.removeModal("modalCategoria");
+            })
+            .catch(error => {
+              this.strError = "Se ha producido un error al crear la categoria.";
+            });
+        } //end Result
+      }); // end validator
+    },
+    borrarCategoria: function() {
+      var formData = this.toFormData(this.clickedCate);
+      this.$http
+        .post(this.url_api + "/categorias/delete", formData, this.config_header)
+        .then(response => {
+          if (!response.data.response) {
+            this.$toasted.show(response.data.message, this.$toast_error);
+          } else {
+            this.$toasted.show(response.data.message, this.$toast_success);
+          }
+          this.getCategorias();
+        })
+        .catch(error => {
+          this.strError = "Se ha producido un error al borrar la categoría.";
+        });
+    },
+    paginador: function(categorias) {
+      // Calcular páginador
+      const indiceInicio = (this.currentPage - 1) * this.perPage;
+      const indiceFinal =
+        indiceInicio + this.perPage > categorias.length
+          ? categorias.length
+          : indiceInicio + this.perPage;
+      return categorias.slice(indiceInicio, indiceFinal);
+    },
+    moverCategoria: function() {
+      this.updateOrder = true;
+      var obj = this.categorias;
+      var bodyFormData = new FormData();
+      obj.forEach((element, i) => {
+        i++;
+        bodyFormData.append(element.IDCA, i);
+      });
+      this.arrOrder = bodyFormData;
+    },
+    actualizarOrdenCategorias: function() {
+      var formData = this.arrOrder;
+      this.isLoading = true;
+      this.$http
+        .post(this.url_api + "/categorias/order", formData, this.config_header)
+        .then(response => {
+          this.isLoading = false;
+          this.updateOrder = false;
+          if (!response.data.response) {
+            this.$toasted.show(response.data.message, this.$toast_error);
+          } else {
+            this.$toasted.show(response.data.message, this.$toast_success);
+          }
+          this.getCategorias();
+        })
+        .catch(error => {
+          this.strError = "Se ha producido un error al ordenar la categoría.";
+        });
+    },
+    actualizarImagenes: function(action) {
+      if (action) {
+        this.getImages("CA");
+      }
+    },
+    borrarFiltro() {
+      this.filtroFecha.fechaInicial = "";
+      this.filtroFecha.fechaFinal = "";
+      this.filtroFecha.buscarFechaDias = 0;
+      this.buscar = "";
+      this.filtroFecha.filtroSeleccionado = "Cualquier fecha";
+      this.clickedFiltro.IDSE = "";
+      this.clickedFiltro.IDCA = "";
+    },
+    comenzarAMoverElementos(ordenandoElementos) {
+      this.ordenarElementos = ordenandoElementos;
+      if (this.ordenarElementos) {
+        this.perPage = this.categorias.length;
+        this.paginador(this.categorias);
+      } else {
+        this.perPage = 25;
+        this.paginador(this.categorias);
+      }
+    }
+  },
+  computed: {
+    categoriasEnModal() {
+      let idSeccion = this.clickedFiltro.IDSE;
+      return this.categorias.filter(categoria => categoria.IDSE === idSeccion);
+    },
+    filtroCategorias() {
+      var categoriasFiltradas = [];
+      var categoriasFiltradasPorInput = [];
+      var fechaAhora = moment().format("YYYY-MM-DD");
+
+      //Filtro de Modal Búsqueda Avanzada
+      var idSeccion = this.clickedFiltro.IDSE;
+      var idCategoria = this.clickedFiltro.IDCA;
+
+      var categoriasModal = this.categorias.filter(categoria => {
+        if (idSeccion !== "" && idCategoria === "") {
+          return categoria.IDSE === idSeccion;
+        }
+        if (idSeccion !== "" && idCategoria !== "") {
+          return categoria.IDSE === idSeccion && categoria.IDCA === idCategoria;
+        }
+        return categoria;
+      });
+
+      // filtra por lo que escriba el usuario en la caja de texto
+      categoriasFiltradasPorInput = categoriasModal.filter(categoria => {
+        return categoria.CATE.toLowerCase().match(this.buscar.toLowerCase());
+      });
+
+      switch (this.filtroFecha.buscarFechaDias) {
+        case -1:
+          // Intervalo personalizado
+          categoriasFiltradas = categoriasFiltradasPorInput.filter(
+            categoria => {
+              let fechaFormato = moment(categoria.FECR, "DD-MMM-YY").format(
+                "YYYY-MM-DD"
+              );
+
+              if (
+                !(
+                  moment(fechaFormato).isBefore(
+                    this.filtroFecha.fechaInicial
+                  ) || moment(fechaFormato).isAfter(this.filtroFecha.fechaFinal)
+                )
+              ) {
+                return categoria;
+              }
+            }
+          );
+          break;
+        case 1:
+        case 7:
+          // Filtrar ultimos 7 días y 24 hrs
+          var dias = moment()
+            .subtract(this.filtroFecha.buscarFechaDias, "days")
+            .format("YYYY-MM-DD");
+
+          categoriasFiltradas = categoriasFiltradasPorInput.filter(
+            categoria => {
+              let fechaFormato = moment(categoria.FECR, "DD-MMM-YY").format(
+                "YYYY-MM-DD"
+              );
+              if (
+                moment(fechaFormato).isBetween(dias, fechaAhora) ||
+                moment(fechaFormato).isSame(fechaAhora)
+              ) {
+                return categoria;
+              }
+            }
+          );
+          break;
+        default:
+          categoriasFiltradas = categoriasFiltradasPorInput;
+          break;
+      }
+      return this.paginador(categoriasFiltradas);
+    }
+  }
+};
+</script>
+<style scoped>
+.dropdown-item {
+  padding: 0px 7px 2px 40px;
+}
+.page-titles .breadcrumb .breadcrumb-item + .breadcrumb-item::before {
+  content: "/";
+}
+.uploading-image {
+  display: flex;
+}
+.sortable-chosen {
+  background: #c2dbff;
+}
+.table-responsive {
+  box-shadow: 0px 0px 7px 2px #f2f2f2;
+  padding: 15px;
+}
+.spinner-grow-sm {
+  width: 1.6rem;
+  height: 1.6rem;
+}
+
+.dropdown-toggle {
+  border: 1px solid#d8d8d8;
+  padding: 3px;
+}
+.btn-group label {
+  color: #6c757d !important;
+}
+
+/* Modal filtro */
+.modal-mask {
+  position: fixed;
+  z-index: 9998;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  background-color: rgba(0, 0, 0, 0.1);
+  display: table;
+  transition: opacity 0.3s ease;
+}
+.modal-wrapper {
+  display: table-cell;
+  vertical-align: middle;
+}
+.modal-dialog {
+  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2);
+}
+</style>

+ 873 - 0
src/views/admin/EstantesAdmin.vue

@@ -0,0 +1,873 @@
+<template>
+  <div>
+    <ol class="breadcrumb mb-2">
+      <li class="breadcrumb-item">
+        <router-link :to="{name:'homeAdmin'}">Inicio</router-link>
+      </li>
+      <li class="breadcrumb-item active">Estantes</li>
+    </ol>
+    <div class="row">
+      <div class="col-12">
+        <div class="card">
+          <div class="card-body">
+            <!-- Manejo de error -->
+            <div class="alert alert-danger alert-rounded" v-if="strError.length != 0">
+              <button type="button" class="close" data-dismiss="alert" aria-label="Close"></button>
+              <i class="material-icons icon-align">report_problem</i>
+              {{ strError }}
+            </div>
+            <h4 class="card-title">Estantes</h4>
+            <span class="text-dark">En esta página se pueden visualizar los estantes disponibles.</span>
+
+            <form class="form-material mt-2">
+              <input
+                type="text"
+                class="form-control col-sm-3 mb-4 float-left"
+                placeholder="Nombre del estante"
+                v-model="buscar"
+              />
+            </form>
+            <div class="btn-group">
+              <b-tooltip target="busqueda-avanzada" placement="bottom">
+                <span>Filtro de Fecha</span>
+              </b-tooltip>
+
+              <a
+                href="#"
+                class="dropdown-toggle pl-2 text-dark"
+                data-toggle="dropdown"
+                aria-haspopup="true"
+                aria-expanded="false"
+                id="busqueda-avanzada"
+                :class="[filtroFecha.buscarFechaDias !== 0 ? 'font-bold':'']"
+              >{{ filtroFecha.filtroSeleccionado }}</a>
+
+              <div class="dropdown-menu">
+                <a
+                  class="dropdown-item"
+                  href="#"
+                  @click.prevent="filtroFecha.buscarFechaDias = 0;
+                  filtroFecha.filtroSeleccionado = 'Cualquier fecha'"
+                >
+                  <i
+                    class="material-icons align-middle"
+                    style="font-weight:bold;margin-left: -30px;"
+                    v-show="filtroFecha.buscarFechaDias == 0"
+                  >check</i>
+                  &nbsp; Cualquier fecha
+                </a>
+                <a
+                  class="dropdown-item"
+                  href="#"
+                  @click="filtroFecha.buscarFechaDias = 1;
+                filtroFecha.filtroSeleccionado = 'Últimas 24 hrs'"
+                >
+                  <i
+                    class="material-icons align-middle"
+                    style="font-weight:bold;margin-left: -30px;"
+                    v-show="filtroFecha.buscarFechaDias == 1"
+                  >check</i>
+                  &nbsp; Últimas 24 hrs
+                </a>
+                <a
+                  class="dropdown-item"
+                  href="#"
+                  @click.prevent="filtroFecha.buscarFechaDias = 7
+                  filtroFecha.filtroSeleccionado = 'Última semana'"
+                >
+                  <i
+                    class="material-icons align-middle"
+                    style="font-weight:bold;margin-left: -30px;"
+                    v-show="filtroFecha.buscarFechaDias == 7"
+                  >check</i>
+                  &nbsp; Última semana
+                </a>
+                <div class="dropdown-divider"></div>
+                <a
+                  class="dropdown-item"
+                  href="#"
+                  @click.prevent="filtroFecha.buscarFechaDias=-1;
+                  filtroFecha.filtroSeleccionado = 'Intervalo personalizado'"
+                >
+                  <i
+                    class="material-icons align-middle"
+                    style="font-weight:bold;margin-left: -30px;"
+                    v-show="filtroFecha.buscarFechaDias==-1"
+                  >check</i>
+                  &nbsp; Intervalo personalizado
+                </a>
+              </div>
+            </div>
+
+            <div class="btn-group">
+              <div class="form-material" v-show="filtroFecha.buscarFechaDias == -1">
+                <!-- Datepicker Show -->
+                <div class="datepicker-trigger">
+                  <div class="input-group">
+                    <span
+                      class="input-group-text"
+                      style="background-color: transparent; border: none;"
+                    >
+                      <i class="material-icons align-middle">date_range</i>
+                    </span>
+                    <input
+                      type="text"
+                      class="form-control"
+                      id="datepicker-trigger"
+                      placeholder="Selecciona un rango..."
+                      :value="formatoFechaCalendario(filtroFecha.fechaInicial,filtroFecha.fechaFinal)"
+                    />
+                  </div>
+                  <AirbnbStyleDatepicker
+                    :trigger-element-id="'datepicker-trigger'"
+                    :mode="'range'"
+                    :fullscreen-mobile="false"
+                    :date-one="filtroFecha.fechaInicial"
+                    :date-two="filtroFecha.fechaFinal"
+                    :end-date="filtroFecha.fechaLimiteCalendario"
+                    @date-one-selected="val => { filtroFecha.fechaInicial = val }"
+                    @date-two-selected="val => { filtroFecha.fechaFinal = val }"
+                    @apply="filtroFecha.buscarFechaDias = -1"
+                  />
+                </div>
+              </div>
+            </div>
+
+            <div class="btn-group">
+              <a
+                href="#"
+                class="ml-3 text-secondary"
+                @click="borrarFiltro()"
+                v-if="buscar.length !== 0 || 
+              filtroFecha.buscarFechaDias > 0 ||
+              filtroFecha.fechaInicial !== '' "
+              >Borrar Filtro</a>
+            </div>
+
+            <a
+            class="ml-2"                 
+              href="#"
+              @click.prevent="comenzarAMoverElementos(ordenarElementos = !ordenarElementos)"
+            >
+              <b-tooltip target="filtro-ordenar" placement="bottom">
+                <span >Ordenar</span>
+              </b-tooltip>
+              <i id="filtro-ordenar" class="material-icons icon-align" :class="[ordenarElementos ? 'text-primary':'text-danger']">filter_list</i>
+            </a>
+
+            <a
+              @click.prevent="$root.$emit('bv::show::modal', 'modalClonarEstante', '')"
+              href="#"
+              class="text-dark ml-3"
+            >
+              <i class="material-icons text-danger icon-align">dynamic_feed</i>
+              Clonar Estante
+            </a>
+
+            <!-- Botones de ordenamiento -->
+            <button
+              v-show="updateOrder"
+              class="mr-2 ml-2 btn btn-sm  btn-outline-info"
+              @click="getEstantes();updateOrder = false"
+              :disabled="isLoading"
+            >
+              <i class="material-icons icon-align">refresh</i> Deshacer
+            </button>
+            <button
+              v-show="updateOrder"
+              class="mr-2 btn btn-sm  btn-outline-success"
+              @click="actualizarOrdenEstantes()"
+              :disabled="isLoading"
+            >
+              <div class="save" v-if="!isLoading">
+                <i class="material-icons icon-align">save</i>
+                Guardar Orden
+              </div>
+              <div class="loading" v-if="isLoading">
+                <span class="spinner-border spinner-grow-sm" role="status" aria-hidden="true"></span> Cargando...
+              </div>
+            </button>
+
+            <button
+              class="btn float-right  btn-outline-danger"
+              data-toggle="modal"
+              data-target="#modalEstante"
+              @click="seleccionarEstante('','new')"
+              v-if="strError.length == 0"
+            >
+              <i class="material-icons icon-align">add_circle</i> Crear estante
+            </button>
+
+            <!-- modal clonar estante -->
+            <b-modal
+              content-class="shadow"
+              id="modalClonarEstante"
+              title="Clonar estante"
+              size="md"
+              ok-only
+            >
+              <div class="form-group">
+                <label class="font-bold">Nombre</label>
+                <input
+                  type="text"
+                  class="form-control"
+                  v-validate="'required|regex:^(?:[A-Za-z0-9 _.]*)$'"
+                  :class="{'input': true, 'is-danger': errors.has('nombre') }"
+                  name="nombre"
+                  v-model="estanteSeleccionado"
+                  placeholder="Escriba el nombre del nuevo estante"
+                />
+                <span
+                  v-show="errors.has('nombre')"
+                  class="invalid-feedback-form"
+                >{{ errors.first('nombre') }}</span>
+              </div>
+              <div class="form-group">
+                <label class="font-bold">Estante espejo</label>
+                <select
+                  class="custom-select"
+                  v-model="estanteEspejo"
+                  name="estanteEspejo"
+                  v-validate="'required'"
+                  :class="{'input': true, 'is-danger': errors.has('estanteEspejo') }"
+                >
+                  <option value selected>Seleccione una opción</option>
+                  <option
+                    v-for="estante in estantes"
+                    :key="estante.IDES"
+                    :value="estante.IDES"
+                  >{{estante.ESTA }}</option>
+                </select>
+                <span
+                  v-show="errors.has('estanteEspejo')"
+                  class="invalid-feedback-form"
+                >{{ errors.first('estanteEspejo') }}</span>
+              </div>
+
+              <template v-slot:modal-footer="{  hide }">
+                <div class="w-100">
+                  <button
+                    id
+                    type="button"
+                    class="btn btn-danger float-right"
+                    @click="clonarEstante()"
+                    :disabled=" cargando ? true: false"
+                  >Guardar</button>
+                  <button
+                    id
+                    type="button"
+                    class="btn btn-secondary mr-2 float-right"
+                    @click="hide('hide')"
+                  >Cancelar</button>
+                </div>
+              </template>
+            </b-modal>
+            <!-- end modal -->
+            <!-- add estante modal -->
+            <div
+              class="modal animated bounceInDown fast"
+              id="modalEstante"
+              tabindex="-1"
+              role="dialog"
+              aria-labelledby="modalEstante"
+            >
+              <div class="modal-dialog" role="document">
+                <div class="modal-content">
+                  <div class="modal-header">
+                    <div style="width:100%">
+                      <h4 class="float-left">{{ mensajeModal }}</h4>
+                      <uploadImage
+                        class="float-right"
+                        v-on:imagenUpdatedEvent="actualizarImagenes"
+                      />
+                    </div>
+                    <button type="button" class="close" data-dismiss="modal" aria-label="Close">
+                      <span aria-hidden="true">&times;</span>
+                    </button>
+                  </div>
+                  <div class="modal-body">
+                    <div class="form-group">
+                      <label class="font-bold">Nombre</label>
+                      <input
+                        type="text"
+                        class="form-control"
+                        v-validate="'required|regex:^(?:[A-Za-z0-9 _.]*)$'"
+                        :class="{'input': true, 'is-danger': errors.has('estante') }"
+                        name="estante"
+                        v-model="clickedEstante.ESTA"
+                        placeholder="Escriba el nombre del estante"
+                      />
+                      <span
+                        v-show="errors.has('estante')"
+                        class="invalid-feedback-form"
+                      >{{ errors.first('estante') }}</span>
+                    </div>
+                    <label class="typo__label font-bold">Seleccione una imagen</label>
+                    <multiselect
+                      v-model="imagen"
+                      placeholder="Seleccione una imagen de la lista"
+                      label="title"
+                      track-by="IMAG"
+                      :options="imagenesData"
+                      :option-height="104"
+                      :custom-label="({ title, desc }) =>  `${title}` "
+                      :show-labels="false"
+                    >
+                      <template slot="singleLabel" slot-scope="props">
+                        <img class="option__image" :src="props.option.URL" style="width:100px" />
+                        <span class="option__desc">
+                          <span class="option__title ml-2">{{ props.option.IMAG }}</span>
+                        </span>
+                      </template>
+                      <template slot="option" slot-scope="props">
+                        <img class="option__image" :src="props.option.URL" style="width:100px" />
+                        <span class="option__title ml-2">{{ props.option.IMAG }}</span>
+                      </template>
+                      <span slot="noOptions">Sin elementos para mostrar.</span>
+                    </multiselect>
+                    <span
+                      v-show="imagenInvalid && imagen.IMAG == 'Seleccione una imagen de la lista'"
+                      class="invalid-feedback-form"
+                    >La imagen es un campo obligatorio</span>
+                    <!-- <pre class="language-json"><code>{{ imagen  }}</code></pre> -->
+                  </div>
+                  <div class="modal-footer">
+                    <button type="button" class="btn btn-light" data-dismiss="modal">Cerrar</button>
+                    <!-- Buttons save and edit -->
+                    <button
+                      type="button"
+                      v-if="mensajeButtonModal=='Editar'"
+                      @click="editarEstante()"
+                      class="btn btn-danger"
+                    >{{ mensajeButtonModal }}</button>
+
+                    <button
+                      type="button"
+                      v-if="mensajeButtonModal=='Agregar'"
+                      v-on:keyup.13="crearEstante(clickedEstante.ESTA,clickedEstante.IDIM)"
+                      @click="crearEstante(clickedEstante.ESTA,clickedEstante.IDIM)"
+                      class="btn btn-danger"
+                    >{{ mensajeButtonModal }}</button>
+                  </div>
+                </div>
+              </div>
+            </div>
+            <!-- end modal -->
+            <!-- Start delete Estante -->
+            <div id="deleteEstante" class="modal animated bounceInDown fast">
+              <div class="modal-dialog">
+                <div class="modal-content">
+                  <form>
+                    <div class="modal-header">
+                      <h4 class="modal-title">Borrar estante</h4>
+                      <button
+                        type="button"
+                        class="close"
+                        data-dismiss="modal"
+                        aria-hidden="true"
+                      >&times;</button>
+                    </div>
+                    <div class="modal-body">
+                      <p>
+                        Esta a punto de borrar
+                        <b>'{{ clickedEstante.ESTA }}'</b>
+                      </p>
+                      <p class="text-danger">Esta acción no se puede deshacer.</p>
+                    </div>
+                    <div class="modal-footer">
+                      <input
+                        type="button"
+                        class="btn btn-default"
+                        data-dismiss="modal"
+                        value="Cerrar"
+                      />
+                      <input
+                        type="submit"
+                        @click="borrarEstante()"
+                        class="btn btn-danger"
+                        value="Borrar"
+                        data-dismiss="modal"
+                      />
+                    </div>
+                  </form>
+                </div>
+              </div>
+            </div>
+            <!-- End delete Estante -->
+            <div class="table-responsive mt-3" v-if="strError.length === 0">
+              <table class="table no-wrap table-hover">
+                <thead>
+                  <tr>
+                    <th>Imagen</th>
+                    <th>Nombre</th>
+                    <th>Fecha Creación</th>
+                    <th>Fecha de Modificación</th>
+                    <th>Acciones</th>
+                  </tr>
+                </thead>
+                <draggable
+                  v-model="estantes"
+                  tag="tbody"
+                  v-if="estantes.length != 0"
+                  @start="drag=true"
+                  @end="drag = false"
+                  @update="moverEstantes"
+                  v-bind="configuracionDragg"
+                  :disabled="!ordenarElementos"
+                  :style="ordenarElementos==true ?'background: aliceblue;':''"
+                >
+                  <tr v-for="estante in filtroEstantes" :key="estante.IDES">
+                    <td>
+                      <div class="profile-img">
+                        <img
+                          v-lazy="estante.IDIM"
+                          :alt="estante.ESTA"
+                          style="max-width:65px;"
+                          class="img-thumbnail"
+                        />
+                      </div>
+                    </td>
+                    <td>{{ estante.ESTA }}</td>
+                    <td>
+                      <span v-if="estante.FECR !== null ">
+                        <i class="material-icons icon-align" style="color:red">access_time</i>
+                        {{ estante.FECR | formatDate }}
+                      </span>
+                    </td>
+                    <td>
+                      <span v-if="estante.FEMO !== null ">
+                        <i class="material-icons icon-align" style="color:red">access_time</i>
+                        {{ estante.FEMO | formatDate }}
+                      </span>
+                    </td>
+
+                    <td>
+                      <!-- Botones de acciones -->
+                      <a
+                        href="#modalEstante"
+                        @click="modalEstante = true; seleccionarEstante(estante,'edit')"
+                        class="edit"
+                        data-toggle="modal"
+                      >
+                        <i
+                          class="material-icons"
+                          data-toggle="tooltip"
+                          title="Editar"
+                          style="color:#f42849"
+                        >&#xE254;</i>
+                      </a>
+
+                      <a
+                        href="#deleteEstante"
+                        @click="seleccionarEstante(estante,'delete')"
+                        class="delete"
+                        data-toggle="modal"
+                      >
+                        <i
+                          class="material-icons"
+                          data-toggle="tooltip"
+                          title="Borrar"
+                          style="color:#f42849"
+                        >&#xE872;</i>
+                      </a>
+                    </td>
+                  </tr>
+                </draggable>
+              </table>
+              <!-- Pagination -->
+              <b-pagination
+                v-show="verPaginador && !loading && filtroEstantes.length !== 0"
+                v-model="currentPage"
+                :total-rows="estantes.length"
+                :per-page="perPage"
+                align="center"
+              ></b-pagination>
+              
+              <div v-if="loading" class="text-center">
+                <p>Cargando...</p>
+                <span
+                  class="spinner-border spinner-grow-sm"
+                  role="status"
+                  aria-hidden="true"
+                  style="color:red"
+                ></span>
+              </div>
+              <div class="text-center">
+                <p
+                  class="mt-3"
+                  v-if="estantes.length == 0 && !loading"
+                >No se encontraron estantes para mostrar.</p>
+                <p
+                  class="mt-3"
+                  v-show="estantes.length !== 0 && 
+                  filtroEstantes.length === 0 && 
+                  (filtroFecha.buscarFechaDias !== 0 || buscar.length !== 0) &&
+                  !loading"
+                >No se encontró ningún estante en su búsqueda.</p>
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+    <vueTopprogress ref="topProgress" color="#ed022c" errorColor="#ffbc34" />
+    </div>
+  </div>
+</template>
+<style src="vue-multiselect/dist/vue-multiselect.min.css"></style>
+<script>
+import { vueTopprogress } from "vue-top-progress";
+import uploadImage from "@/components/UploadImage.vue";
+import Multiselect from "vue-multiselect";
+import moment from "moment";
+import draggable from "vuedraggable";
+import { BPagination,BTooltip,BModal } from "bootstrap-vue";
+import { token } from "../../_mixin/user_mixin.js";
+
+export default {
+  name: "EstantesAdmin",
+  mixins: [token],
+  components: {
+    uploadImage,
+    Multiselect,
+    draggable,
+    BPagination,
+    BTooltip,
+    BModal
+  },
+  data: () => ({
+    estanteSeleccionado: "",
+    estanteEspejo: "",
+    cargando: false,
+    modalEstante: false,
+    mensajeModal: "Agregar estante",
+    mensajeButtonModal: "Agregar",
+    clickedEstante: {},
+    imagen: { IMAG: "Seleccione una imagen de la lista" },
+    buscar: "",
+    perPage: 25,
+    currentPage: 1,
+    verPaginador: true,
+    updateOrder: false,
+    arrOrder: {},
+    isLoading: false,
+    imagenInvalid: false,
+    ordenarElementos: false
+  }),
+  created: function() {
+    this.getEstantes();
+    this.getImages("ES");
+  },
+  methods: {
+    editarEstante: function() {
+      // Verificar que tengamos imagen seleccionada
+      this.$validator.validateAll().then(errors => {
+        if (errors) {
+          if (this.imagen.IMAG != "Seleccione una imagen de la lista") {
+            let img = this.imagen.URL;
+            this.clickedEstante.IDIM = img;
+          }
+          var formData = this.toFormData(this.clickedEstante);
+          this.$http
+            .post(
+              this.url_api + "/estantes/update",
+              formData,
+              this.config_header
+            )
+            .then(response => {
+              this.clickedEstante = {};
+              if (!response.data.response) {
+                this.$toasted.show(response.data.message, this.$toast_error);
+              } else {
+                this.$toasted.show(response.data.message, this.$toast_success);
+              }
+              this.getEstantes();
+              this.removeModal("modalEstante");
+            })
+            .catch(error => {
+              this.strError =
+                "Se ha producido un error al actualizar el estante.";
+            });
+        }
+      });
+    },
+    seleccionarEstante: function(estante, accion) {
+      this.clickedEstante = {};
+      this.imagen = { IMAG: "Seleccione una imagen de la lista" };
+      this.imagenInvalid = false;
+      // Remove Menssages error
+      this.$nextTick().then(() => {
+        this.$validator.reset();
+        this.errors.clear();
+      });
+      if (accion == "new") {
+        this.mensajeModal = "Agregar estante";
+        this.mensajeButtonModal = "Agregar";
+      }
+      // Si estamos editando
+      if (accion == "edit") {
+        var datos = {
+          IDES: estante.IDES,
+          ESTA: estante.ESTA,
+          IDIM: estante.IDIM,
+          USMO: this.$store.getters.user.id
+        };
+        this.clickedEstante = datos;
+        this.mensajeButtonModal = "Editar";
+        this.mensajeModal = "Editar Estante";
+      }
+      if (accion == "delete") {
+        this.clickedEstante = estante;
+      }
+    },
+    crearEstante: function(esta, idim) {
+      // Verificar que tengamos imagen seleccionada
+      this.imagenInvalid = false;
+      if (this.imagen.IMAG == "Seleccione una imagen de la lista") {
+        this.imagenInvalid = true;
+      }
+      this.$validator.validateAll().then(result => {
+        if (result && !this.imagenInvalid) {
+          let imagenUrl = this.imagen.URL;
+          var datos = {
+            ESTA: esta,
+            IDIM: imagenUrl,
+            USCR: this.$store.getters.user.id
+          };
+          var formData = this.toFormData(datos);
+          this.$http
+            .post(this.url_api + "/estantes", formData, this.config_header)
+            .then(response => {
+              if (!response.data.response) {
+                this.$toasted.show(response.data.message, this.$toast_error);
+              } else {
+                this.$toasted.show(response.data.message, this.$toast_success);
+              }
+              this.getEstantes();
+              this.removeModal("modalEstante");
+            })
+            .catch(error => {
+              this.strError = "Se ha producido un error al crear el estante.";
+            });
+        } //end Result
+      }); // end validator
+    },
+    borrarEstante: function() {
+      var formData = this.toFormData(this.clickedEstante);
+      this.$http
+        .post(this.url_api + "/estantes/delete", formData, this.config_header)
+        .then(response => {
+          if (!response.data.response) {
+            this.$toasted.show(response.data.message, this.$toast_error);
+          } else {
+            this.$toasted.show(response.data.message, this.$toast_success);
+          }
+          this.getEstantes();
+        })
+        .catch(error => {
+          this.strError = "Se ha producido un error al borrar el estante.";
+        });
+    },
+    paginador: function(estantes) {
+      // Calcular páginador
+      const indiceInicio = (this.currentPage - 1) * this.perPage;
+      const indiceFinal =
+        indiceInicio + this.perPage > estantes.length
+          ? estantes.length
+          : indiceInicio + this.perPage;
+      return estantes.slice(indiceInicio, indiceFinal);
+    },
+    moverEstantes: function() {
+      this.updateOrder = true;
+      var obj = this.estantes;
+      var bodyFormData = new FormData();
+      obj.forEach((element, i) => {
+        i++;
+        bodyFormData.append(element.IDES, i);
+      });
+      this.arrOrder = bodyFormData;
+    },
+    actualizarOrdenEstantes: function() {
+      var formData = this.arrOrder;
+      this.isLoading = true;
+      this.$http
+        .post(this.url_api + "/estantes/order", formData, this.config_header)
+        .then(response => {
+          this.isLoading = false;
+          this.updateOrder = false;
+          if (!response.data.response) {
+            this.$toasted.show(response.data.message, this.$toast_error);
+          } else {
+            this.$toasted.show(response.data.message, this.$toast_success);
+          }
+          this.getEstantes();
+        })
+        .catch(error => {
+          this.strError = "Se ha producido un error al ordenar el estante.";
+        });
+    },
+    actualizarImagenes: function(action) {
+      if (action) {
+        this.getImages("ES");
+      }
+    },
+    borrarFiltro() {
+      this.filtroFecha.fechaInicial = "";
+      this.filtroFecha.fechaFinal = "";
+      this.filtroFecha.buscarFechaDias = 0;
+      this.buscar = "";
+      this.filtroFecha.filtroSeleccionado = "Cualquier fecha";
+    },
+    comenzarAMoverElementos(ordenandoElementos) {
+      this.ordenarElementos = ordenandoElementos;
+      if (this.ordenarElementos) {
+        this.perPage = this.estantes.length;
+        this.paginador(this.estantes);
+      } else {
+        this.perPage = 25;
+        this.paginador(this.estantes);
+      }
+    },
+    async clonarEstante() {
+      const validateNombre = await this.$validator.validate('nombre');
+      const validateEspejo = await this.$validator.validate('estanteEspejo');
+      
+      if ( validateNombre && validateEspejo ) {
+
+        // lanzar Warning
+        const alert = await this.$swal({
+          position: 'center',
+          title: "¡Cuidado!",
+          text: "Esta a punto de clonar un Estante, esta acción no se puede regresar una vez ejecutada.",
+          type: "warning",
+          confirmButtonText: "Aceptar",
+          cancelButtonText: "Cancelar",
+          showCloseButton: true,
+          showCancelButton: true,
+        });
+        
+        if(!alert.value){
+            return;
+        }
+
+        this.$refs.topProgress.start();
+        this.cargando = true;
+
+        let datos = {
+          nombre: this.estanteSeleccionado,
+          espejo: this.estanteEspejo,
+          uscr: this.$store.getters.user.id
+        };
+
+        const response = await this.$http.post(
+          this.url_api + "/estante/clonar",
+          datos,
+          this.config_header
+        );
+
+        try {
+          
+          if (!response.data.response) {
+            throw new Error(response.data.message);
+          }
+
+          this.$toasted.show(response.data.message, this.$toast_success);
+          this.$refs.topProgress.done();
+          this.estanteEspejo = "";
+          this.estanteSeleccionado = "";
+          this.getEstantes();
+        } catch (error) {
+          this.$toasted.show(error, this.$toast_error);
+          this.$refs.topProgress.fail();
+        }
+        this.cargando = false;
+        this.$root.$emit("bv::hide::modal", "modalClonarEstante");
+
+      } // End validation
+    }
+  },
+  computed: {
+    filtroEstantes() {
+      var estantesFiltrados = [];
+      var estantesFiltradosPorInput = [];
+      var fechaAhora = moment().format("YYYY-MM-DD");
+
+      // Resultados de input buscar
+      estantesFiltradosPorInput = this.estantes.filter(estante => {
+        return estante.ESTA.toLowerCase().match(this.buscar.toLowerCase());
+      });
+
+      switch (this.filtroFecha.buscarFechaDias) {
+        case -1:
+          // Intervalo personalizado
+          estantesFiltrados = estantesFiltradosPorInput.filter(estante => {
+            let fechaFormato = moment(estante.FECR, "DD-MMM-YY").format(
+              "YYYY-MM-DD"
+            );
+
+            if (
+              !(
+                moment(fechaFormato).isBefore(this.filtroFecha.fechaInicial) ||
+                moment(fechaFormato).isAfter(this.filtroFecha.fechaFinal)
+              )
+            ) {
+              return estante;
+            }
+          });
+          break;
+        case 1:
+        case 7:
+          // Filtrar ultimos 7 días y 24 hrs
+          var dias = moment()
+            .subtract(this.filtroFecha.buscarFechaDias, "days")
+            .format("YYYY-MM-DD");
+
+          estantesFiltrados = estantesFiltradosPorInput.filter(estante => {
+            let fechaEstante = moment(estante.FECR, "DD-MMM-YY").format(
+              "YYYY-MM-DD"
+            );
+            if (
+              moment(fechaEstante).isBetween(dias, fechaAhora) ||
+              moment(fechaEstante).isSame(fechaAhora)
+            ) {
+              return estante;
+            }
+          });
+          break;
+        default:
+          estantesFiltrados = estantesFiltradosPorInput;
+          break;
+      }
+
+      return this.paginador(estantesFiltrados);
+    }
+  }
+};
+</script>
+
+<style scoped>
+.dropdown-item {
+  padding: 0px 7px 2px 40px;
+}
+.page-titles .breadcrumb .breadcrumb-item + .breadcrumb-item::before {
+  content: "/";
+}
+.uploading-image {
+  display: flex;
+}
+.sortable-chosen {
+  background: #c2dbff;
+}
+.table-responsive {
+  box-shadow: 0px 0px 7px 2px #f2f2f2;
+  padding: 15px;
+}
+.spinner-grow-sm {
+  width: 1.6rem;
+  height: 1.6rem;
+}
+.table td,
+.table th {
+  vertical-align: middle;
+}
+
+</style>

+ 235 - 0
src/views/admin/HomeAdmin.vue

@@ -0,0 +1,235 @@
+<template>
+  <div class="home">
+    <div class="alert alert-danger alert-rounded mt-5" v-if="strError.length != 0">
+      <button type="button" class="close" data-dismiss="alert" aria-label="Close"></button>
+      <i class="material-icons icon-align">report_problem</i>
+      {{ strError }}
+    </div>
+    <div class="row">
+      <div class="col-sm-12">
+        <h5 class="pb-2">Estadísticas generales</h5>
+      </div>
+      <!-- Column -->
+      <div class="col-12 col-sm-4 col-md-4 col-lg-4">
+        <div class="card card-inverse card-info">
+          <div class="box bg-info text-center">
+            <h1 class="font-light text-white">{{ contador.est }}</h1>
+            <h6 class="text-white">Estantes</h6>
+          </div>
+        </div>
+      </div>
+      <!-- Column -->
+      <div class="col-12 col-sm-4 col-md-4 col-lg-4">
+        <div class="card card-inverse bg-danger">
+          <div class="box text-center">
+            <h1 class="font-light text-white">{{ contador.secc }}</h1>
+            <h6 class="text-white">Secciones</h6>
+          </div>
+        </div>
+      </div>
+      <!-- Column -->
+      <div class="col-12 col-sm-4 col-md-4 col-lg-4">
+        <div class="card card-inverse bg-warning">
+          <div class="box text-center">
+            <h1 class="font-light text-white">{{ contador.cate }}</h1>
+            <h6 class="text-white">Categorías</h6>
+          </div>
+        </div>
+      </div>
+    </div>
+    <div class="row">
+      <div class="col-sm-12">
+        <h5 class="pb-2">Estadísticas de archivos</h5>
+      </div>
+      <div class="col-12 col-sm-6 col-md-4 col-lg-4">
+        <div class="card card-body shadow">
+          <!-- Row -->
+          <div class="row pt-2 pb-2">
+            <!-- Column -->
+            <div class="col pr-0">
+              <h1 class="font-light">{{ contador.arch }}</h1>
+              <h6 class="text-muted">Total</h6>
+            </div>
+            <!-- Column -->
+            <div class="col text-right align-self-center">
+              <div data-label="30%" class="css-bar mb-0 css-bar-info css-bar-60">
+                <i class="material-icons">library_books</i>
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+      <div class="col-12 col-sm-6 col-md-4 col-lg-4">
+        <div class="card card-body shadow">
+          <!-- Row -->
+          <div class="row pt-2 pb-2">
+            <!-- Column -->
+            <div class="col pr-0">
+              <h1 class="font-light">{{ semana }}</h1>
+              <h6 class="text-muted">Última Semana</h6>
+            </div>
+            <!-- Column -->
+            <div class="col text-right align-self-center">
+              <div class="css-bar mb-0 css-bar-info css-bar-45">
+                <!-- <i class="mdi mdi-receipt"></i> -->
+                <i class="material-icons">library_books</i>
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+      <div class="col-12 col-sm-6 col-md-4 col-lg-4">
+        <div class="card card-body shadow">
+          <!-- Row -->
+          <div class="row pt-2 pb-2">
+            <!-- Column -->
+            <div class="col pr-0">
+              <h1 class="font-light"> {{ dia }} </h1>
+              <h6 class="text-muted">Últimas 24hrs</h6>
+            </div>
+            <!-- Column -->
+            <div class="col text-right align-self-center">
+              <div class="css-bar mb-0 css-bar-info css-bar-20">
+                <!-- <i class="mdi mdi-receipt"></i> -->
+                <i class="material-icons">library_books</i>
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+    <!-- <div class="row">
+      <div class="col-lg-12">
+        <h5>Sesiones de usuarios</h5>
+      </div>
+      <div class="col-lg-3 col-md-3">
+        <div class="card">
+          <div class="card-body">
+            <div class="d-flex flex-row">
+              <div class="round align-self-center round-success">
+                <i class="material-icons">person</i>
+              </div>
+              <div class="ml-2 align-self-center">
+                <h4 class="mb-0">65</h4>
+                <h6 class="text-muted mb-0">Últimos 6 meses</h6>
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+      <div class="col-lg-3 col-md-3">
+        <div class="card">
+          <div class="card-body">
+            <div class="d-flex flex-row">
+              <div class="round align-self-center round-info">
+                <i class="material-icons">person</i>
+              </div>
+              <div class="ml-2 align-self-center">
+                <h4 class="mb-0">34</h4>
+                <h6 class="text-muted mb-0">Último mes</h6>
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+      <div class="col-lg-3 col-md-3">
+        <div class="card">
+          <div class="card-body">
+            <div class="d-flex flex-row">
+              <div class="round align-self-center round-danger">
+                <i class="material-icons">person</i>
+              </div>
+              <div class="ml-2 align-self-center">
+                <h4 class="mb-0">20</h4>
+                <h6 class="text-muted mb-0">Última Semana</h6>
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+      <div class="col-lg-3 col-md-3">
+        <div class="card">
+          <div class="card-body">
+            <div class="d-flex flex-row">
+              <div class="round align-self-center round-success">
+                <i class="material-icons">person</i>
+              </div>
+              <div class="ml-2 align-self-center">
+                <h4 class="mb-0">10</h4>
+                <h6 class="text-muted mb-0">Últimas 24hrs</h6>
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>-->
+  </div>
+</template>
+
+<script>
+import { token } from "../../_mixin/user_mixin.js";
+import moment from "moment";
+export default {
+  name: "homeAdmin",
+  mixins: [token],
+  data() {
+    return {
+      contador: {
+        est: 0,
+        secc: 0,
+        cate: 0,
+        arch: 0
+      },
+      semana: 0,
+      dia: 0
+    };
+  },
+  created() {
+    this.contAll();
+  },
+  methods: {
+    async contAll() {
+      await this.getEstantes();
+      await this.getSecciones();
+      await this.getCategorias();
+      await this.getArchivos(
+        this.$store.getters.user.id,
+        this.$store.getters.user.role
+      );
+
+      this.contador.est = this.estantes.length;
+      this.contador.secc = this.secciones.length;
+      this.contador.cate = this.categorias.length;
+      this.contador.arch = this.archivos.length;
+      this.filtros();
+    },
+    filtros() {
+      // Fecha actual
+      var now = moment().format("YYYY-MM-DD");
+      // Filtrar ultimos 7 días
+      var semana = moment()
+        .subtract(7, "days")
+        .format("YYYY-MM-DD");
+
+      var dia = moment()
+        .subtract(1, "days")
+        .format("YYYY-MM-DD");
+      let contSemana = [];
+      let contDia = [];
+      this.archivos.filter(arch => {
+        let frm = moment(arch.FECR, "DD-MMM-YY").format("YYYY-MM-DD");
+        // semana
+        if (moment(frm).isBetween(semana, now) || moment(frm).isSame(now)) {
+          contSemana.push(this.archivos);
+        }
+        //24hrs
+        if (moment(frm).isBetween(dia, now) || moment(frm).isSame(now)) {
+          contDia.push(this.archivos);
+        }
+      });
+      this.semana = contSemana.length;
+      this.dia = contDia.length;
+    }
+  }
+};
+</script>

+ 907 - 0
src/views/admin/SeccionesAdmin.vue

@@ -0,0 +1,907 @@
+<template>
+  <div>
+    <ol class="breadcrumb mb-2">
+      <li class="breadcrumb-item">
+        <router-link :to=" {name:'homeAdmin'} ">Inicio</router-link>
+      </li>
+      <li class="breadcrumb-item active">Secciones</li>
+    </ol>
+    <div class="row">
+      <div class="col-12">
+        <div class="card">
+          <div class="card-body">
+            <!-- Manejo de error -->
+            <div class="alert alert-danger alert-rounded" v-if="strError.length != 0">
+              <button type="button" class="close" data-dismiss="alert" aria-label="Close"></button>
+              <i class="material-icons icon-align">report_problem</i>
+              {{ strError }}
+            </div>
+            <h4 class="card-title">Secciones</h4>
+            <span class="text-dark">En esta página se pueden visualizar las secciones disponibles.</span>
+            <div class="col-sm-12">
+              <form class="form-material mt-2">
+                <input
+                  type="text"
+                  class="form-control col-sm-3 mb-2 float-left"
+                  placeholder="Nombre de la sección"
+                  v-model="buscar"
+                />
+
+                <div class="btn-group dropright">
+                  <a
+                    href="#"
+                    class="align-middle text-danger"
+                    @click.prevent="filtroFecha.verFiltrosBusqueda = !filtroFecha.verFiltrosBusqueda"
+                  >
+                    <i class="material-icons align-middle" style="color: #f42849;" id="busqueda-avanzada">more_vert</i>
+                    <b-tooltip target="busqueda-avanzada" placement="bottom">
+                      <span>Búsqueda avanzada</span>
+                    </b-tooltip>
+                  </a>
+                </div>
+                
+                <a                 
+                  href=""
+                  @click.prevent="comenzarAMoverElementos(ordenarElementos = !ordenarElementos)"
+                >
+                  <b-tooltip target="filtro-ordenar" placement="bottom">
+                    <span >Ordenar</span>
+                  </b-tooltip>
+                  <i id="filtro-ordenar" class="material-icons icon-align" :class="[ordenarElementos ? 'text-primary':'text-danger']">filter_list</i>
+                </a>
+
+              <!-- botones de ordenar -->
+                <button
+                  v-show="updateOrder"
+                  class="mr-2 btn btn-sm  btn-outline-info"
+                  @click.prevent="getSecciones();updateOrder = false;ordenarElementos=false;"
+                  :disabled="isLoading"
+                >
+                  <i class="material-icons icon-align">refresh</i> Deshacer
+                </button>
+                <button
+                  v-show="updateOrder"
+                  class="mr-2 ml-2 btn btn-sm  btn-outline-success"
+                  @click.prevent="actualizarOrdenSecciones()"
+                  :disabled="isLoading"
+                >
+                  <div class="save" v-if="!isLoading">
+                    <i class="material-icons icon-align">save</i>
+                    Guardar Orden
+                  </div>
+                  <div class="loading" v-if="isLoading">
+                    <span class="spinner-border spinner-grow-sm" role="status" aria-hidden="true"></span> Cargando...
+                  </div>
+                </button>
+
+                <button
+                  class="btn float-right btn-outline-danger"
+                  data-toggle="modal"
+                  data-target="#modalSeccion"
+                  @click.prevent="seleccionarSeccion('','new')"
+                  v-if="strError.length == 0"
+                >
+                  <i class="material-icons icon-align">add_circle</i> Crear Sección
+                </button>
+              </form>
+            </div>
+
+            <div class="col-sm-12 float-left mb-2" v-show="filtroFecha.verFiltrosBusqueda">
+              <div class="btn-group">
+                <a
+                  href
+                  class="dropdown-toggle mr-2 text-dark"
+                  :class="[clickedFiltro.IDES !== ''
+                  ? 'font-bold':'']"
+                  @click.prevent="filtroFecha.verModalBusqueda = true"
+                >Estantes / Secciones</a>
+              </div>
+
+              <div class="btn-group">
+                <a
+                  href="#"
+                  class="dropdown-toggle pl-2 text-dark"
+                  data-toggle="dropdown"
+                  aria-haspopup="true"
+                  aria-expanded="false"
+                  :class="[filtroFecha.buscarFechaDias !== 0 ? 'font-bold':'']"
+                >{{ filtroFecha.filtroSeleccionado }}</a>
+
+                <div class="dropdown-menu">
+                  <a
+                    class="dropdown-item"
+                    href="#"
+                    @click.prevent="filtroFecha.buscarFechaDias = 0;
+                        filtroFecha.filtroSeleccionado = 'Cualquier fecha'"
+                  >
+                    <i
+                      class="material-icons align-middle"
+                      style="font-weight:bold;margin-left: -30px;"
+                      v-show="filtroFecha.buscarFechaDias == 0"
+                    >check</i>
+                    &nbsp; Cualquier fecha
+                  </a>
+                  <a
+                    class="dropdown-item"
+                    href="#"
+                    @click="filtroFecha.buscarFechaDias = 1;
+                      filtroFecha.filtroSeleccionado = 'Últimas 24 hrs'"
+                  >
+                    <i
+                      class="material-icons align-middle"
+                      style="font-weight:bold;margin-left: -30px;"
+                      v-show="filtroFecha.buscarFechaDias == 1"
+                    >check</i>
+                    &nbsp; Últimas 24 hrs
+                  </a>
+                  <a
+                    class="dropdown-item"
+                    href="#"
+                    @click.prevent="filtroFecha.buscarFechaDias = 7
+                        filtroFecha.filtroSeleccionado = 'Última semana'"
+                  >
+                    <i
+                      class="material-icons align-middle"
+                      style="font-weight:bold;margin-left: -30px;"
+                      v-show="filtroFecha.buscarFechaDias == 7"
+                    >check</i>
+                    &nbsp; Última semana
+                  </a>
+                  <div class="dropdown-divider"></div>
+                  <a
+                    class="dropdown-item"
+                    href="#"
+                    @click.prevent="filtroFecha.buscarFechaDias=-1;
+                        filtroFecha.filtroSeleccionado = 'Intervalo personalizado'"
+                  >
+                    <i
+                      class="material-icons align-middle"
+                      style="font-weight:bold;margin-left: -30px;"
+                      v-show="filtroFecha.buscarFechaDias==-1"
+                    >check</i>
+                    &nbsp; Intervalo personalizado
+                  </a>
+                </div>
+              </div>
+
+              <div class="btn-group">
+                <div class="form-material" v-show="filtroFecha.buscarFechaDias == -1">
+                  <!-- Datepicker Show -->
+                  <div class="datepicker-trigger">
+                    <div class="input-group">
+                      <span
+                        class="input-group-text"
+                        style="background-color: transparent; border: none;"
+                      >
+                        <i class="material-icons align-middle">date_range</i>
+                      </span>
+                      <input
+                        type="text"
+                        class="form-control"
+                        id="datepicker-trigger"
+                        placeholder="Selecciona un rango..."
+                        :value="formatoFechaCalendario(filtroFecha.fechaInicial,filtroFecha.fechaFinal)"
+                      />
+                    </div>
+                    <AirbnbStyleDatepicker
+                      :trigger-element-id="'datepicker-trigger'"
+                      :mode="'range'"
+                      :fullscreen-mobile="false"
+                      :date-one="filtroFecha.fechaInicial"
+                      :date-two="filtroFecha.fechaFinal"
+                      :end-date="filtroFecha.fechaLimiteCalendario"
+                      @date-one-selected="val => { filtroFecha.fechaInicial = val }"
+                      @date-two-selected="val => { filtroFecha.fechaFinal = val }"
+                      @apply="filtroFecha.buscarFechaDias = -1"
+                    />
+                  </div>
+                </div>
+              </div>
+
+              <div class="btn-group">
+                <a
+                  href="#"
+                  class="ml-3 text-dark"
+                  style="border: 1px solid #d8d8d8;padding: 3px"
+                  @click="borrarFiltro()"
+                  v-if="buscar.length !== 0 || 
+                    filtroFecha.buscarFechaDias > 0 ||
+                    filtroFecha.fechaInicial !== ''  || 
+                    clickedFiltro.IDES !== ''"
+                >Borrar Filtro</a>
+              </div>
+            </div>
+            <!-- Modal filtro buscar -->
+            <transition name="fade" v-if="filtroFecha.verModalBusqueda ">
+              <div class="modal-mask">
+                <div class="modal-wrapper">
+                  <div class="modal-dialog" role="document">
+                    <div class="modal-content">
+                      <div class="modal-header">
+                        <h6>Filtro avanzado de secciones</h6>
+                        <button
+                          type="button"
+                          class="close"
+                          @click="filtroFecha.verModalBusqueda = false;"
+                          aria-label="Cerrar"
+                        >
+                          <span aria-hidden="true">&times;</span>
+                        </button>
+                      </div>
+                      <div class="modal-body">
+                        <div class="form-group row">
+                          <label for="lbl_estantes" class="col-sm-3 col-form-label">Estantes</label>
+                          <div class="col-sm-9">
+                            <select
+                              class="custom-select"
+                              v-model="clickedFiltro.IDES"
+                              id="estanteFiltro"
+                              @change="clickedFiltro.IDSE = ''"
+                            >
+                              <option value>Seleccione una opción</option>
+                              <option
+                                v-for="estante in estantes"
+                                :key="estante.IDES"
+                                :value="estante.IDES"
+                              >{{estante.ESTA }}</option>
+                            </select>
+                          </div>
+                        </div>
+                        <div class="form-group row">
+                          <label for="lbl_secciones" class="col-sm-3 col-form-label">Secciones</label>
+                          <div class="col-sm-9">
+                            <select
+                              class="custom-select"
+                              v-model="clickedFiltro.IDSE"
+                              id="seccionFiltro"
+                            >
+                              <option value>Seleccione una sección</option>
+                              <option
+                                v-for="seccion in seccionesFiltro"
+                                :key="seccion.IDSE"
+                                :value="seccion.IDSE"
+                              >{{seccion.SECC }}</option>
+                            </select>
+                          </div>
+                        </div>
+                        <button
+                          class="btn btn-danger float-right mt-3"
+                          @click="filtroFecha.verModalBusqueda = false;"
+                        >Aplicar</button>
+                      </div>
+                    </div>
+                  </div>
+                </div>
+              </div>
+            </transition>
+
+            <!-- add seccion modal -->
+            <div
+              class="modal animated bounceInDown fast"
+              id="modalSeccion"
+              tabindex="-1"
+              role="dialog"
+              aria-labelledby="modalSeccion"
+            >
+              <div class="modal-dialog" role="document">
+                <div class="modal-content">
+                  <div class="modal-header">
+                    <div style="width:100%">
+                      <h4 class="float-left">{{ mensajeModal }}</h4>
+                      <uploadImage
+                        class="float-right"
+                        v-on:imagenUpdatedEvent="actualizarImagenes"
+                      />
+                    </div>
+                    <button type="button" class="close" data-dismiss="modal" aria-label="Close">
+                      <span aria-hidden="true">&times;</span>
+                    </button>
+                  </div>
+                  <div class="modal-body">
+                    <div class="form-group">
+                      <label class="font-bold">Nombre</label>
+                      <input
+                        type="text"
+                        name="seccion"
+                        v-model="clickedSec.SECC"
+                        class="form-control"
+                        placeholder="Escriba el nombre de la Sección"
+                        v-validate="'required|regex:^(?:[A-Za-z0-9 _.]*)$'"
+                        :class="{'input': true, 'is-danger': errors.has('seccion') }"
+                      />
+                      <span
+                        v-show="errors.has('seccion')"
+                        class="invalid-feedback-form"
+                      >{{ errors.first('seccion') }}</span>
+                    </div>
+                    <div class="form-group">
+                      <label class="font-bold">Estante</label>
+                      <select
+                        class="custom-select"
+                        v-model="clickedSec.IDES"
+                        name="estante"
+                        v-validate="'required'"
+                        :class="{'input': true, 'is-danger': errors.has('estante') }"
+                      >
+                        <option value selected>Seleccione una opción</option>
+                        <option
+                          v-for="estante in estantes"
+                          :key="estante.IDES"
+                          :value="estante.IDES"
+                        >{{estante.ESTA }}</option>
+                      </select>
+                      <span
+                        v-show="errors.has('estante')"
+                        class="invalid-feedback-form"
+                      >{{ errors.first('estante') }}</span>
+                    </div>
+                    <label class="typo__label font-bold">Seleccione una imagen</label>
+                    <multiselect
+                      v-model="imagen"
+                      placeholder="Seleccione una imagen de la lista"
+                      label="title"
+                      track-by="IMAG"
+                      :options="imagenesData"
+                      :option-height="104"
+                      :custom-label="({ title, desc }) => `${title}`"
+                      :show-labels="false"
+                    >
+                      <template slot="singleLabel" slot-scope="props">
+                        <img class="option__image" :src="props.option.URL" style="width:100px" />
+                        <span class="option__desc">
+                          <span class="option__title ml-2">{{ props.option.IMAG }}</span>
+                        </span>
+                      </template>
+                      <template slot="option" slot-scope="props">
+                        <img class="option__image" :src="props.option.URL" style="width:100px" />
+                        <span class="option__title ml-2">{{ props.option.IMAG }}</span>
+                      </template>
+                      <span slot="noOptions">Sin elementos para mostrar.</span>
+                    </multiselect>
+                    <span
+                      v-show="imagenInvalid && imagen.IMAG == 'Seleccione una imagen de la lista'"
+                      class="invalid-feedback-form"
+                    >La imagen es un campo obligatorio</span>
+                  </div>
+                  <div class="modal-footer">
+                    <button type="button" class="btn btn-light" data-dismiss="modal">Cerrar</button>
+                    <!-- Buttons save and edit -->
+                    <button
+                      type="button"
+                      v-if="mensajeButtonModal=='Editar'"
+                      @click.prevent="editarSeccion()"
+                      class="btn btn-danger"
+                    >{{ mensajeButtonModal }}</button>
+
+                    <button
+                      type="button"
+                      v-if="mensajeButtonModal=='Agregar'"
+                      @click.prevent="crearSeccion(clickedSec.SECC,clickedSec.IDES)"
+                      class="btn btn-danger"
+                    >{{ mensajeButtonModal }}</button>
+                  </div>
+                </div>
+              </div>
+            </div>
+            <!-- end modal -->
+            <!-- Start delete Sección -->
+            <div id="deleteSeccion" class="modal animated bounceInDown fast">
+              <div class="modal-dialog">
+                <div class="modal-content">
+                  <form>
+                    <div class="modal-header">
+                      <h4 class="modal-title">Borrar Sección</h4>
+                      <button
+                        type="button"
+                        class="close"
+                        data-dismiss="modal"
+                        aria-hidden="true"
+                      >&times;</button>
+                    </div>
+                    <div class="modal-body">
+                      <p>
+                        Esta a punto de borrar
+                        <b>'{{ clickedSec.SECC }}'</b>
+                      </p>
+                      <p class="text-danger">Esta acción no se puede deshacer.</p>
+                    </div>
+                    <div class="modal-footer">
+                      <input
+                        type="button"
+                        class="btn btn-default"
+                        data-dismiss="modal"
+                        value="Cerrar"
+                      />
+                      <input
+                        type="submit"
+                        @click="deleteSeccion = false; borrarSeccion()"
+                        class="btn btn-danger"
+                        value="Borrar"
+                        data-dismiss="modal"
+                      />
+                    </div>
+                  </form>
+                </div>
+              </div>
+            </div>
+            <!-- End delete Sección -->
+            <div class="table-responsive mt-2" v-if="strError.length == 0">
+              <table class="table no-wrap table-hover">
+                <thead>
+                  <tr>
+                    <th>Imagen</th>
+                    <th>Nombre</th>
+                    <th>Estante</th>
+                    <th>Fecha Creación</th>
+                    <th>Fecha de Modificación</th>
+                    <th>Acciones</th>
+                  </tr>
+                </thead>
+                <draggable
+                  v-model="secciones"
+                  tag="tbody"
+                  v-if="secciones.length !== 0"
+                  @start="drag=true"
+                  @end="drag = false"
+                  @update="moverSeccion"
+                  v-bind="configuracionDragg"
+                  :disabled="!ordenarElementos"
+                  :style="ordenarElementos==true ?'background: aliceblue;':''"
+                >
+                  <tr v-for="secciones in filtroSecciones" :key="secciones.IDSE">
+                    <td>
+                      <div class="profile-img">
+                        <img
+                          v-lazy="secciones.IDIM"
+                          style="max-width:65px"
+                          alt="user"
+                          class="img-thumbnail"
+                        />
+                      </div>
+                    </td>
+
+                    <td>{{ secciones.SECC }}</td>
+                    <td>
+                      
+                      <span class="label label-danger">
+                        <i class="material-icons icon-align">dns</i>
+                        {{ secciones.ESTA }}</span>
+                    </td>
+                    <td>
+                      <span v-if="secciones.FECR !== null ">
+                        <i class="material-icons icon-align" style="color:red">access_time</i>
+                        {{ secciones.FECR | formatDate }}
+                      </span>
+                    </td>
+                    <td>
+                      <span v-if="secciones.FEMO !== null ">
+                        <i class="material-icons icon-align" style="color:red">access_time</i>
+                        {{ secciones.FEMO | formatDate }}
+                      </span>
+                    </td>
+                    <td>
+                      <!-- Botones de acciones -->
+                      <a
+                        href="#modalSeccion"
+                        @click="modalSeccion = true; seleccionarSeccion(secciones,'edit')"
+                        class="edit"
+                        data-toggle="modal"
+                      >
+                        <i
+                          class="material-icons"
+                          data-toggle="tooltip"
+                          title="Editar"
+                          style="color:#f42849"
+                        >&#xE254;</i>
+                      </a>
+
+                      <a
+                        href="#deleteSeccion"
+                        @click="deleteSeccion = true;seleccionarSeccion(secciones,'delete')"
+                        class="delete"
+                        data-toggle="modal"
+                      >
+                        <i
+                          class="material-icons"
+                          data-toggle="tooltip"
+                          title="Borrar"
+                          style="color:#f42849"
+                        >&#xE872;</i>
+                      </a>
+                    </td>
+                  </tr>
+                  <!-- </tbody> -->
+                </draggable>
+              </table>
+              <!-- Pagination -->
+              <b-pagination
+                v-show="verPaginador && !loading && filtroSecciones.length !== 0"
+                v-model="currentPage"
+                :total-rows="this.secciones.length"
+                :per-page="perPage"
+                align="center"
+              ></b-pagination>          
+
+              <div v-if="loading" class="text-center">
+                <p>Cargando...</p>
+                <span
+                  class="spinner-border spinner-grow-sm"
+                  role="status"
+                  aria-hidden="true"
+                  style="color:red"
+                ></span>
+              </div>
+              <div class="text-center">
+                <p
+                  class="mt-3"
+                  v-if="secciones.length == 0 && !loading"
+                >No se encontraron secciones para mostrar.</p>
+
+                <p
+                  class="mt-3"
+                  v-show="secciones.length !== 0 && 
+                  filtroSecciones.length === 0 && 
+                  (filtroFecha.buscarFechaDias !== 0 || buscar.length !== 0 || clickedFiltro.IDES !== '') &&
+                  !loading"
+                >No se encontró ninguna sección en su búsqueda.</p>
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+<style src="vue-multiselect/dist/vue-multiselect.min.css"></style>
+<script>
+import uploadImage from "@/components/UploadImage.vue";
+import Multiselect from "vue-multiselect";
+import moment from "moment";
+import draggable from "vuedraggable";
+import { BPagination,BTooltip } from "bootstrap-vue";
+import { token } from "../../_mixin/user_mixin.js";
+
+export default {
+  name: "SeccionesAdmin",
+  mixins: [token],
+  components: {
+    uploadImage,
+    Multiselect,
+    draggable,
+    BPagination,
+    BTooltip
+
+  },
+  data: () => ({
+    deleteSeccion: false,
+    mensajeModal: "Agregar Sección",
+    mensajeButtonModal: "Agregar",
+    clickedSec: {},
+    imagen: { IMAG: "Seleccione una imagen de la lista" },
+    buscar: "",
+    perPage: 25,
+    currentPage: 1,
+    verPaginador: true,
+    updateOrder: false,
+    arrOrder: {},
+    isLoading: false,
+    imagenInvalid: false,
+    ordenarElementos: false
+  }),
+  mounted: function() {
+    this.getSecciones();
+    this.getEstantes();
+    this.getImages("SE");
+  },
+  methods: {
+    editarSeccion: function() {
+      // Verificar que tengamos imagen seleccionada
+      this.$validator.validateAll().then(errors => {
+        if (errors) {
+          if (this.imagen.IMAG != "Seleccione una imagen de la lista") {
+            let img = this.imagen.URL;
+            this.clickedSec.IDIM = img;
+          }
+          var formData = this.toFormData(this.clickedSec);
+          this.$http
+            .post(
+              this.url_api + "/secciones/update",
+              formData,
+              this.config_header
+            )
+            .then(response => {
+              this.clickedSec = {};
+              if (!response.data.response) {
+                this.$toasted.show(response.data.message, this.$toast_error);
+              } else {
+                this.$toasted.show(response.data.message, this.$toast_success);
+              }
+              this.getSecciones();
+              this.removeModal("modalSeccion");
+            })
+            .catch(error => {
+              this.strError =
+                "Se ha producido un error al actualizar el sección.";
+            });
+        }
+      });
+    },
+    seleccionarSeccion: function(seccion, accion) {
+      this.clickedSec = {};
+      this.imagen = { IMAG: "Seleccione una imagen de la lista" };
+      this.imagenInvalid = false;
+      // Remove Menssages error
+      this.$nextTick().then(() => {
+        this.$validator.reset();
+        this.errors.clear();
+      });
+      if (accion == "new") {
+        this.clickedSec = { IDES: "" };
+        this.mensajeModal = "Agregar Sección";
+        this.mensajeButtonModal = "Agregar";
+      }
+      // Si estamos editando
+      if (accion == "edit") {
+        var datos = {
+          IDSE: seccion.IDSE,
+          IDES: seccion.IDES,
+          SECC: seccion.SECC,
+          IDIM: seccion.IDIM,
+          USMO: this.$store.getters.user.id
+        };
+        this.clickedSec = datos;
+        this.mensajeButtonModal = "Editar";
+        this.mensajeModal = "Editar Sección";
+      }
+      if (accion == "delete") {
+        this.clickedSec = seccion;
+      }
+    },
+    crearSeccion: function(secc, ides) {
+      // Verificar que tengamos imagen seleccionada
+      this.imagenInvalid = false;
+      if (this.imagen.IMAG == "Seleccione una imagen de la lista") {
+        this.imagenInvalid = true;
+      }
+      this.$validator.validateAll().then(result => {
+        if (result && !this.imagenInvalid) {
+          let imagenUrl = this.imagen.URL;
+          var datos = {
+            SECC: secc,
+            IDES: ides,
+            IDIM: imagenUrl,
+            USCR: this.$store.getters.user.id
+          };
+          var formData = this.toFormData(datos);
+          this.$http
+            .post(this.url_api + "/secciones", formData, this.config_header)
+            .then(response => {
+              if (!response.data.response) {
+                this.$toasted.show(response.data.message, this.$toast_error);
+              } else {
+                this.$toasted.show(response.data.message, this.$toast_success);
+              }
+              this.getSecciones();
+              this.removeModal("modalSeccion");
+            })
+            .catch(error => {
+              this.strError = "Se ha producido un error al crear la sección.";
+            });
+        } //end Result
+      }); // end validator
+    },
+    borrarSeccion: function() {
+      var formData = this.toFormData(this.clickedSec);
+      this.$http
+        .post(this.url_api + "/secciones/delete", formData, this.config_header)
+        .then(response => {
+          if (!response.data.response) {
+            this.$toasted.show(response.data.message, this.$toast_error);
+          } else {
+            this.$toasted.show(response.data.message, this.$toast_success);
+          }
+          this.getSecciones();
+        })
+        .catch(error => {
+          this.strError = "Se ha producido un error al borrar la sección.";
+        });
+    },
+    paginador: function(secciones) {
+      // Calcular páginador
+      const indiceInicio = (this.currentPage - 1) * this.perPage;
+      const indiceFinal =
+        indiceInicio + this.perPage > secciones.length
+          ? secciones.length
+          : indiceInicio + this.perPage;
+      return secciones.slice(indiceInicio, indiceFinal);
+    },
+    moverSeccion: function() {
+      this.updateOrder = true;
+      var obj = this.secciones;
+      var bodyFormData = new FormData();
+      obj.forEach((element, i) => {
+        i++;
+        bodyFormData.append(element.IDSE, i);
+      });
+      this.arrOrder = bodyFormData;
+    },
+    actualizarOrdenSecciones: function() {
+      var formData = this.arrOrder;
+      this.isLoading = true;
+      this.$http
+        .post(this.url_api + "/secciones/order", formData, this.config_header)
+        .then(response => {
+          this.isLoading = false;
+          this.updateOrder = false;
+          if (!response.data.response) {
+            this.$toasted.show(response.data.message, this.$toast_error);
+          } else {
+            this.$toasted.show(response.data.message, this.$toast_success);
+          }
+          this.getSecciones();
+        })
+        .catch(error => {
+          this.strError = "Se ha producido un error al ordenar las secciones.";
+        });
+    },
+    actualizarImagenes: function(action) {
+      if (action) {
+        this.getImages("SE");
+      }
+    },
+    borrarFiltro() {
+      this.filtroFecha.fechaInicial = "";
+      this.filtroFecha.fechaFinal = "";
+      this.filtroFecha.buscarFechaDias = 0;
+      this.buscar = "";
+      this.filtroFecha.filtroSeleccionado = "Cualquier fecha";
+      this.clickedFiltro.IDES = "";
+      this.clickedFiltro.IDSE = "";
+    },
+    comenzarAMoverElementos(ordenandoElementos) {
+      this.ordenarElementos = ordenandoElementos;
+      if (this.ordenarElementos) {
+        this.perPage = this.secciones.length;
+        this.paginador(this.secciones);
+      } else {
+        this.perPage = 25;
+        this.paginador(this.secciones);
+      }
+    }
+  },
+  computed: {
+    seccionesFiltro() {
+      let idEstante = this.clickedFiltro.IDES;
+      return this.secciones.filter(seccion => seccion.IDES === idEstante);
+    },
+    filtroSecciones() {
+      var seccionesFiltradas = [];
+      var seccionesFiltradasPorInput = [];
+      var fechaAhora = moment().format("YYYY-MM-DD");
+
+      //Filtro de Modal Búsqueda Avanzada
+      var idEstante = this.clickedFiltro.IDES;
+      var idSeccion = this.clickedFiltro.IDSE;
+
+      var seccionesModal = this.secciones.filter(seccion => {
+        if (idEstante !== "" && idSeccion === "") {
+          return seccion.IDES === idEstante;
+        }
+        if (idEstante !== "" && idSeccion !== "") {
+          return seccion.IDES === idEstante && seccion.IDSE === idSeccion;
+        }
+        return seccion;
+      });
+
+      // filtra por lo que escriba el usuario en la caja de texto
+      seccionesFiltradasPorInput = seccionesModal.filter(seccion => {
+        return seccion.SECC.toLowerCase().match(this.buscar.toLowerCase());
+      });
+
+      switch (this.filtroFecha.buscarFechaDias) {
+        case -1:
+          // Intervalo personalizado
+          seccionesFiltradas = seccionesFiltradasPorInput.filter(seccion => {
+            let fechaFormato = moment(seccion.FECR, "DD-MMM-YY").format(
+              "YYYY-MM-DD"
+            );
+
+            if (
+              !(
+                moment(fechaFormato).isBefore(this.filtroFecha.fechaInicial) ||
+                moment(fechaFormato).isAfter(this.filtroFecha.fechaFinal)
+              )
+            ) {
+              return seccion;
+            }
+          });
+          break;
+        case 1:
+        case 7:
+          // Filtrar ultimos 7 días y 24 hrs
+          var dias = moment()
+            .subtract(this.filtroFecha.buscarFechaDias, "days")
+            .format("YYYY-MM-DD");
+
+          seccionesFiltradas = seccionesFiltradasPorInput.filter(seccion => {
+            let fechaFormato = moment(seccion.FECR, "DD-MMM-YY").format(
+              "YYYY-MM-DD"
+            );
+            if (
+              moment(fechaFormato).isBetween(dias, fechaAhora) ||
+              moment(fechaFormato).isSame(fechaAhora)
+            ) {
+              return seccion;
+            }
+          });
+          break;
+        default:
+          seccionesFiltradas = seccionesFiltradasPorInput;
+          break;
+      }
+      return this.paginador(seccionesFiltradas);
+    }
+  }
+};
+</script>
+<style scoped>
+.page-titles .breadcrumb .breadcrumb-item + .breadcrumb-item::before {
+  content: "/";
+}
+.uploading-image {
+  display: flex;
+}
+.sortable-chosen {
+  background: #c2dbff;
+}
+.table-responsive {
+  box-shadow: 0px 0px 7px 2px #f2f2f2;
+  padding: 15px;
+}
+.spinner-grow-sm {
+  width: 1.6rem;
+  height: 1.6rem;
+}
+.pagination > .active > a,
+.pagination > .active > span,
+.pagination > .active > a:hover,
+.pagination > .active > span:hover,
+.pagination > .active > a:focus,
+.pagination > .active > span:focus {
+  background-color: #f62d51 !important;
+  border-color: #f62d51 !important;
+}
+.dropdown-item {
+  padding: 0px 7px 2px 40px;
+}
+.fade-enter-active,
+.fade-leave-active {
+  transition: opacity 0.2s;
+}
+.fade-enter, .fade-leave-to /* .fade-leave-active below version 2.1.8 */ {
+  opacity: 0;
+}
+.dropdown-toggle {
+  border: 1px solid#d8d8d8;
+  padding: 3px;
+}
+/* Modal filtro */
+.modal-mask {
+  position: fixed;
+  z-index: 9998;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  background-color: rgba(0, 0, 0, 0.1);
+  display: table;
+  transition: opacity 0.3s ease;
+}
+.modal-wrapper {
+  display: table-cell;
+  vertical-align: middle;
+}
+.modal-dialog {
+  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2);
+}
+</style>

+ 771 - 0
src/views/admin/UsuariosAdmin.vue

@@ -0,0 +1,771 @@
+<template>
+  <div>
+    <ol class="breadcrumb mb-2">
+      <li class="breadcrumb-item">
+        <router-link :to="{name:'homeAdmin'}">Inicio</router-link>
+      </li>
+      <li class="breadcrumb-item active">Usuarios</li>
+    </ol>
+    <div class="row">
+      <div class="col-12">
+        <div class="card">
+          <div class="card-body">
+            <!-- Manejo de error -->
+            <div class="alert alert-danger alert-rounded" v-if="strError.length != 0">
+              <button type="button" class="close" data-dismiss="alert" aria-label="Close"></button>
+              <i class="material-icons icon-align">report_problem</i>
+              {{ strError }}
+            </div>
+            <h4 class="card-title">Usuarios</h4>
+            <span
+              class="text-dark"
+            >En esta página se pueden visualizar una lista de usuarios disponible.</span>
+            <div class="col-sm-12 mt-3">
+              <form class="form-material">
+                <input
+                  type="text"
+                  class="form-control col-sm-3 mb-4 float-left"
+                  placeholder="Buscar..."
+                  v-model="buscar"
+                />
+                
+              </form>
+
+              <b-table
+                responsive
+                hover
+                table-bordered
+                show-empty
+                :fields="titulos"
+                :items="usuarios"
+                :busy.sync="loading"
+                :current-page="currentPage"
+                :per-page="perPage"
+                :filter="buscar"
+                @filtered="onFiltered"
+              >
+                <!-- Mensaje cuando no hay registros -->
+                <template v-slot:empty="scope">
+                  <p class="text-center">No se encontraron usuarios para mostrar.</p>
+                </template>
+                <!-- Mensaje cuando no se encuentra ningun registro en la busqueda -->
+                <template v-slot:emptyfiltered="scope">
+                  <p class="text-center">Su búsqueda no arrojó resultados.</p>
+                </template>
+                <!-- columna Perfil -->
+                <template v-slot:cell(IDPERFIL)="data">
+                  <label
+                    class="label"
+                    :class="[data.item.IDPERFIL=='ADMINISTRADOR' ?'label-primary' :'label-info']"
+                  >{{ data.item.IDPERFIL }}</label>
+                </template>
+                <!-- columna activo -->
+                <template v-slot:cell(ACTI)="data">
+                  <label class="label label-primary" v-if="data.item.ACTI == null">No definido</label>
+                  <label
+                    class="label"
+                    v-else
+                    :class="[data.item.ACTI ==='S'?'label-success':'label-danger']"
+                  >{{ (data.item.ACTI ==='S')? 'Activado':'Desactivado' }}</label>
+                </template>
+                <!-- Loading al extraer los datos -->
+                <template v-slot:table-busy>
+                  <div class="text-center text-danger my-2">
+                    <b-spinner variant="danger" class="align-middle"></b-spinner>
+                    <p>Cargando...</p>
+                  </div>
+                </template>
+                <!-- Integrar columna de acciones a la tabla -->
+                <template v-slot:cell(actions)="row">
+                  <i
+                    class="material-icons"
+                    data-toggle="tooltip"
+                    title="Ver Usuario"
+                    style="color:#f42849;cursor:pointer"
+                    @click="infoModalEvento(row.item, $event.target)"
+                  >remove_red_eye</i>
+                  <i
+                    class="material-icons"
+                    data-toggle="tooltip"
+                    title="Permisos"
+                    style="color:#f42849;cursor:pointer"
+                    @click="permisosModalEvento(row.item, $event.target)"
+                    v-if="row.item.ACTI !== null && row.item.ACTI !== 'N' "
+                  >lock</i>
+                </template>
+              </b-table>
+              <!-- Paginación de la tabla -->
+              <b-pagination
+                v-model="currentPage"
+                :total-rows="totalRows"
+                :per-page="perPage"
+                align="center"
+                size="md"
+                class="my-0"
+                v-show="usuarios.length > 0"
+              ></b-pagination>
+              <!-- Modal para mostrar información del usuario -->
+              <b-modal
+                hide-backdrop
+                content-class="shadow"
+                :id="infoModal.id"
+                :title="infoModal.title"
+                size="lg"
+              >
+                <div class="row">
+                  <div class="col-lg-12">
+                    <div class="card card-outline-secondary">
+                      <div class="card-body">
+                        <form class="form-horizontal" role="form">
+                          <div class="form-body">
+                            <div class="row">
+                              <div class="col-lg-6 col-md-12">
+                                <div class="form-group row">
+                                  <label
+                                    class="control-label text-right col-md-4 col-4 font-bold"
+                                  >Nombre:</label>
+                                  <div class="col-md-8 col-8">
+                                    <p class="form-control-static">{{ infoModal.content.IDUSNOMBRE}}</p>
+                                  </div>
+                                </div>
+                              </div>
+                              <!--/span-->
+                              <div class="col-lg-6 col-md-12">
+                                <div class="form-group row">
+                                  <label
+                                    class="control-label text-right col-md-4 col-4 font-bold"
+                                  >ID Usuario:</label>
+                                  <div class="col-md-8 col-4">
+                                    <p class="form-control-static">{{ infoModal.content.IDUS}}</p>
+                                  </div>
+                                </div>
+                              </div>
+                              <!--/span-->
+                            </div>
+                            <!--/row-->
+                            <div class="row">
+                              <div class="col-lg-6 col-md-12">
+                                <div class="form-group row">
+                                  <label
+                                    class="control-label text-right col-md-4 col-4 font-bold"
+                                  >Perfil:</label>
+                                  <div class="col-md-8 col-8">
+                                    <p class="form-control-static">{{ infoModal.content.IDPERFIL}}</p>
+                                  </div>
+                                </div>
+                              </div>
+                              <!--/span-->
+                              <div class="col-lg-6 col-md-12">
+                                <div class="form-group row">
+                                  <label
+                                    class="control-label text-right col-md-4 col-4 font-bold"
+                                  >Activo:</label>
+                                  <div class="col-md-8 col-8">
+                                    <p v-if="infoModal.content.ACTI == null">
+                                      <label class="label label-warning mr-2">No definido</label>
+                                      <a
+                                        href="#"
+                                        @click.prevent="cambiarEstadoUsuarioEvento(infoModal.content.IDUS,'activar', $event.target)"
+                                      >Activar</a>
+                                    </p>
+                                    <p v-else>
+                                      <b-form-checkbox
+                                        v-model="userActive"
+                                        value="SI"
+                                        unchecked-value="NO"
+                                        :disabled="infoModal.content.ACTI !== null && infoModal.content.PERM === null ? true: false"
+                                        switch
+                                      >
+                                        <label
+                                          class="label"
+                                          :class="[userActive === 'SI' ? 'label-success' :'label-danger']"
+                                        >{{ userActive }}</label>
+                                      </b-form-checkbox>
+                                    </p>
+                                  </div>
+                                </div>
+                              </div>
+                              <!--/span-->
+                            </div>
+                            <div class="row" v-show="infoModal.content.ACTI != null">
+                              <div class="col-lg-6 col-md-12">
+                                <div class="row">
+                                  <div class="col-md-4 col-4">
+                                    <label
+                                      class="control-label text-right font-bold"
+                                    >Android ID o IMEI:</label>
+                                  </div>
+                                  <div class="col-md-8 col-8">
+                                      <input
+                                        v-model="infoModal.content.IMEI"
+                                        type="text"
+                                        class="form-control float-right"
+                                        placeholder="Android ID o IMEI"
+                                      />
+                                  </div>
+                                </div>
+                              </div>
+                              <div class="col-lg-6 col-md-12">
+                                <div class="form-group row">
+                                  <label
+                                    class="control-label text-right col-md-4 col-4 font-bold"
+                                  >Omitir 2-Factores:</label>
+                                  <div class="col-md-8 col-8">
+                                    <p>
+                                      <b-form-checkbox
+                                        v-model="infoModal.content.LSEC"
+                                        value="Si"
+                                        unchecked-value="No"
+                                        switch
+                                      >
+                                        <label
+                                          class="label text-uppercase"
+                                          :class="[infoModal.content.LSEC === 'Si' ? 'label-success' :'label-danger']"
+                                        >{{ infoModal.content.LSEC }}</label>
+                                      </b-form-checkbox>
+                                    </p>
+                                  </div>
+                                </div>
+                              </div>
+                            </div>
+                            <p
+                              class="text-center pt-3"
+                              v-show="infoModal.content.ACTI !== null && infoModal.content.PERM === null"
+                            >
+                              Este Usuario no tiene permisos asignados,
+                              <a
+                                href="#"
+                                @click.prevent="permisosModalEvento(infoModal.content, $event.target)"
+                              >asignar permisos</a>.
+                              <br />
+                              <i>Mientras no tenga permisos asignados no podrá iniciar sesión en el Portal de Quiosco.</i>
+                            </p>
+                          </div>
+                        </form>
+                      </div>
+                    </div>
+                  </div>
+                </div>
+                <template v-slot:modal-footer="{  hide }">
+                  <div class="w-100">
+                    <button
+                      id="aceptarInfo"
+                      type="button"
+                      class="btn btn-danger float-right"
+                      @click="hide('forget'); actualizarUsuario(infoModal.content);"
+                    >Aceptar</button>
+                    <button
+                      v-show="infoModal.content.ACTI == 'S' && userActive === 'NO'"
+                      id="guardar"
+                      type="button"
+                      class="btn btn-outline-danger mr-1 float-right"
+                      @click="cambiarEstadoUsuarioEvento(infoModal.content.IDUS,'desactivar', $event.target)"
+                    >Guardar</button>
+                  </div>
+                </template>
+              </b-modal>
+              <!-- end info modal -->
+
+              <!-- start activar/desactivar usuario modal -->
+              <b-modal
+                content-class="shadow"
+                :id="cambiarEstadoUsuarioModal.id"
+                :title="cambiarEstadoUsuarioModal.title"
+                size="sm"
+                ok-only
+              >
+                <p class="my-1" v-html="cambiarEstadoUsuarioModal.msg"></p>
+                <template v-slot:modal-footer="{ hide }">
+                  <div class="w-100">
+                    <button
+                      id="cambiarEstadoUsuario"
+                      type="button"
+                      class="btn btn-danger float-right"
+                      @click="cambiarEstadoUsuarioModal.estado === 'activar' 
+                      ? cambiarEstadoUser('activar'): cambiarEstadoUser('desactivar')"
+                    >
+                      <span v-if="!loading">
+                        {{ cambiarEstadoUsuarioModal.estado === 'activar'
+                        ? 'Activar': 'Desactivar'}}
+                      </span>
+                      <span
+                        class="spinner-border spinner-grow-sm"
+                        role="status"
+                        aria-hidden="true"
+                        style="color:white"
+                        v-if="loading"
+                      ></span>
+                    </button>
+
+                    <button
+                      type="button"
+                      class="btn btn-secondary mr-2 float-right"
+                      @click="hide('forget')"
+                    >Cancelar</button>
+                  </div>
+                </template>
+              </b-modal>
+              <!-- end activar/desactivar usuario modal -->
+              <!-- Modal permisos -->
+              <b-modal
+                hide-backdrop
+                content-class="shadow"
+                :id="permisosModal.id"
+                :title="permisosModal.title"
+                size="lg"
+              >
+                <div class="row">
+                  <div class="col-sm-12">
+                    <label class="float-left">
+                      <b>USUARIO:</b>
+                      {{ permisosModal.content.IDUSNOMBRE}}
+                    </label>
+                    <!-- <label class="float-right"> -->
+                    <!-- <h5 class="text-left">Permisos</h5> -->
+
+                    <!-- </label> -->
+                  </div>
+                </div>
+
+                <div class="row">
+                  <div class="col-lg-8 col-sm-12 col-12">
+                    <div class="navigation-filter">
+                      <input
+                        class="form-control mb-2"
+                        type="text"
+                        v-model="treeFilter"
+                        value
+                        placeholder="Buscar..."
+                      />
+                    </div>
+                    <tree
+                      v-if="treeData"
+                      :data="treeData"
+                      @node:selected="onSelect"
+                      :options="optsTree"
+                      :filter="treeFilter"
+                      ref="tree"
+                      class="card card-outline-secondary"
+                    >
+                      <span class="tree-scope" slot-scope="{ node }">
+                        <template v-if="!node.hasChildren()">
+                          <i class="material-icons icon-align" style="color:#f42849">folder_open</i>
+                          {{ node.text }}
+                        </template>
+                        <template v-else>
+                          <i
+                            class="material-icons icon-align"
+                            style="color:#f42849"
+                          >{{ node.expanded() ? 'folder' : 'create_new_folder' }}</i>
+                          {{ node.text }}
+                        </template>
+                      </span>
+                    </tree>
+                  </div>
+                  <div
+                    class="col-lg-4 col-sm-12 col-12 card card-outline-secondary pb-4"
+                    v-if="selectedNode"
+                  >
+                    <h6 class="mt-3 text-center">Establecer permisos</h6>
+                    <!-- checkbox permisos -->
+                    <div class="m-auto p-3 card card-outline-secondary">
+                      <b-form-radio
+                        v-model="permisos.tipo"
+                        name="permisoSinAcceso"
+                        @input="cambiarPermisoRadio"
+                        value="Sin Acceso"
+                      >Sin Acceso</b-form-radio>
+
+                      <b-form-radio
+                        v-model="permisos.tipo"
+                        name="permisoPublicaryVer"
+                        @input="cambiarPermisoRadio"
+                        value="Ver + Publicar"
+                      >Ver + Publicar</b-form-radio>
+
+                      <b-form-radio
+                        v-model="permisos.tipo"
+                        name="permisoVer"
+                        value="Ver"
+                        @input="cambiarPermisoRadio"
+                      >Ver</b-form-radio>
+                    </div>
+                  </div>
+                </div>
+                <template v-slot:modal-footer="{  hide }">
+                  <div class="w-100">
+                    <button
+                      id="guardarPermisos"
+                      type="button"
+                      class="btn btn-danger float-right"
+                      @click="guardarPermisos()"
+                      :disabled="!verBtnGuardarPermisos"
+                    >Guardar</button>
+                    <button
+                      id="cancelarPermisos"
+                      type="button"
+                      class="btn btn-secondary mr-1 float-right"
+                      @click="hide('forget')"
+                    >Cancelar</button>
+
+                    <button
+                      v-if="editandoPermiso"
+                      id="previsualizarPermisos"
+                      @click="cambiarPermisos"
+                      class="btn btn-outline-danger mr-1 float-right"
+                      type="button"
+                      value="Previsualizar"
+                    >Previsualizar</button>
+                  </div>
+                </template>
+              </b-modal>
+              <!-- end modal permisos -->
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+   
+  </div>
+</template>
+
+<script>
+
+import "bootstrap-vue/dist/bootstrap-vue.css";
+import {
+  BTable,
+  BSpinner,
+  BModal,
+  BPagination,
+  BFormCheckbox,
+  BFormRadio,
+  ModalPlugin,
+  BTooltip
+} from "bootstrap-vue";
+import { token } from "../../_mixin/user_mixin.js";
+import LiquorTree from "liquor-tree";
+
+var _ = require("lodash");
+export default {
+  name: "UsuariosAdmin",
+  mixins: [token],
+  components: {
+    BTable,
+    BModal,
+    BSpinner,
+    BPagination,
+    BFormCheckbox,
+    BFormRadio,
+    ModalPlugin,
+    BTooltip,
+    [LiquorTree.name]: LiquorTree
+  },
+  data: () => ({
+    selectedNode: "",
+    treeFilter: "",
+    buscar: "",
+    totalRows: 1,
+    currentPage: 1,
+    perPage: 25,
+    userActive: "NO",
+    IMEI: "",
+    verBtnGuardarInfo: false,
+    editandoPermiso: false,
+    verBtnGuardarPermisos: false,
+    infoModal: {
+      id: "info-modal",
+      title: "",
+      content: {}
+    },
+    cambiarEstadoUsuarioModal: {
+      id: "modal-activar",
+      title: "",
+      msg: "",
+      estado: ""
+    },
+    permisosModal: {
+      id: "permisos-modal",
+      title: "",
+      content: {}
+    },
+    permisos: {
+      tipo: "Sin Acceso"
+    },
+    optsTree: {
+      parentSelect: true,
+      filter: {
+        emptyText: "No se ha encontrado nada :( "
+      }
+    },
+    treeData: [],
+    titulos: [
+      {
+        key: "IDUS",
+        label: "ID",
+        sortable: true
+      },
+      {
+        key: "IDUSNOMBRE",
+        label: "Nombre",
+        sortable: true
+      },
+      {
+        key: "IDPERFIL",
+        label: "Perfil",
+        sortable: true
+      },
+      {
+        key: "ACTI",
+        label: "Estado",
+        sortable: true
+      },
+      { key: "actions", label: "Acciones" },
+    ]
+  }),
+  async created() {
+    await this.getUsuarios();
+    await this.getEstantes();
+    this.totalRows = this.usuarios.length;
+  },
+  methods: {
+    cambiarPermisoRadio(permisoSelect) {
+      let arrNode = this.selectedNode.text.split("|");
+      let permisoSeleccionado = arrNode[1];
+      this.editandoPermiso =
+        this.permisos.tipo.trim() !== permisoSeleccionado.trim() ? true : false;
+
+      this.cambiarPermisos();
+    },
+    getJsonPermisosFiltrado() {
+      var dataTree = this.$refs.tree;
+      var data = dataTree.toJSON();
+      var treeCloned = _.cloneDeep(data);
+      // Remover nodo data
+      var startTree = _.omit(treeCloned[0], "data");
+      // Filtrar solo estado expanded y selectable para primer nivel
+      var stateFirst = _.pick(startTree.state, ["expanded", "selectable"]);
+      // Asignar primer estado
+      startTree.state = stateFirst;
+      var firtsChildren = _.values(
+        _.mapValues(startTree.children, function(esta) {
+          return _.omit(esta, ["state", "data"]);
+        })
+      );
+      // Filtros anidados para remover state y data de estantes,categorias y secciones.
+      var filterChild = _.values(
+        _.mapValues(firtsChildren, estantes => {
+          estantes.children = _.values(
+            _.mapValues(estantes.children, seccciones => {
+              seccciones.children = _.values(
+                _.mapValues(seccciones.children, categorias => {
+                  return _.omit(categorias, ["state", "data"]);
+                })
+              );
+              return _.omit(seccciones, ["state", "data"]);
+            })
+          );
+          return _.omit(estantes, ["state", "data"]);
+        })
+      );
+      // Asignar hijos filtrados al arreglo padre
+      startTree.children = filterChild;
+      let treeFiltered = "[" + JSON.stringify(startTree) + "]";
+      return treeFiltered;
+    },
+    guardarPermisos() {
+      let jsonPermisos = this.getJsonPermisosFiltrado();
+
+      let datos = {
+        IDUS: this.permisosModal.content.IDUS,
+        PERM: jsonPermisos
+      };
+      this.sendPermisos(datos).then(data => {
+        this.getUsuarios();
+        //cerrar modales
+        this.$root.$emit("bv::hide::modal", "permisos-modal");
+        this.$root.$emit("bv::hide::modal", "info-modal");
+      });
+    },
+    cambiarPermisos() {
+      this.getFullPath(this.selectedNode);
+      this.editandoPermiso = false;
+    },
+    onSelect(node) {
+      var arrNode = node.text.split("|");
+      if (arrNode.length > 1) {
+        var arrPermiso = arrNode[1];
+        this.permisos.tipo = arrPermiso.trim();
+      }
+      this.selectedNode = node;
+    },
+    getFullPath(nodoSeleccionado) {
+      this.verBtnGuardarPermisos = true;
+      // asignamos el nodo actual para despues iterarlo
+      const nodoActual = [nodoSeleccionado];
+      const fullPathUp = [nodoSeleccionado];
+      const fullPathDown = [nodoSeleccionado];
+      // asignamos los elementos recursivos a fullPathUp y FullPathDown
+      nodoSeleccionado.recurseUp(parent => fullPathUp.unshift(parent));
+      nodoSeleccionado.recurseDown(children => fullPathDown.push(children));
+
+      if (this.permisos.tipo === "Sin Acceso" || this.permisos.tipo === "Ver") {
+        // Recursivos hacia abajo
+        return fullPathDown.forEach(node => {
+          const perm = node.text.split("| ");
+          const text = perm[0];
+          const tipoPermiso = perm[1];
+          return (node.text = text + "| " + this.permisos.tipo);
+        });
+      } else {
+        // Recursivo hacia arriba
+        return fullPathUp.forEach((node, key) => {
+          const perm = node.text.split("| ");
+          const text = perm[0];
+          const tipoPermiso = perm[1];
+          // si es == sin acceso o ver el parent cambiarlo
+          if (tipoPermiso === "Sin Acceso" || tipoPermiso === "Ver") {
+            return (node.text = text + "| " + this.permisos.tipo);
+          }
+          if (nodoSeleccionado.text === node.text) {
+            // Solo cambiar el nodo actual
+            return (node.text = text + "| " + this.permisos.tipo);
+          }
+        });
+      }
+    },
+    infoModalEvento(item, button) {
+      this.infoModal.title = "Información de usuario";
+      this.infoModal.content = item;
+      this.$root.$emit("bv::show::modal", this.infoModal.id, button);
+      this.verBtnGuardarInfo = false;
+      this.userActive = this.infoModal.content.ACTI === "S" ? "SI" : "NO";
+    },
+    permisosModalEvento(item, button) {
+      this.permisosModal.title = "Permisos de usuario";
+      this.permisosModal.content = item;
+      // Obtener los permisos del usuario
+      this.getPermisos(item.IDUS).then(resut => {
+        this.$refs.tree.setModel(this.permisosDatos);
+      });
+      this.$root.$emit("bv::show::modal", this.permisosModal.id, button);
+      this.selectedNode = "";
+      this.verBtnGuardarPermisos = false;
+      this.treeFilter = "";
+    },
+    cambiarEstadoUsuarioEvento(id, estado, button) {
+      this.cambiarEstadoUsuarioModal.estado = estado;
+      if (estado === "activar") {
+        this.cambiarEstadoUsuarioModal.title = "Activar usuario";
+        this.cambiarEstadoUsuarioModal.msg =
+          "¿ Desea activar el usuario con ID: <b>" + id + "</b> en el sistema?";
+      } else {
+        this.cambiarEstadoUsuarioModal.title = "Desactivar usuario";
+        this.cambiarEstadoUsuarioModal.msg =
+          "¿ Desea desactivar el usuario con ID: <b>" +
+          id +
+          "</b> ?, si desactiva al usuario, no podrá <i>iniciar sesión</i> y sus permisos serán <b>removidos</b>.";
+      }
+      this.$root.$emit(
+        "bv::show::modal",
+        this.cambiarEstadoUsuarioModal.id,
+        button
+      );
+    },
+    onFiltered(filteredItems) {
+      // Actualiza el paginador al utilizar el filtro
+      this.totalRows = filteredItems.length;
+      this.currentPage = 1;
+    },
+    cambiarEstadoUser(action) {
+      const idperfil = this.infoModal.content.IDPERFIL;
+      const idus = this.infoModal.content.IDUS;
+      // Insertamos los datos en BD
+      this.sendEstadoUser(idus, action).then(res => {
+        this.$root.$emit("bv::hide::modal", "modal-activar");
+        // Actualizamos los datos del usuario actual
+        this.getUsuarios().then(() => {
+          const setNewContent = this.usuarios.filter(
+            user => user.IDUS === idus && user.IDPERFIL === idperfil
+          );
+          this.infoModal.content = setNewContent[0];
+        });
+      });
+      this.verBtnGuardarInfo = false;
+    },
+    sendPermisos(datos) {
+      return this.$http
+        .post(this.url_api + "/usuario/permisos", datos, this.config_header)
+        .then(response => {
+          if (response.data.response) {
+            return this.$toasted.show(
+              response.data.message,
+              this.$toast_success
+            );
+          } else {
+            return this.$toasted.show(response.data.message, this.$toast_error);
+          }
+        })
+        .catch(error => {
+          this.strError =
+            "Se ha producido un error al guardar los permisos. error: " + error;
+        });
+    },
+    sendEstadoUser(idus, method) {
+      let action =
+        method === "activar" ? "/usuario/activar" : "/usuario/desactivar";
+      return this.$http
+        .post(this.url_api + action, [idus], this.config_header)
+        .then(response => {
+          if (response.data.response) {
+            return this.$toasted.show(
+              response.data.message,
+              this.$toast_success
+            );
+          } else {
+            return this.$toasted.show(response.data.message, this.$toast_error);
+          }
+        })
+        .catch(error => {
+          this.strError =
+            "Se ha producido un error al activar el usuario, error: " + error;
+        });
+    },
+    actualizarUsuario(datosUsuario){
+      let datos = {
+        IDUS: datosUsuario.IDUS,
+        IMEI: datosUsuario.IMEI,
+        LSEC: datosUsuario.LSEC
+      };
+      return this.$http
+        .post(this.url_api + '/usuario/actualizar', datos, this.config_header)
+        .then(response => {
+          if (response.data.response) {
+            return this.$toasted.show(
+              response.data.message,
+              this.$toast_success
+            );
+          } else {
+            return this.$toasted.show(response.data.message, this.$toast_error);
+          }
+        })
+        .catch(error => {
+          this.strError =
+            "Se ha producido un error al modificar el usuario, error: " + error;
+        });
+    }
+  }
+};
+</script>
+
+<style scoped>
+table.b-table[aria-busy="true"] {
+  opacity: 0.7;
+}
+.page-titles .breadcrumb .breadcrumb-item + .breadcrumb-item::before {
+  content: "/";
+}
+.table-responsive {
+  box-shadow: 0px 0px 7px 2px #f2f2f2;
+  padding: 15px;
+}
+</style>

+ 709 - 0
src/views/admin/UsuariosAdminOld.vue

@@ -0,0 +1,709 @@
+<template>
+  <div>
+    <ol class="breadcrumb mb-2">
+      <li class="breadcrumb-item">
+        <router-link :to="{name:'homeAdmin'}">Inicio</router-link>
+      </li>
+      <li class="breadcrumb-item active">Usuarios</li>
+    </ol>
+    <div class="row">
+      <div class="col-12">
+        <div class="card">
+          <div class="card-body">
+            <!-- Manejo de error -->
+            <div class="alert alert-danger alert-rounded" v-if="strError.length != 0">
+              <button type="button" class="close" data-dismiss="alert" aria-label="Close"></button>
+              <i class="material-icons icon-align">report_problem</i>
+              {{ strError }}
+            </div>
+            <h4 class="card-title">Usuarios</h4>
+            <span
+              class="text-dark"
+            >En esta página se pueden visualizar una lista de usuarios disponible.</span>
+            <div class="col-sm-12 mt-3">
+              <form class="form-material">
+                <input
+                  type="text"
+                  class="form-control col-sm-3 mb-4 float-left"
+                  placeholder="Buscar..."
+                  v-model="buscar"
+                />
+                
+              </form>
+
+              <b-table
+                responsive
+                hover
+                table-bordered
+                show-empty
+                :fields="titulos"
+                :items="usuarios"
+                :busy.sync="loading"
+                :current-page="currentPage"
+                :per-page="perPage"
+                :filter="buscar"
+                @filtered="onFiltered"
+              >
+                <!-- Mensaje cuando no hay registros -->
+                <template v-slot:empty="scope">
+                  <p class="text-center">No se encontraron usuarios para mostrar.</p>
+                </template>
+                <!-- Mensaje cuando no se encuentra ningun registro en la busqueda -->
+                <template v-slot:emptyfiltered="scope">
+                  <p class="text-center">Su búsqueda no arrojó resultados.</p>
+                </template>
+                <!-- columna Perfil -->
+                <template v-slot:cell(IDPERFIL)="data">
+                  <label
+                    class="label"
+                    :class="[data.item.IDPERFIL=='ADMINISTRADOR' ?'label-primary' :'label-info']"
+                  >{{ data.item.IDPERFIL }}</label>
+                </template>
+                <!-- columna activo -->
+                <template v-slot:cell(ACTI)="data">
+                  <label class="label label-primary" v-if="data.item.ACTI == null">No definido</label>
+                  <label
+                    class="label"
+                    v-else
+                    :class="[data.item.ACTI ==='S'?'label-success':'label-danger']"
+                  >{{ (data.item.ACTI ==='S')? 'Activado':'Desactivado' }}</label>
+                </template>
+                <!-- Loading al extraer los datos -->
+                <template v-slot:table-busy>
+                  <div class="text-center text-danger my-2">
+                    <b-spinner variant="danger" class="align-middle"></b-spinner>
+                    <p>Cargando...</p>
+                  </div>
+                </template>
+                <!-- Integrar columna de acciones a la tabla -->
+                <template v-slot:cell(actions)="row">
+                  <i
+                    class="material-icons"
+                    data-toggle="tooltip"
+                    title="Ver Usuario"
+                    style="color:#f42849;cursor:pointer"
+                    @click="infoModalEvento(row.item, $event.target)"
+                  >remove_red_eye</i>
+                  <i
+                    class="material-icons"
+                    data-toggle="tooltip"
+                    title="Permisos"
+                    style="color:#f42849;cursor:pointer"
+                    @click="permisosModalEvento(row.item, $event.target)"
+                    v-if="row.item.ACTI !== null && row.item.ACTI !== 'N' "
+                  >lock</i>
+                </template>
+              </b-table>
+              <!-- Paginación de la tabla -->
+              <b-pagination
+                v-model="currentPage"
+                :total-rows="totalRows"
+                :per-page="perPage"
+                align="center"
+                size="md"
+                class="my-0"
+                v-show="usuarios.length > 0"
+              ></b-pagination>
+              <!-- Modal para mostrar información del usuario -->
+              <b-modal
+                hide-backdrop
+                content-class="shadow"
+                :id="infoModal.id"
+                :title="infoModal.title"
+                size="lg"
+              >
+                <div class="row">
+                  <div class="col-lg-12">
+                    <div class="card card-outline-secondary">
+                      <div class="card-body">
+                        <form class="form-horizontal" role="form">
+                          <div class="form-body">
+                            <div class="row">
+                              <div class="col-lg-6 col-md-12">
+                                <div class="form-group row">
+                                  <label
+                                    class="control-label text-right col-md-4 col-4 font-bold"
+                                  >Nombre:</label>
+                                  <div class="col-md-8 col-8">
+                                    <p class="form-control-static">{{ infoModal.content.IDUSNOMBRE}}</p>
+                                  </div>
+                                </div>
+                              </div>
+                              <!--/span-->
+                              <div class="col-lg-6 col-md-12">
+                                <div class="form-group row">
+                                  <label
+                                    class="control-label text-right col-md-4 col-4 font-bold"
+                                  >ID Usuario:</label>
+                                  <div class="col-md-8 col-4">
+                                    <p class="form-control-static">{{ infoModal.content.IDUS}}</p>
+                                  </div>
+                                </div>
+                              </div>
+                              <!--/span-->
+                            </div>
+                            <!--/row-->
+                            <div class="row">
+                              <div class="col-lg-6 col-md-12">
+                                <div class="form-group row">
+                                  <label
+                                    class="control-label text-right col-md-4 col-4 font-bold"
+                                  >Perfil:</label>
+                                  <div class="col-md-8 col-8">
+                                    <p class="form-control-static">{{ infoModal.content.IDPERFIL}}</p>
+                                  </div>
+                                </div>
+                              </div>
+                              <!--/span-->
+                              <div class="col-lg-6 col-md-12">
+                                <div class="form-group row">
+                                  <label
+                                    class="control-label text-right col-md-4 col-4 font-bold"
+                                  >Activo:</label>
+                                  <div class="col-md-8 col-8">
+                                    <p v-if="infoModal.content.ACTI == null">
+                                      <label class="label label-warning mr-2">No definido</label>
+                                      <a
+                                        href="#"
+                                        @click.prevent="cambiarEstadoUsuarioEvento(infoModal.content.IDUS,'activar', $event.target)"
+                                      >Activar</a>
+                                    </p>
+                                    <p v-else>
+                                      <b-form-checkbox
+                                        v-model="userActive"
+                                        value="SI"
+                                        unchecked-value="NO"
+                                        :disabled="infoModal.content.ACTI !== null && infoModal.content.PERM === null ? true: false"
+                                        switch
+                                      >
+                                        <label
+                                          class="label"
+                                          :class="[userActive === 'SI' ? 'label-success' :'label-danger']"
+                                        >{{ userActive }}</label>
+                                      </b-form-checkbox>
+                                    </p>
+                                  </div>
+                                </div>
+                              </div>
+                              <!--/span-->
+                            </div>
+                            <p
+                              class="text-center"
+                              v-show="infoModal.content.ACTI !== null && infoModal.content.PERM === null"
+                            >
+                              Este Usuario no tiene permisos asignados,
+                              <a
+                                href="#"
+                                @click.prevent="permisosModalEvento(infoModal.content, $event.target)"
+                              >asignar permisos</a>.
+                              <br />
+                              <i>Mientras no tenga permisos asignados no podrá iniciar sesión en el Portal de Quiosco.</i>
+                            </p>
+                          </div>
+                        </form>
+                      </div>
+                    </div>
+                  </div>
+                </div>
+                <template v-slot:modal-footer="{  hide }">
+                  <div class="w-100">
+                    <button
+                      id="aceptarInfo"
+                      type="button"
+                      class="btn btn-danger float-right"
+                      @click="hide('forget')"
+                    >Aceptar</button>
+                    <button
+                      v-show="infoModal.content.ACTI == 'S' && userActive === 'NO'"
+                      id="guardar"
+                      type="button"
+                      class="btn btn-outline-danger mr-1 float-right"
+                      @click="cambiarEstadoUsuarioEvento(infoModal.content.IDUS,'desactivar', $event.target)"
+                    >Guardar</button>
+                  </div>
+                </template>
+              </b-modal>
+              <!-- end info modal -->
+
+              <!-- start activar/desactivar usuario modal -->
+              <b-modal
+                content-class="shadow"
+                :id="cambiarEstadoUsuarioModal.id"
+                :title="cambiarEstadoUsuarioModal.title"
+                size="sm"
+                ok-only
+              >
+                <p class="my-1" v-html="cambiarEstadoUsuarioModal.msg"></p>
+                <template v-slot:modal-footer="{ hide }">
+                  <div class="w-100">
+                    <button
+                      id="cambiarEstadoUsuario"
+                      type="button"
+                      class="btn btn-danger float-right"
+                      @click="cambiarEstadoUsuarioModal.estado === 'activar' 
+                      ? cambiarEstadoUser('activar'): cambiarEstadoUser('desactivar')"
+                    >
+                      <span v-if="!loading">
+                        {{ cambiarEstadoUsuarioModal.estado === 'activar'
+                        ? 'Activar': 'Desactivar'}}
+                      </span>
+                      <span
+                        class="spinner-border spinner-grow-sm"
+                        role="status"
+                        aria-hidden="true"
+                        style="color:white"
+                        v-if="loading"
+                      ></span>
+                    </button>
+
+                    <button
+                      type="button"
+                      class="btn btn-secondary mr-2 float-right"
+                      @click="hide('forget')"
+                    >Cancelar</button>
+                  </div>
+                </template>
+              </b-modal>
+              <!-- end activar/desactivar usuario modal -->
+
+             
+
+              <!-- Modal permisos -->
+              <b-modal
+                hide-backdrop
+                content-class="shadow"
+                :id="permisosModal.id"
+                :title="permisosModal.title"
+                size="lg"
+              >
+                <div class="row">
+                  <div class="col-sm-12">
+                    <label class="float-left">
+                      <b>USUARIO:</b>
+                      {{ permisosModal.content.IDUSNOMBRE}}
+                    </label>
+                    <!-- <label class="float-right"> -->
+                    <!-- <h5 class="text-left">Permisos</h5> -->
+
+                    <!-- </label> -->
+                  </div>
+                </div>
+
+                <div class="row">
+                  <div class="col-lg-8 col-sm-12 col-12">
+                    <div class="navigation-filter">
+                      <input
+                        class="form-control mb-2"
+                        type="text"
+                        v-model="treeFilter"
+                        value
+                        placeholder="Buscar..."
+                      />
+                    </div>
+                    <tree
+                      v-if="treeData"
+                      :data="treeData"
+                      @node:selected="onSelect"
+                      :options="optsTree"
+                      :filter="treeFilter"
+                      ref="tree"
+                      class="card card-outline-secondary"
+                    >
+                      <span class="tree-scope" slot-scope="{ node }">
+                        <template v-if="!node.hasChildren()">
+                          <i class="material-icons icon-align" style="color:#f42849">folder_open</i>
+                          {{ node.text }}
+                        </template>
+                        <template v-else>
+                          <i
+                            class="material-icons icon-align"
+                            style="color:#f42849"
+                          >{{ node.expanded() ? 'folder' : 'create_new_folder' }}</i>
+                          {{ node.text }}
+                        </template>
+                      </span>
+                    </tree>
+                  </div>
+                  <div
+                    class="col-lg-4 col-sm-12 col-12 card card-outline-secondary pb-4"
+                    v-if="selectedNode"
+                  >
+                    <h6 class="mt-3 text-center">Establecer permisos</h6>
+                    <!-- checkbox permisos -->
+                    <div class="m-auto p-3 card card-outline-secondary">
+                      <b-form-radio
+                        v-model="permisos.tipo"
+                        name="permisoSinAcceso"
+                        @input="cambiarPermisoRadio"
+                        value="Sin Acceso"
+                      >Sin Acceso</b-form-radio>
+
+                      <b-form-radio
+                        v-model="permisos.tipo"
+                        name="permisoPublicaryVer"
+                        @input="cambiarPermisoRadio"
+                        value="Ver + Publicar"
+                      >Ver + Publicar</b-form-radio>
+
+                      <b-form-radio
+                        v-model="permisos.tipo"
+                        name="permisoVer"
+                        value="Ver"
+                        @input="cambiarPermisoRadio"
+                      >Ver</b-form-radio>
+                    </div>
+                  </div>
+                </div>
+                <template v-slot:modal-footer="{  hide }">
+                  <div class="w-100">
+                    <button
+                      id="guardarPermisos"
+                      type="button"
+                      class="btn btn-danger float-right"
+                      @click="guardarPermisos()"
+                      :disabled="!verBtnGuardarPermisos"
+                    >Guardar</button>
+                    <button
+                      id="cancelarPermisos"
+                      type="button"
+                      class="btn btn-secondary mr-1 float-right"
+                      @click="hide('forget')"
+                    >Cancelar</button>
+
+                    <button
+                      v-if="editandoPermiso"
+                      id="previsualizarPermisos"
+                      @click="cambiarPermisos"
+                      class="btn btn-outline-danger mr-1 float-right"
+                      type="button"
+                      value="Previsualizar"
+                    >Previsualizar</button>
+                  </div>
+                </template>
+              </b-modal>
+              <!-- end modal permisos -->
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+   
+  </div>
+</template>
+
+<script>
+
+import "bootstrap-vue/dist/bootstrap-vue.css";
+import {
+  BTable,
+  BSpinner,
+  BModal,
+  BPagination,
+  BFormCheckbox,
+  BFormRadio,
+  ModalPlugin,
+  BTooltip
+} from "bootstrap-vue";
+import { token } from "../../_mixin/user_mixin.js";
+import LiquorTree from "liquor-tree";
+
+var _ = require("lodash");
+export default {
+  name: "UsuariosAdmin",
+  mixins: [token],
+  components: {
+    BTable,
+    BModal,
+    BSpinner,
+    BPagination,
+    BFormCheckbox,
+    BFormRadio,
+    ModalPlugin,
+    BTooltip,
+    [LiquorTree.name]: LiquorTree
+  },
+  data: () => ({
+    selectedNode: "",
+    treeFilter: "",
+    buscar: "",
+    totalRows: 1,
+    currentPage: 1,
+    perPage: 25,
+    userActive: "NO",
+    verBtnGuardarInfo: false,
+    editandoPermiso: false,
+    verBtnGuardarPermisos: false,
+    infoModal: {
+      id: "info-modal",
+      title: "",
+      content: {}
+    },
+    cambiarEstadoUsuarioModal: {
+      id: "modal-activar",
+      title: "",
+      msg: "",
+      estado: ""
+    },
+    permisosModal: {
+      id: "permisos-modal",
+      title: "",
+      content: {}
+    },
+    permisos: {
+      tipo: "Sin Acceso"
+    },
+    optsTree: {
+      parentSelect: true,
+      filter: {
+        emptyText: "No se ha encontrado nada :( "
+      }
+    },
+    treeData: [],
+    titulos: [
+      {
+        key: "IDUS",
+        label: "ID",
+        sortable: true
+      },
+      {
+        key: "IDUSNOMBRE",
+        label: "Nombre",
+        sortable: true
+      },
+      {
+        key: "IDPERFIL",
+        label: "Perfil",
+        sortable: true
+      },
+      {
+        key: "ACTI",
+        label: "Estado",
+        sortable: true
+      },
+      { key: "actions", label: "Acciones" }
+    ]
+  }),
+  async created() {
+    await this.getUsuarios();
+    await this.getEstantes();
+    this.totalRows = this.usuarios.length;
+  },
+  methods: {
+    cambiarPermisoRadio(permisoSelect) {
+      let arrNode = this.selectedNode.text.split("|");
+      let permisoSeleccionado = arrNode[1];
+      this.editandoPermiso =
+        this.permisos.tipo.trim() !== permisoSeleccionado.trim() ? true : false;
+
+      this.cambiarPermisos();
+    },
+    getJsonPermisosFiltrado() {
+      var dataTree = this.$refs.tree;
+      var data = dataTree.toJSON();
+      var treeCloned = _.cloneDeep(data);
+      // Remover nodo data
+      var startTree = _.omit(treeCloned[0], "data");
+      // Filtrar solo estado expanded y selectable para primer nivel
+      var stateFirst = _.pick(startTree.state, ["expanded", "selectable"]);
+      // Asignar primer estado
+      startTree.state = stateFirst;
+      var firtsChildren = _.values(
+        _.mapValues(startTree.children, function(esta) {
+          return _.omit(esta, ["state", "data"]);
+        })
+      );
+      // Filtros anidados para remover state y data de estantes,categorias y secciones.
+      var filterChild = _.values(
+        _.mapValues(firtsChildren, estantes => {
+          estantes.children = _.values(
+            _.mapValues(estantes.children, seccciones => {
+              seccciones.children = _.values(
+                _.mapValues(seccciones.children, categorias => {
+                  return _.omit(categorias, ["state", "data"]);
+                })
+              );
+              return _.omit(seccciones, ["state", "data"]);
+            })
+          );
+          return _.omit(estantes, ["state", "data"]);
+        })
+      );
+      // Asignar hijos filtrados al arreglo padre
+      startTree.children = filterChild;
+      let treeFiltered = "[" + JSON.stringify(startTree) + "]";
+      return treeFiltered;
+    },
+    guardarPermisos() {
+      let jsonPermisos = this.getJsonPermisosFiltrado();
+
+      let datos = {
+        IDUS: this.permisosModal.content.IDUS,
+        PERM: jsonPermisos
+      };
+      this.sendPermisos(datos).then(data => {
+        this.getUsuarios();
+        //cerrar modales
+        this.$root.$emit("bv::hide::modal", "permisos-modal");
+        this.$root.$emit("bv::hide::modal", "info-modal");
+      });
+    },
+    cambiarPermisos() {
+      this.getFullPath(this.selectedNode);
+      this.editandoPermiso = false;
+    },
+    onSelect(node) {
+      var arrNode = node.text.split("|");
+      if (arrNode.length > 1) {
+        var arrPermiso = arrNode[1];
+        this.permisos.tipo = arrPermiso.trim();
+      }
+      this.selectedNode = node;
+    },
+    getFullPath(nodoSeleccionado) {
+      this.verBtnGuardarPermisos = true;
+      // asignamos el nodo actual para despues iterarlo
+      const nodoActual = [nodoSeleccionado];
+      const fullPathUp = [nodoSeleccionado];
+      const fullPathDown = [nodoSeleccionado];
+      // asignamos los elementos recursivos a fullPathUp y FullPathDown
+      nodoSeleccionado.recurseUp(parent => fullPathUp.unshift(parent));
+      nodoSeleccionado.recurseDown(children => fullPathDown.push(children));
+
+      if (this.permisos.tipo === "Sin Acceso" || this.permisos.tipo === "Ver") {
+        // Recursivos hacia abajo
+        return fullPathDown.forEach(node => {
+          const perm = node.text.split("| ");
+          const text = perm[0];
+          const tipoPermiso = perm[1];
+          return (node.text = text + "| " + this.permisos.tipo);
+        });
+      } else {
+        // Recursivo hacia arriba
+        return fullPathUp.forEach((node, key) => {
+          const perm = node.text.split("| ");
+          const text = perm[0];
+          const tipoPermiso = perm[1];
+          // si es == sin acceso o ver el parent cambiarlo
+          if (tipoPermiso === "Sin Acceso" || tipoPermiso === "Ver") {
+            return (node.text = text + "| " + this.permisos.tipo);
+          }
+          if (nodoSeleccionado.text === node.text) {
+            // Solo cambiar el nodo actual
+            return (node.text = text + "| " + this.permisos.tipo);
+          }
+        });
+      }
+    },
+    infoModalEvento(item, button) {
+      this.infoModal.title = "Información de usuario";
+      this.infoModal.content = item;
+      this.$root.$emit("bv::show::modal", this.infoModal.id, button);
+      this.verBtnGuardarInfo = false;
+      this.userActive = this.infoModal.content.ACTI === "S" ? "SI" : "NO";
+    },
+    permisosModalEvento(item, button) {
+      this.permisosModal.title = "Permisos de usuario";
+      this.permisosModal.content = item;
+      // Obtener los permisos del usuario
+      this.getPermisos(item.IDUS).then(resut => {
+        this.$refs.tree.setModel(this.permisosDatos);
+      });
+      this.$root.$emit("bv::show::modal", this.permisosModal.id, button);
+      this.selectedNode = "";
+      this.verBtnGuardarPermisos = false;
+      this.treeFilter = "";
+    },
+    cambiarEstadoUsuarioEvento(id, estado, button) {
+      this.cambiarEstadoUsuarioModal.estado = estado;
+      if (estado === "activar") {
+        this.cambiarEstadoUsuarioModal.title = "Activar usuario";
+        this.cambiarEstadoUsuarioModal.msg =
+          "¿ Desea activar el usuario con ID: <b>" + id + "</b> en el sistema?";
+      } else {
+        this.cambiarEstadoUsuarioModal.title = "Desactivar usuario";
+        this.cambiarEstadoUsuarioModal.msg =
+          "¿ Desea desactivar el usuario con ID: <b>" +
+          id +
+          "</b> ?, si desactiva al usuario, no podrá <i>iniciar sesión</i> y sus permisos serán <b>removidos</b>.";
+      }
+      this.$root.$emit(
+        "bv::show::modal",
+        this.cambiarEstadoUsuarioModal.id,
+        button
+      );
+    },
+    onFiltered(filteredItems) {
+      // Actualiza el paginador al utilizar el filtro
+      this.totalRows = filteredItems.length;
+      this.currentPage = 1;
+    },
+    cambiarEstadoUser(action) {
+      const idperfil = this.infoModal.content.IDPERFIL;
+      const idus = this.infoModal.content.IDUS;
+      // Insertamos los datos en BD
+      this.sendEstadoUser(idus, action).then(res => {
+        this.$root.$emit("bv::hide::modal", "modal-activar");
+        // Actualizamos los datos del usuario actual
+        this.getUsuarios().then(() => {
+          const setNewContent = this.usuarios.filter(
+            user => user.IDUS === idus && user.IDPERFIL === idperfil
+          );
+          this.infoModal.content = setNewContent[0];
+        });
+      });
+      this.verBtnGuardarInfo = false;
+    },
+    sendPermisos(datos) {
+      return this.$http
+        .post(this.url_api + "/usuario/permisos", datos, this.config_header)
+        .then(response => {
+          if (response.data.response) {
+            return this.$toasted.show(
+              response.data.message,
+              this.$toast_success
+            );
+          } else {
+            return this.$toasted.show(response.data.message, this.$toast_error);
+          }
+        })
+        .catch(error => {
+          this.strError =
+            "Se ha producido un error al guardar los permisos. error: " + error;
+        });
+    },
+    sendEstadoUser(idus, method) {
+      let action =
+        method === "activar" ? "/usuario/activar" : "/usuario/desactivar";
+      return this.$http
+        .post(this.url_api + action, [idus], this.config_header)
+        .then(response => {
+          if (response.data.response) {
+            return this.$toasted.show(
+              response.data.message,
+              this.$toast_success
+            );
+          } else {
+            return this.$toasted.show(response.data.message, this.$toast_error);
+          }
+        })
+        .catch(error => {
+          this.strError =
+            "Se ha producido un error al activar el usuario, error: " + error;
+        });
+    }
+  }
+};
+</script>
+
+<style scoped>
+table.b-table[aria-busy="true"] {
+  opacity: 0.7;
+}
+.page-titles .breadcrumb .breadcrumb-item + .breadcrumb-item::before {
+  content: "/";
+}
+.table-responsive {
+  box-shadow: 0px 0px 7px 2px #f2f2f2;
+  padding: 15px;
+}
+</style>

+ 541 - 0
src/views/public/Categorias.vue

@@ -0,0 +1,541 @@
+<template>
+  <div class="mt-5">
+    <div class="container-fluid whithoutPadding" id="divCategorias">
+      <div class="row pt-4">
+        <div class="col-sm-3">
+          <div class="row mt-3 mb-3">
+            <div class="col-sm-12" v-if="seccionActive">
+              <div
+                class="card text-left shadow-sm w-50"
+                style="border:1px solid #E30425;margin: 0 auto;"
+              >
+                <h6 class="text-danger mt-2 ml-1 p-1">{{ seccionActive.nombre }}</h6>
+                <img class="img-responsive p-3" :src="seccionActive.img" />
+              </div>
+            </div>
+          </div>
+
+          <div class="row" style="background: #E30425 none repeat scroll 0% 0%">
+            <div class="col-sm-12 p-4">
+              <h6 class="pb-1 text-white">BUSCAR</h6>
+              <div class="input-group">
+                <div class="input-group-append">
+                  <span class="input-group-text" id="inputGroup">
+                    <i class="material-icons">search</i>
+                  </span>
+                </div>
+                <input
+                  type="text"
+                  class="form-control"
+                  v-model="buscar"
+                  :placeholder="[tabActive === 'categoria' ? 'Buscar categoría' : 'Buscar archivo']"
+                />
+              </div>
+              <hr />
+
+              <h6 class="pb-1 text-white">ORDENAR POR</h6>
+              <div class="datepicker-trigger">
+                <div class="input-group">
+                  <div class="input-group-append">
+                    <span class="input-group-text" id="basic-addon2">
+                      <i class="material-icons align-middle">date_range</i>
+                    </span>
+                  </div>
+                  <input
+                    type="text"
+                    class="form-control"
+                    id="datepicker-trigger"
+                    placeholder="Selecciona un rango"
+                    :value="formatoFechaCalendario(filtroFecha.fechaInicial,filtroFecha.fechaFinal)"
+                  />
+                </div>
+                <p>
+                  <a
+                    href="#"
+                    @click.prevent="filtroFecha.fechaInicial='';
+                    filtroFecha.fechaFinal='';
+                    filtroFecha.activar = false"
+                    class="btn btn-sm btn-danger bg-dark text-white mt-3 float-right"
+                    v-show="filtroFecha.fechaInicial !== ''&&filtroFecha.fechaFinal !== ''"
+                  >Borrar filtro</a>
+                </p>
+              </div>
+              <AirbnbStyleDatepicker
+                :trigger-element-id="'datepicker-trigger'"
+                :mode="'range'"
+                :fullscreen-mobile="false"
+                :date-one="filtroFecha.fechaInicial"
+                :date-two="filtroFecha.fechaFinal"
+                :end-date="filtroFecha.fechaLimiteCalendario"
+                @date-one-selected="val => { filtroFecha.fechaInicial = val }"
+                @date-two-selected="val => { filtroFecha.fechaFinal = val }"
+                @apply="filtroFecha.activar = true"
+              />
+            </div>
+          </div>
+        </div>
+        <div class="col-sm-9">
+          <div class="container-fluid background-todosEstantes">
+            <div class="col-sm-12">
+              <nav aria-label="breadcrumb">
+                <ol class="breadcrumb">
+                  <li class="breadcrumb-item">
+                    <router-link
+                      class="text-danger"
+                      :to="{name: 'home'}"
+                    >Estantes: {{$route.params.est | capitalize }}</router-link>
+                  </li>
+                  <li class="breadcrumb-item">
+                    <router-link
+                      class="text-danger"
+                      :to="{name: 'estantes', params:  { sec: $route.params.est }}"
+                    >Secciones: {{$route.params.sec | capitalize }}</router-link>
+                  </li>
+                  <li class="breadcrumb-item active" aria-current="page">Categorías</li>
+                </ol>
+              </nav>
+            </div>
+            <div class="col-sm-12 pb-1">
+              <h4 class="text-center mt-5">
+                {{ tabActive === 'categoria' ? 'CATEGORÍAS':'ARCHIVOS' }} EN
+                <span
+                  class="text-danger"
+                >{{ seccionActive.nombre }}</span>
+              </h4>
+              <hr
+                style="border: 3px solid #E30425;
+                width: 40px;
+                border-radius: 30px 30px 30px 30px;"
+              />
+            </div>
+            <!-- navs -->
+            <nav>
+              <div class="nav nav-tabs justify-content-center" id="nav-tab" role="tablist">
+                <a
+                  class="nav-item nav-link active"
+                  id="nav-categorias-tab"
+                  data-toggle="tab"
+                  href="#nav-categorias"
+                  role="tab"
+                  aria-controls="nav-categorias"
+                  aria-selected="true"
+                  @click="tabActive='categoria'"
+                >
+                  Categorías
+                  <span class="badge badge-danger">{{ categoriaFilter.length }}</span>
+                </a>
+                <a
+                  class="nav-item nav-link"
+                  id="nav-documents-tab"
+                  data-toggle="tab"
+                  href="#nav-documents"
+                  role="tab"
+                  aria-controls="nav-documents"
+                  aria-selected="false"
+                  @click="tabActive='files'"
+                >
+                  Archivos
+                  <span class="badge badge-danger">{{ archivosFilter.length }}</span>
+                </a>
+              </div>
+            </nav>
+            <!-- end navs -->
+            <!-- start tabs -->
+            <div class="tab-content" id="nav-tabContent">
+              <div
+                class="tab-pane fade show active"
+                id="nav-categorias"
+                role="tabpanel"
+                aria-labelledby="nav-categorias-tab"
+              >
+                <!-- start categorias -->
+                <div class="row m-3">
+                  <div class="col-sm-12">
+                    <transition-group name="flip-list" tag="div">
+                      <div
+                        v-for="categoria in buscarCategoria"
+                        :key="categoria.IDCA"
+                        class="col-12 col-sm-6 col-md-4 col-lg-3 col-xl-3 list-item"
+                        style="cursor:pointer"
+                      >
+                        <router-link
+                          :to="{ name:'categoria', params:  { cat: slug(categoria.CATE) } }"
+                          tag="div"
+                          class="ribbon-wrapper-reverse card shadow-sm p-1"
+                        >
+                          <h6 class="mb-0 ml-3 mr-3 mt-3">{{ categoria.CATE }}</h6>
+                          <img class="img-responsive p-3" v-lazy="categoria.IDIM" />
+                          <label class="m-1 label text-muted">Publicado el {{categoria.FECR }}</label>
+                        </router-link>
+                      </div>
+                    </transition-group>
+                    <div v-if="loading" class="text-center">
+                      <p>Cargando...</p>
+                      <span
+                        class="spinner-border spinner-grow-sm"
+                        role="status"
+                        aria-hidden="true"
+                        style="color:red"
+                      ></span>
+                    </div>
+                    <p
+                      v-if="mensajeCategoriasVacias"
+                    >No se encontraron categorías para esta sección.</p>
+                    <p
+                      class="text-center"
+                      v-if="buscarCategoria.length === 0 && 
+                      !loading && 
+                      (buscar !== '' || filtroFecha.fechaFinal !== '')"
+                    >No se encontraron resultados para su búsqueda.</p>
+                  </div>
+                </div>
+                <!-- end categorias -->
+              </div>
+              <div
+                class="tab-pane fade"
+                id="nav-documents"
+                role="tabpanel"
+                aria-labelledby="nav-documents-tab"
+              >
+                <!-- start documentos -->
+                <div class="row m-3">
+                  <div class="col-sm-12">
+                    <transition-group name="flip-list" tag="div">
+                      <div
+                        v-for="archivo in buscarArchivos"
+                        :key="archivo.IDAR"
+                        class="col-12 col-sm-6 col-md-4 col-lg-3 col-xl-3 list-item"
+                        style="cursor:pointer"
+                      >
+                        <div
+                          class="card p-1"
+                          @click.prevent="downloadFile(archivo.IDAR,archivo.ARCH)"
+                        >
+                          <img v-lazy="archivo.IDIM" class="card-img-top" />
+                          <div class="card-body">
+                            <h6 class="text-center">{{ archivo.ARCH }}</h6>
+                            <label
+                              class="text-center label text-muted"
+                            >Publicado el {{ archivo.FECR }}</label>
+                          </div>
+                        </div>
+                      </div>
+                    </transition-group>
+                    <p v-if="archivosFilter.length === 0 && !loading" class="text-center">
+                      <img
+                        class="img-responsive"
+                        :src="require('@/assets/images/no-files.png')"
+                        alt="Sin Archivos"
+                        style="max-width:600px"
+                      />
+                    </p>
+                    <p
+                      class="text-center"
+                      v-if="buscarArchivos.length === 0 &&
+                       !loading && 
+                       (buscar !== '' || filtroFecha.fechaFinal !== '')"
+                    >No se encontraron resultados para su búsqueda.</p>
+                  </div>
+                </div>
+                <!-- end documentos -->
+              </div>
+            </div>
+            <!-- end tabs -->
+          </div>
+        </div>
+      </div>
+
+      <!-- start toast de descarga -->
+      <div
+        role="status"
+        aria-live="polite"
+        aria-atomic="true"
+        class="toast"
+        data-autohide="false"
+        data-animation="true"
+        id="toastNew"
+      >
+        <div class="toast-header">
+          <i class="material-icons v-middle">save_alt</i>
+          <strong v-if="downloadingFile">Descargando archivo...</strong>
+          <strong class="mr-auto" v-else-if="downloadedItem.length == 1">
+            Se ha descargado
+            <span class="badge badge-danger badge-pill">1</span> elemento
+          </strong>
+          <strong class="mr-auto" v-else>
+            Se han descargado
+            <span class="badge badge-danger badge-pill">{{ downloadedItem.length }}</span> elementos
+          </strong>
+          <button
+            class="ml-3 mb-1 close"
+            type="button"
+            data-toggle="collapse"
+            data-target="#downloadFiles"
+            aria-expanded="false"
+            aria-controls="downloadFiles"
+          >
+            <i
+              class="material-icons v-middle"
+              @click="toastCollapse=true"
+              v-if="!toastCollapse"
+            >keyboard_arrow_down</i>
+            <i
+              class="material-icons v-middle"
+              @click="toastCollapse=false"
+              v-if="toastCollapse"
+            >keyboard_arrow_up</i>
+          </button>
+          <button
+            type="button"
+            class="ml-2 mb-1 close"
+            data-dismiss="toast"
+            aria-label="Close"
+            @click="downloadedItem= []"
+          >
+            <i class="material-icons v-middle">close</i>
+          </button>
+        </div>
+        <div class="collapse" id="downloadFiles">
+          <div class="toast-body">
+            <div class="row p-1" v-for="item in downloadedItem" :key="item.index">
+              <div class="col-sm-12">
+                <img
+                  class="img_toast"
+                  src="https://statics.solerpalau.com/skin/frontend/solerpalau/default/images/svg/icon_pdf_1.svg"
+                />
+                {{ item.nombre }}
+                <i
+                  class="material-icons spin"
+                  style="color:  rgb(92, 184, 92);vertical-align:middle;float:right"
+                  v-if="item.loading"
+                >sync</i>
+                <i
+                  class="material-icons"
+                  style="color:  rgb(92, 184, 92);vertical-align:middle;float:right"
+                  v-else
+                >check_circle_outline</i>
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+      <!-- end toast downloads -->
+    </div>
+  </div>
+</template>
+<script>
+import moment from "moment";
+
+import { token } from "../../_mixin/user_mixin.js";
+import { downloadMixin } from "../../_mixin/public_mixin.js";
+
+var _ = require("lodash");
+export default {
+  name: "Categorias",
+  mixins: [downloadMixin, token],
+  data() {
+    return {
+      mensajeCategoriasVacias: false,
+      tabActive: "categoria",
+      permisosDatos: [],
+      categoriasPermitidas: [],
+      seccionActive: {
+        IDES: "",
+        IDSE: "",
+        img: "",
+        nombre: ""
+      }
+    };
+  },
+  async mounted() {
+    var secc = await this.getSecciones();
+    var cate = await this.getCategorias();
+    var arch = await this.getArchivosPublicos();
+    this.permisosDatos = JSON.parse(this.$store.getters.permisos);
+
+    Promise.all([secc, cate, arch])
+      .then(values => {
+        this.permisosCategorias();
+      })
+      .catch(err => {
+        console.log(err);
+      });
+  },
+  methods: {
+    permisosCategorias() {
+      if (this.$store.getters.user.role === "colaborador") {
+        let permisosInCategorias = [];
+        // Encontrar los permisos disponibles para cada categoria
+        _.filter(this.permisosDatos[0].children, perm => {
+          let estante = perm.text.split(" | ");
+          let strEstante = estante[0].split(" - ");
+
+          if (this.slug(strEstante[0]) === this.$route.params.est) {
+            _.filter(perm.children, pe => {
+              let seccion = pe.text.split(" | ");
+              let strSeccion = seccion[0].split(" - ");
+
+              if (this.slug(strSeccion[0]) === this.$route.params.sec) {
+                permisosInCategorias.push(pe);
+              }
+            });
+          }
+        });
+        // Filtramos solo los permisos de las categorias que tengan permiso !== Sin Acceso
+        let categoriasPermisos = _.filter(
+          permisosInCategorias[0].children,
+          perm => !perm.text.match("Sin Acceso")
+        );
+
+        // filtramos las categorias que le pertenecen a la seccion activa
+        let categorias = this.categorias.filter(
+          cate => cate.IDSE === this.seccionActive.IDSE
+        );
+
+        // Buscamos las categorias que coincidan con el nombre de las secciones con permisos Ver y/o Ver + Publicar
+        let categoriasPermitidas = _.values(
+          _.mapValues(categoriasPermisos, cate => {
+            let categoria = cate.text.split(" | ");
+            let strCategoria = categoria[0].split(" - ");
+            return _.find(categorias, ["CATE", strCategoria[0]]);
+          })
+        );
+        this.categoriasPermitidas = categoriasPermitidas;
+      }
+      if (this.$store.getters.user.role === "admin") {
+        this.categoriasPermitidas = this.categorias.filter(
+          cate => cate.IDSE === this.seccionActive.IDSE
+        );
+
+        this.mensajeCategoriasVacias =
+          this.categoriasPermitidas.length === 0 ? true : false;
+      }
+    }
+  },
+  computed: {
+    buscarCategoria() {
+      if (
+        this.tabActive === "categoria" &&
+        (this.buscar.length !== 0 || this.filtroFecha.activar)
+      ) {
+        // la funcion filtrarFecha viene de public_mixin.js
+        var categoriasFiltroFecha = this.filtrarFecha("cate");
+        return categoriasFiltroFecha.filter(cate => {
+          return cate.CATE.toLowerCase().match(this.buscar.toLowerCase());
+        });
+      }
+      return this.categoriaFilter;
+    },
+    buscarArchivos() {
+      if (
+        this.tabActive === "files" &&
+        (this.buscar.length !== 0 || this.filtroFecha.activar)
+      ) {
+        this.scrollBottom();
+        var archivosFiltroFecha = this.filtrarFecha("arch");
+        return archivosFiltroFecha.filter(arch => {
+          return arch.ARCH.toLowerCase().match(this.buscar.toLowerCase());
+        });
+      }
+      return this.archivosFilter;
+    },
+    categoriaFilter() {
+      if (this.secciones.length !== 0) {
+        var arrFilter = this.secciones.filter(
+          sec => this.slug(sec.SECC) === this.$route.params.sec
+        );
+        if (arrFilter.length !== 0) {
+          this.seccionActive.IDES = arrFilter[0].IDES;
+          this.seccionActive.IDSE = arrFilter[0].IDSE;
+          this.seccionActive.img = arrFilter[0].IDIM;
+          this.seccionActive.nombre = arrFilter[0].SECC;
+          return this.categoriasPermitidas;
+        }
+        return [];
+      }
+      return [];
+    },
+    archivosFilter() {
+      if (this.archivosPublicos.length !== 0) {
+        return this.archivosPublicos.filter(arch => {
+          return (
+            arch.IDES === this.seccionActive.IDES &&
+            arch.IDSE == this.seccionActive.IDSE &&
+            arch.IDCA == null
+          );
+        });
+      }
+      return [];
+    }
+  }
+};
+</script>
+
+<style scoped>
+#divCategorias {
+  background: #fff;
+}
+.container-fluid.whithoutPadding {
+  padding: 0px 0px !important;
+}
+@keyframes spin {
+  from {
+    transform: rotate(0deg);
+  }
+  to {
+    transform: rotate(360deg);
+  }
+}
+.spin {
+  animation-name: spin;
+  animation-duration: 4000ms;
+  animation-iteration-count: infinite;
+  animation-timing-function: linear;
+}
+.img_toast {
+  width: 28px;
+  margin-right: 8px;
+  margin-top: -6px;
+  float: left;
+}
+.toast_load {
+  background: rgb(248, 103, 103) !important;
+}
+.toast_success {
+  background: rgb(92, 184, 92) !important;
+}
+#toastNew {
+  margin: auto;
+  text-align: center;
+  border-radius: 4px;
+  position: fixed;
+  z-index: 1;
+  right: 10px;
+  bottom: 30px;
+  font-size: 15px;
+  padding: 10px;
+}
+.toast {
+  max-width: 380px !important;
+}
+.card {
+  transition: ease-in 0.3s;
+}
+.card:hover {
+  box-shadow: 0 0.6rem 0.95rem rgba(0, 0, 0, 0.18) !important;
+}
+/* transition flip */
+.flip-list-move {
+  transition: transform 0.8s;
+}
+.list-item {
+  display: inline-block;
+  margin-bottom: 10px;
+}
+a {
+  color: #e30425;
+  text-decoration: none;
+  background-color: transparent;
+}
+</style>
+

+ 369 - 0
src/views/public/CategoriasArchivos.vue

@@ -0,0 +1,369 @@
+<template>
+  <div class="mt-5">
+    <div class="container-fluid whithoutPadding" id="divArchivos">
+      <div class="row pt-4">
+        <div class="col-sm-3">
+          <div class="row mt-3 mb-3">
+            <div class="col-sm-12" v-if="categoriaActive">
+              <div
+                class="card text-left shadow-sm w-50"
+                style="border:1px solid #E30425;margin: 0 auto;"
+              >
+                <h6 class="text-danger mt-2 ml-1 p-1">{{ categoriaActive.nombre }}</h6>
+                <img class="img-responsive p-3" :src="categoriaActive.img" />
+              </div>
+            </div>
+          </div>
+
+          <div class="row" style="background: #E30425 none repeat scroll 0% 0%">
+            <div class="col-sm-12 p-4">
+              <h6 class="pb-1 text-white">BUSCAR</h6>
+              <div class="input-group">
+                <div class="input-group-append">
+                  <span class="input-group-text" id="inputGroup">
+                    <i class="material-icons">search</i>
+                  </span>
+                </div>
+                <input
+                  type="text"
+                  class="form-control"
+                  v-model="buscar"
+                  placeholder="buscar archivo"
+                />
+              </div>
+              <hr />
+
+              <h6 class="pb-1 text-white">ORDENAR POR</h6>
+              <div class="datepicker-trigger">
+                <div class="input-group">
+                  <div class="input-group-append">
+                    <span class="input-group-text" id="basic-addon2">
+                      <i class="material-icons align-middle">date_range</i>
+                    </span>
+                  </div>
+                  <input
+                    type="text"
+                    class="form-control"
+                    id="datepicker-trigger"
+                    placeholder="Selecciona un rango"
+                    :value="formatoFechaCalendario(filtroFecha.fechaInicial,filtroFecha.fechaFinal)"
+                  />
+                </div>
+                <p>
+                  <a
+                    href="#"
+                    @click.prevent="filtroFecha.fechaInicial='';
+                    filtroFecha.fechaFinal='';
+                    filtroFecha.activar = false"
+                    class="btn btn-sm btn-danger bg-dark text-white mt-3 float-right"
+                    v-show="filtroFecha.fechaInicial !== ''&&filtroFecha.fechaFinal !== ''"
+                  >Borrar filtro</a>
+                </p>
+              </div>
+              <AirbnbStyleDatepicker
+                :trigger-element-id="'datepicker-trigger'"
+                :mode="'range'"
+                :fullscreen-mobile="false"
+                :date-one="filtroFecha.fechaInicial"
+                :date-two="filtroFecha.fechaFinal"
+                :end-date="filtroFecha.fechaLimiteCalendario"
+                @date-one-selected="val => { filtroFecha.fechaInicial = val }"
+                @date-two-selected="val => { filtroFecha.fechaFinal = val }"
+                @apply="filtroFecha.activar = true"
+              />
+            </div>
+          </div>
+        </div>
+        <div class="col-sm-9">
+          <div class="container-fluid background-todosEstantes">
+            <div class="col-sm-12">
+              <nav aria-label="breadcrumb">
+                <ol class="breadcrumb">
+                  <li class="breadcrumb-item">
+                    <router-link
+                      class="text-danger"
+                      :to="{name: 'home'}"
+                    >Estantes: {{ $route.params.est }}</router-link>
+                  </li>
+                  <li class="breadcrumb-item">
+                    <router-link
+                      class="text-danger"
+                      :to="{name: 'estantes', params:  { sec: $route.params.est }}"
+                    >Secciones: {{ $route.params.sec }}</router-link>
+                  </li>
+                  <li class="breadcrumb-item">
+                    <router-link
+                      class="text-danger"
+                      :to="{name: 'seccion', params:  { sec: $route.params.sec }}"
+                    >Categoría: {{ $route.params.cat }}</router-link>
+                  </li>
+                  <li class="breadcrumb-item active" aria-current="page">Archivos</li>
+                </ol>
+              </nav>
+            </div>
+            <div class="col-sm-12 pb-1">
+              <h4 class="text-center mt-5">
+                <span
+                  class="badge badge-danger v-middle m-1"
+                  v-if="archivosFilter.length !== 0"
+                >{{ archivosFilter.length }}</span>
+                ARCHIVOS EN
+                <span class="text-danger">{{ categoriaActive.nombre }}</span>
+                <br />
+              </h4>
+              <hr
+                style="border: 3px solid #E30425;
+                width: 40px;
+                border-radius: 30px 30px 30px 30px;"
+              />
+            </div>
+            <!-- start documentos -->
+            <div class="row m-3">
+              <div class="col-sm-12">
+                <transition-group name="flip-list" tag="div">
+                  <div
+                    v-for="archivo in buscarArchivos"
+                    :key="archivo.IDAR"
+                    class="col-12 col-sm-6 col-md-4 col-lg-3 col-xl-2 list-item"
+                  >
+                    <div
+                      class="card p-1"
+                      @click.prevent="downloadFile(archivo.IDAR,archivo.ARCH)"
+                      style="cursor:pointer"
+                    >
+                      <img v-lazy="archivo.IDIM" class="card-img-top" />
+                      <div class="card-body">
+                        <h6 class="text-center">{{ archivo.ARCH }}</h6>
+                        <label class="text-center label text-muted">Publicado el {{ archivo.FECR }}</label>
+                      </div>
+                    </div>
+                  </div>
+                </transition-group>
+                <p v-if="mensajeArchivosVacios" class="text-center">
+                  <!-- No se encontraron archivos. -->
+                  <img
+                    class="img-responsive"
+                    :src="require('@/assets/images/no-files.png')"
+                    alt="Sin Archivos"
+                    style="max-width:600px"
+                  />
+                </p>
+                <p
+                  v-if="buscarArchivos.length === 0 && !loading"
+                >No se encontraron archivos en su busqueda.</p>
+              </div>
+            </div>
+            <!-- end documentos -->
+          </div>
+        </div>
+      </div>
+      <!-- start toast de descarga -->
+      <div
+        role="status"
+        aria-live="polite"
+        aria-atomic="true"
+        class="toast"
+        data-autohide="false"
+        data-animation="true"
+        id="toastNew"
+      >
+        <div class="toast-header">
+          <i class="material-icons v-middle">save_alt</i>
+          <strong v-if="downloadingFile">Descargando archivo...</strong>
+          <strong class="mr-auto" v-else-if="downloadedItem.length == 1">
+            Se ha descargado
+            <span class="badge badge-danger badge-pill">1</span> elemento
+          </strong>
+          <strong class="mr-auto" v-else>
+            Se han descargado
+            <span class="badge badge-danger badge-pill">{{ downloadedItem.length }}</span> elementos
+          </strong>
+          <button
+            class="ml-3 mb-1 close"
+            type="button"
+            data-toggle="collapse"
+            data-target="#downloadFiles"
+            aria-expanded="false"
+            aria-controls="downloadFiles"
+          >
+            <i
+              class="material-icons v-middle"
+              @click="toastCollapse=true"
+              v-if="!toastCollapse"
+            >keyboard_arrow_down</i>
+            <i
+              class="material-icons v-middle"
+              @click="toastCollapse=false"
+              v-if="toastCollapse"
+            >keyboard_arrow_up</i>
+          </button>
+          <button
+            type="button"
+            class="ml-2 mb-1 close"
+            data-dismiss="toast"
+            aria-label="Close"
+            @click="downloadedItem= []"
+          >
+            <i class="material-icons v-middle">close</i>
+          </button>
+        </div>
+        <div class="collapse" id="downloadFiles">
+          <div class="toast-body">
+            <div class="row p-1" v-for="item in downloadedItem" :key="item.index">
+              <div class="col-sm-12">
+                <img
+                  class="img_toast"
+                  src="https://statics.solerpalau.com/skin/frontend/solerpalau/default/images/svg/icon_pdf_1.svg"
+                />
+                {{ item.nombre }}
+                <i
+                  class="material-icons spin"
+                  style="color:  rgb(92, 184, 92);vertical-align:middle;float:right"
+                  v-if="item.loading"
+                >sync</i>
+                <i
+                  class="material-icons"
+                  style="color:  rgb(92, 184, 92);vertical-align:middle;float:right"
+                  v-else
+                >check_circle_outline</i>
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+      <!-- end toast downloads -->
+    </div>
+  </div>
+</template>
+<script>
+import moment from "moment";
+
+import { token } from "../../_mixin/user_mixin.js";
+import { downloadMixin } from "../../_mixin/public_mixin.js";
+
+export default {
+  name: "CategoriasArchivos",
+  mixins: [downloadMixin, token],
+  data() {
+    return {
+      mensajeArchivosVacios: false,
+      categoriaActive: {
+        buscar: "",
+        IDCA: "",
+        IDSE: "",
+        img: "",
+        nombre: ""
+      }
+    };
+  },
+  async mounted() {
+    await this.getSecciones();
+    await this.getCategorias();
+    await this.getArchivosPublicos();
+  },
+  computed: {
+    buscarArchivos() {
+      if (this.buscar.length !== 0 || this.filtroFecha.activar) {
+        // la funcion filtrarFecha viene de public_mixin.js
+        let archivosFiltro = this.filtrarFecha("arch");
+        return archivosFiltro.filter(arch => {
+          return arch.ARCH.toLowerCase().match(this.buscar.toLowerCase());
+        });
+      }
+      // Esperar para establecer el mensaje
+      setTimeout( time =>{
+        this.mensajeArchivosVacios = this.archivosFilter.length === 0 ? true: false;
+      },1000)
+      return this.archivosFilter;
+    },
+    archivosFilter() {
+      if (this.archivosPublicos.length) {
+        var arrFilter = this.categorias.filter(
+          cate => this.slug(cate.CATE) === this.$route.params.cat
+        );
+
+        if (arrFilter.length) {
+          this.categoriaActive.IDCA = arrFilter[0].IDCA;
+          this.categoriaActive.IDSE = arrFilter[0].IDSE;
+          this.categoriaActive.img = arrFilter[0].IDIM;
+          this.categoriaActive.nombre = arrFilter[0].CATE;
+        }
+        return this.archivosPublicos.filter(arch => {
+          return (
+            this.slug(arch.ESTA) == this.$route.params.est &&
+            arch.IDSE == this.categoriaActive.IDSE &&
+            arch.IDCA == this.categoriaActive.IDCA
+          );
+        });
+      }
+     return []; 
+    }
+  }
+};
+</script>
+
+<style scoped>
+#divArchivos {
+  background: #fff;
+}
+
+.container-fluid.whithoutPadding {
+  padding: 0px 0px !important;
+}
+
+@keyframes spin {
+  from {
+    transform: rotate(0deg);
+  }
+  to {
+    transform: rotate(360deg);
+  }
+}
+.spin {
+  animation-name: spin;
+  animation-duration: 4000ms;
+  animation-iteration-count: infinite;
+  animation-timing-function: linear;
+}
+.img_toast {
+  width: 28px;
+  margin-right: 8px;
+  margin-top: -6px;
+  float: left;
+}
+.toast_load {
+  background: rgb(248, 103, 103) !important;
+}
+.toast_success {
+  background: rgb(92, 184, 92) !important;
+}
+#toastNew {
+  margin: auto;
+  text-align: center;
+  border-radius: 4px;
+  position: fixed;
+  z-index: 1;
+  right: 10px;
+  bottom: 30px;
+  font-size: 15px;
+  padding: 10px;
+}
+.toast {
+  max-width: 380px !important;
+}
+.card {
+  transition: ease-in 0.3s;
+}
+.card:hover {
+  box-shadow: 0 0.6rem 0.95rem rgba(0, 0, 0, 0.18) !important;
+}
+/* transition flip */
+.flip-list-move {
+  transition: transform 0.8s;
+}
+.list-item {
+  display: inline-block;
+  margin-bottom: 10px;
+}
+</style>
+

+ 376 - 0
src/views/public/Home.vue

@@ -0,0 +1,376 @@
+<template>
+  <div class="mt-5">
+    <div class="alert alert-danger alert-rounded pt-5 m-3" v-if="strError.length != 0">
+      <button type="button" class="close" data-dismiss="alert" aria-label="Close"></button>
+      <i class="material-icons icon-align">report_problem</i>
+      {{ strError }}
+    </div>
+    <div class="container-fluid background-estantes">
+      <div class="row">
+        <div class="col-12 offset-sm-2 col-sm-9 offset-lg-2 col-lg-9 pb-5">
+          <div class="row">
+            <div class="col-sm-12">
+              <h4 class="text-center mt-5">
+                ÚLTIMOS
+                <span class="text-danger">CAMBIOS</span>
+              </h4>
+              <hr
+                style="border: 3px solid rgb(228, 47, 43);
+                width: 40px;
+                border-radius: 30px 30px 30px 30px;"
+              />
+            </div>
+            <div class="col-sm-12" style="background:#fff;border-radius:30px;">
+              <div class="news-estantes p-2 m-3">
+                <slick ref="slick" :options="slickOptions" v-if="newEstantes.length">
+                  <router-link
+                    :to="{ name:'estantes', params:  { est: slug(estante.ESTA) } }"
+                    tag="a"
+                    class="p-2"
+                    v-for="estante in newEstantes"
+                    :key="estante.IDES"
+                  >
+                    <div class="card p-1">
+                      <img :data-lazy="estante.IDIM" class="img-responsive" :id="estante.IDES" />
+                      <b-tooltip :target="estante.IDES" placement="bottom">
+                        <span class="text-lowercase">{{ estante.ESTA }}</span>
+                      </b-tooltip>
+                    </div>
+                  </router-link>
+                </slick>
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+    <div class="container-fluid whithoutPadding" id="divEstantes">
+      <div class="row">
+        <div class="col-sm-3" style="background: #fff">
+          <div class="row" style="background: #E30425 none repeat scroll 0% 0%">
+            <div class="col-sm-12 p-4">
+              <h6 class="pb-1 text-white">BUSCAR</h6>
+              <div class="input-group">
+                <div class="input-group-append">
+                  <span class="input-group-text" id="inputGroup">
+                    <i class="material-icons">search</i>
+                  </span>
+                </div>
+                <input
+                  type="text"
+                  class="form-control"
+                  v-model="buscar"
+                  placeholder="Escriba su búsqueda"
+                />
+              </div>
+              <hr />
+
+              <h6 class="pb-1 text-white">ORDENAR POR</h6>
+              <div class="datepicker-trigger">
+                <div class="input-group">
+                  <div class="input-group-append">
+                    <span class="input-group-text" id="basic-addon2">
+                      <i class="material-icons align-middle">date_range</i>
+                    </span>
+                  </div>
+                  <input
+                    type="text"
+                    class="form-control"
+                    id="datepicker-trigger"
+                    placeholder="Selecciona un rango"
+                    :value="formatoFechaCalendario(filtroFecha.fechaInicial,filtroFecha.fechaFinal)"
+                  />
+                </div>
+                <p>
+                  <a
+                    href="#"
+                    @click.prevent="filtroFecha.fechaInicial='';
+                    filtroFecha.fechaFinal='';
+                    filtroFecha.activar = false"
+                    class="btn btn-sm btn-danger bg-dark text-white mt-3 float-right"
+                    v-show="filtroFecha.fechaInicial !== ''&&filtroFecha.fechaFinal !== ''"
+                  >Borrar filtro</a>
+                </p>
+              </div>
+              <AirbnbStyleDatepicker
+                :trigger-element-id="'datepicker-trigger'"
+                :mode="'range'"
+                :fullscreen-mobile="false"
+                :date-one="filtroFecha.fechaInicial"
+                :date-two="filtroFecha.fechaFinal"
+                :end-date="filtroFecha.fechaLimiteCalendario"
+                @date-one-selected="val => { filtroFecha.fechaInicial = val }"
+                @date-two-selected="val => { filtroFecha.fechaFinal = val }"
+                @apply="filtroFecha.activar = true"
+              />
+            </div>
+          </div>
+        </div>
+        <div class="col-sm-9" style="background: #fff">
+          <div class="container-fluid background-todosEstantes">
+            <div class="col-sm-12 pb-1">
+              <h4 class="text-center">
+                TODOS LOS
+                <span class="text-danger">ESTANTES</span>
+              </h4>
+              <hr
+                style="border: 3px solid rgb(228, 47, 43);
+                width: 40px;
+                border-radius: 30px 30px 30px 30px;"
+              />
+            </div>
+            <div class="row">
+              <div class="col-sm-12 offset-lg-1 offset-xl-1">
+                <transition-group name="flip-list" tag="div">
+                  <div
+                    v-for="estante in buscarEstante"
+                    :key="estante.IDES"
+                    class="col-12 col-sm-6 col-md-4 col-lg-3 col-xl-2 list-item m-lg-4 m-xl-4 p-4 p-sm-3 p-md-3 p-lg-0 p-xl-0"
+                  >
+                    <router-link
+                      :to="{ name:'estantes', params:  { est: slug(estante.ESTA) } }"
+                      tag="div"
+                      class="ribbon-wrapper-reverse card shadow-sm p-1"
+                      style="cursor:pointer"
+                    >
+                      <h6 class="mb-0 ml-3 mr-3 mt-3">{{ estante.ESTA }}</h6>
+                      <img class="img-responsive p-1" v-lazy="estante.IDIM" />
+                      <label class="m-1 label text-muted">Publicado el {{estante.FECR }}</label>
+                    </router-link>
+                  </div>
+                </transition-group>
+              </div>
+              <div class="col-sm-12">
+                <p
+                  class="text-center"
+                  v-if="buscarEstante.length === 0 && !loading  && 
+                  (buscar !== '' || filtroFecha.fechaFinal !== '') && estantesPermitidos.length !== 0"
+                >No se encontraron resultados para su búsqueda.</p>
+              </div>
+            </div>
+
+            <div
+              v-if="mensajeEstantesVacios"
+              class="alert alert-secondary text-center mt-3"
+              role="alert"
+            >No se encontraron estantes para mostrar.</div>
+
+            <infinite-loading @infinite="infiniteEstante" v-else>
+              <div slot="no-more" class="text-muted"></div>
+              <div slot="no-results">No tenemos estantes... :c</div>
+            </infinite-loading>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+require("../../../node_modules/slick-carousel/slick/slick.css");
+require("../../../node_modules/slick-carousel/slick/slick-theme.css");
+
+import { token } from "../../_mixin/user_mixin.js";
+import { downloadMixin } from "../../_mixin/public_mixin.js";
+
+import slick from "vue-slick";
+import InfiniteLoading from "vue-infinite-loading";
+import { BTooltip } from "bootstrap-vue";
+import moment from "moment";
+var _ = require("lodash");
+export default {
+  name: "Estantes",
+  components: {
+    BTooltip,
+    slick,
+    InfiniteLoading
+  },
+  mixins: [downloadMixin, token],
+  data() {
+    return {
+      porPagina: 6,
+      inicioPagina: 1,
+      estantesTemp: [],
+      estantesPermitidos: [],
+      permisosDatos: [],
+      slickOptions: {
+        centerPadding: "10px",
+        slidesToShow: 6,
+        adaptiveHeight: true,
+        autoplay: true,
+        lazyLoad: "ondemand",
+        responsive: [
+          {
+            breakpoint: 768,
+            settings: {
+              centerMode: true,
+              centerPadding: "15px",
+              slidesToShow: 3
+            }
+          },
+          {
+            breakpoint: 480,
+            settings: {
+              arrows: false,
+              centerMode: true,
+              centerPadding: "15px",
+              slidesToShow: 1
+            }
+          }
+        ]
+      },
+      mensajeEstantesVacios: false
+    };
+  },
+  async mounted() {
+    await this.getEstantes();
+    this.permisosEstantes();
+    this.inicioPagina = 0;
+  },
+  methods: {
+    permisosEstantes() {
+      if (this.$store.getters.user.role === "colaborador") {
+        this.permisosDatos = JSON.parse(this.$store.getters.permisos);
+
+        let estantesConAcceso = _.filter(
+          this.permisosDatos[0].children,
+          perm => !perm.text.match("Sin Acceso")
+        );
+
+        let estantesPermitidos = _.values(
+          _.mapValues(estantesConAcceso, est => {
+            let arrEstanteAndId = est.text.split(" | ");
+            let strEstante = arrEstanteAndId[0].split(" - ");
+            let resultadoFinal = _.find(this.estantes, ["ESTA", strEstante[0]]);
+            return resultadoFinal;
+          })
+        );
+        this.estantesPermitidos = estantesPermitidos;
+      }
+
+      if (this.$store.getters.user.role === "admin") {
+        this.estantesPermitidos = this.estantes;
+        this.mensajeEstantesVacios =
+          this.estantesPermitidos.length === 0 ? true : false;
+      }
+    },
+    infiniteEstante(state) {
+      setTimeout(() => {
+        // Calcular la página
+        const indiceInicio = (this.inicioPagina - 1) * this.porPagina;
+        const indiceFinal =
+          indiceInicio + this.porPagina >= this.estantesPermitidos.length
+            ? this.estantesPermitidos.length
+            : indiceInicio + this.porPagina;
+        if (indiceInicio >= 0) {
+          this.estantesTemp = this.estantesTemp.concat(
+            this.estantesPermitidos.slice(indiceInicio, indiceFinal)
+          );
+        }
+        state.loaded();
+        if (this.inicioPagina * this.porPagina > indiceFinal) {
+          return state.complete();
+        }
+      }, 1000);
+      // Aumentamos pagina cada vez que se ejecute la funcion
+      this.inicioPagina++;
+    }
+  },
+  computed: {
+    newEstantes: function() {
+      return this.estantesPermitidos.slice(0, 7);
+    },
+    buscarEstante() {
+      if (this.filtroFecha.activar || this.buscar.length) {
+        var estantesFiltroFecha = this.filtrarFecha("est");
+        // filtramos por input busqueda
+        return estantesFiltroFecha.filter(estante => {
+          return estante.ESTA.toLowerCase().match(this.buscar.toLowerCase());
+        });
+      }
+      return this.estantesTemp;
+    }
+  }
+};
+</script>
+<style  scoped>
+.card {
+  transition: ease-in 0.3s;
+}
+.card:hover {
+  box-shadow: 0 0.3rem 0.95rem rgba(0, 0, 0, 0.18) !important;
+}
+</style>
+
+<style>
+/* infinite scroll */
+.loading {
+  text-align: center;
+  position: absolute;
+  color: #fff;
+  z-index: 9;
+  background: blue;
+  padding: 8px 18px;
+  border-radius: 5px;
+  left: calc(50% - 45px);
+  top: calc(50% - 18px);
+}
+
+.fade-enter-active,
+.fade-leave-active {
+  transition: opacity 0.5s;
+}
+.fade-enter,
+.fade-leave-to {
+  opacity: 0;
+}
+.slick-prev::before,
+.slick-next::before {
+  font-size: 27px !important;
+  color: #dc3545 !important;
+}
+
+.background-estantes {
+  background-image: url("../../assets/images/fondo-nuevos-estantes.png");
+  height: 100%;
+  background-repeat: no-repeat;
+  background-size: cover;
+  background-position: center center;
+}
+
+.bg-image--2 {
+  background-repeat: no-repeat;
+  background-size: cover;
+  background-position: center center;
+  height: 350px;
+}
+.section__title {
+  margin: 0 auto;
+  max-width: 650px;
+}
+.section__title h2 {
+  color: #333;
+  display: block;
+  font-size: 30px;
+  font-weight: 700;
+  line-height: 30px;
+  margin-bottom: 20px;
+  position: relative;
+  text-transform: uppercase;
+}
+.section__title h2 span {
+  color: #0581d3;
+}
+.container-fluid.whithoutPadding {
+  padding: 0px 0px !important;
+}
+
+/* transition flip */
+.flip-list-move {
+  transition: transform 0.8s;
+}
+.list-item {
+  display: inline-block;
+  margin-bottom: 10px;
+}
+</style>

+ 74 - 0
src/views/public/Loading.vue

@@ -0,0 +1,74 @@
+<template>
+    <div class="container-fluid bg-white">
+      <div class="row">
+        <div class="col-sm-12 pt-5 text-center">
+          <img :src="require('@/assets/images/loading2.gif')" class="" alt="" width="250px">
+          <h5>Cargando... </h5>
+        </div>
+      </div>
+    </div>
+</template>
+
+<script>
+export default {
+  name: "Loading",
+  data() {
+    return {
+      datos: this.$route.params.datos,
+      error: ""
+    };
+  },
+  mounted () {
+    if (this.datos === "error") {
+      return this.alertError('Se ha producido un error al tratar de entrar al módulo de Quiosco.')
+    }
+    if (this.datos) {
+      var datosBs64 = atob(this.datos);
+      var datosParse = JSON.parse(datosBs64);
+
+      var DIPERFIL =
+        datosParse.PERFIL === "QUI.ADMINISTRADOR"
+          ? "ADMINISTRADOR"
+          : "COLABORADOR";
+      var DINUM = datosParse.DINUM.trim();
+      var DIPASS = atob(datosParse.PW).trim();
+
+      if (DINUM !== "" && DIPASS !== "" && DIPERFIL !== "") {
+        this.$store
+          .dispatch("login", { DINUM, DIPASS, DIPERFIL })
+          .then(resp => {
+            this.$router.push("/");
+          })
+          .catch(err => {
+            if (this.$store.getters.authStatus) {
+              this.error = this.$store.getters.msgResponse;
+              return this.alertError(this.$store.getters.msgResponse)
+            }
+            if (!err.data.response) {
+              this.error = err.data.message;
+            }
+          });
+      }else {
+        return this.alertError('Parece que sus credenciales de inicio de sesión en este módulo no se pueden procesar correctamente, contacte al Departamento de Tecnologias de Informacion.');
+      }
+      
+    }    
+  },
+  methods: {    
+    alertError(mensaje) {
+     return this.$swal({
+        title: "Error",
+        text: mensaje,
+        type: "error",
+        confirmButtonText: "Regresar",
+        showCloseButton: false,
+        showLoaderOnConfirm: true
+      }).then(result => {
+        if (result.value) {
+          window.open(this.URL_SOLER, "_parent");
+        }
+      });      
+    }
+  },
+};
+</script>

+ 333 - 0
src/views/public/Login.vue

@@ -0,0 +1,333 @@
+<template>
+  <div>
+    <div class="container" v-if="loading">
+      <div class="row">
+        <div class="col-sm-12 pt-5">
+          <div class="bubblingG">
+            <span id="bubblingG_1"></span>
+            <span id="bubblingG_2"></span>
+            <span id="bubblingG_3"></span>
+          </div>
+        </div>
+      </div>
+    </div>
+
+    <div class="container h-100" style="padding-top: 10rem">
+      <div class="d-flex justify-content-center h-100">
+        <div class="user_card">
+          <div class="d-flex justify-content-center">
+            <div class="brand_logo_container">
+              <img
+                :src="require('@/assets/quiosco-icon.png')"
+                alt="Logo Quiosco"
+                class="brand_logo"
+              />
+            </div>
+          </div>
+          <div class="d-flex justify-content-center form_container">
+            <form class="login">
+              <h5 class="text-center font-weight-light">QUIOSCO</h5>
+              <!-- <span> -->
+              <p class="text-center text-danger">{{ error }}</p>
+              <!-- </span> -->
+              <div class="input-group mb-1">
+                <div class="input-group-append">
+                  <span class="input-group-text">
+                    <i class="material-icons">person</i>
+                  </span>
+                </div>
+                <input
+                  type="text"
+                  required
+                  v-model="DINUM"
+                  class="form-control input_user"
+                  placeholder="Usuario"
+                />
+              </div>
+              <div class="input-group mb-2">
+                <div class="input-group-append">
+                  <span class="input-group-text">
+                    <i class="material-icons">vpn_key</i>
+                  </span>
+                </div>
+                <input
+                  type="password"
+                  required
+                  v-model="DIPASS"
+                  class="form-control input_pass"
+                  placeholder="Contraseña"
+                />
+              </div>
+
+              <div class="input-group mb-2">
+                <div class="input-group-append">
+                  <span class="input-group-text">
+                    <i class="material-icons">lock</i>
+                  </span>
+                </div>
+                <select class="form-control" id="perfil" v-model="DIPERFIL">
+                  <option selected >ADMINISTRADOR</option>
+                  <option>COLABORADOR</option>
+                </select>
+              </div>
+            </form>
+          </div>
+          <div class="d-flex justify-content-center mt-3 login_container">
+            <button
+              type="submit"
+              name="button"
+              class="btn login_btn"
+              @click="loginDirecto()"
+            >Iniciar sesión</button>
+          </div>
+
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+<script>
+export default {
+  name: "Login",
+  data() {
+    return {
+      DINUM: "",
+      DIPASS: "",
+      DIPERFIL: "",
+      loading: "",
+      error: "",
+      user: {}
+    };
+  },
+  methods: {
+    loginDirecto() {
+      let DINUM = this.DINUM;
+      let DIPASS = this.DIPASS;
+      let DIPERFIL = this.DIPERFIL;
+
+      if (DINUM !== "" && DIPASS !== "") {
+        this.loading = true;
+        this.$store
+          .dispatch("login", { DINUM, DIPASS, DIPERFIL })
+          .then(resp => {
+            this.$router.push("/");
+            this.loading = false;
+          })
+          .catch(err => {
+            if(this.$store.getters.authStatus){
+              this.error = this.$store.getters.msgResponse;
+              this.loading = false;
+              return false;
+            }
+            if (!err.data.response) {
+              this.error = err.data.message;
+              this.loading = false;
+            }
+          });
+      }
+    }
+  }
+};
+</script>
+
+<style scoped>
+.user_card {
+  height: 400px;
+  width: 350px;
+  margin-top: auto;
+  margin-bottom: auto;
+  background: #fbfbfb;
+  position: relative;
+  display: flex;
+  justify-content: center;
+  flex-direction: column;
+  padding: 10px;
+  box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
+  -webkit-box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2),
+    0 6px 20px 0 rgba(0, 0, 0, 0.19);
+  -moz-box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2),
+    0 6px 20px 0 rgba(0, 0, 0, 0.19);
+  border-radius: 5px;
+}
+.brand_logo_container {
+  position: absolute;
+  height: 170px;
+  width: 170px;
+  top: -75px;
+  border-radius: 50%;
+  background: #dd0302;
+  padding: 10px;
+  text-align: center;
+}
+.brand_logo {
+  height: 150px;
+  width: 150px;
+  border-radius: 50%;
+  border: 2px solid white;
+}
+.form_container {
+  margin-top: 75px;
+}
+.login_btn {
+  width: 100%;
+  background: #c0392b !important;
+  color: white !important;
+}
+.login_btn:focus {
+  box-shadow: none !important;
+  outline: 0px !important;
+}
+.login_container {
+  padding: 0 2rem;
+}
+.input-group-text {
+  background: #c0392b !important;
+  color: white !important;
+  border: 0 !important;
+  border-radius: 0.25rem 0 0 0.25rem !important;
+}
+.input_user,
+.input_pass:focus {
+  box-shadow: none !important;
+  outline: 0px !important;
+}
+.custom-checkbox .custom-control-input:checked ~ .custom-control-label::before {
+  background-color: #c0392b !important;
+}
+
+/* Loader */
+.bubblingG {
+  width: 90px;
+  height: 56px;
+  position: fixed;
+  z-index: 999;
+  overflow: visible;
+  top: 0;
+  left: 0;
+  bottom: 0;
+  right: 0;
+  margin: auto;
+}
+
+.bubblingG span {
+  display: inline-block;
+  vertical-align: middle;
+  width: 11px;
+  height: 11px;
+  margin: 28px auto;
+  background: rgba(224, 54, 68, 0.9);
+  border-radius: 56px;
+  -o-border-radius: 56px;
+  -ms-border-radius: 56px;
+  -webkit-border-radius: 56px;
+  -moz-border-radius: 56px;
+  animation: bubblingG 0.715s infinite alternate;
+  -o-animation: bubblingG 0.715s infinite alternate;
+  -ms-animation: bubblingG 0.715s infinite alternate;
+  -webkit-animation: bubblingG 0.715s infinite alternate;
+  -moz-animation: bubblingG 0.715s infinite alternate;
+}
+
+#bubblingG_1 {
+  animation-delay: 0s;
+  -o-animation-delay: 0s;
+  -ms-animation-delay: 0s;
+  -webkit-animation-delay: 0s;
+  -moz-animation-delay: 0s;
+}
+
+#bubblingG_2 {
+  animation-delay: 0.2095s;
+  -o-animation-delay: 0.2095s;
+  -ms-animation-delay: 0.2095s;
+  -webkit-animation-delay: 0.2095s;
+  -moz-animation-delay: 0.2095s;
+}
+
+#bubblingG_3 {
+  animation-delay: 0.429s;
+  -o-animation-delay: 0.429s;
+  -ms-animation-delay: 0.429s;
+  -webkit-animation-delay: 0.429s;
+  -moz-animation-delay: 0.429s;
+}
+@keyframes bubblingG {
+  0% {
+    width: 11px;
+    height: 11px;
+    background-color: rgb(245, 0, 0);
+    transform: translateY(0);
+  }
+
+  100% {
+    width: 27px;
+    height: 27px;
+    background-color: rgb(255, 255, 255);
+    transform: translateY(-24px);
+  }
+}
+
+@-o-keyframes bubblingG {
+  0% {
+    width: 11px;
+    height: 11px;
+    background-color: rgb(245, 0, 0);
+    -o-transform: translateY(0);
+  }
+
+  100% {
+    width: 27px;
+    height: 27px;
+    background-color: rgb(255, 255, 255);
+    -o-transform: translateY(-24px);
+  }
+}
+
+@-ms-keyframes bubblingG {
+  0% {
+    width: 11px;
+    height: 11px;
+    background-color: rgb(245, 0, 0);
+    -ms-transform: translateY(0);
+  }
+
+  100% {
+    width: 27px;
+    height: 27px;
+    background-color: rgb(255, 255, 255);
+    -ms-transform: translateY(-24px);
+  }
+}
+
+@-webkit-keyframes bubblingG {
+  0% {
+    width: 11px;
+    height: 11px;
+    background-color: rgb(245, 0, 0);
+    -webkit-transform: translateY(0);
+  }
+
+  100% {
+    width: 27px;
+    height: 27px;
+    background-color: rgb(255, 255, 255);
+    -webkit-transform: translateY(-24px);
+  }
+}
+
+@-moz-keyframes bubblingG {
+  0% {
+    width: 11px;
+    height: 11px;
+    background-color: rgb(245, 0, 0);
+    -moz-transform: translateY(0);
+  }
+
+  100% {
+    width: 27px;
+    height: 27px;
+    background-color: rgb(255, 255, 255);
+    -moz-transform: translateY(-24px);
+  }
+}
+</style>

+ 19 - 0
src/views/public/ProtectedPage.vue

@@ -0,0 +1,19 @@
+<template>
+  <div class="pt-5">
+    <div class="pt-5">
+      <section id="wrapper" class="error-page">
+        <div class="error-box">
+          <div class="error-body text-center">
+            <h1 class="text-danger">401</h1>
+            <h3 class="text-uppercase">Not Authorized!</h3>
+            <p class="text-muted mt-4 mb-4">NO TIENES PERMISOS PARA ACCEDER A ESTE RECURSO.</p>
+            <router-link
+              :to="{name: 'home'}"
+              class="btn btn-danger btn-rounded waves-effect waves-light mb-5"
+            >Regresar a Inicio</router-link>
+          </div>
+        </div>
+      </section>
+    </div>
+  </div>
+</template>

+ 515 - 0
src/views/public/Secciones.vue

@@ -0,0 +1,515 @@
+<template>
+  <div class="mt-5">
+    <div class="container-fluid whithoutPadding" id="divSecciones">
+      <div class="row pt-4">
+        <div class="col-sm-3">
+          <div class="row mt-3 mb-3">
+            <div class="col-sm-12" v-if="estanteActivo">
+              <div
+                class="card text-left shadow-sm w-50"
+                style="border: 1px solid red;margin:0 auto;"
+              >
+                <h6 class="text-danger mt-2 ml-1 p-1">{{ estanteActivo.nombre }}</h6>
+                <img class="img-responsive p-1" :src="estanteActivo.img" />
+              </div>
+            </div>
+          </div>
+
+          <div class="row" style="background: #E30425 none repeat scroll 0% 0%">
+            <div class="col-sm-12 p-4">
+              <h6 class="pb-1 text-white">BUSCAR</h6>
+              <div class="input-group">
+                <div class="input-group-append">
+                  <span class="input-group-text" id="inputGroup">
+                    <i class="material-icons">search</i>
+                  </span>
+                </div>
+                <input
+                  type="text"
+                  class="form-control"
+                  v-model="buscar"
+                  :placeholder="[tabActive === 'section' ? 'Buscar sección' : 'Buscar archivo']"
+                />
+              </div>
+              <hr />
+
+              <h6 class="pb-1 text-white">ORDENAR POR</h6>
+              <div class="datepicker-trigger">
+                <div class="input-group">
+                  <div class="input-group-append">
+                    <span class="input-group-text" id="basic-addon2">
+                      <i class="material-icons align-middle">date_range</i>
+                    </span>
+                  </div>
+                  <input
+                    type="text"
+                    class="form-control"
+                    id="datepicker-trigger"
+                    placeholder="Selecciona un rango"
+                    :value="formatoFechaCalendario(filtroFecha.fechaInicial,filtroFecha.fechaFinal)"
+                  />
+                </div>
+                <p>
+                  <a
+                    href="#"
+                    @click.prevent="filtroFecha.fechaInicial='';
+                    filtroFecha.fechaFinal='';
+                    filtroFecha.activar = false"
+                    class="btn btn-sm btn-danger bg-dark text-white mt-3 float-right"
+                    v-show="filtroFecha.fechaInicial !== ''&&filtroFecha.fechaFinal !== ''"
+                  >Borrar filtro</a>
+                </p>
+              </div>
+              <AirbnbStyleDatepicker
+                :trigger-element-id="'datepicker-trigger'"
+                :mode="'range'"
+                :fullscreen-mobile="false"
+                :date-one="filtroFecha.fechaInicial"
+                :date-two="filtroFecha.fechaFinal"
+                :end-date="filtroFecha.fechaLimiteCalendario"
+                @date-one-selected="val => { filtroFecha.fechaInicial = val }"
+                @date-two-selected="val => { filtroFecha.fechaFinal = val }"
+                @apply="filtroFecha.activar = true"
+              />
+            </div>
+          </div>
+        </div>
+        <div class="col-sm-9">
+          <div class="container-fluid background-todosEstantes">
+            <div class="col-sm-12">
+              <nav aria-label="breadcrumb">
+                <ol class="breadcrumb">
+                  <li class="breadcrumb-item">
+                    <router-link
+                      :to="{ name: 'home' }"
+                      class="text-danger"
+                    >Estantes: {{ $route.params.est | capitalize }}</router-link>
+                  </li>
+                  <li class="breadcrumb-item active" aria-current="page">Secciones</li>
+                </ol>
+              </nav>
+            </div>
+            <div class="col-sm-12 pb-1">
+              <h4 class="text-center mt-5">
+                {{ tabActive === 'section' ? 'SECCIONES':'ARCHIVOS' }} EN
+                <span
+                  class="text-danger"
+                >{{ estanteActivo.nombre }}</span>
+              </h4>
+              <hr
+                style="border: 3px solid #E30425;
+                width: 40px;
+                border-radius: 30px 30px 30px 30px;"
+              />
+            </div>
+            <nav>
+              <div class="nav nav-tabs justify-content-center" id="nav-tab" role="tablist">
+                <a
+                  class="nav-item nav-link active"
+                  id="nav-secciones-tab"
+                  data-toggle="tab"
+                  href="#nav-sections"
+                  role="tab"
+                  aria-controls="nav-sections"
+                  aria-selected="true"
+                  @click="tabActive='section'"
+                >
+                  Secciones
+                  <span class="badge badge-danger">{{ seccionesFilter.length }}</span>
+                </a>
+                <a
+                  class="nav-item nav-link"
+                  id="nav-documents-tab"
+                  data-toggle="tab"
+                  href="#nav-documents"
+                  role="tab"
+                  aria-controls="nav-documents"
+                  aria-selected="false"
+                  @click="tabActive='files'"
+                >
+                  Archivos
+                  <span class="badge badge-danger">{{ archivosFilter.length }}</span>
+                </a>
+              </div>
+            </nav>
+            <div class="tab-content" id="nav-tabContent">
+              <div
+                class="tab-pane fade show active"
+                id="nav-sections"
+                role="tabpanel"
+                aria-labelledby="nav-secciones-tab"
+              >
+                <!-- start secciones -->
+                <div class="row m-3">
+                  <div class="col-sm-12">
+                    <transition-group name="flip-list" tag="a">
+                      <div
+                        v-for="seccion in buscarSeccion"
+                        :key="seccion.IDSE"
+                        class="col-12 col-sm-6 col-md-4 col-lg-3 col-xl-3 list-item"
+                        style="cursor:pointer"
+                      >
+                        <router-link
+                          :to="{ name:'seccion', params:  { sec: slug( seccion.SECC ) } }"
+                          tag="div"
+                          class="ribbon-wrapper-reverse card shadow-sm p-1"
+                        >
+                          <h6 class="mb-0 ml-3 mr-3 mt-3">{{ seccion.SECC }}</h6>
+                          <img class="img-responsive p-1" v-lazy="seccion.IDIM" />
+                          <label class="m-1 label text-muted">Publicado el {{ seccion.FECR }}</label>
+                        </router-link>
+                      </div>
+                    </transition-group>
+                    <div v-if="loading" class="text-center">
+                      <p>Cargando...</p>
+                      <span
+                        class="spinner-border spinner-grow-sm"
+                        role="status"
+                        aria-hidden="true"
+                        style="color:red"
+                      ></span>
+                    </div>
+                    <p v-if="mensajeSeccionesVacias">No se encontraron secciones para este estante.</p>
+                    <p
+                      class="text-center"
+                      v-if="buscarSeccion.length === 0 &&
+                      !loading && 
+                      (buscar !== '' || filtroFecha.fechaFinal !== '')"
+                    >No se encontraron resultados para su búsqueda.</p>
+                  </div>
+                </div>
+                <!-- end secciones -->
+              </div>
+              <div
+                class="tab-pane fade"
+                id="nav-documents"
+                role="tabpanel"
+                aria-labelledby="nav-documents-tab"
+              >
+                <!-- start documentos -->
+                <div class="row m-3">
+                  <div class="col-sm-12">
+                    <transition-group name="flip-list" tag="div">
+                      <div
+                        v-for="archivo in buscarArchivos"
+                        :key="archivo.IDAR"
+                        class="col-12 col-sm-6 col-md-4 col-lg-3 col-xl-3 list-item"
+                        style="cursor:pointer"
+                      >
+                        <div
+                          class="card p-1"
+                          @click.prevent="downloadFile(archivo.IDAR,archivo.ARCH)"
+                        >
+                          <img v-lazy="archivo.IDIM" class="card-img-top" />
+                          <div class="card-body">
+                            <h6 class="text-center">{{ archivo.ARCH }}</h6>
+                            <label
+                              class="text-center label text-muted"
+                            >Publicado el {{ archivo.FECR }}</label>
+                          </div>
+                        </div>
+                      </div>
+                    </transition-group>
+                    <p v-if="archivosFilter.length === 0 && !loading" class="text-center">
+                      <img
+                        class="img-responsive"
+                        :src="require('@/assets/images/no-files.png')"
+                        alt="Sin Archivos"
+                        style="max-width:600px"
+                      />
+                    </p>
+                    <p
+                      class="text-center"
+                      v-if="buscarArchivos.length === 0 && !loading  && 
+                      (buscar !== '' || filtroFecha.fechaFinal !== '')"
+                    >No se encontraron resultados para su búsqueda.</p>
+                  </div>
+                </div>
+                <!-- end documentos -->
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+      <!-- start toast de descarga -->
+      <div
+        role="status"
+        aria-live="polite"
+        aria-atomic="true"
+        class="toast"
+        data-autohide="false"
+        data-animation="true"
+        id="toastNew"
+      >
+        <div class="toast-header">
+          <i class="material-icons v-middle">save_alt</i>
+          <strong v-if="downloadingFile">Descargando archivo...</strong>
+          <strong class="mr-auto" v-else-if="downloadedItem.length == 1">
+            Se ha descargado
+            <span class="badge badge-danger badge-pill">1</span> elemento
+          </strong>
+          <strong class="mr-auto" v-else>
+            Se han descargado
+            <span class="badge badge-danger badge-pill">{{ downloadedItem.length }}</span> elementos
+          </strong>
+          <button
+            class="ml-3 mb-1 close"
+            type="button"
+            data-toggle="collapse"
+            data-target="#downloadFiles"
+            aria-expanded="false"
+            aria-controls="downloadFiles"
+          >
+            <i
+              class="material-icons v-middle"
+              @click="toastCollapse=true"
+              v-if="!toastCollapse"
+            >keyboard_arrow_down</i>
+            <i
+              class="material-icons v-middle"
+              @click="toastCollapse=false"
+              v-if="toastCollapse"
+            >keyboard_arrow_up</i>
+          </button>
+          <button
+            type="button"
+            class="ml-2 mb-1 close"
+            data-dismiss="toast"
+            aria-label="Close"
+            @click="downloadedItem= []"
+          >
+            <i class="material-icons v-middle">close</i>
+          </button>
+        </div>
+        <div class="collapse" id="downloadFiles">
+          <div class="toast-body">
+            <div class="row p-1" v-for="item in downloadedItem" :key="item.index">
+              <div class="col-sm-12">
+                <img
+                  class="img_toast"
+                  src="https://statics.solerpalau.com/skin/frontend/solerpalau/default/images/svg/icon_pdf_1.svg"
+                />
+                {{ item.nombre }}
+                <i
+                  class="material-icons spin"
+                  style="color:  rgb(92, 184, 92);vertical-align:middle;float:right"
+                  v-if="item.loading"
+                >sync</i>
+                <i
+                  class="material-icons"
+                  style="color:  rgb(92, 184, 92);vertical-align:middle;float:right"
+                  v-else
+                >check_circle_outline</i>
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+      <!-- end toast downloads -->
+    </div>
+  </div>
+</template>
+<script>
+import { token } from "../../_mixin/user_mixin.js";
+import { downloadMixin } from "../../_mixin/public_mixin.js";
+var _ = require("lodash");
+export default {
+  name: "Secciones",
+  mixins: [downloadMixin, token],
+  components: {},
+  data() {
+    return {
+      mensajeSeccionesVacias: false,
+      tabActive: "section",
+      permisosDatos: [],
+      seccionesPermitidas: [],
+      estanteActivo: {
+        IDES: "",
+        img: "",
+        nombre: ""
+      }
+    };
+  },
+  async mounted() {
+    var est = await this.getEstantes();
+    var secc = await this.getSecciones();
+    var arch = await this.getArchivosPublicos();
+    this.permisosDatos = JSON.parse(this.$store.getters.permisos);
+
+    Promise.all([est, secc, arch])
+      .then(values => {
+        this.permisosSecciones();
+      })
+      .catch(err => {
+        console.log(err);
+      });
+  },
+  methods: {
+    permisosSecciones() {
+      if (this.$store.getters.user.role === "colaborador") {
+        // Buscamos los permisos que coincidan con el estante activo
+        let seccionesInEstante = _.values(
+          _.filter(this.permisosDatos[0].children, perm => {
+            let seccion = perm.text.split(" | ");
+            let strPermiso = seccion[0].split(" - ");
+            return strPermiso[0] === this.estanteActivo.nombre;
+          })
+        );
+        // Filtramos solo los permisos de las secciones que tengan permiso !== Sin Acceso
+        let seccionesPermisos = _.filter(
+          seccionesInEstante[0].children,
+          perm => !perm.text.match("Sin Acceso")
+        );
+        // filtramos las secciones que le pertenecen al estante activo
+        let secciones = this.secciones.filter(
+          secc => secc.IDES === this.estanteActivo.IDES
+        );
+        // Buscamos las secciones que coincidan con el nombre de las secciones con permisos Ver y/o Ver + Publicar
+        let seccionesPermitidas = _.values(
+          _.mapValues(seccionesPermisos, secc => {
+            let seccion = secc.text.split(" | ");
+            let strSeccion = seccion[0].split(" - ");
+            return _.find(secciones, ["SECC", strSeccion[0]]);
+          })
+        );
+        this.seccionesPermitidas = seccionesPermitidas;
+      }
+      if (this.$store.getters.user.role === "admin") {
+        this.seccionesPermitidas = this.secciones.filter(
+          secc => secc.IDES === this.estanteActivo.IDES
+        );
+        this.mensajeSeccionesVacias =
+          this.seccionesPermitidas.length === 0 ? true : false;
+      }
+    }
+  },
+  computed: {
+    buscarSeccion() {
+      if (
+        this.tabActive === "section" &&
+        (this.buscar.length !== 0 || this.filtroFecha.activar)
+      ) {
+        // la funcion filtrarFecha viene de public_mixin.js
+        var seccionesFiltroFecha = this.filtrarFecha("sec");
+        this.scrollBottom();
+        return seccionesFiltroFecha.filter(secc => {
+          return secc.SECC.toLowerCase().match(this.buscar.toLowerCase());
+        });
+      }
+      return this.seccionesFilter;
+    },
+    seccionesFilter() {
+      if (this.estantes.length !== 0) {
+        var arrFilter = this.estantes.filter(
+          esta => this.slug(esta.ESTA) === this.$route.params.est
+        );
+        if (arrFilter.length !== 0) {
+          this.estanteActivo.IDES = arrFilter[0].IDES;
+          this.estanteActivo.img = arrFilter[0].IDIM;
+          this.estanteActivo.nombre = arrFilter[0].ESTA;
+          return this.seccionesPermitidas;
+        }
+        return [];
+      }
+      return [];
+    },
+    buscarArchivos() {
+      if (
+        this.tabActive === "files" &&
+        (this.buscar.length !== 0 || this.filtroFecha.activar)
+      ) {
+        this.scrollBottom();
+        var archivosFiltroFecha = this.filtrarFecha("arch");
+        return archivosFiltroFecha.filter(arch => {
+          return arch.ARCH.toLowerCase().match(this.buscar.toLowerCase());
+        });
+      }
+      return this.archivosFilter;
+    },
+    archivosFilter() {
+      if (this.archivosPublicos.length !== 0) {
+        return this.archivosPublicos.filter(arch => {
+          return (
+            arch.IDES === this.estanteActivo.IDES &&
+            arch.IDSE == null &&
+            arch.IDCA == null
+          );
+        });
+      }
+      return [];
+    }
+  }
+};
+</script>
+
+<style scoped>
+#divSecciones {
+  background: #fff;
+}
+.container-fluid.whithoutPadding {
+  padding: 0px 0px !important;
+}
+@keyframes spin {
+  from {
+    transform: rotate(0deg);
+  }
+  to {
+    transform: rotate(360deg);
+  }
+}
+.spin {
+  animation-name: spin;
+  animation-duration: 4000ms;
+  animation-iteration-count: infinite;
+  animation-timing-function: linear;
+}
+.img_toast {
+  width: 28px;
+  margin-right: 8px;
+  margin-top: -6px;
+  float: left;
+}
+.toast_load {
+  background: rgb(248, 103, 103) !important;
+}
+.toast_success {
+  background: rgb(92, 184, 92) !important;
+}
+#toastNew {
+  margin: auto;
+  text-align: center;
+  border-radius: 4px;
+  position: fixed;
+  z-index: 1;
+  right: 10px;
+  bottom: 30px;
+  font-size: 15px;
+  padding: 10px;
+}
+.toast {
+  max-width: 380px !important;
+}
+.card {
+  transition: ease-in 0.3s;
+}
+.card:hover {
+  box-shadow: 0 0.6rem 0.95rem rgba(0, 0, 0, 0.18) !important;
+}
+/* transition flip */
+.flip-list-move {
+  transition: transform 0.8s;
+}
+.list-item {
+  display: inline-block;
+  margin-bottom: 10px;
+}
+a {
+  color: #e30425;
+  text-decoration: none;
+  background-color: transparent;
+}
+
+a:hover {
+  color: #dc3545;
+}
+</style>