SIGESA: Crear Proceso de Envio de Correos

Tarea:

  • El correo debe ser remitido al rol digitador y aprobador
  • El correo debe venir con copia a los analistas de PPO
  • El correo debe indicar la fecha de inicio y la fecha de fin de la etapa de planificación
  • El correo debe indicar:
    • Lista de códigos presupuestarios vinculados a metas operativas en la etapa de formulación sin presupuesto asignado.
    • Lista de códigos presupuestarios en ejecución que tienen dinero y que no han sido incluidos en la formulación operativa.

Paso01: Crear Proceso

  • Lo primero que vamos hacer es crear el archivo que va manejar el proceso
  • Para esto
  • Debemos ubicarnos en la ruta
    • Proyecto-> Service->src->main->resources->META-INF->spring
  • Aquí se encuentran todos los archivos que manejan procesos
  • Podríamos crear uno nuevo o copiar uno y modificarlo
  • Creamos el archivo
applicationContext-procesoPPIAvisoInicioFinalizacionEtapasPPOJob.xml
  • El contenido basico es:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:batch="http://www.springframework.org/schema/batch"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:task="http://www.springframework.org/schema/task"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/batch http://www.springframework.org/schema/batch/spring-batch.xsd
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd 
	http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd 
        http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task.xsd
         http://www.springframework.org/schema/context
         http://www.springframework.org/schema/context/spring-context.xsd
         http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">


</beans>

Paso1.1 Relación SIGESA -> Proceso

  • Ahora debemos agregarle el código necesario para crear un listener que relaciona
    • procesoPPIAvisoInicioFinalizacionEtapasPPOJob
    • Con
      • procesoPPIAvisoInicioFinalizacionEtapasPPO
<batch:job id="procesoPPIAvisoInicioFinalizacionEtapasPPOJob">
        <batch:step id="simpleTaskletStepProcesoPPIAvisoInicioFinalizacionEtapasPPOJob">
            <batch:tasklet ref="procesoPPIAvisoInicioFinalizacionEtapasPPO" />
            <batch:listeners>
                <batch:listener ref="batchJobStepListener" />
            </batch:listeners>
        </batch:step>
    </batch:job>
  • NOTA:
  • Notar que los nombres son diferentes
  • procesoPPIFinalizacionEtapasPPOJob:
    • este referecia al nombre del proceso en la interfaz de SIGESA
<batch:job id="procesoPPIAvisoInicioFinalizacionEtapasPPOJob">
  • procesoPPIFinalizacionEtapasPPO:
    • Y este se refiere al bean que contiene la información de servicio que debe ejecutar
<batch:tasklet ref="procesoPPIAvisoInicioFinalizacionEtapasPPO" />

Paso 1.2: relación Proceso -> Servicio

  • Ahora debemos agregarle el bean que contiene la información de cual servicio debe ejecutar al invocarse el proceso
  • Y ademas va establecer donde esta el servicio y el método que va a ejecutar
  • Y tambien va establecer cuales parametros se van a pasar desde la interfaz de SIGESA
    • #{stepExecution.id}: siempre se pasa es el id del proceso
    • #{jobParameters[‘etapaPlan’]} y este es uno adicional que se le debe pasar para el funcionamiento del proceso
  • Codigo:
<bean id="procesoPPIAvisoInicioFinalizacionEtapasPPO" class="org.springframework.batch.core.step.tasklet.MethodInvokingTaskletAdapter" scope="step">
        <property name="targetObject" ref="planOperativoServiceImpl"  />
        <property name="targetMethod" value="procesoPPIAvisoInicioFinalizacionEtapasPPO" />
        <property name="arguments">
            <list>           
                <value>#{stepExecution.id}</value>
                <value>#{jobParameters['etapaPlan']}</value>
            </list>
        </property>
    </bean>

Paso 1.3: lanzador del proceso

  • Además es necesario crear el lanzador del proceso para esto agregamos
  • NOTAR que es con JOB
procesoPPIAvisoInicioFinalizacionEtapasPPO
  • Agregamos
<bean id="procesoPPIAvisoInicioFinEtapasPPOJobLauncher" class="cr.ac.una.cgi.sdkuna.service.BatchJobLauncherService" scope="prototype">
        <property name="jobLauncher" ref="jobLauncher"></property>
        <property name="job" ref="procesoPPIAvisoInicioFinEtapasPPOJob"></property>
    </bean>

Paso 1.4: Resultado Final

  • Resultado
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:batch="http://www.springframework.org/schema/batch"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:task="http://www.springframework.org/schema/task"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/batch http://www.springframework.org/schema/batch/spring-batch.xsd
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd 
	http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd 
        http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task.xsd
         http://www.springframework.org/schema/context
         http://www.springframework.org/schema/context/spring-context.xsd
         http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">

 

    <batch:job id="procesoPPIAvisoInicioFinalizacionEtapasPPOJob">
        <batch:step id="simpleTaskletStepProcesoPPIAvisoInicioFinalizacionEtapasPPOJob">
            <batch:tasklet ref="procesoPPIAvisoInicioFinalizacionEtapasPPO" />
            <batch:listeners>
                <batch:listener ref="batchJobStepListener" />
            </batch:listeners>
        </batch:step>
    </batch:job>
    
    
    
    <bean id="procesoPPIAvisoInicioFinalizacionEtapasPPO" class="org.springframework.batch.core.step.tasklet.MethodInvokingTaskletAdapter" scope="step">
        <property name="targetObject" ref="planOperativoServiceImpl"  />
        <property name="targetMethod" value="procesoPPIAvisoInicioFinalizacionEtapasPPO" />
        <property name="arguments">
            <list>           
                <value>#{stepExecution.id}</value>
                <value>#{jobParameters['etapaPlan']}</value>
            </list>
        </property>
    </bean>
  
  

    <bean id="procesoPPIAvisoInicioFinEtapasPPOJobLauncher" class="cr.ac.una.cgi.sdkuna.service.BatchJobLauncherService" scope="prototype">
        <property name="jobLauncher" ref="jobLauncher"></property>
        <property name="job" ref="procesoPPIAvisoInicioFinEtapasPPOJob"></property>
    </bean>
    
</beans>

