importacion de app de github KevinOlarte1(User)

This commit is contained in:
kevin 2025-06-06 16:53:03 +02:00
commit 4121c7b930
133 changed files with 15908 additions and 0 deletions

11
.env Normal file
View File

@ -0,0 +1,11 @@
#Database configuration
SPRING_DATASOURCE_URL=jdbc:postgresql://localhost:5432/resident
SPRING_DATASOURCE_USERNAME=
SPRING_DATASOURCE_PASSWORD=
#JWT secret Key
JWT_SECRET_KEY=VGhpcyBpcyBhIHZlcnkgc2VjdXJlIHNlY3JldCBrZXkgZm9yIEpXVC4uLg==
#Mail properties
SUPPORT_EMAIL=ktrabajo870@gmail.com
APP_PASSWORD=bhqu vpzu oxuy lntp

2
.gitattributes vendored Normal file
View File

@ -0,0 +1,2 @@
/mvnw text eol=lf
*.cmd text eol=crlf

33
.gitignore vendored Normal file
View File

@ -0,0 +1,33 @@
HELP.md
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/

19
.mvn/wrapper/maven-wrapper.properties vendored Normal file
View File

@ -0,0 +1,19 @@
# 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
#
# http://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.
wrapperVersion=3.3.2
distributionType=only-script
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip

3
README.md Normal file
View File

@ -0,0 +1,3 @@
# Proyecto Fundación Residencia Benissa
### Parte Backend de la aplicación, API REST.

259
mvnw vendored Normal file
View File

@ -0,0 +1,259 @@
#!/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
#
# http://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.3.2
#
# Optional ENV vars
# -----------------
# JAVA_HOME - location of a JDK home dir, required when download maven via java source
# MVNW_REPOURL - repo url base for downloading maven distribution
# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output
# ----------------------------------------------------------------------------
set -euf
[ "${MVNW_VERBOSE-}" != debug ] || set -x
# OS specific support.
native_path() { printf %s\\n "$1"; }
case "$(uname)" in
CYGWIN* | MINGW*)
[ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")"
native_path() { cygpath --path --windows "$1"; }
;;
esac
# set JAVACMD and JAVACCMD
set_java_home() {
# For Cygwin and MinGW, ensure paths are in Unix format before anything is touched
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"
JAVACCMD="$JAVA_HOME/jre/sh/javac"
else
JAVACMD="$JAVA_HOME/bin/java"
JAVACCMD="$JAVA_HOME/bin/javac"
if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then
echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2
echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2
return 1
fi
fi
else
JAVACMD="$(
'set' +e
'unset' -f command 2>/dev/null
'command' -v java
)" || :
JAVACCMD="$(
'set' +e
'unset' -f command 2>/dev/null
'command' -v javac
)" || :
if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then
echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2
return 1
fi
fi
}
# hash string like Java String::hashCode
hash_string() {
str="${1:-}" h=0
while [ -n "$str" ]; do
char="${str%"${str#?}"}"
h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296))
str="${str#?}"
done
printf %x\\n $h
}
verbose() { :; }
[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; }
die() {
printf %s\\n "$1" >&2
exit 1
}
trim() {
# MWRAPPER-139:
# Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds.
# Needed for removing poorly interpreted newline sequences when running in more
# exotic environments such as mingw bash on Windows.
printf "%s" "${1}" | tr -d '[:space:]'
}
# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties
while IFS="=" read -r key value; do
case "${key-}" in
distributionUrl) distributionUrl=$(trim "${value-}") ;;
distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;;
esac
done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties"
[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties"
case "${distributionUrl##*/}" in
maven-mvnd-*bin.*)
MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/
case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in
*AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;;
:Darwin*x86_64) distributionPlatform=darwin-amd64 ;;
:Darwin*arm64) distributionPlatform=darwin-aarch64 ;;
:Linux*x86_64*) distributionPlatform=linux-amd64 ;;
*)
echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2
distributionPlatform=linux-amd64
;;
esac
distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip"
;;
maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;;
*) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;;
esac
# apply MVNW_REPOURL and calculate MAVEN_HOME
# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-<version>,maven-mvnd-<version>-<platform>}/<hash>
[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}"
distributionUrlName="${distributionUrl##*/}"
distributionUrlNameMain="${distributionUrlName%.*}"
distributionUrlNameMain="${distributionUrlNameMain%-bin}"
MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}"
MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")"
exec_maven() {
unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || :
exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD"
}
if [ -d "$MAVEN_HOME" ]; then
verbose "found existing MAVEN_HOME at $MAVEN_HOME"
exec_maven "$@"
fi
case "${distributionUrl-}" in
*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;;
*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;;
esac
# prepare tmp dir
if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then
clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; }
trap clean HUP INT TERM EXIT
else
die "cannot create temp dir"
fi
mkdir -p -- "${MAVEN_HOME%/*}"
# Download and Install Apache Maven
verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
verbose "Downloading from: $distributionUrl"
verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
# select .zip or .tar.gz
if ! command -v unzip >/dev/null; then
distributionUrl="${distributionUrl%.zip}.tar.gz"
distributionUrlName="${distributionUrl##*/}"
fi
# verbose opt
__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR=''
[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v
# normalize http auth
case "${MVNW_PASSWORD:+has-password}" in
'') MVNW_USERNAME='' MVNW_PASSWORD='' ;;
has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;;
esac
if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then
verbose "Found wget ... using wget"
wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl"
elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then
verbose "Found curl ... using curl"
curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl"
elif set_java_home; then
verbose "Falling back to use Java to download"
javaSource="$TMP_DOWNLOAD_DIR/Downloader.java"
targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName"
cat >"$javaSource" <<-END
public class Downloader extends java.net.Authenticator
{
protected java.net.PasswordAuthentication getPasswordAuthentication()
{
return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() );
}
public static void main( String[] args ) throws Exception
{
setDefault( new Downloader() );
java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() );
}
}
END
# For Cygwin/MinGW, switch paths to Windows format before running javac and java
verbose " - Compiling Downloader.java ..."
"$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java"
verbose " - Running Downloader.java ..."
"$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")"
fi
# If specified, validate the SHA-256 sum of the Maven distribution zip file
if [ -n "${distributionSha256Sum-}" ]; then
distributionSha256Result=false
if [ "$MVN_CMD" = mvnd.sh ]; then
echo "Checksum validation is not supported for maven-mvnd." >&2
echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
exit 1
elif command -v sha256sum >/dev/null; then
if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then
distributionSha256Result=true
fi
elif command -v shasum >/dev/null; then
if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then
distributionSha256Result=true
fi
else
echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2
echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
exit 1
fi
if [ $distributionSha256Result = false ]; then
echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2
echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2
exit 1
fi
fi
# unzip and move
if command -v unzip >/dev/null; then
unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip"
else
tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar"
fi
printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url"
mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME"
clean || :
exec_maven "$@"

149
mvnw.cmd vendored Normal file
View File

@ -0,0 +1,149 @@
<# : batch portion
@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 http://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.3.2
@REM
@REM Optional ENV vars
@REM MVNW_REPOURL - repo url base for downloading maven distribution
@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output
@REM ----------------------------------------------------------------------------
@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0)
@SET __MVNW_CMD__=
@SET __MVNW_ERROR__=
@SET __MVNW_PSMODULEP_SAVE=%PSModulePath%
@SET PSModulePath=
@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @(
IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B)
)
@SET PSModulePath=%__MVNW_PSMODULEP_SAVE%
@SET __MVNW_PSMODULEP_SAVE=
@SET __MVNW_ARG0_NAME__=
@SET MVNW_USERNAME=
@SET MVNW_PASSWORD=
@IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*)
@echo Cannot start maven from wrapper >&2 && exit /b 1
@GOTO :EOF
: end batch / begin powershell #>
$ErrorActionPreference = "Stop"
if ($env:MVNW_VERBOSE -eq "true") {
$VerbosePreference = "Continue"
}
# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties
$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl
if (!$distributionUrl) {
Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties"
}
switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) {
"maven-mvnd-*" {
$USE_MVND = $true
$distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip"
$MVN_CMD = "mvnd.cmd"
break
}
default {
$USE_MVND = $false
$MVN_CMD = $script -replace '^mvnw','mvn'
break
}
}
# apply MVNW_REPOURL and calculate MAVEN_HOME
# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-<version>,maven-mvnd-<version>-<platform>}/<hash>
if ($env:MVNW_REPOURL) {
$MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" }
$distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')"
}
$distributionUrlName = $distributionUrl -replace '^.*/',''
$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$',''
$MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain"
if ($env:MAVEN_USER_HOME) {
$MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain"
}
$MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join ''
$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME"
if (Test-Path -Path "$MAVEN_HOME" -PathType Container) {
Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME"
Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"
exit $?
}
if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) {
Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl"
}
# prepare tmp dir
$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile
$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir"
$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null
trap {
if ($TMP_DOWNLOAD_DIR.Exists) {
try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
}
}
New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null
# Download and Install Apache Maven
Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
Write-Verbose "Downloading from: $distributionUrl"
Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
$webclient = New-Object System.Net.WebClient
if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) {
$webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD)
}
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null
# If specified, validate the SHA-256 sum of the Maven distribution zip file
$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum
if ($distributionSha256Sum) {
if ($USE_MVND) {
Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties."
}
Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash
if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) {
Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property."
}
}
# unzip and move
Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null
Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null
try {
Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null
} catch {
if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) {
Write-Error "fail to move MAVEN_HOME"
}
} finally {
try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
}
Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"

131
pom.xml Normal file
View File

@ -0,0 +1,131 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.4.4</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.kevinolarte</groupId>
<artifactId>resibenissa</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>resibenissa</name>
<description>Demo project for Spring Boot</description>
<url/>
<licenses>
<license/>
</licenses>
<developers>
<developer/>
</developers>
<scm>
<connection/>
<developerConnection/>
<tag/>
<url/>
</scm>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.github.librepdf</groupId>
<artifactId>openpdf</artifactId>
<version>1.3.30</version>
</dependency>
<dependency>
<groupId>net.datafaker</groupId>
<artifactId>datafaker</artifactId>
<version>2.1.0</version> <!-- Puedes cambiar por la última disponible -->
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,17 @@
package com.kevinolarte.resibenissa;
public class LogContext {
private static final ThreadLocal<Long> currentLogId = new ThreadLocal<>();
public static void setCurrentLogId(Long id) {
currentLogId.set(id);
}
public static Long getCurrentLogId() {
return currentLogId.get();
}
public static void clear() {
currentLogId.remove();
}
}

View File

@ -0,0 +1,13 @@
package com.kevinolarte.resibenissa;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ResibenissaApplication {
public static void main(String[] args) {
SpringApplication.run(ResibenissaApplication.class, args);
}
}

View File

@ -0,0 +1,80 @@
package com.kevinolarte.resibenissa.config;
import com.kevinolarte.resibenissa.exceptions.ApiErrorCode;
import com.kevinolarte.resibenissa.exceptions.ResiException;
import com.kevinolarte.resibenissa.repositories.UserRepository;
import lombok.AllArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import com.kevinolarte.resibenissa.models.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
/**
* Configuración de seguridad de la aplicación.
* <p>
* Define los beans necesarios para la autenticación de usuarios,
* incluyendo el codificador de contraseñas, el proveedor de autenticación
* y el servicio de obtención de detalles de usuario.
*
* @author Kevin Olarte
*/
@Configuration
@AllArgsConstructor
public class AplicationConfiguration {
private final UserRepository userRepository;
/**
* Devuelve un UserDetailsService que busca usuarios por email en la base de datos.
*
* @return Implementación de UserDetailsService personalizada.
* @throws UsernameNotFoundException si el usuario no existe.
*/
@Bean
UserDetailsService userDetailsService(){
return email -> {
User user = userRepository.findByEmail(email);
if(user == null){
throw new ResiException(ApiErrorCode.USUARIO_INVALIDO);
}
if (user.isBaja()){
throw new ResiException(ApiErrorCode.USUARIO_BAJA);
}
return user;
};
}
@Bean
BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
AuthenticationManager authenticationManager(AuthenticationConfiguration config)throws Exception {
return config.getAuthenticationManager();
}
/**
* Crea un bean de {@link AuthenticationProvider} personalizado que usa
* el {@link UserDetailsService} y el codificador de contraseñas definidos.
*
* @return el proveedor de autenticación configurado.
*/
@Bean
AuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
authProvider.setUserDetailsService(userDetailsService());
authProvider.setPasswordEncoder(passwordEncoder());
return authProvider;
}
}

View File

@ -0,0 +1,34 @@
package com.kevinolarte.resibenissa.config;
import org.springframework.beans.factory.annotation.Value;
/**
* Clase de configuración que contiene valores por defecto utilizados en la aplicación.
* <p>
* Estos valores suelen emplearse para inicializar entidades, usuarios de sistema, o
* como fallback en lógica que requiere datos por omisión.
* </p>
*
* <p><b>Nota:</b> Estas constantes no deberían modificarse dinámicamente durante la ejecución.</p>
*
* @author Kevin Olarte
*/
public class Conf {
public static final Long idResidenciaDefault = 1L;
public static final Long idUsuarioDefault = 1L;
public static final String emailDefault = "default@default.com";
public static final String PATH_PUBLIC_AUTH= "/auth/";
public static final String PATH_PUBLIC_SWAGGER = "/swagger-ui/";
public static final String PATH_PUBLIC_RESI_GET = "/resi/getAll";
public static final String PATH_PUBLIC_RESI_CONTROLLER = "/public/";
@Value("${upload.dir}")
public static String imageResource;
public static final String imageDefault = "defaultPerfil.png";
public static final int POS_ESTADO_ABIERTO = 0;
public static final int POS_ESTADO_CERRADO = 1;
public static final int POS_ESTADO_EN_CURSO = 2;
public static final int POS_ESTADO_FINALIZADA = 3;
}

View File

@ -0,0 +1,32 @@
package com.kevinolarte.resibenissa.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* Configuración global de CORS para permitir acceso desde el frontend.
* Esto permite que navegadores puedan hacer peticiones a la API desde dominios distintos.
*/
@Configuration
public class CorsConfig {
@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins(
"http://localhost:5500",
"http://127.0.0.1:5500"
)
.allowedMethods("*")
.allowedHeaders("*")
.allowCredentials(false);
}
};
}
}

View File

@ -0,0 +1,60 @@
package com.kevinolarte.resibenissa.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.JavaMailSenderImpl;
import java.util.Properties;
/**
* Configuración del servicio de envío de correos electrónicos.
* <p>
* Esta clase define un {@link JavaMailSender} que se configura automáticamente
* con los datos definidos en el archivo {@code application.properties} o {@code application.yml}.
* Utiliza los servidores de Gmail como proveedor SMTP.
* </p>
*
* <p>
* Las credenciales y configuraciones sensibles se inyectan desde las propiedades:
* <ul>
* <li><code>spring.mail.username</code></li>
* <li><code>spring.mail.password</code></li>
* </ul>
* </p>
*
* @return Bean de {@link JavaMailSender} listo para ser inyectado y utilizado en servicios.
*
* @author Kevin Olarte
*/
@Configuration
public class EmailConfiguration {
@Value("${spring.mail.username}")
private String username;
@Value("${spring.mail.password}")
private String password;
/**
* Crea e inicializa el {@link JavaMailSender} con configuración para SMTP (Gmail).
*
* @return Instancia de {@link JavaMailSender} configurada.
*/
@Bean
public JavaMailSender javaMailSender() {
JavaMailSenderImpl mailSender = new JavaMailSenderImpl();
mailSender.setHost("smtp.gmail.com");
mailSender.setPort(587);
mailSender.setUsername(username);
mailSender.setPassword(password);
Properties props = mailSender.getJavaMailProperties();
props.put("mail.transport.protocol", "smtp");
props.put("mail.smtp.auth", "true");
props.put("mail.smtp.starttls.enable", "true");
props.put("mail.debug", "true");
return mailSender;
}
}

View File

@ -0,0 +1,142 @@
package com.kevinolarte.resibenissa.config;
import com.kevinolarte.resibenissa.exceptions.ApiErrorCode;
import com.kevinolarte.resibenissa.exceptions.ApiException;
import com.kevinolarte.resibenissa.exceptions.ResiException;
import com.kevinolarte.resibenissa.models.User;
import com.kevinolarte.resibenissa.services.JwtService;
import com.kevinolarte.resibenissa.services.LoggerService;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.AllArgsConstructor;
import lombok.NonNull;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.servlet.HandlerExceptionResolver;
import java.io.IOException;
/**
* Filtro de autenticación que intercepta todas las peticiones HTTP y verifica si contienen
* un token JWT válido en la cabecera {@code Authorization}.
* <p>
* Si el token es válido, autentica al usuario y lo registra en el {@link SecurityContextHolder}.
* En caso de error, delega la excepción al {@link HandlerExceptionResolver} para devolver
* una respuesta estructurada.
* </p>
*
* Este filtro se ejecuta una sola vez por petición, al extender de {@link OncePerRequestFilter}.
*
* @author Kevin
*/
@Component
@AllArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final JwtService jwtService;
private final UserDetailsService userDetailsService;
private final HandlerExceptionResolver handlerExceptionResolver;
private final LoggerService loggerService;
/**
* Lógica principal del filtro. Verifica si la petición contiene un token JWT válido
* y, si es así, autentica al usuario en el contexto de seguridad de Spring.
*
* @param request Petición HTTP entrante.
* @param response Respuesta HTTP saliente.
* @param filterChain Cadena de filtros que se continúa tras el procesamiento.
* @throws ServletException si ocurre un error en el filtro.
* @throws IOException si ocurre un error de entrada/salida.
*/
@Override
protected void doFilterInternal(
@NonNull HttpServletRequest request,
@NonNull HttpServletResponse response,
@NonNull FilterChain filterChain) throws ServletException, IOException {
final String authHeader = request.getHeader("Authorization");
String endpoint = request.getRequestURI();
String metodo = request.getMethod();
// Aquí puedes añadir más lógica para determinar el usuario autenticado
String descripcion = "Acceso a endpoint";
loggerService.registrarLog(endpoint, metodo, descripcion);
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
handlerExceptionResolver.resolveException(
request,
response,
null,
new ApiException(new ResiException(ApiErrorCode.ENDPOINT_PROTEGIDO), "Falta el token de autorización.")
);
return;
}
try {
final String jwt = authHeader.substring(7);
final String userEmail = jwtService.extrtractEmail(jwt);
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (userEmail != null && authentication == null) {
UserDetails userDetails = this.userDetailsService.loadUserByUsername(userEmail);
if (jwtService.isTokenValid(jwt, userDetails)) {
UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(
userDetails,
null,
userDetails.getAuthorities()
);
authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authToken);
}
}
String uri = request.getRequestURI();
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (uri.startsWith("/admin/resi") && auth != null && auth.isAuthenticated()) {
boolean isAdmin = auth.getAuthorities().stream()
.anyMatch(a -> a.getAuthority().equals("ROLE_ADMIN"));
if (!isAdmin) {
handlerExceptionResolver.resolveException(
request,
response,
null,
new ApiException(new ResiException(ApiErrorCode.ENDPOINT_PROTEGIDO), "Falta el token de autorización.")
);
return;
}
}
filterChain.doFilter(request, response);
} catch(Exception e){
handlerExceptionResolver.resolveException(request, response, null, new ApiException(new ResiException(ApiErrorCode.ENDPOINT_PROTEGIDO), e.getMessage()));
}
}
@Override
protected boolean shouldNotFilter(HttpServletRequest request) {
String path = request.getRequestURI();
System.out.println("URI: " + path);
return path.startsWith(Conf.PATH_PUBLIC_SWAGGER)
|| path.startsWith(Conf.PATH_PUBLIC_AUTH)
|| path.startsWith(Conf.PATH_PUBLIC_RESI_GET)
|| path.startsWith(Conf.PATH_PUBLIC_RESI_CONTROLLER);
}
}

View File

@ -0,0 +1,89 @@
package com.kevinolarte.resibenissa.config;
import jakarta.servlet.http.HttpServletRequest;
import lombok.AllArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import java.util.List;
/**
* Clase de configuración de seguridad para la API.
* <p>
* Define la cadena de filtros de seguridad, política de sesiones,
* rutas públicas y configuración CORS para permitir solicitudes seguras desde el frontend.
* </p>
*
* Esta configuración se basa en JWT y es stateless (sin sesiones de servidor).
*
* @author Kevin
*/
@Configuration
@EnableWebSecurity
@AllArgsConstructor
public class SecurityConfiguration {
private final AuthenticationProvider authenticationProvider;
private final JwtAuthenticationFilter jwtAuthenticationFilter;
/**
* Configura la cadena de filtros de seguridad para la aplicación.
* <p>
* - Desactiva CSRF.<br>
* - Permite acceso libre a rutas que comienzan con {@code /auth/**}.<br>
* - Requiere autenticación para cualquier otra petición.<br>
* - Aplica política de sesión stateless (usada con JWT).<br>
* - Agrega el filtro personalizado para validar JWT antes de {@link UsernamePasswordAuthenticationFilter}.
* </p>
*
* @param http Objeto {@link HttpSecurity} para construir la configuración.
* @return Cadena de filtros de seguridad {@link SecurityFilterChain} configurada.
* @throws Exception Si ocurre un error en la construcción de la cadena.
*/
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(authorize -> authorize
.requestMatchers(Conf.PATH_PUBLIC_AUTH + "**").permitAll()
.requestMatchers(Conf.PATH_PUBLIC_SWAGGER + "**").permitAll()
.requestMatchers(Conf.PATH_PUBLIC_RESI_CONTROLLER + "**").permitAll()
.requestMatchers(Conf.PATH_PUBLIC_RESI_GET).permitAll()
.anyRequest().authenticated()
)
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authenticationProvider(authenticationProvider)
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
@Bean
public CorsConfigurationSource corsConfigurationSource(){
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(List.of("http://localhost:8080"));
configuration.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE"));
configuration.setAllowedHeaders(List.of("Authorization","Content-Type"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}

View File

@ -0,0 +1,194 @@
package com.kevinolarte.resibenissa.config;
import com.kevinolarte.resibenissa.dto.in.moduloOrgSalida.EventoSalidaDto;
import com.kevinolarte.resibenissa.dto.in.moduloOrgSalida.ParticipanteDto;
import com.kevinolarte.resibenissa.dto.in.modulojuego.RegistroJuegoDto;
import com.kevinolarte.resibenissa.enums.Role;
import com.kevinolarte.resibenissa.enums.moduloOrgSalida.EstadoSalida;
import com.kevinolarte.resibenissa.enums.modulojuego.Dificultad;
import com.kevinolarte.resibenissa.models.Residencia;
import com.kevinolarte.resibenissa.models.Residente;
import com.kevinolarte.resibenissa.models.User;
import com.kevinolarte.resibenissa.models.moduloOrgSalida.EventoSalida;
import com.kevinolarte.resibenissa.models.moduloOrgSalida.Participante;
import com.kevinolarte.resibenissa.models.moduloWallet.Wallet;
import com.kevinolarte.resibenissa.models.modulojuego.Juego;
import com.kevinolarte.resibenissa.models.modulojuego.RegistroJuego;
import com.kevinolarte.resibenissa.repositories.ResidenciaRepository;
import com.kevinolarte.resibenissa.repositories.ResidenteRepository;
import com.kevinolarte.resibenissa.repositories.UserRepository;
import com.kevinolarte.resibenissa.repositories.moduloOrgSalida.EventoSalidaRepository;
import com.kevinolarte.resibenissa.repositories.moduloOrgSalida.ParticipanteRepository;
import com.kevinolarte.resibenissa.repositories.moduloWallet.WalletRepository;
import com.kevinolarte.resibenissa.repositories.modulojuego.JuegoRepository;
import com.kevinolarte.resibenissa.repositories.modulojuego.RegistroJuegoRepository;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import net.datafaker.Faker;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Component;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.HashSet;
import java.util.List;
import java.util.Random;
import java.util.Set;
@Component
@RequiredArgsConstructor
public class StartupDataLoader {
private final ResidenciaRepository residenciaRepository;
private final UserRepository userRepository;
private final ResidenteRepository residenteRepository;
private final JuegoRepository juegoRepository;
private final RegistroJuegoRepository registroJuegoRepository;
private final EventoSalidaRepository eventoSalidaRepository;
private final ParticipanteRepository participanteRepository;
private final BCryptPasswordEncoder passwordEncoder;
private final WalletRepository walletRepository;
private final Faker faker = new Faker();
private final Set<String> dniGenerados = new HashSet<>();
private final Set<String> emailsGenerados = new HashSet<>();
private final Random random = new Random();
@PostConstruct
public void init() {
Residencia residenciaAdmin = residenciaRepository.save(new Residencia("Residencia Admin", "resiAdmin@gmail.com"));
User user = new User("Kevin", "olarte", "dafault@gmail.com", passwordEncoder.encode("default"), Role.ADMIN);
user.setResidencia(residenciaAdmin);
user.setEnabled(true);
user = userRepository.save(user);
// Cargar datos de prueba al iniciar la aplicación
cargarDatosPrueba();
cargarJuegosPrueba();
cargarRegistrosJuegosPrueba();
cargarEventosSalidaPrueba();
}
public void cargarRegistrosJuegosPrueba() {
List<Residente> residentes = residenteRepository.findAll();
List<Juego> juegos = juegoRepository.findAll();
List<User> user = userRepository.findAll();
if (residentes.isEmpty() || juegos.isEmpty()) {
System.out.println("⚠️ No hay residentes o juegos cargados en la base de datos.");
return;
}
for (Residente residente : residentes) {
for (int i = 0; i < 7; i++) {
RegistroJuego registro = new RegistroJuego();
registro.setResidente(residente);
registro.setJuego(juegos.get(random.nextInt(juegos.size())));
registro.setFecha(LocalDateTime.now().minusDays(random.nextInt(30)));
registro.setNum(random.nextInt(10)); // Fallos entre 0 y 9
registro.setDuracion(30 + random.nextDouble() * 90); // Entre 30 y 120 segundos
registro.setDificultad(Dificultad.values()[random.nextInt(Dificultad.values().length)]);
registro.setObservacion("Prueba generada automáticamente");
registro.setUsuario(user.get(0));
registroJuegoRepository.save(registro);
}
}
}
private void cargarJuegosPrueba() {
Juego juego1 = new Juego("Seguir la línea");
Juego juego2 = new Juego("Memory");
Juego juego3 = new Juego("Bongo");
juegoRepository.save(juego1);
juegoRepository.save(juego2);
juegoRepository.save(juego3);
}
public void cargarEventosSalidaPrueba() {
List<Residencia> residencias = residenciaRepository.findAll();
for (Residencia residencia : residencias) {
// 1 evento esta mañana
eventoSalidaRepository.save(crearEvento(residencia, LocalDateTime.now().with(LocalTime.of(10, 0))));
// 2 eventos pasado mañana
eventoSalidaRepository.save(crearEvento(residencia, LocalDateTime.now().plusDays(2).with(LocalTime.of(9 + random.nextInt(3), 0))));
eventoSalidaRepository.save(crearEvento(residencia, LocalDateTime.now().plusDays(2).with(LocalTime.of(14 + random.nextInt(3), 0))));
// 2 eventos la semana que viene
eventoSalidaRepository.save(crearEvento(residencia, LocalDateTime.now().plusDays(7).with(LocalTime.of(10, 0))));
eventoSalidaRepository.save(crearEvento(residencia, LocalDateTime.now().plusDays(8).with(LocalTime.of(17, 30))));
}
}
private EventoSalida crearEvento(Residencia residencia, LocalDateTime fecha) {
EventoSalida evento = new EventoSalida();
evento.setNombre(faker.funnyName().name());
evento.setDescripcion(faker.lorem().sentence(10));
evento.setFechaInicio(fecha);
evento.setEstado(EstadoSalida.ABIERTO); // Ajusta si usas otro valor por defecto
evento.setResidencia(residencia);
return evento;
}
public void cargarDatosPrueba() {
for (int i = 0; i < 3; i++) {
String ciudad = faker.address().cityName().replaceAll("[^A-Za-z]", "");
String emailResidencia = generarEmailUnico("residencia" + ciudad.toLowerCase());
Residencia residencia = new Residencia();
residencia.setNombre("Residencia " + ciudad);
residencia.setEmail(emailResidencia);
residenciaRepository.save(residencia);
User user = new User();
user.setNombre("Admin " + ciudad);
user.setApellido("Administrador " + ciudad);
user.setEnabled(true);
user.setEmail("admin@" + ciudad.toLowerCase() + ".com");
user.setPassword(passwordEncoder.encode("admin123"));
user.setResidencia(residencia);
user.setRole(Role.NORMAL);
userRepository.save(user);
for (int j = 0; j < 30; j++) {
String nombre = faker.name().firstName();
String apellido = faker.name().lastName();
LocalDate nacimiento = faker.date().birthday(65, 95).toLocalDateTime().toLocalDate();
String dni = generarDniUnico();
String familiar1 = generarEmailUnico(faker.name().firstName());
String familiar2 = null;
Residente residente = new Residente(nombre, apellido, nacimiento, dni, familiar1, familiar2);
residente.setResidencia(residencia);
residenteRepository.save(residente);
}
}
}
private String generarDniUnico() {
String dni;
do {
// DNI español típico: 8 números + letra final (omitimos la letra final para cumplir tu validador de 8)
dni = String.format("%08d", faker.number().numberBetween(10000000, 99999999));
} while (!dniGenerados.add(dni));
return dni;
}
private String generarEmailUnico(String base) {
String email;
int intento = 0;
do {
email = base + (intento == 0 ? "" : intento) + "@resisuite.com";
intento++;
} while (!emailsGenerados.add(email));
return email;
}
}

View File

@ -0,0 +1,21 @@
package com.kevinolarte.resibenissa.config;
import com.kevinolarte.resibenissa.config.interceptor.ControllerLoggerInterceptor;
import lombok.AllArgsConstructor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
@AllArgsConstructor
public class WebConfig implements WebMvcConfigurer {
private final ControllerLoggerInterceptor controllerLoggerInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(controllerLoggerInterceptor)
.addPathPatterns("/**"); // Intercepta todos los endpoints
}
}

View File

@ -0,0 +1,44 @@
package com.kevinolarte.resibenissa.config.interceptor;
import com.kevinolarte.resibenissa.LogContext;
import com.kevinolarte.resibenissa.models.User;
import com.kevinolarte.resibenissa.services.LoggerService;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.AllArgsConstructor;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
@Component
@AllArgsConstructor
public class ControllerLoggerInterceptor implements HandlerInterceptor {
private final LoggerService loggerService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (!(handler instanceof HandlerMethod)) return true;
String endpoint = request.getRequestURI();
String metodo = request.getMethod();
// Aquí puedes añadir más lógica para determinar el usuario autenticado
String descripcion = "Acceso a endpoint";
loggerService.registrarLog(endpoint, metodo, descripcion);
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("Interceptor afterCompletion called");
LogContext.clear();
}
}

View File

@ -0,0 +1,125 @@
package com.kevinolarte.resibenissa.controllers;
import com.kevinolarte.resibenissa.dto.in.auth.LoginUserDto;
import com.kevinolarte.resibenissa.dto.in.auth.RegisterUserDto;
import com.kevinolarte.resibenissa.dto.in.auth.VerifyUserDto;
import com.kevinolarte.resibenissa.dto.out.UserResponseDto;
import com.kevinolarte.resibenissa.exceptions.ApiErrorCode;
import com.kevinolarte.resibenissa.exceptions.ApiException;
import com.kevinolarte.resibenissa.exceptions.ResiException;
import com.kevinolarte.resibenissa.models.User;
import com.kevinolarte.resibenissa.dto.out.LoginResponseDto;
import com.kevinolarte.resibenissa.services.AuthenticationService;
import com.kevinolarte.resibenissa.services.JwtService;
import lombok.AllArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
/**
* Controlador REST para la creacion de usuarios
* <p>
* Expone endpoints relacionados con el registro, inicio de sesión,
* verificación de cuenta por correo y reenvío de códigos de verificación.
* </p>
*
* Todas las rutas están bajo el prefijo <code>/auth</code>.
*
* @author Kevin
*/
@RequestMapping("/auth")
@RestController
@AllArgsConstructor
public class AuthenticationController {
private final JwtService jwtService;
private final AuthenticationService authenticationService;
/**
* Registra un nuevo usuario en el sistema.
* <p>
* El usuario creado se guarda con estado "no activado" hasta que complete
* el proceso de verificación vía código enviado al correo.
* </p>
*
* @param registerUserDto Datos necesarios para registrar al usuario.
* @return {@link ResponseEntity} con los datos del usuario registrado.
*/
@PostMapping("/signup")
public ResponseEntity<UserResponseDto> register(@RequestBody RegisterUserDto registerUserDto){
UserResponseDto user;
try{
user = authenticationService.singUp(registerUserDto);
} catch (ResiException e){
throw new ApiException(e, e.getMessage());
}catch (Exception e){
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), e.getMessage());
}
return ResponseEntity.ok(user);
}
/**
* Autentica a un usuario existente y devuelve un token JWT válido.
*
* @param loginUserDto DTO con email y contraseña.
* @return {@link ResponseEntity} con el token JWT y su tiempo de expiración.
*/
@PostMapping("/login")
public ResponseEntity<LoginResponseDto> authenticate(@RequestBody LoginUserDto loginUserDto){
LoginResponseDto loginResponse;
try{
User userauthentication = authenticationService.authenticate(loginUserDto);
String token = jwtService.generateToken(userauthentication);
loginResponse = new LoginResponseDto(
token,
jwtService.getExpirationTime(),
userauthentication
);
}catch (ResiException e){
throw new ApiException(e, e.getMessage());
}catch (Exception e){
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), e.getMessage());
}
return ResponseEntity.ok(loginResponse);
}
/**
* Verifica un usuario usando el código enviado por correo.
*
* @param verifyUserDto DTO con email y código de verificación.
* @return {@link ResponseEntity} con mensaje de éxito.
*/
@PostMapping("/verify")
public ResponseEntity<String> verifyUser(@RequestBody VerifyUserDto verifyUserDto){
try{
authenticationService.verifyUser(verifyUserDto);
}catch (ResiException e){
throw new ApiException(e, e.getMessage());
}catch (Exception e){
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), e.getMessage());
}
return ResponseEntity.ok("AcountVerfied");
}
/**
* Reenvía un nuevo código de verificación al correo indicado.
*
* @param email Dirección de correo del usuario.
* @return {@link ResponseEntity} con mensaje de confirmación.
*/
@PostMapping("/resend")
public ResponseEntity<String> resendVerificationCode(@RequestParam String email ){
try{
authenticationService.resendVerificationCode(email);
}catch (ResiException e){
throw new ApiException(e, e.getMessage());
}catch (Exception e){
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), e.getMessage());
}
return ResponseEntity.ok("Verification Code Resent");
}
}

View File

@ -0,0 +1,71 @@
package com.kevinolarte.resibenissa.controllers;
import com.kevinolarte.resibenissa.exceptions.ApiErrorCode;
import com.kevinolarte.resibenissa.exceptions.ApiException;
import com.kevinolarte.resibenissa.exceptions.ResiException;
import com.kevinolarte.resibenissa.services.JwtService;
import com.kevinolarte.resibenissa.services.moduloOrgSalida.ParticipanteService;
import lombok.AllArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
/**
* Controlador REST para manejar operaciones públicas relacionadas con participantes en eventos.
* Permite confirmar o denegar permisos de participantes en eventos de una residencia.
* <P>
* URL Base: {@code /public}
* @author Kevin Olarte
*
*/
@RequestMapping("/public")
@RestController
@AllArgsConstructor
public class PublicController {
private final JwtService jwtService;
private final ParticipanteService participanteService;
/**
* Endpoint para confirmar el permiso de un participante en un evento.
* @param token Token JWT que contiene la información del participante, evento y residencia.
* @return ResponseEntity con un mensaje de éxito o error.
*/
@GetMapping("/allowParticipante")
public ResponseEntity<String> confirmarPermiso(@RequestParam String token) {
try {
Long idParticipante = Long.parseLong(jwtService.extractClaim(token, claims -> claims.get("idParticipante").toString()));
Long idEvento = Long.parseLong(jwtService.extractClaim(token, claims -> claims.get("idEvento").toString()));
Long idResidencia = Long.parseLong(jwtService.extractClaim(token, claims -> claims.get("idResidencia").toString()));
participanteService.allow(idResidencia, idEvento, idParticipante);
return ResponseEntity.ok("✅ Permiso registrado correctamente.");
} catch (ResiException e) {
throw new ApiException(e, e.getMessage());
}catch (Exception e) {
throw new ApiException(new ResiException(ApiErrorCode.ENDPOINT_PROTEGIDO), e.getMessage());
}
}
/**
* Endpoint para confirmar la denegación de un participante en un evento.
* @param token Token JWT que contiene la información del participante, evento y residencia.
* @return ResponseEntity con un mensaje de éxito o error.
*/
@GetMapping("/denyParticipante")
public ResponseEntity<String> confirmarDenegacion(@RequestParam String token) {
try {
Long idParticipante = Long.parseLong(jwtService.extractClaim(token, claims -> claims.get("idParticipante").toString()));
Long idEvento = Long.parseLong(jwtService.extractClaim(token, claims -> claims.get("idEvento").toString()));
Long idResidencia = Long.parseLong(jwtService.extractClaim(token, claims -> claims.get("idResidencia").toString()));
participanteService.deny(idResidencia, idEvento, idParticipante);
return ResponseEntity.ok("✅ Denegación registrada correctamente.");
} catch (ResiException e) {
throw new ApiException(e, e.getMessage());
}catch (Exception e) {
throw new ApiException(new ResiException(ApiErrorCode.ENDPOINT_PROTEGIDO), e.getMessage());
}
}
}

View File

