Browse Source

Codigo base

Drowsito 3 weeks ago
commit
42a8807aff
33 changed files with 4645 additions and 0 deletions
  1. 7 0
      .gitignore
  2. 14 0
      .vscode/launch.json
  3. 17 0
      .vscode/settings.json
  4. 286 0
      mvnw
  5. 161 0
      mvnw.cmd
  6. 185 0
      pom.xml
  7. 157 0
      src/main/java/META-INF/additional-spring-configuration-metadata.json
  8. 17 0
      src/main/java/es/uv/saic/SaicApplication.java
  9. 46 0
      src/main/java/es/uv/saic/config/ApplicationLocaleResolver.java
  10. 26 0
      src/main/java/es/uv/saic/config/AuthSuccessHandler.java
  11. 43 0
      src/main/java/es/uv/saic/config/Globals.java
  12. 124 0
      src/main/java/es/uv/saic/config/SecurityConfig.java
  13. 49 0
      src/main/java/es/uv/saic/config/WebConfig.java
  14. 160 0
      src/main/java/es/uv/saic/service/AuthProvider.java
  15. 564 0
      src/main/java/es/uv/saic/service/DataService.java
  16. 30 0
      src/main/java/es/uv/saic/service/DatasourceService.java
  17. 37 0
      src/main/java/es/uv/saic/service/EvidenciaIndicadorEnquestaService.java
  18. 144 0
      src/main/java/es/uv/saic/service/IndicadorEnquestaService.java
  19. 73 0
      src/main/java/es/uv/saic/service/IndicadorEnquestaTmpService.java
  20. 206 0
      src/main/java/es/uv/saic/service/IndicadorService.java
  21. 104 0
      src/main/java/es/uv/saic/service/UsuariService.java
  22. 177 0
      src/main/java/es/uv/saic/service/UsuarisRolService.java
  23. 114 0
      src/main/java/es/uv/saic/web/DataController.java
  24. 301 0
      src/main/java/es/uv/saic/web/IndicadorController.java
  25. 38 0
      src/main/resources/application-dev.properties
  26. 40 0
      src/main/resources/application-graal.properties
  27. 29 0
      src/main/resources/application-local.properties
  28. 38 0
      src/main/resources/application-prod.properties
  29. 51 0
      src/main/resources/application.properties
  30. 462 0
      src/main/resources/messages.properties
  31. 469 0
      src/main/resources/messages_ca.properties
  32. 463 0
      src/main/resources/messages_es.properties
  33. 13 0
      src/test/java/es/uv/docentia/SaicApplicationTests.java

+ 7 - 0
.gitignore

