diff --git a/010_spring_boot/api_rest/api2/src/main/java/med/voll/api/infra/security/SecurityConfigurations.java b/010_spring_boot/api_rest/api2/src/main/java/med/voll/api/infra/security/SecurityConfigurations.java
index 3ef98a2..2e20567 100644
--- a/010_spring_boot/api_rest/api2/src/main/java/med/voll/api/infra/security/SecurityConfigurations.java
+++ b/010_spring_boot/api_rest/api2/src/main/java/med/voll/api/infra/security/SecurityConfigurations.java
@@ -33,12 +33,6 @@ public class SecurityConfigurations {
.and()
.addFilterBefore(securityFilter, UsernamePasswordAuthenticationFilter.class)
.build();
- //return httpSecurity.csrf().disable()
- // .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
- // .and().authorizeHttpRequests()
- // .requestMatchers(HttpMethod.POST, "/login").permitAll()
- // .anyRequest().authenticated()
- // .and().build();
}
@Bean
diff --git a/010_spring_boot/api_rest/api2/src/main/java/med/voll/api/infra/security/SecurityFilter.java b/010_spring_boot/api_rest/api2/src/main/java/med/voll/api/infra/security/SecurityFilter.java
index a6da087..4618062 100644
--- a/010_spring_boot/api_rest/api2/src/main/java/med/voll/api/infra/security/SecurityFilter.java
+++ b/010_spring_boot/api_rest/api2/src/main/java/med/voll/api/infra/security/SecurityFilter.java
@@ -30,10 +30,10 @@ public class SecurityFilter extends OncePerRequestFilter {
if (authHeader != null) {
System.out.println("- ".repeat(10) + "filtro no null"+" -".repeat(10));
var token = authHeader.replace("Bearer ", "");
- var subject = tokenService.getSubject(token);
- if (subject != null) {
+ var nombreUsuario = tokenService.getSubject(token);
+ if (nombreUsuario != null) {
// token válido
- var usuario = usuarioRepository.findByLogin(subject);
+ var usuario = usuarioRepository.findByLogin(nombreUsuario);
var authentication = new UsernamePasswordAuthenticationToken(
usuario,
null,
diff --git a/010_spring_boot/api_rest/api3/mvnw b/010_spring_boot/api_rest/api3/mvnw
new file mode 100755
index 0000000..66df285
--- /dev/null
+++ b/010_spring_boot/api_rest/api3/mvnw
@@ -0,0 +1,308 @@
+#!/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.
+# ----------------------------------------------------------------------------
+
+# ----------------------------------------------------------------------------
+# Apache Maven Wrapper startup batch script, version 3.2.0
+#
+# Required ENV vars:
+# ------------------
+# JAVA_HOME - location of a JDK home dir
+#
+# Optional ENV vars
+# -----------------
+# 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 /usr/local/etc/mavenrc ] ; then
+ . /usr/local/etc/mavenrc
+ fi
+
+ 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
+ JAVA_HOME="$(/usr/libexec/java_home)"; export JAVA_HOME
+ else
+ JAVA_HOME="/Library/Java/Home"; export JAVA_HOME
+ fi
+ fi
+ ;;
+esac
+
+if [ -z "$JAVA_HOME" ] ; then
+ if [ -r /etc/gentoo-release ] ; then
+ JAVA_HOME=$(java-config --jre-home)
+ fi
+fi
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched
+if $cygwin ; then
+ [ -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 "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] &&
+ JAVA_HOME="$(cd "$JAVA_HOME" || (echo "cannot cd into $JAVA_HOME."; exit 1); pwd)"
+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="$(\unset -f command 2>/dev/null; \command -v 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
+
+# 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/.." || exit 1; pwd)
+ fi
+ # end of workaround
+ done
+ printf '%s' "$(cd "$basedir" || exit 1; pwd)"
+}
+
+# concatenates all lines of a file
+concat_lines() {
+ if [ -f "$1" ]; then
+ # Remove \r in case we run on Windows within Git Bash
+ # and check out the repository with auto CRLF management
+ # enabled. Otherwise, we may read lines that are delimited with
+ # \r\n and produce $'-Xarg\r' rather than -Xarg due to word
+ # splitting rules.
+ tr -s '\r\n' ' ' < "$1"
+ fi
+}
+
+log() {
+ if [ "$MVNW_VERBOSE" = true ]; then
+ printf '%s\n' "$1"
+ fi
+}
+
+BASE_DIR=$(find_maven_basedir "$(dirname "$0")")
+if [ -z "$BASE_DIR" ]; then
+ exit 1;
+fi
+
+MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR
+log "$MAVEN_PROJECTBASEDIR"
+
+##########################################################################################
+# 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.
+##########################################################################################
+wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar"
+if [ -r "$wrapperJarPath" ]; then
+ log "Found $wrapperJarPath"
+else
+ log "Couldn't find $wrapperJarPath, downloading it ..."
+
+ if [ -n "$MVNW_REPOURL" ]; then
+ wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
+ else
+ wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
+ fi
+ while IFS="=" read -r key value; do
+ # Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' )
+ safeValue=$(echo "$value" | tr -d '\r')
+ case "$key" in (wrapperUrl) wrapperUrl="$safeValue"; break ;;
+ esac
+ done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties"
+ log "Downloading from: $wrapperUrl"
+
+ if $cygwin; then
+ wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath")
+ fi
+
+ if command -v wget > /dev/null; then
+ log "Found wget ... using wget"
+ [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet"
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
+ else
+ wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
+ fi
+ elif command -v curl > /dev/null; then
+ log "Found curl ... using curl"
+ [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent"
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath"
+ else
+ curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath"
+ fi
+ else
+ log "Falling back to using Java to download"
+ javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java"
+ javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class"
+ # For Cygwin, switch paths to Windows format before running javac
+ if $cygwin; then
+ javaSource=$(cygpath --path --windows "$javaSource")
+ javaClass=$(cygpath --path --windows "$javaClass")
+ fi
+ if [ -e "$javaSource" ]; then
+ if [ ! -e "$javaClass" ]; then
+ log " - Compiling MavenWrapperDownloader.java ..."
+ ("$JAVA_HOME/bin/javac" "$javaSource")
+ fi
+ if [ -e "$javaClass" ]; then
+ log " - Running MavenWrapperDownloader.java ..."
+ ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath"
+ fi
+ fi
+ fi
+fi
+##########################################################################################
+# End of extension
+##########################################################################################
+
+# If specified, validate the SHA-256 sum of the Maven wrapper jar file
+wrapperSha256Sum=""
+while IFS="=" read -r key value; do
+ case "$key" in (wrapperSha256Sum) wrapperSha256Sum=$value; break ;;
+ esac
+done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties"
+if [ -n "$wrapperSha256Sum" ]; then
+ wrapperSha256Result=false
+ if command -v sha256sum > /dev/null; then
+ if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c > /dev/null 2>&1; then
+ wrapperSha256Result=true
+ fi
+ elif command -v shasum > /dev/null; then
+ if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c > /dev/null 2>&1; then
+ wrapperSha256Result=true
+ fi
+ else
+ echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available."
+ echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties."
+ exit 1
+ fi
+ if [ $wrapperSha256Result = false ]; then
+ echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2
+ echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2
+ echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2
+ exit 1
+ fi
+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 "$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
+
+# Provide a "standardized" way to retrieve the CLI args that will
+# work with both Windows and non-Windows executions.
+MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*"
+export MAVEN_CMD_LINE_ARGS
+
+WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+# shellcheck disable=SC2086 # safe args
+exec "$JAVACMD" \
+ $MAVEN_OPTS \
+ $MAVEN_DEBUG_OPTS \
+ -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
+ "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
+ ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
diff --git a/010_spring_boot/api_rest/api3/mvnw.cmd b/010_spring_boot/api_rest/api3/mvnw.cmd
new file mode 100644
index 0000000..95ba6f5
--- /dev/null
+++ b/010_spring_boot/api_rest/api3/mvnw.cmd
@@ -0,0 +1,205 @@
+@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 Apache Maven Wrapper startup batch script, version 3.2.0
+@REM
+@REM Required ENV vars:
+@REM JAVA_HOME - location of a JDK home dir
+@REM
+@REM Optional ENV vars
+@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 keystroke 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 by 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 "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %*
+if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\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 WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
+
+FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
+ IF "%%A"=="wrapperUrl" SET WRAPPER_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% (
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Found %WRAPPER_JAR%
+ )
+) else (
+ if not "%MVNW_REPOURL%" == "" (
+ SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
+ )
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Couldn't find %WRAPPER_JAR%, downloading it ...
+ echo Downloading from: %WRAPPER_URL%
+ )
+
+ powershell -Command "&{"^
+ "$webclient = new-object System.Net.WebClient;"^
+ "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
+ "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
+ "}"^
+ "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^
+ "}"
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Finished downloading %WRAPPER_JAR%
+ )
+)
+@REM End of extension
+
+@REM If specified, validate the SHA-256 sum of the Maven wrapper jar file
+SET WRAPPER_SHA_256_SUM=""
+FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
+ IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B
+)
+IF NOT %WRAPPER_SHA_256_SUM%=="" (
+ powershell -Command "&{"^
+ "$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^
+ "If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^
+ " Write-Output 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^
+ " Write-Output 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^
+ " Write-Output 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^
+ " exit 1;"^
+ "}"^
+ "}"
+ if ERRORLEVEL 1 goto error
+)
+
+@REM Provide a "standardized" way to retrieve the CLI args that will
+@REM work with both Windows and non-Windows executions.
+set MAVEN_CMD_LINE_ARGS=%*
+
+%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 "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat"
+if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\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%
+
+cmd /C exit /B %ERROR_CODE%
diff --git a/010_spring_boot/api_rest/api3/pom.xml b/010_spring_boot/api_rest/api3/pom.xml
new file mode 100644
index 0000000..cdec0b2
--- /dev/null
+++ b/010_spring_boot/api_rest/api3/pom.xml
@@ -0,0 +1,99 @@
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 3.1.3
+
+
+ med.voll
+ api
+ 0.0.1-SNAPSHOT
+ api
+ API Rest para clínica Voll
+
+ 17
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.springframework.boot
+ spring-boot-devtools
+ runtime
+ true
+
+
+ org.projectlombok
+ lombok
+ true
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+ org.springframework.boot
+ spring-boot-starter-data-jpa
+
+
+ org.flywaydb
+ flyway-core
+
+
+ org.flywaydb
+ flyway-mysql
+
+
+
+ com.mysql
+ mysql-connector-j
+ runtime
+
+
+
+ org.springframework.boot
+ spring-boot-starter-validation
+
+
+
+ org.springframework.boot
+ spring-boot-starter-security
+
+
+ org.springframework.security
+ spring-security-test
+ test
+
+
+
+ com.auth0
+ java-jwt
+ 4.4.0
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+ org.projectlombok
+ lombok
+
+
+
+
+
+
+
+
diff --git a/010_spring_boot/api_rest/api3/src/main/java/med/voll/api/ApiApplication.java b/010_spring_boot/api_rest/api3/src/main/java/med/voll/api/ApiApplication.java
new file mode 100644
index 0000000..e08ef48
--- /dev/null
+++ b/010_spring_boot/api_rest/api3/src/main/java/med/voll/api/ApiApplication.java
@@ -0,0 +1,13 @@
+package med.voll.api;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class ApiApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(ApiApplication.class, args);
+ }
+
+}
diff --git a/010_spring_boot/api_rest/api3/src/main/java/med/voll/api/controller/AutenticacionController.java b/010_spring_boot/api_rest/api3/src/main/java/med/voll/api/controller/AutenticacionController.java
new file mode 100644
index 0000000..53d93ba
--- /dev/null
+++ b/010_spring_boot/api_rest/api3/src/main/java/med/voll/api/controller/AutenticacionController.java
@@ -0,0 +1,38 @@
+package med.voll.api.controller;
+
+import jakarta.validation.Valid;
+import med.voll.api.domain.usuario.DatosAutenticacionUsuario;
+import med.voll.api.domain.usuario.Usuario;
+import med.voll.api.infra.security.DatosJWTtoken;
+import med.voll.api.infra.security.TokenService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+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;
+
+@RestController
+@RequestMapping("/login")
+public class AutenticacionController {
+
+ @Autowired
+ private AuthenticationManager authenticationManager;
+
+ @Autowired
+ private TokenService tokenService;
+
+ @PostMapping
+ public ResponseEntity autenticarUsuario(@RequestBody @Valid DatosAutenticacionUsuario datosAutenticacionUsuario) {
+ Authentication authtoken = new UsernamePasswordAuthenticationToken(
+ datosAutenticacionUsuario.login(),
+ datosAutenticacionUsuario.clave());
+ var usuarioAutenticado = authenticationManager.authenticate(authtoken);
+ var JWTtoken = tokenService.generarToken((Usuario) usuarioAutenticado.getPrincipal());
+ return ResponseEntity.ok(new DatosJWTtoken(JWTtoken));
+ }
+
+}
diff --git a/010_spring_boot/api_rest/api3/src/main/java/med/voll/api/controller/ConsultaController.java b/010_spring_boot/api_rest/api3/src/main/java/med/voll/api/controller/ConsultaController.java
new file mode 100644
index 0000000..14e7caf
--- /dev/null
+++ b/010_spring_boot/api_rest/api3/src/main/java/med/voll/api/controller/ConsultaController.java
@@ -0,0 +1,32 @@
+package med.voll.api.controller;
+
+import jakarta.validation.Valid;
+import med.voll.api.domain.consulta.AgendaDeConsultaService;
+import med.voll.api.domain.consulta.DatosAgendarConsulta;
+import med.voll.api.domain.consulta.DatosDetalleConsulta;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Controller;
+import org.springframework.transaction.annotation.Transactional;
+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.ResponseBody;
+
+@Controller
+@ResponseBody
+@RequestMapping("/consultas")
+public class ConsultaController {
+
+ @Autowired
+ private AgendaDeConsultaService service;
+
+ @PostMapping
+ @Transactional // ojo de donde se importa esta anotación
+ public ResponseEntity agendar(@RequestBody @Valid DatosAgendarConsulta datos) {
+ System.out.println(datos);
+ service.agendar(datos);
+
+ return ResponseEntity.ok(new DatosDetalleConsulta(null, null, null, null));
+ }
+}
diff --git a/010_spring_boot/api_rest/api3/src/main/java/med/voll/api/controller/HelloController.java b/010_spring_boot/api_rest/api3/src/main/java/med/voll/api/controller/HelloController.java
new file mode 100644
index 0000000..e4d9877
--- /dev/null
+++ b/010_spring_boot/api_rest/api3/src/main/java/med/voll/api/controller/HelloController.java
@@ -0,0 +1,16 @@
+package med.voll.api.controller;
+
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequestMapping("/hello")
+public class HelloController {
+
+ @GetMapping
+ public String helloWorld() {
+ return "Hello World! Test AutoReload";
+ }
+
+}
diff --git a/010_spring_boot/api_rest/api3/src/main/java/med/voll/api/controller/MedicoController.java b/010_spring_boot/api_rest/api3/src/main/java/med/voll/api/controller/MedicoController.java
new file mode 100644
index 0000000..6e1f110
--- /dev/null
+++ b/010_spring_boot/api_rest/api3/src/main/java/med/voll/api/controller/MedicoController.java
@@ -0,0 +1,71 @@
+package med.voll.api.controller;
+
+import jakarta.transaction.Transactional;
+import jakarta.validation.Valid;
+import med.voll.api.domain.direccion.DatosDireccion;
+import med.voll.api.domain.medico.*;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.web.PageableDefault;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.util.UriComponentsBuilder;
+
+import java.net.URI;
+
+@RestController
+@RequestMapping("/medicos")
+public class MedicoController {
+
+ @Autowired
+ private MedicoRepository medicoRepository;
+
+ @PostMapping
+ public ResponseEntity registrarMedico(
+ @RequestBody @Valid DatosRegistroMedico datosRegistroMedico,
+ UriComponentsBuilder uriComponentsBuilder) {
+ Medico medico = medicoRepository.save(new Medico(datosRegistroMedico));
+ DatosRespuestaMedico datosRespuestaMedico = new DatosRespuestaMedico(
+ medico, new DatosDireccion(medico.getDireccion())
+ );
+ URI url = uriComponentsBuilder.path("/medicos/{id}") .buildAndExpand(medico.getId()).toUri();
+ return ResponseEntity.created(url).body(datosRespuestaMedico);
+ }
+
+ @GetMapping
+ public ResponseEntity> listadoMedicos(
+ @PageableDefault(size = 5) Pageable paginacion) {
+ return ResponseEntity.ok(medicoRepository.findByActivoTrue(paginacion).map(DatosListadoMedicos::new));
+ }
+
+ @PutMapping
+ @Transactional
+ public ResponseEntity actualizarMedico(
+ @RequestBody @Valid DatosActualizarMedico datosActualizarMedico) {
+ Medico medico = medicoRepository.getReferenceById(datosActualizarMedico.id());
+ medico.actualizarDatos(datosActualizarMedico);
+ DatosRespuestaMedico datosRespuestaMedico = new DatosRespuestaMedico(
+ medico, new DatosDireccion(medico.getDireccion())
+ );
+ return ResponseEntity.ok(datosRespuestaMedico);
+ }
+
+ @DeleteMapping("/{id}")
+ @Transactional
+ public ResponseEntity eliminarMedico(@PathVariable Long id) {
+ Medico medico = medicoRepository.getReferenceById(id);
+ medico.desactivarMedico();
+ return ResponseEntity.noContent().build();
+ }
+
+ @GetMapping("/{id}")
+ public ResponseEntity retornaDatosMedico(@PathVariable Long id) {
+ Medico medico = medicoRepository.getReferenceById(id);
+ DatosRespuestaMedico datosRespuestaMedico = new DatosRespuestaMedico(
+ medico, new DatosDireccion(medico.getDireccion())
+ );
+ return ResponseEntity.ok(datosRespuestaMedico);
+ }
+
+}
diff --git a/010_spring_boot/api_rest/api3/src/main/java/med/voll/api/controller/PacienteController.java b/010_spring_boot/api_rest/api3/src/main/java/med/voll/api/controller/PacienteController.java
new file mode 100644
index 0000000..3b978ee
--- /dev/null
+++ b/010_spring_boot/api_rest/api3/src/main/java/med/voll/api/controller/PacienteController.java
@@ -0,0 +1,76 @@
+package med.voll.api.controller;
+
+import jakarta.transaction.Transactional;
+import jakarta.validation.Valid;
+import med.voll.api.domain.direccion.DatosDireccion;
+import med.voll.api.domain.paciente.*;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.web.PageableDefault;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.util.UriComponentsBuilder;
+
+import java.net.URI;
+
+@RestController
+@RequestMapping("/pacientes")
+public class PacienteController {
+
+ /* No es recomendable usar @Autowired a nivel de declaración pues genera
+ problemas al realizar pruebas unitarias */
+ @Autowired
+ private PacienteRepository pacienteRepository;
+
+ @PostMapping
+ public ResponseEntity registrarPaciente(
+ @RequestBody @Valid DatosRegistroPaciente datosRegistroPaciente,
+ UriComponentsBuilder uriComponentsBuilder) {
+ Paciente paciente = pacienteRepository.save(new Paciente(datosRegistroPaciente));
+ DatosRespuestaPaciente datosRespuestaPaciente = new DatosRespuestaPaciente(
+ paciente, new DatosDireccion(paciente.getDireccion())
+ );
+ URI url = uriComponentsBuilder.path("/pacientes/{id}") .buildAndExpand(paciente.getId()).toUri();
+ return ResponseEntity.created(url).body(datosRespuestaPaciente);
+ }
+
+ @GetMapping
+ public ResponseEntity> listadoPacientes(
+ @PageableDefault(size = 5) Pageable paginacion) {
+ return ResponseEntity.ok(
+ pacienteRepository.findByActivoTrue(paginacion).map(DatosListadoPacientes::new)
+ );
+ }
+
+ @PutMapping
+ @Transactional
+ public ResponseEntity actualizarPaciente(
+ @RequestBody @Valid DatosActualizarPaciente datosActualizarPaciente) {
+ Paciente paciente = pacienteRepository.getReferenceById(datosActualizarPaciente.id());
+ paciente.actualizarDatos(datosActualizarPaciente);
+ DatosRespuestaPaciente datosRespuestaPaciente = new DatosRespuestaPaciente(
+ paciente, new DatosDireccion(paciente.getDireccion())
+ );
+ return ResponseEntity.ok(datosRespuestaPaciente);
+ }
+
+ // Desactivar Paciente
+ @DeleteMapping("/{id}")
+ @Transactional
+ public ResponseEntity eliminarPaciente(@PathVariable Long id) {
+ Paciente paciente = pacienteRepository.getReferenceById(id);
+ paciente.desactivarPaciente();
+ return ResponseEntity.noContent().build();
+ }
+
+ @GetMapping("/{id}")
+ public ResponseEntity retornaDatosPaciente(@PathVariable Long id) {
+ Paciente paciente = pacienteRepository.getReferenceById(id);
+ DatosRespuestaPaciente datosRespuestaPaciente = new DatosRespuestaPaciente(
+ paciente, new DatosDireccion(paciente.getDireccion())
+ );
+ return ResponseEntity.ok(datosRespuestaPaciente);
+ }
+
+}
diff --git a/010_spring_boot/api_rest/api3/src/main/java/med/voll/api/domain/consulta/AgendaDeConsultaService.java b/010_spring_boot/api_rest/api3/src/main/java/med/voll/api/domain/consulta/AgendaDeConsultaService.java
new file mode 100644
index 0000000..bb154c6
--- /dev/null
+++ b/010_spring_boot/api_rest/api3/src/main/java/med/voll/api/domain/consulta/AgendaDeConsultaService.java
@@ -0,0 +1,45 @@
+package med.voll.api.domain.consulta;
+
+import med.voll.api.domain.medico.Medico;
+import med.voll.api.domain.medico.MedicoRepository;
+import med.voll.api.domain.paciente.Paciente;
+import med.voll.api.domain.paciente.PacienteRepository;
+import med.voll.api.infra.errores.ValidacionDeIntegridad;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+public class AgendaDeConsultaService {
+
+ @Autowired
+ private ConsultaRepository consultaRepository;
+ @Autowired
+ private MedicoRepository medicoRepository;
+ @Autowired
+ private PacienteRepository pacienteRepository;
+
+ public void agendar(DatosAgendarConsulta datos) {
+
+ if (!pacienteRepository.findById(datos.idPaciente()).isPresent()) {
+ throw new ValidacionDeIntegridad("Id de paciente no encontrado");
+ }
+ if (datos.idMedico() != null && !medicoRepository.existsById(datos.idMedico())) {
+ throw new ValidacionDeIntegridad("Id de médico no encontrado");
+ }
+
+ var paciente = pacienteRepository.findById(datos.idPaciente()).get();
+ var medico = seleccionarMedico(datos);
+ var consulta = new Consulta(null, medico, paciente, datos.fecha());
+ consultaRepository.save(consulta);
+ }
+
+ private Medico seleccionarMedico(DatosAgendarConsulta datos) {
+ if (datos.idMedico() != null) {
+ return medicoRepository.getReferenceById(datos.idMedico());
+ }
+ if (datos.especialidad() == null) {
+ throw new ValidacionDeIntegridad("Debe indicarse una especialidad médica");
+ }
+ return medicoRepository.seleccionarMedicoConEspecialidadEnFecha(datos.especialidad(), datos.fecha());
+ }
+}
diff --git a/010_spring_boot/api_rest/api3/src/main/java/med/voll/api/domain/consulta/Consulta.java b/010_spring_boot/api_rest/api3/src/main/java/med/voll/api/domain/consulta/Consulta.java
new file mode 100644
index 0000000..f5f39f8
--- /dev/null
+++ b/010_spring_boot/api_rest/api3/src/main/java/med/voll/api/domain/consulta/Consulta.java
@@ -0,0 +1,35 @@
+package med.voll.api.domain.consulta;
+
+import jakarta.persistence.*;
+import lombok.AllArgsConstructor;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import med.voll.api.domain.medico.Medico;
+import med.voll.api.domain.paciente.Paciente;
+
+import java.time.LocalDateTime;
+
+@Table(name = "consultas")
+@Entity(name = "Consulta")
+@Getter
+@NoArgsConstructor
+@AllArgsConstructor
+@EqualsAndHashCode(of = "id")
+public class Consulta {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "medico_id")
+ private Medico medico;
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "paciente_id")
+ private Paciente paciente;
+
+ private LocalDateTime fecha;
+
+}
diff --git a/010_spring_boot/api_rest/api3/src/main/java/med/voll/api/domain/consulta/ConsultaRepository.java b/010_spring_boot/api_rest/api3/src/main/java/med/voll/api/domain/consulta/ConsultaRepository.java
new file mode 100644
index 0000000..9f6ef4f
--- /dev/null
+++ b/010_spring_boot/api_rest/api3/src/main/java/med/voll/api/domain/consulta/ConsultaRepository.java
@@ -0,0 +1,8 @@
+package med.voll.api.domain.consulta;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface ConsultaRepository extends JpaRepository {
+}
diff --git a/010_spring_boot/api_rest/api3/src/main/java/med/voll/api/domain/consulta/DatosAgendarConsulta.java b/010_spring_boot/api_rest/api3/src/main/java/med/voll/api/domain/consulta/DatosAgendarConsulta.java
new file mode 100644
index 0000000..abafbca
--- /dev/null
+++ b/010_spring_boot/api_rest/api3/src/main/java/med/voll/api/domain/consulta/DatosAgendarConsulta.java
@@ -0,0 +1,14 @@
+package med.voll.api.domain.consulta;
+
+import jakarta.validation.constraints.Future;
+import jakarta.validation.constraints.NotNull;
+import med.voll.api.domain.medico.Especialidad;
+
+import java.time.LocalDateTime;
+
+public record DatosAgendarConsulta(
+ @NotNull Long idPaciente,
+ Long idMedico,
+ @NotNull @Future LocalDateTime fecha,
+ Especialidad especialidad) {
+}
diff --git a/010_spring_boot/api_rest/api3/src/main/java/med/voll/api/domain/consulta/DatosDetalleConsulta.java b/010_spring_boot/api_rest/api3/src/main/java/med/voll/api/domain/consulta/DatosDetalleConsulta.java
new file mode 100644
index 0000000..4c75389
--- /dev/null
+++ b/010_spring_boot/api_rest/api3/src/main/java/med/voll/api/domain/consulta/DatosDetalleConsulta.java
@@ -0,0 +1,12 @@
+package med.voll.api.domain.consulta;
+
+import java.time.LocalDateTime;
+
+public record DatosDetalleConsulta(Long id, Long idPaciente, Long idMedico, LocalDateTime fecha) {
+ public DatosDetalleConsulta(Consulta consulta) {
+ this(consulta.getId(),
+ consulta.getPaciente().getId(),
+ consulta.getMedico().getId(),
+ consulta.getFecha());
+ }
+}
diff --git a/010_spring_boot/api_rest/api3/src/main/java/med/voll/api/domain/direccion/DatosDireccion.java b/010_spring_boot/api_rest/api3/src/main/java/med/voll/api/domain/direccion/DatosDireccion.java
new file mode 100644
index 0000000..5e35446
--- /dev/null
+++ b/010_spring_boot/api_rest/api3/src/main/java/med/voll/api/domain/direccion/DatosDireccion.java
@@ -0,0 +1,20 @@
+package med.voll.api.domain.direccion;
+
+import jakarta.validation.constraints.NotBlank;
+
+public record DatosDireccion(
+ @NotBlank String calle,
+ @NotBlank String distrito,
+ @NotBlank String ciudad,
+ String numero,
+ String complemento) {
+
+ public DatosDireccion(Direccion direccion){
+ this(direccion.getCalle(),
+ direccion.getDistrito(),
+ direccion.getCiudad(),
+ direccion.getNumero(),
+ direccion.getComplemento()
+ );
+ }
+}
diff --git a/010_spring_boot/api_rest/api3/src/main/java/med/voll/api/domain/direccion/Direccion.java b/010_spring_boot/api_rest/api3/src/main/java/med/voll/api/domain/direccion/Direccion.java
new file mode 100644
index 0000000..1c2073e
--- /dev/null
+++ b/010_spring_boot/api_rest/api3/src/main/java/med/voll/api/domain/direccion/Direccion.java
@@ -0,0 +1,36 @@
+package med.voll.api.domain.direccion;
+
+import jakarta.persistence.Embeddable;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+
+@Embeddable
+@Getter
+@NoArgsConstructor
+@AllArgsConstructor
+public class Direccion {
+
+ private String calle;
+ private String numero;
+ private String complemento;
+ private String distrito;
+ private String ciudad;
+
+ public Direccion(DatosDireccion direccion) {
+ this.calle = direccion.calle();
+ this.numero = direccion.numero();
+ this.complemento = direccion.complemento();
+ this.distrito = direccion.distrito();
+ this.ciudad = direccion.ciudad();
+ }
+
+ public Direccion actualizarDatosDireccion(DatosDireccion direccion) {
+ this.calle = direccion.calle();
+ this.numero = direccion.numero();
+ this.complemento = direccion.complemento();
+ this.distrito = direccion.distrito();
+ this.ciudad = direccion.ciudad();
+ return this;
+ }
+}
diff --git a/010_spring_boot/api_rest/api3/src/main/java/med/voll/api/domain/medico/DatosActualizarMedico.java b/010_spring_boot/api_rest/api3/src/main/java/med/voll/api/domain/medico/DatosActualizarMedico.java
new file mode 100644
index 0000000..1eece23
--- /dev/null
+++ b/010_spring_boot/api_rest/api3/src/main/java/med/voll/api/domain/medico/DatosActualizarMedico.java
@@ -0,0 +1,8 @@
+package med.voll.api.domain.medico;
+
+import jakarta.validation.constraints.NotNull;
+import med.voll.api.domain.direccion.DatosDireccion;
+
+public record DatosActualizarMedico(@NotNull Long id, String nombre, String documento, DatosDireccion direccion) {
+
+}
diff --git a/010_spring_boot/api_rest/api3/src/main/java/med/voll/api/domain/medico/DatosListadoMedicos.java b/010_spring_boot/api_rest/api3/src/main/java/med/voll/api/domain/medico/DatosListadoMedicos.java
new file mode 100644
index 0000000..89c365b
--- /dev/null
+++ b/010_spring_boot/api_rest/api3/src/main/java/med/voll/api/domain/medico/DatosListadoMedicos.java
@@ -0,0 +1,17 @@
+package med.voll.api.domain.medico;
+
+public record DatosListadoMedicos(
+ Long id,
+ String nombre,
+ String especialidad,
+ String documento, String email) {
+
+ public DatosListadoMedicos (Medico medico) {
+ this(medico.getId(),
+ medico.getNombre(),
+ medico.getEspecialidad().toString(),
+ medico.getDocumento(),
+ medico.getEmail());
+ }
+
+}
\ No newline at end of file
diff --git a/010_spring_boot/api_rest/api3/src/main/java/med/voll/api/domain/medico/DatosRegistroMedico.java b/010_spring_boot/api_rest/api3/src/main/java/med/voll/api/domain/medico/DatosRegistroMedico.java
new file mode 100644
index 0000000..5812ccc
--- /dev/null
+++ b/010_spring_boot/api_rest/api3/src/main/java/med/voll/api/domain/medico/DatosRegistroMedico.java
@@ -0,0 +1,17 @@
+package med.voll.api.domain.medico;
+
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.Email;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Pattern;
+import med.voll.api.domain.direccion.DatosDireccion;
+
+public record DatosRegistroMedico(
+ @NotBlank String nombre,
+ @NotBlank @Email String email,
+ @NotBlank String telefono,
+ @NotBlank @Pattern(regexp = "\\d{4,6}") String documento,
+ @NotNull Especialidad especialidad,
+ @NotNull @Valid DatosDireccion direccion
+) {}
diff --git a/010_spring_boot/api_rest/api3/src/main/java/med/voll/api/domain/medico/DatosRespuestaMedico.java b/010_spring_boot/api_rest/api3/src/main/java/med/voll/api/domain/medico/DatosRespuestaMedico.java
new file mode 100644
index 0000000..cdf1753
--- /dev/null
+++ b/010_spring_boot/api_rest/api3/src/main/java/med/voll/api/domain/medico/DatosRespuestaMedico.java
@@ -0,0 +1,19 @@
+package med.voll.api.domain.medico;
+
+import jakarta.validation.constraints.NotNull;
+import med.voll.api.domain.direccion.DatosDireccion;
+
+public record DatosRespuestaMedico(@NotNull Long id, String nombre,
+ String email, String telefono, String documento,
+ DatosDireccion direccion) {
+
+ public DatosRespuestaMedico(Medico medico, DatosDireccion direccion){
+ this(medico.getId(),
+ medico.getNombre(),
+ medico.getEmail(),
+ medico.getTelefono(),
+ medico.getDocumento(),
+ direccion);
+ }
+
+}
diff --git a/010_spring_boot/api_rest/api3/src/main/java/med/voll/api/domain/medico/Especialidad.java b/010_spring_boot/api_rest/api3/src/main/java/med/voll/api/domain/medico/Especialidad.java
new file mode 100644
index 0000000..44d3d85
--- /dev/null
+++ b/010_spring_boot/api_rest/api3/src/main/java/med/voll/api/domain/medico/Especialidad.java
@@ -0,0 +1,8 @@
+package med.voll.api.domain.medico;
+
+public enum Especialidad {
+ ORTOPEDIA,
+ CARDIOLOGIA,
+ GINECOLOGIA,
+ PEDIATRIA
+}
diff --git a/010_spring_boot/api_rest/api3/src/main/java/med/voll/api/domain/medico/Medico.java b/010_spring_boot/api_rest/api3/src/main/java/med/voll/api/domain/medico/Medico.java
new file mode 100644
index 0000000..c5c0d83
--- /dev/null
+++ b/010_spring_boot/api_rest/api3/src/main/java/med/voll/api/domain/medico/Medico.java
@@ -0,0 +1,57 @@
+package med.voll.api.domain.medico;
+
+import jakarta.persistence.*;
+import lombok.AllArgsConstructor;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import med.voll.api.domain.direccion.Direccion;
+
+@Table(name="medicos")
+@Entity(name="Medico")
+@Getter
+@NoArgsConstructor
+@AllArgsConstructor
+@EqualsAndHashCode(of = "id")
+public class Medico {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+ private String nombre;
+ private String email;
+ private String telefono;
+ private String documento;
+ private Boolean activo;
+ @Enumerated(EnumType.STRING)
+ private Especialidad especialidad;
+ @Embedded
+ private Direccion direccion;
+
+ public Medico(DatosRegistroMedico datosRegistroMedico) {
+ this.activo = true;
+ this.nombre = datosRegistroMedico.nombre();
+ this.email = datosRegistroMedico.email();
+ this.documento = datosRegistroMedico.documento();
+ this.telefono = datosRegistroMedico.telefono();
+ this.especialidad = datosRegistroMedico.especialidad();
+ this.direccion = new Direccion(datosRegistroMedico.direccion());
+ }
+
+ public void actualizarDatos(DatosActualizarMedico datosActualizarMedico) {
+ if (datosActualizarMedico.nombre() != null) {
+ this.nombre = datosActualizarMedico.nombre();
+ }
+ if (datosActualizarMedico.documento() != null) {
+ this.documento = datosActualizarMedico.documento();
+ }
+ if (datosActualizarMedico.direccion() != null) {
+ this.direccion = direccion.actualizarDatosDireccion(datosActualizarMedico.direccion());
+ }
+ }
+
+ public void desactivarMedico() {
+ this.activo = false;
+ }
+
+}
diff --git a/010_spring_boot/api_rest/api3/src/main/java/med/voll/api/domain/medico/MedicoRepository.java b/010_spring_boot/api_rest/api3/src/main/java/med/voll/api/domain/medico/MedicoRepository.java
new file mode 100644
index 0000000..b9efddf
--- /dev/null
+++ b/010_spring_boot/api_rest/api3/src/main/java/med/voll/api/domain/medico/MedicoRepository.java
@@ -0,0 +1,53 @@
+package med.voll.api.domain.medico;
+
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Query;
+
+import java.time.LocalDateTime;
+
+public interface MedicoRepository extends JpaRepository {
+ Page findByActivoTrue(Pageable paginacion);
+
+ //@Query("select Medico from Medico m where m.activo=true and m.especialidad=:especialidad")
+ //@Query("""
+ // SELECT m FROM Medico m
+ // WHERE m.activo=1 and
+ // m.especialidad=:especialidad and
+ // m.id NOT IN(
+ // SELECT c.medico.id FROM Consulta c
+ // WHERE c.fecha=:fecha
+ // ) ORDER BY RAND() LIMIT 1
+ // """)
+ //@Query("""
+ // SELECT m FROM Medico m
+ // WHERE m.activo=true
+ // AND m.especialidad=?1 AND m.id NOT IN(
+ // SELECT c.medico.id FROM Consulta c
+ // WHERE c.fecha=?2
+ // ) ORDER BY RAND() LIMIT 1
+ // """)
+ @Query("""
+ select m from Medico m
+ where m.activo= true\s
+ and
+ m.especialidad=:especialidad\s
+ and
+ m.id not in( \s
+ select c.medico.id from Consulta c
+ where
+ c.fecha=:fecha
+ )
+ order by rand()
+ limit 1
+ """)
+ Medico seleccionarMedicoConEspecialidadEnFecha(Especialidad especialidad, LocalDateTime fecha);
+
+ //@Query("""
+ // select m.activo
+ // from Medico m
+ // where m.id=:idMedico
+ // """)
+ //Boolean findActivoById(Long idMedico);
+}
diff --git a/010_spring_boot/api_rest/api3/src/main/java/med/voll/api/domain/paciente/DatosActualizarPaciente.java b/010_spring_boot/api_rest/api3/src/main/java/med/voll/api/domain/paciente/DatosActualizarPaciente.java
new file mode 100644
index 0000000..fd52cd8
--- /dev/null
+++ b/010_spring_boot/api_rest/api3/src/main/java/med/voll/api/domain/paciente/DatosActualizarPaciente.java
@@ -0,0 +1,8 @@
+package med.voll.api.domain.paciente;
+
+import jakarta.validation.constraints.NotNull;
+import med.voll.api.domain.direccion.DatosDireccion;
+
+public record DatosActualizarPaciente(@NotNull Long id, String nombre, String documento, DatosDireccion direccion) {
+
+}
diff --git a/010_spring_boot/api_rest/api3/src/main/java/med/voll/api/domain/paciente/DatosListadoPacientes.java b/010_spring_boot/api_rest/api3/src/main/java/med/voll/api/domain/paciente/DatosListadoPacientes.java
new file mode 100644
index 0000000..cd280bc
--- /dev/null
+++ b/010_spring_boot/api_rest/api3/src/main/java/med/voll/api/domain/paciente/DatosListadoPacientes.java
@@ -0,0 +1,12 @@
+package med.voll.api.domain.paciente;
+
+public record DatosListadoPacientes(Long id, String nombre, String documento, String email) {
+
+ public DatosListadoPacientes(Paciente paciente) {
+ this(paciente.getId(),
+ paciente.getNombre(),
+ paciente.getDocumento(),
+ paciente.getEmail());
+ }
+
+}
\ No newline at end of file
diff --git a/010_spring_boot/api_rest/api3/src/main/java/med/voll/api/domain/paciente/DatosRegistroPaciente.java b/010_spring_boot/api_rest/api3/src/main/java/med/voll/api/domain/paciente/DatosRegistroPaciente.java
new file mode 100644
index 0000000..2a0e13f
--- /dev/null
+++ b/010_spring_boot/api_rest/api3/src/main/java/med/voll/api/domain/paciente/DatosRegistroPaciente.java
@@ -0,0 +1,16 @@
+package med.voll.api.domain.paciente;
+
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.Email;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Pattern;
+import med.voll.api.domain.direccion.DatosDireccion;
+
+public record DatosRegistroPaciente(
+ @NotBlank String nombre,
+ @NotBlank @Email String email,
+ @NotBlank String telefono,
+ @NotBlank @Pattern(regexp = "\\d{4,6}") String documento,
+ @NotNull @Valid DatosDireccion direccion
+) {}
diff --git a/010_spring_boot/api_rest/api3/src/main/java/med/voll/api/domain/paciente/DatosRespuestaPaciente.java b/010_spring_boot/api_rest/api3/src/main/java/med/voll/api/domain/paciente/DatosRespuestaPaciente.java
new file mode 100644
index 0000000..9cfba30
--- /dev/null
+++ b/010_spring_boot/api_rest/api3/src/main/java/med/voll/api/domain/paciente/DatosRespuestaPaciente.java
@@ -0,0 +1,19 @@
+package med.voll.api.domain.paciente;
+
+import jakarta.validation.constraints.NotNull;
+import med.voll.api.domain.direccion.DatosDireccion;
+
+public record DatosRespuestaPaciente(@NotNull Long id, String nombre,
+ String email, String telefono, String documento,
+ DatosDireccion direccion) {
+
+ public DatosRespuestaPaciente(Paciente paciente, DatosDireccion direccion){
+ this(paciente.getId(),
+ paciente.getNombre(),
+ paciente.getEmail(),
+ paciente.getTelefono(),
+ paciente.getDocumento(),
+ direccion);
+ }
+
+}
diff --git a/010_spring_boot/api_rest/api3/src/main/java/med/voll/api/domain/paciente/Paciente.java b/010_spring_boot/api_rest/api3/src/main/java/med/voll/api/domain/paciente/Paciente.java
new file mode 100644
index 0000000..5cea5e6
--- /dev/null
+++ b/010_spring_boot/api_rest/api3/src/main/java/med/voll/api/domain/paciente/Paciente.java
@@ -0,0 +1,54 @@
+package med.voll.api.domain.paciente;
+
+import jakarta.persistence.*;
+import lombok.AllArgsConstructor;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import med.voll.api.domain.direccion.Direccion;
+
+@Table(name="pacientes")
+@Entity(name="Paciente")
+@Getter
+@NoArgsConstructor
+@AllArgsConstructor
+@EqualsAndHashCode(of = "id")
+public class Paciente {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+ private String nombre;
+ private String email;
+ private String telefono;
+ private String documento;
+ private Boolean activo;
+ @Embedded
+ private Direccion direccion;
+
+ public Paciente(DatosRegistroPaciente datosRegistroPaciente) {
+ this.activo = true;
+ this.nombre = datosRegistroPaciente.nombre();
+ this.email = datosRegistroPaciente.email();
+ this.documento = datosRegistroPaciente.documento();
+ this.telefono = datosRegistroPaciente.telefono();
+ this.direccion = new Direccion(datosRegistroPaciente.direccion());
+ }
+
+ public void actualizarDatos(DatosActualizarPaciente datosActualizarPaciente) {
+ if (datosActualizarPaciente.nombre() != null) {
+ this.nombre = datosActualizarPaciente.nombre();
+ }
+ if (datosActualizarPaciente.documento() != null) {
+ this.documento = datosActualizarPaciente.documento();
+ }
+ if (datosActualizarPaciente.direccion() != null) {
+ this.direccion = direccion.actualizarDatosDireccion(datosActualizarPaciente.direccion());
+ }
+ }
+
+ public void desactivarPaciente() {
+ this.activo = false;
+ }
+
+}
diff --git a/010_spring_boot/api_rest/api3/src/main/java/med/voll/api/domain/paciente/PacienteRepository.java b/010_spring_boot/api_rest/api3/src/main/java/med/voll/api/domain/paciente/PacienteRepository.java
new file mode 100644
index 0000000..ab69c37
--- /dev/null
+++ b/010_spring_boot/api_rest/api3/src/main/java/med/voll/api/domain/paciente/PacienteRepository.java
@@ -0,0 +1,9 @@
+package med.voll.api.domain.paciente;
+
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+public interface PacienteRepository extends JpaRepository {
+ Page findByActivoTrue(Pageable paginacion);
+}
diff --git a/010_spring_boot/api_rest/api3/src/main/java/med/voll/api/domain/usuario/DatosAutenticacionUsuario.java b/010_spring_boot/api_rest/api3/src/main/java/med/voll/api/domain/usuario/DatosAutenticacionUsuario.java
new file mode 100644
index 0000000..6881ca6
--- /dev/null
+++ b/010_spring_boot/api_rest/api3/src/main/java/med/voll/api/domain/usuario/DatosAutenticacionUsuario.java
@@ -0,0 +1,5 @@
+package med.voll.api.domain.usuario;
+
+public record DatosAutenticacionUsuario(String login, String clave) {
+
+}
diff --git a/010_spring_boot/api_rest/api3/src/main/java/med/voll/api/domain/usuario/Usuario.java b/010_spring_boot/api_rest/api3/src/main/java/med/voll/api/domain/usuario/Usuario.java
new file mode 100644
index 0000000..d005768
--- /dev/null
+++ b/010_spring_boot/api_rest/api3/src/main/java/med/voll/api/domain/usuario/Usuario.java
@@ -0,0 +1,63 @@
+package med.voll.api.domain.usuario;
+
+import jakarta.persistence.*;
+import lombok.AllArgsConstructor;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.core.userdetails.UserDetails;
+
+import java.util.Collection;
+import java.util.List;
+
+@Table(name = "usuarios")
+@Entity(name = "Usuario")
+@Getter
+@NoArgsConstructor
+@AllArgsConstructor
+@EqualsAndHashCode(of = "id")
+public class Usuario implements UserDetails {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+ private String login;
+ private String clave;
+
+ @Override
+ public Collection extends GrantedAuthority> getAuthorities() {
+ return List.of(new SimpleGrantedAuthority("ROLE_USER"));
+ }
+
+ @Override
+ public String getPassword() {
+ return clave;
+ }
+
+ @Override
+ public String getUsername() {
+ return login;
+ }
+
+ @Override
+ public boolean isAccountNonExpired() {
+ return true;
+ }
+
+ @Override
+ public boolean isAccountNonLocked() {
+ return true;
+ }
+
+ @Override
+ public boolean isCredentialsNonExpired() {
+ return true;
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return true;
+ }
+}
diff --git a/010_spring_boot/api_rest/api3/src/main/java/med/voll/api/domain/usuario/UsuarioRepository.java b/010_spring_boot/api_rest/api3/src/main/java/med/voll/api/domain/usuario/UsuarioRepository.java
new file mode 100644
index 0000000..f7c70d8
--- /dev/null
+++ b/010_spring_boot/api_rest/api3/src/main/java/med/voll/api/domain/usuario/UsuarioRepository.java
@@ -0,0 +1,9 @@
+package med.voll.api.domain.usuario;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsService;
+
+public interface UsuarioRepository extends JpaRepository {
+ UserDetails findByLogin(String login);
+}
diff --git a/010_spring_boot/api_rest/api3/src/main/java/med/voll/api/infra/errores/ManejadorDeErrores.java b/010_spring_boot/api_rest/api3/src/main/java/med/voll/api/infra/errores/ManejadorDeErrores.java
new file mode 100644
index 0000000..fe6db0c
--- /dev/null
+++ b/010_spring_boot/api_rest/api3/src/main/java/med/voll/api/infra/errores/ManejadorDeErrores.java
@@ -0,0 +1,36 @@
+package med.voll.api.infra.errores;
+
+import jakarta.persistence.EntityNotFoundException;
+import org.springframework.dao.DataIntegrityViolationException;
+import org.springframework.http.ResponseEntity;
+import org.springframework.validation.FieldError;
+import org.springframework.web.bind.MethodArgumentNotValidException;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+
+@RestControllerAdvice
+public class ManejadorDeErrores {
+
+ @ExceptionHandler(EntityNotFoundException.class)
+ public ResponseEntity manejarError404(){
+ return ResponseEntity.notFound().build();
+ }
+
+ @ExceptionHandler(MethodArgumentNotValidException.class)
+ public ResponseEntity manejarError400(MethodArgumentNotValidException e){
+ var errores = e.getFieldErrors().stream().map(DatosErrorValidacion::new).toList();
+ return ResponseEntity.badRequest().body(errores);
+ }
+
+ @ExceptionHandler(DataIntegrityViolationException.class)
+ public ResponseEntity manejarError500(DataIntegrityViolationException e) {
+ var errores = e.getMostSpecificCause().getLocalizedMessage();
+ return ResponseEntity.badRequest().body(errores);
+ }
+
+ private record DatosErrorValidacion(String campo, String error) {
+ public DatosErrorValidacion(FieldError error) {
+ this(error.getField(), error.getDefaultMessage());
+ }
+ }
+}
diff --git a/010_spring_boot/api_rest/api3/src/main/java/med/voll/api/infra/errores/ValidacionDeIntegridad.java b/010_spring_boot/api_rest/api3/src/main/java/med/voll/api/infra/errores/ValidacionDeIntegridad.java
new file mode 100644
index 0000000..32604b4
--- /dev/null
+++ b/010_spring_boot/api_rest/api3/src/main/java/med/voll/api/infra/errores/ValidacionDeIntegridad.java
@@ -0,0 +1,7 @@
+package med.voll.api.infra.errores;
+
+public class ValidacionDeIntegridad extends RuntimeException {
+ public ValidacionDeIntegridad(String s) {
+ super(s);
+ }
+}
diff --git a/010_spring_boot/api_rest/api3/src/main/java/med/voll/api/infra/security/AutenticacionService.java b/010_spring_boot/api_rest/api3/src/main/java/med/voll/api/infra/security/AutenticacionService.java
new file mode 100644
index 0000000..14f0add
--- /dev/null
+++ b/010_spring_boot/api_rest/api3/src/main/java/med/voll/api/infra/security/AutenticacionService.java
@@ -0,0 +1,20 @@
+package med.voll.api.infra.security;
+
+import med.voll.api.domain.usuario.UsuarioRepository;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+import org.springframework.stereotype.Service;
+
+@Service
+public class AutenticacionService implements UserDetailsService {
+
+ @Autowired
+ private UsuarioRepository usuarioRepository;
+
+ @Override
+ public UserDetails loadUserByUsername(String login) throws UsernameNotFoundException {
+ return usuarioRepository.findByLogin(login);
+ }
+}
diff --git a/010_spring_boot/api_rest/api3/src/main/java/med/voll/api/infra/security/DatosJWTtoken.java b/010_spring_boot/api_rest/api3/src/main/java/med/voll/api/infra/security/DatosJWTtoken.java
new file mode 100644
index 0000000..f586187
--- /dev/null
+++ b/010_spring_boot/api_rest/api3/src/main/java/med/voll/api/infra/security/DatosJWTtoken.java
@@ -0,0 +1,4 @@
+package med.voll.api.infra.security;
+
+public record DatosJWTtoken(String jwTtoken) {
+}
diff --git a/010_spring_boot/api_rest/api3/src/main/java/med/voll/api/infra/security/SecurityConfigurations.java b/010_spring_boot/api_rest/api3/src/main/java/med/voll/api/infra/security/SecurityConfigurations.java
new file mode 100644
index 0000000..2e20567
--- /dev/null
+++ b/010_spring_boot/api_rest/api3/src/main/java/med/voll/api/infra/security/SecurityConfigurations.java
@@ -0,0 +1,49 @@
+package med.voll.api.infra.security;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.http.HttpMethod;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
+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.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.security.web.SecurityFilterChain;
+import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
+
+@Configuration
+@EnableWebSecurity
+public class SecurityConfigurations {
+
+ @Autowired
+ private SecurityFilter securityFilter;
+
+ @Bean
+ public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
+ return httpSecurity.csrf().disable().sessionManagement()
+ .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
+ .and().authorizeHttpRequests()
+ .requestMatchers(HttpMethod.POST, "/login")
+ .permitAll()
+ .anyRequest()
+ .authenticated()
+ .and()
+ .addFilterBefore(securityFilter, UsernamePasswordAuthenticationFilter.class)
+ .build();
+ }
+
+ @Bean
+ public AuthenticationManager authenticationManager(
+ AuthenticationConfiguration authenticationConfiguration) throws Exception {
+ return authenticationConfiguration.getAuthenticationManager();
+ }
+
+ @Bean
+ public PasswordEncoder passwordEncoder () {
+ return new BCryptPasswordEncoder();
+ }
+
+}
diff --git a/010_spring_boot/api_rest/api3/src/main/java/med/voll/api/infra/security/SecurityFilter.java b/010_spring_boot/api_rest/api3/src/main/java/med/voll/api/infra/security/SecurityFilter.java
new file mode 100644
index 0000000..2a2be54
--- /dev/null
+++ b/010_spring_boot/api_rest/api3/src/main/java/med/voll/api/infra/security/SecurityFilter.java
@@ -0,0 +1,47 @@
+package med.voll.api.infra.security;
+
+import jakarta.servlet.FilterChain;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import med.voll.api.domain.usuario.UsuarioRepository;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.stereotype.Component;
+import org.springframework.web.filter.OncePerRequestFilter;
+
+import java.io.IOException;
+
+@Component
+public class SecurityFilter extends OncePerRequestFilter {
+
+ @Autowired
+ private TokenService tokenService;
+ @Autowired
+ private UsuarioRepository usuarioRepository;
+
+ @Override
+ protected void doFilterInternal(HttpServletRequest request,
+ HttpServletResponse response,
+ FilterChain filterChain
+ ) throws ServletException, IOException {
+ var authHeader = request.getHeader("Authorization");
+ if (authHeader != null) {
+ var token = authHeader.replace("Bearer ", "");
+ var nombreUsuario = tokenService.getSubject(token);
+ if (nombreUsuario != null) {
+ // token válido
+ var usuario = usuarioRepository.findByLogin(nombreUsuario);
+ var authentication = new UsernamePasswordAuthenticationToken(
+ usuario,
+ null,
+ usuario.getAuthorities() // forzar inicio de sesión
+ );
+ SecurityContextHolder.getContext().setAuthentication(authentication);
+ }
+ }
+ filterChain.doFilter(request, response);
+ }
+
+}
diff --git a/010_spring_boot/api_rest/api3/src/main/java/med/voll/api/infra/security/TokenService.java b/010_spring_boot/api_rest/api3/src/main/java/med/voll/api/infra/security/TokenService.java
new file mode 100644
index 0000000..ea6c199
--- /dev/null
+++ b/010_spring_boot/api_rest/api3/src/main/java/med/voll/api/infra/security/TokenService.java
@@ -0,0 +1,63 @@
+package med.voll.api.infra.security;
+
+import com.auth0.jwt.JWT;
+import com.auth0.jwt.algorithms.Algorithm;
+import com.auth0.jwt.exceptions.JWTCreationException;
+import com.auth0.jwt.exceptions.JWTVerificationException;
+import com.auth0.jwt.interfaces.DecodedJWT;
+import med.voll.api.domain.usuario.Usuario;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.ZoneOffset;
+
+@Service
+public class TokenService {
+
+ @Value("${api.security.secret}")
+ private String apiSecret;
+
+ public String generarToken(Usuario usuario) {
+ try {
+ Algorithm algorithm = Algorithm.HMAC256(apiSecret) ;
+ return JWT.create()
+ .withIssuer("voll med")
+ .withSubject(usuario.getLogin())
+ .withClaim("id", usuario.getId())
+ .withExpiresAt(generarFechaExpiracion())
+ .sign(algorithm);
+ } catch (JWTCreationException exception){
+ throw new RuntimeException();
+ }
+ }
+
+ public String getSubject(String token) {
+ if (token == null) {
+ throw new RuntimeException("token nulo");
+ }
+ DecodedJWT verifier = null;
+ try {
+ Algorithm algorithm = Algorithm.HMAC256(apiSecret);
+ verifier = JWT.require(algorithm)
+ // specify an specific claim validations
+ .withIssuer("voll med")
+ // reusable verifier instance
+ .build()
+ .verify(token);
+ verifier.getSubject();
+ } catch (JWTVerificationException exception) {
+ // Invalid signature/claims
+ System.out.println(exception.toString());
+ }
+ if (verifier.getSubject() == null) {
+ throw new RuntimeException("Verifier inválido");
+ }
+ return verifier.getSubject();
+ }
+
+ private Instant generarFechaExpiracion() {
+ return LocalDateTime.now().plusHours(2).toInstant(ZoneOffset.of("-03:00"));
+ }
+}
diff --git a/010_spring_boot/api_rest/api3/src/main/resources/application.properties b/010_spring_boot/api_rest/api3/src/main/resources/application.properties
new file mode 100644
index 0000000..88e1c83
--- /dev/null
+++ b/010_spring_boot/api_rest/api3/src/main/resources/application.properties
@@ -0,0 +1,13 @@
+spring.datasource.url=jdbc:mysql://${DB_URL}
+spring.datasource.username=${DB_USER}
+spring.datasource.password=${DB_PASS}
+
+spring.jpa.show-sql=true
+spring.jpa.properties.hibernate.format_sql=true
+
+server.error.include-stacktrace=never
+
+api.security.secret=${JWT_SECRET}
+
+spring.main.banner-mode=CONSOLE
+spring.output.ansi.enabled=ALWAYS
\ No newline at end of file
diff --git a/010_spring_boot/api_rest/api3/src/main/resources/db/migration/V1__create-table-medicos.sql b/010_spring_boot/api_rest/api3/src/main/resources/db/migration/V1__create-table-medicos.sql
new file mode 100644
index 0000000..2601507
--- /dev/null
+++ b/010_spring_boot/api_rest/api3/src/main/resources/db/migration/V1__create-table-medicos.sql
@@ -0,0 +1,17 @@
+create table medicos(
+
+ id bigint not null auto_increment,
+ nombre varchar(100) not null,
+ email varchar(100) not null unique,
+ documento varchar(6) not null unique,
+ especialidad varchar(100) not null,
+ telefono varchar(20) not null,
+ calle varchar(100) not null,
+ distrito varchar(100) not null,
+ complemento varchar(100),
+ numero varchar(20),
+ ciudad varchar(100) not null,
+
+ primary key(id)
+
+);
\ No newline at end of file
diff --git a/010_spring_boot/api_rest/api3/src/main/resources/db/migration/V2__create-table-pacientes.sql b/010_spring_boot/api_rest/api3/src/main/resources/db/migration/V2__create-table-pacientes.sql
new file mode 100644
index 0000000..540c256
--- /dev/null
+++ b/010_spring_boot/api_rest/api3/src/main/resources/db/migration/V2__create-table-pacientes.sql
@@ -0,0 +1,16 @@
+create table pacientes(
+
+ id bigint not null auto_increment,
+ nombre varchar(100) not null,
+ email varchar(100) not null unique,
+ documento varchar(6) not null unique,
+ telefono varchar(20) not null,
+ calle varchar(100) not null,
+ distrito varchar(100) not null,
+ complemento varchar(100),
+ numero varchar(20),
+ ciudad varchar(100) not null,
+
+ primary key(id)
+
+);
\ No newline at end of file
diff --git a/010_spring_boot/api_rest/api3/src/main/resources/db/migration/V3__alter-table-medicos-add-activo.sql b/010_spring_boot/api_rest/api3/src/main/resources/db/migration/V3__alter-table-medicos-add-activo.sql
new file mode 100644
index 0000000..7b42ad7
--- /dev/null
+++ b/010_spring_boot/api_rest/api3/src/main/resources/db/migration/V3__alter-table-medicos-add-activo.sql
@@ -0,0 +1,2 @@
+alter table medicos add activo tinyint;
+update medicos set activo=1;
\ No newline at end of file
diff --git a/010_spring_boot/api_rest/api3/src/main/resources/db/migration/V4__alter-table-pacientes-add-activo.sql b/010_spring_boot/api_rest/api3/src/main/resources/db/migration/V4__alter-table-pacientes-add-activo.sql
new file mode 100644
index 0000000..68589f4
--- /dev/null
+++ b/010_spring_boot/api_rest/api3/src/main/resources/db/migration/V4__alter-table-pacientes-add-activo.sql
@@ -0,0 +1,2 @@
+alter table pacientes add activo tinyint;
+update pacientes set activo=1;
\ No newline at end of file
diff --git a/010_spring_boot/api_rest/api3/src/main/resources/db/migration/V5__create-table-usuarios.sql b/010_spring_boot/api_rest/api3/src/main/resources/db/migration/V5__create-table-usuarios.sql
new file mode 100644
index 0000000..bf6e084
--- /dev/null
+++ b/010_spring_boot/api_rest/api3/src/main/resources/db/migration/V5__create-table-usuarios.sql
@@ -0,0 +1,9 @@
+create table usuarios(
+
+ id bigint not null auto_increment,
+ login varchar(100) not null unique,
+ clave varchar(300) not null,
+
+ primary key(id)
+
+);
\ No newline at end of file
diff --git a/010_spring_boot/api_rest/api3/src/main/resources/db/migration/V6__create-table-consultas.sql b/010_spring_boot/api_rest/api3/src/main/resources/db/migration/V6__create-table-consultas.sql
new file mode 100644
index 0000000..8e94b48
--- /dev/null
+++ b/010_spring_boot/api_rest/api3/src/main/resources/db/migration/V6__create-table-consultas.sql
@@ -0,0 +1,12 @@
+CREATE TABLE consultas(
+
+ id BIGINT NOT NULL AUTO_INCREMENT,
+ medico_id BIGINT NOT NULL,
+ paciente_id BIGINT NOT NULL,
+ fecha DATETIME NOT NULL,
+
+ PRIMARY KEY(id),
+ CONSTRAINT fk_consultas_medico_id FOREIGN KEY(medico_id) REFERENCES medicos(id),
+ CONSTRAINT fk_consultas_paciente_id FOREIGN KEY(paciente_id) REFERENCES pacientes(id)
+
+);
\ No newline at end of file
diff --git a/010_spring_boot/api_rest/api3/src/test/java/med/voll/api/ApiApplicationTests.java b/010_spring_boot/api_rest/api3/src/test/java/med/voll/api/ApiApplicationTests.java
new file mode 100644
index 0000000..eb360a5
--- /dev/null
+++ b/010_spring_boot/api_rest/api3/src/test/java/med/voll/api/ApiApplicationTests.java
@@ -0,0 +1,13 @@
+package med.voll.api;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+
+@SpringBootTest
+class ApiApplicationTests {
+
+ @Test
+ void contextLoads() {
+ }
+
+}
diff --git a/010_spring_boot/spring_boot_2.md b/010_spring_boot/spring_boot_2.md
index e7f1d7d..a733dc7 100644
--- a/010_spring_boot/spring_boot_2.md
+++ b/010_spring_boot/spring_boot_2.md
@@ -951,3 +951,7 @@ Más detalles sobre la función de seguridad del método en la
documentación de
[Spring Security](https://docs.spring.io/spring-security/reference/servlet/authorization/method-security.html)
+---
+
+Continua en
+[Documentar, probar y preparar una API para su implementación](./spring_boot_3.md)
diff --git a/010_spring_boot/spring_boot_3.md b/010_spring_boot/spring_boot_3.md
index e69de29..d9d7944 100644
--- a/010_spring_boot/spring_boot_3.md
+++ b/010_spring_boot/spring_boot_3.md
@@ -0,0 +1,614 @@
+# API Rest Java - Documentar, Probar y Preparar una API para su Implementación
+
+Continuación de
+[Buenas Prácticas y Protección de una API Rest](./spring_boot_2.md) donde se
+vio:
+
+- Creación de un API Rest
+- Crud (Create, Read, Update, Delete)
+- Validaciones
+- Paginación y orden
+- Buenas prácticas REST
+- Tratamiento de errores
+- Control de acceso con JWT
+
+### Objetivos
+
+- Funcionalidad agendar consultas
+- Documentación de la API
+- Tests Automatizados
+- Build del proyecto
+
+[trello](https://trello.com/b/NiWWC55L/vollmed-api-3ra-parte) -
+[figma](https://www.figma.com/file/tWpylp7pB4n8rX1AsKTsY2/Voll_med-FRONT-Mobile)
+
+
+## Nuevas Funcionalidades
+
+- Controller
+- DTOs
+- Entidades JPA
+- Repository
+- Migration
+- Security
+- Reglas de negocio
+
+Proyecto [Voll_Med API](./api_rest/api3/)
+
+Para implementar esta u otras funcionalidades, siempre es necesario crear los
+siguientes tipos de códigos:
+
+- **Controller**(s), para mapear la solicitud de la nueva funcionalidad
+- **DTO**s, que representan los datos que llegan y salen de la API
+- **Entidad**(es) **JPA**
+- **Repository**(s), para aislar el acceso a la base de datos
+- **Migration**, para hacer las alteraciones en la base de datos
+
+Estos son los cinco tipos de código que **siempre** se desarrollan para una nueva
+funcionalidad. Esto también se aplica al agendamiento de las consultas,
+incluyendo un sexto elemento a la lista, ***las reglas de negocio***.
+En este proyecto, se implementan las reglas de negocio con algoritmos más
+complejos.
+
+### Implementando la funcionalidad
+
+Se desarrolla la funcionalidad por partes. Empezaremos por los primeros cinco
+elementos de la lista, que son más básicos. Luego, la parte de reglas de negocio.
+
+Se crea un nuevo `ConsultaController` en el paquete
+[`src.main.java.med.voll.api.controller`](./api_rest/api2/src/main/java/med/voll/api/controller).
+
+La idea es tener un **Controller** para recibir esas solicitudes relacionadas con
+el agendado de consultas.
+
+Es una clase Controller, con las ***anotaciones*** de Spring `@Controller`,
+`@ResponseBody`, `@RequestMapping("consultas")` o `@RestController`. Mapea las
+solicitudes que llegan con la **URI** `/consultas`, que debe llamar a este
+controller y no a los otros.
+
+Luego, el método anotado con `@PostMapping`. Entonces, la solicitud para programar
+una consulta será del tipo **POST**, como en otras funcionalidades.
+
+[ConsultaController](./api_rest/api3/src/main/java/med/voll/api/controller/ConsultaController.java)
+
+```java
+@Controller
+@ResponseBody
+@RequestMapping("/consultas")
+public class ConsultaController {
+
+ @Autowired
+ private AgendaDeConsultaService service;
+
+ @PostMapping
+ @Transactional
+ public ResponseEntity agendar(
+ @RequestBody @Valid DatosAgendarConsulta datos) {
+ System.out.println(datos);
+ service.agendar(datos);
+ return ResponseEntity.ok(new DatosDetalleConsulta(null, null, null, null));
+ }
+}
+```
+
+[DatosAgendarConsulta](./api_rest/api3/src/main/java/med/voll/api/domain/consulta/DatosAgendarConsulta.java)
+se trata de un registro similar a los que visto anteriormente. Tiene los campos
+que provienen de la API (`Long idMedico`, `Long idPaciente` y
+`LocalDateTime fecha`) y las anotaciones de **BEAN validation** `@NotNull` para el
+`Id` del paciente y para la `fecha`, además de que la fecha debe ser en el
+**futuro** (`@Future`), es decir, no se podrá programar una consulta en días
+pasados.
+
+```java
+@Controller
+@ResponseBody
+@RequestMapping("/consultas")
+public class ConsultaController {
+
+ @Autowired
+ private AgendaDeConsultaService service;
+
+ @PostMapping
+ @Transactional
+ public ResponseEntity agendar(@RequestBody @Valid DatosAgendarConsulta datos) {
+ System.out.println(datos);
+ service.agendar(datos);
+ return ResponseEntity.ok(new DatosDetalleConsulta(null, null, null, null));
+ }
+}
+```
+
+Al volver al Controller, el otro DTO es el de respuesta, llamado
+`DatosDetalleConsulta`. Devuelve el `ID` de la consulta creada, del **médico**,
+del **paciente** y la **fecha de la consulta** registrada en el sistema.
+
+En el paquete `src.main.java.med.voll.api.domain`, se crea el subpaquete
+`consulta`, que abarca las clases relacionadas con el dominio de consulta.
+
+Entre ellas, la ***entidad JPA***
+[Consulta](./api_rest/api3/src/main/java/med/voll/api/domain/consulta/Consulta.java),
+que contiene las anotaciones de ***JPA*** y ***Lombok***, así como la
+información de la consulta: `medico`, `paciente` y `fecha`.
+
+```java
+@Table(name = "consultas")
+@Entity(name = "Consulta")
+@Getter
+@NoArgsConstructor
+@AllArgsConstructor
+@EqualsAndHashCode(of = "id")
+public class Consulta {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "medico_id")
+ private Medico medico;
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "paciente_id")
+ private Paciente paciente;
+
+ private LocalDateTime fecha;
+
+}
+```
+
+En este caso, `medico` y `paciente` son relaciones con las otras entidades
+`Medico` y `Paciente`.
+
+También se crea
+[ConsultaRepository](./api_rest/api3/src/main/java/med/voll/api/domain/consulta/ConsultaRepository.java),
+que está vacío por el momento.
+
+```java
+@Repository
+public interface ConsultaRepository extends JpaRepository {}
+```
+
+Por último, la migración número 6 (`V6`) en
+`src.main.java.med.voll.api.resources.db.migration`, que crea la tabla de
+consultas.
+
+```sql
+CREATE TABLE consultas(
+
+ id BIGINT NOT NULL AUTO_INCREMENT,
+ medico_id BIGINT NOT NULL,
+ paciente_id BIGINT NOT NULL,
+ fecha DATETIME NOT NULL,
+
+ PRIMARY KEY(id),
+ CONSTRAINT fk_consultas_medico_id FOREIGN KEY(medico_id)
+ REFERENCES medicos(id),
+ CONSTRAINT fk_consultas_paciente_id FOREIGN KEY(paciente_id)
+ REFERENCES pacientes(id)
+
+);
+```
+
+Los campos de `Id` de **consulta**, `Id` de **paciente**, `Id` de **médico** y
+`fecha`, donde `medico_id` y `paciente_id` son ***claves foráneas*** que apuntan
+a las tablas `medicos` y `pacientes`.
+
+Estos son los códigos estándar para cualquier funcionalidad, con sus respectivos
+cambios de acuerdo con el proyecto. Cada uno creará un controlador o una entidad
+distinta, pero el funcionamiento es el mismo.
+
+Ahora se puede intentar enviar una solicitud a la dirección `/consultas` y
+verificar si se redirige al `ConsultaController` y comprobando el `System.out`
+que muestra los datos que llegaron en el JSON de la solicitud.
+
+```json
+{
+ "idPaciente": 1,
+ "idMedico": 1,
+ "fecha": "2023-09-14T10:00"
+}
+```
+
+Esta es la ipmplementación del esqueleto de la funcionalidad. Ahora se deben
+implementar las reglas de negocio.
+
+El trabajo es un poco diferente a lo ya realizado con la validación de campos
+de formulario vía ***BEAN validation***. Estas validaciones son más complejas.
+
+***¿Cómo implementarlas?***
+
+Observando `ConsultaController.java`, se podrían hacer todas las validaciones
+en el método `agendar()`, antes del retorno. Sin embargo, esa no es una buena
+práctica.
+
+La clase controller no debe traer las reglas de negocio de la aplicación.
+Es solo una clase que controla el flujo de ejecución: cuando llega una solicitud,
+llama a la clase X, devuelve la respuesta Y. Si la condición es Z, devuelve otra
+respuesta y así sucesivamente. Es decir, solo controla el flujo de ejecución y,
+por lo tanto, no debería tener reglas de negocio.
+
+Así, aislando las reglas de negocio, los algoritmos, los cálculos y las
+validaciones en otra clase que será llamada por el Controller.
+
+En el paquete `consulta`. Se creas la clase para contener las reglas de agendado
+de consultas llamada
+[AgendaDeConsultasService](./api_rest/api3/src/main/java/med/voll/api/domain/consulta/AgendaDeConsultaService.java).
+
+El nombre es muy autoexplicativo, esta clase contendrá la agenda de consultas.
+Se pueden tener otras funcionalidades en esta clase, relacionadas con el
+agendamiento de consultas.
+
+Esta no es una clase ***Controller*** ni una clase de ***configuraciones***. Esta
+clase representa un servicio de la aplicación, el de agendado de consultas. Por
+lo tanto, será una ***clase de servicios*** (**Service**) y llevará la anotación
+`@Service`. El objetivo de esta anotación es declarar el componente de servicio
+a Spring Boot.
+
+Dentro de esta clase, se crear un método `public void agendar()`, que recibe
+como parámetro el **DTO** `DatosAgendarConsulta`.
+
+```java
+@Service
+public class AgendaDeConsultaService {
+
+ @Autowired
+ private ConsultaRepository consultaRepository;
+ @Autowired
+ private MedicoRepository medicoRepository;
+ @Autowired
+ private PacienteRepository pacienteRepository;
+
+ public void agendar(DatosAgendarConsulta datos) {
+
+ if (pacienteRepository.findById(datos.idPaciente()).isPresent()) {
+ throw new ValidacionDeIntegridad("Id de paciente no encontrado");
+ }
+ if (datos.idMedico() != null && medicoRepository.existsById(datos.idMedico())) {
+ throw new ValidacionDeIntegridad("Id de médico no encontrado");
+ }
+
+ var paciente = pacienteRepository.findById(datos.idPaciente()).get();
+ var medico = medicoRepository.findById(datos.idMedico()).get();
+ var consulta = new Consulta(null, medico, paciente, datos.fecha());
+ consultaRepository.save(consulta);
+ }
+}
+```
+
+La clase Service ejecuta las reglas de negocio y las validaciones de la aplicación.
+
+Esta clase se utliza en `ConsultaController`, con `@Autowired` se comuníca a
+Spring que instancie este objeto
+
+Con esto, se inyecta la clase `AgendaDeConsultas` en el Controller. En el método
+`agendar` del `Controller`, se obtiene el objeto `agenda` y se llama al método
+`agendar()`, pasando como parámetro los datos que llegan al `Controller`.
+Todo esto antes del retorno.
+
+El **Controller** recibe la información, hace solo la validación de
+***BIN validation*** y llama a la clase **Service** `AgendaDeConsultas`, que
+ejecutará las reglas de negocio. Esta es la forma correcta de manejar las reglas
+de negocio.
+
+En la clase `AgendaDeConsultas` y están todas las validaciones para el método
+`agendar()`.
+
+El requerimiento especifica que se debe recibir la solicitud con los datos de
+agendamiento y se deben guardar en la tabla de consultas.
+
+Por lo tanto, se necesita acceder a la base de datos y a la tabla de consultas
+en esta clase. Así que se declara un atributo `ConsultaRepository`, llamándolo
+`consultaRepository`.
+
+Se usa la anotación `@Autowired` para que el Spring Boot inyecte este repository
+en la clase `Service`.
+
+Al final del método `agendar()`, se inserta `consultaRepository.save()` pasandole
+un objeto del tipo consulta, la entidad **JPA**. Obviamente, solo se puede llamar
+a este método si todas las validaciones se han ejecutado de acuerdo con las
+reglas de negocio.
+
+La entidad `Consulta` está anotada con `@AllArgsConstructor` de ***Lombok***,
+que genera un constructor con todos los atributos. Se puede usar este mismo
+constructor en `AgendamientoDeConsultas`.
+El primer parámetro es el `Id null`, ya que es la base de datos la que pasará
+el `Id`. El segundo es `medico`, `paciente` y `fecha`. Esta última viene en el
+**DTO**, a través del parámetro `datos`.
+
+Sucede que el médico y el paciente no llegan en la solicitud, sino el `Id` del
+médico y el `Id` del paciente. Por lo tanto, es necesario establecer el objeto
+completo en la entidad y no solo el `Id`.
+
+Por lo tanto, es necesario cargar médico y paciente desde la base de datos.
+Se necesita inyectar, entonces, dos Repositories más en `Service`:
+`MedicoRepository` y `PacienteRepository`.
+
+En el método `agendar()`, se crea un objeto paciente también. Usando
+`pacienteRepository.findById()` para buscar el objeto por `Id`, que está dentro
+del DTO datos.
+
+En la solicitud solo viene el `Id`, pero se necesita cargar el objeto completo.
+Por lo tanto, se usa el Repository para cargar por el `Id` de la base de datos.
+El médico seguirá la misma dinámica (
+[AgendaDeConsultasService](./api_rest/api3/src/main/java/med/voll/api/domain/consulta/AgendaDeConsultaService.java)
+).
+
+Aparecerá un error de compilación porque el método `findById()` no devuelve la
+entidad, sino un Optional. Por lo tanto, al final de la línea, antes del punto
+y coma, es necesario incluir `.get()` junto a `findById()`. Esto hace que tome
+la entidad cargada.
+
+El método `agendar()` en la clase `Service` obteniene el `Id`, cargar el paciente
+y el médico desde la base de datos creando una entidad consulta pasando el médico,
+el paciente y la fecha que viene en el DTO, y se guarda en la base de datos.
+
+Pero antes de esto, se necesita escribir el código para realizar todas las
+validaciones que forman parte de las reglas de negocio.
+
+A continuación, se aborda cómo realizar las validaciones de la mejor manera
+posible.
+
+### Validaciones
+
+Para verificar el ID del paciente, se usa un `if`. La idea es comprobar si el `Id`
+del paciente existe. El Repository es el que consulta la base de datos.
+
+Se puede lanzar una excepción dentro del `if`, que muestre un mensaje indicando
+el problema. Incluso se puede crear una excepción personalizada para el proyecto
+llamada `ValidacaoException()`. Con un mensaje como
+`"El ID del paciente proporcionado no existe"` o similar (
+[DatosDetalleConsulta](./api_rest/api3/src/main/java/med/voll/api/domain/consulta/DatosDetalleConsulta.java)
+).
+
+En *package* `med.voll.api.infra.errores` se crea la clase
+[ValidacionDeIntegridad](./api_rest/api3/src/main/java/med/voll/api/infra/errores/ValidacionDeIntegridad.java)
+
+```java
+package med.voll.api.infra.errores;
+
+public class ValidacionDeIntegridad extends RuntimeException {
+ public ValidacionDeIntegridad(String s) {
+ super(s);
+ }
+}
+```
+
+En la clase
+[AgendaDeConsultasService](./api_rest/api3/src/main/java/med/voll/api/domain/consulta/AgendaDeConsultaService.java),
+se realiza la verificación con. Primero, se verifica si existe un paciente con
+el `Id` que está llegando a la base de datos. Si no existe, se lanzará una
+excepción con un mensaje de error.
+
+Se utiliza el método de la ***interfaz Repository*** en Spring Data llamado
+`existsById`. Realiza una consulta a la base de datos para verificar si existe
+un registro con un determinado `Id` que devuelve un booleano, `true` si existe,
+`false` si no.
+Pasando el parámetro `datos.idMedico()`. Se niega la expresión. Con esto, si no
+hay un paciente con el `Id` en la base de datos, se debe detener la ejecución
+del resto del código.
+
+Recordando la última regla de negocio.
+
+***La elección del médico es opcional, y en ese caso el sistema debe elegir
+aleatoriamente algún médico disponible en la fecha/hora indicada.***
+
+Por lo tanto, es posible que un `Id` de médico no llegue en la solicitud.
+No se puede llamar a `existsById` si el `Id` del médico es nulo. Esto resultará
+en un error para **JPA**.
+
+Solo se puede llamar al `if` si el `Id` no es nulo. Por lo tanto, se agrega una
+condición al if antes de la condición actual:
+
+```java
+if(datos.idMedico()!=null && !medicoRepository.existsById(datos.idMedico())){
+ throw new ValidacionDeIntegridad("Id de medico no encontrado");
+}
+```
+
+En el caso del médico, al ser un campo opcional, puede ser que la línea
+`var medico = medicoRepository.findById(dados.idMedico()).get()` tenga un
+`idMedico` nulo.
+
+De acuerdo con la regla de negocio analizada, se necesita escribir un algoritmo
+que elija aleatoriamente un médico en la base de datos. Por lo tanto, la línea
+anterior necesita ser reemplazada. Pare ello se llama al método privado
+`seleccionarMedico(datos)` que recibe un objeto `DatosAgendarConsulta` como
+parametro y retorna un objeto `Medico`.
+
+Esto sirve para aislar el algoritmo y evitar que esté suelto dentro del método
+de programación de citas. En el método `seleccionarMedico()`, se necesita
+verificar si está llegando el `Id` del médico en la solicitud o no.
+
+Si lo está, se obtiene el médico correspondiente de la base de datos. Si no es
+así, se debe elegir aleatoriamente un profesional de la salud, según lo indica
+la regla de negocio.
+
+Para implementar el algoritmo que elige al médico de manera aleatoria, se deben
+cubrir todos los posibles escenarios. La primera comprobación es que si la
+persona eligió un médico al hacer la solicitud, usando
+`if (dados.idMedico() != null)`.
+
+En este caso, se carga la información de la base de datos con
+`return medicoRepository.getReferenceById(dados.idMedico())`. En lugar de usar
+`findById()`, se podemos usar `getReferenceById()` y no es necesario llamar al
+`.get()` usamdo anteriormente.
+
+También se puedemo cambiar `findById()` por `getReferenceById()` en la variable
+paciente, ya que no se quiere cargar el objeto para manipularlo, sino, solo para
+asignarlo a otro objeto.
+
+En el método `seleccionarMedico()` , lo primero es verificar si se realiza la
+con un médico específico para su atención. Si es así, simplemente se carga la
+información del médico que viene de la base de datos.
+
+[DatosAgendarConsulta](./api_rest/api3/src/main/java/med/voll/api/domain/consulta/DatosAgendarConsulta.java)
+
+```java
+public record DatosAgendarConsulta(
+ @NotNull Long idPaciente,
+ Long idMedico,
+ @NotNull @Future LocalDateTime fecha,
+ Especialidad especialidad) {
+}
+```
+
+***¿Cómo elegir un médico aleatorio de la especialidad elegida, disponible en
+la fecha y hora seleccionadas?***
+
+Existen varias formas de hacer esto. Se podrían cargar los médicos, filtrarlos por
+especialidad y comprobar la fecha de la consulta en Java.
+
+Lo ideal es cargar un profesional aleatorio directamente de la base de datos.
+Sin embargo, esta consulta es específica para nuestro proyecto, es decir, no está
+lista en Spring Data JPA.
+
+Se necesita crear un método para hacer esto:
+
+```java
+return medicoRepository.seleccionarMedicoConEspecialidadEnFecha(
+ datos.especialidad(),datos.fecha()
+ );
+```
+
+Creación del método `seleccionarMedicoConEspecialidadEnFecha(especialidad, fecha)`
+en
+[MedicoRepository](./api_rest/api3/src/main/java/med/voll/api/domain/medico/MedicoRepository.java).
+
+Como el nombre del método está en español. No se estam siguiendo el estándar
+de nomenclatura, como el `findAllByAtivoTrue()`. De esta manera, Spring Data no
+podrá construir el SQL automáticamente. La idea es precisamente esa, para este método,
+se escribe el comando SQL manualmente.
+
+Para hacerlo, se usa la anotación `@Query()` justo encima del método. Que viene
+del paquete `org.springframework.data.jpa`. Y entre paréntesis, se construye la
+consulta utilizando la sintaxis del ***Java Persistence Query Language (JPQL)***.
+
+```java
+ @Query("""
+ select m from Medico m
+ where m.activo= true\s
+ and
+ m.especialidad=:especialidad\s
+ and
+ m.id not in( \s
+ select c.medico.id from Consulta c
+ where
+ c.fecha=:fecha
+ )
+ order by rand()
+ limit 1
+ """)
+ Medico seleccionarMedicoConEspecialidadEnFecha(
+ Especialidad especialidad,
+ LocalDateTime fecha
+ );
+}
+```
+
+### En resumen
+Creación de nuevo *package*
+[`domain.consulta`](./api_rest/api3/src/main/java/med/voll/api/domain/consulta/)
+donde se crean entidad **Consulta**, clases `ConsultaRepository`,
+`DatosAgendarConsulta` y `DatosDetalleConsulta`.
+
+- [Consulta](./api_rest/api3/src/main/java/med/voll/api/domain/consulta/Consulta.java)
+- [ConsultaRepository](./api_rest/api3/src/main/java/med/voll/api/domain/consulta/ConsultaRepository.java)
+- [DatosAgendarConsulta](./api_rest/api3/src/main/java/med/voll/api/domain/consulta/DatosAgendarConsulta.java)
+- [DatosDetalleConsulta](./api_rest/api3/src/main/java/med/voll/api/domain/consulta/DatosDetalleConsulta.java)
+- [AgendaDeConsultasService](./api_rest/api3/src/main/java/med/voll/api/domain/consulta/AgendaDeConsultaService.java)
+- [migración](./api_rest/api3/src/main/resources/db/migration/V6__create-table-consultas.sql)
+
+---
+
+### Anotación @JsonAlias
+
+En caso que uno o mas campos enviados en el formato JSON a la API no correspondan
+con los atributos de las clases DTO, se pueden utilizar alias
+
+```java
+public record DatosCompra(
+ @JsonAlias("producto_id") Long idProducto,
+ @JsonAlias("fecha_compra") LocalDate fechaCompra
+ ){}
+```
+
+La anotación `@JsonAlias` sirve para mapear *alias* alternativos para los campos
+que se recibirán del JSON, y es posible asignar múltiples alias:
+
+```java
+public record DatosCompra(
+ @JsonAlias({"producto_id", "id_producto"}) Long idProducto,
+ @JsonAlias({"fecha_compra", "fecha"}) LocalDate fechaCompra
+ ){}
+```
+
+### Formato fechas
+
+Spring usa un formato definido para las fechas `LocalDateTime`, sin embargo, estas
+se pueden personalizar. Por ejemplo, para aceptar el formato `dd/mm/yyy hh:mm`.
+Para ello se utiliza la anotación `@JsonFormat`
+
+```java
+@NotNull
+@Future
+@JsonFormat(pattern = "dd/MM/yyyy HH:mm")
+LocalDateTime fecha
+```
+
+Esta anotación también se puede utilizar en las clases DTO que representan la
+información que devuelve la API, para que el JSON devuelto se formatee de
+acuerdo con el patrón configurado.
+Además, no se limita solo a la clase `LocalDateTime`, sino que también se puede
+utilizar en atributos de tipo `LocalDate` y `LocalTime`.
+
+---
+
+### Patrón Service
+
+El ***Service pattern*** es muy utilizado en la programación y su nombre es muy
+conocido. Pero a pesar de ser un nombre único, **Service** puede ser interpretado
+de varias maneras:
+
+- Puede ser un caso de uso, **Application Service**
+- Un **Domain Service**, que tiene reglas de su dominio
+- Un **Infrastructure Service**, que utiliza algún paquete externo para realizar
+tareas
+- etc
+
+A pesar de que la interpretación puede ocurrir de varias formas, la idea detrás
+del patrón es separar las reglas de negocio, las reglas de la aplicación y las
+reglas de presentación para que puedan ser fácilmente probadas y reutilizadas
+en otras partes del sistema.
+
+Existen dos formas más utilizadas para crear **Services**. Puede crear
+**Services** más genéricos, responsables de todas las asignaciones de un
+**Controller**. O ser aún más específico, aplicando así la S del ***SOLID***:
+***Single Responsibility Principle*** (*Principio de Responsabilidad Única*).
+Este principio nos dice que una clase/función/archivo debe tener sólo una
+única responsabilidad.
+
+Piense en un sistema de ventas, en el que probablemente tendríamos algunas
+funciones como:
+
+- Registrar usuario
+- Iniciar sesión
+- Buscar productos
+- Buscar producto por nombre
+- etc
+
+Entonces, se podrían crear los siguientes **Services**:
+
+- RegistroDeUsuarioService
+- IniciarSesionService
+- BusquedaDeProductosService
+- etc
+
+Pero es importante estar atentos, ya que muchas veces no es necesario crear un
+**Service** y, por lo tanto, agregar otra capa y complejidad innecesarias a
+una aplicación. Una regla que podemos utilizar es la siguiente: si no hay reglas
+de negocio, simplemente se puede realizar la comunicación directa entre los
+controllers y los repositories de la aplicación.
+
+---
+
+
diff --git a/README.md b/README.md
index fe28296..74c4d7b 100644
--- a/README.md
+++ b/README.md
@@ -52,3 +52,4 @@ primoridiales en programación con Javascript
- [JPA consultas avanzadas, rendimiento y modelos complejos](./010_spring_boot/jpa_avanzado.md)
- [Desarrollo API Rest](./010_spring_boot/spring_boot_1.md)
- [Buenas prácticas y protección de API Rest](./010_spring_boot/spring_boot_2.md)
+ - [Docs, Tests y preparación para implementacion](./010_spring_boot/spring_boot_3.md)