@ -0,0 +1,308 @@
package com.kevinolarte.resibenissa.controllers.moduloOrgSalida.evento;
import com.kevinolarte.resibenissa.dto.in.moduloOrgSalida.EventoSalidaDto;
import com.kevinolarte.resibenissa.dto.out.moduloOrgSalida.EventoSalidaResponseDto;
import com.kevinolarte.resibenissa.enums.moduloOrgSalida.EstadoSalida;
import com.kevinolarte.resibenissa.exceptions.ApiErrorCode;
import com.kevinolarte.resibenissa.exceptions.ApiException;
import com.kevinolarte.resibenissa.exceptions.ResiException;
import com.kevinolarte.resibenissa.models.User;
import com.kevinolarte.resibenissa.services.moduloOrgSalida.EventoSalidaService;
import lombok.AllArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.*;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
/**
* Controlador REST para gestionar los eventos de salida de una residencia.
* <p>
* Permite crear, actualizar, eliminar, consultar y listar eventos de salida
* asociados a una residencia específica.
* </p>
*
* URL base: /admin/resi
* @author Kevin Olarte
*/
@RequestMapping("/admin/resi")
@RestController
@AllArgsConstructor
public class EventoSalidaAdminController {
public final EventoSalidaService eventoSalidaService;
/**
* Crea un nuevo evento de salida en una residencia.
*
* @param idResidencia ID de la residencia donde se creará el evento de salida.
* @param input DTO que contiene los datos del evento de salida a crear.
* @return {@link ResponseEntity} con el evento de salida creado.ç
* @throws ApiException si ocurre un error al procesar la solicitud.
*/
@PostMapping("/{idResidencia}/evento/add")
public ResponseEntity<EventoSalidaResponseDto> add(
@PathVariable Long idResidencia,
@RequestBody EventoSalidaDto input) {
try {
return ResponseEntity.status(HttpStatus.CREATED)
.body(eventoSalidaService.add(input, idResidencia));
} catch (ResiException e) {
throw new ApiException(e, e.getMessage());
} catch (Exception e) {
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), e.getMessage());
}
}
/**
* Obtiene los datos de un evento de salida específico.
*
* @param idResidencia ID de la residencia a la que pertenece el evento de salida.
* @param idEventoSalida ID del evento de salida a consultar.
* @return {@link ResponseEntity} con los datos del evento de salida encontrado.
* @throws ApiException si ocurre un error al procesar la solicitud.
*/
@GetMapping("/{idResidencia}/evento/{idEventoSalida}/get")
public ResponseEntity<EventoSalidaResponseDto> getEventoSalida(
@PathVariable Long idResidencia,
@PathVariable Long idEventoSalida) {
try {
return ResponseEntity.ok(eventoSalidaService.get(idEventoSalida, idResidencia));
} catch (ResiException e) {
throw new ApiException(e, e.getMessage());
} catch (Exception e) {
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), e.getMessage());
}
}
/**
* Obtiene una lista de eventos de salida con filtros dinámicos.
*
* @param idResidencia ID de la residencia (obligatorio)
* @param fecha Fecha exacta del evento
* @param minFecha Fecha mínima del evento (rango)
* @param maxFecha Fecha máxima del evento (rango)
* @param estado Estado del evento (PENDIENTE, REALIZADA, CANCELADA...)
* @param idResidente Filtra eventos que contengan al residente con este ID
* @param idParticipante Filtra eventos que contengan al participante con este ID
* @param minRH Mínimo número de participantes con recursos humanos
* @param maxRH Máximo número de participantes con recursos humanos
* @param minRM Mínimo número de participantes con recursos materiales
* @param maxRM Máximo número de participantes con recursos materiales
* @return Lista de eventos de salida que cumplen los filtros
* @throws ApiException si ocurre un error al procesar la solicitud.
*/
@GetMapping("/{idResidencia}/evento/getAll")
public ResponseEntity<List<EventoSalidaResponseDto>> getAllEventosSalida(
@PathVariable Long idResidencia,
@RequestParam(required = false) LocalDate fecha,
@RequestParam(required = false) LocalDate minFecha,
@RequestParam(required = false) LocalDate maxFecha,
@RequestParam(required = false) EstadoSalida estado,
@RequestParam(required = false) Long idResidente,
@RequestParam(required = false) Long idParticipante,
@RequestParam(required = false) Integer minRH,
@RequestParam(required = false) Integer maxRH,
@RequestParam(required = false) Integer minRM,
@RequestParam(required = false) Integer maxRM) {
try {
return ResponseEntity.ok(eventoSalidaService.getAll(
idResidencia, fecha, minFecha, maxFecha, estado,
idResidente, idParticipante, minRH, maxRH, minRM, maxRM));
} catch (ResiException e) {
throw new ApiException(e, e.getMessage());
} catch (Exception e) {
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), e.getMessage());
}
}
/**
* Obtiene una lista de eventos de salida con filtros dinámicos.
*
* @param fecha Fecha exacta del evento
* @param minFecha Fecha mínima del evento (rango)
* @param maxFecha Fecha máxima del evento (rango)
* @param estado Estado del evento (PENDIENTE, REALIZADA, CANCELADA...)
* @param idResidente Filtra eventos que contengan al residente con este ID
* @param idParticipante Filtra eventos que contengan al participante con este ID
* @param minRH Mínimo número de participantes con recursos humanos
* @param maxRH Máximo número de participantes con recursos humanos
* @param minRM Mínimo número de participantes con recursos materiales
* @param maxRM Máximo número de participantes con recursos materiales
* @return Lista de eventos de salida que cumplen los filtros
* @throws ApiException si ocurre un error al procesar la solicitud.
*/
@GetMapping("/evento/getAll")
public ResponseEntity<List<EventoSalidaResponseDto>> getAllEventosSalidaSinResidencia(
@RequestParam(required = false) LocalDate fecha,
@RequestParam(required = false) LocalDate minFecha,
@RequestParam(required = false) LocalDate maxFecha,
@RequestParam(required = false) EstadoSalida estado,
@RequestParam(required = false) Long idResidente,
@RequestParam(required = false) Long idParticipante,
@RequestParam(required = false) Integer minRH,
@RequestParam(required = false) Integer maxRH,
@RequestParam(required = false) Integer minRM,
@RequestParam(required = false) Integer maxRM) {
try {
return ResponseEntity.ok(eventoSalidaService.getAll(
fecha, minFecha, maxFecha, estado,
idResidente, idParticipante, minRH, maxRH, minRM, maxRM));
} catch (ResiException e) {
throw new ApiException(e, e.getMessage());
} catch (Exception e) {
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), e.getMessage());
}
}
/**
* Elimina un evento de salida de una residencia.
* <p>
* La eliminación incluye también a todos los participantes asociados al evento.
* </p>
*
* @param idResidencia ID de la residencia a la que pertenece el evento de salida.
* @param idEventoSalida ID del evento de salida a eliminar.
* @return {@link ResponseEntity} sin contenido si la eliminación fue exitosa.
* @throws ApiException si ocurre un error al procesar la solicitud.
*/
@DeleteMapping("/{idResidencia}/evento/{idEventoSalida}/delete")
public ResponseEntity<Void> delete(
@PathVariable Long idResidencia,
@PathVariable Long idEventoSalida) {
try {
eventoSalidaService.delete(idEventoSalida, idResidencia);
return ResponseEntity.noContent().build();
} catch (ResiException e) {
throw new ApiException(e, e.getMessage());
} catch (Exception e) {
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), e.getMessage());
}
}
/**
* Cambia el nombre de un evento de salida.
*
* @param idResidencia ID de la residencia a la que pertenece el evento de salida.
* @param idEventoSalida ID del evento de salida a actualizar.
* @param nombre Nuevo nombre del evento de salida.
* @return {@link ResponseEntity} con el evento de salida actualizado.
* @throws ApiException si ocurre un error al procesar la solicitud.
*/
@PatchMapping("/{idResidencia}/evento/{idEventoSalida}/changeNombre")
public ResponseEntity<EventoSalidaResponseDto> changeNombre(
@PathVariable Long idResidencia,
@PathVariable Long idEventoSalida,
@RequestParam String nombre) {
try {
return ResponseEntity.ok(eventoSalidaService.changeNombre(idEventoSalida, nombre, idResidencia));
} catch (ResiException e) {
throw new ApiException(e, e.getMessage());
} catch (Exception e) {
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), e.getMessage());
}
}
/**
* Cambia la descripción de un evento de salida.
*
* @param idResidencia ID de la residencia a la que pertenece el evento de salida.
* @param idEventoSalida ID del evento de salida a actualizar.
* @param descripcion Nueva descripción del evento de salida.
* @return {@link ResponseEntity} con el evento de salida actualizado.
* @throws ApiException si ocurre un error al procesar la solicitud.
*/
@PatchMapping("/{idResidencia}/evento/{idEventoSalida}/changeDescripcion")
public ResponseEntity<EventoSalidaResponseDto> changeDescripcion(
@PathVariable Long idResidencia,
@PathVariable Long idEventoSalida,
@RequestParam String descripcion) {
try {
return ResponseEntity.ok(eventoSalidaService.changeDescripcion(idEventoSalida, descripcion, idResidencia));
} catch (ResiException e) {
throw new ApiException(e, e.getMessage());
} catch (Exception e) {
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), e.getMessage());
}
}
/**
* Cambia la fecha de un evento de salida.
*
* @param idResidencia ID de la residencia a la que pertenece el evento de salida.
* @param idEventoSalida ID del evento de salida a actualizar.
* @param fecha Nueva fecha del evento de salida.
* @return {@link ResponseEntity} con el evento de salida actualizado.
* @throws ApiException si ocurre un error al procesar la solicitud.
*/
@PatchMapping("/{idResidencia}/evento/{idEventoSalida}/changeFecha")
public ResponseEntity<EventoSalidaResponseDto> changeFecha(
@PathVariable Long idResidencia,
@PathVariable Long idEventoSalida,
@RequestParam LocalDateTime fecha) {
try {
return ResponseEntity.ok(eventoSalidaService.changeFecha(idEventoSalida, fecha, idResidencia));
} catch (ResiException e) {
throw new ApiException(e, e.getMessage());
} catch (Exception e) {
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), e.getMessage());
}
}
/**
* Cambia el estado de un evento de salida.
*
* @param idResidencia ID de la residencia a la que pertenece el evento de salida.
* @param idEventoSalida ID del evento de salida a actualizar.
* @param estado Nuevo estado del evento de salida.
* @return {@link ResponseEntity} con el evento de salida actualizado.
* @throws ApiException si ocurre un error al procesar la solicitud.
*/
@PatchMapping("/{idResidencia}/evento/{idEventoSalida}/changeEstado")
public ResponseEntity<EventoSalidaResponseDto> changeEstado(
@PathVariable Long idResidencia,
@PathVariable Long idEventoSalida,
@RequestParam EstadoSalida estado) {
try {
return ResponseEntity.ok(eventoSalidaService.changeEstado(idEventoSalida, estado, idResidencia));
} catch (ResiException e) {
throw new ApiException(e, e.getMessage());
} catch (Exception e) {
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), e.getMessage());
}
}
/**
* Actualiza los datos de un evento de salida.
*
* @param idResidencia ID de la residencia a la que pertenece el evento de salida.
* @param idEventoSalida ID del evento de salida a actualizar.
* @param input DTO con los nuevos datos del evento de salida.
* @return {@link ResponseEntity} con el evento de salida actualizado.
* @throws ApiException si ocurre un error al procesar la solicitud.
*/
@PatchMapping("/{idResidencia}/evento/{idEventoSalida}/update")
public ResponseEntity<EventoSalidaResponseDto> update(
@PathVariable Long idResidencia,
@PathVariable Long idEventoSalida,
@RequestBody EventoSalidaDto input) {
try {
return ResponseEntity.ok(eventoSalidaService.update(input, idResidencia, idEventoSalida));
} catch (ResiException e) {
throw new ApiException(e, e.getMessage());
} catch (Exception e) {
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), e.getMessage());
}
}
}

View File

@ -0,0 +1,261 @@
package com.kevinolarte.resibenissa.controllers.moduloOrgSalida.evento;
import com.kevinolarte.resibenissa.dto.in.moduloOrgSalida.EventoSalidaDto;
import com.kevinolarte.resibenissa.dto.out.moduloOrgSalida.EventoSalidaResponseDto;
import com.kevinolarte.resibenissa.enums.moduloOrgSalida.EstadoSalida;
import com.kevinolarte.resibenissa.exceptions.ApiErrorCode;
import com.kevinolarte.resibenissa.exceptions.ApiException;
import com.kevinolarte.resibenissa.exceptions.ResiException;
import com.kevinolarte.resibenissa.models.User;
import com.kevinolarte.resibenissa.services.moduloOrgSalida.EventoSalidaService;
import lombok.AllArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.*;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
/**
* Controlador REST para gestionar los eventos de salida de una residencia.
* <p>
* Permite crear, actualizar, eliminar, consultar y listar eventos de salida
* asociados a una residencia específica.
* </p>
*
* URL base: /resi/evento
* @author Kevin Olarte
*/
@RequestMapping("/resi/evento")
@RestController
@AllArgsConstructor
public class EventoSalidaController {
private final EventoSalidaService eventoSalidaService;
/**
* Crea un nuevo evento de salida en una residencia.
*
* @param input DTO que contiene los datos del evento de salida a crear.
* @return {@link ResponseEntity} con el evento de salida creado.
* @throws ApiException si ocurre un error al procesar la solicitud.
*/
@PostMapping("/add")
public ResponseEntity<EventoSalidaResponseDto> add(@RequestBody EventoSalidaDto input) {
User currentUser = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
try {
return ResponseEntity.status(HttpStatus.CREATED)
.body(eventoSalidaService.add(input, currentUser.getResidencia().getId()));
} catch (ResiException e) {
throw new ApiException(e, currentUser);
} catch (Exception e) {
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), currentUser, e.getMessage());
}
}
/**
* Obtiene los datos de un evento de salida específico.
*
* @param idEventoSalida ID del evento de salida a consultar.
* @return {@link ResponseEntity} con los datos del evento de salida encontrado.
* @throws ApiException si ocurre un error al procesar la solicitud.
*/
@GetMapping("/{idEventoSalida}/get")
public ResponseEntity<EventoSalidaResponseDto> getEventoSalida(@PathVariable Long idEventoSalida) {
User currentUser = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
try {
return ResponseEntity.ok(eventoSalidaService.get(idEventoSalida, currentUser.getResidencia().getId()));
} catch (ResiException e) {
throw new ApiException(e, currentUser);
} catch (Exception e) {
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), currentUser, e.getMessage());
}
}
/**
* Obtiene una lista de eventos de salida filtrados por varios parámetros.
*
* @param fecha Fecha específica del evento de salida.
* @param minFecha Fecha mínima del evento de salida.
* @param maxFecha Fecha máxima del evento de salida.
* @param estado Estado del evento de salida.
* @param idResidente ID del residente asociado al evento de salida.
* @param idParticipante ID del participante asociado al evento de salida.
* @param minRH Mínimo rango horario del evento de salida.
* @param maxRH Máximo rango horario del evento de salida.
* @param minRM Mínimo rango de minutos del evento de salida.
* @param maxRM Máximo rango de minutos del evento de salida.
* @return {@link ResponseEntity} con la lista de eventos de salida encontrados.
* @throws ApiException si ocurre un error al procesar la solicitud.
*/
@GetMapping("/getAll")
public ResponseEntity<List<EventoSalidaResponseDto>> getAllEventosSalida(
@RequestParam(required = false) LocalDate fecha,
@RequestParam(required = false) LocalDate minFecha,
@RequestParam(required = false) LocalDate maxFecha,
@RequestParam(required = false) EstadoSalida estado,
@RequestParam(required = false) Long idResidente,
@RequestParam(required = false) Long idParticipante,
@RequestParam(required = false) Integer minRH,
@RequestParam(required = false) Integer maxRH,
@RequestParam(required = false) Integer minRM,
@RequestParam(required = false) Integer maxRM) {
User currentUser = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
try {
return ResponseEntity.ok(eventoSalidaService.getAll(
currentUser.getResidencia().getId(),
fecha, minFecha, maxFecha, estado,
idResidente, idParticipante, minRH, maxRH, minRM, maxRM));
} catch (ResiException e) {
throw new ApiException(e, currentUser);
} catch (Exception e) {
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), currentUser, e.getMessage());
}
}
/**
* Elimina un evento de salida de una residencia.
* <p>
* La eliminación incluye también a todos los participantes asociados al evento.
* </p>
*
* @param idEventoSalida ID del evento de salida a eliminar.
* @return {@link ResponseEntity} con estado NO_CONTENT si la eliminación fue exitosa.
* @throws ApiException si ocurre un error al procesar la solicitud.
*/
@DeleteMapping("/{idEventoSalida}/delete")
public ResponseEntity<Void> delete(@PathVariable Long idEventoSalida) {
User currentUser = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
try {
eventoSalidaService.delete(idEventoSalida, currentUser.getResidencia().getId());
return ResponseEntity.status(HttpStatus.NO_CONTENT).build();
} catch (ResiException e) {
throw new ApiException(e, currentUser);
} catch (Exception e) {
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), currentUser, e.getMessage());
}
}
/**
* Cambia el nombre de un evento de salida.
*
* @param idEventoSalida ID del evento de salida a actualizar.
* @param nombre Nuevo nombre del evento de salida.
* @return {@link ResponseEntity} con el evento de salida actualizado.
* @throws ApiException si ocurre un error al procesar la solicitud.
*/
@PatchMapping("/{idEventoSalida}/changeNombre")
public ResponseEntity<EventoSalidaResponseDto> changeNombre(
@PathVariable Long idEventoSalida,
@RequestParam String nombre) {
User currentUser = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
try {
return ResponseEntity.ok(eventoSalidaService.changeNombre(idEventoSalida, nombre, currentUser.getResidencia().getId()));
} catch (ResiException e) {
throw new ApiException(e, currentUser);
} catch (Exception e) {
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), currentUser, e.getMessage());
}
}
/**
* Cambia la descripción de un evento de salida.
*
* @param idEventoSalida ID del evento de salida a actualizar.
* @param descripcion Nueva descripción del evento de salida.
* @return {@link ResponseEntity} con el evento de salida actualizado.
* @throws ApiException si ocurre un error al procesar la solicitud.
*/
@PatchMapping("/{idEventoSalida}/changeDescripcion")
public ResponseEntity<EventoSalidaResponseDto> changeDescripcion(
@PathVariable Long idEventoSalida,
@RequestParam String descripcion) {
User currentUser = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
try {
return ResponseEntity.ok(eventoSalidaService.changeDescripcion(idEventoSalida, descripcion, currentUser.getResidencia().getId()));
} catch (ResiException e) {
throw new ApiException(e, currentUser);
} catch (Exception e) {
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), currentUser, e.getMessage());
}
}
/**
* Cambia la fecha de un evento de salida.
*
* @param idEventoSalida ID del evento de salida a actualizar.
* @param fecha Nueva fecha del evento de salida.
* @return {@link ResponseEntity} con el evento de salida actualizado.
* @throws ApiException si ocurre un error al procesar la solicitud.
*/
@PatchMapping("/{idEventoSalida}/ChangeFecha")
public ResponseEntity<EventoSalidaResponseDto> changeFecha(
@PathVariable Long idEventoSalida,
@RequestParam LocalDateTime fecha) {
User currentUser = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
try {
return ResponseEntity.ok(eventoSalidaService.changeFecha(idEventoSalida, fecha, currentUser.getResidencia().getId()));
} catch (ResiException e) {
throw new ApiException(e, currentUser);
} catch (Exception e) {
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), currentUser, e.getMessage());
}
}
/**
* Cambia el estado de un evento de salida.
*
* @param idEventoSalida ID del evento de salida a actualizar.
* @param estado Nuevo estado del evento de salida.
* @return {@link ResponseEntity} con el evento de salida actualizado.
* @throws ApiException si ocurre un error al procesar la solicitud.
*/
@PatchMapping("/{idEventoSalida}/changeEstado")
public ResponseEntity<EventoSalidaResponseDto> changeEstado(
@PathVariable Long idEventoSalida,
@RequestParam EstadoSalida estado) {
User currentUser = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
try {
return ResponseEntity.ok(eventoSalidaService.changeEstado(idEventoSalida, estado, currentUser.getResidencia().getId()));
} catch (ResiException e) {
throw new ApiException(e, currentUser);
} catch (Exception e) {
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), currentUser, e.getMessage());
}
}
/**
* Actualiza los datos de un evento de salida.
*
* @param idEventoSalida ID del evento de salida a actualizar.
* @param input DTO con los nuevos datos del evento de salida.
* @return {@link ResponseEntity} con el evento de salida actualizado.
* @throws ApiException si ocurre un error al procesar la solicitud.
*/
@PatchMapping("/{idEventoSalida}/update")
public ResponseEntity<EventoSalidaResponseDto> update(
@PathVariable Long idEventoSalida,
@RequestBody EventoSalidaDto input) {
User currentUser = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
try {
return ResponseEntity.ok(eventoSalidaService.update(input, currentUser.getResidencia().getId(), idEventoSalida));
} catch (ResiException e) {
throw new ApiException(e, currentUser);
} catch (Exception e) {
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), currentUser, e.getMessage());
}
}
}

View File

@ -0,0 +1,297 @@
package com.kevinolarte.resibenissa.controllers.moduloOrgSalida.participante;
import com.kevinolarte.resibenissa.dto.in.moduloOrgSalida.ParticipanteDto;
import com.kevinolarte.resibenissa.dto.out.moduloOrgSalida.ParticipanteResponseDto;
import com.kevinolarte.resibenissa.exceptions.ApiErrorCode;
import com.kevinolarte.resibenissa.exceptions.ApiException;
import com.kevinolarte.resibenissa.exceptions.ResiException;
import com.kevinolarte.resibenissa.models.User;
import com.kevinolarte.resibenissa.services.moduloOrgSalida.ParticipanteService;
import lombok.AllArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* Controlador REST para gestionar los participantes de un evento de salida en una residencia.
* <p>
* Permite agregar, consultar, actualizar, eliminar y listar participantes asociados
* a un evento específico dentro de una residencia.
* </p>
*
* URL Base: {@code /admin/resi/{idResidencia}/evento/{idEvento}/participante}
* @author Kevin Olarte
*/
@RequestMapping("/admin/resi/{idResidencia}/evento/{idEvento}/participante")
@RestController
@AllArgsConstructor
public class ParticipanteAdminController {
private final ParticipanteService participanteService;
/**
* Registra un nuevo participante en un evento de salida.
*
* @param idResidencia ID de la residencia donde se registra el participante.
* @param idEvento ID del evento de salida al que se registra el participante.
* @param participanteDto DTO con los datos del participante a registrar.
* @return {@link ResponseEntity} con el participante creado.
* @throws ApiException si ocurre un error al procesar la solicitud.
*/
@PostMapping("/add")
public ResponseEntity<ParticipanteResponseDto> add(
@PathVariable Long idResidencia,
@PathVariable Long idEvento,
@RequestBody ParticipanteDto participanteDto) {
try {
return ResponseEntity.status(HttpStatus.CREATED)
.body(participanteService.add(participanteDto, idEvento, idResidencia));
} catch (ResiException e) {
throw new ApiException(e, e.getMessage());
} catch (Exception e) {
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), e.getMessage());
}
}
/**
* Obtiene los datos de un participante específico en un evento de salida.
*
* @param idResidencia ID de la residencia donde se encuentra el evento.
* @param idEvento ID del evento de salida al que pertenece el participante.
* @param idParticipante ID del participante a consultar.
* @return {@link ResponseEntity} con los datos del participante encontrado.
* @throws ApiException si ocurre un error al procesar la solicitud.
*/
@GetMapping("{idParticipante}/get")
public ResponseEntity<ParticipanteResponseDto> get(
@PathVariable Long idResidencia,
@PathVariable Long idEvento,
@PathVariable Long idParticipante) {
try {
return ResponseEntity.ok(participanteService.get(idResidencia, idEvento, idParticipante));
} catch (ResiException e) {
throw new ApiException(e, e.getMessage());
} catch (Exception e) {
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), e.getMessage());
}
}
/**
* Obtiene una lista de todos los participantes de un evento de salida, con opciones de filtrado.
*
* @param idResidencia ID de la residencia donde se encuentra el evento.
* @param idEvento ID del evento de salida del que se obtienen los participantes.
* @param idResidente ID del residente (opcional) para filtrar por residente específico.
* @param rM Recurso material (opcional) para filtrar por recurso material.
* @param rH Recurso humano (opcional) para filtrar por recurso humano.
* @param minEdad Edad mínima del participante (opcional).
* @param maxEdad Edad máxima del participante (opcional).
* @param preOpinion Filtra por participantes con opinión previa (opcional).
* @param postOpinion Filtra por participantes con opinión posterior (opcional).
* @param asistenciPermitida Filtra por participantes con asistencia permitida (opcional).
* @return {@link ResponseEntity} con la lista de participantes encontrados.
* @throws ApiException si ocurre un error al procesar la solicitud.
*/
@GetMapping("/getAll")
public ResponseEntity<List<ParticipanteResponseDto>> getAllParticipantes(
@PathVariable Long idResidencia,
@PathVariable Long idEvento,
@RequestParam(required = false) Long idResidente,
@RequestParam(required = false) Boolean rM,
@RequestParam(required = false) Boolean rH,
@RequestParam(required = false) Integer minEdad,
@RequestParam(required = false) Integer maxEdad,
@RequestParam(required = false) Boolean preOpinion,
@RequestParam(required = false) Boolean postOpinion,
@RequestParam(required = false) Boolean asistenciPermitida) {
try {
List<ParticipanteResponseDto> result = participanteService.getAll(
idResidencia, idEvento, idResidente, rM, rH, minEdad, maxEdad, preOpinion, postOpinion, asistenciPermitida
);
return ResponseEntity.ok(result);
} catch (ResiException e) {
throw new ApiException(e, e.getMessage());
} catch (Exception e) {
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), e.getMessage());
}
}
/**
* Elimina un participante de un evento de salida.
*
* @param idParticipante ID del participante a eliminar.
* @param idResidencia ID de la residencia donde se encuentra el evento.
* @param idEvento ID del evento de salida del que se elimina el participante.
* @return {@link ResponseEntity} con el estado de la operación.
* @throws ApiException si ocurre un error al procesar la solicitud.
*/
@DeleteMapping("/{idParticipante}/delete")
public ResponseEntity<Void> deleteParticipante(
@PathVariable Long idResidencia,
@PathVariable Long idEvento,
@PathVariable Long idParticipante) {
try {
participanteService.deleteParticipante(idResidencia, idEvento, idParticipante);
return ResponseEntity.status(HttpStatus.NO_CONTENT).build();
} catch (ResiException e) {
throw new ApiException(e, e.getMessage());
} catch (Exception e) {
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), e.getMessage());
}
}
/**
* Actualiza los datos de un participante existente en un evento de salida.
*
* @param idParticipante ID del participante a actualizar.
* @param idResidencia ID de la residencia donde se encuentra el evento.
* @param idEvento ID del evento de salida al que pertenece el participante.
* @param participanteDto DTO con los nuevos datos del participante.
* @return {@link ResponseEntity} con el participante actualizado.
* @throws ApiException si ocurre un error al procesar la solicitud.
*/
@PatchMapping("/{idParticipante}/update")
public ResponseEntity<ParticipanteResponseDto> update(
@PathVariable Long idResidencia,
@PathVariable Long idEvento,
@PathVariable Long idParticipante,
@RequestBody ParticipanteDto participanteDto) {
try {
return ResponseEntity.ok(participanteService.update(participanteDto, idResidencia, idEvento, idParticipante));
} catch (ResiException e) {
throw new ApiException(e, e.getMessage());
} catch (Exception e) {
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), e.getMessage());
}
}
/**
* Añade una opinión previa de un participante en un evento de salida.
*
* @param idParticipante ID del participante al que se le añade la opinión.
* @param preOpinion Opinión previa del participante.
* @return {@link ResponseEntity} con el participante actualizado.
* @throws ApiException si ocurre un error al procesar la solicitud.
*/
@PatchMapping("/{idParticipante}/addPreOpinion")
public ResponseEntity<ParticipanteResponseDto> addPreOpinion(
@PathVariable Long idResidencia,
@PathVariable Long idEvento,
@PathVariable Long idParticipante,
@RequestParam String preOpinion) {
try {
return ResponseEntity.ok(participanteService.addPreOpinion(idResidencia, idEvento, idParticipante, preOpinion));
} catch (ResiException e) {
throw new ApiException(e, e.getMessage());
} catch (Exception e) {
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), e.getMessage());
}
}
/**
* Agrega una opinión posterior a la participación de un residente en un evento de salida.
*
* @param idParticipante ID del participante al que se le agregará la opinión.
* @param idResidencia ID de la residencia donde se encuentra el evento.
* @param idEvento ID del evento de salida al que pertenece el participante.
* @param postOpinion Opinión posterior a la participación.
* @return {@link ResponseEntity} con el participante actualizado.
*/
@PatchMapping("/{idParticipante}/addPostOpinion")
public ResponseEntity<ParticipanteResponseDto> addPostOpinion(
@PathVariable Long idResidencia,
@PathVariable Long idEvento,
@PathVariable Long idParticipante,
@RequestParam String postOpinion) {
try {
return ResponseEntity.ok(participanteService.addPostOpinion(idResidencia, idEvento, idParticipante, postOpinion));
} catch (ResiException e) {
throw new ApiException(e, e.getMessage());
} catch (Exception e) {
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), e.getMessage());
}
}
/**
* Cambia los recursos asignados a un participante en un evento de salida.
*
* @param idParticipante ID del participante a actualizar.
* @param idResidencia ID de la residencia donde se encuentra el evento.
* @param idEvento ID del evento de salida al que pertenece el participante.
* @param rH Recurso humano (opcional).
* @param rM Recurso material (opcional).
* @return {@link ResponseEntity} con el participante actualizado.
* @throws ApiException si ocurre un error al procesar la solicitud.
*/
@PatchMapping("/{idParticipante}/changeRecursos")
public ResponseEntity<ParticipanteResponseDto> changeRecursos(
@PathVariable Long idResidencia,
@PathVariable Long idEvento,
@PathVariable Long idParticipante,
@RequestParam(required = false) Boolean rH,
@RequestParam(required = false) Boolean rM) {
try {
return ResponseEntity.ok(participanteService.changeRecursos(idResidencia, idEvento, idParticipante, rH, rM));
} catch (ResiException e) {
throw new ApiException(e, e.getMessage());
} catch (Exception e) {
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), e.getMessage());
}
}
/**
* Acepta la participación un participante en un evento de salida.
*
* @param idParticipante ID del participante a aceptar.
* @param idResidencia ID de la residencia donde se encuentra el evento.
* @param idEvento ID del evento de salida al que pertenece el participante.
* @return {@link ResponseEntity} con el participante aceptado.
* @throws ApiException si ocurre un error al procesar la solicitud.
*/
@PostMapping("/{idParticipante}/allow")
public ResponseEntity<ParticipanteResponseDto> allow(
@PathVariable Long idResidencia,
@PathVariable Long idEvento,
@PathVariable Long idParticipante) {
try {
return ResponseEntity.ok(participanteService.allow(idResidencia, idEvento, idParticipante));
} catch (ResiException e) {
throw new ApiException(e, e.getMessage());
} catch (Exception e) {
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), e.getMessage());
}
}
/**
* Deniega la participación de un residente en un evento de salida.
*
* @param idParticipante ID del participante a denegar.
* @param idResidencia ID de la residencia donde se encuentra el evento.
* @param idEvento ID del evento de salida al que pertenece el participante.
* @return {@link ResponseEntity} con el participante actualizado.
* @throws ApiException si ocurre un error al procesar la solicitud.
*/
@PostMapping("/{idParticipante}/deny")
public ResponseEntity<ParticipanteResponseDto> deny(
@PathVariable Long idResidencia,
@PathVariable Long idEvento,
@PathVariable Long idParticipante) {
try {
return ResponseEntity.ok(participanteService.deny(idResidencia, idEvento, idParticipante));
} catch (ResiException e) {
throw new ApiException(e, e.getMessage());
} catch (Exception e) {
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), e.getMessage());
}
}
}

View File

@ -0,0 +1,278 @@
package com.kevinolarte.resibenissa.controllers.moduloOrgSalida.participante;
import com.kevinolarte.resibenissa.dto.in.moduloOrgSalida.ParticipanteDto;
import com.kevinolarte.resibenissa.dto.out.moduloOrgSalida.ParticipanteResponseDto;
import com.kevinolarte.resibenissa.exceptions.ApiErrorCode;
import com.kevinolarte.resibenissa.exceptions.ApiException;
import com.kevinolarte.resibenissa.exceptions.ResiException;
import com.kevinolarte.resibenissa.models.User;
import com.kevinolarte.resibenissa.services.EmailService;
import com.kevinolarte.resibenissa.services.moduloOrgSalida.ParticipanteService;
import lombok.AllArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* Controlador REST para gestionar los participantes de un evento de salida en una residencia.
* <p>
* Permite agregar, consultar, actualizar, eliminar y listar participantes asociados
* a un evento específico dentro de una residencia.
* </p>
* URL base: /resi/evento/{idEvento}/participante
* @author Kevin Olarte
*/
@RequestMapping("/resi/evento/{idEvento}/participante")
@RestController
@AllArgsConstructor
public class ParticipanteController {
private final ParticipanteService participanteService;
private final EmailService emailService;
/**
* Registra un nuevo participante en un evento de salida.
*
* @param participanteDto DTO con los datos del participante a registrar.
* @return {@link ResponseEntity} con el participante creado.
* @throws ApiException si ocurre un error al procesar la solicitud.
*/
@PostMapping("/add")
public ResponseEntity<ParticipanteResponseDto> add(@PathVariable Long idEvento,
@RequestBody ParticipanteDto participanteDto) {
User currentUser = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
try {
ParticipanteResponseDto dto = participanteService.add(participanteDto, idEvento, currentUser.getResidencia().getId());
emailService.sendNotificationParticipante(dto);
return ResponseEntity.status(HttpStatus.CREATED).body(dto);
} catch (ResiException e) {
throw new ApiException(e, currentUser);
} catch (Exception e) {
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), currentUser, e.getMessage());
}
}
/**
* Obtiene los datos de un participante específico en un evento de salida.
*
* @param idParticipante ID del participante a consultar.
* @return {@link ResponseEntity} con los datos del participante encontrado.
*/
@GetMapping("{idParticipante}/get")
public ResponseEntity<ParticipanteResponseDto> get(@PathVariable Long idEvento,
@PathVariable Long idParticipante) {
User currentUser = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
try {
return ResponseEntity.ok(participanteService.get(currentUser.getResidencia().getId(), idEvento, idParticipante));
} catch (ResiException e) {
throw new ApiException(e, currentUser);
} catch (Exception e) {
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), currentUser, e.getMessage());
}
}
/**
* Obtiene todos los participantes de un evento de salida, con filtros opcionales.
*
* @param idEvento ID del evento del que se obtienen los participantes.
* @param idResidente ID del residente (opcional).
* @param rM Recurso material (opcional).
* @param rH Recurso humano (opcional).
* @param minEdad Edad mínima del participante (opcional).
* @param maxEdad Edad máxima del participante (opcional).
* @param preOpinion Si se filtran por opinión previa (opcional).
* @param postOpinion Si se filtran por opinión posterior (opcional).
* @param asistenciPermitida Si se filtran por asistencia permitida (opcional).
* @return {@link ResponseEntity} con la lista de participantes filtrados.
* @throws ApiException si ocurre un error al procesar la solicitud.
*/
@GetMapping("/getAll")
public ResponseEntity<List<ParticipanteResponseDto>> getAllParticipantes(
@PathVariable Long idEvento,
@RequestParam(required = false) Long idResidente,
@RequestParam(required = false) Boolean rM,
@RequestParam(required = false) Boolean rH,
@RequestParam(required = false) Integer minEdad,
@RequestParam(required = false) Integer maxEdad,
@RequestParam(required = false) Boolean preOpinion,
@RequestParam(required = false) Boolean postOpinion,
@RequestParam(required = false) Boolean asistenciPermitida) {
User currentUser = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
try {
return ResponseEntity.ok(participanteService.getAll(currentUser.getResidencia().getId(), idEvento, idResidente, rM, rH, minEdad, maxEdad, preOpinion, postOpinion, asistenciPermitida));
} catch (ResiException e) {
throw new ApiException(e, currentUser);
} catch (Exception e) {
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), currentUser, e.getMessage());
}
}
/**
* Elimina un participante de un evento de salida.
*
* @param idParticipante ID del participante a eliminar.
* @return {@link ResponseEntity} sin contenido si la eliminación fue exitosa.
* @throws ApiException si ocurre un error al procesar la solicitud.
*/
@DeleteMapping("/{idParticipante}/delete")
public ResponseEntity<Void> deleteParticipante(@PathVariable Long idEvento,
@PathVariable Long idParticipante) {
User currentUser = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
try {
participanteService.deleteParticipante(currentUser.getResidencia().getId(), idEvento, idParticipante);
return ResponseEntity.noContent().build();
} catch (ResiException e) {
throw new ApiException(e, currentUser);
} catch (Exception e) {
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), currentUser, e.getMessage());
}
}
/**
* Añade una opinión previa de un participante en un evento de salida.
*
* @param idParticipante ID del participante al que se le añade la opinión.
* @param preOpinion Opinión previa del participante.
* @return {@link ResponseEntity} con el participante actualizado.
* @throws ApiException si ocurre un error al procesar la solicitud.
*/
@PatchMapping("/{idParticipante}/addPreOpinion")
public ResponseEntity<ParticipanteResponseDto> addPreOpinion(@PathVariable Long idEvento,
@PathVariable Long idParticipante,
@RequestParam String preOpinion) {
User currentUser = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
try {
return ResponseEntity.ok(participanteService.addPreOpinion(currentUser.getResidencia().getId(), idEvento, idParticipante, preOpinion));
} catch (ResiException e) {
throw new ApiException(e, currentUser);
} catch (Exception e) {
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), currentUser, e.getMessage());
}
}
/**
* Agrega una opinión posterior a la participación de un residente en un evento de salida.
*
* @param idParticipante ID del participante al que se le agregará la opinión.
* @param postOpinion Opinión posterior a la participación.
* @return {@link ResponseEntity} con el participante actualizado.
* @throws ApiException si ocurre un error al procesar la solicitud.
*/
@PatchMapping("/{idParticipante}/addPostOpinion")
public ResponseEntity<ParticipanteResponseDto> addPostOpinion(@PathVariable Long idEvento,
@PathVariable Long idParticipante,
@RequestParam String postOpinion) {
User currentUser = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
try {
return ResponseEntity.ok(participanteService.addPostOpinion(currentUser.getResidencia().getId(), idEvento, idParticipante, postOpinion));
} catch (ResiException e) {
throw new ApiException(e, currentUser);
} catch (Exception e) {
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), currentUser, e.getMessage());
}
}
/**
* Cambia los recursos asignados a un participante en un evento de salida.
*
* @param idParticipante ID del participante a actualizar.
* @param rH Recurso humano (opcional).
* @param rM Recurso material (opcional).
* @return {@link ResponseEntity} con el participante actualizado.
* @throws ApiException si ocurre un error al procesar la solicitud.
*/
@PatchMapping("/{idParticipante}/changeRecursos")
public ResponseEntity<ParticipanteResponseDto> changeRecursos(@PathVariable Long idEvento,
@PathVariable Long idParticipante,
@RequestParam(required = false) Boolean rH,
@RequestParam(required = false) Boolean rM) {
User currentUser = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
try {
return ResponseEntity.ok(participanteService.changeRecursos(currentUser.getResidencia().getId(), idEvento, idParticipante, rH, rM));
} catch (ResiException e) {
throw new ApiException(e, currentUser);
} catch (Exception e) {
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), currentUser, e.getMessage());
}
}
/**
* Acepta la participación un participante en un evento de salida.
*
* @param idParticipante ID del participante a aceptar.
* @return {@link ResponseEntity} con el participante aceptado.
* @throws ApiException si ocurre un error al procesar la solicitud.
*/
@PostMapping("/{idParticipante}/allow")
public ResponseEntity<ParticipanteResponseDto> allow(@PathVariable Long idEvento,
@PathVariable Long idParticipante) {
User currentUser = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
try {
return ResponseEntity.ok(participanteService.allow(currentUser.getResidencia().getId(), idEvento, idParticipante));
} catch (ResiException e) {
throw new ApiException(e, currentUser);
} catch (Exception e) {
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), currentUser, e.getMessage());
}
}
/**
* Deniega la participación de un residente en un evento de salida.
*
* @param idParticipante ID del participante a denegar.
* @return {@link ResponseEntity} con el participante actualizado.
* @throws ApiException si ocurre un error al procesar la solicitud.
*/
@PostMapping("/{idParticipante}/deny")
public ResponseEntity<ParticipanteResponseDto> deny(@PathVariable Long idEvento,
@PathVariable Long idParticipante) {
User currentUser = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
try {
return ResponseEntity.ok(participanteService.deny(currentUser.getResidencia().getId(), idEvento, idParticipante));
} catch (ResiException e) {
throw new ApiException(e, currentUser);
} catch (Exception e) {
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), currentUser, e.getMessage());
}
}
/**
* Actualiza los datos de un participante existente en un evento de salida.
*
* @param idParticipante ID del participante a actualizar.
* @param participanteDto DTO con los nuevos datos del participante.
* @return {@link ResponseEntity} con el participante actualizado.
* @throws ApiException si ocurre un error al procesar la solicitud.
*/
@PatchMapping("/{idParticipante}/update")
public ResponseEntity<ParticipanteResponseDto> update(@PathVariable Long idEvento,
@PathVariable Long idParticipante,
@RequestBody ParticipanteDto participanteDto) {
User currentUser = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
try {
return ResponseEntity.ok(participanteService.update(participanteDto, currentUser.getResidencia().getId(), idEvento, idParticipante));
} catch (ResiException e) {
throw new ApiException(e, currentUser);
} catch (Exception e) {
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), currentUser, e.getMessage());
}
}
}