@@ -0,0 +1,7 @@
+/target/**
+.deployables
+.settings
+markdonwGen.py
+/target/
+.classpath
+.project

+ 14 - 0
.vscode/launch.json

@@ -0,0 +1,14 @@
+{
+    "configurations": [
+        {
+            "type": "java",
+            "name": "Spring Boot-SaicApplication<saic>",
+            "request": "launch",
+            "cwd": "${workspaceFolder}",
+            "mainClass": "es.uv.saic.SaicApplication",
+            "projectName": "saic",
+            "envFile": "${workspaceFolder}/.env",
+            "args": "--spring.profiles.active=local"
+        }
+    ]
+}

+ 17 - 0
.vscode/settings.json

@@ -0,0 +1,17 @@
+{
+    "sqltools.connections": [
+        {
+            "server": "coddono.uv.es",
+            "port": 9042,
+            "previewLimit": 50,
+            "driver": "Cassandra",
+            "name": "coddono",
+            "database": "acd425",
+            "username": "acd425",
+            "password": "acd425"
+        }
+    ],
+    "java.configuration.updateBuildConfiguration": "automatic",
+    "java.compile.nullAnalysis.mode": "automatic",
+    "java.dependency.syncWithFolderExplorer": false
+}

+ 286 - 0
mvnw

@@ -0,0 +1,286 @@
+#!/bin/sh
+# ----------------------------------------------------------------------------
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#    https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+# ----------------------------------------------------------------------------
+
+# ----------------------------------------------------------------------------
+# Maven2 Start Up Batch script
+#
+# Required ENV vars:
+# ------------------
+#   JAVA_HOME - location of a JDK home dir
+#
+# Optional ENV vars
+# -----------------
+#   M2_HOME - location of maven2's installed home dir
+#   MAVEN_OPTS - parameters passed to the Java VM when running Maven
+#     e.g. to debug Maven itself, use
+#       set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+#   MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+# ----------------------------------------------------------------------------
+
+if [ -z "$MAVEN_SKIP_RC" ] ; then
+
+  if [ -f /etc/mavenrc ] ; then
+    . /etc/mavenrc
+  fi
+
+  if [ -f "$HOME/.mavenrc" ] ; then
+    . "$HOME/.mavenrc"
+  fi
+
+fi
+
+# OS specific support.  $var _must_ be set to either true or false.
+cygwin=false;
+darwin=false;
+mingw=false
+case "`uname`" in
+  CYGWIN*) cygwin=true ;;
+  MINGW*) mingw=true;;
+  Darwin*) darwin=true
+    # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
+    # See https://developer.apple.com/library/mac/qa/qa1170/_index.html
+    if [ -z "$JAVA_HOME" ]; then
+      if [ -x "/usr/libexec/java_home" ]; then
+        export JAVA_HOME="`/usr/libexec/java_home`"
+      else
+        export JAVA_HOME="/Library/Java/Home"
+      fi
+    fi
+    ;;
+esac
+
+if [ -z "$JAVA_HOME" ] ; then
+  if [ -r /etc/gentoo-release ] ; then
+    JAVA_HOME=`java-config --jre-home`
+  fi
+fi
+
+if [ -z "$M2_HOME" ] ; then
+  ## resolve links - $0 may be a link to maven's home
+  PRG="$0"
+
+  # need this for relative symlinks
+  while [ -h "$PRG" ] ; do
+    ls=`ls -ld "$PRG"`
+    link=`expr "$ls" : '.*-> \(.*\)$'`
+    if expr "$link" : '/.*' > /dev/null; then
+      PRG="$link"
+    else
+      PRG="`dirname "$PRG"`/$link"
+    fi
+  done
+
+  saveddir=`pwd`
+
+  M2_HOME=`dirname "$PRG"`/..
+
+  # make it fully qualified
+  M2_HOME=`cd "$M2_HOME" && pwd`
+
+  cd "$saveddir"
+  # echo Using m2 at $M2_HOME
+fi
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched
+if $cygwin ; then
+  [ -n "$M2_HOME" ] &&
+    M2_HOME=`cygpath --unix "$M2_HOME"`
+  [ -n "$JAVA_HOME" ] &&
+    JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+  [ -n "$CLASSPATH" ] &&
+    CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
+fi
+
+# For Mingw, ensure paths are in UNIX format before anything is touched
+if $mingw ; then
+  [ -n "$M2_HOME" ] &&
+    M2_HOME="`(cd "$M2_HOME"; pwd)`"
+  [ -n "$JAVA_HOME" ] &&
+    JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
+  # TODO classpath?
+fi
+
+if [ -z "$JAVA_HOME" ]; then
+  javaExecutable="`which javac`"
+  if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
+    # readlink(1) is not available as standard on Solaris 10.
+    readLink=`which readlink`
+    if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
+      if $darwin ; then
+        javaHome="`dirname \"$javaExecutable\"`"
+        javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
+      else
+        javaExecutable="`readlink -f \"$javaExecutable\"`"
+      fi
+      javaHome="`dirname \"$javaExecutable\"`"
+      javaHome=`expr "$javaHome" : '\(.*\)/bin'`
+      JAVA_HOME="$javaHome"
+      export JAVA_HOME
+    fi
+  fi
+fi
+
+if [ -z "$JAVACMD" ] ; then
+  if [ -n "$JAVA_HOME"  ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+      # IBM's JDK on AIX uses strange locations for the executables
+      JAVACMD="$JAVA_HOME/jre/sh/java"
+    else
+      JAVACMD="$JAVA_HOME/bin/java"
+    fi
+  else
+    JAVACMD="`which java`"
+  fi
+fi
+
+if [ ! -x "$JAVACMD" ] ; then
+  echo "Error: JAVA_HOME is not defined correctly." >&2
+  echo "  We cannot execute $JAVACMD" >&2
+  exit 1
+fi
+
+if [ -z "$JAVA_HOME" ] ; then
+  echo "Warning: JAVA_HOME environment variable is not set."
+fi
+
+CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
+
+# traverses directory structure from process work directory to filesystem root
+# first directory with .mvn subdirectory is considered project base directory
+find_maven_basedir() {
+
+  if [ -z "$1" ]
+  then
+    echo "Path not specified to find_maven_basedir"
+    return 1
+  fi
+
+  basedir="$1"
+  wdir="$1"
+  while [ "$wdir" != '/' ] ; do
+    if [ -d "$wdir"/.mvn ] ; then
+      basedir=$wdir
+      break
+    fi
+    # workaround for JBEAP-8937 (on Solaris 10/Sparc)
+    if [ -d "${wdir}" ]; then
+      wdir=`cd "$wdir/.."; pwd`
+    fi
+    # end of workaround
+  done
+  echo "${basedir}"
+}
+
+# concatenates all lines of a file
+concat_lines() {
+  if [ -f "$1" ]; then
+    echo "$(tr -s '\n' ' ' < "$1")"
+  fi
+}
+
+BASE_DIR=`find_maven_basedir "$(pwd)"`
+if [ -z "$BASE_DIR" ]; then
+  exit 1;
+fi
+
+##########################################################################################
+# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+# This allows using the maven wrapper in projects that prohibit checking in binary data.
+##########################################################################################
+if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
+    if [ "$MVNW_VERBOSE" = true ]; then
+      echo "Found .mvn/wrapper/maven-wrapper.jar"
+    fi
+else
+    if [ "$MVNW_VERBOSE" = true ]; then
+      echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
+    fi
+    jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar"
+    while IFS="=" read key value; do
+      case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
+      esac
+    done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
+    if [ "$MVNW_VERBOSE" = true ]; then
+      echo "Downloading from: $jarUrl"
+    fi
+    wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
+
+    if command -v wget > /dev/null; then
+        if [ "$MVNW_VERBOSE" = true ]; then
+          echo "Found wget ... using wget"
+        fi
+        wget "$jarUrl" -O "$wrapperJarPath"
+    elif command -v curl > /dev/null; then
+        if [ "$MVNW_VERBOSE" = true ]; then
+          echo "Found curl ... using curl"
+        fi
+        curl -o "$wrapperJarPath" "$jarUrl"
+    else
+        if [ "$MVNW_VERBOSE" = true ]; then
+          echo "Falling back to using Java to download"
+        fi
+        javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
+        if [ -e "$javaClass" ]; then
+            if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+                if [ "$MVNW_VERBOSE" = true ]; then
+                  echo " - Compiling MavenWrapperDownloader.java ..."
+                fi
+                # Compiling the Java class
+                ("$JAVA_HOME/bin/javac" "$javaClass")
+            fi
+            if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+                # Running the downloader
+                if [ "$MVNW_VERBOSE" = true ]; then
+                  echo " - Running MavenWrapperDownloader.java ..."
+                fi
+                ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
+            fi
+        fi
+    fi
+fi
+##########################################################################################
+# End of extension
+##########################################################################################
+
+export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
+if [ "$MVNW_VERBOSE" = true ]; then
+  echo $MAVEN_PROJECTBASEDIR
+fi
+MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin; then
+  [ -n "$M2_HOME" ] &&
+    M2_HOME=`cygpath --path --windows "$M2_HOME"`
+  [ -n "$JAVA_HOME" ] &&
+    JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
+  [ -n "$CLASSPATH" ] &&
+    CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
+  [ -n "$MAVEN_PROJECTBASEDIR" ] &&
+    MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
+fi
+
+WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+exec "$JAVACMD" \
+  $MAVEN_OPTS \
+  -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
+  "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
+  ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"

+ 161 - 0
mvnw.cmd

@@ -0,0 +1,161 @@
+@REM ----------------------------------------------------------------------------
+@REM Licensed to the Apache Software Foundation (ASF) under one
+@REM or more contributor license agreements.  See the NOTICE file
+@REM distributed with this work for additional information
+@REM regarding copyright ownership.  The ASF licenses this file
+@REM to you under the Apache License, Version 2.0 (the
+@REM "License"); you may not use this file except in compliance
+@REM with the License.  You may obtain a copy of the License at
+@REM
+@REM    https://www.apache.org/licenses/LICENSE-2.0
+@REM
+@REM Unless required by applicable law or agreed to in writing,
+@REM software distributed under the License is distributed on an
+@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+@REM KIND, either express or implied.  See the License for the
+@REM specific language governing permissions and limitations
+@REM under the License.
+@REM ----------------------------------------------------------------------------
+
+@REM ----------------------------------------------------------------------------
+@REM Maven2 Start Up Batch script
+@REM
+@REM Required ENV vars:
+@REM JAVA_HOME - location of a JDK home dir
+@REM
+@REM Optional ENV vars
+@REM M2_HOME - location of maven2's installed home dir
+@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
+@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending
+@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
+@REM     e.g. to debug Maven itself, use
+@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+@REM ----------------------------------------------------------------------------
+
+@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
+@echo off
+@REM set title of command window
+title %0
+@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on'
+@if "%MAVEN_BATCH_ECHO%" == "on"  echo %MAVEN_BATCH_ECHO%
+
+@REM set %HOME% to equivalent of $HOME
+if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
+
+@REM Execute a user defined script before this one
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
+@REM check for pre script, once with legacy .bat ending and once with .cmd ending
+if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
+if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
+:skipRcPre
+
+@setlocal
+
+set ERROR_CODE=0
+
+@REM To isolate internal variables from possible post scripts, we use another setlocal
+@setlocal
+
+@REM ==== START VALIDATION ====
+if not "%JAVA_HOME%" == "" goto OkJHome
+
+echo.
+echo Error: JAVA_HOME not found in your environment. >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+:OkJHome
+if exist "%JAVA_HOME%\bin\java.exe" goto init
+
+echo.
+echo Error: JAVA_HOME is set to an invalid directory. >&2
+echo JAVA_HOME = "%JAVA_HOME%" >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+@REM ==== END VALIDATION ====
+
+:init
+
+@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
+@REM Fallback to current working directory if not found.
+
+set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
+IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
+
+set EXEC_DIR=%CD%
+set WDIR=%EXEC_DIR%
+:findBaseDir
+IF EXIST "%WDIR%"\.mvn goto baseDirFound
+cd ..
+IF "%WDIR%"=="%CD%" goto baseDirNotFound
+set WDIR=%CD%
+goto findBaseDir
+
+:baseDirFound
+set MAVEN_PROJECTBASEDIR=%WDIR%
+cd "%EXEC_DIR%"
+goto endDetectBaseDir
+
+:baseDirNotFound
+set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
+cd "%EXEC_DIR%"
+
+:endDetectBaseDir
+
+IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
+
+@setlocal EnableExtensions EnableDelayedExpansion
+for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
+@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
+
+:endReadAdditionalConfig
+
+SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
+set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
+set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar"
+FOR /F "tokens=1,2 delims==" %%A IN (%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties) DO (
+	IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B 
+)
+
+@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
+if exist %WRAPPER_JAR% (
+    echo Found %WRAPPER_JAR%
+) else (
+    echo Couldn't find %WRAPPER_JAR%, downloading it ...
+	echo Downloading from: %DOWNLOAD_URL%
+    powershell -Command "(New-Object Net.WebClient).DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"
+    echo Finished downloading %WRAPPER_JAR%
+)
+@REM End of extension
+
+%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
+if ERRORLEVEL 1 goto error
+goto end
+
+:error
+set ERROR_CODE=1
+
+:end
+@endlocal & set ERROR_CODE=%ERROR_CODE%
+
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
+@REM check for post script, once with legacy .bat ending and once with .cmd ending
+if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
+if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
+:skipRcPost
+
+@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
+if "%MAVEN_BATCH_PAUSE%" == "on" pause
+
+if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
+
+exit /B %ERROR_CODE%

+ 185 - 0
pom.xml

@@ -0,0 +1,185 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+	<parent>
+		<groupId>org.springframework.boot</groupId>
+		<artifactId>spring-boot-starter-parent</artifactId>
+		<version>4.0.2</version>
+		<relativePath/> <!-- lookup parent from repository -->
+	</parent>
+	<groupId>es.uv.saic</groupId>
+	<artifactId>saic</artifactId>
+	<version>3.0.0</version>
+	<name>saic</name>
+	<description>saic</description>
+
+	<properties>
+		<java.version>21</java.version>
+		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+		<spring-cloud.version>2025.1.0</spring-cloud.version>
+	</properties>
+
+	<dependencyManagement>
+		<dependencies>
+			<dependency>
+				<groupId>org.springframework.cloud</groupId>
+				<artifactId>spring-cloud-dependencies</artifactId>
+				<version>${spring-cloud.version}</version>
+				<type>pom</type>
+				<scope>import</scope>
+			</dependency>
+		</dependencies>
+	</dependencyManagement>
+	
+	<dependencies>
+		<dependency>
+			<groupId>org.springframework.boot</groupId>
+			<artifactId>spring-boot-starter-web</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.springframework.boot</groupId>
+			<artifactId>spring-boot-devtools</artifactId>
+			<scope>runtime</scope>
+		</dependency>
+		<dependency>
+			<groupId>org.springframework.boot</groupId>
+			<artifactId>spring-boot-starter-test</artifactId>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+			<groupId>org.springframework.boot</groupId>
+			<artifactId>spring-boot-starter-actuator</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>io.micrometer</groupId>
+			<artifactId>micrometer-registry-prometheus</artifactId>
+		</dependency>
+		<dependency>
+		    <groupId>org.springframework.boot</groupId>
+		    <artifactId>spring-boot-properties-migrator</artifactId>
+		    <scope>runtime</scope>
+		</dependency>
+		<dependency>
+		    <groupId>org.thymeleaf.extras</groupId>
+		    <artifactId>thymeleaf-extras-springsecurity6</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.springframework.boot</groupId>
+			<artifactId>spring-boot-starter-security</artifactId>
+		</dependency>
+		<dependency>
+		    <groupId>org.springframework.security</groupId>
+		    <artifactId>spring-security-ldap</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.postgresql</groupId>
+			<artifactId>postgresql</artifactId>
+			<scope>runtime</scope>
+		</dependency>
+		<dependency>
+			<groupId>org.springframework.boot</groupId>
+			<artifactId>spring-boot-starter-data-jpa</artifactId>
+		</dependency>
+		<dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-mail</artifactId>
+        </dependency>
+			
+		<dependency>
+			<groupId>org.springframework.boot</groupId>
+			<artifactId>spring-boot-configuration-processor</artifactId>
+			<optional>true</optional>
+		</dependency>
+		
+		<dependency>
+		  <groupId>junit</groupId>
+		  <artifactId>junit</artifactId>
+		  <scope>test</scope>
+		</dependency>
+		
+		<dependency>
+		    <groupId>com.mysql</groupId>
+		    <artifactId>mysql-connector-j</artifactId>
+		</dependency>
+		
+		<dependency>
+			<groupId>org.jsoup</groupId>
+			<artifactId>jsoup</artifactId>
+			<version>1.15.3</version>
+		</dependency>
+				
+		<dependency>
+		    <groupId>org.apache.commons</groupId>
+		    <artifactId>commons-csv</artifactId>
+		    <version>1.11.0</version>
+		</dependency>
+		
+		<dependency>
+		    <groupId>org.apache.commons</groupId>
+		    <artifactId>commons-io</artifactId>
+		    <version>1.3.2</version>
+		</dependency>
+		
+		<dependency>
+		    <groupId>commons-io</groupId>
+		    <artifactId>commons-io</artifactId>
+		    <version>2.16.1</version>
+		</dependency>
+
+		<dependency>
+			<groupId>org.springframework.cloud</groupId>
+			<artifactId>spring-cloud-starter-openfeign</artifactId>
+		</dependency>
+
+		<dependency>
+			<groupId>org.springframework.cloud</groupId>
+			<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
+		</dependency>
+		<!-- 
+		<dependency>
+		    <groupId>org.jacoco</groupId> 
+		    <artifactId>jacoco-maven-plugin</artifactId>
+		    <version>0.8.12</version>
+		</dependency>
+	  	 -->
+		<dependency>
+			<groupId>es.uv.saic.shared</groupId>
+		    <artifactId>UV_SAIC_SHARED</artifactId>
+		    <version>0.0.1-SNAPSHOT</version>
+		</dependency>
+	</dependencies>
+	
+
+    <build>
+        <finalName>uv_saic_data</finalName>
+        <resources>
+            <resource>
+                <directory>src/main/resources</directory>
+                <filtering>true</filtering>
+            </resource>
+        </resources>
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+                <configuration>
+                    <parameters>true</parameters>
+                </configuration>
+            </plugin>
+
+            <plugin>
+                <groupId>org.graalvm.buildtools</groupId>
+                <artifactId>native-maven-plugin</artifactId>
+                <configuration>
+                    <imageName>uv_saic_data</imageName>
+                    <buildArgs>
+                        <buildArg>--no-fallback</buildArg>
+                    </buildArgs>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+
+</project>

+ 157 - 0
src/main/java/META-INF/additional-spring-configuration-metadata.json

@@ -0,0 +1,157 @@
+{"properties": [
+  {
+    "name": "saic.data.filePath",
+    "type": "java.lang.String",
+    "description": "Path to store uploaded evidences"
+  },
+  {
+    "name": "saic.data.templates.filePath",
+    "type": "java.lang.String",
+    "description": "Path in where templates are stored"
+  },
+  {
+    "name": "saic.data.templates.logoPath",
+    "type": "java.lang.String",
+    "description": "Path in where logos are stored"
+  },
+  {
+    "name": "saic.mailer.reminder.enabled",
+    "type": "java.lang.String",
+    "description": "Enables or disables the mailer reminders (when assigned task is expired)"
+  },
+  {
+    "name": "saic.mailer.queue.enabled",
+    "type": "java.lang.String",
+    "description": "Enables or disables the mailer queue"
+  },
+  {
+    "name": "saic.mailer.calendar.enabled",
+    "type": "java.lang.String",
+    "description": "Enables or disables the email reminders related to the calendar (instance planifier)"
+  },
+  {
+    "name": "saic.mailer.maxMailsPerRound",
+    "type": "java.lang.String",
+    "description": "Defines de max number of emails that will be sended in a single round"
+  },
+  {
+    "name": "saic.parser.surveys.path",
+    "type": "java.lang.String",
+    "description": "A description for 'saic.parser.surveys.path'"
+  },
+  {
+    "name": "saic.uqserver.dbname.grau",
+    "type": "java.lang.String",
+    "description": "Defines the DB name for 'grau'"
+  },
+  {
+    "name": "saic.uqserver.dbname.master",
+    "type": "java.lang.String",
+    "description": "Defines the DB name for 'master'"
+  },
+  {
+    "name": "saic.uqserver.host",
+    "type": "java.lang.String",
+    "description": "A description for 'saic.uqserver.host'"
+  },
+  {
+    "name": "saic.uqserver.user",
+    "type": "java.lang.String",
+    "description": "Defines the final survey DB username"
+  },
+  {
+    "name": "saic.uqserver.passwd",
+    "type": "java.lang.String",
+    "description": "Defines the final survey DB password"
+  },
+  {
+    "name": "saic.uqserver2.host",
+    "type": "java.lang.String",
+    "description": "Defines the intermediate survey DB host"
+  },
+  {
+    "name": "saic.uqserver2.user",
+    "type": "java.lang.String",
+    "description": "Defines the intermediate survey DB username"
+  },
+  {
+    "name": "saic.uqserver2.passwd",
+    "type": "java.lang.String",
+    "description": "Defines the intermediate survey DB password"
+  },
+  {
+    "name": "saic.uqserver2.dbname.prof",
+    "type": "java.lang.String",
+    "description": "Defines de DB name for 'prof'"
+  },
+  {
+    "name": "saic.uqserver2.dbname.otros",
+    "type": "java.lang.String",
+    "description": "Defines de DB name for 'otros'"
+  },
+  {
+    "name": "saic.uqserver2.dbname.doct",
+    "type": "java.lang.String",
+    "description": "Defines the DB name for 'doctorado'"
+  },
+  {
+    "name": "saic.uqserver2.dbname.master",
+    "type": "java.lang.String",
+    "description": "Defines the DB name for 'master'"
+  },
+  {
+    "name": "saic.data.doctorado",
+    "type": "java.lang.String",
+    "description": "Defines the path to 'doctorado' data files"
+  },
+  {
+    "name": "saic.data.evdocente",
+    "type": "java.lang.String",
+    "description": "Defines the path to 'evdocente' data files"
+  },
+  {
+    "name": "saic.data.master",
+    "type": "java.lang.String",
+    "description": "Defines the path to 'master' data files"
+  },
+  {
+    "name": "saic.scheduler.expired.enabled",
+    "type": "java.lang.String",
+    "description": "Enables or disables the email reminders related to procedure tasks expiration"
+  },
+  {
+    "name": "saic.data.tmpPath",
+    "type": "java.lang.String",
+    "description": "Path to the temporal directory"
+  },
+  {
+    "name": "saic.data.templates.fileNotFound",
+    "type": "java.lang.String",
+    "description": "Path to the file that will be returned when the requested file does not exist"
+  },
+  {
+    "name": "saic.parser.surveys.enabled",
+    "type": "java.lang.String",
+    "description": "Enables or disables the surveys related scheduled tasks"
+  },
+  {
+    "name": "saic.actuator.validIp",
+    "type": "java.lang.String",
+    "description": "Actuator endpoints access will be restricted to the specified IP address"
+  },
+  {
+    "name": "saic.url.public",
+    "type": "java.lang.String",
+    "description": "A description for 'saic.url.public'"
+  },
+  {
+    "name": "saic.url.core.domain",
+    "type": "java.lang.String",
+    "description": "A description for 'saic.url.core.domain'"
+  },
+  {
+    "name": "saic.url.data.domain",
+    "type": "java.lang.String",
+    "description": "A description for 'saic.url.data.domain'"
+  }
+]}

+ 17 - 0
src/main/java/es/uv/saic/SaicApplication.java

@@ -0,0 +1,17 @@
+package es.uv.saic;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.cloud.openfeign.EnableFeignClients;
+import org.springframework.scheduling.annotation.EnableScheduling;
+
+@SpringBootApplication
+@EnableScheduling
+@EnableFeignClients
+public class SaicApplication {
+	
+	public static void main(String[] args) {
+		SpringApplication.run(SaicApplication.class, args);
+	}
+
+}

+ 46 - 0
src/main/java/es/uv/saic/config/ApplicationLocaleResolver.java

@@ -0,0 +1,46 @@
+package es.uv.saic.config;
+
+import java.util.Locale;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.authentication.AnonymousAuthenticationToken;
+import org.springframework.security.core.context.SecurityContext;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.web.servlet.i18n.SessionLocaleResolver;
+
+import es.uv.saic.shared.domain.Usuari;
+import es.uv.saic.service.UsuariService;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+
+@Configuration
+public class ApplicationLocaleResolver extends SessionLocaleResolver {
+    @Autowired
+    UsuariService us;
+
+    @Override
+    public Locale resolveLocale(HttpServletRequest request) {
+        SecurityContext securityContext = SecurityContextHolder.getContext();
+        Locale userLocale = Locale.forLanguageTag("ca"); 
+        if(!(securityContext.getAuthentication() instanceof AnonymousAuthenticationToken)) {
+        	Usuari usuari = ((Usuari)securityContext.getAuthentication().getPrincipal());
+        	String locale = usuari.getLocale();
+        	userLocale = locale == null ? userLocale : Locale.forLanguageTag(locale);
+        }
+        
+        return userLocale;
+    }
+
+    @Override
+    public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {
+        super.setLocale(request, response, locale);
+
+        SecurityContext securityContext = SecurityContextHolder.getContext();
+        if(!(securityContext.getAuthentication() instanceof AnonymousAuthenticationToken)) {
+        	Usuari usuari = ((Usuari)securityContext.getAuthentication().getPrincipal());
+	        usuari.setLocale(locale.toLanguageTag());
+	        us.save(usuari);
+        }        
+    }
+}

+ 26 - 0
src/main/java/es/uv/saic/config/AuthSuccessHandler.java

@@ -0,0 +1,26 @@
+package es.uv.saic.config;
+
+import java.io.IOException;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+
+import org.springframework.security.core.Authentication;
+import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
+import org.springframework.security.web.savedrequest.DefaultSavedRequest;
+
+public class AuthSuccessHandler implements AuthenticationSuccessHandler{
+	
+	@Override
+	public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication auth)
+	        throws IOException,  ServletException {
+	    request.getSession(false).setMaxInactiveInterval(3600); //3600
+		if(request.getSession().getAttribute("SPRING_SECURITY_SAVED_REQUEST") != null){
+			DefaultSavedRequest defaultSavedRequest = (DefaultSavedRequest) request.getSession().getAttribute("SPRING_SECURITY_SAVED_REQUEST");
+	    	response.sendRedirect(defaultSavedRequest.getRedirectUrl());
+		}
+		else{
+			response.sendRedirect(request.getContextPath()+"/procedures?_new=1");
+		}
+	}
+}

+ 43 - 0
src/main/java/es/uv/saic/config/Globals.java

@@ -0,0 +1,43 @@
+package es.uv.saic.config;
+
+import java.time.LocalDate;
+import java.time.ZoneId;
+import java.util.Date;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+
+@Configuration
+@ConfigurationProperties(prefix="globals")
+public class Globals {
+
+	private String filePath = "/tmp/uploads/";
+	//private String filePath = "/DATA/saic-data/files/";
+
+	public String getFilePath() {
+		return filePath;
+	}
+	
+	public String getFileName(String npi, int curs, int informe, String apartat, int item) {
+		return npi+"_"+Integer.toString(curs)+"_"+Integer.toString(informe)+"_"+apartat+Integer.toString(item);
+	}
+	
+	public int getCurrentYear() {
+
+		Date date = new Date();
+		LocalDate localDate = date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
+		int month = localDate.getMonthValue();
+		int year = localDate.getYear();
+		
+		if(month < 9) {
+			return year-1;
+		}
+		else {
+			return year;
+		}
+	}
+	
+
+	
+}

+ 124 - 0
src/main/java/es/uv/saic/config/SecurityConfig.java

@@ -0,0 +1,124 @@
+package es.uv.saic.config;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
+import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.http.SessionCreationPolicy;
+import org.springframework.security.core.session.SessionRegistry;
+import org.springframework.security.core.session.SessionRegistryImpl;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.security.ldap.DefaultSpringSecurityContextSource;
+import org.springframework.security.web.SecurityFilterChain;
+import org.springframework.security.web.access.expression.WebExpressionAuthorizationManager;
+import org.springframework.security.web.authentication.session.CompositeSessionAuthenticationStrategy;
+import org.springframework.security.web.authentication.session.ConcurrentSessionControlAuthenticationStrategy;
+import org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy;
+import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
+import org.springframework.security.web.authentication.session.SessionFixationProtectionStrategy;
+import org.springframework.security.web.session.HttpSessionEventPublisher;
+import es.uv.saic.service.AuthProvider;
+
+
+@Configuration
+@EnableWebSecurity
+@EnableMethodSecurity
+public class SecurityConfig {
+
+	@Autowired
+	AuthProvider authProvider;
+	
+	@Value("${saic.actuator.validIp}")
+	private String validIp;
+	
+	@Bean
+	public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
+		http.authorizeHttpRequests((auth) -> auth
+	        	.requestMatchers("/", "/css/**", "/js/**", "/img/**", "/logos/*", "/logos/**").permitAll()
+	        	.requestMatchers("/login**").permitAll()
+	        	.requestMatchers("/keepalive").permitAll()
+	        	.requestMatchers("/actuator/**").access(new WebExpressionAuthorizationManager("hasIpAddress('" + this.validIp + "')"))
+				.requestMatchers("/actuator/**").access(new WebExpressionAuthorizationManager("hasIpAddress('127.0.0.1')"))
+	        )
+	        .authorizeHttpRequests((auth)-> auth
+	            //.anyRequest().fullyAuthenticated()
+				.anyRequest().permitAll()
+	        )
+	        .csrf((csrf) -> csrf.disable());
+		
+		http.sessionManagement((session) -> session
+				.sessionAuthenticationErrorUrl("/login?error=expired")
+				.invalidSessionUrl("/login?error=expired")
+				.maximumSessions(1)
+				.expiredUrl("/login?error=expired")
+				.maxSessionsPreventsLogin(false)
+			    .sessionRegistry(sessionRegistry())
+		    )
+			.sessionManagement((session) -> session
+				.sessionAuthenticationStrategy(concurrentSession())
+			    .sessionFixation()
+			    .newSession()
+			    .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
+			);
+	
+		http.headers((headers) -> headers
+				.frameOptions((options) -> options.sameOrigin())
+		    );
+	
+	    return http.build();
+	}
+	    
+    @Bean
+    public AuthenticationManager authenticationManager(HttpSecurity http) throws Exception {
+        return http.getSharedObject(AuthenticationManagerBuilder.class)
+        		   .authenticationProvider(authProvider)
+        		   .build();
+    }
+    
+    @Bean
+    public SessionRegistry sessionRegistry() {
+        return new SessionRegistryImpl();
+    }
+    
+    @Bean
+    public DefaultSpringSecurityContextSource contextSource() {
+        return  new DefaultSpringSecurityContextSource(
+                Collections.singletonList("ldap://ldap.uv.es"), "dc=uv,dc=es");
+    }
+    
+    @Bean
+    public PasswordEncoder passwordEncoder() {
+        return new BCryptPasswordEncoder();
+    }
+    
+    @Bean
+    public HttpSessionEventPublisher httpSessionEventPublisher() {
+        return new HttpSessionEventPublisher();
+    }
+     
+    @Bean
+    public CompositeSessionAuthenticationStrategy concurrentSession() {
+
+         ConcurrentSessionControlAuthenticationStrategy concurrentAuthenticationStrategy = new ConcurrentSessionControlAuthenticationStrategy(sessionRegistry());
+         concurrentAuthenticationStrategy.setMaximumSessions(1);
+         concurrentAuthenticationStrategy.setExceptionIfMaximumExceeded(true);
+         List<SessionAuthenticationStrategy> delegateStrategies = new ArrayList<SessionAuthenticationStrategy>();
+         delegateStrategies.add(concurrentAuthenticationStrategy);
+         delegateStrategies.add(new SessionFixationProtectionStrategy());
+         delegateStrategies.add(new RegisterSessionAuthenticationStrategy(sessionRegistry()));
+
+         CompositeSessionAuthenticationStrategy authenticationStrategy =  new CompositeSessionAuthenticationStrategy(delegateStrategies);
+         return authenticationStrategy;
+     }
+	
+}

+ 49 - 0
src/main/java/es/uv/saic/config/WebConfig.java

@@ -0,0 +1,49 @@
+package es.uv.saic.config;
+
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 
+import org.springframework.boot.web.servlet.FilterRegistrationBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.Ordered;
+import org.springframework.web.filter.ForwardedHeaderFilter;
+import org.springframework.web.servlet.LocaleResolver;
+import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
+
+import jakarta.servlet.DispatcherType;
+
+@Configuration
+public class WebConfig implements WebMvcConfigurer{
+	
+	@Bean(name = "localeResolver")
+	public LocaleResolver localeResolver() {
+	    return new ApplicationLocaleResolver();
+	}
+	
+	@Bean
+	public LocaleChangeInterceptor localeChangeInterceptor() {
+	    LocaleChangeInterceptor lci = new LocaleChangeInterceptor();
+	    lci.setParamName("lang");
+	    return lci;
+	}
+	
+	@Override
+	public void addInterceptors(InterceptorRegistry registry) {
+	    registry.addInterceptor(localeChangeInterceptor());
+	}
+	
+	@Bean
+	@ConditionalOnMissingBean(ForwardedHeaderFilter.class)
+	@ConditionalOnProperty(value = "server.forward-headers-strategy", havingValue = "native")
+	public FilterRegistrationBean<ForwardedHeaderFilter> forwardedHeaderFilter() {
+	    ForwardedHeaderFilter filter = new ForwardedHeaderFilter();
+	    FilterRegistrationBean<ForwardedHeaderFilter> registration = new FilterRegistrationBean<>(filter);
+	    registration.setDispatcherTypes(DispatcherType.REQUEST, DispatcherType.ASYNC, DispatcherType.ERROR);
+	    registration.setOrder(Ordered.HIGHEST_PRECEDENCE);
+	    return registration;
+	}
+	
+	
+}

+ 160 - 0
src/main/java/es/uv/saic/service/AuthProvider.java

@@ -0,0 +1,160 @@
+package es.uv.saic.service;
+
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+import java.util.Hashtable;
+import java.util.List;
+
+import javax.naming.Context;
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.DirContext;
+import javax.naming.directory.InitialDirContext;
+import javax.naming.directory.SearchControls;
+import javax.naming.directory.SearchResult;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.authentication.AuthenticationProvider;
+import org.springframework.security.authentication.AuthenticationServiceException;
+import org.springframework.security.authentication.BadCredentialsException;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.stereotype.Component;
+
+import es.uv.saic.SaicApplication;
+import es.uv.saic.shared.domain.Usuari;
+
+@Component
+public class AuthProvider implements AuthenticationProvider {
+
+	@Autowired
+	private UsuariService us;
+	@Autowired
+    private UsuarisRolService urs;
+		
+	private static final Logger logger = LoggerFactory.getLogger(SaicApplication.class);
+	
+	@Override
+	public Authentication authenticate(Authentication auth) throws AuthenticationException {
+		String username = auth.getName().toLowerCase().trim();
+        String password = auth.getCredentials().toString(); 
+        
+        List<SimpleGrantedAuthority> authorities = new ArrayList<SimpleGrantedAuthority>();
+    	authorities.add(new SimpleGrantedAuthority("ROLE_USER"));
+        
+        Usuari u = this.us.findByUsername(username);
+        
+        if(u != null) {
+        	u.setGranted(this.urs.isGrantedUser(u));
+            u.setAdmin(this.urs.isAdminUser(u));
+            u.setDataTest(this.urs.isDataTestUser(u));
+        	boolean vigent = this.us.hasActiveRol(u);
+        	
+	        if(!u.getLdap() && vigent) {
+	            if (u.getUsuari().equals(username) && u.getClau().equals(password)) {
+	            	logger.info("Autenticación LOCAL correcta: "+username);
+	            	if(u.isAdmin()) {
+	            		authorities.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
+	            	}
+	            	if(u.isGranted()) {
+	            		authorities.add(new SimpleGrantedAuthority("ROLE_MANAGER"));
+	            	}
+	            	if(u.isDataTest()) {
+	            		authorities.add(new SimpleGrantedAuthority("ROLE_TESTER"));
+	            	}
+	            	UsernamePasswordAuthenticationToken authUser = new UsernamePasswordAuthenticationToken(u, password, authorities);
+	            	authUser.setDetails(u);
+	                return authUser ;
+	            } 
+	            else {
+	            	logger.info("Error de autenticación LOCAL ["+username+"]: el usuario o la contraseña no coinciden");
+	                throw new BadCredentialsException("Error de autenticación LOCAL ["+username+"]: el usuario o la contraseña no coinciden");
+	            }
+	        }
+	        else if(vigent) {
+	        	
+	        	Hashtable<String, String> env = new Hashtable<String, String>();
+
+                env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
+                env.put(Context.PROVIDER_URL, "ldap://ldap.uv.es/");
+                env.put(Context.SECURITY_AUTHENTICATION, "simple");
+                env.put(Context.SECURITY_PRINCIPAL, "uid=" + username.toLowerCase().trim() + ", dc=uv, dc=es ");
+                env.put(Context.SECURITY_CREDENTIALS, password);
+                
+                try {
+                    DirContext dc = new InitialDirContext(env);
+                    String base = "dc=uv,dc=es";
+                    String filter = "(&(uid=" + username + "))";
+                    int state = 0;
+
+                    SearchControls ctls = new SearchControls();
+                    ctls.setSearchScope(SearchControls.SUBTREE_SCOPE);
+                    ctls.setReturningAttributes(new String[] { "uid" });
+
+                    NamingEnumeration<SearchResult> resultEnum = dc.search(base, filter, ctls);
+
+                    while (resultEnum.hasMore() && state < 2) {
+                        SearchResult result = resultEnum.next();
+
+                        Attributes attrs = result.getAttributes();
+                        if (attrs.size() != 1) {
+                            logger.info("Error de autenticación LDAP ["+username+"]: el usuario o la contraseña no coinciden");
+                            throw new BadCredentialsException("Error de autenticación LDAP ["+username+"]: el usuario o la contraseña no coinciden");
+                        }
+                        NamingEnumeration<?> e = attrs.getAll();
+                        while (e.hasMore()) {
+                            Attribute attr = (Attribute) e.next();
+                            if (!((String) attr.get()).equals(username)) {
+                                logger.info("Error de autenticación LDAP ["+username+"]: el usuario no coincide con el devuelto por el servidor");
+                                throw new BadCredentialsException("Error de autenticación LDAP ["+username+"]: el usuario no coincide con el devuelto por el servidor");
+                            }
+                        }
+                        state++;
+                    }
+
+                    dc.close();
+
+                    if (state < 1 || state > 1) {
+                        logger.info("Error de autenticación LDAP ["+username+"]: -> el servidor LDAP ha devuelto un estado incorrecto.");
+                        throw new BadCredentialsException("Error de autenticación LDAP ["+username+"]: -> el servidor LDAP ha devuelto un estado incorrecto.");
+                    }
+                    logger.info("Autenticación LDAP correcta: " + u.getUsuari());
+                    u.setDataUltim(LocalDateTime.now());
+                    this.us.save(u);
+	            	if(u.isAdmin()) {
+	            		authorities.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
+	            	}
+	            	if(u.isGranted()) {
+	            		authorities.add(new SimpleGrantedAuthority("ROLE_MANAGER"));
+	            	}
+	            	if(u.isDataTest()) {
+	            		authorities.add(new SimpleGrantedAuthority("ROLE_TESTER"));
+	            	}
+	            	UsernamePasswordAuthenticationToken authUser = new UsernamePasswordAuthenticationToken(u, password, authorities);
+	            	authUser.setDetails(u);
+	                return authUser ;
+                } 
+                catch (NamingException ex) {
+                    logger.info("Error de autenticación ["+username+"]: " + ex.getMessage());
+                    throw new AuthenticationServiceException("Error de autenticación ["+username+"]: " + ex.getMessage());
+                }
+	        }
+        }
+        
+        throw new BadCredentialsException("Error general en el sistema de autenticación");
+ 
+
+	}
+
+	@Override
+	public boolean supports(Class<?> auth) {
+		return auth.equals(UsernamePasswordAuthenticationToken.class);
+	}
+
+}

+ 564 - 0
src/main/java/es/uv/saic/service/DataService.java

@@ -0,0 +1,564 @@
+package es.uv.saic.service;
+
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.sql.rowset.CachedRowSet;
+import javax.sql.rowset.RowSetFactory;
+import javax.sql.rowset.RowSetProvider;
+
+import org.apache.commons.csv.CSVFormat;
+import org.apache.commons.csv.CSVParser;
+import org.apache.commons.csv.CSVRecord;
+import org.apache.commons.lang.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.util.LinkedCaseInsensitiveMap;
+
+import es.uv.saic.shared.domain.Datasource;
+import es.uv.saic.shared.domain.IndicadorEnquestaTmp;
+import es.uv.saic.shared.domain.Usuari;
+import es.uv.saic.shared.dto.EmailDTO;
+import es.uv.saic.shared.dto.IndicadorEnquestaTmpDup;
+import es.uv.saic.shared.dto.OrganDTO;
+import es.uv.saic.shared.feign.EmailClient;
+import es.uv.saic.shared.feign.OrganClient;
+import jakarta.mail.MessagingException;
+
+
+@Service
+public class DataService {
+	
+	@Autowired
+	private DatasourceService dss;
+	@Autowired
+	private UsuariService us;
+	@Autowired
+	private IndicadorEnquestaService ies;
+	@Autowired
+	private IndicadorEnquestaTmpService iets;
+
+	@Autowired
+	private OrganClient oc;
+
+	@Autowired
+	private EmailClient ec;
+	
+	private HashMap<String, String> titsMap;
+	private HashMap<String, String> centresMap;
+	
+	public DataService() {
+		titsMap = new HashMap<String, String>();
+		titsMap.put("TRAD", "1099");
+		centresMap = new HashMap<String, String>();
+		centresMap.put("INDV", "121");
+		centresMap.put("POST", "121");
+		centresMap.put("EDOC", "62");
+	}
+
+	public String doImport(InputStreamReader in, String usuari, String enquesta, String ambit, String estudi, 
+						   String locale, String delim, String clau, List<String> ignoredColumns) throws IOException, MessagingException {
+		
+		CSVFormat format =  CSVFormat.DEFAULT.builder()
+											 .setHeader()
+											 .setIgnoreHeaderCase(true)
+											 .setSkipHeaderRecord(true)
+											 .setIgnoreEmptyLines(true)
+											 .setIgnoreSurroundingSpaces(true)
+											 .setDelimiter(delim)
+											 .setRecordSeparator('\n')
+											 .build();
+		CSVParser parser = format.parse(in);
+		List<CSVRecord> records = parser.getRecords();
+		List<String> header = parser.getHeaderNames();
+		List<String> headerInds = new ArrayList<>(header);
+		StringBuilder errors = new StringBuilder();
+				
+		/*  1) Comprobar columnas obligatorias  */
+		List<String> mandatoryColumns = new ArrayList<String>();
+		List<String> knownColumns = new ArrayList<String>();
+		if(clau.equals("ruct")) {
+			this.setColumnsRuct(ambit, knownColumns, mandatoryColumns);
+		}
+		else {
+			this.setColumnsCod(ambit, knownColumns, mandatoryColumns);
+		}
+		
+		if(!this.checkColumns(header, mandatoryColumns, errors, locale)) {
+			return errors.append("[ERROR] " + (locale.equals("es") ? "Se han encontrado errores en el fichero proporcionado. No se ha importado ningún registro. " : "S'han trobat errors al fitxer proporcionat. No s'ha importat cap registre.")).append("<br>").toString();
+		}
+		
+		/*  2) Eliminar las columnas obligatorias, conocidas e ignoradas. El resto se consideran indicadores a importar */
+		knownColumns.addAll(mandatoryColumns);
+		knownColumns.addAll(ignoredColumns);
+		for(String c : knownColumns) {
+			headerInds.removeIf(v->v.equalsIgnoreCase(c));
+		}
+		
+		/*  3) Comprobar valores y tipos  */	
+		if(!this.checkValues(records, ambit, clau, errors, locale)) {
+			return errors.append(("[ERROR] " + (locale.equals("es") ? "Se han encontrado errores en el fichero proporcionado. No se ha importado ningún registro. " : "S'han trobat errors al fitxer proporcionat. No s'ha importat cap registre."))).append("<br>").toString();
+		}
+		
+		/*  4) Importar datos  */
+		Integer numRecords = 0;
+		Integer numTuples = 0;
+		for (CSVRecord record : records) {
+			Map<String, String> recordMap = new LinkedCaseInsensitiveMap<>();
+			recordMap.putAll(record.toMap());
+			String c = ambit.equals("T") || ambit.equals("C") ? translateCentre(recordMap.get("centre")) : null;
+			String t = ambit.equals("T") ? translateTitulacio(recordMap.get("titulacio")) : null;  
+			String type = recordMap.get("tipus");
+			Integer curs = Integer.parseInt(recordMap.get("curs").replaceAll("\\D+",""));
+			
+			String cursEnquesta = recordMap.containsKey("curs_enquesta") ? recordMap.get("curs_enquesta") : null;
+	    	String titulacioOrigen = recordMap.containsKey("titulacio_origen") ? recordMap.get("titulacio_origen") : null;
+	    	String centreOrigen = recordMap.containsKey("centre_origen") ? recordMap.get("centre_origen") : null;
+	    	Integer nenq = recordMap.containsKey("nenq") ? Integer.parseInt(recordMap.get("nenq").replaceAll("\\D+","")) : null;
+	    	String ructStr = recordMap.containsKey("ruct") ? recordMap.get("ruct").replaceAll("\\D+","") : null;
+			String cursd = recordMap.containsKey("cursd") ? recordMap.get("cursd") : null;
+	    	Integer ruct = null;
+	    	
+	    	if(ructStr != null) {
+		    	if(!(ructStr.isBlank() || ructStr.isEmpty() || ructStr.equals("NULL") || ructStr.equals("null"))) {
+		    		ruct = Integer.parseInt(ructStr);
+				}
+	    	}
+			
+			Integer centre;
+	    	Integer titulacio;
+			if(clau.equals("ruct") && ambit.equals("T") && ruct != null) {
+				OrganDTO o = findOrgByRuct(ruct);
+				if(o == null) {
+					errors.append("[WARNING] " + (locale.equals("es") ? "No se ha encontrado el RUCT "+ructStr+", se omiten los registros de esta entrada." : "No s'ha trobat el RUCT "+ructStr+", s'ometen els registres d'aquesta entrada.")).append("<br>").toString();
+					continue;
+				}
+				titulacio = o.getLugar();
+				centre = o.getLugar2();
+			}
+			else{
+				centre = ambit.equals("T") || ambit.equals("C") ? Integer.parseInt(c.replaceAll("\\D+","")) : null;
+		    	titulacio = ambit.equals("T") ? Integer.parseInt(t) : null;
+				if(!existsOrg("T", titulacio)){
+					errors.append("[WARNING] " + (locale.equals("es") ? "No se ha encontrado la titulación con código "+titulacio+" pero se importa igualmente." : "No s'ha trobat la titulació amb codi "+titulacio+" però s'importa igualment")).append("<br>").toString();
+				}
+			}
+	    	
+	    	if(header.contains("cursd")) {
+	    		cursd = recordMap.get("cursd").replaceAll("\\D+","");
+	    	}
+			for(String r : headerInds) {
+				IndicadorEnquestaTmp ie = new IndicadorEnquestaTmp();
+	        	ie.setEnquesta(enquesta);
+	    		ie.setTitulacio(titulacio);
+	    		ie.setCentre(centre);
+	    		ie.setCurs(curs);
+	    		ie.setAmbit(ambit); 
+	    		ie.setEstudi(estudi);
+	        	ie.setIndicador(r.replace("_min", "").replace("_max", "").toLowerCase());
+	        	ie.setNum(null);
+	        	ie.setCursd(cursd);
+	        	ie.setTipus(type);
+	        	ie.setUsuari(usuari);
+	        	ie.setData(LocalDateTime.now());
+	        	ie.setCursEnquesta(cursEnquesta);
+	        	ie.setTitulacioOrigen(titulacioOrigen);
+	        	ie.setCentreOrigen(centreOrigen);
+	        	ie.setNenq(nenq);
+	        	ie.setRuct(ruct);
+	        	
+	        	try {
+	        		ie.setValor(record.get(r).replace(",", "."));
+	        	}
+	        	catch(Exception ex) {
+	        		ie.setValor(null);
+	        	}
+	        	
+	        	save(ie);
+	        	numRecords++;
+			}
+			numTuples++;
+		}
+
+		/*  5) Notificar administradores y usuario implicado  */
+		this.sendNotificacion(usuari, enquesta, ambit, estudi);
+		Usuari usuario = us.findByUsername(usuari);
+		if(!usuario.isAdmin()){
+			this.sendConfirmation(usuari, enquesta, ambit, estudi);
+		}
+
+		errors.append("[INFO] " + (locale.equals("es") ? ("Se han importado "+numRecords.toString()+" nuevos registros para un total de "+numTuples.toString()+" titulaciones") : ("[INFO] S'han importat "+numRecords.toString()+" nous registres per a un total de "+numTuples.toString()+" titulacions")));
+		return errors.toString();
+	}
+
+	public String doDbImport(Integer dbOrigen, String vista, Integer srcCurs, Integer dstCurs, String usuari, String enquesta, String ambit, 
+							 String estudi, String locale, String clau, List<String> ignoredColumns) throws SQLException, ClassNotFoundException {
+
+		StringBuilder errors = new StringBuilder();
+		Datasource source = this.dss.findById(dbOrigen);
+		if(source == null){
+			return errors.append("[ERROR] " + (locale.equals("es") ? "No se ha proporcionado un origen de datos válido." : "No s'ha proporcionat un origen de dades vàlid.")).append("<br>").toString();
+		}
+		String query = "SELECT * FROM "+vista+ " WHERE curs = "+srcCurs;
+		Class.forName(source.getDriver());
+		Integer numRecords = 0;
+		Integer numTuples = 0;
+		try (Connection connection = DriverManager.getConnection(source.getConn())) {
+
+			/*  1) Ejecutar consulta  */
+			Statement statement = connection.createStatement();
+			ResultSet resultset = statement.executeQuery(query);
+			
+			ResultSetMetaData resultMetadata = resultset.getMetaData();
+			int columns = resultMetadata.getColumnCount();
+
+			/*  2) Obtener resultados para procesar online y cerrar conexión  */
+			RowSetFactory factory = RowSetProvider.newFactory();
+			CachedRowSet result = factory.createCachedRowSet();
+			result.populate(resultset);
+			connection.close();
+
+			/*  3) Comprobar columnas obligatorias  */
+			List<String> mandatoryColumns = new ArrayList<String>();
+			List<String> knownColumns = new ArrayList<String>();
+			if(clau.equals("ruct")) {
+				this.setColumnsRuct(ambit, knownColumns, mandatoryColumns);
+			}
+			else {
+				this.setColumnsCod(ambit, knownColumns, mandatoryColumns);
+			}
+
+			List<String> allColNames = new ArrayList<String>();
+			for(int i = 1; i <= columns; i++){
+				allColNames.add(resultMetadata.getColumnLabel(i).toLowerCase());
+			}
+			List<String> colnames = new ArrayList<String>(allColNames);
+			
+			if(!this.checkColumns(colnames, mandatoryColumns, errors, locale)) {
+				return errors.append("[ERROR] " + (locale.equals("es") ? "Se han encontrado errores al procesar la vista seleccionada. No se ha importado ningún registro. " : "S'han trobat errors al processar la vista seleccionada. No s'ha importat cap registre.")).append("<br>").toString();
+			}
+
+			knownColumns.addAll(mandatoryColumns);
+			knownColumns.addAll(ignoredColumns);
+			for(String c : knownColumns) {
+				colnames.removeIf(v->v.equalsIgnoreCase(c));
+			}
+
+			if(ambit.equals("T") && !clau.equals("ruct")){
+				return errors.append("[ERROR] " + (locale.equals("es") ? "Los datos de titulación deben de importarse por código RUCT" : "Les dades de titulació s'han d'importar per códi RUCT.")).append("<br>").toString();
+			}
+			
+			/*  4) Importar datos  */
+			while(result.next()) {
+				Integer ruct = null;
+				Integer titulacio = null;
+				Integer centre = null;
+				String c = ambit.equals("C") ? translateCentre(result.getString("centre")) : null;
+				boolean isValid = true; 
+
+				if(ambit.equals("T")) {
+					String ructStr = result.getString("ruct");
+					if(ructStr != null) {
+						if(!(ructStr.isBlank() || ructStr.isEmpty() || ructStr.equals("NULL") || ructStr.equals("null"))) {
+							ruct = Integer.parseInt(ructStr);
+						}
+						else{
+							isValid = false;
+						}
+					}
+					else{
+						isValid = false;
+					}
+					OrganDTO o = findOrgByRuct(ruct);
+					if(o == null) {
+						errors.append("[WARNING] " + (locale.equals("es") ? "No se ha encontrado el RUCT "+ructStr+", se omiten los registros de esta entrada." : "No s'ha trobat el RUCT "+ructStr+", s'ometen els registres d'aquesta entrada.")).append("<br>").toString();
+						continue;
+					}
+					titulacio = o.getLugar();
+					centre = o.getLugar2();
+				}
+				else if(ambit.equals("C")) {
+					centre = ambit.equals("T") || ambit.equals("C") ? Integer.parseInt(c.replaceAll("\\D+","")) : null;
+					if(centre == null){
+						isValid = false;
+					}
+				}
+								
+				if(isValid) {				
+					Integer curs = dstCurs == null ? result.getInt("curs") : dstCurs;
+					String tipus = allColNames.contains("tipus") ? result.getString("tipus") : "avg";
+					String cursEnquesta = allColNames.contains("curs_enquesta") ? result.getString("curs_enquesta") : null;
+					String titulacioOrigen = allColNames.contains("titulacio_origen") ? result.getString("titulacio_origen") : null;
+					String centreOrigen = allColNames.contains("centre_origen") ? result.getString("centre_origen") : null;
+					Integer nenq = allColNames.contains("nenq") ? result.getInt("nenq") : null;
+					String cursd = allColNames.contains("cursd") ? result.getString("cursd") : null;
+					for(String colName : colnames) {
+						IndicadorEnquestaTmp ie = new IndicadorEnquestaTmp();
+	
+						ie.setEnquesta(enquesta);
+						ie.setTitulacio(titulacio);
+						ie.setCentre(centre);
+						ie.setCurs(curs);
+						ie.setAmbit(ambit); 
+						ie.setEstudi(estudi);
+						ie.setIndicador(colName.replace("_min", "").replace("_max", "").toLowerCase());
+						ie.setNum(null);
+						ie.setCursd(cursd);
+						ie.setTipus(tipus);
+						ie.setUsuari(usuari);
+						ie.setData(LocalDateTime.now());
+						ie.setCursEnquesta(cursEnquesta);
+						ie.setTitulacioOrigen(titulacioOrigen);
+						ie.setCentreOrigen(centreOrigen);
+						ie.setNenq(nenq);
+						ie.setRuct(ruct);
+	
+						try {
+							ie.setValor(result.getString(colName).replace(",", "."));
+						}
+						catch(Exception ex) {
+							ie.setValor(null);
+						}
+						
+						save(ie);
+						numRecords++;
+					} 
+					numTuples++;  
+				}
+			}
+		}
+
+		/*  5) Notificar administradores y usuario implicado  */
+		this.sendNotificacion(usuari, enquesta, ambit, estudi);
+		Usuari usuario = us.findByUsername(usuari);
+		if(!usuario.isAdmin()){
+			this.sendConfirmation(usuari, enquesta, ambit, estudi);
+		}
+		errors.append("[INFO] " + (locale.equals("es") ? ("Se han importado "+numRecords.toString()+" nuevos registros para un total de "+numTuples.toString()+" titulaciones") : ("[INFO] S'han importat "+numRecords.toString()+" nous registres per a un total de "+numTuples.toString()+" titulacions")));
+		return errors.toString();
+	}
+
+	public List<String> listTableColumns(Integer dbOrigen, String vista, String locale) throws SQLException, ClassNotFoundException {
+		
+		Datasource source = this.dss.findById(dbOrigen);
+		String query = "SELECT * FROM "+vista+" LIMIT 1;";
+		Class.forName(source.getDriver());
+		List<String> colNames = new ArrayList<String>();
+		try (Connection connection = DriverManager.getConnection(source.getConn())) {
+
+			Statement statement = connection.createStatement();
+			ResultSet resultset = statement.executeQuery(query);
+			
+			ResultSetMetaData resultMetadata = resultset.getMetaData();
+			int columns = resultMetadata.getColumnCount();
+
+			for(int i = 1; i <= columns; i++){
+				colNames.add(resultMetadata.getColumnLabel(i));
+			}
+
+		}
+
+		return colNames;
+	}
+	
+	public List<IndicadorEnquestaTmpDup> checkDuplicates(String enquesta) {
+		return checkDuplicatesAux(enquesta);
+	}
+
+	public Integer consolidateByEnquesta(String enquesta) {
+		if(checkDuplicates(enquesta).size() > 0) {
+			return -1;
+		}
+		else {
+			Integer i = ies.consolidateByEnquesta(enquesta);
+			deleteByEnquesta(enquesta);
+			return i;
+		}
+	}
+
+    public Integer countByEnquesta(String enquesta) {
+		return countByEnquestaAux(enquesta);
+	}
+
+	public List<IndicadorEnquestaTmpDup> checkIntegrity(String enquesta){
+		return iets.checkIntegrity(enquesta);
+	}
+
+	public Integer deleteFromCurrent(String enquesta){
+		return iets.deleteFromCurrent(enquesta);
+	}
+
+	public Integer deleteFromPending(String enquesta){
+		return iets.deleteFromPending(enquesta);
+	}
+
+	private void setColumnsCod(String ambit, List<String> knownColumns, List<String> mandatoryColumns) {
+		knownColumns.addAll(Arrays.asList("ruct", "curs_enquesta", "centre_origen", "titulacio_origen", "nenq", "cursd"));	
+		if(ambit.equals("U")) {
+			mandatoryColumns.addAll(Arrays.asList("curs", "tipus"));
+		}
+		else if(ambit.equals("C")) {
+			mandatoryColumns.addAll(Arrays.asList("curs", "centre", "tipus"));
+		}
+		else {
+			mandatoryColumns.addAll(Arrays.asList("curs", "titulacio", "centre", "tipus"));
+		}
+	}
+	
+	private void setColumnsRuct(String ambit, List<String> knownColumns, List<String> mandatoryColumns) {
+		knownColumns.addAll(Arrays.asList("curs_enquesta", "centre_origen", "titulacio_origen", "nenq", "cursd"));	
+		if(ambit.equals("U")) {
+			mandatoryColumns.addAll(Arrays.asList("curs", "tipus"));
+		}
+		else if(ambit.equals("C")) {
+			mandatoryColumns.addAll(Arrays.asList("curs", "tipus", "ruct"));
+			knownColumns.add("centre");
+		}
+		else {
+			mandatoryColumns.addAll(Arrays.asList("curs", "tipus", "ruct"));
+			knownColumns.addAll(Arrays.asList("centre", "titulacio"));
+		}
+	}
+	
+	private boolean checkColumns(List<String> header, List<String> mandatoryColumns, StringBuilder errors, String locale) {
+		boolean isvalid = true;
+		for(String c : mandatoryColumns) {			
+			if(!header.stream().anyMatch(c::equalsIgnoreCase)) {
+				isvalid = false;
+		    	errors.append((locale.equals("es") ? "[ERROR] No se ha encontrado la columna " : "[ERROR] No s'ha trobat la columna ") +c).append("<br>");
+		    }
+		}
+		return isvalid;
+	}
+	
+	private boolean checkValues(List<CSVRecord> records, String ambit, String clau, StringBuilder errors, String locale) {
+		boolean isvalid = true;
+		Integer numRecords = 0;
+		for (CSVRecord record : records) {
+			Map<String, String> recordMap = new LinkedCaseInsensitiveMap<>();
+			recordMap.putAll(record.toMap());
+			
+			String type = recordMap.get("tipus");
+			numRecords++;
+			
+			if((ambit.equals("T") || ambit.equals("C")) && !clau.equals("ruct")) {
+				String c = recordMap.get("centre");
+				c = translateCentre(c);
+				if(!StringUtils.isNumeric(c.replace("C", ""))) {
+					isvalid = false;
+					errors.append("[ERROR] " + (locale.equals("es") ? "En la linea " : "A la línia ") + numRecords.toString() + ": " + (locale.equals("es") ? c+" no es un valor válido para la columna centre." : c+" no es un valor vàlid per la columna centre.")).append("<br>");
+				} 
+			}
+			
+			if(ambit.equals("T")) {
+				String t = recordMap.get("titulacio");
+				if(!clau.equals("ruct")) {
+					t = translateTitulacio(t);
+					if(!StringUtils.isNumeric(t)) {
+						isvalid = false;
+						errors.append("[ERROR] " + (locale.equals("es") ? "En la linea " : "A la línia ") + numRecords.toString() + ": " + (locale.equals("es") ? t+" no es un valor válido para la columna titulacio." : t+" no es un valor vàlid per la columna titulacio.")).append("<br>");
+					}
+				}
+				else {
+					String ructStr = recordMap.get("ruct");
+					if(!ructStr.isBlank() && ructStr.isEmpty() && ructStr.equals("NULL") && ructStr.equals("null") && !StringUtils.isNumeric(t)) {
+						isvalid = false;
+						errors.append("[ERROR] " + (locale.equals("es") ? "En la linea " : "A la línia ") + numRecords.toString() + ": " + (locale.equals("es") ? ructStr+" no es un valor válido para la columna ruct." : ructStr+" no es un valor vàlid per la columna ruct.")).append("<br>");
+					}
+				}
+			}
+			
+			if(!type.equals("avg") && !type.equals("min") && !type.equals("max")) {
+				isvalid = false;
+				errors.append("[ERROR] " + (locale.equals("es") ? "En la linea " : "A la línia ") + numRecords.toString() + ": " + (locale.equals("es") ? type+" no es un valor válido para la columna tipus." : type+" no es un valor vàlid per la columna tipus.")).append("<br>");
+			}
+		}
+		return isvalid;
+	}
+
+	private void sendNotificacion(String usuari, String enquesta, String ambit, String estudi) {
+		sendMail("saic@uv.es", "[SYS] Datos pendientes de consolidar", "Estimado/a administrador: \n"
+				+ "\n"
+				+ "El usuario "+usuari+" ha importado nuevos datos referentes a: \n"
+				+ "Origen: "+enquesta+"\n"
+				+ "Ámbito: "+ambit+"\n"
+				+ "Tipo titulación: "+estudi+"\n"
+				+ "\n"
+				+ "Acceda a https://saic.uv.es y realice las acciones pertinentes para consolidar los datos. ");
+
+	}
+
+	private void sendConfirmation(String to, String enquesta, String ambit, String estudi) {
+		sendMail(to, "[SYS] Nuevos datos importados", "Estimado/a usuario: \n"
+				+ "\n"
+				+ "Se han importado correctamente nuevos datos referentes a: \n"
+				+ "Origen: "+enquesta+"\n"
+				+ "Ámbito: "+ambit+"\n"
+				+ "Tipo titulación: "+estudi+"\n"
+				+ "\n"
+				+ "Los datos se quedarán en estado pendiente de consolidar hasta que un administrador inicie el proceso de consolidación.");
+
+	}
+	
+	/* Exepciones en el código de titulación que hay que traducir a código SAIC */
+	private String translateTitulacio(String t) {
+		if(!StringUtils.isNumeric(t)) {
+			if(this.titsMap.containsKey(t)) {
+				return this.titsMap.get(t); 
+			}
+		}
+		return t;
+	}
+	
+	/* Exepciones en el código de centro que hay que traducir a código SAIC */
+	private String translateCentre(String c) {
+		if(!StringUtils.isNumeric(c)) {
+			if(this.centresMap.containsKey(c)) {
+				return this.centresMap.get(c); 
+			}
+		}
+		return c;
+	}
+
+	private OrganDTO findOrgByRuct(Integer ruct) {
+		return oc.getOrgByRuct(ruct);
+	}
+
+	private boolean existsOrg(String tlugar, Integer titulacio) {
+		return oc.existsOrg(tlugar, titulacio);
+	}
+
+	private void deleteByEnquesta(String enquesta) {
+		iets.deleteByEnquesta(enquesta);
+	}
+
+	private void save(IndicadorEnquestaTmp ie) {
+		iets.save(ie);
+	}
+
+	private List<IndicadorEnquestaTmpDup> checkDuplicatesAux(String enquesta) {
+		return iets.checkDuplicates(enquesta);
+	}
+
+
+	private Integer countByEnquestaAux(String enquesta) {
+		return iets.countByEnquesta(enquesta);
+	}
+
+	private void sendMail(String string, String string2, String string3) {
+		EmailDTO email = new EmailDTO(string, string2, string3);
+		ec.sendEmail(email);
+	}
+}

