15 Commits

Author SHA1 Message Date
  mramirezg 5eb73d6786 Fusion 2 weeks ago
  mramirezg ea5d0ab2d9 Merge branch 'delta' of http://gitea.jumapacelaya.gob.mx:3000/dbaylonv/Sistema_mantenimiento_correctivo_y_preventivo 2 weeks ago
  mramirezg 0c754d72c2 Se añadio el dashboard para vizualizar los resultados y estadisticas de las encuestas enviadas y respondidas 3 weeks ago
  MRAMIREZG 5fe9056a00 fusion de delta a main 10 de febrero 2026 4 weeks ago
  mramirezg 1ac333173c Se actualizo la version de Vaadin a la nueva 24.8 + Spring Boot v3.5.8 4 weeks ago
  mramirezg dd94512a1b limpieza de codigo siguiendo buenas paracticas y tambien se cambiaron los logs con un sistema mas robusto 1 month ago
  mramirezg 33d988b052 se soluciono un problema de vulnerabilidad en la cookie de session 1 month ago
  mramirezg 8db9020309 se arreglo un error que no mantenia los valores seleccionados de colores y temas claro u obscuro 1 month ago
  mramirezg e71a863fba Cambio de colores, tipo de letra y diseño de la interfaz, asi como tambien se agrego una nueva vista de configuracion donde se pueden cambiar todos estos parametros 1 month ago
  mramirezg ac72f4d828 Update PlanAnualView.java 1 month ago
  mramirezg 0f65ece163 Ya se agrego la funcion de enviar los correos desde el dialog de mantenimientos faltantes 1 month ago
  mramirezg 6d89c36f59 Se agrego el boton en la vista principal para obtener todos los mantenimientos que aun no realizan encuesta, falta agregar la funcionalidad de enviar los correos masivamente 1 month ago
  mramirezg d5e4f9382d Se agrego formato al reporte de tickets para que tenga mejor apariencia 2 months ago
  mramirezg cf6544619c Se corrigio el problema de que no se filtraban bien los tickets por fecha desde y hasta, asi como tambien se cambio a una forma mas vizual de cuando esta generando el excel con lo tickes en el perdiodo indicado. 2 months ago
  mramirezg 6f93fef9c2 ajustes en la encuesta, se agrefo un campo para que el usuario pueda escribir comntarios 2 months ago
31 changed files with 5055 additions and 11446 deletions
Unified View
  1. +3594
    -10844
      package-lock.json
  2. +50
    -48
      package.json
  3. +3
    -3
      pom.xml
  4. BIN
      src/main/bundles/dev.bundle
  5. +20
    -180
      src/main/frontend/themes/sistema-mantenimiento/styles.css
  6. +5
    -3
      src/main/java/mx/gob/jumapacelaya/Application.java
  7. +48
    -66
      src/main/java/mx/gob/jumapacelaya/api/RedmineClient.java
  8. +0
    -1
      src/main/java/mx/gob/jumapacelaya/api/SecurityService.java
  9. +16
    -33
      src/main/java/mx/gob/jumapacelaya/controller/SecurityConfiguration.java
  10. +0
    -1
      src/main/java/mx/gob/jumapacelaya/datasource/MysqlDataSource.java
  11. +0
    -1
      src/main/java/mx/gob/jumapacelaya/datasource/OracleDataSource.java
  12. +38
    -0
      src/main/java/mx/gob/jumapacelaya/models/encuestas/ConteoEncuestas.java
  13. +68
    -0
      src/main/java/mx/gob/jumapacelaya/models/encuestas/ConteoRespuestas.java
  14. +102
    -0
      src/main/java/mx/gob/jumapacelaya/models/encuestas/MantenimientosSinEncuesta.java
  15. +147
    -69
      src/main/java/mx/gob/jumapacelaya/services/DatabaseService.java
  16. +8
    -12
      src/main/java/mx/gob/jumapacelaya/services/EmailService.java
  17. +1
    -1
      src/main/java/mx/gob/jumapacelaya/services/LdapService.java
  18. +261
    -113
      src/main/java/mx/gob/jumapacelaya/ui/ActDiariaView.java
  19. +203
    -0
      src/main/java/mx/gob/jumapacelaya/ui/ConfiguracionView.java
  20. +16
    -5
      src/main/java/mx/gob/jumapacelaya/ui/DetallesMantView.java
  21. +14
    -5
      src/main/java/mx/gob/jumapacelaya/ui/EncuestaView.java
  22. +28
    -7
      src/main/java/mx/gob/jumapacelaya/ui/MainLayout.java
  23. +6
    -8
      src/main/java/mx/gob/jumapacelaya/ui/MantCorrectivoView.java
  24. +7
    -12
      src/main/java/mx/gob/jumapacelaya/ui/MantenimientoView.java
  25. +214
    -28
      src/main/java/mx/gob/jumapacelaya/ui/PlanAnualView.java
  26. +200
    -0
      src/main/java/mx/gob/jumapacelaya/ui/ResultEncuestasView.java
  27. +0
    -3
      src/main/java/mx/gob/jumapacelaya/ui/login/LoginView.java
  28. BIN
      src/main/resources/META-INF/resources/images/LOGO_admon2.png
  29. BIN
      src/main/resources/META-INF/resources/images/imgCorreo/imgEncuesta.png
  30. +5
    -2
      src/main/resources/application.properties
  31. +1
    -1
      tsconfig.json

+ 3594
- 10844
package-lock.json
File diff suppressed because it is too large
View File


+ 50
- 48
package.json View File

@ -4,42 +4,43 @@
"type": "module", "type": "module",
"dependencies": { "dependencies": {
"@polymer/polymer": "3.5.2", "@polymer/polymer": "3.5.2",
"@vaadin/bundles": "24.5.5",
"@vaadin/bundles": "24.8.0",
"@vaadin/common-frontend": "0.0.19", "@vaadin/common-frontend": "0.0.19",
"@vaadin/polymer-legacy-adapter": "24.5.5",
"@vaadin/react-components": "24.5.5",
"@vaadin/react-components-pro": "24.5.5",
"@vaadin/polymer-legacy-adapter": "24.8.0",
"@vaadin/react-components": "24.8.0",
"@vaadin/react-components-pro": "24.8.0",
"@vaadin/vaadin-development-mode-detector": "2.0.7", "@vaadin/vaadin-development-mode-detector": "2.0.7",
"@vaadin/vaadin-lumo-styles": "24.5.5",
"@vaadin/vaadin-material-styles": "24.5.5",
"@vaadin/vaadin-themable-mixin": "24.5.5",
"@vaadin/vaadin-lumo-styles": "24.8.0",
"@vaadin/vaadin-material-styles": "24.8.0",
"@vaadin/vaadin-themable-mixin": "24.8.0",
"@vaadin/vaadin-usage-statistics": "2.1.3", "@vaadin/vaadin-usage-statistics": "2.1.3",
"construct-style-sheets-polyfill": "3.1.0", "construct-style-sheets-polyfill": "3.1.0",
"date-fns": "2.29.3", "date-fns": "2.29.3",
"lit": "3.2.1",
"proj4": "2.12.1",
"lit": "3.3.0",
"proj4": "2.15.0",
"react": "18.3.1", "react": "18.3.1",
"react-dom": "18.3.1", "react-dom": "18.3.1",
"react-router-dom": "6.28.0",
"react-router": "7.6.1",
"signature_pad": "4.1.5" "signature_pad": "4.1.5"
}, },
"devDependencies": { "devDependencies": {
"@babel/preset-react": "7.26.3",
"@preact/signals-react-transform": "0.4.0",
"@rollup/plugin-replace": "6.0.1",
"@rollup/pluginutils": "5.1.3",
"@types/react": "18.3.13",
"@types/react-dom": "18.3.1",
"@vitejs/plugin-react": "4.3.3",
"@babel/preset-react": "7.27.1",
"@preact/signals-react-transform": "0.5.1",
"@rollup/plugin-replace": "6.0.2",
"@rollup/pluginutils": "5.1.4",
"@types/react": "18.3.23",
"@types/react-dom": "18.3.7",
"@vitejs/plugin-react": "4.5.0",
"async": "3.2.6", "async": "3.2.6",
"glob": "10.4.5",
"glob": "11.0.2",
"magic-string": "0.30.17",
"rollup-plugin-brotli": "3.1.0", "rollup-plugin-brotli": "3.1.0",
"rollup-plugin-visualizer": "5.12.0",
"rollup-plugin-visualizer": "5.14.0",
"strip-css-comments": "5.0.0", "strip-css-comments": "5.0.0",
"transform-ast": "2.4.4", "transform-ast": "2.4.4",
"typescript": "5.6.3",
"vite": "5.4.11",
"vite-plugin-checker": "0.8.0",
"typescript": "5.8.3",
"vite": "6.3.5",
"vite-plugin-checker": "0.9.3",
"workbox-build": "7.3.0", "workbox-build": "7.3.0",
"workbox-core": "7.3.0", "workbox-core": "7.3.0",
"workbox-precaching": "7.3.0" "workbox-precaching": "7.3.0"
@ -47,47 +48,48 @@
"vaadin": { "vaadin": {
"dependencies": { "dependencies": {
"@polymer/polymer": "3.5.2", "@polymer/polymer": "3.5.2",
"@vaadin/bundles": "24.5.5",
"@vaadin/bundles": "24.8.0",
"@vaadin/common-frontend": "0.0.19", "@vaadin/common-frontend": "0.0.19",
"@vaadin/polymer-legacy-adapter": "24.5.5",
"@vaadin/react-components": "24.5.5",
"@vaadin/react-components-pro": "24.5.5",
"@vaadin/polymer-legacy-adapter": "24.8.0",
"@vaadin/react-components": "24.8.0",
"@vaadin/react-components-pro": "24.8.0",
"@vaadin/vaadin-development-mode-detector": "2.0.7", "@vaadin/vaadin-development-mode-detector": "2.0.7",
"@vaadin/vaadin-lumo-styles": "24.5.5",
"@vaadin/vaadin-material-styles": "24.5.5",
"@vaadin/vaadin-themable-mixin": "24.5.5",
"@vaadin/vaadin-lumo-styles": "24.8.0",
"@vaadin/vaadin-material-styles": "24.8.0",
"@vaadin/vaadin-themable-mixin": "24.8.0",
"@vaadin/vaadin-usage-statistics": "2.1.3", "@vaadin/vaadin-usage-statistics": "2.1.3",
"construct-style-sheets-polyfill": "3.1.0", "construct-style-sheets-polyfill": "3.1.0",
"date-fns": "2.29.3", "date-fns": "2.29.3",
"lit": "3.2.1",
"proj4": "2.12.1",
"lit": "3.3.0",
"proj4": "2.15.0",
"react": "18.3.1", "react": "18.3.1",
"react-dom": "18.3.1", "react-dom": "18.3.1",
"react-router-dom": "6.28.0",
"react-router": "7.6.1",
"signature_pad": "4.1.5" "signature_pad": "4.1.5"
}, },
"devDependencies": { "devDependencies": {
"@babel/preset-react": "7.26.3",
"@preact/signals-react-transform": "0.4.0",
"@rollup/plugin-replace": "6.0.1",
"@rollup/pluginutils": "5.1.3",
"@types/react": "18.3.13",
"@types/react-dom": "18.3.1",
"@vitejs/plugin-react": "4.3.3",
"@babel/preset-react": "7.27.1",
"@preact/signals-react-transform": "0.5.1",
"@rollup/plugin-replace": "6.0.2",
"@rollup/pluginutils": "5.1.4",
"@types/react": "18.3.23",
"@types/react-dom": "18.3.7",
"@vitejs/plugin-react": "4.5.0",
"async": "3.2.6", "async": "3.2.6",
"glob": "10.4.5",
"glob": "11.0.2",
"magic-string": "0.30.17",
"rollup-plugin-brotli": "3.1.0", "rollup-plugin-brotli": "3.1.0",
"rollup-plugin-visualizer": "5.12.0",
"rollup-plugin-visualizer": "5.14.0",
"strip-css-comments": "5.0.0", "strip-css-comments": "5.0.0",
"transform-ast": "2.4.4", "transform-ast": "2.4.4",
"typescript": "5.6.3",
"vite": "5.4.11",
"vite-plugin-checker": "0.8.0",
"typescript": "5.8.3",
"vite": "6.3.5",
"vite-plugin-checker": "0.9.3",
"workbox-build": "7.3.0", "workbox-build": "7.3.0",
"workbox-core": "7.3.0", "workbox-core": "7.3.0",
"workbox-precaching": "7.3.0" "workbox-precaching": "7.3.0"
}, },
"hash": "6126bd412c2a8696938f50edd1abae217c30c0119a0496af47b9d6ba1762921f"
"hash": "5e2a99194f7c0b939e722574ef486d86e32935203f1b5047346903f60dcb24d6"
}, },
"overrides": { "overrides": {
"@vaadin/bundles": "$@vaadin/bundles", "@vaadin/bundles": "$@vaadin/bundles",
@ -99,7 +101,6 @@
"@vaadin/common-frontend": "$@vaadin/common-frontend", "@vaadin/common-frontend": "$@vaadin/common-frontend",
"react-dom": "$react-dom", "react-dom": "$react-dom",
"construct-style-sheets-polyfill": "$construct-style-sheets-polyfill", "construct-style-sheets-polyfill": "$construct-style-sheets-polyfill",
"react-router-dom": "$react-router-dom",
"lit": "$lit", "lit": "$lit",
"@polymer/polymer": "$@polymer/polymer", "@polymer/polymer": "$@polymer/polymer",
"react": "$react", "react": "$react",
@ -108,6 +109,7 @@
"@vaadin/vaadin-themable-mixin": "$@vaadin/vaadin-themable-mixin", "@vaadin/vaadin-themable-mixin": "$@vaadin/vaadin-themable-mixin",
"@vaadin/vaadin-lumo-styles": "$@vaadin/vaadin-lumo-styles", "@vaadin/vaadin-lumo-styles": "$@vaadin/vaadin-lumo-styles",
"@vaadin/vaadin-material-styles": "$@vaadin/vaadin-material-styles", "@vaadin/vaadin-material-styles": "$@vaadin/vaadin-material-styles",
"signature_pad": "$signature_pad"
"signature_pad": "$signature_pad",
"react-router": "$react-router"
} }
}
}

+ 3
- 3
pom.xml View File

@ -6,18 +6,18 @@
<groupId>mx.gob.jumapacelaya</groupId> <groupId>mx.gob.jumapacelaya</groupId>
<artifactId>sistema-mantenimiento</artifactId> <artifactId>sistema-mantenimiento</artifactId>
<name>sistema-mantenimiento</name> <name>sistema-mantenimiento</name>
<version>1.0-SNAPSHOT</version>
<version>2.1</version>
<packaging>jar</packaging> <packaging>jar</packaging>
<properties> <properties>
<java.version>17</java.version> <java.version>17</java.version>
<vaadin.version>24.5.8</vaadin.version>
<vaadin.version>24.8.0</vaadin.version>
</properties> </properties>
<parent> <parent>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId> <artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.8</version>
<version>3.5.8</version>
</parent> </parent>
<repositories> <repositories>


BIN
src/main/bundles/dev.bundle View File


+ 20
- 180
src/main/frontend/themes/sistema-mantenimiento/styles.css View File