View File

@ -0,0 +1,91 @@
package com.kevinolarte.resibenissa.controllers.moduloWallet;
import com.kevinolarte.resibenissa.dto.in.modeloWallet.MovimientoRequestDTO;
import com.kevinolarte.resibenissa.exceptions.ApiErrorCode;
import com.kevinolarte.resibenissa.exceptions.ApiException;
import com.kevinolarte.resibenissa.exceptions.ResiException;
import com.kevinolarte.resibenissa.models.User;
import com.kevinolarte.resibenissa.services.moduloWallet.WalletService;
import lombok.AllArgsConstructor;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/admin/resi/{idResidencia}/resi/{idResidente}/wallet")
@AllArgsConstructor
public class WalletAdminController {
private final WalletService walletService;
@GetMapping("/informe")
public ResponseEntity<byte[]> getInforme(
@PathVariable Long idResidencia,
@PathVariable Long idResidente) {
try{
byte[] pdf = walletService.getInforme(idResidencia, idResidente);
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=informe_wallet_" + idResidente + ".pdf")
.contentType(MediaType.APPLICATION_PDF)
.body(pdf);
}catch (ResiException e){
throw new ApiException(e, e.getMessage());
} catch (Exception e) {
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), e.getMessage());
}
}
@PostMapping("/deposit")
public ResponseEntity<String> deposit(
@PathVariable Long idResidencia,
@PathVariable Long idResidente,
@RequestBody MovimientoRequestDTO input) {
try {
walletService.deposit(idResidencia, idResidente, input);
return ResponseEntity.ok("Deposit successful");
} catch (ResiException e) {
throw new ApiException(e, e.getMessage());
} catch (Exception e) {
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), e.getMessage());
}
}
@PostMapping("/retire")
public ResponseEntity<String> retire(
@PathVariable Long idResidencia,
@PathVariable Long idResidente,
@RequestBody MovimientoRequestDTO input) {
try {
walletService.retire(idResidencia, idResidente, input);
return ResponseEntity.ok("Retire successful");
} catch (ResiException e) {
throw new ApiException(e, e.getMessage());
} catch (Exception e) {
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), e.getMessage());
}
}
@GetMapping("/getSaldo")
public ResponseEntity<Double> getSaldo(
@PathVariable Long idResidencia,
@PathVariable Long idResidente) {
try {
Double saldo = walletService.getSaldo(idResidencia, idResidente);
return ResponseEntity.ok(saldo);
} catch (ResiException e) {
throw new ApiException(e, e.getMessage());
} catch (Exception e) {
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), e.getMessage());
}
}
}

View File

@ -0,0 +1,94 @@
package com.kevinolarte.resibenissa.controllers.moduloWallet;
import com.kevinolarte.resibenissa.dto.in.modeloWallet.MovimientoRequestDTO;
import com.kevinolarte.resibenissa.exceptions.ApiErrorCode;
import com.kevinolarte.resibenissa.exceptions.ApiException;
import com.kevinolarte.resibenissa.exceptions.ResiException;
import com.kevinolarte.resibenissa.models.User;
import com.kevinolarte.resibenissa.services.moduloWallet.WalletService;
import lombok.AllArgsConstructor;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.*;
@RequestMapping("/resi/resident/{idResidente}/wallet")
@RestController
@AllArgsConstructor
public class WalletController {
private final WalletService walletService;
@GetMapping("/informe")
public ResponseEntity<byte[]> getInforme(
@PathVariable Long idResidente) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
User currentUser = (User) auth.getPrincipal();
try{
byte[] pdf = walletService.getInforme(currentUser.getResidencia().getId(), idResidente);
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=informe_wallet_" + currentUser.getId() + ".pdf")
.contentType(MediaType.APPLICATION_PDF)
.body(pdf);
}catch (ResiException e){
throw new ApiException(e, currentUser);
} catch (Exception e) {
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), currentUser, e.getMessage());
}
}
@PostMapping("/deposit")
public ResponseEntity<String> deposit(
@PathVariable Long idResidente,
@RequestBody MovimientoRequestDTO input) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
User currentUser = (User) auth.getPrincipal();
try {
walletService.deposit(currentUser.getResidencia().getId(), idResidente, input);
return ResponseEntity.ok("Deposit successful");
} catch (ResiException e) {
throw new ApiException(e, currentUser);
} catch (Exception e) {
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), currentUser, e.getMessage());
}
}
@PostMapping("/retire")
public ResponseEntity<String> retire(
@PathVariable Long idResidente,
@RequestBody MovimientoRequestDTO input) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
User currentUser = (User) auth.getPrincipal();
try {
walletService.retire(currentUser.getResidencia().getId(), idResidente, input);
return ResponseEntity.ok("Retire successful");
} catch (ResiException e) {
throw new ApiException(e, currentUser);
} catch (Exception e) {
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), currentUser, e.getMessage());
}
}
@GetMapping("/getSaldo")
public ResponseEntity<Double> getSaldo(
@PathVariable Long idResidente) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
User currentUser = (User) auth.getPrincipal();
try {
Double saldo = walletService.getSaldo(currentUser.getResidencia().getId(), idResidente);
return ResponseEntity.ok(saldo);
} catch (ResiException e) {
throw new ApiException(e, currentUser);
} catch (Exception e) {
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), currentUser, e.getMessage());
}
}
}

View File

@ -0,0 +1,137 @@
package com.kevinolarte.resibenissa.controllers.modulojuego.juego;
import com.kevinolarte.resibenissa.dto.in.modulojuego.JuegoDto;
import com.kevinolarte.resibenissa.dto.out.modulojuego.JuegoResponseDto;
import com.kevinolarte.resibenissa.dto.out.modulojuego.MediaRegistroDTO;
import com.kevinolarte.resibenissa.enums.modulojuego.Dificultad;
import com.kevinolarte.resibenissa.enums.modulojuego.TipoAgrupacion;
import com.kevinolarte.resibenissa.exceptions.ApiErrorCode;
import com.kevinolarte.resibenissa.exceptions.ApiException;
import com.kevinolarte.resibenissa.exceptions.ResiException;
import com.kevinolarte.resibenissa.repositories.modulojuego.RegistroJuegoRepository;
import com.kevinolarte.resibenissa.services.modulojuego.JuegoService;
import com.kevinolarte.resibenissa.services.modulojuego.RegistroJuegoService;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* Controlador REST que gestiona las operaciones relacionadas con los juegos en una residencia.
* <p>
* Permite registrar, consultar, listar, actualizar y eliminar juegos asociados a una residencia.
* </p>
*
* Ruta base: <b>/admin/resi/juego</b>
*
* @author Kevin Olarte
*/
@RequestMapping("/admin/resi/juego")
@RestController
@AllArgsConstructor
public class JuegoAdminController {
private final JuegoService juegoService;
private final RegistroJuegoRepository registroJuegoRepository;
private final RegistroJuegoService registroJuegoService;
/**
* Registra un nuevo juego asociado a una residencia.
*
* @param juegoDto Datos del juego a registrar.
* @return {@link ResponseEntity} con los datos del juego creado y estado 201 CREATED.
* @throws ApiException si ocurre un error al procesar la solicitud.
*/
@PostMapping("/add")
public ResponseEntity<JuegoResponseDto> add(@RequestBody JuegoDto juegoDto) {
try {
return ResponseEntity.status(HttpStatus.CREATED).body(juegoService.save(juegoDto));
} catch (ResiException e) {
throw new ApiException(e, e.getMessage());
} catch (Exception e) {
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), e.getMessage());
}
}
/**
* Obtiene los datos de un juego específico en una residencia.
* @param idJuego ID del juego a consultar.
* @return {@link ResponseEntity} con los datos del juego solicitado y estado 200 OK.
* @throws ApiException si ocurre un error al procesar la solicitud.
*/
@GetMapping("/{idJuego}/get")
public ResponseEntity<JuegoResponseDto> get(@PathVariable Long idJuego) {
try {
return ResponseEntity.ok(juegoService.get(idJuego));
} catch (ResiException e) {
throw new ApiException(e, e.getMessage());
} catch (Exception e) {
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), e.getMessage());
}
}
/**
* Obtiene una lista de todos los juegos registrados en una residencia.
*
* @param nombreJuego Nombre del juego para filtrar (opcional).
* @param maxRegistros Indica si se debe limitar la cantidad de registros devueltos (opcional).
* @return {@link ResponseEntity} con la lista de juegos y estado 200 OK.
* @throws ApiException si ocurre un error al procesar la solicitud.
*/
@GetMapping("/getAll")
public ResponseEntity<?> getAll(
@RequestParam(required = false) String nombreJuego,
@RequestParam(required = false) Boolean maxRegistros) {
try {
return ResponseEntity.ok(juegoService.getAll(nombreJuego, maxRegistros));
} catch (ResiException e) {
throw new ApiException(e, e.getMessage());
} catch (Exception e) {
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), e.getMessage());
}
}
/**
* Elimina un juego de una residencia si no tiene referencias dependientes.
*
* @param idJuego ID del juego a eliminar.
* @return {@link ResponseEntity} con estado 204 NO CONTENT si la eliminación es exitosa.
* @throws ApiException si ocurre un error al procesar la solicitud o si el juego no puede ser eliminado.
*/
@DeleteMapping("/{idJuego}/delete")
public ResponseEntity<Void> delete(@PathVariable Long idJuego) {
try {
juegoService.delete(idJuego);
return ResponseEntity.status(HttpStatus.NO_CONTENT).build();
} catch (ResiException e) {
throw new ApiException(e, e.getMessage());
} catch (Exception e) {
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO),e.getMessage());
}
}
/**
* Actualiza los datos de un juego ya registrado en una residencia.
*
* @param idJuego ID del juego a actualizar.
* @param juegoDto Datos nuevos del juego.
* @return {@link ResponseEntity} con los datos del juego actualizado.
* @throws ApiException si ocurre un error al procesar la solicitud o si el juego no puede ser actualizado.
*/
@PatchMapping("/{idJuego}/update")
public ResponseEntity<JuegoResponseDto> update(
@PathVariable Long idJuego,
@RequestBody JuegoDto juegoDto) {
try {
return ResponseEntity.ok(juegoService.update(idJuego, juegoDto));
} catch (ResiException e) {
throw new ApiException(e,e.getMessage());
} catch (Exception e) {
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO),e.getMessage());
}
}
}

View File

@ -0,0 +1,85 @@
package com.kevinolarte.resibenissa.controllers.modulojuego.juego;
import com.kevinolarte.resibenissa.dto.out.modulojuego.JuegoResponseDto;
import com.kevinolarte.resibenissa.dto.out.modulojuego.MediaRegistroDTO;
import com.kevinolarte.resibenissa.enums.modulojuego.Dificultad;
import com.kevinolarte.resibenissa.enums.modulojuego.TipoAgrupacion;
import com.kevinolarte.resibenissa.exceptions.ApiErrorCode;
import com.kevinolarte.resibenissa.exceptions.ApiException;
import com.kevinolarte.resibenissa.exceptions.ResiException;
import com.kevinolarte.resibenissa.models.User;
import com.kevinolarte.resibenissa.services.modulojuego.JuegoService;
import com.kevinolarte.resibenissa.services.modulojuego.RegistroJuegoService;
import lombok.AllArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* Controlador REST que gestiona las operaciones relacionadas con los juegos en una residencia.
* <p>
* Permite registrar, consultar, listar, actualizar y eliminar juegos asociados a una residencia.
* </p>
*
* Ruta base: <b>/resi/juego</b>
*
* @author Kevin Olarte
*/
@RequestMapping("/resi/juego")
@RestController
@AllArgsConstructor
public class JuegoController {
private final JuegoService juegoService;
private final RegistroJuegoService registroJuegoService;
/**
* Obtiene los datos de un juego específico dentro de una residencia.
*
* @param idJuego ID del juego a consultar.
* @return {@link ResponseEntity} con los datos del juego solicitado.
* @throws ResiException si ocurre un error al obtener el juego.
*/
@GetMapping("/{idJuego}/get")
public ResponseEntity<JuegoResponseDto> get(
@PathVariable Long idJuego) {
User currentUser = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
JuegoResponseDto juego;
try {
juego = juegoService.get(idJuego);
} catch (ResiException e) {
throw new ApiException(e, currentUser);
} catch (Exception e) {
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), currentUser, e.getMessage());
}
return ResponseEntity.ok(juego);
}
/**
* Obtiene una lista de juegos de una residencia, con filtros opcionales por nombre y cantidad máxima.
*
* @param nombreJuego Filtro por nombre del juego (opcional).
* @param maxRegistros Si es {@code true}, limita los resultados a un número máximo (definido en servicio).
* @return {@link ResponseEntity} con la lista de juegos.
* @throws ApiException si ocurre un error al obtener los juegos.g
*/
@GetMapping("/getAll")
public ResponseEntity<List<JuegoResponseDto>> getAll(
@RequestParam(required = false) String nombreJuego,
@RequestParam(required = false) Boolean maxRegistros) {
User currentUser = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
List<JuegoResponseDto> juegos;
try {
juegos = juegoService.getAll(nombreJuego, maxRegistros);
} catch (ResiException e) {
throw new ApiException(e, currentUser);
} catch (Exception e) {
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), currentUser, e.getMessage());
}
return ResponseEntity.ok(juegos);
}
}

View File

@ -0,0 +1,352 @@
package com.kevinolarte.resibenissa.controllers.modulojuego.registroJuego;
import com.kevinolarte.resibenissa.dto.in.modulojuego.RegistroJuegoDto;
import com.kevinolarte.resibenissa.dto.out.modulojuego.MediaRegistroDTO;
import com.kevinolarte.resibenissa.dto.out.modulojuego.RegistroJuegoResponseDto;
import com.kevinolarte.resibenissa.enums.modulojuego.Dificultad;
import com.kevinolarte.resibenissa.enums.Filtrado.RegistroJuegoFiltrado;
import com.kevinolarte.resibenissa.enums.modulojuego.TipoAgrupacion;
import com.kevinolarte.resibenissa.exceptions.ApiErrorCode;
import com.kevinolarte.resibenissa.exceptions.ApiException;
import com.kevinolarte.resibenissa.exceptions.ResiException;
import com.kevinolarte.resibenissa.models.User;
import com.kevinolarte.resibenissa.services.modulojuego.RegistroJuegoService;
import lombok.AllArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.*;
import javax.print.attribute.standard.Media;
import java.time.LocalDate;
import java.util.List;
/**
* Controlador REST para gestionar los registros de juegos jugados por los residentes.
* <p>
* Permite agregar nuevos registros, consultar estadísticas con múltiples filtros,
* actualizar observaciones y eliminar registros específicos.
* </p>
* <p>
* URL base: <code>admin/resi</code>.
* </p>
*
* @author Kevin Olarte
*/
@RequestMapping("/admin/resi")
@RestController
@AllArgsConstructor
public class RegistroJuegoAdminController {
private final RegistroJuegoService registroJuegoService;
/**
* Crea un nuevo registro de juego para un residente en una residencia y juego específicos.
*
* @param idResidencia ID de la residencia.
* @param idJuego ID del juego.
* @param registroJuegoDto Datos del registro a guardar.
* @return El registro de juego creado.
* @throws ApiException si ocurre un error al procesar la solicitud.
*/
@PostMapping("{idResidencia}/registro/add")
public ResponseEntity<RegistroJuegoResponseDto> add(
@PathVariable Long idResidencia,
@RequestParam Long idJuego,
@RequestBody RegistroJuegoDto registroJuegoDto) {
try {
RegistroJuegoResponseDto registroJuego = registroJuegoService.add(idResidencia, idJuego, registroJuegoDto);
return ResponseEntity.status(HttpStatus.CREATED).body(registroJuego);
} catch (ResiException e) {
throw new ApiException(e, e.getMessage());
} catch (Exception e) {
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), e.getMessage());
}
}
/**
* Obtiene un registro de juego específico mediante su ID.
*
* @param idResidencia ID de la residencia.
* @param idRegistroJuego ID del registro de juego.
* @return El registro de juego solicitado.
* @throws ApiException si ocurre un error al procesar la solicitud.
*/
@GetMapping("/{idResidencia}/registro/{idRegistroJuego}/get")
public ResponseEntity<RegistroJuegoResponseDto> get(
@PathVariable Long idResidencia,
@PathVariable Long idRegistroJuego) {
try {
return ResponseEntity.ok(registroJuegoService.get(idResidencia, idRegistroJuego));
} catch (ResiException e) {
throw new ApiException(e, e.getMessage());
} catch (Exception e) {
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), e.getMessage());
}
}
/**
* Obtiene una lista de registros de juego filtrados por varios parámetros.
* @param idJuego ID del juego.
* @param dificultad Dificultad del juego (opcional).
* @param edad Edad del residente (opcional).
* @param minEdad Edad mínima del residente (opcional).
* @param maxEdad Edad máxima del residente (opcional).
* @param idResidente ID del residente (opcional).
* @param fecha Fecha del registro (opcional).
* @param minFecha Fecha mínima del registro (opcional).
* @param maxFecha Fecha máxima del registro (opcional).
* @param promedio true si se desea obtener el promedio de los registros, false en caso contrario.
* @param masPromedio true si se desea obtener los registros con puntajes superiores al promedio, false en caso contrario.
* @param menosPromedio true si se desea obtener los registros con puntajes inferiores al promedio, false en caso contrario.
* @return Lista de registros de juego filtrados.
* @throws ApiException si ocurre un error al procesar la solicitud.
*/
@GetMapping("/registro/getAll")
public ResponseEntity<List<RegistroJuegoResponseDto>> getAll(
@RequestParam(required = false) Long idJuego,
@RequestParam(required = false) Dificultad dificultad,
@RequestParam(required = false) Integer edad,
@RequestParam(required = false) Integer minEdad,
@RequestParam(required = false) Integer maxEdad,
@RequestParam(required = false) Long idResidente,
@RequestParam(required = false) LocalDate fecha,
@RequestParam(required = false) LocalDate minFecha,
@RequestParam(required = false) LocalDate maxFecha,
@RequestParam(required = false) boolean promedio,
@RequestParam(required = false) boolean masPromedio,
@RequestParam(required = false) boolean menosPromedio,
@RequestParam(required = false) RegistroJuegoFiltrado filtrado,
@RequestParam(required = false) Boolean comentado) {
try {
return ResponseEntity.ok(registroJuegoService.getAll(
idJuego, dificultad, edad, minEdad, maxEdad,
idResidente, fecha, minFecha, maxFecha,
promedio, masPromedio, menosPromedio,
filtrado, comentado));
} catch (ResiException e) {
throw new ApiException(e, e.getMessage());
} catch (Exception e) {
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), e.getMessage());
}
}
/**
* Obtiene una lista de registros de juego filtrados por varios parámetros de una residencia especifica
* @param idResidencia ID de la residencia.
* @param idJuego ID del juego.
* @param dificultad Dificultad del juego (opcional).
* @param edad Edad del residente (opcional).
* @param minEdad Edad mínima del residente (opcional).
* @param maxEdad Edad máxima del residente (opcional).
* @param idResidente ID del residente (opcional).
* @param fecha Fecha del registro (opcional).
* @param minFecha Fecha mínima del registro (opcional).
* @param maxFecha Fecha máxima del registro (opcional).
* @param promedio true si se desea obtener el promedio de los registros, false en caso contrario.
* @param masPromedio true si se desea obtener los registros con puntajes superiores al promedio, false en caso contrario.
* @param menosPromedio true si se desea obtener los registros con puntajes inferiores al promedio, false en caso contrario.
* @return Lista de registros de juego filtrados.
* @throws ApiException si ocurre un error al procesar la solicitud.
*/
@GetMapping("/{idResidencia}/registro/getAll")
public ResponseEntity<List<RegistroJuegoResponseDto>> getAll(
@PathVariable Long idResidencia,
@RequestParam(required = false) Long idJuego,
@RequestParam(required = false) Dificultad dificultad,
@RequestParam(required = false) Integer edad,
@RequestParam(required = false) Integer minEdad,
@RequestParam(required = false) Integer maxEdad,
@RequestParam(required = false) Long idResidente,
@RequestParam(required = false) LocalDate fecha,
@RequestParam(required = false) LocalDate minFecha,
@RequestParam(required = false) LocalDate maxFecha,
@RequestParam(required = false) boolean promedio,
@RequestParam(required = false) boolean masPromedio,
@RequestParam(required = false) boolean menosPromedio,
@RequestParam(required = false) RegistroJuegoFiltrado filtrado,
@RequestParam(required = false) Boolean comentado) {
try {
return ResponseEntity.ok(registroJuegoService.getAll(
idResidencia, idJuego, dificultad, edad, minEdad, maxEdad,
idResidente, fecha, minFecha, maxFecha,
promedio, masPromedio, menosPromedio,
filtrado, comentado));
} catch (ResiException e) {
throw new ApiException(e, e.getMessage());
} catch (Exception e) {
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), e.getMessage());
}
}
/**
* Actualiza un registro de juego existente añadiendo o modificando una observación.
*
* @param idResidencia ID de la residencia donde se encuentra el registro.
* @param idRegistroJuego ID del registro a actualizar.
* @param registroJuegoDto DTO con los nuevos datos (principalmente observación).
* @return Registro de juego actualizado.
* @throws ApiException si ocurre un error al procesar la solicitud.
*/
@PatchMapping("/{idResidencia}/registro/{idRegistroJuego}/addComment")
public ResponseEntity<RegistroJuegoResponseDto> update(
@PathVariable Long idResidencia,
@PathVariable Long idRegistroJuego,
@RequestBody RegistroJuegoDto registroJuegoDto) {
try {
return ResponseEntity.ok(registroJuegoService.update(idResidencia, idRegistroJuego, registroJuegoDto));
} catch (ResiException e) {
throw new ApiException(e, e.getMessage());
} catch (Exception e) {
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), e.getMessage());
}
}
/**
* Elimina un registro de juego específico.
*
* @param idResidencia ID de la residencia donde se encuentra el registro.
* @param idRegistroJuego ID del registro a eliminar.
* @return Respuesta sin contenido si la eliminación fue exitosa.
* @throws ApiException si ocurre un error al procesar la solicitud.
*/
@DeleteMapping("/{idResidencia}/registro/{idRegistroJuego}/delete")
public ResponseEntity<Void> delete(
@PathVariable Long idResidencia,
@PathVariable Long idRegistroJuego) {
try {
registroJuegoService.delete(idResidencia, idRegistroJuego);
return ResponseEntity.noContent().build();
} catch (ResiException e) {
throw new ApiException(e, e.getMessage());
} catch (Exception e) {
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), e.getMessage());
}
}
@GetMapping("/{idResidencia}/registro/media-duracion")
public ResponseEntity<List<MediaRegistroDTO>> getMediaDuracionPorResidencia(
@PathVariable Long idResidencia,
@RequestParam(required = false, defaultValue = "DIARIO") TipoAgrupacion tipo,
@RequestParam(required = false) Dificultad dificultad,
@RequestParam(required = false) Long idJuego) {
try{
List<MediaRegistroDTO> medias = registroJuegoService.getMediaDuracionPorResidencia(idResidencia, tipo, dificultad, idJuego);
return ResponseEntity.ok(medias);
}catch (ResiException e) {
throw new ApiException(e, e.getMessage());
} catch (Exception e) {
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), e.getMessage());
}
}
@GetMapping("/{idResidencia}/registro/media-num")
public ResponseEntity<List<MediaRegistroDTO>> getMediaErroresPorResidencia(
@PathVariable Long idResidencia,
@RequestParam(required = false, defaultValue = "DIARIO") TipoAgrupacion tipo,
@RequestParam(required = false) Dificultad dificultad,
@RequestParam(required = false) Long idJuego) {
try {
List<MediaRegistroDTO> medias = registroJuegoService.getMediaErroresPorResidencia(idResidencia, tipo, dificultad, idJuego);
return ResponseEntity.ok(medias);
} catch (ResiException e) {
throw new ApiException(e, e.getMessage());
} catch (Exception e) {
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), e.getMessage());
}
}
@GetMapping("{idResidencia}/registro/resident/{idResidente}/media-duracion")
public ResponseEntity<List<MediaRegistroDTO>> getMediaDuracionPorResidente(@PathVariable Long idResidencia,
@PathVariable Long idResidente,
@RequestParam(required = false, defaultValue = "DIARIO") TipoAgrupacion tipo,
@RequestParam(required = false) Dificultad dificultad,
@RequestParam(required = false) Long idJuego) {
try {
List<MediaRegistroDTO> medias = registroJuegoService.getMediaDuracion(idResidencia, idResidente, tipo, dificultad, idJuego);
return ResponseEntity.ok(medias);
} catch (ResiException e) {
throw new ApiException(e, e.getMessage());
} catch (Exception e) {
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), e.getMessage());
}
}
@GetMapping("{idResidencia}/registro/resident/{idResidente}/media-num")
public ResponseEntity<List<MediaRegistroDTO>> getMediaNumPorResidente(@PathVariable Long idResidencia,
@PathVariable Long idResidente,
@RequestParam(required = false, defaultValue = "DIARIO") TipoAgrupacion tipo,
@RequestParam(required = false) Dificultad dificultad,
@RequestParam(required = false) Long idJuego) {
try {
List<MediaRegistroDTO> medias = registroJuegoService.getMediaErrores(idResidencia, idResidente, tipo, dificultad, idJuego);
return ResponseEntity.ok(medias);
} catch (ResiException e) {
throw new ApiException(e, e.getMessage());
} catch (Exception e) {
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), e.getMessage());
}
}
/**
* Endpoint global que calcula el promedio de duración de los registros de juego
* agrupados por día, mes o año, para todos los registros del sistema.
*
* <p>
* Permite filtrar opcionalmente por:
* <ul>
* <li><b>Dificultad</b> del juego (FACIL, MEDIO, DIFICIL)</li>
* <li><b>ID del juego</b> específico</li>
* </ul>
* Si no se proporciona ningún filtro, se devolverá la media de duración de todos los juegos y dificultades del sistema.
* </p>
*
* @param tipo Tipo de agrupación temporal: DIARIO (por defecto), MENSUAL o ANUAL.
* @param dificultad Nivel de dificultad del juego a filtrar (opcional).
* @param idJuego ID del juego a filtrar (opcional).
* @return Lista de {@link MediaRegistroDTO} que contienen el agrupamiento temporal (día, mes o año),
* el promedio de duración y el número de registros correspondientes.
*/
@GetMapping("/registro/media-duracion")
public ResponseEntity<List<MediaRegistroDTO>> getMediaDuracionPorResidencia(
@RequestParam(required = false, defaultValue = "DIARIO") TipoAgrupacion tipo,
@RequestParam(required = false) Dificultad dificultad,
@RequestParam(required = false) Long idJuego) {
try{
List<MediaRegistroDTO> medias = registroJuegoService.getMediaDuracionGlobal(tipo, dificultad, idJuego);
return ResponseEntity.ok(medias);
}catch (ResiException e) {
throw new ApiException(e, e.getMessage());
} catch (Exception e) {
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), e.getMessage());
}
}
@GetMapping("/registro/media-num")
public ResponseEntity<List<MediaRegistroDTO>> getMediaErroresPorResidencia(
@RequestParam(required = false, defaultValue = "DIARIO") TipoAgrupacion tipo,
@RequestParam(required = false) Dificultad dificultad,
@RequestParam(required = false) Long idJuego) {
try {
List<MediaRegistroDTO> medias = registroJuegoService.getMediaErroresGlobal(tipo, dificultad, idJuego);
return ResponseEntity.ok(medias);
} catch (ResiException e) {
throw new ApiException(e, e.getMessage());
} catch (Exception e) {
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), e.getMessage());
}
}
}

View File

@ -0,0 +1,251 @@
package com.kevinolarte.resibenissa.controllers.modulojuego.registroJuego;
import com.kevinolarte.resibenissa.dto.in.modulojuego.RegistroJuegoDto;
import com.kevinolarte.resibenissa.dto.out.modulojuego.MediaRegistroDTO;
import com.kevinolarte.resibenissa.dto.out.modulojuego.RegistroJuegoResponseDto;
import com.kevinolarte.resibenissa.enums.modulojuego.Dificultad;
import com.kevinolarte.resibenissa.enums.Filtrado.RegistroJuegoFiltrado;
import com.kevinolarte.resibenissa.enums.modulojuego.TipoAgrupacion;
import com.kevinolarte.resibenissa.exceptions.ApiErrorCode;
import com.kevinolarte.resibenissa.exceptions.ApiException;
import com.kevinolarte.resibenissa.exceptions.ResiException;
import com.kevinolarte.resibenissa.models.User;
import com.kevinolarte.resibenissa.services.modulojuego.RegistroJuegoService;
import lombok.AllArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.*;
import java.time.LocalDate;
import java.util.List;
/**
* Controlador REST para gestionar los registros de juegos jugados por los residentes.
* <p>
* Permite agregar nuevos registros, consultar estadísticas con múltiples filtros,
* actualizar observaciones y eliminar registros específicos.
* </p>
* <p>
* Todas las rutas expuestas están bajo el prefijo <code>/resi/registro</code>.
* </p>
*
* @author Kevin Olarte
*/
@RequestMapping("/resi/registro")
@RestController
@AllArgsConstructor
public class RegistroJuegoController {
private final RegistroJuegoService registroJuegoService;
/**
* Crea un nuevo registro de juego para un residente en una residencia y juego específicos.
*
* @param idJuego ID del juego.
* @param registroJuegoDto Datos del registro a guardar.
* @return El registro de juego creado.
* @throws ApiException si ocurre un error al procesar la solicitud.
*/
@PostMapping("/juego/{idJuego}/add")
public ResponseEntity<RegistroJuegoResponseDto> add(
@PathVariable Long idJuego,
@RequestBody RegistroJuegoDto registroJuegoDto) {
User currentUser = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
try {
registroJuegoDto.setIdUsuario(currentUser.getId());
RegistroJuegoResponseDto registroJuego = registroJuegoService.add(currentUser.getResidencia().getId(), idJuego, registroJuegoDto);
return ResponseEntity.status(HttpStatus.CREATED).body(registroJuego);
} catch (ResiException e) {
throw new ApiException(e, currentUser);
} catch (Exception e) {
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), currentUser, e.getMessage());
}
}
/**
* Obtiene un registro de juego específico mediante su ID.
*
*@param idRegistroJuego ID del registro de juego.
* @return El registro de juego solicitado.
* @throws ApiException si ocurre un error al procesar la solicitud.
*/
@GetMapping("/{idRegistroJuego}/get")
public ResponseEntity<RegistroJuegoResponseDto> get(@PathVariable Long idRegistroJuego) {
User currentUser = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
try {
return ResponseEntity.ok(registroJuegoService.get(currentUser.getResidencia().getId(), idRegistroJuego));
} catch (ResiException e) {
throw new ApiException(e, currentUser);
} catch (Exception e) {
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), currentUser, e.getMessage());
}
}
/**
* Obtiene una lista de registros de juego filtrados por varios parámetros.
* @param idJuego ID del juego.
* @param edad Edad del residente (opcional).
* @param minEdad Edad mínima del residente (opcional).
* @param maxEdad Edad máxima del residente (opcional).
* @param idResidente ID del residente (opcional).
* @param fecha Fecha del registro (opcional).
* @param minFecha Fecha mínima del registro (opcional).
* @param maxFecha Fecha máxima del registro (opcional).
* @param promedio true si se desea obtener el promedio de los registros, false en caso contrario.
* @param masPromedio true si se desea obtener los registros con puntajes superiores al promedio, false en caso contrario.
* @param menosPromedio true si se desea obtener los registros con puntajes inferiores al promedio, false en caso contrario.
* @param dificultad Dificultad del juego (opcional).
* @return Lista de registros de juego filtrados.
* @throws ApiException si ocurre un error al procesar la solicitud.
*/
@GetMapping("/getAll")
public ResponseEntity<List<RegistroJuegoResponseDto>> getAll(
@RequestParam(required = false) Long idJuego,
@RequestParam(required = false) Long idResidente,
@RequestParam(required = false) Integer edad,
@RequestParam(required = false) Integer minEdad,
@RequestParam(required = false) Integer maxEdad,
@RequestParam(required = false) LocalDate fecha,
@RequestParam(required = false) LocalDate minFecha,
@RequestParam(required = false) LocalDate maxFecha,
@RequestParam(required = false) boolean promedio,
@RequestParam(required = false) boolean masPromedio,
@RequestParam(required = false) boolean menosPromedio,
@RequestParam(required = false) RegistroJuegoFiltrado filtrado,
@RequestParam(required = false) Dificultad dificultad,
@RequestParam(required = false) Boolean comentado) {
User currentUser = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
try {
return ResponseEntity.ok(
registroJuegoService.getAll(
currentUser.getResidencia().getId(),
idJuego, dificultad, edad, minEdad, maxEdad,
idResidente, fecha, minFecha, maxFecha,
promedio, masPromedio, menosPromedio, filtrado, comentado
)
);
} catch (ResiException e) {
throw new ApiException(e, currentUser);
} catch (Exception e) {
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), currentUser, e.getMessage());
}
}
/**
* Actualiza un registro de juego existente añadiendo o modificando una observación.
*
* @param idRegistroJuego ID del registro a actualizar.
* @param registroJuegoDto DTO con los nuevos datos (principalmente observación).
* @return Registro de juego actualizado.
* @throws ApiException si ocurre un error al procesar la solicitud.
*/
@PatchMapping("/{idRegistroJuego}/addComment")
public ResponseEntity<RegistroJuegoResponseDto> update(
@PathVariable Long idRegistroJuego,
@RequestBody RegistroJuegoDto registroJuegoDto) {
User currentUser = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
try {
return ResponseEntity.ok(registroJuegoService.update(currentUser.getResidencia().getId(), idRegistroJuego, registroJuegoDto));
} catch (ResiException e) {
throw new ApiException(e, currentUser);
} catch (Exception e) {
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), currentUser, e.getMessage());
}
}
/**
* Elimina un registro de juego específico.
*
* @param idRegistroJuego ID del registro a eliminar.
* @return Respuesta sin contenido si la eliminación fue exitosa.
* @throws ApiException si ocurre un error al procesar la solicitud.
*/
@DeleteMapping("/{idRegistroJuego}/delete")
public ResponseEntity<Void> delete(@PathVariable Long idRegistroJuego) {
User currentUser = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
try {
registroJuegoService.delete(currentUser.getResidencia().getId(), idRegistroJuego);
return ResponseEntity.noContent().build();
} catch (ResiException e) {
throw new ApiException(e, currentUser);
} catch (Exception e) {
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), currentUser, e.getMessage());
}
}
@GetMapping("/media-duracion")
public ResponseEntity<List<MediaRegistroDTO>> getMediaDuracionPorResidencia(
@RequestParam(required = false, defaultValue = "DIARIO") TipoAgrupacion tipo,
@RequestParam(required = false) Dificultad dificultad,
@RequestParam(required = false) Long idJuego) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
User currentUser = (User) auth.getPrincipal();
try{
List<MediaRegistroDTO> medias = registroJuegoService.getMediaDuracionPorResidencia(currentUser.getResidencia().getId(), tipo, dificultad, idJuego);
return ResponseEntity.ok(medias);
}catch (ResiException e) {
throw new ApiException(e, currentUser);
} catch (Exception e) {
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), currentUser, e.getMessage());
}
}
@GetMapping("/media-num")
public ResponseEntity<List<MediaRegistroDTO>> getMediaErroresPorResidencia(
@RequestParam(required = false, defaultValue = "DIARIO") TipoAgrupacion tipo,
@RequestParam(required = false) Dificultad dificultad,
@RequestParam(required = false) Long idJuego) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
User currentUser = (User) auth.getPrincipal();
try {
List<MediaRegistroDTO> medias = registroJuegoService.getMediaErroresPorResidencia(currentUser.getResidencia().getId(), tipo, dificultad, idJuego);
return ResponseEntity.ok(medias);
} catch (ResiException e) {
throw new ApiException(e, currentUser);
} catch (Exception e) {
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), currentUser, e.getMessage());
}
}
@GetMapping("/resident/{idResidente}/media-duracion")
public ResponseEntity<List<MediaRegistroDTO>> getMediaDuracion(
@PathVariable Long idResidente,
@RequestParam(required = false, defaultValue = "DIARIO") TipoAgrupacion tipo,
@RequestParam(required = false) Dificultad dificultad,
@RequestParam(required = false) Long idJuego) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
User currentUser = (User) auth.getPrincipal();
List<MediaRegistroDTO> medias;
try {
medias = registroJuegoService.getMediaDuracion(currentUser.getResidencia().getId(), idResidente, tipo, dificultad, idJuego);
} catch (ResiException e) {
throw new ApiException(e, currentUser);
} catch (Exception e) {
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), currentUser, e.getMessage());
}
return ResponseEntity.ok(medias);
}
}

View File