+ 30 - 0
src/main/java/es/uv/saic/service/DatasourceService.java

@@ -0,0 +1,30 @@
+package es.uv.saic.service;
+
+import java.util.List;
+import java.util.Optional;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import es.uv.saic.shared.domain.Datasource;
+import es.uv.saic.shared.domain.DatasourceRepository;
+
+@Service
+public class DatasourceService {
+
+    @Autowired
+	private DatasourceRepository dr;
+
+    public List<Datasource> findAll(){
+        return dr.findAll();
+    }
+
+    public Datasource findById(Integer id){
+        Optional<Datasource> d = this.dr.findById(id);
+        if(d.isPresent()){
+            return d.get();
+        }
+        return null;
+    }
+
+}

+ 37 - 0
src/main/java/es/uv/saic/service/EvidenciaIndicadorEnquestaService.java

@@ -0,0 +1,37 @@
+package es.uv.saic.service;
+
+import java.util.List;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import es.uv.saic.shared.domain.EvidenciaIndicadorEnquesta;
+import es.uv.saic.shared.domain.EvidenciaIndicadorEnquestaRepository;
+
+@Service
+public class EvidenciaIndicadorEnquestaService {
+
+	@Autowired
+	private EvidenciaIndicadorEnquestaRepository eies;
+	
+	public List<EvidenciaIndicadorEnquesta> getIndicadors(String proces, String evidencia, String enquesta){
+		return this.eies.getIndicadors(proces, evidencia, enquesta);
+	}
+	
+	public List<EvidenciaIndicadorEnquesta> getByProcesEvidencia(String proces, String evidencia){
+		return this.eies.getByProcesEvidencia(proces, evidencia);
+	}
+	
+	public List<EvidenciaIndicadorEnquesta> getAll(){
+		return this.eies.findAll();
+	}
+	
+	public void removeByProcesEvidencia(String proces, String evidencia) {
+		this.eies.removeByProcesEvidencia(proces, evidencia);
+	}
+	
+	public void save(EvidenciaIndicadorEnquesta a) {
+		this.eies.save(a);
+		this.eies.flush();
+	}
+}

+ 144 - 0
src/main/java/es/uv/saic/service/IndicadorEnquestaService.java

