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 4 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
Split 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",
"dependencies": {
"@polymer/polymer": "3.5.2",
"@vaadin/bundles": "24.5.5",
"@vaadin/bundles": "24.8.0",
"@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-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",
"construct-style-sheets-polyfill": "3.1.0",
"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-dom": "18.3.1",
"react-router-dom": "6.28.0",
"react-router": "7.6.1",
"signature_pad": "4.1.5"
},
"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",
"glob": "10.4.5",
"glob": "11.0.2",
"magic-string": "0.30.17",
"rollup-plugin-brotli": "3.1.0",
"rollup-plugin-visualizer": "5.12.0",
"rollup-plugin-visualizer": "5.14.0",
"strip-css-comments": "5.0.0",
"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-core": "7.3.0",
"workbox-precaching": "7.3.0"
@ -47,47 +48,48 @@
"vaadin": {
"dependencies": {
"@polymer/polymer": "3.5.2",
"@vaadin/bundles": "24.5.5",
"@vaadin/bundles": "24.8.0",
"@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-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",
"construct-style-sheets-polyfill": "3.1.0",
"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-dom": "18.3.1",
"react-router-dom": "6.28.0",
"react-router": "7.6.1",
"signature_pad": "4.1.5"
},
"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",
"glob": "10.4.5",
"glob": "11.0.2",
"magic-string": "0.30.17",
"rollup-plugin-brotli": "3.1.0",
"rollup-plugin-visualizer": "5.12.0",
"rollup-plugin-visualizer": "5.14.0",
"strip-css-comments": "5.0.0",
"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-core": "7.3.0",
"workbox-precaching": "7.3.0"
},
"hash": "6126bd412c2a8696938f50edd1abae217c30c0119a0496af47b9d6ba1762921f"
"hash": "5e2a99194f7c0b939e722574ef486d86e32935203f1b5047346903f60dcb24d6"
},
"overrides": {
"@vaadin/bundles": "$@vaadin/bundles",
@ -99,7 +101,6 @@
"@vaadin/common-frontend": "$@vaadin/common-frontend",
"react-dom": "$react-dom",
"construct-style-sheets-polyfill": "$construct-style-sheets-polyfill",
"react-router-dom": "$react-router-dom",
"lit": "$lit",
"@polymer/polymer": "$@polymer/polymer",
"react": "$react",
@ -108,6 +109,7 @@
"@vaadin/vaadin-themable-mixin": "$@vaadin/vaadin-themable-mixin",
"@vaadin/vaadin-lumo-styles": "$@vaadin/vaadin-lumo-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>
<artifactId>sistema-mantenimiento</artifactId>
<name>sistema-mantenimiento</name>
<version>1.0-SNAPSHOT</version>
<version>2.1</version>
<packaging>jar</packaging>
<properties>
<java.version>17</java.version>
<vaadin.version>24.5.8</vaadin.version>
<vaadin.version>24.8.0</vaadin.version>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.8</version>
<version>3.5.8</version>
</parent>
<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');
/* ----------- 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 */
@ -26,12 +25,9 @@
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 -------------------------------------- */
/*
@ -59,19 +55,6 @@
font-size: 18px;
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 -------------------------- */
/*
/*
@ -90,176 +73,21 @@
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 */
vaadin-upload > vaadin-button {
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' */
vaadin-button[theme~="subir-archivo"] {
background-color: #691b31;
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);
}
.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) {


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

@ -1,13 +1,14 @@
package mx.gob.jumapacelaya;
import com.vaadin.flow.component.page.AppShellConfigurator;
import com.vaadin.flow.component.page.Push;
import com.vaadin.flow.server.PWA;
import com.vaadin.flow.theme.Theme;
import com.vaadin.flow.theme.lumo.Lumo;
import mx.gob.jumapacelaya.services.DatabaseService;
import org.springframework.boot.SpringApplication;
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.
@ -17,7 +18,8 @@ import org.springframework.boot.autoconfigure.validation.ValidationAutoConfigura
*
*/
@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")
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.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
@ -51,16 +52,29 @@ public class RedmineClient {
StringBuilder url = new StringBuilder(REDMINE_URL + "/issues.json?key=" + user.getKey()
+ statusFilter + "&offset=" + offset + "&limit=" + limit);
url.append("&sort=created_on:desc");
// Filtro por fechas
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()
.uri(URI.create(url.toString()))
.header("Content-Type", "application/json")
@ -71,10 +85,10 @@ public class RedmineClient {
if (response.statusCode() == 200) {
tickets.addAll(parseTickets(response.body()));
} else {
System.err.println("Error en la respuesta: " + response.statusCode());
log.error("Error al obtener tickets. Codigo de estado: {}", response.statusCode());
}
} catch (Exception e) {
e.printStackTrace();
log.error("Ocurrió una excepción al procesar la respuesta del servidor: ", e);
}
return tickets;
@ -95,11 +109,20 @@ public class RedmineClient {
// Filtro por fechas
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()
@ -113,55 +136,15 @@ public class RedmineClient {
JsonObject jsonObject = JsonParser.parseString(response.body()).getAsJsonObject();
return jsonObject.get("total_count").getAsInt();
} 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) {
e.printStackTrace();
log.error("Ocurrió un error al procesar l respuesta del servidor", e);
}
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
private List<Ticket> parseTickets(String json) {
List<Ticket> tickets = new ArrayList<>();
@ -232,11 +215,10 @@ public class RedmineClient {
tickets.add(ticketObj);
}
} 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) {
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;
}
@ -268,7 +250,7 @@ public class RedmineClient {
//return response.body();
} catch (IOException | InterruptedException e) {
e.printStackTrace();
log.error("Error al obtener la cuenta del usuario: {}", e);
return null;
}
}
@ -309,7 +291,7 @@ public class RedmineClient {
return newUser;
} catch (Exception e) {
e.printStackTrace();
log.error("Error al crear el usuario: {}", e);
return null;
}
}
@ -328,13 +310,13 @@ public class RedmineClient {
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() == 200) {
String responseBody = response.body();
System.out.println("Datos del usuario " + responseBody);
log.info("Datos del usuario " + responseBody);
return parseUser(responseBody);
} else {
System.err.println("Error en la respuesta: " + response.statusCode());
log.error("Error en la respuesta {}", response.statusCode());
}
} catch (Exception e) {
e.printStackTrace();
log.error("Error al obtener el usuario por nombre de usuario: {}", e);
}
return null;
}
@ -376,10 +358,10 @@ public class RedmineClient {
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() == 204) {
String responseBody = response.body();
System.out.println("Ticket " + ticketId + " cerrado correctamente.");
log.info("Ticket " + ticketId + " cerrado correctamente.");
} 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);
}
} catch (DateTimeParseException ex) {
System.err.println("Error al parsear fecha '" + fieldName + "': " + dateStr);
log.error("Error al parsear fecha '" + fieldName + "': " + dateStr);
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 {
@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
protected void configure(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(auth -> auth
@ -40,45 +55,13 @@ public class SecurityConfiguration extends VaadinWebSecurity {
// !Esta es la real autenticacion con ldap
@Autowired
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.setUseAuthenticationRequestCredentials(true);
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
public LdapContextSource 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")
@Autowired
public JdbcTemplate mysqlTemplate(@Qualifier("mysqlDB") DataSource 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")
@Autowired
public JdbcTemplate oracleJdbcTemplate(@Qualifier("oracleDB") DataSource 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;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.security.interfaces.RSAKey;
import java.sql.*;
import java.time.LocalDate;
import java.util.ArrayList;
@ -24,6 +23,7 @@ import java.util.UUID;
@Service
public class DatabaseService {
private static final Logger logger = LoggerFactory.getLogger(DatabaseService.class);
@Value("${db.url}")
private String dbUrl;
@ -62,7 +62,7 @@ public class DatabaseService {
tiposDeMantenimientos.add(tipo);
}
} catch (SQLException e) {
e.printStackTrace();
logger.error("Error al obtener tipos de mantenimientos: ", e);
}
return tiposDeMantenimientos;
}
@ -83,13 +83,11 @@ public class DatabaseService {
}
} catch (SQLException e) {
e.printStackTrace();
System.err.println("Error al obtener nomenclatura: " + e.getMessage());
logger.error("Error al obtener nomenclatura: ", e);
}
return nomenclatura;
}
/* -------------- Metodo para obtener a los usuarios ---------------- */
public List<Usuario> getUsuarios() {
List<Usuario> usuarios = new ArrayList<>();
@ -109,12 +107,11 @@ public class DatabaseService {
usuarios.add(usuario);
}
} catch (SQLException e) {
e.printStackTrace();
logger.error("Error al obtener usuarios: ", e);
}
return usuarios;
}
/* -------------- Metodo para obtener los departamentos ---------------- */
public List<DepartamentosModel> getDepartamentos() {
List<DepartamentosModel> departamentos = new ArrayList<>();
@ -132,12 +129,11 @@ public class DatabaseService {
departamentos.add(departamentosModel);
}
} catch (SQLException e) {
e.printStackTrace();
logger.error("Error al obtener departamentos: ", e);
}
return departamentos;
}
/* -------------- Metodo para obtener los tipos de hardware ---------------- */
public List<TiposHardware> getTiposHardware() {
List<TiposHardware> tiposHardware = new ArrayList<>();
@ -155,13 +151,11 @@ public class DatabaseService {
tiposHardware.add(tiposHardwareModel);
}
} catch (SQLException e) {
e.printStackTrace();
logger.error("Error al obtener tipos de hardware: ", e);
}
return tiposHardware;
}
/* ----------------Obtener el Plan Anual de Mantenimiento ---------------- */
public List<PlanAnual> getPlanAnual() {
List<PlanAnual> planAnualList = new ArrayList<>();
@ -208,14 +202,12 @@ public class DatabaseService {
);
planAnualList.add(planAnual);
}
System.out.println("Registros obtenidos: " + planAnualList.size());
} catch (SQLException e) {
e.printStackTrace();
logger.error("Error al obtener plan anual: ", e);
}
return planAnualList;
}
/* ----------------Obtener el Plan Anual de Mantenimiento por ID ---------------- */
public PlanAnual getPlanAnualPorId(int id) {
String query = """
@ -262,13 +254,11 @@ public class DatabaseService {
}
}
} catch (SQLException e) {
e.printStackTrace();
logger.error("Error al obtener plan anual por id: ", e);
}
return null;
}
/* ----------------Obtener detalles del mantenimiento ---------------- */
public DetalleMantenimientoModel getDetalleMantenimientoPorPlanAnualId(int planAnualId) {
String query = "SELECT\n" +
@ -296,7 +286,7 @@ public class DatabaseService {
try (Connection connection = getMysqlConnection();
PreparedStatement statement = connection.prepareStatement(query)) {
statement.setInt(1, planAnualId);
try (ResultSet rs = statement.executeQuery()) {
@ -319,12 +309,71 @@ public class DatabaseService {
}
}
} catch (SQLException e) {
e.printStackTrace();
logger.error("Error al obtener detalles del mantenimiento por plan anual id: ", e);
}
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 ---------------- */
public List<HardwareDetalle> getHardwaredetallePorMantId(int mantenimientoId) {
@ -333,10 +382,10 @@ public class DatabaseService {
"FROM HARDWAREDET h\r\n" + //
"INNER JOIN TIPOSHARDWARE t ON h.TIPOHARDWAREID = t.TIPOHARDWAREID \r\n" + //
"WHERE h.MANTENIMIENTOID = ?";
try (Connection conn = getMysqlConnection();
PreparedStatement stmt = conn.prepareStatement(query)) {
stmt.setInt(1, mantenimientoId);
ResultSet rs = stmt.executeQuery();
@ -353,19 +402,18 @@ public class DatabaseService {
}
} catch (SQLException e) {
e.printStackTrace();
logger.error("Error al obtener detalles del hardaware por ID: ", e);
}
return detalles;
}
/* ---------------- Obtener las actualizaciones de seguridad ---------------- */
public List<ActualizacioneSeguridadModel> getActualizacionesSeg(int mantenimientoId) {
List<ActualizacioneSeguridadModel> actualizaciones = new ArrayList<>();
String query = "SELECT *\r\n" + //
"FROM ACTUALIZACIONESSEG a\r\n" + //
"WHERE a.MANTENIMIENTOID = ?";
try (Connection conn = getMysqlConnection();
PreparedStatement stmt = conn.prepareStatement(query)) {
@ -383,12 +431,11 @@ public class DatabaseService {
}
} catch (SQLException e) {
e.printStackTrace();
logger.error("Error al obtener las actualizaciones de seguridad: ", e);
}
return actualizaciones;
}
/* ---------------- Obtener los mantenimientos correctivos ---------------- */
public List<MantCorrectivosModel> getMantenimientosCorrectivos(int tipomantId) {
List<MantCorrectivosModel> mantCorrectivos = new ArrayList<>();
@ -430,13 +477,11 @@ public class DatabaseService {
}
}
} catch (SQLException e) {
e.printStackTrace();
logger.error("Error al obtener los mantenimientos correctivos: ", e);
}
return mantCorrectivos;
}
/*-=iii=<() *-=iii=<() *-=iii=<() *-=iii=<() *-=iii=<() *-=iii=<()*/
/*-=iii=<() *-=iii=<() *-=iii=<() *-=iii=<() *-=iii=<()*/
/*-=iii=<() *-=iii=<() *-=iii=<() *-=iii=<()*/
@ -517,14 +562,13 @@ public class DatabaseService {
connection.commit();
} 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
if (connection != null) {
try {
connection.rollback();
} catch (SQLException rollbackEx) {
System.err.println("Error al hacer rollback: " + rollbackEx.getMessage());
logger.error("Error al hacer rollback: ", rollbackEx);
}
}
} finally {
@ -560,12 +604,11 @@ public class DatabaseService {
int rowsAffected = preparedStatement.executeUpdate();
isInserted = rowsAffected > 0;
} catch (SQLException e) {
e.printStackTrace();
logger.error("Error al insertar hardware: ", e);
}
return isInserted;
}
public int getUltimoMantenimientoId() {
int ultimoId = -1;
try (Connection connection = getMysqlConnection()) {
@ -577,12 +620,11 @@ public class DatabaseService {
}
}
} catch (SQLException e) {
e.printStackTrace();
logger.error("Error al obtener el ultimo id de mantenimiento: ", e);
}
return ultimoId;
}
// INSERTAR EN LA TABLA: ACTUALIZACIONESSEG
public boolean insertActualizacionSeg(String descripcion, String otras, int mantenimientoId) {
String query = "INSERT INTO ACTUALIZACIONESSEG (descripcion, otrasactualizaciones, mantenimientoid) VALUES (?, ?, ?)";
@ -602,7 +644,6 @@ public class DatabaseService {
}
}
// INSERTAR ARCHIVO EXCEL EN LA TABLA: PLANANUAL
public void insertarDesdeExcel(InputStream inputStream) {
String query = "INSERT INTO PLANANUAL (NOMEQUIPO, AREA, MONITOR, TECLADO, MOUSE, " +
@ -677,7 +718,6 @@ public class DatabaseService {
}
}
// INSERTAR NUEVO EQUIPO INDIVIDUAL EN PLANANUAL
public void insertarNuevoEquipo(String nomequipo, String area, boolean monitor, boolean teclado,
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
private Date getDateCellValue(Cell cell) {
if (cell != null) {
@ -780,7 +819,6 @@ public class DatabaseService {
return false; // Valor por defecto si la celda es nula
}
/* ----------------Actualizar los detalles del del mantenimiento por ID ---------------- */
public boolean actualizarPlanAnual(int planAnualId, String nombreEquipo) {
String sql = "UPDATE PLANANUAL SET NOMEQUIPO=? WHERE PLANANUALID=?";
@ -790,7 +828,7 @@ public class DatabaseService {
stmt.setInt(2, planAnualId);
return stmt.executeUpdate() > 0;
} catch (SQLException e) {
e.printStackTrace();
logger.error("Error al actualizar el plan anual: ", e);
return false;
}
}
@ -817,12 +855,11 @@ public class DatabaseService {
stmt.setInt(8, mantenimientoId);
return stmt.executeUpdate() > 0;
} catch (SQLException e) {
e.printStackTrace();
logger.error("Error al actualizar el mantenimiento: ", e);
return false;
}
}
/* ----------------Actualizar los detalles del hardaware por ID ---------------- */
public boolean actualizarHardwareDetalle(HardwareDetalle detalle) {
String sql = "UPDATE HARDWAREDET SET MODELO=?, NUMSERIE=?, PLACA=? WHERE HARDWAREDETID=?";
@ -834,12 +871,11 @@ public class DatabaseService {
stmt.setInt(4, detalle.getHardwareDetId());
return stmt.executeUpdate() > 0;
} catch (SQLException e) {
e.printStackTrace();
logger.error("Error al actualizar el detalle del hardware: ", e);
return false;
}
}
/* ----------------Actualizar las actualizaciones de seguridad por ID ---------------- */
public boolean actualizarActualizacionSeg(ActualizacioneSeguridadModel actualizacion) {
String sql = "UPDATE ACTUALIZACIONESSEG SET OTRASACTUALIZACIONES=? WHERE ACTUALIZACIONSEGID=?";
@ -849,12 +885,11 @@ public class DatabaseService {
stmt.setInt(2, actualizacion.getActualizacionsegId());
return stmt.executeUpdate() > 0;
} catch (SQLException e) {
e.printStackTrace();
logger.error("Error al actualizar la actualización de seguridad: ", e);
return false;
}
}
/* ---------------- Insertar en bitacora ---------------- */
public boolean insertarBitacora(int mantenimientoid, String usuarioid, LocalDate fechora, String motivo) {
String query = "INSERT INTO BITACORACTUALIZACIONES (MANTENIMIENTOID, USUARIOID, FECHORA, MOTIVO) VALUES (?, ?, ?, ?)";
@ -866,12 +901,11 @@ public class DatabaseService {
stmt.setString(4, motivo);
return stmt.executeUpdate() > 0;
} catch (SQLException e) {
e.printStackTrace();
logger.error("Error al insertar en bitacora: ", e);
return false;
}
}
/* ---------------- Insertar en bitacora de eliminacion de equipos ---------------- */
public boolean insertarBitacoraEliminacion(int plananualid, String usuarioid, LocalDate fechora, String motivo) {
String query = "INSERT INTO BITACORAELIMINACIONES (PLANANUALID, USUARIOID, FECHAHORA, MOTIVO) VALUES (?, ?, ?, ?)";
@ -883,12 +917,11 @@ public class DatabaseService {
stmt.setString(4, motivo);
return stmt.executeUpdate() > 0;
} catch (SQLException e) {
e.printStackTrace();
logger.error("Error al insertar en bitacora de eliminacion de equipos: ", e);
return false;
}
}
/* ---------------- Eliminar equipo de PLAN ANUAL ---------------- */
public void eliminarEquipoPlanAnual(int 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);
}
} 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);
}
} catch (SQLException e) {
e.printStackTrace();
logger.error("Error al obtener las preguntas de la encuesta: ", e);
}
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 = """
insert into RESPUESTAS
(MANTENIMIENTOID,PREGUNTAID,FECHARESPUESTA,RESPUESTA,EMPLEADOID)
(MANTENIMIENTOID,PREGUNTAID,FECHARESPUESTA,RESPUESTA,EMPLEADOID,COMENTARIOS)
values
(?,?,?,?,?)
(?,?,?,?,?,?)
""";
String updateQuery = """
@ -978,6 +1009,7 @@ public class DatabaseService {
insertStmt.setTimestamp(3, ahora);
insertStmt.setBoolean(4, r.getRespuesta());
insertStmt.setInt(5, empleadoid);
insertStmt.setString(6, comentarios);
insertStmt.addBatch();
}
@ -1003,7 +1035,6 @@ public class DatabaseService {
}
}
public String crearTokenEncuesta(int mantenimientoid) {
String token = UUID.randomUUID().toString();
Timestamp expira = new Timestamp(System.currentTimeMillis() + (24 * 60 * 60 * 1000));
@ -1018,7 +1049,7 @@ public class DatabaseService {
stmt.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
logger.error("Error al crear el token de encuesta: ", e);
}
return token;
}
@ -1035,9 +1066,56 @@ public class DatabaseService {
return rs.getInt("MANTENIMIENTOID");
}
} catch (SQLException e) {
e.printStackTrace();
logger.error("Error al validar el token de encuesta: ", e);
}
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;
import com.vaadin.flow.component.notification.Notification;
import jakarta.activation.DataSource;
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.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.MimeMailMessage;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.stereotype.Service;
import java.io.File;
import java.io.InputStream;
@Service
public class EmailService {
@ -23,10 +16,12 @@ public class EmailService {
@Autowired
private JavaMailSender mailSender;
private static final Logger log = LoggerFactory.getLogger(EmailService.class);
public void enviarCorreo(String destinatario, String asunto, String cuerpo, String imagePath) {
try {
MimeMessage mensaje = mailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(mensaje, true);
MimeMessageHelper helper = new MimeMessageHelper(mensaje, true, "UTF-8");
helper.setTo(destinatario);
helper.setSubject(asunto);
helper.setFrom("noreply@jumapacelaya.gob.mx");
@ -37,10 +32,11 @@ public class EmailService {
helper.addInline("image_id", imgResource);
mailSender.send(mensaje);
System.out.println("Correo enviado con imagen exitosamente");
log.info("Correo enviado a {}", destinatario);
} catch (Exception e) {
e.printStackTrace();
log.error("Error al enviar el correo: ", e);
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.setSearchScope(SearchControls.SUBTREE_SCOPE);
searchControls.setReturningAttributes(new String[]{"givenName", "sn", "mail"});
searchControls.setReturningObjFlag(true);
searchControls.setReturningObjFlag(false);
List<CustomUserDetails> result = ldapTemplate.search("", filter.encode(), searchControls, (Attributes attrs) -> {
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.grid.Grid;
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.H3;
import com.vaadin.flow.component.html.Span;
import com.vaadin.flow.component.icon.Icon;
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.popover.Popover;
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.data.renderer.ComponentRenderer;
import com.vaadin.flow.function.SerializableBiConsumer;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.server.StreamRegistration;
@ -34,31 +32,29 @@ import com.vaadin.flow.server.StreamResource;
import jakarta.annotation.security.PermitAll;
import mx.gob.jumapacelaya.api.RedmineClient;
import mx.gob.jumapacelaya.api.ServerProperties;
import mx.gob.jumapacelaya.models.ActividadDiaria;
import mx.gob.jumapacelaya.models.RedmineUser;
import mx.gob.jumapacelaya.models.Ticket;
import mx.gob.jumapacelaya.services.LdapService;
import mx.gob.jumapacelaya.services.UserService;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.vaadin.lineawesome.LineAwesomeIcon;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.io.InputStream;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
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;
@ -91,11 +87,12 @@ public class ActDiariaView extends VerticalLayout {
// Layout de opciones
opcionesLyt = new HorizontalLayout();
opcionesLyt.setWidthFull();
opcionesLyt.setMargin(false);
opcionesLyt.setAlignItems(Alignment.BASELINE);
//opcionesLyt.setMargin(false);
opcionesLyt.getStyle()
.set("box-shadow", "0 4px 8px rgba(0,0,0,0.2)")
.set("border-radius", "10px")
.set("background-color", "white")
//.set("background-color", "white")
.set("padding", "1rem")
.set("margin", "1rem auto");
@ -103,18 +100,42 @@ public class ActDiariaView extends VerticalLayout {
chkSoloAbiertos = new Checkbox("Ver solo abiertos");
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
chkSoloAbiertos.addValueChangeListener(e -> {
boolean soloAbiertos = e.getValue(); // marcado = abiertos
loadTickets(soloAbiertos);
fechaDesde.setEnabled(!soloAbiertos);
fechaHasta.setEnabled(!soloAbiertos);
btnBuscar.setEnabled(!soloAbiertos);
grid.getDataProvider().refreshAll();
});
// Estado inicial
@ -128,6 +149,30 @@ public class ActDiariaView extends VerticalLayout {
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
grid.addColumn(Ticket::getId).setHeader("No.")
.setAutoWidth(true)
@ -148,6 +193,7 @@ public class ActDiariaView extends VerticalLayout {
return fecha != null ? fecha.format(DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm:ss")) : "";
}).setHeader("Fecha creación")
.setAutoWidth(true)
.setSortable(true)
.setKey("fechaCreacion");
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")) : "";
}).setHeader("Fecha cierre")
.setAutoWidth(true)
.setSortable(true)
.setKey("fechaCierre");
grid.addColumn(ticket -> {
@ -198,7 +245,7 @@ public class ActDiariaView extends VerticalLayout {
} else {
return "";
}
}).setHeader("Duración").setAutoWidth(true).setKey("duracion");
}).setHeader("Duración").setAutoWidth(true).setSortable(true).setKey("duracion");
grid.addColumn(ticket ->
@ -217,7 +264,6 @@ public class ActDiariaView extends VerticalLayout {
grid.addComponentColumn(ticket -> {
Button btnVer = new Button(new Icon(VaadinIcon.EYE));
btnVer.addClickListener(event -> showDescription(ticket));
btnVer.getStyle().set("color", "#A02142");
return btnVer;
});
@ -229,6 +275,7 @@ public class ActDiariaView extends VerticalLayout {
btnColumns = new Button(VaadinIcon.GRID_H.create());
// Botón para exportar
btnExport = new Button("Exportar", LineAwesomeIcon.FILE_EXCEL.create());
btnExport.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
btnExport.addClickListener(e -> exportTicketsCerrados());
showColumnsLyt = new HorizontalLayout(btnExport, btnColumns);
showColumnsLyt.setJustifyContentMode(JustifyContentMode.END);
@ -281,30 +328,6 @@ public class ActDiariaView extends VerticalLayout {
// Invertir parámetro para que coincida con RedmineClient
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);");
} 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());
}
}
@ -399,85 +422,210 @@ public class ActDiariaView extends VerticalLayout {
// Metodo para exportar los tickets cerrados a un archivo de Excel
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() {
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 com.vaadin.flow.component.UI;
import com.vaadin.flow.component.button.ButtonVariant;
import com.vaadin.flow.component.html.*;
import com.vaadin.flow.component.page.History;
import com.vaadin.flow.server.StreamRegistration;
import com.vaadin.flow.server.StreamResource;
import mx.gob.jumapacelaya.services.EmailService;
import mx.gob.jumapacelaya.services.ReportService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
@ -53,6 +57,8 @@ import mx.gob.jumapacelaya.services.SecurityService;
@CssImport("./themes/sistema-mantenimiento/styles.css")
public class DetallesMantView extends VerticalLayout implements BeforeEnterObserver {
private static final Logger logger = LoggerFactory.getLogger(DetallesMantView.class);
@Value("${app.base-url}")
private String baseUrl;
@ -75,6 +81,7 @@ public class DetallesMantView extends VerticalLayout implements BeforeEnterObser
private final Button btnCancelar;
private final Button btnGuardar;
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 mantenimientoIdActual;
private Dialog confirmDialog;
@ -102,7 +109,6 @@ public class DetallesMantView extends VerticalLayout implements BeforeEnterObser
mainLayout.getStyle()
.set("box-shadow","0 4px 8px rgba(0,0,0,0.2)")
.set("border-radius", "12px")
.set("background-color", "white")
.set("padding", "1rem")
.set("margin", "1rem auto");
@ -197,9 +203,14 @@ public class DetallesMantView extends VerticalLayout implements BeforeEnterObser
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();
botonesHeaderLyt.setWidthFull();
botonesHeaderLyt.add(btnImprimirRepo, btnEnviarEncuesta);
botonesHeaderLyt.add(btnVolverAtras, btnImprimirRepo, btnEnviarEncuesta);
HorizontalLayout botonesLayout = new HorizontalLayout();
botonesLayout.setWidthFull();
@ -297,7 +308,7 @@ public class DetallesMantView extends VerticalLayout implements BeforeEnterObser
} catch (Exception ex) {
Notification.show("Error al generar el reporte: " + ex.getMessage(), 4000, Notification.Position.MIDDLE)
.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";
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);
} else {
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);
avisoEncuestaNtf = new Notification(layout);
avisoEncuestaNtf.setPosition(Notification.Position.TOP_CENTER);
avisoEncuestaNtf.setPosition(Notification.Position.TOP_END);
avisoEncuestaNtf.addThemeVariants(NotificationVariant.LUMO_WARNING);
avisoEncuestaNtf.setDuration(5000);
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.VerticalLayout;
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.router.BeforeEnterEvent;
import com.vaadin.flow.router.BeforeEnterObserver;
@ -32,11 +33,10 @@ public class EncuestaView extends VerticalLayout implements BeforeEnterObserver
private final DatabaseService encuestasDBService;
private int mantenimientoId = -1;
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 TextField txtNumEmpl = new TextField("No. Empleado:");
private String token;
private final TextArea txtComentarios = new TextArea("Comentarios:");
private static class RespuestaComponente {
@ -56,7 +56,6 @@ public class EncuestaView extends VerticalLayout implements BeforeEnterObserver
setSpacing(true);
setPadding(true);
setSizeFull();
this.getStyle()
.set("background-image", "url('/images/LOGO_1024X768.jpg')")
@ -65,9 +64,11 @@ public class EncuestaView extends VerticalLayout implements BeforeEnterObserver
.set("background-position", "center");
mainLyt.setHeightFull();
mainLyt.setWidth("55%");
//mainLyt.setMaxHeight("90vh");
mainLyt.getStyle()
.set("overflow-y", "auto")
.set("box-shadow", "0 4px 8px rgba(0,0,0,0.2)")
.set("border-radius", "12px")
.set("background-color", "white")
@ -119,6 +120,10 @@ public class EncuestaView extends VerticalLayout implements BeforeEnterObserver
respuestasUI.add(new RespuestaComponente(p,radios));
}
txtComentarios.setWidthFull();
txtComentarios.setMaxHeight("5rem");
preguntasLyt.add(txtComentarios);
HorizontalLayout gracias = new HorizontalLayout(new H3("¡Gracias!"));
gracias.setWidthFull();
gracias.setJustifyContentMode(JustifyContentMode.CENTER);
@ -133,7 +138,7 @@ public class EncuestaView extends VerticalLayout implements BeforeEnterObserver
mainLyt.removeAll();
mainLyt.add(titulos,preguntasLyt,gracias, btnEnviarLyt);
mainLyt.add(titulos,preguntasLyt ,gracias, btnEnviarLyt);
}
private void procesarRespuestas() {
@ -155,11 +160,15 @@ public class EncuestaView extends VerticalLayout implements BeforeEnterObserver
rc.radios.getValue().equals("Si")
));
}
txtComentarios.getValue();
String comentarios = txtComentarios.isEmpty() ? null : txtComentarios.getValue();
encuestasDBService.insertRespuestas(
mantenimientoId,
respuestas,
empleadoId,
comentarios,
this.token
);


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

@ -1,5 +1,6 @@
package mx.gob.jumapacelaya.ui;
import com.vaadin.flow.component.UI;
import com.vaadin.flow.component.applayout.AppLayout;
import com.vaadin.flow.component.applayout.DrawerToggle;
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.sidenav.SideNav;
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 mx.gob.jumapacelaya.services.SecurityService;
@CssImport("./themes/sistema-mantenimiento/styles.css")
public class MainLayout extends AppLayout {
public class MainLayout extends AppLayout implements BeforeEnterObserver {
private H2 viewTitle;
private final SecurityService securityService;
@ -45,7 +49,7 @@ public class MainLayout extends AppLayout {
String u = securityService.getAuthenticatedUser();
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-size", "20px");
@ -70,7 +74,7 @@ public class MainLayout extends AppLayout {
headerLayout.setPadding(true);
headerLayout.setSpacing(false);
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");
imgLogo.setWidthFull();
@ -78,7 +82,7 @@ public class MainLayout extends AppLayout {
headerLayout.add(imgLogo);
Scroller scroller = new Scroller(createNavigation());
scroller.getStyle().set("background-color", "#691b31");
//scroller.getStyle().set("background-color", "#691b31");
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("Listado de Actividades", ActDiariaView.class, VaadinIcon.EDIT.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("opacity", "0.9");
return nav;
}
@ -101,9 +106,25 @@ public class MainLayout extends AppLayout {
return layout;
}
private String getCurrentPageTitle() {
return MenuConfiguration.getPageHeader(getContent()).orElse("");
}
@Override
protected void 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;
import com.vaadin.flow.component.Text;
import com.vaadin.flow.component.UI;
import com.vaadin.flow.component.button.Button;
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.TextField;
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.Route;
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.SecurityService;
import mx.gob.jumapacelaya.services.UserService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.vaadin.lineawesome.LineAwesomeIcon;
import java.time.LocalDate;
@ -48,6 +46,8 @@ import java.util.*;
@CssImport("./themes/sistema-mantenimiento/styles.css")
public class MantCorrectivoView extends VerticalLayout {
private static final Logger logger = LoggerFactory.getLogger(MantCorrectivoView.class);
private VerticalLayout mainLayout;
private final SecurityService securityService;
private final VerticalLayout controlsLayout;
@ -96,7 +96,6 @@ public class MantCorrectivoView extends VerticalLayout {
headerLayout.getStyle()
.set("box-shadow", "0 4px 8px rgba(0,0,0,0.2)")
.set("border-radius", "10px")
.set("background-color", "white")
.set("padding", "1rem")
.set("margin", "1rem auto");
@ -107,7 +106,6 @@ public class MantCorrectivoView extends VerticalLayout {
mainLayout.getStyle()
.set("box-shadow", "0 4px 8px rgba(0,0,0,0.2)")
.set("border-radius", "12px")
.set("background-color", "white")
.set("padding", "1rem")
.set("margin", "1rem auto");
@ -594,7 +592,7 @@ public class MantCorrectivoView extends VerticalLayout {
} catch (Exception e) {
Notification.show("Error al enviar el correo", 4000, Notification.Position.MIDDLE)
.addThemeVariants(NotificationVariant.LUMO_ERROR);
e.printStackTrace();
logger.error("Error al enviar correo: ", e);
}
// Limpiar campos
@ -624,7 +622,7 @@ public class MantCorrectivoView extends VerticalLayout {
} catch (Exception ex) {
Notification.show("Ocurrio un error inesperado: " + ex.getMessage(), 5000, Notification.Position.MIDDLE)
.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;
import com.vaadin.flow.component.Component;
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.checkbox.CheckboxGroup;
import com.vaadin.flow.component.combobox.ComboBox;
import com.vaadin.flow.component.datepicker.DatePicker;
import com.vaadin.flow.component.dependency.CssImport;
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.Span;
import com.vaadin.flow.component.icon.Icon;
import com.vaadin.flow.component.icon.VaadinIcon;
import com.vaadin.flow.component.notification.Notification;
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.VerticalLayout;
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.TextField;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.router.BeforeEnterEvent;
import com.vaadin.flow.router.BeforeEnterObserver;
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.SecurityService;
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 java.time.LocalDate;
@ -50,6 +44,8 @@ import java.util.*;
@CssImport("./themes/sistema-mantenimiento/styles.css")
public class MantenimientoView extends VerticalLayout implements BeforeEnterObserver {
private static final Logger logger = LoggerFactory.getLogger(MantenimientoView.class);
private final SecurityService securityService;
private final VerticalLayout controlsLayout;
private final DatabaseService databaseService;
@ -98,7 +94,6 @@ public class MantenimientoView extends VerticalLayout implements BeforeEnterObse
mainLayout.getStyle()
.set("box-shadow","0 4px 8px rgba(0,0,0,0.2)")
.set("border-radius", "12px")
.set("background-color", "white")
.set("padding", "1rem")
.set("margin", "1rem auto");
@ -718,7 +713,7 @@ public class MantenimientoView extends VerticalLayout implements BeforeEnterObse
} catch (Exception e) {
Notification.show("Error al enviar el correo", 4000, Notification.Position.MIDDLE)
.addThemeVariants(NotificationVariant.LUMO_ERROR);
e.printStackTrace();
logger.error("Error al enviar el correo", e);
}
// Limpiar campos
@ -748,7 +743,7 @@ public class MantenimientoView extends VerticalLayout implements BeforeEnterObse
} catch (Exception ex) {
Notification.show("Ocurrio un error inesperado: " + ex.getMessage(), 5000, Notification.Position.MIDDLE)
.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.button.Button;
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.CheckboxGroupVariant;
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.dependency.CssImport;
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.GridVariant;
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.popover.Popover;
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.TextField;
import com.vaadin.flow.component.upload.Upload;
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.function.ValueProvider;
import com.vaadin.flow.router.PageTitle;
@ -42,20 +38,24 @@ import com.vaadin.flow.server.StreamRegistration;
import com.vaadin.flow.server.StreamResource;
import jakarta.annotation.security.PermitAll;
import mx.gob.jumapacelaya.models.PlanAnual;
import mx.gob.jumapacelaya.models.encuestas.MantenimientosSinEncuesta;
import mx.gob.jumapacelaya.services.DatabaseService;
import mx.gob.jumapacelaya.services.EmailService;
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.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.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
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.Year;
import java.time.format.DateTimeFormatter;
@ -70,6 +70,11 @@ import java.util.stream.IntStream;
@CssImport("./themes/sistema-mantenimiento/styles.css")
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 ReportService reportService;
@ -90,7 +95,7 @@ public class PlanAnualView extends VerticalLayout {
private Popover reportePopover;
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.env = env;
this.reportService = reportService;
@ -113,9 +118,14 @@ public class PlanAnualView extends VerticalLayout {
PlanAnualFilter planAnualFilter = new PlanAnualFilter(dataView);
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.getCell(planAnualGrid.getColumnByKey("smtColumnKey"))
@ -130,6 +140,9 @@ public class PlanAnualView extends VerticalLayout {
headerRow.getCell(planAnualGrid.getColumnByKey("mesplaneado"))
.setComponent(createFilterHeader("Mes Planeado", planAnualFilter::setMesPlaneado));
headerRow.getCell(planAnualGrid.getColumnByKey("encuesta"))
.setComponent(createFilterHeader("Encuesta", planAnualFilter::setEncuesta));
// MENU CONTEXTUAL DEL GRID
@ -228,6 +241,7 @@ public class PlanAnualView extends VerticalLayout {
this.setSizeFull();
add(filtrosLayout, uploadLayout);
add(gridLayout);
this.emailService = emailService;
}
private void setupHeader() {
@ -264,7 +278,6 @@ public class PlanAnualView extends VerticalLayout {
if ("PENDIENTE".equalsIgnoreCase(estado)) {
btn = new Button(new Icon(VaadinIcon.EDIT));
btn.setTooltipText("Realizar mantenimiento");
btn.getStyle().set("color", "#A02142");
btn.addClickListener(event -> {
int idPlananual = planAnual.getNumero();
@ -286,7 +299,6 @@ public class PlanAnualView extends VerticalLayout {
} else if ("REALIZADO".equalsIgnoreCase(estado)) {
btn = new Button(new Icon(VaadinIcon.EYE));
btn.setTooltipText("Ver detalles");
btn.getStyle().set("color", "#A02142");
btn.addClickListener(event -> {
int idPlananual = planAnual.getNumero();
@ -369,14 +381,8 @@ public class PlanAnualView extends VerticalLayout {
btnAddEquipo.setTooltipText("Agregar nuevo equipo");
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<>();
int currentYear = Year.now().getValue();
@ -384,12 +390,14 @@ public class PlanAnualView extends VerticalLayout {
.boxed().collect(Collectors.toList());
yearFilter.setItems(years);
yearFilter.setPlaceholder("Año");
yearFilter.setValue(currentYear);
yearFilter.setClearButtonVisible(true);
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);
HorizontalLayout columnSelectorLayout = new HorizontalLayout();
columnSelectorLayout.setAlignItems(Alignment.END);
@ -541,6 +549,11 @@ public class PlanAnualView extends VerticalLayout {
private String equipo;
private String departamento;
private String mesPlaneado;
private LocalDate fechaProgramada;
private LocalDate fechaRealizacion;
private String encuesta;
private String estado;
private String situacion;
private Integer year;
private boolean excludeRealizado = true;
@ -569,6 +582,11 @@ public class PlanAnualView extends VerticalLayout {
this.dataView.refreshAll();
}
public void setEncuesta(String encuesta) {
this.encuesta = encuesta;
this.dataView.refreshAll();
}
public void setYear(Integer year) {
this.year = year;
dataView.refreshAll();
@ -586,6 +604,7 @@ public class PlanAnualView extends VerticalLayout {
boolean matchesEquipo = matches(planAnual.getNomEquipo(), equipo);
boolean matchesDepartamento = matches(planAnual.getDepartamento(), departamento);
boolean matchesMesPlaneado = matches(planAnual.getMesplaneado(), mesPlaneado);
boolean matchesEncuesta = matches(planAnual.getEncuesta(), encuesta);
boolean matchesYear = true;
if (year != null) {
@ -608,7 +627,8 @@ public class PlanAnualView extends VerticalLayout {
&& matchesDepartamento
&& matchesMesPlaneado
&& matchesYear
&& matchesEstado;
&& matchesEstado
&& matchesEncuesta;
}
private boolean matches(String value, String serachTerm) {
@ -689,7 +709,7 @@ public class PlanAnualView extends VerticalLayout {
} catch (Exception ex) {
Notification.show("Error al genrar el reporte: " + ex.getMessage(), 5000, Notification.Position.MIDDLE)
.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) {
Notification.show("Error al generar el reporte: " + ex.getMessage(), 5000, Notification.Position.MIDDLE)
.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.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 final LoginForm login = new LoginForm();
// Usuario local para demostracion
private final String localUser = "admin";
private final String localPassword = "admin";
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}
logging.level.org.atmosphere = warn
spring.profiles.active=dev
server.servlet.session.cookie.secure=true
# Launch the default browser when starting the application in development mode
vaadin.launch-browser=true
@ -12,13 +13,15 @@ spring.jpa.defer-datasource-initialization = true
#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.username=administrator
spring.ldap.password=Dr3na$134%4guA
###################PRODUCTIVO####################
redmine.url=https://proyman.jumapacelaya.gob.mx/
redmine.url=https://proyman.jumapacelaya.gob.mx
redmine.api_key=69be2a5df9bacce02722f566fdf0731d728a1b86


+ 1
- 1
tsconfig.json View File

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


Loading…
Cancel
Save