@ -0,0 +1,167 @@
package com.kevinolarte.resibenissa.controllers.residencia;
import com.kevinolarte.resibenissa.dto.in.ResidenciaDto;
import com.kevinolarte.resibenissa.dto.out.ResidenciaPublicResponseDto;
import com.kevinolarte.resibenissa.dto.out.ResidenciaResponseDto;
import com.kevinolarte.resibenissa.exceptions.ApiErrorCode;
import com.kevinolarte.resibenissa.exceptions.ApiException;
import com.kevinolarte.resibenissa.exceptions.ResiException;
import com.kevinolarte.resibenissa.models.Residencia;
import com.kevinolarte.resibenissa.services.ResidenciaService;
import lombok.AllArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* Controlador que expone endpoints REST para gestionar entidades {@link Residencia}.
* <p>
* Permite crear nuevas residencias, obtenerlas por ID y eliminarlas.
* </p>
*
* URL Base: {@code /admin/resi}
*
* Autor: Kevin Olarte
*/
@RequestMapping("/admin/resi/")
@RestController
@AllArgsConstructor
public class ResidenciaAdminController {
private final ResidenciaService residenciaService;
/**
* Crea una nueva residencia.
*
* @param residenciaDto DTO con los datos de la residencia a crear.
* @return {@link ResponseEntity} con estado {@code 201 Created} y el DTO de la residencia creada.
* @throws ApiException si ocurre un error al procesar la solicitud.
*/
@PostMapping("/add")
public ResponseEntity<ResidenciaResponseDto> add(
@RequestBody ResidenciaDto residenciaDto) {
ResidenciaResponseDto residencia;
try {
residencia = residenciaService.add(residenciaDto);
} catch (ResiException e) {
// Manejo de excepciones específicas de la aplicación
throw new ApiException(e, e.getMessage());
} catch (Exception e) {
// Manejo de excepciones genéricas
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), e.getMessage());
}
return ResponseEntity.status(HttpStatus.CREATED).body(residencia);
}
/**
* Obtiene una residencia por su ID.
*
* @param idResidencia ID de la residencia a recuperar.
* @return {@link ResponseEntity} con estado {@code 200 OK} y el DTO de la residencia encontrada.
*/
@GetMapping("/{idResidencia}/get")
public ResponseEntity<ResidenciaResponseDto> get(
@PathVariable Long idResidencia) {
ResidenciaResponseDto residencia;
try {
residencia = residenciaService.get(idResidencia);
} catch (ResiException e) {
// Manejo de excepciones específicas de la aplicación
throw new ApiException(e, e.getMessage());
} catch (Exception e) {
// Manejo de excepciones genéricas
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), e.getMessage());
}
return ResponseEntity.ok(residencia);
}
/**
* Obtiene todas las residencias.
*
* @return {@link ResponseEntity} con estado {@code 200 OK} y una lista de DTOs de residencias.
* @throws ApiException si ocurre un error al intentar obtener las residencias.
*/
@GetMapping("/getAll")
public ResponseEntity<List<ResidenciaPublicResponseDto>> getAll() {
List<ResidenciaPublicResponseDto> residencias;
try {
residencias = residenciaService.getAll();
} catch (Exception e) {
// Manejo de excepciones genéricas
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), e.getMessage());
}
return ResponseEntity.ok(residencias);
}
/**
* Obtiene todos los que estan de baja
* @return {@link ResponseEntity} con estado {@code 200 OK} y una lista de DTOs de residencias.
* @throws ApiException si ocurre un error al intentar obtener las residencias.
*/
@GetMapping("/getAll/baja")
public ResponseEntity<List<ResidenciaPublicResponseDto>> getAllBaja() {
List<ResidenciaPublicResponseDto> residencias;
try {
residencias = residenciaService.getAllBaja();
} catch (Exception e) {
// Manejo de excepciones genéricas
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), e.getMessage());
}
return ResponseEntity.ok(residencias);
}
/**
* Elimina una residencia por su ID.
*
* @return {@link ResponseEntity} con estado {@code 204 No Content} si la eliminación fue exitosa.
* @param idResidencia ID de la residencia a eliminar.
* @throws ApiException si ocurre un error al intentar eliminar la residencia.
*/
@DeleteMapping("/{idResidencia}/delete")
public ResponseEntity<Void> remove(
@PathVariable Long idResidencia) {
try {
residenciaService.deleteFisico(idResidencia);
}catch (ResiException e) {
// Manejo de excepciones específicas de la aplicación
throw new ApiException(e, e.getMessage());
} catch (Exception e) {
// Manejo de excepciones genéricas
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), e.getMessage());
}
return ResponseEntity.status(HttpStatus.NO_CONTENT).build();
}
/**
* Elimina una residencia de forma lógica. Dandolo de baja.
*
* @return {@link ResponseEntity} con estado {@code 204 No Content} si la eliminación fue exitosa.
* @param idResidencia ID de la residencia a dar de baja.
* @throws ApiException si ocurre un error al intentar eliminar la residencia.
*/
@PatchMapping("/{idResidencia}/baja")
public ResponseEntity<Void> baja(
@PathVariable Long idResidencia) {
try {
residenciaService.deleteLogico(idResidencia);
}catch (ResiException e) {
// Manejo de excepciones específicas de la aplicación
throw new ApiException(e, e.getMessage());
} catch (Exception e) {
// Manejo de excepciones genéricas
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), e.getMessage());
}
return ResponseEntity.status(HttpStatus.NO_CONTENT).build();
}
}

View File

@ -0,0 +1,84 @@
package com.kevinolarte.resibenissa.controllers.residencia;
import com.kevinolarte.resibenissa.dto.in.ResidenciaDto;
import com.kevinolarte.resibenissa.dto.out.ResidenciaPublicResponseDto;
import com.kevinolarte.resibenissa.dto.out.ResidenciaResponseDto;
import com.kevinolarte.resibenissa.exceptions.ApiErrorCode;
import com.kevinolarte.resibenissa.exceptions.ApiException;
import com.kevinolarte.resibenissa.exceptions.ResiException;
import com.kevinolarte.resibenissa.models.Residencia;
import com.kevinolarte.resibenissa.models.User;
import com.kevinolarte.resibenissa.services.ResidenciaService;
import lombok.AllArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* Controlador que expone endpoints REST para gestionar entidades {@link Residencia}.
* <p>
* Permite crear nuevas residencias, obtenerlas por ID y eliminarlas.
* </p>
*
* URL Base: {@code /resi}
*
* Autor: Kevin Olarte
*/
@RequestMapping("/resi")
@RestController
@AllArgsConstructor
public class ResidenciaController {
private final ResidenciaService residenciaService;
/**
* Obtiene una residencia por su ID.
*
* @return {@link ResponseEntity} con estado {@code 200 OK} y el DTO de la residencia encontrada.
* @throws ResiException si ocurre un error al obtener la residencia.
*/
@GetMapping("/get")
public ResponseEntity<ResidenciaResponseDto> get() {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
User currentUser = (User) auth.getPrincipal();
ResidenciaResponseDto residencia;
try{
residencia = residenciaService.get(currentUser.getResidencia().getId());
} catch (ResiException e) {
throw new ApiException(e, currentUser);
} catch (Exception e) {
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), currentUser, e.getMessage());
}
return ResponseEntity.ok(residencia);
}
/**
* Obtiene todas las residencias.
*
* @return {@link ResponseEntity} con estado {@code 200 OK} y una lista de DTOs de residencias.
* @throws ApiException si ocurre un error al obtener las residencias.
*/
@GetMapping("/getAll")
public ResponseEntity<List<ResidenciaPublicResponseDto>> getAll() {
List<ResidenciaPublicResponseDto> residencias;
try{
residencias = residenciaService.getAll();
}catch (Exception e){
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), e.getMessage());
}
return ResponseEntity.ok(residencias);
}
}

View File

@ -0,0 +1,315 @@
package com.kevinolarte.resibenissa.controllers.residente;
import com.kevinolarte.resibenissa.dto.in.ResidenteDto;
import com.kevinolarte.resibenissa.dto.in.moduloReporting.EmailRequestDto;
import com.kevinolarte.resibenissa.dto.out.ResidenteResponseDto;
import com.kevinolarte.resibenissa.dto.out.modulojuego.MediaRegistroDTO;
import com.kevinolarte.resibenissa.enums.Filtrado.ResidenteFiltrado;
import com.kevinolarte.resibenissa.enums.modulojuego.Dificultad;
import com.kevinolarte.resibenissa.enums.modulojuego.TipoAgrupacion;
import com.kevinolarte.resibenissa.exceptions.ApiErrorCode;
import com.kevinolarte.resibenissa.exceptions.ApiException;
import com.kevinolarte.resibenissa.exceptions.ResiException;
import com.kevinolarte.resibenissa.models.User;
import com.kevinolarte.resibenissa.services.ResidenteService;
import com.kevinolarte.resibenissa.services.modulojuego.RegistroJuegoService;
import lombok.AllArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.*;
import java.time.LocalDate;
import java.util.List;
/**
* Controlador REST para la administración de residentes dentro de una residencia.
* <p>
* Permite registrar, consultar, actualizar, eliminar y listar residentes
* aplicando filtros opcionales, tanto a nivel de una residencia específica como global.
* </p>
*
* URL base: {@code /admin/resi}
*
* @author Kevin Olarte
*/
@RequestMapping("/admin/resi/")
@RestController
@AllArgsConstructor
public class ResidenteAdminController {
private final ResidenteService residenteService;
private final RegistroJuegoService registroJuegoService;
/**
* Registra un nuevo residente dentro de una residencia.
*
* @param idResidencia ID de la residencia.
* @param residenteDto DTO con los datos del residente a crear.
* @return {@link ResponseEntity} con estado {@code 201 Created} y el residente creado.
* @throws ApiException si ocurre un error al procesar la solicitud, como campos obligatorios faltantes
*/
@PostMapping("/{idResidencia}/resident/add")
public ResponseEntity<ResidenteResponseDto> add(
@PathVariable Long idResidencia,
@RequestBody ResidenteDto residenteDto){
ResidenteResponseDto residenteResponseDto;
try{
residenteResponseDto = residenteService.add(idResidencia, residenteDto);
}catch (ResiException e){
throw new ApiException(e, e.getMessage());
}catch (Exception e) {
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), e.getMessage());
}
return ResponseEntity.status(HttpStatus.CREATED).body(residenteResponseDto);
}
/**
* Obtiene los detalles de un residente específico dentro de una residencia.
*
* @param idResidencia ID de la residencia.
* @param idResidente ID del residente.
* @return {@link ResponseEntity} con estado {@code 200 OK} y los datos del residente.
* @throws ApiException si ocurre un error al procesar la solicitud, como campos obligatorios faltantes
*/
@GetMapping("/{idResidencia}/resident/{idResidente}/get")
public ResponseEntity<ResidenteResponseDto> get(
@PathVariable Long idResidencia,
@PathVariable Long idResidente) {
ResidenteResponseDto residenteResponseDto;
try {
residenteResponseDto = residenteService.get(idResidencia, idResidente);
} catch (ResiException e) {
throw new ApiException(e, e.getMessage());
} catch (Exception e) {
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), e.getMessage());
}
return ResponseEntity.ok(residenteResponseDto);
}
/**
* Lista todos los residentes de una residencia, aplicando filtros opcionales.
*
* @param idResidencia ID de la residencia.
* @param fechaNacimiento Fecha exacta de nacimiento.
* @param minFNac Fecha mínima de nacimiento.
* @param maxFNac Fecha máxima de nacimiento.
* @param maxAge Edad máxima.
* @param minAge Edad mínima.
* @param idJuego ID de juego asociado (opcional).
* @param idEvento ID de evento asociado (opcional).
* @return {@link ResponseEntity} con la lista de residentes filtrados.
* @throws ApiException si ocurre un error al procesar la solicitud, como campos obligatorios faltantes
*/
@GetMapping("/{idResidencia}/resident/getAll")
public ResponseEntity<List<ResidenteResponseDto>> getAll(
@PathVariable Long idResidencia,
@RequestParam(required = false) LocalDate fechaNacimiento,
@RequestParam(required = false) LocalDate minFNac,
@RequestParam(required = false) LocalDate maxFNac,
@RequestParam(required = false) Integer maxAge,
@RequestParam(required = false) Integer minAge,
@RequestParam(required = false) Long idJuego,
@RequestParam(required = false) Long idEvento,
@RequestParam(required = false)ResidenteFiltrado filtrado) {
List<ResidenteResponseDto> residentes;
try {
residentes = residenteService.getAll(idResidencia, fechaNacimiento, minFNac, maxFNac, maxAge, minAge, idJuego, idEvento, filtrado);
} catch (ResiException e) {
throw new ApiException(e, e.getMessage());
} catch (Exception e) {
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), e.getMessage());
}
return ResponseEntity.ok(residentes);
}
/**
* Lista todos los residentes en el sistema, sin filtrar por residencia.
*
* @param fechaNacimiento Fecha exacta de nacimiento.
* @param minFNac Fecha mínima de nacimiento.
* @param maxFNac Fecha máxima de nacimiento.
* @param maxAge Edad máxima.
* @param minAge Edad mínima.
* @param idJuego ID de juego asociado (opcional).
* @param idEvento ID de evento asociado (opcional).
* @return {@link ResponseEntity} con la lista global de residentes filtrados.
* @throws ApiException si ocurre un error al procesar la solicitud, como campos obligatorios faltantes
*/
@GetMapping("/resident/getAll")
public ResponseEntity<List<ResidenteResponseDto>> getAll(
@RequestParam(required = false) LocalDate fechaNacimiento,
@RequestParam(required = false) LocalDate minFNac,
@RequestParam(required = false) LocalDate maxFNac,
@RequestParam(required = false) Integer maxAge,
@RequestParam(required = false) Integer minAge,
@RequestParam(required = false) Long idJuego,
@RequestParam(required = false) Long idEvento,
@RequestParam(required = false) ResidenteFiltrado filtrado) {
List<ResidenteResponseDto> residentes;
try {
residentes = residenteService.getAll(fechaNacimiento, minFNac, maxFNac, maxAge, minAge, idJuego, idEvento, filtrado);
} catch (ResiException e) {
throw new ApiException(e, e.getMessage());
} catch (Exception e) {
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), e.getMessage());
}
return ResponseEntity.ok(residentes);
}
/**
* Lista todos los residentes dados de baja dentro de una residencia, filtrando opcionalmente por fecha.
*
* @param idResidencia ID de la residencia.
* @param fecha Fecha exacta de baja (opcional).
* @param minFecha Fecha mínima de baja (opcional).
* @param maxFecha Fecha máxima de baja (opcional).
* @return {@link ResponseEntity} con la lista de residentes dados de baja.
* @throws ApiException si ocurre un error al procesar la solicitud, como campos obligatorios faltantes
*/
@GetMapping("/{idResidencia}/resident/getAll/bajas")
public ResponseEntity<List<ResidenteResponseDto>> getAllBajas(
@PathVariable Long idResidencia,
@RequestParam(required = false) LocalDate fecha,
@RequestParam(required = false) LocalDate minFecha,
@RequestParam(required = false) LocalDate maxFecha){
List<ResidenteResponseDto> residentes;
try{
residentes = residenteService.getAllBajas(idResidencia, fecha, minFecha, maxFecha);
}catch (ResiException e) {
throw new ApiException(e, e.getMessage());
} catch (Exception e) {
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), e.getMessage());
}
return ResponseEntity.ok(residentes);
}
/**
* Lista todos los residentes dados de baja del sistema (sin filtro de residencia).
*
* @param fecha Fecha exacta de baja (opcional).
* @param minFecha Fecha mínima de baja (opcional).
* @param maxFecha Fecha máxima de baja (opcional).
* @return {@link ResponseEntity} con la lista global de residentes dados de baja.
* @throws ApiException si ocurre un error al procesar la solicitud, como campos obligatorios faltantes
*/
@GetMapping("/resident/getAll/bajas")
public ResponseEntity<List<ResidenteResponseDto>> getAllBajas(
@RequestParam(required = false) LocalDate fecha,
@RequestParam(required = false) LocalDate minFecha,
@RequestParam(required = false) LocalDate maxFecha){
List<ResidenteResponseDto> residentes;
try {
residentes = residenteService.getAllBajas(fecha, minFecha, maxFecha);
} catch (ResiException e) {
throw new ApiException(e, e.getMessage());
} catch (Exception e) {
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), e.getMessage());
}
return ResponseEntity.ok(residentes);
}
/**
* Elimina físicamente un residente de una residencia.
*
* @param idResidencia ID de la residencia.
* @param idResidente ID del residente.
* @return {@link ResponseEntity} con estado {@code 204 No Content} si la eliminación fue exitosa.
* @throws ApiException si ocurre un error al procesar la solicitud, como campos obligatorios faltantes
*/
@DeleteMapping("/{idResidencia}/resident/{idResidente}/delete")
public ResponseEntity<Void> delete(
@PathVariable Long idResidencia,
@PathVariable Long idResidente) {
try{
residenteService.deleteFisico(idResidencia,idResidente);
}catch (ResiException e) {
throw new ApiException(e, e.getMessage());
} catch (Exception e) {
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), e.getMessage());
}
return ResponseEntity.status(HttpStatus.NO_CONTENT).build();
}
/**
* Marca lógicamente como dado de baja a un residente (sin eliminarlo físicamente).
*
* @param idResidencia ID de la residencia.
* @param idResidente ID del residente.
* @return {@link ResponseEntity} con estado {@code 204 No Content} si se realizó correctamente.
* @throws ApiException si ocurre un error al procesar la solicitud, como campos obligatorios faltantes
*/
@PatchMapping("{idResidencia}/resident/{idResidente}/baja")
public ResponseEntity<Void> deleteLogico(
@PathVariable Long idResidencia,
@PathVariable Long idResidente) {
try{
residenteService.deleteLogico(idResidencia,idResidente);
}catch (ResiException e) {
throw new ApiException(e, e.getMessage());
} catch (Exception e) {
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), e.getMessage());
}
return ResponseEntity.status(HttpStatus.NO_CONTENT).build();
}
/**
* Actualiza parcialmente los datos de un residente dentro de una residencia.
*
* @param idResidencia ID de la residencia.
* @param idResidente ID del residente a actualizar.
* @param residenteDto DTO con los datos a actualizar.
* @return {@link ResponseEntity} con estado {@code 200 OK} y el residente actualizado.
* @throws ApiException si ocurre un error al procesar la solicitud, como campos obligatorios faltantes
*/
@PatchMapping("{idResidencia}/resident/{idResidente}/update")
public ResponseEntity<ResidenteResponseDto> update(
@PathVariable Long idResidencia,
@PathVariable Long idResidente,
@RequestBody ResidenteDto residenteDto) {
ResidenteResponseDto residenteResponseDto;
try {
residenteResponseDto = residenteService.update(idResidencia, idResidente, residenteDto);
} catch (ResiException e) {
throw new ApiException(e, e.getMessage());
} catch (Exception e) {
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), e.getMessage());
}
return ResponseEntity.ok(residenteResponseDto);
}
/**
* Envía un correo electrónico a un familiar de un residente.
*
* @param idResidencia ID de la residencia.
* @param idResidente ID del residente.
* @param emailRequestDto DTO con los datos del correo a enviar.
* @return {@link ResponseEntity} con estado {@code 204 No Content} si el envío fue exitoso.
*/
@PostMapping("/{idResidencia}/resident/{idResidente}/sendEmailFamily")
public ResponseEntity<Void> sendEmailFamily(
@PathVariable Long idResidencia,
@PathVariable Long idResidente,
@RequestBody EmailRequestDto emailRequestDto) {
try{
residenteService.sendEmailFamiliar(idResidencia, idResidente, emailRequestDto);
}catch (ResiException e) {
throw new ApiException(e, e.getMessage());
} catch (Exception e) {
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), e.getMessage());
}
return ResponseEntity.status(HttpStatus.NO_CONTENT).build();
}
}

View File

@ -0,0 +1,267 @@
package com.kevinolarte.resibenissa.controllers.residente;
import com.kevinolarte.resibenissa.config.Conf;
import com.kevinolarte.resibenissa.dto.in.ResidenteDto;
import com.kevinolarte.resibenissa.dto.in.moduloReporting.EmailRequestDto;
import com.kevinolarte.resibenissa.dto.out.ResidenteResponseDto;
import com.kevinolarte.resibenissa.dto.out.modulojuego.MediaRegistroDTO;
import com.kevinolarte.resibenissa.enums.Filtrado.ResidenteFiltrado;
import com.kevinolarte.resibenissa.enums.modulojuego.Dificultad;
import com.kevinolarte.resibenissa.enums.modulojuego.TipoAgrupacion;
import com.kevinolarte.resibenissa.exceptions.ApiErrorCode;
import com.kevinolarte.resibenissa.exceptions.ApiException;
import com.kevinolarte.resibenissa.exceptions.ResiException;
import com.kevinolarte.resibenissa.models.User;
import com.kevinolarte.resibenissa.services.ResidenteService;
import com.kevinolarte.resibenissa.services.modulojuego.RegistroJuegoService;
import lombok.AllArgsConstructor;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.*;
import java.time.LocalDate;
import java.util.List;
/**
* Controlador REST que gestiona las operaciones relacionadas con los residentes de una residencia.
* <p>
* Permite registrar, consultar, actualizar, listar y dar de baja residentes.
* Todas las operaciones se contextualizan dentro de la residencia del usuario autenticado.
* </p>
*
* URL base: {@code /resi/resident}
*
* Autor: Kevin Olarte
*/
@RequestMapping("/resi/resident")
@RestController
@AllArgsConstructor
public class ResidenteController {
private final ResidenteService residenteService;
private final RegistroJuegoService registroJuegoService;
/**
* Registra un nuevo residente en la residencia del usuario autenticado.
*
* @param residenteDto DTO con los datos del nuevo residente.
* @return {@link ResponseEntity} con estado {@code 201 Created} y el residente creado.
* @throws ApiException si ocurre un error durante el proceso de registro.
*/
@PostMapping("/add")
public ResponseEntity<ResidenteResponseDto> add(
@RequestBody ResidenteDto residenteDto){
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
User currentUser = (User) auth.getPrincipal();
ResidenteResponseDto residenteResponseDto;
try {
residenteResponseDto = residenteService.add(currentUser.getResidencia().getId(), residenteDto);
} catch (ResiException e) {
throw new ApiException(e, currentUser);
} catch (Exception e) {
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), currentUser, e.getMessage());
}
return ResponseEntity.status(HttpStatus.CREATED).body(residenteResponseDto);
}
/**
* Obtiene los detalles de un residente específico perteneciente a la misma residencia que el usuario autenticado.
*
* @param idResidente ID del residente.
* @return {@link ResponseEntity} con estado {@code 200 OK} y el residente encontrado.
* @throws ApiException si ocurre un error al intentar obtener el residente.
*/
@GetMapping("/{idResidente}/get")
public ResponseEntity<ResidenteResponseDto> get(
@PathVariable Long idResidente) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
User currentUser = (User) auth.getPrincipal();
ResidenteResponseDto residenteResponseDto;
try {
residenteResponseDto = residenteService.get(currentUser.getResidencia().getId(), idResidente);
} catch (ResiException e) {
throw new ApiException(e, currentUser);
} catch (Exception e) {
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), currentUser, e.getMessage());
}
return ResponseEntity.ok(residenteResponseDto);
}
/**
* Lista todos los residentes de la residencia del usuario autenticado, con múltiples filtros opcionales.
*
* @param fechaNacimiento Fecha exacta de nacimiento.
* @param minFNac Fecha mínima de nacimiento.
* @param maxFNac Fecha máxima de nacimiento.
* @param maxAge Edad máxima.
* @param minAge Edad mínima.
* @param idJuego ID de juego asociado (opcional).
* @param idEvento ID de evento asociado (opcional).
* @return {@link ResponseEntity} con la lista de residentes filtrados.
* @throws ApiException si ocurre un error al intentar obtener los residentes.
*/
@GetMapping("/getAll")
public ResponseEntity<List<ResidenteResponseDto>> getAll(
@RequestParam(required = false) LocalDate fechaNacimiento,
@RequestParam(required = false) LocalDate minFNac,
@RequestParam(required = false) LocalDate maxFNac,
@RequestParam(required = false) Integer maxAge,
@RequestParam(required = false) Integer minAge,
@RequestParam(required = false) Long idJuego,
@RequestParam(required = false) Long idEvento,
@RequestParam(required = false)ResidenteFiltrado filtrado) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
User currentUser = (User) auth.getPrincipal();
List<ResidenteResponseDto> residentes;
try{
residentes = residenteService.getAll(currentUser.getResidencia().getId(),fechaNacimiento, minFNac, maxFNac, maxAge, minAge, idJuego, idEvento, filtrado);
}catch (ResiException e){
throw new ApiException(e, currentUser);
}catch (Exception e){
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), currentUser, e.getMessage());
}
return ResponseEntity.ok(residentes);
}
/**
* Lista todos los residentes dados de baja en la residencia del usuario autenticado,
* con posibilidad de filtrar por fechas.
*
* @param fecha Fecha exacta de baja (opcional).
* @param minFecha Fecha mínima de baja (opcional).
* @param maxFecha Fecha máxima de baja (opcional).
* @return {@link ResponseEntity} con la lista de residentes dados de baja.
* @throws ApiException si ocurre un error al intentar obtener los residentes dados de baja.
*/
@GetMapping("/getAll/bajas")
public ResponseEntity<List<ResidenteResponseDto>> getAllBajas(
@RequestParam(required = false) LocalDate fecha,
@RequestParam(required = false) LocalDate minFecha,
@RequestParam(required = false) LocalDate maxFecha){
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
User currentUser = (User) auth.getPrincipal();
List<ResidenteResponseDto> residentesBajas;
try {
residentesBajas = residenteService.getAllBajas(currentUser.getResidencia().getId(),fecha, minFecha, maxFecha);
} catch (ResiException e) {
throw new ApiException(e, currentUser);
} catch (Exception e) {
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), currentUser, e.getMessage());
}
return ResponseEntity.ok(residentesBajas);
}
/**
* Marca lógicamente como dado de baja a un residente (no lo elimina físicamente).
*
* @param idResidente ID del residente a dar de baja.
* @return {@link ResponseEntity} con estado {@code 204 No Content} si se realizó correctamente.
* @throws ApiException si ocurre un error al intentar dar de baja al residente.
*/
@PatchMapping("/{idResidente}/baja")
public ResponseEntity<Void> deleteLogico(
@PathVariable Long idResidente) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
User currentUser = (User) auth.getPrincipal();
try{
residenteService.deleteLogico(currentUser.getResidencia().getId(),idResidente);
} catch (ResiException e) {
throw new ApiException(e, currentUser);
} catch (Exception e) {
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), currentUser, e.getMessage());
}
return ResponseEntity.status(HttpStatus.NO_CONTENT).build();
}
/**
* Actualiza parcialmente los datos de un residente específico.
*
* @param idResidente ID del residente a actualizar.
* @param residenteDto DTO con los nuevos datos del residente.
* @return {@link ResponseEntity} con estado {@code 200 OK} y el residente actualizado.
* @throws ApiException si ocurre un error al intentar actualizar el residente.
*/
@PatchMapping("/{idResidente}/update")
public ResponseEntity<ResidenteResponseDto> update(
@PathVariable Long idResidente,
@RequestBody ResidenteDto residenteDto) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
User currentUser = (User) auth.getPrincipal();
ResidenteResponseDto residenteResponseDto;
try {
residenteResponseDto = residenteService.update(currentUser.getResidencia().getId(), idResidente, residenteDto);
} catch (ResiException e) {
throw new ApiException(e, currentUser);
} catch (Exception e) {
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO),currentUser, e.getMessage());
}
return ResponseEntity.ok(residenteResponseDto);
}
/**
* Envía un correo electrónico a un familiar de un residente específico.
*
* @param idResidente ID del residente.
* @param emailRequestDto DTO con los detalles del correo electrónico.
* @return {@link ResponseEntity} con estado {@code 204 No Content} si se envió correctamente.
*/
@PostMapping("/{idResidente}/sendEmailFamily")
public ResponseEntity<Void> sendEmailFamily(
@PathVariable Long idResidente,
@RequestBody EmailRequestDto emailRequestDto) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
User currentUser = (User) auth.getPrincipal();
try{
residenteService.sendEmailFamiliar(currentUser.getResidencia().getId(), idResidente, emailRequestDto);
} catch (ResiException e) {
throw new ApiException(e, currentUser, e.getMessage());
} catch (Exception e) {
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), currentUser, e.getMessage());
}
return ResponseEntity.status(HttpStatus.NO_CONTENT).build();
}
/**
* Descarga la imagen por defecto del usuario.
*
* @return Recurso de imagen por defecto como archivo adjunto.
*/
@GetMapping("/defualtImage")
public ResponseEntity<Resource> downloadImage(){
Resource resource = residenteService.getImage(Conf.imageDefault);
return ResponseEntity.ok()
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + resource.getFilename() + "\"")
.body(resource);
}
}

View File

@ -0,0 +1,342 @@
package com.kevinolarte.resibenissa.controllers.user;
import com.kevinolarte.resibenissa.dto.in.UserDto;
import com.kevinolarte.resibenissa.dto.in.auth.ChangePasswordUserDto;
import com.kevinolarte.resibenissa.dto.out.UserResponseDto;
import com.kevinolarte.resibenissa.exceptions.ApiErrorCode;
import com.kevinolarte.resibenissa.exceptions.ApiException;
import com.kevinolarte.resibenissa.exceptions.ResiException;
import com.kevinolarte.resibenissa.services.UserService;
import lombok.AllArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.time.LocalDate;
import java.util.List;
/**
* Controlador REST para la administración de usuarios dentro de una residencia.
*
* Proporciona operaciones para registrar, consultar, actualizar, desactivar
* y eliminar usuarios. Admite filtros por estado, juego asociado y fecha de baja.
*
* URL base: {@code /admin/resi}
*
* @author Kevin Olarte
*/
@RequestMapping("/admin/resi")
@RestController
@AllArgsConstructor
public class UserAdminController {
private final UserService userService;
/**
* Registra un nuevo usuario en una residencia.
*
* @param idResidencia ID de la residencia.
* @param userDto Datos del usuario a registrar.
* @return Usuario creado.
* @throws ApiException si ocurre un error durante el registro.
*/
@PostMapping("{idResidencia}/user/add")
public ResponseEntity<UserResponseDto> addUser(
@PathVariable Long idResidencia,
@RequestBody UserDto userDto) {
UserResponseDto user;
try {
user = userService.add(idResidencia, userDto);
}catch (ResiException e){
throw new ApiException(e, e.getMessage());
} catch (Exception e) {
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), e.getMessage());
}
return ResponseEntity.ok(user);
}
/**
* Obtiene un usuario por su ID dentro de una residencia.
*
* @param idResidencia ID de la residencia.
* @param idUser ID del usuario.
* @return Datos del usuario.
* @throws ApiException si ocurre un error al intentar obtener el usuario.
*/
@GetMapping("{idResidencia}/user/{idUser}/get")
public ResponseEntity<UserResponseDto> get(
@PathVariable Long idResidencia,
@PathVariable Long idUser) {
UserResponseDto user;
try {
user = userService.get(idResidencia, idUser);
} catch (ResiException e) {
throw new ApiException(e, e.getMessage());
} catch (Exception e) {
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), e.getMessage());
}
return ResponseEntity.ok(user);
}
/**
* Obtiene un usuario por su email dentro de una residencia.
*
* @param idResidencia ID de la residencia.
* @param email Email del usuario.
* @return Datos del usuario.
* @throws ApiException si ocurre un error al intentar obtener el usuario.
*/
@GetMapping("/{idResidencia}/user/get")
public ResponseEntity<UserResponseDto> get(
@PathVariable Long idResidencia,
@RequestParam(required = true) String email) {
UserResponseDto user;
try {
user = userService.get(idResidencia, email);
} catch (ResiException e) {
throw new ApiException(e, e.getMessage());
} catch (Exception e) {
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), e.getMessage());
}
return ResponseEntity.ok(user);
}
/**
* Obtiene una lista de usuarios filtrada por estado y/o juego dentro de una residencia.
*
* @param idResidencia ID de la residencia.
* @param enabled Filtro por estado habilitado (opcional).
* @param idJuego Filtro por ID de juego (opcional).
* @return Lista de usuarios.
* @throws ApiException si ocurre un error al intentar obtener los usuarios.
*/
@GetMapping("{idResidencia}/user/getAll")
public ResponseEntity<List<UserResponseDto>> getAll(
@PathVariable Long idResidencia,
@RequestParam(required = false) Boolean enabled,
@RequestParam(required = false) Long idJuego) {
List<UserResponseDto> user;
try {
user = userService.getAll(idResidencia, enabled, idJuego);
} catch (ResiException e) {
throw new ApiException(e, e.getMessage());
} catch (Exception e) {
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), e.getMessage());
}
return ResponseEntity.ok(user);
}
/**
* Obtiene todos los usuarios, con filtros por estado y/o juego (sin filtrar por residencia).
*
* @param enabled Filtro por estado habilitado (opcional).
* @param idJuego Filtro por ID de juego (opcional).
* @return Lista de usuarios.
* @throws ApiException si ocurre un error al intentar obtener los usuarios.
*/
@GetMapping("/user/getAll")
public ResponseEntity<List<UserResponseDto>> getAll(
@RequestParam(required = false) Boolean enabled,
@RequestParam(required = false) Long idJuego) {
List<UserResponseDto> user;
try {
user = userService.getAll(enabled, idJuego);
} catch (ResiException e) {
throw new ApiException(e, e.getMessage());
} catch (Exception e) {
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), e.getMessage());
}
return ResponseEntity.ok(user);
}
/**
* Obtiene una lista de usuarios dados de baja en una residencia, con filtros de fecha.
*
* @param idResidencia ID de la residencia.
* @param fecha Fecha exacta de baja (opcional).
* @param minFecha Fecha mínima de baja (opcional).
* @param maxFecha Fecha máxima de baja (opcional).
* @return Lista de usuarios dados de baja.
* @throws ApiException si ocurre un error al intentar obtener los usuarios dados de baja.
*/
@GetMapping("{idResidencia}/user/getAll/bajas")
public ResponseEntity<List<UserResponseDto>> getAllBajas(
@PathVariable Long idResidencia,
@RequestParam(required = false)LocalDate fecha,
@RequestParam(required = false) LocalDate minFecha,
@RequestParam(required = false) LocalDate maxFecha){
List<UserResponseDto> user;
try {
user = userService.getAllBajas(idResidencia, fecha, minFecha, maxFecha);
} catch (ResiException e) {
throw new ApiException(e, e.getMessage());
} catch (Exception e) {
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), e.getMessage());
}
return ResponseEntity.ok(user);
}
/**
* Obtiene todos los usuarios dados de baja (sin filtrar por residencia).
*
* @param fecha Fecha exacta de baja (opcional).
* @param minFecha Fecha mínima de baja (opcional).
* @param maxFecha Fecha máxima de baja (opcional).
* @return Lista de usuarios dados de baja.
* @throws ApiException si ocurre un error al intentar obtener los usuarios dados de baja.
*/
@GetMapping("/user/getAll/bajas")
public ResponseEntity<List<UserResponseDto>> getAllBajas(
@RequestParam(required = false)LocalDate fecha,
@RequestParam(required = false) LocalDate minFecha,
@RequestParam(required = false) LocalDate maxFecha){
List<UserResponseDto> user;
try {
user = userService.getAllBajas(fecha, minFecha, maxFecha);
} catch (ResiException e) {
throw new ApiException(e, e.getMessage());
} catch (Exception e) {
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), e.getMessage());
}
return ResponseEntity.ok(user);
}
/**
* Elimina físicamente un usuario de una residencia.
*
* @param idResidencia ID de la residencia.
* @param idUser ID del usuario.
* @return HTTP 204 si se elimina correctamente.
* @throws ApiException si ocurre un error al intentar eliminar el usuario.
*/
@DeleteMapping("/{idResidencia}/user/{idUser}/delete")
public ResponseEntity<Void> delete(
@PathVariable Long idResidencia,
@PathVariable Long idUser) {
try{
userService.deleteFisico(idResidencia,idUser);
}catch (ResiException e){
throw new ApiException(e, e.getMessage());
} catch (Exception e) {
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), e.getMessage());
}
return ResponseEntity.status(HttpStatus.NO_CONTENT).build();
}
/**
* Elimina las referencias del usuario a registros dependientes (sin eliminar el usuario).
*
* @param idResidencia ID de la residencia.
* @param idUser ID del usuario.
* @return HTTP 204 si se eliminan correctamente las referencias.
* @throws ApiException si ocurre un error al intentar eliminar las referencias.
*/
@DeleteMapping("/{idResidencia}/{idUser}/delete/referencies")
public ResponseEntity<Void> deleteReferencies(
@PathVariable Long idResidencia,
@PathVariable Long idUser) {
try{
userService.deleteReferencies(idResidencia, idUser);
}catch (ResiException e){
throw new ApiException(e, e.getMessage());
} catch (Exception e) {
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), e.getMessage());
}
return ResponseEntity.status(HttpStatus.NO_CONTENT).build();
}
/**
* Actualiza los datos de un usuario.
*
* @param idResidencia ID de la residencia.
* @param idUser ID del usuario.
* @param userDto Nuevos datos del usuario.
* @return Usuario actualizado.
* @throws ApiException si ocurre un error al intentar actualizar el usuario.
*/
@PatchMapping("/{idResidencia}/user/{idUser}/update")
public ResponseEntity<UserResponseDto> update(
@PathVariable Long idResidencia,
@PathVariable Long idUser,
@RequestBody UserDto userDto) {
UserResponseDto user;
try {
user = userService.update(idResidencia, idUser, userDto);
} catch (ResiException e) {
throw new ApiException(e, e.getMessage());
} catch (Exception e) {
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), e.getMessage());
}
return ResponseEntity.ok(user);
}
/**
* Cambia la contraseña de un usuario validando la anterior.
*
* @param idResidencia ID de la residencia a la que pertenece el usuario.
* @param idUser ID del usuario.
* @param changePasswordUserDto DTO con la contraseña actual y la nueva.
* @return {@link ResponseEntity} con los datos del usuario tras el cambio.
* * @throws ApiException si ocurre un error al intentar cambiar la contraseña.
*/
@PatchMapping("/{idResidencia}/user/{idUser}/update/changePassword")
public ResponseEntity<UserResponseDto> changePassword(
@PathVariable Long idResidencia,
@PathVariable Long idUser,
@RequestBody ChangePasswordUserDto changePasswordUserDto) {
UserResponseDto userResponseDto;
try {
userResponseDto = userService.updatePassword(idResidencia, idUser, changePasswordUserDto);
} catch (ResiException e) {
throw new ApiException(e, e.getMessage());
} catch (Exception e) {
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), e.getMessage());
}
return ResponseEntity.ok(userResponseDto);
}
/**
* Marca un usuario como dado de baja (borrado lógico).
*
* @param idResidencia ID de la residencia.
* @param idUser ID del usuario.
* @return HTTP 204 si se desactiva correctamente.
* @throws ApiException si ocurre un error al intentar dar de baja al usuario.
*/
@PatchMapping("/{idResidencia}/user/{idUser}/baja")
public ResponseEntity<Void> baja(
@PathVariable Long idResidencia,
@PathVariable Long idUser) {
try{
userService.deleteLogico(idResidencia, idUser);
} catch (ResiException e) {
throw new ApiException(e, e.getMessage());
} catch (Exception e) {
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), e.getMessage());
}
return ResponseEntity.status(HttpStatus.NO_CONTENT).build();
}
}

View File