Paso2: reportar el proceso

Paso 2.1: Reportar proceso

  • Ahora debemos reportar el proceso dentro de SIGESA
  • Para esto ingresamos a SIGESA -> SAS – Seguridad y administración -> SAS – Mantenimiento -> Lista de Procesos
  • Le damos agregar
  • Le agregamos los datos:
    • Nombre del trabajo: procesoPPIAvisoInicioFinalizacionEtapasPPOJob
    • Descripción: Proceso que enviá un correo a los usuarios con rol PPO Analista, PPO Aprobador Unidad Ejecutora, Aprobador Unidad Integradora y PPO Digitador para avisarles el inicio o finalización de una etapa y errores en los códigos presupuestarios.
  • Ahora le agregamos los parametros
  • EtapaPlan:
  • datos:
    • Parametro: etapaPlan
    • Descripción: Etapa de Planificación
    • Tipo parametro: objeto
    • Requerido: true
  • Le damos agregar
  • Regresamos y le damos guardar y retornamos a configurar el parametro

  • le damos en propiedades
  • Datos:
    • Clase Mapeada: cr.ac.una.cgi.sigesa.ppi.ppo.domain.EtapaPlan
    • Propiedad objeto: nombre
    • Título del LOV Bean: etapa_list_form_title
    • Nombre bean: EtapaPlanBean
    • Servicio clase: EtapaPlanService
  • NOTA: Ahora como no seleccionamos el «método de filtrado» ni «Método filtrado LOV» va mostrar todas las etapas
  • Regresamos y guardamos todo

PASO2.2: Reportar el recurso

  • Ahora debemos reportar el proceso como un recurso
  • Para esto ingresamos a SAS – Seguridad y Administración->SAS – Mantenimiento -> «Lista de recursos»
  • Datos:
    • Nombre: /pages/batchJobLauncher.xhtml?proceso=procesoPPIAvisoInicioFinalizacionEtapasPPOJob (NOTA: ES EL NOMBRE DEL ARCHIVO XML)
    • Tipo de recurso: Página
    • Visible: true
    • Secuencia: 1
    • Notas: Proceso que enviá un correo a los usuarios con rol PPO Analista, PPO Aprobador Unidad Ejecutora, Aprobador Unidad Integradora y PPO Digitador para avisarles el inicio o finalización de una etapa y errores en los códigos presupuestarios.
    • Estado: Activo
    • rol: UNA_PPI_PPO_ADMIN

Paso3: crear menú

  • Ahora que ya definimos el proceso y definimos el recurso debemos crear la opción de menú
  • Ingresamos a SAS – Seguridad y Administración -> SAS – Mantenimiento -> «Lista de Menú»
  • Le damos nuevo
  • Datos:
    • Crear menú de un menú padre:
    • Menú: PPO – Procesos
    • Recurso: /pages/batchJobLauncher.xhtml?proceso=procesoPPIAvisoInicioFinalizacionEtapasPPOJob
    • Nombre: Proceso Aviso Inicio Finalización de Etapas PPO
    • Notas: Proceso que enviá un correo a los usuarios con rol PPO Analista, PPO Aprobador Unidad Ejecutora, Aprobador Unidad Integradora y PPO Digitador para avisarles el inicio o finalización de una etapa y errores en los códigos presupuestarios.
    • Estado: Activo
    • Secuencia 2

Paso 2.3: Volver a ingresar

  • Ahora debemos salir del sistema y volver a ingresar
  • Y podemos ingresar a PPI -> PPO-> PPO – Procesos -> «Proceso Aviso Inicio Finalización de Etapas PPO»

Paso3: Creación de funciones en PlanOperativoRepository

  • Para la tarea específica se debio crear los siguientes metodos en el repository
/**
     * 
     * Defnición que retorna una lista de Planes Operativos según:
     * 
     * @param periodoAnual
     * @param unidadEjecutora
     * @author Gustavo Matamoros González
     * @since 13/03/2023
     * @issue: PPI-275
     * @return List<PlanOperativo>
     */
    public List<PlanOperativo> findByPeriodoAnualAndUnidadEjecutora (PeriodoAnual periodoAnual, UnidadEjecutora unidadEjecutora);

Paso4: creación de la definición del servicio (planOperativoService)

/**
     * Definición del proceso que envia un correo de notificacion a usuarios con rol de digitador, aprobador y analista PPO sobre:
     * * Códigos presupuestarios Sin presupuesto vínculados a metas
     * * Códigos presupuestarios Con presupuesto No vículados a metas
     *
     * @param jobId : Id del proceso
     * @param etapaPlan : etapaPlan a notificar
     * @author Gustavo Matamoros González
     * @fechaCreacion: 10/03/2023
     * @Version: 1.0.0
     * @modulo: PPI-PPO
     * @issue: PPI-275
     */
    public void procesoPPIAvisoInicioFinalizacionEtapasPPO(Long jobId, String idEtapaPlan);

    /**
     * Definición de método que obtiene los planes operativos por PeriodoAnual y Unidad Ejecutora:
     *
     * @param periodoAnual
     * @param unidadEjecutora
     * @author Gustavo Matamoros González
     * @fechaCreacion: 10/03/2023
     * @Version: 1.0.0
     * @modulo: PPI-PPO
     * @issue: PPI-275
     */
    public List<PlanOperativo> findByPeriodoAnualAndUnidadEjecutora(PeriodoAnual periodoAnual, UnidadEjecutora unidadEjecutora);

    /**
     * Definición de método que obtiene los códigos presupuestarios sin presupuesto asignados a Metas de un plan operativo
     *
     * @param planOperativo
     * @author Gustavo Matamoros González
     * @fechaCreacion: 22/03/2023
     * @Version: 1.0.0
     * @modulo: PPI-PPO
     * @issue: PPI-275
     */
    public List<CodigoPresupuestario> obtenerCodigosPresupuestariosSinPresupuestoAsignadosAMetas(PlanOperativo planOperativo);

    /**
     * Definición de método que obtiene los códigos presupuestarios con presupuesto sin asignar a Metas de un plan operativo
     *
     * @param planOperativo
     * @author Gustavo Matamoros González
     * @fechaCreacion: 22/03/2023
     * @Version: 1.0.0
     * @modulo: PPI-PPO
     * @issue: PPI-275
     */
    public List<CodigoPresupuestario> obtenerCodigosPresupuestariosConPresupuestoSinAsignarAMetas(PlanOperativo planOperativo);

    /**
     * Definición que obtiene el plan operativo duplicado
     *
     * @param planOperativo
     * @author Gustavo Matamoros González
     * @fechaCreacion: 22/03/2023
     * @Version: 1.0.0
     * @modulo: PPI-PPO
     * @issue: PPI-275
     */
    public PlanOperativo obtenerPlanOperativoDuplicadoPorPlanOperativo( PlanOperativo planOperativo);