@ -1,11 +1,10 @@
@import url('./main-layout.css'); @import url('./main-layout.css');
/* ----------- Estilos para el MainLayout.java ----------- */
/* Estilos para el DrawerToggle*/
.drawer-toggle {
background-color: #bc955b;
:root {
--lumo-primary-color: #BC955B;
--lumo-primary-text-color: #BC955B;
--lumo-border-radius: 15px;
--lumo-font-family: 'Montserrat';
} }
/* Estilos para el encabezado */ /* Estilos para el encabezado */
@ -26,12 +25,9 @@
width: 300px; width: 300px;
} }
/* Estilos para el fondo de la aplicacion */
.app-layout {
background-image: url('/images/bckgndNvo.png');
background-size: cover;
background-position: center;
background-repeat: no-repeat;
vaadin-grid::part(selected-row-cell) {
background-color: #a02142;
color: white;
} }
/* ------------------ FIN -------------------------------------- */ /* ------------------ FIN -------------------------------------- */
/* /*
@ -59,19 +55,6 @@
font-size: 18px; font-size: 18px;
color: #ddc9a3; color: #ddc9a3;
} }
/* Estilo para el campo de texto nomenclatura */
.nomenclatura-txt {
margin-top: 15px;
margin-right: 15px;
}
/* Estilo para el logo CELAYA */
.celaya-logo {
width: 100px;
margin-left: 15px;
margin-top: 15px;
}
/* ------------------------ FIN -------------------------- */ /* ------------------------ FIN -------------------------- */
/* /*
/* /*
@ -90,176 +73,21 @@
text-align: center; text-align: center;
} }
/* Estilo para el grid */
.act-diaria-grid {
margin-top: 20px;
}
/* ---------------------------- FIN------------------------ */
/* (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) */ /* (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) */
/* (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) */ /* (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) */
/* (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) */ /* (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) */
/* (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) */ /* (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) */
/* Estilos especificos para el TextField, TextArea y Fecha*/
vaadin-text-field::part(label), vaadin-text-area::part(label), vaadin-date-picker::part(label) {
color: #691b31; /* Color de la etiqueta */
}
vaadin-text-field::part(input-field), vaadin-text-area::part(input-field), vaadin-password-field::part(input-field),
vaadin-number-field::part(input-field), vaadin-date-picker::part(input-field) {
border: 1px solid #691b31; /* Color del borde */
color: #691b31; /* Color del texto */
}
vaadin-month-calendar::part(date) {
color: #691b31; /* Color para los dias del calendario */
}
vaadin-button {
border-radius: 10px;
overflow: hidden;
box-shadow: 0 4px 8px rgba(105, 27, 49, 0.28), 0 2px 8px rgba(0, 0, 0, 0.13);
transition: box-shadow 0.2s, border 0.2s;
}
vaadin-button::after {
content: "";
position: absolute;
inset: 0;
background: rgba(240,223,223,0.2);
opacity: 0;
transition: opacity 0.2s;
pointer-events: none;
}
vaadin-button:hover::after {
box-shadow: 0 16px 36px rgba(105,27,49,0.35), 0 4px 16px rgba(0,0,0,0.18);
border-color: #BC955B;
opacity: 1;
}
vaadin-month-calendar::part(date):hover /* Estilos para cuando se posiciona el puntero sobre el dia */{
background-color: #a02142;
opacity: 50%;
border-radius: 5px;
}
vaadin-month-calendar::part(date focused) {
background-color: #ddc9a3; /* Color de la fecha seleccionada */
}
vaadin-date-picker-overlay-content > vaadin-button, vaadin-date-picker-year::part(year-number) {
color: #691b31;
}
/* (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) */
/* (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) */
/* (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) */
/* (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) */
/* Estilos específicos para el Upload */ /* Estilos específicos para el Upload */
vaadin-upload > vaadin-button { vaadin-upload > vaadin-button {
color: #691b31; color: #691b31;
} }
/* (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) */
/* (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) */
/* Estilos específicos para el ComboBox */
vaadin-combo-box::part(input-field) {
background-color: #ddc9a3; /* Color de fondo */
color: #691b31; /* Color del texto */
border: 1px solid #691b31; /* Color del borde */
}
vaadin-combo-box::part(label) {
color: #691b31; /* Color de las etiquetas */
}
vaadin-combo-box::part(dropdown) {
background-color: rgba(221, 201, 163, 1); /* Color del fondo del dropdown */
}
vaadin-combo-box-item::part(checkmark)::before, vaadin-combo-box-item:hover
{
color: #691b31;
}
/* (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) */
/* (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) */
/* (*)(*) (*)(*) INDICADOR DE CARGA DE LA APLICACION (*)(*) (*)(*) */
.v-loading-indicator { /* Cambiar el color de la barra de carga que sale cuando la aplicacion esta cargando */
background: #691b31 !important;
}
/* (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) */
/* (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) */
/* (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) */
/* (*)(*) (*)(*) ESTILOS PARA LA SIDEBAR (*)(*) (*)(*) */
vaadin-side-nav-item[aria-current="page"]::part(content) {
color: #691b31; /* Color de texto seleccionado */
}
vaadin-side-nav-item:hover::part(content) {
background-color: #ddc9a3; /* Color de fondo al hacer hover*/
fill-opacity: 50%;
border-radius: 5px;
}
vaadin-side-nav-item::part(content) {
color: #a02142; /* Color del texto deseado */
}
/* (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) */
/* (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) */
/*Estilos especificos para el Grid */
vaadin-grid::part(selected-row-cell) {
background-color: rgba(221, 201, 163, 0.7);
}
vaadin-grid::part(selected-row) {
color: #691b31;
font-weight: bold;
}
/* (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) */
/* (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) */
/*Estilos especificos para los botones */
vaadin-button[theme~="primary"] {
background-color: #691b31;
}
vaadin-button:not([theme]) {
color: #a02142;
}
vaadin-button[theme~="tertiary-inline"], vaadin-button[theme~="icon"] {
color: #a02142;
}
/* Estilos para el theme personalizado 'subir-archivo' */ /* Estilos para el theme personalizado 'subir-archivo' */
vaadin-button[theme~="subir-archivo"] { vaadin-button[theme~="subir-archivo"] {
background-color: #691b31; background-color: #691b31;
color: #ffffff; color: #ffffff;
} }
/* (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) */
/* (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) */
/*Estilos especificos para los CheckBox y RadioButtons */
vaadin-checkbox[checked]::part(checkbox) {
background-color: #691b31; /* Cambia el color del CheckBox */
}
vaadin-checkbox-group::part(label), vaadin-radio-group::part(label) {
color: #691b31;
}
vaadin-radio-button[checked]::part(radio) {
background-color: #691b31;
}
/* (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) */ /* (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) */
/* (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) */ /* (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) */
/* (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) */ /* (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) (*)(*) */
@ -298,6 +126,18 @@ vaadin-popover-overlay::part(overlay) {
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
} }
.dashboard-widget-content {
padding: 1.5rem;
}
.pregunta-widget-container {
border-top: 3px solid #a02142;
}
.pregunta-widget-container span {
line-height: 1.2;
}
@media (max-width: 900px) { @media (max-width: 900px) {


+ 5
- 3
src/main/java/mx/gob/jumapacelaya/Application.java View File

@ -1,13 +1,14 @@
package mx.gob.jumapacelaya; package mx.gob.jumapacelaya;
import com.vaadin.flow.component.page.AppShellConfigurator; import com.vaadin.flow.component.page.AppShellConfigurator;
import com.vaadin.flow.component.page.Push;
import com.vaadin.flow.server.PWA; import com.vaadin.flow.server.PWA;
import com.vaadin.flow.theme.Theme; import com.vaadin.flow.theme.Theme;
import com.vaadin.flow.theme.lumo.Lumo;
import mx.gob.jumapacelaya.services.DatabaseService; import mx.gob.jumapacelaya.services.DatabaseService;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration;
/** /**
* The entry point of the Spring Boot application. * The entry point of the Spring Boot application.
@ -17,7 +18,8 @@ import org.springframework.boot.autoconfigure.validation.ValidationAutoConfigura
* *
*/ */
@SpringBootApplication @SpringBootApplication
@Theme(value = "sistema-mantenimiento")
@Push
@Theme(value = "sistema-mantenimiento", variant = Lumo.LIGHT)
@PWA(name = "Aplicacion de Mantenimiento de Equipo de Computo", shortName = "Mantenimiento de Computo", iconPath = "icons/icon.png") @PWA(name = "Aplicacion de Mantenimiento de Equipo de Computo", shortName = "Mantenimiento de Computo", iconPath = "icons/icon.png")
public class Application implements AppShellConfigurator { public class Application implements AppShellConfigurator {


+ 48
- 66
src/main/java/mx/gob/jumapacelaya/api/RedmineClient.java View File

@ -7,6 +7,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.io.IOException; import java.io.IOException;
import java.net.URI; import java.net.URI;
import java.net.http.HttpClient; import java.net.http.HttpClient;
@ -51,16 +52,29 @@ public class RedmineClient {
StringBuilder url = new StringBuilder(REDMINE_URL + "/issues.json?key=" + user.getKey() StringBuilder url = new StringBuilder(REDMINE_URL + "/issues.json?key=" + user.getKey()
+ statusFilter + "&offset=" + offset + "&limit=" + limit); + statusFilter + "&offset=" + offset + "&limit=" + limit);
url.append("&sort=created_on:desc");
// Filtro por fechas // Filtro por fechas
DateTimeFormatter formatter = DateTimeFormatter.ISO_DATE; DateTimeFormatter formatter = DateTimeFormatter.ISO_DATE;
if (fechaDesde != null) {
url.append("&created_on=%3E%3D").append(fechaDesde.format(formatter));
}
if (fechaHasta != null) {
url.append("&created_on=%3C%3D").append(fechaHasta.format(formatter));
if (fechaDesde != null || fechaHasta != null) {
url.append("&created_on=");
if (fechaDesde != null && fechaHasta != null) {
// En lugar de >< usamos %3E%3C y en lugar de | usamos %7C
url.append("%3E%3C")
.append(fechaDesde.toString())
.append("%7C")
.append(fechaHasta.toString());
} else if (fechaDesde != null) {
url.append("%3E%3D").append(fechaDesde.toString());
} else {
url.append("%3C%3D").append(fechaHasta.toString());
}
} }
//log.info("URL: " + url.toString());
HttpRequest request = HttpRequest.newBuilder() HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(url.toString())) .uri(URI.create(url.toString()))
.header("Content-Type", "application/json") .header("Content-Type", "application/json")
@ -71,10 +85,10 @@ public class RedmineClient {
if (response.statusCode() == 200) { if (response.statusCode() == 200) {
tickets.addAll(parseTickets(response.body())); tickets.addAll(parseTickets(response.body()));
} else { } else {
System.err.println("Error en la respuesta: " + response.statusCode());
log.error("Error al obtener tickets. Codigo de estado: {}", response.statusCode());
} }
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace();
log.error("Ocurrió una excepción al procesar la respuesta del servidor: ", e);
} }
return tickets; return tickets;
@ -95,11 +109,20 @@ public class RedmineClient {
// Filtro por fechas // Filtro por fechas
DateTimeFormatter formatter = DateTimeFormatter.ISO_DATE; DateTimeFormatter formatter = DateTimeFormatter.ISO_DATE;
if (fechaDesde != null) {
url.append("&created_on=%3E%3D").append(fechaDesde.format(formatter));
}
if (fechaHasta != null) {
url.append("&created_on=%3C%3D").append(fechaHasta.format(formatter));
if (fechaDesde != null || fechaHasta != null) {
url.append("&created_on=");
if (fechaDesde != null && fechaHasta != null) {
// En lugar de >< usamos %3E%3C y en lugar de | usamos %7C
url.append("%3E%3C")
.append(fechaDesde.toString())
.append("%7C")
.append(fechaHasta.toString());
} else if (fechaDesde != null) {
url.append("%3E%3D").append(fechaDesde.toString());
} else {
url.append("%3C%3D").append(fechaHasta.toString());
}
} }
HttpRequest request = HttpRequest.newBuilder() HttpRequest request = HttpRequest.newBuilder()
@ -113,55 +136,15 @@ public class RedmineClient {
JsonObject jsonObject = JsonParser.parseString(response.body()).getAsJsonObject(); JsonObject jsonObject = JsonParser.parseString(response.body()).getAsJsonObject();
return jsonObject.get("total_count").getAsInt(); return jsonObject.get("total_count").getAsInt();
} else { } else {
System.err.println("Error en la respuesta: " + response.statusCode());
log.error("Error al obtener los tickets. Codigo de estado: {}", response.statusCode());
} }
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace();
log.error("Ocurrió un error al procesar l respuesta del servidor", e);
} }
return 0; return 0;
} }
//AQUI OBTENGO LOS TICKETS DESDE REDMINE
/*public List<Ticket> getTicketsAuthor(RedmineUser user, boolean includeClosed) {
List<Ticket> tickets = new ArrayList<>();
HttpClient client = HttpClient.newHttpClient();
int offset = 0;
// Si includeClose es true, incluira todos los tikets si no, incluira solo los que estan abiertos
String statusFilter = includeClosed ? "&status_id=*" : "&status_id=open";
while (true) {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(REDMINE_URL + "/issues.json?key=" + user.getKey() + "&author_id=" + user.getId() + statusFilter + "&offset=" + offset))
.header("Content-Type", "application/json")
.build();
try {
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() == 200) {
String responseBody = response.body();
List<Ticket> pageTickets = parseTickets(responseBody);
tickets.addAll(pageTickets);
if (pageTickets.size() < PAGE_SIZE) {
break;
}
offset += PAGE_SIZE;
} else {
System.err.println("Error en la respuesta: " + response.statusCode());
break;
}
} catch (Exception e) {
e.printStackTrace();
break;
}
}
System.out.println("Total tickets obtenidos: " + tickets.size());
return tickets;
}*/
// Aquí se parsean todos los tickets que existen y se les da un formato con los campos a mostrarse // Aquí se parsean todos los tickets que existen y se les da un formato con los campos a mostrarse
private List<Ticket> parseTickets(String json) { private List<Ticket> parseTickets(String json) {
List<Ticket> tickets = new ArrayList<>(); List<Ticket> tickets = new ArrayList<>();
@ -232,11 +215,10 @@ public class RedmineClient {
tickets.add(ticketObj); tickets.add(ticketObj);
} }
} else { } else {
System.out.println("La respuesta JSON no contiene la clave 'issues'");
log.info("La respuesta JSON no contiene la clave 'issues'. Respuesta completa: {}", json);
} }
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace();
System.out.println("Ocurrió un error al parsear los tickets: " + e.getMessage());
log.error("Ocurrió un error al parsear los tickets: ", e);
} }
return tickets; return tickets;
} }
@ -268,7 +250,7 @@ public class RedmineClient {
//return response.body(); //return response.body();
} catch (IOException | InterruptedException e) { } catch (IOException | InterruptedException e) {
e.printStackTrace();
log.error("Error al obtener la cuenta del usuario: {}", e);
return null; return null;
} }
} }
@ -309,7 +291,7 @@ public class RedmineClient {
return newUser; return newUser;
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace();
log.error("Error al crear el usuario: {}", e);
return null; return null;
} }
} }
@ -328,13 +310,13 @@ public class RedmineClient {
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString()); HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() == 200) { if (response.statusCode() == 200) {
String responseBody = response.body(); String responseBody = response.body();
System.out.println("Datos del usuario " + responseBody);
log.info("Datos del usuario " + responseBody);
return parseUser(responseBody); return parseUser(responseBody);
} else { } else {
System.err.println("Error en la respuesta: " + response.statusCode());
log.error("Error en la respuesta {}", response.statusCode());
} }
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace();
log.error("Error al obtener el usuario por nombre de usuario: {}", e);
} }
return null; return null;
} }
@ -376,10 +358,10 @@ public class RedmineClient {
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString()); HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() == 204) { if (response.statusCode() == 204) {
String responseBody = response.body(); String responseBody = response.body();
System.out.println("Ticket " + ticketId + " cerrado correctamente.");
log.info("Ticket " + ticketId + " cerrado correctamente.");
} else { } else {
System.err.println("Error al cerrar el ticket: " + response.statusCode());
System.err.println(response.body());
log.error("Error al cerrar el ticket: {}", response.statusCode());
log.error(response.body());
} }
} }
@ -411,7 +393,7 @@ public class RedmineClient {
return LocalDateTime.parse(dateStr); return LocalDateTime.parse(dateStr);
} }
} catch (DateTimeParseException ex) { } catch (DateTimeParseException ex) {
System.err.println("Error al parsear fecha '" + fieldName + "': " + dateStr);
log.error("Error al parsear fecha '" + fieldName + "': " + dateStr);
return null; return null;
} }
} }


+ 0
- 1
src/main/java/mx/gob/jumapacelaya/api/SecurityService.java View File

@ -1 +0,0 @@
package mx.gob.jumapacelaya.api;

+ 16
- 33
src/main/java/mx/gob/jumapacelaya/controller/SecurityConfiguration.java View File