@@ -0,0 +1,144 @@
+package es.uv.saic.service;
+
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import es.uv.saic.shared.domain.EvidenciaIndicadorEnquesta;
+import es.uv.saic.shared.domain.EvidenciaIndicadorEnquestaRepository;
+import es.uv.saic.shared.domain.IndicadorEnquesta;
+import es.uv.saic.shared.domain.IndicadorEnquestaRepository;
+import es.uv.saic.shared.domain.Organ;
+import es.uv.saic.shared.dto.IndicadorDTO;
+import es.uv.saic.shared.dto.IndicadorEnquestaValorDTO;
+import es.uv.saic.shared.dto.OrganDTO;
+
+@Service
+public class IndicadorEnquestaService {
+
+	@Autowired
+	private IndicadorEnquestaRepository r;
+	@Autowired
+	private EvidenciaIndicadorEnquestaRepository rie;
+	
+	
+	public List<IndicadorEnquesta> findAll() {
+		return this.r.findAll();
+	}
+	
+	public List<String> findTypes(){
+		return this.r.findTypes();
+	}
+	
+	public List<IndicadorEnquesta> findByEnquestaCursAmbitEstudi(String enquesta, Integer curs, String ambit, String estudi) {
+		return this.r.findByEnquestaCursAmbitEstudi(enquesta, curs, ambit, estudi);
+	}
+	
+	public List<IndicadorEnquesta> findByEnquestaCursAmbitEstudiCentre(String enquesta, Integer curs, String ambit, String estudi, Integer centre) {
+		return this.r.findByEnquestaCursAmbitEstudiCentre(enquesta, curs, ambit, estudi, centre);
+	}
+	
+	public List<IndicadorEnquesta> findByEnquestaCursAmbitEstudiCentreTitulacio(String enquesta, Integer curs, String ambit, String estudi, Integer centre, Integer titulacio) {
+		return this.r.findByEnquestaCursAmbitEstudiCentreTitulacio(enquesta, curs, ambit, estudi, centre, titulacio);
+	}
+	
+	public List<IndicadorEnquestaValorDTO> getAllInds(Integer titulacio, Integer centre, Integer curs, Integer ruct) {
+		//String estudi = titulacio < 2000 ? "G" : "M";
+		//estudi = titulacio == 2 ? "M" : estudi;
+		
+		String estudi = "G";
+		/*  TODO replace with tambit  */
+		if(titulacio == 1) {
+			estudi = "G";
+		}
+		else if(titulacio == 2) {
+			estudi = "M";
+		}
+		else if(titulacio >= 1000 & titulacio < 2000) {
+			estudi = "G";
+		}
+		else if(titulacio >= 2000 & titulacio < 3000) {
+			estudi = "M";
+		}
+		else if(titulacio >= 3000) {
+			estudi = "D";
+		}	
+
+		if(titulacio == 0) {
+			return  Stream.concat(this.r.getAllIndValsByEstudi(ruct, centre, curs, "G").stream(), 
+					              this.r.getAllIndValsByEstudi(ruct, centre, curs, "M").stream())
+                    .collect(Collectors.toList());
+		}
+		return this.r.getAllIndValsByEstudi(ruct, centre, curs, estudi);
+	}
+	
+	public List<IndicadorEnquesta> getAllInds(Organ o, Integer curs){
+		return this.r.getAllIndsByEstudi(o.getId().getLugar(), o.getOrgan().getId().getLugar(), curs, o.getTambit());
+	}
+
+	public List<IndicadorEnquesta> getAllInds(OrganDTO o, Integer curs){
+		return this.r.getAllIndsByEstudi(o.getLugar(), o.getLugar2(), curs, o.getTambit());
+	}
+
+	public List<IndicadorEnquesta> getAllIndsByRuct(Integer ruct, Integer curs){
+		return this.r.getAllIndsByRuct(ruct, curs);
+	}
+			
+	public List<IndicadorDTO> getGraphData(List<Integer> titulacions, Integer centre, String estudi, Integer cursMax) {
+		return this.r.getGraphData(titulacions, centre, estudi, cursMax);
+	}
+	
+	public List<IndicadorDTO> getGraphData(Integer centre, String estudi, Integer cursMax) {
+		return this.r.getGraphData(centre, estudi, cursMax);
+	}
+		
+	public boolean contains(String evidencia, String proces, String enquesta) {
+		List<EvidenciaIndicadorEnquesta> inds = this.rie.getIndicadors(proces, evidencia, enquesta);
+		if(inds.size() > 0) {
+			return true;
+		}
+		return false;
+	}
+	
+	public int deleteCentroNoAdscrito() {
+		return this.r.deleteCentroNoAdscrito();
+	}
+	
+	public int deleteByEnquesta(String enquesta) {
+		return this.r.deleteByEnquesta(enquesta);
+	}
+	
+	public int deleteByEnquestaCurs(String enquesta, Integer curs) {
+		return this.r.deleteByEnquestaCurs(enquesta, curs);
+	}
+	
+	public int fixBiennials(String enquesta, Integer curs) {
+		return this.r.fixBiennials(enquesta, curs);
+	}
+	
+	public Integer consolidateByEnquesta(String enquesta) {
+		return this.r.consolidateByEnquesta(enquesta);
+	}
+	
+	public void deleteAll() {
+		this.r.deleteAll();
+		this.r.flush();
+	}
+	
+	public void save(IndicadorEnquesta x) {
+		this.r.save(x);
+		this.r.flush();
+	}
+	
+	public void saveAll(List<IndicadorEnquesta> l) {
+		this.r.saveAll(l);
+		this.r.flush();
+	}
+	
+	public Integer getRuctTitulacio(Integer titulacio) {
+		return this.r.getRuctTitulacio(titulacio);
+	}
+}

+ 73 - 0
src/main/java/es/uv/saic/service/IndicadorEnquestaTmpService.java

@@ -0,0 +1,73 @@
+package es.uv.saic.service;
+
+import java.util.List;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import es.uv.saic.shared.domain.IndicadorEnquestaTmp;
+import es.uv.saic.shared.domain.IndicadorEnquestaTmpRepository;
+import es.uv.saic.shared.dto.IndicadorEnquestaTmpDup;
+
+@Service
+public class IndicadorEnquestaTmpService {
+
+	@Autowired
+	private IndicadorEnquestaTmpRepository r;
+	
+	public List<IndicadorEnquestaTmp> findAll() {
+		return this.r.findAll();
+	}
+	
+	public List<IndicadorEnquestaTmp> findByEnquestaCursAmbitEstudi(String enquesta, Integer curs, String ambit, String estudi) {
+		return this.r.findByEnquestaCursAmbitEstudi(enquesta, curs, ambit, estudi);
+	}
+	
+	public int deleteByEnquesta(String enquesta) {
+		return this.r.deleteByEnquesta(enquesta);
+	}
+	
+	public int deleteByEnquestaCursAmbitEstudi(String enquesta, Integer curs, String ambit, String estudi) {
+		return this.r.deleteByEnquestaCursAmbitEstudi(enquesta, curs, ambit, estudi);
+	}
+	
+	public List<IndicadorEnquestaTmpDup> checkDuplicates(String enquesta){
+		return this.r.checkDupicates(enquesta);
+	}
+
+	public Integer deleteFromCurrent(String enquesta){
+		return this.r.deleteFromCurrent(enquesta);
+	}
+
+	public Integer deleteFromPending(String enquesta){
+		return this.r.deleteFromPending(enquesta);
+	}
+
+	public Integer deleteDuplicates(String enquesta){
+		return this.r.deleteDuplicates(enquesta);
+	}
+	
+	public Integer countByEnquesta(String enquesta) {
+		return this.r.countByEnquesta(enquesta);
+	}
+	
+	public List<IndicadorEnquestaTmpDup> checkIntegrity(String enquesta){
+		return this.r.checkIntegrity(enquesta);
+	}
+	
+	public void deleteAll() {
+		this.r.deleteAll();
+		this.r.flush();
+	}
+	
+	public void save(IndicadorEnquestaTmp x) {
+		this.r.save(x);
+		this.r.flush();
+	}
+	
+	public void saveAll(List<IndicadorEnquestaTmp> l) {
+		this.r.saveAll(l);
+		this.r.flush();
+	}
+	
+}

+ 206 - 0
src/main/java/es/uv/saic/service/IndicadorService.java

@@ -0,0 +1,206 @@
+package es.uv.saic.service;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.StringReader;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URLConnection;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
+import es.uv.saic.shared.domain.Indicador;
+import es.uv.saic.shared.feign.OrganClient;
+
+@Service
+public class IndicadorService {
+	
+	@Autowired
+	private OrganClient oc;
+	
+	public IndicadorService() {}
+
+	public List<Indicador> getFromTitulacion(Integer tit, Integer curs) throws ParserConfigurationException, MalformedURLException, IOException, SAXException {
+	    DocumentBuilderFactory f = DocumentBuilderFactory.newInstance();
+	    f.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
+	    f.setNamespaceAware(false);
+	    f.setValidating(false);
+	    DocumentBuilder build = f.newDocumentBuilder();
+	    
+	    List<Integer> tits = this.oc.getEquivalents(tit, "T");
+	    tits.add(tit);
+	    //Collections.sort(tits).reverse(); 
+	    	    
+	    String periodo = Integer.toString(curs-1)+"-"+Integer.toString(curs);
+	    String periodoAnt = Integer.toString(curs-2)+"-"+Integer.toString(curs-1);
+	    List<Indicador> indicadores = new ArrayList<Indicador>();
+	    boolean hasCurs = false;
+	    int xtits = 0, xurls = 0;
+	    List<String> urls = Arrays.asList(new String[]{"http://bancuv.uv.es/wwwuv/stuff/web/siga/SGIC", "http://bancuv.uv.es/wwwuv/stuff/web/siga/previo/SGIC"});
+	    //List<String> urls = Arrays.asList(new String[]{"http://bancuv.uv.es/wwwuv/stuff/web/siga/SGIC"});
+	    
+	    while(!hasCurs && xtits < tits.size() && xurls < urls.size()) {
+	    	String url = urls.get(xurls)+tits.get(xtits)+".xml";
+	    	String resText = "";
+	    	try {
+	    		URLConnection urlConnection = new URI(url).toURL().openConnection();
+	    		urlConnection.addRequestProperty("Accept", "application/xml");
+	    		InputStream res = urlConnection.getInputStream();
+	    		resText = new String(res.readAllBytes(), StandardCharsets.UTF_8);
+	    	}
+	    	catch(Exception e){
+	    		resText = "";
+	    	}
+
+		    if(!resText.contains("curso=\""+periodo+"\"")) {  // not contains data from selected year
+		    	xtits+=1;
+		    	if(xtits > tits.size()-1 && xurls < urls.size()-1) {
+		    		xtits=0;
+		    		xurls+=1;
+		    	}
+		    }
+		    else {
+		    	hasCurs = true;
+		    	indicadores = this.extract(build, periodo, periodoAnt, resText);
+		    }
+	    }	
+	    return indicadores;
+	}
+	
+	private List<Indicador> extract(DocumentBuilder build, String periodo, String periodoAnt, String resText) throws SAXException, IOException {
+    	Document doc = build.parse(new InputSource(new StringReader(resText)));
+	    doc.getDocumentElement().normalize();
+	    NodeList nodedim = doc.getElementsByTagName("Dimension");
+	    List<Indicador> indicadores = new ArrayList<Indicador>();
+    
+	    for(int i = 0; i < nodedim.getLength(); i++) { // each dim
+	    	Element a = (Element)nodedim.item(i);
+	    	NodeList nodeind = a.getElementsByTagName("indicador");
+	    	for(int j = 0; j < nodeind.getLength(); j++) { // each ind
+	    		Element b = (Element)nodeind.item(j);
+	    		NodeList nodesub = b.getElementsByTagName("subindicador");
+	    		if(nodesub.getLength() > 0) { // has subind
+	    			for(int k = 0; k < nodesub.getLength(); k++) { // each subind
+	    				Element c = (Element)nodesub.item(k);
+	    				NodeList nodevals = c.getElementsByTagName("cursoAcademico");
+	    				Indicador ind = new Indicador(a.getAttribute("codigo"), c.getAttribute("codigo"), "");
+				    	for(int z = 0; z < nodevals.getLength(); z++) { // each val
+				    		Element d = (Element)nodevals.item(z);
+				    		if(d.getAttribute("curso").equals(periodo)) {
+				    			ind.setValor(d.getAttribute("valor"));
+				    		}
+				    		else if((c.getAttribute("codigo").equals("I_DE7_04") || (c.getAttribute("codigo").equals("I_DE7_06"))) && d.getAttribute("curso").equals(periodoAnt)) {
+				    			ind.setValor(d.getAttribute("valor"));
+				    		}
+				    	}
+				    	indicadores.add(ind);
+	    			}
+	    		}
+	    		else { // not has subind
+			    	NodeList nodevals = b.getElementsByTagName("cursoAcademico");
+			    	Indicador ind = new Indicador(a.getAttribute("codigo"), b.getAttribute("codigo"), "");
+			    	for(int k = 0; k < nodevals.getLength(); k++) { // each val
+			    		Element c = (Element)nodevals.item(k);
+			    		if(c.getAttribute("curso").equals(periodo)) {
+			    			ind.setValor(c.getAttribute("valor"));
+			    		}
+			    		else if((b.getAttribute("codigo").equals("I_DE7_04") || (b.getAttribute("codigo").equals("I_DE7_06"))) && c.getAttribute("curso").equals(periodoAnt)) {
+			    			ind.setValor(c.getAttribute("valor"));
+			    		}
+			    	}
+			    	indicadores.add(ind);
+	    		}	
+	    	}
+	    }
+		return indicadores;
+	}
+	
+	public List<Indicador> getGraphData(Integer tit) {
+		String url = "http://bancuv.uv.es/wwwuv/stuff/web/siga/SGIC"+tit.toString()+".xml";
+	    String fileText;
+		List<Indicador> inds = new ArrayList<Indicador>();
+		DocumentBuilderFactory f = DocumentBuilderFactory.newInstance();
+		f.setNamespaceAware(false);
+		f.setValidating(false);
+		DocumentBuilder build;
+
+		try {
+			build = f.newDocumentBuilder();
+			
+			List<Integer> tits = this.oc.getEquivalents(tit, "T");
+			if(tits.size() > 0) {
+				tit = tits.get(tits.size()-1);
+			}
+	    
+    		URLConnection urlConnection = new URI(url).toURL().openConnection();
+    		urlConnection.addRequestProperty("Accept", "application/xml");
+    		InputStream res = urlConnection.getInputStream();
+    		fileText = new String(res.readAllBytes(), StandardCharsets.UTF_8);
+    		inds = this.extractAll(build, fileText);
+			
+		} catch (ParserConfigurationException e) {
+			System.err.println("Error creating DocumentBuilder");
+			e.printStackTrace();
+		}
+    	catch(Exception e){
+    		fileText = "";
+    		return null;
+    	}
+	    
+	    return inds;
+	}
+	
+	private List<Indicador> extractAll(DocumentBuilder build, String fileText) throws SAXException, IOException {
+    	Document doc = build.parse(new InputSource(new StringReader(fileText)));
+	    doc.getDocumentElement().normalize();
+	    NodeList nodedim = doc.getElementsByTagName("Dimension");
+	    List<Indicador> indicadores = new ArrayList<Indicador>();
+    
+	    for(int i = 0; i < nodedim.getLength(); i++) { // each dim
+	    	Element a = (Element)nodedim.item(i);
+	    	NodeList nodeind = a.getElementsByTagName("indicador");
+	    	for(int j = 0; j < nodeind.getLength(); j++) { // each ind
+	    		Element b = (Element)nodeind.item(j);
+	    		NodeList nodesub = b.getElementsByTagName("subindicador");
+	    		if(nodesub.getLength() > 0) { // has subind
+	    			for(int k = 0; k < nodesub.getLength(); k++) { // each subind
+	    				Element c = (Element)nodesub.item(k);
+	    				NodeList nodevals = c.getElementsByTagName("cursoAcademico");
+	    				Indicador ind = new Indicador(a.getAttribute("codigo"), c.getAttribute("codigo"), "");
+				    	for(int z = 0; z < nodevals.getLength(); z++) { // each val
+				    		Element d = (Element)nodevals.item(z);
+				    		ind.addCursoValor(d.getAttribute("curso"), d.getAttribute("valor").replace("%",""));
+				    		//ind.setValor();
+				    	}
+				    	indicadores.add(ind);
+	    			}
+	    		}
+	    		else { // not has subind
+			    	NodeList nodevals = b.getElementsByTagName("cursoAcademico");
+			    	Indicador ind = new Indicador(a.getAttribute("codigo"), b.getAttribute("codigo"), "");
+			    	for(int k = 0; k < nodevals.getLength(); k++) { // each val
+			    		Element c = (Element)nodevals.item(k);
+			    		ind.addCursoValor(c.getAttribute("curso"), c.getAttribute("valor").replace("%",""));
+			    	}
+			    	indicadores.add(ind);
+	    		}	
+	    	}
+	    }
+		return indicadores;
+	}
+	
+}

+ 104 - 0
src/main/java/es/uv/saic/service/UsuariService.java

@@ -0,0 +1,104 @@
+package es.uv.saic.service;
+
+import java.math.BigInteger;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import es.uv.saic.shared.domain.Usuari;
+import es.uv.saic.shared.domain.UsuariRepository;
+import es.uv.saic.shared.domain.UsuarisRol;
+import es.uv.saic.shared.dto.TascaAsignadaDTO;
+
+@Service
+public class UsuariService {
+
+	@Autowired
+	private UsuariRepository usuariRepository;
+	@Autowired
+	private UsuarisRolService usuarisRolService;
+	
+	public List<Usuari> findAll() {
+		return this.usuariRepository.findAll();
+	}
+	
+	public Usuari findByUsername(String usuari) {
+		return this.usuariRepository.findByUsername(usuari);
+	}
+	
+	public boolean hasActiveRol(Usuari usuari) {
+		return this.usuarisRolService.hasActiveRol(usuari);
+	}
+	
+	public List<TascaAsignadaDTO> getActiveInstanciaTasques(Usuari usuari){
+		return this.usuariRepository.findActiveInstanciaTasques(usuari.getUsuari());
+	}
+	
+	public List<BigInteger> getActiveInstancies(Usuari usuari){
+		
+		if(usuari.isGranted() || usuarisRolService.findActiveRols(usuari).stream().anyMatch(r -> "adeit".equals(r.getRol().getNomRol()))) {
+			return this.usuariRepository.findActiveInstanciesGranted(usuari.getUsuari());
+		}
+		else{
+			return this.usuariRepository.findActiveInstancies(usuari.getUsuari());
+		}
+	}
+	
+	public List<BigInteger> getSupervisableProcessos(List<UsuarisRol> usuarisRols){
+		List<BigInteger> a = new ArrayList<BigInteger>();
+		for(UsuarisRol urol : usuarisRols) {
+			if(urol.getRol().getNomRol().equals("c_resp")) {  // Responsable de centro
+				a.addAll(this.usuariRepository.findSupervisableProcessosResp(urol.getOrgan().getId().getLugar()));
+			}
+			else if(urol.getRol().getNomRol().equals("c_supe")) { // Supervisor de centro
+				a.addAll(this.usuariRepository.findSupervisableProcessosResp(urol.getOrgan().getId().getLugar()));
+			}
+			else if(urol.getRol().getNomRol().equals("e_supe")) { // Supervisor externo
+				a.addAll(this.usuariRepository.findSupervisableProcessosResp(urol.getOrgan().getId().getLugar()));
+			}
+			else if(this.usuarisRolService.hasAssociatedProcs(urol.getRol().getNomRol())) { // Roles especiales (asociados a proceso)
+				a.addAll(this.usuariRepository.findSupervisableProcessosSupervisor(this.usuarisRolService.findAssociatedProcs(urol.getRol().getNomRol())));
+			}
+			else { // Cualquier otro rol
+				a.addAll(this.usuariRepository.findSupervisableProcessos(urol.getOrgan().getId().getLugar(), urol.getOrgan().getId().getTlugar()));
+			}
+		}
+		
+		return a.stream()
+				 .distinct()
+				 .collect(Collectors.toList());
+	}
+	
+	public List<Integer> getSupervisableCentres(Usuari usuari){	
+		List<Integer> a = new ArrayList<Integer>();
+		for(UsuarisRol urol : usuarisRolService.findActiveRols(usuari)) {
+			if(this.usuarisRolService.hasAssociatedProcs(urol.getRol().getNomRol())) { // special roles
+				a.addAll(this.usuariRepository.findSupervisableCentresSupervisor(this.usuarisRolService.findAssociatedProcs(urol.getRol().getNomRol())));
+			}
+			else {
+				a.addAll(this.usuariRepository.findSupervisableCentres(usuari.getUsuari()));
+			}
+		}
+		
+		return a.stream()
+				 .distinct()
+				 .collect(Collectors.toList());
+	}
+	
+	public List<Usuari> getInstanceAsignedUsers(BigInteger idInstancia){
+		return this.usuariRepository.getInstanceAsignedUsers(idInstancia);
+	}
+	
+	public List<Usuari> findByRolCentre(Integer idRol, List<Integer> centres){
+		return this.usuariRepository.findByRolCentre(idRol, centres);
+	}
+	
+	public void save(Usuari u) {
+		this.usuariRepository.save(u);
+		this.usuariRepository.flush();
+	}
+	
+}

+ 177 - 0
src/main/java/es/uv/saic/service/UsuarisRolService.java

@@ -0,0 +1,177 @@
+package es.uv.saic.service;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import es.uv.saic.shared.domain.Usuari;
+import es.uv.saic.shared.domain.UsuarisRol;
+import es.uv.saic.shared.domain.UsuarisRolRepository;
+
+@Service
+public class UsuarisRolService {
+
+	@Autowired
+	private UsuarisRolRepository usuarisRolRepository;
+	
+	
+	public List<UsuarisRol> findAll() {
+		return this.usuarisRolRepository.findAll();
+	}
+	
+	public boolean exists(String usuari, String tlugar, Integer lugar) {
+		if(this.usuarisRolRepository.findActiveByUsuariTlugarLugar(usuari, tlugar, lugar) != null) {
+			return true;
+		}
+		return false;
+	}
+	
+	public UsuarisRol find(Integer idRol, String usuari, Integer lugar, String tlugar) {
+		return this.usuarisRolRepository.find(idRol, usuari, lugar, tlugar);
+	}
+	
+	public UsuarisRol findActive(Integer idRol, String usuari, Integer lugar, String tlugar) {
+		return this.usuarisRolRepository.findActive(idRol, usuari, lugar, tlugar);
+	}
+	
+	public UsuarisRol findLast(Integer idRol, String usuari, Integer lugar, String tlugar) {
+		return this.usuarisRolRepository.findLast(idRol, usuari, lugar, tlugar);
+	}
+	
+	public Integer findLastNum(Integer idRol, Integer lugar, String tlugar) {
+		return this.usuarisRolRepository.findLastNum(idRol, lugar, tlugar);
+	}
+	
+	public boolean hasActiveRol(Usuari usuari) {
+		List<UsuarisRol> rols = this.usuarisRolRepository.findByUsernameActive(usuari);
+		if(rols != null) {
+			if(rols.size() > 0) {
+				return true;
+			}
+		}
+		return false;
+	}
+	
+	public List<UsuarisRol> findActiveRols(Usuari usuari){
+		return this.usuarisRolRepository.findByUsernameActive(usuari);
+	}
+	
+	public boolean isGrantedUser(Usuari usuari) {
+		if(this.usuarisRolRepository.findByUsernameActiveRol(usuari, "u_uq") != null || 
+		   this.usuarisRolRepository.findByUsernameActiveRol(usuari, "u_admi") != null) {
+			return true;
+		}
+		else {
+			return false;
+		}
+	}
+	
+	public boolean isGrantedSupervisor(Usuari usuari) {
+		if(this.usuarisRolRepository.findByUsernameActiveRol(usuari, "adeit") != null ||
+		   this.usuarisRolRepository.findByUsernameActiveRol(usuari, "relint") != null ||
+		   this.usuarisRolRepository.findByUsernameActiveRol(usuari, "u_supe") != null) {
+			return true;
+		}
+		else {
+			return false;
+		}
+	}
+	
+	public boolean isAdminUser(Usuari usuari) {
+		if(this.usuarisRolRepository.findByUsernameActiveRol(usuari, "u_admi") != null) {
+			return true;
+		}
+		else {
+			return false;
+		}
+	}
+	
+	public boolean isDataTestUser(Usuari usuari) {
+		if(this.usuarisRolRepository.findByUsernameActiveRol(usuari, "u_data") != null) {
+			return true;
+		}
+		else {
+			return false;
+		}
+	}
+	
+	public boolean hasActiveRol(Usuari usuari, String rol) {
+		if(this.usuarisRolRepository.findByUsernameActiveRol(usuari, rol) != null) {
+			return true;
+		}
+		else {
+			return false;
+		}
+	}
+	
+	public boolean hasActiveRoles(Usuari usuari, ArrayList<String> rols) {
+		if(this.usuarisRolRepository.findByUsernameActiveRoles(usuari, rols) != null) {
+			return true;
+		}
+		else {
+			return false;
+		}
+	}
+	
+	public boolean hasActiveAmbit(Usuari usuari, String ambit) {
+		if(this.usuarisRolRepository.findByUsernameActiveAmbit(usuari.getUsuari(), ambit) != null) {
+			return true;
+		}
+		else {
+			return false;
+		}
+	}
+	
+	public boolean hasAssociatedProcs(String rol) {
+		List<String> procs = this.usuarisRolRepository.findAssociatedProcs(rol);
+		if(procs != null) {
+			if(procs.size() > 0) {
+				return true;
+			}
+			else {
+				return false;
+			}
+		}
+		else {
+			return false;
+		}
+	}
+	
+	public List<String> findAssociatedProcs(String rol) {
+		return this.usuarisRolRepository.findAssociatedProcs(rol);
+	}
+	
+	public List<UsuarisRol> findManagerByCentre(Integer centre){
+		return this.usuarisRolRepository.findManagerByCentre(centre);
+	}
+	
+	public List<UsuarisRol> findManagerByTitulacio(Integer titulacio){
+		return this.usuarisRolRepository.findManagerByTitulacio(titulacio);
+	}
+	
+	public List<UsuarisRol> findManagerByCentres(List<Integer> centre){
+		return this.usuarisRolRepository.findManagerByCentres(centre);
+	}
+	
+	public List<UsuarisRol> findManagerByTitulacions(List<Integer> titulacio){
+		return this.usuarisRolRepository.findManagerByTitulacions(titulacio);
+	}
+	
+	public ArrayList<String> getSpecialRoles(){
+		return new ArrayList<String>(Arrays.asList("adeit", "relint"));
+	}
+	
+	public void save(UsuarisRol u) {
+		this.usuarisRolRepository.save(u);
+		this.usuarisRolRepository.flush();
+	}
+	
+	public void delete(UsuarisRol ur) {
+		this.usuarisRolRepository.delete(ur);
+	}
+	
+	
+}