@ -0,0 +1,226 @@
package com.kevinolarte.resibenissa.controllers.user;
import com.kevinolarte.resibenissa.config.Conf;
import com.kevinolarte.resibenissa.dto.in.UserDto;
import com.kevinolarte.resibenissa.dto.in.auth.ChangePasswordUserDto;
import com.kevinolarte.resibenissa.dto.out.UserResponseDto;
import com.kevinolarte.resibenissa.exceptions.ApiErrorCode;
import com.kevinolarte.resibenissa.exceptions.ApiException;
import com.kevinolarte.resibenissa.exceptions.ResiException;
import com.kevinolarte.resibenissa.models.User;
import com.kevinolarte.resibenissa.services.UserService;
import lombok.AllArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.*;
import java.time.LocalDate;
import java.util.List;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
/**
* Controlador REST para gestionar las operaciones relacionadas con los usuarios.
* Expone endpoints para obtener, actualizar y dar de baja usuarios dentro de una residencia.
* <p>
* URL Base: {@code /resi/user}
* @author Kevin Olarte
*/
@RequestMapping("/resi/user")
@RestController
@AllArgsConstructor
public class UserController {
private final UserService userService;
/**
* Obtiene la información del usuario actualmente autenticado.
*
* @return DTO con los datos del usuario autenticado.
*/
@GetMapping("/me")
public ResponseEntity<UserResponseDto> getMe() {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
User currentUser = (User) auth.getPrincipal();
return ResponseEntity.ok(new UserResponseDto(currentUser));
}
/**
* Obtiene la información de un usuario específico por su ID, perteneciente a la misma residencia del usuario autenticado.
*
* @param idUser ID del usuario a consultar.
* @return DTO con los datos del usuario.
* @throws ApiException si ocurre un error al intentar obtener el usuario.
*/
@GetMapping("/{idUser}/get")
public ResponseEntity<UserResponseDto> get(
@PathVariable Long idUser) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
User currentUser = (User) auth.getPrincipal();
UserResponseDto userResponseDto;
try{
userResponseDto = userService.get(currentUser.getResidencia().getId(), idUser);
}catch (ResiException e){
throw new ApiException(e, currentUser);
}
return ResponseEntity.ok(userResponseDto);
}
/**
* Obtiene todos los usuarios filtrando opcionalmente por si están habilitados y
* por ID de juego que han registrado partidas.
*
* @param enabled Filtro opcional para usuarios habilitados.
* @param idJuego Filtro opcional por ID de juego.
* @return Lista de DTOs con los datos de los usuarios.
* @throws ApiException si ocurre un error al intentar obtener los usuarios.
*/
@GetMapping("/getAll")
public ResponseEntity<List<UserResponseDto>> getAll(
@RequestParam(required = false) Boolean enabled,
@RequestParam(required = false) Long idJuego) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
User currentUser = (User) auth.getPrincipal();
List<UserResponseDto> userResponseDtos;
try {
userResponseDtos = userService.getAll(currentUser.getResidencia().getId(), enabled, idJuego);
} catch (ResiException e) {
throw new ApiException(e, currentUser);
}catch (Exception e){
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), currentUser, e.getMessage());
}
return ResponseEntity.ok(userResponseDtos);
}
/**
* Obtiene todos los usuarios dados de baja, filtrando opcionalmente por fechas.
*
* @param fecha Fecha exacta de baja.
* @param minFecha Fecha mínima de baja.
* @param maxFecha Fecha máxima de baja.
* @return Lista de usuarios dados de baja en el rango indicado.
* @throws ApiException si ocurre un error al intentar obtener los usuarios dados de baja.
*/
@GetMapping("/getAll/bajas")
public ResponseEntity<List<UserResponseDto>> getAllBajas(
@RequestParam(required = false) LocalDate fecha,
@RequestParam(required = false) LocalDate minFecha,
@RequestParam(required = false) LocalDate maxFecha){
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
User currentUser = (User) auth.getPrincipal();
List<UserResponseDto> userResponseDtos;
try {
userResponseDtos = userService.getAllBajas(currentUser.getResidencia().getId(), fecha, minFecha, maxFecha);
} catch (ResiException e) {
throw new ApiException(e, currentUser);
}catch (Exception e){
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), currentUser, e.getMessage());
}
return ResponseEntity.ok(userResponseDtos);
}
/**
* Descarga la imagen por defecto del usuario.
*
* @return Recurso de imagen por defecto como archivo adjunto.
*/
@GetMapping("/defualtImage")
public ResponseEntity<Resource> downloadImage(){
Resource resource = userService.getImage(Conf.imageDefault);
return ResponseEntity.ok()
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + resource.getFilename() + "\"")
.body(resource);
}
/**
* Actualiza los datos personales del usuario autenticado.
*
* @param userDto Objeto DTO con los nuevos datos del usuario.
* @return DTO actualizado con la información del usuario.
* @throws ApiException si ocurre un error al intentar actualizar los datos del usuario.
*/
@PatchMapping("/update")
public ResponseEntity<UserResponseDto> update(
@RequestBody UserDto userDto) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
User currentUser = (User) auth.getPrincipal();
UserResponseDto userResponseDto;
try {
userResponseDto = userService.update(currentUser.getResidencia().getId(), currentUser.getId(), userDto);
} catch (ResiException e) {
throw new ApiException(e, currentUser);
}catch (Exception e){
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), currentUser, e.getMessage());
}
return ResponseEntity.ok(userResponseDto);
}
/**
* Cambia la contraseña del usuario autenticado.
*
* @param changePasswordUserDto DTO con la contraseña antigua y la nueva.
* @return DTO actualizado del usuario tras el cambio de contraseña.
* @throws ApiException si ocurre un error al intentar cambiar la contraseña.
*/
@PatchMapping("/update/changePassword")
public ResponseEntity<UserResponseDto> changePassword(
@RequestBody ChangePasswordUserDto changePasswordUserDto) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
User currentUser = (User) auth.getPrincipal();
UserResponseDto userResponseDto;
try {
userResponseDto = userService.updatePassword(currentUser.getResidencia().getId(), currentUser.getId(), changePasswordUserDto);
} catch (ResiException e) {
throw new ApiException(e, currentUser);
} catch (Exception e){
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), currentUser, e.getMessage());
}
return ResponseEntity.ok(userResponseDto);
}
/**
* Marca lógicamente como dado de baja al usuario.
*
* @return Respuesta sin contenido (HTTP 204) si se realiza correctamente.
* @throws ApiException si ocurre un error al intentar dar de baja al usuario.
*/
@PatchMapping("/baja")
public ResponseEntity<Void> baja() {
System.out.println("baja");
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
User currentUser = (User) auth.getPrincipal();
try {
userService.deleteLogico(currentUser.getResidencia().getId(), currentUser.getId());
}catch (ResiException e){
throw new ApiException(e, currentUser);
} catch (Exception e){
throw new ApiException(new ResiException(ApiErrorCode.PROBLEMA_INTERNO), currentUser, e.getMessage());
}
return ResponseEntity.status(HttpStatus.NO_CONTENT).build();
}
}

View File

@ -0,0 +1,22 @@
package com.kevinolarte.resibenissa.dto.in;
import lombok.Getter;
import lombok.Setter;
/**
* Objeto de transferencia de datos (DTO) utilizado para crear o actualizar una residencia.
* <p>
* Contiene los datos básicos necesarios para registrar una residencia en el sistema,
* como su nombre y correo electrónico de contacto.
* <p>
* No contiene lógica de negocio ni persistencia.
*
* @author Kevin Olarte
*/
@Getter
@Setter
public class ResidenciaDto {
private String nombre;
private String email;
}

View File

@ -0,0 +1,34 @@
package com.kevinolarte.resibenissa.dto.in;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Getter;
import lombok.Setter;
import java.time.LocalDate;
/**
* Objeto de transferencia de datos (DTO) utilizado para crear o actualizar residentes.
* <p>
* Contiene los datos personales básicos necesarios para registrar un residente
* y asociarlo a una residencia existente.
* <p>
* No contiene lógica de negocio ni anotaciones de persistencia.
*
* @author Kevin Olarte
*/
@Getter
@Setter
public class ResidenteDto {
private String nombre;
private String apellido;
private LocalDate fechaNacimiento;
private String documentoIdentidad;
private String familiar1;
private String familiar2;
private Integer year;
private Integer month;
}

View File

@ -0,0 +1,22 @@
package com.kevinolarte.resibenissa.dto.in;
import lombok.Getter;
import lombok.Setter;
/**
* Objeto de transferencia de datos (DTO) utilizado para crear o actualizar usuarios.
* <p>
* Contiene los campos necesarios para registrar un nuevo usuario en el sistema.
* No debe contener lógica de negocio ni anotaciones de persistencia.
*
* @author Kevin Olarte
*/
@Getter
@Setter
public class UserDto {
private String nombre;
private String apellido;
private String email;
private String password;
private Long idResidencia;
}

View File

@ -0,0 +1,11 @@
package com.kevinolarte.resibenissa.dto.in.auth;
import lombok.Getter;
import lombok.Setter;
@Setter
@Getter
public class ChangePasswordUserDto {
private String oldPassword;
private String newPassword;
}

View File

@ -0,0 +1,21 @@
package com.kevinolarte.resibenissa.dto.in.auth;
import lombok.Getter;
import lombok.Setter;
/**
* Objeto de transferecia de datos (DTO) utilizado para logearse y obtener el token de un usario activado anteriormente.
* <p>
* Este DTO permite obtener acceso a los demas endPoint porque se usara para crear un token. A partir de los parametros que tiene.
*
* @author Kevin Olarte
*/
@Getter
@Setter
public class LoginUserDto {
private String email;
private String password;
}

View File

@ -0,0 +1,23 @@
package com.kevinolarte.resibenissa.dto.in.auth;
import lombok.Getter;
import lombok.Setter;
/**
* Objecto de transferencia de datos (DTO) utilizado para registrar por primera vez un usuaario.
* <p>
* Este DTO permite asociar un nuevo usuario a una residencia especifica,
* enviando unicamente los siguentes parametros.
*
* @author Kevin Olarte
*/
@Getter
@Setter
public class RegisterUserDto {
private String nombre;
private String apellido;
private String email;
private String password;
private Long idResidencia;
}

View File

@ -0,0 +1,18 @@
package com.kevinolarte.resibenissa.dto.in.auth;
import lombok.Getter;
import lombok.Setter;
/**
* Objecto de transferencia de datos (DTO) utilizado para verificar el usuario creado anteriormente
* <p>
* Este DTO permite actibar el usuario creado anteriormete, con solo pasando
* los siguientes parametros.
*/
@Getter
@Setter
public class VerifyUserDto {
private String email;
private String verificationCode;
}

View File

@ -0,0 +1,12 @@
package com.kevinolarte.resibenissa.dto.in.modeloWallet;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class MovimientoRequestDTO {
private double cantidad;
private String concepto; // opcional, se puede usar "Depósito manual" por defecto si es null
}

View File

@ -0,0 +1,25 @@
package com.kevinolarte.resibenissa.dto.in.moduloOrgSalida;
import com.kevinolarte.resibenissa.enums.moduloOrgSalida.EstadoSalida;
import lombok.Getter;
import lombok.Setter;
import java.time.LocalDate;
import java.time.LocalDateTime;
/**
* DTO para representar un evento de salida.
*<p>
* Esta clase contiene información básica sobre un evento de salida, incluyendo su ID,
* nombre, descripción, fecha y estado.
*
* @author Kevin Olarte
*/
@Getter
@Setter
public class EventoSalidaDto {
private String nombre;
private String descripcion;
private LocalDateTime fecha;
private EstadoSalida estado;
}

View File

@ -0,0 +1,25 @@
package com.kevinolarte.resibenissa.dto.in.moduloOrgSalida;
import lombok.Getter;
import lombok.Setter;
/**
* Objeto de transferencia de datos (DTO) utilizado para representar la asistencia de un residente a una salida.
*
* <p>
* Esta clase encapsula la información necesaria para identificar al residente y el evento de salida
* al que asistió, así como su estado de asistencia.
* </p>
*
* @author Kevin Olarte
*/
@Getter
@Setter
public class ParticipanteDto {
private Long idResidente;
private Boolean recursosHumanos;
private Boolean recursosMateriales;
private String preOpinion;
private String postOpinion;
}

View File

@ -0,0 +1,11 @@
package com.kevinolarte.resibenissa.dto.in.moduloReporting;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class EmailRequestDto {
private String subject;
private String body;
}

View File

@ -0,0 +1,18 @@
package com.kevinolarte.resibenissa.dto.in.modulojuego;
import lombok.Getter;
import lombok.Setter;
/**
* Objeto de transferencia de datos (DTO) utilizado para crear o actualizar un juego.
* <p>
* Este DTO permite asociar un nuevo juego a una residencia específica,
* enviando únicamente el nombre del juego y el ID de la residencia.
*
* @author Kevin Olarte
*/
@Getter
@Setter
public class JuegoDto {
private String nombre;
}

View File

@ -0,0 +1,43 @@
package com.kevinolarte.resibenissa.dto.in.modulojuego;
import com.kevinolarte.resibenissa.enums.modulojuego.Dificultad;
import lombok.Getter;
import lombok.Setter;
/**
* Objeto de transferencia de datos (DTO) utilizado para registrar una partida de juego
* realizada por un residente en una residencia específica.
* <p>
* Incluye los datos mínimos necesarios para asociar el registro con el juego y la residencia,
* así como la información del desempeño durante la sesión.
*
* @author Kevin Olarte
*/
@Getter
@Setter
public class RegistroJuegoDto {
private Long idJuego;
private Long idResidente;
private Long idUsuario;
private Integer num;
private Double duracion;
private Dificultad dificultad;
private String observacion;
@Override
public String toString() {
return "RegistroJuegoDto{" +
"idJuego=" + idJuego +
", idResidente=" + idResidente +
", idUsuario=" + idUsuario +
", fallos=" + num +
", duracion=" + duracion +
", dificultad=" + dificultad +
'}';
}
}

View File

@ -0,0 +1,32 @@
package com.kevinolarte.resibenissa.dto.out;
import com.kevinolarte.resibenissa.models.User;
import lombok.Getter;
import lombok.Setter;
/**
* DTO de salida utilizado como respuesta al iniciar sesión.
* <p>
* Contiene el token JWT generado para el usuario autenticado, así como el tiempo
* de expiración en milisegundos, indicando cuánto tiempo es válido el token.
* </p>
*
* Este DTO es devuelto por el endpoint de autenticación {@code /auth/login}.
*
* @author Kevin Olarte
*/
@Getter
@Setter
public class LoginResponseDto {
private String token;
private Long expiresIn;
private Long idUser;
private Long idResidencia;
public LoginResponseDto(String token, long expiresIn, User user) {
this.token = token;
this.expiresIn = expiresIn;
this.idUser = user.getId();
this.idResidencia = user.getResidencia().getId();
}
}

View File

@ -0,0 +1,15 @@
package com.kevinolarte.resibenissa.dto.out;
import com.kevinolarte.resibenissa.models.Residencia;
public class ResidenciaPublicResponseDto {
public Long id;
public String nombre;
public String email;
public ResidenciaPublicResponseDto(Residencia residencia) {
this.id = residencia.getId();
this.nombre = residencia.getNombre();
this.email = residencia.getEmail();
}
}

View File

@ -0,0 +1,51 @@
package com.kevinolarte.resibenissa.dto.out;
import com.kevinolarte.resibenissa.models.Residencia;
import com.kevinolarte.resibenissa.models.Residente;
import com.kevinolarte.resibenissa.models.User;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
/**
* DTO de salida que representa la información pública de una residencia.
* <p>
* Esta clase se utiliza para enviar al cliente los datos esenciales de una residencia,
* incluyendo su nombre, email y los identificadores de usuarios y residentes asociados.
* </p>
*
* <p>
* Se construye a partir de una instancia de {@link Residencia}, mapeando únicamente los IDs
* de las relaciones con usuarios y residentes para evitar sobrecarga de datos en la respuesta.
* </p>
*
* @author Kevin Olarte
*/
@Getter
@Setter
public class ResidenciaResponseDto {
private Long id;
private String nombre;
private String email;
private List<Long> usuarios;
private List<Long> residentes;
public ResidenciaResponseDto(Residencia residencia) {
this.id = residencia.getId();
this.nombre = residencia.getNombre();
this.email = residencia.getEmail();
this.usuarios = residencia.getUsuarios()
.stream()
.map(User::getId)
.toList();
this.residentes = residencia.getResidentes()
.stream()
.map(Residente::getId)
.toList();
}
}

View File

@ -0,0 +1,44 @@
package com.kevinolarte.resibenissa.dto.out;
import com.kevinolarte.resibenissa.models.Residente;
import lombok.Getter;
import lombok.Setter;
import java.time.LocalDate;
/**
* DTO de salida que representa los datos públicos de un residente.
* <p>
* Esta clase se utiliza para enviar al cliente información relevante sobre
* un residente sin exponer detalles internos del modelo ni relaciones sensibles.
* </p>
*
* Contiene campos como el ID, nombre completo, fecha de nacimiento y la residencia asociada.
*
* @author Kevin Olate
*/
@Getter
@Setter
public class ResidenteResponseDto {
private Long id;
private String nombre;
private String apellido;
private LocalDate fechaNacimiento;
private String documentoIdentidad;
private String familiar1;
private String familiar2;
private Long idResidencia;
private Boolean baja;
public ResidenteResponseDto(Residente residente) {
this.id = residente.getId();
this.nombre = residente.getNombre();
this.apellido = residente.getApellido();
this.fechaNacimiento = residente.getFechaNacimiento();
this.idResidencia = residente.getResidencia().getId();
this.documentoIdentidad = residente.getDocuemntoIdentidad();
this.baja = residente.isBaja();
this.familiar1 = residente.getFamiliar1();
this.familiar2 = residente.getFamiliar2();
}
}

View File

@ -0,0 +1,48 @@
package com.kevinolarte.resibenissa.dto.out;
import com.kevinolarte.resibenissa.models.User;
import lombok.Getter;
import lombok.Setter;
/**
* DTO de salida para representar los datos públicos de un usuario del sistema.
* <p>
* Esta clase se utiliza para devolver información básica del usuario en las respuestas
* de la API, sin incluir detalles sensibles como contraseñas o tokens de autenticación.
* </p>
*
* <p>
* Contiene datos como el ID del usuario, nombre, email, estado de activación y la residencia asociada.
* </p>
*
* @author Kevin Olarte
*/
@Getter
@Setter
public class UserResponseDto {
private Long id;
private String nombre;
private String apellido;
private String email;
private String verificationCode;
private boolean enabled;
private Long idResidencia;
private String fotoPerfil;
private boolean baja;
public UserResponseDto(User user) {
this.id = user.getId();
this.nombre = user.getNombre();
this.apellido = user.getApellido();
this.email = user.getEmail();
this.enabled = user.isEnabled();
this.idResidencia = user.getResidencia().getId();
this.fotoPerfil = user.getFotoPerfil();
this.verificationCode = user.getVerificationCode();
this.baja = user.isBaja();
}
}

View File

@ -0,0 +1,47 @@
package com.kevinolarte.resibenissa.dto.out.moduloOrgSalida;
import com.kevinolarte.resibenissa.enums.moduloOrgSalida.EstadoSalida;
import com.kevinolarte.resibenissa.models.moduloOrgSalida.EventoSalida;
import com.kevinolarte.resibenissa.models.moduloOrgSalida.Participante;
import jdk.jfr.Event;
import lombok.Getter;
import lombok.Setter;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
/**
* DTO de salida que representa los datos públicos de un evento de salida.
* <p>
* Esta clase se utiliza para enviar al cliente información relevante sobre
* un evento de salida sin exponer detalles internos del modelo ni relaciones sensibles.
* </p>
*
* Contiene campos como el ID, nombre, descripción, fecha de inicio, estado y lista de participantes.
*
* @author Kevin Olarte
*/
@Getter
@Setter
public class EventoSalidaResponseDto {
private Long id;
private String nombre;
private String descripcion;
private LocalDateTime fechaInicio;
private EstadoSalida estado;
private List<Long> participantes;
private Long idResidencia;
public EventoSalidaResponseDto(EventoSalida e) {
this.id = e.getId();
this.nombre = e.getNombre();
this.descripcion = e.getDescripcion();
this.fechaInicio = e.getFechaInicio();
this.estado = e.getEstado();
this.participantes = e.getParticipantes().stream()
.map(Participante::getId)
.toList();
this.idResidencia = e.getResidencia().getId();
}
}

View File

@ -0,0 +1,48 @@
package com.kevinolarte.resibenissa.dto.out.moduloOrgSalida;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.kevinolarte.resibenissa.models.moduloOrgSalida.Participante;
import lombok.Getter;
import lombok.Setter;
/*
* Objeto de transferencia de datos (DTO) utilizado para representar la respuesta de un participante en un evento de salida.
* <p>
* Este DTO contiene información sobre el participante, incluyendo su ID, el ID del residente, el ID del evento de salida,
* si asistió al evento y sus opiniones antes y después del evento.
*
* @author Kevin Olarte
*/
@Getter
@Setter
public class ParticipanteResponseDto {
private Long id;
private Long idResidente;
private Long idEvento;
private boolean recursosHumanos;
private boolean recursosMateriales;
private boolean asistenciaPermitida;
private String preOpinion;
private String postOpinion;
@JsonIgnore
private Long idResidencia;
@JsonIgnore
private String familiar1;
@JsonIgnore
private String familiar2;
public ParticipanteResponseDto(Participante participante) {
this.id = participante.getId();
this.idResidente = participante.getResidente().getId();
this.idEvento = participante.getEvento().getId();
this.recursosHumanos = participante.isRecursosHumanos();
this.recursosMateriales = participante.isRecursosMateriales();
this.preOpinion = participante.getPreOpinion();
this.postOpinion = participante.getPostOpinion();
this.asistenciaPermitida = participante.isAsistenciaPermitida();
this.idResidencia = participante.getResidente().getResidencia().getId();
this.familiar1 = participante.getResidente().getFamiliar1();
this.familiar2 = participante.getResidente().getFamiliar2();
}
}

View File

@ -0,0 +1,28 @@
package com.kevinolarte.resibenissa.dto.out.modulojuego;
import com.kevinolarte.resibenissa.models.modulojuego.Juego;
import lombok.Getter;
import lombok.Setter;
/**
* DTO de salida que representa los datos públicos de un juego registrado.
* <p>
* Esta clase es utilizada para enviar al cliente información sobre un juego,
* como su identificador, nombre y la residencia a la que pertenece.
* </p>
*
* Se construye a partir de una entidad {@link Juego}.
*
* @author Kevin Olarte
*/
@Getter
@Setter
public class JuegoResponseDto {
private Long id;
private String nombre;
public JuegoResponseDto(Juego juego){
this.id = juego.getId();
this.nombre = juego.getNombre();
}
}

View File

@ -0,0 +1,19 @@
package com.kevinolarte.resibenissa.dto.out.modulojuego;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class MediaRegistroDTO {
private String agrupacion;
private Double promedio;
private Long total;
public MediaRegistroDTO(String agrupacion, Double promedio, Long total) {
this.agrupacion = agrupacion;
this.promedio = promedio;
this.total = total;
}
}

View File

@ -0,0 +1,49 @@
package com.kevinolarte.resibenissa.dto.out.modulojuego;
import com.kevinolarte.resibenissa.models.modulojuego.RegistroJuego;
import com.kevinolarte.resibenissa.enums.modulojuego.Dificultad;
import lombok.Getter;
import lombok.Setter;
import java.time.LocalDateTime;
/**
* DTO de salida que representa un registro del uso de un juego por parte de un residente.
* <p>
* Este DTO se utiliza para enviar al cliente información sobre una sesión de juego,
* incluyendo detalles como duración, dificultad, número de errores y fecha de ejecución.
* También identifica al residente, al juego y al usuario (trabajador) que registró la partida.
* </p>
*
* <p>
* Si el usuario asociado a la partida es nulo, el campo {@code idUsario} se establece en {@code null}.
* </p>
*
* @author Kevin Olarte.
*/
@Getter
@Setter
public class RegistroJuegoResponseDto {
private Long id;
private Long idResidente;
private Long idJuego;
private Long idUsuario;
private Integer num;
private Double duracion;
private Dificultad dificultad;
private LocalDateTime fecha;
private String observacion;
public RegistroJuegoResponseDto(RegistroJuego registroJuego) {
this.id = registroJuego.getId();
this.idResidente = registroJuego.getResidente().getId();
this.idJuego = registroJuego.getJuego().getId();
this.idUsuario = registroJuego.getUsuario() == null? null : registroJuego.getUsuario().getId();
this.num = registroJuego.getNum();
this.duracion = registroJuego.getDuracion();
this.dificultad = registroJuego.getDificultad();
this.fecha = registroJuego.getFecha();
this.observacion = registroJuego.getObservacion() == null ? null : registroJuego.getObservacion();
}
}

View File

@ -0,0 +1,14 @@
package com.kevinolarte.resibenissa.enums;
import lombok.Getter;
@Getter
public enum CategoriaLog {
DEBUG_INSERT(3L), DEBUG_UPDATE(4L), DEBUG_DELETE(5L), DEBUG_SELECT(6L),
ERROR_INSERT(7L), ERROR_UPDATE(8L), ERROR_DELETE(9L), ERROR_SELECT(10L);
private final Long valor;
CategoriaLog(Long valor) {
this.valor = valor;
}
}

View File

@ -0,0 +1,24 @@
package com.kevinolarte.resibenissa.enums.Filtrado;
import org.springframework.data.domain.Sort;
public enum RegistroJuegoFiltrado {
FECHA_ASC("fecha", Sort.Direction.ASC),
FECHA_DESC("fecha", Sort.Direction.DESC),
NUM_ASC("num", Sort.Direction.ASC),
NUM_DESC("num", Sort.Direction.DESC),
DURACION_ASC("duracion", Sort.Direction.ASC),
DURACION_DESC("duracion", Sort.Direction.DESC);
private final String campo;
private final Sort.Direction direccion;
RegistroJuegoFiltrado(String campo, Sort.Direction direccion) {
this.campo = campo;
this.direccion = direccion;
}
public Sort toSort() {
return Sort.by(direccion, campo);
}
}

View File

@ -0,0 +1,24 @@
package com.kevinolarte.resibenissa.enums.Filtrado;
import org.springframework.data.domain.Sort;
public enum ResidenteFiltrado {
FECHA_NAC_ASC("fechaNacimiento", Sort.Direction.ASC),
FECHA_NAC_DESC("fechaNacimiento", Sort.Direction.DESC),
NOMBRE_ASC("nombre", Sort.Direction.ASC),
NOMBRE_DESC("nombre", Sort.Direction.DESC),
APELLIDO_ASC("apellido", Sort.Direction.ASC),
APELLIDO_DESC("apellido", Sort.Direction.DESC);
private final String campo;
private final Sort.Direction direccion;
ResidenteFiltrado(String campo, Sort.Direction direccion) {
this.campo = campo;
this.direccion = direccion;
}
public Sort toSort() {
return Sort.by(direccion, campo);
}
}

View File

@ -0,0 +1,6 @@
package com.kevinolarte.resibenissa.enums;
public enum Role {
ADMIN, NORMAL, SENDER;
}

View File

@ -0,0 +1,19 @@
package com.kevinolarte.resibenissa.enums.moduloOrgSalida;
import lombok.Getter;
/**
* Enumeración que representa los posibles estados de una salida.
* @author Kevin Olarte
*/
@Getter
public enum EstadoSalida {
ABIERTO(0), CERRADO(1), EN_CURSO(2), FINALIZADA(3);
private final int estado;
EstadoSalida(int estado) {
this.estado = estado;
}
}

View File

@ -0,0 +1,5 @@
package com.kevinolarte.resibenissa.enums.moduloWallet;
public enum TipoMovimiento {
IN, OUT;
}

View File

@ -0,0 +1,17 @@
package com.kevinolarte.resibenissa.enums.modulojuego;
/**
* Enum que representa la dificultad de una partida.
* <p>
* La interpretación de la dificultad depende del tipo de juego:
* <ul>
* <li>En juegos tipo A, determina el modo de juego (p. ej. memoria, parejas, secuencia).</li>
* <li>En juegos tipo B, define la complejidad de las reglas (p. ej. número de cartas, tiempo disponible).</li>
* </ul>
* Es responsabilidad del juego interpretar estos valores adecuadamente.
*
* author Kevin Olarte
*/
public enum Dificultad {
DIFICULTAD1, DIFICULTAD2, DIFICULTAD3;
}

View File

@ -0,0 +1,7 @@
package com.kevinolarte.resibenissa.enums.modulojuego;
public enum TipoAgrupacion {
DIARIO,
MENSUAL,
ANUAL;
}

View File

@ -0,0 +1,75 @@
package com.kevinolarte.resibenissa.exceptions;
import lombok.Getter;
import org.springframework.http.HttpStatus;
/**
* Enumeración que define los distintos códigos de error personalizados utilizados en la API.
* <p>
* Cada valor del enum representa un tipo específico de error que puede ocurrir en la lógica
* de negocio de la aplicación. Cada error contiene:
* <ul>
* <li>Un código numérico interno único para facilitar el rastreo.</li>
* <li>Un mensaje amigable que puede mostrarse al usuario.</li>
* <li>Un {@link HttpStatus} que será devuelto como código HTTP en la respuesta.</li>
* </ul>
*
* <p>Esta clase se utiliza junto a {@link ResiException} para lanzar errores consistentes.</p>
*
* @see ResiException
* @author Kevin Olarte
*/
@Getter
public enum ApiErrorCode {
CAMPOS_OBLIGATORIOS(1001, "Faltan campos obligatorios", HttpStatus.BAD_REQUEST),
CORREO_INVALIDO(1002, "Email invalid", HttpStatus.NOT_ACCEPTABLE),
CORREO_DUPLICADO(1003, "Email ya existente", HttpStatus.CONFLICT),
NOMBRE_DUPLICADO(1004, "Nombre ya existente", HttpStatus.CONFLICT),
FECHA_INVALIDO(1005, "Fecha invalida", HttpStatus.NOT_ACCEPTABLE),
RESIDENCIA_INVALIDO(1006, "Residencia invalida", HttpStatus.NOT_FOUND),
VALORES_NEGATIVOS(1007,"No puede ser negativos los valores", HttpStatus.NOT_ACCEPTABLE),
JUEGO_INVALIDO(1008, "Juego invalido", HttpStatus.NOT_FOUND),
RESIDENTE_INVALIDO(1009, "Residente invalido", HttpStatus.NOT_FOUND),
USUARIO_INVALIDO(1010, "Usuario invalido", HttpStatus.NOT_FOUND),
CONFLICTO_REFERENCIAS(1011, "Problemas con las referencias de las entidades, no corresponden a las mismas", HttpStatus.CONFLICT),
REGISTRO_JUEGO_INVALIDO(1012,"Registro juego invalido" , HttpStatus.NOT_FOUND ),
REFERENCIAS_DEPENDIENTES(1013, "Esta entidad tiene referencias asociadas que dependen de el", HttpStatus.CONFLICT),
USER_EXIST(1014, "Usuario ya existente" , HttpStatus.CONFLICT ),
USER_NO_ACTIVADO(1015,"Usuario no activado" , HttpStatus.NOT_ACCEPTABLE ),
CODIGO_EXPIRADO(1016,"El codigo de verificacion a caducado" , HttpStatus.GONE ),
CODIGO_INVALIDO(1017,"El codigo de verificacion no es valdio" , HttpStatus.NOT_ACCEPTABLE ),
USER_YA_ACTIVADO(1018,"El usuario ya activado" , HttpStatus.CONFLICT ),
ERROR_MAIL_SENDER(1019,"Error enviando el correo" , HttpStatus.BAD_REQUEST ),
ENDPOINT_PROTEGIDO(1020, "Tiene que tener permiso para acceder aqui", HttpStatus.METHOD_NOT_ALLOWED),
PROBLEMAS_CON_FILE(1021,"A surgido un problema con la imagen" , HttpStatus.INTERNAL_SERVER_ERROR ),
PARTICIPANTE_YA_REGISTRADO(1022,"Este residente ya participa en otro evento ese mismo dia" ,HttpStatus.NOT_ACCEPTABLE),
EVENTO_SALIDA_INVALIDO(1023,"EventoSalida invalida" ,HttpStatus.NOT_FOUND),
EVENTO_SALIDA_NO_DISPONIBLE(1024, "No se puede hacer nada con este evento ciertos problemas", HttpStatus.BAD_REQUEST),
PARTICIPANTE_INVALIDO(1025, "Participante invalido" , HttpStatus.NOT_FOUND),
PARTICIPANTE_INMUTABLE(1026, "No se puede hacer esta operacion con el participante porque ahora mismo es inmutable", HttpStatus.NOT_ACCEPTABLE),
DOCUMENTO_INVALIDO(1027,"El docuemnto de identidad no tiene el formato correcto" ,HttpStatus.NOT_ACCEPTABLE),
DOCUMENTO_DUPLICADO(1028,"El docuemnto de identidad debe ser unico" ,HttpStatus.CONFLICT ),
CONTRASENA_INCORRECTA(1029,"Contraseña no valida" , HttpStatus.NOT_ACCEPTABLE ),
RESIDENTE_BAJA(1028, "Este residente ya esta dado de baja", HttpStatus.NOT_ACCEPTABLE ),
RANGO_EDAD_INVALIDO(1029, "Rango de edad inadecuado" , HttpStatus.NOT_ACCEPTABLE ),
USUARIO_BAJA(1030, "Usuario dado de baja", HttpStatus.NOT_ACCEPTABLE),
RESIDENCIA_BAJA(1031, "Residencia dado de baja", HttpStatus.NOT_ACCEPTABLE),
ESTADO_INVALIDO(1032, "Este estado no se puede cambiar por el otro, sigue la secuencia.",HttpStatus.NOT_ACCEPTABLE),
PROBLEMA_INTERNO(5000, "Error interno del servidor", HttpStatus.INTERNAL_SERVER_ERROR),
WALLET_NO_ENCONTRADA(1033,"Wallet no encontrada" , HttpStatus.NOT_FOUND ),
MONTO_INVALIDO(1034,"El monto seleccionado no es valido" , HttpStatus.NOT_ACCEPTABLE ),
SALDO_INSUFICIENTE(1035, "No tiene suficiente saldo", HttpStatus.NOT_ACCEPTABLE);
private final int code;
private final String message;
private final HttpStatus httpStatus;
ApiErrorCode(int code, String message, HttpStatus httpStatus) {
this.code = code;
this.message = message;
this.httpStatus = httpStatus;
}
}

View File

@ -0,0 +1,29 @@
package com.kevinolarte.resibenissa.exceptions;
import com.kevinolarte.resibenissa.models.User;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class ApiException extends RuntimeException {
private ResiException resiException;
private String mensaje;
public ApiException(ResiException resiException, User user) {
super(resiException.getMessage());
this.resiException = resiException;
this.mensaje = resiException.getMessage() + " - Usuario: " + user.getUsername();
}
public ApiException(ResiException resiException, User user, String mensaje) {
super(resiException.getMessage());
this.resiException = resiException;
this.mensaje = mensaje + " - Usuario: " + user.getUsername();
}
public ApiException(ResiException resiException, String mensaje) {
super(resiException.getMessage());
this.resiException = resiException;
this.mensaje = mensaje;
}
}

View File

@ -0,0 +1,45 @@
package com.kevinolarte.resibenissa.exceptions;
import lombok.Getter;
import lombok.Setter;
import java.time.LocalDateTime;
/**
* DTO de salida que representa la respuesta de error estándar en la API.
* <p>
* Esta clase encapsula toda la información relevante cuando ocurre una {@link ResiException}
* y se desea devolver una respuesta estructurada al cliente.
* </p>
*
* Contiene:
* <ul>
* <li>Mensaje de error amigable.</li>
* <li>Código de error interno (definido en {@link ApiErrorCode}).</li>
* <li>Estado HTTP correspondiente.</li>
* <li>Fecha y hora del error.</li>
* </ul>
*
* Este DTO es ideal para ser devuelto por un {@code @ControllerAdvice}.
*
* @author Kevin Olarte
*/
@Getter
@Setter
public class ErrorResponseDto {
private String mensaje;
private int codigo; // Código del enum
private int status; // Código HTTP
private String timestamp;
public ErrorResponseDto(ResiException ex) {
this.mensaje = ex.getErrorCode().getMessage();
this.codigo = ex.getErrorCode().getCode();
this.status = ex.getHttpStatus().value();
this.timestamp = LocalDateTime.now().toString();
}
}

View File

@ -0,0 +1,41 @@
package com.kevinolarte.resibenissa.exceptions;
import com.kevinolarte.resibenissa.services.LoggerService;
import lombok.AllArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
/**
* Manejador global de excepciones para la API REST.
* <p>
* Captura y transforma excepciones de tipo {@link ResiException} en una respuesta estructurada
* de tipo {@link ErrorResponseDto}, con el código, mensaje y estado HTTP correspondiente.
* </p>
*
* Esta clase garantiza respuestas consistentes en todos los endpoints ante errores controlados.
*
* @author Kevin Olarte
*/
@RestControllerAdvice
@AllArgsConstructor
public class GlobalExceptionHandler {
private LoggerService loggerService;
/**
* Maneja cualquier excepción de tipo {@link ResiException} lanzada en los controladores.
*
* @param ex Excepción personalizada que contiene el código de error y estado HTTP.
* @return {@link ResponseEntity} con el cuerpo {@link ErrorResponseDto} y el código HTTP correspondiente.
*/
@ExceptionHandler(ApiException.class)
public ResponseEntity<ErrorResponseDto> handleApiException(ApiException ex) {
loggerService.registrarLogError(ex.getMensaje());
ErrorResponseDto error =
new ErrorResponseDto(ex.getResiException());
return ResponseEntity.status(ex.getResiException().getHttpStatus()).body(error);
}
}

View File

@ -0,0 +1,39 @@
package com.kevinolarte.resibenissa.exceptions;
import lombok.Getter;
import org.springframework.http.HttpStatus;
/**
* Excepción personalizada para representar errores controlados en la lógica de negocio de la aplicación.
* <p>
* Esta excepción encapsula un {@link ApiErrorCode}, el cual contiene:
* <ul>
* <li>Un mensaje de error legible.</li>
* <li>Un código HTTP asociado.</li>
* <li>Un código interno único para identificar el tipo de error.</li>
* </ul>
*
* Al lanzar esta excepción, los controladores o filtros pueden capturarla y devolver una respuesta estructurada
* al cliente con la información correspondiente.
*
* @see ApiErrorCode
* @author Kevin Olarte
*/
@Getter
public class ResiException extends RuntimeException{
private final ApiErrorCode errorCode;
public ResiException(ApiErrorCode errorCode) {
super(errorCode.getMessage());
this.errorCode = errorCode;
}
public int getStatusCode() {
return errorCode.getHttpStatus().value();
}
public HttpStatus getHttpStatus() {
return errorCode.getHttpStatus();
}
}

View File

@ -0,0 +1,55 @@
package com.kevinolarte.resibenissa.models;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
/**
* Modelo que representa un registro de acción en el sistema.
* <p>
* Esta clase almacena información sobre acciones realizadas por los usuarios,
* incluyendo el nombre de la acción, una descripción, el correo del usuario que la realizó,ç
* y la fecha de la acción.
* <p>
* @Author Kevin Olarte
*/
@Entity
@Table(name = "logger")
@Getter
@Setter
public class Logger {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String endpoint;
private String metodo;
@Column(length = 1000)
private String descripcion;
private LocalDateTime fecha;
@ManyToOne
@JoinColumn(name = "id_padre")
private Logger padre;
@OneToMany(mappedBy = "padre", cascade = CascadeType.ALL)
private List<Logger> hijos = new ArrayList<>();
// Constructores
public Logger() {}
public Logger(String endpoint, String metodo, String descripcion) {
this.endpoint = endpoint;
this.metodo = metodo;
this.descripcion = descripcion;
this.fecha = LocalDateTime.now();
}
// Getters y setters
}

View File