@ -19,6 +19,21 @@ import org.springframework.ldap.core.support.LdapContextSource;
public class SecurityConfiguration extends VaadinWebSecurity { public class SecurityConfiguration extends VaadinWebSecurity {
@Value("${spring.ldap.domain}")
private String ldapDomain;
@Value("${spring.ldap.urls}")
private String ldapUrls;
@Value("${spring.ldap.url}")
private String ldapUrl;
@Value("${spring.ldap.base}")
private String ldapBase;
@Value("${spring.ldap.password}")
private String ldapPassword;
@Override @Override
protected void configure(HttpSecurity http) throws Exception { protected void configure(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(auth -> auth http.authorizeHttpRequests(auth -> auth
@ -40,45 +55,13 @@ public class SecurityConfiguration extends VaadinWebSecurity {
// !Esta es la real autenticacion con ldap // !Esta es la real autenticacion con ldap
@Autowired @Autowired
public void configure(AuthenticationManagerBuilder auth) throws Exception { public void configure(AuthenticationManagerBuilder auth) throws Exception {
ActiveDirectoryLdapAuthenticationProvider provider = new ActiveDirectoryLdapAuthenticationProvider("JUMAPACELAYA.GOB.MX", "ldap://172.16.0.1");
ActiveDirectoryLdapAuthenticationProvider provider = new ActiveDirectoryLdapAuthenticationProvider(ldapDomain, ldapUrl);
provider.setConvertSubErrorCodesToExceptions(true); provider.setConvertSubErrorCodesToExceptions(true);
provider.setUseAuthenticationRequestCredentials(true); provider.setUseAuthenticationRequestCredentials(true);
auth.authenticationProvider(provider); auth.authenticationProvider(provider);
} }
// !Autenticacion local solo para que lo vea el departamento de calidad
/*@Autowired
public void configure(AuthenticationManagerBuilder auth) throws Exception {
// Configura la autenticación en memoria con un usuario local
auth.inMemoryAuthentication()
.withUser("admin")
.password("{noop}admin") // {noop} indica que la contraseña no está cifrada (solo para pruebas)
.roles("ADMIN")
.and()
.withUser("jlermal")
.password("{noop}Temporal1")
.roles("USER")
.and()
.withUser("mramirezg")
.password("{noop}Temporal1")
.roles("ADMIN");
}*/
@Value("${spring.ldap.urls}")
private String ldapUrls;
@Value("${spring.ldap.base}")
private String ldapBase;
@Value("${spring.ldap.password}")
private String ldapPassword;
@Bean @Bean
public LdapContextSource ldapContextSource() { public LdapContextSource ldapContextSource() {
LdapContextSource contextSource = new LdapContextSource(); LdapContextSource contextSource = new LdapContextSource();


+ 0
- 1
src/main/java/mx/gob/jumapacelaya/datasource/MysqlDataSource.java View File

@ -24,7 +24,6 @@ public class MysqlDataSource {
@Bean(name = "mysqlJdbcTemplate") @Bean(name = "mysqlJdbcTemplate")
@Autowired
public JdbcTemplate mysqlTemplate(@Qualifier("mysqlDB") DataSource mysqlDB) { public JdbcTemplate mysqlTemplate(@Qualifier("mysqlDB") DataSource mysqlDB) {
return new JdbcTemplate(mysqlDB); return new JdbcTemplate(mysqlDB);
} }

+ 0
- 1
src/main/java/mx/gob/jumapacelaya/datasource/OracleDataSource.java View File

@ -20,7 +20,6 @@ public class OracleDataSource {
} }
@Bean(name = "oracleTemplate") @Bean(name = "oracleTemplate")
@Autowired
public JdbcTemplate oracleJdbcTemplate(@Qualifier("oracleDB") DataSource oracleDB) { public JdbcTemplate oracleJdbcTemplate(@Qualifier("oracleDB") DataSource oracleDB) {
return new JdbcTemplate(oracleDB); return new JdbcTemplate(oracleDB);
} }


+ 38
- 0
src/main/java/mx/gob/jumapacelaya/models/encuestas/ConteoEncuestas.java View File

@ -0,0 +1,38 @@
package mx.gob.jumapacelaya.models.encuestas;
public class ConteoEncuestas {
private int totalEnviadas;
private int totalRespondidas;
private Double porcentajeRespondidas;
public ConteoEncuestas(int totalEnviadas, int totalRespondidas, Double porcentajeRespondidas) {
this.totalEnviadas = totalEnviadas;
this.totalRespondidas = totalRespondidas;
this.porcentajeRespondidas = porcentajeRespondidas;
}
public int getTotalEnviadas() {
return totalEnviadas;
}
public void setTotalEnviadas(int totalEnviadas) {
this.totalEnviadas = totalEnviadas;
}
public int getTotalRespondidas() {
return totalRespondidas;
}
public void setTotalRespondidas(int totalRespondidas) {
this.totalRespondidas = totalRespondidas;
}
public Double getPorcentajeRespondidas() {
return porcentajeRespondidas;
}
public void setPorcentajeRespondidas(Double porcentajeRespondidas) {
this.porcentajeRespondidas = porcentajeRespondidas;
}
}

+ 68
- 0
src/main/java/mx/gob/jumapacelaya/models/encuestas/ConteoRespuestas.java View File

@ -0,0 +1,68 @@
package mx.gob.jumapacelaya.models.encuestas;
public class ConteoRespuestas {
private int preguntaId;
private String pregunta;
private int totalRespuestas;
private int totalSi;
private int totalNo;
private Double porcentaje;
public ConteoRespuestas(int preguntaId, String pregunta, int totalRespuestas, int totalSi, int totalNo, Double porcentaje) {
this.preguntaId = preguntaId;
this.pregunta = pregunta;
this.totalRespuestas = totalRespuestas;
this.totalSi = totalSi;
this.totalNo = totalNo;
this.porcentaje = porcentaje;
}
public int getPreguntaId() {
return preguntaId;
}
public void setPreguntaId(int preguntaId) {
this.preguntaId = preguntaId;
}
public String getPregunta() {
return pregunta;
}
public void setPregunta(String pregunta) {
this.pregunta = pregunta;
}
public int getTotalRespuestas() {
return totalRespuestas;
}
public void setTotalRespuestas(int totalRespuestas) {
this.totalRespuestas = totalRespuestas;
}
public int getTotalSi() {
return totalSi;
}
public void setTotalSi(int totalSi) {
this.totalSi = totalSi;
}
public int getTotalNo() {
return totalNo;
}
public void setTotalNo(int totalNo) {
this.totalNo = totalNo;
}
public Double getPorcentaje() {
return porcentaje;
}
public void setPorcentaje(Double porcentaje) {
this.porcentaje = porcentaje;
}
}

+ 102
- 0
src/main/java/mx/gob/jumapacelaya/models/encuestas/MantenimientosSinEncuesta.java View File

@ -0,0 +1,102 @@
package mx.gob.jumapacelaya.models.encuestas;
import java.time.LocalDate;
public class MantenimientosSinEncuesta {
private int mantenimientoId;
private int planId;
private LocalDate fecha;
private String periodo;
private String tipomant;
private String departamento;
private String nomUsuario;
private String email;
private String encuesta;
public MantenimientosSinEncuesta(int mantenimientoId, int planId, LocalDate fecha,
String periodo, String tipomant, String departamento,
String nomUsuario, String email, String encuesta) {
this.mantenimientoId = mantenimientoId;
this.planId = planId;
this.fecha = fecha;
this.periodo = periodo;
this.tipomant = tipomant;
this.departamento = departamento;
this.nomUsuario = nomUsuario;
this.email = email;
this.encuesta = encuesta;
}
public int getMantenimientoId() {
return mantenimientoId;
}
public void setMantenimientoId(int mantenimientoId) {
this.mantenimientoId = mantenimientoId;
}
public int getPlanId() {
return planId;
}
public void setPlanId(int planId) {
this.planId = planId;
}
public LocalDate getFecha() {
return fecha;
}
public void setFecha(LocalDate fecha) {
this.fecha = fecha;
}
public String getPeriodo() {
return periodo;
}
public void setPeriodo(String periodo) {
this.periodo = periodo;
}
public String getTipomant() {
return tipomant;
}
public void setTipomant(String tipomant) {
this.tipomant = tipomant;
}
public String getDepartamento() {
return departamento;
}
public void setDepartamento(String departamento) {
this.departamento = departamento;
}
public String getNomUsuario() {
return nomUsuario;
}
public void setNomUsuario(String nomUsuario) {
this.nomUsuario = nomUsuario;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getEncuesta() {
return encuesta;
}
public void setEncuesta(String encuesta) {
this.encuesta = encuesta;
}
}

+ 147
- 69
src/main/java/mx/gob/jumapacelaya/services/DatabaseService.java View File

@ -1,20 +1,19 @@
package mx.gob.jumapacelaya.services; package mx.gob.jumapacelaya.services;
import mx.gob.jumapacelaya.models.*; import mx.gob.jumapacelaya.models.*;
import mx.gob.jumapacelaya.models.encuestas.Pregunta;
import mx.gob.jumapacelaya.models.encuestas.Respuesta;
import oracle.jdbc.OracleConnection;
import oracle.jdbc.OraclePreparedStatement;
import oracle.sql.CLOB;
import org.apache.poi.ss.usermodel.*;
import mx.gob.jumapacelaya.models.encuestas.*;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook; import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.StringReader;
import java.security.interfaces.RSAKey;
import java.sql.*; import java.sql.*;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.ArrayList; import java.util.ArrayList;
@ -24,6 +23,7 @@ import java.util.UUID;
@Service @Service
public class DatabaseService { public class DatabaseService {
private static final Logger logger = LoggerFactory.getLogger(DatabaseService.class);
@Value("${db.url}") @Value("${db.url}")
private String dbUrl; private String dbUrl;
@ -62,7 +62,7 @@ public class DatabaseService {
tiposDeMantenimientos.add(tipo); tiposDeMantenimientos.add(tipo);
} }
} catch (SQLException e) { } catch (SQLException e) {
e.printStackTrace();
logger.error("Error al obtener tipos de mantenimientos: ", e);
} }
return tiposDeMantenimientos; return tiposDeMantenimientos;
} }
@ -83,13 +83,11 @@ public class DatabaseService {
} }
} catch (SQLException e) { } catch (SQLException e) {
e.printStackTrace();
System.err.println("Error al obtener nomenclatura: " + e.getMessage());
logger.error("Error al obtener nomenclatura: ", e);
} }
return nomenclatura; return nomenclatura;
} }
/* -------------- Metodo para obtener a los usuarios ---------------- */ /* -------------- Metodo para obtener a los usuarios ---------------- */
public List<Usuario> getUsuarios() { public List<Usuario> getUsuarios() {
List<Usuario> usuarios = new ArrayList<>(); List<Usuario> usuarios = new ArrayList<>();
@ -109,12 +107,11 @@ public class DatabaseService {
usuarios.add(usuario); usuarios.add(usuario);
} }
} catch (SQLException e) { } catch (SQLException e) {
e.printStackTrace();
logger.error("Error al obtener usuarios: ", e);
} }
return usuarios; return usuarios;
} }
/* -------------- Metodo para obtener los departamentos ---------------- */ /* -------------- Metodo para obtener los departamentos ---------------- */
public List<DepartamentosModel> getDepartamentos() { public List<DepartamentosModel> getDepartamentos() {
List<DepartamentosModel> departamentos = new ArrayList<>(); List<DepartamentosModel> departamentos = new ArrayList<>();
@ -132,12 +129,11 @@ public class DatabaseService {
departamentos.add(departamentosModel); departamentos.add(departamentosModel);
} }
} catch (SQLException e) { } catch (SQLException e) {
e.printStackTrace();
logger.error("Error al obtener departamentos: ", e);
} }
return departamentos; return departamentos;
} }
/* -------------- Metodo para obtener los tipos de hardware ---------------- */ /* -------------- Metodo para obtener los tipos de hardware ---------------- */
public List<TiposHardware> getTiposHardware() { public List<TiposHardware> getTiposHardware() {
List<TiposHardware> tiposHardware = new ArrayList<>(); List<TiposHardware> tiposHardware = new ArrayList<>();
@ -155,13 +151,11 @@ public class DatabaseService {
tiposHardware.add(tiposHardwareModel); tiposHardware.add(tiposHardwareModel);
} }
} catch (SQLException e) { } catch (SQLException e) {
e.printStackTrace();
logger.error("Error al obtener tipos de hardware: ", e);
} }
return tiposHardware; return tiposHardware;
} }
/* ----------------Obtener el Plan Anual de Mantenimiento ---------------- */ /* ----------------Obtener el Plan Anual de Mantenimiento ---------------- */
public List<PlanAnual> getPlanAnual() { public List<PlanAnual> getPlanAnual() {
List<PlanAnual> planAnualList = new ArrayList<>(); List<PlanAnual> planAnualList = new ArrayList<>();
@ -208,14 +202,12 @@ public class DatabaseService {
); );
planAnualList.add(planAnual); planAnualList.add(planAnual);
} }
System.out.println("Registros obtenidos: " + planAnualList.size());
} catch (SQLException e) { } catch (SQLException e) {
e.printStackTrace();
logger.error("Error al obtener plan anual: ", e);
} }
return planAnualList; return planAnualList;
} }
/* ----------------Obtener el Plan Anual de Mantenimiento por ID ---------------- */ /* ----------------Obtener el Plan Anual de Mantenimiento por ID ---------------- */
public PlanAnual getPlanAnualPorId(int id) { public PlanAnual getPlanAnualPorId(int id) {
String query = """ String query = """
@ -262,13 +254,11 @@ public class DatabaseService {
} }
} }
} catch (SQLException e) { } catch (SQLException e) {
e.printStackTrace();
logger.error("Error al obtener plan anual por id: ", e);
} }
return null; return null;
} }
/* ----------------Obtener detalles del mantenimiento ---------------- */ /* ----------------Obtener detalles del mantenimiento ---------------- */
public DetalleMantenimientoModel getDetalleMantenimientoPorPlanAnualId(int planAnualId) { public DetalleMantenimientoModel getDetalleMantenimientoPorPlanAnualId(int planAnualId) {
String query = "SELECT\n" + String query = "SELECT\n" +
@ -296,7 +286,7 @@ public class DatabaseService {
try (Connection connection = getMysqlConnection(); try (Connection connection = getMysqlConnection();
PreparedStatement statement = connection.prepareStatement(query)) { PreparedStatement statement = connection.prepareStatement(query)) {
statement.setInt(1, planAnualId); statement.setInt(1, planAnualId);
try (ResultSet rs = statement.executeQuery()) { try (ResultSet rs = statement.executeQuery()) {
@ -319,12 +309,71 @@ public class DatabaseService {
} }
} }
} catch (SQLException e) { } catch (SQLException e) {
e.printStackTrace();
logger.error("Error al obtener detalles del mantenimiento por plan anual id: ", e);
} }
return null; return null;
} }
/* ----------------Obtener mantenimientos sin encuesta respondida por periodo ---------------- */
public List<MantenimientosSinEncuesta> getEncuestPendientes(String mes, int anio) {
List<MantenimientosSinEncuesta> lista = new ArrayList<>();
String query = """
SELECT
m.MANTENIMIENTOID MANTID,
m.PLANANUALID PLANID,
m.FECHA,
ms.NOMBRE PERIODO,
t.NOMBRE TIPOMANT,
d.DESCRIPCION DEPTO,
u.NOMBRE NOMUSUARIO,
u.EMAIL,
m.ENCUESTA
FROM MANTENIMIENTOS m
JOIN TIPOMANT t
ON t.TIPOMANTID = m.TIPOMANTID
JOIN DEPARTAMENTOS d
on d.DEPARTAMENTOID = m.DEPARTAMENTOID
JOIN USUARIOS u
on u.EMPLEADOID = m.EMPLEADOID
JOIN PLANANUAL p
ON p.PLANANUALID = m.PLANANUALID
JOIN MESES ms
ON ms.MESID = p.MESID
WHERE m.ENCUESTA = 'N'
AND m.TIPOMANTID = 1
AND m.PLANANUALID IS NOT NULL
AND UPPER(ms.NOMBRE) = UPPER(?)
AND EXTRACT(YEAR FROM m.FECHA) = ?
""";
try(Connection connection = getMysqlConnection();
PreparedStatement stmt = connection.prepareStatement(query)) {
stmt.setString(1, mes.toUpperCase());
stmt.setInt(2, anio);
ResultSet rs = stmt.executeQuery();
while (rs.next()) {
lista.add(new MantenimientosSinEncuesta(
rs.getInt("MANTID"),
rs.getInt("PLANID"),
rs.getDate("FECHA").toLocalDate(),
rs.getString("PERIODO"),
rs.getString("TIPOMANT"),
rs.getString("DEPTO"),
rs.getString("NOMUSUARIO"),
rs.getString("EMAIL"),
rs.getString("ENCUESTA")
));
}
} catch (SQLException e) {
logger.error("Error al obtener mantenimientos sin encuesta respondida por periodo: ", e);
}
return lista;
}
/* ----------------Obtener detalles del hardaware por ID ---------------- */ /* ----------------Obtener detalles del hardaware por ID ---------------- */
public List<HardwareDetalle> getHardwaredetallePorMantId(int mantenimientoId) { public List<HardwareDetalle> getHardwaredetallePorMantId(int mantenimientoId) {
@ -333,10 +382,10 @@ public class DatabaseService {
"FROM HARDWAREDET h\r\n" + // "FROM HARDWAREDET h\r\n" + //
"INNER JOIN TIPOSHARDWARE t ON h.TIPOHARDWAREID = t.TIPOHARDWAREID \r\n" + // "INNER JOIN TIPOSHARDWARE t ON h.TIPOHARDWAREID = t.TIPOHARDWAREID \r\n" + //
"WHERE h.MANTENIMIENTOID = ?"; "WHERE h.MANTENIMIENTOID = ?";
try (Connection conn = getMysqlConnection(); try (Connection conn = getMysqlConnection();
PreparedStatement stmt = conn.prepareStatement(query)) { PreparedStatement stmt = conn.prepareStatement(query)) {
stmt.setInt(1, mantenimientoId); stmt.setInt(1, mantenimientoId);
ResultSet rs = stmt.executeQuery(); ResultSet rs = stmt.executeQuery();
@ -353,19 +402,18 @@ public class DatabaseService {
} }
} catch (SQLException e) { } catch (SQLException e) {
e.printStackTrace();
logger.error("Error al obtener detalles del hardaware por ID: ", e);
} }
return detalles; return detalles;
} }
/* ---------------- Obtener las actualizaciones de seguridad ---------------- */ /* ---------------- Obtener las actualizaciones de seguridad ---------------- */
public List<ActualizacioneSeguridadModel> getActualizacionesSeg(int mantenimientoId) { public List<ActualizacioneSeguridadModel> getActualizacionesSeg(int mantenimientoId) {
List<ActualizacioneSeguridadModel> actualizaciones = new ArrayList<>(); List<ActualizacioneSeguridadModel> actualizaciones = new ArrayList<>();
String query = "SELECT *\r\n" + // String query = "SELECT *\r\n" + //
"FROM ACTUALIZACIONESSEG a\r\n" + // "FROM ACTUALIZACIONESSEG a\r\n" + //
"WHERE a.MANTENIMIENTOID = ?"; "WHERE a.MANTENIMIENTOID = ?";
try (Connection conn = getMysqlConnection(); try (Connection conn = getMysqlConnection();
PreparedStatement stmt = conn.prepareStatement(query)) { PreparedStatement stmt = conn.prepareStatement(query)) {
@ -383,12 +431,11 @@ public class DatabaseService {
} }
} catch (SQLException e) { } catch (SQLException e) {
e.printStackTrace();
logger.error("Error al obtener las actualizaciones de seguridad: ", e);
} }
return actualizaciones; return actualizaciones;
} }
/* ---------------- Obtener los mantenimientos correctivos ---------------- */ /* ---------------- Obtener los mantenimientos correctivos ---------------- */
public List<MantCorrectivosModel> getMantenimientosCorrectivos(int tipomantId) { public List<MantCorrectivosModel> getMantenimientosCorrectivos(int tipomantId) {
List<MantCorrectivosModel> mantCorrectivos = new ArrayList<>(); List<MantCorrectivosModel> mantCorrectivos = new ArrayList<>();
@ -430,13 +477,11 @@ public class DatabaseService {
} }
} }
} catch (SQLException e) { } catch (SQLException e) {
e.printStackTrace();
logger.error("Error al obtener los mantenimientos correctivos: ", e);
} }
return mantCorrectivos; return mantCorrectivos;
} }
/*-=iii=<() *-=iii=<() *-=iii=<() *-=iii=<() *-=iii=<() *-=iii=<()*/ /*-=iii=<() *-=iii=<() *-=iii=<() *-=iii=<() *-=iii=<() *-=iii=<()*/
/*-=iii=<() *-=iii=<() *-=iii=<() *-=iii=<() *-=iii=<()*/ /*-=iii=<() *-=iii=<() *-=iii=<() *-=iii=<() *-=iii=<()*/
/*-=iii=<() *-=iii=<() *-=iii=<() *-=iii=<()*/ /*-=iii=<() *-=iii=<() *-=iii=<() *-=iii=<()*/
@ -517,14 +562,13 @@ public class DatabaseService {
connection.commit(); connection.commit();
} catch (SQLException e) { } catch (SQLException e) {
System.err.println("Error al insertar mantenimiento: " + e.getMessage());
e.printStackTrace();
logger.error("Error al insertar mantenimiento: ", e);
// En caso de error, se hace rollback // En caso de error, se hace rollback
if (connection != null) { if (connection != null) {
try { try {
connection.rollback(); connection.rollback();
} catch (SQLException rollbackEx) { } catch (SQLException rollbackEx) {
System.err.println("Error al hacer rollback: " + rollbackEx.getMessage());
logger.error("Error al hacer rollback: ", rollbackEx);
} }
} }
} finally { } finally {
@ -560,12 +604,11 @@ public class DatabaseService {
int rowsAffected = preparedStatement.executeUpdate(); int rowsAffected = preparedStatement.executeUpdate();
isInserted = rowsAffected > 0; isInserted = rowsAffected > 0;
} catch (SQLException e) { } catch (SQLException e) {
e.printStackTrace();
logger.error("Error al insertar hardware: ", e);
} }
return isInserted; return isInserted;
} }
public int getUltimoMantenimientoId() { public int getUltimoMantenimientoId() {
int ultimoId = -1; int ultimoId = -1;
try (Connection connection = getMysqlConnection()) { try (Connection connection = getMysqlConnection()) {
@ -577,12 +620,11 @@ public class DatabaseService {
} }
} }
} catch (SQLException e) { } catch (SQLException e) {
e.printStackTrace();
logger.error("Error al obtener el ultimo id de mantenimiento: ", e);
} }
return ultimoId; return ultimoId;
} }
// INSERTAR EN LA TABLA: ACTUALIZACIONESSEG // INSERTAR EN LA TABLA: ACTUALIZACIONESSEG
public boolean insertActualizacionSeg(String descripcion, String otras, int mantenimientoId) { public boolean insertActualizacionSeg(String descripcion, String otras, int mantenimientoId) {
String query = "INSERT INTO ACTUALIZACIONESSEG (descripcion, otrasactualizaciones, mantenimientoid) VALUES (?, ?, ?)"; String query = "INSERT INTO ACTUALIZACIONESSEG (descripcion, otrasactualizaciones, mantenimientoid) VALUES (?, ?, ?)";
@ -602,7 +644,6 @@ public class DatabaseService {
} }
} }
// INSERTAR ARCHIVO EXCEL EN LA TABLA: PLANANUAL // INSERTAR ARCHIVO EXCEL EN LA TABLA: PLANANUAL
public void insertarDesdeExcel(InputStream inputStream) { public void insertarDesdeExcel(InputStream inputStream) {
String query = "INSERT INTO PLANANUAL (NOMEQUIPO, AREA, MONITOR, TECLADO, MOUSE, " + String query = "INSERT INTO PLANANUAL (NOMEQUIPO, AREA, MONITOR, TECLADO, MOUSE, " +
@ -677,7 +718,6 @@ public class DatabaseService {
} }
} }
// INSERTAR NUEVO EQUIPO INDIVIDUAL EN PLANANUAL // INSERTAR NUEVO EQUIPO INDIVIDUAL EN PLANANUAL
public void insertarNuevoEquipo(String nomequipo, String area, boolean monitor, boolean teclado, public void insertarNuevoEquipo(String nomequipo, String area, boolean monitor, boolean teclado,
boolean mouse, boolean regulador, boolean cpu, boolean impresora, boolean mouse, boolean regulador, boolean cpu, boolean impresora,
@ -724,7 +764,6 @@ public class DatabaseService {
} }
// Método auxiliar para obtener un valor de tipo Date de una celda // Método auxiliar para obtener un valor de tipo Date de una celda
private Date getDateCellValue(Cell cell) { private Date getDateCellValue(Cell cell) {
if (cell != null) { if (cell != null) {
@ -780,7 +819,6 @@ public class DatabaseService {
return false; // Valor por defecto si la celda es nula return false; // Valor por defecto si la celda es nula
} }
/* ----------------Actualizar los detalles del del mantenimiento por ID ---------------- */ /* ----------------Actualizar los detalles del del mantenimiento por ID ---------------- */
public boolean actualizarPlanAnual(int planAnualId, String nombreEquipo) { public boolean actualizarPlanAnual(int planAnualId, String nombreEquipo) {
String sql = "UPDATE PLANANUAL SET NOMEQUIPO=? WHERE PLANANUALID=?"; String sql = "UPDATE PLANANUAL SET NOMEQUIPO=? WHERE PLANANUALID=?";
@ -790,7 +828,7 @@ public class DatabaseService {
stmt.setInt(2, planAnualId); stmt.setInt(2, planAnualId);
return stmt.executeUpdate() > 0; return stmt.executeUpdate() > 0;
} catch (SQLException e) { } catch (SQLException e) {
e.printStackTrace();
logger.error("Error al actualizar el plan anual: ", e);
return false; return false;
} }
} }
@ -817,12 +855,11 @@ public class DatabaseService {
stmt.setInt(8, mantenimientoId); stmt.setInt(8, mantenimientoId);
return stmt.executeUpdate() > 0; return stmt.executeUpdate() > 0;
} catch (SQLException e) { } catch (SQLException e) {
e.printStackTrace();
logger.error("Error al actualizar el mantenimiento: ", e);
return false; return false;
} }
} }
/* ----------------Actualizar los detalles del hardaware por ID ---------------- */ /* ----------------Actualizar los detalles del hardaware por ID ---------------- */
public boolean actualizarHardwareDetalle(HardwareDetalle detalle) { public boolean actualizarHardwareDetalle(HardwareDetalle detalle) {
String sql = "UPDATE HARDWAREDET SET MODELO=?, NUMSERIE=?, PLACA=? WHERE HARDWAREDETID=?"; String sql = "UPDATE HARDWAREDET SET MODELO=?, NUMSERIE=?, PLACA=? WHERE HARDWAREDETID=?";
@ -834,12 +871,11 @@ public class DatabaseService {
stmt.setInt(4, detalle.getHardwareDetId()); stmt.setInt(4, detalle.getHardwareDetId());
return stmt.executeUpdate() > 0; return stmt.executeUpdate() > 0;
} catch (SQLException e) { } catch (SQLException e) {
e.printStackTrace();
logger.error("Error al actualizar el detalle del hardware: ", e);
return false; return false;
} }
} }
/* ----------------Actualizar las actualizaciones de seguridad por ID ---------------- */ /* ----------------Actualizar las actualizaciones de seguridad por ID ---------------- */
public boolean actualizarActualizacionSeg(ActualizacioneSeguridadModel actualizacion) { public boolean actualizarActualizacionSeg(ActualizacioneSeguridadModel actualizacion) {
String sql = "UPDATE ACTUALIZACIONESSEG SET OTRASACTUALIZACIONES=? WHERE ACTUALIZACIONSEGID=?"; String sql = "UPDATE ACTUALIZACIONESSEG SET OTRASACTUALIZACIONES=? WHERE ACTUALIZACIONSEGID=?";
@ -849,12 +885,11 @@ public class DatabaseService {
stmt.setInt(2, actualizacion.getActualizacionsegId()); stmt.setInt(2, actualizacion.getActualizacionsegId());
return stmt.executeUpdate() > 0; return stmt.executeUpdate() > 0;
} catch (SQLException e) { } catch (SQLException e) {
e.printStackTrace();
logger.error("Error al actualizar la actualización de seguridad: ", e);
return false; return false;
} }
} }
/* ---------------- Insertar en bitacora ---------------- */ /* ---------------- Insertar en bitacora ---------------- */
public boolean insertarBitacora(int mantenimientoid, String usuarioid, LocalDate fechora, String motivo) { public boolean insertarBitacora(int mantenimientoid, String usuarioid, LocalDate fechora, String motivo) {
String query = "INSERT INTO BITACORACTUALIZACIONES (MANTENIMIENTOID, USUARIOID, FECHORA, MOTIVO) VALUES (?, ?, ?, ?)"; String query = "INSERT INTO BITACORACTUALIZACIONES (MANTENIMIENTOID, USUARIOID, FECHORA, MOTIVO) VALUES (?, ?, ?, ?)";
@ -866,12 +901,11 @@ public class DatabaseService {
stmt.setString(4, motivo); stmt.setString(4, motivo);
return stmt.executeUpdate() > 0; return stmt.executeUpdate() > 0;
} catch (SQLException e) { } catch (SQLException e) {
e.printStackTrace();
logger.error("Error al insertar en bitacora: ", e);
return false; return false;
} }
} }
/* ---------------- Insertar en bitacora de eliminacion de equipos ---------------- */ /* ---------------- Insertar en bitacora de eliminacion de equipos ---------------- */
public boolean insertarBitacoraEliminacion(int plananualid, String usuarioid, LocalDate fechora, String motivo) { public boolean insertarBitacoraEliminacion(int plananualid, String usuarioid, LocalDate fechora, String motivo) {
String query = "INSERT INTO BITACORAELIMINACIONES (PLANANUALID, USUARIOID, FECHAHORA, MOTIVO) VALUES (?, ?, ?, ?)"; String query = "INSERT INTO BITACORAELIMINACIONES (PLANANUALID, USUARIOID, FECHAHORA, MOTIVO) VALUES (?, ?, ?, ?)";
@ -883,12 +917,11 @@ public class DatabaseService {
stmt.setString(4, motivo); stmt.setString(4, motivo);
return stmt.executeUpdate() > 0; return stmt.executeUpdate() > 0;
} catch (SQLException e) { } catch (SQLException e) {
e.printStackTrace();
logger.error("Error al insertar en bitacora de eliminacion de equipos: ", e);
return false; return false;
} }
} }
/* ---------------- Eliminar equipo de PLAN ANUAL ---------------- */ /* ---------------- Eliminar equipo de PLAN ANUAL ---------------- */
public void eliminarEquipoPlanAnual(int planAnualId) { public void eliminarEquipoPlanAnual(int planAnualId) {
String sql = "DELETE FROM PLANANUAL WHERE PLANANUALID = ?"; String sql = "DELETE FROM PLANANUAL WHERE PLANANUALID = ?";
@ -903,8 +936,7 @@ public class DatabaseService {
System.out.println("No se encontró el equipo con ID: " + planAnualId); System.out.println("No se encontró el equipo con ID: " + planAnualId);
} }
} catch (SQLException e) { } catch (SQLException e) {
System.err.println("Error al eliminar el equipo de PLANANUAL: " + e.getMessage());
e.printStackTrace();
logger.error("Error al eliminar el equipo de PLANANUAL: ", e);
} }
} }
@ -935,19 +967,18 @@ public class DatabaseService {
preguntas.add(pregunta); preguntas.add(pregunta);
} }
} catch (SQLException e) { } catch (SQLException e) {
e.printStackTrace();
logger.error("Error al obtener las preguntas de la encuesta: ", e);
} }
return preguntas; return preguntas;
} }
public void insertRespuestas(int manteniminetoId, List<Respuesta> respuestas, int empleadoid, String token) {
public void insertRespuestas(int manteniminetoId, List<Respuesta> respuestas, int empleadoid, String comentarios, String token) {
String insertQuery = """ String insertQuery = """
insert into RESPUESTAS insert into RESPUESTAS
(MANTENIMIENTOID,PREGUNTAID,FECHARESPUESTA,RESPUESTA,EMPLEADOID)
(MANTENIMIENTOID,PREGUNTAID,FECHARESPUESTA,RESPUESTA,EMPLEADOID,COMENTARIOS)
values values
(?,?,?,?,?)
(?,?,?,?,?,?)
"""; """;
String updateQuery = """ String updateQuery = """
@ -978,6 +1009,7 @@ public class DatabaseService {
insertStmt.setTimestamp(3, ahora); insertStmt.setTimestamp(3, ahora);
insertStmt.setBoolean(4, r.getRespuesta()); insertStmt.setBoolean(4, r.getRespuesta());
insertStmt.setInt(5, empleadoid); insertStmt.setInt(5, empleadoid);
insertStmt.setString(6, comentarios);
insertStmt.addBatch(); insertStmt.addBatch();
} }
@ -1003,7 +1035,6 @@ public class DatabaseService {
} }
} }
public String crearTokenEncuesta(int mantenimientoid) { public String crearTokenEncuesta(int mantenimientoid) {
String token = UUID.randomUUID().toString(); String token = UUID.randomUUID().toString();
Timestamp expira = new Timestamp(System.currentTimeMillis() + (24 * 60 * 60 * 1000)); Timestamp expira = new Timestamp(System.currentTimeMillis() + (24 * 60 * 60 * 1000));
@ -1018,7 +1049,7 @@ public class DatabaseService {
stmt.executeUpdate(); stmt.executeUpdate();
} catch (SQLException e) { } catch (SQLException e) {
e.printStackTrace();
logger.error("Error al crear el token de encuesta: ", e);
} }
return token; return token;
} }
@ -1035,9 +1066,56 @@ public class DatabaseService {
return rs.getInt("MANTENIMIENTOID"); return rs.getInt("MANTENIMIENTOID");
} }
} catch (SQLException e) { } catch (SQLException e) {
e.printStackTrace();
logger.error("Error al validar el token de encuesta: ", e);
} }
return null; return null;
} }
public List<ConteoEncuestas> getTotalEncuestas() {
List<ConteoEncuestas> totalEncuestas = new ArrayList<>();
String query = "SELECT * FROM VW_CONTEO_ENCUESTAS";
try (Connection conn = getMysqlConnection();
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(query)) {
while (rs.next()) {
ConteoEncuestas conteo = new ConteoEncuestas(
rs.getInt("TOTAL_ENVIADAS"),
rs.getInt("TOTAL_RESPONDIDAS"),
rs.getDouble("PORCENTAJE_TOTAL")
);
totalEncuestas.add(conteo);
}
} catch (SQLException e) {
logger.error("Error al obtener el total de encuestas: ", e);
}
return totalEncuestas;
}
public List<ConteoRespuestas> getTotalRespuestas() {
List<ConteoRespuestas> totalRespuestas = new ArrayList<>();
String query = "SELECT * FROM VW_CONTEO_RESPUESTAS";
try (Connection conn = getMysqlConnection();
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(query)) {
while (rs.next()) {
ConteoRespuestas conteo = new ConteoRespuestas(
rs.getInt("PREGUNTAID"),
rs.getString("PREGUNTA"),
rs.getInt("TOTALREGISTROS"),
rs.getInt("TOTALSI"),
rs.getInt("TOTALNO"),
rs.getDouble("PORCENTAJE")
);
totalRespuestas.add(conteo);
}
} catch (SQLException e) {
logger.error("Error al obtener el total de respuestas: ", e);
}
return totalRespuestas;
}
/***************************************************************************************************************************************/ /***************************************************************************************************************************************/
} }

