diff --git a/README.md b/README.md
index 81f17ce..9098978 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,3 @@
# BIAN-Quarkus
-Repositorio de APIs BIAN implementadas con Quarkus
\ No newline at end of file
+Repositorio de APIs BIAN de consulta de productos. Con implementación del mock para la consulta de TDD.
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/.dockerignore b/bus-evaluate-customer-product/.dockerignore
new file mode 100644
index 0000000..94810d0
--- /dev/null
+++ b/bus-evaluate-customer-product/.dockerignore
@@ -0,0 +1,5 @@
+*
+!target/*-runner
+!target/*-runner.jar
+!target/lib/*
+!target/quarkus-app/*
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/.gitignore b/bus-evaluate-customer-product/.gitignore
new file mode 100644
index 0000000..91a800a
--- /dev/null
+++ b/bus-evaluate-customer-product/.gitignore
@@ -0,0 +1,45 @@
+#Maven
+target/
+pom.xml.tag
+pom.xml.releaseBackup
+pom.xml.versionsBackup
+release.properties
+.flattened-pom.xml
+
+# Eclipse
+.project
+.classpath
+.settings/
+bin/
+
+# IntelliJ
+.idea
+*.ipr
+*.iml
+*.iws
+
+# NetBeans
+nb-configuration.xml
+
+# Visual Studio Code
+.vscode
+.factorypath
+
+# OSX
+.DS_Store
+
+# Vim
+*.swp
+*.swo
+
+# patch
+*.orig
+*.rej
+
+# Local environment
+.env
+
+# Plugin directory
+/.quarkus/cli/plugins/
+# TLS Certificates
+.certs/
diff --git a/bus-evaluate-customer-product/.mvn/wrapper/.gitignore b/bus-evaluate-customer-product/.mvn/wrapper/.gitignore
new file mode 100644
index 0000000..e72f5e8
--- /dev/null
+++ b/bus-evaluate-customer-product/.mvn/wrapper/.gitignore
@@ -0,0 +1 @@
+maven-wrapper.jar
diff --git a/bus-evaluate-customer-product/.mvn/wrapper/MavenWrapperDownloader.java b/bus-evaluate-customer-product/.mvn/wrapper/MavenWrapperDownloader.java
new file mode 100644
index 0000000..fe7d037
--- /dev/null
+++ b/bus-evaluate-customer-product/.mvn/wrapper/MavenWrapperDownloader.java
@@ -0,0 +1,93 @@
+/*
+ * 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.
+ */
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.Authenticator;
+import java.net.PasswordAuthentication;
+import java.net.URI;
+import java.net.URL;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardCopyOption;
+import java.util.concurrent.ThreadLocalRandom;
+
+public final class MavenWrapperDownloader {
+ private static final String WRAPPER_VERSION = "3.3.2";
+
+ private static final boolean VERBOSE = Boolean.parseBoolean(System.getenv("MVNW_VERBOSE"));
+
+ public static void main(String[] args) {
+ log("Apache Maven Wrapper Downloader " + WRAPPER_VERSION);
+
+ if (args.length != 2) {
+ System.err.println(" - ERROR wrapperUrl or wrapperJarPath parameter missing");
+ System.exit(1);
+ }
+
+ try {
+ log(" - Downloader started");
+ final URL wrapperUrl = URI.create(args[0]).toURL();
+ final String jarPath = args[1].replace("..", ""); // Sanitize path
+ final Path wrapperJarPath = Paths.get(jarPath).toAbsolutePath().normalize();
+ downloadFileFromURL(wrapperUrl, wrapperJarPath);
+ log("Done");
+ } catch (IOException e) {
+ System.err.println("- Error downloading: " + e.getMessage());
+ if (VERBOSE) {
+ e.printStackTrace();
+ }
+ System.exit(1);
+ }
+ }
+
+ private static void downloadFileFromURL(URL wrapperUrl, Path wrapperJarPath)
+ throws IOException {
+ log(" - Downloading to: " + wrapperJarPath);
+ if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) {
+ final String username = System.getenv("MVNW_USERNAME");
+ final char[] password = System.getenv("MVNW_PASSWORD").toCharArray();
+ Authenticator.setDefault(new Authenticator() {
+ @Override
+ protected PasswordAuthentication getPasswordAuthentication() {
+ return new PasswordAuthentication(username, password);
+ }
+ });
+ }
+ Path temp = wrapperJarPath
+ .getParent()
+ .resolve(wrapperJarPath.getFileName() + "."
+ + Long.toUnsignedString(ThreadLocalRandom.current().nextLong()) + ".tmp");
+ try (InputStream inStream = wrapperUrl.openStream()) {
+ Files.copy(inStream, temp, StandardCopyOption.REPLACE_EXISTING);
+ Files.move(temp, wrapperJarPath, StandardCopyOption.REPLACE_EXISTING);
+ } finally {
+ Files.deleteIfExists(temp);
+ }
+ log(" - Downloader complete");
+ }
+
+ private static void log(String msg) {
+ if (VERBOSE) {
+ System.out.println(msg);
+ }
+ }
+
+}
diff --git a/bus-evaluate-customer-product/.mvn/wrapper/maven-wrapper.properties b/bus-evaluate-customer-product/.mvn/wrapper/maven-wrapper.properties
new file mode 100644
index 0000000..1a580be
--- /dev/null
+++ b/bus-evaluate-customer-product/.mvn/wrapper/maven-wrapper.properties
@@ -0,0 +1,20 @@
+# 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=source
+distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip
+wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.3.2/maven-wrapper-3.3.2.jar
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/CHANGELOG.md b/bus-evaluate-customer-product/CHANGELOG.md
new file mode 100644
index 0000000..dcb4852
--- /dev/null
+++ b/bus-evaluate-customer-product/CHANGELOG.md
@@ -0,0 +1,10 @@
+# Changelog
+
+Todos los cambios notables en este proyecto se documentarán en este archivo.
+
+El formato se basa en [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
+y este proyecto se adhiere a [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+
+## v1.0.0 - UNRELEASED
+### Updated
+- Migración completada a Quarkus 3.25.3
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/README.md b/bus-evaluate-customer-product/README.md
new file mode 100644
index 0000000..048d399
--- /dev/null
+++ b/bus-evaluate-customer-product/README.md
@@ -0,0 +1,61 @@
+# bus-evaluate-customer-product
+
+This project uses Quarkus, the Supersonic Subatomic Java Framework.
+
+If you want to learn more about Quarkus, please visit its website: .
+
+## Running the application in dev mode
+
+You can run your application in dev mode that enables live coding using:
+
+```shell script
+./mvnw quarkus:dev
+```
+
+> **_NOTE:_** Quarkus now ships with a Dev UI, which is available in dev mode only at .
+
+## Packaging and running the application
+
+The application can be packaged using:
+
+```shell script
+./mvnw package
+```
+
+It produces the `quarkus-run.jar` file in the `target/quarkus-app/` directory.
+Be aware that it’s not an _über-jar_ as the dependencies are copied into the `target/quarkus-app/lib/` directory.
+
+The application is now runnable using `java -jar target/quarkus-app/quarkus-run.jar`.
+
+If you want to build an _über-jar_, execute the following command:
+
+```shell script
+./mvnw package -Dquarkus.package.jar.type=uber-jar
+```
+
+The application, packaged as an _über-jar_, is now runnable using `java -jar target/*-runner.jar`.
+
+## Creating a native executable
+
+You can create a native executable using:
+
+```shell script
+./mvnw package -Dnative
+```
+
+Or, if you don't have GraalVM installed, you can run the native executable build in a container using:
+
+```shell script
+./mvnw package -Dnative -Dquarkus.native.container-build=true
+```
+
+You can then execute your native executable with: `./target/bus-evaluate-customer-product-1.0.0-runner`
+
+If you want to learn more about building native executables, please consult .
+
+## Related Guides
+
+- Hibernate Validator ([guide](https://quarkus.io/guides/validation)): Validate object properties (field, getter) and method parameters for your beans (REST, CDI, Jakarta Persistence)
+- REST Jackson ([guide](https://quarkus.io/guides/rest#json-serialisation)): Jackson serialization support for Quarkus REST. This extension is not compatible with the quarkus-resteasy extension, or any of the extensions that depend on it
+- YAML Configuration ([guide](https://quarkus.io/guides/config-yaml)): Use YAML to configure your Quarkus application
+- SmallRye Health ([guide](https://quarkus.io/guides/smallrye-health)): Monitor service health
diff --git a/bus-evaluate-customer-product/TASKS.md b/bus-evaluate-customer-product/TASKS.md
new file mode 100644
index 0000000..5da32bf
--- /dev/null
+++ b/bus-evaluate-customer-product/TASKS.md
@@ -0,0 +1,2 @@
+- user login de spring tiene respuesta diferente a la hecha en quarkus (migracion cargo en cuenta)
+- Condicionar que el postgres guarde en cache
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/mvnw b/bus-evaluate-customer-product/mvnw
new file mode 100644
index 0000000..5e9618c
--- /dev/null
+++ b/bus-evaluate-customer-product/mvnw
@@ -0,0 +1,332 @@
+#!/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
+#
+# Required ENV vars:
+# ------------------
+# JAVA_HOME - location of a JDK home dir
+#
+# Optional ENV vars
+# -----------------
+# MAVEN_OPTS - parameters passed to the Java VM when running Maven
+# e.g. to debug Maven itself, use
+# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+# ----------------------------------------------------------------------------
+
+if [ -z "$MAVEN_SKIP_RC" ]; then
+
+ if [ -f /usr/local/etc/mavenrc ]; then
+ . /usr/local/etc/mavenrc
+ fi
+
+ if [ -f /etc/mavenrc ]; then
+ . /etc/mavenrc
+ fi
+
+ if [ -f "$HOME/.mavenrc" ]; then
+ . "$HOME/.mavenrc"
+ fi
+
+fi
+
+# OS specific support. $var _must_ be set to either true or false.
+cygwin=false
+darwin=false
+mingw=false
+case "$(uname)" in
+CYGWIN*) cygwin=true ;;
+MINGW*) mingw=true ;;
+Darwin*)
+ darwin=true
+ # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
+ # See https://developer.apple.com/library/mac/qa/qa1170/_index.html
+ if [ -z "$JAVA_HOME" ]; then
+ if [ -x "/usr/libexec/java_home" ]; then
+ JAVA_HOME="$(/usr/libexec/java_home)"
+ export JAVA_HOME
+ else
+ JAVA_HOME="/Library/Java/Home"
+ export JAVA_HOME
+ fi
+ fi
+ ;;
+esac
+
+if [ -z "$JAVA_HOME" ]; then
+ if [ -r /etc/gentoo-release ]; then
+ JAVA_HOME=$(java-config --jre-home)
+ fi
+fi
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched
+if $cygwin; then
+ [ -n "$JAVA_HOME" ] \
+ && JAVA_HOME=$(cygpath --unix "$JAVA_HOME")
+ [ -n "$CLASSPATH" ] \
+ && CLASSPATH=$(cygpath --path --unix "$CLASSPATH")
+fi
+
+# For Mingw, ensure paths are in UNIX format before anything is touched
+if $mingw; then
+ [ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] \
+ && JAVA_HOME="$(
+ cd "$JAVA_HOME" || (
+ echo "cannot cd into $JAVA_HOME." >&2
+ exit 1
+ )
+ pwd
+ )"
+fi
+
+if [ -z "$JAVA_HOME" ]; then
+ javaExecutable="$(which javac)"
+ if [ -n "$javaExecutable" ] && ! [ "$(expr "$javaExecutable" : '\([^ ]*\)')" = "no" ]; then
+ # readlink(1) is not available as standard on Solaris 10.
+ readLink=$(which readlink)
+ if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then
+ if $darwin; then
+ javaHome="$(dirname "$javaExecutable")"
+ javaExecutable="$(cd "$javaHome" && pwd -P)/javac"
+ else
+ javaExecutable="$(readlink -f "$javaExecutable")"
+ fi
+ javaHome="$(dirname "$javaExecutable")"
+ javaHome=$(expr "$javaHome" : '\(.*\)/bin')
+ JAVA_HOME="$javaHome"
+ export JAVA_HOME
+ fi
+ fi
+fi
+
+if [ -z "$JAVACMD" ]; then
+ if [ -n "$JAVA_HOME" ]; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ]; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ else
+ JAVACMD="$(
+ \unset -f command 2>/dev/null
+ \command -v java
+ )"
+ fi
+fi
+
+if [ ! -x "$JAVACMD" ]; then
+ echo "Error: JAVA_HOME is not defined correctly." >&2
+ echo " We cannot execute $JAVACMD" >&2
+ exit 1
+fi
+
+if [ -z "$JAVA_HOME" ]; then
+ echo "Warning: JAVA_HOME environment variable is not set." >&2
+fi
+
+# traverses directory structure from process work directory to filesystem root
+# first directory with .mvn subdirectory is considered project base directory
+find_maven_basedir() {
+ if [ -z "$1" ]; then
+ echo "Path not specified to find_maven_basedir" >&2
+ return 1
+ fi
+
+ basedir="$1"
+ wdir="$1"
+ while [ "$wdir" != '/' ]; do
+ if [ -d "$wdir"/.mvn ]; then
+ basedir=$wdir
+ break
+ fi
+ # workaround for JBEAP-8937 (on Solaris 10/Sparc)
+ if [ -d "${wdir}" ]; then
+ wdir=$(
+ cd "$wdir/.." || exit 1
+ pwd
+ )
+ fi
+ # end of workaround
+ done
+ printf '%s' "$(
+ cd "$basedir" || exit 1
+ pwd
+ )"
+}
+
+# concatenates all lines of a file
+concat_lines() {
+ if [ -f "$1" ]; then
+ # Remove \r in case we run on Windows within Git Bash
+ # and check out the repository with auto CRLF management
+ # enabled. Otherwise, we may read lines that are delimited with
+ # \r\n and produce $'-Xarg\r' rather than -Xarg due to word
+ # splitting rules.
+ tr -s '\r\n' ' ' <"$1"
+ fi
+}
+
+log() {
+ if [ "$MVNW_VERBOSE" = true ]; then
+ printf '%s\n' "$1"
+ fi
+}
+
+BASE_DIR=$(find_maven_basedir "$(dirname "$0")")
+if [ -z "$BASE_DIR" ]; then
+ exit 1
+fi
+
+MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
+export MAVEN_PROJECTBASEDIR
+log "$MAVEN_PROJECTBASEDIR"
+
+##########################################################################################
+# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+# This allows using the maven wrapper in projects that prohibit checking in binary data.
+##########################################################################################
+wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar"
+if [ -r "$wrapperJarPath" ]; then
+ log "Found $wrapperJarPath"
+else
+ log "Couldn't find $wrapperJarPath, downloading it ..."
+
+ if [ -n "$MVNW_REPOURL" ]; then
+ wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.3.2/maven-wrapper-3.3.2.jar"
+ else
+ wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.3.2/maven-wrapper-3.3.2.jar"
+ fi
+ while IFS="=" read -r key value; do
+ # Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' )
+ safeValue=$(echo "$value" | tr -d '\r')
+ case "$key" in wrapperUrl)
+ wrapperUrl="$safeValue"
+ break
+ ;;
+ esac
+ done <"$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties"
+ log "Downloading from: $wrapperUrl"
+
+ if $cygwin; then
+ wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath")
+ fi
+
+ if command -v wget >/dev/null; then
+ log "Found wget ... using wget"
+ [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet"
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
+ else
+ wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
+ fi
+ elif command -v curl >/dev/null; then
+ log "Found curl ... using curl"
+ [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent"
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath"
+ else
+ curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath"
+ fi
+ else
+ log "Falling back to using Java to download"
+ javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java"
+ javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class"
+ # For Cygwin, switch paths to Windows format before running javac
+ if $cygwin; then
+ javaSource=$(cygpath --path --windows "$javaSource")
+ javaClass=$(cygpath --path --windows "$javaClass")
+ fi
+ if [ -e "$javaSource" ]; then
+ if [ ! -e "$javaClass" ]; then
+ log " - Compiling MavenWrapperDownloader.java ..."
+ ("$JAVA_HOME/bin/javac" "$javaSource")
+ fi
+ if [ -e "$javaClass" ]; then
+ log " - Running MavenWrapperDownloader.java ..."
+ ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath"
+ fi
+ fi
+ fi
+fi
+##########################################################################################
+# End of extension
+##########################################################################################
+
+# If specified, validate the SHA-256 sum of the Maven wrapper jar file
+wrapperSha256Sum=""
+while IFS="=" read -r key value; do
+ case "$key" in wrapperSha256Sum)
+ wrapperSha256Sum=$value
+ break
+ ;;
+ esac
+done <"$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties"
+if [ -n "$wrapperSha256Sum" ]; then
+ wrapperSha256Result=false
+ if command -v sha256sum >/dev/null; then
+ if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c >/dev/null 2>&1; then
+ wrapperSha256Result=true
+ fi
+ elif command -v shasum >/dev/null; then
+ if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c >/dev/null 2>&1; then
+ wrapperSha256Result=true
+ fi
+ else
+ echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2
+ echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties." >&2
+ exit 1
+ fi
+ if [ $wrapperSha256Result = false ]; then
+ echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2
+ echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2
+ echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2
+ exit 1
+ fi
+fi
+
+MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin; then
+ [ -n "$JAVA_HOME" ] \
+ && JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME")
+ [ -n "$CLASSPATH" ] \
+ && CLASSPATH=$(cygpath --path --windows "$CLASSPATH")
+ [ -n "$MAVEN_PROJECTBASEDIR" ] \
+ && MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR")
+fi
+
+# Provide a "standardized" way to retrieve the CLI args that will
+# work with both Windows and non-Windows executions.
+MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*"
+export MAVEN_CMD_LINE_ARGS
+
+WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+# shellcheck disable=SC2086 # safe args
+exec "$JAVACMD" \
+ $MAVEN_OPTS \
+ $MAVEN_DEBUG_OPTS \
+ -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
+ "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
+ ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
diff --git a/bus-evaluate-customer-product/mvnw.cmd b/bus-evaluate-customer-product/mvnw.cmd
new file mode 100644
index 0000000..4136715
--- /dev/null
+++ b/bus-evaluate-customer-product/mvnw.cmd
@@ -0,0 +1,206 @@
+@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 Required ENV vars:
+@REM JAVA_HOME - location of a JDK home dir
+@REM
+@REM Optional ENV vars
+@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
+@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
+@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
+@REM e.g. to debug Maven itself, use
+@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+@REM ----------------------------------------------------------------------------
+
+@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
+@echo off
+@REM set title of command window
+title %0
+@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
+@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
+
+@REM set %HOME% to equivalent of $HOME
+if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
+
+@REM Execute a user defined script before this one
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
+@REM check for pre script, once with legacy .bat ending and once with .cmd ending
+if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %*
+if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %*
+:skipRcPre
+
+@setlocal
+
+set ERROR_CODE=0
+
+@REM To isolate internal variables from possible post scripts, we use another setlocal
+@setlocal
+
+@REM ==== START VALIDATION ====
+if not "%JAVA_HOME%" == "" goto OkJHome
+
+echo. >&2
+echo Error: JAVA_HOME not found in your environment. >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo. >&2
+goto error
+
+:OkJHome
+if exist "%JAVA_HOME%\bin\java.exe" goto init
+
+echo. >&2
+echo Error: JAVA_HOME is set to an invalid directory. >&2
+echo JAVA_HOME = "%JAVA_HOME%" >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo. >&2
+goto error
+
+@REM ==== END VALIDATION ====
+
+:init
+
+@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
+@REM Fallback to current working directory if not found.
+
+set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
+IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
+
+set EXEC_DIR=%CD%
+set WDIR=%EXEC_DIR%
+:findBaseDir
+IF EXIST "%WDIR%"\.mvn goto baseDirFound
+cd ..
+IF "%WDIR%"=="%CD%" goto baseDirNotFound
+set WDIR=%CD%
+goto findBaseDir
+
+:baseDirFound
+set MAVEN_PROJECTBASEDIR=%WDIR%
+cd "%EXEC_DIR%"
+goto endDetectBaseDir
+
+:baseDirNotFound
+set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
+cd "%EXEC_DIR%"
+
+:endDetectBaseDir
+
+IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
+
+@setlocal EnableExtensions EnableDelayedExpansion
+for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
+@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
+
+:endReadAdditionalConfig
+
+SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
+set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
+set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.3.2/maven-wrapper-3.3.2.jar"
+
+FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
+ IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B
+)
+
+@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
+if exist %WRAPPER_JAR% (
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Found %WRAPPER_JAR%
+ )
+) else (
+ if not "%MVNW_REPOURL%" == "" (
+ SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.3.2/maven-wrapper-3.3.2.jar"
+ )
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Couldn't find %WRAPPER_JAR%, downloading it ...
+ echo Downloading from: %WRAPPER_URL%
+ )
+
+ powershell -Command "&{"^
+ "$webclient = new-object System.Net.WebClient;"^
+ "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
+ "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
+ "}"^
+ "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^
+ "}"
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Finished downloading %WRAPPER_JAR%
+ )
+)
+@REM End of extension
+
+@REM If specified, validate the SHA-256 sum of the Maven wrapper jar file
+SET WRAPPER_SHA_256_SUM=""
+FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
+ IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B
+)
+IF NOT %WRAPPER_SHA_256_SUM%=="" (
+ powershell -Command "&{"^
+ "Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash;"^
+ "$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^
+ "If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^
+ " Write-Error 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^
+ " Write-Error 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^
+ " Write-Error 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^
+ " exit 1;"^
+ "}"^
+ "}"
+ if ERRORLEVEL 1 goto error
+)
+
+@REM Provide a "standardized" way to retrieve the CLI args that will
+@REM work with both Windows and non-Windows executions.
+set MAVEN_CMD_LINE_ARGS=%*
+
+%MAVEN_JAVA_EXE% ^
+ %JVM_CONFIG_MAVEN_PROPS% ^
+ %MAVEN_OPTS% ^
+ %MAVEN_DEBUG_OPTS% ^
+ -classpath %WRAPPER_JAR% ^
+ "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^
+ %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
+if ERRORLEVEL 1 goto error
+goto end
+
+:error
+set ERROR_CODE=1
+
+:end
+@endlocal & set ERROR_CODE=%ERROR_CODE%
+
+if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost
+@REM check for post script, once with legacy .bat ending and once with .cmd ending
+if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat"
+if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd"
+:skipRcPost
+
+@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
+if "%MAVEN_BATCH_PAUSE%"=="on" pause
+
+if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE%
+
+cmd /C exit /B %ERROR_CODE%
diff --git a/bus-evaluate-customer-product/pom.xml b/bus-evaluate-customer-product/pom.xml
new file mode 100644
index 0000000..6a51abd
--- /dev/null
+++ b/bus-evaluate-customer-product/pom.xml
@@ -0,0 +1,226 @@
+
+
+ 4.0.0
+ com.banesco
+ bus-evaluate-customer-product
+ bus-evaluate-customer-product
+ API REST Customer Product (consulta de cuentas) BUS
+ 1.0.0
+
+
+ 3.14.0
+ 17
+ UTF-8
+ UTF-8
+ quarkus-bom
+ io.quarkus.platform
+ 3.25.3
+ true
+ com.banesco.Main
+ 3.5.3
+ 4.0.4
+
+
+
+
+
+ ${quarkus.platform.group-id}
+ ${quarkus.platform.artifact-id}
+ ${quarkus.platform.version}
+ pom
+ import
+
+
+
+
+
+
+ com.banesco
+ commons
+ 1.0
+
+
+ io.quarkus
+ quarkus-rest-jackson
+
+
+ io.quarkus
+ quarkus-rest-client-jackson
+
+
+ io.quarkus
+ quarkus-config-yaml
+
+
+ io.quarkus
+ quarkus-arc
+
+
+ io.quarkus
+ quarkus-junit5
+ test
+
+
+ org.projectlombok
+ lombok
+ 1.18.38
+
+
+ io.quarkus
+ quarkus-redis-client
+
+
+ io.quarkus
+ quarkus-redis-cache
+
+
+ io.quarkiverse.cxf
+ quarkus-cxf
+ 3.15.1
+
+
+ io.quarkus
+ quarkus-smallrye-openapi
+
+
+ io.quarkus
+ quarkus-swagger-ui
+
+
+ io.quarkus
+ quarkus-smallrye-health
+
+
+
+ io.quarkus
+ quarkus-hibernate-validator
+
+
+
+
+ io.quarkus
+ quarkus-jdbc-postgresql
+
+
+
+
+
+
+
+ ${quarkus.platform.group-id}
+ quarkus-maven-plugin
+ ${quarkus.platform.version}
+ true
+
+
+
+ build
+ generate-code
+ generate-code-tests
+ native-image-agent
+
+
+
+
+
+ org.apache.cxf
+ cxf-codegen-plugin
+ ${cxf.version}
+
+
+ generate-sources
+ generate-sources
+
+ ${project.build.directory}/generated-sources/cxf
+
+
+ ${project.basedir}/src/main/resources/wsdl/PruebaService.wsdl
+
+ -client
+ -verbose
+
+
+
+
+
+ wsdl2java
+
+
+
+
+
+
+ maven-compiler-plugin
+ ${compiler-plugin.version}
+
+ true
+
+
+
+ maven-surefire-plugin
+ ${surefire-plugin.version}
+
+
+ org.jboss.logmanager.LogManager
+ ${maven.home}
+
+
+
+
+ maven-failsafe-plugin
+ ${surefire-plugin.version}
+
+
+
+ integration-test
+ verify
+
+
+
+
+
+ ${project.build.directory}/${project.build.finalName}-runner
+ org.jboss.logmanager.LogManager
+ ${maven.home}
+
+
+
+
+
+ org.codehaus.mojo
+ build-helper-maven-plugin
+ 3.6.0
+
+
+ add-source
+ generate-sources
+
+ add-source
+
+
+
+ ${project.build.directory}/generated-sources/cxf
+
+
+
+
+
+
+
+
+
+
+
+ native
+
+
+ native
+
+
+
+ false
+ true
+
+
+
+
diff --git a/bus-evaluate-customer-product/scripts/native/Dockerfile b/bus-evaluate-customer-product/scripts/native/Dockerfile
new file mode 100644
index 0000000..6c0f979
--- /dev/null
+++ b/bus-evaluate-customer-product/scripts/native/Dockerfile
@@ -0,0 +1,12 @@
+FROM quay.io/quarkus/ubi9-quarkus-micro-image:2.0
+RUN mkdir -p /work
+ENV TZ="America/Caracas"
+ENV LANGUAGE='en_US:en'
+VOLUME /tmp
+COPY /file/*-runner /work/busCustomerAccounts
+RUN chmod -R 775 /work
+RUN ls -ltra /work/
+EXPOSE 8080
+WORKDIR /work/
+
+ENTRYPOINT ["./busCustomerAccounts", "-Dquarkus.http.host=0.0.0.0"]
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/scripts/native/file/bus-evaluate-customer-product-1.0.0-runner b/bus-evaluate-customer-product/scripts/native/file/bus-evaluate-customer-product-1.0.0-runner
new file mode 100644
index 0000000..ae03720
Binary files /dev/null and b/bus-evaluate-customer-product/scripts/native/file/bus-evaluate-customer-product-1.0.0-runner differ
diff --git a/bus-evaluate-customer-product/src/main/docker/Dockerfile.jvm b/bus-evaluate-customer-product/src/main/docker/Dockerfile.jvm
new file mode 100644
index 0000000..156f18c
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/docker/Dockerfile.jvm
@@ -0,0 +1,98 @@
+####
+# This Dockerfile is used in order to build a container that runs the Quarkus application in JVM mode
+#
+# Before building the container image run:
+#
+# ./mvnw package
+#
+# Then, build the image with:
+#
+# docker build -f src/main/docker/Dockerfile.jvm -t quarkus/pub-get-accounts-client-jvm .
+#
+# Then run the container using:
+#
+# docker run -i --rm -p 8080:8080 quarkus/pub-get-accounts-client-jvm
+#
+# If you want to include the debug port into your docker image
+# you will have to expose the debug port (default 5005 being the default) like this : EXPOSE 8080 5005.
+# Additionally you will have to set -e JAVA_DEBUG=true and -e JAVA_DEBUG_PORT=*:5005
+# when running the container
+#
+# Then run the container using :
+#
+# docker run -i --rm -p 8080:8080 quarkus/pub-get-accounts-client-jvm
+#
+# This image uses the `run-java.sh` script to run the application.
+# This scripts computes the command line to execute your Java application, and
+# includes memory/GC tuning.
+# You can configure the behavior using the following environment properties:
+# - JAVA_OPTS: JVM options passed to the `java` command (example: "-verbose:class") - Be aware that this will override
+# the default JVM options, use `JAVA_OPTS_APPEND` to append options
+# - JAVA_OPTS_APPEND: User specified Java options to be appended to generated options
+# in JAVA_OPTS (example: "-Dsome.property=foo")
+# - JAVA_MAX_MEM_RATIO: Is used when no `-Xmx` option is given in JAVA_OPTS. This is
+# used to calculate a default maximal heap memory based on a containers restriction.
+# If used in a container without any memory constraints for the container then this
+# option has no effect. If there is a memory constraint then `-Xmx` is set to a ratio
+# of the container available memory as set here. The default is `50` which means 50%
+# of the available memory is used as an upper boundary. You can skip this mechanism by
+# setting this value to `0` in which case no `-Xmx` option is added.
+# - JAVA_INITIAL_MEM_RATIO: Is used when no `-Xms` option is given in JAVA_OPTS. This
+# is used to calculate a default initial heap memory based on the maximum heap memory.
+# If used in a container without any memory constraints for the container then this
+# option has no effect. If there is a memory constraint then `-Xms` is set to a ratio
+# of the `-Xmx` memory as set here. The default is `25` which means 25% of the `-Xmx`
+# is used as the initial heap size. You can skip this mechanism by setting this value
+# to `0` in which case no `-Xms` option is added (example: "25")
+# - JAVA_MAX_INITIAL_MEM: Is used when no `-Xms` option is given in JAVA_OPTS.
+# This is used to calculate the maximum value of the initial heap memory. If used in
+# a container without any memory constraints for the container then this option has
+# no effect. If there is a memory constraint then `-Xms` is limited to the value set
+# here. The default is 4096MB which means the calculated value of `-Xms` never will
+# be greater than 4096MB. The value of this variable is expressed in MB (example: "4096")
+# - JAVA_DIAGNOSTICS: Set this to get some diagnostics information to standard output
+# when things are happening. This option, if set to true, will set
+# `-XX:+UnlockDiagnosticVMOptions`. Disabled by default (example: "true").
+# - JAVA_DEBUG: If set remote debugging will be switched on. Disabled by default (example:
+# true").
+# - JAVA_DEBUG_PORT: Port used for remote debugging. Defaults to 5005 (example: "8787").
+# - CONTAINER_CORE_LIMIT: A calculated core limit as described in
+# https://www.kernel.org/doc/Documentation/scheduler/sched-bwc.txt. (example: "2")
+# - CONTAINER_MAX_MEMORY: Memory limit given to the container (example: "1024").
+# - GC_MIN_HEAP_FREE_RATIO: Minimum percentage of heap free after GC to avoid expansion.
+# (example: "20")
+# - GC_MAX_HEAP_FREE_RATIO: Maximum percentage of heap free after GC to avoid shrinking.
+# (example: "40")
+# - GC_TIME_RATIO: Specifies the ratio of the time spent outside the garbage collection.
+# (example: "4")
+# - GC_ADAPTIVE_SIZE_POLICY_WEIGHT: The weighting given to the current GC time versus
+# previous GC times. (example: "90")
+# - GC_METASPACE_SIZE: The initial metaspace size. (example: "20")
+# - GC_MAX_METASPACE_SIZE: The maximum metaspace size. (example: "100")
+# - GC_CONTAINER_OPTIONS: Specify Java GC to use. The value of this variable should
+# contain the necessary JRE command-line options to specify the required GC, which
+# will override the default of `-XX:+UseParallelGC` (example: -XX:+UseG1GC).
+# - HTTPS_PROXY: The location of the https proxy. (example: "myuser@127.0.0.1:8080")
+# - HTTP_PROXY: The location of the http proxy. (example: "myuser@127.0.0.1:8080")
+# - NO_PROXY: A comma separated lists of hosts, IP addresses or domains that can be
+# accessed directly. (example: "foo.example.com,bar.example.com")
+#
+###
+FROM registry.access.redhat.com/ubi9/openjdk-17:1.23
+
+ENV LANGUAGE='en_US:en'
+
+
+# We make four distinct layers so if there are application changes the library layers can be re-used
+COPY --chown=185 target/quarkus-app/lib/ /deployments/lib/
+COPY --chown=185 target/quarkus-app/*.jar /deployments/
+COPY --chown=185 target/quarkus-app/app/ /deployments/app/
+COPY --chown=185 target/quarkus-app/quarkus/ /deployments/quarkus/
+
+EXPOSE 8080
+USER 185
+ENV JAVA_OPTS_APPEND="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager"
+ENV JAVA_APP_JAR="/deployments/quarkus-run.jar"
+
+ENTRYPOINT [ "/opt/jboss/container/java/run/run-java.sh" ]
+
diff --git a/bus-evaluate-customer-product/src/main/docker/Dockerfile.legacy-jar b/bus-evaluate-customer-product/src/main/docker/Dockerfile.legacy-jar
new file mode 100644
index 0000000..88079f9
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/docker/Dockerfile.legacy-jar
@@ -0,0 +1,94 @@
+####
+# This Dockerfile is used in order to build a container that runs the Quarkus application in JVM mode
+#
+# Before building the container image run:
+#
+# ./mvnw package -Dquarkus.package.jar.type=legacy-jar
+#
+# Then, build the image with:
+#
+# docker build -f src/main/docker/Dockerfile.legacy-jar -t quarkus/pub-get-accounts-client-legacy-jar .
+#
+# Then run the container using:
+#
+# docker run -i --rm -p 8080:8080 quarkus/pub-get-accounts-client-legacy-jar
+#
+# If you want to include the debug port into your docker image
+# you will have to expose the debug port (default 5005 being the default) like this : EXPOSE 8080 5005.
+# Additionally you will have to set -e JAVA_DEBUG=true and -e JAVA_DEBUG_PORT=*:5005
+# when running the container
+#
+# Then run the container using :
+#
+# docker run -i --rm -p 8080:8080 quarkus/pub-get-accounts-client-legacy-jar
+#
+# This image uses the `run-java.sh` script to run the application.
+# This scripts computes the command line to execute your Java application, and
+# includes memory/GC tuning.
+# You can configure the behavior using the following environment properties:
+# - JAVA_OPTS: JVM options passed to the `java` command (example: "-verbose:class") - Be aware that this will override
+# the default JVM options, use `JAVA_OPTS_APPEND` to append options
+# - JAVA_OPTS_APPEND: User specified Java options to be appended to generated options
+# in JAVA_OPTS (example: "-Dsome.property=foo")
+# - JAVA_MAX_MEM_RATIO: Is used when no `-Xmx` option is given in JAVA_OPTS. This is
+# used to calculate a default maximal heap memory based on a containers restriction.
+# If used in a container without any memory constraints for the container then this
+# option has no effect. If there is a memory constraint then `-Xmx` is set to a ratio
+# of the container available memory as set here. The default is `50` which means 50%
+# of the available memory is used as an upper boundary. You can skip this mechanism by
+# setting this value to `0` in which case no `-Xmx` option is added.
+# - JAVA_INITIAL_MEM_RATIO: Is used when no `-Xms` option is given in JAVA_OPTS. This
+# is used to calculate a default initial heap memory based on the maximum heap memory.
+# If used in a container without any memory constraints for the container then this
+# option has no effect. If there is a memory constraint then `-Xms` is set to a ratio
+# of the `-Xmx` memory as set here. The default is `25` which means 25% of the `-Xmx`
+# is used as the initial heap size. You can skip this mechanism by setting this value
+# to `0` in which case no `-Xms` option is added (example: "25")
+# - JAVA_MAX_INITIAL_MEM: Is used when no `-Xms` option is given in JAVA_OPTS.
+# This is used to calculate the maximum value of the initial heap memory. If used in
+# a container without any memory constraints for the container then this option has
+# no effect. If there is a memory constraint then `-Xms` is limited to the value set
+# here. The default is 4096MB which means the calculated value of `-Xms` never will
+# be greater than 4096MB. The value of this variable is expressed in MB (example: "4096")
+# - JAVA_DIAGNOSTICS: Set this to get some diagnostics information to standard output
+# when things are happening. This option, if set to true, will set
+# `-XX:+UnlockDiagnosticVMOptions`. Disabled by default (example: "true").
+# - JAVA_DEBUG: If set remote debugging will be switched on. Disabled by default (example:
+# true").
+# - JAVA_DEBUG_PORT: Port used for remote debugging. Defaults to 5005 (example: "8787").
+# - CONTAINER_CORE_LIMIT: A calculated core limit as described in
+# https://www.kernel.org/doc/Documentation/scheduler/sched-bwc.txt. (example: "2")
+# - CONTAINER_MAX_MEMORY: Memory limit given to the container (example: "1024").
+# - GC_MIN_HEAP_FREE_RATIO: Minimum percentage of heap free after GC to avoid expansion.
+# (example: "20")
+# - GC_MAX_HEAP_FREE_RATIO: Maximum percentage of heap free after GC to avoid shrinking.
+# (example: "40")
+# - GC_TIME_RATIO: Specifies the ratio of the time spent outside the garbage collection.
+# (example: "4")
+# - GC_ADAPTIVE_SIZE_POLICY_WEIGHT: The weighting given to the current GC time versus
+# previous GC times. (example: "90")
+# - GC_METASPACE_SIZE: The initial metaspace size. (example: "20")
+# - GC_MAX_METASPACE_SIZE: The maximum metaspace size. (example: "100")
+# - GC_CONTAINER_OPTIONS: Specify Java GC to use. The value of this variable should
+# contain the necessary JRE command-line options to specify the required GC, which
+# will override the default of `-XX:+UseParallelGC` (example: -XX:+UseG1GC).
+# - HTTPS_PROXY: The location of the https proxy. (example: "myuser@127.0.0.1:8080")
+# - HTTP_PROXY: The location of the http proxy. (example: "myuser@127.0.0.1:8080")
+# - NO_PROXY: A comma separated lists of hosts, IP addresses or domains that can be
+# accessed directly. (example: "foo.example.com,bar.example.com")
+#
+###
+FROM registry.access.redhat.com/ubi9/openjdk-17:1.23
+
+ENV LANGUAGE='en_US:en'
+
+
+COPY target/lib/* /deployments/lib/
+COPY target/*-runner.jar /deployments/quarkus-run.jar
+
+EXPOSE 8080
+USER 185
+ENV JAVA_OPTS_APPEND="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager"
+ENV JAVA_APP_JAR="/deployments/quarkus-run.jar"
+
+ENTRYPOINT [ "/opt/jboss/container/java/run/run-java.sh" ]
diff --git a/bus-evaluate-customer-product/src/main/docker/Dockerfile.native b/bus-evaluate-customer-product/src/main/docker/Dockerfile.native
new file mode 100644
index 0000000..de4d60d
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/docker/Dockerfile.native
@@ -0,0 +1,29 @@
+####
+# This Dockerfile is used in order to build a container that runs the Quarkus application in native (no JVM) mode.
+#
+# Before building the container image run:
+#
+# ./mvnw package -Dnative
+#
+# Then, build the image with:
+#
+# docker build -f src/main/docker/Dockerfile.native -t quarkus/pub-get-accounts-client .
+#
+# Then run the container using:
+#
+# docker run -i --rm -p 8080:8080 quarkus/pub-get-accounts-client
+#
+# The ` registry.access.redhat.com/ubi9/ubi-minimal:9.6` base image is based on UBI 9.
+# To use UBI 8, switch to `quay.io/ubi8/ubi-minimal:8.10`.
+###
+FROM registry.access.redhat.com/ubi9/ubi-minimal:9.6
+WORKDIR /work/
+RUN chown 1001 /work \
+ && chmod "g+rwX" /work \
+ && chown 1001:root /work
+COPY --chown=1001:root --chmod=0755 target/*-runner /work/application
+
+EXPOSE 8080
+USER 1001
+
+ENTRYPOINT ["./application", "-Dquarkus.http.host=0.0.0.0"]
diff --git a/bus-evaluate-customer-product/src/main/docker/Dockerfile.native-micro b/bus-evaluate-customer-product/src/main/docker/Dockerfile.native-micro
new file mode 100644
index 0000000..c1df759
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/docker/Dockerfile.native-micro
@@ -0,0 +1,32 @@
+####
+# This Dockerfile is used in order to build a container that runs the Quarkus application in native (no JVM) mode.
+# It uses a micro base image, tuned for Quarkus native executables.
+# It reduces the size of the resulting container image.
+# Check https://quarkus.io/guides/quarkus-runtime-base-image for further information about this image.
+#
+# Before building the container image run:
+#
+# ./mvnw package -Dnative
+#
+# Then, build the image with:
+#
+# docker build -f src/main/docker/Dockerfile.native-micro -t quarkus/pub-get-accounts-client .
+#
+# Then run the container using:
+#
+# docker run -i --rm -p 8080:8080 quarkus/pub-get-accounts-client
+#
+# The `quay.io/quarkus/ubi9-quarkus-micro-image:2.0` base image is based on UBI 9.
+# To use UBI 8, switch to `quay.io/quarkus/quarkus-micro-image:2.0`.
+###
+FROM quay.io/quarkus/ubi9-quarkus-micro-image:2.0
+WORKDIR /work/
+RUN chown 1001 /work \
+ && chmod "g+rwX" /work \
+ && chown 1001:root /work
+COPY --chown=1001:root --chmod=0755 target/*-runner /work/application
+
+EXPOSE 8080
+USER 1001
+
+ENTRYPOINT ["./application", "-Dquarkus.http.host=0.0.0.0"]
diff --git a/bus-evaluate-customer-product/src/main/java/Main.java b/bus-evaluate-customer-product/src/main/java/Main.java
new file mode 100644
index 0000000..6dd9085
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/Main.java
@@ -0,0 +1,12 @@
+import io.quarkus.runtime.Quarkus;
+import io.quarkus.runtime.annotations.QuarkusMain;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+@QuarkusMain
+public class Main {
+ public static void main(String[] args) {
+ log.info("Running bus-evaluate-customer-product...");
+ Quarkus.run(args);
+ }
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/java/application/exception/GatewayServiceException.java b/bus-evaluate-customer-product/src/main/java/application/exception/GatewayServiceException.java
new file mode 100644
index 0000000..61dd76a
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/application/exception/GatewayServiceException.java
@@ -0,0 +1,12 @@
+package application.exception;
+
+public class GatewayServiceException extends RuntimeException {
+ public GatewayServiceException(String message) {
+ super(message);
+ }
+
+ public GatewayServiceException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/java/application/exception/InvalidTokenException.java b/bus-evaluate-customer-product/src/main/java/application/exception/InvalidTokenException.java
new file mode 100644
index 0000000..0ccf895
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/application/exception/InvalidTokenException.java
@@ -0,0 +1,7 @@
+package application.exception;
+
+public class InvalidTokenException extends RuntimeException {
+ public InvalidTokenException(String message) {
+ super(message);
+ }
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/java/application/exception/ServiceCommunicationException.java b/bus-evaluate-customer-product/src/main/java/application/exception/ServiceCommunicationException.java
new file mode 100644
index 0000000..1b55999
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/application/exception/ServiceCommunicationException.java
@@ -0,0 +1,7 @@
+package application.exception;
+
+public class ServiceCommunicationException extends GatewayServiceException {
+ public ServiceCommunicationException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/java/application/factory/SecurityTraceFactory.java b/bus-evaluate-customer-product/src/main/java/application/factory/SecurityTraceFactory.java
new file mode 100644
index 0000000..65edf32
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/application/factory/SecurityTraceFactory.java
@@ -0,0 +1,37 @@
+package application.factory;
+
+import application.port.input.command.RegisterSecurityCommand;
+import domain.dto.response.StatusResponse;
+import domain.model.AccountClientContext;
+import jakarta.enterprise.context.ApplicationScoped;
+import lombok.extern.slf4j.Slf4j;
+
+import java.sql.Timestamp;
+import java.time.LocalDateTime;
+
+@Slf4j
+@ApplicationScoped
+public class SecurityTraceFactory {
+
+ public RegisterSecurityCommand createCommandFromContext(AccountClientContext context, StatusResponse statusResponse) {
+ var request = context.getRequest();
+ var customerReference = request.getCustomerPositionState().getCustomerReference();
+ var device = request.getDevice();
+
+ long durationInMillis = System.currentTimeMillis() - context.getStartExecution();
+
+ return RegisterSecurityCommand.builder()
+ .login(context.getUsername())
+ .fecHor(Timestamp.valueOf(LocalDateTime.now()))
+ .nacCli(customerReference.getCustomerIdType())
+ .cedRifCli(Integer.valueOf(customerReference.getCustomerId()))
+ .tipoRespuesta(Integer.valueOf(statusResponse.getStatusCode()))
+ .msgRespuesta(statusResponse.getMessage())
+ .codFintech(context.getFintechId())
+ .tiempoRespuesta((int) durationInMillis)
+ .tipoDispositivo(device.getDeviceType())
+ .desDispositivo(device.getDeviceDescription())
+ .ipCli(device.getDeviceIp())
+ .build();
+ }
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/java/application/helper/CustomerHelper.java b/bus-evaluate-customer-product/src/main/java/application/helper/CustomerHelper.java
new file mode 100644
index 0000000..2f4831d
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/application/helper/CustomerHelper.java
@@ -0,0 +1,25 @@
+package application.helper;
+
+import lombok.extern.slf4j.Slf4j;
+
+import java.util.Optional;
+
+@Slf4j
+public class CustomerHelper {
+ /**
+ * Construye de forma segura un ID de cliente a partir de su tipo y número.
+ *
+ * @param idType El tipo de identificación (ej. "V", "J").
+ * @param idNumber El número de identificación.
+ * @return Un {@link Optional} con el ID concatenado si ambos parámetros son válidos (no nulos ni vacíos),
+ * o un {@code Optional.empty()} en caso contrario.
+ */
+ public static Optional buildCustomerId(String idType, String idNumber) {
+ return Optional.ofNullable(idType)
+ .filter(type -> !type.isBlank())
+ .flatMap(type -> Optional.ofNullable(idNumber)
+ .filter(number -> !number.isBlank())
+ .map(number -> type + number));
+ }
+
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/java/application/helper/ResponseHelper.java b/bus-evaluate-customer-product/src/main/java/application/helper/ResponseHelper.java
new file mode 100644
index 0000000..5df09c1
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/application/helper/ResponseHelper.java
@@ -0,0 +1,79 @@
+package application.helper;
+
+import domain.dto.response.CustomerPositionStateResponse;
+import domain.dto.response.CustomerResponse;
+import domain.dto.response.StatusResponse;
+import domain.model.BankingProduct;
+import domain.model.ErrorCode;
+import infrastructure.service.StatusDescriptionResolver;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.ws.rs.core.Response;
+
+import java.util.List;
+
+@ApplicationScoped
+public class ResponseHelper {
+
+ private final StatusDescriptionResolver statusDescriptionResolver;
+
+ public ResponseHelper(StatusDescriptionResolver statusDescriptionResolver) {
+ this.statusDescriptionResolver = statusDescriptionResolver;
+ }
+
+ public Response buildErrorResponse(String errorCodeKey, String requestId) {
+ var errorCode = statusDescriptionResolver.getCode(errorCodeKey);
+ return this.buildErrorResponse(errorCode, requestId);
+ }
+
+ /**
+ * Construye una respuesta de error estandarizada de forma genérica.
+ * @param errorCode Objeto con el código de error interno.
+ * @return Un objeto Response estándar de JAX-RS.
+ */
+ public Response buildErrorResponse(ErrorCode errorCode, String requestId) {
+
+ var statusResponse = StatusResponse.builder()
+ .status(errorCode.status())
+ .statusCode(errorCode.statusCode())
+ .message(errorCode.description())
+ .traceId(requestId)
+ .build();
+
+ var response = new CustomerResponse(statusResponse);
+
+ int httpStatus = errorCode.getHttpStatusCode();
+
+ return Response.status(httpStatus)
+ .entity(response)
+ .build();
+ }
+
+ /**
+ * Construye una respuesta de éxito (200) CON DATOS.
+ * @param customerAccounts La lista de productos (el cuerpo de la respuesta).
+ * @param successCode El ErrorCode "200" (para los detalles del status).
+ * @param requestId El ID de traza.
+ * @return Un objeto Response estándar de JAX-RS.
+ */
+ public Response buildSuccessResponse(List customerAccounts, ErrorCode successCode, String requestId) {
+
+ var status = StatusResponse.builder()
+ .status(successCode.status())
+ .statusCode(successCode.statusCode())
+ .message(successCode.description())
+ .traceId(requestId)
+ .build();
+
+ var accounts = CustomerPositionStateResponse.builder()
+ .bankingProducts(customerAccounts)
+ .record(customerAccounts.size())
+ .build();
+
+ var response = new CustomerResponse(status, accounts);
+
+ return Response.status(successCode.getHttpStatusCode())
+ .entity(response)
+ .build();
+ }
+
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/java/application/port/input/IAppConfig.java b/bus-evaluate-customer-product/src/main/java/application/port/input/IAppConfig.java
new file mode 100644
index 0000000..a71f302
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/application/port/input/IAppConfig.java
@@ -0,0 +1,7 @@
+package application.port.input;
+
+import domain.entity.AppConfiguration;
+
+public interface IAppConfig {
+ AppConfiguration execute(String appName);
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/java/application/port/input/ICustomerProduct.java b/bus-evaluate-customer-product/src/main/java/application/port/input/ICustomerProduct.java
new file mode 100644
index 0000000..81acebc
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/application/port/input/ICustomerProduct.java
@@ -0,0 +1,13 @@
+package application.port.input;
+
+import application.port.input.command.CustomerAccountCommand;
+import domain.entity.AppConfiguration;
+import domain.model.AccountClientContext;
+import domain.model.BankingProduct;
+
+import java.util.List;
+
+public interface ICustomerProduct {
+ // TODO: mejorar
+ List execute(CustomerAccountCommand command, AccountClientContext context, AppConfiguration appConfig, String otp, Boolean isCacheable, String requestId);
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/java/application/port/input/IEncodeValue.java b/bus-evaluate-customer-product/src/main/java/application/port/input/IEncodeValue.java
new file mode 100644
index 0000000..ceff2e4
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/application/port/input/IEncodeValue.java
@@ -0,0 +1,7 @@
+package application.port.input;
+
+import application.port.input.command.EncodeValueCommand;
+
+public interface IEncodeValue {
+ String execute(EncodeValueCommand command, String requestId);
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/java/application/port/input/IRegisterSecurity.java b/bus-evaluate-customer-product/src/main/java/application/port/input/IRegisterSecurity.java
new file mode 100644
index 0000000..dd12f77
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/application/port/input/IRegisterSecurity.java
@@ -0,0 +1,7 @@
+package application.port.input;
+
+import application.port.input.command.RegisterSecurityCommand;
+
+public interface IRegisterSecurity {
+ void execute(RegisterSecurityCommand command, String requestId);
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/java/application/port/input/IServiceStatus.java b/bus-evaluate-customer-product/src/main/java/application/port/input/IServiceStatus.java
new file mode 100644
index 0000000..9ddd810
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/application/port/input/IServiceStatus.java
@@ -0,0 +1,7 @@
+package application.port.input;
+
+import application.port.input.command.ServiceStatusCommand;
+
+public interface IServiceStatus {
+ Boolean execute(ServiceStatusCommand command, String requestId);
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/java/application/port/input/IUserLogin.java b/bus-evaluate-customer-product/src/main/java/application/port/input/IUserLogin.java
new file mode 100644
index 0000000..37e4ad0
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/application/port/input/IUserLogin.java
@@ -0,0 +1,13 @@
+package application.port.input;
+
+import application.port.input.command.UserLoginCommand;
+import domain.model.UserLogin;
+
+import java.util.Optional;
+
+/**
+ * Puerto de entrada para el caso de uso de login de usuario (API privada)
+ */
+public interface IUserLogin {
+ Optional execute(UserLoginCommand command, String requestId);
+}
diff --git a/bus-evaluate-customer-product/src/main/java/application/port/input/IValidateOtp.java b/bus-evaluate-customer-product/src/main/java/application/port/input/IValidateOtp.java
new file mode 100644
index 0000000..7d88f76
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/application/port/input/IValidateOtp.java
@@ -0,0 +1,7 @@
+package application.port.input;
+
+import application.port.input.command.ValidateOtpCommand;
+
+public interface IValidateOtp {
+ Boolean isValidOtp(ValidateOtpCommand command, String requestId);
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/java/application/port/input/IValidateProfile.java b/bus-evaluate-customer-product/src/main/java/application/port/input/IValidateProfile.java
new file mode 100644
index 0000000..170f3ca
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/application/port/input/IValidateProfile.java
@@ -0,0 +1,7 @@
+package application.port.input;
+
+import application.port.input.command.ValidateProfileCommand;
+
+public interface IValidateProfile {
+ Boolean execute(ValidateProfileCommand command, String requestId);
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/java/application/port/input/command/CustomerAccountCommand.java b/bus-evaluate-customer-product/src/main/java/application/port/input/command/CustomerAccountCommand.java
new file mode 100644
index 0000000..97fb1ca
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/application/port/input/command/CustomerAccountCommand.java
@@ -0,0 +1,25 @@
+package application.port.input.command;
+
+import io.quarkus.runtime.annotations.RegisterForReflection;
+import lombok.Builder;
+import lombok.Value;
+
+@Value
+@Builder
+@RegisterForReflection
+public class CustomerAccountCommand {
+ String rif;
+ String bankCode;
+ String currencyCode;
+ String status;
+ String statusMe;
+ String productCv;
+ String product;
+ String channel;
+ String service;
+ String operator;
+ String phone;
+ String affiliationStatus;
+ String counterpartyAccount;
+ String holder;
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/java/application/port/input/command/EncodeValueCommand.java b/bus-evaluate-customer-product/src/main/java/application/port/input/command/EncodeValueCommand.java
new file mode 100644
index 0000000..04599c8
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/application/port/input/command/EncodeValueCommand.java
@@ -0,0 +1,5 @@
+package application.port.input.command;
+
+public record EncodeValueCommand(
+ String sApp
+) {}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/java/application/port/input/command/RegisterSecurityCommand.java b/bus-evaluate-customer-product/src/main/java/application/port/input/command/RegisterSecurityCommand.java
new file mode 100644
index 0000000..b875535
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/application/port/input/command/RegisterSecurityCommand.java
@@ -0,0 +1,39 @@
+package application.port.input.command;
+
+import io.quarkus.runtime.annotations.RegisterForReflection;
+import lombok.Builder;
+import lombok.Value;
+
+import java.util.Date;
+
+@Value
+@Builder
+@RegisterForReflection
+public class RegisterSecurityCommand {
+ String login;
+ Date fecHor;
+ String nacCli;
+ Integer cedRifCli;
+ String tipoProductoCli;
+ String tipoProductoBen;
+ String productoCli;
+ String codEmpresa;
+ String nacBen;
+ Integer cedBen;
+ String nombreBen;
+ String productoBen;
+ Double monto;
+ String referencia;
+ String nroDePago;
+ String desPago;
+ String objeto;
+ Integer tipoRespuesta;
+ String msgRespuesta;
+ Integer tiempoRespuesta;
+ String codFintech;
+ String cedRifFintech;
+ String tipoDispositivo;
+ String desDispositivo;
+ String ipCli;
+ String sp;
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/java/application/port/input/command/ServiceStatusCommand.java b/bus-evaluate-customer-product/src/main/java/application/port/input/command/ServiceStatusCommand.java
new file mode 100644
index 0000000..30f4c3e
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/application/port/input/command/ServiceStatusCommand.java
@@ -0,0 +1,9 @@
+package application.port.input.command;
+
+import domain.model.BankService;
+
+public record ServiceStatusCommand(
+ String applicationId,
+ String transactionId,
+ BankService bankService
+) { }
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/java/application/port/input/command/UserLoginCommand.java b/bus-evaluate-customer-product/src/main/java/application/port/input/command/UserLoginCommand.java
new file mode 100644
index 0000000..15ac2d8
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/application/port/input/command/UserLoginCommand.java
@@ -0,0 +1,14 @@
+package application.port.input.command;
+
+import io.quarkus.runtime.annotations.RegisterForReflection;
+
+/**
+ * Contiene los datos entradas para el caso de uso de login de usuario
+ * @param customerId
+ * @param customerIdType
+ */
+@RegisterForReflection
+public record UserLoginCommand(
+ String customerId,
+ String customerIdType
+) { }
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/java/application/port/input/command/ValidateOtpCommand.java b/bus-evaluate-customer-product/src/main/java/application/port/input/command/ValidateOtpCommand.java
new file mode 100644
index 0000000..29a8d3a
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/application/port/input/command/ValidateOtpCommand.java
@@ -0,0 +1,11 @@
+package application.port.input.command;
+
+public record ValidateOtpCommand(
+ String otpToken,
+ String appOrigen,
+ String login,
+ String ipAddress,
+ String canal,
+ String servicio,
+ String domain
+) { }
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/java/application/port/input/command/ValidateProfileCommand.java b/bus-evaluate-customer-product/src/main/java/application/port/input/command/ValidateProfileCommand.java
new file mode 100644
index 0000000..503c688
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/application/port/input/command/ValidateProfileCommand.java
@@ -0,0 +1,10 @@
+package application.port.input.command;
+
+/**
+ * Contiene los datos de entrada para el caso de uso de validate profile
+ */
+public record ValidateProfileCommand(
+ String customerId,
+ String ipAddress,
+ String hash
+) {}
diff --git a/bus-evaluate-customer-product/src/main/java/application/port/output/IAppConfigRepository.java b/bus-evaluate-customer-product/src/main/java/application/port/output/IAppConfigRepository.java
new file mode 100644
index 0000000..86c5b79
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/application/port/output/IAppConfigRepository.java
@@ -0,0 +1,5 @@
+package application.port.output;
+
+public interface IAppConfigRepository {
+
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/java/application/port/output/IMaintenanceCheckPort.java b/bus-evaluate-customer-product/src/main/java/application/port/output/IMaintenanceCheckPort.java
new file mode 100644
index 0000000..826a981
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/application/port/output/IMaintenanceCheckPort.java
@@ -0,0 +1,5 @@
+package application.port.output;
+
+public interface IMaintenanceCheckPort {
+ Boolean isUnderMaintenance(String requestId);
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/java/application/port/output/RegisterSecurityUseCase.java b/bus-evaluate-customer-product/src/main/java/application/port/output/RegisterSecurityUseCase.java
new file mode 100644
index 0000000..c61619e
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/application/port/output/RegisterSecurityUseCase.java
@@ -0,0 +1,35 @@
+package application.port.output;
+
+import application.exception.GatewayServiceException;
+import application.port.input.IRegisterSecurity;
+import application.port.input.command.RegisterSecurityCommand;
+import com.banesco.common.infraestructure.helpers.LoggerHelper;
+import domain.repository.IRegisterSecurityPort;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+@ApplicationScoped
+public class RegisterSecurityUseCase implements IRegisterSecurity {
+
+ private final IRegisterSecurityPort registerSecurityPort;
+
+ @Inject
+ public RegisterSecurityUseCase(IRegisterSecurityPort registerSecurityPort) {
+ this.registerSecurityPort = registerSecurityPort;
+ }
+
+ @Override
+ public void execute(RegisterSecurityCommand command, String requestId) {
+ log.debug(LoggerHelper.buildInfoPrivateRequest(requestId, "Iniciando caso de uso: Registrar traza de seguridad"));
+ try {
+ registerSecurityPort.writeTrace(command, requestId);
+ } catch (GatewayServiceException ex) {
+ log.error(LoggerHelper.buildError(requestId, "Fallo la comunicacion con el servicio de register security"));
+ throw ex;
+ } finally {
+ log.debug(LoggerHelper.buildInfoPrivateResponse(requestId, "Finalizado caso de uso: Registrar traza de seguridad"));
+ }
+ }
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/java/application/service/AccountService.java b/bus-evaluate-customer-product/src/main/java/application/service/AccountService.java
new file mode 100644
index 0000000..b3ba549
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/application/service/AccountService.java
@@ -0,0 +1,43 @@
+package application.service;
+
+import com.banesco.common.domain.model.redis.UserAccountRedis;
+import com.banesco.common.infraestructure.helpers.LoggerHelper;
+import com.banesco.common.infraestructure.repository.UserAccountRedisRepository;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+import lombok.extern.slf4j.Slf4j;
+
+import java.util.List;
+
+@Slf4j
+@ApplicationScoped
+public class AccountService {
+
+ @Inject
+ UserAccountRedisRepository userAccountRedisRepository;
+
+ /**
+ * Guarda una lista de cuentas en la caché de Redis y establece un tiempo de expiración (TTL).
+ *
+ * @param requestId ID de la solicitud para trazabilidad en los logs.
+ * @param id El identificador único del cliente para la clave del hash.
+ * @param sid El identificador de sesión para la clave del índice.
+ * @param accounts La lista de cuentas a guardar.
+ * @param ttlInSeconds El tiempo de vida (en segundos) para las claves en caché.
+ */
+ public void saveAccounts(String requestId, String id, String sid, List accounts, long ttlInSeconds) {
+ log.info(LoggerHelper.buildInfoPrivateRequest(requestId, "Iniciando guardado en Redis para el sid", sid));
+ if (accounts == null || accounts.isEmpty()) {
+ log.warn(LoggerHelper.buildInfoPrivateRequest(requestId, "Se intento guardar una lista de cuentas vacia o nula. No se realiza la operacion."));
+ return;
+ }
+
+ try {
+ userAccountRedisRepository.saveOrUpdate(sid, accounts);
+ log.info(LoggerHelper.buildInfoPrivateRequest(requestId, "Guardado exitoso en Redis para el sid", sid));
+ } catch (Exception ex) {
+ log.error(LoggerHelper.buildError(requestId, String.format("Error al intentar guardar en Redis para el sid %s. %s", sid, ex.getMessage())));
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/java/application/service/OtpValidationService.java b/bus-evaluate-customer-product/src/main/java/application/service/OtpValidationService.java
new file mode 100644
index 0000000..b13002a
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/application/service/OtpValidationService.java
@@ -0,0 +1,67 @@
+package application.service;
+
+import application.port.input.IValidateOtp;
+import application.port.input.command.ValidateOtpCommand;
+import com.banesco.common.infraestructure.helpers.LoggerHelper;
+import infrastructure.config.DomainConfig;
+import infrastructure.config.OtpConfig;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+@ApplicationScoped
+public class OtpValidationService {
+
+ private final IValidateOtp validateOtpUseCase;
+ private final OtpConfig otpConfig;
+ private final DomainConfig domainConfig;
+
+ @Inject
+ public OtpValidationService(
+ IValidateOtp validateOtpUseCase,
+ OtpConfig otpConfig,
+ DomainConfig domainConfig
+ ) {
+ this.validateOtpUseCase = validateOtpUseCase;
+ this.otpConfig = otpConfig;
+ this.domainConfig = domainConfig;
+ }
+
+ /**
+ * Punto de entrada principal para validar un OTP.
+ *
+ * @return {@code true} si el OTP es válido, de lo contrario {@code false}.
+ */
+ public boolean isOtpValid(
+ String otpToken,
+ String originApp,
+ String login,
+ String ipAddress,
+ String requestId
+ ) {
+ return validateOtpFromApiService(otpToken, originApp, login, ipAddress, requestId);
+ }
+
+ private boolean validateOtpFromApiService(
+ String otpToken,
+ String originApp,
+ String login,
+ String ipAddress,
+ String requestId
+ ) {
+ log.info(LoggerHelper.buildInfoPrivateRequest(requestId, "Validando OTP usando servicio externo"));
+
+ var otpCommand = new ValidateOtpCommand(
+ otpToken,
+ originApp,
+ login,
+ ipAddress,
+ otpConfig.channel(),
+ otpConfig.servicio(),
+ domainConfig.payment()
+ );
+
+ return validateOtpUseCase.isValidOtp(otpCommand, requestId);
+ }
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/java/application/service/PostgresConfigService.java b/bus-evaluate-customer-product/src/main/java/application/service/PostgresConfigService.java
new file mode 100644
index 0000000..6d2fedb
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/application/service/PostgresConfigService.java
@@ -0,0 +1,63 @@
+package application.service;
+
+import com.banesco.common.infraestructure.helpers.LoggerHelper;
+import com.banesco.common.infraestructure.repository.PostgresRedisRepository;
+import domain.entity.AppConfiguration;
+import infrastructure.mapper.PostgresMapper;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+import lombok.extern.slf4j.Slf4j;
+
+import java.util.Optional;
+
+@Slf4j
+@ApplicationScoped
+public class PostgresConfigService {
+
+ @Inject
+ PostgresRedisRepository postgresRedisRepository;
+
+ /**
+ * Recibe una entidad AppConfiguration de la lógica de negocio y la mapea
+ * a un CustomerAccountPostgresConfig del "commons" para guardarla en Redis.
+ */
+ public void savePostgresConfig(String configName, AppConfiguration appConfigItem, long ttlInSeconds, String requestId) {
+ log.info(LoggerHelper.buildInfoPrivateRequest(requestId, "Iniciando guardado de configuracion postgres en Redis", appConfigItem.toString()));
+
+ try {
+ var commonConfigToSave = PostgresMapper.mapToCommon(appConfigItem);
+ postgresRedisRepository.save(configName, commonConfigToSave, ttlInSeconds);
+ log.info(LoggerHelper.buildInfoPrivateRequest(requestId, "Guardado exitoso en Redis. Clave: " + configName));
+ } catch (Exception ex) {
+ log.error(LoggerHelper.buildError(requestId, "Error al intentar guardar configuracion de postgres en Redis"), ex);
+ }
+ }
+
+ /**
+ * Busca una configuración en Redis por su nombre (clave).
+ *
+ * @param configName La clave de Redis (ej: el appId).
+ * @param requestId El ID de la traza para logging.
+ * @return Un Optional con la entidad AppConfiguration si se encuentra en caché.
+ */
+ public Optional findByName(String configName, String requestId) {
+ log.info(LoggerHelper.buildInfoPrivateRequest(requestId, "Buscando configuracion en Redis. Clave: " + configName));
+ try {
+ var commonConfigOpt = postgresRedisRepository.findByName(configName);
+
+ if (commonConfigOpt.isEmpty()) {
+ log.info(LoggerHelper.buildInfoPrivateRequest(requestId, "Cache miss para clave: " + configName));
+ return Optional.empty();
+ }
+
+ log.info(LoggerHelper.buildInfoPrivateRequest(requestId, "Cache hit para clave: " + configName));
+
+ return Optional.of(PostgresMapper.mapToApp(commonConfigOpt.get()));
+
+ } catch (Exception ex) {
+ log.error(LoggerHelper.buildError(requestId, "Error al intentar leer configuracion de postgres en Redis"), ex);
+ // Si hay error en Redis, es mejor tratarlo como un "cache miss" (no encontrado)
+ return Optional.empty();
+ }
+ }
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/java/application/usecase/CustomerProductUseCase.java b/bus-evaluate-customer-product/src/main/java/application/usecase/CustomerProductUseCase.java
new file mode 100644
index 0000000..20b0acb
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/application/usecase/CustomerProductUseCase.java
@@ -0,0 +1,84 @@
+package application.usecase;
+
+import application.exception.GatewayServiceException;
+import application.port.input.ICustomerProduct;
+import application.port.input.command.CustomerAccountCommand;
+import application.service.AccountService;
+import com.banesco.common.infraestructure.helpers.LoggerHelper;
+import domain.entity.AppConfiguration;
+import domain.model.AccountClientContext;
+import domain.model.BankingProduct;
+import domain.repository.ICustomerProductPort;
+import infrastructure.mapper.BankingProductMapper;
+import infrastructure.mapper.CacheMapper;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+import lombok.extern.slf4j.Slf4j;
+import org.eclipse.microprofile.config.inject.ConfigProperty;
+
+import java.util.List;
+
+@Slf4j
+@ApplicationScoped
+public class CustomerProductUseCase implements ICustomerProduct {
+
+ private final ICustomerProductPort customerProductPort;
+ private final AccountService accountService;
+
+ @ConfigProperty(name = "app.redis.expiration")
+ long cacheTtl;
+
+ @Inject
+ public CustomerProductUseCase(
+ ICustomerProductPort customerProductPort,
+ AccountService accountService
+ ) {
+ this.customerProductPort = customerProductPort;
+ this.accountService = accountService;
+ }
+
+ @Override
+ public List execute(CustomerAccountCommand command, AccountClientContext context, AppConfiguration appConfig, String otp, Boolean isCacheable, String requestId) {
+ log.debug(LoggerHelper.buildInfoPrivateRequest(requestId, "Iniciando caso de uso: api-get-db2-accounts-client-v2"));
+ try {
+
+ var customerAccountsFromService = customerProductPort.execute(command, requestId);
+
+ // Si el servicio no devuelve nada, no hay nada que guardar ni devolver.
+ if (customerAccountsFromService == null || customerAccountsFromService.isEmpty()) {
+ log.info(LoggerHelper.buildInfoPrivateRequest(requestId, "El servicio principal no devolvio cuentas para requestId {}", requestId));
+ return List.of();
+ }
+
+ var bankingProducts = BankingProductMapper.fromCustomerAccountList(customerAccountsFromService, appConfig.isEncryptionData());
+
+ if(isCacheable) {
+ var accountsToCache = CacheMapper.toUserAccountRedisList(
+ customerAccountsFromService,
+ context.getRedisKey(),
+ context.getFinalCustomerId(),
+ otp
+ );
+
+ log.info(LoggerHelper.buildInfoPrivateRequest(
+ requestId, String.format("Guardando %s cuentas en cache", accountsToCache.size())
+ ));
+
+ accountService.saveAccounts(
+ requestId,
+ context.getFinalCustomerId(),
+ context.getRedisKey(),
+ accountsToCache,
+ cacheTtl
+ );
+ }
+ return bankingProducts;
+ } catch (GatewayServiceException ex) {
+ log.error(LoggerHelper.buildError(requestId, "Fallo la comunicacion con el servicio de api-get-db2-accounts-client-v2"));
+ throw ex;
+ } finally {
+ log.debug(LoggerHelper.buildInfoPrivateRequest(requestId, "Finalizado caso de uso: api-get-db2-accounts-client-v2"));
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/java/application/usecase/EncodeValueUseCase.java b/bus-evaluate-customer-product/src/main/java/application/usecase/EncodeValueUseCase.java
new file mode 100644
index 0000000..b4fc9cf
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/application/usecase/EncodeValueUseCase.java
@@ -0,0 +1,36 @@
+package application.usecase;
+
+import application.exception.GatewayServiceException;
+import application.port.input.IEncodeValue;
+import application.port.input.command.EncodeValueCommand;
+import com.banesco.common.infraestructure.helpers.LoggerHelper;
+import domain.repository.IEncodeValuePort;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+@ApplicationScoped
+public class EncodeValueUseCase implements IEncodeValue {
+
+ private final IEncodeValuePort encodeValuePort;
+
+ @Inject
+ public EncodeValueUseCase(IEncodeValuePort encodeValuePort) {
+ this.encodeValuePort = encodeValuePort;
+ }
+
+ @Override
+ public String execute(EncodeValueCommand command, String requestId) {
+ log.debug(LoggerHelper.buildInfoPrivateRequest(requestId, "Iniciando caso de uso: Encode Value"));
+ try {
+ return encodeValuePort.execute(command, requestId);
+ } catch (GatewayServiceException ex) {
+ log.error(LoggerHelper.buildError(requestId, "Fallo la comunicacion con el servicio de Encode Value: " + ex.getMessage()));
+ throw ex;
+ } finally {
+ log.debug(LoggerHelper.buildInfoPrivateRequest(requestId, "Finalizado caso de uso: Encode Value"));
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/java/application/usecase/GetAppConfigUseCase.java b/bus-evaluate-customer-product/src/main/java/application/usecase/GetAppConfigUseCase.java
new file mode 100644
index 0000000..f7e06a6
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/application/usecase/GetAppConfigUseCase.java
@@ -0,0 +1,28 @@
+package application.usecase;
+
+import application.port.input.IAppConfig;
+import domain.entity.AppConfiguration;
+import domain.repository.AppConfigRepository;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+import jakarta.ws.rs.NotFoundException;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+@ApplicationScoped
+public class GetAppConfigUseCase implements IAppConfig {
+
+ private final AppConfigRepository appConfigRepository;
+
+ @Inject
+ public GetAppConfigUseCase(AppConfigRepository appConfigRepository) {
+ this.appConfigRepository = appConfigRepository;
+ }
+
+ public AppConfiguration execute(String appName) {
+
+ return appConfigRepository.findConfigByAppName(appName)
+ .orElseThrow(() -> new NotFoundException("Configuracion no encontrada para la app: " + appName));
+ }
+
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/java/application/usecase/ServiceStatusUseCase.java b/bus-evaluate-customer-product/src/main/java/application/usecase/ServiceStatusUseCase.java
new file mode 100644
index 0000000..6d6e592
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/application/usecase/ServiceStatusUseCase.java
@@ -0,0 +1,72 @@
+package application.usecase;
+
+import application.exception.GatewayServiceException;
+import application.port.input.IServiceStatus;
+import application.port.input.command.ServiceStatusCommand;
+import application.port.output.IMaintenanceCheckPort;
+import com.banesco.common.infraestructure.helpers.JsonHelper;
+import com.banesco.common.infraestructure.helpers.LoggerHelper;
+import domain.repository.IStatusPort;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+import lombok.extern.slf4j.Slf4j;
+
+
+@Slf4j
+@ApplicationScoped
+public class ServiceStatusUseCase implements IServiceStatus {
+
+ private static final String INACTIVE_STATUS = "I";
+
+ private final IStatusPort statusApiPort;
+ private final IMaintenanceCheckPort maintenancePort;
+
+ @Inject
+ public ServiceStatusUseCase(
+ IStatusPort statusApiService,
+ IMaintenanceCheckPort maintenancePort
+ ) {
+ this.statusApiPort = statusApiService;
+ this.maintenancePort = maintenancePort;
+ }
+
+ /**
+ * Determina el estado de disponibilidad de un servicio, devolviendo true si está activo y false si no lo está.
+ *
+ * @param command El objeto de comando que contiene los datos necesarios para la consulta, como el ID de aplicación y transacción.
+ * @param requestId Un identificador único de la solicitud, utilizado principalmente para la trazabilidad en los logs.
+ * @return {@code true} si el servicio se considera activo; {@code false} si está inactivo, en mantenimiento o si la verificación falló.
+ */
+ @Override
+ public Boolean execute(ServiceStatusCommand command, String requestId) {
+ log.debug(LoggerHelper.buildInfoPrivateRequest(requestId, "Iniciando caso de uso: Service Status"));
+
+ // Si no estamos en ventana de mantenimiento, el servicio está activo.
+ if(!maintenancePort.isUnderMaintenance(requestId)) {
+ log.info(LoggerHelper.buildInfoPrivateRequest(requestId, "No estamos en ventana de mantenimiento. Servicio ACTIVO"));
+ return true;
+ }
+
+ log.info(LoggerHelper.buildInfoPrivateRequest(requestId, "En ventana de mantenimiento, consultando estado remoto..."));
+
+ try {
+ // Si estamos en horario de validacion, consultamos el estado remoto.
+ var remoteStatus = statusApiPort.getServiceStatus(command, requestId);
+
+ var isActive = remoteStatus != null && !INACTIVE_STATUS.equalsIgnoreCase(remoteStatus.getStatus());
+
+ if (isActive) {
+ log.info(LoggerHelper.buildInfoPrivateRequest(requestId, "El servicio externo confirmo estado ACTIVO"));
+ } else {
+ log.warn(LoggerHelper.buildInfoPrivateRequest(requestId, "El servicio externo esta nulo o en estado INACTIVO"));
+ }
+ return isActive;
+ } catch (GatewayServiceException ex) {
+ log.error(LoggerHelper.buildError(requestId, "Fallo la comunicacion con el servicio de service status"));
+ throw ex;
+ } finally {
+ log.debug(LoggerHelper.buildInfoPrivateRequest(requestId, "Finalizado caso de uso: Service Status"));
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/java/application/usecase/UserLoginUseCase.java b/bus-evaluate-customer-product/src/main/java/application/usecase/UserLoginUseCase.java
new file mode 100644
index 0000000..f6131f6
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/application/usecase/UserLoginUseCase.java
@@ -0,0 +1,52 @@
+package application.usecase;
+
+import application.exception.GatewayServiceException;
+import application.port.input.IUserLogin;
+import application.port.input.command.UserLoginCommand;
+import com.banesco.common.infraestructure.helpers.LoggerHelper;
+import domain.model.UserLogin;
+import domain.repository.IUserLoginPort;
+import jakarta.enterprise.context.ApplicationScoped;
+import lombok.extern.slf4j.Slf4j;
+
+import java.util.Optional;
+
+@Slf4j
+@ApplicationScoped
+public class UserLoginUseCase implements IUserLogin {
+
+ private final IUserLoginPort userLoginApiPort;
+
+ public UserLoginUseCase(IUserLoginPort userLoginApiPort) {
+ this.userLoginApiPort = userLoginApiPort;
+ }
+
+ /**
+ * Orquesta la ejecución del caso de uso de login de usuario
+ * Encapsula la lógica de negocio, incluyendo la generación de códigos de referencia
+ * y el manejo de fallos de comunicación con el servicio externo.
+ * @param command El comando con los datos de entrada para el login.
+ * @param requestId El ID de la solicitud para trazabilidad.
+ * @return Un {@link Optional} con el objeto {@link UserLogin} si el login fue exitoso,
+ * o un {@code Optional.empty()} si falló por cualquier motivo.
+ */
+ @Override
+ public Optional execute(UserLoginCommand command, String requestId) {
+ log.debug(LoggerHelper.buildInfoPrivateRequest(requestId, "Iniciando caso de uso: User Login"));
+ try {
+ var result = userLoginApiPort.execute(command, requestId);
+
+ if(result == null) {
+ log.error(LoggerHelper.buildError(requestId, " El login no fue exitoso segun la respuesta del servicio"));
+ return Optional.empty();
+ }
+
+ log.debug(LoggerHelper.buildInfoPrivateResponse(requestId, "Finalizado caso de uso: User Login"));
+ return Optional.of(result);
+ } catch (GatewayServiceException ex) {
+ log.error(LoggerHelper.buildError(requestId, "Fallo la comunicacion con el servicio de User Login"));
+ throw ex;
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/java/application/usecase/ValidateOtpUseCase.java b/bus-evaluate-customer-product/src/main/java/application/usecase/ValidateOtpUseCase.java
new file mode 100644
index 0000000..90c00ad
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/application/usecase/ValidateOtpUseCase.java
@@ -0,0 +1,57 @@
+package application.usecase;
+
+import application.exception.GatewayServiceException;
+import application.exception.InvalidTokenException;
+import application.port.input.IValidateOtp;
+import application.port.input.command.ValidateOtpCommand;
+import com.banesco.common.infraestructure.helpers.LoggerHelper;
+import domain.repository.IOtpPort;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+import lombok.extern.slf4j.Slf4j;
+import org.jboss.resteasy.reactive.RestResponse;
+
+@Slf4j
+@ApplicationScoped
+public class ValidateOtpUseCase implements IValidateOtp {
+
+ private final IOtpPort otpApiPort;
+
+ @Inject
+ public ValidateOtpUseCase(IOtpPort otpApiService) {
+ this.otpApiPort = otpApiService;
+ }
+
+ /**
+ * Orquesta la validación de un token OTP.
+ * Encapsula la lógica de negocio para determinar si un OTP es válido,
+ * manejando también los fallos de comunicación con el servicio externo.
+ *
+ * @param command El comando con los datos necesarios para validar el OTP.
+ * @param requestId El ID de la solicitud para trazabilidad.
+ * @return {@code true} si el servicio externo responde con un status HTTP 200 (OK),
+ * {@code false} en cualquier otro caso (respuesta de error o fallo de comunicación).
+ */
+ @Override
+ public Boolean isValidOtp(ValidateOtpCommand command, String requestId) {
+ log.debug(LoggerHelper.buildInfoPrivateRequest(requestId, "Iniciando caso de uso: Validate OTP"));
+
+ try {
+ var apiResponse = otpApiPort.validateOtp(command, requestId);
+ var isValid = apiResponse.getStatus() == RestResponse.Status.OK.getStatusCode();
+
+ log.info("Validacion de OTP para requestId {}. Resultado: {}. Codigo de estado recibido: {}",
+ requestId, isValid, apiResponse.getStatus());
+
+ return isValid;
+ } catch(InvalidTokenException ex) {
+ return false;
+ } catch(GatewayServiceException ex) {
+ log.error(LoggerHelper.buildError(requestId, "Fallo la comunicacion con el servicio de validate otp"));
+ throw ex;
+ } finally {
+ log.debug(LoggerHelper.buildInfoPrivateResponse(requestId, "Finalizado caso de uso: Validate OTP"));
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/java/application/usecase/ValidateProfileUseCase.java b/bus-evaluate-customer-product/src/main/java/application/usecase/ValidateProfileUseCase.java
new file mode 100644
index 0000000..39b3c47
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/application/usecase/ValidateProfileUseCase.java
@@ -0,0 +1,81 @@
+package application.usecase;
+
+import application.exception.GatewayServiceException;
+import application.port.input.IValidateProfile;
+import application.port.input.command.ValidateProfileCommand;
+import com.banesco.common.infraestructure.helpers.LoggerHelper;
+import domain.model.ClientProfile;
+import domain.repository.IValidateProfilePort;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+import lombok.extern.slf4j.Slf4j;
+import java.util.Map;
+import java.util.Optional;
+
+@Slf4j
+@ApplicationScoped
+public class ValidateProfileUseCase implements IValidateProfile {
+
+ private final IValidateProfilePort validateProfilePort;
+
+ @Inject
+ public ValidateProfileUseCase(IValidateProfilePort validateProfilePort) {
+ this.validateProfilePort = validateProfilePort;
+ }
+
+ /**
+ * Orquesta la validación del perfil de un cliente.
+ * Determina si un perfil es válido según la respuesta de un servicio externo,
+ * navegando de forma segura a través de una estructura de datos anidada.
+ *
+ * @param command El comando con los datos de entrada para la validación.
+ * @param requestId El ID de la solicitud para trazabilidad.
+ * @return {@code true} si el perfil es válido (status = 0), {@code false} en cualquier otro caso.
+ */
+ @Override
+ public Boolean execute(ValidateProfileCommand command, String requestId) {
+ log.debug(LoggerHelper.buildInfoPrivateRequest(requestId, "Iniciando caso de uso: Validate Profile"));
+ try {
+ var result = validateProfilePort.execute(command, requestId);
+
+ // La validación completa se realiza en una única cadena funcional y segura.
+ var isValidProfile = Optional.ofNullable(result)
+ .filter(profile -> !profile.getHasError())
+ .flatMap(this::extractStatusFromData)
+ .map(status -> status == 0)
+ .orElse(false);
+
+ log.info("{} - Validacion de perfil completada ¿Es valido?: {}", requestId, isValidProfile);
+ return isValidProfile;
+
+ } catch (GatewayServiceException ex) {
+ log.error(LoggerHelper.buildError(requestId, "Fallo la comunicacion con el servicio de Validate Profile"));
+ throw ex;
+ } finally {
+ log.debug(LoggerHelper.buildInfoPrivateRequest(requestId, "Finalizado caso de uso: Validate Profile"));
+ }
+ }
+
+ /**
+ * Helper privado para navegar de forma segura la estructura de Mapas anidada
+ * y extraer el valor de 'stausu'.
+ *
+ * @param profile El objeto ClientProfile que contiene los datos.
+ * @return Un Optional con el valor de 'stausu' si se encuentra.
+ */
+ @SuppressWarnings("unchecked")
+ private Optional extractStatusFromData(ClientProfile profile) {
+ return Optional.ofNullable(profile.getData())
+ .filter(Map.class::isInstance).map(m -> (Map) m)
+ .map(data -> data.get("banesco"))
+ .filter(Map.class::isInstance).map(m -> (Map) m)
+ .map(banesco -> banesco.get("respuesta"))
+ .filter(Map.class::isInstance).map(m -> (Map) m)
+ .map(res -> res.get("respuesta"))
+ .filter(Map.class::isInstance).map(m -> (Map) m)
+ .map(respuesta -> respuesta.get("stausu"))
+ .filter(Integer.class::isInstance)
+ .map(Integer.class::cast);
+ }
+
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/java/domain/dto/request/CustomerRequest.java b/bus-evaluate-customer-product/src/main/java/domain/dto/request/CustomerRequest.java
new file mode 100644
index 0000000..bcefbc2
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/domain/dto/request/CustomerRequest.java
@@ -0,0 +1,60 @@
+package domain.dto.request;
+
+import io.quarkus.runtime.annotations.RegisterForReflection;
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.NotNull;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+@RegisterForReflection
+public class CustomerRequest {
+
+ @Valid
+ @NotNull(message = "VDE01")
+ private CustomerPositionState customerPositionState;
+
+ @Valid
+ @NotNull(message = "VDE01")
+ private Device device;
+
+ private TokenAssignmentInstance tokenAssignmentInstance;
+
+ @Data
+ public static class CustomerPositionState {
+ private CustomerReference customerReference;
+
+ @Data
+ public static class CustomerReference {
+
+ /**
+ * Valida que sea una única letra del conjunto [V, E, P, G, J].
+ */
+ private String customerIdType;
+
+ /**
+ * Valida que sea numérico con un máximo de 9 dígitos.
+ */
+ private String customerId;
+
+ }
+
+ }
+
+ @Data
+ public static class Device {
+ private String deviceType;
+ private String deviceDescription;
+ private String deviceIp;
+ private String deviceSessionReference;
+ }
+
+ @Data
+ public static class TokenAssignmentInstance {
+ private String tokenIdentificationCode;
+ }
+
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/java/domain/dto/response/CustomerPositionStateResponse.java b/bus-evaluate-customer-product/src/main/java/domain/dto/response/CustomerPositionStateResponse.java
new file mode 100644
index 0000000..4a1a529
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/domain/dto/response/CustomerPositionStateResponse.java
@@ -0,0 +1,16 @@
+package domain.dto.response;
+
+import domain.model.BankingProduct;
+import io.quarkus.runtime.annotations.RegisterForReflection;
+import lombok.Builder;
+import lombok.Value;
+
+import java.util.List;
+
+@Value
+@Builder
+@RegisterForReflection
+public class CustomerPositionStateResponse {
+ int record;
+ List bankingProducts;
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/java/domain/dto/response/CustomerResponse.java b/bus-evaluate-customer-product/src/main/java/domain/dto/response/CustomerResponse.java
new file mode 100644
index 0000000..70840e2
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/domain/dto/response/CustomerResponse.java
@@ -0,0 +1,23 @@
+package domain.dto.response;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.quarkus.runtime.annotations.RegisterForReflection;
+import lombok.Getter;
+
+@Getter
+@RegisterForReflection
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class CustomerResponse {
+ StatusResponse statusResponse;
+ CustomerPositionStateResponse customerPositionState;
+
+ public CustomerResponse(StatusResponse statusResponse, CustomerPositionStateResponse customerPositionState) {
+ this.statusResponse = statusResponse;
+ this.customerPositionState = customerPositionState;
+ }
+
+ public CustomerResponse(StatusResponse statusResponse) {
+ this.statusResponse = statusResponse;
+ }
+
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/java/domain/dto/response/StatusResponse.java b/bus-evaluate-customer-product/src/main/java/domain/dto/response/StatusResponse.java
new file mode 100644
index 0000000..362b096
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/domain/dto/response/StatusResponse.java
@@ -0,0 +1,15 @@
+package domain.dto.response;
+
+import io.quarkus.runtime.annotations.RegisterForReflection;
+import lombok.Builder;
+import lombok.Value;
+
+@Value
+@Builder
+@RegisterForReflection
+public class StatusResponse {
+ String status;
+ String statusCode;
+ String message;
+ String traceId;
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/java/domain/entity/AppConfiguration.java b/bus-evaluate-customer-product/src/main/java/domain/entity/AppConfiguration.java
new file mode 100644
index 0000000..8734bb8
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/domain/entity/AppConfiguration.java
@@ -0,0 +1,31 @@
+package domain.entity;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import domain.model.Configuration;
+import io.quarkus.runtime.annotations.RegisterForReflection;
+import lombok.Getter;
+import lombok.Setter;
+import lombok.ToString;
+
+@Getter
+@Setter
+@ToString
+@RegisterForReflection
+public class AppConfiguration {
+
+ @JsonIgnore
+ private Long appId;
+
+ private String appName;
+
+ private String apiName;
+
+ private String type;
+
+ private String event;
+
+ private boolean encryptionData;
+
+ private Configuration configuration;
+
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/java/domain/entity/ConfigItem.java b/bus-evaluate-customer-product/src/main/java/domain/entity/ConfigItem.java
new file mode 100644
index 0000000..dc49715
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/domain/entity/ConfigItem.java
@@ -0,0 +1,33 @@
+package domain.entity;
+
+import io.quarkus.runtime.annotations.RegisterForReflection;
+import lombok.Getter;
+import lombok.Setter;
+import lombok.ToString;
+
+@Getter
+@Setter
+@ToString
+@RegisterForReflection
+public class ConfigItem {
+ boolean cacheable;
+ String bankCode;
+ String currencyCode;
+ String status;
+ String statusMe;
+ String productCv;
+ String product;
+ String channel;
+ String service;
+ String operator;
+ String phone;
+ String affiliationStatus;
+ String counterpartyAccount;
+ String holder;
+ String codProduct;
+ String statusOperation;
+ String statusRestriction;
+ String statusOperationRestriction;
+
+ public ConfigItem() {}
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/java/domain/model/AccountClientContext.java b/bus-evaluate-customer-product/src/main/java/domain/model/AccountClientContext.java
new file mode 100644
index 0000000..5d4ad68
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/domain/model/AccountClientContext.java
@@ -0,0 +1,41 @@
+package domain.model;
+
+import domain.dto.request.CustomerRequest;
+import io.quarkus.runtime.annotations.RegisterForReflection;
+import lombok.Data;
+
+import java.util.Optional;
+
+@Data
+@RegisterForReflection
+public class AccountClientContext {
+ // Datos de entrada
+ final long startExecution;
+ final String requestId;
+ final CustomerRequest request;
+ final String appId, fintechId, finalCustomerId, redisKey;
+ String username;
+
+ Optional finalError = Optional.empty();
+
+ public AccountClientContext(long startExecution, String requestId, CustomerRequest request, String... headers) {
+ var customer = request.getCustomerPositionState().getCustomerReference();
+ var sid = request.getDevice().getDeviceSessionReference();
+
+ this.startExecution = startExecution;
+ this.requestId = requestId;
+ this.request = request;
+
+ this.appId = headers[0];
+ this.fintechId = headers[1];
+ this.finalCustomerId = customer.getCustomerIdType().concat(customer.getCustomerId());
+
+ this.username = Optional.ofNullable(headers[2]).orElse(this.finalCustomerId);
+
+ this.redisKey = Optional.ofNullable(sid)
+ .filter(s -> !s.isBlank())
+ .map(s -> this.finalCustomerId + sid)
+ .orElse(this.finalCustomerId);
+ }
+
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/java/domain/model/BankService.java b/bus-evaluate-customer-product/src/main/java/domain/model/BankService.java
new file mode 100644
index 0000000..2c2f4fa
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/domain/model/BankService.java
@@ -0,0 +1,10 @@
+package domain.model;
+
+import io.quarkus.runtime.annotations.RegisterForReflection;
+
+@RegisterForReflection
+public record BankService(
+ String bankCode,
+ String serviceCode,
+ String eventCode
+) {}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/java/domain/model/BankingProduct.java b/bus-evaluate-customer-product/src/main/java/domain/model/BankingProduct.java
new file mode 100644
index 0000000..8c6ff25
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/domain/model/BankingProduct.java
@@ -0,0 +1,17 @@
+package domain.model;
+
+import io.quarkus.runtime.annotations.RegisterForReflection;
+import lombok.Builder;
+import lombok.Value;
+
+import java.math.BigDecimal;
+
+@Value
+@Builder
+@RegisterForReflection
+public class BankingProduct {
+ String productNumber;
+ String productType;
+ BigDecimal balance;
+ String currency;
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/java/domain/model/ClientProfile.java b/bus-evaluate-customer-product/src/main/java/domain/model/ClientProfile.java
new file mode 100644
index 0000000..4a79d99
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/domain/model/ClientProfile.java
@@ -0,0 +1,15 @@
+package domain.model;
+
+import io.quarkus.runtime.annotations.RegisterForReflection;
+import lombok.Builder;
+import lombok.Value;
+
+@Value
+@Builder
+@RegisterForReflection
+public class ClientProfile {
+ Boolean hasError;
+ String errorMessage;
+ String errorCode;
+ Object data;
+}
diff --git a/bus-evaluate-customer-product/src/main/java/domain/model/Configuration.java b/bus-evaluate-customer-product/src/main/java/domain/model/Configuration.java
new file mode 100644
index 0000000..f2a421f
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/domain/model/Configuration.java
@@ -0,0 +1,19 @@
+package domain.model;
+
+import domain.entity.ConfigItem;
+import io.quarkus.runtime.annotations.RegisterForReflection;
+import lombok.Getter;
+import lombok.Setter;
+import lombok.ToString;
+
+import java.util.Map;
+
+@Getter
+@Setter
+@ToString
+@RegisterForReflection
+public class Configuration {
+ private Map validationRules;
+ private Map aliases;
+ private Map configuration;
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/java/domain/model/CustomerAccount.java b/bus-evaluate-customer-product/src/main/java/domain/model/CustomerAccount.java
new file mode 100644
index 0000000..4e029a9
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/domain/model/CustomerAccount.java
@@ -0,0 +1,36 @@
+package domain.model;
+
+import io.quarkus.runtime.annotations.RegisterForReflection;
+import lombok.Builder;
+import lombok.Value;
+
+import java.math.BigDecimal;
+
+@Value
+@Builder
+@RegisterForReflection
+public class CustomerAccount {
+ String banco;
+ String cuenta;
+ String producto;
+ String descProd;
+ String claseCta;
+ String tipoCta;
+ String moneda;
+ BigDecimal saldoDisponible;
+ String estatus;
+ String statusMe;
+ String titular;
+ BigDecimal maximumPerDay;
+ BigDecimal maximumPerTransaction;
+ BigDecimal maximumPerMonth;
+ BigDecimal maximumMonthlyFee;
+ BigDecimal minimumPerTransaction;
+ String operator;
+ String phone;
+ String channel;
+ String service;
+ String affiliationStatus;
+ String codeError;
+ String descError;
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/java/domain/model/ErrorCode.java b/bus-evaluate-customer-product/src/main/java/domain/model/ErrorCode.java
new file mode 100644
index 0000000..76bc51c
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/domain/model/ErrorCode.java
@@ -0,0 +1,28 @@
+package domain.model;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import io.quarkus.runtime.annotations.RegisterForReflection;
+
+/**
+ * Representa un código de error de la aplicación.
+ */
+@RegisterForReflection
+public record ErrorCode(
+ @JsonProperty("backend_code") String backendCode,
+ @JsonProperty("http_code") String httpCode,
+ @JsonProperty("status_code") String statusCode,
+ @JsonProperty("description") String description,
+ @JsonProperty("status") String status
+) {
+ /**
+ * Convierte de forma segura el 'httpCode' a un código de estado HTTP numérico.
+ * @return El código de estado HTTP como un entero.
+ */
+ public int getHttpStatusCode() {
+ try {
+ return Integer.parseInt(this.httpCode);
+ } catch (NumberFormatException e) {
+ return 500;
+ }
+ }
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/java/domain/model/FoundAccountInfo.java b/bus-evaluate-customer-product/src/main/java/domain/model/FoundAccountInfo.java
new file mode 100644
index 0000000..45be319
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/domain/model/FoundAccountInfo.java
@@ -0,0 +1,11 @@
+package domain.model;
+
+import io.quarkus.runtime.annotations.RegisterForReflection;
+
+@RegisterForReflection
+public record FoundAccountInfo(
+ String clearAccount,
+ String username,
+ String otp,
+ String accountType
+) {}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/java/domain/model/Otp.java b/bus-evaluate-customer-product/src/main/java/domain/model/Otp.java
new file mode 100644
index 0000000..566a2d6
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/domain/model/Otp.java
@@ -0,0 +1,15 @@
+package domain.model;
+
+import com.banesco.xmlns.enterpriseobjects.msgrshdr.MsgRsHdr;
+import infrastructure.client.otp.dto.response.Status;
+import io.quarkus.runtime.annotations.RegisterForReflection;
+import lombok.Builder;
+import lombok.Value;
+
+@Value
+@Builder
+@RegisterForReflection
+public class Otp {
+ MsgRsHdr msgRsHdr;
+ Status status;
+}
diff --git a/bus-evaluate-customer-product/src/main/java/domain/model/ServiceStatus.java b/bus-evaluate-customer-product/src/main/java/domain/model/ServiceStatus.java
new file mode 100644
index 0000000..e1ec746
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/domain/model/ServiceStatus.java
@@ -0,0 +1,12 @@
+package domain.model;
+
+import io.quarkus.runtime.annotations.RegisterForReflection;
+import lombok.Builder;
+import lombok.Value;
+
+@Value
+@Builder
+@RegisterForReflection
+public class ServiceStatus {
+ String status;
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/java/domain/model/UserLogin.java b/bus-evaluate-customer-product/src/main/java/domain/model/UserLogin.java
new file mode 100644
index 0000000..ceb4874
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/domain/model/UserLogin.java
@@ -0,0 +1,14 @@
+package domain.model;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.quarkus.runtime.annotations.RegisterForReflection;
+import lombok.Builder;
+
+@Builder
+@RegisterForReflection
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public record UserLogin(
+ String usernameBol,
+ String usernameBOLE,
+ String idNumber
+) { }
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/java/domain/repository/AppConfigRepository.java b/bus-evaluate-customer-product/src/main/java/domain/repository/AppConfigRepository.java
new file mode 100644
index 0000000..70f1524
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/domain/repository/AppConfigRepository.java
@@ -0,0 +1,9 @@
+package domain.repository;
+
+import domain.entity.AppConfiguration;
+
+import java.util.Optional;
+
+public interface AppConfigRepository {
+ Optional findConfigByAppName(String appName);
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/java/domain/repository/ICustomerProductPort.java b/bus-evaluate-customer-product/src/main/java/domain/repository/ICustomerProductPort.java
new file mode 100644
index 0000000..19e66b7
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/domain/repository/ICustomerProductPort.java
@@ -0,0 +1,10 @@
+package domain.repository;
+
+import application.port.input.command.CustomerAccountCommand;
+import domain.model.CustomerAccount;
+
+import java.util.List;
+
+public interface ICustomerProductPort {
+ List execute(CustomerAccountCommand command, String requestId);
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/java/domain/repository/IEncodeValuePort.java b/bus-evaluate-customer-product/src/main/java/domain/repository/IEncodeValuePort.java
new file mode 100644
index 0000000..ee9a050
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/domain/repository/IEncodeValuePort.java
@@ -0,0 +1,7 @@
+package domain.repository;
+
+import application.port.input.command.EncodeValueCommand;
+
+public interface IEncodeValuePort {
+ String execute(EncodeValueCommand command, String requestId);
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/java/domain/repository/IOtpPort.java b/bus-evaluate-customer-product/src/main/java/domain/repository/IOtpPort.java
new file mode 100644
index 0000000..9659554
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/domain/repository/IOtpPort.java
@@ -0,0 +1,8 @@
+package domain.repository;
+
+import application.port.input.command.ValidateOtpCommand;
+import org.jboss.resteasy.reactive.RestResponse;
+
+public interface IOtpPort {
+ RestResponse validateOtp(ValidateOtpCommand command, String requestId);
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/java/domain/repository/IRegisterSecurityPort.java b/bus-evaluate-customer-product/src/main/java/domain/repository/IRegisterSecurityPort.java
new file mode 100644
index 0000000..efc08a8
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/domain/repository/IRegisterSecurityPort.java
@@ -0,0 +1,7 @@
+package domain.repository;
+
+import application.port.input.command.RegisterSecurityCommand;
+
+public interface IRegisterSecurityPort {
+ void writeTrace(RegisterSecurityCommand request, String requestId);
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/java/domain/repository/IStatusPort.java b/bus-evaluate-customer-product/src/main/java/domain/repository/IStatusPort.java
new file mode 100644
index 0000000..b0dcca0
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/domain/repository/IStatusPort.java
@@ -0,0 +1,8 @@
+package domain.repository;
+
+import application.port.input.command.ServiceStatusCommand;
+import domain.model.ServiceStatus;
+
+public interface IStatusPort {
+ ServiceStatus getServiceStatus(ServiceStatusCommand query, String requestId);
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/java/domain/repository/IUserLoginPort.java b/bus-evaluate-customer-product/src/main/java/domain/repository/IUserLoginPort.java
new file mode 100644
index 0000000..b66c50b
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/domain/repository/IUserLoginPort.java
@@ -0,0 +1,11 @@
+package domain.repository;
+
+import application.port.input.command.UserLoginCommand;
+import domain.model.UserLogin;
+
+public interface IUserLoginPort {
+ UserLogin execute(
+ UserLoginCommand command,
+ String requestId
+ );
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/java/domain/repository/IValidateProfilePort.java b/bus-evaluate-customer-product/src/main/java/domain/repository/IValidateProfilePort.java
new file mode 100644
index 0000000..f513f4d
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/domain/repository/IValidateProfilePort.java
@@ -0,0 +1,8 @@
+package domain.repository;
+
+import application.port.input.command.ValidateProfileCommand;
+import domain.model.ClientProfile;
+
+public interface IValidateProfilePort {
+ ClientProfile execute(ValidateProfileCommand command, String requestId);
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/adapter/input/rest/AppConfigResource.java b/bus-evaluate-customer-product/src/main/java/infrastructure/adapter/input/rest/AppConfigResource.java
new file mode 100644
index 0000000..a5ef84d
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/infrastructure/adapter/input/rest/AppConfigResource.java
@@ -0,0 +1,111 @@
+package infrastructure.adapter.input.rest;
+
+import application.helper.ResponseHelper;
+import application.port.input.IAppConfig;
+import com.banesco.common.infraestructure.helpers.LoggerHelper;
+import domain.dto.response.CustomerResponse;
+import domain.entity.AppConfiguration;
+import infrastructure.context.TraceIdContext;
+import infrastructure.service.StatusDescriptionResolver;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.NotFoundException;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.Response;
+import lombok.extern.slf4j.Slf4j;
+import org.eclipse.microprofile.openapi.annotations.Operation;
+import org.eclipse.microprofile.openapi.annotations.enums.ParameterIn;
+import org.eclipse.microprofile.openapi.annotations.enums.SchemaType;
+import org.eclipse.microprofile.openapi.annotations.media.Content;
+import org.eclipse.microprofile.openapi.annotations.media.ExampleObject;
+import org.eclipse.microprofile.openapi.annotations.media.Schema;
+import org.eclipse.microprofile.openapi.annotations.parameters.Parameter;
+import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
+import org.eclipse.microprofile.openapi.annotations.responses.APIResponses;
+import org.jboss.resteasy.reactive.RestPath;
+
+@Slf4j
+@Path("/customer")
+@Produces(MediaType.APPLICATION_JSON)
+public class AppConfigResource {
+
+ // --- CASOS DE USO Y SERVICIOS ---
+ private final IAppConfig appConfigUseCase;
+
+ // --- DEPENDENCIAS ADICIONALES ---
+ private final ResponseHelper responseHelper;
+ private final TraceIdContext traceIdContext;
+
+ public AppConfigResource(
+ IAppConfig appConfigUseCase,
+ ResponseHelper responseHelper,
+ TraceIdContext traceIdContext,
+ StatusDescriptionResolver statusDescriptionResolver
+ ) {
+ this.appConfigUseCase = appConfigUseCase;
+ this.responseHelper = responseHelper;
+ this.traceIdContext = traceIdContext;
+ }
+
+ @GET
+ @Path("/config/{appName}")
+ @Operation(
+ summary = "Obtener Configuración de Aplicación",
+ description = "Obtiene la configuración JSON completa para un 'appName' (appId) específico. Este endpoint se usa para testing o recarga manual."
+ )
+ @Parameter(
+ name = "appName",
+ in = ParameterIn.PATH,
+ description = "El 'appName' (appId) que se usará para buscar la configuración.",
+ required = true,
+ schema = @Schema(type = SchemaType.STRING),
+ example = "abc123"
+ )
+ @APIResponses(value = {
+ @APIResponse(
+ responseCode = "200",
+ description = "Configuración encontrada.",
+ content = @Content(
+ mediaType = MediaType.APPLICATION_JSON,
+ schema = @Schema(implementation = AppConfiguration.class),
+ examples = {
+ @ExampleObject(
+ name = "Configuración encontrada",
+ externalValue = "/openapi-examples/response/app-config.json"
+ )
+ }
+ )
+ ),
+ @APIResponse(
+ responseCode = "400",
+ description = "Configuración no encontrada. El 'appName' no existe en la base de datos.",
+ content = @Content(
+ mediaType = MediaType.APPLICATION_JSON,
+ schema = @Schema(implementation = CustomerResponse.class),
+ examples = {
+ @ExampleObject(
+ name = "Configuración no encontrada",
+ summary = "VRN_CONFIG_NOT_FOUND",
+ externalValue = "/openapi-examples/response/config-not-found.json"
+ )
+ }
+ )
+ )
+ })
+ public Response getConfig(@RestPath String appName) {
+ final var requestId = traceIdContext.getTraceId();
+
+ try {
+ var config = appConfigUseCase.execute(appName);
+ log.info(LoggerHelper.buildInfoPrivateRequest(requestId, "Configuracion encontrada para appName: " + appName));
+ log.info(LoggerHelper.buildInfoPrivateRequest(requestId, "Configuracion: " + config));
+
+ return Response.ok(config).build();
+
+ } catch (NotFoundException ex) {
+ log.warn(LoggerHelper.buildError(requestId, "Configuracion no encontrada (VRN_CONFIG_NOT_FOUND) para appName: " + appName));
+ return responseHelper.buildErrorResponse("VRN_CONFIG_NOT_FOUND", requestId);
+ }
+ }
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/adapter/input/rest/CustomerResource.java b/bus-evaluate-customer-product/src/main/java/infrastructure/adapter/input/rest/CustomerResource.java
new file mode 100644
index 0000000..fef9312
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/infrastructure/adapter/input/rest/CustomerResource.java
@@ -0,0 +1,540 @@
+package infrastructure.adapter.input.rest;
+
+import application.factory.SecurityTraceFactory;
+import application.helper.ResponseHelper;
+import application.port.input.*;
+import application.port.input.command.*;
+import application.service.OtpValidationService;
+import application.service.PostgresConfigService;
+import com.banesco.common.infraestructure.helpers.JsonHelper;
+import com.banesco.common.infraestructure.helpers.LoggerHelper;
+import domain.dto.request.CustomerRequest;
+import domain.dto.response.CustomerResponse;
+import domain.dto.response.StatusResponse;
+import domain.entity.AppConfiguration;
+import domain.entity.ConfigItem;
+import domain.model.AccountClientContext;
+import domain.model.BankService;
+import domain.model.ErrorCode;
+import domain.model.UserLogin;
+import infrastructure.config.AvailabilityBolConfig;
+import infrastructure.context.TraceIdContext;
+import infrastructure.service.StatusDescriptionResolver;
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.NotNull;
+import jakarta.ws.rs.HeaderParam;
+import jakarta.ws.rs.NotFoundException;
+import jakarta.ws.rs.POST;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.Response;
+import lombok.extern.slf4j.Slf4j;
+import org.eclipse.microprofile.config.inject.ConfigProperty;
+import org.eclipse.microprofile.openapi.annotations.Operation;
+import org.eclipse.microprofile.openapi.annotations.enums.ParameterIn;
+import org.eclipse.microprofile.openapi.annotations.enums.SchemaType;
+import org.eclipse.microprofile.openapi.annotations.media.Content;
+import org.eclipse.microprofile.openapi.annotations.media.ExampleObject;
+import org.eclipse.microprofile.openapi.annotations.media.Schema;
+import org.eclipse.microprofile.openapi.annotations.parameters.Parameter;
+import org.eclipse.microprofile.openapi.annotations.parameters.Parameters;
+import org.eclipse.microprofile.openapi.annotations.parameters.RequestBody;
+import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
+import org.eclipse.microprofile.openapi.annotations.responses.APIResponses;
+
+import java.util.Map;
+import java.util.Optional;
+
+@Slf4j
+@Path("/customer")
+public class CustomerResource {
+
+ @ConfigProperty(name = "app.redis.expiration")
+ long cacheTtl;
+
+ // --- DEPENDENCIAS INYECTADAS ---
+ private final TraceIdContext traceIdContext;
+ private final ResponseHelper responseHelper;
+ private final SecurityTraceFactory securityTraceFactory;
+ private final StatusDescriptionResolver statusDescriptionResolver;
+
+ // --- CONFIGURACIONES ---
+ private final AvailabilityBolConfig bolConfig;
+
+ // --- CASOS DE USO Y SERVICIOS ---
+ private final IAppConfig appConfigUseCase;
+ private final IServiceStatus serviceStatusUseCase;
+ private final IUserLogin userLoginUseCase;
+ private final IValidateProfile validateProfileUseCase;
+ private final IEncodeValue encodeValueUseCase;
+ private final ICustomerProduct customerProductUseCase;
+ private final IRegisterSecurity registerSecurityUseCase;
+ private final OtpValidationService otpValidationService;
+ private final PostgresConfigService postgresConfigService;
+
+ public CustomerResource(
+ TraceIdContext traceIdContext,
+ ResponseHelper responseHelper,
+ SecurityTraceFactory securityTraceFactory,
+ AvailabilityBolConfig bolConfig,
+ StatusDescriptionResolver statusDescriptionResolver,
+ IAppConfig appConfigUseCase,
+ IServiceStatus serviceStatusUseCase,
+ IUserLogin userLoginUseCase,
+ IValidateProfile validateProfileUseCase,
+ IEncodeValue encodeValueUseCase,
+ ICustomerProduct customerProductUseCase,
+ IRegisterSecurity registerSecurityUseCase,
+ OtpValidationService otpValidationService,
+ PostgresConfigService postgresConfigService
+ ) {
+ this.traceIdContext = traceIdContext;
+ this.responseHelper = responseHelper;
+ this.securityTraceFactory = securityTraceFactory;
+ this.bolConfig = bolConfig;
+ this.statusDescriptionResolver = statusDescriptionResolver;
+ this.appConfigUseCase = appConfigUseCase;
+ this.serviceStatusUseCase = serviceStatusUseCase;
+ this.userLoginUseCase = userLoginUseCase;
+ this.validateProfileUseCase = validateProfileUseCase;
+ this.encodeValueUseCase = encodeValueUseCase;
+ this.customerProductUseCase = customerProductUseCase;
+ this.registerSecurityUseCase = registerSecurityUseCase;
+ this.otpValidationService = otpValidationService;
+ this.postgresConfigService = postgresConfigService;
+ }
+
+ @POST
+ @Path("/position/evaluate")
+ @Operation(
+ summary = "Consulta de cuentas",
+ description = "Orquesta el flujo de validaciones (estatus de servicio, perfil, OTP, etc.) y, si son exitosas, consulta los productos del cliente."
+ )
+ @Parameters({
+ @Parameter(
+ name = "requestId",
+ in = ParameterIn.HEADER,
+ description = "Identificador de la solicitud para la trazabilidad. Si se omite, se genera uno automático.",
+ schema = @Schema(type = SchemaType.STRING),
+ example = "7WCnvznd0CPrBZ0jF4af"
+ ),
+ @Parameter(
+ name = "appId",
+ in = ParameterIn.HEADER,
+ description = "Identificador de la aplicación cliente para cargar una configuración de header específica. Si se omite, se usa la configuración por defecto.",
+ schema = @Schema(type = SchemaType.STRING),
+ example = "abc123"
+ )
+ })
+ @RequestBody(
+ description = "Cuerpo de la solicitud con los datos necesarios para consultar las cuentas.",
+ content = @Content(mediaType = MediaType.APPLICATION_JSON, schema =
+ @Schema(implementation = CustomerRequest.class),
+ examples = {
+ @ExampleObject(
+ name = "Solicitud Estándar",
+ summary = "Ejemplo de solicitud con éxito",
+ externalValue = "/openapi-examples/request/customer-product-success.json"
+ ),
+ @ExampleObject(
+ name = "Solicitud con Token",
+ summary = "Ejemplo de solicitud con token (éxito)",
+ externalValue = "/openapi-examples/request/customer-product-token-success.json"
+ )
+ }
+ )
+ )
+ @APIResponses(value = {
+ @APIResponse(
+ responseCode = "200",
+ description = "Operación Exitosa. Devuelve la lista de productos del cliente.",
+ content = @Content(
+ mediaType = MediaType.APPLICATION_JSON,
+ schema = @Schema(implementation = CustomerResponse.class),
+ examples = {
+ @ExampleObject(
+ name = "Cuentas Enmascaradas",
+ summary = "Respuesta exitosa (Enmascaramiento activo)",
+ description = "La configuración de la app indica que 'encryptionData' es true.",
+ externalValue = "/openapi-examples/response/customer-product-response.json"
+ ),
+ @ExampleObject(
+ name = "Cuentas Reales",
+ summary = "Respuesta exitosa (Sin enmascaramiento)",
+ description = "La configuración de la app indica que 'encryptionData' es false.",
+ externalValue = "/openapi-examples/response/customer-product-response-unmasked.json"
+ ),
+ @ExampleObject(
+ name = "Sin Resultados",
+ summary = "Respuesta exitosa (Cliente sin productos)",
+ description = "La consulta se completó, pero el cliente no tiene productos que coincidan.",
+ externalValue = "/openapi-examples/response/customer-product-without-results.json"
+ )
+ }
+ )
+ ),
+ @APIResponse(
+ responseCode = "401",
+ description = "Error de Autorización o Perfil. El perfil del cliente no es válido o falló una validación de seguridad.",
+ content = @Content(
+ mediaType = MediaType.APPLICATION_JSON,
+ schema = @Schema(implementation = StatusResponse.class),
+ examples = {
+ @ExampleObject(
+ name = "Perfil Inválido",
+ summary = "VRN01 - Perfil no válido",
+ externalValue = "/openapi-examples/response/invalid-profile.json"
+ )
+ }
+ )
+ ),
+ @APIResponse(
+ responseCode = "400",
+ description = "Datos de entrada inválidos, como un token OTP incorrecto o una configuración de app no encontrada.",
+ content = @Content(
+ mediaType = MediaType.APPLICATION_JSON,
+ schema = @Schema(implementation = StatusResponse.class),
+ examples = {
+ @ExampleObject(
+ name = "Token Inválido",
+ summary = "VRN07 - OTP Inválido",
+ externalValue = "/openapi-examples/response/invalid-token.json"
+ ),
+ @ExampleObject(
+ name = "Configuración no encontrada",
+ summary = "VRN_CONFIG_NOT_FOUND",
+ externalValue = "/openapi-examples/response/config-not-found.json"
+ )
+ }
+ )
+ ),
+ @APIResponse(
+ responseCode = "404",
+ description = "Recurso no encontrado. Puede ser un usuario BOL, un login vacío.",
+ content = @Content(
+ mediaType = MediaType.APPLICATION_JSON,
+ schema = @Schema(implementation = CustomerResponse.class),
+ examples = {
+ @ExampleObject(
+ name = "Usuario no existe",
+ summary = "AUTH02 - Usuario no existe",
+ externalValue = "/openapi-examples/response/user-bol-not-exist.json"
+ ),
+ @ExampleObject(
+ name = "Usuario vacío",
+ summary = "AUTH01 - Login vacío",
+ externalValue = "/openapi-examples/response/user-not-exist.json"
+ ),
+ }
+ )
+ ),
+ @APIResponse(
+ responseCode = "409",
+ description = "CONFLICTO.",
+ content = @Content(
+ mediaType = MediaType.APPLICATION_JSON,
+ schema = @Schema(implementation = StatusResponse.class),
+ examples = {
+ @ExampleObject(
+ name = "Conflicto",
+ summary = "Conflicto (revisar)",
+ externalValue = "/openapi-examples/response/conflict.json"
+ )
+ }
+ )
+ ),
+ @APIResponse(
+ responseCode = "500",
+ description = "Uso Interno. Ocurrio un error inesperado.",
+ content = @Content(
+ mediaType = MediaType.APPLICATION_JSON,
+ schema = @Schema(implementation = StatusResponse.class),
+ examples = {
+ @ExampleObject(
+ name = "Uso Interno",
+ summary = "Uso Interno (revisar)",
+ externalValue = "/openapi-examples/response/internal-use.json"
+ )
+ }
+ )
+ ),
+ @APIResponse(
+ responseCode = "503",
+ description = "Servicio no disponible. Ocurrió un error con una dependencia interna o el servicio está en mantenimiento.",
+ content = @Content(
+ mediaType = MediaType.APPLICATION_JSON,
+ schema = @Schema(implementation = StatusResponse.class),
+ examples = {
+ @ExampleObject(
+ name = "Mantenimiento",
+ summary = "VRN04 - Servicio en mantenimiento",
+ externalValue = "/openapi-examples/response/service-unavailable.json"
+ ),
+ @ExampleObject(
+ name = "Error de Dependencia",
+ summary = "Error de CI",
+ externalValue = "/openapi-examples/response/customer-product-rest-client-error.json"
+ )
+ }
+ )
+ )
+ })
+ public Response getCustomerProducts(
+ @HeaderParam("appId") String appId,
+ @HeaderParam("customerReferenceFintechId") String customerReferenceFintechId,
+ @HeaderParam("customerReferenceUser") String customerReferenceUser,
+ @Valid @NotNull(message = "VDE01") CustomerRequest request
+ ) {
+ final var requestId = traceIdContext.getTraceId();
+ final long startExecution = System.currentTimeMillis();
+
+ log.info(LoggerHelper.buildInfoPrivateRequest(requestId, JsonHelper.getJsonFromObject(request)));
+
+ var context = new AccountClientContext(startExecution, requestId, request,
+ appId, customerReferenceFintechId, customerReferenceUser
+ );
+
+ var appConfigurationOpt = getPgConfig(context, requestId);
+
+ if (appConfigurationOpt.isEmpty()) {
+ var error = statusDescriptionResolver.getCode("VRN_CONFIG_NOT_FOUND");
+ return responseHelper.buildErrorResponse(error, requestId);
+ }
+
+ var appConfiguration = appConfigurationOpt.get();
+
+ var validationRules = appConfiguration.getConfiguration().getValidationRules();
+ var validationErrorOpt = runValidationPipeline(context, validationRules, requestId);
+
+ Response finalResponse;
+ if (validationErrorOpt.isPresent()) {
+ context.setFinalError(validationErrorOpt);
+ finalResponse = responseHelper.buildErrorResponse(validationErrorOpt.get(), requestId);
+ } else {
+ var configItem = findSpecificConfigItem(appConfiguration, context, requestId);
+
+ if (configItem == null) {
+ log.error(LoggerHelper.buildError(requestId, "FATAL: No se encontro un ConfigItem (ni especifico ni 'all')"));
+ var error = statusDescriptionResolver.getCode("VRN_ITEM_NOT_FOUND");
+ return responseHelper.buildErrorResponse(error, requestId);
+ }
+
+ var otp = Optional.ofNullable(request.getTokenAssignmentInstance())
+ .map(CustomerRequest.TokenAssignmentInstance::getTokenIdentificationCode)
+ .filter(otpCode -> !otpCode.isEmpty())
+ .orElse(null);
+
+ finalResponse = getUserAccounts(context, appConfiguration, configItem, otp, requestId);
+ }
+
+ var fullResponse = (CustomerResponse) finalResponse.getEntity();
+
+ var statusRegister = fullResponse.getStatusResponse();
+
+ log.info(LoggerHelper.buildInfoPrivateRequest(requestId, "Ejecutando tareas finales (traza de seguridad)"));
+ var securityCommand = securityTraceFactory.createCommandFromContext(context, statusRegister);
+ registerSecurityUseCase.execute(securityCommand, context.getRequestId());
+
+ return finalResponse;
+ }
+
+ /**
+ * Ejecuta la secuencia de validaciones. Si alguna falla, devuelve la respuesta de error.
+ * Si todas pasan, devuelve un Optional vacío.
+ */
+ @SuppressWarnings("t")
+ private Optional runValidationPipeline(AccountClientContext context, Map validationRules, String requestId) {
+ var data = context.getRequest();
+ var device = data.getDevice();
+ var customerReference = data.getCustomerPositionState().getCustomerReference();
+ var tokenInstance = data.getTokenAssignmentInstance();
+
+ if (shouldRun(validationRules, "serviceStatus")) {
+ if (!isServiceActive(context.getRequestId())) {
+ return Optional.of(statusDescriptionResolver.getCode("VRN04"));
+ }
+ }
+
+ // Primero intenta obtener el usuario del contexto
+ var userOpt = Optional.ofNullable(context.getUsername())
+ .filter(u -> !u.isBlank())
+ .map(u -> UserLogin.builder()
+ .usernameBol(u)
+ .idNumber(customerReference.getCustomerId())
+ .build());
+
+ // Si no está en el contexto Y la regla dice que se debe ejecutar, búscalo en la API
+ if (shouldRun(validationRules, "getUserLogin")) {
+ userOpt = getUserLogin(context.getRequestId(), customerReference.getCustomerId(), customerReference.getCustomerIdType());
+
+ if (userOpt.isEmpty() ) {
+ log.info(LoggerHelper.buildInfoPrivateResponse(requestId, "No se pudo obtener el usuario ni desde el contexto ni desde la API"));
+ return Optional.of(statusDescriptionResolver.getCode("VRN19"));
+ }
+
+ // Si viene vacio el usernameBol ""
+ if(userOpt.get().usernameBol().isBlank()) {
+ log.info(LoggerHelper.buildInfoPrivateResponse(requestId, "No se pudo obtener el usernameBol"));
+ return Optional.of(statusDescriptionResolver.getCode("VRN20"));
+ }
+
+ context.setUsername(userOpt.get().usernameBol());
+ }
+
+ String hash = ""; // Inicializar
+ if (shouldRun(validationRules, "encodeValue")) {
+ hash = encodeUsername(context.getUsername(), requestId);
+ }
+
+ if (shouldRun(validationRules, "validateOtp")) {
+ if(!tokenInstance.getTokenIdentificationCode().isEmpty()) {
+ var isOtpValid = otpValidationService.isOtpValid(
+ tokenInstance.getTokenIdentificationCode(),
+ context.getFintechId(),
+ context.getUsername(),
+ device.getDeviceIp(),
+ requestId
+ );
+ if (!isOtpValid) {
+ return Optional.of(statusDescriptionResolver.getCode("VRN07"));
+ }
+ }
+ }
+
+ if (shouldRun(validationRules, "validateProfile")) {
+ var command = new ValidateProfileCommand(
+ device.getDeviceSessionReference() == null || device.getDeviceSessionReference().isBlank() ? context.getFinalCustomerId() + device.getDeviceSessionReference() : context.getFinalCustomerId(),
+ device.getDeviceIp(),
+ hash
+ );
+ var isValidProfile = validateProfileUseCase.execute(command, requestId);
+
+ if(!isValidProfile) {
+ log.info(LoggerHelper.buildInfoPrivateResponse(requestId, "FALLO LA VALIDACION DEL CLIENTE EN TRANSACT"));
+ return Optional.of(statusDescriptionResolver.getCode("VRN01"));
+ }
+ }
+
+ return Optional.empty();
+
+ }
+
+ // --- MÉTODOS PRIVADOS DE ORQUESTACIÓN ---
+ private boolean isServiceActive(String requestId) {
+ var bankService = new BankService(bolConfig.codBan(), bolConfig.codEve(), bolConfig.codServ());
+ var command = new ServiceStatusCommand(requestId, requestId, bankService);
+ return serviceStatusUseCase.execute(command, requestId);
+ }
+
+ private Optional getUserLogin(String requestId, String... params) {
+ var command = new UserLoginCommand(
+ params[0],
+ params[1]
+ );
+
+ return userLoginUseCase.execute(command, requestId);
+ }
+
+ private String encodeUsername(String username, String requestId) {
+ var command = new EncodeValueCommand(username);
+ return encodeValueUseCase.execute(command, requestId);
+ }
+
+ private Optional getPgConfig(AccountClientContext context, String requestId) {
+ var configKey = context.getAppId();
+
+ var cachedConfig = postgresConfigService.findByName(configKey, requestId);
+ if (cachedConfig.isPresent()) {
+ log.info(LoggerHelper.buildInfoPrivateRequest(requestId, "Configuracion obtenida desde CACHE"));
+ return cachedConfig;
+ }
+
+ log.info(LoggerHelper.buildInfoPrivateRequest(requestId, "Configuracion no encontrada en cache, buscando en BD..."));
+
+ AppConfiguration appConfiguration;
+ try {
+ appConfiguration = appConfigUseCase.execute(configKey);
+
+ log.info(LoggerHelper.buildInfoPrivateResponse(requestId, "Configuracion obtenida desde BD"));
+ } catch (NotFoundException ex) {
+ log.error(LoggerHelper.buildError(requestId, "FATAL: No se encontro AppConfiguration para appId: " + configKey), ex);
+ return Optional.empty();
+ }
+
+ postgresConfigService.savePostgresConfig(configKey, appConfiguration, cacheTtl, requestId);
+ return Optional.of(appConfiguration);
+ }
+
+ /**
+ * Extrae el ConfigItem específico de la AppConfiguration basado en el contexto.
+ * Cae a "all" si no se encuentra uno específico.
+ */
+ private ConfigItem findSpecificConfigItem(AppConfiguration appConfiguration, AccountClientContext context, String requestId) {
+ var data = context.getRequest().getCustomerPositionState().getCustomerReference();
+ var postgresConfig = appConfiguration.getConfiguration();
+
+ var aliases = postgresConfig.getAliases();
+ var configMap = postgresConfig.getConfiguration();
+
+ // Usamos el appId del contexto como prefijo de la clave
+ var literal = data.getCustomerIdType();
+ var finalLiteral = aliases.getOrDefault(literal, literal);
+ var specificKey = context.getAppId() + "_" + finalLiteral;
+
+ log.info(LoggerHelper.buildInfoPrivateRequest(requestId, "Buscando ConfigItem con KEY: " + specificKey));
+
+ ConfigItem config;
+ if (configMap.containsKey(specificKey)) {
+ config = configMap.get(specificKey);
+ log.info(LoggerHelper.buildInfoPrivateRequest(requestId, "Configuracion encontrada (Especifica): " + specificKey));
+ } else {
+ config = configMap.get("all");
+ log.info(LoggerHelper.buildInfoPrivateRequest(requestId, "Configuracion encontrada (General): all"));
+ }
+
+ return config; // Puede ser null si "all" tampoco existe
+ }
+
+ /**
+ * Helper para verificar de forma segura si una regla debe ejecutarse.
+ * Por defecto, si una regla no está en el mapa, SÍ se ejecuta (falla seguro).
+ */
+ private boolean shouldRun(Map rules, String ruleName) {
+ if (rules == null) {
+ // Si no hay mapa, orquesta completo
+ return true;
+ }
+ return rules.getOrDefault(ruleName, true);
+ }
+
+
+ private Response getUserAccounts(AccountClientContext context, AppConfiguration appConfig, ConfigItem configItem, String otp, String requestId) {
+
+ var customerAccountCommand = CustomerAccountCommand.builder()
+ .rif(context.getFinalCustomerId())
+ .bankCode(configItem.getBankCode())
+ .currencyCode(configItem.getCurrencyCode())
+ .status(configItem.getStatus())
+ .statusMe(configItem.getStatusMe())
+ .productCv(configItem.getProductCv())
+ .product(configItem.getProduct())
+ .channel(configItem.getChannel())
+ .service(configItem.getService())
+ .operator(configItem.getOperator())
+ .phone(configItem.getPhone())
+ .affiliationStatus(configItem.getAffiliationStatus())
+ .counterpartyAccount(configItem.getCounterpartyAccount())
+ .holder(configItem.getHolder())
+ .build();
+
+ var products = customerProductUseCase.execute(customerAccountCommand, context, appConfig, otp, configItem.isCacheable(), requestId);
+
+ return responseHelper.buildSuccessResponse(
+ products.stream().toList(),
+ products.isEmpty() ?
+ statusDescriptionResolver.getCode("204") :
+ statusDescriptionResolver.getCode("200"),
+ requestId
+ );
+ }
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/adapter/output/MaintenanceCheckAdapter.java b/bus-evaluate-customer-product/src/main/java/infrastructure/adapter/output/MaintenanceCheckAdapter.java
new file mode 100644
index 0000000..ce118c4
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/infrastructure/adapter/output/MaintenanceCheckAdapter.java
@@ -0,0 +1,25 @@
+package infrastructure.adapter.output;
+
+import application.port.output.IMaintenanceCheckPort;
+import com.banesco.common.infraestructure.utils.MaintenanceUtil;
+import infrastructure.config.AccountAvailabilityConfig;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+@ApplicationScoped
+public class MaintenanceCheckAdapter implements IMaintenanceCheckPort {
+
+ private final AccountAvailabilityConfig availabilityConfig;
+
+ @Inject
+ MaintenanceCheckAdapter(AccountAvailabilityConfig paymentAvailabilityConfig) {
+ this.availabilityConfig = paymentAvailabilityConfig;
+ }
+
+ @Override
+ public Boolean isUnderMaintenance(String requestId) {
+ return MaintenanceUtil.isDateInRange(requestId, availabilityConfig.begin(), availabilityConfig.finish());
+ }
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/adapter/output/persistence/AppConfigRepositoryAdapter.java b/bus-evaluate-customer-product/src/main/java/infrastructure/adapter/output/persistence/AppConfigRepositoryAdapter.java
new file mode 100644
index 0000000..793c33a
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/infrastructure/adapter/output/persistence/AppConfigRepositoryAdapter.java
@@ -0,0 +1,53 @@
+package infrastructure.adapter.output.persistence;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import domain.entity.AppConfiguration;
+import domain.model.Configuration;
+import domain.repository.AppConfigRepository;
+import io.agroal.api.AgroalDataSource;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+import java.util.Optional;
+
+@ApplicationScoped
+public class AppConfigRepositoryAdapter implements AppConfigRepository {
+
+ @Inject
+ AgroalDataSource dataSource;
+
+ @Inject
+ ObjectMapper objectMapper;
+
+ @Override
+ public Optional findConfigByAppName(String appName) {
+ String sql = "SELECT * FROM get_application_config(?)";
+
+ try (var conn = dataSource.getConnection();
+ var ps = conn.prepareStatement(sql)) {
+
+ ps.setString(1, appName);
+
+ try (var rs = ps.executeQuery()) {
+ if (rs.next()) {
+ var config = new AppConfiguration();
+
+ config.setAppId(rs.getLong("id_appconfig"));
+ config.setAppName(rs.getString("app_name"));
+ config.setApiName(rs.getString("api_name"));
+ config.setType(rs.getString("type"));
+ config.setEvent(rs.getString("event"));
+ config.setEncryptionData(rs.getBoolean("encryption_data"));
+ var jsonString = rs.getString("configuration");
+ if (jsonString != null) {
+ config.setConfiguration(objectMapper.readValue(jsonString, Configuration.class));
+ }
+
+ return Optional.of(config);
+ }
+ }
+ } catch (Exception e) {
+ throw new RuntimeException("Error al llamar a get_application_config", e);
+ }
+ return Optional.empty();
+ }
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/client/accounts/adapter/CustomerProductAdapter.java b/bus-evaluate-customer-product/src/main/java/infrastructure/client/accounts/adapter/CustomerProductAdapter.java
new file mode 100644
index 0000000..6e021bd
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/infrastructure/client/accounts/adapter/CustomerProductAdapter.java
@@ -0,0 +1,75 @@
+package infrastructure.client.accounts.adapter;
+
+import application.exception.GatewayServiceException;
+import application.port.input.command.CustomerAccountCommand;
+import com.banesco.common.infraestructure.helpers.LoggerHelper;
+import domain.model.CustomerAccount;
+import domain.repository.ICustomerProductPort;
+import infrastructure.client.accounts.api.CustomerAccountClient;
+import infrastructure.mapper.CustomerAccountMapper;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.ws.rs.WebApplicationException;
+import lombok.extern.slf4j.Slf4j;
+import org.eclipse.microprofile.rest.client.inject.RestClient;
+
+import java.util.List;
+
+@Slf4j
+@ApplicationScoped
+public class CustomerProductAdapter implements ICustomerProductPort {
+
+ private final CustomerAccountClient client;
+
+ public CustomerProductAdapter(@RestClient CustomerAccountClient client) {
+ this.client = client;
+ }
+
+ @Override
+ public List execute(CustomerAccountCommand command, String requestId) {
+ try {
+ log.debug(LoggerHelper.buildInfoPrivateRequest(requestId, "Adaptador: Mapeando Command a DTO para api-get-db2-accounts-client-v2"));
+ var request = CustomerAccountMapper.toRequestDto(command);
+ log.info(LoggerHelper.buildInfoPrivateRequest(requestId, "Llamando al servicio de api-get-db2-accounts-client-v2", request));
+
+ var response = client.readCustomerProducts(requestId, request);
+ log.info(LoggerHelper.buildInfoPrivateResponse(requestId, "Respuesta del servicio de api-get-db2-accounts-client-v2", response));
+
+ // Validación de contrato: asegura que la respuesta no sea nula o inválida.
+ if (response == null) {
+ log.warn(LoggerHelper.buildError(requestId, "Respuesta invalida o nula del servicio de api-get-db2-accounts-client-v2"));
+ throw new GatewayServiceException("Respuesta invalida del servicio externo de api-get-db2-accounts-client-v2");
+ }
+
+ return CustomerAccountMapper.toEntityList(response);
+ } catch (WebApplicationException ex) {
+ var errorResponse = ex.getResponse();
+ var status = errorResponse.getStatus();
+ String errorDetails;
+
+ // Verifica si el Content-Type es HTML
+ if (errorResponse.getMediaType() != null && errorResponse.getMediaType().toString().contains("text/html")) {
+ errorDetails = "Se recibio una respuesta HTML no esperada del servicio.";
+ log.error(LoggerHelper.buildError(requestId,
+ String.format("Error HTTP %s del servicio de api-get-db2-accounts-client-v2 %s", status, errorDetails)));
+ } else {
+ // Si no es HTML, probablemente es un error JSON que sí queremos ver.
+ var errorBody = errorResponse.readEntity(String.class);
+
+ // Aun así, es buena idea truncarlo por si es muy largo
+ errorDetails = errorBody.substring(0, Math.min(errorBody.length(), 1000));
+ log.error(LoggerHelper.buildError(requestId,
+ String.format("Error HTTP no manejado del servicio de api-get-db2-accounts-client-v2 Status: %s. Body: %s", status, errorDetails)));
+ }
+
+ throw new GatewayServiceException("Error en el servicio de api-get-db2-accounts-client-v2: " + errorDetails);
+ } catch (RuntimeException ex) {
+ // Captura errores de más bajo nivel (red, timeouts, host desconocido, etc.).
+ log.error(LoggerHelper.buildError(
+ requestId,
+ String.format("Error de comunicacion con el servicio de api-get-db2-accounts-client-v2: %s", ex.getMessage())
+ ));
+ // Traduce el error técnico a una excepción de la aplicación.
+ throw new GatewayServiceException("No se pudo comunicar con el servicio externo de api-get-db2-accounts-client-v2");
+ }
+ }
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/client/accounts/api/CustomerAccountClient.java b/bus-evaluate-customer-product/src/main/java/infrastructure/client/accounts/api/CustomerAccountClient.java
new file mode 100644
index 0000000..4a4b7df
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/infrastructure/client/accounts/api/CustomerAccountClient.java
@@ -0,0 +1,21 @@
+package infrastructure.client.accounts.api;
+
+import infrastructure.client.accounts.dto.request.CustomerAccountRequest;
+import infrastructure.client.accounts.dto.respone.CustomerAccountResponse;
+import infrastructure.interceptor.LoggingFilter;
+import jakarta.ws.rs.HeaderParam;
+import jakarta.ws.rs.POST;
+import org.eclipse.microprofile.rest.client.annotation.ClientHeaderParam;
+import org.eclipse.microprofile.rest.client.annotation.RegisterProvider;
+import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
+
+@RegisterRestClient(configKey = "customer-accounts")
+@RegisterProvider(LoggingFilter.class)
+public interface CustomerAccountClient {
+ @POST
+ @ClientHeaderParam(name = "appId", value = "${app.appId}")
+ CustomerAccountResponse readCustomerProducts(
+ @HeaderParam("requestId") String requestId,
+ CustomerAccountRequest request
+ );
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/client/accounts/dto/request/CustomerAccountRequest.java b/bus-evaluate-customer-product/src/main/java/infrastructure/client/accounts/dto/request/CustomerAccountRequest.java
new file mode 100644
index 0000000..cf4cfac
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/infrastructure/client/accounts/dto/request/CustomerAccountRequest.java
@@ -0,0 +1,25 @@
+package infrastructure.client.accounts.dto.request;
+
+import io.quarkus.runtime.annotations.RegisterForReflection;
+import lombok.Builder;
+import lombok.Value;
+
+@Value
+@Builder
+@RegisterForReflection
+public class CustomerAccountRequest {
+ String rif;
+ String bankCode;
+ String currencyCode;
+ String status;
+ String statusMe;
+ String productCv;
+ String product;
+ String channel;
+ String service;
+ String operator;
+ String phone;
+ String affiliationStatus;
+ String counterpartyAccount;
+ String holder;
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/client/accounts/dto/respone/CustomerAccountResponse.java b/bus-evaluate-customer-product/src/main/java/infrastructure/client/accounts/dto/respone/CustomerAccountResponse.java
new file mode 100644
index 0000000..d80951f
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/infrastructure/client/accounts/dto/respone/CustomerAccountResponse.java
@@ -0,0 +1,44 @@
+package infrastructure.client.accounts.dto.respone;
+
+import io.quarkus.runtime.annotations.RegisterForReflection;
+import lombok.Builder;
+import lombok.Value;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+@Value
+@Builder
+@RegisterForReflection
+public class CustomerAccountResponse {
+ List data;
+ int record;
+
+ @Value
+ @RegisterForReflection
+ public static class CustomerAccount {
+ String banco;
+ String cuenta;
+ String producto;
+ String descProd;
+ String claseCta;
+ String tipoCta;
+ String moneda;
+ BigDecimal saldoDisponible;
+ String estatus;
+ String statusMe;
+ String titular;
+ BigDecimal maximumPerDay;
+ BigDecimal maximumPerTransaction;
+ BigDecimal maximumPerMonth;
+ BigDecimal maximumMonthlyFee;
+ BigDecimal minimumPerTransaction;
+ String operator;
+ String phone;
+ String channel;
+ String service;
+ String affiliationStatus;
+ String codeError;
+ String descError;
+ }
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/client/encodevalue/adapter/EncodeValueAdapter.java b/bus-evaluate-customer-product/src/main/java/infrastructure/client/encodevalue/adapter/EncodeValueAdapter.java
new file mode 100644
index 0000000..f02cdd0
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/infrastructure/client/encodevalue/adapter/EncodeValueAdapter.java
@@ -0,0 +1,74 @@
+package infrastructure.client.encodevalue.adapter;
+
+import application.exception.GatewayServiceException;
+import application.exception.ServiceCommunicationException;
+import application.port.input.command.EncodeValueCommand;
+import com.banesco.common.infraestructure.helpers.LoggerHelper;
+import domain.repository.IEncodeValuePort;
+import infrastructure.client.encodevalue.api.EncodeValueClient;
+import infrastructure.mapper.EncodeValueMapper;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.ws.rs.WebApplicationException;
+import lombok.extern.slf4j.Slf4j;
+import org.eclipse.microprofile.rest.client.inject.RestClient;
+
+@Slf4j
+@ApplicationScoped
+public class EncodeValueAdapter implements IEncodeValuePort {
+
+ private final EncodeValueClient client;
+
+ public EncodeValueAdapter(@RestClient EncodeValueClient client) {
+ this.client = client;
+ }
+
+ @Override
+ public String execute( EncodeValueCommand command, String requestId) {
+ try {
+ log.debug(LoggerHelper.buildInfoPrivateRequest(requestId, "Adaptador: Mapeando Command a DTO para Encode Value"));
+ var requestDto = EncodeValueMapper.toRequestDto(command);
+
+ log.info(LoggerHelper.buildInfoPrivateRequest(requestId, "Llamando al servicio de encode value", requestDto));
+ var response = client.encodeValue(requestId, requestDto);
+
+ log.info(LoggerHelper.buildInfoPrivateResponse(requestId, "Respuesta del servicio de encode value", response));
+
+ // Validación de contrato: asegura que la respuesta no sea nula o inválida.
+ if (response == null) {
+ log.warn(LoggerHelper.buildError(requestId, "Respuesta invalida o nula del servicio de encode value"));
+ throw new GatewayServiceException("Respuesta invalida del servicio externo de encode value");
+ }
+
+ return EncodeValueMapper.toResponseDto(response);
+
+ } catch (WebApplicationException ex) {
+ var errorResponse = ex.getResponse();
+ var status = errorResponse.getStatus();
+ String errorDetails;
+
+ // Verifica si el Content-Type es HTML
+ if (errorResponse.getMediaType() != null && errorResponse.getMediaType().toString().contains("text/html")) {
+ errorDetails = "Se recibio una respuesta HTML no esperada del servicio.";
+ log.error(LoggerHelper.buildError(requestId,
+ String.format("Error HTTP %s del servicio de encode value. %s", status, errorDetails)));
+ } else {
+ // Si no es HTML, probablemente es un error JSON que sí queremos ver.
+ var errorBody = errorResponse.readEntity(String.class);
+ // Aun así, es buena idea truncarlo por si es muy largo
+ errorDetails = errorBody.substring(0, Math.min(errorBody.length(), 1000));
+ log.error(LoggerHelper.buildError(requestId,
+ String.format("Error HTTP no manejado del servicio de encode value. Status: %s. Body: %s", status, errorDetails)));
+ }
+
+ throw new GatewayServiceException("Error en el servicio de encode value: " + errorDetails);
+ } catch (RuntimeException ex) {
+ // Captura errores de más bajo nivel (red, timeouts, host desconocido, etc.).
+ log.error(LoggerHelper.buildError(
+ requestId,
+ String.format("Error de comunicacion con el servicio de encode value: %s", ex)
+ ));
+
+ throw new ServiceCommunicationException("No se pudo comunicar con el servicio externo de encode value", ex);
+ }
+ }
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/client/encodevalue/api/EncodeValueClient.java b/bus-evaluate-customer-product/src/main/java/infrastructure/client/encodevalue/api/EncodeValueClient.java
new file mode 100644
index 0000000..c4c7a22
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/infrastructure/client/encodevalue/api/EncodeValueClient.java
@@ -0,0 +1,21 @@
+package infrastructure.client.encodevalue.api;
+
+import infrastructure.interceptor.LoggingFilter;
+import jakarta.ws.rs.HeaderParam;
+import jakarta.ws.rs.POST;
+import org.eclipse.microprofile.rest.client.annotation.ClientHeaderParam;
+import org.eclipse.microprofile.rest.client.annotation.RegisterProvider;
+import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
+import org.tempuri.Prueba;
+import org.tempuri.PruebaResponse;
+
+@RegisterRestClient(configKey = "encode-value")
+@RegisterProvider(LoggingFilter.class)
+public interface EncodeValueClient {
+ @POST
+ @ClientHeaderParam(name = "appId", value = "${app.appId}")
+ PruebaResponse encodeValue(
+ @HeaderParam("requestId") String requestId,
+ Prueba request
+ );
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/client/otp/adapter/OtpAdapter.java b/bus-evaluate-customer-product/src/main/java/infrastructure/client/otp/adapter/OtpAdapter.java
new file mode 100644
index 0000000..a37c31b
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/infrastructure/client/otp/adapter/OtpAdapter.java
@@ -0,0 +1,91 @@
+package infrastructure.client.otp.adapter;
+
+import application.exception.GatewayServiceException;
+import application.exception.InvalidTokenException;
+import application.port.input.command.ValidateOtpCommand;
+import com.banesco.common.infraestructure.helpers.LoggerHelper;
+import domain.repository.IOtpPort;
+import infrastructure.client.otp.api.OtpClient;
+import infrastructure.mapper.OtpMapper;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+import jakarta.ws.rs.WebApplicationException;
+import lombok.extern.slf4j.Slf4j;
+import org.eclipse.microprofile.rest.client.inject.RestClient;
+import org.jboss.resteasy.reactive.RestResponse;
+
+@Slf4j
+@ApplicationScoped
+public class OtpAdapter implements IOtpPort {
+
+ private final OtpClient client;
+
+ @Inject
+ public OtpAdapter(@RestClient OtpClient client) {
+ this.client = client;
+ }
+
+ /**
+ * Implementa la comunicación con la API externa para validar un OTP.
+ * Se encarga de mapear el DTO, llamar al cliente REST y TRADUCIR errores
+ * técnicos (de red) o respuestas HTTP inesperadas en excepciones de la aplicación.
+ *
+ * @param command El comando de dominio con los datos de validación.
+ * @param requestId El ID de la solicitud para trazabilidad.
+ * @return Un objeto {@link RestResponse} que contiene la respuesta HTTP completa de la API.
+ * @throws GatewayServiceException si ocurre un error de red o la API responde con un error 5xx.
+ */
+ @Override
+ public RestResponse validateOtp(ValidateOtpCommand command, String requestId) {
+ try {
+ log.debug(LoggerHelper.buildInfoPrivateRequest(requestId, "Adaptador: Mapeando Command a DTO para la API privada de validate otp"));
+ var requestDto = OtpMapper.toRequestDto(command);
+ log.info(LoggerHelper.buildInfoPrivateRequest(requestId, "Llamando al servicio de validate otp", requestDto));
+
+ var response = client.validateOtp(
+ requestId,
+ requestDto.getOtpToken(),
+ requestDto.getAppOrigen(),
+ requestDto.getLogin(),
+ requestDto.getIpAddress(),
+ requestDto.getCanal(),
+ requestDto.getServicio()
+ );
+
+ log.info(LoggerHelper.buildInfoPrivateResponse(requestId, "Respuesta del servicio de otp", response.getStatus()));
+
+ return response;
+ } catch(WebApplicationException ex) {
+ var errorResponse = ex.getResponse();
+ var status = errorResponse.getStatus();
+ String errorDetails;
+
+ // OTP inválido (respuesta de la privada)
+ if(status == 403) {
+ log.error(LoggerHelper.buildError(requestId, String.format("token %s invalido, respuesta HTTP API privada de otp: %s", command.otpToken(), status)));
+ throw new InvalidTokenException("Token inválido");
+ }
+
+ // Verifica si el Content-Type es HTML
+ if (errorResponse.getMediaType() != null && errorResponse.getMediaType().toString().contains("text/html")) {
+ errorDetails = "Se recibio una respuesta HTML no esperada del servicio.";
+ log.error(LoggerHelper.buildError(requestId,
+ String.format("Error HTTP %s del servicio de otp. %s", status, errorDetails)));
+ } else {
+ // Si no es HTML, probablemente es un error JSON que sí queremos ver.
+ var errorBody = errorResponse.readEntity(String.class);
+
+ // Se trunca por si es el error es muy largo
+ errorDetails = errorBody.substring(0, Math.min(errorBody.length(), 1000));
+ log.error(LoggerHelper.buildError(requestId,
+ String.format("Error HTTP no manejado del servicio de otp. Status: %s. Body: %s", status, errorDetails)));
+ }
+
+ throw new GatewayServiceException("Error en el servicio de otp: " + errorDetails);
+ } catch(RuntimeException ex) {
+ // Error de red (Timeout, no se puede conectar, etc.)
+ log.error(LoggerHelper.buildError(requestId, String.format("Error de comunicacion con el servicio de otp %s", ex)));
+ throw new GatewayServiceException("No se pudo comunicar con el servicio externo de otp.");
+ }
+ }
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/client/otp/api/OtpClient.java b/bus-evaluate-customer-product/src/main/java/infrastructure/client/otp/api/OtpClient.java
new file mode 100644
index 0000000..d990de0
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/infrastructure/client/otp/api/OtpClient.java
@@ -0,0 +1,28 @@
+package infrastructure.client.otp.api;
+
+import infrastructure.interceptor.LoggingFilter;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.HeaderParam;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.PathParam;
+import org.eclipse.microprofile.rest.client.annotation.ClientHeaderParam;
+import org.eclipse.microprofile.rest.client.annotation.RegisterProvider;
+import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
+import org.jboss.resteasy.reactive.RestResponse;
+
+@RegisterRestClient(configKey = "otp")
+@RegisterProvider(LoggingFilter.class)
+public interface OtpClient {
+ @GET
+ @ClientHeaderParam(name = "appId", value = "${app.appId}")
+ @Path("/{otpToken}/{appOrigen}/{login}/{ipAddress}/{canal}/{servicio}")
+ RestResponse validateOtp(
+ @HeaderParam("requestId") String requestId,
+ @PathParam("otpToken") String otpToken,
+ @PathParam("appOrigen") String appOrigen,
+ @PathParam("login") String login,
+ @PathParam("ipAddress") String ipAddress,
+ @PathParam("canal") String canal,
+ @PathParam("servicio") String servicio
+ );
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/client/otp/dto/request/ValidateOtpRequest.java b/bus-evaluate-customer-product/src/main/java/infrastructure/client/otp/dto/request/ValidateOtpRequest.java
new file mode 100644
index 0000000..3dfb663
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/infrastructure/client/otp/dto/request/ValidateOtpRequest.java
@@ -0,0 +1,19 @@
+package infrastructure.client.otp.dto.request;
+
+import io.quarkus.runtime.annotations.RegisterForReflection;
+import lombok.Builder;
+import lombok.Value;
+
+@Value
+@Builder
+@RegisterForReflection
+public class ValidateOtpRequest {
+// MsgRqHdr msgRqHdr;
+ String otpToken;
+ String appOrigen;
+ String login;
+ String ipAddress;
+ String canal;
+ String servicio;
+// String domain;
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/client/otp/dto/response/AditionalStatus.java b/bus-evaluate-customer-product/src/main/java/infrastructure/client/otp/dto/response/AditionalStatus.java
new file mode 100644
index 0000000..bb96c54
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/infrastructure/client/otp/dto/response/AditionalStatus.java
@@ -0,0 +1,13 @@
+package infrastructure.client.otp.dto.response;
+
+import lombok.Value;
+
+@Value
+public class AditionalStatus {
+ String statusType;
+ String statusCode;
+ String statusDesc;
+ String validationType;
+ String severity;
+ String lineNumber;
+}
diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/client/otp/dto/response/Status.java b/bus-evaluate-customer-product/src/main/java/infrastructure/client/otp/dto/response/Status.java
new file mode 100644
index 0000000..db7341b
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/infrastructure/client/otp/dto/response/Status.java
@@ -0,0 +1,22 @@
+package infrastructure.client.otp.dto.response;
+
+import com.banesco.xmlns.enterpriseobjects.status.AdditionalStatus;
+import io.quarkus.runtime.annotations.RegisterForReflection;
+import lombok.Value;
+
+import java.math.BigInteger;
+import java.util.List;
+
+@Value
+@RegisterForReflection
+public class Status {
+ String statusType;
+ String statusCode;
+ String statusDesc;
+ String applicationName;
+ BigInteger lineNumber;
+ List additionalStatus;
+ String severity;
+ String statusInd;
+ String logId;
+}
diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/client/otp/dto/response/ValidateOtpResponse.java b/bus-evaluate-customer-product/src/main/java/infrastructure/client/otp/dto/response/ValidateOtpResponse.java
new file mode 100644
index 0000000..51a62e7
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/infrastructure/client/otp/dto/response/ValidateOtpResponse.java
@@ -0,0 +1,14 @@
+package infrastructure.client.otp.dto.response;
+
+import com.banesco.xmlns.enterpriseobjects.msgrshdr.MsgRsHdr;
+import io.quarkus.runtime.annotations.RegisterForReflection;
+import lombok.Builder;
+import lombok.Value;
+
+@Value
+@Builder
+@RegisterForReflection
+public class ValidateOtpResponse {
+ MsgRsHdr msgRsHdr;
+ Status status;
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/client/registersecurity/adapter/RegisterSecurityAdapter.java b/bus-evaluate-customer-product/src/main/java/infrastructure/client/registersecurity/adapter/RegisterSecurityAdapter.java
new file mode 100644
index 0000000..3eced53
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/infrastructure/client/registersecurity/adapter/RegisterSecurityAdapter.java
@@ -0,0 +1,89 @@
+package infrastructure.client.registersecurity.adapter;
+
+import application.port.input.command.RegisterSecurityCommand;
+import com.banesco.common.infraestructure.helpers.LoggerHelper;
+import domain.repository.IRegisterSecurityPort;
+import infrastructure.client.registersecurity.api.RegisterSecurityClient;
+import infrastructure.mapper.RegisterSecurityMapper;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+import jakarta.ws.rs.WebApplicationException;
+import lombok.extern.slf4j.Slf4j;
+import org.eclipse.microprofile.rest.client.inject.RestClient;
+
+@Slf4j
+@ApplicationScoped
+public class RegisterSecurityAdapter implements IRegisterSecurityPort {
+
+ private final RegisterSecurityClient client;
+ private final RegisterSecurityMapper mapper;
+
+ @Inject
+ public RegisterSecurityAdapter(
+ @RestClient RegisterSecurityClient client,
+ RegisterSecurityMapper mapper
+ ) {
+ this.client = client;
+ this.mapper = mapper;
+ }
+
+ @Override
+ public void writeTrace(RegisterSecurityCommand command, String requestId) {
+ try {
+ log.debug(LoggerHelper.buildInfoPrivateRequest(requestId, "Adaptador: Mapeando Command a DTO para register security"));
+ var requestDto = mapper.toRequestDto(command);
+ log.info(LoggerHelper.buildInfoPrivateRequest(requestId, "Llamando al servicio de register security (asincrono)", requestDto));
+
+ client.registerSecurity(requestId, requestDto)
+ .subscribe().with(
+ // 1. Callback de ÉXITO (sin cambios)
+ response -> log.info(LoggerHelper.buildInfoPrivateResponse(
+ requestId,
+ String.format("Traza de seguridad registrada. Status: %s", response.getStatus())
+ )),
+
+ // 2. Callback de FALLO (con la nueva lógica detallada)
+ failure -> handleFailure(failure, requestId)
+ );
+
+ } catch (Exception e) {
+ // Este bloque captura errores síncronos (ej. en el mapeo)
+ log.error(LoggerHelper.buildError(
+ requestId,
+ String.format("Error al preparar la llamada para registrar la traza de seguridad: %s", e.getMessage())
+ ), e);
+ }
+ }
+
+ /**
+ * Centralizar y detallar el manejo de errores asíncronos.
+ *
+ * @param failure La excepción ocurrida.
+ * @param requestId El ID de la traza.
+ */
+ private void handleFailure(Throwable failure, String requestId) {
+ if (failure instanceof WebApplicationException ex) {
+ // Lógica para errores HTTP (4xx, 5xx) - Equivalente al primer catch
+ var errorResponse = ex.getResponse();
+ var status = errorResponse.getStatus();
+ String errorDetails;
+
+ if (errorResponse.getMediaType() != null && errorResponse.getMediaType().toString().contains("text/html")) {
+ errorDetails = "Se recibio una respuesta HTML no esperada del servicio.";
+ log.error(LoggerHelper.buildError(requestId,
+ String.format("Error HTTP %s del servicio de register security. %s", status, errorDetails)));
+ } else {
+ var errorBody = errorResponse.readEntity(String.class);
+ errorDetails = errorBody.substring(0, Math.min(errorBody.length(), 1000));
+ log.error(LoggerHelper.buildError(requestId,
+ String.format("Error HTTP no manejado del servicio de register security. Status: %s. Body: %s", status, errorDetails)));
+ }
+ } else {
+ // Lógica para otros errores (red, timeout, etc.) - Equivalente al segundo catch
+ log.error(LoggerHelper.buildError(
+ requestId,
+ String.format("Error de comunicacion con el servicio de register security: %s", failure.getMessage())
+ ), failure);
+ }
+ }
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/client/registersecurity/api/RegisterSecurityClient.java b/bus-evaluate-customer-product/src/main/java/infrastructure/client/registersecurity/api/RegisterSecurityClient.java
new file mode 100644
index 0000000..5d64c80
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/infrastructure/client/registersecurity/api/RegisterSecurityClient.java
@@ -0,0 +1,20 @@
+package infrastructure.client.registersecurity.api;
+
+import infrastructure.client.registersecurity.dto.request.RegisterSecurityRequest;
+import infrastructure.interceptor.LoggingFilter;
+import io.smallrye.mutiny.Uni;
+import jakarta.ws.rs.HeaderParam;
+import jakarta.ws.rs.POST;
+import jakarta.ws.rs.core.Response;
+import org.eclipse.microprofile.rest.client.annotation.RegisterProvider;
+import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
+
+@RegisterRestClient(configKey = "register-security")
+@RegisterProvider(LoggingFilter.class)
+public interface RegisterSecurityClient {
+ @POST
+ Uni registerSecurity(
+ @HeaderParam("requestId") String requestId,
+ RegisterSecurityRequest request
+ );
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/client/registersecurity/dto/request/RegisterSecurityRequest.java b/bus-evaluate-customer-product/src/main/java/infrastructure/client/registersecurity/dto/request/RegisterSecurityRequest.java
new file mode 100644
index 0000000..d1bad2b
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/infrastructure/client/registersecurity/dto/request/RegisterSecurityRequest.java
@@ -0,0 +1,39 @@
+package infrastructure.client.registersecurity.dto.request;
+
+import io.quarkus.runtime.annotations.RegisterForReflection;
+
+import java.util.Date;
+
+@RegisterForReflection
+public record RegisterSecurityRequest (
+ String codBan,
+ String codMon,
+ String codEve,
+ String codEve2,
+ String login,
+ Date fecHor,
+ String nacCli,
+ Integer cedRifCli,
+ String tipoProductoCli,
+ String tipoProductoBen,
+ String productoCli,
+ String codEmpresa,
+ String nacBen,
+ Integer cedBen,
+ String nombreBen,
+ String productoBen,
+ Double monto,
+ String referencia,
+ String nroDePago,
+ String desPago,
+ String objeto,
+ Integer tipoRespuesta,
+ String msgRespuesta,
+ Integer tiempoRespuesta,
+ String codFintech,
+ String cedRifFintech,
+ String tipoDispositivo,
+ String desDispositivo,
+ String ipCli,
+ String sp
+) {}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/client/servicestatus/adapter/ServiceStatusAdapter.java b/bus-evaluate-customer-product/src/main/java/infrastructure/client/servicestatus/adapter/ServiceStatusAdapter.java
new file mode 100644
index 0000000..2b97fa0
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/infrastructure/client/servicestatus/adapter/ServiceStatusAdapter.java
@@ -0,0 +1,86 @@
+package infrastructure.client.servicestatus.adapter;
+
+import application.exception.GatewayServiceException;
+import application.exception.ServiceCommunicationException;
+import application.port.input.command.ServiceStatusCommand;
+import com.banesco.common.infraestructure.helpers.LoggerHelper;
+import domain.model.ServiceStatus;
+import domain.repository.IStatusPort;
+import infrastructure.client.servicestatus.api.ServiceStatusClient;
+import infrastructure.mapper.ServiceStatusMapper;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+import jakarta.ws.rs.WebApplicationException;
+import lombok.extern.slf4j.Slf4j;
+import org.eclipse.microprofile.rest.client.inject.RestClient;
+
+@Slf4j
+@ApplicationScoped
+public class ServiceStatusAdapter implements IStatusPort {
+
+ private final ServiceStatusClient client;
+
+ @Inject
+ public ServiceStatusAdapter(@RestClient ServiceStatusClient client) {
+ this.client = client;
+ }
+
+ /**
+ * Implementa la comunicación con la API externa para obtener el estado de un servicio.
+ *
+ * @param command El comando de dominio con los datos de la consulta.
+ * @param requestId El ID de la solicitud para trazabilidad.
+ * @return Un objeto {@link ServiceStatus} con el estado del servicio.
+ * @throws GatewayServiceException si el servicio externo no es accesible, devuelve un error,
+ * o la respuesta tiene un formato inesperado.
+ */
+ @Override
+ public ServiceStatus getServiceStatus(ServiceStatusCommand command, String requestId) {
+ try {
+ log.debug(LoggerHelper.buildInfoPrivateRequest(requestId, "Adaptador: Mapeando Command a DTO para Service Status"));
+ var requestDto = ServiceStatusMapper.toRequestDto(command);
+
+ log.info(LoggerHelper.buildInfoPrivateRequest(requestId, "Llamando al servicio de service status", requestDto));
+ var response = client.getServiceStatus(requestId, requestDto);
+
+ log.info(LoggerHelper.buildInfoPrivateResponse(requestId, "Respuesta del servicio de service status", response));
+
+ // Validación de contrato: asegura que la respuesta no sea nula o inválida.
+ if (response == null || response.getStatus() == null) {
+ log.warn(LoggerHelper.buildError(requestId, "Respuesta invalida o nula del servicio de service status."));
+ throw new GatewayServiceException("Respuesta invalida del servicio externo de estado.");
+ }
+
+ return ServiceStatusMapper.toEntity(response);
+
+ } catch (WebApplicationException ex) {
+ var errorResponse = ex.getResponse();
+ var status = errorResponse.getStatus();
+ String errorDetails;
+
+ // Verifica si el Content-Type es HTML
+ if (errorResponse.getMediaType() != null && errorResponse.getMediaType().toString().contains("text/html")) {
+ errorDetails = "Se recibio una respuesta HTML no esperada del servicio.";
+ log.error(LoggerHelper.buildError(requestId,
+ String.format("Error HTTP %s del servicio de service status. %s", status, errorDetails)));
+ } else {
+ // Si no es HTML, probablemente es un error JSON que sí queremos ver.
+ var errorBody = errorResponse.readEntity(String.class);
+ // Aun así, es buena idea truncarlo por si es muy largo
+ errorDetails = errorBody.substring(0, Math.min(errorBody.length(), 1000));
+ log.error(LoggerHelper.buildError(requestId,
+ String.format("Error HTTP no manejado del servicio de service status. Status: %s. Body: %s", status, errorDetails)));
+ }
+
+ throw new GatewayServiceException("Error en el servicio de service status: " + errorDetails);
+ } catch (RuntimeException ex) {
+ // Captura errores de más bajo nivel (red, timeouts, host desconocido, etc.).
+ log.error(LoggerHelper.buildError(
+ requestId,
+ String.format("Error de comunicacion con el servicio de estado: %s", ex.getMessage())
+ ));
+
+ throw new ServiceCommunicationException("No se pudo comunicar con el servicio externo de service status", ex);
+ }
+ }
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/client/servicestatus/api/ServiceStatusClient.java b/bus-evaluate-customer-product/src/main/java/infrastructure/client/servicestatus/api/ServiceStatusClient.java
new file mode 100644
index 0000000..d03d52f
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/infrastructure/client/servicestatus/api/ServiceStatusClient.java
@@ -0,0 +1,21 @@
+package infrastructure.client.servicestatus.api;
+
+import com.banesco.common.infraestructure.web.dto.request.ServiceStatusRequest;
+import com.banesco.common.infraestructure.web.dto.response.ServiceStatusResponse;
+import infrastructure.interceptor.LoggingFilter;
+import jakarta.ws.rs.HeaderParam;
+import jakarta.ws.rs.POST;
+import org.eclipse.microprofile.rest.client.annotation.ClientHeaderParam;
+import org.eclipse.microprofile.rest.client.annotation.RegisterProvider;
+import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
+
+@RegisterRestClient(configKey = "get-service-status")
+@RegisterProvider(LoggingFilter.class)
+public interface ServiceStatusClient {
+ @POST
+ @ClientHeaderParam(name = "appId", value = "${app.appId}")
+ ServiceStatusResponse getServiceStatus(
+ @HeaderParam("requestId") String requestId,
+ ServiceStatusRequest request
+ );
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/client/userlogin/adapter/UserLoginAdapter.java b/bus-evaluate-customer-product/src/main/java/infrastructure/client/userlogin/adapter/UserLoginAdapter.java
new file mode 100644
index 0000000..11605b5
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/infrastructure/client/userlogin/adapter/UserLoginAdapter.java
@@ -0,0 +1,75 @@
+package infrastructure.client.userlogin.adapter;
+
+import application.exception.GatewayServiceException;
+import application.exception.ServiceCommunicationException;
+import application.port.input.command.UserLoginCommand;
+import com.banesco.common.infraestructure.helpers.LoggerHelper;
+import domain.model.UserLogin;
+import domain.repository.IUserLoginPort;
+import infrastructure.client.userlogin.api.UserLoginClient;
+import infrastructure.mapper.UserLoginMapper;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.ws.rs.WebApplicationException;
+import lombok.extern.slf4j.Slf4j;
+import org.eclipse.microprofile.rest.client.inject.RestClient;
+
+@Slf4j
+@ApplicationScoped
+public class UserLoginAdapter implements IUserLoginPort {
+
+ private final UserLoginClient client;
+ private final UserLoginMapper mapper;
+
+ public UserLoginAdapter(
+ @RestClient UserLoginClient client,
+ UserLoginMapper mapper
+ ) {
+ this.client = client;
+ this.mapper = mapper;
+ }
+
+ @Override
+ public UserLogin execute(UserLoginCommand command, String requestId) {
+ try {
+ log.debug(LoggerHelper.buildInfoPrivateRequest(requestId, "Adaptador: Mapeando Command a DTO para la API privada de user login"));
+ var requestDto = mapper.toRequestDto(command, requestId);
+
+ log.info(LoggerHelper.buildInfoPrivateRequest(requestId, "Llamando al servicio de user login", requestDto));
+ var response = client.getUserLogin(requestId, requestDto);
+
+ log.info(LoggerHelper.buildInfoPrivateResponse(requestId, "Respuesta del servicio de user login", response));
+
+ if (response == null) {
+ log.warn(LoggerHelper.buildError(requestId, "Respuesta invalida o nula del servicio de user login."));
+ throw new GatewayServiceException("La respuesta del servicio de user login fue nula.");
+ }
+
+ return UserLoginMapper.toEntity(response);
+ } catch (WebApplicationException ex) {
+ var errorResponse = ex.getResponse();
+ var status = errorResponse.getStatus();
+ String errorDetails;
+
+ // Verifica si el Content-Type es HTML
+ if (errorResponse.getMediaType() != null && errorResponse.getMediaType().toString().contains("text/html")) {
+ errorDetails = "Se recibio una respuesta HTML no esperada del servicio.";
+ log.error(LoggerHelper.buildError(requestId,
+ String.format("Error HTTP %s del servicio de user login. %s", status, errorDetails)));
+ } else {
+ // Si no es HTML, probablemente es un error JSON que sí queremos ver.
+ var errorBody = errorResponse.readEntity(String.class);
+ // Aun así, es buena idea truncarlo por si es muy largo
+ errorDetails = errorBody.substring(0, Math.min(errorBody.length(), 1000));
+ log.error(LoggerHelper.buildError(requestId,
+ String.format("Error HTTP no manejado del servicio de user login. Status: %s. Body: %s", status, errorDetails)));
+ }
+
+ throw new GatewayServiceException("Error en el servicio de user login: " + errorDetails);
+ } catch (RuntimeException ex) {
+ // Error de red (Timeout, no se puede conectar, etc.)
+ log.error(LoggerHelper.buildError(requestId, String.format("Error de comunicacion con el servicio de user login %s", ex)));
+
+ throw new ServiceCommunicationException("No se pudo comunicar con el servicio externo de user login", ex);
+ }
+ }
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/client/userlogin/api/UserLoginClient.java b/bus-evaluate-customer-product/src/main/java/infrastructure/client/userlogin/api/UserLoginClient.java
new file mode 100644
index 0000000..600d5c8
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/infrastructure/client/userlogin/api/UserLoginClient.java
@@ -0,0 +1,22 @@
+package infrastructure.client.userlogin.api;
+
+import com.banesco.common.domain.model.BaseResponse;
+import infrastructure.client.userlogin.dto.request.UserLoginRequest;
+import infrastructure.client.userlogin.dto.response.UserLoginResponse;
+import infrastructure.interceptor.LoggingFilter;
+import jakarta.ws.rs.HeaderParam;
+import jakarta.ws.rs.POST;
+import org.eclipse.microprofile.rest.client.annotation.ClientHeaderParam;
+import org.eclipse.microprofile.rest.client.annotation.RegisterProvider;
+import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
+
+@RegisterRestClient(configKey = "user-login")
+@RegisterProvider(LoggingFilter.class)
+public interface UserLoginClient {
+ @POST
+ @ClientHeaderParam(name = "appId", value = "${app.appId}")
+ BaseResponse getUserLogin(
+ @HeaderParam("requestId") String requestId,
+ UserLoginRequest userLoginRequest
+ );
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/client/userlogin/dto/request/UserLoginRequest.java b/bus-evaluate-customer-product/src/main/java/infrastructure/client/userlogin/dto/request/UserLoginRequest.java
new file mode 100644
index 0000000..cc63e66
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/infrastructure/client/userlogin/dto/request/UserLoginRequest.java
@@ -0,0 +1,28 @@
+package infrastructure.client.userlogin.dto.request;
+
+import io.quarkus.runtime.annotations.RegisterForReflection;
+import lombok.Builder;
+import lombok.Value;
+
+@Value
+@Builder
+@RegisterForReflection
+public class UserLoginRequest {
+ DataRequest dataRequest;
+
+ @Value
+ @Builder
+ public static class DataRequest {
+ String requestId;
+ String sourceChannelCode;
+ DataUser dataUser;
+ }
+
+ @Value
+ @Builder
+ public static class DataUser {
+ String customerId;
+ String customerIdType;
+ }
+
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/client/userlogin/dto/response/UserLoginResponse.java b/bus-evaluate-customer-product/src/main/java/infrastructure/client/userlogin/dto/response/UserLoginResponse.java
new file mode 100644
index 0000000..ee1ab41
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/infrastructure/client/userlogin/dto/response/UserLoginResponse.java
@@ -0,0 +1,32 @@
+package infrastructure.client.userlogin.dto.response;
+
+import io.quarkus.runtime.annotations.RegisterForReflection;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.Value;
+
+@Data
+@NoArgsConstructor
+@RegisterForReflection
+public class UserLoginResponse {
+ // --- Campos de la respuesta de DEV ---
+ String usernameBol;
+ String usernameBOLE;
+
+ // --- Campos de la respuesta de QA ---
+ String requestId;
+ String sourceChannelCode;
+ DataUser dataUser;
+
+ /**
+ * DTO anidado para representar el objeto "dataUser" de QA.
+ */
+ @Data
+ @NoArgsConstructor
+ @RegisterForReflection
+ public static class DataUser {
+ String usernameBol;
+ String usernameBOLE;
+ }
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/client/validateprofile/adapter/ValidateProfileAdapter.java b/bus-evaluate-customer-product/src/main/java/infrastructure/client/validateprofile/adapter/ValidateProfileAdapter.java
new file mode 100644
index 0000000..72e232e
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/infrastructure/client/validateprofile/adapter/ValidateProfileAdapter.java
@@ -0,0 +1,74 @@
+package infrastructure.client.validateprofile.adapter;
+
+import application.exception.GatewayServiceException;
+import application.port.input.command.ValidateProfileCommand;
+import com.banesco.common.infraestructure.helpers.LoggerHelper;
+import domain.model.ClientProfile;
+import domain.repository.IValidateProfilePort;
+import infrastructure.client.validateprofile.api.ValidateProfileClient;
+import infrastructure.mapper.ValidateProfileMapper;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.ws.rs.WebApplicationException;
+import lombok.extern.slf4j.Slf4j;
+import org.eclipse.microprofile.rest.client.inject.RestClient;
+
+@Slf4j
+@ApplicationScoped
+public class ValidateProfileAdapter implements IValidateProfilePort {
+
+ private final ValidateProfileClient client;
+
+ public ValidateProfileAdapter(@RestClient ValidateProfileClient client) {
+ this.client = client;
+ }
+
+ @Override
+ public ClientProfile execute(ValidateProfileCommand command, String requestId) {
+ try {
+ log.debug(LoggerHelper.buildInfoPrivateRequest(requestId, "Adaptador: Mapeando Command a DTO para Service Status"));
+ var requestDto = ValidateProfileMapper.toRequestDto(command);
+
+ log.info(LoggerHelper.buildInfoPrivateRequest(requestId, "Llamando al servicio de estado de validate profile", requestDto));
+ var response = client.validateProfile(requestId, requestDto);
+
+ log.info(LoggerHelper.buildInfoPrivateResponse(requestId, "Respuesta del servicio de estado de validate profile", response));
+
+ // Validación de contrato: asegura que la respuesta no sea nula o inválida.
+ if (response == null) {
+ log.warn(LoggerHelper.buildError(requestId, "Respuesta invalida o nula del servicio de validate profile."));
+ throw new GatewayServiceException("Respuesta invalida del servicio externo de validate profile.");
+ }
+
+ return ValidateProfileMapper.toEntity(response);
+
+ } catch (WebApplicationException ex) {
+ var errorResponse = ex.getResponse();
+ var status = errorResponse.getStatus();
+ String errorDetails;
+
+ // Verifica si el Content-Type es HTML
+ if (errorResponse.getMediaType() != null && errorResponse.getMediaType().toString().contains("text/html")) {
+ errorDetails = "Se recibio una respuesta HTML no esperada del servicio.";
+ log.error(LoggerHelper.buildError(requestId,
+ String.format("Error HTTP %s del servicio validate profile. %s", status, errorDetails)));
+ } else {
+ // Si no es HTML, probablemente es un error JSON que sí queremos ver.
+ var errorBody = errorResponse.readEntity(String.class);
+ // Aun así, es buena idea truncarlo por si es muy largo
+ errorDetails = errorBody.substring(0, Math.min(errorBody.length(), 1000));
+ log.error(LoggerHelper.buildError(requestId,
+ String.format("Error HTTP no manejado del servicio de validate profile. Status: %s. Body: %s", status, errorDetails)));
+ }
+
+ throw new GatewayServiceException("Error en el servicio de validate profile: " + errorDetails);
+ } catch (RuntimeException ex) {
+ // Captura errores de más bajo nivel (red, timeouts, host desconocido, etc.).
+ log.error(LoggerHelper.buildError(
+ requestId,
+ String.format("Error de comunicacion con el servicio de validate profile: %s", ex.getMessage())
+ ));
+ // Traduce el error técnico a una excepción de la aplicación.
+ throw new GatewayServiceException("No se pudo comunicar con el servicio externo de validate profile.");
+ }
+ }
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/client/validateprofile/api/ValidateProfileClient.java b/bus-evaluate-customer-product/src/main/java/infrastructure/client/validateprofile/api/ValidateProfileClient.java
new file mode 100644
index 0000000..5c00991
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/infrastructure/client/validateprofile/api/ValidateProfileClient.java
@@ -0,0 +1,21 @@
+package infrastructure.client.validateprofile.api;
+
+import infrastructure.client.validateprofile.dto.request.ValidateProfileRequest;
+import infrastructure.client.validateprofile.dto.response.ValidateProfileResponse;
+import infrastructure.interceptor.LoggingFilter;
+import jakarta.ws.rs.HeaderParam;
+import jakarta.ws.rs.POST;
+import org.eclipse.microprofile.rest.client.annotation.ClientHeaderParam;
+import org.eclipse.microprofile.rest.client.annotation.RegisterProvider;
+import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
+
+@RegisterRestClient(configKey = "validate-profile")
+@RegisterProvider(LoggingFilter.class)
+public interface ValidateProfileClient {
+ @POST
+ @ClientHeaderParam(name = "appId", value = "${app.appId}")
+ ValidateProfileResponse validateProfile(
+ @HeaderParam("requestId") String requestId,
+ ValidateProfileRequest validateProfileRequest
+ );
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/client/validateprofile/dto/request/ValidateProfileRequest.java b/bus-evaluate-customer-product/src/main/java/infrastructure/client/validateprofile/dto/request/ValidateProfileRequest.java
new file mode 100644
index 0000000..b730573
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/infrastructure/client/validateprofile/dto/request/ValidateProfileRequest.java
@@ -0,0 +1,14 @@
+package infrastructure.client.validateprofile.dto.request;
+
+import io.quarkus.runtime.annotations.RegisterForReflection;
+import lombok.Builder;
+import lombok.Value;
+
+@Value
+@Builder
+@RegisterForReflection
+public class ValidateProfileRequest {
+ String customerId;
+ String ipAddress;
+ String hash;
+}
diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/client/validateprofile/dto/response/ValidateProfileResponse.java b/bus-evaluate-customer-product/src/main/java/infrastructure/client/validateprofile/dto/response/ValidateProfileResponse.java
new file mode 100644
index 0000000..45c91e0
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/infrastructure/client/validateprofile/dto/response/ValidateProfileResponse.java
@@ -0,0 +1,30 @@
+package infrastructure.client.validateprofile.dto.response;
+
+import io.quarkus.runtime.annotations.RegisterForReflection;
+import lombok.Builder;
+import lombok.Value;
+
+@Value
+@Builder
+@RegisterForReflection
+public class ValidateProfileResponse {
+ Boolean hasError;
+ String errorMessage;
+ String errorCode;
+ Object data;
+
+ public ValidateProfileResponse() {
+ this.hasError = false;
+ this.errorMessage = "";
+ this.errorCode = "";
+ this.data = null;
+ }
+
+ public ValidateProfileResponse(Boolean hasError, String errorMessage, String errorCode, Object data) {
+ this.hasError = hasError;
+ this.errorMessage = errorMessage;
+ this.errorCode = errorCode;
+ this.data = data;
+ }
+
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/config/AccountAvailabilityConfig.java b/bus-evaluate-customer-product/src/main/java/infrastructure/config/AccountAvailabilityConfig.java
new file mode 100644
index 0000000..fcc58b0
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/infrastructure/config/AccountAvailabilityConfig.java
@@ -0,0 +1,9 @@
+package infrastructure.config;
+
+import io.smallrye.config.ConfigMapping;
+
+@ConfigMapping(prefix = "app.account-client.availability.schedule")
+public interface AccountAvailabilityConfig {
+ String begin();
+ String finish();
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/config/AppConfig.java b/bus-evaluate-customer-product/src/main/java/infrastructure/config/AppConfig.java
new file mode 100644
index 0000000..99eb65a
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/infrastructure/config/AppConfig.java
@@ -0,0 +1,88 @@
+package infrastructure.config;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import domain.model.ErrorCode;
+import jakarta.annotation.PostConstruct;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+import lombok.Getter;
+import lombok.extern.slf4j.Slf4j;
+import org.eclipse.microprofile.config.inject.ConfigProperty;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+@Slf4j
+@Getter
+@ApplicationScoped
+public class AppConfig {
+
+ @Inject
+ ObjectMapper objectMapper;
+
+ @ConfigProperty(name = "app.response-json")
+ private Optional responseJsonString;
+
+ @ConfigProperty(name = "app.print-response", defaultValue = "false")
+ private Boolean printResponse;
+
+ private List errorCodesList;
+ private Map errorCodeMap;
+
+ @PostConstruct
+ public void init() {
+ log.info("Inicializando configuracion de la aplicacion...");
+
+ if (responseJsonString.isPresent() && !responseJsonString.get().isBlank()) {
+ try {
+ this.errorCodesList = objectMapper.readValue(responseJsonString.get(), new TypeReference>() {});
+ this.errorCodeMap = this.errorCodesList.stream()
+ .collect(Collectors.toMap(
+ ErrorCode::backendCode,
+ Function.identity(),
+ (existing, replacement) -> existing
+ ));
+ log.info("Se cargaron {} codigos de error en el mapa.", this.errorCodeMap.size());
+ } catch (JsonProcessingException e) {
+ log.error("Error fatal al parsear la propiedad 'app.response-json'.", e);
+ this.errorCodesList = Collections.emptyList();
+ this.errorCodeMap = Collections.emptyMap();
+ }
+ } else {
+ log.warn("La propiedad 'app.response-json' no esta definida o esta vacia.");
+ this.errorCodesList = Collections.emptyList();
+ this.errorCodeMap = Collections.emptyMap();
+ }
+
+ log.info("--- Validando propiedades de configuracion ---");
+
+ if (errorCodesList.isEmpty()) {
+ log.warn("JSON de respuesta no configurado o vacio. El resolvedor de codigos podria no operar correctamente.");
+ } else {
+ log.info("Configuracion de 'app.response-json' validada.");
+ }
+
+ log.info("app.print-response configurado: {}", printResponse);
+
+ log.info("Configuracion completada.");
+ }
+
+ public List getAppJson() {
+ return errorCodesList;
+ }
+
+ public Map getErrorCodeMap() {
+ return errorCodeMap;
+ }
+
+ public Boolean getPrintResponse() {
+ return printResponse;
+ }
+
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/config/AvailabilityBolConfig.java b/bus-evaluate-customer-product/src/main/java/infrastructure/config/AvailabilityBolConfig.java
new file mode 100644
index 0000000..a0dfd69
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/infrastructure/config/AvailabilityBolConfig.java
@@ -0,0 +1,10 @@
+package infrastructure.config;
+
+import io.smallrye.config.ConfigMapping;
+
+@ConfigMapping(prefix = "app.account-client.availability.schedule.param")
+public interface AvailabilityBolConfig {
+ String codBan();
+ String codEve();
+ String codServ();
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/config/DomainConfig.java b/bus-evaluate-customer-product/src/main/java/infrastructure/config/DomainConfig.java
new file mode 100644
index 0000000..0cbb5ea
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/infrastructure/config/DomainConfig.java
@@ -0,0 +1,8 @@
+package infrastructure.config;
+
+import io.smallrye.config.ConfigMapping;
+
+@ConfigMapping(prefix = "app.domain.api.public")
+public interface DomainConfig {
+ String payment();
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/config/OtpConfig.java b/bus-evaluate-customer-product/src/main/java/infrastructure/config/OtpConfig.java
new file mode 100644
index 0000000..54ad7fc
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/infrastructure/config/OtpConfig.java
@@ -0,0 +1,9 @@
+package infrastructure.config;
+
+import io.smallrye.config.ConfigMapping;
+
+@ConfigMapping(prefix = "app.otp")
+public interface OtpConfig {
+ String channel();
+ String servicio();
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/config/PostgresRepositoryProducer.java b/bus-evaluate-customer-product/src/main/java/infrastructure/config/PostgresRepositoryProducer.java
new file mode 100644
index 0000000..8a01fc0
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/infrastructure/config/PostgresRepositoryProducer.java
@@ -0,0 +1,34 @@
+package infrastructure.config;
+
+import com.banesco.common.domain.model.redis.postgres.customer_product.CustomerAccountConfiguration;
+import com.banesco.common.domain.model.redis.postgres.customer_product.CustomerAccountJsonConfig;
+import com.banesco.common.domain.model.redis.postgres.customer_product.CustomerAccountPostgresConfig;
+import com.banesco.common.infraestructure.repository.PostgresRedisRepository;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import io.quarkus.redis.client.RedisClient;
+import io.quarkus.runtime.annotations.RegisterForReflection;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+import jakarta.ws.rs.Produces;
+
+@ApplicationScoped
+@RegisterForReflection(targets = {
+ CustomerAccountPostgresConfig.class,
+ CustomerAccountConfiguration.class,
+ CustomerAccountJsonConfig.class
+})
+public class PostgresRepositoryProducer {
+
+ @Inject
+ RedisClient redisClient;
+
+ @Inject
+ ObjectMapper objectMapper;
+
+ @Produces
+ @ApplicationScoped
+ public PostgresRedisRepository postgresRedisRepository() {
+ return new PostgresRedisRepository(redisClient, objectMapper);
+ }
+
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/config/RedisRepositoryProducer.java b/bus-evaluate-customer-product/src/main/java/infrastructure/config/RedisRepositoryProducer.java
new file mode 100644
index 0000000..f5a2674
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/infrastructure/config/RedisRepositoryProducer.java
@@ -0,0 +1,27 @@
+package infrastructure.config;
+
+import com.banesco.common.domain.model.redis.UserAccountRedis;
+import com.banesco.common.infraestructure.repository.UserAccountRedisRepository;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import io.quarkus.redis.client.RedisClient;
+import io.quarkus.runtime.annotations.RegisterForReflection;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+import jakarta.ws.rs.Produces;
+
+@ApplicationScoped
+@RegisterForReflection(targets = {UserAccountRedis.class})
+public class RedisRepositoryProducer {
+
+ @Inject
+ RedisClient redisClient;
+
+ @Inject
+ ObjectMapper objectMapper;
+
+ @Produces
+ @ApplicationScoped
+ public UserAccountRedisRepository userAccountRedisRepository() {
+ return new UserAccountRedisRepository(redisClient, objectMapper);
+ }
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/config/RegisterSecurityConfig.java b/bus-evaluate-customer-product/src/main/java/infrastructure/config/RegisterSecurityConfig.java
new file mode 100644
index 0000000..369f104
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/infrastructure/config/RegisterSecurityConfig.java
@@ -0,0 +1,11 @@
+package infrastructure.config;
+
+import io.smallrye.config.ConfigMapping;
+
+@ConfigMapping(prefix = "app.register-security")
+public interface RegisterSecurityConfig {
+ String eventCod();
+ String bankCod();
+ String curCod();
+ String sp();
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/config/qualifiers/ServiceType.java b/bus-evaluate-customer-product/src/main/java/infrastructure/config/qualifiers/ServiceType.java
new file mode 100644
index 0000000..a7b676d
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/infrastructure/config/qualifiers/ServiceType.java
@@ -0,0 +1,8 @@
+package infrastructure.config.qualifiers;
+
+
+public enum ServiceType {
+ BLACKLIST,
+ COMPANY_DATA,
+ PAYMENT,
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/config/qualifiers/SoapService.java b/bus-evaluate-customer-product/src/main/java/infrastructure/config/qualifiers/SoapService.java
new file mode 100644
index 0000000..6def084
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/infrastructure/config/qualifiers/SoapService.java
@@ -0,0 +1,15 @@
+package infrastructure.config.qualifiers;
+
+import jakarta.inject.Qualifier;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Qualifier
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER, ElementType.TYPE})
+public @interface SoapService {
+ ServiceType value();
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/context/TraceIdContext.java b/bus-evaluate-customer-product/src/main/java/infrastructure/context/TraceIdContext.java
new file mode 100644
index 0000000..530ff19
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/infrastructure/context/TraceIdContext.java
@@ -0,0 +1,12 @@
+package infrastructure.context;
+
+import jakarta.enterprise.context.RequestScoped;
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+@RequestScoped
+public class TraceIdContext {
+ private String traceId;
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/exception/ConstraintViolationExceptionHandler.java b/bus-evaluate-customer-product/src/main/java/infrastructure/exception/ConstraintViolationExceptionHandler.java
new file mode 100644
index 0000000..b67e6b3
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/infrastructure/exception/ConstraintViolationExceptionHandler.java
@@ -0,0 +1,116 @@
+package infrastructure.exception;
+
+import domain.dto.response.CustomerResponse;
+import domain.dto.response.StatusResponse;
+import infrastructure.context.TraceIdContext;
+import infrastructure.service.StatusDescriptionResolver;
+import jakarta.inject.Inject;
+import jakarta.validation.ConstraintViolation;
+import jakarta.validation.ConstraintViolationException;
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.ext.ExceptionMapper;
+import jakarta.ws.rs.ext.Provider;
+import lombok.extern.slf4j.Slf4j;
+import org.jboss.logging.Logger;
+
+import java.util.Set;
+import java.util.stream.Collectors;
+
+@Slf4j
+@Provider
+public class ConstraintViolationExceptionHandler implements ExceptionMapper {
+
+ private static final Logger LOG = Logger.getLogger(ConstraintViolationExceptionHandler.class);
+ private static final String DEFAULT_VALIDATION_ERROR_CODE = "VDE-400"; // Código de fallback
+
+ @Inject
+ TraceIdContext traceIdContext;
+
+ @Inject
+ StatusDescriptionResolver statusDescriptionResolver;
+
+ @Override
+ public Response toResponse(ConstraintViolationException exception) {
+ var traceId = traceIdContext.getTraceId();
+ var violations = exception.getConstraintViolations();
+
+ if (violations.isEmpty()) {
+ return buildGenericErrorResponse(traceId, exception);
+ }
+
+ // Se busca la violación con la mayor prioridad según la lista definida.
+ var highestPriorityViolation = findHighestPriorityViolation(violations);
+
+ // Extraer el backendCode directamente del mensaje de la anotación.
+ var backendCode = highestPriorityViolation.getMessage();
+ var fieldPath = highestPriorityViolation.getPropertyPath().toString();
+
+ // Obtenemos el nombre simple del campo para el mensaje.
+ var jsonProperty = fieldPath.substring(fieldPath.lastIndexOf('.') + 1);
+
+ LOG.warnf("[traceId: %s] Error de validacion en el campo '%s'. Mensaje/Codigo: '%s'", traceId, fieldPath, backendCode);
+
+ // Obtener los detalles del error.
+ var resolvedCode = statusDescriptionResolver.getCode(backendCode);
+
+ var statusResponse = StatusResponse.builder()
+ .status("error")
+ .statusCode(resolvedCode.statusCode())
+ .message(resolvedCode.description() + ": " + jsonProperty)
+ .traceId(traceId)
+ .build();
+
+ var response = new CustomerResponse(statusResponse);
+
+ return Response.status(Response.Status.BAD_REQUEST)
+ .entity(response)
+ .build();
+ }
+
+ private ConstraintViolation> findHighestPriorityViolation(Set> violations) {
+ var violationMap = violations.stream()
+ .collect(Collectors.toMap(
+ v -> {
+ String path = v.getPropertyPath().toString();
+ return path.substring(path.lastIndexOf('.') + 1);
+ },
+ v -> v,
+ // En caso de duplicados, toma el primero
+ (v1, v2) -> v1
+ ));
+
+// for (var fieldName : FIELD_PRIORITY_ORDER) {
+// if (violationMap.containsKey(fieldName)) {
+// return violationMap.get(fieldName);
+// }
+// }
+
+ // Si ninguna violación coincide con la lista de prioridades (fallback), devuelve la primera que encuentre.
+ return violations.stream().findFirst().get();
+ }
+
+ /**
+ * Construye una respuesta de error genérica en caso de que no se encuentren violaciones específicas.
+ */
+ private Response buildGenericErrorResponse(String traceId, ConstraintViolationException exception) {
+ var detailsForLog = exception.getConstraintViolations()
+ .stream()
+ .map(cv -> cv.getPropertyPath() + ": " + cv.getMessage())
+ .collect(Collectors.joining("; "));
+
+ LOG.warnf("[traceId: %s] Error de validación genérico detectado, %s", traceId, detailsForLog);
+
+ var statusResponse = StatusResponse.builder()
+ .status("error")
+ .statusCode("VDE-400")
+ .message("Error en los datos de entrada. Verifique la información enviada")
+ .traceId(traceId)
+ .build();
+
+ var response = new CustomerResponse(statusResponse);
+
+ return Response.status(Response.Status.BAD_REQUEST)
+ .entity(response)
+ .build();
+ }
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/exception/GlobalExceptionHandler.java b/bus-evaluate-customer-product/src/main/java/infrastructure/exception/GlobalExceptionHandler.java
new file mode 100644
index 0000000..bee5fca
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/infrastructure/exception/GlobalExceptionHandler.java
@@ -0,0 +1,83 @@
+package infrastructure.exception;
+
+import application.exception.GatewayServiceException;
+import com.banesco.common.application.exception.BanBackendException;
+import domain.dto.response.CustomerResponse;
+import domain.model.ErrorCode;
+import infrastructure.context.TraceIdContext;
+import infrastructure.service.StatusDescriptionResolver;
+import jakarta.inject.Inject;
+import jakarta.ws.rs.BadRequestException;
+import jakarta.ws.rs.NotAllowedException;
+import jakarta.ws.rs.NotFoundException;
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.ext.ExceptionMapper;
+import jakarta.ws.rs.ext.Provider;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+@Provider
+public class GlobalExceptionHandler implements ExceptionMapper {
+
+ @Inject
+ TraceIdContext traceIdContext;
+
+ @Inject
+ StatusDescriptionResolver statusDescriptionResolver;
+
+ @Override
+ public Response toResponse(Throwable exception) {
+ final var requestId = traceIdContext.getTraceId();
+ ErrorCode resolvedCode;
+
+ log.info("[ERROR: {}] Error de backend no manejado que llego al GlobalExceptionHandler", requestId, exception);
+
+ var exceptionClassName = exception.getClass().getName();
+
+ if (exception instanceof BanBackendException bbe) {
+ resolvedCode = statusDescriptionResolver.getCode(bbe.getBackendCode());
+ if (resolvedCode == null) {
+ resolvedCode = statusDescriptionResolver.getCode("500");
+ }
+ } else if (exceptionClassName.equals(GatewayServiceException.class.getName()) ||
+ exceptionClassName.equals(application.exception.ServiceCommunicationException.class.getName())) {
+ resolvedCode = statusDescriptionResolver.getCode("503-URL");
+
+ } else if (exception instanceof NotFoundException) {
+ resolvedCode = statusDescriptionResolver.getCode("ERR-404");
+
+ } else if (exception instanceof NotAllowedException) {
+ resolvedCode = statusDescriptionResolver.getCode("405");
+
+ } else if (isClientError(exception)) {
+ resolvedCode = statusDescriptionResolver.getCode("ERR-400");
+
+ } else {
+ // Default para cualquier otra excepción no controlada
+ log.error("[ERROR: {}] Error de backend no manejado que llego al GlobalExceptionHandler", requestId, exception);
+ resolvedCode = statusDescriptionResolver.getCode("500");
+ }
+
+ var statusResponse = domain.dto.response.StatusResponse.builder()
+ .status("error")
+ .statusCode(resolvedCode.statusCode())
+ .message(resolvedCode.description())
+ .traceId(requestId)
+ .build();
+
+ var response = new CustomerResponse(statusResponse);
+
+ // Es importante devolver el código de estado HTTP correcto.
+ int httpStatus = Integer.parseInt(resolvedCode.statusCode().split("-")[0]);
+
+ return Response.status(httpStatus)
+ .entity(response)
+ .build();
+ }
+
+ private boolean isClientError(Throwable throwable) {
+ return throwable instanceof BadRequestException ||
+ throwable instanceof com.fasterxml.jackson.core.JsonProcessingException ||
+ throwable instanceof NullPointerException;
+ }
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/filter/HeadersFilter.java b/bus-evaluate-customer-product/src/main/java/infrastructure/filter/HeadersFilter.java
new file mode 100644
index 0000000..d46bdb5
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/infrastructure/filter/HeadersFilter.java
@@ -0,0 +1,43 @@
+package infrastructure.filter;
+
+import com.banesco.common.infraestructure.helpers.LoggerHelper;
+import infrastructure.config.AppConfig;
+import infrastructure.context.TraceIdContext;
+import jakarta.annotation.Priority;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+import jakarta.ws.rs.container.ContainerRequestContext;
+import jakarta.ws.rs.container.ContainerRequestFilter;
+import jakarta.ws.rs.ext.Provider;
+import lombok.extern.slf4j.Slf4j;
+
+import java.util.stream.Collectors;
+
+
+@Slf4j
+@Provider
+@Priority(200)
+@ApplicationScoped
+public class HeadersFilter implements ContainerRequestFilter {
+
+ @Inject
+ AppConfig appConfig;
+
+ @Inject
+ TraceIdContext traceIdContext;
+
+ @Override
+ public void filter(ContainerRequestContext requestContext) {
+ var requestId = traceIdContext.getTraceId();
+
+ var multiMappedHeaders = requestContext.getHeaders();
+
+ var headersMap = multiMappedHeaders.keySet().stream()
+ .collect(Collectors.toMap(
+ key -> key,
+ multiMappedHeaders::getFirst
+ ));
+
+ log.info(LoggerHelper.buildInfoPrivateRequest(requestId, LoggerHelper.formatHeaders(headersMap)));
+ }
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/filter/TraceIdFilter.java b/bus-evaluate-customer-product/src/main/java/infrastructure/filter/TraceIdFilter.java
new file mode 100644
index 0000000..67357b4
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/infrastructure/filter/TraceIdFilter.java
@@ -0,0 +1,26 @@
+package infrastructure.filter;
+
+import com.banesco.common.infraestructure.helpers.RequestHelper;
+import infrastructure.context.TraceIdContext;
+import jakarta.annotation.Priority;
+import jakarta.inject.Inject;
+import jakarta.ws.rs.container.ContainerRequestContext;
+import jakarta.ws.rs.container.ContainerRequestFilter;
+import jakarta.ws.rs.ext.Provider;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+@Provider
+@Priority(100)
+public class TraceIdFilter implements ContainerRequestFilter {
+
+ @Inject
+ TraceIdContext traceIdContext;
+
+ @Override
+ public void filter(ContainerRequestContext requestContext) {
+ var headerId = requestContext.getHeaderString("requestId");
+ var finalTraceId = (headerId != null && !headerId.isBlank()) ? headerId : RequestHelper.randomAlphanumeric(20);
+ traceIdContext.setTraceId(finalTraceId);
+ }
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/healthcheck/ApplicationLivenessCheck.java b/bus-evaluate-customer-product/src/main/java/infrastructure/healthcheck/ApplicationLivenessCheck.java
new file mode 100644
index 0000000..7016427
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/infrastructure/healthcheck/ApplicationLivenessCheck.java
@@ -0,0 +1,36 @@
+package infrastructure.healthcheck;
+
+import jakarta.enterprise.context.ApplicationScoped;
+import lombok.extern.slf4j.Slf4j;
+import org.eclipse.microprofile.config.inject.ConfigProperty;
+import org.eclipse.microprofile.health.HealthCheck;
+import org.eclipse.microprofile.health.HealthCheckResponse;
+import org.eclipse.microprofile.health.Liveness;
+
+@Slf4j
+@Liveness
+@ApplicationScoped
+public class ApplicationLivenessCheck implements HealthCheck {
+
+ private final String applicationName;
+ private final String applicationVersion;
+
+
+ public ApplicationLivenessCheck(
+ @ConfigProperty(name = "quarkus.application.name", defaultValue = "api-application") String applicationName,
+ @ConfigProperty(name = "quarkus.application.version", defaultValue = "unknown") String applicationVersion) {
+ this.applicationName = applicationName;
+ this.applicationVersion = applicationVersion;
+ }
+
+ @Override
+ public HealthCheckResponse call() {
+ log.debug("Ejecutando liveness health check: {}", System.currentTimeMillis());
+ return HealthCheckResponse.named("API")
+ .up()
+ .withData("application", applicationName)
+ .withData("version", applicationVersion)
+ .withData("timestamp", System.currentTimeMillis())
+ .build();
+ }
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/healthcheck/ApplicationLivenessCheck.java~ b/bus-evaluate-customer-product/src/main/java/infrastructure/healthcheck/ApplicationLivenessCheck.java~
new file mode 100644
index 0000000..ae975c8
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/infrastructure/healthcheck/ApplicationLivenessCheck.java~
@@ -0,0 +1,36 @@
+package infrastructure.healthcheck;
+
+import jakarta.enterprise.context.ApplicationScoped;
+import lombok.extern.slf4j.Slf4j;
+import org.eclipse.microprofile.config.inject.ConfigProperty;
+import org.eclipse.microprofile.health.HealthCheck;
+import org.eclipse.microprofile.health.HealthCheckResponse;
+import org.eclipse.microprofile.health.Liveness;
+
+@Liveness
+@ApplicationScoped
+@Slf4j
+public class ApplicationLivenessCheck implements HealthCheck {
+
+ private final String applicationName;
+ private final String applicationVersion;
+
+
+ public ApplicationLivenessCheck(
+ @ConfigProperty(name = "quarkus.application.name", defaultValue = "api-application") String applicationName,
+ @ConfigProperty(name = "quarkus.application.version", defaultValue = "unknown") String applicationVersion) {
+ this.applicationName = applicationName;
+ this.applicationVersion = applicationVersion;
+ }
+
+ @Override
+ public HealthCheckResponse call() {
+ log.debug("Ejecutando liveness health check: {}", System.currentTimeMillis());
+ return HealthCheckResponse.named("API")
+ .up()
+ .withData("application", applicationName)
+ .withData("version", applicationVersion)
+ .withData("timestamp", System.currentTimeMillis())
+ .build();
+ }
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/healthcheck/ApplicationStartupCheck.java b/bus-evaluate-customer-product/src/main/java/infrastructure/healthcheck/ApplicationStartupCheck.java
new file mode 100644
index 0000000..2a0636b
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/infrastructure/healthcheck/ApplicationStartupCheck.java
@@ -0,0 +1,38 @@
+package infrastructure.healthcheck;
+
+import jakarta.enterprise.context.ApplicationScoped;
+import lombok.extern.slf4j.Slf4j;
+import org.eclipse.microprofile.config.inject.ConfigProperty;
+import org.eclipse.microprofile.health.HealthCheck;
+import org.eclipse.microprofile.health.HealthCheckResponse;
+import org.eclipse.microprofile.health.Startup;
+
+/**
+ * Startup Check: Verifica que el contexto de la aplicación (CDI)
+ * se ha inicializado correctamente y las configuraciones básicas están disponibles.
+ * Este chequeo le dice a Kubernetes "El arranque interno finalizó",
+ * permitiendo que los chequeos de Liveness y Readiness tomen el control.
+ */
+@Slf4j
+@Startup
+@ApplicationScoped
+public class ApplicationStartupCheck implements HealthCheck {
+
+ private final String applicationName;
+
+ public ApplicationStartupCheck(
+ @ConfigProperty(name = "quarkus.application.name", defaultValue = "api-application") String applicationName) {
+ this.applicationName = applicationName;
+ }
+
+ @Override
+ public HealthCheckResponse call() {
+ log.info("Ejecutando startup health check para {}...", applicationName);
+
+ return HealthCheckResponse.named("Application Startup")
+ .up()
+ .withData("application", applicationName)
+ .withData("status", "Contexto de aplicación (CDI) iniciado.")
+ .build();
+ }
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/interceptor/LoggingFilter.java b/bus-evaluate-customer-product/src/main/java/infrastructure/interceptor/LoggingFilter.java
new file mode 100644
index 0000000..778a2f7
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/infrastructure/interceptor/LoggingFilter.java
@@ -0,0 +1,31 @@
+package infrastructure.interceptor;
+
+import infrastructure.context.TraceIdContext;
+import jakarta.ws.rs.client.ClientRequestContext;
+import jakarta.ws.rs.client.ClientRequestFilter;
+import jakarta.ws.rs.ext.Provider;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+
+@Provider
+public class LoggingFilter implements ClientRequestFilter {
+ private static final Logger LOG = LoggerFactory.getLogger(LoggingFilter.class);
+
+ private final TraceIdContext traceIdContext;
+
+ public LoggingFilter(TraceIdContext traceIdContext) {
+ this.traceIdContext = traceIdContext;
+ }
+
+ @Override
+ public void filter(ClientRequestContext requestContext) throws IOException {
+ var requestId = traceIdContext.getTraceId();
+
+ LOG.info("[{}] Peticion REST a: {} {}",
+ requestId,
+ requestContext.getMethod(),
+ requestContext.getUri());
+ }
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/mapper/BankingProductMapper.java b/bus-evaluate-customer-product/src/main/java/infrastructure/mapper/BankingProductMapper.java
new file mode 100644
index 0000000..bdbfca4
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/infrastructure/mapper/BankingProductMapper.java
@@ -0,0 +1,28 @@
+package infrastructure.mapper;
+
+import com.banesco.common.infraestructure.helpers.AccountHelper;
+import domain.model.BankingProduct;
+import domain.model.CustomerAccount;
+
+import java.util.List;
+
+public class BankingProductMapper {
+
+ public static List fromCustomerAccountList(List customerAccount, boolean maskAccount) {
+ final String USDCurrency = "USD";
+ return customerAccount.stream()
+ .map(data -> BankingProduct.builder()
+ .productNumber(
+ maskAccount ?
+ (USDCurrency.equals(data.getMoneda()) ?
+ AccountHelper.getTotalMaskedAccountId(data.getCuenta())
+ : AccountHelper.getMaskedAccountId(data.getCuenta())
+ ) :
+ data.getCuenta()
+ )
+ .productType(data.getTipoCta())
+ .balance(data.getSaldoDisponible())
+ .currency(data.getMoneda())
+ .build()).toList();
+ }
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/mapper/CacheMapper.java b/bus-evaluate-customer-product/src/main/java/infrastructure/mapper/CacheMapper.java
new file mode 100644
index 0000000..2fbc7c7
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/infrastructure/mapper/CacheMapper.java
@@ -0,0 +1,57 @@
+package infrastructure.mapper;
+
+import com.banesco.common.domain.model.redis.UserAccountRedis;
+import com.banesco.common.infraestructure.helpers.AccountHelper;
+import domain.model.CustomerAccount;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * Clase de utilidad para mapear (convertir) objetos del dominio
+ * a objetos específicos para la caché de Redis y viceversa.
+ */
+public class CacheMapper {
+
+ // Constructor privado para que nadie pueda instanciar esta clase de utilidad.
+ private CacheMapper() {}
+
+ /**
+ * Convierte una lista de BankingProduct (modelo de dominio) a una lista
+ * de UserAccountRedis (modelo de caché).
+ *
+ * @param bankingProducts La lista de productos a convertir.
+ * @param sid El Session ID que se asignará a cada objeto en la caché.
+ * @param customerId El ID del cliente.
+ * @return Una lista de UserAccountRedis lista para ser guardada en Redis.
+ */
+ public static List toUserAccountRedisList(List bankingProducts, String sid, String customerId, String otp) {
+ if (bankingProducts == null || bankingProducts.isEmpty()) {
+ return Collections.emptyList();
+ }
+
+ return bankingProducts.stream()
+ .map(product -> toUserAccountRedis(product, sid, customerId, otp))
+ .collect(Collectors.toList());
+ }
+
+ /**
+ * Lógica de conversión para un único objeto.
+ */
+ private static UserAccountRedis toUserAccountRedis(CustomerAccount product, String sid, String customerId, String otp) {
+ var redisAccount = new UserAccountRedis();
+ redisAccount.setSid(sid);
+ redisAccount.setCustomerId(customerId);
+ redisAccount.setClearAccount(product.getCuenta());
+ redisAccount.setMaskedAccount(AccountHelper.getMaskedAccountId(product.getCuenta()));
+ redisAccount.setCurrency(product.getMoneda().trim());
+ redisAccount.setAccountType(product.getTipoCta());
+ redisAccount.setUsername(customerId);
+ redisAccount.setOtp(otp);
+
+ return redisAccount;
+ }
+
+
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/mapper/CustomerAccountMapper.java b/bus-evaluate-customer-product/src/main/java/infrastructure/mapper/CustomerAccountMapper.java
new file mode 100644
index 0000000..ed4fe48
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/infrastructure/mapper/CustomerAccountMapper.java
@@ -0,0 +1,63 @@
+package infrastructure.mapper;
+
+import application.port.input.command.CustomerAccountCommand;
+import domain.model.CustomerAccount;
+import infrastructure.client.accounts.dto.request.CustomerAccountRequest;
+import infrastructure.client.accounts.dto.respone.CustomerAccountResponse;
+
+import java.util.Collections;
+import java.util.List;
+
+public final class CustomerAccountMapper {
+
+ public static List toEntityList(CustomerAccountResponse response) {
+ if (response == null || response.getData() == null) {
+ return Collections.emptyList();
+ }
+
+ return response.getData().stream()
+ .map(data -> CustomerAccount.builder()
+ .banco(data.getBanco())
+ .cuenta(data.getCuenta())
+ .producto(data.getProducto())
+ .descProd(data.getDescProd())
+ .claseCta(data.getClaseCta())
+ .tipoCta(data.getTipoCta())
+ .moneda(data.getMoneda())
+ .saldoDisponible(data.getSaldoDisponible())
+ .estatus(data.getEstatus())
+ .statusMe(data.getStatusMe())
+ .titular(data.getTitular())
+ .maximumPerDay(data.getMaximumPerDay())
+ .maximumPerTransaction(data.getMaximumPerTransaction())
+ .maximumPerMonth(data.getMaximumPerMonth())
+ .maximumMonthlyFee(data.getMaximumMonthlyFee())
+ .minimumPerTransaction(data.getMinimumPerTransaction())
+ .operator(data.getOperator())
+ .phone(data.getPhone())
+ .channel(data.getChannel())
+ .service(data.getService())
+ .affiliationStatus(data.getAffiliationStatus())
+ .build()).toList();
+ }
+
+ public static CustomerAccountRequest toRequestDto(CustomerAccountCommand command) {
+ return CustomerAccountRequest.builder()
+ .rif(command.getRif())
+ .bankCode(command.getBankCode())
+ .currencyCode(command.getCurrencyCode())
+ .status(command.getStatus())
+ .statusMe(command.getStatusMe())
+ .productCv(command.getProductCv())
+ .product(command.getProduct())
+ .channel(command.getChannel())
+ .service(command.getService())
+ .operator(command.getOperator())
+ .phone(command.getPhone())
+ .affiliationStatus(command.getAffiliationStatus())
+ .counterpartyAccount(command.getCounterpartyAccount())
+ .holder(command.getHolder())
+ .build();
+ }
+
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/mapper/EncodeValueMapper.java b/bus-evaluate-customer-product/src/main/java/infrastructure/mapper/EncodeValueMapper.java
new file mode 100644
index 0000000..6c1d186
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/infrastructure/mapper/EncodeValueMapper.java
@@ -0,0 +1,18 @@
+package infrastructure.mapper;
+
+import application.port.input.command.EncodeValueCommand;
+import org.tempuri.Prueba;
+import org.tempuri.PruebaResponse;
+
+public class EncodeValueMapper {
+ public static Prueba toRequestDto(EncodeValueCommand command) {
+ var prueba = new Prueba();
+ prueba.setSApp(command.sApp());
+ return prueba;
+ }
+
+ public static String toResponseDto(PruebaResponse response) {
+ return response.getPruebaResult();
+ }
+
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/mapper/OtpMapper.java b/bus-evaluate-customer-product/src/main/java/infrastructure/mapper/OtpMapper.java
new file mode 100644
index 0000000..06b18fd
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/infrastructure/mapper/OtpMapper.java
@@ -0,0 +1,26 @@
+package infrastructure.mapper;
+
+import application.port.input.command.ValidateOtpCommand;
+import domain.model.Otp;
+import infrastructure.client.otp.dto.request.ValidateOtpRequest;
+import infrastructure.client.otp.dto.response.ValidateOtpResponse;
+
+public class OtpMapper {
+ public static Otp toEntity(ValidateOtpResponse dto) {
+ return Otp.builder()
+ .msgRsHdr(dto.getMsgRsHdr())
+ .status(dto.getStatus())
+ .build();
+ }
+
+ public static ValidateOtpRequest toRequestDto(ValidateOtpCommand otp) {
+ return ValidateOtpRequest.builder()
+ .servicio(otp.servicio())
+ .canal(otp.canal())
+ .ipAddress(otp.ipAddress())
+ .login(otp.login())
+ .appOrigen(otp.appOrigen())
+ .otpToken(otp.otpToken())
+ .build();
+ }
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/mapper/PostgresMapper.java b/bus-evaluate-customer-product/src/main/java/infrastructure/mapper/PostgresMapper.java
new file mode 100644
index 0000000..6eaa730
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/infrastructure/mapper/PostgresMapper.java
@@ -0,0 +1,185 @@
+package infrastructure.mapper;
+
+import com.banesco.common.domain.model.redis.postgres.customer_product.CustomerAccountConfiguration;
+import com.banesco.common.domain.model.redis.postgres.customer_product.CustomerAccountJsonConfig;
+import com.banesco.common.domain.model.redis.postgres.customer_product.CustomerAccountPostgresConfig;
+
+import domain.entity.AppConfiguration;
+import domain.entity.ConfigItem;
+import domain.model.Configuration;
+
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * Mapeador estático para convertir entidades de la App (JPA)
+ * a DTOs de "commons" (para Redis).
+ */
+public class PostgresMapper {
+
+ /**
+ * Constructor privado para evitar que esta clase de utilidad sea instanciada.
+ */
+ private PostgresMapper() {}
+
+ public static CustomerAccountPostgresConfig mapToCommon(AppConfiguration appEntity) {
+ if (appEntity == null) {
+ return null;
+ }
+
+ var commonDto = new CustomerAccountPostgresConfig();
+
+ commonDto.setAppId(appEntity.getAppId());
+ commonDto.setAppName(appEntity.getAppName());
+ commonDto.setApiName(appEntity.getApiName());
+ commonDto.setType(appEntity.getType());
+ commonDto.setEvent(appEntity.getEvent());
+ commonDto.setEncryptionData(appEntity.isEncryptionData());
+
+ commonDto.setConfiguration(mapConfigurationToCommon(appEntity.getConfiguration()));
+
+ return commonDto;
+ }
+
+ private static CustomerAccountConfiguration mapConfigurationToCommon(Configuration appConfig) {
+ if (appConfig == null) {
+ return null;
+ }
+
+ var commonConfigDto = new CustomerAccountConfiguration();
+
+ // Mapea los aliases (son Map, se copian directo)
+ commonConfigDto.setValidationRules(appConfig.getValidationRules());
+ commonConfigDto.setAliases(appConfig.getAliases());
+
+ // Mapea el mapa de configuración, convirtiendo cada ConfigItem (llama al Nivel 3)
+ if (appConfig.getConfiguration() != null) {
+ var commonConfigMap = appConfig.getConfiguration().entrySet().stream()
+ .collect(Collectors.toMap(
+ Map.Entry::getKey,
+ entry -> mapConfigItemToCommon(entry.getValue()) // El valor (ConfigItem) se mapea
+ ));
+ commonConfigDto.setConfiguration(commonConfigMap);
+ }
+
+ return commonConfigDto;
+ }
+
+ /**
+ * Nivel 3: Mapea un ConfigItem (App) a su DTO CustomerAccountJsonConfig (Commons).
+ * Como tienen los mismos campos, es una copia directa.
+ */
+ private static CustomerAccountJsonConfig mapConfigItemToCommon(ConfigItem appItem) {
+ if (appItem == null) {
+ return null;
+ }
+
+ var commonItemDto = new CustomerAccountJsonConfig();
+
+ commonItemDto.setCacheable(appItem.isCacheable());
+ commonItemDto.setBankCode(appItem.getBankCode());
+ commonItemDto.setCurrencyCode(appItem.getCurrencyCode());
+ commonItemDto.setStatus(appItem.getStatus());
+ commonItemDto.setStatusMe(appItem.getStatusMe());
+ commonItemDto.setProductCv(appItem.getProductCv());
+ commonItemDto.setProduct(appItem.getProduct());
+ commonItemDto.setChannel(appItem.getChannel());
+ commonItemDto.setService(appItem.getService());
+ commonItemDto.setOperator(appItem.getOperator());
+ commonItemDto.setPhone(appItem.getPhone());
+ commonItemDto.setAffiliationStatus(appItem.getAffiliationStatus());
+ commonItemDto.setCounterpartyAccount(appItem.getCounterpartyAccount());
+ commonItemDto.setHolder(appItem.getHolder());
+ commonItemDto.setCodProduct(appItem.getCodProduct());
+ commonItemDto.setStatusOperation(appItem.getStatusOperation());
+ commonItemDto.setStatusRestriction(appItem.getStatusRestriction());
+ commonItemDto.setStatusOperationRestriction(appItem.getStatusOperationRestriction());
+
+ return commonItemDto;
+ }
+
+ /**
+ * Nivel (Inverso): Mapea el DTO de "commons" a la entidad AppConfiguration (App).
+ * NOTA: ¡Esto requiere que tu entidad AppConfiguration tenga setters!
+ */
+ public static AppConfiguration mapToApp(CustomerAccountPostgresConfig commonDto) {
+ if (commonDto == null) {
+ return null;
+ }
+
+ var appEntity = new AppConfiguration();
+
+ // Asumiendo que existen setters... (ver nota abajo)
+ appEntity.setAppId(commonDto.getAppId());
+ appEntity.setAppName(commonDto.getAppName());
+ appEntity.setApiName(commonDto.getApiName());
+ appEntity.setType(commonDto.getType());
+ appEntity.setEvent(commonDto.getEvent());
+ appEntity.setEncryptionData(commonDto.isEncryptionData());
+
+ // Mapeo inverso de objetos anidados (Nivel 2)
+ appEntity.setConfiguration(mapConfigurationToApp(commonDto.getConfiguration()));
+
+ return appEntity;
+ }
+
+ /**
+ * Nivel 2 (Inverso): Mapea el DTO de Configuración (Commons) a la clase de Modelo (App).
+ */
+ private static Configuration mapConfigurationToApp(CustomerAccountConfiguration commonConfigDto) {
+ if (commonConfigDto == null) {
+ return null;
+ }
+
+ var appConfig = new Configuration();
+
+ appConfig.setValidationRules(commonConfigDto.getValidationRules());
+ appConfig.setAliases(commonConfigDto.getAliases());
+
+ // Mapea el mapa de configuración (Nivel 3)
+ if (commonConfigDto.getConfiguration() != null) {
+ var appConfigMap = commonConfigDto.getConfiguration().entrySet().stream()
+ .collect(Collectors.toMap(
+ Map.Entry::getKey,
+ entry -> mapConfigItemToApp(entry.getValue())
+ ));
+ appConfig.setConfiguration(appConfigMap);
+ }
+
+ return appConfig;
+ }
+
+ /**
+ * Nivel 3 (Inverso): Mapea el DTO de ConfigItem (Commons) a la Entidad (App).
+ */
+ private static ConfigItem mapConfigItemToApp(CustomerAccountJsonConfig commonItemDto) {
+ if (commonItemDto == null) {
+ return null;
+ }
+
+ var appItem = new ConfigItem();
+
+ // Mapeo 1:1
+ appItem.setCacheable(commonItemDto.isCacheable());
+ appItem.setBankCode(commonItemDto.getBankCode());
+ appItem.setCurrencyCode(commonItemDto.getCurrencyCode());
+ appItem.setStatus(commonItemDto.getStatus());
+ appItem.setStatusMe(commonItemDto.getStatusMe());
+ appItem.setProductCv(commonItemDto.getProductCv());
+ appItem.setProduct(commonItemDto.getProduct());
+ appItem.setChannel(commonItemDto.getChannel());
+ appItem.setService(commonItemDto.getService());
+ appItem.setOperator(commonItemDto.getOperator());
+ appItem.setPhone(commonItemDto.getPhone());
+ appItem.setAffiliationStatus(commonItemDto.getAffiliationStatus());
+ appItem.setCounterpartyAccount(commonItemDto.getCounterpartyAccount());
+ appItem.setHolder(commonItemDto.getHolder());
+ appItem.setCodProduct(commonItemDto.getCodProduct());
+ appItem.setStatusOperation(commonItemDto.getStatusOperation());
+ appItem.setStatusRestriction(commonItemDto.getStatusRestriction());
+ appItem.setStatusOperationRestriction(commonItemDto.getStatusOperationRestriction());
+
+ return appItem;
+ }
+
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/mapper/RegisterSecurityMapper.java b/bus-evaluate-customer-product/src/main/java/infrastructure/mapper/RegisterSecurityMapper.java
new file mode 100644
index 0000000..c826212
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/infrastructure/mapper/RegisterSecurityMapper.java
@@ -0,0 +1,57 @@
+package infrastructure.mapper;
+
+import application.port.input.command.RegisterSecurityCommand;
+import infrastructure.client.registersecurity.dto.request.RegisterSecurityRequest;
+import infrastructure.config.RegisterSecurityConfig;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+
+@ApplicationScoped
+public class RegisterSecurityMapper {
+
+ private final RegisterSecurityConfig config;
+
+ @Inject
+ public RegisterSecurityMapper(RegisterSecurityConfig config) {
+ this.config = config;
+ }
+
+ public RegisterSecurityRequest toRequestDto(RegisterSecurityCommand command) {
+ if (command == null) {
+ return null;
+ }
+
+ return new RegisterSecurityRequest(
+ config.bankCod(), // Dato de configuración
+ config.curCod(), // Dato de configuración
+ config.eventCod(), // Dato de configuración
+ config.eventCod(), // Dato de configuración
+ command.getLogin(), // Dato del comando
+ command.getFecHor(), // Dato del comando
+ command.getNacCli(), // Dato del comando
+ command.getCedRifCli(), // Dato del comando
+ command.getTipoProductoCli(),
+ command.getTipoProductoBen(),
+ command.getProductoCli(),
+ command.getCodEmpresa(),// Dato del comando
+ command.getNacBen(), // Dato del comando
+ command.getCedBen(), // Dato del comando
+ command.getNombreBen(),
+ command.getProductoBen(), // Dato del comando
+ command.getMonto(), // Dato del comando
+ command.getReferencia(), // Dato del comando
+ command.getNroDePago(), // Dato del comando
+ command.getDesPago(), // Dato del comando
+ command.getObjeto(), // Dato del comando
+ command.getTipoRespuesta(), // Dato del comando
+ command.getMsgRespuesta(), // Dato del comando
+ command.getTiempoRespuesta(),// Dato del comando
+ command.getCodFintech(), // Dato del comando
+ command.getCedRifFintech(), // Dato del comando
+ command.getTipoDispositivo(),// Dato del comando
+ command.getDesDispositivo(),// Dato del comando
+ command.getIpCli(), // Dato del comando
+ config.sp() // Dato de configuración
+ );
+ }
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/mapper/ServiceStatusMapper.java b/bus-evaluate-customer-product/src/main/java/infrastructure/mapper/ServiceStatusMapper.java
new file mode 100644
index 0000000..d490d86
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/infrastructure/mapper/ServiceStatusMapper.java
@@ -0,0 +1,32 @@
+package infrastructure.mapper;
+
+import application.port.input.command.ServiceStatusCommand;
+import com.banesco.common.infraestructure.web.dto.request.ServiceStatusRequest;
+import com.banesco.common.infraestructure.web.dto.response.ServiceStatusResponse;
+import domain.model.ServiceStatus;
+
+public class ServiceStatusMapper {
+ public static ServiceStatus toEntity(ServiceStatusResponse dto) {
+ return ServiceStatus.builder()
+ .status(dto.getStatus())
+ .build();
+ }
+
+ public static ServiceStatusRequest toRequestDto(ServiceStatusCommand command) {
+ var bankServiceDto = new com.banesco.common.infraestructure.web.dto.request.BankService();
+ bankServiceDto.setBankCode(command.bankService().bankCode());
+ bankServiceDto.setServiceCode(command.bankService().serviceCode());
+ bankServiceDto.setEventCode(command.bankService().eventCode());
+
+ // Construye y devuelve el DTO principal
+ var requestDto = new ServiceStatusRequest();
+ requestDto.setApplicationId(command.applicationId());
+ requestDto.setTransactionId(command.transactionId());
+ requestDto.setBankService(bankServiceDto);
+ return requestDto;
+
+ }
+
+
+
+}
diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/mapper/UserLoginMapper.java b/bus-evaluate-customer-product/src/main/java/infrastructure/mapper/UserLoginMapper.java
new file mode 100644
index 0000000..ad1707a
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/infrastructure/mapper/UserLoginMapper.java
@@ -0,0 +1,74 @@
+package infrastructure.mapper;
+
+import application.port.input.command.UserLoginCommand;
+import com.banesco.common.domain.model.BaseResponse;
+import domain.model.UserLogin;
+import infrastructure.client.userlogin.dto.request.UserLoginRequest;
+import infrastructure.client.userlogin.dto.response.UserLoginResponse;
+import infrastructure.config.RegisterSecurityConfig;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+
+/**
+ * Clase de utilidad para mapear objetos entre las capas de la aplicación
+ * para el flujo de validación de listas negras.
+ */
+@ApplicationScoped
+public class UserLoginMapper {
+
+ private final RegisterSecurityConfig config;
+
+ @Inject
+ public UserLoginMapper(RegisterSecurityConfig config) {
+ this.config = config;
+ }
+
+
+ /**
+ * Convierte la respuesta anidada de la API a la entidad de dominio.
+ * @param dto La respuesta completa de la API, con la estructura BaseResponse.
+ * @return Un objeto UserLogin del dominio.
+ */
+ public static UserLogin toEntity(BaseResponse dto) {
+ if (dto == null || dto.getDataResponse() == null) {
+ return null;
+ }
+
+ var data = dto.getDataResponse();
+
+ if (data.getDataUser() != null) {
+ return UserLogin.builder()
+ .usernameBol(data.getDataUser().getUsernameBol())
+ .usernameBOLE(data.getDataUser().getUsernameBOLE())
+ .build();
+ } else {
+ // Es la estructura de DEV
+ return UserLogin.builder()
+ .usernameBol(data.getUsernameBol())
+ .usernameBOLE(data.getUsernameBOLE())
+ .build();
+ }
+ }
+
+ public UserLoginRequest toRequestDto(UserLoginCommand command, String requestId) {
+ if(command == null) {
+ return null;
+ }
+
+ var userDataRequest = UserLoginRequest.DataUser.builder()
+ .customerId(command.customerId())
+ .customerIdType(command.customerIdType())
+ .build();
+
+ var dataRequest = UserLoginRequest.DataRequest.builder()
+ .requestId(requestId)
+ .sourceChannelCode(config.eventCod())
+ .dataUser(userDataRequest)
+ .build();
+
+ return UserLoginRequest.builder()
+ .dataRequest(dataRequest)
+ .build();
+
+ }
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/mapper/ValidateProfileMapper.java b/bus-evaluate-customer-product/src/main/java/infrastructure/mapper/ValidateProfileMapper.java
new file mode 100644
index 0000000..a44c6eb
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/infrastructure/mapper/ValidateProfileMapper.java
@@ -0,0 +1,30 @@
+package infrastructure.mapper;
+
+import application.port.input.command.ValidateProfileCommand;
+import domain.model.ClientProfile;
+import infrastructure.client.validateprofile.dto.request.ValidateProfileRequest;
+import infrastructure.client.validateprofile.dto.response.ValidateProfileResponse;
+
+/**
+ * Clase de utilidad para mapear objetos entre las capas de la aplicación
+ * para el flujo de validate profile
+ */
+public class ValidateProfileMapper {
+ public static ClientProfile toEntity(ValidateProfileResponse dto) {
+ return ClientProfile.builder()
+ .errorMessage(dto.getErrorMessage())
+ .errorCode(dto.getErrorCode())
+ .hasError(dto.getHasError())
+ .data(dto.getData())
+ .build();
+ }
+
+ public static ValidateProfileRequest toRequestDto(ValidateProfileCommand command) {
+ return ValidateProfileRequest.builder()
+ .customerId(command.customerId())
+ .ipAddress(command.ipAddress())
+ .hash(command.hash())
+ .build();
+ }
+
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/java/infrastructure/service/StatusDescriptionResolver.java b/bus-evaluate-customer-product/src/main/java/infrastructure/service/StatusDescriptionResolver.java
new file mode 100644
index 0000000..15d0606
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/java/infrastructure/service/StatusDescriptionResolver.java
@@ -0,0 +1,45 @@
+package infrastructure.service;
+
+import domain.model.ErrorCode;
+import infrastructure.config.AppConfig;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+
+import java.util.Optional;
+
+@ApplicationScoped
+public class StatusDescriptionResolver {
+
+ private final AppConfig appConfig;
+ private final ErrorCode genericError;
+
+ @Inject
+ public StatusDescriptionResolver(AppConfig appConfig) {
+ this.appConfig = appConfig;
+ // Definimos un código de error genérico por si no se encuentra el que buscamos
+ this.genericError = new ErrorCode("409", "CONFLICT", "409", "CONFLICT", "error");
+ }
+
+ /**
+ * Busca un ErrorCode por su backend_code.
+ * Si no lo encuentra, devuelve un ErrorCode genérico de "Error Interno".
+ *
+ * @param backendCode El código a buscar (ej. "200", "VDE01", "404").
+ * @return El ErrorCode encontrado o uno genérico. Nunca es nulo.
+ */
+ public ErrorCode getCode(String backendCode) {
+ // Usamos Optional.ofNullable para manejar el caso en que el código no exista en el mapa
+ return Optional.ofNullable(appConfig.getErrorCodeMap().get(backendCode))
+ .orElse(genericError);
+ }
+
+ /**
+ * Alternativa manejar el caso "no encontrado" manualmente.
+ *
+ * @param backendCode El código a buscar.
+ * @return Un Optional que estará vacío si no se encuentra el código.
+ */
+ public Optional findCode(String backendCode) {
+ return Optional.ofNullable(appConfig.getErrorCodeMap().get(backendCode));
+ }
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/resources/META-INF/resources/img/banesco-logo-favicon.png b/bus-evaluate-customer-product/src/main/resources/META-INF/resources/img/banesco-logo-favicon.png
new file mode 100644
index 0000000..9e7463a
Binary files /dev/null and b/bus-evaluate-customer-product/src/main/resources/META-INF/resources/img/banesco-logo-favicon.png differ
diff --git a/bus-evaluate-customer-product/src/main/resources/META-INF/resources/img/banesco-logo.png b/bus-evaluate-customer-product/src/main/resources/META-INF/resources/img/banesco-logo.png
new file mode 100644
index 0000000..8e1b25d
Binary files /dev/null and b/bus-evaluate-customer-product/src/main/resources/META-INF/resources/img/banesco-logo.png differ
diff --git a/bus-evaluate-customer-product/src/main/resources/META-INF/resources/index.html b/bus-evaluate-customer-product/src/main/resources/META-INF/resources/index.html
new file mode 100644
index 0000000..03837dd
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/resources/META-INF/resources/index.html
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+ API Banesco - Pública Consulta de Producto BIAN
+
+
+
+
+
+
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/request/customer-product-success.json b/bus-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/request/customer-product-success.json
new file mode 100644
index 0000000..71a4f26
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/request/customer-product-success.json
@@ -0,0 +1,17 @@
+{
+ "customerPositionState": {
+ "customerReference": {
+ "customerIdType": "J",
+ "customerId": "000004293"
+ }
+ },
+ "device": {
+ "deviceType": "Mobile",
+ "deviceDescription": "Xiaomi Note 11 PRO",
+ "deviceIp": "127.0.0.1",
+ "deviceSessionReference": "prueba"
+ },
+ "tokenAssignmentInstance": {
+ "tokenIdentificationCode": ""
+ }
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/request/customer-product-token-success.json b/bus-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/request/customer-product-token-success.json
new file mode 100644
index 0000000..a90178b
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/request/customer-product-token-success.json
@@ -0,0 +1,17 @@
+{
+ "customerPositionState": {
+ "customerReference": {
+ "customerIdType": "J",
+ "customerId": "000004293"
+ }
+ },
+ "device": {
+ "deviceType": "Mobile",
+ "deviceDescription": "Xiaomi Note 11 PRO",
+ "deviceIp": "127.0.0.1",
+ "deviceSessionReference": "prueba"
+ },
+ "tokenAssignmentInstance": {
+ "tokenIdentificationCode": "12345678"
+ }
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/app-config.json b/bus-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/app-config.json
new file mode 100644
index 0000000..7f92dbf
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/app-config.json
@@ -0,0 +1,85 @@
+{
+ "appName": "BANCA_MOVIL",
+ "apiName": "bus-evaluate-customer-product",
+ "type": "external",
+ "event": "CPROD",
+ "encryptionData": true,
+ "configuration": {
+ "validationRules": {
+ "serviceStatus": false,
+ "getUserLogin": false,
+ "encodeValue": false,
+ "validateOtp": false,
+ "validateProfile": false
+ },
+ "aliases": {
+ "V": "V",
+ "E": "V",
+ "J": "J",
+ "G": "J",
+ "C": "J"
+ },
+ "configuration": {
+ "all": {
+ "cacheable": true,
+ "bankCode": "01",
+ "currencyCode": "BS",
+ "status": "A",
+ "statusMe": "",
+ "productCv": "",
+ "product": "",
+ "channel": "",
+ "service": "",
+ "operator": "",
+ "phone": "",
+ "affiliationStatus": "",
+ "counterpartyAccount": "",
+ "holder": "",
+ "codProduct": "",
+ "statusOperation": "",
+ "statusRestriction": "",
+ "statusOperationRestriction": ""
+ },
+ "BANCA_MOVIL_V": {
+ "cacheable": true,
+ "bankCode": "",
+ "currencyCode": "",
+ "status": "ACTBSUSD",
+ "statusMe": "",
+ "productCv": "CVFL",
+ "product": "",
+ "channel": "",
+ "service": "",
+ "operator": "",
+ "phone": "",
+ "affiliationStatus": "",
+ "counterpartyAccount": "",
+ "holder": "TIT",
+ "codProduct": "",
+ "statusOperation": "",
+ "statusRestriction": "",
+ "statusOperationRestriction": ""
+ },
+ "BANCA_MOVIL_J": {
+ "cacheable": true,
+ "bankCode": "",
+ "currencyCode": "",
+ "status": "ACTBSUSD",
+ "statusMe": "",
+ "productCv": "CVFL",
+ "product": "",
+ "channel": "APP",
+ "service": "P2P",
+ "operator": "",
+ "phone": "",
+ "affiliationStatus": "A",
+ "counterpartyAccount": "PAG",
+ "holder": "TIT",
+ "codProduct": "",
+ "statusOperation": "",
+ "statusRestriction": "",
+ "statusOperationRestriction": ""
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/config-not-found.json b/bus-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/config-not-found.json
new file mode 100644
index 0000000..1b26bf5
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/config-not-found.json
@@ -0,0 +1,8 @@
+{
+ "statusResponse": {
+ "status": "error",
+ "statusCode": "400",
+ "message": "Configuracion no encontrada",
+ "traceId": "YxwjZEbfHuYc3wG4jDdN"
+ }
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/conflict.json b/bus-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/conflict.json
new file mode 100644
index 0000000..d42706e
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/conflict.json
@@ -0,0 +1,8 @@
+{
+ "statusResponse": {
+ "status": "error",
+ "statusCode": "409",
+ "message": "CONFLICT",
+ "traceId": "b81LdZyGXYCCcLvyaQdz"
+ }
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/customer-product-response-unmasked.json b/bus-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/customer-product-response-unmasked.json
new file mode 100644
index 0000000..5edef7d
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/customer-product-response-unmasked.json
@@ -0,0 +1,24 @@
+{
+ "statusResponse": {
+ "status": "ok",
+ "statusCode": "200",
+ "message": "Operacion Exitosa",
+ "traceId": "U2VzaLNEEPeX9ODZiRnC"
+ },
+ "customerPositionState": {
+ "bankingProducts": [
+ {
+ "productNumber": "01340552220001000249",
+ "productType": "DDA",
+ "balance": 0.84,
+ "currency": "BS "
+ },
+ {
+ "productNumber": "01341740730001281663",
+ "productType": "DDA",
+ "balance": 0.00,
+ "currency": "USD"
+ }
+ ]
+ }
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/customer-product-response.json b/bus-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/customer-product-response.json
new file mode 100644
index 0000000..f442cbf
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/customer-product-response.json
@@ -0,0 +1,24 @@
+{
+ "statusResponse": {
+ "status": "ok",
+ "statusCode": "200",
+ "message": "Operacion Exitosa",
+ "traceId": "SKeIGjD1lWmJD992LfKZ"
+ },
+ "customerPositionState": {
+ "bankingProducts": [
+ {
+ "productNumber": "0134************0249",
+ "productType": "DDA",
+ "balance": 0.84,
+ "currency": "BS "
+ },
+ {
+ "productNumber": "********************",
+ "productType": "DDA",
+ "balance": 0.00,
+ "currency": "USD"
+ }
+ ]
+ }
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/customer-product-rest-client-error.json b/bus-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/customer-product-rest-client-error.json
new file mode 100644
index 0000000..881be26
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/customer-product-rest-client-error.json
@@ -0,0 +1,8 @@
+{
+ "statusResponse": {
+ "status": "error",
+ "statusCode": "503",
+ "message": "Incidencia con la resolución de CI",
+ "traceId": "qV3cYzb5SNIGKbozmF6T"
+ }
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/customer-product-without-results.json b/bus-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/customer-product-without-results.json
new file mode 100644
index 0000000..342d52e
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/customer-product-without-results.json
@@ -0,0 +1,11 @@
+{
+ "statusResponse": {
+ "status": "ok",
+ "statusCode": "200",
+ "message": "Operacion Exitosa",
+ "traceId": "5YJYkSZdRA0El6QaBjr4"
+ },
+ "customerPositionState": {
+ "bankingProducts": []
+ }
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/internal-use.json b/bus-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/internal-use.json
new file mode 100644
index 0000000..70149a3
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/internal-use.json
@@ -0,0 +1,8 @@
+{
+ "statusResponse": {
+ "status": "error",
+ "statusCode": "500",
+ "message": "Uso interno",
+ "traceId": "oHsLZM4xSVVC8ZoU6yDF"
+ }
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/invalid-profile.json b/bus-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/invalid-profile.json
new file mode 100644
index 0000000..cda132e
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/invalid-profile.json
@@ -0,0 +1,8 @@
+{
+ "statusResponse": {
+ "status": "error",
+ "statusCode": "401",
+ "message": "VRN01 - Fallo en la validación del perfil del cliente",
+ "traceId": "gLoRYYHkZONFOcntMvfn"
+ }
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/invalid-token.json b/bus-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/invalid-token.json
new file mode 100644
index 0000000..cf5098d
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/invalid-token.json
@@ -0,0 +1,8 @@
+{
+ "statusResponse": {
+ "status": "error",
+ "statusCode": "VRN07",
+ "message": "VRN07 – Codigo de OTP Invalido",
+ "traceId": "xJPt6yoN6LA8QJxJpGC6"
+ }
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/service-unavailable.json b/bus-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/service-unavailable.json
new file mode 100644
index 0000000..af8ed48
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/service-unavailable.json
@@ -0,0 +1,8 @@
+{
+ "statusResponse": {
+ "status": "error",
+ "statusCode": "VRN04",
+ "message": "Servicio en horario de mantenimiento",
+ "traceId": "xif7wH0xUbIB3e6cHcr3"
+ }
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/user-bol-not-exist.json b/bus-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/user-bol-not-exist.json
new file mode 100644
index 0000000..331e8b7
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/user-bol-not-exist.json
@@ -0,0 +1,8 @@
+{
+ "statusResponse": {
+ "status": "error",
+ "statusCode": "AUTH02",
+ "message": "Usuario no existe",
+ "traceId": "uAWXR6yNPa6IlQ51FOAU"
+ }
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/user-not-exist.json b/bus-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/user-not-exist.json
new file mode 100644
index 0000000..ecb3a78
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/user-not-exist.json
@@ -0,0 +1,8 @@
+{
+ "statusResponse": {
+ "status": "error",
+ "statusCode": "AUTH01",
+ "message": "Usuario no existe",
+ "traceId": "jptc0JT9mh8ioDhZeZfa"
+ }
+}
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/resources/application.yml b/bus-evaluate-customer-product/src/main/resources/application.yml
new file mode 100644
index 0000000..49635e8
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/resources/application.yml
@@ -0,0 +1,198 @@
+quarkus:
+ log:
+ level: INFO
+ http:
+ port: 8082
+ idle-timeout: 30s
+ non-application-root-path: /actuator
+ application:
+ name: bus-evaluate-customer-product
+ version: 1.0.0
+ smallrye-openapi:
+ path: /openapi
+ enable: true
+ info-title: bus-evaluate-customer-product
+ info-version: 1.0.0
+ info-description: 'API Bus evaluate customer accounts'
+ swagger-ui:
+ path: /swagger-ui
+ always-include: true
+ redis:
+ hosts: "redis://localhost:6379"
+ password: redis
+ timeout: 3S
+ max-pool-size: 32
+ max-pool-waiting: 100
+ datasource:
+ db-kind: postgresql
+ jdbc:
+ url: jdbc:postgresql://localhost:5433/apiban001
+ # Tamaño del Pool
+ min-size: 4 # Conexiones mínimas que el pool mantendrá abiertas.
+ max-size: 10 # Máximo de conexiones permitidas. ¡Ajusta esto según tu carga!
+ initial-size: 4 # Conexiones que se crearán al iniciar la aplicación.
+ # Tiempos de Espera (Timeouts)
+ acquisition-timeout: 8s # Tiempo máximo (8 seg) que un hilo esperará por una conexión si el pool está lleno.
+ idle-removal-interval: 30s # Cierra conexiones inactivas (por encima de min-size) cada 30 segundos.
+ max-lifetime: 30m # Tiempo máximo de vida de una conexión (10 min) para forzar su renovación.
+ background-validation-interval: 30s # Valida conexiones inactivas en segundo plano cada 30 segundos.
+ validation-query: "SELECT 1"
+ username: us3r001
+ password: 8gbNQ.7XZK5c
+ rest-client:
+ get-service-status:
+ url: http://api-get-service-status-route-apis-banesco-dev.apps.desplakur3.desintra.banesco.com/service/status
+ scope: jakarta.inject.Singleton
+ user-login:
+ #(quarkus)
+# url: http://api-get-user-login-route-apis-banesco-dev.apps.desplakur3.desintra.banesco.com/user/login
+ # (spring)
+ url: http://api-get-user-login-route-apis-banesco-dev.apps.desplakur3.desintra.banesco.com/user/login/
+ scope: jakarta.inject.Singleton
+ validate-profile:
+ url: http://api-validate-profile-client-route-apis-banesco-dev.apps.desplakur3.desintra.banesco.com/security/profile/validate
+ scope: jakarta.inject.Singleton
+ encode-value:
+ url: http://api-encode-value-route-apis-banesco-dev.apps.desplakur3.desintra.banesco.com/security/encode
+ scope: jakarta.inject.Singleton
+ otp:
+ url: http://api-validate-otp-route-apis-banesco-qa.apps.desplakur3.desintra.banesco.com/security/otp/validate
+ scope: jakarta.inject.Singleton
+ customer-accounts:
+ url: http://api-get-db2-accounts-client-v2-route-apis-banesco-dev.apps.desplakur3.desintra.banesco.com/customer/accounts
+ scope: jakarta.inject.Singleton
+ register-security:
+ url: http://api-register-security-route-apis-banesco-qa.apps.desplakur3.desintra.banesco.com/register-security/save
+ scope: jakarta.inject.Singleton
+app:
+ appId: BUS_EVALUATE_CUSTOMER_PRODUCT
+ redis:
+ expiration: 300
+ domain:
+ api:
+ public:
+ payment: osbp2p
+ otp:
+ channel: 2
+ servicio: APIFICACION
+ account-client:
+ availability:
+ schedule:
+ param:
+ cod-ban: 01
+ cod-eve: P2PVUEL
+ cod-serv: APIFI
+ begin: 10:00:00
+ finish: 23:00:00
+ register-security:
+ event-cod: CPROD
+ bank-cod: 01
+ cur-cod: BS
+ sp: spAPI_ConsultaProducto
+ response-json: |
+ [
+ {
+ "backend_code": "default",
+ "http_code": "409",
+ "status_code": "CONFLICT",
+ "description": "Conflicto",
+ "status": "error"
+ },
+ {
+ "backend_code": "VRN_CONFIG_NOT_FOUND",
+ "http_code": "400",
+ "status_code": "400",
+ "description": "Configuracion no encontrada",
+ "status": "error"
+ },
+ {
+ "backend_code": "405",
+ "http_code": "405",
+ "status_code": "405",
+ "description": "Metodo no permitido",
+ "status": "error"
+ },
+ {
+ "backend_code": "200",
+ "http_code": "200",
+ "status_code": "200",
+ "description": "Operacion exitosa",
+ "status": "ok"
+ },
+ {
+ "backend_code": "204",
+ "http_code": "200",
+ "status_code": "200",
+ "description": "Cliente sin productos",
+ "status": "ok"
+ },
+ {
+ "backend_code": "503-URL",
+ "http_code": "503",
+ "status_code": "503",
+ "description": "Incidencia con la resolución de CI",
+ "status": "error"
+ },
+ {
+ "backend_code": "500",
+ "http_code": "503",
+ "status_code": "500",
+ "description": "Uso interno",
+ "status": "error"
+ },
+ {
+ "backend_code": "406",
+ "http_code": "406",
+ "status_code": "406",
+ "description": "Respuesta no aceptable",
+ "status": "error"
+ },
+ {
+ "backend_code": "VDE01",
+ "status_code": "VDE01",
+ "http_code": "BAD_REQUEST",
+ "description": "Error en dato de entrada obligatorio"
+ },
+ {
+ "backend_code": "VRN01",
+ "http_code": "401",
+ "status_code": "401",
+ "description": "VRN01 - Fallo en la validación del perfil del cliente",
+ "status": "error"
+ },
+ {
+ "backend_code": "VRN04",
+ "http_code": "503",
+ "status_code": "VRN04",
+ "description": "Servicio en horario de mantenimiento",
+ "status": "error"
+ },
+ {
+ "backend_code": "VRN07",
+ "http_code": "401",
+ "status_code": "VRN07",
+ "description": "VRN07 – Codigo de OTP Invalido",
+ "status": "error"
+ },
+ {
+ "backend_code": "409",
+ "http_code": "409",
+ "status_code": "CONFLICT",
+ "description": "Conflicto",
+ "status": "error"
+ },
+ {
+ "backend_code": "VRN19",
+ "http_code": "401",
+ "status_code": "AUTH01",
+ "description": "Usuario no existe",
+ "status": "error"
+ },
+ {
+ "backend_code": "VRN20",
+ "http_code": "401",
+ "status_code": "AUTH02",
+ "description": "Usuario no existe",
+ "status": "error"
+ }
+ ]
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/resources/configmap.yaml b/bus-evaluate-customer-product/src/main/resources/configmap.yaml
new file mode 100644
index 0000000..b20457f
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/resources/configmap.yaml
@@ -0,0 +1,246 @@
+# ===================================================================
+# CONFIGURACIÓN GENERAL DE QUARKUS
+# ===================================================================
+
+# Nombre de la aplicación.
+quarkus.application.name: bus-evaluate-customer-product
+# Versión de la aplicación.
+quarkus.application.version: 1.0.0
+# Puerto HTTP en el que se ejecutará la aplicación.
+quarkus.http.port: 8080
+# Tiempo máximo de inactividad antes de que el servidor cierre una conexión HTTP.
+quarkus.http.idle-timeout: 30s
+# Ruta raíz para los endpoints que no son de la aplicación (ej. /health, /metrics).
+quarkus.http.non-application-root-path: /actuator
+
+# ===================================================================
+# DOCUMENTACIÓN DE API (OPENAPI & SWAGGER UI)
+# ===================================================================
+
+# Habilita la generación de la especificación OpenAPI.
+quarkus.smallrye-openapi.enable: true
+# Ruta donde se expondrá la especificación OpenAPI (formato JSON/YAML).
+quarkus.smallrye-openapi.path: /openapi
+# Título que se mostrará en la documentación de la API.
+quarkus.smallrye-openapi.info-title: bus-evaluate-customer-product
+# Versión que se mostrará en la documentación de la API.
+quarkus.smallrye-openapi.info-version: 1.0.0
+# Descripción general de la API para la documentación.
+quarkus.smallrye-openapi.info-description: API Bus evaluate customer accounts
+
+# Habilita la interfaz de usuario de Swagger.
+quarkus.swagger-ui.always-include: true
+# Ruta donde estará disponible la interfaz de Swagger UI.
+quarkus.swagger-ui.path: /swagger-ui
+
+# ===================================================================
+# CONFIGURACIÓN DE REDIS
+# ===================================================================
+
+# URL de conexión al servidor de Redis.
+quarkus.redis.hosts: redis://localhost:6379
+# Contraseña para la autenticación en Redis (si es necesaria).
+quarkus.redis.password: redis
+# Tiempo máximo de espera para las operaciones de Redis.
+quarkus.redis.timeout: 3S
+# Tamaño máximo del pool de conexiones hacia Redis.
+quarkus.redis.max-pool-size: 32
+# Tiempo máximo de espera para obtener una conexión del pool.
+quarkus.redis.max-pool-waiting: 100
+
+# ===================================================================
+# CONFIGURACIÓN DE LA BASE DE DATOS (POSTGRESQL)
+# ===================================================================
+
+# Tipo de base de datos a utilizar.
+quarkus.datasource.db-kind: postgresql
+# URL de conexión JDBC a la base de datos.
+quarkus.datasource.jdbc.url: jdbc:postgresql://localhost:5433/apiban001
+# Tamaño del Pool
+# Conexiones mínimas que el pool mantendrá abiertas.
+quarkus.datasource.jdbc.min-size: 4
+# Máximo de conexiones permitidas. ¡Ajusta esto según tu carga!
+quarkus.datasource.jdbc.max-size: 10
+# Conexiones que se crearán al iniciar la aplicación.
+quarkus.datasource.jdbc.initial-size: 4
+# Tiempos de Espera (Timeouts)
+# Tiempo máximo (8 seg) que un hilo esperará por una conexión si el pool está lleno.
+quarkus.datasource.jdbc.acquisition-timeout: 8s
+# Cierra conexiones inactivas (por encima de min-size) cada 30 segundos.
+quarkus.datasource.jdbc.idle-removal-interval: 30s
+# Tiempo máximo de vida de una conexión (30 min) para forzar su renovación.
+quarkus.datasource.jdbc.max-lifetime: 30m
+# Valida conexiones inactivas en segundo plano cada 30 segundos.
+quarkus.datasource.jdbc.background-validation-interval: 30s
+quarkus.datasource.jdbc.validation-query: SELECT 1
+
+
+
+# Usuario para la conexión a la base de datos.
+quarkus.datasource.username: -------
+# Contraseña para la conexión a la base de datos.
+quarkus.datasource.password: --------
+
+# ===================================================================
+# CONFIGURACIÓN DE CLIENTES REST
+# ===================================================================
+
+# --- Cliente para verificar el estado de disponibilidad de un servicio.
+quarkus.rest-client.get-service-status.url: http://api-get-service-status-route-apis-banesco-dev.apps.desplakur3.desintra.banesco.com/service/status
+quarkus.rest-client.get-service-status.scope: jakarta.inject.Singleton
+
+# --- Cliente para el inicio de sesión de usuario.
+quarkus.rest-client.user-login.url: http://api-get-user-login-route-apis-banesco-dev.apps.desplakur3.desintra.banesco.com/user/login
+quarkus.rest-client.user-login.scope: jakarta.inject.Singleton
+
+# --- Cliente para validar el perfil de un cliente.
+quarkus.rest-client.validate-profile.url: http://api-validate-profile-client-route-apis-banesco-qa.apps.desplakur3.desintra.banesco.com/security/profile/validate
+quarkus.rest-client.validate-profile.scope: jakarta.inject.Singleton
+
+# --- Cliente para codificar valores.
+quarkus.rest-client.encode-value.url: http://api-encode-value-route-apis-banesco-qa.apps.desplakur3.desintra.banesco.com/security/encode
+quarkus.rest-client.encode-value.scope: jakarta.inject.Singleton
+
+# --- Cliente para validar el token OTP.
+quarkus.rest-client.otp.url: http://api-validate-otp-route-apis-banesco-qa.apps.desplakur3.desintra.banesco.com/security/otp/validate
+quarkus.rest-client.otp.scope: jakarta.inject.Singleton
+
+# --- Cliente para obtener las cuentas de un cliente desde DB2.
+quarkus.rest-client.customer-accounts.url: http://api-get-db2-accounts-client-v2-route-apis-banesco-dev.apps.desplakur3.desintra.banesco.com/customer/accounts
+quarkus.rest-client.customer-accounts.scope: jakarta.inject.Singleton
+
+# --- Cliente para registrar la traza de seguridad.
+quarkus.rest-client.register-security.url: http://api-register-security-route-apis-banesco-qa.apps.desplakur3.desintra.banesco.com/register-security/save
+quarkus.rest-client.register-security.scope: jakarta.inject.Singleton
+
+# ===================================================================
+# PROPIEDADES ESPECÍFICAS DE LA APLICACIÓN
+# ===================================================================
+
+# Identificador único de la aplicación.
+app.appId: BUS_EVALUATE_CUSTOMER_PRODUCTS
+# (Propiedad personalizada) Tiempo de expiración por defecto para las claves en caché.
+app.redis.expiration: 300
+# Dominio específico de la aplicación.
+app.domain.api.public.payment: osbp2p
+
+# --- Parámetros para el servicio de OTP ---
+# Canal utilizado para la validación OTP.
+app.otp.channel: 2
+# Nombre del servicio que solicita la validación OTP.
+app.otp.servicio: APIFICACION
+
+# --- Parámetros para la ventana de disponibilidad del servicio ---
+app.account-client.availability.schedule.param.cod-ban: 1
+app.account-client.availability.schedule.param.cod-eve: P2PVUEL
+app.account-client.availability.schedule.param.cod-serv: APIFI
+# Hora de inicio de la ventana de disponibilidad
+app.account-client.availability.schedule.begin: 10:00:00
+# Hora de fin de la ventana de disponibilidad
+app.account-client.availability.schedule.finish: 23:00:00
+
+# --- Parámetros para la Traza de Seguridad ---
+# Código de evento para la traza.
+app.register-security.event-cod: CPROD
+# Código de banco para la traza.
+app.register-security.bank-cod: 01
+# Código de moneda para la traza.
+app.register-security.cur-cod: BS
+# Nombre del Stored Procedure para la traza.
+app.register-security.sp: spAPI_ConsultaProducto
+
+# --- Mapeo centralizado de códigos de respuesta de la aplicación ---
+app.response-json: |
+ [
+ {
+ "backend_code": "default",
+ "http_code": "409",
+ "status_code": "CONFLICT",
+ "description": "Conflicto",
+ "status": "error"
+ },
+ {
+ "backend_code": "VRN_CONFIG_NOT_FOUND",
+ "http_code": "400",
+ "status_code": "400",
+ "description": "Configuracion no encontrada",
+ "status": "error"
+ },
+ {
+ "backend_code": "200",
+ "http_code": "200",
+ "status_code": "200",
+ "description": "Operacion exitosa",
+ "status": "ok"
+ },
+ {
+ "backend_code": "204",
+ "http_code": "200",
+ "status_code": "204",
+ "description": "Cliente sin productos",
+ "status": "ok"
+ },
+ {
+ "backend_code": "503-URL",
+ "http_code": "503",
+ "status_code": "503",
+ "description": "Incidencia con la resolución de CI",
+ "status": "error"
+ },
+ {
+ "backend_code": "500",
+ "http_code": "503",
+ "status_code": "500",
+ "description": "Uso interno",
+ "status": "error"
+ },
+ {
+ "backend_code": "406",
+ "http_code": "406",
+ "status_code": "406",
+ "description": "Respuesta no aceptable",
+ "status": "error"
+ },
+ {
+ "backend_code": "VRN01",
+ "http_code": "401",
+ "status_code": "401",
+ "description": "VRN01 - Fallo en la validación del perfil del cliente",
+ "status": "error"
+ },
+ {
+ "backend_code": "VRN04",
+ "http_code": "503",
+ "status_code": "VRN04",
+ "description": "Servicio en horario de mantenimiento",
+ "status": "error"
+ },
+ {
+ "backend_code": "VRN07",
+ "http_code": "401",
+ "status_code": "VRN07",
+ "description": "VRN07 – Codigo de OTP Invalido",
+ "status": "error"
+ },
+ {
+ "backend_code": "409",
+ "http_code": "409",
+ "status_code": "CONFLICT",
+ "description": "Conflicto",
+ "status": "error"
+ },
+ {
+ "backend_code": "VRN19",
+ "http_code": "401",
+ "status_code": "AUTH01",
+ "description": "Usuario no existe",
+ "status": "error"
+ },
+ {
+ "backend_code": "VRN20",
+ "http_code": "401",
+ "status_code": "AUTH02",
+ "description": "Usuario no existe",
+ "status": "error"
+ }
+ ]
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/resources/wsdl/APISecurityOutAppSvc.old b/bus-evaluate-customer-product/src/main/resources/wsdl/APISecurityOutAppSvc.old
new file mode 100644
index 0000000..349a8fc
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/resources/wsdl/APISecurityOutAppSvc.old
@@ -0,0 +1,57 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ OSB Service
+
+
+
+
+
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/resources/wsdl/APISecurityOutAppSvc.wsdl b/bus-evaluate-customer-product/src/main/resources/wsdl/APISecurityOutAppSvc.wsdl
new file mode 100644
index 0000000..9bdf0b8
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/resources/wsdl/APISecurityOutAppSvc.wsdl
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ OSB Service
+
+
+
+
+
\ No newline at end of file
diff --git a/bus-evaluate-customer-product/src/main/resources/wsdl/PruebaService.wsdl b/bus-evaluate-customer-product/src/main/resources/wsdl/PruebaService.wsdl
new file mode 100644
index 0000000..62066f2
--- /dev/null
+++ b/bus-evaluate-customer-product/src/main/resources/wsdl/PruebaService.wsdl
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/rec-evaluate-customer-product/.dockerignore b/rec-evaluate-customer-product/.dockerignore
new file mode 100644
index 0000000..94810d0
--- /dev/null
+++ b/rec-evaluate-customer-product/.dockerignore
@@ -0,0 +1,5 @@
+*
+!target/*-runner
+!target/*-runner.jar
+!target/lib/*
+!target/quarkus-app/*
\ No newline at end of file
diff --git a/rec-evaluate-customer-product/.gitignore b/rec-evaluate-customer-product/.gitignore
new file mode 100644
index 0000000..91a800a
--- /dev/null
+++ b/rec-evaluate-customer-product/.gitignore
@@ -0,0 +1,45 @@
+#Maven
+target/
+pom.xml.tag
+pom.xml.releaseBackup
+pom.xml.versionsBackup
+release.properties
+.flattened-pom.xml
+
+# Eclipse
+.project
+.classpath
+.settings/
+bin/
+
+# IntelliJ
+.idea
+*.ipr
+*.iml
+*.iws
+
+# NetBeans
+nb-configuration.xml
+
+# Visual Studio Code
+.vscode
+.factorypath
+
+# OSX
+.DS_Store
+
+# Vim
+*.swp
+*.swo
+
+# patch
+*.orig
+*.rej
+
+# Local environment
+.env
+
+# Plugin directory
+/.quarkus/cli/plugins/
+# TLS Certificates
+.certs/
diff --git a/rec-evaluate-customer-product/.mvn/wrapper/maven-wrapper.properties b/rec-evaluate-customer-product/.mvn/wrapper/maven-wrapper.properties
new file mode 100644
index 0000000..c0bcafe
--- /dev/null
+++ b/rec-evaluate-customer-product/.mvn/wrapper/maven-wrapper.properties
@@ -0,0 +1,3 @@
+wrapperVersion=3.3.4
+distributionType=only-script
+distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.11/apache-maven-3.9.11-bin.zip
diff --git a/rec-evaluate-customer-product/CHANGELOG.md b/rec-evaluate-customer-product/CHANGELOG.md
new file mode 100644
index 0000000..dcb4852
--- /dev/null
+++ b/rec-evaluate-customer-product/CHANGELOG.md
@@ -0,0 +1,10 @@
+# Changelog
+
+Todos los cambios notables en este proyecto se documentarán en este archivo.
+
+El formato se basa en [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
+y este proyecto se adhiere a [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+
+## v1.0.0 - UNRELEASED
+### Updated
+- Migración completada a Quarkus 3.25.3
\ No newline at end of file
diff --git a/rec-evaluate-customer-product/README.md b/rec-evaluate-customer-product/README.md
new file mode 100644
index 0000000..319f10a
--- /dev/null
+++ b/rec-evaluate-customer-product/README.md
@@ -0,0 +1,68 @@
+# rec-evaluate-customer-product
+
+This project uses Quarkus, the Supersonic Subatomic Java Framework.
+
+If you want to learn more about Quarkus, please visit its website: .
+
+## Running the application in dev mode
+
+You can run your application in dev mode that enables live coding using:
+
+```shell script
+./mvnw quarkus:dev
+```
+
+> **_NOTE:_** Quarkus now ships with a Dev UI, which is available in dev mode only at .
+
+## Packaging and running the application
+
+The application can be packaged using:
+
+```shell script
+./mvnw package
+```
+
+It produces the `quarkus-run.jar` file in the `target/quarkus-app/` directory.
+Be aware that it’s not an _über-jar_ as the dependencies are copied into the `target/quarkus-app/lib/` directory.
+
+The application is now runnable using `java -jar target/quarkus-app/quarkus-run.jar`.
+
+If you want to build an _über-jar_, execute the following command:
+
+```shell script
+./mvnw package -Dquarkus.package.jar.type=uber-jar
+```
+
+The application, packaged as an _über-jar_, is now runnable using `java -jar target/*-runner.jar`.
+
+## Creating a native executable
+
+You can create a native executable using:
+
+```shell script
+./mvnw package -Dnative
+```
+
+Or, if you don't have GraalVM installed, you can run the native executable build in a container using:
+
+```shell script
+./mvnw package -Dnative -Dquarkus.native.container-build=true
+```
+
+<<<<<<< HEAD
+You can then execute your native executable with: `./target/rec-customer-accounts-1.0.0-SNAPSHOT-runner`
+=======
+You can then execute your native executable with: `./target/rec-customer-accounts-1.0.0-runner`
+>>>>>>> testing
+
+If you want to learn more about building native executables, please consult .
+
+## Related Guides
+
+- Hibernate Validator ([guide](https://quarkus.io/guides/validation)): Validate object properties (field, getter) and method parameters for your beans (REST, CDI, Jakarta Persistence)
+- REST Jackson ([guide](https://quarkus.io/guides/rest#json-serialisation)): Jackson serialization support for Quarkus REST. This extension is not compatible with the quarkus-resteasy extension, or any of the extensions that depend on it
+- YAML Configuration ([guide](https://quarkus.io/guides/config-yaml)): Use YAML to configure your Quarkus application
+<<<<<<< HEAD
+=======
+- SmallRye Health ([guide](https://quarkus.io/guides/smallrye-health)): Monitor service health
+>>>>>>> testing
diff --git a/rec-evaluate-customer-product/TASKS.md b/rec-evaluate-customer-product/TASKS.md
new file mode 100644
index 0000000..e69de29
diff --git a/rec-evaluate-customer-product/mvnw b/rec-evaluate-customer-product/mvnw
new file mode 100644
index 0000000..bd8896b
--- /dev/null
+++ b/rec-evaluate-customer-product/mvnw
@@ -0,0 +1,295 @@
+#!/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.4
+#
+# 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:]'
+}
+
+scriptDir="$(dirname "$0")"
+scriptName="$(basename "$0")"
+
+# 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 <"$scriptDir/.mvn/wrapper/maven-wrapper.properties"
+[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in $scriptDir/.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${scriptName#mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;;
+esac
+
+# apply MVNW_REPOURL and calculate MAVEN_HOME
+# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/
+[ -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
+
+# Find the actual extracted directory name (handles snapshots where filename != directory name)
+actualDistributionDir=""
+
+# First try the expected directory name (for regular distributions)
+if [ -d "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" ]; then
+ if [ -f "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/bin/$MVN_CMD" ]; then
+ actualDistributionDir="$distributionUrlNameMain"
+ fi
+fi
+
+# If not found, search for any directory with the Maven executable (for snapshots)
+if [ -z "$actualDistributionDir" ]; then
+ # enable globbing to iterate over items
+ set +f
+ for dir in "$TMP_DOWNLOAD_DIR"/*; do
+ if [ -d "$dir" ]; then
+ if [ -f "$dir/bin/$MVN_CMD" ]; then
+ actualDistributionDir="$(basename "$dir")"
+ break
+ fi
+ fi
+ done
+ set -f
+fi
+
+if [ -z "$actualDistributionDir" ]; then
+ verbose "Contents of $TMP_DOWNLOAD_DIR:"
+ verbose "$(ls -la "$TMP_DOWNLOAD_DIR")"
+ die "Could not find Maven distribution directory in extracted archive"
+fi
+
+verbose "Found extracted Maven distribution directory: $actualDistributionDir"
+printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$actualDistributionDir/mvnw.url"
+mv -- "$TMP_DOWNLOAD_DIR/$actualDistributionDir" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME"
+
+clean || :
+exec_maven "$@"
diff --git a/rec-evaluate-customer-product/mvnw.cmd b/rec-evaluate-customer-product/mvnw.cmd
new file mode 100644
index 0000000..92450f9
--- /dev/null
+++ b/rec-evaluate-customer-product/mvnw.cmd
@@ -0,0 +1,189 @@
+<# : 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.4
+@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-,maven-mvnd--}/
+if ($env:MVNW_REPOURL) {
+ $MVNW_REPO_PATTERN = if ($USE_MVND -eq $False) { "/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_M2_PATH = "$HOME/.m2"
+if ($env:MAVEN_USER_HOME) {
+ $MAVEN_M2_PATH = "$env:MAVEN_USER_HOME"
+}
+
+if (-not (Test-Path -Path $MAVEN_M2_PATH)) {
+ New-Item -Path $MAVEN_M2_PATH -ItemType Directory | Out-Null
+}
+
+$MAVEN_WRAPPER_DISTS = $null
+if ((Get-Item $MAVEN_M2_PATH).Target[0] -eq $null) {
+ $MAVEN_WRAPPER_DISTS = "$MAVEN_M2_PATH/wrapper/dists"
+} else {
+ $MAVEN_WRAPPER_DISTS = (Get-Item $MAVEN_M2_PATH).Target[0] + "/wrapper/dists"
+}
+
+$MAVEN_HOME_PARENT = "$MAVEN_WRAPPER_DISTS/$distributionUrlNameMain"
+$MAVEN_HOME_NAME = ([System.Security.Cryptography.SHA256]::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
+
+# Find the actual extracted directory name (handles snapshots where filename != directory name)
+$actualDistributionDir = ""
+
+# First try the expected directory name (for regular distributions)
+$expectedPath = Join-Path "$TMP_DOWNLOAD_DIR" "$distributionUrlNameMain"
+$expectedMvnPath = Join-Path "$expectedPath" "bin/$MVN_CMD"
+if ((Test-Path -Path $expectedPath -PathType Container) -and (Test-Path -Path $expectedMvnPath -PathType Leaf)) {
+ $actualDistributionDir = $distributionUrlNameMain
+}
+
+# If not found, search for any directory with the Maven executable (for snapshots)
+if (!$actualDistributionDir) {
+ Get-ChildItem -Path "$TMP_DOWNLOAD_DIR" -Directory | ForEach-Object {
+ $testPath = Join-Path $_.FullName "bin/$MVN_CMD"
+ if (Test-Path -Path $testPath -PathType Leaf) {
+ $actualDistributionDir = $_.Name
+ }
+ }
+}
+
+if (!$actualDistributionDir) {
+ Write-Error "Could not find Maven distribution directory in extracted archive"
+}
+
+Write-Verbose "Found extracted Maven distribution directory: $actualDistributionDir"
+Rename-Item -Path "$TMP_DOWNLOAD_DIR/$actualDistributionDir" -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"
diff --git a/rec-evaluate-customer-product/pom.xml b/rec-evaluate-customer-product/pom.xml
new file mode 100644
index 0000000..e393a13
--- /dev/null
+++ b/rec-evaluate-customer-product/pom.xml
@@ -0,0 +1,154 @@
+
+
+ 4.0.0
+ com.banesco
+ rec-evaluate-customer-product
+ rec-evaluate-customer-product
+ API REST Customer Product (consulta de cuentas) REC
+ 1.0.0
+
+ 3.14.0
+ 17
+ UTF-8
+ UTF-8
+ quarkus-bom
+ io.quarkus.platform
+ 3.25.3
+ true
+ 3.5.3
+
+
+
+
+
+ ${quarkus.platform.group-id}
+ ${quarkus.platform.artifact-id}
+ ${quarkus.platform.version}
+ pom
+ import
+
+
+
+
+
+
+ com.banesco
+ commons
+ 1.0
+
+
+ io.quarkus
+ quarkus-hibernate-validator
+
+
+ io.quarkus
+ quarkus-rest-jackson
+
+
+ io.quarkus
+ quarkus-smallrye-openapi
+
+
+ io.quarkus
+ quarkus-swagger-ui
+
+
+ io.quarkus
+ quarkus-rest-client-jackson
+
+
+ io.quarkus
+ quarkus-config-yaml
+
+
+ io.quarkus
+ quarkus-smallrye-health
+
+
+ io.quarkus
+ quarkus-arc
+
+
+ io.quarkus
+ quarkus-junit5
+ test
+
+
+ org.projectlombok
+ lombok
+ 1.18.38
+
+
+
+
+
+ ${quarkus.platform.group-id}
+ quarkus-maven-plugin
+ ${quarkus.platform.version}
+ true
+
+
+
+ build
+ generate-code
+ generate-code-tests
+ native-image-agent
+
+
+
+
+
+ maven-compiler-plugin
+ ${compiler-plugin.version}
+
+ true
+
+
+
+ maven-surefire-plugin
+ ${surefire-plugin.version}
+
+
+ org.jboss.logmanager.LogManager
+ ${maven.home}
+
+
+
+
+ maven-failsafe-plugin
+ ${surefire-plugin.version}
+
+
+
+ integration-test
+ verify
+
+
+
+
+
+ ${project.build.directory}/${project.build.finalName}-runner
+ org.jboss.logmanager.LogManager
+ ${maven.home}
+
+
+
+
+
+
+
+
+ native
+
+
+ native
+
+
+
+ false
+ false
+ true
+
+
+
+
diff --git a/rec-evaluate-customer-product/scripts/native/Dockerfile b/rec-evaluate-customer-product/scripts/native/Dockerfile
new file mode 100644
index 0000000..d81a3ad
--- /dev/null
+++ b/rec-evaluate-customer-product/scripts/native/Dockerfile
@@ -0,0 +1,12 @@
+FROM quay.io/quarkus/ubi9-quarkus-micro-image:2.0
+RUN mkdir -p /work
+ENV TZ="America/Caracas"
+ENV LANGUAGE='en_US:en'
+VOLUME /tmp
+COPY /file/*-runner /work/recCustomerAccounts
+RUN chmod -R 775 /work
+RUN ls -ltra /work/
+EXPOSE 8080
+WORKDIR /work/
+
+ENTRYPOINT ["./recCustomerAccounts", "-Dquarkus.http.host=0.0.0.0"]
\ No newline at end of file
diff --git a/rec-evaluate-customer-product/scripts/native/Dockerfile~ b/rec-evaluate-customer-product/scripts/native/Dockerfile~
new file mode 100644
index 0000000..6c0f979
--- /dev/null
+++ b/rec-evaluate-customer-product/scripts/native/Dockerfile~
@@ -0,0 +1,12 @@
+FROM quay.io/quarkus/ubi9-quarkus-micro-image:2.0
+RUN mkdir -p /work
+ENV TZ="America/Caracas"
+ENV LANGUAGE='en_US:en'
+VOLUME /tmp
+COPY /file/*-runner /work/busCustomerAccounts
+RUN chmod -R 775 /work
+RUN ls -ltra /work/
+EXPOSE 8080
+WORKDIR /work/
+
+ENTRYPOINT ["./busCustomerAccounts", "-Dquarkus.http.host=0.0.0.0"]
\ No newline at end of file
diff --git a/rec-evaluate-customer-product/scripts/native/file/rec-evaluate-customer-product-1.0.0-runner b/rec-evaluate-customer-product/scripts/native/file/rec-evaluate-customer-product-1.0.0-runner
new file mode 100644
index 0000000..d968959
Binary files /dev/null and b/rec-evaluate-customer-product/scripts/native/file/rec-evaluate-customer-product-1.0.0-runner differ
diff --git a/rec-evaluate-customer-product/src/main/Main.java b/rec-evaluate-customer-product/src/main/Main.java
new file mode 100644
index 0000000..dc7558e
--- /dev/null
+++ b/rec-evaluate-customer-product/src/main/Main.java
@@ -0,0 +1,14 @@
+package main;
+
+import io.quarkus.runtime.Quarkus;
+import io.quarkus.runtime.annotations.QuarkusMain;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+@QuarkusMain
+public class Main {
+ public static void main(String[] args) {
+ log.info("Running rec-evaluate-customer-product...");
+ Quarkus.run(args);
+ }
+}
diff --git a/rec-evaluate-customer-product/src/main/docker/Dockerfile.jvm b/rec-evaluate-customer-product/src/main/docker/Dockerfile.jvm
new file mode 100644
index 0000000..dff602a
--- /dev/null
+++ b/rec-evaluate-customer-product/src/main/docker/Dockerfile.jvm
@@ -0,0 +1,98 @@
+####
+# This Dockerfile is used in order to build a container that runs the Quarkus application in JVM mode
+#
+# Before building the container image run:
+#
+# ./mvnw package
+#
+# Then, build the image with:
+#
+# docker build -f src/main/docker/Dockerfile.jvm -t quarkus/rec-customer-accounts-jvm .
+#
+# Then run the container using:
+#
+# docker run -i --rm -p 8080:8080 quarkus/rec-customer-accounts-jvm
+#
+# If you want to include the debug port into your docker image
+# you will have to expose the debug port (default 5005 being the default) like this : EXPOSE 8080 5005.
+# Additionally you will have to set -e JAVA_DEBUG=true and -e JAVA_DEBUG_PORT=*:5005
+# when running the container
+#
+# Then run the container using :
+#
+# docker run -i --rm -p 8080:8080 quarkus/rec-customer-accounts-jvm
+#
+# This image uses the `run-java.sh` script to run the application.
+# This scripts computes the command line to execute your Java application, and
+# includes memory/GC tuning.
+# You can configure the behavior using the following environment properties:
+# - JAVA_OPTS: JVM options passed to the `java` command (example: "-verbose:class") - Be aware that this will override
+# the default JVM options, use `JAVA_OPTS_APPEND` to append options
+# - JAVA_OPTS_APPEND: User specified Java options to be appended to generated options
+# in JAVA_OPTS (example: "-Dsome.property=foo")
+# - JAVA_MAX_MEM_RATIO: Is used when no `-Xmx` option is given in JAVA_OPTS. This is
+# used to calculate a default maximal heap memory based on a containers restriction.
+# If used in a container without any memory constraints for the container then this
+# option has no effect. If there is a memory constraint then `-Xmx` is set to a ratio
+# of the container available memory as set here. The default is `50` which means 50%
+# of the available memory is used as an upper boundary. You can skip this mechanism by
+# setting this value to `0` in which case no `-Xmx` option is added.
+# - JAVA_INITIAL_MEM_RATIO: Is used when no `-Xms` option is given in JAVA_OPTS. This
+# is used to calculate a default initial heap memory based on the maximum heap memory.
+# If used in a container without any memory constraints for the container then this
+# option has no effect. If there is a memory constraint then `-Xms` is set to a ratio
+# of the `-Xmx` memory as set here. The default is `25` which means 25% of the `-Xmx`
+# is used as the initial heap size. You can skip this mechanism by setting this value
+# to `0` in which case no `-Xms` option is added (example: "25")
+# - JAVA_MAX_INITIAL_MEM: Is used when no `-Xms` option is given in JAVA_OPTS.
+# This is used to calculate the maximum value of the initial heap memory. If used in
+# a container without any memory constraints for the container then this option has
+# no effect. If there is a memory constraint then `-Xms` is limited to the value set
+# here. The default is 4096MB which means the calculated value of `-Xms` never will
+# be greater than 4096MB. The value of this variable is expressed in MB (example: "4096")
+# - JAVA_DIAGNOSTICS: Set this to get some diagnostics information to standard output
+# when things are happening. This option, if set to true, will set
+# `-XX:+UnlockDiagnosticVMOptions`. Disabled by default (example: "true").
+# - JAVA_DEBUG: If set remote debugging will be switched on. Disabled by default (example:
+# true").
+# - JAVA_DEBUG_PORT: Port used for remote debugging. Defaults to 5005 (example: "8787").
+# - CONTAINER_CORE_LIMIT: A calculated core limit as described in
+# https://www.kernel.org/doc/Documentation/scheduler/sched-bwc.txt. (example: "2")
+# - CONTAINER_MAX_MEMORY: Memory limit given to the container (example: "1024").
+# - GC_MIN_HEAP_FREE_RATIO: Minimum percentage of heap free after GC to avoid expansion.
+# (example: "20")
+# - GC_MAX_HEAP_FREE_RATIO: Maximum percentage of heap free after GC to avoid shrinking.
+# (example: "40")
+# - GC_TIME_RATIO: Specifies the ratio of the time spent outside the garbage collection.
+# (example: "4")
+# - GC_ADAPTIVE_SIZE_POLICY_WEIGHT: The weighting given to the current GC time versus
+# previous GC times. (example: "90")
+# - GC_METASPACE_SIZE: The initial metaspace size. (example: "20")
+# - GC_MAX_METASPACE_SIZE: The maximum metaspace size. (example: "100")
+# - GC_CONTAINER_OPTIONS: Specify Java GC to use. The value of this variable should
+# contain the necessary JRE command-line options to specify the required GC, which
+# will override the default of `-XX:+UseParallelGC` (example: -XX:+UseG1GC).
+# - HTTPS_PROXY: The location of the https proxy. (example: "myuser@127.0.0.1:8080")
+# - HTTP_PROXY: The location of the http proxy. (example: "myuser@127.0.0.1:8080")
+# - NO_PROXY: A comma separated lists of hosts, IP addresses or domains that can be
+# accessed directly. (example: "foo.example.com,bar.example.com")
+#
+###
+FROM registry.access.redhat.com/ubi9/openjdk-17:1.23
+
+ENV LANGUAGE='en_US:en'
+
+
+# We make four distinct layers so if there are application changes the library layers can be re-used
+COPY --chown=185 target/quarkus-app/lib/ /deployments/lib/
+COPY --chown=185 target/quarkus-app/*.jar /deployments/
+COPY --chown=185 target/quarkus-app/app/ /deployments/app/
+COPY --chown=185 target/quarkus-app/quarkus/ /deployments/quarkus/
+
+EXPOSE 8080
+USER 185
+ENV JAVA_OPTS_APPEND="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager"
+ENV JAVA_APP_JAR="/deployments/quarkus-run.jar"
+
+ENTRYPOINT [ "/opt/jboss/container/java/run/run-java.sh" ]
+
diff --git a/rec-evaluate-customer-product/src/main/docker/Dockerfile.legacy-jar b/rec-evaluate-customer-product/src/main/docker/Dockerfile.legacy-jar
new file mode 100644
index 0000000..d9ad43f
--- /dev/null
+++ b/rec-evaluate-customer-product/src/main/docker/Dockerfile.legacy-jar
@@ -0,0 +1,94 @@
+####
+# This Dockerfile is used in order to build a container that runs the Quarkus application in JVM mode
+#
+# Before building the container image run:
+#
+# ./mvnw package -Dquarkus.package.jar.type=legacy-jar
+#
+# Then, build the image with:
+#
+# docker build -f src/main/docker/Dockerfile.legacy-jar -t quarkus/rec-customer-accounts-legacy-jar .
+#
+# Then run the container using:
+#
+# docker run -i --rm -p 8080:8080 quarkus/rec-customer-accounts-legacy-jar
+#
+# If you want to include the debug port into your docker image
+# you will have to expose the debug port (default 5005 being the default) like this : EXPOSE 8080 5005.
+# Additionally you will have to set -e JAVA_DEBUG=true and -e JAVA_DEBUG_PORT=*:5005
+# when running the container
+#
+# Then run the container using :
+#
+# docker run -i --rm -p 8080:8080 quarkus/rec-customer-accounts-legacy-jar
+#
+# This image uses the `run-java.sh` script to run the application.
+# This scripts computes the command line to execute your Java application, and
+# includes memory/GC tuning.
+# You can configure the behavior using the following environment properties:
+# - JAVA_OPTS: JVM options passed to the `java` command (example: "-verbose:class") - Be aware that this will override
+# the default JVM options, use `JAVA_OPTS_APPEND` to append options
+# - JAVA_OPTS_APPEND: User specified Java options to be appended to generated options
+# in JAVA_OPTS (example: "-Dsome.property=foo")
+# - JAVA_MAX_MEM_RATIO: Is used when no `-Xmx` option is given in JAVA_OPTS. This is
+# used to calculate a default maximal heap memory based on a containers restriction.
+# If used in a container without any memory constraints for the container then this
+# option has no effect. If there is a memory constraint then `-Xmx` is set to a ratio
+# of the container available memory as set here. The default is `50` which means 50%
+# of the available memory is used as an upper boundary. You can skip this mechanism by
+# setting this value to `0` in which case no `-Xmx` option is added.
+# - JAVA_INITIAL_MEM_RATIO: Is used when no `-Xms` option is given in JAVA_OPTS. This
+# is used to calculate a default initial heap memory based on the maximum heap memory.
+# If used in a container without any memory constraints for the container then this
+# option has no effect. If there is a memory constraint then `-Xms` is set to a ratio
+# of the `-Xmx` memory as set here. The default is `25` which means 25% of the `-Xmx`
+# is used as the initial heap size. You can skip this mechanism by setting this value
+# to `0` in which case no `-Xms` option is added (example: "25")
+# - JAVA_MAX_INITIAL_MEM: Is used when no `-Xms` option is given in JAVA_OPTS.
+# This is used to calculate the maximum value of the initial heap memory. If used in
+# a container without any memory constraints for the container then this option has
+# no effect. If there is a memory constraint then `-Xms` is limited to the value set
+# here. The default is 4096MB which means the calculated value of `-Xms` never will
+# be greater than 4096MB. The value of this variable is expressed in MB (example: "4096")
+# - JAVA_DIAGNOSTICS: Set this to get some diagnostics information to standard output
+# when things are happening. This option, if set to true, will set
+# `-XX:+UnlockDiagnosticVMOptions`. Disabled by default (example: "true").
+# - JAVA_DEBUG: If set remote debugging will be switched on. Disabled by default (example:
+# true").
+# - JAVA_DEBUG_PORT: Port used for remote debugging. Defaults to 5005 (example: "8787").
+# - CONTAINER_CORE_LIMIT: A calculated core limit as described in
+# https://www.kernel.org/doc/Documentation/scheduler/sched-bwc.txt. (example: "2")
+# - CONTAINER_MAX_MEMORY: Memory limit given to the container (example: "1024").
+# - GC_MIN_HEAP_FREE_RATIO: Minimum percentage of heap free after GC to avoid expansion.
+# (example: "20")
+# - GC_MAX_HEAP_FREE_RATIO: Maximum percentage of heap free after GC to avoid shrinking.
+# (example: "40")
+# - GC_TIME_RATIO: Specifies the ratio of the time spent outside the garbage collection.
+# (example: "4")
+# - GC_ADAPTIVE_SIZE_POLICY_WEIGHT: The weighting given to the current GC time versus
+# previous GC times. (example: "90")
+# - GC_METASPACE_SIZE: The initial metaspace size. (example: "20")
+# - GC_MAX_METASPACE_SIZE: The maximum metaspace size. (example: "100")
+# - GC_CONTAINER_OPTIONS: Specify Java GC to use. The value of this variable should
+# contain the necessary JRE command-line options to specify the required GC, which
+# will override the default of `-XX:+UseParallelGC` (example: -XX:+UseG1GC).
+# - HTTPS_PROXY: The location of the https proxy. (example: "myuser@127.0.0.1:8080")
+# - HTTP_PROXY: The location of the http proxy. (example: "myuser@127.0.0.1:8080")
+# - NO_PROXY: A comma separated lists of hosts, IP addresses or domains that can be
+# accessed directly. (example: "foo.example.com,bar.example.com")
+#
+###
+FROM registry.access.redhat.com/ubi9/openjdk-17:1.23
+
+ENV LANGUAGE='en_US:en'
+
+
+COPY target/lib/* /deployments/lib/
+COPY target/*-runner.jar /deployments/quarkus-run.jar
+
+EXPOSE 8080
+USER 185
+ENV JAVA_OPTS_APPEND="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager"
+ENV JAVA_APP_JAR="/deployments/quarkus-run.jar"
+
+ENTRYPOINT [ "/opt/jboss/container/java/run/run-java.sh" ]
diff --git a/rec-evaluate-customer-product/src/main/docker/Dockerfile.native b/rec-evaluate-customer-product/src/main/docker/Dockerfile.native
new file mode 100644
index 0000000..1b065f2
--- /dev/null
+++ b/rec-evaluate-customer-product/src/main/docker/Dockerfile.native
@@ -0,0 +1,29 @@
+####
+# This Dockerfile is used in order to build a container that runs the Quarkus application in native (no JVM) mode.
+#
+# Before building the container image run:
+#
+# ./mvnw package -Dnative
+#
+# Then, build the image with:
+#
+# docker build -f src/main/docker/Dockerfile.native -t quarkus/rec-customer-accounts .
+#
+# Then run the container using:
+#
+# docker run -i --rm -p 8080:8080 quarkus/rec-customer-accounts
+#
+# The ` registry.access.redhat.com/ubi9/ubi-minimal:9.6` base image is based on UBI 9.
+# To use UBI 8, switch to `quay.io/ubi8/ubi-minimal:8.10`.
+###
+FROM registry.access.redhat.com/ubi9/ubi-minimal:9.6
+WORKDIR /work/
+RUN chown 1001 /work \
+ && chmod "g+rwX" /work \
+ && chown 1001:root /work
+COPY --chown=1001:root --chmod=0755 target/*-runner /work/application
+
+EXPOSE 8080
+USER 1001
+
+ENTRYPOINT ["./application", "-Dquarkus.http.host=0.0.0.0"]
diff --git a/rec-evaluate-customer-product/src/main/docker/Dockerfile.native-micro b/rec-evaluate-customer-product/src/main/docker/Dockerfile.native-micro
new file mode 100644
index 0000000..1e72495
--- /dev/null
+++ b/rec-evaluate-customer-product/src/main/docker/Dockerfile.native-micro
@@ -0,0 +1,32 @@
+####
+# This Dockerfile is used in order to build a container that runs the Quarkus application in native (no JVM) mode.
+# It uses a micro base image, tuned for Quarkus native executables.
+# It reduces the size of the resulting container image.
+# Check https://quarkus.io/guides/quarkus-runtime-base-image for further information about this image.
+#
+# Before building the container image run:
+#
+# ./mvnw package -Dnative
+#
+# Then, build the image with:
+#
+# docker build -f src/main/docker/Dockerfile.native-micro -t quarkus/rec-customer-accounts .
+#
+# Then run the container using:
+#
+# docker run -i --rm -p 8080:8080 quarkus/rec-customer-accounts
+#
+# The `quay.io/quarkus/ubi9-quarkus-micro-image:2.0` base image is based on UBI 9.
+# To use UBI 8, switch to `quay.io/quarkus/quarkus-micro-image:2.0`.
+###
+FROM quay.io/quarkus/ubi9-quarkus-micro-image:2.0
+WORKDIR /work/
+RUN chown 1001 /work \
+ && chmod "g+rwX" /work \
+ && chown 1001:root /work
+COPY --chown=1001:root --chmod=0755 target/*-runner /work/application
+
+EXPOSE 8080
+USER 1001
+
+ENTRYPOINT ["./application", "-Dquarkus.http.host=0.0.0.0"]
diff --git a/rec-evaluate-customer-product/src/main/java/application/exception/GatewayServiceException.java b/rec-evaluate-customer-product/src/main/java/application/exception/GatewayServiceException.java
new file mode 100644
index 0000000..61dd76a
--- /dev/null
+++ b/rec-evaluate-customer-product/src/main/java/application/exception/GatewayServiceException.java
@@ -0,0 +1,12 @@
+package application.exception;
+
+public class GatewayServiceException extends RuntimeException {
+ public GatewayServiceException(String message) {
+ super(message);
+ }
+
+ public GatewayServiceException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+}
\ No newline at end of file
diff --git a/rec-evaluate-customer-product/src/main/java/application/exception/InvalidTokenException.java b/rec-evaluate-customer-product/src/main/java/application/exception/InvalidTokenException.java
new file mode 100644
index 0000000..0ccf895
--- /dev/null
+++ b/rec-evaluate-customer-product/src/main/java/application/exception/InvalidTokenException.java
@@ -0,0 +1,7 @@
+package application.exception;
+
+public class InvalidTokenException extends RuntimeException {
+ public InvalidTokenException(String message) {
+ super(message);
+ }
+}
\ No newline at end of file
diff --git a/rec-evaluate-customer-product/src/main/java/application/exception/InvalidTokenException.java~ b/rec-evaluate-customer-product/src/main/java/application/exception/InvalidTokenException.java~
new file mode 100644
index 0000000..4c9c2c9
--- /dev/null
+++ b/rec-evaluate-customer-product/src/main/java/application/exception/InvalidTokenException.java~
@@ -0,0 +1,7 @@
+package application.exception;
+
+public class InvalidTokenException extends RuntimeException {
+ public InvalidTokenException(String message) {
+ super(message);
+ }
+}
diff --git a/rec-evaluate-customer-product/src/main/java/application/exception/ServiceCommunicationException.java b/rec-evaluate-customer-product/src/main/java/application/exception/ServiceCommunicationException.java
new file mode 100644
index 0000000..1b55999
--- /dev/null
+++ b/rec-evaluate-customer-product/src/main/java/application/exception/ServiceCommunicationException.java
@@ -0,0 +1,7 @@
+package application.exception;
+
+public class ServiceCommunicationException extends GatewayServiceException {
+ public ServiceCommunicationException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
\ No newline at end of file
diff --git a/rec-evaluate-customer-product/src/main/java/application/helper/CustomerHelper.java b/rec-evaluate-customer-product/src/main/java/application/helper/CustomerHelper.java
new file mode 100644
index 0000000..2f4831d
--- /dev/null
+++ b/rec-evaluate-customer-product/src/main/java/application/helper/CustomerHelper.java
@@ -0,0 +1,25 @@
+package application.helper;
+
+import lombok.extern.slf4j.Slf4j;
+
+import java.util.Optional;
+
+@Slf4j
+public class CustomerHelper {
+ /**
+ * Construye de forma segura un ID de cliente a partir de su tipo y número.
+ *
+ * @param idType El tipo de identificación (ej. "V", "J").
+ * @param idNumber El número de identificación.
+ * @return Un {@link Optional} con el ID concatenado si ambos parámetros son válidos (no nulos ni vacíos),
+ * o un {@code Optional.empty()} en caso contrario.
+ */
+ public static Optional buildCustomerId(String idType, String idNumber) {
+ return Optional.ofNullable(idType)
+ .filter(type -> !type.isBlank())
+ .flatMap(type -> Optional.ofNullable(idNumber)
+ .filter(number -> !number.isBlank())
+ .map(number -> type + number));
+ }
+
+}
\ No newline at end of file
diff --git a/rec-evaluate-customer-product/src/main/java/application/helper/ResponseHelper.java b/rec-evaluate-customer-product/src/main/java/application/helper/ResponseHelper.java
new file mode 100644
index 0000000..8ea4992
--- /dev/null
+++ b/rec-evaluate-customer-product/src/main/java/application/helper/ResponseHelper.java
@@ -0,0 +1,78 @@
+package application.helper;
+
+import domain.dto.response.CustomerPositionStateResponse;
+import domain.dto.response.CustomerResponse;
+import domain.dto.response.StatusResponse;
+import domain.model.BankingProduct;
+import domain.model.ErrorCode;
+import infrastructure.service.StatusDescriptionResolver;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.ws.rs.core.Response;
+
+import java.util.List;
+
+@ApplicationScoped
+public class ResponseHelper {
+
+ private final StatusDescriptionResolver statusDescriptionResolver;
+
+ public ResponseHelper(StatusDescriptionResolver statusDescriptionResolver) {
+ this.statusDescriptionResolver = statusDescriptionResolver;
+ }
+
+ public Response buildErrorResponse(String errorCodeKey, String requestId) {
+ ErrorCode errorCode = statusDescriptionResolver.getCode(errorCodeKey);
+ return this.buildErrorResponse(errorCode, requestId);
+ }
+
+ /**
+ * Construye una respuesta de error estandarizada de forma genérica.
+ * @param errorCode Objeto con el código de error interno.
+ * @return Un objeto Response estándar de JAX-RS.
+ */
+ public Response buildErrorResponse(ErrorCode errorCode, String requestId) {
+
+ var statusResponse = StatusResponse.builder()
+ .status(errorCode.status())
+ .statusCode(errorCode.statusCode())
+ .message(errorCode.description())
+ .traceId(requestId)
+ .build();
+
+ var response = new CustomerResponse(statusResponse);
+
+ int httpStatus = errorCode.getHttpStatusCode();
+
+ return Response.status(httpStatus)
+ .entity(response)
+ .build();
+ }
+
+ /**
+ * Construye una respuesta de éxito (200) CON DATOS.
+ * @param customerAccounts La lista de productos (el cuerpo de la respuesta).
+ * @param successCode El ErrorCode "200" (para los detalles del status).
+ * @param requestId El ID de traza.
+ * @return Un objeto Response estándar de JAX-RS.
+ */
+ public Response buildSuccessResponse(List customerAccounts, ErrorCode successCode, String requestId) {
+
+ var status = StatusResponse.builder()
+ .status(successCode.status())
+ .statusCode(successCode.statusCode())
+ .message(successCode.description())
+ .traceId(requestId)
+ .build();
+
+ var accounts = CustomerPositionStateResponse.builder()
+ .bankingProducts(customerAccounts)
+ .build();
+
+ var response = new CustomerResponse(status, accounts);
+
+ return Response.status(successCode.getHttpStatusCode())
+ .entity(response)
+ .build();
+ }
+
+}
\ No newline at end of file
diff --git a/rec-evaluate-customer-product/src/main/java/application/usecase/CustomerProductUseCase.java b/rec-evaluate-customer-product/src/main/java/application/usecase/CustomerProductUseCase.java
new file mode 100644
index 0000000..b0669f6
--- /dev/null
+++ b/rec-evaluate-customer-product/src/main/java/application/usecase/CustomerProductUseCase.java
@@ -0,0 +1,52 @@
+package application.usecase;
+
+import application.exception.GatewayServiceException;
+import com.banesco.common.infraestructure.helpers.LoggerHelper;
+import domain.dto.request.CustomerRequest;
+import domain.model.ExecutionResult;
+import infrastructure.client.accounts.adapter.CustomerProductAdapter;
+import infrastructure.service.StatusDescriptionResolver;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+@ApplicationScoped
+public class CustomerProductUseCase {
+ private final CustomerProductAdapter customerProductAdapter;
+ private final StatusDescriptionResolver statusDescriptionResolver;
+
+ @Inject
+ public CustomerProductUseCase(
+ CustomerProductAdapter customerProductAdapter,
+ StatusDescriptionResolver statusDescriptionResolver
+ ) {
+ this.customerProductAdapter = customerProductAdapter;
+ this.statusDescriptionResolver = statusDescriptionResolver;
+ }
+
+ public ExecutionResult getCustomerProduct(
+ CustomerRequest request,
+ String requestId,
+ String appId,
+ String customerReferenceFintechId,
+ String customerReferenceUser
+ ) {
+ log.debug(LoggerHelper.buildInfoPrivateRequest(requestId, "Iniciando caso de uso: CustomerProductUseCase"));
+ try {
+ return customerProductAdapter.getCustomerProduct(
+ request,
+ requestId,
+ appId,
+ customerReferenceFintechId,
+ customerReferenceUser
+ );
+ } catch (GatewayServiceException ex) {
+ log.error(LoggerHelper.buildError(requestId, "Fallo la comunicacion con el servicio: " + ex.getMessage()));
+ var error = statusDescriptionResolver.getCode("503-URL");
+ return ExecutionResult.failure(error);
+ } finally {
+ log.debug(LoggerHelper.buildInfoPrivateRequest(requestId, "Finalizado caso de uso: CustomerProductUseCase"));
+ }
+ }
+}
\ No newline at end of file
diff --git a/rec-evaluate-customer-product/src/main/java/domain/dto/request/CustomerRequest.java b/rec-evaluate-customer-product/src/main/java/domain/dto/request/CustomerRequest.java
new file mode 100644
index 0000000..c4f3951
--- /dev/null
+++ b/rec-evaluate-customer-product/src/main/java/domain/dto/request/CustomerRequest.java
@@ -0,0 +1,96 @@
+package domain.dto.request;
+
+import io.quarkus.runtime.annotations.RegisterForReflection;
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Pattern;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+@RegisterForReflection
+public class CustomerRequest {
+
+ @Valid
+ @NotNull(message = "VDE01")
+ private CustomerPositionState customerPositionState;
+
+ @Valid
+ @NotNull(message = "VDE01")
+ private Device device;
+
+// @Valid
+// @NotNull(message = "VDE01")
+ private TokenAssignmentInstance tokenAssignmentInstance;
+ private String productReferenceSearch;
+
+ @Data
+ @AllArgsConstructor
+ @NoArgsConstructor
+ public static class CustomerPositionState {
+
+ @Valid
+ @NotNull(message = "VDE01")
+ private CustomerReference customerReference;
+
+ @Data
+ @AllArgsConstructor
+ @NoArgsConstructor
+ @RegisterForReflection
+ public static class CustomerReference {
+
+ /**
+ * Valida que sea una única letra del conjunto [V, E, P, G, J].
+ */
+ @NotBlank(message = "VDE01")
+ @Pattern(regexp = "^[VEPGJ]$", message = "VDE02")
+ private String customerIdType;
+
+ /**
+ * Valida que sea numérico con un máximo de 9 dígitos.
+ */
+ @NotBlank(message = "VDE01")
+ @Pattern(regexp = "^$|^[0-9]{4,9}$", message = "VDE02")
+ private String customerId;
+
+ }
+
+ }
+
+ @Data
+ @AllArgsConstructor
+ @NoArgsConstructor
+ @RegisterForReflection
+ public static class Device {
+ @NotBlank(message = "VDE01")
+ private String deviceType;
+
+ @NotBlank(message = "VDE01")
+ private String deviceDescription;
+
+ @NotBlank(message = "VDE01")
+ private String deviceIp;
+
+ @NotBlank(message = "VDE01")
+ private String deviceSessionReference;
+ }
+
+ @Data
+ @AllArgsConstructor
+ @NoArgsConstructor
+ @RegisterForReflection
+ public static class TokenAssignmentInstance {
+
+ /**
+ * Este campo puede ser nulo, vacío ("") o un string de 8 dígitos.
+ */
+ @Pattern(regexp = "^$|^[0-9]{8}$", message = "VDE02")
+ private String tokenIdentificationCode;
+
+ }
+
+}
\ No newline at end of file
diff --git a/rec-evaluate-customer-product/src/main/java/domain/dto/response/CustomerPositionStateResponse.java b/rec-evaluate-customer-product/src/main/java/domain/dto/response/CustomerPositionStateResponse.java
new file mode 100644
index 0000000..4a1a529
--- /dev/null
+++ b/rec-evaluate-customer-product/src/main/java/domain/dto/response/CustomerPositionStateResponse.java
@@ -0,0 +1,16 @@
+package domain.dto.response;
+
+import domain.model.BankingProduct;
+import io.quarkus.runtime.annotations.RegisterForReflection;
+import lombok.Builder;
+import lombok.Value;
+
+import java.util.List;
+
+@Value
+@Builder
+@RegisterForReflection
+public class CustomerPositionStateResponse {
+ int record;
+ List bankingProducts;
+}
\ No newline at end of file
diff --git a/rec-evaluate-customer-product/src/main/java/domain/dto/response/CustomerResponse.java b/rec-evaluate-customer-product/src/main/java/domain/dto/response/CustomerResponse.java
new file mode 100644
index 0000000..8c9ffbb
--- /dev/null
+++ b/rec-evaluate-customer-product/src/main/java/domain/dto/response/CustomerResponse.java
@@ -0,0 +1,27 @@
+package domain.dto.response;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.quarkus.runtime.annotations.RegisterForReflection;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+@Getter
+@Setter
+@NoArgsConstructor
+@RegisterForReflection
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class CustomerResponse {
+ StatusResponse statusResponse;
+ CustomerPositionStateResponse customerPositionState;
+
+ public CustomerResponse(StatusResponse statusResponse, CustomerPositionStateResponse customerPositionState) {
+ this.statusResponse = statusResponse;
+ this.customerPositionState = customerPositionState;
+ }
+
+ public CustomerResponse(StatusResponse statusResponse) {
+ this.statusResponse = statusResponse;
+ }
+
+}
\ No newline at end of file
diff --git a/rec-evaluate-customer-product/src/main/java/domain/dto/response/StatusResponse.java b/rec-evaluate-customer-product/src/main/java/domain/dto/response/StatusResponse.java
new file mode 100644
index 0000000..d228655
--- /dev/null
+++ b/rec-evaluate-customer-product/src/main/java/domain/dto/response/StatusResponse.java
@@ -0,0 +1,15 @@
+package domain.dto.response;
+
+import io.quarkus.runtime.annotations.RegisterForReflection;
+import lombok.Builder;
+import lombok.Value;
+
+@Value
+@Builder
+@RegisterForReflection
+public class StatusResponse {
+ String status;
+ String statusCode;
+ String message;
+ String traceId;
+}
\ No newline at end of file
diff --git a/rec-evaluate-customer-product/src/main/java/domain/model/BankingProduct.java b/rec-evaluate-customer-product/src/main/java/domain/model/BankingProduct.java
new file mode 100644
index 0000000..a8e2ba2
--- /dev/null
+++ b/rec-evaluate-customer-product/src/main/java/domain/model/BankingProduct.java
@@ -0,0 +1,19 @@
+package domain.model;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.quarkus.runtime.annotations.RegisterForReflection;
+import lombok.Builder;
+import lombok.Value;
+
+import java.math.BigDecimal;
+
+@Value
+@Builder
+@RegisterForReflection
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class BankingProduct {
+ String productNumber;
+ String productType;
+ BigDecimal balance;
+ String currency;
+}
\ No newline at end of file
diff --git a/rec-evaluate-customer-product/src/main/java/domain/model/ErrorCode.java b/rec-evaluate-customer-product/src/main/java/domain/model/ErrorCode.java
new file mode 100644
index 0000000..76bc51c
--- /dev/null
+++ b/rec-evaluate-customer-product/src/main/java/domain/model/ErrorCode.java
@@ -0,0 +1,28 @@
+package domain.model;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import io.quarkus.runtime.annotations.RegisterForReflection;
+
+/**
+ * Representa un código de error de la aplicación.
+ */
+@RegisterForReflection
+public record ErrorCode(
+ @JsonProperty("backend_code") String backendCode,
+ @JsonProperty("http_code") String httpCode,
+ @JsonProperty("status_code") String statusCode,
+ @JsonProperty("description") String description,
+ @JsonProperty("status") String status
+) {
+ /**
+ * Convierte de forma segura el 'httpCode' a un código de estado HTTP numérico.
+ * @return El código de estado HTTP como un entero.
+ */
+ public int getHttpStatusCode() {
+ try {
+ return Integer.parseInt(this.httpCode);
+ } catch (NumberFormatException e) {
+ return 500;
+ }
+ }
+}
\ No newline at end of file
diff --git a/rec-evaluate-customer-product/src/main/java/domain/model/ExecutionResult.java b/rec-evaluate-customer-product/src/main/java/domain/model/ExecutionResult.java
new file mode 100644
index 0000000..d46d1ba
--- /dev/null
+++ b/rec-evaluate-customer-product/src/main/java/domain/model/ExecutionResult.java
@@ -0,0 +1,43 @@
+package domain.model;
+
+import domain.dto.response.CustomerResponse;
+
+public class ExecutionResult {
+
+ private final CustomerResponse successResponse;
+ private final ErrorCode errorCode;
+
+ // Constructor privado para éxito
+ private ExecutionResult(CustomerResponse successResponse) {
+ this.successResponse = successResponse;
+ this.errorCode = null;
+ }
+
+ // Constructor privado para error
+ private ExecutionResult(ErrorCode errorCode) {
+ this.successResponse = null;
+ this.errorCode = errorCode;
+ }
+
+ // --- Métodos de fábrica (Factory) ---
+ public static ExecutionResult success(CustomerResponse response) {
+ return new ExecutionResult(response);
+ }
+
+ public static ExecutionResult failure(ErrorCode code) {
+ return new ExecutionResult(code);
+ }
+
+ // --- Métodos de comprobación ---
+ public boolean isSuccess() {
+ return this.successResponse != null;
+ }
+
+ public CustomerResponse getSuccessResponse() {
+ return successResponse;
+ }
+
+ public ErrorCode getErrorCode() {
+ return errorCode;
+ }
+}
\ No newline at end of file
diff --git a/rec-evaluate-customer-product/src/main/java/domain/model/ExecutionResult.java~ b/rec-evaluate-customer-product/src/main/java/domain/model/ExecutionResult.java~
new file mode 100644
index 0000000..3ef2d42
--- /dev/null
+++ b/rec-evaluate-customer-product/src/main/java/domain/model/ExecutionResult.java~
@@ -0,0 +1,4 @@
+package domain.model;
+
+public class ExecutionResult {
+}
diff --git a/rec-evaluate-customer-product/src/main/java/infrastructure/adapter/input/rest/CustomerResource.java b/rec-evaluate-customer-product/src/main/java/infrastructure/adapter/input/rest/CustomerResource.java
new file mode 100644
index 0000000..2df9cf1
--- /dev/null
+++ b/rec-evaluate-customer-product/src/main/java/infrastructure/adapter/input/rest/CustomerResource.java
@@ -0,0 +1,286 @@
+package infrastructure.adapter.input.rest;
+
+import application.helper.ResponseHelper;
+import application.usecase.CustomerProductUseCase;
+import com.banesco.common.infraestructure.helpers.JsonHelper;
+import com.banesco.common.infraestructure.helpers.LoggerHelper;
+import domain.dto.request.CustomerRequest;
+import domain.dto.response.CustomerResponse;
+import domain.dto.response.StatusResponse;
+import infrastructure.context.TraceIdContext;
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.NotNull;
+import jakarta.ws.rs.HeaderParam;
+import jakarta.ws.rs.POST;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.Response;
+import lombok.extern.slf4j.Slf4j;
+import org.eclipse.microprofile.openapi.annotations.Operation;
+import org.eclipse.microprofile.openapi.annotations.enums.ParameterIn;
+import org.eclipse.microprofile.openapi.annotations.enums.SchemaType;
+import org.eclipse.microprofile.openapi.annotations.media.Content;
+import org.eclipse.microprofile.openapi.annotations.media.ExampleObject;
+import org.eclipse.microprofile.openapi.annotations.media.Schema;
+import org.eclipse.microprofile.openapi.annotations.parameters.Parameter;
+import org.eclipse.microprofile.openapi.annotations.parameters.Parameters;
+import org.eclipse.microprofile.openapi.annotations.parameters.RequestBody;
+import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
+import org.eclipse.microprofile.openapi.annotations.responses.APIResponses;
+
+@Slf4j
+@Path("/customer")
+public class CustomerResource {
+
+ // --- DEPENDENCIAS INYECTADAS ---
+ private final TraceIdContext traceIdContext;
+ private final ResponseHelper responseHelper;
+
+ // -- CASO DE USO
+ private final CustomerProductUseCase customerProductUseCase;
+
+ public CustomerResource(
+ TraceIdContext traceIdContext,
+ ResponseHelper responseHelper,
+ CustomerProductUseCase customerProductUseCase
+ ) {
+ this.traceIdContext = traceIdContext;
+ this.responseHelper = responseHelper;
+ this.customerProductUseCase = customerProductUseCase;
+ }
+
+ @POST
+ @Path("/position/evaluate")
+ @Operation(
+ summary = "Consulta de Productos",
+ description = "Orquesta a bus-evaluate-customer-product (BUS Consulta de Producto) para consultar las cuentas del cliente."
+ )
+ @Parameters({
+ @Parameter(
+ name = "requestId",
+ in = ParameterIn.HEADER,
+ description = "Identificador de la solicitud para la trazabilidad.",
+ schema = @Schema(type = SchemaType.STRING),
+ example = "7WCnvznd0CPrBZ0jF4af"
+ ),
+ @Parameter(
+ name = "appId",
+ required = true,
+ in = ParameterIn.HEADER,
+ description = "Identificador de la aplicación cliente para cargar una configuración de header específica.",
+ schema = @Schema(type = SchemaType.STRING),
+ example = "DANIAPP"
+ )
+ })
+ @RequestBody(
+ description = "Cuerpo de la solicitud con los datos necesarios para consultar las cuentas.",
+ content = @Content(mediaType = MediaType.APPLICATION_JSON, schema =
+ @Schema(implementation = CustomerRequest.class),
+ examples = {
+ @ExampleObject(
+ name = "Solicitud Estándar de Búsqueda por Producto (TDD)",
+ summary = "Ejemplo de solicitud de productos (TDD) con éxito",
+ externalValue = "/openapi-examples/request/customer-product-search-success.json"
+ ),
+ @ExampleObject(
+ name = "Solicitud Estándar de Búsqueda por Producto sin Resultados (TDD)",
+ summary = "Ejemplo de solicitud de productos (TDD) sin resultados",
+ externalValue = "/openapi-examples/request/customer-product-search-wihtout-result.json"
+ ),
+ @ExampleObject(
+ name = "Solicitud Estándar",
+ summary = "Ejemplo de solicitud con éxito",
+ externalValue = "/openapi-examples/request/customer-product-success.json"
+ ),
+ @ExampleObject(
+ name = "Solicitud Estándar (Sin tokenAssignmentInstance)",
+ summary = "Ejemplo de solicitud con éxito (sin objeto para OTP)",
+ externalValue = "/openapi-examples/request/customer-product-success-without-token.json"
+ ),
+ @ExampleObject(
+ name = "Solicitud con Token",
+ summary = "Ejemplo de solicitud con token (éxito)",
+ externalValue = "/openapi-examples/request/customer-product-token-success.json"
+ )
+ }
+ )
+ )
+ @APIResponses(value = {
+ @APIResponse(
+ responseCode = "200",
+ description = "Operación Exitosa. Devuelve la lista de productos del cliente.",
+ content = @Content(
+ mediaType = MediaType.APPLICATION_JSON,
+ schema = @Schema(implementation = CustomerResponse.class),
+ examples = {
+ @ExampleObject(
+ name = "Búsqueda por Producto",
+ summary = "Respuesta exitosa",
+ description = "Realiza busqueda de por tipo de producto (productReferenceSearch).",
+ externalValue = "/openapi-examples/response/customer-product-search-response.json"
+ ),
+ @ExampleObject(
+ name = "Búsqueda por Producto sin Resultados",
+ summary = "Respuesta exitosa",
+ description = "Realiza busqueda de por tipo de producto (productReferenceSearch) sin resultados.",
+ externalValue = "/openapi-examples/response/customer-product-search-response-without-results.json"
+ ),
+ @ExampleObject(
+ name = "Cuentas Enmascaradas",
+ summary = "Respuesta exitosa (Enmascaramiento activo)",
+ description = "La configuración de la app indica que 'encryptionData' es true.",
+ externalValue = "/openapi-examples/response/customer-product-response.json"
+ ),
+ @ExampleObject(
+ name = "Cuentas Reales",
+ summary = "Respuesta exitosa (Sin enmascaramiento)",
+ description = "La configuración de la app indica que 'encryptionData' es false.",
+ externalValue = "/openapi-examples/response/customer-product-response-unmasked.json"
+ ),
+ @ExampleObject(
+ name = "Sin Resultados",
+ summary = "Respuesta exitosa (Cliente sin productos)",
+ description = "La consulta se completó, pero el cliente no tiene productos que coincidan.",
+ externalValue = "/openapi-examples/response/customer-product-without-results.json"
+ )
+ }
+ )
+ ),
+ @APIResponse(
+ responseCode = "401",
+ description = "Error de Autorización o Perfil. El perfil del cliente no es válido o falló una validación de seguridad.",
+ content = @Content(
+ mediaType = MediaType.APPLICATION_JSON,
+ schema = @Schema(implementation = StatusResponse.class),
+ examples = {
+ @ExampleObject(
+ name = "Perfil Inválido",
+ summary = "VRN01 - Perfil no válido",
+ externalValue = "/openapi-examples/response/invalid-profile.json"
+ )
+ }
+ )
+ ),
+ @APIResponse(
+ responseCode = "400",
+ description = "Datos de entrada inválidos, como un token OTP incorrecto o una configuración de app no encontrada.",
+ content = @Content(
+ mediaType = MediaType.APPLICATION_JSON,
+ schema = @Schema(implementation = StatusResponse.class),
+ examples = {
+ @ExampleObject(
+ name = "Token Inválido",
+ summary = "VRN07 - OTP Inválido",
+ externalValue = "/openapi-examples/response/invalid-token.json"
+ ),
+ @ExampleObject(
+ name = "Configuración no encontrada",
+ summary = "VRN_CONFIG_NOT_FOUND",
+ externalValue = "/openapi-examples/response/config-not-found.json"
+ )
+ }
+ )
+ ),
+ @APIResponse(
+ responseCode = "404",
+ description = "Recurso no encontrado. Puede ser un usuario BOL, un login vacío.",
+ content = @Content(
+ mediaType = MediaType.APPLICATION_JSON,
+ schema = @Schema(implementation = CustomerResponse.class),
+ examples = {
+ @ExampleObject(
+ name = "Usuario no existe",
+ summary = "AUTH02 - Usuario no existe",
+ externalValue = "/openapi-examples/response/user-bol-not-exist.json"
+ ),
+ @ExampleObject(
+ name = "Usuario vacío",
+ summary = "AUTH01 - Login vacío",
+ externalValue = "/openapi-examples/response/user-not-exist.json"
+ ),
+ }
+ )
+ ),
+ @APIResponse(
+ responseCode = "409",
+ description = "CONFLICTO.",
+ content = @Content(
+ mediaType = MediaType.APPLICATION_JSON,
+ schema = @Schema(implementation = StatusResponse.class),
+ examples = {
+ @ExampleObject(
+ name = "Conflicto",
+ summary = "Conflicto (revisar)",
+ externalValue = "/openapi-examples/response/conflict.json"
+ )
+ }
+ )
+ ),
+ @APIResponse(
+ responseCode = "500",
+ description = "Uso Interno. Ocurrio un error inesperado.",
+ content = @Content(
+ mediaType = MediaType.APPLICATION_JSON,
+ schema = @Schema(implementation = StatusResponse.class),
+ examples = {
+ @ExampleObject(
+ name = "Uso Interno",
+ summary = "Uso Interno (revisar)",
+ externalValue = "/openapi-examples/response/internal-use.json"
+ )
+ }
+ )
+ ),
+ @APIResponse(
+ responseCode = "503",
+ description = "Servicio no disponible. Ocurrió un error con una dependencia interna o el servicio está en mantenimiento.",
+ content = @Content(
+ mediaType = MediaType.APPLICATION_JSON,
+ schema = @Schema(implementation = StatusResponse.class),
+ examples = {
+ @ExampleObject(
+ name = "Mantenimiento",
+ summary = "VRN04 - Servicio en mantenimiento",
+ externalValue = "/openapi-examples/response/service-unavailable.json"
+ ),
+ @ExampleObject(
+ name = "Error de Dependencia",
+ summary = "Error de CI",
+ externalValue = "/openapi-examples/response/customer-product-rest-client-error.json"
+ )
+ }
+ )
+ )
+ })
+ public Response evaluatePosition(
+ @HeaderParam("appId") String appId,
+ @HeaderParam("customerReferenceFintechId") String customerReferenceFintechId,
+ @HeaderParam("customerReferenceUser") String customerReferenceUser,
+ @Valid @NotNull(message = "VDE01") CustomerRequest request
+ ) {
+ final var requestId = traceIdContext.getTraceId();
+
+ log.info(LoggerHelper.buildInfoPrivateRequest(requestId, appId, JsonHelper.getJsonFromObject(request)));
+
+ var result = customerProductUseCase.getCustomerProduct(
+ request,
+ requestId,
+ appId,
+ customerReferenceFintechId,
+ customerReferenceUser
+ );
+
+ if (result.isSuccess()) {
+ var customerProducts = result.getSuccessResponse();
+ log.info(LoggerHelper.buildInfoPrivateResponse(requestId, "Respuesta del servicio de bus-evaluate-customer-product", customerProducts));
+
+ return Response.ok(customerProducts).build();
+
+ } else {
+ var errorCode = result.getErrorCode();
+ log.warn(LoggerHelper.buildError(requestId, "Error de negocio manejado en Resource: " + errorCode.statusCode()));
+
+ return responseHelper.buildErrorResponse(errorCode, requestId);
+ }
+ }
+}
\ No newline at end of file
diff --git a/rec-evaluate-customer-product/src/main/java/infrastructure/client/accounts/adapter/CustomerProductAdapter.java b/rec-evaluate-customer-product/src/main/java/infrastructure/client/accounts/adapter/CustomerProductAdapter.java
new file mode 100644
index 0000000..60d471b
--- /dev/null
+++ b/rec-evaluate-customer-product/src/main/java/infrastructure/client/accounts/adapter/CustomerProductAdapter.java
@@ -0,0 +1,226 @@
+package infrastructure.client.accounts.adapter;
+
+import application.exception.GatewayServiceException;
+import com.banesco.common.infraestructure.helpers.LoggerHelper;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import domain.dto.request.CustomerRequest;
+import domain.dto.response.CustomerResponse;
+import domain.model.ErrorCode;
+import domain.model.ExecutionResult;
+import infrastructure.client.accounts.api.CustomerAccountClient;
+import infrastructure.service.StatusDescriptionResolver;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.ws.rs.WebApplicationException;
+import jakarta.ws.rs.core.Response;
+import lombok.extern.slf4j.Slf4j;
+import org.eclipse.microprofile.rest.client.inject.RestClient;
+
+@Slf4j
+@ApplicationScoped
+public class CustomerProductAdapter {
+
+ private final CustomerAccountClient client;
+ private final ObjectMapper objectMapper;
+ private final StatusDescriptionResolver statusDescriptionResolver;
+
+ public CustomerProductAdapter(
+ @RestClient CustomerAccountClient client,
+ ObjectMapper objectMapper,
+ StatusDescriptionResolver statusDescriptionResolver
+ ) {
+ this.client = client;
+ this.objectMapper = objectMapper;
+ this.statusDescriptionResolver = statusDescriptionResolver;
+ }
+
+ public ExecutionResult getCustomerProduct(
+ CustomerRequest request,
+ String requestId,
+ String appId,
+ String customerReferenceFintechId,
+ String customerReferenceUser
+ ) {
+ try {
+ log.info(LoggerHelper.buildInfoPrivateRequest(requestId, "Llamando al servicio de bus-evaluate-customer-product", request));
+
+ // Mock para cliente sin productos
+ if (request.getCustomerPositionState() != null
+ && request.getCustomerPositionState().getCustomerReference() != null
+ && "V".equals(request.getCustomerPositionState().getCustomerReference().getCustomerIdType())
+ && "8678988".equals(request.getCustomerPositionState().getCustomerReference().getCustomerId())) {
+
+ domain.dto.response.StatusResponse statusResponse = domain.dto.response.StatusResponse.builder()
+ .status("ok")
+ .statusCode("200")
+ .message("Cliente sin productos")
+ .traceId(requestId)
+ .build();
+
+ domain.dto.response.CustomerPositionStateResponse customerPositionState =
+ domain.dto.response.CustomerPositionStateResponse.builder()
+ .record(0)
+ .bankingProducts(java.util.Collections.emptyList())
+ .build();
+
+ CustomerResponse customerResponse = new CustomerResponse(statusResponse, customerPositionState);
+
+ return ExecutionResult.success(customerResponse);
+ }
+
+ if (request.getProductReferenceSearch() != null && !request.getProductReferenceSearch().isEmpty()) {
+ // Mock para cliente con productos
+ domain.dto.response.StatusResponse statusResponse = domain.dto.response.StatusResponse.builder()
+ .status("ok")
+ .statusCode("200")
+ .message("Operacion Exitosa")
+ .traceId(requestId)
+ .build();
+
+ // Producto
+ domain.model.BankingProduct product = domain.model.BankingProduct.builder()
+ .productNumber("****-****-****-8514")
+ .productType("TDB")
+ // .balance(null)
+ // .currency(null)
+ .build();
+
+ java.util.List products = java.util.Collections.singletonList(product);
+
+ // CustomerPositionStateResponse
+ domain.dto.response.CustomerPositionStateResponse customerPositionState =
+ domain.dto.response.CustomerPositionStateResponse.builder()
+ .record(1)
+ .bankingProducts(products)
+ .build();
+
+ // CustomerResponse
+ CustomerResponse customerResponse = new CustomerResponse(statusResponse, customerPositionState);
+
+ return ExecutionResult.success(customerResponse);
+ }
+
+ var response = client.readCustomerProducts(
+ requestId,
+ appId,
+ customerReferenceFintechId,
+ customerReferenceUser,
+ request
+ );
+
+ if (response == null) {
+ log.warn(LoggerHelper.buildError(requestId, "Respuesta invalida o nula del servicio de bus-evaluate-customer-product"));
+ throw new GatewayServiceException("Respuesta invalida del servicio externo de bus-evaluate-customer-product");
+ }
+
+ return ExecutionResult.success(response);
+
+ } catch (WebApplicationException ex) {
+ return handleHttpError(ex, requestId);
+
+ } catch (RuntimeException ex) {
+ // Error de red, timeout, etc.
+ throw handleNetworkError(ex, requestId);
+ }
+ }
+
+ /**
+ * Orquesta el manejo de errores HTTP (WebApplicationException).
+ */
+ private ExecutionResult handleHttpError(WebApplicationException ex, String requestId) {
+ var errorResponse = ex.getResponse();
+ int status = errorResponse.getStatus();
+ String errorBody = readErrorBody(errorResponse);
+
+ // Si es un error de infraestructura (HTML), lanza excepción.
+ if (isInfrastructureError(errorResponse)) {
+ String htmlError = String.format("Error HTTP %s del servicio. Se recibio una respuesta HTML no esperada.", status);
+ log.error(LoggerHelper.buildError(requestId, htmlError));
+ throw new GatewayServiceException(htmlError);
+ }
+
+ // Si no es HTML, debe ser un error de negocio (JSON). Intentamos parsearlo.
+ try {
+ return parseBusinessError(errorBody, status, requestId);
+ } catch (Exception parseEx) {
+ // Si falla el parseo, es un error de infraestructura (contrato JSON roto).
+ var rawError = String.format("Error HTTP no manejado (body no parseable). Status: %s. Body: %s",
+ status, errorBody.substring(0, Math.min(errorBody.length(), 1000)));
+ log.error(LoggerHelper.buildError(requestId, rawError), parseEx);
+ throw new GatewayServiceException(rawError, parseEx);
+ }
+ }
+
+ /**
+ * Parsea un error de negocio (JSON) y lo convierte en un ExecutionResult.
+ */
+ private ExecutionResult parseBusinessError(String errorBody, int httpStatus, String requestId) throws com.fasterxml.jackson.core.JsonProcessingException {
+ var errorDto = objectMapper.readValue(errorBody, CustomerResponse.class);
+
+ if (errorDto != null && errorDto.getStatusResponse() != null) {
+ var statusCode = errorDto.getStatusResponse().getStatusCode();
+ var message = errorDto.getStatusResponse().getMessage();
+
+ var bizError = String.format("Error de negocio (HTTP %s) del servicio. Code: %s, Message: %s", httpStatus, statusCode, message);
+ log.warn(LoggerHelper.buildError(requestId, bizError));
+
+ var error = resolveErrorCode(statusCode, message, httpStatus, requestId);
+ return ExecutionResult.failure(error);
+ }
+
+ // Si el JSON es válido, pero incompleto (ej. statusResponse: null), es un error de contrato
+ log.error(LoggerHelper.buildError(requestId, "Error de contrato: El JSON de error no contiene 'statusResponse'"));
+ throw new GatewayServiceException("Error de contrato: JSON de error invalido");
+ }
+
+ /**
+ * Busca el ErrorCode o crea uno de fallback.
+ */
+ private ErrorCode resolveErrorCode(String statusCode, String message, int httpStatus, String requestId) {
+ var error = statusDescriptionResolver.getCode(statusCode);
+ if (error == null) {
+ log.warn("Codigo de error '{}' no encontrado en StatusDescriptionResolver. Usando valores del body.", statusCode);
+ error = new ErrorCode(
+ statusCode,
+ String.valueOf(httpStatus),
+ statusCode,
+ message,
+ "error"
+ );
+ }
+ return error;
+ }
+
+ /**
+ * Lee de forma segura el body de una respuesta de error.
+ */
+ private String readErrorBody(Response errorResponse) {
+ if (!errorResponse.hasEntity()) {
+ return "No body";
+ }
+ try {
+ return errorResponse.readEntity(String.class);
+ } catch (Exception e) {
+ log.error("No se pudo leer el stream del body de error: {}", e.getMessage());
+ return "Error al leer stream";
+ }
+ }
+
+ /**
+ * Comprueba si la respuesta es un error de infraestructura (ej. HTML).
+ */
+ private boolean isInfrastructureError(Response errorResponse) {
+ var mediaType = errorResponse.getMediaType();
+ return mediaType != null && mediaType.toString().contains("text/html");
+ }
+
+ /**
+ * Maneja errores de red/comunicación (RuntimeException).
+ */
+ private GatewayServiceException handleNetworkError(RuntimeException ex, String requestId) {
+ log.error(LoggerHelper.buildError(
+ requestId,
+ String.format("Error de comunicacion con el servicio: %s", ex.getMessage())
+ ), ex);
+ return new GatewayServiceException("No se pudo comunicar con el servicio externo de bus-evaluate-customer-product.", ex);
+ }
+}
\ No newline at end of file
diff --git a/rec-evaluate-customer-product/src/main/java/infrastructure/client/accounts/api/CustomerAccountClient.java b/rec-evaluate-customer-product/src/main/java/infrastructure/client/accounts/api/CustomerAccountClient.java
new file mode 100644
index 0000000..c66fc11
--- /dev/null
+++ b/rec-evaluate-customer-product/src/main/java/infrastructure/client/accounts/api/CustomerAccountClient.java
@@ -0,0 +1,22 @@
+package infrastructure.client.accounts.api;
+
+import domain.dto.request.CustomerRequest;
+import domain.dto.response.CustomerResponse;
+import infrastructure.interceptor.LoggingFilter;
+import jakarta.ws.rs.HeaderParam;
+import jakarta.ws.rs.POST;
+import org.eclipse.microprofile.rest.client.annotation.RegisterProvider;
+import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
+
+@RegisterRestClient(configKey = "customer-accounts")
+@RegisterProvider(LoggingFilter.class)
+public interface CustomerAccountClient {
+ @POST
+ CustomerResponse readCustomerProducts(
+ @HeaderParam("requestId") String requestId,
+ @HeaderParam("appId") String appId,
+ @HeaderParam("customerReferenceFintechId") String customerReferenceFintechId,
+ @HeaderParam("customerReferenceUser") String customerReferenceUser,
+ CustomerRequest request
+ );
+}
\ No newline at end of file
diff --git a/rec-evaluate-customer-product/src/main/java/infrastructure/config/AppConfig.java b/rec-evaluate-customer-product/src/main/java/infrastructure/config/AppConfig.java
new file mode 100644
index 0000000..e7f545b
--- /dev/null
+++ b/rec-evaluate-customer-product/src/main/java/infrastructure/config/AppConfig.java
@@ -0,0 +1,82 @@
+package infrastructure.config;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import domain.model.ErrorCode;
+import jakarta.annotation.PostConstruct;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+import lombok.Getter;
+import lombok.extern.slf4j.Slf4j;
+import org.eclipse.microprofile.config.inject.ConfigProperty;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+@Slf4j
+@Getter
+@ApplicationScoped
+public class AppConfig {
+
+ @Inject
+ ObjectMapper objectMapper;
+
+ @ConfigProperty(name = "app.response-json")
+ private Optional responseJsonString;
+
+ @ConfigProperty(name = "app.print-response", defaultValue = "false")
+ private Boolean printResponse;
+
+ private List errorCodesList;
+
+ @Getter
+ private Map errorCodeMap;
+
+ @PostConstruct
+ public void init() {
+ log.info("Inicializando configuracion de la aplicacion...");
+
+ if (responseJsonString.isPresent() && !responseJsonString.get().isBlank()) {
+ try {
+ this.errorCodesList = objectMapper.readValue(responseJsonString.get(), new TypeReference>() {});
+ this.errorCodeMap = this.errorCodesList.stream()
+ .collect(Collectors.toMap(
+ ErrorCode::backendCode,
+ Function.identity(),
+ (existing, replacement) -> existing
+ ));
+ log.info("Se cargaron {} codigos de error en el mapa.", this.errorCodeMap.size());
+ } catch (JsonProcessingException e) {
+ log.error("Error fatal al parsear la propiedad 'app.response-json'.", e);
+ this.errorCodesList = Collections.emptyList();
+ this.errorCodeMap = Collections.emptyMap();
+ }
+ } else {
+ log.warn("La propiedad 'app.response-json' no esta definida o esta vacia.");
+ this.errorCodesList = Collections.emptyList();
+ this.errorCodeMap = Collections.emptyMap();
+ }
+
+ log.info("--- Validando propiedades de configuracion ---");
+
+ if (errorCodesList.isEmpty()) {
+ log.warn("JSON de respuesta no configurado o vacio. El resolvedor de codigos podria no operar correctamente.");
+ } else {
+ log.info("Configuracion de 'app.response-json' validada.");
+ }
+
+ log.info("app.print-response configurado: {}", printResponse);
+
+ log.info("Configuracion completada.");
+ }
+
+ public List getAppJson() {
+ return errorCodesList;
+ }
+
+}
\ No newline at end of file
diff --git a/rec-evaluate-customer-product/src/main/java/infrastructure/config/RegisterSecurityConfig.java b/rec-evaluate-customer-product/src/main/java/infrastructure/config/RegisterSecurityConfig.java
new file mode 100644
index 0000000..369f104
--- /dev/null
+++ b/rec-evaluate-customer-product/src/main/java/infrastructure/config/RegisterSecurityConfig.java
@@ -0,0 +1,11 @@
+package infrastructure.config;
+
+import io.smallrye.config.ConfigMapping;
+
+@ConfigMapping(prefix = "app.register-security")
+public interface RegisterSecurityConfig {
+ String eventCod();
+ String bankCod();
+ String curCod();
+ String sp();
+}
\ No newline at end of file
diff --git a/rec-evaluate-customer-product/src/main/java/infrastructure/context/TraceIdContext.java b/rec-evaluate-customer-product/src/main/java/infrastructure/context/TraceIdContext.java
new file mode 100644
index 0000000..530ff19
--- /dev/null
+++ b/rec-evaluate-customer-product/src/main/java/infrastructure/context/TraceIdContext.java
@@ -0,0 +1,12 @@
+package infrastructure.context;
+
+import jakarta.enterprise.context.RequestScoped;
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+@RequestScoped
+public class TraceIdContext {
+ private String traceId;
+}
\ No newline at end of file
diff --git a/rec-evaluate-customer-product/src/main/java/infrastructure/exception/ConstraintViolationExceptionHandler.java b/rec-evaluate-customer-product/src/main/java/infrastructure/exception/ConstraintViolationExceptionHandler.java
new file mode 100644
index 0000000..9c92ada
--- /dev/null
+++ b/rec-evaluate-customer-product/src/main/java/infrastructure/exception/ConstraintViolationExceptionHandler.java
@@ -0,0 +1,97 @@
+package infrastructure.exception;
+
+import domain.dto.response.CustomerResponse;
+import domain.dto.response.StatusResponse;
+import infrastructure.context.TraceIdContext;
+import infrastructure.service.StatusDescriptionResolver;
+import jakarta.inject.Inject;
+import jakarta.validation.ConstraintViolation;
+import jakarta.validation.ConstraintViolationException;
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.ext.ExceptionMapper;
+import jakarta.ws.rs.ext.Provider;
+import lombok.extern.slf4j.Slf4j;
+import org.jboss.logging.Logger;
+
+import java.util.Set;
+import java.util.stream.Collectors;
+
+@Slf4j
+@Provider
+public class ConstraintViolationExceptionHandler implements ExceptionMapper {
+
+ private static final Logger LOG = Logger.getLogger(ConstraintViolationExceptionHandler.class);
+
+ @Inject
+ TraceIdContext traceIdContext;
+
+ @Inject
+ StatusDescriptionResolver statusDescriptionResolver;
+
+ @Override
+ public Response toResponse(ConstraintViolationException exception) {
+ var traceId = traceIdContext.getTraceId();
+ var violations = exception.getConstraintViolations();
+
+ if (violations.isEmpty()) {
+ return buildGenericErrorResponse(traceId, exception);
+ }
+
+ // Se busca la violación con la mayor prioridad según la lista definida.
+ var highestPriorityViolation = findHighestPriorityViolation(violations);
+
+ // Extraer el backendCode directamente del mensaje de la anotación.
+ var backendCode = highestPriorityViolation.getMessage();
+ var fieldPath = highestPriorityViolation.getPropertyPath().toString();
+
+ // Obtenemos el nombre simple del campo para el mensaje.
+ var jsonProperty = fieldPath.substring(fieldPath.lastIndexOf('.') + 1);
+
+ LOG.warnf("[traceId: %s] Error de validacion en el campo '%s'. Mensaje/Codigo: '%s'", traceId, fieldPath, backendCode);
+
+ // Obtener los detalles del error.
+ var resolvedCode = statusDescriptionResolver.getCode(backendCode);
+
+ var statusResponse = StatusResponse.builder()
+ .status("error")
+ .statusCode(resolvedCode.statusCode())
+ .message(resolvedCode.description() + ": " + jsonProperty)
+ .traceId(traceId)
+ .build();
+
+ var response = new CustomerResponse(statusResponse);
+
+ return Response.status(Response.Status.BAD_REQUEST)
+ .entity(response)
+ .build();
+ }
+
+ private ConstraintViolation> findHighestPriorityViolation(Set> violations) {
+ return violations.stream().findFirst().get();
+ }
+
+ /**
+ * Construye una respuesta de error genérica en caso de que no se encuentren violaciones específicas.
+ */
+ private Response buildGenericErrorResponse(String traceId, ConstraintViolationException exception) {
+ var detailsForLog = exception.getConstraintViolations()
+ .stream()
+ .map(cv -> cv.getPropertyPath() + ": " + cv.getMessage())
+ .collect(Collectors.joining("; "));
+
+ LOG.warnf("[traceId: %s] Error de validación genérico detectado, %s", traceId, detailsForLog);
+
+ var statusResponse = StatusResponse.builder()
+ .status("error")
+ .statusCode("VDE-400")
+ .message("Error en los datos de entrada. Verifique la información enviada")
+ .traceId(traceId)
+ .build();
+
+ var response = new CustomerResponse(statusResponse);
+
+ return Response.status(Response.Status.BAD_REQUEST)
+ .entity(response)
+ .build();
+ }
+}
\ No newline at end of file
diff --git a/rec-evaluate-customer-product/src/main/java/infrastructure/exception/GlobalExceptionHandler.java b/rec-evaluate-customer-product/src/main/java/infrastructure/exception/GlobalExceptionHandler.java
new file mode 100644
index 0000000..423c1d7
--- /dev/null
+++ b/rec-evaluate-customer-product/src/main/java/infrastructure/exception/GlobalExceptionHandler.java
@@ -0,0 +1,84 @@
+package infrastructure.exception;
+
+import application.exception.GatewayServiceException;
+import com.banesco.common.application.exception.BanBackendException;
+import domain.dto.response.CustomerResponse;
+import domain.model.ErrorCode;
+import infrastructure.context.TraceIdContext;
+import infrastructure.service.StatusDescriptionResolver;
+import jakarta.inject.Inject;
+import jakarta.ws.rs.BadRequestException;
+import jakarta.ws.rs.NotAllowedException;
+import jakarta.ws.rs.NotFoundException;
+import jakarta.ws.rs.NotSupportedException;
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.ext.ExceptionMapper;
+import jakarta.ws.rs.ext.Provider;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+@Provider
+public class GlobalExceptionHandler implements ExceptionMapper {
+
+ @Inject
+ TraceIdContext traceIdContext;
+
+ @Inject
+ StatusDescriptionResolver statusDescriptionResolver;
+
+ @Override
+ public Response toResponse(Throwable exception) {
+ final var requestId = traceIdContext.getTraceId();
+ ErrorCode resolvedCode;
+
+ var exceptionClassName = exception.getClass().getName();
+
+ if (exception instanceof BanBackendException bbe) {
+ resolvedCode = statusDescriptionResolver.getCode(bbe.getBackendCode());
+ if (resolvedCode == null) {
+ resolvedCode = statusDescriptionResolver.getCode("500");
+ }
+ } else if (exceptionClassName.equals(GatewayServiceException.class.getName()) ||
+ exceptionClassName.equals(application.exception.ServiceCommunicationException.class.getName())) {
+ resolvedCode = statusDescriptionResolver.getCode("503-URL");
+
+ } else if (exception instanceof NotSupportedException) {
+ resolvedCode = statusDescriptionResolver.getCode("VDE01");
+
+ } else if (exception instanceof NotFoundException) {
+ resolvedCode = statusDescriptionResolver.getCode("ERR-404");
+
+ } else if (exception instanceof NotAllowedException) {
+ resolvedCode = statusDescriptionResolver.getCode("405");
+
+ } else if (isClientError(exception)) {
+ resolvedCode = statusDescriptionResolver.getCode("ERR-400");
+
+ } else {
+ // Default para cualquier otra excepción no controlada
+ log.error("[ERROR: {}] Error de backend no manejado que llego al GlobalExceptionHandler", requestId, exception);
+ resolvedCode = statusDescriptionResolver.getCode("500");
+ }
+
+ var statusResponse = domain.dto.response.StatusResponse.builder()
+ .status("error")
+ .statusCode(resolvedCode.statusCode())
+ .message(resolvedCode.description())
+ .traceId(requestId)
+ .build();
+
+ var response = new CustomerResponse(statusResponse);
+
+ int httpStatus = Integer.parseInt(resolvedCode.httpCode().split("-")[0]);
+
+ return Response.status(httpStatus)
+ .entity(response)
+ .build();
+ }
+
+ private boolean isClientError(Throwable throwable) {
+ return throwable instanceof BadRequestException ||
+ throwable instanceof com.fasterxml.jackson.core.JsonProcessingException ||
+ throwable instanceof NullPointerException;
+ }
+}
\ No newline at end of file
diff --git a/rec-evaluate-customer-product/src/main/java/infrastructure/filter/HeadersFilter.java b/rec-evaluate-customer-product/src/main/java/infrastructure/filter/HeadersFilter.java
new file mode 100644
index 0000000..d46bdb5
--- /dev/null
+++ b/rec-evaluate-customer-product/src/main/java/infrastructure/filter/HeadersFilter.java
@@ -0,0 +1,43 @@
+package infrastructure.filter;
+
+import com.banesco.common.infraestructure.helpers.LoggerHelper;
+import infrastructure.config.AppConfig;
+import infrastructure.context.TraceIdContext;
+import jakarta.annotation.Priority;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+import jakarta.ws.rs.container.ContainerRequestContext;
+import jakarta.ws.rs.container.ContainerRequestFilter;
+import jakarta.ws.rs.ext.Provider;
+import lombok.extern.slf4j.Slf4j;
+
+import java.util.stream.Collectors;
+
+
+@Slf4j
+@Provider
+@Priority(200)
+@ApplicationScoped
+public class HeadersFilter implements ContainerRequestFilter {
+
+ @Inject
+ AppConfig appConfig;
+
+ @Inject
+ TraceIdContext traceIdContext;
+
+ @Override
+ public void filter(ContainerRequestContext requestContext) {
+ var requestId = traceIdContext.getTraceId();
+
+ var multiMappedHeaders = requestContext.getHeaders();
+
+ var headersMap = multiMappedHeaders.keySet().stream()
+ .collect(Collectors.toMap(
+ key -> key,
+ multiMappedHeaders::getFirst
+ ));
+
+ log.info(LoggerHelper.buildInfoPrivateRequest(requestId, LoggerHelper.formatHeaders(headersMap)));
+ }
+}
\ No newline at end of file
diff --git a/rec-evaluate-customer-product/src/main/java/infrastructure/filter/TraceIdFilter.java b/rec-evaluate-customer-product/src/main/java/infrastructure/filter/TraceIdFilter.java
new file mode 100644
index 0000000..1b7fd8f
--- /dev/null
+++ b/rec-evaluate-customer-product/src/main/java/infrastructure/filter/TraceIdFilter.java
@@ -0,0 +1,30 @@
+package infrastructure.filter;
+
+import com.banesco.common.infraestructure.helpers.RequestHelper;
+import infrastructure.context.TraceIdContext;
+import jakarta.annotation.Priority;
+import jakarta.inject.Inject;
+import jakarta.ws.rs.container.ContainerRequestContext;
+import jakarta.ws.rs.container.ContainerRequestFilter;
+import jakarta.ws.rs.container.PreMatching;
+import jakarta.ws.rs.ext.Provider;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+@Provider
+@PreMatching // run before any other filter
+@Priority(100)
+public class TraceIdFilter implements ContainerRequestFilter {
+
+ @Inject
+ TraceIdContext traceIdContext;
+
+ @Override
+ public void filter(ContainerRequestContext requestContext) {
+ var headerId = requestContext.getHeaderString("requestId");
+
+ var finalTraceId = (headerId != null && !headerId.isBlank()) ? headerId : RequestHelper.randomAlphanumeric(20);
+
+ traceIdContext.setTraceId(finalTraceId);
+ }
+}
\ No newline at end of file
diff --git a/rec-evaluate-customer-product/src/main/java/infrastructure/healthcheck/ApplicationLivenessCheck.java b/rec-evaluate-customer-product/src/main/java/infrastructure/healthcheck/ApplicationLivenessCheck.java
new file mode 100644
index 0000000..7016427
--- /dev/null
+++ b/rec-evaluate-customer-product/src/main/java/infrastructure/healthcheck/ApplicationLivenessCheck.java
@@ -0,0 +1,36 @@
+package infrastructure.healthcheck;
+
+import jakarta.enterprise.context.ApplicationScoped;
+import lombok.extern.slf4j.Slf4j;
+import org.eclipse.microprofile.config.inject.ConfigProperty;
+import org.eclipse.microprofile.health.HealthCheck;
+import org.eclipse.microprofile.health.HealthCheckResponse;
+import org.eclipse.microprofile.health.Liveness;
+
+@Slf4j
+@Liveness
+@ApplicationScoped
+public class ApplicationLivenessCheck implements HealthCheck {
+
+ private final String applicationName;
+ private final String applicationVersion;
+
+
+ public ApplicationLivenessCheck(
+ @ConfigProperty(name = "quarkus.application.name", defaultValue = "api-application") String applicationName,
+ @ConfigProperty(name = "quarkus.application.version", defaultValue = "unknown") String applicationVersion) {
+ this.applicationName = applicationName;
+ this.applicationVersion = applicationVersion;
+ }
+
+ @Override
+ public HealthCheckResponse call() {
+ log.debug("Ejecutando liveness health check: {}", System.currentTimeMillis());
+ return HealthCheckResponse.named("API")
+ .up()
+ .withData("application", applicationName)
+ .withData("version", applicationVersion)
+ .withData("timestamp", System.currentTimeMillis())
+ .build();
+ }
+}
\ No newline at end of file
diff --git a/rec-evaluate-customer-product/src/main/java/infrastructure/healthcheck/ApplicationStartupCheck.java b/rec-evaluate-customer-product/src/main/java/infrastructure/healthcheck/ApplicationStartupCheck.java
new file mode 100644
index 0000000..2a0636b
--- /dev/null
+++ b/rec-evaluate-customer-product/src/main/java/infrastructure/healthcheck/ApplicationStartupCheck.java
@@ -0,0 +1,38 @@
+package infrastructure.healthcheck;
+
+import jakarta.enterprise.context.ApplicationScoped;
+import lombok.extern.slf4j.Slf4j;
+import org.eclipse.microprofile.config.inject.ConfigProperty;
+import org.eclipse.microprofile.health.HealthCheck;
+import org.eclipse.microprofile.health.HealthCheckResponse;
+import org.eclipse.microprofile.health.Startup;
+
+/**
+ * Startup Check: Verifica que el contexto de la aplicación (CDI)
+ * se ha inicializado correctamente y las configuraciones básicas están disponibles.
+ * Este chequeo le dice a Kubernetes "El arranque interno finalizó",
+ * permitiendo que los chequeos de Liveness y Readiness tomen el control.
+ */
+@Slf4j
+@Startup
+@ApplicationScoped
+public class ApplicationStartupCheck implements HealthCheck {
+
+ private final String applicationName;
+
+ public ApplicationStartupCheck(
+ @ConfigProperty(name = "quarkus.application.name", defaultValue = "api-application") String applicationName) {
+ this.applicationName = applicationName;
+ }
+
+ @Override
+ public HealthCheckResponse call() {
+ log.info("Ejecutando startup health check para {}...", applicationName);
+
+ return HealthCheckResponse.named("Application Startup")
+ .up()
+ .withData("application", applicationName)
+ .withData("status", "Contexto de aplicación (CDI) iniciado.")
+ .build();
+ }
+}
\ No newline at end of file
diff --git a/rec-evaluate-customer-product/src/main/java/infrastructure/interceptor/LoggingFilter.java b/rec-evaluate-customer-product/src/main/java/infrastructure/interceptor/LoggingFilter.java
new file mode 100644
index 0000000..778a2f7
--- /dev/null
+++ b/rec-evaluate-customer-product/src/main/java/infrastructure/interceptor/LoggingFilter.java
@@ -0,0 +1,31 @@
+package infrastructure.interceptor;
+
+import infrastructure.context.TraceIdContext;
+import jakarta.ws.rs.client.ClientRequestContext;
+import jakarta.ws.rs.client.ClientRequestFilter;
+import jakarta.ws.rs.ext.Provider;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+
+@Provider
+public class LoggingFilter implements ClientRequestFilter {
+ private static final Logger LOG = LoggerFactory.getLogger(LoggingFilter.class);
+
+ private final TraceIdContext traceIdContext;
+
+ public LoggingFilter(TraceIdContext traceIdContext) {
+ this.traceIdContext = traceIdContext;
+ }
+
+ @Override
+ public void filter(ClientRequestContext requestContext) throws IOException {
+ var requestId = traceIdContext.getTraceId();
+
+ LOG.info("[{}] Peticion REST a: {} {}",
+ requestId,
+ requestContext.getMethod(),
+ requestContext.getUri());
+ }
+}
\ No newline at end of file
diff --git a/rec-evaluate-customer-product/src/main/java/infrastructure/service/StatusDescriptionResolver.java b/rec-evaluate-customer-product/src/main/java/infrastructure/service/StatusDescriptionResolver.java
new file mode 100644
index 0000000..15d0606
--- /dev/null
+++ b/rec-evaluate-customer-product/src/main/java/infrastructure/service/StatusDescriptionResolver.java
@@ -0,0 +1,45 @@
+package infrastructure.service;
+
+import domain.model.ErrorCode;
+import infrastructure.config.AppConfig;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+
+import java.util.Optional;
+
+@ApplicationScoped
+public class StatusDescriptionResolver {
+
+ private final AppConfig appConfig;
+ private final ErrorCode genericError;
+
+ @Inject
+ public StatusDescriptionResolver(AppConfig appConfig) {
+ this.appConfig = appConfig;
+ // Definimos un código de error genérico por si no se encuentra el que buscamos
+ this.genericError = new ErrorCode("409", "CONFLICT", "409", "CONFLICT", "error");
+ }
+
+ /**
+ * Busca un ErrorCode por su backend_code.
+ * Si no lo encuentra, devuelve un ErrorCode genérico de "Error Interno".
+ *
+ * @param backendCode El código a buscar (ej. "200", "VDE01", "404").
+ * @return El ErrorCode encontrado o uno genérico. Nunca es nulo.
+ */
+ public ErrorCode getCode(String backendCode) {
+ // Usamos Optional.ofNullable para manejar el caso en que el código no exista en el mapa
+ return Optional.ofNullable(appConfig.getErrorCodeMap().get(backendCode))
+ .orElse(genericError);
+ }
+
+ /**
+ * Alternativa manejar el caso "no encontrado" manualmente.
+ *
+ * @param backendCode El código a buscar.
+ * @return Un Optional que estará vacío si no se encuentra el código.
+ */
+ public Optional findCode(String backendCode) {
+ return Optional.ofNullable(appConfig.getErrorCodeMap().get(backendCode));
+ }
+}
\ No newline at end of file
diff --git a/rec-evaluate-customer-product/src/main/resources/META-INF/resources/img/banesco-logo-favicon.png b/rec-evaluate-customer-product/src/main/resources/META-INF/resources/img/banesco-logo-favicon.png
new file mode 100644
index 0000000..9e7463a
Binary files /dev/null and b/rec-evaluate-customer-product/src/main/resources/META-INF/resources/img/banesco-logo-favicon.png differ
diff --git a/rec-evaluate-customer-product/src/main/resources/META-INF/resources/img/banesco-logo.png b/rec-evaluate-customer-product/src/main/resources/META-INF/resources/img/banesco-logo.png
new file mode 100644
index 0000000..8e1b25d
Binary files /dev/null and b/rec-evaluate-customer-product/src/main/resources/META-INF/resources/img/banesco-logo.png differ
diff --git a/rec-evaluate-customer-product/src/main/resources/META-INF/resources/index.html b/rec-evaluate-customer-product/src/main/resources/META-INF/resources/index.html
new file mode 100644
index 0000000..6e15ce1
--- /dev/null
+++ b/rec-evaluate-customer-product/src/main/resources/META-INF/resources/index.html
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+ API Banesco - rec-evaluate-customer-product
+
+
+
+
+
+
\ No newline at end of file
diff --git a/rec-evaluate-customer-product/src/main/resources/META-INF/resources/index.html~ b/rec-evaluate-customer-product/src/main/resources/META-INF/resources/index.html~
new file mode 100644
index 0000000..03837dd
--- /dev/null
+++ b/rec-evaluate-customer-product/src/main/resources/META-INF/resources/index.html~
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+ API Banesco - Pública Consulta de Producto BIAN
+
+
+
+
+
+
\ No newline at end of file
diff --git a/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/request/customer-product-search-success.json b/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/request/customer-product-search-success.json
new file mode 100644
index 0000000..16f50a6
--- /dev/null
+++ b/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/request/customer-product-search-success.json
@@ -0,0 +1,15 @@
+{
+ "customerPositionState": {
+ "customerReference": {
+ "customerIdType": "J",
+ "customerId": "000004293"
+ }
+ },
+ "device": {
+ "deviceType": "Mobile",
+ "deviceDescription": "Xiaomi Note 11 PRO",
+ "deviceIp": "127.0.0.1",
+ "deviceSessionReference": "prueba"
+ },
+ "productReferenceSearch": "DEBITCARD"
+}
\ No newline at end of file
diff --git a/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/request/customer-product-search-wihtout-result.json b/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/request/customer-product-search-wihtout-result.json
new file mode 100644
index 0000000..a3ebb8b
--- /dev/null
+++ b/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/request/customer-product-search-wihtout-result.json
@@ -0,0 +1,15 @@
+{
+ "customerPositionState": {
+ "customerReference": {
+ "customerIdType": "V",
+ "customerId": "8678988"
+ }
+ },
+ "device": {
+ "deviceType": "Mobile",
+ "deviceDescription": "Xiaomi Note 11 PRO",
+ "deviceIp": "127.0.0.1",
+ "deviceSessionReference": "prueba"
+ },
+ "productReferenceSearch": "DEBITCARD"
+}
\ No newline at end of file
diff --git a/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/request/customer-product-success-without-token.json b/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/request/customer-product-success-without-token.json
new file mode 100644
index 0000000..28bb7ef
--- /dev/null
+++ b/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/request/customer-product-success-without-token.json
@@ -0,0 +1,14 @@
+{
+ "customerPositionState": {
+ "customerReference": {
+ "customerIdType": "J",
+ "customerId": "000004293"
+ }
+ },
+ "device": {
+ "deviceType": "Mobile",
+ "deviceDescription": "Xiaomi Note 11 PRO",
+ "deviceIp": "127.0.0.1",
+ "deviceSessionReference": "prueba"
+ }
+}
\ No newline at end of file
diff --git a/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/request/customer-product-success.json b/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/request/customer-product-success.json
new file mode 100644
index 0000000..71a4f26
--- /dev/null
+++ b/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/request/customer-product-success.json
@@ -0,0 +1,17 @@
+{
+ "customerPositionState": {
+ "customerReference": {
+ "customerIdType": "J",
+ "customerId": "000004293"
+ }
+ },
+ "device": {
+ "deviceType": "Mobile",
+ "deviceDescription": "Xiaomi Note 11 PRO",
+ "deviceIp": "127.0.0.1",
+ "deviceSessionReference": "prueba"
+ },
+ "tokenAssignmentInstance": {
+ "tokenIdentificationCode": ""
+ }
+}
\ No newline at end of file
diff --git a/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/request/customer-product-token-success.json b/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/request/customer-product-token-success.json
new file mode 100644
index 0000000..a90178b
--- /dev/null
+++ b/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/request/customer-product-token-success.json
@@ -0,0 +1,17 @@
+{
+ "customerPositionState": {
+ "customerReference": {
+ "customerIdType": "J",
+ "customerId": "000004293"
+ }
+ },
+ "device": {
+ "deviceType": "Mobile",
+ "deviceDescription": "Xiaomi Note 11 PRO",
+ "deviceIp": "127.0.0.1",
+ "deviceSessionReference": "prueba"
+ },
+ "tokenAssignmentInstance": {
+ "tokenIdentificationCode": "12345678"
+ }
+}
\ No newline at end of file
diff --git a/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/app-config.json b/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/app-config.json
new file mode 100644
index 0000000..7f92dbf
--- /dev/null
+++ b/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/app-config.json
@@ -0,0 +1,85 @@
+{
+ "appName": "BANCA_MOVIL",
+ "apiName": "bus-evaluate-customer-product",
+ "type": "external",
+ "event": "CPROD",
+ "encryptionData": true,
+ "configuration": {
+ "validationRules": {
+ "serviceStatus": false,
+ "getUserLogin": false,
+ "encodeValue": false,
+ "validateOtp": false,
+ "validateProfile": false
+ },
+ "aliases": {
+ "V": "V",
+ "E": "V",
+ "J": "J",
+ "G": "J",
+ "C": "J"
+ },
+ "configuration": {
+ "all": {
+ "cacheable": true,
+ "bankCode": "01",
+ "currencyCode": "BS",
+ "status": "A",
+ "statusMe": "",
+ "productCv": "",
+ "product": "",
+ "channel": "",
+ "service": "",
+ "operator": "",
+ "phone": "",
+ "affiliationStatus": "",
+ "counterpartyAccount": "",
+ "holder": "",
+ "codProduct": "",
+ "statusOperation": "",
+ "statusRestriction": "",
+ "statusOperationRestriction": ""
+ },
+ "BANCA_MOVIL_V": {
+ "cacheable": true,
+ "bankCode": "",
+ "currencyCode": "",
+ "status": "ACTBSUSD",
+ "statusMe": "",
+ "productCv": "CVFL",
+ "product": "",
+ "channel": "",
+ "service": "",
+ "operator": "",
+ "phone": "",
+ "affiliationStatus": "",
+ "counterpartyAccount": "",
+ "holder": "TIT",
+ "codProduct": "",
+ "statusOperation": "",
+ "statusRestriction": "",
+ "statusOperationRestriction": ""
+ },
+ "BANCA_MOVIL_J": {
+ "cacheable": true,
+ "bankCode": "",
+ "currencyCode": "",
+ "status": "ACTBSUSD",
+ "statusMe": "",
+ "productCv": "CVFL",
+ "product": "",
+ "channel": "APP",
+ "service": "P2P",
+ "operator": "",
+ "phone": "",
+ "affiliationStatus": "A",
+ "counterpartyAccount": "PAG",
+ "holder": "TIT",
+ "codProduct": "",
+ "statusOperation": "",
+ "statusRestriction": "",
+ "statusOperationRestriction": ""
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/config-not-found.json b/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/config-not-found.json
new file mode 100644
index 0000000..1b26bf5
--- /dev/null
+++ b/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/config-not-found.json
@@ -0,0 +1,8 @@
+{
+ "statusResponse": {
+ "status": "error",
+ "statusCode": "400",
+ "message": "Configuracion no encontrada",
+ "traceId": "YxwjZEbfHuYc3wG4jDdN"
+ }
+}
\ No newline at end of file
diff --git a/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/conflict.json b/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/conflict.json
new file mode 100644
index 0000000..d42706e
--- /dev/null
+++ b/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/conflict.json
@@ -0,0 +1,8 @@
+{
+ "statusResponse": {
+ "status": "error",
+ "statusCode": "409",
+ "message": "CONFLICT",
+ "traceId": "b81LdZyGXYCCcLvyaQdz"
+ }
+}
\ No newline at end of file
diff --git a/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/customer-product-response-unmasked.json b/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/customer-product-response-unmasked.json
new file mode 100644
index 0000000..abe8d5d
--- /dev/null
+++ b/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/customer-product-response-unmasked.json
@@ -0,0 +1,25 @@
+{
+ "statusResponse": {
+ "status": "ok",
+ "statusCode": "200",
+ "message": "Operacion Exitosa",
+ "traceId": "U2VzaLNEEPeX9ODZiRnC"
+ },
+ "customerPositionState": {
+ "record": 2,
+ "bankingProducts": [
+ {
+ "productNumber": "01340552220001000249",
+ "productType": "DDA",
+ "balance": 0.84,
+ "currency": "BS "
+ },
+ {
+ "productNumber": "01341740730001281663",
+ "productType": "DDA",
+ "balance": 0.00,
+ "currency": "USD"
+ }
+ ]
+ }
+}
\ No newline at end of file
diff --git a/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/customer-product-response.json b/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/customer-product-response.json
new file mode 100644
index 0000000..29e755e
--- /dev/null
+++ b/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/customer-product-response.json
@@ -0,0 +1,25 @@
+{
+ "statusResponse": {
+ "status": "ok",
+ "statusCode": "200",
+ "message": "Operacion Exitosa",
+ "traceId": "SKeIGjD1lWmJD992LfKZ"
+ },
+ "customerPositionState": {
+ "record": 2,
+ "bankingProducts": [
+ {
+ "productNumber": "0134************0249",
+ "productType": "DDA",
+ "balance": 0.84,
+ "currency": "BS "
+ },
+ {
+ "productNumber": "********************",
+ "productType": "DDA",
+ "balance": 0.00,
+ "currency": "USD"
+ }
+ ]
+ }
+}
\ No newline at end of file
diff --git a/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/customer-product-rest-client-error.json b/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/customer-product-rest-client-error.json
new file mode 100644
index 0000000..881be26
--- /dev/null
+++ b/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/customer-product-rest-client-error.json
@@ -0,0 +1,8 @@
+{
+ "statusResponse": {
+ "status": "error",
+ "statusCode": "503",
+ "message": "Incidencia con la resolución de CI",
+ "traceId": "qV3cYzb5SNIGKbozmF6T"
+ }
+}
\ No newline at end of file
diff --git a/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/customer-product-search-response.json b/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/customer-product-search-response.json
new file mode 100644
index 0000000..51a8f6b
--- /dev/null
+++ b/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/customer-product-search-response.json
@@ -0,0 +1,17 @@
+{
+ "statusResponse": {
+ "status": "ok",
+ "statusCode": "200",
+ "message": "Operacion Exitosa",
+ "traceId": "fJfPYbDnweXTRrcQHyN0"
+ },
+ "customerPositionState": {
+ "record": 1,
+ "bankingProducts": [
+ {
+ "productNumber": "****-****-****-8514",
+ "productType": "TDB"
+ }
+ ]
+ }
+}
\ No newline at end of file
diff --git a/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/customer-product-without-results.json b/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/customer-product-without-results.json
new file mode 100644
index 0000000..39740a3
--- /dev/null
+++ b/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/customer-product-without-results.json
@@ -0,0 +1,12 @@
+{
+ "statusResponse": {
+ "status": "ok",
+ "statusCode": "200",
+ "message": "Cliente sin productos",
+ "traceId": "BOBfbbCpAR3ElrbEPFMl"
+ },
+ "customerPositionState": {
+ "record": 0,
+ "bankingProducts": []
+ }
+}
\ No newline at end of file
diff --git a/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/internal-use.json b/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/internal-use.json
new file mode 100644
index 0000000..70149a3
--- /dev/null
+++ b/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/internal-use.json
@@ -0,0 +1,8 @@
+{
+ "statusResponse": {
+ "status": "error",
+ "statusCode": "500",
+ "message": "Uso interno",
+ "traceId": "oHsLZM4xSVVC8ZoU6yDF"
+ }
+}
\ No newline at end of file
diff --git a/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/invalid-profile.json b/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/invalid-profile.json
new file mode 100644
index 0000000..cda132e
--- /dev/null
+++ b/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/invalid-profile.json
@@ -0,0 +1,8 @@
+{
+ "statusResponse": {
+ "status": "error",
+ "statusCode": "401",
+ "message": "VRN01 - Fallo en la validación del perfil del cliente",
+ "traceId": "gLoRYYHkZONFOcntMvfn"
+ }
+}
\ No newline at end of file
diff --git a/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/invalid-token.json b/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/invalid-token.json
new file mode 100644
index 0000000..cf5098d
--- /dev/null
+++ b/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/invalid-token.json
@@ -0,0 +1,8 @@
+{
+ "statusResponse": {
+ "status": "error",
+ "statusCode": "VRN07",
+ "message": "VRN07 – Codigo de OTP Invalido",
+ "traceId": "xJPt6yoN6LA8QJxJpGC6"
+ }
+}
\ No newline at end of file
diff --git a/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/service-unavailable.json b/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/service-unavailable.json
new file mode 100644
index 0000000..af8ed48
--- /dev/null
+++ b/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/service-unavailable.json
@@ -0,0 +1,8 @@
+{
+ "statusResponse": {
+ "status": "error",
+ "statusCode": "VRN04",
+ "message": "Servicio en horario de mantenimiento",
+ "traceId": "xif7wH0xUbIB3e6cHcr3"
+ }
+}
\ No newline at end of file
diff --git a/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/user-bol-not-exist.json b/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/user-bol-not-exist.json
new file mode 100644
index 0000000..331e8b7
--- /dev/null
+++ b/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/user-bol-not-exist.json
@@ -0,0 +1,8 @@
+{
+ "statusResponse": {
+ "status": "error",
+ "statusCode": "AUTH02",
+ "message": "Usuario no existe",
+ "traceId": "uAWXR6yNPa6IlQ51FOAU"
+ }
+}
\ No newline at end of file
diff --git a/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/user-not-exist.json b/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/user-not-exist.json
new file mode 100644
index 0000000..ecb3a78
--- /dev/null
+++ b/rec-evaluate-customer-product/src/main/resources/META-INF/resources/openapi-examples/response/user-not-exist.json
@@ -0,0 +1,8 @@
+{
+ "statusResponse": {
+ "status": "error",
+ "statusCode": "AUTH01",
+ "message": "Usuario no existe",
+ "traceId": "jptc0JT9mh8ioDhZeZfa"
+ }
+}
\ No newline at end of file
diff --git a/rec-evaluate-customer-product/src/main/resources/application.yml b/rec-evaluate-customer-product/src/main/resources/application.yml
new file mode 100644
index 0000000..6e9a98b
--- /dev/null
+++ b/rec-evaluate-customer-product/src/main/resources/application.yml
@@ -0,0 +1,149 @@
+quarkus:
+ log:
+ level: INFO
+ http:
+ port: 8080
+ idle-timeout: 30s
+ non-application-root-path: /actuator
+ application:
+ name: rec-evaluate-customer-product
+ version: 1.0.0
+ smallrye-openapi:
+ path: /openapi
+ enable: true
+ info-title: rec-evaluate-customer-product
+ info-version: 1.0.0
+ info-description: 'API Rec evaluate customer products'
+ swagger-ui:
+ path: /swagger-ui
+ always-include: true
+ health:
+ enabled: false
+ rest-client:
+ customer-accounts:
+ url: http://bus-evaluate-customer-product-route-apis-banesco-dev.apps.desplakur3.desintra.banesco.com/customer/position/evaluate
+ scope: jakarta.inject.Singleton
+app:
+ appId: REC_EVALUATE_CUSTOMER_PRODUCT
+ response-json: |
+ [
+ {
+ "backend_code": "default",
+ "http_code": "409",
+ "status_code": "CONFLICT",
+ "description": "Conflicto",
+ "status": "error"
+ },
+ {
+ "backend_code": "VRN_CONFIG_NOT_FOUND",
+ "http_code": "400",
+ "status_code": "400",
+ "description": "Configuracion no encontrada",
+ "status": "error"
+ },
+ {
+ "backend_code": "405",
+ "http_code": "405",
+ "status_code": "405",
+ "description": "Metodo no permitido",
+ "status": "error"
+ },
+ {
+ "backend_code": "200",
+ "http_code": "200",
+ "status_code": "200",
+ "description": "Operacion exitosa",
+ "status": "ok"
+ },
+ {
+ "backend_code": "204",
+ "http_code": "200",
+ "status_code": "204",
+ "description": "Cliente sin productos",
+ "status": "ok"
+ },
+ {
+ "backend_code": "503-URL",
+ "http_code": "503",
+ "status_code": "503",
+ "description": "Incidencia con la resolución de CI",
+ "status": "error"
+ },
+ {
+ "backend_code": "500",
+ "http_code": "503",
+ "status_code": "500",
+ "description": "Uso interno",
+ "status": "error"
+ },
+ {
+ "backend_code": "406",
+ "http_code": "406",
+ "status_code": "406",
+ "description": "Respuesta no aceptable",
+ "status": "error"
+ },
+ {
+ "backend_code": "VDE01",
+ "http_code": "400",
+ "status_code": "VDE01",
+ "description": "Error en dato de entrada obligatorio",
+ "status": "error"
+ },
+ {
+ "backend_code": "VDE02",
+ "http_code": "400",
+ "status_code": "VDE02",
+ "description": "Error en valor permitido para campo",
+ "status": "error"
+ },
+ {
+ "backend_code": "VDE03",
+ "http_code": "400",
+ "status_code": "VDE03",
+ "description": "Longitud no permitida para campo",
+ "status": "error"
+ },
+ {
+ "backend_code": "VRN01",
+ "http_code": "401",
+ "status_code": "401",
+ "description": "VRN01 - Fallo en la validación del perfil del cliente",
+ "status": "error"
+ },
+ {
+ "backend_code": "VRN04",
+ "http_code": "503",
+ "status_code": "VRN04",
+ "description": "Servicio en horario de mantenimiento",
+ "status": "error"
+ },
+ {
+ "backend_code": "VRN07",
+ "http_code": "401",
+ "status_code": "VRN07",
+ "description": "VRN07 – Codigo de OTP Invalido",
+ "status": "error"
+ },
+ {
+ "backend_code": "409",
+ "http_code": "409",
+ "status_code": "CONFLICT",
+ "description": "Conflicto",
+ "status": "error"
+ },
+ {
+ "backend_code": "VRN19",
+ "http_code": "401",
+ "status_code": "AUTH01",
+ "description": "Usuario no existe",
+ "status": "error"
+ },
+ {
+ "backend_code": "VRN20",
+ "http_code": "401",
+ "status_code": "AUTH02",
+ "description": "Usuario no existe",
+ "status": "error"
+ }
+ ]
diff --git a/rec-evaluate-customer-product/src/main/resources/configmap.yaml b/rec-evaluate-customer-product/src/main/resources/configmap.yaml
new file mode 100644
index 0000000..1b4b862
--- /dev/null
+++ b/rec-evaluate-customer-product/src/main/resources/configmap.yaml
@@ -0,0 +1,173 @@
+# ===================================================================
+# CONFIGURACIÓN GENERAL DE QUARKUS
+# ===================================================================
+
+# Nombre de la aplicación.
+quarkus.application.name: rec-evaluate-customer-product
+# Versión de la aplicación.
+quarkus.application.version: 1.0.0
+# Puerto HTTP en el que se ejecutará la aplicación.
+quarkus.http.port: 8080
+# Tiempo máximo de inactividad antes de que el servidor cierre una conexión HTTP.
+quarkus.http.idle-timeout: 30s
+# Ruta raíz para los endpoints que no son de la aplicación (ej. /health, /metrics).
+quarkus.http.non-application-root-path: /actuator
+
+# ===================================================================
+# DOCUMENTACIÓN DE API (OPENAPI & SWAGGER UI)
+# ===================================================================
+
+# Habilita la generación de la especificación OpenAPI.
+quarkus.smallrye-openapi.enable: true
+# Ruta donde se expondrá la especificación OpenAPI (formato JSON/YAML).
+quarkus.smallrye-openapi.path: /openapi
+# Título que se mostrará en la documentación de la API.
+quarkus.smallrye-openapi.info-title: rec-evaluate-customer-product
+# Versión que se mostrará en la documentación de la API.
+quarkus.smallrye-openapi.info-version: 1.0.0
+# Descripción general de la API para la documentación.
+quarkus.smallrye-openapi.info-description: API Rec evaluate customer accounts
+
+# Habilita la interfaz de usuario de Swagger.
+quarkus.swagger-ui.always-include: true
+# Ruta donde estará disponible la interfaz de Swagger UI.
+quarkus.swagger-ui.path: /swagger-ui
+
+# ===================================================================
+# CONFIGURACIÓN DE CLIENTES REST
+# ===================================================================
+
+# --- Cliente para obtener las cuentas de un cliente desde DB2.
+quarkus.rest-client.customer-accounts.url: http://bus-evaluate-customer-product-route-apis-banesco-dev.apps.desplakur3.desintra.banesco.com/customer/position/evaluate
+quarkus.rest-client.customer-accounts.scope: jakarta.inject.Singleton
+
+# ===================================================================
+# PROPIEDADES ESPECÍFICAS DE LA APLICACIÓN
+# ===================================================================
+
+# Identificador único de la aplicación.
+app.appId: REC_EVALUATE_CUSTOMER_PRODUCTS
+
+# --- Mapeo centralizado de códigos de respuesta de la aplicación ---
+app.response-json: |
+ [
+ {
+ "backend_code": "default",
+ "http_code": "409",
+ "status_code": "CONFLICT",
+ "description": "Conflicto",
+ "status": "error"
+ },
+ {
+ "backend_code": "VRN_CONFIG_NOT_FOUND",
+ "http_code": "400",
+ "status_code": "400",
+ "description": "Configuracion no encontrada",
+ "status": "error"
+ },
+ {
+ "backend_code": "405",
+ "http_code": "405",
+ "status_code": "405",
+ "description": "Metodo no permitido",
+ "status": "error"
+ },
+ {
+ "backend_code": "200",
+ "http_code": "200",
+ "status_code": "200",
+ "description": "Operacion exitosa",
+ "status": "ok"
+ },
+ {
+ "backend_code": "204",
+ "http_code": "200",
+ "status_code": "204",
+ "description": "Cliente sin productos",
+ "status": "ok"
+ },
+ {
+ "backend_code": "503-URL",
+ "http_code": "503",
+ "status_code": "503",
+ "description": "Incidencia con la resolución de CI",
+ "status": "error"
+ },
+ {
+ "backend_code": "500",
+ "http_code": "503",
+ "status_code": "500",
+ "description": "Uso interno",
+ "status": "error"
+ },
+ {
+ "backend_code": "406",
+ "http_code": "406",
+ "status_code": "406",
+ "description": "Respuesta no aceptable",
+ "status": "error"
+ },
+ {
+ "backend_code": "VDE01",
+ "http_code": "400",
+ "status_code": "VDE01",
+ "description": "Error en dato de entrada obligatorio",
+ "status": "error"
+ },
+ {
+ "backend_code": "VDE02",
+ "http_code": "400",
+ "status_code": "VDE02",
+ "description": "Error en valor permitido para campo",
+ "status": "error"
+ },
+ {
+ "backend_code": "VDE03",
+ "http_code": "400",
+ "status_code": "VDE03",
+ "description": "Longitud no permitida para campo",
+ "status": "error"
+ },
+ {
+ "backend_code": "VRN01",
+ "http_code": "401",
+ "status_code": "401",
+ "description": "VRN01 - Fallo en la validación del perfil del cliente",
+ "status": "error"
+ },
+ {
+ "backend_code": "VRN04",
+ "http_code": "503",
+ "status_code": "VRN04",
+ "description": "Servicio en horario de mantenimiento",
+ "status": "error"
+ },
+ {
+ "backend_code": "VRN07",
+ "http_code": "401",
+ "status_code": "VRN07",
+ "description": "VRN07 – Codigo de OTP Invalido",
+ "status": "error"
+ },
+ {
+ "backend_code": "409",
+ "http_code": "409",
+ "status_code": "CONFLICT",
+ "description": "Conflicto",
+ "status": "error"
+ },
+ {
+ "backend_code": "VRN19",
+ "http_code": "401",
+ "status_code": "AUTH01",
+ "description": "Usuario no existe",
+ "status": "error"
+ },
+ {
+ "backend_code": "VRN20",
+ "http_code": "401",
+ "status_code": "AUTH02",
+ "description": "Usuario no existe",
+ "status": "error"
+ }
+ ]
\ No newline at end of file