@ -0,0 +1,84 @@
package com.kevinolarte.resibenissa.models;
import com.kevinolarte.resibenissa.models.moduloOrgSalida.EventoSalida;
import com.kevinolarte.resibenissa.models.modulojuego.Juego;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
import java.time.LocalDateTime;
import java.util.LinkedHashSet;
import java.util.Set;
import com.fasterxml.jackson.annotation.JsonIgnore;
/**
* Entidad que representa una residencia en la base de datos.
* <p>
* Cada residencia tiene un identificador, un nombre único y un correo electrónico.
* Está relacionada con múltiples usuarios del sistema (como cuidadores o administradores),
* así como con los residentes (personas mayores) que viven en ella.
*
* @author Kevin Olarte
*/
@Entity
@Table(name = "residencias")
@Getter
@Setter
public class Residencia {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
/**
* Nombre de la residencia. Debe ser único.
*/
@Column(nullable = false, unique = true)
private String nombre;
/**
* Correo electrónico de contacto de la residencia. También debe ser único.
*/
@Column(unique = true, nullable = false)
private String email;
/**
* Usuarios asociados a esta residencia (por ejemplo, personal que la gestiona).
* Relación uno a muchos.
*/
@OneToMany(mappedBy = "residencia", cascade = CascadeType.ALL)
@JsonIgnore
private Set<User> usuarios = new LinkedHashSet<>();
/**
* Residentes (personas mayores) que viven en esta residencia.
* Relación uno a muchos.
*/
@OneToMany(mappedBy = "residencia", cascade = CascadeType.ALL)
@JsonIgnore
private Set<Residente> residentes = new LinkedHashSet<>();
/**
* Eventos que tienen implementados en esta residencia
* Relacion uno a muchos
*/
@OneToMany(mappedBy = "residencia", cascade = CascadeType.ALL)
@JsonIgnore
private Set<EventoSalida> eventos = new LinkedHashSet<>();
private boolean baja;
private LocalDateTime fechaBaja;
public Residencia(String nombre, String email) {
this.nombre = nombre;
this.email = email;
this.baja = false;
}
public Residencia() {
}
}

View File

@ -0,0 +1,104 @@
package com.kevinolarte.resibenissa.models;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.kevinolarte.resibenissa.models.moduloOrgSalida.Participante;
import com.kevinolarte.resibenissa.models.moduloWallet.Wallet;
import com.kevinolarte.resibenissa.models.modulojuego.RegistroJuego;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.LinkedHashSet;
import java.util.Set;
/**
* Entidad que representa a un residente (persona mayor) de una residencia.
* <p>
* A diferencia de {@link User}, los residentes no se autentican en el sistema.
* Esta clase almacena datos personales y permite vincular al residente con su residencia,
* así como con registros de actividades (como juegos, entrenos, etc).
* <p>
* Esta entidad es gestionada por usuarios autenticados del sistema.
*
* @author Kevin Olarte
*/
@Entity
@Table(name = "residentes")
@Getter
@Setter
public class Residente {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@Column(nullable = false)
private String nombre;
@Column(nullable = false)
private String apellido;
@Column(name = "fecha_nacimiento",nullable = false)
private LocalDate fechaNacimiento;
/**
* documento de identidad del residente.
* DNI o NIE.
*/
@Column(name = "documento_identidad", nullable = false, unique = true)
private String docuemntoIdentidad;
@Column(nullable = false)
private String familiar1;
private String familiar2;
/**
* Relación con la residencia donde vive este residente.
* Múltiples residentes pueden estar en una misma residencia.
*/
@ManyToOne
@JoinColumn(name = "fk_residencia", nullable = false)
private Residencia residencia;
/**
* Registros de juegos realizados por este residente.
* Relación uno a muchos.
*/
@OneToMany(mappedBy = "residente", cascade = CascadeType.REMOVE)
@JsonIgnore
private Set<RegistroJuego> registros = new LinkedHashSet<>();
/**
* Conjunto de veces que participa el resudente a las excuirsiones.
*/
@OneToMany(mappedBy = "residente", cascade = CascadeType.ALL)
@JsonIgnore
private Set<Participante> participantes = new LinkedHashSet<>();
@OneToOne(mappedBy = "residente", cascade = CascadeType.ALL)
@JsonIgnore
private Wallet wallet;
private boolean baja;
private LocalDateTime fechaBaja;
public Residente(String nombre, String apellido, LocalDate fechaNacimiento, String documentoIdentidad, String familiar1, String familiar2) {
this.nombre = nombre;
this.apellido = apellido;
this.fechaNacimiento = fechaNacimiento;
this.docuemntoIdentidad = documentoIdentidad;
this.baja = false;
this.familiar1 = familiar1;
this.familiar2 = familiar2;
}
public Residente(){
}
}

View File

@ -0,0 +1,180 @@
package com.kevinolarte.resibenissa.models;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.kevinolarte.resibenissa.enums.Role;
import com.kevinolarte.resibenissa.models.modulojuego.RegistroJuego;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.time.LocalDateTime;
import java.util.*;
/**
* Entidad que representa un usuario del sistema.
* <p>
* Implementa la interfaz {@link UserDetails} de Spring Security para integrarse
* con el sistema de autenticación.
* Cada usuario está asociado a una residencia.
*
* @author Kevin Olarte
*/
@Entity
@Table(name = "usuarios")
@Setter
@Getter
public class User implements UserDetails {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@Column(nullable = false)
private String nombre;
@Column(nullable = false)
private String apellido;
/**
* Correo electrónico del usuario, usado como username para iniciar sesión.
* Debe ser único en el sistema.
*/
@Column(nullable = false, unique = true)
private String email;
@Column(nullable = false)
private String password;
/**
* Código de verificación utilizado en el proceso de activación de la cuenta.
*/
@Column(name = "verification_code")
private String verificationCode;
/**
* Fecha y hora de expiración del código de verificación.
*/
@Column(name = "verification_expiration")
private LocalDateTime verificationExpiration;
/**
* Indica si el usuario está habilitado para acceder al sistema.
*/
private boolean enabled;
/**
* Ruta de la foto de perfil.
*/
@Column(name = "foto_perfil")
private String fotoPerfil;
/**
* Roles o permisos del usuario. En este caso no se están usando, se devuelve una lista vacía.
*/
@JsonIgnore
private boolean accountNonExpired;
@JsonIgnore
private boolean credentialsNonExpired;
@JsonIgnore
private boolean accountNonLocked;
/**
* Relación con la residencia a la que pertenece el usuario.
* Varios usuarios pueden estar asociados a la misma residencia.
*/
@ManyToOne
@JoinColumn(name = "fk_residencia", nullable = false)
@JsonIgnore
private Residencia residencia;
private boolean baja;
private LocalDateTime fechaBaja;
/**
* Relacion con los registros de los juegos que son los que se encargan de asignar el jugador.
* Para llevar un mayor control.
* <p>
* Este ususario puede tener varios registros.
*/
@OneToMany(mappedBy = "usuario")
@JsonIgnore
private Set<RegistroJuego> registroJuegos = new LinkedHashSet<>();
private Role role;
public User() {
}
public User(String nombre, String apellido, String email, String password){
this.nombre = nombre;
this.apellido = apellido;
this.email = email;
this.password = password;
this.baja = false;
this.role = Role.NORMAL;
}
public User(String nombre, String apellido, String email, String password, Role role){
this.nombre = nombre;
this.apellido = apellido;
this.email = email;
this.password = password;
this.baja = false;
this.role = role;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
if (role != null) {
return List.of(new SimpleGrantedAuthority("ROLE_" + role));
}
return List.of();
}
@Override
public String getPassword(){
return this.password;
}
@Override
public String getUsername() {
return this.email; // el correo es usado como nombre de usuario
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return this.enabled;
}
@Override
public String toString() {
return "User{" +
"nombre='" + nombre + '\'' +
", id=" + id +
", email='" + email + '\'' +
", apellido='" + apellido + '\'' +
", password='" + password + '\'' +
", residencia=" + residencia.getId() +
'}';
}
}

View File

@ -0,0 +1,88 @@
package com.kevinolarte.resibenissa.models.moduloOrgSalida;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.kevinolarte.resibenissa.dto.in.moduloOrgSalida.EventoSalidaDto;
import com.kevinolarte.resibenissa.enums.moduloOrgSalida.EstadoSalida;
import com.kevinolarte.resibenissa.models.Residencia;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.LinkedHashSet;
import java.util.Set;
/**
* Representa un evento de salida organizado en el sistema.
* <p>
* Cada salida contiene información sobre su nombre, descripción, fecha de inicio y estado.
* También mantiene una lista de residentes que participan en ella.
* @author Kevin Olarte
*/
@Entity
@Table(name = "eventos")
@Getter
@Setter
public class EventoSalida {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
/**
* Nombre del evento de salida.
*/
@Column(nullable = false)
private String nombre;
/**
* Descripción del evento, explicando de qué trata o qué actividades incluye.
*/
@Column(nullable = false)
private String descripcion;
/**
* Fecha en la que se realizará el evento de salida.
*/
@Column(nullable = false)
private LocalDateTime fechaInicio;
/**
* Estado actual del evento de salida.
* <p>
* Puede ser uno de los valores definidos en el enum {@link EstadoSalida}, como por ejemplo: PENDIENTE, REALIZADA, CANCELADA.
*/
@Column(nullable = false)
private EstadoSalida estado;
/**
* Conjunto de participantes que asisten a esta salida.
* <p>
* Relación uno-a-muchos con {@link Participante}, donde esta salida es la referencia inversa.
* <p>
* Se ignora en la serialización JSON para evitar bucles y sobrecarga de datos innecesarios.
*/
@OneToMany(mappedBy = "evento", cascade = CascadeType.REMOVE)
@JsonIgnore
private Set<Participante> participantes = new LinkedHashSet<>();
/**
* Relación con la residencia donde pueden tener varias salidas.
* Múltiples Eventos pueden estar en una misma residencia.
*/
@ManyToOne
@JoinColumn(name = "fk_residencia")
private Residencia residencia;
public EventoSalida(EventoSalidaDto e) {
this.nombre = e.getNombre();
this.descripcion = e.getDescripcion();
this.fechaInicio = e.getFecha();
this.estado = EstadoSalida.ABIERTO;
}
public EventoSalida() {}
}

View File

@ -0,0 +1,104 @@
package com.kevinolarte.resibenissa.models.moduloOrgSalida;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.kevinolarte.resibenissa.dto.in.moduloOrgSalida.ParticipanteDto;
import com.kevinolarte.resibenissa.models.Residente;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
import java.time.LocalDateTime;
/**
* Representa la participación de un residente en un evento de salida.
* <p>
* Esta entidad contiene información sobre el residente que participa, la salida a la que asiste
* y otros datos como si necesita ayuda y sus opiniones antes y después del evento.
* <p>
* Se asegura que no pueda existir más de un participante con la misma combinación
* de {@code fk_salida} y {@code fk_residente}, garantizando que un residente no se registre
* dos veces en la misma salida.
* @author Kevin Olarte
*/
@Entity
@Getter
@Setter
@Table(
name = "participantes",
uniqueConstraints = {
@UniqueConstraint(columnNames = {"fk_evento", "fk_residente"})
}
)
public class Participante {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
/**
* Evento de salida en el que participa el residente.
* <p>
* Relación muchos-a-uno con {@link EventoSalida}.
* Esta relación es ignorada en la serialización JSON para evitar bucles.
*/
@ManyToOne
@JoinColumn(name = "fk_evento", nullable = false)
@JsonIgnore
private EventoSalida evento;
/**
* Residente que participa en la salida.
* <p>
* Relación muchos-a-uno con {@link Residente}.
* Esta relación es ignorada en la serialización JSON para evitar bucles.
*/
@ManyToOne
@JoinColumn(name = "fk_residente", nullable = false)
@JsonIgnore
private Residente residente;
/**
* Indica si el residente requiere ayuda durante la salida.
*/
@Column(nullable = false)
private boolean recursosHumanos;
@Column(nullable = false)
private boolean recursosMateriales;
private boolean asistenciaPermitida;
/**
* Opinión del residente antes de asistir a la salida (opcional).
*/
private String preOpinion;
/**
* Opinión del residente después de asistir a la salida (opcional).
*/
private String postOpinion;
private boolean baja;
private LocalDateTime fechaBaja;
public Participante() {
this.recursosHumanos = false;
this.recursosMateriales = false;
this.preOpinion = "";
this.postOpinion = "";
this.baja = false;
this.asistenciaPermitida = false;
}
}

View File

@ -0,0 +1,35 @@
package com.kevinolarte.resibenissa.models.moduloWallet;
import com.kevinolarte.resibenissa.enums.moduloWallet.TipoMovimiento;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
import java.time.LocalDateTime;
@Entity
@Setter
@Getter
public class MovimientoWallet {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne
@JoinColumn(name = "fk_wallet", nullable = false)
private Wallet wallet;
private Double cantidad;
@Enumerated(EnumType.STRING)
private TipoMovimiento tipo; // IN / OUT
private String concepto;
private LocalDateTime fecha;
public MovimientoWallet() {
// Constructor por defecto
}
}

View File

@ -0,0 +1,43 @@
package com.kevinolarte.resibenissa.models.moduloWallet;
import com.kevinolarte.resibenissa.models.Residente;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
@Entity
@Getter
@Setter
public class Wallet {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@OneToOne
@JoinColumn(name = "fk_residente", unique = true)
private Residente residente;
private Double saldoTotal;
@OneToMany(mappedBy = "wallet", cascade = CascadeType.ALL)
private Set<MovimientoWallet> movimientos = new LinkedHashSet<>();
public Wallet() {
this.saldoTotal = 0.0;
this.movimientos = new LinkedHashSet<>();
}
@Override
public String toString() {
return "Wallet{" +
"id=" + id +
", residente=" + residente +
", saldoTotal=" + saldoTotal +
", movimientos=" + movimientos +
'}';
}
}

View File

@ -0,0 +1,60 @@
package com.kevinolarte.resibenissa.models.modulojuego;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.kevinolarte.resibenissa.models.Residencia;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
import java.util.LinkedHashSet;
import java.util.Set;
/**
* Entidad que representa un juego disponible en una residencia.
* <p>
* Cada juego tiene un nombre único dentro de su residencia.
* Está asociado a múltiples registros que indican cuándo y cómo fue jugado por los residentes.
*
* @author Kevin Olarte
*/
@Entity
@Table(
name = "juegos"
)
@Getter
@Setter
public class Juego {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
/**
* Nombre del juego. Debe ser único dentro de cada residencia.
*/
@Column(nullable = false, unique = true)
private String nombre;
/**
* Registros de uso de este juego por parte de residentes.
*/
@OneToMany(mappedBy = "juego", cascade = CascadeType.REMOVE)
@JsonIgnore
private Set<RegistroJuego> registro = new LinkedHashSet<>();
public Juego(String nombre){
this.nombre = nombre;
}
public Juego(){}
@Override
public String toString() {
return "Juego{" +
"id=" + id +
", nombre='" + nombre + '\'' +
'}';
}
}

View File

@ -0,0 +1,91 @@
package com.kevinolarte.resibenissa.models.modulojuego;
import com.kevinolarte.resibenissa.dto.in.modulojuego.RegistroJuegoDto;
import com.kevinolarte.resibenissa.enums.modulojuego.Dificultad;
import com.kevinolarte.resibenissa.models.Residente;
import com.kevinolarte.resibenissa.models.User;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
import java.time.LocalDateTime;
/**
* Entidad que representa un registro del uso de un juego por parte de un residente.
* <p>
* Cada vez que un residente juega, se guarda un registro con la fecha, la duración
* del juego y el número de fallos cometidos.
* Este historial puede ser usado para analizar la evolución cognitiva o motriz del residente.
*
* @author Kevin Olarte
*/
@Entity
@Table(name = "registros_juegos")
@Getter
@Setter
public class RegistroJuego {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
/**
* Residente que ha jugado.
*/
@ManyToOne(optional = false)
@JoinColumn(name = "fk_residente")
private Residente residente;
/**
* Juego que fue utilizado.
*/
@ManyToOne(optional = false)
@JoinColumn(name = "fk_juego")
private Juego juego;
/**
* Trabajador que registra la partida.
*/
@ManyToOne
@JoinColumn(name = "fk_usuario")
private User usuario;
/**
* Fecha en la que se jugó.
*/
@Column(nullable = false)
private LocalDateTime fecha;
/**
* Número de fallos cometidos por el residente durante el juego.
*/
private Integer num;
/**
* Duración del juego en segundos (o minutos, según convención del sistema).
*/
@Column(nullable = false)
private Double duracion;
private Dificultad dificultad;
private String observacion;
public RegistroJuego(RegistroJuegoDto input) {
this.num = input.getNum();
this.duracion = input.getDuracion();
this.fecha = LocalDateTime.now();
this.dificultad = input.getDificultad();
this.observacion = "";
}
public RegistroJuego(RegistroJuegoDto input, LocalDateTime fecha) {
this.num = input.getNum();
this.duracion = input.getDuracion();
this.fecha = fecha;
this.dificultad = input.getDificultad();
this.observacion = "";
}
public RegistroJuego() {
}
}

View File

@ -0,0 +1,14 @@
package com.kevinolarte.resibenissa.repositories;
import com.kevinolarte.resibenissa.models.Logger;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import java.util.List;
public interface LoggerRepository extends JpaRepository<Logger, Long> {
}

View File

@ -0,0 +1,46 @@
package com.kevinolarte.resibenissa.repositories;
import com.kevinolarte.resibenissa.models.Residencia;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Optional;
/**
* Repositorio para acceder y gestionar entidades {@link Residencia}.
* <p>
* Proporciona métodos personalizados para buscar residencias por nombre o correo electrónico,
* además de los métodos estándar de {@link JpaRepository}.
*
* @author Kevin Olarte
*/
@Repository
public interface ResidenciaRepository extends JpaRepository<Residencia, Long> {
/**
* Busca una residencia por su correo electrónico.
*
* @param email Correo electrónico de la residencia.
* @return Un Optional con la residencia si se encuentra.
*/
Optional<Residencia> findByEmail(String email);
/**
* Busca una residencia por su nombre.
*
* @param nombre Nombre de la residencia.
* @return Un Optional con la residencia si se encuentra.
*/
Optional<Residencia> findByNombre(String nombre);
/**
* Busca residencias que estén marcadas como dadas de baja.
*
* @return Lista de residencias que tienen el campo baja en true.
*/
List<Residencia> findByBajaTrue();
}

View File

@ -0,0 +1,30 @@
package com.kevinolarte.resibenissa.repositories;
import com.kevinolarte.resibenissa.models.Residencia;
import com.kevinolarte.resibenissa.models.Residente;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
/**
* Repositorio para acceder y gestionar entidades {@link Residente}.
* <p>
* Permite realizar operaciones CRUD y buscar residentes por su residencia.
*
* @author Kevin Olarte
*/
@Repository
public interface ResidenteRepository extends JpaRepository<Residente, Long>, JpaSpecificationExecutor<Residente>{
/**
* Busca residentes por su documento de identidad.
* @param docuemntoIdentidad Documento de identidad del residente.
* @return residente que coincide con el documento de identidad proporcionado.
*/
Residente findByDocuemntoIdentidad(String docuemntoIdentidad);
}

View File

@ -0,0 +1,30 @@
package com.kevinolarte.resibenissa.repositories;
import com.kevinolarte.resibenissa.models.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.stereotype.Repository;
import java.util.List;
/**
* Repositorio para gestionar entidades {@link User}.
* <p>
* Proporciona métodos personalizados para buscar usuarios por correo electrónico
* o por la residencia a la que pertenecen, además de los métodos básicos de {@link JpaRepository}.
*
* @author Kevin Olarte
*/
@Repository
public interface UserRepository extends JpaRepository<User, Long>, JpaSpecificationExecutor<User> {
/**
* Busca un usuario por su correo electrónico.
*
* @param email Correo electrónico del usuario a buscar.
* @return Usuario encontrado o {@code null} si no existe.
*/
User findByEmail(String email);
}

View File

@ -0,0 +1,41 @@
package com.kevinolarte.resibenissa.repositories.moduloOrgSalida;
import com.kevinolarte.resibenissa.enums.moduloOrgSalida.EstadoSalida;
import com.kevinolarte.resibenissa.models.moduloOrgSalida.EventoSalida;
import jakarta.transaction.Transactional;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import java.time.LocalDate;
import java.util.List;
@Repository
public interface EventoSalidaRepository extends JpaRepository<EventoSalida, Long>, JpaSpecificationExecutor<EventoSalida> {
/**
* Exsite un nombre igual en la misma residencia
*/
boolean existsByNombreAndResidenciaId(String nombre, Long residenciaId);
/**
* Elimina todos los eventos de salida asociados a una residencia.
* @param idResidencia ID de la residencia cuyos eventos de salida
*/
@Modifying
@Transactional
@Query("DELETE FROM EventoSalida p WHERE p.residencia.id = :idResidencia")
void deleteAllByResidenciaId(@Param("idResidencia") Long idResidencia);
/**
* Obtiene el evento de salida por su nombre y id de residencia perteneciente.
* @param nombre Nombre del evento de salida a buscar.
* @param residenciaId ID de la residencia a la que pertenece el evento de salida.
* @return EventoSalida si se encuentra, null en caso contrario.
*/
EventoSalida findByNombreAndResidencia_Id(String nombre, Long residenciaId);
}

View File

@ -0,0 +1,70 @@
package com.kevinolarte.resibenissa.repositories.moduloOrgSalida;
import com.kevinolarte.resibenissa.models.Residente;
import com.kevinolarte.resibenissa.models.moduloOrgSalida.EventoSalida;
import com.kevinolarte.resibenissa.models.moduloOrgSalida.Participante;
import jakarta.transaction.Transactional;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface ParticipanteRepository extends JpaRepository<Participante, Long>, JpaSpecificationExecutor<Participante> {
/**
* Verifica si un residente esta inscrito el otro evento a partir del idEvento
* @param idResidente ID del residente
* @param idEvento ID del evento actual
* @return true si el residente está inscrito en otro evento el mismo día, false en caso contrario
*/
@Query("""
SELECT COUNT(p) > 0
FROM Participante p
WHERE p.residente.id = :idResidente
AND p.evento.fechaInicio = (
SELECT s.fechaInicio
FROM EventoSalida s
WHERE s.id = :idEvento
)
AND p.evento.id <> :idEvento
""")
boolean existsByResidenteInOtherEventoSameDay(
@Param("idResidente") Long idResidente,
@Param("idEvento") Long idEvento
);
/**
* Verifica si un residente está inscrito en un evento específico.
*
* @param idResidente ID del residente.
* @param idEvento ID del evento.
* @return true si el residente está inscrito en el evento, false en caso contrario.
*/
@Query("""
SELECT COUNT(p) > 0
FROM Participante p
WHERE p.residente.id = :idResidente
AND p.evento.id = :idEvento
AND p.baja = false
""")
boolean isResidenteInscritoEnEvento(Long idResidente, Long idEvento);
/**
* Elimina todos los participantes asociados a una residencia específica.
*
* @param idResidencia ID de la residencia.
*/
@Modifying
@Transactional
@Query("DELETE FROM Participante p WHERE p.residente.residencia.id = :idResidencia")
void deleteAllByResidenciaId(@Param("idResidencia") Long idResidencia);
}

View File

@ -0,0 +1,12 @@
package com.kevinolarte.resibenissa.repositories.moduloWallet;
import com.kevinolarte.resibenissa.models.moduloWallet.MovimientoWallet;
import com.kevinolarte.resibenissa.models.moduloWallet.Wallet;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface MovimientoWalletRepository extends JpaRepository<MovimientoWallet, Long> {
List<MovimientoWallet> findByWallet(Wallet wallet);
}

View File

@ -0,0 +1,7 @@
package com.kevinolarte.resibenissa.repositories.moduloWallet;
import com.kevinolarte.resibenissa.models.moduloWallet.Wallet;
import org.springframework.data.jpa.repository.JpaRepository;
public interface WalletRepository extends JpaRepository<Wallet, Long> {
}

View File

@ -0,0 +1,35 @@
package com.kevinolarte.resibenissa.repositories.modulojuego;
import com.kevinolarte.resibenissa.models.Residencia;
import com.kevinolarte.resibenissa.models.modulojuego.Juego;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
/**
* Repositorio para acceder y gestionar entidades de tipo {@link Juego}.
* <p>
* Extiende {@link JpaRepository} para aprovechar métodos CRUD y de paginación,
* y define métodos personalizados para búsquedas por nombre y residencia.
*
* @author Kevin Olarte
*/
@Repository
public interface JuegoRepository extends JpaRepository<Juego, Long> {
/**
* Busca si existe juegos por su nombre, ignorando mayúsculas y minúsculas.
* @param nombre Nombre del juego a buscar.
* @return Lista de juegos que coinciden con el nombre dado.
*/
boolean existsByNombreIgnoreCase(String nombre);
/**
* Busca juegos cuyo nombre contenga una cadena específica, ignorando mayúsculas y minúsculas.
* @param nombre Cadena a buscar en el nombre del juego.
* @return Lista de juegos que contienen la cadena en su nombre.
*/
List<Juego> findByNombreContainingIgnoreCase(String nombre);
}

View File