+ 8
- 12
src/main/java/mx/gob/jumapacelaya/services/EmailService.java View File

@ -1,21 +1,14 @@
package mx.gob.jumapacelaya.services; package mx.gob.jumapacelaya.services;
import com.vaadin.flow.component.notification.Notification;
import jakarta.activation.DataSource;
import jakarta.mail.internet.MimeMessage; import jakarta.mail.internet.MimeMessage;
import jakarta.mail.util.ByteArrayDataSource;
import mx.gob.jumapacelaya.models.Usuario;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.InputStreamResource;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender; import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMailMessage;
import org.springframework.mail.javamail.MimeMessageHelper; import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.io.File;
import java.io.InputStream;
@Service @Service
public class EmailService { public class EmailService {
@ -23,10 +16,12 @@ public class EmailService {
@Autowired @Autowired
private JavaMailSender mailSender; private JavaMailSender mailSender;
private static final Logger log = LoggerFactory.getLogger(EmailService.class);
public void enviarCorreo(String destinatario, String asunto, String cuerpo, String imagePath) { public void enviarCorreo(String destinatario, String asunto, String cuerpo, String imagePath) {
try { try {
MimeMessage mensaje = mailSender.createMimeMessage(); MimeMessage mensaje = mailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(mensaje, true);
MimeMessageHelper helper = new MimeMessageHelper(mensaje, true, "UTF-8");
helper.setTo(destinatario); helper.setTo(destinatario);
helper.setSubject(asunto); helper.setSubject(asunto);
helper.setFrom("noreply@jumapacelaya.gob.mx"); helper.setFrom("noreply@jumapacelaya.gob.mx");
@ -37,10 +32,11 @@ public class EmailService {
helper.addInline("image_id", imgResource); helper.addInline("image_id", imgResource);
mailSender.send(mensaje); mailSender.send(mensaje);
System.out.println("Correo enviado con imagen exitosamente");
log.info("Correo enviado a {}", destinatario);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace();
log.error("Error al enviar el correo: ", e);
throw new RuntimeException("Error al enviar el correo: " + e.getMessage()); throw new RuntimeException("Error al enviar el correo: " + e.getMessage());
} }
} }


+ 1
- 1
src/main/java/mx/gob/jumapacelaya/services/LdapService.java View File

@ -25,7 +25,7 @@ public class LdapService {
SearchControls searchControls = new SearchControls(); SearchControls searchControls = new SearchControls();
searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE); searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
searchControls.setReturningAttributes(new String[]{"givenName", "sn", "mail"}); searchControls.setReturningAttributes(new String[]{"givenName", "sn", "mail"});
searchControls.setReturningObjFlag(true);
searchControls.setReturningObjFlag(false);
List<CustomUserDetails> result = ldapTemplate.search("", filter.encode(), searchControls, (Attributes attrs) -> { List<CustomUserDetails> result = ldapTemplate.search("", filter.encode(), searchControls, (Attributes attrs) -> {
String firstName = getAttribute(attrs, "givenName"); String firstName = getAttribute(attrs, "givenName");


+ 261
- 113
src/main/java/mx/gob/jumapacelaya/ui/ActDiariaView.java View File

@ -11,9 +11,7 @@ import com.vaadin.flow.component.dependency.CssImport;
import com.vaadin.flow.component.dialog.Dialog; import com.vaadin.flow.component.dialog.Dialog;
import com.vaadin.flow.component.grid.Grid; import com.vaadin.flow.component.grid.Grid;
import com.vaadin.flow.component.grid.GridVariant; import com.vaadin.flow.component.grid.GridVariant;
import com.vaadin.flow.component.html.Anchor;
import com.vaadin.flow.component.html.Div; import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.component.html.H3;
import com.vaadin.flow.component.html.Span; import com.vaadin.flow.component.html.Span;
import com.vaadin.flow.component.icon.Icon; import com.vaadin.flow.component.icon.Icon;
import com.vaadin.flow.component.icon.VaadinIcon; import com.vaadin.flow.component.icon.VaadinIcon;
@ -24,9 +22,9 @@ import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.orderedlayout.VerticalLayout; import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.component.popover.Popover; import com.vaadin.flow.component.popover.Popover;
import com.vaadin.flow.component.popover.PopoverPosition; import com.vaadin.flow.component.popover.PopoverPosition;
import com.vaadin.flow.component.progressbar.ProgressBar;
import com.vaadin.flow.component.richtexteditor.RichTextEditor; import com.vaadin.flow.component.richtexteditor.RichTextEditor;
import com.vaadin.flow.data.renderer.ComponentRenderer; import com.vaadin.flow.data.renderer.ComponentRenderer;
import com.vaadin.flow.function.SerializableBiConsumer;
import com.vaadin.flow.router.PageTitle; import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route; import com.vaadin.flow.router.Route;
import com.vaadin.flow.server.StreamRegistration; import com.vaadin.flow.server.StreamRegistration;
@ -34,31 +32,29 @@ import com.vaadin.flow.server.StreamResource;
import jakarta.annotation.security.PermitAll; import jakarta.annotation.security.PermitAll;
import mx.gob.jumapacelaya.api.RedmineClient; import mx.gob.jumapacelaya.api.RedmineClient;
import mx.gob.jumapacelaya.api.ServerProperties; import mx.gob.jumapacelaya.api.ServerProperties;
import mx.gob.jumapacelaya.models.ActividadDiaria;
import mx.gob.jumapacelaya.models.RedmineUser; import mx.gob.jumapacelaya.models.RedmineUser;
import mx.gob.jumapacelaya.models.Ticket; import mx.gob.jumapacelaya.models.Ticket;
import mx.gob.jumapacelaya.services.LdapService;
import mx.gob.jumapacelaya.services.UserService; import mx.gob.jumapacelaya.services.UserService;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.util.IOUtils;
import org.apache.poi.xssf.usermodel.XSSFWorkbook; import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.vaadin.lineawesome.LineAwesomeIcon; import org.vaadin.lineawesome.LineAwesomeIcon;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.text.SimpleDateFormat;
import java.io.InputStream;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit; import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.stream.Stream; import java.util.stream.Stream;
@ -91,11 +87,12 @@ public class ActDiariaView extends VerticalLayout {
// Layout de opciones // Layout de opciones
opcionesLyt = new HorizontalLayout(); opcionesLyt = new HorizontalLayout();
opcionesLyt.setWidthFull(); opcionesLyt.setWidthFull();
opcionesLyt.setMargin(false);
opcionesLyt.setAlignItems(Alignment.BASELINE);
//opcionesLyt.setMargin(false);
opcionesLyt.getStyle() opcionesLyt.getStyle()
.set("box-shadow", "0 4px 8px rgba(0,0,0,0.2)") .set("box-shadow", "0 4px 8px rgba(0,0,0,0.2)")
.set("border-radius", "10px") .set("border-radius", "10px")
.set("background-color", "white")
//.set("background-color", "white")
.set("padding", "1rem") .set("padding", "1rem")
.set("margin", "1rem auto"); .set("margin", "1rem auto");
@ -103,18 +100,42 @@ public class ActDiariaView extends VerticalLayout {
chkSoloAbiertos = new Checkbox("Ver solo abiertos"); chkSoloAbiertos = new Checkbox("Ver solo abiertos");
chkSoloAbiertos.setValue(false); // inicial: mostrar cerrados chkSoloAbiertos.setValue(false); // inicial: mostrar cerrados
fechaDesde = new DatePicker("Fecha desde:");
fechaHasta = new DatePicker("Fecha hasta:");
btnBuscar = new Button("Buscar");
fechaDesde = new DatePicker();
fechaDesde.setPlaceholder("Fecha desde:");
fechaDesde.setLocale(new Locale("es", "MX"));
DatePicker.DatePickerI18n i18n = new DatePicker.DatePickerI18n()
.setWeekdays(List.of("Domingo","Lunes","Martes","Miércoles","Jueves","Viernes","Sábado"))
.setWeekdaysShort(List.of("Dom","Lun","Mar","Mié","Jue","Vie","Sab"))
.setMonthNames(List.of("Enero","Febrero","Marzo","Abril","Mayo","Junio","Julio","Agosto","Septiembre","Octubre","Noviembre","Diciembre"))
.setFirstDayOfWeek(1)
.setToday("Hoy")
.setCancel("Cancelar")
.setDateFormat("dd/MM/yyyy");
fechaDesde.setI18n(i18n);
fechaHasta = new DatePicker();
fechaHasta.setPlaceholder("Fecha hasta:");
fechaHasta.setLocale(new Locale("es", "MX"));
DatePicker.DatePickerI18n i18nh = new DatePicker.DatePickerI18n()
.setWeekdays(List.of("Domingo","Lunes","Martes","Miércoles","Jueves","Viernes","Sábado"))
.setWeekdaysShort(List.of("Dom","Lun","Mar","Mié","Jue","Vie","Sab"))
.setMonthNames(List.of("Enero","Febrero","Marzo","Abril","Mayo","Junio","Julio","Agosto","Septiembre","Octubre","Noviembre","Diciembre"))
.setFirstDayOfWeek(1)
.setToday("Hoy")
.setCancel("Cancelar")
.setDateFormat("dd/MM/yyyy");
fechaHasta.setI18n(i18nh);
btnBuscar = new Button("Buscar", VaadinIcon.SEARCH.create());
// Listener del checkbox // Listener del checkbox
chkSoloAbiertos.addValueChangeListener(e -> { chkSoloAbiertos.addValueChangeListener(e -> {
boolean soloAbiertos = e.getValue(); // marcado = abiertos boolean soloAbiertos = e.getValue(); // marcado = abiertos
loadTickets(soloAbiertos);
fechaDesde.setEnabled(!soloAbiertos); fechaDesde.setEnabled(!soloAbiertos);
fechaHasta.setEnabled(!soloAbiertos); fechaHasta.setEnabled(!soloAbiertos);
btnBuscar.setEnabled(!soloAbiertos); btnBuscar.setEnabled(!soloAbiertos);
grid.getDataProvider().refreshAll();
}); });
// Estado inicial // Estado inicial
@ -128,6 +149,30 @@ public class ActDiariaView extends VerticalLayout {
opcionesLyt.add(chkSoloAbiertos, fechaDesde, fechaHasta, btnBuscar); opcionesLyt.add(chkSoloAbiertos, fechaDesde, fechaHasta, btnBuscar);
grid.setItems(
query -> {
try {
return redmineClient.getTickets(
userService.getRedmineUser(),
chkSoloAbiertos.getValue(),
query.getOffset(),
query.getLimit(),
fechaDesde.getValue(),
fechaHasta.getValue()
).stream();
} catch (Exception e) {
log.error("Error al cargar tickets", e);
return Stream.empty();
}
},
query -> redmineClient.getTotalTickets(
userService.getRedmineUser(),
chkSoloAbiertos.getValue(),
fechaDesde.getValue(),
fechaHasta.getValue()
)
);
// Configuración de columnas del grid // Configuración de columnas del grid
grid.addColumn(Ticket::getId).setHeader("No.") grid.addColumn(Ticket::getId).setHeader("No.")
.setAutoWidth(true) .setAutoWidth(true)
@ -148,6 +193,7 @@ public class ActDiariaView extends VerticalLayout {
return fecha != null ? fecha.format(DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm:ss")) : ""; return fecha != null ? fecha.format(DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm:ss")) : "";
}).setHeader("Fecha creación") }).setHeader("Fecha creación")
.setAutoWidth(true) .setAutoWidth(true)
.setSortable(true)
.setKey("fechaCreacion"); .setKey("fechaCreacion");
grid.addColumn(ticket -> { grid.addColumn(ticket -> {
@ -155,6 +201,7 @@ public class ActDiariaView extends VerticalLayout {
return fecha != null ? fecha.format(DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm:ss")) : ""; return fecha != null ? fecha.format(DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm:ss")) : "";
}).setHeader("Fecha cierre") }).setHeader("Fecha cierre")
.setAutoWidth(true) .setAutoWidth(true)
.setSortable(true)
.setKey("fechaCierre"); .setKey("fechaCierre");
grid.addColumn(ticket -> { grid.addColumn(ticket -> {
@ -198,7 +245,7 @@ public class ActDiariaView extends VerticalLayout {
} else { } else {
return ""; return "";
} }
}).setHeader("Duración").setAutoWidth(true).setKey("duracion");
}).setHeader("Duración").setAutoWidth(true).setSortable(true).setKey("duracion");
grid.addColumn(ticket -> grid.addColumn(ticket ->
@ -217,7 +264,6 @@ public class ActDiariaView extends VerticalLayout {
grid.addComponentColumn(ticket -> { grid.addComponentColumn(ticket -> {
Button btnVer = new Button(new Icon(VaadinIcon.EYE)); Button btnVer = new Button(new Icon(VaadinIcon.EYE));
btnVer.addClickListener(event -> showDescription(ticket)); btnVer.addClickListener(event -> showDescription(ticket));
btnVer.getStyle().set("color", "#A02142");
return btnVer; return btnVer;
}); });
@ -229,6 +275,7 @@ public class ActDiariaView extends VerticalLayout {
btnColumns = new Button(VaadinIcon.GRID_H.create()); btnColumns = new Button(VaadinIcon.GRID_H.create());
// Botón para exportar // Botón para exportar
btnExport = new Button("Exportar", LineAwesomeIcon.FILE_EXCEL.create()); btnExport = new Button("Exportar", LineAwesomeIcon.FILE_EXCEL.create());
btnExport.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
btnExport.addClickListener(e -> exportTicketsCerrados()); btnExport.addClickListener(e -> exportTicketsCerrados());
showColumnsLyt = new HorizontalLayout(btnExport, btnColumns); showColumnsLyt = new HorizontalLayout(btnExport, btnColumns);
showColumnsLyt.setJustifyContentMode(JustifyContentMode.END); showColumnsLyt.setJustifyContentMode(JustifyContentMode.END);
@ -281,30 +328,6 @@ public class ActDiariaView extends VerticalLayout {
// Invertir parámetro para que coincida con RedmineClient // Invertir parámetro para que coincida con RedmineClient
boolean paramCliente = !soloAbiertos; boolean paramCliente = !soloAbiertos;
grid.setItems(
query -> {
try {
return redmineClient.getTickets(
user,
chkSoloAbiertos.getValue(),
query.getOffset(),
query.getLimit(),
fechaDesde.getValue(),
fechaHasta.getValue()
).stream();
} catch (Exception e) {
e.printStackTrace();
return Stream.empty();
}
},
query -> redmineClient.getTotalTickets(
user,
chkSoloAbiertos.getValue(),
fechaDesde.getValue(),
fechaHasta.getValue()
)
);
} }
@ -365,7 +388,7 @@ public class ActDiariaView extends VerticalLayout {
UI.getCurrent().getPage().executeJs("setTimeout(() => { window.location.reload(); }, 3000);"); UI.getCurrent().getPage().executeJs("setTimeout(() => { window.location.reload(); }, 3000);");
} catch (IOException | InterruptedException e) { } catch (IOException | InterruptedException e) {
e.printStackTrace();
log.error("Error al cerrar el ticket " + ticket.getId(), e);
Notification.show("Error al cerrar el ticket " + ticket.getId()); Notification.show("Error al cerrar el ticket " + ticket.getId());
} }
} }
@ -399,85 +422,210 @@ public class ActDiariaView extends VerticalLayout {
// Metodo para exportar los tickets cerrados a un archivo de Excel // Metodo para exportar los tickets cerrados a un archivo de Excel
private void exportTicketsCerrados() { private void exportTicketsCerrados() {
try {
RedmineUser user = userService.getRedmineUser();
// Traer TODOS los tickets cerrados, respetando fechas si están seleccionadas
List<Ticket> ticketsCerrados = redmineClient.getAllTickets(
user,
false, // false = cerrados
fechaDesde.getValue(),
fechaHasta.getValue()
);
Workbook workbook = new XSSFWorkbook();
Sheet sheet = workbook.createSheet("Tickets cerrados");
String[] headers = {"ID","Tipo","Tiempo estimado","Fecha creación","Fecha cierre",
"Fecha actualización","Situación","Estado","Duración","Autor","Asunto"};
Row headerRow = sheet.createRow(0);
for (int i = 0; i < headers.length; i++) {
Cell cell = headerRow.createCell(i);
cell.setCellValue(headers[i]);
}
final UI ui = UI.getCurrent();
final RedmineUser user = userService.getRedmineUser();
final LocalDate desde = fechaDesde.getValue();
final LocalDate hasta = fechaHasta.getValue();
int rowNum = 1;
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm:ss");
for (Ticket ticket : ticketsCerrados) {
Row row = sheet.createRow(rowNum++);
row.createCell(0).setCellValue(ticket.getId());
row.createCell(1).setCellValue(ticket.getType());
row.createCell(2).setCellValue(ticket.getEstimatedHrs() + " horas");
row.createCell(3).setCellValue(ticket.getDateCreate() != null ? ticket.getDateCreate().format(formatter) : "");
row.createCell(4).setCellValue(ticket.getDateClose() != null ? ticket.getDateClose().format(formatter) : "");
row.createCell(5).setCellValue(ticket.getUpdateOn() != null ? ticket.getUpdateOn().format(formatter) : "");
row.createCell(7).setCellValue(ticket.getStatus());
row.createCell(9).setCellValue(ticket.getAuthor() != null ? ticket.getAuthor().getUsername() : "");
row.createCell(10).setCellValue(ticket.getSubject());
if (ticket.getDateCreateLocal() != null && ticket.getDateCloseLocal() != null) {
long dias = ChronoUnit.DAYS.between(ticket.getDateCreateLocal(), ticket.getDateCloseLocal());
row.createCell(8).setCellValue(dias + " días");
} else {
row.createCell(8).setCellValue("");
if (ui == null) return;
Dialog loadingDialog = createLoadingDialog("Generando archivo por favor espere...");
loadingDialog.open();
new Thread(() -> {
try {
// Traer TODOS los tickets cerrados, respetando fechas si están seleccionadas
List<Ticket> ticketsCerrados = redmineClient.getAllTickets(
user,
false, // false = cerrados
desde,
hasta
);
Workbook workbook = new XSSFWorkbook();
Sheet sheet = workbook.createSheet("Tickets cerrados");
CellStyle titleStyle = workbook.createCellStyle();
Font titleFont = workbook.createFont();
titleFont.setBold(true);
titleFont.setFontHeightInPoints((short) 20);
titleStyle.setFont(titleFont);
titleStyle.setAlignment(HorizontalAlignment.CENTER);
CellStyle subTitleStyle = workbook.createCellStyle();
Font subTitleFont = workbook.createFont();
subTitleFont.setFontHeightInPoints((short) 18);
subTitleStyle.setFont(subTitleFont);
subTitleStyle.setAlignment(HorizontalAlignment.CENTER);
int ultimaCol = 10;
Row row1 = sheet.createRow(1);
Cell cell1 = row1.createCell(0);
cell1.setCellValue("JUNTA MUNICIPAL DE AGUA POTABLE Y ALCANTARILLADO DE CELAYA.");
cell1.setCellStyle(titleStyle);
sheet.addMergedRegion(new CellRangeAddress(1, 1, 0, ultimaCol));
Row row2 = sheet.createRow(2);
Cell cell2 = row2.createCell(0);
cell2.setCellValue("División Del Norte #132, Col. El Vergel, Celaya, Gto. C.P: 38078");
cell2.setCellStyle(subTitleStyle);
sheet.addMergedRegion(new CellRangeAddress(2, 2, 0, ultimaCol));
try (
InputStream jmpaLogo = ActDiariaView.class.getClassLoader()
.getResourceAsStream("META-INF/resources/images/LOGO_24'27.png");
InputStream adminLogo = ActDiariaView.class.getClassLoader()
.getResourceAsStream("META-INF/resources/images/LOGO_admon2.png")
) {
if (jmpaLogo != null || adminLogo != null) {
byte[] bytes = IOUtils.toByteArray(jmpaLogo);
byte[] bytes1 = IOUtils.toByteArray(adminLogo);
int pictureIdx = workbook.addPicture(bytes, Workbook.PICTURE_TYPE_PNG);
int pictureIdx1 = workbook.addPicture(bytes1, Workbook.PICTURE_TYPE_PNG);
Drawing<?> drawing = sheet.createDrawingPatriarch();
CreationHelper helper = workbook.getCreationHelper();
CreationHelper helper1 = workbook.getCreationHelper();
ClientAnchor anchor = helper.createClientAnchor();
ClientAnchor anchor1 = helper1.createClientAnchor();
// Posición en la esquina superior izquierda
anchor.setCol1(1);
anchor.setRow1(1);
anchor1.setCol1(10);
anchor1.setRow1(1);
Picture pict = drawing.createPicture(anchor, pictureIdx);
pict.resize(0.30, 0.60); // Ajusta según el tamaño de tu imagen original
Picture pict1 = drawing.createPicture(anchor1, pictureIdx1);
pict1.resize(0.30, 0.37);
} else {
log.error("No se encontró el archivo de imagen en la ruta especificada");
}
} catch (Exception e) {
log.error("Error al procesar el logo", e);
} }
if (ticket.getEstimatedHrs() != null && ticket.getDateCreateLocal() != null && ticket.getDateCloseLocal() != null) {
long horasReales = ChronoUnit.HOURS.between(ticket.getDateCreateLocal(), ticket.getDateCloseLocal());
row.createCell(6).setCellValue(horasReales <= ticket.getEstimatedHrs() ? "En tiempo" : "Excedido");
} else {
row.createCell(6).setCellValue("Sin estimación");
CellStyle headerStyle = workbook.createCellStyle();
Font headerFont = workbook.createFont();
headerFont.setBold(true);
headerFont.setColor(IndexedColors.WHITE.getIndex());
headerStyle.setFont(headerFont);
headerStyle.setFillForegroundColor(IndexedColors.MAROON.getIndex());
headerStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
headerStyle.setAlignment(HorizontalAlignment.CENTER);
headerStyle.setBorderBottom(BorderStyle.THIN);
String[] headers = {"ID","Tipo","Tiempo estimado","Fecha creación","Fecha cierre",
"Fecha actualización","Situación","Estado","Duración","Autor","Asunto"};
int rowNum = 6;
Row headerRow = sheet.createRow(rowNum++);
for (int i = 0; i < headers.length; i++) {
Cell cell = headerRow.createCell(i);
cell.setCellValue(headers[i]);
cell.setCellStyle(headerStyle);
} }
}
for (int i = 0; i < headers.length; i++) {
sheet.autoSizeColumn(i);
}
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm:ss");
for (Ticket ticket : ticketsCerrados) {
Row row = sheet.createRow(rowNum++);
row.createCell(0).setCellValue(ticket.getId());
row.createCell(1).setCellValue(ticket.getType());
row.createCell(2).setCellValue(ticket.getEstimatedHrs() + " horas");
row.createCell(3).setCellValue(ticket.getDateCreate() != null ? ticket.getDateCreate().format(formatter) : "");
row.createCell(4).setCellValue(ticket.getDateClose() != null ? ticket.getDateClose().format(formatter) : "");
row.createCell(5).setCellValue(ticket.getUpdateOn() != null ? ticket.getUpdateOn().format(formatter) : "");
row.createCell(7).setCellValue(ticket.getStatus());
row.createCell(9).setCellValue(ticket.getAuthor() != null ? ticket.getAuthor().getUsername() : "");
row.createCell(10).setCellValue(ticket.getSubject());
if (ticket.getDateCreateLocal() != null && ticket.getDateCloseLocal() != null) {
long dias = ChronoUnit.DAYS.between(ticket.getDateCreateLocal(), ticket.getDateCloseLocal());
row.createCell(8).setCellValue(dias + " días");
} else {
row.createCell(8).setCellValue("");
}
ByteArrayOutputStream out = new ByteArrayOutputStream();
workbook.write(out);
workbook.close();
if (ticket.getEstimatedHrs() != null && ticket.getDateCreateLocal() != null && ticket.getDateCloseLocal() != null) {
long horasReales = ChronoUnit.HOURS.between(ticket.getDateCreateLocal(), ticket.getDateCloseLocal());
row.createCell(6).setCellValue(horasReales <= ticket.getEstimatedHrs() ? "En tiempo" : "Excedido");
} else {
row.createCell(6).setCellValue("Sin estimación");
}
}
StreamResource resource = new StreamResource("tickets_cerrados.xlsx",
() -> new ByteArrayInputStream(out.toByteArray()));
for (int i = 0; i < headers.length; i++) {
sheet.autoSizeColumn(i);
}
StreamRegistration registration = UI.getCurrent().getSession()
.getResourceRegistry().registerResource(resource);
UI.getCurrent().getPage().executeJs(
"window.open('" + registration.getResourceUri().toString() + "','_blank')"
);
ByteArrayOutputStream out = new ByteArrayOutputStream();
workbook.write(out);
workbook.close();
} catch (Exception e) {
log.error("Error exportando tickets", e);
Notification.show("Error al exportar los tickets", 5000, Notification.Position.MIDDLE)
.addThemeVariants(NotificationVariant.LUMO_ERROR);
}
ui.access(() -> {
try {
StreamResource resource = new StreamResource("rptGeneralDeTicketsMsaAyda.xlsx",
() -> new ByteArrayInputStream(out.toByteArray()));
StreamRegistration registration = ui.getSession()
.getResourceRegistry().registerResource(resource);
ui.getPage().executeJs(
"window.open('" + registration.getResourceUri().toString() + "','_blank')"
);
} catch (Exception ex) {
log.error("Error al registrar recurso", ex);
} finally {
loadingDialog.close();
}
});
} catch (Exception e) {
log.error("Error exportando tickets", e);
ui.access(() -> {
loadingDialog.close();
Notification.show("Error al generar el reporte", 5000, Notification.Position.MIDDLE)
.addThemeVariants(NotificationVariant.LUMO_ERROR);
});
}
}).start();
} }
private void filtrarTickets() { private void filtrarTickets() {
boolean soloAbiertos = chkSoloAbiertos.getValue();
loadTickets(soloAbiertos);
grid.getDataProvider().refreshAll();
}
private Dialog createLoadingDialog(String mensaje) {
Dialog dialog = new Dialog();
dialog.setCloseOnEsc(false);
dialog.setCloseOnOutsideClick(false);
VerticalLayout layout = new VerticalLayout();
layout.setAlignItems(Alignment.CENTER);
layout.setPadding(true);
layout.setSpacing(true);
ProgressBar progressBar = new ProgressBar();
progressBar.setIndeterminate(true);
progressBar.setWidth("15rem");
Span texto = new Span(mensaje);
texto.getStyle().set("font-weight", "bold");
texto.getStyle().set("color", "var(--lumo-primary-text-color)");
layout.add(texto, progressBar);
dialog.add(layout);
return dialog;
} }
} }

+ 203
- 0
src/main/java/mx/gob/jumapacelaya/ui/ConfiguracionView.java View File

@ -0,0 +1,203 @@
package mx.gob.jumapacelaya.ui;
import com.vaadin.flow.component.UI;
import com.vaadin.flow.component.combobox.ComboBox;
import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.component.html.H2;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.component.radiobutton.RadioButtonGroup;
import com.vaadin.flow.data.renderer.ComponentRenderer;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.server.VaadinSession;
import jakarta.annotation.security.PermitAll;
import java.util.List;
@PermitAll
@PageTitle("Preferencias del Sistema")
@Route(value = "configuracion", layout = MainLayout.class)
public class ConfiguracionView extends VerticalLayout {
private static final String THEME_LOCALSTORAGE_KEY = "appThemePreference";
private boolean initializing = true;
private final List<ColorOptions> primaryTextColorOptions = List.of(
new ColorOptions("Big Dip O´Ruby", "#A02142"),
new ColorOptions("Claret", "#691B31"),
new ColorOptions("Aztec Gold", "#BC955B"),
new ColorOptions("Lion", "#DDC9A3"),
new ColorOptions("Nickel", "#6F7271")
);
private final List<ColorOptions> primaryColorOptions = List.of(
new ColorOptions("Big Dip O´Ruby", "#A02142"),
new ColorOptions("Claret", "#691B31"),
new ColorOptions("Aztec Gold", "#BC955B"),
new ColorOptions("Lion", "#DDC9A3"),
new ColorOptions("Nickel", "#6F7271")
);
public ConfiguracionView() {
setSpacing(true);
add(new H2("⚙\uFE0F Preferencias del Sistema"));
loadCssVariablesFromLocalStorage();
RadioButtonGroup<String> themeSelector = createThemeSelector();
ComboBox<ColorOptions> primaryColor = createColorCombo(
"Color primario",
"--lumo-primary-color",
primaryColorOptions
);
ComboBox<ColorOptions> primaryTextColor = createColorCombo(
"Color de textos",
"--lumo-primary-text-color",
primaryTextColorOptions
);
HorizontalLayout row1 = new HorizontalLayout(themeSelector);
HorizontalLayout row2 = new HorizontalLayout(primaryColor, primaryTextColor);
VerticalLayout layoutPadre = new VerticalLayout(row1, row2);
layoutPadre.getStyle()
.set("border-radius", "10px")
.set("box-shadow", "0 4px 8px rgba(0, 0, 0, 0.2)");
add(layoutPadre);
UI.getCurrent().getPage().executeJs("return true;")
.then(Boolean.class, ok -> initializing = false);
}
/* -------------------- THEME -------------------- */
private RadioButtonGroup<String> createThemeSelector() {
RadioButtonGroup<String> selector = new RadioButtonGroup<>("Modo de interfaz");
selector.setItems("Claro", "Oscuro");
UI.getCurrent().getPage().executeJs(
"return localStorage.getItem($0)", THEME_LOCALSTORAGE_KEY
).then(String.class, theme ->
selector.setValue("dark".equals(theme) ? "Oscuro" : "Claro")
);
selector.addValueChangeListener(e -> {
if (initializing) return;
boolean dark = "Oscuro".equals(e.getValue());
UI.getCurrent().getPage().executeJs(
dark
? "document.documentElement.setAttribute('theme','dark')"
: "document.documentElement.removeAttribute('theme')"
);
UI.getCurrent().getPage().executeJs(
"localStorage.setItem($0,$1)",
THEME_LOCALSTORAGE_KEY,
dark ? "dark" : "light"
);
});
return selector;
}
/* -------------------- COLORS -------------------- */
private ComboBox<ColorOptions> createColorCombo(
String label,
String cssVariable,
List<ColorOptions> options
) {
ComboBox<ColorOptions> combo = new ComboBox<>(label);
combo.setItems(options);
combo.setWidth("250px");
combo.setAllowCustomValue(false);
UI.getCurrent().getPage().executeJs(
"return localStorage.getItem($0)",
"config:" + cssVariable
).then(String.class, saved -> {
options.stream()
.filter(o -> o.getHexValue().equalsIgnoreCase(saved))
.findFirst()
.ifPresent(combo::setValue);
});
combo.addValueChangeListener(e -> {
if (initializing || e.getValue() == null) return;
applyCssVariable(cssVariable, e.getValue().getHexValue());
});
combo.setRenderer(colorRenderer());
return combo;
}
private void applyCssVariable(String variable, String value) {
UI.getCurrent().getPage().executeJs(
"""
document.documentElement.style.setProperty($0,$1);
localStorage.setItem($2,$1);
""",
variable,
value,
"config:" + variable
);
}
private void loadCssVariablesFromLocalStorage() {
UI.getCurrent().getPage().executeJs(
"""
['--lumo-primary-color','--lumo-primary-text-color'].forEach(v=>{
const val = localStorage.getItem('config:'+v);
if(val) document.documentElement.style.setProperty(v,val);
});
"""
);
}
/* -------------------- RENDERER -------------------- */
private ComponentRenderer<Div, ColorOptions> colorRenderer() {
return new ComponentRenderer<>(opt -> {
Div wrapper = new Div();
wrapper.getStyle().set("display","flex").set("align-items","center");
Div dot = new Div();
dot.getStyle()
.set("width","14px")
.set("height","14px")
.set("border-radius","50%")
.set("margin-right","8px")
.set("background-color", opt.getHexValue())
.set("border","1px solid var(--lumo-border-color)");
wrapper.add(dot);
wrapper.add(opt.getName());
return wrapper;
});
}
/* -------------------- MODEL -------------------- */
public static class ColorOptions {
private final String name;
private final String hexValue;
public ColorOptions(String name, String hexValue) {
this.name = name;
this.hexValue = hexValue;
}
public String getName() { return name; }
public String getHexValue() { return hexValue; }
@Override
public String toString() {
return name;
}
}
}

+ 16
- 5
src/main/java/mx/gob/jumapacelaya/ui/DetallesMantView.java View File

@ -9,11 +9,15 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import com.vaadin.flow.component.UI; import com.vaadin.flow.component.UI;
import com.vaadin.flow.component.button.ButtonVariant;
import com.vaadin.flow.component.html.*; import com.vaadin.flow.component.html.*;
import com.vaadin.flow.component.page.History;
import com.vaadin.flow.server.StreamRegistration; import com.vaadin.flow.server.StreamRegistration;
import com.vaadin.flow.server.StreamResource; import com.vaadin.flow.server.StreamResource;
import mx.gob.jumapacelaya.services.EmailService; import mx.gob.jumapacelaya.services.EmailService;
import mx.gob.jumapacelaya.services.ReportService; import mx.gob.jumapacelaya.services.ReportService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
@ -53,6 +57,8 @@ import mx.gob.jumapacelaya.services.SecurityService;
@CssImport("./themes/sistema-mantenimiento/styles.css") @CssImport("./themes/sistema-mantenimiento/styles.css")
public class DetallesMantView extends VerticalLayout implements BeforeEnterObserver { public class DetallesMantView extends VerticalLayout implements BeforeEnterObserver {
private static final Logger logger = LoggerFactory.getLogger(DetallesMantView.class);
@Value("${app.base-url}") @Value("${app.base-url}")
private String baseUrl; private String baseUrl;
@ -75,6 +81,7 @@ public class DetallesMantView extends VerticalLayout implements BeforeEnterObser
private final Button btnCancelar; private final Button btnCancelar;
private final Button btnGuardar; private final Button btnGuardar;
private final Button btnEnviarEncuesta = new Button("Enviar encuesta", LineAwesomeIcon.ENVELOPE_SOLID.create()); private final Button btnEnviarEncuesta = new Button("Enviar encuesta", LineAwesomeIcon.ENVELOPE_SOLID.create());
private final Button btnVolverAtras = new Button(LineAwesomeIcon.ARROW_LEFT_SOLID.create());
private int planAnualIdActual; private int planAnualIdActual;
private int mantenimientoIdActual; private int mantenimientoIdActual;
private Dialog confirmDialog; private Dialog confirmDialog;
@ -102,7 +109,6 @@ public class DetallesMantView extends VerticalLayout implements BeforeEnterObser
mainLayout.getStyle() mainLayout.getStyle()
.set("box-shadow","0 4px 8px rgba(0,0,0,0.2)") .set("box-shadow","0 4px 8px rgba(0,0,0,0.2)")
.set("border-radius", "12px") .set("border-radius", "12px")
.set("background-color", "white")
.set("padding", "1rem") .set("padding", "1rem")
.set("margin", "1rem auto"); .set("margin", "1rem auto");
@ -197,9 +203,14 @@ public class DetallesMantView extends VerticalLayout implements BeforeEnterObser
gridActualizaciones.addThemeVariants(GridVariant.LUMO_ROW_STRIPES); gridActualizaciones.addThemeVariants(GridVariant.LUMO_ROW_STRIPES);
History history = UI.getCurrent().getPage().getHistory();
btnVolverAtras.addClickListener(e -> history.back());
btnVolverAtras.addThemeVariants(ButtonVariant.LUMO_TERTIARY_INLINE);
btnVolverAtras.setTooltipText("Volver a la lista de mantenimientos");
HorizontalLayout botonesHeaderLyt = new HorizontalLayout(); HorizontalLayout botonesHeaderLyt = new HorizontalLayout();
botonesHeaderLyt.setWidthFull(); botonesHeaderLyt.setWidthFull();
botonesHeaderLyt.add(btnImprimirRepo, btnEnviarEncuesta);
botonesHeaderLyt.add(btnVolverAtras, btnImprimirRepo, btnEnviarEncuesta);
HorizontalLayout botonesLayout = new HorizontalLayout(); HorizontalLayout botonesLayout = new HorizontalLayout();
botonesLayout.setWidthFull(); botonesLayout.setWidthFull();
@ -297,7 +308,7 @@ public class DetallesMantView extends VerticalLayout implements BeforeEnterObser
} catch (Exception ex) { } catch (Exception ex) {
Notification.show("Error al generar el reporte: " + ex.getMessage(), 4000, Notification.Position.MIDDLE) Notification.show("Error al generar el reporte: " + ex.getMessage(), 4000, Notification.Position.MIDDLE)
.addThemeVariants(NotificationVariant.LUMO_ERROR); .addThemeVariants(NotificationVariant.LUMO_ERROR);
ex.printStackTrace();
logger.error("Error al generar el reporte: ", ex);
} }
}); });
@ -542,7 +553,7 @@ public class DetallesMantView extends VerticalLayout implements BeforeEnterObser
String imagePath = "META-INF/resources/images/imgCorreo/imgEncuesta.png"; String imagePath = "META-INF/resources/images/imgCorreo/imgEncuesta.png";
emailService.enviarCorreo(destinatario,asunto,cuerpo,imagePath); emailService.enviarCorreo(destinatario,asunto,cuerpo,imagePath);
Notification.show("Encuesta enviada correctamente para el mantenimiento No. " + mantenimientoIdActual, 3000, Notification.Position.MIDDLE)
Notification.show("Encuesta enviada correctamente para el mantenimiento No. " + mantenimientoIdActual, 3000, Notification.Position.BOTTOM_END)
.addThemeVariants(NotificationVariant.LUMO_SUCCESS); .addThemeVariants(NotificationVariant.LUMO_SUCCESS);
} else { } else {
Notification.show("Por favor, seleccione un usuario destino", 3000, Notification.Position.MIDDLE); Notification.show("Por favor, seleccione un usuario destino", 3000, Notification.Position.MIDDLE);
@ -580,7 +591,7 @@ public class DetallesMantView extends VerticalLayout implements BeforeEnterObser
layout.setAlignItems(Alignment.CENTER); layout.setAlignItems(Alignment.CENTER);
avisoEncuestaNtf = new Notification(layout); avisoEncuestaNtf = new Notification(layout);
avisoEncuestaNtf.setPosition(Notification.Position.TOP_CENTER);
avisoEncuestaNtf.setPosition(Notification.Position.TOP_END);
avisoEncuestaNtf.addThemeVariants(NotificationVariant.LUMO_WARNING); avisoEncuestaNtf.addThemeVariants(NotificationVariant.LUMO_WARNING);
avisoEncuestaNtf.setDuration(5000); avisoEncuestaNtf.setDuration(5000);
avisoEncuestaNtf.open(); avisoEncuestaNtf.open();


+ 14
- 5
src/main/java/mx/gob/jumapacelaya/ui/EncuestaView.java View File

@ -10,6 +10,7 @@ import com.vaadin.flow.component.notification.NotificationVariant;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout; import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.orderedlayout.VerticalLayout; import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.component.radiobutton.RadioButtonGroup; import com.vaadin.flow.component.radiobutton.RadioButtonGroup;
import com.vaadin.flow.component.textfield.TextArea;
import com.vaadin.flow.component.textfield.TextField; import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.router.BeforeEnterEvent; import com.vaadin.flow.router.BeforeEnterEvent;
import com.vaadin.flow.router.BeforeEnterObserver; import com.vaadin.flow.router.BeforeEnterObserver;
@ -32,11 +33,10 @@ public class EncuestaView extends VerticalLayout implements BeforeEnterObserver
private final DatabaseService encuestasDBService; private final DatabaseService encuestasDBService;
private int mantenimientoId = -1; private int mantenimientoId = -1;
private final VerticalLayout mainLyt = new VerticalLayout(); private final VerticalLayout mainLyt = new VerticalLayout();
private final Span pregunta1Txt = new Span();
private final RadioButtonGroup<String> pregunta1Rb = new RadioButtonGroup<>();
private final Button btnEnviar = new Button("Enviar"); private final Button btnEnviar = new Button("Enviar");
private final TextField txtNumEmpl = new TextField("No. Empleado:"); private final TextField txtNumEmpl = new TextField("No. Empleado:");
private String token; private String token;
private final TextArea txtComentarios = new TextArea("Comentarios:");
private static class RespuestaComponente { private static class RespuestaComponente {
@ -56,7 +56,6 @@ public class EncuestaView extends VerticalLayout implements BeforeEnterObserver
setSpacing(true); setSpacing(true);
setPadding(true); setPadding(true);
setSizeFull();
this.getStyle() this.getStyle()
.set("background-image", "url('/images/LOGO_1024X768.jpg')") .set("background-image", "url('/images/LOGO_1024X768.jpg')")
@ -65,9 +64,11 @@ public class EncuestaView extends VerticalLayout implements BeforeEnterObserver
.set("background-position", "center"); .set("background-position", "center");
mainLyt.setHeightFull();
mainLyt.setWidth("55%"); mainLyt.setWidth("55%");
//mainLyt.setMaxHeight("90vh");
mainLyt.getStyle() mainLyt.getStyle()
.set("overflow-y", "auto")
.set("box-shadow", "0 4px 8px rgba(0,0,0,0.2)") .set("box-shadow", "0 4px 8px rgba(0,0,0,0.2)")
.set("border-radius", "12px") .set("border-radius", "12px")
.set("background-color", "white") .set("background-color", "white")
@ -119,6 +120,10 @@ public class EncuestaView extends VerticalLayout implements BeforeEnterObserver
respuestasUI.add(new RespuestaComponente(p,radios)); respuestasUI.add(new RespuestaComponente(p,radios));
} }
txtComentarios.setWidthFull();
txtComentarios.setMaxHeight("5rem");
preguntasLyt.add(txtComentarios);
HorizontalLayout gracias = new HorizontalLayout(new H3("¡Gracias!")); HorizontalLayout gracias = new HorizontalLayout(new H3("¡Gracias!"));
gracias.setWidthFull(); gracias.setWidthFull();
gracias.setJustifyContentMode(JustifyContentMode.CENTER); gracias.setJustifyContentMode(JustifyContentMode.CENTER);
@ -133,7 +138,7 @@ public class EncuestaView extends VerticalLayout implements BeforeEnterObserver
mainLyt.removeAll(); mainLyt.removeAll();
mainLyt.add(titulos,preguntasLyt,gracias, btnEnviarLyt);
mainLyt.add(titulos,preguntasLyt ,gracias, btnEnviarLyt);
} }
private void procesarRespuestas() { private void procesarRespuestas() {
@ -155,11 +160,15 @@ public class EncuestaView extends VerticalLayout implements BeforeEnterObserver
rc.radios.getValue().equals("Si") rc.radios.getValue().equals("Si")
)); ));
} }
txtComentarios.getValue();
String comentarios = txtComentarios.isEmpty() ? null : txtComentarios.getValue();
encuestasDBService.insertRespuestas( encuestasDBService.insertRespuestas(
mantenimientoId, mantenimientoId,
respuestas, respuestas,
empleadoId, empleadoId,
comentarios,
this.token this.token
); );