+ 114 - 0
src/main/java/es/uv/saic/web/DataController.java

@@ -0,0 +1,114 @@
+package es.uv.saic.web;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.annotation.Secured;
+import org.springframework.security.core.Authentication;
+import org.springframework.ui.Model;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import es.uv.saic.shared.domain.Datasource;
+import es.uv.saic.shared.dto.ImportDTO;
+import es.uv.saic.shared.dto.IndicadorEnquestaTmpDup;
+import es.uv.saic.shared.dto.TablaDTO;
+import es.uv.saic.service.DataService;
+import es.uv.saic.service.DatasourceService;
+import jakarta.mail.MessagingException;
+
+@RestController
+@RequestMapping("/data")
+public class DataController {
+
+	@Autowired
+	private DataService ips;
+
+	@Autowired
+	private DatasourceService das;
+	
+	// GET que carga la interfaz relacionada con toda la importación de datos
+	@GetMapping("/import")
+	public List<Datasource> renderImport() {
+		return this.das.findAll();
+	}
+	
+	// POST encargado de importar los datos que han sido adjuntados
+	@PostMapping("/import")
+	public String uploadFile(@RequestBody ImportDTO importDTO) throws IOException, MessagingException, ClassNotFoundException, SQLException { 
+		String retval = "";
+		if(importDTO.getTipus().equals("file")){
+			byte[] fileBytes = importDTO.getFileContent();
+			ByteArrayInputStream bais = new ByteArrayInputStream(fileBytes);
+			InputStreamReader in = new InputStreamReader(bais);
+			retval = ips.doImport(in, importDTO.getPrincipal(), importDTO.getEnquesta(), importDTO.getAmbit(), importDTO.getEstudi(), importDTO.getLocale(),
+								importDTO.getDelim(), importDTO.getClau(), importDTO.getIgnoredColumns());
+		}
+		else if(importDTO.getTipus().equals("db")){
+			retval = ips.doDbImport(importDTO.getDbOrigen(), importDTO.getVista(), importDTO.getCurs(), importDTO.getDstCurs(),
+								importDTO.getPrincipal(), importDTO.getEnquesta(), importDTO.getAmbit(), importDTO.getEstudi(),
+								importDTO.getLocale(), importDTO.getClau(), importDTO.getIgnoredColumns());
+		}
+		
+		return retval;
+	}
+	
+	//GET para cargar el apartado de coonsolidate
+	@GetMapping("/consolidate")
+	@Secured({"ROLE_ADMIN", "ROLE_TESTER"})
+	public String consolidate(Model model, Authentication auth) throws IOException {    
+		return "dataConsolidate";
+	}
+	
+	// POST para comprobar si existen problemas con los datos, si estan duplicados o su integridad
+	@PostMapping("/check/{type}")
+	public List<IndicadorEnquestaTmpDup> check(@RequestBody String enquesta, @PathVariable Integer type) throws IOException {   
+		if(type == 1) {
+			return this.ips.checkDuplicates(enquesta);
+		}
+		else {
+			return this.ips.checkIntegrity(enquesta);
+		}
+		
+	}
+	
+	// POST que consolida los datos pasadas por la encuesta
+	@PostMapping("/consolidate")
+	public Integer consolidate(@RequestBody String enquesta) throws IOException {    
+		return this.ips.consolidateByEnquesta(enquesta);
+	}
+	
+	// POST que comprueba los datos a partir de la encuesta
+	@PostMapping("/count")
+	public Integer countByEnquesta(@RequestBody String enquesta) throws IOException {    
+		return this.ips.countByEnquesta(enquesta);
+	}
+
+	// POST para mostrar los datos en columnas
+	@PostMapping("/view/columns")
+	public List<String> listTableColumns(@RequestBody TablaDTO tablaDto) {    
+		try {
+			return this.ips.listTableColumns(tablaDto.getDbOrigen(), tablaDto.getVista(), tablaDto.getLocale());
+		} catch (ClassNotFoundException | SQLException e) {
+			return new ArrayList<String>();
+		}
+	}
+
+	@GetMapping("/sources")
+	public List<Datasource> getDatasources() throws IOException {  
+		List<Datasource> sources = this.das.findAll();
+		for(Datasource d : sources){
+			d.setConn(null);
+		}
+        return sources;
+	}
+}

+ 301 - 0
src/main/java/es/uv/saic/web/IndicadorController.java

@@ -0,0 +1,301 @@
+package es.uv.saic.web;
+
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import javax.xml.parsers.ParserConfigurationException;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+import org.xml.sax.SAXException;
+
+import es.uv.saic.shared.domain.EvidenciaIndicadorEnquesta;
+import es.uv.saic.shared.domain.Indicador;
+import es.uv.saic.shared.domain.IndicadorEnquesta;
+import es.uv.saic.shared.dto.IndicadorDTOimp;
+import es.uv.saic.shared.dto.IndicadorEnquestaDTO;
+import es.uv.saic.shared.dto.IndicadorEnquestaTmpDup;
+import es.uv.saic.shared.dto.IndicadorEnquestaValorDTOImp;
+import es.uv.saic.service.EvidenciaIndicadorEnquestaService;
+import es.uv.saic.service.IndicadorEnquestaService;
+import es.uv.saic.service.IndicadorEnquestaTmpService;
+import es.uv.saic.service.IndicadorService;
+
+@RestController
+@RequestMapping("/indicadorEnquesta")
+public class IndicadorController {
+    @Autowired
+    private IndicadorService is;
+
+    @Autowired
+    private IndicadorEnquestaService ies;
+
+    @Autowired
+    private IndicadorEnquestaTmpService iets;
+
+    @Autowired
+	private EvidenciaIndicadorEnquestaService eies;
+
+    @GetMapping("/{ruct}/{curs}")
+    public List<IndicadorEnquesta> getAllIndsByRuct(@PathVariable Integer ruct, @PathVariable Integer curs) {
+        return ies.getAllIndsByRuct(ruct, curs);
+    }
+
+    /*
+    * Endpoint para la funcion deleteByEnquestaCursAmbitEstudi de iets
+    */
+    @DeleteMapping("/{enquesta}/{curs}/{ambit}/{estudi}")
+    public Integer deleteByEnquestaCursAmbitEstudi(@PathVariable("enquesta") String enquesta, @PathVariable("curs") Integer curs, 
+        @PathVariable("ambit") String ambit, @PathVariable("estudi") String estudi) {
+
+        Integer i = 0;
+        try {
+            i = iets.deleteByEnquestaCursAmbitEstudi(enquesta, curs, ambit, estudi);
+
+        } catch(Exception e) {
+            e.printStackTrace();
+        }
+    return i;
+    }
+
+    @DeleteMapping("/{enquesta}")
+    public Integer deleteByEnquesta(@PathVariable("enquesta") String enquesta) {
+        return iets.deleteByEnquesta(enquesta);
+    }
+
+    /*
+    * Endpoint para la funcion deleteFromCurrent de iets, dev
+    */
+    @DeleteMapping("/current/{enquesta}")
+    public Integer deleteFromCurrent(@PathVariable("enquesta") String enquesta) {
+        return iets.deleteFromCurrent(enquesta);
+    }
+
+    /*
+    * Endpoint para la funcion deleteFromPending de iets
+    */
+    @DeleteMapping("/pending/{enquesta}")
+    public Integer deleteFromPending(@PathVariable("enquesta") String enquesta) {
+        return iets.deleteFromPending(enquesta);
+    }
+
+    /*
+    * Endpoint para la funcion deleteDuplicates de iets
+    */
+    @DeleteMapping("/duplicates/{enquesta}")
+    public Integer deleteDuplicates(@PathVariable("enquesta") String enquesta) {
+       return iets.deleteDuplicates(enquesta);
+    }
+
+    /*
+     * Endpoint para la funcion findByEnquestaCursAmbitEstudi de ies
+     */
+    @PostMapping("/{enquesta}/{curs}/{ambit}/{estudi}")
+    public List<IndicadorEnquesta> findByEnquestaCursAmbitEstudi(@PathVariable("enquesta") String enquesta, @PathVariable("curs") Integer curs, 
+        @PathVariable("ambit") String ambit, @PathVariable("estudi") String estudi) {
+        return ies.findByEnquestaCursAmbitEstudi(enquesta, curs, ambit, estudi);
+    }
+
+    /*
+     * Endpoing para la función findByEnquestaCursAmbitEstudiCentre
+     */
+    @PostMapping("/{enquesta}/{curs}/{ambit}/{estudi}/{centre}")
+    public List<IndicadorEnquesta> findByEnquestaCursAmbitEstudiCentre(@PathVariable("enquesta") String enquesta, @PathVariable("curs") Integer curs, 
+        @PathVariable("ambit") String ambit, @PathVariable("estudi") String estudi, @PathVariable("centre") Integer centre) {
+        return ies.findByEnquestaCursAmbitEstudiCentre(enquesta, curs, ambit, estudi, centre);
+    }
+
+    @PostMapping("/{enquesta}/{curs}/{ambit}/{estudi}/{centre}/{titulacio}")
+    public List<IndicadorEnquesta> findByEnquestaCursAmbitEstudiCentreTitulacio(@PathVariable("enquesta") String enquesta, @PathVariable("curs") Integer curs, 
+        @PathVariable("ambit") String ambit, @PathVariable("estudi") String estudi, @PathVariable("centre") Integer centre,
+        @PathVariable("titulacio") Integer titulacio) {
+        return ies.findByEnquestaCursAmbitEstudiCentreTitulacio(enquesta, curs, ambit, estudi, centre, titulacio);
+    }
+
+    /*
+     * Endpoint para la funcion getFromTitulacion de ies
+     */
+    @GetMapping("/getFromTitulacio/{idTitulacio}/{curs}")
+    public List<Indicador> getFromTitulacion(@PathVariable Integer idTitulacio, @PathVariable Integer curs) 
+        throws ParserConfigurationException, MalformedURLException, IOException, SAXException  {
+        
+        return is.getFromTitulacion(idTitulacio, curs);
+    }
+
+    @PostMapping("/save")
+    public void save(@RequestBody EvidenciaIndicadorEnquesta eie) {
+        eies.save(eie);
+    }
+
+    @GetMapping("/count/{enquesta}")
+    public Integer countByEnquesta(@PathVariable String enquesta) {
+        return iets.countByEnquesta(enquesta);
+    }
+    
+    @GetMapping("/checkDuplicates/{enquesta}")
+    public List<IndicadorEnquestaTmpDup> checkDuplicates(@PathVariable String enquesta) {
+        return iets.checkDuplicates(enquesta);
+    }
+    
+    @GetMapping("/checkIntegrity/{enquesta}")
+    public List<IndicadorEnquestaTmpDup> checkIntegrity(@PathVariable String enquesta) {
+        return iets.checkIntegrity(enquesta);
+    }
+
+    @GetMapping("/consolidate/{enquesta}")
+    public Integer consolidateByEnquesta(@PathVariable String enquesta) {
+        return ies.consolidateByEnquesta(enquesta);
+    }
+    
+    @GetMapping
+    public List<String> findTypes() {
+        return ies.findTypes();
+    }
+
+    @GetMapping("/graph/{lugar}")
+    public List<Indicador> getGraphData(@PathVariable Integer lugar) {
+        return is.getGraphData(lugar);
+    }
+
+    @PostMapping("/graph/centre")
+    public List<IndicadorDTOimp> getGraphData(@RequestParam Integer centre, @RequestParam String tambit, @RequestParam Integer year) {
+        return ies.getGraphData(centre, tambit, year).stream().map(ind -> new IndicadorDTOimp(ind)).collect(Collectors.toList());
+    }
+
+    @PostMapping("/graph/titulacions")
+    public List<IndicadorDTOimp> getGraphData(@RequestParam List<Integer> tits, @RequestParam Integer centre, @RequestParam String tambit, @RequestParam Integer year) {
+        return ies.getGraphData(tits, centre, tambit, year).stream().map(ind -> new IndicadorDTOimp(ind)).collect(Collectors.toList());
+    }
+
+    @DeleteMapping("/{proces}/{enquesta}")
+    public void deleteByProcesEnquesta(@PathVariable String proces, @PathVariable String enquesta) {
+        eies.removeByProcesEvidencia(proces, enquesta);
+    }
+    
+    @PostMapping("/templatedata")
+    public void getTemplateData(@RequestParam Integer idTitulacio, @RequestParam Integer idCentre, @RequestParam Integer curs, @RequestParam HashMap<String, Object> context) {
+		
+		/*  Indicadores del data warehouse */
+		try {
+			List<Indicador> indicadores;
+			indicadores = is.getFromTitulacion(idTitulacio, curs);
+			for(Indicador i : indicadores) { 
+				context.put(i.getIndicador(), i.getValor());
+			}
+		} 
+		catch(Exception e) { }
+		
+		/*  Indicadores de encuestas y otros almacenados en BD  */
+		IndicadorEnquestaDTO indicadorEnquestaDTO = new IndicadorEnquestaDTO(idTitulacio, idCentre, curs);
+		List<IndicadorEnquestaValorDTOImp> enquestesT = ies.getAllInds(indicadorEnquestaDTO.getIdTitulacio(), indicadorEnquestaDTO.getIdCentre(), indicadorEnquestaDTO.getCurs(), ies.getRuctTitulacio(idTitulacio))
+                .stream()
+                .map(ind -> new IndicadorEnquestaValorDTOImp(ind))
+                .collect(Collectors.toList());
+				
+		for(IndicadorEnquestaValorDTOImp i : enquestesT) {
+			String indicador = i.getAmbit().toLowerCase().equals("t") ? (i.getEnquesta().toLowerCase()+"_"+i.getIndicador().toLowerCase()) : (i.getEnquesta().toLowerCase()+"_"+i.getIndicador().toLowerCase()+"_"+i.getAmbit().toLowerCase());
+			if(i.getNum() == null) {
+				indicador = i.getTipus().toLowerCase().equals("avg") ? indicador : (indicador += "_"+i.getTipus().toLowerCase());		
+				indicador = i.getCursd() == null ? indicador : (indicador += "_"+i.getCursd().toLowerCase());
+				context.put(indicador, formatValue(i.getValor()));
+			}
+			else {
+				context.put(indicador, i.getNum());
+			}
+			
+		}
+	}
+
+
+    @PostMapping("/download/templatedata")
+    public HashMap<String, String> getTemplateData(@RequestParam Integer idTitulacio, @RequestParam Integer idCentre, @RequestParam Integer curs) {
+		HashMap<String, String> context = new HashMap<String, String>();
+		
+		/*  Indicadores del data warehouse */
+		try {
+			List<Indicador> indicadores;
+			indicadores = is.getFromTitulacion(idTitulacio, curs);
+			for(Indicador i : indicadores) { 
+				context.put(i.getIndicador(), i.getValor());
+			}
+		} 
+		catch(Exception e) { }
+		
+		/*  Indicadores de encuestas  */
+		List<IndicadorEnquestaValorDTOImp> enquestesT;
+		IndicadorEnquestaDTO iEnquestaDTO = new IndicadorEnquestaDTO(idTitulacio, idCentre, curs);
+		enquestesT = ies.getAllInds(iEnquestaDTO.getIdTitulacio(), iEnquestaDTO.getIdCentre(), iEnquestaDTO.getCurs(), ies.getRuctTitulacio(idTitulacio)).stream()
+                .map(ind -> new IndicadorEnquestaValorDTOImp(ind))
+                .collect(Collectors.toList());
+				
+		for(IndicadorEnquestaValorDTOImp i : enquestesT) {
+			String indicador = i.getAmbit().equals("t") ? (i.getEnquesta()+"_"+i.getIndicador()) : (i.getEnquesta()+"_"+i.getIndicador()+"_"+i.getAmbit());
+			indicador = i.getTipus().equals("avg") ? indicador : (indicador += "_"+i.getTipus());		
+			indicador = i.getCursd() == null ? indicador : (indicador += "_"+i.getCursd());
+			context.put(indicador, formatValue(i.getValor()));
+		}
+
+        return context;
+	}
+
+    @PostMapping("/templatedata/array")
+    public HashMap<String, String> getTemplateDataArray(@RequestParam Integer idTitulacio, @RequestParam Integer idCentre, @RequestParam Integer curs) {
+		HashMap<String, String> info = new HashMap<String, String>();
+		
+		/*  Indicadores del data warehouse */
+		try {
+			List<Indicador> indicadores;
+			indicadores = is.getFromTitulacion(idTitulacio, curs);
+			for(Indicador i : indicadores) { 
+				info.put(i.getIndicador(), i.getValor());
+			}
+		} 
+		catch(Exception e) { }
+		
+		/*  Indicadores de encuestas  */
+		List<IndicadorEnquestaValorDTOImp> enquestesT;	
+		IndicadorEnquestaDTO iEnquestaDTO = new IndicadorEnquestaDTO(idTitulacio, idCentre, curs);
+		enquestesT = ies.getAllInds(iEnquestaDTO.getIdTitulacio(), iEnquestaDTO.getIdCentre(), iEnquestaDTO.getCurs(), ies.getRuctTitulacio(idTitulacio)).stream()
+                .map(ind -> new IndicadorEnquestaValorDTOImp(ind))
+                .collect(Collectors.toList());
+				
+		for(IndicadorEnquestaValorDTOImp i : enquestesT) {
+			String indicador = i.getAmbit().equals("t") ? (i.getEnquesta()+"_"+i.getIndicador()) : (i.getEnquesta()+"_"+i.getIndicador()+"_"+i.getAmbit());
+			indicador = i.getTipus().equals("avg") ? indicador : (indicador += "_"+i.getTipus());	
+			indicador = i.getCursd() == null ? indicador : (indicador += "_"+i.getCursd());
+			info.put(indicador, formatValue(i.getValor()));
+		}	
+		
+		return info;
+	}
+
+    private String formatValue(String v) {	
+		if(v == null) return ""; 
+		if(v.equals("NP")) return "NP";
+		if(v.isEmpty() | v.isBlank()) {
+			return "";
+		}
+		
+		try{
+	        return String.format("%.2f", Float.parseFloat(v)).replace(",", ".");
+	    } 
+		catch(NumberFormatException e){ }
+		
+		try{
+	        return Integer.toString(Integer.parseInt(v));
+	    } 
+		catch(NumberFormatException e){ }
+		
+		return v;
+	}
+}

+ 38 - 0
src/main/resources/application-dev.properties

@@ -0,0 +1,38 @@
+# Urls
+saic.url.domain = https://saicd.uv.es:4443
+saic.url.public = ${saic.url.domain}/public
+
+# Email config
+saic.mailer.queue.enabled = true
+saic.mailer.reminder.enabled = false
+saic.mailer.calendar.enabled = false
+saic.mailer.maxMailsPerRound = 20
+
+# Data parser config
+saic.parser.surveys.path = /DATA/saic-data/databases/
+saic.parser.surveys.enabled = false
+
+# Database config
+spring.datasource.url=jdbc:postgresql://127.0.0.1/saic_v2
+spring.datasource.password=docent1ia2.l6
+
+# Files and log config
+logging.file.path=/DATA/saic-data/logs
+logging.file.name=/DATA/saic-data/logs/saicd.log
+saic.data.filePath=/DATA/saic-data/files/
+saic.data.tmpPath=/DATA/saic-data/tmp/
+saic.data.docsPath=/DATA/saic-data/documents/
+saic.data.templates.filePath=/DATA/saic-data/templates/
+saic.data.templates.logoPath=/DATA/saic-data/templates/logos/
+saic.data.templates.fileNotFound=/DATA/saic-data/utils/filenotfound.pdf
+saic.data.master = /DATA/saic-data/databases/MasterOficial/
+saic.data.doctorado = /DATA/saic-data/databases/doctorat/
+saic.data.evdocente = /DATA/saic-data/databases/ev_docente/latest/
+
+# Scheduler
+saic.scheduler.expired.enabled=false
+
+# Desactivar RefreshScope en Eureka Client
+eureka.client.refresh.enable=false
+# Desactivar RefreshScope en SpringCloud
+spring.cloud.refresh.enabled=false

+ 40 - 0
src/main/resources/application-graal.properties

@@ -0,0 +1,40 @@
+# Urls
+saic.url.domain = https://saicd.uv.es:4443
+saic.url.public = ${saic.url.domain}/public
+
+# Email config
+saic.mailer.queue.enabled = true
+saic.mailer.reminder.enabled = false
+saic.mailer.calendar.enabled = false
+saic.mailer.maxMailsPerRound = 20
+
+# Data parser config
+saic.parser.surveys.path = /DATA/saic-data/databases/
+saic.parser.surveys.enabled = false
+
+# Database config
+spring.datasource.url=jdbc:postgresql://saicd.uv.es:5432/saic_v2
+spring.datasource.password=docent1ia2.l6
+
+# Files and log config
+logging.file.path = saic-data/logs
+logging.file.name = saic-data/logs/saic.log
+
+# Dummy
+saic.data.filePath = saic-data/files/
+saic.data.tmpPath = saic-data/tmp/
+saic.data.docsPath = saic-data/documents/
+saic.data.templates.filePath = saic-data/templates/
+saic.data.templates.logoPath = saic-data/templates/logos/
+saic.data.templates.fileNotFound = saic-data/utils/filenotfound.pdf
+saic.data.master = saic-data/databases/MasterOficial/
+saic.data.doctorado = saic-data/databases/doctorat/
+saic.data.evdocente = saic-data/databases/ev_docente/latest/
+
+# Scheduler
+saic.scheduler.expired.enabled=false
+
+# Desactivar RefreshScope en Eureka Client
+eureka.client.refresh.enable=false
+# Desactivar RefreshScope en SpringCloud
+spring.cloud.refresh.enabled=false

+ 29 - 0
src/main/resources/application-local.properties

@@ -0,0 +1,29 @@
+# Urls
+saic.url.domain = http://127.0.0.1
+saic.url.public = ${saic.url.data.domain}/public
+
+# Data parser config
+saic.parser.surveys.path = /home/dagarcos/DATA_SYNC/UV_APPS/SAIC/DATA/saic-data/databases/
+saic.parser.surveys.enabled = false
+
+# Database config
+spring.datasource.url=jdbc:postgresql://saicd.uv.es:5432/saic_v2
+spring.datasource.password=docent1ia2.l6
+
+# Files and log config
+saic.data.filePath=/home/mariomh/Documentos/SAIC/docs/files/
+saic.data.tmpPath=/home/mariomh/Documentos/SAIC/docs/tmp/
+saic.data.docsPath=/home/mariomh/Documentos/SAIC/docs/documents/
+saic.data.templates.filePath = /home/mariomh/Documentos/SAIC/docs/templates/
+saic.data.templates.logoPath = /home/mariomh/Documentos/SAIC/docs/templates/logos/
+saic.data.templates.fileNotFound = /home/mariomh/Documentos/SAIC/docs/filenotfound.pdf
+saic.data.master = /home/mariomh/Documentos/SAIC/docs/databases/MasterOficial/
+saic.data.doctorado = /home/mariomh/Documentos/SAIC/docs/databases/doctorat/
+saic.data.evdocente = /home/mariomh/Documentos/SAIC/docs/databases/ev_docente/latest/
+logging.file.path=/home/mariomh/Documentos/SAIC/docs/logs
+logging.file.name=/home/mariomh/Documentos/SAIC/docs/logs/saic.log
+
+# Scheduler
+saic.scheduler.expired.enabled=false
+
+

+ 38 - 0
src/main/resources/application-prod.properties

@@ -0,0 +1,38 @@
+# Urls
+saic.url.domain = https://saic.uv.es:4443
+saic.url.public = ${saic.url.domain}/public
+
+# Email config
+saic.mailer.queue.enabled = true
+saic.mailer.reminder.enabled = false
+saic.mailer.calendar.enabled = true
+saic.mailer.maxMailsPerRound = 20
+
+# Data parser config
+saic.parser.surveys.path = /DATA/saic-data/databases/
+saic.parser.surveys.enabled = false
+
+# Database config
+spring.datasource.url=jdbc:postgresql://127.0.0.1/saic_v2
+spring.datasource.password=docent1ia2.l6
+
+# Files and log config
+logging.file.path=/DATA/saic-data/logs
+logging.file.name=/DATA/saic-data/logs/saic.log
+saic.data.filePath=/DATA/saic-data/files/
+saic.data.tmpPath=/DATA/saic-data/tmp/
+saic.data.docsPath=/DATA/saic-data/documents/
+saic.data.templates.filePath=/DATA/saic-data/templates/
+saic.data.templates.logoPath=/DATA/saic-data/templates/logos/
+saic.data.templates.fileNotFound=/DATA/saic-data/utils/filenotfound.pdf
+saic.data.master = /DATA/saic-data/databases/MasterOficial/
+saic.data.doctorado = /DATA/saic-data/databases/doctorat/
+saic.data.evdocente = /DATA/saic-data/databases/ev_docente/latest/
+
+# Scheduler
+saic.scheduler.expired.enabled=false
+
+# Desactivar RefreshScope en Eureka Client
+eureka.client.refresh.enable=false
+# Desactivar RefreshScope en SpringCloud
+spring.cloud.refresh.enabled=false