Paso4: creación de la definición del servicio (planOperativoServiceImpl)

/**
     * Proceso que envia un correo de notificacion a usuarios con rol de digitador, aprobador y Analista sobre:
     * * Códigos presupuestarios Sin presupuesto vínculados a metas
     * * Códigos presupuestarios Con presupuesto No vículados a metas
     *
     * @param jobId : Id del proceso
     * @param etapaPlan : etapaPlan a notificar
     * @author Gustavo Matamoros González
     * @fechaCreacion: 22/03/2023
     * @Version: 1.0.0
     * @modulo: PPI-PPO
     * @issue: PPI-275
     */
    @Override
    public void procesoPPIAvisoInicioFinalizacionEtapasPPO(Long jobId, String idEtapaPlan) {


        // *************************************************
        // Variables Necesarias
        // *************************************************
        // Etapa plan pasada por parametro
        EtapaPlan etapaPlan = etapaPlanService.find(Long.parseLong(idEtapaPlan));
        
        TipoEtapa tipoEtapaModificacion = tipoEtapaService.findTipoEtapaByParametro("param_ppi_ppo_tipoEtapa_modificacion");
        TipoPlanOperativo tipoPlanUnidad = tipoPlanOperativoService.findTipoPlanUnidad();
        
        // Creamos una lista de planes operativos por notificar
        List<PlanOperativo> planesOperativosPorNotificar = new ArrayList();
        
        // Cuerpo del correo
        String bodyCorreo=""; 

        // *************************************************
        // Crear lista de planOperativos por notificar (Si existe duplicado o sino formulado)
        // *************************************************

        // Obtenemos los planes activos
        List<PlanOperativo> planesOperativosActivos = repository.findByActivo(Boolean.TRUE);

        // Si existen planes activos
        if (!planesOperativosActivos.isEmpty()) {

            // Recorremos los planes activos
            for (PlanOperativo planOperativoActivo : planesOperativosActivos) {
                // Obtenemos el duplicado del plan
                PlanOperativo planOperativoDuplicado = obtenerPlanOperativoDuplicadoPorPlanOperativo(planOperativoActivo);
                
                // Existe duplicado
                if(planOperativoDuplicado != null){
                    planesOperativosPorNotificar.add(planOperativoDuplicado);

                    // Si no existe duplicado utilizamos el formulado
                }else{
                    planesOperativosPorNotificar.add(planOperativoActivo);
                }
            }

            // *************************************************
            // Si la lista no esta vacia
            // *************************************************
            if (!planesOperativosPorNotificar.isEmpty()) {

                // *************************************************
                // Obtener los roles necesarios
                // *************************************************
                // Obtenemos los usuarios con rol de Aprobador Unidad Ejecutora
                Rol rolPPOAprobadorUnidadEjecutora = rolService.findOne(Long.parseLong(parametroService.findOneByLlave("param_ppi_ppo_rol_aprobador_unidad_ejecutora").getValor()));
                List<Usuario> usuariosPPOUnidadEjecutora = rolUsuarioService.getByRolActivo(rolPPOAprobadorUnidadEjecutora);

                // Obtenemos los usuarios con rol de Aprobador Unidad Integradora
                Rol rolPPOAprobadorUnidadIntegradora = rolService.findOne(Long.parseLong(parametroService.findOneByLlave("param_ppi_ppo_rol_aprobador_unidad_integradora").getValor()));
                List<Usuario> usuariosPPOUnidadIntegradora = rolUsuarioService.getByRolActivo(rolPPOAprobadorUnidadIntegradora);

                // Obtenemos los usuarios con rol de Analista PPO
                Rol rolPPOAnalista = rolService.findOne(Long.parseLong(parametroService.findOneByLlave("param_ppi_ppo_rol_analista").getValor()));
                List<Usuario> usuariosPPOAnalista = rolUsuarioService.getByRolActivo(rolPPOAnalista);


                // Obtenemos los usuarios con rol de Digitador PPO
                Rol rolPPODigitador = rolService.findOne(Long.parseLong(parametroService.findOneByLlave("param_ppi_ppo_rol_digitador").getValor()));
                List<Usuario> usuariosPPODigitador = rolUsuarioService.getByRolActivo(rolPPODigitador);

                // *************************************************
                // Crea asunto correo
                // *************************************************
                // Año de la etapa
                String etapaPlanAnno = Integer.toString(etapaPlan.getPeriodoAnual().getAno());
                
                // Variable para el asunto del correo
                String subject="";

                // Obtenemos el parametro del subject
                String titulo = i18nService.findOneByLlaveAndIdioma("param_ppi_ppo_AvisoInicioFinalizacionEtapasPPO_subject", "es").getValor();
                
                // Subject
                subject = titulo + ": " + etapaPlan.getNombre() + " " + etapaPlanAnno;

                // *************************************************
                // Recorrer los planes operativos activos
                // *************************************************
                for (PlanOperativo planOperativo : planesOperativosPorNotificar) {

                    // *************************************************
                    // Parametros Correo
                    // *************************************************
                    Map<String, String> parametros = new HashMap<>();

                    parametros.put("etapaPlanNombre", etapaPlan.getNombre());
                    parametros.put("etapaPlanFechaInicial", etapaPlan.getFechaInicialStr());
                    parametros.put("etapaPlanFechaFinal", etapaPlan.getFechaFinalStr());
                    parametros.put("etapaPlanAnno", etapaPlanAnno);

                    // Variable para almacenar los codigos presupuestarios Sin Presupuesto Asignados a Metas
                    String mensajeCodigosPresupuestariosSinPresupuestoAsignadoMeta = "";

                    // Variable para almacenar los codigos Presupuestarios Con Presupuesto Sin Asignar a Metas
                    String mensajeCodigosPresupuestariosConPresupuestoSinAsignarAMeta = "";

                    // *************************************************
                    // Obtener usuarios ACL por Unidad Ejecutora
                    // *************************************************
                    // Obtenemos los usuarios por Rol ACL
                    List<Usuario> usuariosUE = this.obtieneUsuariosPorRolAcl(planOperativo.getUnidadEjecutora().getCodigo());

                    // Creamos una lista de usuarios para envió de correo
                    List<Usuario> usuariosEnviarCorreo = new ArrayList();

                    // Le agregamos la lista de usuarios de analista a la lista de envió de correo
                    usuariosEnviarCorreo.addAll(usuariosPPOAnalista);

                    // Que corremos la lista de usuarios ACL
                    for (Usuario usuarioACL : usuariosUE) {
            
                        // Si el el usuarioACL esta dentro de de la lista de unidadEjecutora o unidadIntegradora o digitador o no esta en la lista de envió de correo 
                        if (
                            (
                                usuariosPPOUnidadEjecutora.contains(usuarioACL) || 
                                usuariosPPOUnidadIntegradora.contains(usuarioACL)|| 
                                usuariosPPODigitador.contains(usuarioACL)
                            ) 
                            && 
                            (
                                !usuariosEnviarCorreo.contains(usuarioACL)
                            )) {
                            
                            // Agregue el usuario a la lista
                            usuariosEnviarCorreo.add(usuarioACL);
                        }
                    }


                    // *************************************************
                    // PASO2.1 Si es tipo Modificación y Plan de Unidad 
                    // *************************************************
                    if(etapaPlan.getTipoEtapa().equals(tipoEtapaModificacion) && (planOperativo.getTipoPlanOperativo().equals(tipoPlanUnidad) )){
                        
                        
                        // ****************************************************************
                        // Lista de Codigos Presupuestarios Sin Presupuesto Asignado a Meta
                        // ****************************************************************
                        List<CodigoPresupuestario> listaCodigosPresupuestariosSinPresupuestoAsignadosAMetas =  this.obtenerCodigosPresupuestariosSinPresupuestoAsignadosAMetas(planOperativo);

                        // Si la lista no esta vacia
                        if (!listaCodigosPresupuestariosSinPresupuestoAsignadosAMetas.isEmpty()) {
                            for(CodigoPresupuestario codigoPresupuestario : listaCodigosPresupuestariosSinPresupuestoAsignadosAMetas) {

                                if (codigoPresupuestario.equals(listaCodigosPresupuestariosSinPresupuestoAsignadosAMetas.get(0))) {
                                    mensajeCodigosPresupuestariosSinPresupuestoAsignadoMeta += i18nService.findOneByLlaveAndIdioma("planOperativo_codigosSinPresupuesto_titleWarnnig", "es").getValor()+"<br/>";
                                } 
                                if(codigoPresupuestario.getSubprogramaPresupuestario().getProgramaPresupuestario().equals(programaPresupuestarioService.getProgramaPresupuestarioAcademico()) && (!mensajeCodigosPresupuestariosSinPresupuestoAsignadoMeta.contains(i18nService.findOneByLlaveAndIdioma("planOperativo_codigosSinPresupuesto_programaPresupuestarioAcademico_titleWarnnig", "es").getValor() + "<br/>"))){
                                    mensajeCodigosPresupuestariosSinPresupuestoAsignadoMeta += i18nService.findOneByLlaveAndIdioma("planOperativo_codigosSinPresupuesto_programaPresupuestarioAcademico_titleWarnnig", "es").getValor() + "<br/>";
                               
                                } else if(codigoPresupuestario.getSubprogramaPresupuestario().getProgramaPresupuestario().equals(programaPresupuestarioService.getProgramaPresupuestarioVidaEstudiantil()) && (!mensajeCodigosPresupuestariosSinPresupuestoAsignadoMeta.contains(i18nService.findOneByLlaveAndIdioma("planOperativo_codigosSinPresupuesto_programaPresupuestarioVidaUniversitaria_titleWarnnig", "es").getValor()+ "<br/>"))){
                                    mensajeCodigosPresupuestariosSinPresupuestoAsignadoMeta += i18nService.findOneByLlaveAndIdioma("planOperativo_codigosSinPresupuesto_programaPresupuestarioVidaUniversitaria_titleWarnnig", "es").getValor() + "<br/>";
                                } else if(codigoPresupuestario.getSubprogramaPresupuestario().getProgramaPresupuestario().equals(programaPresupuestarioService.getProgramaPresupuestarioAdministrativo()) && (!mensajeCodigosPresupuestariosSinPresupuestoAsignadoMeta.contains(i18nService.findOneByLlaveAndIdioma("planOperativo_codigosSinPresupuesto_programaPresupuestarioAdministrativo_titleWarnnig", "es").getValor()+ "<br/>"))){
                                    mensajeCodigosPresupuestariosSinPresupuestoAsignadoMeta += i18nService.findOneByLlaveAndIdioma("planOperativo_codigosSinPresupuesto_programaPresupuestarioAdministrativo_titleWarnnig", "es").getValor()+ "<br/>";
                                } 
                                mensajeCodigosPresupuestariosSinPresupuestoAsignadoMeta += codigoPresupuestario.getCodigo() + " " + codigoPresupuestario.getNombre() + "<br />";
                            }
                            parametros.put("mensajeCodigosPresupuestariosSinPresupuestoAsignadoMeta", mensajeCodigosPresupuestariosSinPresupuestoAsignadoMeta);

                        } else {
                            parametros.put("mensajeCodigosPresupuestariosSinPresupuestoAsignadoMeta", "");
                        }

                        // ****************************************************************
                        // Lista de Codigos Presupuestarios Con Presupuesto Sin Asignar a Meta
                        // ****************************************************************
                        List<CodigoPresupuestario> listaCodigosPresupuestariosConPresupuestoSinAsignarAMeta =  this.obtenerCodigosPresupuestariosConPresupuestoSinAsignarAMetas(planOperativo);

                        // Si la lista no esta vacia
                        if(!listaCodigosPresupuestariosConPresupuestoSinAsignarAMeta.isEmpty()){
                            for(CodigoPresupuestario codigoPresupuestario : listaCodigosPresupuestariosConPresupuestoSinAsignarAMeta){
                                if (codigoPresupuestario.equals(listaCodigosPresupuestariosConPresupuestoSinAsignarAMeta.get(0))) {
                                    mensajeCodigosPresupuestariosConPresupuestoSinAsignarAMeta += i18nService.findOneByLlaveAndIdioma("planOperativo_codigosConPresupuesto_titleWarnnig", "es").getValor()+"<br/>";
                                } 
                                mensajeCodigosPresupuestariosConPresupuestoSinAsignarAMeta += codigoPresupuestario.getCodigo() + " " + codigoPresupuestario.getNombre() + "<br />";
                            }
                            parametros.put("mensajeCodigosPresupuestariosConPresupuestoSinAsignarAMeta", mensajeCodigosPresupuestariosConPresupuestoSinAsignarAMeta);

                        }else{
                            parametros.put("mensajeCodigosPresupuestariosConPresupuestoSinAsignarAMeta", "");
                        }

                        // Verificar si almenos una lista tiene datos
                        if( (!mensajeCodigosPresupuestariosSinPresupuestoAsignadoMeta.equals("")) || (!mensajeCodigosPresupuestariosConPresupuestoSinAsignarAMeta.equals(""))){
                            parametros.put("mensajeListaCodigosPresupuestarios", "Se le informa que el plan operativo código: '"+ planOperativo.getCodigo()+"' contiene lo siguiente:");
                        }else{
                            parametros.put("mensajeListaCodigosPresupuestarios", "");
                        }


                    // *************************************************
                    // Si no es etapa modificacion
                    // *************************************************
                    }else{
                        // Oculte las siguientes variables del mensaje
                        parametros.put("mensajeListaCodigosPresupuestarios", "");
                        parametros.put("mensajeCodigosPresupuestariosSinPresupuestoAsignadoMeta", "");
                        parametros.put("mensajeCodigosPresupuestariosConPresupuestoSinAsignarAMeta", "");
                    }

                    bodyCorreo = plantillaCorreoService.getHtmlContent("TPPIPPOAVISOINICIOFINALIZACIONETAPASPPO", parametros);

                    // Verificamos si existe un plan 
                    this.enviarCorreoAvisoInicioFinalizacionEtapasPPO(
                                    usuariosEnviarCorreo, 
                                    bodyCorreo,
                                    subject);

                }
            }
        }
       

        
    }

    /**
     * Metodo que envia un correo de notificacion a usuarios con rol de digitador, aprobador y Analista sobre:
     * * Códigos presupuestarios Sin presupuesto vínculados a metas
     * * Códigos presupuestarios Con presupuesto No vículados a metas
     *
     * @param usuariosEnviarCorreo
     * @param bodyCorreo
     * @param subject
     * @author Gustavo Matamoros González
     * @fechaCreacion: 22/03/2023
     * @Version: 1.0.0
     * @modulo: PPI-PPO
     * @issue: PPI-275
     */
    public void enviarCorreoAvisoInicioFinalizacionEtapasPPO(
                List<Usuario> usuariosEnviarCorreo, 
                String bodyCorreo,
                String subject) {
        try {

            // Creamos una lista vacia de personas
            List<Persona> personas = new ArrayList();

            // Agregamos personas por usuario
            personas = agregarPersonasPorUsuarios(personas, usuariosEnviarCorreo);

            // Si la lista no esta vacia
            if (!personas.isEmpty()) {

                // Recorremos la lista de personas a enviar correo
                for (Persona persona : personas) {
                    
                    // Crear lista de correos
                    List<String> correos = new ArrayList();

                    // *************************************************
                    // PASO3: Enviar correo 
                    // *************************************************
                    // Obtenemos los correos de las personas
                    persona.getPersonaCorreos().stream()
                            .filter(personaCorreo -> ((personaCorreo.getActivo() == 1) && (personaCorreo.getPrioridad() == 1)))
                            .map(personaCorreo -> personaCorreo.getCorreoElectronico())
                            .forEachOrdered(destinatario -> {
                                correos.add(destinatario);
                            });

                    // Enviamos el correo
                    if (!correos.isEmpty()) {
                        mailService.sendMail(MailMessageData.builder()
                                .to(correos)
                                .subject(subject)
                                .body(bodyCorreo)
                                .build());
                    }
                }
            }

        } catch (Exception ex) {

        }
    }

    
    /**
     * Método que obtiene los códigos presupuestarios sin presupuesto asignados a metas de un plan operativo
     *
     * @param planOperativo
     * @author Gustavo Matamoros González
     * @fechaCreacion: 22/03/2023
     * @Version: 1.0.0
     * @modulo: PPI-PPO
     * @issue: PPI-275
     */
    @Override
    public List<CodigoPresupuestario> obtenerCodigosPresupuestariosSinPresupuestoAsignadosAMetas(PlanOperativo planOperativo) {

        //*************************************************************
        // Declaración de Variables
        //*************************************************************
        List<CodigoPresupuestario> listaCodigosPresupuestarios = new ArrayList();
        List<CodigoPresupuestario> listaCodigosPresupuestariosAC = new ArrayList();
        List<CodigoPresupuestario> listaCodigosPresupuestariosVU = new ArrayList();
        List<CodigoPresupuestario> listaCodigosPresupuestariosAD = new ArrayList();

        
        List<CodigoPresupuestario> listaCodigosFormuladosSinPresupuesto = new ArrayList();
        List<CodigoPresupuestario> listaCodigosEnPlanOperativo = new ArrayList();
        List<MetaPoa> listaMetasEnPlanOperativoAcademico = new ArrayList();
        List<MetaPoa> listaMetasEnPlanOperativoAdministrativo = new ArrayList();
        List<MetaPoa> listaMetasEnPlanOperativoVidaUniversitaria = new ArrayList();
        List<ObjetivoPOA> listaObjetivosPoa = planOperativo.getListaObjetivosPOA();

        TipoPlanOperativo tipoPlanUnidad = tipoPlanOperativoService.findTipoPlanUnidad();
        VinculacionPoa vinculacionPoaActual = vinculacionPoaService.findByUnidadEjecutoraAndPeriodoAnualAndTipoPlanOperativo(planOperativo.getUnidadEjecutora(), planOperativo.getPeriodoAnual(), tipoPlanUnidad);
        List<VinPoaUnidadEjecutora> listaVinPoaUnidadEjecutora = new ArrayList();
        List<CodigoPresupuestario> listaCodigosPresupuestariosConPresupuesto = new ArrayList();

        //*************************************************************
        // Si Existe vinculación POA: Obtenemos Los códigos Presupuestarios Con Presupuesto
        //*************************************************************
        if (vinculacionPoaActual != null) {

            if (vinculacionPoaActual.getId() != null) {
                listaVinPoaUnidadEjecutora.addAll(vinculacionPoaActual.getListaVinPoaUnidadEjecutora());
            } else {
                listaVinPoaUnidadEjecutora.clear();
            }
                for (ObjetivoPOA objetivoPoa : planOperativo.getListaObjetivosPOA()) {
                    listaCodigosPresupuestariosConPresupuesto.addAll(codigoPresupuestarioService.findByUnidadEjecutoraAndProgramaPresupuestarioAndPeriodoAnual(planOperativo.getUnidadEjecutora(), objetivoPoa.getProgramaPresupuestario(), planOperativo.getPeriodoAnual()));

                    if (!listaVinPoaUnidadEjecutora.isEmpty()) {
                        for (VinPoaUnidadEjecutora uniVin : listaVinPoaUnidadEjecutora) {
                            listaCodigosPresupuestariosConPresupuesto.addAll(codigoPresupuestarioService.findByUnidadEjecutoraAndProgramaPresupuestarioAndPeriodoAnual(uniVin.getUnidadEjecutora(), objetivoPoa.getProgramaPresupuestario(), planOperativo.getPeriodoAnual()));
                        }
                    }
                }
        }
        
        //*************************************************************
        // Se eliminan Repetidos
        //*************************************************************
        Set setConPresupuesto = new HashSet<>(listaCodigosPresupuestariosConPresupuesto);
        listaCodigosPresupuestariosConPresupuesto.clear();
        listaCodigosPresupuestariosConPresupuesto.addAll(setConPresupuesto);


        //*************************************************************
        // Obtener las Metas por Programa Presupuestario Y Códigos Presupuestarios por Metas
        //*************************************************************
        for (ObjetivoPOA objetivo : listaObjetivosPoa) {
            for (MetaPoa meta : objetivo.getListaMetasPoa()) {
                for (MetaPoaPresupuesto presupuesto : meta.getListaMetaPoaPresupuestos()) {                  
                    listaCodigosEnPlanOperativo.add(presupuesto.getCodigoPresupuestario());
                }
                if (objetivo.getProgramaPresupuestario().equals(programaPresupuestarioService.getProgramaPresupuestarioAcademico())) {
                    listaMetasEnPlanOperativoAcademico.add(meta);
                } else if (objetivo.getProgramaPresupuestario().equals(programaPresupuestarioService.getProgramaPresupuestarioAdministrativo())) {
                    listaMetasEnPlanOperativoAdministrativo.add(meta);
                } else if (objetivo.getProgramaPresupuestario().equals(programaPresupuestarioService.getProgramaPresupuestarioVidaEstudiantil())) {
                    listaMetasEnPlanOperativoVidaUniversitaria.add(meta);
                }
            }
        }

        //*************************************************************
        // Obtener las Excepciones Presupuestarias y removerlas
        //*************************************************************
        EtapaPlan etapaPlan = planOperativo.getEtapaPlan();
        UnidadEjecutora unidadEjecutora = planOperativo.getUnidadEjecutora();
        List<ExcepcionPresupuestaria> listaExcepcionPresupuestaria = excepcionPresupuestariaService.findAllExcepcionPresupuestariaByEtapaPlanAndUnidadEjecutora(etapaPlan, unidadEjecutora);

        if (listaExcepcionPresupuestaria != null && !listaExcepcionPresupuestaria.isEmpty()) {
            List<ExcepcionCodigo> listaCodigosPresupuestariosExcepcionUnidad = excepcionCodigoService.findAllExcepcionCodigoByExcepcionPresupuestaria(listaExcepcionPresupuestaria.get(0));
            for (ExcepcionCodigo excepcionCodigo : listaCodigosPresupuestariosExcepcionUnidad) {
                if (listaCodigosPresupuestariosConPresupuesto.contains(excepcionCodigo.getCodigoPresupuestario())) {
                    listaCodigosPresupuestariosConPresupuesto.remove(excepcionCodigo.getCodigoPresupuestario());
                }
            }
        }

        //*************************************************************
        // Crear lista de listaCodigosFormuladosSinPresupuesto
        //*************************************************************
        for (CodigoPresupuestario codigoFormulado : listaCodigosEnPlanOperativo) {
            if (!listaCodigosPresupuestariosConPresupuesto.contains(codigoFormulado) && !listaCodigosFormuladosSinPresupuesto.contains(codigoFormulado)) {
                listaCodigosFormuladosSinPresupuesto.add(codigoFormulado);
            }
        }

        // Si la lista no esta vacia
        if (!listaCodigosFormuladosSinPresupuesto.isEmpty()) {
            for (CodigoPresupuestario codigoPresupuestario : listaCodigosFormuladosSinPresupuesto) {
                String mensajeMeta = "";
                for (MetaPoa meta : listaMetasEnPlanOperativoAcademico) {
                    for (MetaPoaPresupuesto presupuesto : meta.getListaMetaPoaPresupuestos()) {
                        if (presupuesto.getCodigoPresupuestario().getId().equals(codigoPresupuestario.getId()) && !listaCodigosPresupuestariosAC.contains(presupuesto.getCodigoPresupuestario())) {
                            listaCodigosPresupuestariosAC.add(presupuesto.getCodigoPresupuestario());
                        }
                    }
                }
                for (MetaPoa meta : listaMetasEnPlanOperativoVidaUniversitaria) {
                    for (MetaPoaPresupuesto presupuesto : meta.getListaMetaPoaPresupuestos()) {
                        if (presupuesto.getCodigoPresupuestario().getId().equals(codigoPresupuestario.getId()) && !listaCodigosPresupuestariosVU.contains(presupuesto.getCodigoPresupuestario())) {
                           listaCodigosPresupuestariosVU.add(presupuesto.getCodigoPresupuestario());
                        }
                    }
                }
                for (MetaPoa meta : listaMetasEnPlanOperativoAdministrativo) {
                    for (MetaPoaPresupuesto presupuesto : meta.getListaMetaPoaPresupuestos()) {
                        if (presupuesto.getCodigoPresupuestario().getId().equals(codigoPresupuestario.getId()) && !listaCodigosPresupuestariosAD.contains(presupuesto.getCodigoPresupuestario())) {
                            listaCodigosPresupuestariosAD.add(presupuesto.getCodigoPresupuestario());
                        }
                    }
                }
            }
        }

        // Contatenar las listas
        if(!listaCodigosPresupuestariosAC.isEmpty()){
            listaCodigosPresupuestarios.addAll(listaCodigosPresupuestariosAC);
        }else if(!listaCodigosPresupuestariosVU.isEmpty()){
            listaCodigosPresupuestarios.addAll(listaCodigosPresupuestariosVU);
        }else if(!listaCodigosPresupuestariosAD.isEmpty()){
            listaCodigosPresupuestarios.addAll(listaCodigosPresupuestariosAD);
        }
        return listaCodigosPresupuestarios;
    }

    /**
     * Definición de método que obtiene los códigos presupuestarios con presupuesto sin asignar a metas de un plan operativo
     *
     * @param planOperativo
     * @author Gustavo Matamoros González
     * @fechaCreacion: 22/03/2023
     * @Version: 1.0.0
     * @modulo: PPI-PPO
     * @issue: PPI-275
     */
    @Override
    public List<CodigoPresupuestario> obtenerCodigosPresupuestariosConPresupuestoSinAsignarAMetas(PlanOperativo planOperativo) {

        //*************************************************************
        // Declaración de Variables
        //*************************************************************
        List<CodigoPresupuestario> listaCodigosFormuladosSinPresupuesto = new ArrayList();
        List<CodigoPresupuestario> listaCodigosEnPlanOperativo = new ArrayList();
        List<CodigoPresupuestario> listaCodigosConPresupuestoFaltantes = new ArrayList();
        List<MetaPoa> listaMetasEnPlanOperativoAcademico = new ArrayList();
        List<MetaPoa> listaMetasEnPlanOperativoAdministrativo = new ArrayList();
        List<MetaPoa> listaMetasEnPlanOperativoVidaUniversitaria = new ArrayList();
        List<ObjetivoPOA> listaObjetivosPoa = planOperativo.getListaObjetivosPOA();


        TipoPlanOperativo tipoPlanUnidad = tipoPlanOperativoService.findTipoPlanUnidad();
        VinculacionPoa vinculacionPoaActual = vinculacionPoaService.findByUnidadEjecutoraAndPeriodoAnualAndTipoPlanOperativo(planOperativo.getUnidadEjecutora(), planOperativo.getPeriodoAnual(), tipoPlanUnidad);
        List<VinPoaUnidadEjecutora> listaVinPoaUnidadEjecutora = new ArrayList();
        List<CodigoPresupuestario> listaCodigosPresupuestariosConPresupuesto = new ArrayList();

        //*************************************************************
        // Si Existe vinculación POA: Obtenemos Los códigos Presupuestarios Con Presupuesto
        //*************************************************************
        if (vinculacionPoaActual != null) {

            if (vinculacionPoaActual.getId() != null) {
                listaVinPoaUnidadEjecutora.addAll(vinculacionPoaActual.getListaVinPoaUnidadEjecutora());
            } else {
                listaVinPoaUnidadEjecutora.clear();
            }
                for (ObjetivoPOA objetivoPoa : planOperativo.getListaObjetivosPOA()) {
                    listaCodigosPresupuestariosConPresupuesto.addAll(codigoPresupuestarioService.findByUnidadEjecutoraAndProgramaPresupuestarioAndPeriodoAnual(planOperativo.getUnidadEjecutora(), objetivoPoa.getProgramaPresupuestario(), planOperativo.getPeriodoAnual()));

                    if (!listaVinPoaUnidadEjecutora.isEmpty()) {
                        for (VinPoaUnidadEjecutora uniVin : listaVinPoaUnidadEjecutora) {
                            listaCodigosPresupuestariosConPresupuesto.addAll(codigoPresupuestarioService.findByUnidadEjecutoraAndProgramaPresupuestarioAndPeriodoAnual(uniVin.getUnidadEjecutora(), objetivoPoa.getProgramaPresupuestario(), planOperativo.getPeriodoAnual()));
                        }
                    }
                }
        }
        
        //*************************************************************
        // Se eliminan Repetidos
        //*************************************************************
        Set setConPresupuesto = new HashSet<>(listaCodigosPresupuestariosConPresupuesto);
        listaCodigosPresupuestariosConPresupuesto.clear();
        listaCodigosPresupuestariosConPresupuesto.addAll(setConPresupuesto);

        //*************************************************************
        // Obtener las Metas por Programa Presupuestario Y Códigos Presupuestarios por Metas
        //*************************************************************
        for (ObjetivoPOA objetivo : listaObjetivosPoa) {
            for (MetaPoa meta : objetivo.getListaMetasPoa()) {
                for (MetaPoaPresupuesto presupuesto : meta.getListaMetaPoaPresupuestos()) {                  
                    listaCodigosEnPlanOperativo.add(presupuesto.getCodigoPresupuestario());
                }
                if (objetivo.getProgramaPresupuestario().equals(programaPresupuestarioService.getProgramaPresupuestarioAcademico())) {
                    listaMetasEnPlanOperativoAcademico.add(meta);
                } else if (objetivo.getProgramaPresupuestario().equals(programaPresupuestarioService.getProgramaPresupuestarioAdministrativo())) {
                    listaMetasEnPlanOperativoAdministrativo.add(meta);
                } else if (objetivo.getProgramaPresupuestario().equals(programaPresupuestarioService.getProgramaPresupuestarioVidaEstudiantil())) {
                    listaMetasEnPlanOperativoVidaUniversitaria.add(meta);
                }
            }
        }

        //*************************************************************
        // Obtener las Excepciones Presupuestarias y removerlas
        //*************************************************************
        EtapaPlan etapaPlan = planOperativo.getEtapaPlan();
        UnidadEjecutora unidadEjecutora = planOperativo.getUnidadEjecutora();
        List<ExcepcionPresupuestaria> listaExcepcionPresupuestaria = excepcionPresupuestariaService.findAllExcepcionPresupuestariaByEtapaPlanAndUnidadEjecutora(etapaPlan, unidadEjecutora);

        if (listaExcepcionPresupuestaria != null && !listaExcepcionPresupuestaria.isEmpty()) {
            List<ExcepcionCodigo> listaCodigosPresupuestariosExcepcionUnidad = excepcionCodigoService.findAllExcepcionCodigoByExcepcionPresupuestaria(listaExcepcionPresupuestaria.get(0));
            for (ExcepcionCodigo excepcionCodigo : listaCodigosPresupuestariosExcepcionUnidad) {
                if (listaCodigosPresupuestariosConPresupuesto.contains(excepcionCodigo.getCodigoPresupuestario())) {
                    listaCodigosPresupuestariosConPresupuesto.remove(excepcionCodigo.getCodigoPresupuestario());
                }
            }
        }

        

        //*************************************************************
        // Crear lista de listaCodigosConPresupuestoFaltantes
        //*************************************************************
        for (CodigoPresupuestario codigoConPresupuesto : listaCodigosPresupuestariosConPresupuesto) {
            if (!listaCodigosEnPlanOperativo.contains(codigoConPresupuesto) && !listaCodigosConPresupuestoFaltantes.contains(codigoConPresupuesto)) {
                listaCodigosConPresupuestoFaltantes.add(codigoConPresupuesto);
            }
        }

        
        
        if (!listaCodigosConPresupuestoFaltantes.isEmpty()) {
            Set setCodigos = new HashSet<>(listaCodigosConPresupuestoFaltantes);
            listaCodigosConPresupuestoFaltantes.clear();
            listaCodigosConPresupuestoFaltantes.addAll(setCodigos);
        
        }

        return listaCodigosConPresupuestoFaltantes;
    }

    /**
     * Método que obtiene los planes operativos por PeriodoAnual y Unidad Ejecutora:
     *
     * @param periodoAnual
     * @param unidadEjecutora
     * @author Gustavo Matamoros González
     * @fechaCreacion: 10/03/2023
     * @Version: 1.0.0
     * @modulo: PPI-PPO
     * @issue: PPI-275
     */
    @Override
    public List<PlanOperativo> findByPeriodoAnualAndUnidadEjecutora(PeriodoAnual periodoAnual, UnidadEjecutora unidadEjecutora) {
        return repository.findByPeriodoAnualAndUnidadEjecutora(periodoAnual, unidadEjecutora);
    }

    /**
     * Método que obtiene el plan operativo duplicado
     *
     * @param planOperativo
     * @author Gustavo Matamoros González
     * @fechaCreacion: 22/03/2023
     * @Version: 1.0.0
     * @modulo: PPI-PPO
     * @issue: PPI-275
     */
    @Override
    public PlanOperativo obtenerPlanOperativoDuplicadoPorPlanOperativo( PlanOperativo planOperativo) {
        
        // Obtenemos el estado de borrador
        EstadoFormulacionOpe estadoAnulado = estadoFormulacionOpeService.obtenerEstadoAnulado();

        // Obtenemos el tipo de Etapa Modificación
        TipoEtapa tipoEtapaModificacion = tipoEtapaService.findTipoEtapaByParametro("param_ppi_ppo_tipoEtapa_modificacion");

        // Creamos una lista de planes operativos por Unidad Ejecutora
        List<PlanOperativo> planesOperativosPorUnidadEjecutora = new ArrayList();

        // Obtenemos los planes operativos por PeriodoAnual y Unidad Ejecutora
        planesOperativosPorUnidadEjecutora = this.findByPeriodoAnualAndUnidadEjecutora(planOperativo.getPeriodoAnual(),planOperativo.getUnidadEjecutora());

        // Crear variable de plan Operativo Duplicado
        PlanOperativo planOperativoDuplicado = null;

        // Si la lista de Planes Operativos x Unidad Ejecutora no esta vacia
        if (!planesOperativosPorUnidadEjecutora.isEmpty()) {

            // Recorremos la lista de planes operativos por Unidad ejecutora
            for (PlanOperativo planOperativoPorUnidad : planesOperativosPorUnidadEjecutora) {

                // Obtenemos el tipo de Etapa Modificación
                EtapaPlan etapaPlan = etapaPlanService.findByTipoEtapaAndTipoPlanAndTipoPlanOperativoAndPeriodoAnualAndFechaInicialLessThanEqualAndFechaFinalGreaterThanEqual(tipoEtapaModificacion, planOperativo.getTipoPlan(), planOperativo.getTipoPlanOperativo(), planOperativo.getPeriodoAnual());

                // Si la etapa de Plan de Modificación existe
                if (etapaPlan != null) {

                    // Si existe un duplicado para la etapaPlan modificacion
                    if( (planOperativoPorUnidad.getEstadoFormulacionOpe() != estadoAnulado) && (planOperativoPorUnidad.getEtapaPlan()==etapaPlan)){
                        planOperativoDuplicado = planOperativoPorUnidad;
                    } 
                }
            }
            
        }
        return planOperativoDuplicado;
        
    }

Paso5: funciones adicionales en EtapaPlan.java

import java.text.SimpleDateFormat;


public String getFechaInicialStr() {
        if (fechaInicial != null) {
            SimpleDateFormat dt = new SimpleDateFormat("dd-MM-yyyy");
            return dt.format(fechaInicial);
        } else {
            return "";
        }
    }

    public String getFechaFinalStr() {
        if (fechaFinal != null) {
            SimpleDateFormat dt = new SimpleDateFormat("dd-MM-yyyy");
            return dt.format(fechaFinal);
        } else {
            return "";
        }
    }