+ 28
- 7
src/main/java/mx/gob/jumapacelaya/ui/MainLayout.java View File

@ -1,5 +1,6 @@
package mx.gob.jumapacelaya.ui; package mx.gob.jumapacelaya.ui;
import com.vaadin.flow.component.UI;
import com.vaadin.flow.component.applayout.AppLayout; import com.vaadin.flow.component.applayout.AppLayout;
import com.vaadin.flow.component.applayout.DrawerToggle; import com.vaadin.flow.component.applayout.DrawerToggle;
import com.vaadin.flow.component.button.Button; import com.vaadin.flow.component.button.Button;
@ -13,12 +14,15 @@ import com.vaadin.flow.component.orderedlayout.Scroller;
import com.vaadin.flow.component.orderedlayout.VerticalLayout; import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.component.sidenav.SideNav; import com.vaadin.flow.component.sidenav.SideNav;
import com.vaadin.flow.component.sidenav.SideNavItem; import com.vaadin.flow.component.sidenav.SideNavItem;
import com.vaadin.flow.router.BeforeEnterEvent;
import com.vaadin.flow.router.BeforeEnterObserver;
import com.vaadin.flow.server.menu.MenuConfiguration;
import com.vaadin.flow.theme.lumo.LumoUtility; import com.vaadin.flow.theme.lumo.LumoUtility;
import mx.gob.jumapacelaya.services.SecurityService; import mx.gob.jumapacelaya.services.SecurityService;
@CssImport("./themes/sistema-mantenimiento/styles.css") @CssImport("./themes/sistema-mantenimiento/styles.css")
public class MainLayout extends AppLayout {
public class MainLayout extends AppLayout implements BeforeEnterObserver {
private H2 viewTitle; private H2 viewTitle;
private final SecurityService securityService; private final SecurityService securityService;
@ -45,7 +49,7 @@ public class MainLayout extends AppLayout {
String u = securityService.getAuthenticatedUser(); String u = securityService.getAuthenticatedUser();
Span usrNameLabel = new Span(u); Span usrNameLabel = new Span(u);
usrNameLabel.getStyle().set("color", "#691b31");
//usrNameLabel.getStyle().set("color", "#691b31");
usrNameLabel.getStyle().set("font-weight", "bold"); usrNameLabel.getStyle().set("font-weight", "bold");
usrNameLabel.getStyle().set("font-size", "20px"); usrNameLabel.getStyle().set("font-size", "20px");
@ -70,7 +74,7 @@ public class MainLayout extends AppLayout {
headerLayout.setPadding(true); headerLayout.setPadding(true);
headerLayout.setSpacing(false); headerLayout.setSpacing(false);
headerLayout.setAlignItems(FlexComponent.Alignment.CENTER); headerLayout.setAlignItems(FlexComponent.Alignment.CENTER);
headerLayout.getStyle().set("background-color", "#DDC9A3");
//headerLayout.getStyle().set("background-color", "#DDC9A3");
Image imgLogo = new Image("images/LOGO_900X160.png", "Logo"); Image imgLogo = new Image("images/LOGO_900X160.png", "Logo");
imgLogo.setWidthFull(); imgLogo.setWidthFull();
@ -78,7 +82,7 @@ public class MainLayout extends AppLayout {
headerLayout.add(imgLogo); headerLayout.add(imgLogo);
Scroller scroller = new Scroller(createNavigation()); Scroller scroller = new Scroller(createNavigation());
scroller.getStyle().set("background-color", "#691b31");
//scroller.getStyle().set("background-color", "#691b31");
addToDrawer(headerLayout, scroller, createFooter()); addToDrawer(headerLayout, scroller, createFooter());
} }
@ -89,9 +93,10 @@ public class MainLayout extends AppLayout {
nav.addItem(new SideNavItem("Plan Anual", PlanAnualView.class, VaadinIcon.CALENDAR.create())); nav.addItem(new SideNavItem("Plan Anual", PlanAnualView.class, VaadinIcon.CALENDAR.create()));
nav.addItem(new SideNavItem("Listado de Actividades", ActDiariaView.class, VaadinIcon.EDIT.create())); nav.addItem(new SideNavItem("Listado de Actividades", ActDiariaView.class, VaadinIcon.EDIT.create()));
nav.addItem(new SideNavItem("Mantenimiento Correctivo", MantCorrectivoView.class, VaadinIcon.WRENCH.create())); nav.addItem(new SideNavItem("Mantenimiento Correctivo", MantCorrectivoView.class, VaadinIcon.WRENCH.create()));
nav.getStyle().set("background-color", "white");
nav.addItem(new SideNavItem("Resultados de Encuestas", ResultEncuestasView.class, VaadinIcon.CLIPBOARD_TEXT.create()));
nav.addItem(new SideNavItem("Preferencias del Sistema", ConfiguracionView.class, VaadinIcon.COG.create()));
nav.getStyle().set("border-radius", "5px"); nav.getStyle().set("border-radius", "5px");
nav.getStyle().set("opacity", "0.9");
return nav; return nav;
} }
@ -101,9 +106,25 @@ public class MainLayout extends AppLayout {
return layout; return layout;
} }
private String getCurrentPageTitle() {
return MenuConfiguration.getPageHeader(getContent()).orElse("");
}
@Override @Override
protected void afterNavigation() { protected void afterNavigation() {
super.afterNavigation(); super.afterNavigation();
viewTitle.setText("Mantenimiento de Hardware");
viewTitle.setText(getCurrentPageTitle());
}
@Override
public void beforeEnter(BeforeEnterEvent beforeEnterEvent) {
UI.getCurrent().getPage().executeJs("""
const theme = localStorage.getItem('appThemePreference');
if (theme === 'dark') {
document.documentElement.setAttribute('theme', 'dark');
} else {
document.documentElement.removeAttribute('theme');
}
""");
} }
} }