+ 51 - 0
src/main/resources/application.properties

@@ -0,0 +1,51 @@
+spring.application.name = saic-data-service
+
+#EUREKA
+eureka.client.service-url.defaultZone=http://127.0.0.1:8761/eureka
+eureka.instance.prefer-ip-address=false
+eureka.instance.status-page-url-path=/
+
+# Actuator
+management.endpoints.web.exposure.include=*
+management.endpoint.health.show-details=always
+saic.actuator.validIp=147.156.0.0/16
+
+# Activate spring profiles
+spring.profiles.active=@activatedProperties@
+
+# Multilang config
+spring.messages.encoding=UTF-8
+
+# JPA config
+spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
+spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true
+spring.jpa.hibernate.ddl-auto=none
+#spring.jpa.properties.hibernate.show_sql=true
+
+# Logs config
+logging.logback.rollingpolicy.max-file-size=10MB
+logging.level.root=INFO
+
+# Max request and file size
+spring.servlet.multipart.max-file-size=128MB
+spring.servlet.multipart.max-request-size=128MB
+
+# Datasource config
+spring.datasource.driver-class-name=org.postgresql.Driver
+spring.datasource.username=postgres
+spring.sql.init.platform=postgres
+
+#spring.jpa.show-sql=true 
+
+# Server
+server.port=0
+server.forward-headers-strategy=native
+server.tomcat.remoteip.remote-ip-header=X-Forwarded-For
+server.tomcat.remoteip.protocol-header=X-Forwarded-Proto
+server.tomcat.remoteip.port-header=X-Forwarded-Port
+server.tomcat.use-relative-redirects=true
+server.tomcat.remoteip.internal-proxies=.*
+
+# Nombre de la cookie de sesion
+server.servlet.session.cookie.name=SAICSESSIONID
+

+ 462 - 0
src/main/resources/messages.properties

@@ -0,0 +1,462 @@
+
+##########################################
+#	GLOBAL LOCALES 
+##########################################
+global.lang.va = Valencià
+global.lang.es = Castellano
+global.exit = Eixir
+global.login = Accedir
+global.help = Instruccions
+global.lang = Idioma
+global.footer.span1 = Consultes procedimentals
+global.footer.span2 = Consultes tècniques
+global.footer.span3 = Unitat de Qualitat
+global.menu.tits = Panell de control
+global.menu.procedures = Tasques assignades
+global.menu.supervision = Supervisió
+global.menu.managers = Responsables
+global.menu.admin = Administració
+global.menu.admin.stats = Informació
+global.menu.admin.instances = Instanciació
+global.menu.admin.procedures = Procediments
+global.menu.admin.mailing = Correus
+global.menu.admin.calendar = Planificador
+global.menu.admin.acredita = Acreditacions
+global.menu.data = Dades i plantilles
+global.menu.data.templates = Plantilles (word)
+global.menu.data.editor = Plantilles (online)
+global.menu.data.import = Importar dades
+global.menu.data.data = Dades consolidades
+global.menu.data.parse = Consolidar dades
+global.menu.contact = Contacte
+global.menu.contact.title = Contacte
+global.menu.contact.title = Adreces de correu electrònic de contacte
+global.accept = Acceptar
+global.edit = Editar
+global.new = Afegir
+global.titleNew = Afegir element
+global.delete = Esborrar 
+global.cancel = Cancel·lar
+global.close = Tancar
+global.close.confirm = Es perdran les dades no guardades
+global.save = Guardar
+global.confirm = Confirmar
+global.return = Tornar
+global.loading = Carregant...
+global.download = Descarregar
+global.titleDownload = Descarregar evidència
+global.deleteWarning = Atenció, aquesta acció no es podrà desfer
+global.error.msg.i = Hi ha hagut un error, per favor, torneu a intentar-ho o contacteu amb 
+global.error.msg.f = reportant el següent error
+global.editEvidence = Eliminar/Editar evidència
+global.uploadLimits = Només es permet pujar un fitxer, per a pujar-ne més utilitzi un fitxer comprimit
+global.uploadSelectFile = Seleccione un fitxer
+global.noData = No hi ha dades disponibles
+global.noTasks = No té cap tasca assignada
+global.expire.notice = En 5 minuts es tancarà automàticament la sessió per inactivitat.
+global.expire.keepalive = Tanque aquesta alerta per seguir fent servir l'aplicació
+global.error.msg = Hi ha hagut un error, per favor, torneu a intentar-ho. Si l'error persisteix contacteu amb
+global.unautorized.msg = El seu usuari no esta autoritzat a accedir a aquest apartat
+global.selectors.noData = No hi ha elements marcats
+
+##########################################
+#	LOGIN FORM LOCALES
+##########################################
+login.h2 = Identificació UV
+login.button = Iniciar Sessió
+login.placeholder.username = Usuari
+login.placeholder.passwd = Contrasenya
+login.error = L'usuari o contrasenya introduïts no són correctes o el seu usuari no està autoritzat.
+login.expired = La seua sessió s'ha tancat per inactivitat o per accedir des d'un altre dispositiu/navegador, per favor, torneu a identificar-vos.
+login.session.info1 = La sessió es tancarà automàticament després de 60 minuts d'inactivitat.
+login.session.info2 = Per favor, deseu els canvis periòdicament.
+
+##########################################
+#	PROCEDURES MAIN PAGE LOCALES
+##########################################
+procedures.title = Tasques assignades
+procedures.subtitle = Faça clic sobre el procediment per a realizar la tasca
+procedures.title.tasks = Tasques associades al procediment
+procedures.title.flow = veure flux
+procedures.center = Centre
+procedures.titulation = Titulació
+procedures.year.eval = Curs avaluat
+procedures.year.act = activat el curs
+procedures.dateLimit = Data límit
+procedures.dateInstance = Data darrera acció
+procedures.status = Estat
+procedures.btn.continue = Acceptar i avançar
+procedures.btn.yes = Si
+procedures.btn.no = No
+procedures.btn.selectFile = Seleccione l'evidència
+procedures.btn.draft = Guardar esborrany
+procedures.done.user = Realitzada per
+procedures.done.date = Data de realització
+procedures.done.evidence = Evidència aportada
+procedures.done.response = Resposta
+procedures.done.response.s = SI
+procedures.done.response.n = NO
+procedures.status.a = Activa
+procedures.status.n = Realitzada (Resposta NO)
+procedures.status.s = Realitzada (Resposta SI)
+procedures.status.f = Realitzada
+procedures.status.e = Realitzada (Evidència adjunta)
+procedures.status.i = Inactiva
+procedures.status.title.a = Tasca activa
+procedures.status.title.p = Tasca inactiva
+procedures.status.title.o = No li correspon realitzar aquesta tasca
+procedures.status.title.r = Tasca realitzada
+procedures.activeTask = Tasca activa
+procedures.olderEvidences = Evidències generades en el procés anteriorment
+procedures.closed = Procediment finalitzat
+procedures.cancelled = Procediment tancat
+procedures.extensionAlert = Només es permet pujar fitxers amb extensió PDF
+procedures.assignedTo = Assignada a
+procedures.back = Tornar
+procedures.template = Descarregue la plantilla
+procedures.legend.active = Activa
+procedures.legend.other = No li correspon
+procedures.legend.blocked = Inactiva
+procedures.legend.done = Realitzada
+procedures.legend.expired = Endarrerida
+procedures.list.legend.active = Actiu
+procedures.list.legend.other = No li correspon
+procedures.list.legend.done = Finalitzat
+procedures.list.legend.expired = Endarrerit
+procedures.otherActions = Accions realitzades anteriorment en aquesta tasca
+procedures.editor.confirm = Esta segur que desitja continuar? En enviar l'evidència ja no podrà fer canvis sobre aquesta tasca.
+procedures.editor.msgsave = Esborrany guardat correctament
+procedures.session.expired = La seua sessió s'ha tancat per inactivitat o per accedir des d'un altre dispositiu/navegador. Tanque aquest diàleg per tornar a entrar a SAIC.
+procedures.nosupported = El seu navegador no suporta les funcionalitats necessàries per treballar amb SAIC. Es recomana utilitzar una versió actualitzada de Chrome o Firefox.
+procedures.autosave.lastdate = Última modificació
+procedures.autosave.none = Encara no s'ha guardat cap esborrany
+
+##########################################
+#	SUPERVISION PAGE LOCALES
+##########################################
+supervision.title = Supervisió dels procediments
+supervision.subtitle = Seleccione els criteris de filtrat dels procediments que desitja supervisar i faça clic en "Consultar".
+supervision.searchBy = Consultar per 
+supervision.years = Curs(os) d'avaluació del procediment
+supervision.titulations = Titulació(ns)
+supervision.centers = Centre(s)
+supervision.titulations.all = Totes
+supervision.titulations.all.g = Tots els graus del centre
+supervision.titulations.all.m = Tots els masters del centre
+supervision.procedures = Procediment(s)
+supervision.evidences = Evidència(es)
+supervision.search = Consultar
+supervision.procedures.title = Procediments que coincideixen amb els criteris seleccionats
+supervision.filter.apha = Ordenar alfabèticament
+supervision.filter.date = Ordenar per data de darrera acció
+supervision.filter.none = Eliminar filtres
+supervision.filter.download1 = Exportar llistat
+supervision.filter.download2 = Exportar per titulació
+
+##########################################
+#	MANAGERS PAGE LOCALES
+##########################################
+managers.title = Cerca de responsables
+managers.title2 = Responsables del seu centre
+managers.subtitle = Seleccione els criteris de filtrat dels responsables que desitja cercar i faça clic en "Cercar".
+managers.centers = Centre
+managers.titulations = Titulació(ns)
+managers.search = Cercar
+managers.search.title = Resultats de la cerca
+managers.noResults = No s'ha trobar cap responsable amb els criteris de cerca seleccionats
+managers.since = des del
+
+##########################################
+#	ADMIN PAGE LOCALES
+##########################################
+admin.instances.title = Instanciació de procediments
+admin.instances.subtitle = Seleccione un procediment junt amb el centre i la titulació sobre els que vol instanciar-lo
+admin.instances.error = Els següents procediments no s'han pogut instanciar
+admin.instances.done = S'han instanciat correctament els següents procediments
+admin.instances.selector.titulation = Titulació
+admin.instances.selector.center = Centre
+admin.instances.selector.procedure = Procediment
+admin.instances.selector.curs = Curs
+admin.instances.selector.taskType = Tipus de tasca
+admin.actions.instance = Instanciar
+admin.actions.load = Carregar
+admin.templates.title = Cerca i prova de plantilles
+admin.templates.subtitle = Seleccione un procediment junt amb el centre i la titulació sobre els que vol provar una plantilla
+admin.templates.info = Faça click sobre la plantilla que vol provar per a descarregar-la i veure els associats que s'han carregat correctament o faça click en editar per a modificar la llista d'indicadors
+admin.templates.indicators = Indicadors associats a la plantilla
+admin.templates.table.enq = Dades de enquestes
+admin.templates.table.serv = Dades del web service
+admin.procedures.title = Administració de procediments
+admin.procedures.subtitle = Seleccione el procediment i feu click en l'opció desitjada
+admin.procedures.action.new = Nou
+admin.procedures.action.duplicate = Duplicar
+admin.procedures.action.edit = Editar
+admin.procedures.action.remove = Esborrar
+admin.procedures.selector.procedure = Procediment
+admin.procedures.selector.years = Curs d' avaluació del procediment
+admin.procedures.selector.titulations = Titulació
+admin.procedures.selector.centers = Centre
+admin.procedures.confirm = Confirmar
+admin.procedures.info.procedure = Informació del procediment
+admin.procedures.info.task = Tasca
+admin.procedures.form.id = ID
+admin.procedures.form.nameProcedure = Codi
+admin.procedures.form.subtitleProcedure = Dimensió abreujada
+admin.procedures.form.context = Àmbit
+admin.procedures.form.version = Versió
+admin.procedures.form.activationYear = Curs d'activació
+admin.procedures.form.evalYear = Curs avaluat
+admin.procedures.form.titleCas = Titol en castellà
+admin.procedures.form.titleVal = Titol en valencià
+admin.procedures.form.descriptionCas = Descripció en castellà
+admin.procedures.form.descriptionVal = Descripció en valencià
+admin.procedures.form.comments = Informació adicional (ocult als usuaris) 
+admin.procedures.form.nameEvCas = Nom de la evidència en castellà
+admin.procedures.form.nameEvVal = Nom de la evidència en valencià
+admin.procedures.form.taskp = ID Tascap
+admin.procedures.form.limitDate = Data límit
+admin.procedures.form.options = Opcions
+admin.procedures.form.report = ¿Genera informe?
+admin.procedures.form.type = Tipus
+admin.procedures.form.type.1 = Evidència
+admin.procedures.form.type.2 = Si/No
+admin.procedures.form.type.3 = Continuar
+admin.procedures.form.evCode = Codi de l'evidència
+admin.procedures.form.next = ID tasca següent
+admin.procedures.form.next2 = ID tasca següent alternativa
+admin.procedures.form.role = Rol
+admin.procedures.form.addTask = Afegir tasca
+admin.procedures.form.removeConfirm = Si esborra aquest procediment les dades no es podràn recuperar
+admin.action.delete = Esborrar
+admin.action.deleteInstance = Esborrar procediment
+admin.action.deleteTask = Esborrar acció
+admin.action.reloadInstance = Esborrar i reiniciar procediment
+admin.action.closeInstance = Tancar procediment
+admin.action.reloadTask = Esborrar i repetir tasca
+admin.action.reactivate = Repetir tasca
+admin.action.edit = Editar tasca
+admin.action.attach = Adjuntar nova evidència
+admin.managers.add = Afegir responsable
+admin.managers.center = Centre
+admin.managers.titulation = Titulació
+admin.managers.user = Usuari
+admin.managers.firstname = Nom
+admin.managers.lastname = Cognoms
+admin.managers.email = Email
+admin.managers.role = Rol
+admin.managers.exists = Ja hi ha una entrada amb les dades seleccionades
+admin.managers.newRole = Afegir reponsable
+admin.managers.newUser = Nou usuari
+admin.stats.pending.title = Correus pendents d'enviar
+admin.stats.pending.total = Total de correus pendents d'enviar
+admin.stats.pending.username = Usuari
+admin.stats.pending.fullname = Nom
+admin.stats.pending.email = Correu
+admin.stats.sessions.title = Sesions actives en aquest moment
+admin.stats.sessions.id = ID de sessió
+admin.stats.sessions.username = Usuari
+admin.stats.sessions.last = Darrera acció
+admin.stats.sessions.expired = Expirada
+admin.stats.sessions.active.total = Total de sessions actives
+admin.stats.sessions.expired.total = Total de sessions expirades
+admin.stats.table.lengthMenu = Files
+admin.stats.table.zeroRecords = No s'han trobat resultats
+admin.stats.table.info = Pàgina _PAGE_ de _PAGES_
+admin.stats.table.infoEmpty = No hi ha dades per a mostrar
+admin.stats.table.infoFiltered = (filtrats de un total de _MAX_)
+admin.stats.table.next = Següent
+admin.stats.table.previous = Anterior
+admin.mailing.title = Correus electrònics
+admin.mailing.subtitle = Seleccione un centre i un rol al que enviar el correu
+admin.mailing.send = Enviar
+admin.mailing.center = Centre
+admin.mailing.role = Rol
+admin.mailing.subject = Assumpte
+admin.mailing.body = Missatge
+admin.mailing.sent.success = El missatge s'ha processat i serà enviat al més aviat possible 
+admin.mailing.sent.error = No s'han trobat usuaris per al rol i centres especificats
+admin.calendar.form.titleAdd = Planificar nova instància
+admin.calendar.form.titleEdit = Editar instància planificada
+admin.calendar.form.titleCopy = Copiar instancia existent
+admin.calendar.form.delete = Esborrar
+admin.calendar.form.instanciar = Instanciar
+admin.calendar.form.add = Afegir
+admin.calendar.form.date = Data
+admin.calendar.form.load = Carregar
+admin.calendar.form.changeDate = Canviar la data al
+admin.calendar.form.acredSel = Selecció per acreditació
+admin.calendar.form.input.grup.1 = Gener 
+admin.calendar.form.input.grup.4 = Abril
+admin.calendar.form.input.grup.9 = Septembre
+admin.calendar.form.input.grup.11 = Novembre
+admin.calendar.form.input.tambit.g = Graus
+admin.calendar.form.input.tambit.m = Másters
+admin.calendar.form.input.tambit.d = Doctorats
+admin.acredita.title = Resumen d'acreditacions 
+admin.acredita.form.view = Carregar
+admin.acredita.form.year = Any
+admin.acredita.form.group = Grup
+admin.acredita.form.groupnum.0 = Tots 
+admin.acredita.form.groupnum.1 = Gener 
+admin.acredita.form.groupnum.4 = Abril
+admin.acredita.form.groupnum.9 = Septembre
+admin.acredita.form.groupnum.11 = Novembre
+
+##########################################
+#	DASHBOARD PAGE LOCALES
+##########################################
+dashboard.header.published = Informes publicats
+dashboard.menu.summary = Resum
+dashboard.menu.procedures = Procediments SAIC
+dashboard.menu.docs = Documentació
+dashboard.menu.data = Indicadors
+dashboard.menu.tits = Titulacions
+dashboard.menu.managers = Responsables
+dashboard.summary.legend.active = Finalitzat
+dashboard.summary.legend.ontime = En curs
+dashboard.summary.legend.delayed = Endarrerit
+dashboard.summary.legend.closed = Tancat
+dashboard.data.sel.g = Dades de Grau
+dashboard.data.sel.m = Dades de Màster
+dashboard.data.sel.d = Dades de Doctorat
+dashboard.data.sel.rates = Taxes
+dashboard.data.sel.estud = Satisfacció estudiantat
+dashboard.data.sel.estud1 = Satisfacció estudiantat de 1er
+dashboard.data.sel.estud3 = Satisfacció estudiantat de 3er
+dashboard.data.sel.gradu = Satisfacció graduats/des
+dashboard.data.sel.prof = Satisfacció professorat
+dashboard.data.sel.eval = Evaluació docent
+dashboard.data.sel.ptgas = Satisfacció PTGAS
+dashboard.data.legend.1 = Titulació
+dashboard.data.legend.2 = Centre
+dashboard.data.legend.3 = Universitat
+dashboard.data.table.hr1 = Enquesta
+dashboard.data.table.hr2 = Indicador
+dashboard.data.table.hr3 = Ámbit
+dashboard.data.table.hr4 = Estudi
+dashboard.data.table.hr5 = Valor
+dashboard.data.table.hr6 = Curs
+dashboard.data.table.hr7 = CursD
+dashboard.data.table.hr8 = Tipus
+dashboard.data.table.hr9 = NºEnq
+dashboard.data.table.hr10 = Curs Enq
+dashboard.data.table.hr11 = Centre Orig
+dashboard.data.table.hr12 = Tit. Orig
+dashboard.data.table.hr13 = RUCT
+dashboard.data.table.hr14 = Data
+dashboard.data.table.btn1 = Dades del curs
+dashboard.data.table.btn2 = Exportar a Excel
+dashboard.data.table.btn3 = Exportar a CSV
+dashboard.data.table.btn4 = Exportar a PDF
+dashboard.data.table.btn5 = Mostrar/Ocultar columnes
+dashboard.data.table.btn6 = Veure gràfiques
+dashboard.data.table.btn7 = Veure totes les dades en format taula
+dashboard.acred.title.edit = Canviar dates
+dashboard.acred.date.segui = Seguiment
+dashboard.acred.date.acred = Pròxima acreditació
+dashboard.acred.date.renov = Última acreditació
+dashboard.acred.date.verif = Verificació
+dashboard.acred.date.impla = Implantació
+dashboard.acred.group.year = Curs pròx. acred.
+dashboard.acred.group.name = Grup pròx. acred.
+dashboard.acred.recom.title = Recommanacions *
+dashboard.acred.recom.msg = Hi ha recomanacions d'obligat compliment derivades de la darrera renovació.
+dashboard.acred.recom.info = Indicar si hi ha recomanacions d'obligat compliment pendents
+dashboard.acred.segui.title = Seguimiento **
+dashboard.acred.segui.info = Indicar si la titulació ha de passar seguiment obligatòriament
+dashboard.acred.segui.msg = La titulació ha de passar obligatòriament un seguiment en la data indicada al camp seguiment.
+dashboard.acred.codes = Codis interns
+dashboard.acred.table.title = Titulacions del centre
+dashboard.acred.table.ruct = RUCT
+dashboard.acred.table.tit = Titulació
+dashboard.acred.table.type = Tipus
+dashboard.acred.table.next = Pròx Accr.
+dashboard.acred.table.acred = Fecha Accr.
+dashboard.acred.table.renov = Renovació
+dashboard.acred.table.verif = Verificació
+dashboard.acred.table.impla = Implantació
+dashboard.acred.table.segui = Seguiment
+dashboard.acred.table.inter = InterUniv.
+dashboard.acred.docs.title = Documentació
+dashboard.gantt.selector = Veure procediments de
+dashboard.tree.selector.title = Veure procediments de
+dashboard.tree.selector.centre = Centre i totes les titulacions
+dashboard.tits.ruct = RUCT
+dashboard.tits.codes = Codis
+dashboard.tits.centre = Centre
+dashboard.tits.tit = Titulació
+dashboard.tits.type = Tipus 
+
+##########################################
+#	DATA PAGE LOCALES
+##########################################
+data.import.title = Importador de dades
+data.import.subtitle = Les dades que importi es quedaran en estat pendent de consolidar. De la mateixa manera, només podreu consultar les dades que es trobin en aquest mateix estat.
+data.import.note1 = El fitxer CSV ha de contenir, com a mínim, les columnes "curs", "titulacio", "centre" i "tipus"
+data.import.note2 = La columna "tipus" pot pendre els valors "avg", "min" o "max"
+data.import.note3 = * Només per a importació 
+data.import.note4 = * Només per a consulta
+data.import.note5 = S'han detectat les següents columnes en el fitxer seleccionat. Si desitja que s'ignore alguna columna, desmarque-la de la llista.
+data.show.title = Consultar dades consolidades
+data.consolide.title = Consolidar dades
+data.consolide.subtitle = Seleccione el conjunt de dades que desitja consolidar
+data.consolide.err1 = S'han detectat dades duplicades en el conjunt pendent de consolidar, si us plau, corregiu-lo abans de consolidar.
+data.consolide.err2 = S'han detectat errors d'integritat entre el conjunt seleccionat i les dades ja consolidades, si us plau, corregiu-les abans de consolidar.
+data.consolide.err3 = No s'ha pogut executar l'acció, comproveu de nou la integritat de les dades.
+data.consolide.count = Nombre de registres pendents de consolidar per al conjunt seleccionat
+data.consolide.tabdesc = Es mostren els registres per als quals s'han detectat problemes
+data.consolide.nodata = No hi ha dades pendents de consolidar per al conjunt seleccionat
+data.consolide.noerr = No s'han detectat errors
+data.consolide.ok = Acció realitzada correctament
+data.consolide.actions.info = Accions automàtiques disponibles
+data.consolide.actions.action1 = Esborrar duplicats
+data.consolide.actions.action2 = Esborrar consolidades
+data.consolide.actions.action3 = Esborrar pendents
+data.consolide.actions.msg = Nombre de registres esborrats
+
+data.input.source = Tipus de dades
+data.input.survey = Grup de dades
+data.input.year = Curs
+data.input.dstyear = Curs a imputar
+data.input.scope = Àmbit
+data.input.scope.t = Titulació
+data.input.scope.c = Centre
+data.input.scope.u = Universitat
+data.input.type = Tipus Titulació
+data.input.type.g = Grau
+data.input.type.m = Màster
+data.input.type.d = Doctorat
+data.input.type.u = Global
+data.input.tit = Titulació
+data.input.cen = Centre
+data.input.file = Seleccioneu o arrossegueu ací el fitxer CSV
+data.input.select = Trieu un conjunt de dades
+data.input.option = Trieu una opció
+data.input.delim = Delimitador
+data.input.dbsource = Origen
+data.input.importType = Tipus d'importació
+data.input.importType.file = Fitxer
+data.input.importType.db = Base de dades
+data.input.view = Vista/Taula a importar (nom_esquema.nom_vista)
+data.input.key = Clau
+
+data.btn.import = Importar
+data.btn.connect = Comprovar connexió
+data.btn.show = Consultar
+data.btn.delete = Eliminar
+data.btn.query = Consultar
+data.btn.check = Comprobar
+data.btn.run = Executar
+data.table.0 = Curs
+data.table.1 = Titulació
+data.table.2 = Centre
+data.table.3 = Àmbit
+data.table.4 = Tipus
+data.table.5 = Indicador
+data.table.6 = Valor
+data.table.7 = Num enq.
+data.table.8 = Titulació Orig.
+data.table.9 = Centre Orig.
+data.table.10 = RUCT
+data.table.11 = Usuari
+data.alert.connect = No s'ha pogut establir la connexió amb la vista indicada