@ -0,0 +1,647 @@
package com.kevinolarte.resibenissa.repositories.modulojuego;
import com.kevinolarte.resibenissa.dto.out.modulojuego.MediaRegistroDTO;
import com.kevinolarte.resibenissa.enums.modulojuego.Dificultad;
import com.kevinolarte.resibenissa.models.modulojuego.Juego;
import com.kevinolarte.resibenissa.models.modulojuego.RegistroJuego;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import java.util.List;
/**
* Repositorio para acceder y gestionar registros de juegos ({@link RegistroJuego}).
* <p>
* Permite obtener estadísticas o historiales de juegos filtrando por residencia,
* por juego, por residente o por fecha.
*
* @author Kevin Olarte
*/
@Repository
public interface RegistroJuegoRepository extends JpaRepository<RegistroJuego, Long>, JpaSpecificationExecutor<RegistroJuego> {
/**
* Calcula el promedio diario de duración de los registros de juego de un residente específico.
*
* <p>
* Se agrupan los registros por día (formato YYYY-MM-DD), y se calcula la duración media y el total de registros
* por cada día. Permite filtrar por dificultad y por un juego concreto (ambos opcionales).
* </p>
*
* @param idResidente ID del residente a analizar.
* @param dificultad Nivel de dificultad a filtrar (opcional, puede ser {@code null}).
* @param idJuego ID del juego a filtrar (opcional, puede ser {@code null} para incluir todos).
* @return Lista de {@link MediaRegistroDTO} con fecha (día), duración media y cantidad de registros por día.
*/
@Query("""
SELECT new com.kevinolarte.resibenissa.dto.out.modulojuego.MediaRegistroDTO(TO_CHAR(r.fecha, 'YYYY-MM-DD'), AVG(r.duracion), COUNT(r))
FROM RegistroJuego r
WHERE r.residente.id = :idResidente
AND (:dificultad IS NULL OR r.dificultad = :dificultad)
AND (:idJuego IS NULL OR r.juego.id = :idJuego)
GROUP BY TO_CHAR(r.fecha, 'YYYY-MM-DD')
ORDER BY TO_CHAR(r.fecha, 'YYYY-MM-DD')
""")
List<MediaRegistroDTO> getMediaDuracionDiaria(@Param("idResidente") Long idResidente,
@Param("dificultad") Dificultad dificultad,
@Param("idJuego") Long idJuego);
/**
* Calcula el promedio mensual de duración de los registros de juego de un residente específico.
*
* <p>
* Agrupa por mes (formato YYYY-MM), mostrando duración media y número de registros por cada mes.
* Se puede filtrar por dificultad y juego (ambos opcionales).
* </p>
*
* @param idResidente ID del residente.
* @param dificultad Dificultad del juego (opcional).
* @param idJuego ID del juego a filtrar (opcional, puede ser {@code null} para todos los juegos).
* @return Lista de {@link MediaRegistroDTO} con la media mensual de duración y el total de registros.
*/
@Query("""
SELECT new com.kevinolarte.resibenissa.dto.out.modulojuego.MediaRegistroDTO(TO_CHAR(r.fecha, 'YYYY-MM'), AVG(r.duracion), COUNT(r))
FROM RegistroJuego r
WHERE r.residente.id = :idResidente
AND (:dificultad IS NULL OR r.dificultad = :dificultad)
AND (:idJuego IS NULL OR r.juego.id = :idJuego)
GROUP BY TO_CHAR(r.fecha, 'YYYY-MM')
ORDER BY TO_CHAR(r.fecha, 'YYYY-MM')
""")
List<MediaRegistroDTO> getMediaDuracionMensual(@Param("idResidente") Long idResidente,
@Param("dificultad") Dificultad dificultad,
@Param("idJuego") Long idJuego);
/**
* Calcula el promedio anual de duración de los registros de juego de un residente específico.
*
* <p>
* Agrupa por año (YYYY) y devuelve la duración media y el total de registros de cada año.
* Se puede aplicar filtro por dificultad y juego.
* </p>
*
* @param idResidente ID del residente cuyos registros se agrupan.
* @param dificultad Dificultad del juego (opcional).
* @param idJuego ID del juego a filtrar (opcional, puede ser {@code null}).
* @return Lista de {@link MediaRegistroDTO} con año, duración media y conteo de registros por año.
*/
@Query("""
SELECT new com.kevinolarte.resibenissa.dto.out.modulojuego.MediaRegistroDTO(CAST(EXTRACT(YEAR FROM r.fecha) AS string), AVG(r.duracion), COUNT(r))
FROM RegistroJuego r
WHERE r.residente.id = :idResidente
AND (:dificultad IS NULL OR r.dificultad = :dificultad)
AND (:idJuego IS NULL OR r.juego.id = :idJuego)
GROUP BY EXTRACT(YEAR FROM r.fecha)
ORDER BY EXTRACT(YEAR FROM r.fecha)
""")
List<MediaRegistroDTO> getMediaDuracionAnual(@Param("idResidente") Long idResidente,
@Param("dificultad") Dificultad dificultad,
@Param("idJuego") Long idJuego);
/**
* Calcula el promedio diario de errores cometidos por un residente específico.
*
* <p>
* Agrupa los registros por día (formato YYYY-MM-DD), y calcula el promedio de errores (`num`)
* y el total de registros por día. Permite filtrar por dificultad y juego de forma opcional.
* </p>
*
* @param idResidente ID del residente a analizar.
* @param dificultad Nivel de dificultad del juego (opcional).
* @param idJuego ID del juego a filtrar (opcional, {@code null} para incluir todos).
* @return Lista de {@link MediaRegistroDTO} con fecha (día), promedio de errores y número total de registros por día.
*/
@Query("""
SELECT new com.kevinolarte.resibenissa.dto.out.modulojuego.MediaRegistroDTO(TO_CHAR(r.fecha, 'YYYY-MM-DD'), AVG(r.num), COUNT(r))
FROM RegistroJuego r
WHERE r.residente.id = :idResidente
AND (:dificultad IS NULL OR r.dificultad = :dificultad)
AND (:idJuego IS NULL OR r.juego.id = :idJuego)
GROUP BY TO_CHAR(r.fecha, 'YYYY-MM-DD')
ORDER BY TO_CHAR(r.fecha, 'YYYY-MM-DD')
""")
List<MediaRegistroDTO> getMediaErroresDiario(@Param("idResidente") Long idResidente,
@Param("dificultad") Dificultad dificultad,
@Param("idJuego") Long idJuego);
/**
* Calcula el promedio mensual de errores cometidos por un residente específico.
*
* <p>
* Agrupa por mes (formato YYYY-MM), y devuelve el promedio de errores (`num`)
* junto con el total de registros por cada mes. Se puede filtrar por dificultad y juego.
* </p>
*
* @param idResidente ID del residente.
* @param dificultad Dificultad del juego (opcional).
* @param idJuego ID del juego a filtrar (opcional, {@code null} para incluir todos).
* @return Lista de {@link MediaRegistroDTO} con promedio mensual de errores y conteo de registros.
*/
@Query("""
SELECT new com.kevinolarte.resibenissa.dto.out.modulojuego.MediaRegistroDTO(TO_CHAR(r.fecha, 'YYYY-MM'), AVG(r.num), COUNT(r))
FROM RegistroJuego r
WHERE r.residente.id = :idResidente
AND (:dificultad IS NULL OR r.dificultad = :dificultad)
AND (:idJuego IS NULL OR r.juego.id = :idJuego)
GROUP BY TO_CHAR(r.fecha, 'YYYY-MM')
ORDER BY TO_CHAR(r.fecha, 'YYYY-MM')
""")
List<MediaRegistroDTO> getMediaErroresMensual(@Param("idResidente") Long idResidente,
@Param("dificultad") Dificultad dificultad,
@Param("idJuego") Long idJuego);
/**
* Calcula el promedio anual de errores cometidos por un residente específico.
*
* <p>
* Agrupa por año (YYYY), devolviendo el promedio de errores (`num`)
* y el total de registros por cada año. Admite filtros opcionales por dificultad y juego.
* </p>
*
* @param idResidente ID del residente cuyos registros se analizarán.
* @param dificultad Nivel de dificultad (opcional).
* @param idJuego ID del juego (opcional, {@code null} para todos los juegos).
* @return Lista de {@link MediaRegistroDTO} con año, promedio de errores y total de registros por año.
*/
@Query("""
SELECT new com.kevinolarte.resibenissa.dto.out.modulojuego.MediaRegistroDTO(CAST(EXTRACT(YEAR FROM r.fecha) AS string), AVG(r.num), COUNT(r))
FROM RegistroJuego r
WHERE r.residente.id = :idResidente
AND (:dificultad IS NULL OR r.dificultad = :dificultad)
AND (:idJuego IS NULL OR r.juego.id = :idJuego)
GROUP BY EXTRACT(YEAR FROM r.fecha)
ORDER BY EXTRACT(YEAR FROM r.fecha)
""")
List<MediaRegistroDTO> getMediaErroresAnual(@Param("idResidente") Long idResidente,
@Param("dificultad") Dificultad dificultad,
@Param("idJuego") Long idJuego);
/**
* Calcula el promedio mensual de duración de los juegos jugados por todos los residentes
* de una residencia, con opción de filtrar por dificultad.
*
* @param idResidencia ID de la residencia cuyos residentes serán considerados.
* @param dificultad Dificultad específica a filtrar (puede ser null para ignorar el filtro).
* @param idJuego ID del juego a filtrar (puede ser null para ignorar el filtro).
* @return Lista de {@link MediaRegistroDTO} donde cada elemento representa un mes y su promedio de duración.
*/
@Query("""
SELECT new com.kevinolarte.resibenissa.dto.out.modulojuego.MediaRegistroDTO(TO_CHAR(r.fecha, 'YYYY-MM'), AVG(r.duracion), COUNT(r))
FROM RegistroJuego r
WHERE r.residente.residencia.id = :idResidencia
AND (:dificultad IS NULL OR r.dificultad = :dificultad)
AND (:idJuego IS NULL OR r.juego.id = :idJuego)
GROUP BY TO_CHAR(r.fecha, 'YYYY-MM')
ORDER BY TO_CHAR(r.fecha, 'YYYY-MM')
""")
List<MediaRegistroDTO> getMediaDuracionResidenciaMensual(@Param("idResidencia") Long idResidencia,
@Param("dificultad") Dificultad dificultad,
@Param("idJuego") Long idJuego);
/**
* Calcula el promedio anual de duración de los juegos jugados por todos los residentes
* de una residencia, con opción de filtrar por dificultad.
*
* @param idResidencia ID de la residencia cuyos residentes serán considerados.
* @param dificultad Dificultad específica a filtrar (puede ser null para ignorar el filtro).
* @param idJuego ID del juego a filtrar (puede ser null para ignorar el filtro).
* @return Lista de {@link MediaRegistroDTO} donde cada elemento representa un año y su promedio de duración.
*/
@Query("""
SELECT new com.kevinolarte.resibenissa.dto.out.modulojuego.MediaRegistroDTO(CAST(EXTRACT(YEAR FROM r.fecha) AS string), AVG(r.duracion), COUNT(r))
FROM RegistroJuego r
WHERE r.residente.residencia.id = :idResidencia
AND (:dificultad IS NULL OR r.dificultad = :dificultad)
AND (:idJuego IS NULL OR r.juego.id = :idJuego)
GROUP BY EXTRACT(YEAR FROM r.fecha)
ORDER BY EXTRACT(YEAR FROM r.fecha)
""")
List<MediaRegistroDTO> getMediaDuracionResidenciaAnual(@Param("idResidencia") Long idResidencia,
@Param("dificultad") Dificultad dificultad,
@Param("idJuego") Long idJuego);
/**
* Calcula el promedio diario de errores cometidos en los juegos por todos los residentes
* de una residencia específica, con opción de filtrar por dificultad.
*
* @param idResidencia ID de la residencia cuyos residentes se van a considerar.
* @param dificultad Dificultad de los juegos a filtrar (si es null, se consideran todas las dificultades).
* @param idJuego ID del juego a filtrar (puede ser null para ignorar el filtro).
* @return Lista de {@link MediaRegistroDTO} donde cada elemento representa un día y el promedio de errores cometidos.
*/
@Query("""
SELECT new com.kevinolarte.resibenissa.dto.out.modulojuego.MediaRegistroDTO(TO_CHAR(r.fecha, 'YYYY-MM-DD'), AVG(r.num), COUNT(r))
FROM RegistroJuego r
WHERE r.residente.residencia.id = :idResidencia
AND (:dificultad IS NULL OR r.dificultad = :dificultad)
AND (:idJuego IS NULL OR r.juego.id = :idJuego)
GROUP BY TO_CHAR(r.fecha, 'YYYY-MM-DD')
ORDER BY TO_CHAR(r.fecha, 'YYYY-MM-DD')
""")
List<MediaRegistroDTO> getMediaErroresResidenciaDiaria(@Param("idResidencia") Long idResidencia,
@Param("dificultad") Dificultad dificultad,
@Param("idJuego") Long idJuego);
/**
* Calcula el promedio diario de duración de los juegos jugados por todos los residentes
* de una residencia, con opción de filtrar por dificultad.
*
* @param idResidencia ID de la residencia cuyos residentes serán considerados.
* @param idJuego ID del juego a filtrar (puede ser null para ignorar el filtro).
* @param dificultad Dificultad específica a filtrar (puede ser null para ignorar el filtro).
* @return Lista de {@link MediaRegistroDTO} donde cada elemento representa un día y su promedio de duración.
*/
@Query("""
SELECT new com.kevinolarte.resibenissa.dto.out.modulojuego.MediaRegistroDTO(TO_CHAR(r.fecha, 'YYYY-MM-DD'), AVG(r.duracion), COUNT(r))
FROM RegistroJuego r
WHERE r.residente.residencia.id = :idResidencia
AND (:dificultad IS NULL OR r.dificultad = :dificultad)
AND (:idJuego IS NULL OR r.juego.id = :idJuego)
GROUP BY TO_CHAR(r.fecha, 'YYYY-MM-DD')
ORDER BY TO_CHAR(r.fecha, 'YYYY-MM-DD')
""")
List<MediaRegistroDTO> getMediaDuracionResidenciaDiaria(@Param("idResidencia") Long idResidencia,
@Param("dificultad") Dificultad dificultad,
@Param("idJuego") Long idJuego);
/**
* Calcula el promedio mensual de errores cometidos en los juegos por todos los residentes
* de una residencia específica, con opción de filtrar por dificultad.
*
* @param idResidencia ID de la residencia cuyos residentes se van a considerar.
* @param dificultad Dificultad de los juegos a filtrar (si es null, se consideran todas las dificultades).
* @param idJuego ID del juego a filtrar (puede ser null para ignorar el filtro).
* @return Lista de {@link MediaRegistroDTO} donde cada elemento representa un mes y el promedio de errores cometidos.
*/
@Query("""
SELECT new com.kevinolarte.resibenissa.dto.out.modulojuego.MediaRegistroDTO(TO_CHAR(r.fecha, 'YYYY-MM'), AVG(r.num), COUNT(r))
FROM RegistroJuego r
WHERE r.residente.residencia.id = :idResidencia
AND (:dificultad IS NULL OR r.dificultad = :dificultad)
AND (:idJuego IS NULL OR r.juego.id = :idJuego)
GROUP BY TO_CHAR(r.fecha, 'YYYY-MM')
ORDER BY TO_CHAR(r.fecha, 'YYYY-MM')
""")
List<MediaRegistroDTO> getMediaErroresResidenciaMensual(@Param("idResidencia") Long idResidencia,
@Param("dificultad") Dificultad dificultad,
@Param("idJuego") Long idJuego);
/**
* Calcula el promedio anual de errores cometidos en los juegos por todos los residentes
* de una residencia específica, con opción de filtrar por dificultad.
*
* @param idResidencia ID de la residencia cuyos residentes se van a considerar.
* @param dificultad Dificultad de los juegos a filtrar (si es null, se consideran todas las dificultades).
* @param idJuego ID del juego a filtrar (puede ser null para ignorar el filtro).
* @return Lista de {@link MediaRegistroDTO} donde cada elemento representa un año y el promedio de errores cometidos.
*/
@Query("""
SELECT new com.kevinolarte.resibenissa.dto.out.modulojuego.MediaRegistroDTO(CAST(EXTRACT(YEAR FROM r.fecha) AS string), AVG(r.num), COUNT(r))
FROM RegistroJuego r
WHERE r.residente.residencia.id = :idResidencia
AND (:dificultad IS NULL OR r.dificultad = :dificultad)
AND (:idJuego IS NULL OR r.juego.id = :idJuego)
GROUP BY EXTRACT(YEAR FROM r.fecha)
ORDER BY EXTRACT(YEAR FROM r.fecha)
""")
List<MediaRegistroDTO> getMediaErroresResidenciaAnual(@Param("idResidencia") Long idResidencia,
@Param("dificultad") Dificultad dificultad,
@Param("idJuego") Long idJuego);
/**
* Calcula el promedio diario de errores a nivel global,
* con posibilidad de filtrar por dificultad y juego.
*/
@Query("""
SELECT new com.kevinolarte.resibenissa.dto.out.modulojuego.MediaRegistroDTO(
TO_CHAR(r.fecha, 'YYYY-MM-DD'),
AVG(r.num),
COUNT(r))
FROM RegistroJuego r
WHERE (:dificultad IS NULL OR r.dificultad = :dificultad)
AND (:idJuego IS NULL OR r.juego.id = :idJuego)
GROUP BY TO_CHAR(r.fecha, 'YYYY-MM-DD')
ORDER BY TO_CHAR(r.fecha, 'YYYY-MM-DD')
""")
List<MediaRegistroDTO> getMediaErroresGlobalDiaria(@Param("dificultad") Dificultad dificultad,
@Param("idJuego") Long idJuego);
/**
* Calcula el promedio mensual de errores a nivel global,
* con posibilidad de filtrar por dificultad y juego.
*/
@Query("""
SELECT new com.kevinolarte.resibenissa.dto.out.modulojuego.MediaRegistroDTO(
TO_CHAR(r.fecha, 'YYYY-MM'),
AVG(r.num),
COUNT(r))
FROM RegistroJuego r
WHERE (:dificultad IS NULL OR r.dificultad = :dificultad)
AND (:idJuego IS NULL OR r.juego.id = :idJuego)
GROUP BY TO_CHAR(r.fecha, 'YYYY-MM')
ORDER BY TO_CHAR(r.fecha, 'YYYY-MM')
""")
List<MediaRegistroDTO> getMediaErroresGlobalMensual(@Param("dificultad") Dificultad dificultad,
@Param("idJuego") Long idJuego);
/**
* Calcula el promedio anual de errores a nivel global,
* con posibilidad de filtrar por dificultad y juego.
*/
@Query("""
SELECT new com.kevinolarte.resibenissa.dto.out.modulojuego.MediaRegistroDTO(
CAST(EXTRACT(YEAR FROM r.fecha) AS string),
AVG(r.num),
COUNT(r))
FROM RegistroJuego r
WHERE (:dificultad IS NULL OR r.dificultad = :dificultad)
AND (:idJuego IS NULL OR r.juego.id = :idJuego)
GROUP BY EXTRACT(YEAR FROM r.fecha)
ORDER BY EXTRACT(YEAR FROM r.fecha)
""")
List<MediaRegistroDTO> getMediaErroresGlobalAnual(@Param("dificultad") Dificultad dificultad,
@Param("idJuego") Long idJuego);
/**
* Calcula la media diaria de duración de juegos a nivel global,
* con posibilidad de filtrar por dificultad y juego.
*/
@Query("""
SELECT new com.kevinolarte.resibenissa.dto.out.modulojuego.MediaRegistroDTO(
TO_CHAR(r.fecha, 'YYYY-MM-DD'),
AVG(r.duracion),
COUNT(r))
FROM RegistroJuego r
WHERE (:dificultad IS NULL OR r.dificultad = :dificultad)
AND (:idJuego IS NULL OR r.juego.id = :idJuego)
GROUP BY TO_CHAR(r.fecha, 'YYYY-MM-DD')
ORDER BY TO_CHAR(r.fecha, 'YYYY-MM-DD')
""")
List<MediaRegistroDTO> getMediaDuracionGlobalDiaria(@Param("dificultad") Dificultad dificultad,
@Param("idJuego") Long idJuego);
/**
* Calcula la media mensual de duración de juegos a nivel global,
* con posibilidad de filtrar por dificultad y juego.
*/
@Query("""
SELECT new com.kevinolarte.resibenissa.dto.out.modulojuego.MediaRegistroDTO(
TO_CHAR(r.fecha, 'YYYY-MM'),
AVG(r.duracion),
COUNT(r))
FROM RegistroJuego r
WHERE (:dificultad IS NULL OR r.dificultad = :dificultad)
AND (:idJuego IS NULL OR r.juego.id = :idJuego)
GROUP BY TO_CHAR(r.fecha, 'YYYY-MM')
ORDER BY TO_CHAR(r.fecha, 'YYYY-MM')
""")
List<MediaRegistroDTO> getMediaDuracionGlobalMensual(@Param("dificultad") Dificultad dificultad,
@Param("idJuego") Long idJuego);
/**
* Calcula la media anual de duración de juegos a nivel global,
* con posibilidad de filtrar por dificultad y juego.
*/
@Query("""
SELECT new com.kevinolarte.resibenissa.dto.out.modulojuego.MediaRegistroDTO(
CAST(EXTRACT(YEAR FROM r.fecha) AS string),
AVG(r.duracion),
COUNT(r))
FROM RegistroJuego r
WHERE (:dificultad IS NULL OR r.dificultad = :dificultad)
AND (:idJuego IS NULL OR r.juego.id = :idJuego)
GROUP BY EXTRACT(YEAR FROM r.fecha)
ORDER BY EXTRACT(YEAR FROM r.fecha)
""")
List<MediaRegistroDTO> getMediaDuracionGlobalAnual(@Param("dificultad") Dificultad dificultad,
@Param("idJuego") Long idJuego);
/**
* Calcula el promedio diario de errores en los juegos jugados por todos los residentes para un juego específico.
*
* @param idJuego ID del juego a analizar.
* @param dificultad Dificultad a filtrar (puede ser null para incluir todas).
* @return Lista de {@link MediaRegistroDTO} agrupada por día.
*/
@Query("""
SELECT new com.kevinolarte.resibenissa.dto.out.modulojuego.MediaRegistroDTO(TO_CHAR(r.fecha, 'YYYY-MM-DD'), AVG(r.num), COUNT(r))
FROM RegistroJuego r
WHERE r.juego.id = :idJuego AND (:dificultad IS NULL OR r.dificultad = :dificultad)
GROUP BY TO_CHAR(r.fecha, 'YYYY-MM-DD')
ORDER BY TO_CHAR(r.fecha, 'YYYY-MM-DD')
""")
List<MediaRegistroDTO> getMediaErroresPorJuegoDiaria(@Param("idJuego") Long idJuego,
@Param("dificultad") Dificultad dificultad);
/**
* Calcula el promedio mensual de errores para un juego específico.
*/
@Query("""
SELECT new com.kevinolarte.resibenissa.dto.out.modulojuego.MediaRegistroDTO(TO_CHAR(r.fecha, 'YYYY-MM'), AVG(r.num), COUNT(r))
FROM RegistroJuego r
WHERE r.juego.id = :idJuego AND (:dificultad IS NULL OR r.dificultad = :dificultad)
GROUP BY TO_CHAR(r.fecha, 'YYYY-MM')
ORDER BY TO_CHAR(r.fecha, 'YYYY-MM')
""")
List<MediaRegistroDTO> getMediaErroresPorJuegoMensual(@Param("idJuego") Long idJuego,
@Param("dificultad") Dificultad dificultad);
/**
* Calcula el promedio anual de errores para un juego específico.
*/
@Query("""
SELECT new com.kevinolarte.resibenissa.dto.out.modulojuego.MediaRegistroDTO(CAST(EXTRACT(YEAR FROM r.fecha) AS string), AVG(r.num), COUNT(r))
FROM RegistroJuego r
WHERE r.juego.id = :idJuego AND (:dificultad IS NULL OR r.dificultad = :dificultad)
GROUP BY EXTRACT(YEAR FROM r.fecha)
ORDER BY EXTRACT(YEAR FROM r.fecha)
""")
List<MediaRegistroDTO> getMediaErroresPorJuegoAnual(@Param("idJuego") Long idJuego,
@Param("dificultad") Dificultad dificultad);
/**
* Calcula el promedio diario de duración de un juego específico.
*/
@Query("""
SELECT new com.kevinolarte.resibenissa.dto.out.modulojuego.MediaRegistroDTO(TO_CHAR(r.fecha, 'YYYY-MM-DD'), AVG(r.duracion),COUNT(r))
FROM RegistroJuego r
WHERE r.juego.id = :idJuego AND (:dificultad IS NULL OR r.dificultad = :dificultad)
GROUP BY TO_CHAR(r.fecha, 'YYYY-MM-DD')
ORDER BY TO_CHAR(r.fecha, 'YYYY-MM-DD')
""")
List<MediaRegistroDTO> getMediaDuracionPorJuegoDiaria(@Param("idJuego") Long idJuego,
@Param("dificultad") Dificultad dificultad);
/**
* Calcula el promedio mensual de duración de un juego específico.
*/
@Query("""
SELECT new com.kevinolarte.resibenissa.dto.out.modulojuego.MediaRegistroDTO(TO_CHAR(r.fecha, 'YYYY-MM'), AVG(r.duracion), COUNT(r))
FROM RegistroJuego r
WHERE r.juego.id = :idJuego AND (:dificultad IS NULL OR r.dificultad = :dificultad)
GROUP BY TO_CHAR(r.fecha, 'YYYY-MM')
ORDER BY TO_CHAR(r.fecha, 'YYYY-MM')
""")
List<MediaRegistroDTO> getMediaDuracionPorJuegoMensual(@Param("idJuego") Long idJuego,
@Param("dificultad") Dificultad dificultad);
/**
* Calcula el promedio anual de duración de un juego específico.
*/
@Query("""
SELECT new com.kevinolarte.resibenissa.dto.out.modulojuego.MediaRegistroDTO(CAST(EXTRACT(YEAR FROM r.fecha) AS string), AVG(r.duracion), COUNT(r))
FROM RegistroJuego r
WHERE r.juego.id = :idJuego AND (:dificultad IS NULL OR r.dificultad = :dificultad)
GROUP BY EXTRACT(YEAR FROM r.fecha)
ORDER BY EXTRACT(YEAR FROM r.fecha)
""")
List<MediaRegistroDTO> getMediaDuracionPorJuegoAnual(@Param("idJuego") Long idJuego,
@Param("dificultad") Dificultad dificultad);
/**
* Calcula el promedio diario de duración del juego especificado,
* considerando únicamente los registros pertenecientes a los residentes de una residencia específica.
*
* @param idJuego ID del juego cuyos registros se analizarán.
* @param idResidencia ID de la residencia cuyos residentes serán tenidos en cuenta.
* @param dificultad Nivel de dificultad a filtrar (opcional, puede ser null para incluir todas).
* @return Lista de {@link MediaRegistroDTO} donde cada entrada representa un día con el promedio de duración.
*/
@Query("""
SELECT new com.kevinolarte.resibenissa.dto.out.modulojuego.MediaRegistroDTO(TO_CHAR(r.fecha, 'YYYY-MM-DD'), AVG(r.duracion),COUNT(r))
FROM RegistroJuego r
WHERE r.juego.id = :idJuego AND r.residente.residencia.id = :idResidencia
AND (:dificultad IS NULL OR r.dificultad = :dificultad)
GROUP BY TO_CHAR(r.fecha, 'YYYY-MM-DD')
ORDER BY TO_CHAR(r.fecha, 'YYYY-MM-DD')
""")
List<MediaRegistroDTO> getMediaDuracionPorJuegoYResidenciaDiaria(@Param("idJuego") Long idJuego,
@Param("idResidencia") Long idResidencia,
@Param("dificultad") Dificultad dificultad);
/**
* Calcula el promedio mensual de duración del juego especificado,
* considerando únicamente los registros pertenecientes a los residentes de una residencia específica.
*
* @param idJuego ID del juego cuyos registros se analizarán.
* @param idResidencia ID de la residencia cuyos residentes serán tenidos en cuenta.
* @param dificultad Nivel de dificultad a filtrar (opcional, puede ser null para incluir todas).
* @return Lista de {@link MediaRegistroDTO} donde cada entrada representa un mes con el promedio de duración.
*/
@Query("""
SELECT new com.kevinolarte.resibenissa.dto.out.modulojuego.MediaRegistroDTO(TO_CHAR(r.fecha, 'YYYY-MM'), AVG(r.duracion), COUNT(r))
FROM RegistroJuego r
WHERE r.juego.id = :idJuego AND r.residente.residencia.id = :idResidencia
AND (:dificultad IS NULL OR r.dificultad = :dificultad)
GROUP BY TO_CHAR(r.fecha, 'YYYY-MM')
ORDER BY TO_CHAR(r.fecha, 'YYYY-MM')
""")
List<MediaRegistroDTO> getMediaDuracionPorJuegoYResidenciaMensual(@Param("idJuego") Long idJuego,
@Param("idResidencia") Long idResidencia,
@Param("dificultad") Dificultad dificultad);
/**
* Calcula el promedio anual de duración del juego especificado,
* considerando únicamente los registros pertenecientes a los residentes de una residencia específica.
*
* @param idJuego ID del juego cuyos registros se analizarán.
* @param idResidencia ID de la residencia cuyos residentes serán tenidos en cuenta.
* @param dificultad Nivel de dificultad a filtrar (opcional, puede ser null para incluir todas).
* @return Lista de {@link MediaRegistroDTO} donde cada entrada representa un año con el promedio de duración.
*/
@Query("""
SELECT new com.kevinolarte.resibenissa.dto.out.modulojuego.MediaRegistroDTO(CAST(EXTRACT(YEAR FROM r.fecha) AS string), AVG(r.duracion), COUNT(r))
FROM RegistroJuego r
WHERE r.juego.id = :idJuego AND r.residente.residencia.id = :idResidencia
AND (:dificultad IS NULL OR r.dificultad = :dificultad)
GROUP BY EXTRACT(YEAR FROM r.fecha)
ORDER BY EXTRACT(YEAR FROM r.fecha)
""")
List<MediaRegistroDTO> getMediaDuracionPorJuegoYResidenciaAnual(@Param("idJuego") Long idJuego,
@Param("idResidencia") Long idResidencia,
@Param("dificultad") Dificultad dificultad);
/**
* Calcula el promedio diario de errores del juego especificado,
* considerando únicamente los registros pertenecientes a los residentes de una residencia específica.
*
* @param idJuego ID del juego cuyos registros se analizarán.
* @param idResidencia ID de la residencia cuyos residentes serán tenidos en cuenta.
* @param dificultad Nivel de dificultad a filtrar (opcional, puede ser null para incluir todas).
* @return Lista de {@link MediaRegistroDTO} donde cada entrada representa un día con el promedio de errores.
*/
@Query("""
SELECT new com.kevinolarte.resibenissa.dto.out.modulojuego.MediaRegistroDTO(TO_CHAR(r.fecha, 'YYYY-MM-DD'), AVG(r.num), COUNT(r))
FROM RegistroJuego r
WHERE r.juego.id = :idJuego AND r.residente.residencia.id = :idResidencia
AND (:dificultad IS NULL OR r.dificultad = :dificultad)
GROUP BY TO_CHAR(r.fecha, 'YYYY-MM-DD')
ORDER BY TO_CHAR(r.fecha, 'YYYY-MM-DD')
""")
List<MediaRegistroDTO> getMediaErroresPorJuegoYResidenciaDiaria(@Param("idJuego") Long idJuego,
@Param("idResidencia") Long idResidencia,
@Param("dificultad") Dificultad dificultad);
/**
* Calcula el promedio mensual de errores del juego especificado,
* considerando únicamente los registros pertenecientes a los residentes de una residencia específica.
*
* @param idJuego ID del juego cuyos registros se analizarán.
* @param idResidencia ID de la residencia cuyos residentes serán tenidos en cuenta.
* @param dificultad Nivel de dificultad a filtrar (opcional, puede ser null para incluir todas).
* @return Lista de {@link MediaRegistroDTO} donde cada entrada representa un mes con el promedio de errores.
*/
@Query("""
SELECT new com.kevinolarte.resibenissa.dto.out.modulojuego.MediaRegistroDTO(TO_CHAR(r.fecha, 'YYYY-MM'), AVG(r.num), COUNT(r))
FROM RegistroJuego r
WHERE r.juego.id = :idJuego AND r.residente.residencia.id = :idResidencia
AND (:dificultad IS NULL OR r.dificultad = :dificultad)
GROUP BY TO_CHAR(r.fecha, 'YYYY-MM')
ORDER BY TO_CHAR(r.fecha, 'YYYY-MM')
""")
List<MediaRegistroDTO> getMediaErroresPorJuegoYResidenciaMensual(@Param("idJuego") Long idJuego,
@Param("idResidencia") Long idResidencia,
@Param("dificultad") Dificultad dificultad);
/**
* Calcula el promedio anual de errores del juego especificado,
* considerando únicamente los registros pertenecientes a los residentes de una residencia específica.
*
* @param idJuego ID del juego cuyos registros se analizarán.
* @param idResidencia ID de la residencia cuyos residentes serán tenidos en cuenta.
* @param dificultad Nivel de dificultad a filtrar (opcional, puede ser null para incluir todas).
* @return Lista de {@link MediaRegistroDTO} donde cada entrada representa un año con el promedio de errores.
*/
@Query("""
SELECT new com.kevinolarte.resibenissa.dto.out.modulojuego.MediaRegistroDTO(CAST(EXTRACT(YEAR FROM r.fecha) AS string), AVG(r.num), COUNT(r))
FROM RegistroJuego r
WHERE r.juego.id = :idJuego AND r.residente.residencia.id = :idResidencia
AND (:dificultad IS NULL OR r.dificultad = :dificultad)
GROUP BY EXTRACT(YEAR FROM r.fecha)
ORDER BY EXTRACT(YEAR FROM r.fecha)
""")
List<MediaRegistroDTO> getMediaErroresPorJuegoYResidenciaAnual(@Param("idJuego") Long idJuego,
@Param("idResidencia") Long idResidencia,
@Param("dificultad") Dificultad dificultad);
}

View File

@ -0,0 +1,257 @@
package com.kevinolarte.resibenissa.services;
import com.kevinolarte.resibenissa.config.Conf;
import com.kevinolarte.resibenissa.dto.in.auth.LoginUserDto;
import com.kevinolarte.resibenissa.dto.in.auth.RegisterUserDto;
import com.kevinolarte.resibenissa.dto.in.auth.VerifyUserDto;
import com.kevinolarte.resibenissa.dto.out.UserResponseDto;
import com.kevinolarte.resibenissa.exceptions.ApiErrorCode;
import com.kevinolarte.resibenissa.exceptions.ResiException;
import com.kevinolarte.resibenissa.models.Residencia;
import com.kevinolarte.resibenissa.models.User;
import com.kevinolarte.resibenissa.repositories.UserRepository;
import jakarta.mail.MessagingException;
import lombok.AllArgsConstructor;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.Random;
/**
* Servicio encargado de gestionar la autenticación y verificación de usuarios.
* <p>
* Incluye el registro de nuevos usuarios, generación y envío de códigos de verificación por correo electrónico,
* autenticación mediante email y contraseña, y validación del código de activación.
* </p>
*
* Este servicio utiliza:
* <ul>
* <li>{@link PasswordEncoder} para cifrar contraseñas.</li>
* <li>{@link AuthenticationManager} para autenticación en Spring Security.</li>
* <li>{@link EmailService} para el envío de correos electrónicos.</li>
* <li>{@link ResidenciaService} para validar residencias al registrar usuarios.</li>
* </ul>
*
* @author Kevin Olarte
*/
@Service
@AllArgsConstructor
public class AuthenticationService {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
private final AuthenticationManager authenticationManager;
private final EmailService emailService;
private final ResidenciaService residenciaService;
/**
* Registra un nuevo usuario si no existe previamente.
* <p>
* Genera un código de verificación y lo envía por correo electrónico.
* El usuario se crea con estado desactivado hasta completar la verificación.
* </p>
*
* @param input DTO con los datos del usuario a registrar.
* @return DTO con los datos del usuario creado.
* @throws ResiException si el email es inválido, ya existe, o la residencia no es válida.
*/
public UserResponseDto singUp(RegisterUserDto input){
if (input.getEmail() == null || input.getEmail().trim().isEmpty() || input.getPassword() == null || input.getPassword().trim().isEmpty()
|| input.getIdResidencia() == null || input.getNombre() == null || input.getNombre().trim().isEmpty() || input.getApellido() == null || input.getApellido().trim().isEmpty()){
throw new ResiException(ApiErrorCode.CAMPOS_OBLIGATORIOS);
}
input.setEmail(input.getEmail().trim().toLowerCase());
if (!EmailService.isEmailValid(input.getEmail())){
throw new ResiException(ApiErrorCode.CORREO_INVALIDO);
}
//Miramos si ese usuario y residencia existen
User userTest = userRepository.findByEmail(input.getEmail());
Residencia residenciaTest = residenciaService.getResidencia(input.getIdResidencia());
if(userTest != null){
if (userTest.isBaja())
throw new ResiException(ApiErrorCode.USUARIO_BAJA);
throw new ResiException(ApiErrorCode.USER_EXIST);
}
if (residenciaTest.isBaja())
throw new ResiException(ApiErrorCode.RESIDENCIA_BAJA);
User user = new User(input.getNombre(), input.getApellido(),input.getEmail(), passwordEncoder.encode(input.getPassword()));
user.setVerificationCode(generateVerificationCode());
System.out.println(user.getVerificationCode());
user.setVerificationExpiration(LocalDateTime.now().plusMinutes(15));
user.setEnabled(false);
sendVerificationEmail(user);
user.setResidencia(residenciaTest);
user.setFotoPerfil("/uploads/" + Conf.imageDefault);
User savedUser = userRepository.save(user);
return new UserResponseDto(savedUser);
}
/**
* Autentica un usuario existente mediante email y contraseña.
*
* @param input DTO con credenciales de acceso.
* @return El objeto {@link User} autenticado.
* @throws ResiException si el usuario no existe o no está activado.
*/
public User authenticate(LoginUserDto input){
if (input.getEmail() == null || input.getEmail().trim().isEmpty() || input.getPassword() == null || input.getPassword().trim().isEmpty()){
throw new ResiException(ApiErrorCode.CAMPOS_OBLIGATORIOS);
}
input.setEmail(input.getEmail().trim().toLowerCase());
if (!EmailService.isEmailValid(input.getEmail())){
throw new ResiException(ApiErrorCode.CORREO_INVALIDO);
}
//Ver si ese usuario existe o no
User user = userRepository.findByEmail(input.getEmail());
if (user == null){
throw new ResiException(ApiErrorCode.USUARIO_INVALIDO);
}
//Ver si esta activado
if(!user.isEnabled()){
throw new ResiException(ApiErrorCode.USER_NO_ACTIVADO);
}
//Ver si esta de baja
if(user.isBaja()){
throw new ResiException(ApiErrorCode.USUARIO_BAJA);
}
//Autehnticamos
try{
authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
input.getEmail(),
input.getPassword()
)
);
}catch (AuthenticationException e){
throw new ResiException(ApiErrorCode.CONTRASENA_INCORRECTA);
}
return user;
}
/**
* Verifica el código enviado por correo y activa la cuenta del usuario.
*
* @param input DTO que contiene el email y el código de verificación.
* @throws ResiException si el código está expirado, es inválido, o el usuario no existe.
*/
public void verifyUser(VerifyUserDto input){
if (input.getEmail() == null || input.getEmail().trim().isEmpty() || input.getVerificationCode() == null || input.getVerificationCode().trim().isEmpty()){
throw new ResiException(ApiErrorCode.CAMPOS_OBLIGATORIOS);
}
User user = userRepository.findByEmail(input.getEmail());
if(user != null){
if (user.isBaja()) {
throw new ResiException(ApiErrorCode.USUARIO_BAJA);
}
if (user.isEnabled()) {
throw new ResiException(ApiErrorCode.USER_YA_ACTIVADO);
}
if(user.getVerificationExpiration().isBefore(LocalDateTime.now())){
throw new ResiException(ApiErrorCode.CODIGO_EXPIRADO);
}
if (user.getVerificationCode().equals(input.getVerificationCode())){
user.setEnabled(true);
user.setVerificationCode(null);
user.setVerificationExpiration(null);
userRepository.save(user); //CUIDADO!!!
}else{
throw new ResiException(ApiErrorCode.CODIGO_INVALIDO);
}
}
else{
throw new ResiException(ApiErrorCode.USUARIO_INVALIDO);
}
}
/**
* Reenvía un nuevo código de verificación por correo si el usuario aún no está activado.
*
* @param email Dirección de correo del usuario.
* @throws ResiException si el usuario no existe o ya está activado.
*/
public void resendVerificationCode(String email){
if (email == null || email.trim().isEmpty()){
throw new ResiException(ApiErrorCode.CAMPOS_OBLIGATORIOS);
}
email = email.trim().toLowerCase();
if (!EmailService.isEmailValid(email)){
throw new ResiException(ApiErrorCode.CORREO_INVALIDO);
}
User user = userRepository.findByEmail(email);
if(user != null){
if (user.isBaja()) {
throw new ResiException(ApiErrorCode.USUARIO_BAJA);
}
if (user.isEnabled()){
throw new ResiException(ApiErrorCode.USER_YA_ACTIVADO);
}
user.setVerificationCode(generateVerificationCode());
user.setVerificationExpiration(LocalDateTime.now().plusHours(1));
sendVerificationEmail(user);
userRepository.save(user);
}
else{
throw new ResiException(ApiErrorCode.USUARIO_INVALIDO);
}
}
/**
* Genera un código de verificación de 6 dígitos.
*
* @return Código de verificación como String.
*/
public String generateVerificationCode(){
Random random = new Random();
int code = random.nextInt(900000) + 100000;
return String.valueOf(code);
}
/**
* Envía un correo con el código de verificación al usuario.
*
* @param user Usuario al que se le enviará el correo.
* @throws ResiException si ocurre un error al enviar el correo.
*/
public void sendVerificationEmail(User user){
String subject = "Account verification";
String verificationCode = user.getVerificationCode();
String htmlMessage = "<html>"
+ "<body style=\"font-family: Arial, sans-serif;\">"
+ "<div style=\"background-color: #f5f5f5; padding: 20px;\">"
+ "<h2 style=\"color: #333;\">Welcome to our app!</h2>"
+ "<p style=\"font-size: 16px;\">Please enter the verification code below to continue:</p>"
+ "<div style=\"background-color: #fff; padding: 20px; border-radius: 5px; box-shadow: 0 0 10px rgba(0,0,0,0.1);\">"
+ "<h3 style=\"color: #333;\">Verification Code:</h3>"
+ "<p style=\"font-size: 18px; font-weight: bold; color: #007bff;\">" + verificationCode + "</p>"
+ "</div>"
+ "</div>"
+ "</body>"
+ "</html>";
try{
emailService.sendEmail(user.getEmail(), subject, htmlMessage);
}catch (MessagingException e){
throw new ResiException(ApiErrorCode.ERROR_MAIL_SENDER);
}
}
}

View File

@ -0,0 +1,136 @@
package com.kevinolarte.resibenissa.services;
import com.kevinolarte.resibenissa.dto.out.moduloOrgSalida.ParticipanteResponseDto;
import com.kevinolarte.resibenissa.exceptions.ApiErrorCode;
import com.kevinolarte.resibenissa.exceptions.ResiException;
import com.kevinolarte.resibenissa.models.Residente;
import com.kevinolarte.resibenissa.repositories.ResidenteRepository;
import jakarta.mail.MessagingException;
import jakarta.mail.internet.MimeMessage;
import lombok.AllArgsConstructor;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.stereotype.Service;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Duration;
import java.time.LocalDate;
import java.util.HashMap;
import java.util.Map;
/**
* Servicio encargado del envío de correos electrónicos dentro de la aplicación.
* <p>
* Este servicio utiliza {@link JavaMailSender} para construir y enviar correos
* en formato HTML. También incluye una utilidad para validar la estructura de
* direcciones de correo electrónico.
* </p>
*
* Requiere configuración previa en {@code application.properties} o {@code application.yml}
* con los datos del servidor SMTP (por ejemplo, Gmail).
*
* @author Kevin Olarte
*/
@Service
@AllArgsConstructor
public class EmailService {
private final ResidenteRepository residenteRepository;
private JavaMailSender mailSender;
private JwtService jwtService;
/**
* Verifica si una dirección de correo electrónico tiene un formato válido.
* <p>
* La validación se realiza mediante una expresión regular que comprueba la estructura estándar de emails.
* </p>
*
* @param email Dirección de correo electrónico a validar.
* @return {@code true} si el formato del email es válido, {@code false} en caso contrario.
*/
public static boolean isEmailValid(String email) {
String emailRegex = "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$";
return email != null && email.matches(emailRegex);
}
/**
* Envía un correo electrónico de verificación en formato HTML.
* <p>
* Construye el mensaje a partir de los parámetros recibidos y lo envía utilizando el {@link JavaMailSender}.
* </p>
*
* @param to Dirección de correo del destinatario.
* @param subject Asunto del correo.
* @param text Cuerpo del mensaje en formato HTML.
* @throws MessagingException si ocurre un error al crear o enviar el mensaje.
*/
public void sendEmail(String to, String subject, String text) throws MessagingException {
MimeMessage message = mailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(message, true);
helper.setTo(to);
helper.setSubject(subject);
helper.setText(text, true);
mailSender.send(message);
}
/**
* Envía una notificación por correo electrónico a los familiares de un participante
* para solicitar permiso para una excursión.
* <p>
* Genera un token JWT con información del participante y construye URLs para permitir o rechazar la solicitud.
* Reemplaza los placeholders en una plantilla HTML y envía el correo a los familiares.
* </p>
*
* @param participanteDto DTO que contiene la información del participante.
*/
public void sendNotificationParticipante(ParticipanteResponseDto participanteDto) {
try {
Map<String, Object> claims = new HashMap<>();
claims.put("idParticipante", participanteDto.getId());
claims.put("idEvento", participanteDto.getIdEvento());
claims.put("idResidencia", participanteDto.getIdResidencia());
String token = jwtService.generateTokenConExpiracionCustomClaims(claims, Duration.ofMinutes(30));
System.out.println("Token: " + token);
// Construir URLs
String urlPermitir = "http://localhost:8080/public/allowParticipante?token=" + token;
String urlRechazar = "http://localhost:8080/public/denyParticipante?token=" + token;
// Leer la plantilla HTML desde resources/templates
Path htmlPath = Paths.get("src/main/resources/templates/permiso-excursion.html");
String html = Files.readString(htmlPath);
Residente residente = residenteRepository.findById(participanteDto.getIdResidente())
.orElseThrow(() -> new ResiException(ApiErrorCode.RESIDENTE_INVALIDO));
// Reemplazar los placeholders
html = html.replace("{{nombreFamiliar}}", "Familiar")
.replace("{{nombreResidente}}", "Residente " + residente.getNombre() + " " + residente.getApellido())
.replace("{{nombreExcursion}}", "Excursión especial")
.replace("{{fecha}}", LocalDate.now().plusDays(7).toString()) // ejemplo de fecha
.replace("{{urlPermitir}}", urlPermitir)
.replace("{{urlRechazar}}", urlRechazar);
sendEmail(participanteDto.getFamiliar1(), "Permiso para excursión", html);
if (participanteDto.getFamiliar2() != null) {
sendEmail(participanteDto.getFamiliar2(), "Permiso para excursión", html);
}
} catch (Exception e) {
throw new ResiException(ApiErrorCode.ERROR_MAIL_SENDER);
}
}
}

View File

@ -0,0 +1,211 @@
package com.kevinolarte.resibenissa.services;
import com.kevinolarte.resibenissa.models.User;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Service;
import java.security.Key;
import java.time.Duration;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
/**
* Servicio encargado de la generación, validación y extracción de información de tokens JWT.
* <p>
* Utiliza la clave secreta configurada en las propiedades de la aplicación y respeta el tiempo
* de expiración establecido para los tokens. Este servicio también añade el email como un claim
* adicional en los tokens generados.
* </p>
*
* <p>
* El campo {@code sub} del token corresponde al {@code username}, pero en esta aplicación se trabaja principalmente con {@code email}.
* </p>
*
* @author Kevin Olarte
*/
@Service
public class JwtService {
@Value("${security.jwt.secret-key}")
private String secretKey;
@Value("${security.jwt.expiration-time}")
private long jwtExpiration;
/**
* Extrae el nombre de usuario (username/subject) del token JWT.
*
* @param token Token JWT.
* @return Nombre de usuario extraído del token.
*/
public String extractUsername(String token) {
return extractClaim(token, Claims::getSubject);
}
/**
* Extrae un claim personalizado del token, utilizando una función de resolución.
*
* @param token Token JWT.
* @param claimsResolver Función que define qué claim se desea extraer.
* @return Valor del claim.
* @param <T> Tipo del claim.
*/
public <T> T extractClaim(String token, Function<Claims, T> claimsResolver){
final Claims claims = extractAllClaims(token);
return claimsResolver.apply(claims);
}
/**
* Genera un token JWT para un usuario, sin claims adicionales.
*
* @param userDetails Usuario autenticado.
* @return Token JWT generado.
*/
public String generateToken(UserDetails userDetails) {
return generateToken(new HashMap<>(), userDetails);
}
/**
* Genera un token JWT con claims personalizados y el correo del usuario.
*
* @param extraClaims Claims adicionales a incluir en el token.
* @param userDetails Usuario autenticado.
* @return Token JWT generado.
*/
public String generateToken(Map<String, Object> extraClaims, UserDetails userDetails) {
if (userDetails instanceof User user) {
extraClaims.put("email", user.getEmail());
}
return buildToken(extraClaims, userDetails, jwtExpiration);
}
/**
* Genera un token JWT con claims personalizados y una duración específica.
*
* @param extraClaims Claims adicionales a incluir en el token.
* @param duration Duración del token.
* @return Token JWT generado.
*/
public String generateTokenConExpiracionCustomClaims(Map<String, Object> extraClaims, Duration duration) {
return Jwts.builder()
.setClaims(extraClaims)
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + duration.toMillis()))
.signWith(getSingKey(), SignatureAlgorithm.HS256)
.compact();
}
/**
* Devuelve el tiempo de expiración configurado para los tokens (en milisegundos).
*
* @return Tiempo de expiración en milisegundos.
*/
public long getExpirationTime() {
return this.jwtExpiration;
}
/**
* Construye el token JWT firmándolo con la clave secreta.
*
* @param extraClaims Claims adicionales.
* @param userDetails Datos del usuario.
* @param expirationTime Tiempo de expiración en milisegundos.
* @return Token JWT generado.
*/
private String buildToken(
Map<String, Object> extraClaims,
UserDetails userDetails,
long expirationTime) {
return Jwts
.builder()
.setClaims(extraClaims)
.setSubject(userDetails.getUsername())
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + expirationTime))
.signWith(getSingKey(), SignatureAlgorithm.HS256)
.compact();
}
/**
* Extrae el claim personalizado {@code email} del token.
*
* @param token Token JWT.
* @return Email del usuario.
*/
public String extrtractEmail(String token) {
//Del extra claim que pusimos.
return extractAllClaims(token).get("email", String.class);
}
/**
* Verifica si un token es válido para un usuario dado.
*
* @param token Token JWT.
* @param userDetails Usuario autenticado.
* @return {@code true} si el token es válido y no ha expirado.
*/
public boolean isTokenValid(String token, UserDetails userDetails) {
final String username = extractUsername(token);
return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
}
/**
* Verifica si el token ya ha expirado.
*
* @param token Token JWT.
* @return {@code true} si el token está expirado.
*/
private boolean isTokenExpired(String token) {
return extractExpiration(token).before(new Date());
}
/**
* Extrae la fecha de expiración del token.
*
* @param token Token JWT.
* @return Fecha de expiración.
*/
private Date extractExpiration(String token) {
return extractClaim(token, Claims::getExpiration);
}
/**
* Extrae todos los claims del token.
*
* @param token Token JWT.
* @return Todos los claims contenidos en el token.
*/
private Claims extractAllClaims(String token) {
return Jwts
.parserBuilder()
.setSigningKey(getSingKey())
.build()
.parseClaimsJws(token)
.getBody();
}
/**
* Obtiene la clave secreta firmada a partir del string base64 en la configuración.
*
* @return Clave HMAC SHA válida para firmar/verificar JWT.
*/
private Key getSingKey() {
byte[] keyBytes = Decoders.BASE64.decode(secretKey);
return Keys.hmacShaKeyFor(keyBytes);
}
}

View File

@ -0,0 +1,47 @@
package com.kevinolarte.resibenissa.services;
import com.kevinolarte.resibenissa.LogContext;
import com.kevinolarte.resibenissa.enums.CategoriaLog;
import com.kevinolarte.resibenissa.models.Logger;
import com.kevinolarte.resibenissa.models.User;
import com.kevinolarte.resibenissa.repositories.LoggerRepository;
import lombok.AllArgsConstructor;
import lombok.extern.java.Log;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
@Service
public class LoggerService {
private final LoggerRepository loggerRepository;
public LoggerService(LoggerRepository loggerRepository) {
this.loggerRepository = loggerRepository;
}
public void registrarLog(String endpoint, String metodo, String descripcion) {
Logger log = new Logger(
endpoint,
metodo,
descripcion
);
Logger saved = loggerRepository.save(log);
LogContext.setCurrentLogId(saved.getId());
}
public void registrarLogError(String descripcion) {
Logger log = new Logger(null, null, descripcion);
Long logId = LogContext.getCurrentLogId();
if (logId != null) {
loggerRepository.findById(logId).ifPresent(log::setPadre);
}
loggerRepository.save(log);
}
}