+ 6
- 8
src/main/java/mx/gob/jumapacelaya/ui/MantCorrectivoView.java View File

@ -1,6 +1,5 @@
package mx.gob.jumapacelaya.ui; package mx.gob.jumapacelaya.ui;
import com.vaadin.flow.component.Text;
import com.vaadin.flow.component.UI; import com.vaadin.flow.component.UI;
import com.vaadin.flow.component.button.Button; import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.button.ButtonVariant; import com.vaadin.flow.component.button.ButtonVariant;
@ -24,9 +23,6 @@ import com.vaadin.flow.component.radiobutton.RadioButtonGroup;
import com.vaadin.flow.component.textfield.TextArea; import com.vaadin.flow.component.textfield.TextArea;
import com.vaadin.flow.component.textfield.TextField; import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.data.renderer.ComponentRenderer; import com.vaadin.flow.data.renderer.ComponentRenderer;
import com.vaadin.flow.function.ValueProvider;
import com.vaadin.flow.router.BeforeEnterEvent;
import com.vaadin.flow.router.BeforeEnterObserver;
import com.vaadin.flow.router.PageTitle; import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route; import com.vaadin.flow.router.Route;
import de.f0rce.signaturepad.SignaturePad; import de.f0rce.signaturepad.SignaturePad;
@ -36,6 +32,8 @@ import mx.gob.jumapacelaya.services.DatabaseService;
import mx.gob.jumapacelaya.services.EmailService; import mx.gob.jumapacelaya.services.EmailService;
import mx.gob.jumapacelaya.services.SecurityService; import mx.gob.jumapacelaya.services.SecurityService;
import mx.gob.jumapacelaya.services.UserService; import mx.gob.jumapacelaya.services.UserService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.vaadin.lineawesome.LineAwesomeIcon; import org.vaadin.lineawesome.LineAwesomeIcon;
import java.time.LocalDate; import java.time.LocalDate;
@ -48,6 +46,8 @@ import java.util.*;
@CssImport("./themes/sistema-mantenimiento/styles.css") @CssImport("./themes/sistema-mantenimiento/styles.css")
public class MantCorrectivoView extends VerticalLayout { public class MantCorrectivoView extends VerticalLayout {
private static final Logger logger = LoggerFactory.getLogger(MantCorrectivoView.class);
private VerticalLayout mainLayout; private VerticalLayout mainLayout;
private final SecurityService securityService; private final SecurityService securityService;
private final VerticalLayout controlsLayout; private final VerticalLayout controlsLayout;
@ -96,7 +96,6 @@ public class MantCorrectivoView extends VerticalLayout {
headerLayout.getStyle() headerLayout.getStyle()
.set("box-shadow", "0 4px 8px rgba(0,0,0,0.2)") .set("box-shadow", "0 4px 8px rgba(0,0,0,0.2)")
.set("border-radius", "10px") .set("border-radius", "10px")
.set("background-color", "white")
.set("padding", "1rem") .set("padding", "1rem")
.set("margin", "1rem auto"); .set("margin", "1rem auto");
@ -107,7 +106,6 @@ public class MantCorrectivoView extends VerticalLayout {
mainLayout.getStyle() mainLayout.getStyle()
.set("box-shadow", "0 4px 8px rgba(0,0,0,0.2)") .set("box-shadow", "0 4px 8px rgba(0,0,0,0.2)")
.set("border-radius", "12px") .set("border-radius", "12px")
.set("background-color", "white")
.set("padding", "1rem") .set("padding", "1rem")
.set("margin", "1rem auto"); .set("margin", "1rem auto");
@ -594,7 +592,7 @@ public class MantCorrectivoView extends VerticalLayout {
} catch (Exception e) { } catch (Exception e) {
Notification.show("Error al enviar el correo", 4000, Notification.Position.MIDDLE) Notification.show("Error al enviar el correo", 4000, Notification.Position.MIDDLE)
.addThemeVariants(NotificationVariant.LUMO_ERROR); .addThemeVariants(NotificationVariant.LUMO_ERROR);
e.printStackTrace();
logger.error("Error al enviar correo: ", e);
} }
// Limpiar campos // Limpiar campos
@ -624,7 +622,7 @@ public class MantCorrectivoView extends VerticalLayout {
} catch (Exception ex) { } catch (Exception ex) {
Notification.show("Ocurrio un error inesperado: " + ex.getMessage(), 5000, Notification.Position.MIDDLE) Notification.show("Ocurrio un error inesperado: " + ex.getMessage(), 5000, Notification.Position.MIDDLE)
.addThemeVariants(NotificationVariant.LUMO_ERROR); .addThemeVariants(NotificationVariant.LUMO_ERROR);
ex.printStackTrace();
logger.error("Error al insertar mantenimiento: ", ex);
} }
} }