+ 469 - 0
src/main/resources/messages_ca.properties

@@ -0,0 +1,469 @@
+
+##########################################
+#	GLOBAL LOCALES 
+##########################################
+global.lang.va = Valencià
+global.lang.es = Castellano
+global.exit = Eixir
+global.login = Accedir
+global.help = Instruccions
+global.lang = Idioma
+global.footer.span1 = Consultes procedimentals
+global.footer.span2 = Consultes tècniques
+global.footer.span3 = Unitat de Qualitat
+global.menu.tits = Panell de control
+global.menu.procedures = Tasques assignades
+global.menu.supervision = Supervisió
+global.menu.managers = Responsables
+global.menu.admin = Administració
+global.menu.admin.stats = Informació
+global.menu.admin.instances = Instanciació
+global.menu.admin.procedures = Procediments
+global.menu.admin.mailing = Correus
+global.menu.admin.calendar = Planificador
+global.menu.admin.acredita = Acreditacions
+global.menu.data = Dades i plantilles
+global.menu.data.templates = Plantilles (word)
+global.menu.data.editor = Plantilles (online)
+global.menu.data.import = Importar dades
+global.menu.data.data = Dades consolidades
+global.menu.data.parse = Consolidar dades
+global.menu.contact = Contacte
+global.menu.contact.title = Adreces de correu electrònic de contacte
+global.accept = Acceptar
+global.edit = Editar
+global.new = Afegir
+global.titleNew = Afegir element
+global.delete = Esborrar 
+global.cancel = Cancel·lar
+global.close = Tancar
+global.close.confirm = Es perdran les dades no guardades
+global.save = Guardar
+global.confirm = Confirmar
+global.return = Tornar
+global.loading = Carregant...
+global.download = Descarregar
+global.titleDownload = Descarregar evidència
+global.deleteWarning = Atenció, aquesta acció no es podrà desfer
+global.error.msg.i = Hi ha hagut un error, per favor, torneu a intentar-ho o contacteu amb 
+global.error.msg.f = reportant el següent error
+global.editEvidence = Eliminar/Editar evidència
+global.uploadLimits = Només es permet pujar un fitxer, per a pujar-ne més utilitzi un fitxer comprimit
+global.uploadSelectFile = Seleccione un fitxer
+global.noData = No hi ha dades disponibles
+global.noTasks = No té cap tasca assignada
+global.expire.notice = En 5 minuts es tancarà automàticament la sessió per inactivitat.
+global.expire.keepalive = Tanque aquesta alerta per seguir fent servir l'aplicació
+global.error.msg = Hi ha hagut un error, per favor, torneu a intentar-ho. Si l'error persisteix contacteu amb
+global.unautorized.msg = El seu usuari no esta autoritzat a accedir a aquest apartat
+global.selectors.noData = No hi ha elements marcats
+
+##########################################
+#	LOGIN FORM LOCALES
+##########################################
+login.h2 = Identificació UV
+login.button = Iniciar Sessió
+login.placeholder.username = Usuari
+login.placeholder.passwd = Contrasenya
+login.error = L'usuari o contrasenya introduïts no són correctes o el seu usuari no està autoritzat.
+login.expired = La seua sessió s'ha tancat per inactivitat o per accedir des d'un altre dispositiu/navegador, per favor, torneu a identificar-vos.
+login.session.info1 = Aquesta sessió es tancarà automàticament després de 60 minuts d'inactivitat o en accedir des d'un altre dispositiu/navegador.
+login.session.info2 = Per favor, deseu els canvis periòdicament.
+
+##########################################
+#	PROCEDURES MAIN PAGE LOCALES
+##########################################
+procedures.title = Tasques assignades
+procedures.subtitle = Faça clic sobre el procediment per a realizar la tasca
+procedures.title.tasks = Tasques associades al procediment
+procedures.title.flow = veure flux
+procedures.center = Centre
+procedures.titulation = Titulació
+procedures.year.eval = Curs avaluat
+procedures.year.act = activat el curs
+procedures.dateLimit = Data límit
+procedures.dateInstance = Data darrera acció
+procedures.status = Estat
+procedures.btn.continue = Acceptar i avançar
+procedures.btn.yes = Si
+procedures.btn.no = No
+procedures.btn.selectFile = Seleccione l'evidència
+procedures.btn.draft = Guardar esborrany
+procedures.done.user = Realitzada per
+procedures.done.date = Data de realització
+procedures.done.evidence = Evidència aportada
+procedures.done.response = Resposta
+procedures.done.response.s = SI
+procedures.done.response.n = NO
+procedures.status.a = Activa
+procedures.status.n = Realitzada (Resposta NO)
+procedures.status.s = Realitzada (Resposta SI)
+procedures.status.f = Realitzada
+procedures.status.e = Realitzada (Evidència adjunta)
+procedures.status.i = Inactiva
+procedures.status.title.a = Tasca activa
+procedures.status.title.p = Tasca inactiva
+procedures.status.title.o = No li correspon realitzar aquesta tasca
+procedures.status.title.r = Tasca realitzada
+procedures.activeTask = Tasca activa
+procedures.olderEvidences = Evidències generades en el procés anteriorment
+procedures.closed = Procediment finalitzat
+procedures.cancelled = Procediment tancat
+procedures.extensionAlert = Només es permet pujar fitxers amb extensió PDF
+procedures.assignedTo = Assignada a
+procedures.back = Tornar
+procedures.template = Descarregue la plantilla
+procedures.legend.active = Activa
+procedures.legend.other = No li correspon
+procedures.legend.blocked = Inactiva
+procedures.legend.done = Realitzada
+procedures.legend.expired = Endarrerida
+procedures.list.legend.active = Actiu
+procedures.list.legend.other = No li correspon
+procedures.list.legend.done = Finalitzat
+procedures.list.legend.expired = Endarrerit
+procedures.otherActions = Accions realitzades anteriorment en aquesta tasca
+procedures.editor.confirm = Esta segur que desitja continuar? En enviar l'evidència ja no podrà fer canvis sobre aquesta tasca.
+procedures.editor.msgsave = Esborrany guardat correctament
+procedures.session.expired = La seua sessió s'ha tancat per inactivitat o per accedir des d'un altre dispositiu/navegador. Tanque aquest diàleg per tornar a entrar a SAIC.
+procedures.nosupported = El seu navegador no suporta les funcionalitats necessàries per treballar amb SAIC. Es recomana utilitzar una versió actualitzada de Chrome o Firefox.
+procedures.autosave.lastdate = Última modificació
+procedures.autosave.none = Encara no s'ha guardat cap esborrany
+
+##########################################
+#	SUPERVISION PAGE LOCALES
+##########################################
+supervision.title = Supervisió dels procediments
+supervision.subtitle = Seleccione els criteris de filtrat dels procediments que desitja supervisar i faça clic en "Consultar".
+supervision.searchBy = Consultar per 
+supervision.years = Curs(os) d'avaluació del procediment
+supervision.titulations = Titulació(ns)
+supervision.centers = Centre(s)
+supervision.titulations.all = Totes
+supervision.titulations.all.g = Tots els graus del centre
+supervision.titulations.all.m = Tots els masters del centre
+supervision.procedures = Procediment(s)
+supervision.evidences = Evidència(es)
+supervision.search = Consultar
+supervision.procedures.title = Procediments que coincideixen amb els criteris seleccionats
+supervision.filter.apha = Ordenar alfabèticament
+supervision.filter.date = Ordenar per data de darrera acció
+supervision.filter.none = Eliminar filtres
+supervision.filter.download1 = Exportar llistat
+supervision.filter.download2 = Exportar per titulació
+
+##########################################
+#	MANAGERS PAGE LOCALES
+##########################################
+managers.title = Cerca de responsables
+managers.title2 = Responsables del seu centre
+managers.subtitle = Seleccione els criteris de filtrat dels responsables que desitja cercar i faça clic en "Cercar".
+managers.centers = Centre
+managers.titulations = Titulació(ns)
+managers.search = Cercar
+managers.search.title = Resultats de la cerca
+managers.noResults = No s'ha trobar cap responsable amb els criteris de cerca seleccionats
+managers.since = des del
+
+##########################################
+#	ADMIN PAGE LOCALES
+##########################################
+admin.instances.title = Instanciació de procediments
+admin.instances.subtitle = Seleccione un procediment junt amb el centre i la titulació sobre els que vol instanciar-lo
+admin.instances.error = Els següents procediments no s'han pogut instanciar
+admin.instances.done = S'han instanciat correctament els següents procediments
+admin.instances.selector.titulation = Titulació
+admin.instances.selector.center = Centre
+admin.instances.selector.procedure = Procediment
+admin.instances.selector.curs = Curs
+admin.instances.selector.taskType = Tipus de tasca
+admin.templates.title = Cerca i prova de plantilles
+admin.templates.subtitle = Seleccione un procediment junt amb el centre i la titulació sobre els que vol provar una plantilla
+admin.templates.info = Faça click sobre la plantilla que vol provar per a descarregar-la i veure els associats que s'han carregat correctament o faça click en editar per a modificar la llista d'indicadors
+admin.templates.indicators = Indicadors associats a la plantilla
+admin.templates.table.enq = Dades de enquestes
+admin.templates.table.serv = Dades del web service
+admin.actions.instance = Instanciar
+admin.actions.load = Carregar
+admin.procedures.title = Administració de procediments
+admin.procedures.subtitle = Seleccione el procediment i feu click en l'opció desitjada
+admin.procedures.action.new = Nou
+admin.procedures.action.duplicate = Duplicar
+admin.procedures.action.edit = Editar
+admin.procedures.action.remove = Esborrar
+admin.procedures.selector.procedure = Procediment
+admin.procedures.selector.years = Curs d' avaluació del procediment
+admin.procedures.selector.titulations = Titulació
+admin.procedures.selector.centers = Centre
+admin.procedures.confirm = Confirmar
+admin.procedures.info.procedure = Informació del procediment
+admin.procedures.info.task = Tasca
+admin.procedures.form.id = ID
+admin.procedures.form.nameProcedure = Codi
+admin.procedures.form.subtitleProcedure = Dimensió abreujada
+admin.procedures.form.context = Àmbit
+admin.procedures.form.version = Versió
+admin.procedures.form.activationYear = Curs d'activació
+admin.procedures.form.evalYear = Curs avaluat
+admin.procedures.form.titleCas = Titol en castellà
+admin.procedures.form.titleVal = Titol en valencià
+admin.procedures.form.descriptionCas = Descripció en castellà
+admin.procedures.form.descriptionVal = Descripció en valencià
+admin.procedures.form.nameEvCas = Nom de la evidència en castellà
+admin.procedures.form.nameEvVal = Nom de la evidència en valencià
+admin.procedures.form.comments = Informació adicional (ocult als usuaris) 
+admin.procedures.form.taskp = ID Tascap
+admin.procedures.form.limitDate = Data límit
+admin.procedures.form.options = Opcions
+admin.procedures.form.report = ¿Genera informe?
+admin.procedures.form.type = Tipus
+admin.procedures.form.type.1 = Evidència
+admin.procedures.form.type.2 = Si/No
+admin.procedures.form.type.3 = Continuar
+admin.procedures.form.evCode = Codi de l'evidència
+admin.procedures.form.next = ID tasca següent
+admin.procedures.form.next2 = ID tasca següent alternativa
+admin.procedures.form.role = Rol
+admin.procedures.form.addTask = Afegir tasca
+admin.procedures.form.removeConfirm = Si esborra aquest procediment les dades no es podràn recuperar
+admin.action.delete = Esborrar
+admin.action.deleteInstance = Esborrar procediment
+admin.action.deleteTask = Esborrar acció
+admin.action.reloadInstance = Esborrar i reiniciar procediment
+admin.action.closeInstance = Tancar procediment
+admin.action.reloadTask = Esborrar i repetir tasca
+admin.action.reactivate = Repetir tasca
+admin.action.edit = Editar tasca
+admin.action.attach = Adjuntar nova evidència
+admin.managers.add = Afegir responsable
+admin.managers.center = Centre
+admin.managers.titulation = Titulació
+admin.managers.user = Usuari
+admin.managers.firstname = Nom
+admin.managers.lastname = Cognoms
+admin.managers.email = Email
+admin.managers.role = Rol
+admin.managers.exists = Ja hi ha una entrada amb les dades seleccionades
+admin.managers.newRole = Afegir reponsable
+admin.managers.newUser = Nou usuari
+admin.stats.pending.title = Correus pendents d'enviar
+admin.stats.pending.total = Total de correus pendents d'enviar
+admin.stats.pending.username = Usuari
+admin.stats.pending.fullname = Nom
+admin.stats.pending.email = Correu
+admin.stats.sessions.title = Sesions actives en aquest moment
+admin.stats.sessions.id = ID de sessió
+admin.stats.sessions.username = Usuari
+admin.stats.sessions.last = Darrera acció
+admin.stats.sessions.expired = Expirada
+admin.stats.sessions.active.total = Total de sessions actives
+admin.stats.sessions.expired.total = Total de sessions expirades
+admin.stats.table.lengthMenu = Files
+admin.stats.table.zeroRecords = No s'han trobat resultats
+admin.stats.table.info = Pàgina _PAGE_ de _PAGES_
+admin.stats.table.infoEmpty = No hi ha dades per a mostrar
+admin.stats.table.infoFiltered = (filtrats de un total de _MAX_)
+admin.stats.table.next = Següent
+admin.stats.table.previous = Anterior
+admin.mailing.title = Correus electrònics
+admin.mailing.subtitle = Seleccione un centre i un rol al que enviar el correu
+admin.mailing.send = Enviar
+admin.mailing.center = Centre
+admin.mailing.role = Rol
+admin.mailing.subject = Assumpte
+admin.mailing.body = Missatge
+admin.mailing.sent.success = El missatge s'ha processat i serà enviat al més aviat possible 
+admin.mailing.sent.error = No s'han trobat usuaris per al rol i centres especificats
+admin.calendar.form.titleAdd = Planificar nova instància
+admin.calendar.form.titleEdit = Editar instància planificada
+admin.calendar.form.titleCopy = Copiar instancia existent
+admin.calendar.form.delete = Esborrar
+admin.calendar.form.instanciar = Instanciar
+admin.calendar.form.add = Afegir
+admin.calendar.form.date = Data
+admin.calendar.form.load = Carregar
+admin.calendar.form.changeDate = Canviar la data al
+admin.calendar.form.acredSel = Selecció per acreditació
+admin.calendar.form.input.grup.1 = Gener 
+admin.calendar.form.input.grup.4 = Abril
+admin.calendar.form.input.grup.9 = Septembre
+admin.calendar.form.input.grup.11 = Novembre
+admin.calendar.form.input.tambit.g = Graus
+admin.calendar.form.input.tambit.m = Másters
+admin.calendar.form.input.tambit.d = Doctorats
+admin.acredita.title = Resumen d'acreditacions 
+admin.acredita.form.view = Carregar
+admin.acredita.form.year = Any
+admin.acredita.form.group = Grup
+admin.acredita.form.groupnum.0 = Tots 
+admin.acredita.form.groupnum.1 = Gener 
+admin.acredita.form.groupnum.4 = Abril
+admin.acredita.form.groupnum.9 = Septembre
+admin.acredita.form.groupnum.11 = Novembre
+
+
+##########################################
+#	DASHBOARD PAGE LOCALES
+##########################################
+dashboard.header.published = Informes publicats
+dashboard.menu.summary = Resum
+dashboard.menu.procedures = Procediments SAIC
+dashboard.menu.docs = Documentació
+dashboard.menu.data = Indicadors
+dashboard.menu.tits = Titulacions
+dashboard.menu.managers = Responsables
+dashboard.summary.legend.active = Finalitzat
+dashboard.summary.legend.ontime = En curs
+dashboard.summary.legend.delayed = Endarrerit
+dashboard.summary.legend.closed = Tancat
+dashboard.data.sel.g = Dades de Grau
+dashboard.data.sel.m = Dades de Màster
+dashboard.data.sel.d = Dades de Doctorat
+dashboard.data.sel.rates = Taxes
+dashboard.data.sel.estud = Satisfacció estudiantat
+dashboard.data.sel.estud1 = Satisfacció estudiantat de 1er
+dashboard.data.sel.estud3 = Satisfacció estudiantat de 3er
+dashboard.data.sel.gradu = Satisfacció graduats/des
+dashboard.data.sel.prof = Satisfacció professorat
+dashboard.data.sel.eval = Evaluació docent
+dashboard.data.sel.ptgas = Satisfacció PTGAS
+dashboard.data.legend.1 = Titulació
+dashboard.data.legend.2 = Centre
+dashboard.data.legend.3 = Universitat
+dashboard.data.table.hr1 = Enquesta
+dashboard.data.table.hr2 = Indicador
+dashboard.data.table.hr3 = Ámbit
+dashboard.data.table.hr4 = Estudi
+dashboard.data.table.hr5 = Valor
+dashboard.data.table.hr6 = Curs
+dashboard.data.table.hr7 = CursD
+dashboard.data.table.hr8 = Tipus
+dashboard.data.table.hr9 = NºEnq
+dashboard.data.table.hr10 = Curs Enq
+dashboard.data.table.hr11 = Centre Orig
+dashboard.data.table.hr12 = Tit. Orig
+dashboard.data.table.hr13 = RUCT
+dashboard.data.table.hr14 = Data
+dashboard.data.table.btn1 = Dades del curs
+dashboard.data.table.btn2 = Exportar a Excel
+dashboard.data.table.btn3 = Exportar a CSV
+dashboard.data.table.btn4 = Exportar a PDF
+dashboard.data.table.btn5 = Mostrar/Ocultar columnes
+dashboard.data.table.btn6 = Veure gràfiques
+dashboard.data.table.btn7 = Veure totes les dades en format taula
+dashboard.acred.title.edit = Canviar dates
+dashboard.acred.date.segui = Seguiment
+dashboard.acred.date.acred = Pròxima acreditació
+dashboard.acred.date.renov = Última acreditació
+dashboard.acred.date.renov = Renovació
+dashboard.acred.date.verif = Verificació
+dashboard.acred.date.impla = Implantació
+dashboard.acred.group.year = Curs pròx. acred.
+dashboard.acred.group.name = Grup pròx. acred.
+dashboard.acred.recom.title = Recommanacions *
+dashboard.acred.recom.msg = Hi ha recomanacions d'obligat compliment derivades de la darrera renovació.
+dashboard.acred.recom.info = Indicar si hi ha recomanacions d'obligat compliment pendents
+dashboard.acred.segui.title = Seguimiento **
+dashboard.acred.segui.info = Indicar si la titulació ha de passar seguiment obligatòriament
+dashboard.acred.segui.msg = La titulació ha de passar obligatòriament un seguiment en la data indicada al camp seguiment.
+dashboard.acred.codes = Codis interns
+dashboard.acred.table.title = Titulacions del centre
+dashboard.acred.table.ruct = RUCT
+dashboard.acred.table.tit = Titulació
+dashboard.acred.table.type = Tipus
+dashboard.acred.table.next = Pròx Accr.
+dashboard.acred.table.acred = Fecha Accr.
+dashboard.acred.table.renov = Renovació
+dashboard.acred.table.verif = Verificació
+dashboard.acred.table.impla = Implantació
+dashboard.acred.table.segui = Seguiment
+dashboard.acred.table.inter = InterUniv.
+dashboard.acred.table.date = Data acreditació
+dashboard.acred.docs.title = Documentació
+dashboard.gantt.selector = Veure procediments de
+dashboard.tree.selector.title = Veure procediments de
+dashboard.tree.selector.centre = Centre i totes les titulacions
+dashboard.tits.ruct = RUCT
+dashboard.tits.codes = Codis
+dashboard.tits.centre = Centre
+dashboard.tits.tit = Titulació
+dashboard.tits.type = Tipus 
+
+##########################################
+#	DATA PAGE LOCALES
+##########################################
+data.import.title = Importador de dades
+data.import.subtitle = Les dades que importi es quedaran en estat pendent de consolidar. De la mateixa manera, només podreu consultar les dades que es trobin en aquest mateix estat.
+data.import.note1 = El fitxer CSV ha de contenir, com a mínim, les columnes "curs", "titulacio", "centre" i "tipus"
+data.import.note2 = La columna "tipus" pot pendre els valors "avg", "min" o "max"
+data.import.note3 = * Només per a importació 
+data.import.note4 = * Només per a consulta
+data.import.note5 = S'han detectat les següents columnes en el fitxer seleccionat. Si desitja que s'ignore alguna columna, desmarque-la de la llista.
+data.show.title = Consultar dades consolidades
+data.consolide.title = Consolidar dades
+data.consolide.subtitle = Seleccione el conjunt de dades que desitja consolidar
+data.consolide.err1 = S'han detectat dades duplicades en el conjunt pendent de consolidar, si us plau, corregiu-lo abans de consolidar.
+data.consolide.err2 = S'han detectat errors d'integritat entre el conjunt seleccionat i les dades ja consolidades, si us plau, corregiu-les abans de consolidar.
+data.consolide.err3 = No s'ha pogut executar l'acció, comproveu de nou la integritat de les dades.
+data.consolide.count = Nombre de registres pendents de consolidar per al conjunt seleccionat
+data.consolide.tabdesc = Es mostren els registres per als quals s'han detectat problemes
+data.consolide.nodata = No hi ha dades pendents de consolidar per al conjunt seleccionat
+data.consolide.noerr = No s'han detectat errors
+data.consolide.ok = Acció realitzada correctament
+data.consolide.actions.info = Accions automàtiques disponibles
+data.consolide.actions.action1 = Esborrar duplicats
+data.consolide.actions.action2 = Esborrar consolidades
+data.consolide.actions.action3 = Esborrar pendents
+data.consolide.actions.msg = Nombre de registres esborrats
+
+data.input.source = Tipus de dades
+data.input.survey = Grup de dades
+data.input.year = Curs
+data.input.dstyear = Curs a imputar
+data.input.scope = Àmbit
+data.input.scope.t = Titulació
+data.input.scope.c = Centre
+data.input.scope.u = Universitat
+data.input.type = Tipus Titulació
+data.input.type.g = Grau
+data.input.type.m = Màster
+data.input.type.d = Doctorat
+data.input.type.u = Global
+data.input.tit = Titulació
+data.input.cen = Centre
+data.input.file = Seleccioneu o arrossegueu ací el fitxer CSV
+data.input.select = Trieu un conjunt de dades
+data.input.option = Trieu una opció
+data.input.delim = Delimitador
+data.input.dbsource = DB d'origen
+data.input.importType = Tipus d'importació
+data.input.importType.file = Fitxer
+data.input.importType.db = Base de dades
+data.input.view = Vista/Taula a importar (nom_esquema.nom_vista)
+data.input.key = Clau
+
+data.btn.import = Importar
+data.btn.connect = Comprovar connexió
+data.btn.show = Consultar
+data.btn.delete = Eliminar
+data.btn.query = Consultar
+data.btn.check = Comprobar
+data.btn.run = Executar
+data.table.0 = Curs
+data.table.1 = Titulació
+data.table.2 = Centre
+data.table.3 = Àmbit
+data.table.4 = Tipus
+data.table.5 = Indicador
+data.table.6 = Valor
+data.table.7 = Num enq.
+data.table.8 = Titulació Orig.
+data.table.9 = Centre Orig.
+data.table.10 = RUCT
+data.table.11 = Usuari
+data.alert.connect = No s'ha pogut establir la connexió amb la vista indicada
+
+
+
+
+