View File

@ -0,0 +1,196 @@
package com.kevinolarte.resibenissa.services;
import com.kevinolarte.resibenissa.dto.in.ResidenciaDto;
import com.kevinolarte.resibenissa.dto.out.ResidenciaPublicResponseDto;
import com.kevinolarte.resibenissa.dto.out.ResidenciaResponseDto;
import com.kevinolarte.resibenissa.exceptions.ApiErrorCode;
import com.kevinolarte.resibenissa.exceptions.ResiException;
import com.kevinolarte.resibenissa.models.Residencia;
import com.kevinolarte.resibenissa.repositories.ResidenciaRepository;
import com.kevinolarte.resibenissa.repositories.ResidenteRepository;
import com.kevinolarte.resibenissa.repositories.moduloOrgSalida.EventoSalidaRepository;
import com.kevinolarte.resibenissa.repositories.moduloOrgSalida.ParticipanteRepository;
import lombok.AllArgsConstructor;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
/**
* Servicio que gestiona la lógica de negocio relacionada con entidades {@link Residencia}.
* <p>
* Permite crear, obtener, buscar por ID y eliminar residencias.
* </p>
*
* @author : Kevin Olarte
*/
@Service
@AllArgsConstructor
public class ResidenciaService {
private final ResidenciaRepository residenciaRepository;
private final BCryptPasswordEncoder passwordEncoder;
private final ResidenteRepository residenteRepository;
private final ParticipanteRepository participanteRepository;
private final EventoSalidaRepository eventoSalidaRepository;
/**
* Crea una nueva residencia en el sistema a partir de los datos recibidos.
* <p>
* Valida que el nombre y correo no estén vacíos, que el correo tenga un formato válido,
* y que tanto el nombre como el correo no estén ya registrados.
* </p>
*
* @param input DTO que contiene el nombre y correo de la residencia.
* @return {@link ResidenciaResponseDto} de la residencia creada.
* @throws ResiException en caso de errores de validación o duplicados.
*/
public ResidenciaResponseDto add(ResidenciaDto input) throws RuntimeException{
if (input.getNombre() == null || input.getEmail() == null
|| input.getNombre().trim().isEmpty() || input.getEmail().trim().isEmpty()){
throw new ResiException(ApiErrorCode.CAMPOS_OBLIGATORIOS);
}
// Validar formato del correo electrónico
input.setEmail(input.getEmail().toLowerCase().trim());
if (!EmailService.isEmailValid(input.getEmail())){
throw new ResiException(ApiErrorCode.CORREO_INVALIDO);
}
// Comprobar si ya existe una residencia con ese correo o nombre
Optional<Residencia> residenciaTmp = residenciaRepository.findByEmail(input.getEmail());
Optional<Residencia> residenciaTmp2 = residenciaRepository.findByNombre(input.getNombre());
if(residenciaTmp.isPresent()){
throw new ResiException(ApiErrorCode.CORREO_DUPLICADO);
}
if(residenciaTmp2.isPresent()){
throw new ResiException(ApiErrorCode.NOMBRE_DUPLICADO);
}
Residencia residencia = new Residencia(input.getNombre(), input.getEmail());
return new ResidenciaResponseDto(residenciaRepository.save(residencia));
}
/**
* Obtiene una residencia a partir de su ID validando su existencia.
*
* @param idResidencia ID de la residencia a recuperar.
* @return {@link ResidenciaResponseDto} de la residencia encontrada.
* @throws ResiException si el ID es nulo o no existe una residencia con ese ID.
*/
public ResidenciaResponseDto get(Long idResidencia) {
Residencia resi;
// Comprobar si el ID de residencia es nulo
if (idResidencia == null)
throw new ResiException(ApiErrorCode.CAMPOS_OBLIGATORIOS);
else
resi = residenciaRepository.findById(idResidencia)
.orElseThrow(() -> new ResiException(ApiErrorCode.RESIDENCIA_INVALIDO));
return new ResidenciaResponseDto(resi);
}
/**
* Obtiene una lista de todas las residencias en el sistema. con solo el nombre , correo e id.
* @return Lista de residencias públicas.
*/
public List<ResidenciaPublicResponseDto> getAll() {
return residenciaRepository.findAll()
.stream().map(ResidenciaPublicResponseDto::new)
.collect(Collectors.toList());
}
/**
* Obtiene una lista de todas las residencias que están marcadas como inactivas (baja).
* @return Lista de residencias inactivas.
*/
public List<ResidenciaPublicResponseDto> getAllBaja() {
return residenciaRepository.findByBajaTrue().stream().map(ResidenciaPublicResponseDto::new).toList();
}
/**
* Elimina una residencia a partir de su ID.
* <p>
* Si no se encuentra la residencia, lanza una excepción.
* </p>
*
* @param idResidencia ID de la residencia a eliminar.
* @throws ResiException si no se encuentra la residencia especificada.
*/
public void deleteFisico(Long idResidencia) {
Residencia residenciaTmp = residenciaRepository.findById(idResidencia).orElse(null);
if(residenciaTmp == null){
throw new ResiException(ApiErrorCode.RESIDENCIA_INVALIDO);
}
residenciaRepository.delete(residenciaTmp);
}
/**
* Elimina una residencia de forma lógica, marcando su estado como inactivo.
* @param id ID de la residencia a eliminar.
* @throws ResiException si no se encuentra la residencia.
*/
public void deleteLogico(Long id) {
Residencia residencia = residenciaRepository.findById(id).orElse(null);
if (residencia == null) {
throw new ResiException(ApiErrorCode.RESIDENCIA_INVALIDO);
}
residencia.setBaja(true);
residencia.setFechaBaja(LocalDateTime.now());
residencia.setEmail(passwordEncoder.encode(residencia.getEmail()));
// Cambiar el estado de los residentes a inactivos
residencia.getResidentes().forEach(residente -> {
ResidenteService.darBajaUser(residente, passwordEncoder);
residenteRepository.save(residente);
});
// Cambiar el estado de los usuarios a inactivos
residencia.getUsuarios().forEach(usuario -> {
usuario.setBaja(true);
usuario.setFechaBaja(LocalDateTime.now());
usuario.setPassword(passwordEncoder.encode(usuario.getPassword()));
});
participanteRepository.deleteAllByResidenciaId(residencia.getId());
eventoSalidaRepository.deleteAllByResidenciaId(residencia.getId());
residenciaRepository.save(residencia);
}
/**
* Busca una residencia por su ID.
*
* @param id ID de la residencia.
* @return {@link Residencia} encontrada.
* @throws ResiException si no se encuentra la residencia.
*/
public Residencia getResidencia(Long id){
return residenciaRepository.findById(id).orElseThrow(() -> new ResiException(ApiErrorCode.RESIDENCIA_INVALIDO));
}
}

View File

@ -0,0 +1,440 @@
package com.kevinolarte.resibenissa.services;
import com.kevinolarte.resibenissa.config.Conf;
import com.kevinolarte.resibenissa.dto.in.ResidenteDto;
import com.kevinolarte.resibenissa.dto.in.moduloReporting.EmailRequestDto;
import com.kevinolarte.resibenissa.dto.out.ResidenteResponseDto;
import com.kevinolarte.resibenissa.enums.Filtrado.ResidenteFiltrado;
import com.kevinolarte.resibenissa.exceptions.ApiErrorCode;
import com.kevinolarte.resibenissa.exceptions.ResiException;
import com.kevinolarte.resibenissa.models.Residencia;
import com.kevinolarte.resibenissa.models.Residente;
import com.kevinolarte.resibenissa.models.moduloWallet.Wallet;
import com.kevinolarte.resibenissa.repositories.ResidenteRepository;
import com.kevinolarte.resibenissa.repositories.moduloOrgSalida.ParticipanteRepository;
import com.kevinolarte.resibenissa.repositories.moduloWallet.WalletRepository;
import com.kevinolarte.resibenissa.services.moduloWallet.WalletService;
import com.kevinolarte.resibenissa.specifications.ResidenteSpecification;
import lombok.AllArgsConstructor;
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
/**
* Servicio encargado de gestionar la lógica de negocio relacionada con los residentes.
* <p>
* Permite registrar, consultar, actualizar y eliminar residentes asociados a residencias.
* También permite aplicar filtros básicos sobre los residentes.
* </p>
*
* @author : Kevin Olarte
*/
@Service
@AllArgsConstructor
public class ResidenteService {
private final ResidenteRepository residenteRepository;
private final ResidenciaService residenciaService;
private final PasswordEncoder passwordEncoder;
private final ParticipanteRepository participanteRepository;
private final EmailService emailService;
private final WalletRepository walletRepository;
/**
* Registra un nuevo residente asociado a una residencia.
*
* @param idResidencia ID de la residencia.
* @param input DTO con los datos del residente.
* @return DTO del residente creado.
* @throws ResiException en caso de datos inválidos o duplicidad de documento.
*/
public ResidenteResponseDto add(Long idResidencia, ResidenteDto input) throws ResiException {
if (input.getNombre() == null || input.getApellido() == null || input.getFechaNacimiento() == null || input.getDocumentoIdentidad() == null ||
input.getNombre().trim().isEmpty() || input.getApellido().trim().isEmpty() || input.getDocumentoIdentidad().trim().isEmpty() || idResidencia == null ||
input.getFamiliar1() == null || input.getFamiliar1().trim().isEmpty()) {
throw new ResiException(ApiErrorCode.CAMPOS_OBLIGATORIOS);
}
// Validar el formato del documento de identidad
input.setDocumentoIdentidad(input.getDocumentoIdentidad().trim().toUpperCase());
if (input.getDocumentoIdentidad().length() != 8) {
throw new ResiException(ApiErrorCode.DOCUMENTO_INVALIDO);
}
//Validar correo familiar 1
if (!EmailService.isEmailValid(input.getFamiliar1().toLowerCase().trim())){
throw new ResiException(ApiErrorCode.CORREO_INVALIDO);
}
//Validar correo familiar 2
if (input.getFamiliar2() != null && !input.getFamiliar2().trim().isEmpty())
if (!EmailService.isEmailValid(input.getFamiliar2().toLowerCase().trim()))
throw new ResiException(ApiErrorCode.CORREO_INVALIDO);
// Validar que la fecha de nacimiento no sea futura
if (input.getFechaNacimiento().isAfter(LocalDate.now())) {
throw new ResiException(ApiErrorCode.FECHA_INVALIDO);
}
// Validar que la residencia existe
Residencia residencia = residenciaService.getResidencia(idResidencia);
// Validar que no existe otro residente con el mismo documento de identidad en cualquier residencia
Residente residenteDup = residenteRepository.findByDocuemntoIdentidad(input.getDocumentoIdentidad());
if (residenteDup != null) {
throw new ResiException(ApiErrorCode.DOCUMENTO_DUPLICADO);
}
// Crear el nuevo residente
Residente residente = new Residente(input.getNombre(), input.getApellido(), input.getFechaNacimiento(), input.getDocumentoIdentidad(), input.getFamiliar1(), input.getFamiliar2());
residente.setResidencia(residencia);
Residente residenteSaved = residenteRepository.save(residente);
Wallet wallet = new Wallet();
wallet.setResidente(residenteSaved);
wallet = walletRepository.save(wallet);
System.out.println(wallet);
residenteSaved.setWallet(wallet);
return new ResidenteResponseDto(residenteRepository.save(residenteSaved));
}
/**
* Obtiene un residente asegurando su pertenencia a una residencia.
*
* @param idResidencia ID de la residencia.
* @param idResidente ID del residente.
* @return DTO del residente encontrado.
* @throws ResiException si el residente no existe o no pertenece a la residencia.
*/
public ResidenteResponseDto get(Long idResidencia, Long idResidente) {
Residente residenteTmp = getResidente(idResidencia, idResidente);
return new ResidenteResponseDto(residenteTmp);
}
/**
* Obtiene todos los residentes de todas residencias con filtros opcionales.
* @param fechaNacimiento Fecha exacta de nacimiento (opcional).
* @param minFNac Fecha mínima de nacimiento (opcional).
* @param maxFNac Fecha máxima de nacimiento (opcional).
* @param maxAge Edad máxima (opcional).
* @param minAge Edad mínima (opcional).
* @param idJuego ID de juego asociado (opcional).
* @param idEvento ID de evento asociado (opcional).
* @return Lista de residentes filtrados.
* @throws ResiException si la residencia no existe.
*/
public List<ResidenteResponseDto> getAll(LocalDate fechaNacimiento, LocalDate minFNac, LocalDate maxFNac, Integer maxAge, Integer minAge, Long idJuego, Long idEvento, ResidenteFiltrado filtrado) {
Specification<Residente> spec = ResidenteSpecification.withFilters(null, fechaNacimiento, minFNac, maxFNac, maxAge, minAge, idJuego, idEvento);
Sort sort = (filtrado != null) ? filtrado.toSort() : Sort.by(Sort.Direction.ASC, "apellido");
List<Residente> residentes = residenteRepository.findAll(spec, sort);
return residentes.stream().map(ResidenteResponseDto::new).collect(Collectors.toList());
}
/**
* Obtiene todos los residentes de una residencia con filtros opcionales.
*
* @param idResidencia ID de la residencia.
* @param fechaNacimiento Fecha exacta de nacimiento (opcional).
* @param minFNac Fecha mínima de nacimiento (opcional).
* @param maxFNac Fecha máxima de nacimiento (opcional).
* @param maxAge Edad máxima (opcional).
* @param minAge Edad mínima (opcional).
* @param idJuego ID de juego asociado (opcional).
* @param idEvento ID de evento asociado (opcional).
* @return Lista de residentes filtrados.
* @throws ResiException si la residencia no existe o el ID es nulo.
*/
public List<ResidenteResponseDto> getAll(Long idResidencia, LocalDate fechaNacimiento, LocalDate minFNac, LocalDate maxFNac, Integer maxAge, Integer minAge, Long idJuego, Long idEvento, ResidenteFiltrado filtrado) {
if (idResidencia == null) {
throw new ResiException(ApiErrorCode.CAMPOS_OBLIGATORIOS);
}
// Validar que la residencia existe
Residencia residencia = residenciaService.getResidencia(idResidencia);
Specification<Residente> spec = ResidenteSpecification.withFilters(idResidencia, fechaNacimiento, minFNac, maxFNac, maxAge, minAge, idJuego, idEvento);
Sort sort = (filtrado != null) ? filtrado.toSort() : Sort.by(Sort.Direction.ASC, "apellido");
List<Residente> residentes = residenteRepository.findAll(spec, sort);
return residentes.stream().map(ResidenteResponseDto::new).collect(Collectors.toList());
}
/**
* Obtiene todos los residentes dados de baja en una residencia, con filtros de fecha.
* @param idResidencia ID de la residencia.
* @param fecha Fecha exacta de baja (opcional).
* @param minFecha Fecha mínima de baja (opcional).
* @param maxFecha Fecha máxima de baja (opcional).
* @return Lista de residentes dados de baja.
* @throws ResiException si la residencia no existe o el ID es nulo.
*/
public List<ResidenteResponseDto> getAllBajas(Long idResidencia, LocalDate fecha, LocalDate minFecha, LocalDate maxFecha) {
if (idResidencia == null){
throw new ResiException(ApiErrorCode.CAMPOS_OBLIGATORIOS);
}
List<Residente> lista = residenteRepository.findAll(ResidenteSpecification.withFiltersBaja(fecha, minFecha, maxFecha, idResidencia));
return lista.stream().map(ResidenteResponseDto::new).toList();
}
/**
* Obtiene todos los residentes dados de baja en el sistema, sin filtrar por residencia.
* @param fecha Fecha exacta de baja (opcional).
* @param minFecha Fecha mínima de baja (opcional).
* @param maxFecha Fecha máxima de baja (opcional).
* @return Lista de residentes dados de baja.
* @throws ResiException si la residencia no existe o el ID es nulo.
*/
public List<ResidenteResponseDto> getAllBajas( LocalDate fecha, LocalDate minFecha, LocalDate maxFecha) {
List<Residente> lista = residenteRepository.findAll(ResidenteSpecification.withFiltersBaja(fecha, minFecha, maxFecha, null));
return lista.stream().map(ResidenteResponseDto::new).toList();
}
/**
* Elimina un residente asegurando su pertenencia a una residencia.
*
* @param idResidencia ID de la residencia.
* @param idResidente ID del residente.
* @throws ResiException si no existe el residente o no pertenece a la residencia.
*/
public void deleteFisico(Long idResidencia, Long idResidente) {
Residente residenteTmp = getResidente(idResidencia, idResidente);
// Eliminar el residente
residenteRepository.delete(residenteTmp);
}
/**
* Elimina un residente de forma lógica, marcándolo como dado de baja.
*
* @param idResidencia ID de la residencia.
* @param idResidente ID del residente.
* @throws ResiException si el residente no existe o ya está dado de baja.
*/
public void deleteLogico(Long idResidencia, Long idResidente) {
// Validar que el residente existe
Residente residenteUpdatable = getResidente(idResidencia, idResidente);
//Dar de baja al residente
if (residenteUpdatable.isBaja())
throw new ResiException(ApiErrorCode.RESIDENTE_BAJA);
darBajaUser(residenteUpdatable, passwordEncoder);
participanteRepository.deleteAll(residenteUpdatable.getParticipantes());
residenteRepository.save(residenteUpdatable);
}
/**
* Actualiza los datos de un residente.
*
* @param idResidencia ID de la residencia.
* @param idResidente ID del residente.
* @param input DTO con los nuevos datos a actualizar.
* @return DTO del residente actualizado.
* @throws ResiException si los datos son inválidos o se detecta duplicidad de documento.
*/
public ResidenteResponseDto update(Long idResidencia, Long idResidente, ResidenteDto input) {
if (idResidencia == null || idResidente == null) {
throw new ResiException(ApiErrorCode.CAMPOS_OBLIGATORIOS);
}
// Validar que el residente existe
Residente residenteUpdatable = getResidente(idResidencia, idResidente);
//Comprobar si el residente ya se ha dado de baja
if (residenteUpdatable.isBaja()) {
throw new ResiException(ApiErrorCode.RESIDENTE_BAJA);
}
//Si ha añadido algun campo en el input para actualizar
if (input != null) {
// Validar el formato del documento de identidad
if (input.getDocumentoIdentidad() != null) {
input.setDocumentoIdentidad(input.getDocumentoIdentidad().trim().toUpperCase());
if (input.getDocumentoIdentidad().length() != 8) {
throw new ResiException(ApiErrorCode.DOCUMENTO_INVALIDO);
}
// Comprobar si ya existe otro residente con el mismo documento de identidad
Residente residenteDup = residenteRepository.findByDocuemntoIdentidad(input.getDocumentoIdentidad());
if (residenteDup != null) {
if (!Objects.equals(residenteDup.getId(), idResidente)) {
throw new ResiException(ApiErrorCode.DOCUMENTO_DUPLICADO);
}
} else
residenteUpdatable.setDocuemntoIdentidad(input.getDocumentoIdentidad());
}
if (input.getFechaNacimiento() != null) {
// Validar que la fecha de nacimiento no sea futura
if (input.getFechaNacimiento().isAfter(LocalDate.now())) {
throw new ResiException(ApiErrorCode.FECHA_INVALIDO);
}
residenteUpdatable.setFechaNacimiento(input.getFechaNacimiento());
}
if (input.getNombre() != null && !input.getNombre().trim().isEmpty()) {
residenteUpdatable.setNombre(input.getNombre());
}
if (input.getApellido() != null && !input.getApellido().trim().isEmpty()) {
residenteUpdatable.setApellido(input.getApellido());
}
if (input.getFamiliar1() != null && !input.getFamiliar1().trim().isEmpty()) {
//Validar correo familiar 1
if (!EmailService.isEmailValid(input.getFamiliar1().toLowerCase().trim())) {
throw new ResiException(ApiErrorCode.CORREO_INVALIDO);
}
residenteUpdatable.setFamiliar1(input.getFamiliar1());
}
if (input.getFamiliar2() != null && !input.getFamiliar2().trim().isEmpty()) {
//Validar correo familiar 2
if (!EmailService.isEmailValid(input.getFamiliar2().toLowerCase().trim())) {
throw new ResiException(ApiErrorCode.CORREO_INVALIDO);
}
residenteUpdatable.setFamiliar2(input.getFamiliar2());
}
// Guardar los cambios
residenteUpdatable = residenteRepository.save(residenteUpdatable);
}
return new ResidenteResponseDto(residenteUpdatable);
}
/**
* Envía un correo electrónico a los familiares de un residente.
*
* @param idResidencia ID de la residencia.
* @param idResidente ID del residente.
* @param input DTO con el asunto y cuerpo del correo.
* @throws ResiException si el residente no existe o no tiene familiares asociados.
*/
public void sendEmailFamiliar(Long idResidencia, Long idResidente, EmailRequestDto input) {
// Validar que el residente existe
Residente residenteTmp = getResidente(idResidencia, idResidente);
if (input == null || input.getSubject() == null || input.getBody() == null ||
input.getSubject().trim().isEmpty() || input.getBody().trim().isEmpty()) {
throw new ResiException(ApiErrorCode.CAMPOS_OBLIGATORIOS);
}
//Enviar correo al familiar
try{
emailService.sendEmail(residenteTmp.getFamiliar1(), input.getSubject(), input.getBody());
if(residenteTmp.getFamiliar2() != null)
emailService.sendEmail(residenteTmp.getFamiliar2(), input.getSubject(), input.getBody());
}catch (Exception e){
throw new ResiException(ApiErrorCode.ERROR_MAIL_SENDER);
}
}
/**
* Marca un residente como dado de baja.
* @param residenteUpdatable Residente a actualizar.
* @param passwordEncoder Codificador de contraseñas.
*
*/
public static void darBajaUser(Residente residenteUpdatable, PasswordEncoder passwordEncoder) {
residenteUpdatable.setBaja(true);
residenteUpdatable.setFechaBaja(LocalDateTime.now());
residenteUpdatable.setDocuemntoIdentidad(passwordEncoder.encode(residenteUpdatable.getDocuemntoIdentidad()));
residenteUpdatable.setFamiliar1(passwordEncoder.encode(residenteUpdatable.getFamiliar1()));
if (residenteUpdatable.getFamiliar2() != null && !residenteUpdatable.getFamiliar2().trim().isEmpty())
residenteUpdatable.setFamiliar2(passwordEncoder.encode(residenteUpdatable.getFamiliar2()));
}
/**
* Obtiene un residente y valida que pertenece a la residencia especificada.
*
* @param idResidencia ID de la residencia.
* @param idResidente ID del residente.
* @return Residente encontrado.
* @throws ResiException si no existe o no pertenece a la residencia.
*/
public Residente getResidente(Long idResidencia, Long idResidente) {
if (idResidencia == null || idResidente == null) {
throw new ResiException(ApiErrorCode.CAMPOS_OBLIGATORIOS);
}
// Validar que el residente existe
Residente residenteTmp = residenteRepository.findById(idResidente).orElse(null);
if (residenteTmp == null) {
throw new ResiException(ApiErrorCode.RESIDENTE_INVALIDO);
}
//Comrpobar que el residente pertenece a la residencia
if (!Objects.equals(residenteTmp.getResidencia().getId(), idResidencia)) {
throw new ResiException(ApiErrorCode.RESIDENTE_INVALIDO);
}
return residenteTmp;
}
/**
* Obtiene una imagen como recurso desde el sistema de archivos.
* @param filename Nombre del archivo solicitado (actualmente no se utiliza, se carga siempre la imagen por defecto).
* @return {@link Resource} que representa la imagen cargada desde el sistema de archivos.
* @throws ResiException si el archivo no existe o no puede accederse.
*/
public Resource getImage(String filename) {
Path filePath = Paths.get("src/main/resources/static/uploads").resolve(Conf.imageDefault).normalize();
Resource resource;
try{
resource = new UrlResource(filePath.toUri());
if (!resource.exists()) {
throw new ResiException(ApiErrorCode.PROBLEMAS_CON_FILE);
}
}catch (Exception e){
throw new ResiException(ApiErrorCode.PROBLEMAS_CON_FILE);
}
return resource;
}
}

View File

@ -0,0 +1,416 @@
package com.kevinolarte.resibenissa.services;
import com.kevinolarte.resibenissa.config.Conf;
import com.kevinolarte.resibenissa.dto.in.UserDto;
import com.kevinolarte.resibenissa.dto.in.auth.ChangePasswordUserDto;
import com.kevinolarte.resibenissa.dto.out.UserResponseDto;
import com.kevinolarte.resibenissa.exceptions.ApiErrorCode;
import com.kevinolarte.resibenissa.exceptions.ResiException;
import com.kevinolarte.resibenissa.models.Residencia;
import com.kevinolarte.resibenissa.models.modulojuego.RegistroJuego;
import com.kevinolarte.resibenissa.models.User;
import com.kevinolarte.resibenissa.repositories.UserRepository;
import com.kevinolarte.resibenissa.specifications.UserSpecification;
import lombok.AllArgsConstructor;
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.LinkedHashSet;
import java.util.List;
/**
* Servicio que gestiona la lógica relacionada con los usuarios del sistema.
* <p>
* Permite registrar, consultar, actualizar, eliminar y dar de baja usuarios,
* así como aplicar filtros personalizados en las búsquedas.
* </p>
*
* @author Kevin Olarte
*/
@Service
@AllArgsConstructor
public class UserService {
private final UserRepository userRepository;
private final ResidenciaService residenciaService;
private final BCryptPasswordEncoder passwordEncoder;
/**
* Guarda un nuevo usuario en la base de datos.
* @param idResidencia ID de la residencia a la que pertenece el usuario.
* @param input Datos del usuario a guardar.
* @return DTO con los datos del usuario guardado.
* @throws ResiException si los datos son inválidos o el usuario ya existe.
*/
public UserResponseDto add(Long idResidencia, UserDto input) {
if (input.getEmail() == null || input.getEmail().trim().isEmpty() || input.getPassword() == null || input.getPassword().trim().isEmpty()
|| input.getIdResidencia() == null || input.getNombre() == null || input.getNombre().trim().isEmpty() || input.getApellido() == null || input.getApellido().trim().isEmpty()){
throw new ResiException(ApiErrorCode.CAMPOS_OBLIGATORIOS);
}
input.setEmail(input.getEmail().trim().toLowerCase());
if (!EmailService.isEmailValid(input.getEmail())){
throw new ResiException(ApiErrorCode.CORREO_INVALIDO);
}
//Miramos si ese usuario y residencia existen
User userTest = userRepository.findByEmail(input.getEmail());
Residencia residenciaTest = residenciaService.getResidencia(input.getIdResidencia());
if(userTest != null){
if (userTest.isBaja())
throw new ResiException(ApiErrorCode.USUARIO_BAJA);
throw new ResiException(ApiErrorCode.USER_EXIST);
}
if (residenciaTest.isBaja())
throw new ResiException(ApiErrorCode.RESIDENCIA_BAJA);
User user = new User(input.getNombre(), input.getApellido(),input.getEmail(), passwordEncoder.encode(input.getPassword()));
user.setResidencia(residenciaTest);
//TODO: VERIFICACION CON CODIGO PERO FASE DESAROLLOO, AUN NO.
user.setEnabled(true);
user.setFotoPerfil("/uploads/" + Conf.imageDefault);
User savedUser = userRepository.save(user);
return new UserResponseDto(savedUser);
}
/**
* Obtiene un usuario por su ID dentro de una residencia específica.
* @param idResidencia ID de la residencia.
* @param idUser ID del usuario.
* @return DTO con los datos del usuario.
* @throws ResiException si el ID de residencia o usuario es nulo, o si el usuario no pertenece a la residencia.
*/
public UserResponseDto get(Long idResidencia, Long idUser) {
User userTmp = getUsuario(idResidencia, idUser);
// Si todo es correcto, devolver el usuario
return new UserResponseDto(userTmp);
}
/**
* Obtiene un usuario por su email dentro de una residencia específica.
* @param idResidencia ID de la residencia.
* @param email Email del usuario.
* @return DTO con los datos del usuario.
* @throws ResiException si el ID de residencia o email es nulo, o si el usuario no pertenece a la residencia.
*/
public UserResponseDto get(Long idResidencia, String email){
if (idResidencia == null || email == null){
throw new ResiException(ApiErrorCode.CAMPOS_OBLIGATORIOS);
}
//Validar si existe ese usuario
User user = userRepository.findByEmail(email);
if (user == null){
throw new ResiException(ApiErrorCode.USUARIO_INVALIDO);
}
//Validar si pertenece a esa residencia
if (user.getResidencia() == null || !user.getResidencia().getId().equals(idResidencia)) {
throw new ResiException(ApiErrorCode.USUARIO_INVALIDO);
}
// Si todo es correcto, devolver el usuario
return new UserResponseDto(user);
}
/**
* Obtiene una imagen como recurso desde el sistema de archivos.
* @param filename Nombre del archivo solicitado (actualmente no se utiliza, se carga siempre la imagen por defecto).
* @return {@link Resource} que representa la imagen cargada desde el sistema de archivos.
* @throws ResiException si el archivo no existe o no puede accederse.
*/
public Resource getImage(String filename) {
Path filePath = Paths.get("src/main/resources/static/uploads").resolve(Conf.imageDefault).normalize();
Resource resource;
try{
resource = new UrlResource(filePath.toUri());
if (!resource.exists()) {
throw new ResiException(ApiErrorCode.PROBLEMAS_CON_FILE);
}
}catch (Exception e){
throw new ResiException(ApiErrorCode.PROBLEMAS_CON_FILE);
}
return resource;
}
/**
* Obtiene una lista de todos los usuarios asociados a una residencia con filtros opcionales.
*
* @param idResidencia ID de la residencia.
* @param enabled Estado habilitado para filtrar (opcional).
* @param idJuego ID del juego para filtrar (opcional).
* @return Lista de usuarios que cumplen con los filtros aplicados.
* @throws ResiException si la residencia no existe o el ID es nulo.
*/
public List<UserResponseDto> getAll(Long idResidencia, Boolean enabled, Long idJuego){
if (idResidencia == null){
throw new ResiException(ApiErrorCode.CAMPOS_OBLIGATORIOS);
}
List<User> list = userRepository.findAll(UserSpecification.withFilters(enabled,idResidencia, idJuego));
return list.stream().map(UserResponseDto::new).toList();
}
/**
* Obtiene una lista de todos los usuarios con filtros opcionales.
*
* @param enabled Estado habilitado para filtrar (opcional).
* @param idJuego ID del juego para filtrar (opcional).
* @return Lista de usuarios que cumplen con los filtros aplicados.
* @throws ResiException si la residencia no existe o el ID es nulo.
*/
public List<UserResponseDto> getAll(Boolean enabled, Long idJuego) {
List<User> list = userRepository.findAll(UserSpecification.withFilters(enabled,null, idJuego));
return list.stream().map(UserResponseDto::new).toList();
}
/**
* Obtiene una lista de usuarios dados de baja en una residencia específica.
*
* @param idResidencia ID de la residencia.
* @return Lista de usuarios dados de baja en la residencia.
* @throws ResiException si la residencia no existe o el ID es nulo.
*/
public List<UserResponseDto> getAllBajas(Long idResidencia, LocalDate fecha, LocalDate minFecha, LocalDate maxFecha) {
if (idResidencia == null) throw new ResiException(ApiErrorCode.CAMPOS_OBLIGATORIOS);
List<User> list = userRepository.findAll(UserSpecification.withFiltersBaja(fecha,minFecha, maxFecha, idResidencia));
return list.stream().map(UserResponseDto::new).toList();
}
/**
* Obtiene una lista de usuarios dados de baja.
*
* @param fecha Fecha exacta de baja (opcional).
* @param minFecha Fecha mínima de baja (opcional).
* @param maxFecha Fecha máxima de baja (opcional).
* @return Lista de usuarios dados de baja en la residencia.
* @throws ResiException si la residencia no existe o el ID es nulo.
*/
public List<UserResponseDto> getAllBajas(LocalDate fecha, LocalDate minFecha, LocalDate maxFecha) {
List<User> list = userRepository.findAll(UserSpecification.withFiltersBaja(fecha,minFecha, maxFecha, null));
return list.stream().map(UserResponseDto::new).toList();
}
/**
* Elimina físicamente un usuario si no tiene registros dependientes.
*
* @param idResidencia ID de la residencia.
* @param idUser ID del usuario.
* @throws ResiException si el usuario tiene registros de juego asociados.
*/
public void deleteFisico(Long idResidencia, Long idUser) {
User userTmp = getUsuario(idResidencia, idUser);
// Comprobar juegos asociados
if (userTmp.getRegistroJuegos() != null && !userTmp.getRegistroJuegos().isEmpty()){
throw new ResiException(ApiErrorCode.REFERENCIAS_DEPENDIENTES);
}
userRepository.delete(userTmp);
}
/**
* Marca lógicamente como dado de baja a un usuario.
*
* @param idResidencia ID de la residencia.
* @param idUser ID del usuario.
* @throws ResiException si el usuario no existe, no pertenece a la residencia o ya está dado de baja.
*/
public void deleteLogico(Long idResidencia, Long idUser) {
if (idResidencia == null || idUser == null){
throw new ResiException(ApiErrorCode.CAMPOS_OBLIGATORIOS);
}
//Verificamos si existe el usuario
User user = userRepository.findById(idUser).orElse(null);
if (user == null){
throw new ResiException(ApiErrorCode.USUARIO_INVALIDO);
}
//Validamos si pertenece a la residencia
if (!user.getResidencia().getId().equals(idResidencia)){
throw new ResiException(ApiErrorCode.USUARIO_INVALIDO);
}
//Validamos si el usuario ya esta de baja
if (user.isBaja()){
throw new ResiException(ApiErrorCode.USUARIO_BAJA);
}
//Dar de baja
user.setBaja(true);
user.setEmail(passwordEncoder.encode(user.getEmail()));
user.setFechaBaja(LocalDateTime.now());
userRepository.save(user);
}
/**
* Desvincula registros de juego del usuario sin eliminar al usuario.
*
* @param idResidencia ID de la residencia del usuario.
* @param idUser ID del usuario al que se le eliminarán las referencias.
* @throws ResiException en caso de error
*
*/
public void deleteReferencies(Long idResidencia, Long idUser) {
if (idResidencia == null || idUser == null) {
throw new ResiException(ApiErrorCode.CAMPOS_OBLIGATORIOS);
}
User userTmp = userRepository.findById(idUser)
.orElseThrow(() -> new ResiException(ApiErrorCode.USUARIO_INVALIDO));
//Validar si pertenece a esa residencia
if (!userTmp.getResidencia().getId().equals(idResidencia)) {
throw new ResiException(ApiErrorCode.USUARIO_INVALIDO);
}
// Desvincular el usuario de todos los registros de juego
for(RegistroJuego reg : userTmp.getRegistroJuegos()){
reg.setUsuario(null);
}
userTmp.setRegistroJuegos(new LinkedHashSet<>());
userRepository.save(userTmp);
}
/**
* Actualiza los datos de un usuario existente.
* <p>
* Este método permite modificar nombre, apellido y correo electrónico del usuario,
* con validaciones para cada uno. El correo debe ser válido y no estar ya registrado.
*
* @param idResidencia ID de la residencia.
* @param idUser ID del usuario a actualizar.
* @param input Datos nuevos del usuario.
* @return DTO con los datos del usuario actualizado.
* @throws ResiException si los datos son inválidos o el usuario no pertenece a la residencia.
*/
public UserResponseDto update(Long idResidencia, Long idUser, UserDto input) {
User userTmp = getUsuario(idResidencia, idUser);
//Comprobar si elm usuario esta de baja
if (userTmp.isBaja()) {
throw new ResiException(ApiErrorCode.USUARIO_BAJA);
}
if (input != null){
//Validar si el nombre es valido
if (input.getNombre() != null && !input.getNombre().isEmpty()) {
userTmp.setNombre(input.getNombre().trim());
}
//Validar si el apellido es valido
if (input.getApellido() != null && !input.getApellido().isEmpty()) {
userTmp.setApellido(input.getApellido().trim());
}
//
//Validar si el email es valido
input.setEmail(input.getEmail().trim().toLowerCase());
if (EmailService.isEmailValid(input.getEmail())) {
//Validar si el email ya existe
if (userRepository.findByEmail(input.getEmail()) != null) {
throw new ResiException(ApiErrorCode.CORREO_DUPLICADO);
}
userTmp.setEmail(input.getEmail());
} else {
throw new ResiException(ApiErrorCode.CORREO_INVALIDO);
}
}
return new UserResponseDto(userRepository.save(userTmp));
}
/**
* Cambia la contraseña de un usuario, validando su contraseña actual.
*
* @param idResidencia ID de la residencia.
* @param idUser ID del usuario.
* @param input Objeto con la contraseña actual y la nueva.
* @return DTO con los datos del usuario actualizado.
* @throws ResiException si los datos son inválidos o la contraseña actual no coincide.
*/
public UserResponseDto updatePassword(Long idResidencia, Long idUser, ChangePasswordUserDto input) {
if (idResidencia == null || idUser == null || input == null ||
input.getOldPassword() == null || input.getNewPassword() == null) {
throw new ResiException(ApiErrorCode.CAMPOS_OBLIGATORIOS);
}
//Validar si existe ese usuario
User userTmp = userRepository.findById(idUser)
.orElseThrow(() -> new ResiException(ApiErrorCode.USUARIO_INVALIDO));
//Validar si pertenece a esa residencia
if (!userTmp.getResidencia().getId().equals(idResidencia)) {
throw new ResiException(ApiErrorCode.USUARIO_INVALIDO);
}
//Comprobar si elm usuario esta de baja
if (userTmp.isBaja()) {
throw new ResiException(ApiErrorCode.USUARIO_BAJA);
}
//Validar si la contraseña es correcta
if (!passwordEncoder.matches(input.getOldPassword(), userTmp.getPassword())) {
throw new ResiException(ApiErrorCode.CONTRASENA_INCORRECTA); // Asegúrate de definir este código si no existe
}
userTmp.setPassword(passwordEncoder.encode(input.getNewPassword()));
return new UserResponseDto(userRepository.save(userTmp));
}
/**
* Obtiene un usuario por su ID y valida que pertenezca a la residencia indicada.
* @param idResidencia ID de la residencia.
* @param idUsuario ID del usuario.
* @return DTO del usuario solicitado.
* @throws ResiException si el ID es nulo, el usuario no existe o no pertenece a la residencia.
*/
public User getUsuario(Long idResidencia, Long idUsuario) {
if (idResidencia == null || idUsuario == null) {
throw new ResiException(ApiErrorCode.CAMPOS_OBLIGATORIOS);
}
//validar si existe ese usuario
User userTmp = userRepository.findById(idUsuario)
.orElseThrow(() -> new ResiException(ApiErrorCode.USUARIO_INVALIDO));
//Validar si pertenece a esa residencia
if (userTmp.getResidencia() == null || !userTmp.getResidencia().getId().equals(idResidencia)) {
throw new ResiException(ApiErrorCode.USUARIO_INVALIDO);
}
return userTmp;
}
}

Some files were not shown because too many files have changed in this diff Show More