+ 7
- 12
src/main/java/mx/gob/jumapacelaya/ui/MantenimientoView.java View File

@ -1,29 +1,24 @@
package mx.gob.jumapacelaya.ui; package mx.gob.jumapacelaya.ui;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.UI; import com.vaadin.flow.component.UI;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.button.ButtonVariant; import com.vaadin.flow.component.button.ButtonVariant;
import com.vaadin.flow.component.checkbox.CheckboxGroup; import com.vaadin.flow.component.checkbox.CheckboxGroup;
import com.vaadin.flow.component.combobox.ComboBox; import com.vaadin.flow.component.combobox.ComboBox;
import com.vaadin.flow.component.datepicker.DatePicker; import com.vaadin.flow.component.datepicker.DatePicker;
import com.vaadin.flow.component.dependency.CssImport; import com.vaadin.flow.component.dependency.CssImport;
import com.vaadin.flow.component.dialog.Dialog; import com.vaadin.flow.component.dialog.Dialog;
import com.vaadin.flow.component.html.H1;
import com.vaadin.flow.component.html.H2;
import com.vaadin.flow.component.html.Image; import com.vaadin.flow.component.html.Image;
import com.vaadin.flow.component.html.Span; import com.vaadin.flow.component.html.Span;
import com.vaadin.flow.component.icon.Icon; import com.vaadin.flow.component.icon.Icon;
import com.vaadin.flow.component.icon.VaadinIcon; import com.vaadin.flow.component.icon.VaadinIcon;
import com.vaadin.flow.component.notification.Notification; import com.vaadin.flow.component.notification.Notification;
import com.vaadin.flow.component.notification.NotificationVariant; import com.vaadin.flow.component.notification.NotificationVariant;
import com.vaadin.flow.component.orderedlayout.FlexComponent;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout; import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.orderedlayout.VerticalLayout; import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.component.radiobutton.RadioButtonGroup; import com.vaadin.flow.component.radiobutton.RadioButtonGroup;
import com.vaadin.flow.component.textfield.NumberField;
import com.vaadin.flow.component.textfield.TextArea; import com.vaadin.flow.component.textfield.TextArea;
import com.vaadin.flow.component.textfield.TextField; import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.router.BeforeEnterEvent; import com.vaadin.flow.router.BeforeEnterEvent;
import com.vaadin.flow.router.BeforeEnterObserver; import com.vaadin.flow.router.BeforeEnterObserver;
import com.vaadin.flow.router.PageTitle; import com.vaadin.flow.router.PageTitle;
@ -35,9 +30,8 @@ import mx.gob.jumapacelaya.services.DatabaseService;
import mx.gob.jumapacelaya.services.EmailService; import mx.gob.jumapacelaya.services.EmailService;
import mx.gob.jumapacelaya.services.SecurityService; import mx.gob.jumapacelaya.services.SecurityService;
import mx.gob.jumapacelaya.services.UserService; import mx.gob.jumapacelaya.services.UserService;
import oracle.net.aso.h;
import org.springframework.beans.factory.annotation.Autowired;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.vaadin.lineawesome.LineAwesomeIcon; import org.vaadin.lineawesome.LineAwesomeIcon;
import java.time.LocalDate; import java.time.LocalDate;
@ -50,6 +44,8 @@ import java.util.*;
@CssImport("./themes/sistema-mantenimiento/styles.css") @CssImport("./themes/sistema-mantenimiento/styles.css")
public class MantenimientoView extends VerticalLayout implements BeforeEnterObserver { public class MantenimientoView extends VerticalLayout implements BeforeEnterObserver {
private static final Logger logger = LoggerFactory.getLogger(MantenimientoView.class);
private final SecurityService securityService; private final SecurityService securityService;
private final VerticalLayout controlsLayout; private final VerticalLayout controlsLayout;
private final DatabaseService databaseService; private final DatabaseService databaseService;
@ -98,7 +94,6 @@ public class MantenimientoView extends VerticalLayout implements BeforeEnterObse
mainLayout.getStyle() mainLayout.getStyle()
.set("box-shadow","0 4px 8px rgba(0,0,0,0.2)") .set("box-shadow","0 4px 8px rgba(0,0,0,0.2)")
.set("border-radius", "12px") .set("border-radius", "12px")
.set("background-color", "white")
.set("padding", "1rem") .set("padding", "1rem")
.set("margin", "1rem auto"); .set("margin", "1rem auto");
@ -718,7 +713,7 @@ public class MantenimientoView extends VerticalLayout implements BeforeEnterObse
} catch (Exception e) { } catch (Exception e) {
Notification.show("Error al enviar el correo", 4000, Notification.Position.MIDDLE) Notification.show("Error al enviar el correo", 4000, Notification.Position.MIDDLE)
.addThemeVariants(NotificationVariant.LUMO_ERROR); .addThemeVariants(NotificationVariant.LUMO_ERROR);
e.printStackTrace();
logger.error("Error al enviar el correo", e);
} }
// Limpiar campos // Limpiar campos
@ -748,7 +743,7 @@ public class MantenimientoView extends VerticalLayout implements BeforeEnterObse
} catch (Exception ex) { } catch (Exception ex) {
Notification.show("Ocurrio un error inesperado: " + ex.getMessage(), 5000, Notification.Position.MIDDLE) Notification.show("Ocurrio un error inesperado: " + ex.getMessage(), 5000, Notification.Position.MIDDLE)
.addThemeVariants(NotificationVariant.LUMO_ERROR); .addThemeVariants(NotificationVariant.LUMO_ERROR);
ex.printStackTrace();
logger.error("Error al insertar mantenimiento", ex);
} }
} }


+ 214
- 28
src/main/java/mx/gob/jumapacelaya/ui/PlanAnualView.java View File