+ 463 - 0
src/main/resources/messages_es.properties

@@ -0,0 +1,463 @@
+
+##########################################
+#	GLOBAL LOCALES 
+##########################################
+global.lang.va = Valencià
+global.lang.es = Castellano
+global.exit = Salir
+global.login = Acceder
+global.help = Instrucciones
+global.lang = Idioma
+global.footer.span1 = Consultas procedimentales
+global.footer.span2 = Consultas técnicas
+global.footer.span3 = Unidad de Calidad
+global.menu.tits = Panel de control
+global.menu.procedures = Tareas asignadas
+global.menu.supervision = Supervisión
+global.menu.managers = Responsables
+global.menu.admin = Administración
+global.menu.admin.stats = Información
+global.menu.admin.instances = Instanciación
+global.menu.admin.procedures = Procedimientos
+global.menu.admin.parse = Consolidar datos
+global.menu.admin.mailing = Correos
+global.menu.admin.calendar = Planificador
+global.menu.admin.acredita = Acreditaciones
+global.menu.data = Datos y plantillas
+global.menu.admin.templates = Plantillas (word)
+global.menu.admin.editor = Plantillas (online)
+global.menu.admin.import = Importar datos
+global.menu.admin.data = Datos consolidados
+global.menu.contact = Contacto
+global.menu.contact.title = Direcciones de correo electrónico de contacto
+global.accept = Aceptar
+global.edit = Editar
+global.new = Nuevo
+global.titleNew = Nuevo elemento
+global.delete = Borrar 
+global.cancel = Cancelar
+global.close = Cerrar
+global.close.confirm = Se perderán los datos no guardados
+global.save = Guardar
+global.confirm = Confirmar
+global.return = Volver
+global.loading = Cargando...
+global.titleDownload = Descargar evidencia
+global.download = Descargar
+global.deleteWarning = Atenció, esta acción no se podrá deshacer
+global.error.msg.i = Ha ocurrido un error, por favor, vuelva a intentarlo o contacte con
+global.error.msg.f = reportando el siguiente error
+global.editEvidence = Eliminar/Editar evidencia
+global.uploadLimits = Solo se permite subir un fichero, para subir más utilize un fichero comprimido
+global.uploadSelectFile = Seleccione un fichero
+global.noData = No hay datos disponibles
+global.noTasks = No tiene ninguna tarea asignada
+global.expire.notice = En 5 minutos se cerrará automaticamente la sesión por inactividad.
+global.expire.keepalive = Cierre esta alerta para seguir usando la aplicación
+global.error.msg = Ha habido un error, por favor, vuelva a intentarlo. Si el error persiste contacte con
+global.unautorized.msg = Su usuario no está autorizado a acceder a este apartado
+global.selectors.noData = No hay elementos marcados
+
+##########################################
+#	LOGIN FORM LOCALES
+##########################################
+login.h2 = Identificación UV
+login.button = Iniciar Sesión
+login.placeholder.username = Usuario
+login.placeholder.passwd = Contraseña
+login.error = El usuario o contraseña introducidos no son correctos o su usuario no está autorizado.
+login.expired = Su sesión se ha cerrado por inactividad o por acceder desde otro dispositivo/navegador, por favor, vuelva a identificarse.
+login.session.info1 = La sesión se cerrará automaticamente tras 60 minutos de inactividad.
+login.session.info2 = Por favor, guarde los cambios periodicamente.
+
+##########################################
+#	PROCEDURES MAIN PAGE LOCALES
+##########################################
+procedures.title = Tareas asignadas
+procedures.subtitle = Haga clic sobre el procedimiento para realizar la tarea
+procedures.title.tasks = Tareas asociadas al procedimiento
+procedures.title.flow = ver flujo
+procedures.center = Centro
+procedures.titulation = Titulación
+procedures.year.eval = Curso evaluado
+procedures.year.act = activado el curso
+procedures.dateLimit = Fecha límite
+procedures.dateInstance = Fecha última acción
+procedures.status = Estado
+procedures.btn.continue = Aceptar y avanzar
+procedures.btn.yes = Si
+procedures.btn.no = No
+procedures.btn.selectFile = Seleccione la evidencia
+procedures.btn.draft = Guardar borrador
+procedures.done.user = Realizada por
+procedures.done.date = Fecha de realización
+procedures.done.evidence = Evidencia aportada
+procedures.done.response = Respuesta
+procedures.done.response.s = SI
+procedures.done.response.n = NO
+procedures.status.a = Activa
+procedures.status.n = Realizada (Respuesta NO)
+procedures.status.s = Realizada (Respuesta SI)
+procedures.status.f = Realizada
+procedures.status.e = Realizada (Evidencia adjunta)
+procedures.status.i = Inactiva
+procedures.status.title.a = Tarea activa
+procedures.status.title.p = Tarea inactiva
+procedures.status.title.o = No le corresponde realizar está tarea
+procedures.status.title.r = Tarea realizada
+procedures.activeTask = Tarea activa
+procedures.olderEvidences = Evidencias generadas en el proceso anteriormente
+procedures.closed = Procedimiento finalizado
+procedures.cancelled = Procedimiento cerrado
+procedures.extensionAlert = Solo se permite subir archivos con extensión PDF
+procedures.assignedTo = Asignada a
+procedures.back = Volver
+procedures.template = Descargue la plantilla
+procedures.legend.active = Activa
+procedures.legend.other = No le corresponde
+procedures.legend.blocked = Inactiva
+procedures.legend.done = Realizada
+procedures.legend.expired = Atrasada
+procedures.list.legend.active = Activo
+procedures.list.legend.other = No le corresponde
+procedures.list.legend.done = Finalizado
+procedures.list.legend.expired = Atrasado
+procedures.otherActions = Acciones realizadas anteriormente en esta tarea
+procedures.editor.confirm = ¿Esta seguro de que desea continuar? Al enviar la evidencia ya no podrá realizar cambios sobre esta tarea.
+procedures.editor.msgsave = Borrador guardado correctamente
+procedures.session.expired = Su sesión se ha cerrado por inactividad o por acceder desde otro dispositivo/navegador. Cierre este diálogo para volver a entrar a SAIC.
+procedures.nosupported = Su navegador no soporta las funcionalidades necesarias para trabajar con SAIC. Se recomienda utilizar una versión actualizada de Chrome o Firefox.
+procedures.autosave.lastdate = Última modificación
+procedures.autosave.none = Aun no se ha guardado ningún borrador
+
+##########################################
+#	SUPERVISION PAGE LOCALES
+##########################################
+supervision.title = Supervisión de los procedimientos
+supervision.subtitle = Seleccione los criterios de filtrado de los procedimentos que desea supervisar y pulse "Consultar".
+supervision.searchBy = Buscar por
+supervision.years = Curs(os) de evaluación del procedimiento
+supervision.titulations = Titulación(es)
+supervision.titulations.all = Todos
+supervision.centers = Centro(s)
+supervision.titulations.all.g = Todos los grados del centro
+supervision.titulations.all.m = Todos los másters del centro
+supervision.procedures = Procedimiento(s)
+supervision.evidences = Evidencia(s)
+supervision.search = Consultar
+supervision.procedures.title = Procedimientos que coinciden con los criterios seleccionados
+supervision.filter.apha = Ordenar alfabéticamente
+supervision.filter.date = Ordenar por fecha de última acción
+supervision.filter.none = Eliminar filtros
+supervision.filter.download1 = Exportar listado
+supervision.filter.download2 = Exportar por titulación
+
+
+##########################################
+#	MANAGERS PAGE LOCALES
+##########################################
+managers.title = Búsqueda de responsables
+managers.title2 = Responsables de su centro
+managers.subtitle = Seleccione los criterios de filtrado de los responsables que desea buscar y pulse "Buscar".
+managers.centers = Centro
+managers.titulations = Titulación(es)
+managers.search = Buscar
+managers.search.title = Resultados de la búsqueda
+managers.noResults = No se ha encontrado ningun responsable con los criterios seleccionados
+managers.since = desde el
+
+##########################################
+#	ADMIN PAGE LOCALES
+##########################################
+admin.instances.title = Instanciación de procedimientos
+admin.instances.subtitle = Seleccione un procedimiento junto con el centro y la titulación sobre los que quiere instanciarlo
+admin.instances.error = Los siguientes procedimentos no se han podido instanciar
+admin.instances.done = Se han instanciado correctamente los siguientes procedimientos
+admin.instances.selector.titulation = Titulación
+admin.instances.selector.center = Centro
+admin.instances.selector.procedure = Procedimiento
+admin.instances.selector.curs = Curso
+admin.instances.selector.taskType = Tipo de tarea
+admin.templates.title = Búsqueda y prueba de plantillas
+admin.templates.subtitle = Seleccione un procedimiento junto con el centro y la titulación sobre los que quiere probar una plantilla
+admin.templates.info = Haga click sobre la plantilla que desea probar
+admin.templates.info = Faça click sobre la plantilla que vol provar per a descarregar-la i veure els associats que s'han carregat correctament o faça click en editar per a modificar la llista d'indicadors
+admin.templates.indicators = Indicadores asociados a la plantilla
+admin.templates.table.enq = Datos de encuestas
+admin.templates.table.serv = Datos del web service
+admin.actions.instance = Instanciar
+admin.actions.load = Cargar
+admin.procedures.title = Administración de procedimientos
+admin.procedures.subtitle = Selccione el procedimiento y haga click en la opción deseada
+admin.procedures.action.new = Nuevo
+admin.procedures.action.duplicate = Duplicar
+admin.procedures.action.edit = Editar
+admin.procedures.action.remove = Eliminar
+admin.procedures.selector.procedure = Procedimiento
+admin.procedures.selector.years = Curso de evaluación del procedimiento
+admin.procedures.selector.titulations = Titulación
+admin.procedures.selector.centers = Centro
+admin.procedures.confirm = Confirmar
+admin.procedures.info.procedure = Información del procedimiento
+admin.procedures.info.task = Tarea
+admin.procedures.form.id = ID
+admin.procedures.form.nameProcedure = Código
+admin.procedures.form.subtitleProcedure = Dimensión abreviada
+admin.procedures.form.context = Ámbito
+admin.procedures.form.version = Versión
+admin.procedures.form.activationYear = Curso de activación
+admin.procedures.form.evalYear = Curso evaluado
+admin.procedures.form.titleCas = Titulo en castellano
+admin.procedures.form.titleVal = Titulo en valenciano
+admin.procedures.form.descriptionCas = Descripción en castellano
+admin.procedures.form.descriptionVal = Descripción en valenciano
+admin.procedures.form.comments = Información adicional (oculto a los usuaris) 
+admin.procedures.form.nameEvCas = Nombre de la evidencia en castellano
+admin.procedures.form.nameEvVal = Nombre de la evidencia en valenciano
+admin.procedures.form.taskp = ID Tascap
+admin.procedures.form.limitDate = Fecha límite
+admin.procedures.form.options = Opciones
+admin.procedures.form.report = ¿Genera informe?
+admin.procedures.form.type = Tipo
+admin.procedures.form.type.1 = Evidencia
+admin.procedures.form.type.2 = Si/No
+admin.procedures.form.type.3 = Continuar
+admin.procedures.form.evCode = Código de la evidencia
+admin.procedures.form.next = ID tarea siguiente
+admin.procedures.form.next2 = ID tarea siguiente alternativa
+admin.procedures.form.role = Rol
+admin.procedures.form.addTask = Añadir tarea
+admin.procedures.form.removeConfirm = Si elimina este procedimiento los datos no se podrán recuperar
+admin.action.delete = Eliminar
+admin.action.deleteInstance = Eliminar procedimiento
+admin.action.deleteTask = Eliminar acción
+admin.action.reloadInstance = Borrar y reiniciar procedimiento
+admin.action.closeInstance = Cerrar procedimiento
+admin.action.reloadTask = Borrar y repetir tarea
+admin.action.reactivate = Repetir tarea
+admin.action.edit = Editar tarea
+admin.action.attach = Adjuntar nueva evidencia
+admin.managers.add = Añadir responsable
+admin.managers.center = Centro
+admin.managers.titulation = Titulación
+admin.managers.user = Usuario
+admin.managers.firstname = Nombre
+admin.managers.lastname = Apellidos
+admin.managers.email = Email
+admin.managers.role = Rol
+admin.managers.exists = Ya existe una entrada con los datos seleccionados
+admin.managers.newRole = Añadir responsable
+admin.managers.newUser = Nuevo usuario
+admin.stats.pending.title = Correos pendientes de enviar
+admin.stats.pending.total = Total de correos pendientes de enviar
+admin.stats.pending.username = Usuario
+admin.stats.pending.fullname = Nombre
+admin.stats.pending.email = Correo
+admin.stats.sessions.title = Sesiones activas en este instante
+admin.stats.sessions.id = ID de sesión
+admin.stats.sessions.username = Usuario
+admin.stats.sessions.last = Última acción
+admin.stats.sessions.expired = Expirada
+admin.stats.sessions.active.total = Total de sesiones activas
+admin.stats.sessions.expired.total = Total de sesionrs expiradas
+admin.stats.table.lengthMenu = Filas
+admin.stats.table.zeroRecords = No se han encontrado resultados
+admin.stats.table.info = Página _PAGE_ de _PAGES_
+admin.stats.table.infoEmpty = No hay datos para mostrar
+admin.stats.table.infoFiltered = (filtradas de un total de _MAX_)
+admin.stats.table.next = Siguiente
+admin.stats.table.previous = Anterior
+admin.mailing.title = Correos electronicos
+admin.mailing.subtitle = Seleccione un centro y un rol al que enviar el correo
+admin.mailing.send = Enviar
+admin.mailing.center = Centro
+admin.mailing.role = Rol
+admin.mailing.subject = Asunto
+admin.mailing.body = Mensaje
+admin.mailing.sent.success = El mensaje se ha procesado correctamente y será enviado lo antes posible
+admin.mailing.sent.error = No se han encontrado usuarios para el rol y centros especificados
+admin.calendar.form.titleAdd = Planificar nueva instancia
+admin.calendar.form.titleEdit = Editar instancia planificada
+admin.calendar.form.titleCopy = Copiar instancia existente
+admin.calendar.form.delete = Eliminar
+admin.calendar.form.instanciar = Instanciar
+admin.calendar.form.add = Añadir
+admin.calendar.form.date = Fecha
+admin.calendar.form.load = Cargar
+admin.calendar.form.changeDate = ¿Cambiar la fecha al
+admin.calendar.form.acredSel = Selección por acreditación
+admin.calendar.form.input.grup.1 = Enero 
+admin.calendar.form.input.grup.4 = Abril
+admin.calendar.form.input.grup.9 = Septiembre
+admin.calendar.form.input.grup.11 = Noviembre
+admin.calendar.form.input.tambit.g = Grados
+admin.calendar.form.input.tambit.m = Másters
+admin.calendar.form.input.tambit.d = Doctorados
+admin.acredita.title = Resumen de acreditaciones 
+admin.acredita.form.view = Cargar
+admin.acredita.form.year = Año
+admin.acredita.form.group = Grupo
+admin.acredita.form.groupnum.0 = Todos 
+admin.acredita.form.groupnum.1 = Enero 
+admin.acredita.form.groupnum.4 = Abril
+admin.acredita.form.groupnum.9 = Septiembre
+admin.acredita.form.groupnum.11 = Noviembre
+
+##########################################
+#	DASHBOARD PAGE LOCALES
+##########################################
+dashboard.header.published = Informes publicados
+dashboard.menu.summary = Resumen
+dashboard.menu.procedures = Procedimientos SAIC
+dashboard.menu.docs = Documentación
+dashboard.menu.data = Indicadores
+dashboard.menu.tits = Titulaciones
+dashboard.menu.managers = Responsables
+dashboard.summary.legend.active = Finalizado
+dashboard.summary.legend.ontime = En curso
+dashboard.summary.legend.delayed = Atrasado
+dashboard.summary.legend.closed = Cerrado
+dashboard.data.sel.g = Datos de Grado
+dashboard.data.sel.m = Datos de Máster
+dashboard.data.sel.d = Datos de Doctorado
+dashboard.data.sel.rates = Tasas
+dashboard.data.sel.estud = Satisfacción estudiantado
+dashboard.data.sel.estud1 = Satisfacción estudiantado de 1º
+dashboard.data.sel.estud3 = Satisfacción estudiantado de 3º
+dashboard.data.sel.gradu = Satisfacción graduados/as
+dashboard.data.sel.prof = Satisfacción profesorado
+dashboard.data.sel.eval = Evaluación docente
+dashboard.data.sel.ptgas = Satisfacción del PTGAS
+dashboard.data.legend.1 = Titulación
+dashboard.data.legend.2 = Centro
+dashboard.data.legend.3 = Universidad
+dashboard.data.table.hr1 = Encuesta
+dashboard.data.table.hr2 = Indicador
+dashboard.data.table.hr3 = Ámbito
+dashboard.data.table.hr4 = Estudio
+dashboard.data.table.hr5 = Valor
+dashboard.data.table.hr6 = Curso
+dashboard.data.table.hr7 = CursoD
+dashboard.data.table.hr8 = Tipo
+dashboard.data.table.hr9 = NºEnc
+dashboard.data.table.hr10 = Curso Enc
+dashboard.data.table.hr11 = Centro Orig
+dashboard.data.table.hr12 = Tit. Orig
+dashboard.data.table.hr13 = RUCT
+dashboard.data.table.hr14 = Fecha
+dashboard.data.table.btn1 = Datos del curso
+dashboard.data.table.btn2 = Exportar a Excel
+dashboard.data.table.btn3 = Exportar a CSV
+dashboard.data.table.btn4 = Exportar a PDF
+dashboard.data.table.btn5 = Mostrar/Ocultar columnas
+dashboard.data.table.btn6 = Ver gráficas
+dashboard.data.table.btn7 = Ver todos los datos en formato tabla
+dashboard.acred.title.edit = Cambiar fechas
+dashboard.acred.date.segui = Seguimiento
+dashboard.acred.date.acred = Próxima acreditación
+dashboard.acred.date.renov = Última acreditación
+dashboard.acred.date.verif = Verificación
+dashboard.acred.date.impla = Implantación
+dashboard.acred.group.year = Curso próx. acred.
+dashboard.acred.group.name = Grupo próx. acred.
+dashboard.acred.recom.title = Recomendaciones *
+dashboard.acred.recom.msg = Hay recomendaciones de obligado cumplimiento derivadas de la última renovación.
+dashboard.acred.recom.info = Indicar si hay recomendaciones de obligado cumplimiento pendientes
+dashboard.acred.segui.title = Seguimiento **
+dashboard.acred.segui.info = Indicar si la titulación tiene que hacer especial seguimiento
+dashboard.acred.segui.msg = La titulación tiene que pasar obligatoriamente un seguimiento en la fecha indicada en el campo seguimiento.
+dashboard.acred.codes = Códigos internos
+dashboard.acred.table.title = Titulaciones del centro
+dashboard.acred.table.ruct = RUCT
+dashboard.acred.table.tit = Titulación
+dashboard.acred.table.type = Tipo
+dashboard.acred.table.next = Próx Accr.
+dashboard.acred.table.acred = Fecha Accr.
+dashboard.acred.table.renov = Renovación
+dashboard.acred.table.verif = Verificación
+dashboard.acred.table.impla = Implantación
+dashboard.acred.table.segui = Seguimiento
+dashboard.acred.table.inter = InterUniv.
+dashboard.acred.docs.title = Documentación
+dashboard.gantt.selector = Ver procedimientos de
+dashboard.tree.selector.title = Ver procedimientos de
+dashboard.tree.selector.centre = Centro y todas sus titulaciones
+dashboard.tits.ruct = RUCT
+dashboard.tits.codes = Códigos
+dashboard.tits.centre = Centro
+dashboard.tits.tit = Titulación
+dashboard.tits.type = Tipo
+
+##########################################
+#	DATA PAGE LOCALES
+##########################################
+data.import.title = Importador de datos
+data.import.subtitle = Los datos que importe se quedarán en estado pendiente de consolidar. Del mismo modo, solo podrá consultar los datos que se encuentren en ese mismo estado.
+data.import.note1 = El fichero CSV debe de contener, como mínimo, las columnas "curs", "titulacio", "centre" y "tipus".
+data.import.note2 = La columna "tipus" puede tomar los valores "avg", "min" o "max"
+data.import.note3 = * Solo para importación 
+data.import.note4 = * Solo para consulta
+data.import.note5 = Se han detectado las siguientes columnas en el fichero seleccionado. Si desea que se ignore alguna columna, desmárquela de la lista.
+data.show.title = Consultar datos consolidados
+data.consolide.title = Consultar datos consolidados
+data.consolide.subtitle = Seleccione el conjunto de datos que desea consolidar
+data.consolide.err1 = Se han detectado datos duplicados en el conjunto pendiente de consolidar, por favor, corríjalo antes de consolidar.
+data.consolide.err2 = Se han detectado errores de integridad entre el conjunto seleccionado y los datos ya consolidados, por favor, corríjalos antes de consolidar.
+data.consolide.err3 = No se ha podido ejecutar la acción, compruebe de nuevo la integridad de los datos.
+data.consolide.count = Número de registros pendientes de consolidar para el conjunto seleccionado
+data.consolide.tabdesc = Se muestran los registros para los que se han detectado problemas
+data.consolide.nodata = No hay datos pendientes de consolidar para el conjunto seleccionado
+data.consolide.noerr = No se han detectado errores
+data.consolide.ok = Acción realizada correctamente
+data.consolide.actions.info = Acciones automáticas disponibles
+data.consolide.actions.action1 = Eliminar duplicados
+data.consolide.actions.action2 = Eliminar consolidados
+data.consolide.actions.action3 = Eliminar pendientes
+data.consolide.actions.msg = Número de registros eliminados
+
+data.input.source = Tipo de datos
+data.input.survey = Grupo de datos
+data.input.year = Curso
+data.input.dstyear = Curso a imputar
+data.input.scope = Ámbito
+data.input.scope.t = Titulación
+data.input.scope.c = Centro
+data.input.scope.u = Universidad
+data.input.type = Tipo Titulación
+data.input.type.g = Grado
+data.input.type.m = Máster
+data.input.type.d = Doctorado
+data.input.type.u = Global
+data.input.tit = Titulación
+data.input.cen = Centro
+data.input.file = Seleccione o arrastre aquí el archivo CSV
+data.input.select = Seleccione un conjunto de datos
+data.input.option = Seleccione una opción
+data.input.delim = Delimitador
+data.input.dbsource = DB de origen
+data.input.importType = Tipo de importación
+data.input.importType.file = Fichero CSV
+data.input.importType.db = Base de datos
+data.input.view = Vista/Tabla a importar (nombre_esquema.nombre_vista)
+data.input.key = Clau
+
+data.btn.import = Importar
+data.btn.connect = Comprobar conexión
+data.btn.show = Consultar
+data.btn.delete = Eliminar
+data.btn.query = Consultar
+data.btn.check = Comprobar
+data.btn.run = Ejecutar
+data.table.0 = Curso
+data.table.1 = Titulación
+data.table.2 = Centro
+data.table.3 = Ámbito
+data.table.4 = Tipo
+data.table.5 = Indicador
+data.table.6 = Valor
+data.table.7 = Num enc.
+data.table.8 = Titulacion Orig.
+data.table.9 = Centro Orig.
+data.table.10 = RUCT
+data.table.11 = Usuario
+data.alert.connect = No se ha podido establecer la conexión con la vista indicada

+ 13 - 0
src/test/java/es/uv/docentia/SaicApplicationTests.java

@@ -0,0 +1,13 @@
+package es.uv.docentia;
+
+//import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+
+@SpringBootTest
+class SaicApplicationTests {
+
+	//@Test
+	void contextLoads() {
+	}
+
+}