@ -4,15 +4,12 @@ import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.UI; import com.vaadin.flow.component.UI;
import com.vaadin.flow.component.button.Button; import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.button.ButtonVariant; import com.vaadin.flow.component.button.ButtonVariant;
import com.vaadin.flow.component.checkbox.Checkbox;
import com.vaadin.flow.component.checkbox.CheckboxGroup; import com.vaadin.flow.component.checkbox.CheckboxGroup;
import com.vaadin.flow.component.checkbox.CheckboxGroupVariant; import com.vaadin.flow.component.checkbox.CheckboxGroupVariant;
import com.vaadin.flow.component.combobox.ComboBox; import com.vaadin.flow.component.combobox.ComboBox;
import com.vaadin.flow.component.confirmdialog.ConfirmDialog;
import com.vaadin.flow.component.datepicker.DatePicker; import com.vaadin.flow.component.datepicker.DatePicker;
import com.vaadin.flow.component.dependency.CssImport; import com.vaadin.flow.component.dependency.CssImport;
import com.vaadin.flow.component.dialog.Dialog; import com.vaadin.flow.component.dialog.Dialog;
import com.vaadin.flow.component.formlayout.FormLayout;
import com.vaadin.flow.component.grid.Grid; import com.vaadin.flow.component.grid.Grid;
import com.vaadin.flow.component.grid.GridVariant; import com.vaadin.flow.component.grid.GridVariant;
import com.vaadin.flow.component.grid.HeaderRow; import com.vaadin.flow.component.grid.HeaderRow;
@ -28,12 +25,11 @@ import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.orderedlayout.VerticalLayout; import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.component.popover.Popover; import com.vaadin.flow.component.popover.Popover;
import com.vaadin.flow.component.popover.PopoverPosition; import com.vaadin.flow.component.popover.PopoverPosition;
import com.vaadin.flow.component.radiobutton.RadioButtonGroup;
import com.vaadin.flow.component.textfield.NumberField; import com.vaadin.flow.component.textfield.NumberField;
import com.vaadin.flow.component.textfield.TextField; import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.component.upload.Upload; import com.vaadin.flow.component.upload.Upload;
import com.vaadin.flow.component.upload.receivers.MemoryBuffer; import com.vaadin.flow.component.upload.receivers.MemoryBuffer;
import com.vaadin.flow.data.provider.ListDataProvider;
import com.vaadin.flow.data.renderer.ComponentRenderer;
import com.vaadin.flow.data.value.ValueChangeMode; import com.vaadin.flow.data.value.ValueChangeMode;
import com.vaadin.flow.function.ValueProvider; import com.vaadin.flow.function.ValueProvider;
import com.vaadin.flow.router.PageTitle; import com.vaadin.flow.router.PageTitle;
@ -42,20 +38,24 @@ import com.vaadin.flow.server.StreamRegistration;
import com.vaadin.flow.server.StreamResource; import com.vaadin.flow.server.StreamResource;
import jakarta.annotation.security.PermitAll; import jakarta.annotation.security.PermitAll;
import mx.gob.jumapacelaya.models.PlanAnual; import mx.gob.jumapacelaya.models.PlanAnual;
import mx.gob.jumapacelaya.models.encuestas.MantenimientosSinEncuesta;
import mx.gob.jumapacelaya.services.DatabaseService; import mx.gob.jumapacelaya.services.DatabaseService;
import mx.gob.jumapacelaya.services.EmailService;
import mx.gob.jumapacelaya.services.ReportService; import mx.gob.jumapacelaya.services.ReportService;
import net.sf.jasperreports.engine.JasperFillManager;
import net.sf.jasperreports.engine.JasperPrint;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.poi.ss.usermodel.*; import org.apache.poi.ss.usermodel.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.env.Environment; import org.springframework.core.env.Environment;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
import org.vaadin.lineawesome.LineAwesomeIcon; import org.vaadin.lineawesome.LineAwesomeIcon;
import java.io.*;
import java.lang.reflect.Array;
import java.sql.Date;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.Year; import java.time.Year;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
@ -70,6 +70,11 @@ import java.util.stream.IntStream;
@CssImport("./themes/sistema-mantenimiento/styles.css") @CssImport("./themes/sistema-mantenimiento/styles.css")
public class PlanAnualView extends VerticalLayout { public class PlanAnualView extends VerticalLayout {
private static final Logger logger = LoggerFactory.getLogger(PlanAnualView.class);
private final EmailService emailService;
@Value("${app.base-url}")
private String baseUrl;
private final Environment env; private final Environment env;
private final ReportService reportService; private final ReportService reportService;
@ -90,7 +95,7 @@ public class PlanAnualView extends VerticalLayout {
private Popover reportePopover; private Popover reportePopover;
ComboBox<Integer> yearFilter; ComboBox<Integer> yearFilter;
public PlanAnualView(DatabaseService databaseService, Environment env, ReportService reportService) {
public PlanAnualView(DatabaseService databaseService, Environment env, ReportService reportService, EmailService emailService) {
this.databaseService = databaseService; this.databaseService = databaseService;
this.env = env; this.env = env;
this.reportService = reportService; this.reportService = reportService;
@ -113,9 +118,14 @@ public class PlanAnualView extends VerticalLayout {
PlanAnualFilter planAnualFilter = new PlanAnualFilter(dataView); PlanAnualFilter planAnualFilter = new PlanAnualFilter(dataView);
planAnualFilter.setExcludeRealizado(true); planAnualFilter.setExcludeRealizado(true);
yearFilter.addValueChangeListener(event ->
planAnualFilter.setYear(event.getValue())
);
// Aplicar el valor actual del ComboBox al filtro al inicializar
if (yearFilter != null && yearFilter.getValue() != null) {
planAnualFilter.setYear(yearFilter.getValue());
}
if (yearFilter != null) {
yearFilter.addValueChangeListener(event -> planAnualFilter.setYear(event.getValue()));
}
HeaderRow headerRow = planAnualGrid.appendHeaderRow(); HeaderRow headerRow = planAnualGrid.appendHeaderRow();
/*headerRow.getCell(planAnualGrid.getColumnByKey("smtColumnKey")) /*headerRow.getCell(planAnualGrid.getColumnByKey("smtColumnKey"))
@ -130,6 +140,9 @@ public class PlanAnualView extends VerticalLayout {
headerRow.getCell(planAnualGrid.getColumnByKey("mesplaneado")) headerRow.getCell(planAnualGrid.getColumnByKey("mesplaneado"))
.setComponent(createFilterHeader("Mes Planeado", planAnualFilter::setMesPlaneado)); .setComponent(createFilterHeader("Mes Planeado", planAnualFilter::setMesPlaneado));
headerRow.getCell(planAnualGrid.getColumnByKey("encuesta"))
.setComponent(createFilterHeader("Encuesta", planAnualFilter::setEncuesta));
// MENU CONTEXTUAL DEL GRID // MENU CONTEXTUAL DEL GRID
@ -228,6 +241,7 @@ public class PlanAnualView extends VerticalLayout {
this.setSizeFull(); this.setSizeFull();
add(filtrosLayout, uploadLayout); add(filtrosLayout, uploadLayout);
add(gridLayout); add(gridLayout);
this.emailService = emailService;
} }
private void setupHeader() { private void setupHeader() {
@ -264,7 +278,6 @@ public class PlanAnualView extends VerticalLayout {
if ("PENDIENTE".equalsIgnoreCase(estado)) { if ("PENDIENTE".equalsIgnoreCase(estado)) {
btn = new Button(new Icon(VaadinIcon.EDIT)); btn = new Button(new Icon(VaadinIcon.EDIT));
btn.setTooltipText("Realizar mantenimiento"); btn.setTooltipText("Realizar mantenimiento");
btn.getStyle().set("color", "#A02142");
btn.addClickListener(event -> { btn.addClickListener(event -> {
int idPlananual = planAnual.getNumero(); int idPlananual = planAnual.getNumero();
@ -286,7 +299,6 @@ public class PlanAnualView extends VerticalLayout {
} else if ("REALIZADO".equalsIgnoreCase(estado)) { } else if ("REALIZADO".equalsIgnoreCase(estado)) {
btn = new Button(new Icon(VaadinIcon.EYE)); btn = new Button(new Icon(VaadinIcon.EYE));
btn.setTooltipText("Ver detalles"); btn.setTooltipText("Ver detalles");
btn.getStyle().set("color", "#A02142");
btn.addClickListener(event -> { btn.addClickListener(event -> {
int idPlananual = planAnual.getNumero(); int idPlananual = planAnual.getNumero();
@ -369,14 +381,8 @@ public class PlanAnualView extends VerticalLayout {
btnAddEquipo.setTooltipText("Agregar nuevo equipo"); btnAddEquipo.setTooltipText("Agregar nuevo equipo");
btnEnviarEncuestas = new Button(VaadinIcon.ENVELOPE.create()); btnEnviarEncuestas = new Button(VaadinIcon.ENVELOPE.create());
ConfirmDialog enviarEncConfirm = new ConfirmDialog();
enviarEncConfirm.setHeader("Enviar encuestas");
enviarEncConfirm.setText("¿Deseas enviar las encuestas de satisfacción?, Esto enviara la encuesta solo a los mantenimientos realizados");
enviarEncConfirm.setCancelable(true);
enviarEncConfirm.addCancelListener(e -> enviarEncConfirm.close());
enviarEncConfirm.setConfirmText("Enviar");
enviarEncConfirm.addConfirmListener(e -> {});
btnEnviarEncuestas.addClickListener(e -> enviarEncConfirm.open());
btnEnviarEncuestas.addClickListener(e -> showParametrosDialog());
btnEnviarEncuestas.setTooltipText("Enviar encuestas masivamente");
yearFilter = new ComboBox<>(); yearFilter = new ComboBox<>();
int currentYear = Year.now().getValue(); int currentYear = Year.now().getValue();
@ -384,12 +390,14 @@ public class PlanAnualView extends VerticalLayout {
.boxed().collect(Collectors.toList()); .boxed().collect(Collectors.toList());
yearFilter.setItems(years); yearFilter.setItems(years);
yearFilter.setPlaceholder("Año"); yearFilter.setPlaceholder("Año");
yearFilter.setValue(currentYear);
yearFilter.setClearButtonVisible(true); yearFilter.setClearButtonVisible(true);
List<PlanAnual> todosLosPlanes = databaseService.getPlanAnual(); List<PlanAnual> todosLosPlanes = databaseService.getPlanAnual();
btnImprimirLayout = new HorizontalLayout(btnColumns, btnImprimirRpt, btnAddEquipo/*, btnEnviarEncuestas*/, yearFilter);
btnImprimirLayout = new HorizontalLayout(btnColumns, btnImprimirRpt, btnAddEquipo, btnEnviarEncuestas, yearFilter);
btnImprimirLayout.setAlignItems(Alignment.BASELINE); btnImprimirLayout.setAlignItems(Alignment.BASELINE);
HorizontalLayout columnSelectorLayout = new HorizontalLayout(); HorizontalLayout columnSelectorLayout = new HorizontalLayout();
columnSelectorLayout.setAlignItems(Alignment.END); columnSelectorLayout.setAlignItems(Alignment.END);
@ -541,6 +549,11 @@ public class PlanAnualView extends VerticalLayout {
private String equipo; private String equipo;
private String departamento; private String departamento;
private String mesPlaneado; private String mesPlaneado;
private LocalDate fechaProgramada;
private LocalDate fechaRealizacion;
private String encuesta;
private String estado;
private String situacion;
private Integer year; private Integer year;
private boolean excludeRealizado = true; private boolean excludeRealizado = true;
@ -569,6 +582,11 @@ public class PlanAnualView extends VerticalLayout {
this.dataView.refreshAll(); this.dataView.refreshAll();
} }
public void setEncuesta(String encuesta) {
this.encuesta = encuesta;
this.dataView.refreshAll();
}
public void setYear(Integer year) { public void setYear(Integer year) {
this.year = year; this.year = year;
dataView.refreshAll(); dataView.refreshAll();
@ -586,6 +604,7 @@ public class PlanAnualView extends VerticalLayout {
boolean matchesEquipo = matches(planAnual.getNomEquipo(), equipo); boolean matchesEquipo = matches(planAnual.getNomEquipo(), equipo);
boolean matchesDepartamento = matches(planAnual.getDepartamento(), departamento); boolean matchesDepartamento = matches(planAnual.getDepartamento(), departamento);
boolean matchesMesPlaneado = matches(planAnual.getMesplaneado(), mesPlaneado); boolean matchesMesPlaneado = matches(planAnual.getMesplaneado(), mesPlaneado);
boolean matchesEncuesta = matches(planAnual.getEncuesta(), encuesta);
boolean matchesYear = true; boolean matchesYear = true;
if (year != null) { if (year != null) {
@ -608,7 +627,8 @@ public class PlanAnualView extends VerticalLayout {
&& matchesDepartamento && matchesDepartamento
&& matchesMesPlaneado && matchesMesPlaneado
&& matchesYear && matchesYear
&& matchesEstado;
&& matchesEstado
&& matchesEncuesta;
} }
private boolean matches(String value, String serachTerm) { private boolean matches(String value, String serachTerm) {
@ -689,7 +709,7 @@ public class PlanAnualView extends VerticalLayout {
} catch (Exception ex) { } catch (Exception ex) {
Notification.show("Error al genrar el reporte: " + ex.getMessage(), 5000, Notification.Position.MIDDLE) Notification.show("Error al genrar el reporte: " + ex.getMessage(), 5000, Notification.Position.MIDDLE)
.addThemeVariants(NotificationVariant.LUMO_ERROR); .addThemeVariants(NotificationVariant.LUMO_ERROR);
ex.printStackTrace();
logger.error("Error al generar el reporte", ex);
} }
}); });
@ -903,7 +923,7 @@ public class PlanAnualView extends VerticalLayout {
} catch (Exception ex) { } catch (Exception ex) {
Notification.show("Error al generar el reporte: " + ex.getMessage(), 5000, Notification.Position.MIDDLE) Notification.show("Error al generar el reporte: " + ex.getMessage(), 5000, Notification.Position.MIDDLE)
.addThemeVariants(NotificationVariant.LUMO_ERROR); .addThemeVariants(NotificationVariant.LUMO_ERROR);
ex.printStackTrace();
logger.error("Error al generar el reporte", ex);
} }
}); });
@ -1005,4 +1025,170 @@ public class PlanAnualView extends VerticalLayout {
dialog.getFooter().add(dialogFooter); dialog.getFooter().add(dialogFooter);
dialog.open(); dialog.open();
} }
private void showParametrosDialog() {
Dialog dialog = new Dialog();
dialog.setHeaderTitle("Ingresar periodo");
TextField txtMes = new TextField("Mes:");
txtMes.setPlaceholder("ENERO, FEBRERO, MARZO...");
txtMes.setClearButtonVisible(true);
TextField txtAnio = new TextField("Año");
txtAnio.setPlaceholder("Ej. 2025");
Button btnBuscar = new Button("Buscar", VaadinIcon.SEARCH.create(), e -> {
String mes = txtMes.getValue();
String anioTexto = txtAnio.getValue();
if (mes.isEmpty() || anioTexto.isEmpty()) {
Notification.show("Mes y año son requeridos.", 3000, Notification.Position.MIDDLE);
return;
}
int anio;
try {
anio = Integer.parseInt(anioTexto);
} catch (NumberFormatException ex) {
Notification.show("El año debe ser numérico.", 3000, Notification.Position.MIDDLE);
return;
}
dialog.close();
showEncuestasDialog(mes, anio);
});
btnBuscar.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
Button btnCancelar = new Button("Cancelar", VaadinIcon.CLOSE_CIRCLE.create(), e -> dialog.close());
HorizontalLayout actions = new HorizontalLayout(btnBuscar, btnCancelar);
VerticalLayout layout = new VerticalLayout(txtMes, txtAnio, actions);
layout.setPadding(false);
layout.setSpacing(true);
dialog.add(layout);
dialog.open();
}
private void showEncuestasDialog(String mes, int anio) {
Dialog dialog = new Dialog();
dialog.setWidth("95%");
dialog.setHeight("90%");
dialog.setHeaderTitle("Encuestas pendientes por enviar...");
Grid<MantenimientosSinEncuesta> grid = new Grid<>(MantenimientosSinEncuesta.class, false);
grid.addColumn(MantenimientosSinEncuesta::getMantenimientoId)
.setHeader("ID")
.setAutoWidth(true);
grid.addColumn(item -> item.getFecha().format(DateTimeFormatter.ofPattern("dd/MM/yyyy")))
.setHeader("Fecha")
.setAutoWidth(true);
grid.addColumn(MantenimientosSinEncuesta::getPeriodo)
.setHeader("Periodo")
.setAutoWidth(true);
grid.addColumn(createStatusRender())
.setHeader("Encuesta");
grid.addColumn(MantenimientosSinEncuesta::getDepartamento)
.setHeader("Departamento");
grid.addColumn(MantenimientosSinEncuesta::getNomUsuario)
.setHeader("Usuario")
.setAutoWidth(true);
grid.addColumn(MantenimientosSinEncuesta::getEmail)
.setHeader("Correo")
.setAutoWidth(true);
grid.addComponentColumn(item -> {
Button btnEnviar = new Button(
"Enviar",
LineAwesomeIcon.ENVELOPE_SOLID.create()
);
btnEnviar.addThemeVariants(ButtonVariant.LUMO_PRIMARY,
ButtonVariant.LUMO_SUCCESS);
btnEnviar.addClickListener(e -> {
enviarEncuestaDesdeGrid(item);
btnEnviar.setEnabled(false);
});
return btnEnviar;
}).setHeader("Accion")
.setFrozen(true);
List<MantenimientosSinEncuesta> lista = databaseService.getEncuestPendientes(mes, anio);
grid.setItems(lista);
grid.addThemeVariants(GridVariant.LUMO_WRAP_CELL_CONTENT);
dialog.add(grid);
dialog.getFooter().add(new Button("Cerrar", LineAwesomeIcon.TIMES_SOLID.create(), e -> dialog.close()));
dialog.open();
}
private void enviarEncuestaDesdeGrid(MantenimientosSinEncuesta item) {
if (item.getEmail() == null || item.getEmail().isBlank()) {
Notification.show("El usuario no tiene correo", 3000, Notification.Position.MIDDLE)
.addThemeVariants(NotificationVariant.LUMO_ERROR);
return;
}
int mantenimientoId = item.getMantenimientoId();
String token = databaseService.crearTokenEncuesta(mantenimientoId);
String linkEncuesta = baseUrl + "/encuesta?token=" + token;
String cuerpo = """
<html>
<body>
<a href="%s">
<img src="cid:image_id"/>
</a>
</body>
</html>
""".formatted(linkEncuesta);
String asunto = "Encuesta de satisfacción - Mantenimiento #" + mantenimientoId;
String imagePath = "META-INF/resources/images/imgCorreo/imgEncuesta.png";
emailService.enviarCorreo(
item.getEmail(),
asunto,
cuerpo,
imagePath
);
Notification.show("Encuesta enviada correctamente", 3000, Notification.Position.MIDDLE)
.addThemeVariants(NotificationVariant.LUMO_SUCCESS);
}
private ComponentRenderer<Span, MantenimientosSinEncuesta> createStatusRender() {
return new ComponentRenderer<>(encuesta -> {
Span span = new Span(encuesta.getEncuesta());
switch (encuesta.getEncuesta().toUpperCase()) {
case "N":
String theme1 = String.format("badge %s", "error");
span.getElement().setAttribute("theme", theme1);
break;
case "S":
String theme2 = String.format("badge %s", "success");
span.getElement().setAttribute("theme", theme2);
break;
default:
String theme3 = String.format("badge %s", "");
span.getElement().setAttribute("theme", theme3);
}
return span;
});
}
} }

+ 200
- 0
src/main/java/mx/gob/jumapacelaya/ui/ResultEncuestasView.java View File

@ -0,0 +1,200 @@
package mx.gob.jumapacelaya.ui;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.button.ButtonVariant;
import com.vaadin.flow.component.dashboard.Dashboard;
import com.vaadin.flow.component.dashboard.DashboardSection;
import com.vaadin.flow.component.dashboard.DashboardWidget;
import com.vaadin.flow.component.grid.Grid;
import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.component.html.Span;
import com.vaadin.flow.component.icon.Icon;
import com.vaadin.flow.component.icon.SvgIcon;
import com.vaadin.flow.component.icon.VaadinIcon;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import jakarta.annotation.security.PermitAll;
import mx.gob.jumapacelaya.models.encuestas.ConteoEncuestas;
import mx.gob.jumapacelaya.models.encuestas.ConteoRespuestas;
import mx.gob.jumapacelaya.services.DatabaseService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.vaadin.lineawesome.LineAwesomeIcon;
import java.util.List;
@PermitAll
@PageTitle("Resultados de Encuestas")
@Route(value = "resultados-encuestas", layout = MainLayout.class)
public class ResultEncuestasView extends VerticalLayout {
private static final Logger logger = LoggerFactory.getLogger(ResultEncuestasView.class);
private final DatabaseService databaseService;
private DashboardWidget totalEncu;
private DashboardWidget totalResp;
private DashboardWidget porcentajeResp;
private DashboardWidget pregunta1;
private DashboardWidget pregunta2;
private DashboardWidget pregunta3;
private DashboardWidget pregunta4;
private DashboardWidget pregunta5;
private DashboardWidget pregunta6;
public ResultEncuestasView(DatabaseService databaseService) {
this.databaseService = databaseService;
createToolbar();
createDashboard();
}
public HorizontalLayout createToolbar() {
Button btnExport = new Button("Exportar a PDF", LineAwesomeIcon.FILE_PDF.create());
btnExport.addThemeVariants(ButtonVariant.LUMO_SMALL);
HorizontalLayout toolbar = new HorizontalLayout(btnExport);
toolbar.setAlignItems(Alignment.BASELINE);
toolbar.setWidthFull();
add(toolbar);
return toolbar;
}
public Dashboard createDashboard() {
Dashboard dashboard = new Dashboard();
dashboard.setMinimumColumnWidth("150px");
dashboard.setMaximumColumnCount(3);
// Secciónes del dashboard
DashboardSection generalSection = dashboard.addSection("General");
DashboardSection preguntasSection = dashboard.addSection("Preguntas");
// Widgets para la sección General
totalEncu = new DashboardWidget("Total Encuestas");
totalResp = new DashboardWidget("Total Respuestas");
porcentajeResp = new DashboardWidget("Porcentaje de Respuestas");
generalSection.add(totalEncu);
generalSection.add(totalResp);
generalSection.add(porcentajeResp);
// Widget para la sección Preguntas
pregunta1 = new DashboardWidget("Pregunta 1");
pregunta2 = new DashboardWidget("Pregunta 2");
pregunta3 = new DashboardWidget("Pregunta 3");
pregunta4 = new DashboardWidget("Pregunta 4");
pregunta5 = new DashboardWidget("Pregunta 5");
pregunta6 = new DashboardWidget("Pregunta 6");
preguntasSection.add(pregunta1);
preguntasSection.add(pregunta2);
preguntasSection.add(pregunta3);
preguntasSection.add(pregunta4);
preguntasSection.add(pregunta5);
preguntasSection.add(pregunta6);
mostrarTotales();
mostrarPreguntas();
add(dashboard);
return dashboard;
}
private Div createDiv(String valor, LineAwesomeIcon icon, String color) {
Div content = new Div();
content.setClassName("dashboard-widget-content");
content.getStyle()
.set("display", "flex")
.set("align-items", "center")
.set("justify-content", "center")
.set("gap", "15px");
SvgIcon vIcon = icon.create();
vIcon.getStyle().set("color", color);
vIcon.setSize("40px");
Span text = new Span(valor);
text.getStyle().set("font-size", "2.2rem");
text.getStyle().set("font-weight", "800");
content.add(vIcon, text);
return content;
}
private Div createPreguntaContent(ConteoRespuestas data) {
Div container = new Div();
container.setClassName("pregunta-widget-container");
container.getStyle().set("padding", "10px");
Span txtPregunta = new Span(data.getPregunta());
txtPregunta.getStyle().set("font-size", "0.9em")
.set("display", "block")
.set("margin-bottom", "15px")
.set("height", "45px")
.set("overflow", "hidden");
HorizontalLayout metrics = new HorizontalLayout();
metrics.setJustifyContentMode(JustifyContentMode.BETWEEN);
metrics.setWidthFull();
metrics.add(
createStat("Si", String.valueOf(data.getTotalSi()), "#27ae60"),
createStat("No", String.valueOf(data.getTotalNo()), "#e74c3c"),
createStat("%", String.format("%.0f", data.getPorcentaje()), "#BC955B")
);
container.add(txtPregunta, metrics);
return container;
}
private VerticalLayout createStat(String label, String value, String color) {
Span l = new Span(label);
l.getStyle().set("font-size", "0.7em").set("color", "gray");
Span v = new Span(value);
v.getStyle().set("font-weight", "bold").set("color", color);
VerticalLayout vl = new VerticalLayout(l,v);
vl.setAlignItems(Alignment.CENTER);
vl.setSpacing(false);
vl.setPadding(false);
return vl;
}
public void mostrarTotales() {
List<ConteoEncuestas> datos = databaseService.getTotalEncuestas();
if (!datos.isEmpty()) {
ConteoEncuestas totales = datos.get(0);
totalEncu.setContent(createDiv(
String.valueOf(totales.getTotalEnviadas()),
LineAwesomeIcon.CLIPBOARD_LIST_SOLID, "#BC955B"
));
totalResp.setContent(createDiv(
String.valueOf(totales.getTotalRespondidas()),
LineAwesomeIcon.CHECK_CIRCLE, "#BC955B"
));
porcentajeResp.setContent(createDiv(
String.valueOf(totales.getPorcentajeRespondidas()),
LineAwesomeIcon.PERCENT_SOLID, "#BC955B"
));
}
}
public void mostrarPreguntas() {
List<ConteoRespuestas> lista = databaseService.getTotalRespuestas();
DashboardWidget[] widgets = {pregunta1,pregunta2,pregunta3,pregunta4,pregunta5,pregunta6};
for (int i = 0; i < lista.size() && i < widgets.length; i++) {
ConteoRespuestas data = lista.get(i);
DashboardWidget widget = widgets[i];
widget.setTitle("Pregunta #" + data.getPreguntaId());
widget.setContent(createPreguntaContent(data));
}
}
}

+ 0
- 3
src/main/java/mx/gob/jumapacelaya/ui/login/LoginView.java View File

@ -22,9 +22,6 @@ public class LoginView extends VerticalLayout implements BeforeEnterObserver {
private static final Logger log = LoggerFactory.getLogger(LoginView.class); private static final Logger log = LoggerFactory.getLogger(LoginView.class);
private final LoginForm login = new LoginForm(); private final LoginForm login = new LoginForm();
// Usuario local para demostracion
private final String localUser = "admin";
private final String localPassword = "admin";
public LoginView(){ public LoginView(){


BIN
src/main/resources/META-INF/resources/images/LOGO_admon2.png View File

Before After
Width: 597  |  Height: 569  |  Size: 84 KiB

BIN
src/main/resources/META-INF/resources/images/imgCorreo/imgEncuesta.png View File

Before After
Width: 1024  |  Height: 1536  |  Size: 3.2 MiB Width: 1024  |  Height: 1536  |  Size: 3.2 MiB

+ 5
- 2
src/main/resources/application.properties View File

@ -1,6 +1,7 @@
server.port=${PORT:8080} server.port=${PORT:8080}
logging.level.org.atmosphere = warn logging.level.org.atmosphere = warn
spring.profiles.active=dev spring.profiles.active=dev
server.servlet.session.cookie.secure=true
# Launch the default browser when starting the application in development mode # Launch the default browser when starting the application in development mode
vaadin.launch-browser=true vaadin.launch-browser=true
@ -12,13 +13,15 @@ spring.jpa.defer-datasource-initialization = true
#Configuracion LDAP #Configuracion LDAP
spring.ldap.urls=ldap://172.1.0.1:389
spring.ldap.urls=ldap://172.16.0.1:389
spring.ldap.url=ldap://172.16.0.1
spring.ldap.domain=JUMAPACELAYA.GOB.MX
spring.ldap.base=DC=JUMAPACELAYA,DC=GOB,DC=MX spring.ldap.base=DC=JUMAPACELAYA,DC=GOB,DC=MX
spring.ldap.username=administrator spring.ldap.username=administrator
spring.ldap.password=Dr3na$134%4guA spring.ldap.password=Dr3na$134%4guA
###################PRODUCTIVO#################### ###################PRODUCTIVO####################
redmine.url=https://proyman.jumapacelaya.gob.mx/
redmine.url=https://proyman.jumapacelaya.gob.mx
redmine.api_key=69be2a5df9bacce02722f566fdf0731d728a1b86 redmine.api_key=69be2a5df9bacce02722f566fdf0731d728a1b86


+ 1
- 1
tsconfig.json View File

@ -10,7 +10,7 @@
"jsx": "react-jsx", "jsx": "react-jsx",
"inlineSources": true, "inlineSources": true,
"module": "esNext", "module": "esNext",
"target": "es2020",
"target": "es2022",
"moduleResolution": "bundler", "moduleResolution": "bundler",
"strict": true, "strict": true,
"skipLibCheck": true, "skipLibCheck": true,


Loading…
Cancel
Save