diff --git a/.gitignore b/.gitignore
index 67045665db..d545169072 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,104 +1,39 @@
-# Logs
-logs
+# ===== Java =====
+*.class
+*.log
+*.ctxt
+
+# ===== Maven =====
+target/
+!.mvn/wrapper/maven-wrapper.jar
+
+# ===== Spring Boot =====
+*.jar
+*.war
+*.ear
+*.iml
+
+# ===== IDEs =====
+.idea/
+.vscode/
+*.swp
+*.swo
+
+# ===== OS =====
+.DS_Store
+Thumbs.db
+
+# ===== Logs =====
+logs/
*.log
-npm-debug.log*
-yarn-debug.log*
-yarn-error.log*
-lerna-debug.log*
-
-# Diagnostic reports (https://nodejs.org/api/report.html)
-report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
-
-# Runtime data
-pids
-*.pid
-*.seed
-*.pid.lock
-
-# Directory for instrumented libs generated by jscoverage/JSCover
-lib-cov
-
-# Coverage directory used by tools like istanbul
-coverage
-*.lcov
-
-# nyc test coverage
-.nyc_output
-
-# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
-.grunt
-
-# Bower dependency directory (https://bower.io/)
-bower_components
-
-# node-waf configuration
-.lock-wscript
-
-# Compiled binary addons (https://nodejs.org/api/addons.html)
-build/Release
-
-# Dependency directories
-node_modules/
-jspm_packages/
-
-# TypeScript v1 declaration files
-typings/
-
-# TypeScript cache
-*.tsbuildinfo
-
-# Optional npm cache directory
-.npm
-
-# Optional eslint cache
-.eslintcache
-
-# Microbundle cache
-.rpt2_cache/
-.rts2_cache_cjs/
-.rts2_cache_es/
-.rts2_cache_umd/
-
-# Optional REPL history
-.node_repl_history
-
-# Output of 'npm pack'
-*.tgz
-
-# Yarn Integrity file
-.yarn-integrity
-# dotenv environment variables file
+# ===== Environment =====
.env
-.env.test
-
-# parcel-bundler cache (https://parceljs.org/)
-.cache
-
-# Next.js build output
-.next
-
-# Nuxt.js build / generate output
-.nuxt
-dist
-
-# Gatsby files
-.cache/
-# Comment in the public line in if your project uses Gatsby and *not* Next.js
-# https://nextjs.org/blog/next-9-1#public-directory-support
-# public
-
-# vuepress build output
-.vuepress/dist
-
-# Serverless directories
-.serverless/
-
-# FuseBox cache
-.fusebox/
+.env.local
-# DynamoDB Local files
-.dynamodb/
+# ===== Docker =====
+**/docker-data/
-# TernJS port file
-.tern-port
+# ===== Kafka (local) =====
+kafka-data/
+zookeeper-data/
diff --git a/Dockerfile.antifraud b/Dockerfile.antifraud
new file mode 100644
index 0000000000..8c10861a46
--- /dev/null
+++ b/Dockerfile.antifraud
@@ -0,0 +1,31 @@
+# ===== Etapa de build =====
+FROM maven:3.9.12-eclipse-temurin-21-alpine AS build
+
+WORKDIR /antifraud
+
+# Copiar POM directamente al WORKDIR
+COPY ./yape-financial/yape-antifraud/pom.xml ./pom.xml
+
+# Descargar dependencias sin compilar (aprovecha cache de Docker)
+RUN mvn dependency:go-offline -B
+
+# Copiar el código fuente
+COPY ./yape-financial/yape-antifraud/src ./src
+
+# Compilar microservicio (tests omitidos)
+RUN mvn clean package -DskipTests=true
+
+
+# ===== Etapa runtime =====
+FROM eclipse-temurin:21-jdk-alpine
+
+WORKDIR /antifraud
+
+# Copiar jar construido desde la etapa de build
+COPY --from=build /antifraud/target/yape-antifraud-1.0-SNAPSHOT.jar .
+
+# Exponer puerto del microservicio
+EXPOSE 8082
+
+# Ejecutar la aplicación
+ENTRYPOINT ["java", "-jar", "yape-antifraud-1.0-SNAPSHOT.jar"]
diff --git a/Dockerfile.transaction b/Dockerfile.transaction
new file mode 100644
index 0000000000..11c3ee072b
--- /dev/null
+++ b/Dockerfile.transaction
@@ -0,0 +1,27 @@
+# ===== Etapa de build =====
+FROM maven:3.9.12-eclipse-temurin-21-alpine AS build
+
+# Directorio de trabajo dentro del contenedor
+WORKDIR /transaction
+
+# Copiar POM y código fuente directamente al WORKDIR
+COPY ./yape-financial/yape-transaction/pom.xml ./pom.xml
+COPY ./yape-financial/yape-transaction/src ./src
+
+# Compilar el microservicio (skip tests para acelerar)
+RUN mvn clean package -DskipTests=true
+
+
+# ===== Etapa runtime =====
+FROM eclipse-temurin:21-jdk
+
+WORKDIR /transaction
+
+# Copiar jar construido desde la etapa de build
+COPY --from=build /transaction/target/yape-transaction-1.0-SNAPSHOT.jar .
+
+# Exponer puerto del microservicio
+EXPOSE 8081
+
+# Ejecutar la aplicación
+ENTRYPOINT ["java", "-jar", "yape-transaction-1.0-SNAPSHOT.jar"]
diff --git a/README.md b/README.md
index b067a71026..fbd955ec07 100644
--- a/README.md
+++ b/README.md
@@ -1,82 +1,124 @@
-# Yape Code Challenge :rocket:
-Our code challenge will let you marvel us with your Jedi coding skills :smile:.
+## APLICACIÓN DE TRANSACCIONES: ```YAPE-FINANCIAL```
+Aplicación de microservicios para manejo de transacciones financieras con validación antifraude y comunicación asíncrona mediante Kafka.
+
+### ANTIFRAUDE
+
+- Al crear una transacción, se publica un evento en Kafka.
+- Un consumidor valida reglas antifraude.
+- La transacción puede ser aprobada o rechazada según el monto configurado.
+
+### REQUISITOS
+
+- Docker >= 27.x
+- Docker Compose >= 2.x
+
+### COMPONENTES
+
+- JAVA: 21
+- SPRING BOOT: 3.2.5
+- DOCKER
+- KAFKA
+- POSTGRESQL
+
+### INSTALACIÓN
+
+- Clonar el repositorio:
+
+```bash
+ git clone https://github.com/evercarlos/app-nodejs-codechallenge.git
+ ```
+ - Entrar a la rama del proyecto:
+ ```bash
+ git checkout java-codechallenge
+ ```
+
+- Construir y levantar los contenedores con Docker Compose
+
+ ```bash
+ docker-compose up --build -d
+ ```
+
+### CONFIGURACIÓN
+
+Las variables de entorno principales se encuentran en el archivo `docker-compose.yml`:
+- Base de datos PostgreSQL
+- Kafka
+- Puertos de exposición
+
+### URL DEL MICROSERVICIO Y DOCUMENTACIÓN SWAGGER
+- http://localhost:8081
+- Swagger UI: http://localhost:8081/swagger-ui.html
+
+### ARQUITECTURA
+
+La aplicación sigue una arquitectura hexagonal (Ports & Adapters), separando:
+- Aplication
+- Domain
+- Infraestructure (REST, Kafka, persistencia)
+
+La validación antifraude se realiza de forma desacoplada mediante eventos publicados en Kafka.
+
+### ENDPOINTS DISPONIBLES
+1. Crea una transacción
+
+ ```bash
+curl --location 'http://localhost:8081/api/v1/transaction' \
+--header 'Content-Type: application/json' \
+--data '{
+ "accountExternalIdDebit": "550e8400-e29b-41d4-a716-446655440000",
+ "accountExternalIdCredit": "660e8400-e29b-41d4-a716-446655440111",
+ "tranferTypeId": 1,
+ "value": 10001
+ } '
+ ```
+
+ 2. Lista transacción con paginación
+
+ ```bash
+curl --location 'http://localhost:8081/api/v1/transaction/withPagination?number=1&size=10' \
+--header 'Content-Type: application/json' \
+--data ''
+ ```
+ 3. Lista transacción sin paginación
+
+ ```bash
+curl --location 'http://localhost:8081/api/v1/transaction' \
+--header 'Content-Type: application/json' \
+--data ''
+ ```
+
+ 4. Busca una transaccion por transactionExternalId
+
+ ```bash
+curl --location 'http://localhost:8081/api/v1/transaction/{transactionExternalId}' \
+--header 'Content-Type: application/json' \
+--data ''
+ ```
+ ### FLUJO DE LA TRANSACCIÓN
+
+#### Creación de una transacción
+1. El cliente crea una transacción mediante el endpoint:
+ `[POST] /api/v1/transaction`.
+2. La transacción se persiste inicialmente con estado `PENDIENTE` en la base de datos PostgreSQL.
+3. Se publica un evento de transacción en un tópico de Kafka para su validación antifraude.
+4. El microservicio de antifraude consume el evento y valida las reglas de negocio.
+5. Como resultado de la validación, la transacción es:
+ - `APROBADO` si cumple las reglas.
+ - `RECHAZADO` si no cumple las reglas.
+6. El estado final de la transacción se actualiza en la base de datos.
+
+#### Consulta de transacciones
+- Las transacciones pueden consultarse:
+ - Con paginación.
+ - Sin paginación.
+ - Por `transactionExternalId`.
+
+ ### DIAGRAMA
+
+
+
+### NOTA
+- Para escenarios de alto volumen y concurrencia, una alternativa sería Cassandra, donde el modelo estaría orientado a las
+consultas y permitiría alta disponibilidad y escalabilidad horizontal.
-Don't forget that the proper way to submit your work is to fork the repo and create a PR :wink: ... have fun !!
-
-- [Problem](#problem)
-- [Tech Stack](#tech_stack)
-- [Send us your challenge](#send_us_your_challenge)
-
-# Problem
-
-Every time a financial transaction is created it must be validated by our anti-fraud microservice and then the same service sends a message back to update the transaction status.
-For now, we have only three transaction statuses:
-
-
- - pending
- - approved
- - rejected
-
-
-Every transaction with a value greater than 1000 should be rejected.
-
-```mermaid
- flowchart LR
- Transaction -- Save Transaction with pending Status --> transactionDatabase[(Database)]
- Transaction --Send transaction Created event--> Anti-Fraud
- Anti-Fraud -- Send transaction Status Approved event--> Transaction
- Anti-Fraud -- Send transaction Status Rejected event--> Transaction
- Transaction -- Update transaction Status event--> transactionDatabase[(Database)]
-```
-
-# Tech Stack
-
-
- - Node. You can use any framework you want (i.e. Nestjs with an ORM like TypeOrm or Prisma)
- - Any database
- - Kafka
-
-
-We do provide a `Dockerfile` to help you get started with a dev environment.
-
-You must have two resources:
-
-1. Resource to create a transaction that must containt:
-
-```json
-{
- "accountExternalIdDebit": "Guid",
- "accountExternalIdCredit": "Guid",
- "tranferTypeId": 1,
- "value": 120
-}
-```
-
-2. Resource to retrieve a transaction
-
-```json
-{
- "transactionExternalId": "Guid",
- "transactionType": {
- "name": ""
- },
- "transactionStatus": {
- "name": ""
- },
- "value": 120,
- "createdAt": "Date"
-}
-```
-
-## Optional
-
-You can use any approach to store transaction data but you should consider that we may deal with high volume scenarios where we have a huge amount of writes and reads for the same data at the same time. How would you tackle this requirement?
-
-You can use Graphql;
-
-# Send us your challenge
-
-When you finish your challenge, after forking a repository, you **must** open a pull request to our repository. There are no limitations to the implementation, you can follow the programming paradigm, modularization, and style that you feel is the most appropriate solution.
-
-If you have any questions, please let us know.
diff --git a/docker-compose.yml b/docker-compose.yml
index 0e8807f21c..3671fe764f 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -1,25 +1,81 @@
-version: "3.7"
+version: "3.9"
+
+networks:
+ yape_challenge:
+
services:
- postgres:
- image: postgres:14
- ports:
- - "5432:5432"
- environment:
- - POSTGRES_USER=postgres
- - POSTGRES_PASSWORD=postgres
zookeeper:
- image: confluentinc/cp-zookeeper:5.5.3
+ image: confluentinc/cp-zookeeper:7.4.0
+ container_name: zookeeper
environment:
ZOOKEEPER_CLIENT_PORT: 2181
+ ZOOKEEPER_TICK_TIME: 2000
+ ports:
+ - "2181:2181"
+ networks:
+ - yape_challenge
+
kafka:
- image: confluentinc/cp-enterprise-kafka:5.5.3
- depends_on: [zookeeper]
+ image: confluentinc/cp-kafka:7.4.0
+ container_name: kafka
+ depends_on:
+ - zookeeper
+ ports:
+ - "9092:9092"
environment:
- KAFKA_ZOOKEEPER_CONNECT: "zookeeper:2181"
- KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:29092,PLAINTEXT_HOST://localhost:9092
- KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT
KAFKA_BROKER_ID: 1
+ KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
+ KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:29092,PLAINTEXT_HOST://kafka:9092
+ KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
- KAFKA_JMX_PORT: 9991
+ KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 1
+ KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1
+ restart: always
+ networks:
+ - yape_challenge
+
+ yape-transaction:
+ build:
+ context: .
+ dockerfile: Dockerfile.transaction
+ container_name: yape-transaction
+ environment:
+ SPRING_DATASOURCE_URL: jdbc:postgresql://postgres_yape:5432/dbtransactionv1
+ SPRING_DATASOURCE_USERNAME: postgres
+ SPRING_DATASOURCE_PASSWORD: postgres
+ KAFKA_BOOTSTRAP_SERVERS: kafka:29092
+ ports:
+ - "8081:8081"
+ depends_on:
+ - kafka
+ - postgres_yape
+ networks:
+ - yape_challenge
+
+ yape-antifraud:
+ build:
+ context: .
+ dockerfile: Dockerfile.antifraud
+ container_name: yape-antifraud
+ environment:
+ KAFKA_BOOTSTRAP_SERVERS: kafka:29092
ports:
- - 9092:9092
+ - "8082:8082"
+ depends_on:
+ - kafka
+ networks:
+ - yape_challenge
+
+ postgres_yape:
+ image: postgres:14
+ container_name: postgres_yape
+ ports:
+ - "5433:5432"
+ environment:
+ POSTGRES_USER: postgres
+ POSTGRES_PASSWORD: postgres
+ POSTGRES_DB: dbtransactionv1
+ volumes:
+ - ./init.sql:/docker-entrypoint-initdb.d/init.sql
+ networks:
+ - yape_challenge
diff --git a/init.sql b/init.sql
new file mode 100644
index 0000000000..cd606ff5cd
--- /dev/null
+++ b/init.sql
@@ -0,0 +1,48 @@
+-- ===============================
+-- CONNECT TO POSTGRES
+-- ===============================
+DO $$
+BEGIN
+ IF NOT EXISTS (
+ SELECT 1 FROM pg_database WHERE datname = 'dbtransactionv1'
+ ) THEN
+ CREATE DATABASE dbtransactionv1;
+ END IF;
+END
+$$;
+
+-- CONNECT
+\c dbtransactionv1
+
+-- EXTENSION PARA UUID
+CREATE EXTENSION IF NOT EXISTS "pgcrypto";
+
+-- TABLA: TRANSACTION
+CREATE TABLE IF NOT EXISTS public.transacciones
+(
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+ transaction_external_id UUID NOT NULL UNIQUE,
+ account_external_id_debit UUID NOT NULL,
+ account_external_id_credit UUID NOT NULL,
+ transfer_type_id INTEGER NOT NULL,
+ transaction_status VARCHAR(20) NOT NULL,
+ value NUMERIC(15, 2) NOT NULL,
+ created_at TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT now()
+);
+
+-- INDICES
+CREATE INDEX IF NOT EXISTS idx_transacciones_external_id
+ON public.transacciones (transaction_external_id);
+
+CREATE INDEX IF NOT EXISTS idx_transacciones_status
+ON public.transacciones (transaction_status);
+
+CREATE INDEX IF NOT EXISTS idx_transacciones_created_at
+ON public.transacciones (created_at);
+
+-- ===============================
+-- PERMISSIONS
+-- ===============================
+GRANT CONNECT ON DATABASE dbtransactionv1 TO PUBLIC;
+GRANT ALL PRIVILEGES ON DATABASE dbtransactionv1 TO postgres;
+GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO postgres;
diff --git a/resources/arq.png b/resources/arq.png
new file mode 100644
index 0000000000..e9744e99b4
Binary files /dev/null and b/resources/arq.png differ
diff --git a/yape-financial/yape-antifraud/.gitignore b/yape-financial/yape-antifraud/.gitignore
new file mode 100644
index 0000000000..5ff6309b71
--- /dev/null
+++ b/yape-financial/yape-antifraud/.gitignore
@@ -0,0 +1,38 @@
+target/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+### IntelliJ IDEA ###
+.idea/modules.xml
+.idea/jarRepositories.xml
+.idea/compiler.xml
+.idea/libraries/
+*.iws
+*.iml
+*.ipr
+
+### Eclipse ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/
+
+### Mac OS ###
+.DS_Store
\ No newline at end of file
diff --git a/yape-financial/yape-antifraud/pom.xml b/yape-financial/yape-antifraud/pom.xml
new file mode 100644
index 0000000000..a78d33e6d2
--- /dev/null
+++ b/yape-financial/yape-antifraud/pom.xml
@@ -0,0 +1,152 @@
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 3.2.5
+
+
+ com.tec.yape.antifraud
+ yape-antifraud
+ jar
+ 1.0-SNAPSHOT
+
+
+ 21
+ 21
+ UTF-8
+ 1.6.3
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.springframework.boot
+ spring-boot-starter-validation
+
+
+ org.projectlombok
+ lombok
+ true
+
+
+ org.mapstruct
+ mapstruct
+ ${org.mapstruct.version}
+
+
+ org.mapstruct
+ mapstruct-processor
+ ${org.mapstruct.version}
+ provided
+
+
+ org.springdoc
+ springdoc-openapi-starter-webmvc-ui
+ 2.3.0
+
+
+ org.springdoc
+ springdoc-openapi-data-rest
+ 1.6.4
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+
+
+ com.google.code.gson
+ gson
+ 2.8.9
+
+
+
+ org.springframework.boot
+ spring-boot-starter-data-redis
+
+
+ org.springframework.boot
+ spring-boot-starter-cache
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+
+
+ org.mockito
+ mockito-junit-jupiter
+ 5.11.0
+ test
+
+
+ org.springframework.retry
+ spring-retry
+ 1.3.1
+
+
+ org.springframework.kafka
+ spring-kafka
+
+
+ org.springframework.kafka
+ spring-kafka-test
+ test
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.11.0
+
+ 21
+
+ --enable-preview
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ 3.1.2
+
+ --enable-preview
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+ --enable-preview
+
+
+
+
+
+
\ No newline at end of file
diff --git a/yape-financial/yape-antifraud/src/main/java/com/tec/yape/antifraud/YapeAntiFraudService.java b/yape-financial/yape-antifraud/src/main/java/com/tec/yape/antifraud/YapeAntiFraudService.java
new file mode 100644
index 0000000000..a19f5f98f8
--- /dev/null
+++ b/yape-financial/yape-antifraud/src/main/java/com/tec/yape/antifraud/YapeAntiFraudService.java
@@ -0,0 +1,13 @@
+package com.tec.yape.antifraud;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.scheduling.annotation.EnableAsync;
+
+@SpringBootApplication
+@EnableAsync
+public class YapeAntiFraudService {
+ public static void main(String[] args) {
+ SpringApplication.run(YapeAntiFraudService.class, args);
+ }
+}
\ No newline at end of file
diff --git a/yape-financial/yape-antifraud/src/main/java/com/tec/yape/antifraud/application/usecase/FraudValidationUseCase.java b/yape-financial/yape-antifraud/src/main/java/com/tec/yape/antifraud/application/usecase/FraudValidationUseCase.java
new file mode 100644
index 0000000000..d30081476b
--- /dev/null
+++ b/yape-financial/yape-antifraud/src/main/java/com/tec/yape/antifraud/application/usecase/FraudValidationUseCase.java
@@ -0,0 +1,37 @@
+package com.tec.yape.antifraud.application.usecase;
+
+import com.tec.yape.antifraud.domain.enums.TransactionStatus;
+import com.tec.yape.antifraud.domain.model.TransactionCreatedEvent;
+import com.tec.yape.antifraud.domain.model.TransactionValidatedEvent;
+import com.tec.yape.antifraud.domain.port.input.FraudValidationInPort;
+import com.tec.yape.antifraud.domain.port.output.TransactionStatusPublisherOutPort;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+import java.math.BigDecimal;
+
+@Component
+@RequiredArgsConstructor
+@Slf4j
+public class FraudValidationUseCase implements FraudValidationInPort {
+
+ private final TransactionStatusPublisherOutPort statusPublisher;
+
+ @Override
+ public void validate(TransactionCreatedEvent event) {
+
+
+ TransactionStatus status =
+ event.value().compareTo(BigDecimal.valueOf(1000)) > 0
+ ? TransactionStatus.RECHAZADO
+ : TransactionStatus.APROBADO;
+
+ statusPublisher.publish(
+ new TransactionValidatedEvent(
+ event.transactionExternalId(),
+ status
+ )
+ );
+ }
+}
\ No newline at end of file
diff --git a/yape-financial/yape-antifraud/src/main/java/com/tec/yape/antifraud/domain/enums/TransactionStatus.java b/yape-financial/yape-antifraud/src/main/java/com/tec/yape/antifraud/domain/enums/TransactionStatus.java
new file mode 100644
index 0000000000..4197c41643
--- /dev/null
+++ b/yape-financial/yape-antifraud/src/main/java/com/tec/yape/antifraud/domain/enums/TransactionStatus.java
@@ -0,0 +1,7 @@
+package com.tec.yape.antifraud.domain.enums;
+
+public enum TransactionStatus {
+ PENDIENTE,
+ APROBADO,
+ RECHAZADO
+}
\ No newline at end of file
diff --git a/yape-financial/yape-antifraud/src/main/java/com/tec/yape/antifraud/domain/exception/CommonErrorType.java b/yape-financial/yape-antifraud/src/main/java/com/tec/yape/antifraud/domain/exception/CommonErrorType.java
new file mode 100644
index 0000000000..0cff88117a
--- /dev/null
+++ b/yape-financial/yape-antifraud/src/main/java/com/tec/yape/antifraud/domain/exception/CommonErrorType.java
@@ -0,0 +1,16 @@
+package com.tec.yape.antifraud.domain.exception;
+
+import lombok.Getter;
+
+@Getter
+public enum CommonErrorType {
+
+ COMMON_ERROR_400_1("Error en el criterio de ordenación");
+
+ private final String description;
+
+
+ CommonErrorType(String description) {
+ this.description = description;
+ }
+}
diff --git a/yape-financial/yape-antifraud/src/main/java/com/tec/yape/antifraud/domain/exception/TransactionException.java b/yape-financial/yape-antifraud/src/main/java/com/tec/yape/antifraud/domain/exception/TransactionException.java
new file mode 100644
index 0000000000..a78778f26a
--- /dev/null
+++ b/yape-financial/yape-antifraud/src/main/java/com/tec/yape/antifraud/domain/exception/TransactionException.java
@@ -0,0 +1,18 @@
+package com.tec.yape.antifraud.domain.exception;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.springframework.http.HttpStatus;
+
+@EqualsAndHashCode(callSuper = true)
+@Data
+public class TransactionException extends RuntimeException {
+
+ public HttpStatus status;
+ public String code;
+
+ public TransactionException(HttpStatus httpStatus, String message) {
+ super(message);
+ this.status = httpStatus;
+ }
+}
diff --git a/yape-financial/yape-antifraud/src/main/java/com/tec/yape/antifraud/domain/model/Transaction.java b/yape-financial/yape-antifraud/src/main/java/com/tec/yape/antifraud/domain/model/Transaction.java
new file mode 100644
index 0000000000..64c385a5ab
--- /dev/null
+++ b/yape-financial/yape-antifraud/src/main/java/com/tec/yape/antifraud/domain/model/Transaction.java
@@ -0,0 +1,23 @@
+package com.tec.yape.antifraud.domain.model;
+
+import com.tec.yape.antifraud.domain.enums.TransactionStatus;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.util.UUID;
+
+@Getter
+@Setter
+public class Transaction {
+
+ private UUID transactionExternalId;
+ private UUID accountExternalIdDebit;
+ private UUID accountExternalIdCredit;
+ private Integer transferTypeId;
+ private BigDecimal value;
+ private TransactionStatus transactionStatus;
+ private LocalDateTime createdAt;
+
+}
\ No newline at end of file
diff --git a/yape-financial/yape-antifraud/src/main/java/com/tec/yape/antifraud/domain/model/TransactionCreatedEvent.java b/yape-financial/yape-antifraud/src/main/java/com/tec/yape/antifraud/domain/model/TransactionCreatedEvent.java
new file mode 100644
index 0000000000..79c2a6eefa
--- /dev/null
+++ b/yape-financial/yape-antifraud/src/main/java/com/tec/yape/antifraud/domain/model/TransactionCreatedEvent.java
@@ -0,0 +1,9 @@
+package com.tec.yape.antifraud.domain.model;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+
+public record TransactionCreatedEvent(
+ UUID transactionExternalId,
+ BigDecimal value
+) {}
diff --git a/yape-financial/yape-antifraud/src/main/java/com/tec/yape/antifraud/domain/model/TransactionValidatedEvent.java b/yape-financial/yape-antifraud/src/main/java/com/tec/yape/antifraud/domain/model/TransactionValidatedEvent.java
new file mode 100644
index 0000000000..7d4b2aac55
--- /dev/null
+++ b/yape-financial/yape-antifraud/src/main/java/com/tec/yape/antifraud/domain/model/TransactionValidatedEvent.java
@@ -0,0 +1,11 @@
+package com.tec.yape.antifraud.domain.model;
+
+
+import com.tec.yape.antifraud.domain.enums.TransactionStatus;
+
+import java.util.UUID;
+
+public record TransactionValidatedEvent(
+ UUID transactionExternalId,
+ TransactionStatus status
+) {}
\ No newline at end of file
diff --git a/yape-financial/yape-antifraud/src/main/java/com/tec/yape/antifraud/domain/port/input/FraudValidationInPort.java b/yape-financial/yape-antifraud/src/main/java/com/tec/yape/antifraud/domain/port/input/FraudValidationInPort.java
new file mode 100644
index 0000000000..dc8046c11b
--- /dev/null
+++ b/yape-financial/yape-antifraud/src/main/java/com/tec/yape/antifraud/domain/port/input/FraudValidationInPort.java
@@ -0,0 +1,8 @@
+package com.tec.yape.antifraud.domain.port.input;
+
+import com.tec.yape.antifraud.domain.model.TransactionCreatedEvent;
+
+public interface FraudValidationInPort {
+
+ void validate(TransactionCreatedEvent event);
+}
diff --git a/yape-financial/yape-antifraud/src/main/java/com/tec/yape/antifraud/domain/port/output/TransactionStatusPublisherOutPort.java b/yape-financial/yape-antifraud/src/main/java/com/tec/yape/antifraud/domain/port/output/TransactionStatusPublisherOutPort.java
new file mode 100644
index 0000000000..5baf68493f
--- /dev/null
+++ b/yape-financial/yape-antifraud/src/main/java/com/tec/yape/antifraud/domain/port/output/TransactionStatusPublisherOutPort.java
@@ -0,0 +1,8 @@
+package com.tec.yape.antifraud.domain.port.output;
+
+import com.tec.yape.antifraud.domain.model.TransactionValidatedEvent;
+
+public interface TransactionStatusPublisherOutPort {
+
+ void publish(TransactionValidatedEvent event);
+}
diff --git a/yape-financial/yape-antifraud/src/main/java/com/tec/yape/antifraud/domain/util/BeanConstants.java b/yape-financial/yape-antifraud/src/main/java/com/tec/yape/antifraud/domain/util/BeanConstants.java
new file mode 100644
index 0000000000..01bcd664a3
--- /dev/null
+++ b/yape-financial/yape-antifraud/src/main/java/com/tec/yape/antifraud/domain/util/BeanConstants.java
@@ -0,0 +1,8 @@
+package com.tec.yape.antifraud.domain.util;
+
+public class BeanConstants {
+
+ public static final String ASYNC_SAVE_CALL_HISTORY = "asyncSaveCallHistory";
+
+ public static final String ASYNC_VIRTUAL_SAVE_CALL_HISTORY = "asyncVirtualSaveCallHistory";
+}
diff --git a/yape-financial/yape-antifraud/src/main/java/com/tec/yape/antifraud/domain/util/JsonUtil.java b/yape-financial/yape-antifraud/src/main/java/com/tec/yape/antifraud/domain/util/JsonUtil.java
new file mode 100644
index 0000000000..ef6901a919
--- /dev/null
+++ b/yape-financial/yape-antifraud/src/main/java/com/tec/yape/antifraud/domain/util/JsonUtil.java
@@ -0,0 +1,40 @@
+package com.tec.yape.antifraud.domain.util;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializationFeature;
+import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
+
+public class JsonUtil {
+
+
+ private JsonUtil() {
+
+ }
+
+ public static String ToJSON(Object object) throws JsonProcessingException {
+ ObjectMapper mapper = new ObjectMapper();
+ mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+ mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
+ mapper.registerModule(new Jdk8Module());
+ mapper.registerModule(new JavaTimeModule());
+ return mapper.writeValueAsString(object);
+ }
+
+ public static T fromJson(String message, Class tClass) {
+ ObjectMapper mapper = new ObjectMapper();
+ mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+ mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
+ mapper.registerModule(new Jdk8Module());
+ mapper.registerModule(new JavaTimeModule());
+
+ try {
+ return mapper.readValue(message, tClass);
+ } catch (JsonProcessingException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+}
diff --git a/yape-financial/yape-antifraud/src/main/java/com/tec/yape/antifraud/domain/util/SubscriberResponse.java b/yape-financial/yape-antifraud/src/main/java/com/tec/yape/antifraud/domain/util/SubscriberResponse.java
new file mode 100644
index 0000000000..7d6e3817fb
--- /dev/null
+++ b/yape-financial/yape-antifraud/src/main/java/com/tec/yape/antifraud/domain/util/SubscriberResponse.java
@@ -0,0 +1,7 @@
+package com.tec.yape.antifraud.domain.util;
+
+public enum SubscriberResponse {
+
+ FINALIZED,
+ ERRORS
+}
diff --git a/yape-financial/yape-antifraud/src/main/java/com/tec/yape/antifraud/infrastructure/adapter/TransactionStatusPublisherAdapter.java b/yape-financial/yape-antifraud/src/main/java/com/tec/yape/antifraud/infrastructure/adapter/TransactionStatusPublisherAdapter.java
new file mode 100644
index 0000000000..d7d5cd536c
--- /dev/null
+++ b/yape-financial/yape-antifraud/src/main/java/com/tec/yape/antifraud/infrastructure/adapter/TransactionStatusPublisherAdapter.java
@@ -0,0 +1,22 @@
+package com.tec.yape.antifraud.infrastructure.adapter;
+
+import com.tec.yape.antifraud.domain.model.TransactionValidatedEvent;
+import com.tec.yape.antifraud.domain.port.output.TransactionStatusPublisherOutPort;
+import com.tec.yape.antifraud.infrastructure.messaging.producer.TransactionStatusPublisher;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+
+@Component
+@RequiredArgsConstructor
+@Slf4j
+public class TransactionStatusPublisherAdapter implements TransactionStatusPublisherOutPort {
+
+ private final TransactionStatusPublisher transactionStatusPublisher;
+
+ @Override
+ public void publish(TransactionValidatedEvent event) {
+ transactionStatusPublisher.publish(event);
+ }
+}
diff --git a/yape-financial/yape-antifraud/src/main/java/com/tec/yape/antifraud/infrastructure/config/ExecutorAsyncVirtualConfig.java b/yape-financial/yape-antifraud/src/main/java/com/tec/yape/antifraud/infrastructure/config/ExecutorAsyncVirtualConfig.java
new file mode 100644
index 0000000000..fc13421b7f
--- /dev/null
+++ b/yape-financial/yape-antifraud/src/main/java/com/tec/yape/antifraud/infrastructure/config/ExecutorAsyncVirtualConfig.java
@@ -0,0 +1,19 @@
+package com.tec.yape.antifraud.infrastructure.config;
+
+import com.tec.yape.antifraud.domain.util.BeanConstants;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+
+@Configuration
+public class ExecutorAsyncVirtualConfig {
+
+ @Bean(name = BeanConstants.ASYNC_VIRTUAL_SAVE_CALL_HISTORY)
+ public Executor asyncVirtualSaveCallHistory() {
+ return Executors.newThreadPerTaskExecutor(
+ Thread.ofVirtual().name("virtual-thread-", 0)::unstarted
+ );
+ }
+}
diff --git a/yape-financial/yape-antifraud/src/main/java/com/tec/yape/antifraud/infrastructure/config/GlobalExceptionHandler.java b/yape-financial/yape-antifraud/src/main/java/com/tec/yape/antifraud/infrastructure/config/GlobalExceptionHandler.java
new file mode 100644
index 0000000000..e2bae4e6af
--- /dev/null
+++ b/yape-financial/yape-antifraud/src/main/java/com/tec/yape/antifraud/infrastructure/config/GlobalExceptionHandler.java
@@ -0,0 +1,19 @@
+package com.tec.yape.antifraud.infrastructure.config;
+
+import com.tec.yape.antifraud.infrastructure.config.dto.ErrorDto;
+import com.tec.yape.antifraud.domain.exception.TransactionException;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+
+@RestControllerAdvice
+public class GlobalExceptionHandler {
+
+ @ExceptionHandler(value = TransactionException.class)
+ public ResponseEntity businessExceptionHandler(TransactionException ex) {
+ ErrorDto error = ErrorDto.builder()
+ .status(ex.getStatus())
+ .code(ex.getCode()).message(ex.getMessage()).build();
+ return new ResponseEntity<>(error, ex.getStatus());
+ }
+}
diff --git a/yape-financial/yape-antifraud/src/main/java/com/tec/yape/antifraud/infrastructure/config/OpenApicConfig.java b/yape-financial/yape-antifraud/src/main/java/com/tec/yape/antifraud/infrastructure/config/OpenApicConfig.java
new file mode 100644
index 0000000000..61c9c6859d
--- /dev/null
+++ b/yape-financial/yape-antifraud/src/main/java/com/tec/yape/antifraud/infrastructure/config/OpenApicConfig.java
@@ -0,0 +1,35 @@
+package com.tec.yape.antifraud.infrastructure.config;
+
+import io.swagger.v3.oas.annotations.OpenAPIDefinition;
+import io.swagger.v3.oas.annotations.enums.SecuritySchemeType;
+import io.swagger.v3.oas.annotations.info.Contact;
+import io.swagger.v3.oas.annotations.info.Info;
+import io.swagger.v3.oas.annotations.info.License;
+import io.swagger.v3.oas.annotations.security.SecurityScheme;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+@OpenAPIDefinition(
+ info = @Info(
+ title = "API-REST ANTIFRAUDE",
+ version = "1.0",
+
+ description = "Api Rest para Antifraude",
+
+ license = @License(
+ name = "Apache 2.0",
+ url = "http://www.apache.org/licenses/LICENSE-2.0.html"
+ ),
+
+ contact = @Contact(
+ name = "tec.com",
+ url = "tec.com",
+ email = "evercarlosrojas@gmail.com")
+ )
+)
+@SecurityScheme(name = "bearerAuth", type = SecuritySchemeType.HTTP, bearerFormat = "JWT", description = "Autenticación" +
+ "tipo Bearer API-TC", scheme = "bearer")
+public class OpenApicConfig {
+
+
+}
diff --git a/yape-financial/yape-antifraud/src/main/java/com/tec/yape/antifraud/infrastructure/config/dto/ErrorDto.java b/yape-financial/yape-antifraud/src/main/java/com/tec/yape/antifraud/infrastructure/config/dto/ErrorDto.java
new file mode 100644
index 0000000000..844161ad11
--- /dev/null
+++ b/yape-financial/yape-antifraud/src/main/java/com/tec/yape/antifraud/infrastructure/config/dto/ErrorDto.java
@@ -0,0 +1,13 @@
+package com.tec.yape.antifraud.infrastructure.config.dto;
+
+import lombok.Builder;
+import lombok.Data;
+import org.springframework.http.HttpStatus;
+
+@Data
+@Builder
+public class ErrorDto {
+ private String code;
+ private String message;
+ private HttpStatus status;
+}
diff --git a/yape-financial/yape-antifraud/src/main/java/com/tec/yape/antifraud/infrastructure/config/kafka/consumer/KafkaConsumerConfig.java b/yape-financial/yape-antifraud/src/main/java/com/tec/yape/antifraud/infrastructure/config/kafka/consumer/KafkaConsumerConfig.java
new file mode 100644
index 0000000000..12d6783b66
--- /dev/null
+++ b/yape-financial/yape-antifraud/src/main/java/com/tec/yape/antifraud/infrastructure/config/kafka/consumer/KafkaConsumerConfig.java
@@ -0,0 +1,44 @@
+package com.tec.yape.antifraud.infrastructure.config.kafka.consumer;
+
+import org.apache.kafka.clients.consumer.ConsumerConfig;
+import org.apache.kafka.common.serialization.StringSerializer;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory;
+import org.springframework.kafka.config.KafkaListenerContainerFactory;
+import org.springframework.kafka.core.ConsumerFactory;
+import org.springframework.kafka.core.DefaultKafkaConsumerFactory;
+import org.springframework.kafka.listener.ConcurrentMessageListenerContainer;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@Configuration
+public class KafkaConsumerConfig {
+
+ @Value("${spring.kafka.bootstrap-servers}")
+ private String boostrapServers;
+
+
+ public Map consumerConfig() {
+ Map properties = new HashMap<>();
+ properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, boostrapServers);
+ properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringSerializer.class);// deserializa: convirte de string a bytes
+ properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringSerializer.class);// deserializa
+ return properties;
+ }
+
+ @Bean
+ public ConsumerFactory consumerFactory() {
+ return new DefaultKafkaConsumerFactory<>(consumerConfig());
+ }
+
+ @Bean // Para poder inyectar en otros lugares
+ public KafkaListenerContainerFactory> consumer() {
+ ConcurrentKafkaListenerContainerFactory factory = new ConcurrentKafkaListenerContainerFactory<>();
+ factory.setConsumerFactory(consumerFactory());
+ return factory;
+ }
+
+}
diff --git a/yape-financial/yape-antifraud/src/main/java/com/tec/yape/antifraud/infrastructure/config/kafka/producer/KafkaProducerConfig.java b/yape-financial/yape-antifraud/src/main/java/com/tec/yape/antifraud/infrastructure/config/kafka/producer/KafkaProducerConfig.java
new file mode 100644
index 0000000000..10ba7bc06f
--- /dev/null
+++ b/yape-financial/yape-antifraud/src/main/java/com/tec/yape/antifraud/infrastructure/config/kafka/producer/KafkaProducerConfig.java
@@ -0,0 +1,41 @@
+package com.tec.yape.antifraud.infrastructure.config.kafka.producer;
+
+import org.apache.kafka.clients.producer.ProducerConfig;
+import org.apache.kafka.common.serialization.StringSerializer;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.kafka.core.DefaultKafkaProducerFactory;
+import org.springframework.kafka.core.KafkaTemplate;
+import org.springframework.kafka.core.ProducerFactory;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@Configuration
+public class KafkaProducerConfig {
+
+ @Value("${spring.kafka.bootstrap-servers}")
+ private String boostrapServers;
+
+
+ public Map producerConfig() {
+ Map properties = new HashMap<>();
+ properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, boostrapServers);
+ properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);// serializa: convirte de string a bytes
+ properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class);// serializa
+ return properties;
+ }
+
+ @Bean
+ public ProducerFactory providerFactory() {
+ return new DefaultKafkaProducerFactory<>(producerConfig());
+ }
+
+ // Envian mensaje
+ @Bean
+ public KafkaTemplate kafkaTemplate(ProducerFactory providerFactory) {// inyectando
+ return new KafkaTemplate<>(providerFactory);
+ }
+
+}
diff --git a/yape-financial/yape-antifraud/src/main/java/com/tec/yape/antifraud/infrastructure/messaging/consuper/TransactionEventConsumer.java b/yape-financial/yape-antifraud/src/main/java/com/tec/yape/antifraud/infrastructure/messaging/consuper/TransactionEventConsumer.java
new file mode 100644
index 0000000000..5df3c2a83c
--- /dev/null
+++ b/yape-financial/yape-antifraud/src/main/java/com/tec/yape/antifraud/infrastructure/messaging/consuper/TransactionEventConsumer.java
@@ -0,0 +1,38 @@
+package com.tec.yape.antifraud.infrastructure.messaging.consuper;
+
+import com.tec.yape.antifraud.domain.port.input.FraudValidationInPort;
+import com.tec.yape.antifraud.domain.util.JsonUtil;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.kafka.annotation.KafkaListener;
+import org.springframework.messaging.handler.annotation.Headers;
+import org.springframework.stereotype.Component;
+import com.tec.yape.antifraud.domain.model.TransactionCreatedEvent;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Map;
+
+@Component
+@Slf4j
+@RequiredArgsConstructor
+public class TransactionEventConsumer {
+
+ private final FraudValidationInPort fraudValidationInPort;
+
+ @KafkaListener(
+ topics = "topic-transaction",
+ groupId = "antifraud-service-group",
+ containerFactory = "kafkaListenerContainerFactory"
+ )
+ public void listener(String message, @Headers Map headers) {
+
+ String jsonMessage = new String(message.getBytes(), StandardCharsets.UTF_8);
+
+ TransactionCreatedEvent event = JsonUtil.fromJson(jsonMessage, TransactionCreatedEvent.class);
+
+ log.info("[AntifraudConsumer] Transaction received: {}", jsonMessage);
+ log.info("[AntifraudConsumer] Client-id header: {}", headers.get("client-id"));
+
+ fraudValidationInPort.validate(event);
+ }
+}
diff --git a/yape-financial/yape-antifraud/src/main/java/com/tec/yape/antifraud/infrastructure/messaging/producer/TransactionStatusPublisher.java b/yape-financial/yape-antifraud/src/main/java/com/tec/yape/antifraud/infrastructure/messaging/producer/TransactionStatusPublisher.java
new file mode 100644
index 0000000000..cdadaec8c1
--- /dev/null
+++ b/yape-financial/yape-antifraud/src/main/java/com/tec/yape/antifraud/infrastructure/messaging/producer/TransactionStatusPublisher.java
@@ -0,0 +1,8 @@
+package com.tec.yape.antifraud.infrastructure.messaging.producer;
+
+import com.tec.yape.antifraud.domain.model.TransactionValidatedEvent;
+
+public interface TransactionStatusPublisher {
+
+ void publish(TransactionValidatedEvent message);
+}
diff --git a/yape-financial/yape-antifraud/src/main/java/com/tec/yape/antifraud/infrastructure/messaging/producer/TransactionStatusPublisherImpl.java b/yape-financial/yape-antifraud/src/main/java/com/tec/yape/antifraud/infrastructure/messaging/producer/TransactionStatusPublisherImpl.java
new file mode 100644
index 0000000000..a48e17d324
--- /dev/null
+++ b/yape-financial/yape-antifraud/src/main/java/com/tec/yape/antifraud/infrastructure/messaging/producer/TransactionStatusPublisherImpl.java
@@ -0,0 +1,52 @@
+package com.tec.yape.antifraud.infrastructure.messaging.producer;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.tec.yape.antifraud.domain.model.TransactionValidatedEvent;
+import com.tec.yape.antifraud.domain.util.JsonUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.kafka.core.KafkaTemplate;
+import org.springframework.kafka.support.KafkaHeaders;
+import org.springframework.messaging.Message;
+import org.springframework.messaging.support.MessageBuilder;
+import org.springframework.stereotype.Component;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@Component
+@Slf4j
+public class TransactionStatusPublisherImpl implements TransactionStatusPublisher {
+
+ private final KafkaTemplate kafkaTemplate;
+
+ public TransactionStatusPublisherImpl(KafkaTemplate kafkaTemplate) {
+ this.kafkaTemplate = kafkaTemplate;
+ }
+
+ @Override
+ public void publish(TransactionValidatedEvent event) {
+ log.info("[TransactionStatusPublisherImpl] {}", "Start async process");
+
+ try {
+ String message = JsonUtil.ToJSON(event);
+
+ Message kafkaMessage = MessageBuilder
+ .withPayload(message)
+ .setHeader(KafkaHeaders.TOPIC, "topic-transaction-validated")
+ .copyHeaders(getHeaders())
+ .build();
+
+ kafkaTemplate.send(kafkaMessage);
+
+ } catch (JsonProcessingException e) {
+ log.error("[TransactionStatusPublisherImpl] message:{}", e.getMessage());
+ }
+ }
+
+ private Map getHeaders() {
+ Map headers = new HashMap<>();
+ headers.put("client-id", "EVER CARLOS ROJAS");
+ return headers;
+ }
+
+}
diff --git a/yape-financial/yape-antifraud/src/main/java/com/tec/yape/antifraud/infrastructure/rest/controller/IndexController.java b/yape-financial/yape-antifraud/src/main/java/com/tec/yape/antifraud/infrastructure/rest/controller/IndexController.java
new file mode 100644
index 0000000000..2d41ab515b
--- /dev/null
+++ b/yape-financial/yape-antifraud/src/main/java/com/tec/yape/antifraud/infrastructure/rest/controller/IndexController.java
@@ -0,0 +1,13 @@
+package com.tec.yape.antifraud.infrastructure.rest.controller;
+
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.RequestMapping;
+
+@Controller
+public class IndexController {
+
+ @RequestMapping("/")
+ public String getIndex(){
+ return "redirect:swagger-ui.html";
+ }
+}
\ No newline at end of file
diff --git a/yape-financial/yape-antifraud/src/main/java/com/tec/yape/antifraud/infrastructure/rest/dto/request/TransactionRequestDto.java b/yape-financial/yape-antifraud/src/main/java/com/tec/yape/antifraud/infrastructure/rest/dto/request/TransactionRequestDto.java
new file mode 100644
index 0000000000..900c863e75
--- /dev/null
+++ b/yape-financial/yape-antifraud/src/main/java/com/tec/yape/antifraud/infrastructure/rest/dto/request/TransactionRequestDto.java
@@ -0,0 +1,25 @@
+package com.tec.yape.antifraud.infrastructure.rest.dto.request;
+
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+
+@Getter
+@Setter
+public class TransactionRequestDto {
+
+ private UUID accountExternalIdDebit;
+
+ private UUID accountExternalIdCredit;
+
+ @JsonProperty("tranferTypeId")
+ private Integer transferTypeId;
+
+ private BigDecimal value;
+
+
+}
diff --git a/yape-financial/yape-antifraud/src/main/java/com/tec/yape/antifraud/infrastructure/rest/dto/response/TransactionResponseDto.java b/yape-financial/yape-antifraud/src/main/java/com/tec/yape/antifraud/infrastructure/rest/dto/response/TransactionResponseDto.java
new file mode 100644
index 0000000000..a00bb0fff1
--- /dev/null
+++ b/yape-financial/yape-antifraud/src/main/java/com/tec/yape/antifraud/infrastructure/rest/dto/response/TransactionResponseDto.java
@@ -0,0 +1,24 @@
+package com.tec.yape.antifraud.infrastructure.rest.dto.response;
+
+import lombok.Getter;
+import lombok.Setter;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.util.UUID;
+
+@Setter
+@Getter
+public class TransactionResponseDto {
+
+ private UUID transactionExternalId;
+
+ private TransactionTypeDto transactionType;
+
+ private TransactionStatusDto transactionStatus;
+
+ private BigDecimal value;
+
+ private LocalDateTime createdAt;
+
+}
diff --git a/yape-financial/yape-antifraud/src/main/java/com/tec/yape/antifraud/infrastructure/rest/dto/response/TransactionStatusDto.java b/yape-financial/yape-antifraud/src/main/java/com/tec/yape/antifraud/infrastructure/rest/dto/response/TransactionStatusDto.java
new file mode 100644
index 0000000000..bfbe58df71
--- /dev/null
+++ b/yape-financial/yape-antifraud/src/main/java/com/tec/yape/antifraud/infrastructure/rest/dto/response/TransactionStatusDto.java
@@ -0,0 +1,16 @@
+package com.tec.yape.antifraud.infrastructure.rest.dto.response;
+
+import lombok.Getter;
+import lombok.Setter;
+
+@Setter
+@Getter
+public class TransactionStatusDto {
+
+ String name;
+
+ public TransactionStatusDto(String name) {
+ this.name = name;
+ }
+
+}
diff --git a/yape-financial/yape-antifraud/src/main/java/com/tec/yape/antifraud/infrastructure/rest/dto/response/TransactionTypeDto.java b/yape-financial/yape-antifraud/src/main/java/com/tec/yape/antifraud/infrastructure/rest/dto/response/TransactionTypeDto.java
new file mode 100644
index 0000000000..647447e8fd
--- /dev/null
+++ b/yape-financial/yape-antifraud/src/main/java/com/tec/yape/antifraud/infrastructure/rest/dto/response/TransactionTypeDto.java
@@ -0,0 +1,16 @@
+package com.tec.yape.antifraud.infrastructure.rest.dto.response;
+
+import lombok.Getter;
+import lombok.Setter;
+
+@Setter
+@Getter
+public class TransactionTypeDto {
+
+ String name;
+
+ public TransactionTypeDto(String name) {
+ this.name = name;
+ }
+
+}
diff --git a/yape-financial/yape-antifraud/src/main/resources/application.properties b/yape-financial/yape-antifraud/src/main/resources/application.properties
new file mode 100644
index 0000000000..8293ace2a1
--- /dev/null
+++ b/yape-financial/yape-antifraud/src/main/resources/application.properties
@@ -0,0 +1,8 @@
+server.tomcat.uri-encoding=UTF-8
+server.port=8082
+
+# settings KAFKA
+#spring.kafka.bootstrap-servers = localhost:9092
+spring.kafka.bootstrap-servers=kafka:9092
+
+
diff --git a/yape-financial/yape-antifraud/src/test/java/com/tec/antifraud/FraudValidationUseCaseTest.java b/yape-financial/yape-antifraud/src/test/java/com/tec/antifraud/FraudValidationUseCaseTest.java
new file mode 100644
index 0000000000..522c993588
--- /dev/null
+++ b/yape-financial/yape-antifraud/src/test/java/com/tec/antifraud/FraudValidationUseCaseTest.java
@@ -0,0 +1,51 @@
+package com.tec.antifraud;
+
+import com.tec.yape.antifraud.application.usecase.FraudValidationUseCase;
+import com.tec.yape.antifraud.domain.enums.TransactionStatus;
+import com.tec.yape.antifraud.domain.model.TransactionCreatedEvent;
+import com.tec.yape.antifraud.domain.port.output.TransactionStatusPublisherOutPort;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.verify;
+
+@ExtendWith(MockitoExtension.class)
+class FraudValidationUseCaseTest {
+
+ @Mock
+ private TransactionStatusPublisherOutPort publisher;
+
+ @InjectMocks
+ private FraudValidationUseCase useCase;
+
+ @Test
+ void shouldApproveTransactionWhenValueIsLessOrEqualThan1000() {
+ TransactionCreatedEvent event = new TransactionCreatedEvent(
+ UUID.randomUUID(),
+ new BigDecimal("1000")
+ );
+
+ useCase.validate(event);
+
+ verify(publisher).publish(argThat(e -> e.status().equals(TransactionStatus.APROBADO)));
+ }
+
+ @Test
+ void shouldRejectTransactionWhenValueIsGreaterThan1000() {
+ TransactionCreatedEvent event = new TransactionCreatedEvent(
+ UUID.randomUUID(),
+ new BigDecimal("1500")
+ );
+
+ useCase.validate(event);
+
+ verify(publisher).publish(argThat(e -> e.status().equals(TransactionStatus.RECHAZADO)));
+ }
+}
diff --git a/yape-financial/yape-antifraud/src/test/resources/application-test.properties b/yape-financial/yape-antifraud/src/test/resources/application-test.properties
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/yape-financial/yape-transaction/.gitignore b/yape-financial/yape-transaction/.gitignore
new file mode 100644
index 0000000000..5ff6309b71
--- /dev/null
+++ b/yape-financial/yape-transaction/.gitignore
@@ -0,0 +1,38 @@
+target/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+### IntelliJ IDEA ###
+.idea/modules.xml
+.idea/jarRepositories.xml
+.idea/compiler.xml
+.idea/libraries/
+*.iws
+*.iml
+*.ipr
+
+### Eclipse ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/
+
+### Mac OS ###
+.DS_Store
\ No newline at end of file
diff --git a/yape-financial/yape-transaction/pom.xml b/yape-financial/yape-transaction/pom.xml
new file mode 100644
index 0000000000..9dc16da926
--- /dev/null
+++ b/yape-financial/yape-transaction/pom.xml
@@ -0,0 +1,161 @@
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 3.2.5
+
+
+ com.tec.yape.transaction
+ yape-transaction
+ jar
+ 1.0-SNAPSHOT
+
+
+ 21
+ 21
+ UTF-8
+ 1.6.3
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.springframework.boot
+ spring-boot-starter-validation
+
+
+ org.springframework.boot
+ spring-boot-starter-data-jpa
+
+
+ org.postgresql
+ postgresql
+ runtime
+
+
+ org.projectlombok
+ lombok
+ true
+
+
+ org.mapstruct
+ mapstruct
+ ${org.mapstruct.version}
+
+
+ org.mapstruct
+ mapstruct-processor
+ ${org.mapstruct.version}
+ provided
+
+
+ org.springdoc
+ springdoc-openapi-starter-webmvc-ui
+ 2.3.0
+
+
+ org.springdoc
+ springdoc-openapi-data-rest
+ 1.6.4
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+
+
+ com.google.code.gson
+ gson
+ 2.8.9
+
+
+
+ org.springframework.boot
+ spring-boot-starter-data-redis
+
+
+ org.springframework.boot
+ spring-boot-starter-cache
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+
+
+ org.mockito
+ mockito-junit-jupiter
+ 5.11.0
+ test
+
+
+ org.springframework.retry
+ spring-retry
+ 1.3.1
+
+
+ org.springframework.kafka
+ spring-kafka
+
+
+ org.springframework.kafka
+ spring-kafka-test
+ test
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.11.0
+
+ 21
+
+ --enable-preview
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ 3.1.2
+
+ --enable-preview
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+ --enable-preview
+
+
+
+
+
+
\ No newline at end of file
diff --git a/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/YapeTransactionService.java b/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/YapeTransactionService.java
new file mode 100644
index 0000000000..a6f58b0f0c
--- /dev/null
+++ b/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/YapeTransactionService.java
@@ -0,0 +1,13 @@
+package com.tec.yape.transaction;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.scheduling.annotation.EnableAsync;
+
+@SpringBootApplication
+@EnableAsync
+public class YapeTransactionService {
+ public static void main(String[] args) {
+ SpringApplication.run(YapeTransactionService.class, args);
+ }
+}
\ No newline at end of file
diff --git a/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/application/mapper/TransactionUseCaseMapper.java b/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/application/mapper/TransactionUseCaseMapper.java
new file mode 100644
index 0000000000..8ac7987b51
--- /dev/null
+++ b/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/application/mapper/TransactionUseCaseMapper.java
@@ -0,0 +1,35 @@
+package com.tec.yape.transaction.application.mapper;
+
+import com.tec.yape.transaction.domain.model.TransactionRequest;
+import com.tec.yape.transaction.domain.model.TransactionResponse;
+import com.tec.yape.transaction.domain.model.TransactionStatus;
+import com.tec.yape.transaction.domain.model.TransactionType;
+import com.tec.yape.transaction.infrastructure.rest.dto.request.TransactionRequestDto;
+import com.tec.yape.transaction.infrastructure.rest.dto.response.TransactionResponseDto;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.mapstruct.ReportingPolicy;
+import org.mapstruct.factory.Mappers;
+
+@Mapper(componentModel = "spring", unmappedSourcePolicy = ReportingPolicy.IGNORE,
+ imports = {TransactionStatus.class, TransactionType.class})
+public interface TransactionUseCaseMapper {
+
+ TransactionUseCaseMapper MAPPER = Mappers.getMapper(TransactionUseCaseMapper.class);
+
+ TransactionRequest toTransactionRequest(TransactionRequestDto requestDto);
+
+
+ TransactionRequest toTransactionResponse(TransactionResponse transactionResponse);
+
+
+ @Mapping(
+ target = "transactionStatus",
+ expression = "java(new TransactionStatus(transaction.getTransactionStatus().name()))"
+ )
+ @Mapping(
+ target = "transactionType",
+ expression = "java(new TransactionType(\"TRANSFER\"))"
+ )
+ TransactionResponseDto toTransactionResponseDto(TransactionResponse transaction);
+}
diff --git a/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/application/usecase/TransactionUpdateUseCase.java b/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/application/usecase/TransactionUpdateUseCase.java
new file mode 100644
index 0000000000..c00c061910
--- /dev/null
+++ b/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/application/usecase/TransactionUpdateUseCase.java
@@ -0,0 +1,34 @@
+package com.tec.yape.transaction.application.usecase;
+
+import com.tec.yape.transaction.application.mapper.TransactionUseCaseMapper;
+import com.tec.yape.transaction.domain.model.TransactionRequest;
+import com.tec.yape.transaction.domain.model.TransactionResponse;
+import com.tec.yape.transaction.domain.model.TransactionValidatedEvent;
+import com.tec.yape.transaction.domain.port.input.TransactionUpdateInPort;
+import com.tec.yape.transaction.domain.port.output.TransactionOutPort;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+@Component
+@RequiredArgsConstructor
+@Slf4j
+public class TransactionUpdateUseCase implements TransactionUpdateInPort {
+
+ private final TransactionOutPort transactionOutPort;
+
+ @Override
+ public void updateTransactionStatus(TransactionValidatedEvent event) {
+ log.info("[TransactionUpdateUseCase] Updating transaction {}", event.transactionExternalId());
+
+ TransactionResponse responseDto = transactionOutPort.findByExternalId(event.transactionExternalId());
+
+ TransactionRequest transactionRequest = TransactionUseCaseMapper.MAPPER.toTransactionResponse(responseDto);
+
+ transactionRequest.setTransactionStatus(event.status());
+
+ transactionOutPort.update(transactionRequest);
+
+ log.info("[TransactionUpdateUseCase] Transaction {} updated to {}", transactionRequest.getTransactionExternalId(), transactionRequest.getTransactionStatus());
+ }
+}
\ No newline at end of file
diff --git a/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/application/usecase/TransactionUseCase.java b/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/application/usecase/TransactionUseCase.java
new file mode 100644
index 0000000000..5447ba8bd9
--- /dev/null
+++ b/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/application/usecase/TransactionUseCase.java
@@ -0,0 +1,73 @@
+package com.tec.yape.transaction.application.usecase;
+
+
+import com.tec.yape.transaction.application.mapper.TransactionUseCaseMapper;
+import com.tec.yape.transaction.domain.enums.TransactionStatus;
+import com.tec.yape.transaction.domain.model.TransactionRequest;
+import com.tec.yape.transaction.domain.model.TransactionCreatedEvent;
+import com.tec.yape.transaction.domain.model.TransactionResponse;
+import com.tec.yape.transaction.domain.port.output.TransactionEventPublisherOutPort;
+import com.tec.yape.transaction.infrastructure.rest.dto.request.TransactionRequestDto;
+import com.tec.yape.transaction.infrastructure.rest.dto.response.TransactionResponseDto;
+import com.tec.yape.transaction.domain.port.input.TransactionInPort;
+import com.tec.yape.transaction.domain.port.output.TransactionOutPort;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.stereotype.Component;
+
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.UUID;
+
+@Component
+@RequiredArgsConstructor
+@Slf4j
+public class TransactionUseCase implements TransactionInPort {
+
+ private final TransactionOutPort transactionOutPort;
+ private final TransactionEventPublisherOutPort eventPublisherOutPort;
+
+ @Override
+ public TransactionResponseDto registerTransaction(TransactionRequestDto request) {
+ log.info("[TransactionUseCase] start register transaction");
+
+ TransactionRequest transactionRequest = TransactionUseCaseMapper.MAPPER.toTransactionRequest(request);
+ transactionRequest.setTransactionExternalId(UUID.randomUUID());
+ transactionRequest.setCreatedAt(LocalDateTime.now());
+ transactionRequest.setTransactionStatus(TransactionStatus.PENDIENTE);
+
+ TransactionResponse transactionResponse = transactionOutPort.create(transactionRequest);
+
+ TransactionResponseDto response = TransactionUseCaseMapper.MAPPER.toTransactionResponseDto(transactionResponse);
+
+ TransactionCreatedEvent event = new TransactionCreatedEvent(
+ transactionResponse.getTransactionExternalId(),
+ transactionResponse.getValue()
+ );
+
+ eventPublisherOutPort.publish(event);
+ log.info("[TransactionUseCase] end register transaction");
+ return response;
+ }
+
+ @Override
+ public Page findAllPageable(Pageable pageable) {
+ Page page = transactionOutPort.findAllPageable(pageable);
+ return page.map(TransactionUseCaseMapper.MAPPER::toTransactionResponseDto);
+ }
+
+ @Override
+ public List findAll() {
+ List transactionResponse = transactionOutPort.findAll();
+ return transactionResponse.stream().map(TransactionUseCaseMapper.MAPPER::toTransactionResponseDto).toList();
+ }
+
+ @Override
+ public TransactionResponseDto findByExternalId(UUID transactionExternalId) {
+ TransactionResponse transactionResponse = transactionOutPort.findByExternalId(transactionExternalId);
+ return TransactionUseCaseMapper.MAPPER.toTransactionResponseDto(transactionResponse);
+ }
+
+}
diff --git a/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/domain/enums/TransactionStatus.java b/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/domain/enums/TransactionStatus.java
new file mode 100644
index 0000000000..2849499d02
--- /dev/null
+++ b/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/domain/enums/TransactionStatus.java
@@ -0,0 +1,7 @@
+package com.tec.yape.transaction.domain.enums;
+
+public enum TransactionStatus {
+ PENDIENTE,
+ APROBADO,
+ RECHAZADO
+}
\ No newline at end of file
diff --git a/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/domain/exception/CommonErrorType.java b/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/domain/exception/CommonErrorType.java
new file mode 100644
index 0000000000..f9a0cb9e8a
--- /dev/null
+++ b/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/domain/exception/CommonErrorType.java
@@ -0,0 +1,20 @@
+package com.tec.yape.transaction.domain.exception;
+
+import lombok.Getter;
+
+@Getter
+public enum CommonErrorType {
+
+ COMMON_ERROR_400_1("Error in the sorting criteria"),
+ COMMON_ERROR_400_2("Invalid request"),
+ COMMON_ERROR_400_3("Invalid format for field"),
+
+ COMMON_ERROR_404_1("Record not found");
+
+ private final String description;
+
+
+ CommonErrorType(String description) {
+ this.description = description;
+ }
+}
diff --git a/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/domain/exception/TransactionException.java b/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/domain/exception/TransactionException.java
new file mode 100644
index 0000000000..5b32418a55
--- /dev/null
+++ b/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/domain/exception/TransactionException.java
@@ -0,0 +1,19 @@
+package com.tec.yape.transaction.domain.exception;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.springframework.http.HttpStatus;
+
+@EqualsAndHashCode(callSuper = true)
+@Data
+public class TransactionException extends RuntimeException {
+
+ public HttpStatus status;
+ public String code;
+
+ public TransactionException(HttpStatus httpStatus, String code, String message) {
+ super(message);
+ this.status = httpStatus;
+ this.code = code;
+ }
+}
diff --git a/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/domain/model/TransactionCreatedEvent.java b/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/domain/model/TransactionCreatedEvent.java
new file mode 100644
index 0000000000..20a0e2cb6c
--- /dev/null
+++ b/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/domain/model/TransactionCreatedEvent.java
@@ -0,0 +1,9 @@
+package com.tec.yape.transaction.domain.model;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+
+public record TransactionCreatedEvent(
+ UUID transactionExternalId,
+ BigDecimal value
+) {}
\ No newline at end of file
diff --git a/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/domain/model/TransactionRequest.java b/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/domain/model/TransactionRequest.java
new file mode 100644
index 0000000000..4dd291d25a
--- /dev/null
+++ b/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/domain/model/TransactionRequest.java
@@ -0,0 +1,24 @@
+package com.tec.yape.transaction.domain.model;
+
+import com.tec.yape.transaction.domain.enums.TransactionStatus;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.util.UUID;
+
+@Getter
+@Setter
+public class TransactionRequest {
+
+ private UUID id;
+ private UUID transactionExternalId;
+ private UUID accountExternalIdDebit;
+ private UUID accountExternalIdCredit;
+ private Integer transferTypeId;
+ private BigDecimal value;
+ private TransactionStatus transactionStatus;
+ private LocalDateTime createdAt;
+
+}
\ No newline at end of file
diff --git a/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/domain/model/TransactionResponse.java b/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/domain/model/TransactionResponse.java
new file mode 100644
index 0000000000..824b60d5b9
--- /dev/null
+++ b/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/domain/model/TransactionResponse.java
@@ -0,0 +1,24 @@
+package com.tec.yape.transaction.domain.model;
+
+import com.tec.yape.transaction.domain.enums.TransactionStatus;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.util.UUID;
+
+@Getter
+@Setter
+public class TransactionResponse {
+
+ private UUID id;
+ private UUID transactionExternalId;
+ private UUID accountExternalIdDebit;
+ private UUID accountExternalIdCredit;
+ private Integer transferTypeId;
+ private BigDecimal value;
+ private TransactionStatus transactionStatus;
+ private LocalDateTime createdAt;
+
+}
\ No newline at end of file
diff --git a/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/domain/model/TransactionStatus.java b/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/domain/model/TransactionStatus.java
new file mode 100644
index 0000000000..dc60bf37dc
--- /dev/null
+++ b/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/domain/model/TransactionStatus.java
@@ -0,0 +1,5 @@
+package com.tec.yape.transaction.domain.model;
+
+
+public record TransactionStatus(String name) {
+}
diff --git a/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/domain/model/TransactionType.java b/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/domain/model/TransactionType.java
new file mode 100644
index 0000000000..08255024c6
--- /dev/null
+++ b/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/domain/model/TransactionType.java
@@ -0,0 +1,4 @@
+package com.tec.yape.transaction.domain.model;
+
+public record TransactionType(String name) {
+}
diff --git a/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/domain/model/TransactionValidatedEvent.java b/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/domain/model/TransactionValidatedEvent.java
new file mode 100644
index 0000000000..9965e3d70d
--- /dev/null
+++ b/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/domain/model/TransactionValidatedEvent.java
@@ -0,0 +1,10 @@
+package com.tec.yape.transaction.domain.model;
+
+import com.tec.yape.transaction.domain.enums.TransactionStatus;
+
+import java.util.UUID;
+
+public record TransactionValidatedEvent(
+ UUID transactionExternalId,
+ TransactionStatus status) {
+}
diff --git a/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/domain/port/input/TransactionInPort.java b/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/domain/port/input/TransactionInPort.java
new file mode 100644
index 0000000000..5d68262f30
--- /dev/null
+++ b/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/domain/port/input/TransactionInPort.java
@@ -0,0 +1,24 @@
+package com.tec.yape.transaction.domain.port.input;
+
+import com.tec.yape.transaction.infrastructure.rest.dto.request.TransactionRequestDto;
+import com.tec.yape.transaction.infrastructure.rest.dto.response.TransactionResponseDto;
+import org.springdoc.api.annotations.ParameterObject;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+
+import java.util.List;
+import java.util.UUID;
+
+public interface TransactionInPort {
+
+ TransactionResponseDto registerTransaction(TransactionRequestDto transactionRequestDto);
+
+ Page findAllPageable(
+ @ParameterObject Pageable pageable);
+
+ List findAll();
+
+
+ TransactionResponseDto findByExternalId(UUID transactionExternalId);
+
+}
diff --git a/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/domain/port/input/TransactionUpdateInPort.java b/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/domain/port/input/TransactionUpdateInPort.java
new file mode 100644
index 0000000000..4aa37bd5f3
--- /dev/null
+++ b/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/domain/port/input/TransactionUpdateInPort.java
@@ -0,0 +1,7 @@
+package com.tec.yape.transaction.domain.port.input;
+
+import com.tec.yape.transaction.domain.model.TransactionValidatedEvent;
+
+public interface TransactionUpdateInPort {
+ void updateTransactionStatus(TransactionValidatedEvent event);
+}
\ No newline at end of file
diff --git a/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/domain/port/output/TransactionEventPublisherOutPort.java b/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/domain/port/output/TransactionEventPublisherOutPort.java
new file mode 100644
index 0000000000..32c8ae736c
--- /dev/null
+++ b/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/domain/port/output/TransactionEventPublisherOutPort.java
@@ -0,0 +1,8 @@
+package com.tec.yape.transaction.domain.port.output;
+
+import com.tec.yape.transaction.domain.model.TransactionCreatedEvent;
+
+public interface TransactionEventPublisherOutPort {
+
+ void publish(TransactionCreatedEvent transaction);
+}
diff --git a/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/domain/port/output/TransactionOutPort.java b/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/domain/port/output/TransactionOutPort.java
new file mode 100644
index 0000000000..f8e4e89fb3
--- /dev/null
+++ b/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/domain/port/output/TransactionOutPort.java
@@ -0,0 +1,22 @@
+package com.tec.yape.transaction.domain.port.output;
+
+import com.tec.yape.transaction.domain.model.TransactionRequest;
+import com.tec.yape.transaction.domain.model.TransactionResponse;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+
+import java.util.List;
+import java.util.UUID;
+
+public interface TransactionOutPort {
+
+ Page findAllPageable(Pageable pageable);
+
+ List findAll();
+
+ TransactionResponse create(TransactionRequest transactionRequest);
+
+ void update(TransactionRequest transactionRequest);
+
+ TransactionResponse findByExternalId(UUID transactionExternalId);
+}
diff --git a/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/domain/util/BeanConstants.java b/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/domain/util/BeanConstants.java
new file mode 100644
index 0000000000..88818bba51
--- /dev/null
+++ b/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/domain/util/BeanConstants.java
@@ -0,0 +1,8 @@
+package com.tec.yape.transaction.domain.util;
+
+public class BeanConstants {
+
+ public static final String ASYNC_SAVE_CALL_HISTORY = "asyncSaveCallHistory";
+
+ public static final String ASYNC_VIRTUAL_SAVE_CALL_HISTORY = "asyncVirtualSaveCallHistory";
+}
diff --git a/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/domain/util/JsonUtil.java b/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/domain/util/JsonUtil.java
new file mode 100644
index 0000000000..79cb80cdc6
--- /dev/null
+++ b/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/domain/util/JsonUtil.java
@@ -0,0 +1,40 @@
+package com.tec.yape.transaction.domain.util;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializationFeature;
+import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
+
+public class JsonUtil {
+
+
+ private JsonUtil() {
+
+ }
+
+ public static String ToJSON(Object object) throws JsonProcessingException {
+ ObjectMapper mapper = new ObjectMapper();
+ mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+ mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
+ mapper.registerModule(new Jdk8Module());
+ mapper.registerModule(new JavaTimeModule());
+ return mapper.writeValueAsString(object);
+ }
+
+ public static T fromJson(String message, Class tClass) {
+ ObjectMapper mapper = new ObjectMapper();
+ mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+ mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
+ mapper.registerModule(new Jdk8Module());
+ mapper.registerModule(new JavaTimeModule());
+
+ try {
+ return mapper.readValue(message, tClass);
+ } catch (JsonProcessingException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+}
diff --git a/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/infrastructure/adapter/TransactionAdapter.java b/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/infrastructure/adapter/TransactionAdapter.java
new file mode 100644
index 0000000000..1951ca3577
--- /dev/null
+++ b/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/infrastructure/adapter/TransactionAdapter.java
@@ -0,0 +1,74 @@
+package com.tec.yape.transaction.infrastructure.adapter;
+
+
+import com.tec.yape.transaction.domain.exception.CommonErrorType;
+import com.tec.yape.transaction.domain.exception.TransactionException;
+import com.tec.yape.transaction.domain.model.TransactionRequest;
+import com.tec.yape.transaction.domain.model.TransactionResponse;
+import com.tec.yape.transaction.domain.port.output.TransactionOutPort;
+import com.tec.yape.transaction.infrastructure.helper.TransactionHelper;
+import com.tec.yape.transaction.infrastructure.repository.TransactionRepository;
+import com.tec.yape.transaction.infrastructure.repository.model.entity.TransactionEntity;
+import com.tec.yape.transaction.infrastructure.repository.model.mapper.TransactionMapper;
+import lombok.RequiredArgsConstructor;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.domain.Sort;
+import org.springframework.http.HttpStatus;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+import java.util.UUID;
+
+
+@Component
+@RequiredArgsConstructor
+public class TransactionAdapter implements TransactionOutPort {
+
+ private final TransactionRepository transactionRepository;
+
+ @Override
+ public Page findAllPageable(Pageable pageable) {
+
+ Sort sort = pageable.getSort().isUnsorted() ? Sort.by("id") : pageable.getSort();
+ if (!TransactionHelper.validateSorName(sort)) {
+ throw new TransactionException(HttpStatus.BAD_REQUEST, CommonErrorType.COMMON_ERROR_400_1.name(), CommonErrorType.COMMON_ERROR_400_1.getDescription());
+ }
+
+ return transactionRepository.findAll(PageRequest.of(pageable.getPageNumber(), pageable.getPageSize(), sort))
+ .map(TransactionMapper.MAPPER::toTransactionResponse);
+ }
+
+ @Override
+ public List findAll() {
+ return transactionRepository.findAll().stream()
+ .map(TransactionMapper.MAPPER::toTransactionResponse).toList();
+ }
+
+
+ @Override
+ public TransactionResponse create(TransactionRequest transactionRequest) {
+ TransactionEntity transactionEntity = TransactionMapper.MAPPER.toTransaction(transactionRequest);
+ return TransactionMapper.MAPPER.toTransactionResponse(transactionRepository.save(transactionEntity));
+ }
+
+ @Override
+ public void update(TransactionRequest transactionRequest) {
+ TransactionEntity transactionEntity = TransactionMapper.MAPPER.toTransaction(transactionRequest);
+ if (transactionEntity == null) {
+ throw new TransactionException(HttpStatus.NOT_FOUND, CommonErrorType.COMMON_ERROR_404_1.name(), CommonErrorType.COMMON_ERROR_404_1.getDescription());
+ }
+ TransactionMapper.MAPPER.toTransactionResponse(transactionRepository.save(transactionEntity));
+ }
+
+ @Override
+ public TransactionResponse findByExternalId(UUID transactionExternalId) {
+ TransactionEntity transactionEntity = transactionRepository.findByTransactionExternalId(transactionExternalId);
+ if (transactionEntity == null) {
+ throw new TransactionException(HttpStatus.NOT_FOUND, CommonErrorType.COMMON_ERROR_404_1.name(), CommonErrorType.COMMON_ERROR_404_1.getDescription());
+ }
+
+ return TransactionMapper.MAPPER.toTransactionResponse(transactionEntity);
+ }
+}
diff --git a/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/infrastructure/adapter/TransactionPublisherAdapter.java b/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/infrastructure/adapter/TransactionPublisherAdapter.java
new file mode 100644
index 0000000000..b78116453d
--- /dev/null
+++ b/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/infrastructure/adapter/TransactionPublisherAdapter.java
@@ -0,0 +1,21 @@
+package com.tec.yape.transaction.infrastructure.adapter;
+
+import com.tec.yape.transaction.domain.model.TransactionCreatedEvent;
+import com.tec.yape.transaction.domain.port.output.TransactionEventPublisherOutPort;
+import com.tec.yape.transaction.infrastructure.messaging.producer.TransactionEventPublisher;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+@Component
+@Slf4j
+@RequiredArgsConstructor
+public class TransactionPublisherAdapter implements TransactionEventPublisherOutPort {
+
+ private final TransactionEventPublisher transactionEventPublisher;
+
+ @Override
+ public void publish(TransactionCreatedEvent transaction) {
+ transactionEventPublisher.publish(transaction);
+ }
+}
diff --git a/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/infrastructure/config/ExecutorAsyncVirtualConfig.java b/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/infrastructure/config/ExecutorAsyncVirtualConfig.java
new file mode 100644
index 0000000000..9ef30ef76a
--- /dev/null
+++ b/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/infrastructure/config/ExecutorAsyncVirtualConfig.java
@@ -0,0 +1,19 @@
+package com.tec.yape.transaction.infrastructure.config;
+
+import com.tec.yape.transaction.domain.util.BeanConstants;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+
+@Configuration
+public class ExecutorAsyncVirtualConfig {
+
+ @Bean(name = BeanConstants.ASYNC_VIRTUAL_SAVE_CALL_HISTORY)
+ public Executor asyncVirtualSaveCallHistory() {
+ return Executors.newThreadPerTaskExecutor(
+ Thread.ofVirtual().name("virtual-thread-", 0)::unstarted
+ );
+ }
+}
diff --git a/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/infrastructure/config/GlobalExceptionHandler.java b/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/infrastructure/config/GlobalExceptionHandler.java
new file mode 100644
index 0000000000..288f7ba024
--- /dev/null
+++ b/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/infrastructure/config/GlobalExceptionHandler.java
@@ -0,0 +1,68 @@
+package com.tec.yape.transaction.infrastructure.config;
+
+import com.fasterxml.jackson.databind.JsonMappingException;
+import com.fasterxml.jackson.databind.exc.InvalidFormatException;
+import com.tec.yape.transaction.domain.exception.CommonErrorType;
+import com.tec.yape.transaction.domain.exception.TransactionException;
+import com.tec.yape.transaction.infrastructure.config.dto.ErrorDto;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.http.converter.HttpMessageNotReadableException;
+import org.springframework.web.bind.MethodArgumentNotValidException;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+
+@RestControllerAdvice
+public class GlobalExceptionHandler {
+
+ @ExceptionHandler(value = TransactionException.class)
+ public ResponseEntity businessExceptionHandler(TransactionException ex) {
+ ErrorDto error = ErrorDto.builder()
+ .status(ex.getStatus())
+ .code(ex.getCode()).message(ex.getMessage()).build();
+ return new ResponseEntity<>(error, ex.getStatus());
+ }
+
+ @ExceptionHandler(MethodArgumentNotValidException.class)
+ public ResponseEntity validationExceptionHandler(
+ MethodArgumentNotValidException ex) {
+
+ String message = ex.getBindingResult()
+ .getFieldErrors()
+ .stream()
+ .map(error -> error.getField() + ": " + error.getDefaultMessage())
+ .findFirst()
+ .orElse(CommonErrorType.COMMON_ERROR_400_2.getDescription());
+
+ ErrorDto error = ErrorDto.builder()
+ .status(HttpStatus.BAD_REQUEST)
+ .code( CommonErrorType.COMMON_ERROR_400_1.name())
+ .message(message)
+ .build();
+
+ return ResponseEntity.badRequest().body(error);
+ }
+
+ @ExceptionHandler(HttpMessageNotReadableException.class)
+ public ResponseEntity handleInvalidFormat(HttpMessageNotReadableException ex) {
+
+ String message = "Malformed JSON request";
+
+ if (ex.getCause() instanceof InvalidFormatException invalidFormat) {
+ String fieldName = invalidFormat.getPath().stream()
+ .map(JsonMappingException.Reference::getFieldName)
+ .findFirst()
+ .orElse("unknown");
+
+ message = String.format(CommonErrorType.COMMON_ERROR_400_3.getDescription()+ " '%s'", fieldName);
+ }
+
+ ErrorDto error = ErrorDto.builder()
+ .status(HttpStatus.BAD_REQUEST)
+ .code(CommonErrorType.COMMON_ERROR_400_3.name())
+ .message(message)
+ .build();
+
+ return ResponseEntity.badRequest().body(error);
+ }
+}
diff --git a/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/infrastructure/config/OpenApicConfig.java b/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/infrastructure/config/OpenApicConfig.java
new file mode 100644
index 0000000000..be3bf00da9
--- /dev/null
+++ b/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/infrastructure/config/OpenApicConfig.java
@@ -0,0 +1,35 @@
+package com.tec.yape.transaction.infrastructure.config;
+
+import io.swagger.v3.oas.annotations.OpenAPIDefinition;
+import io.swagger.v3.oas.annotations.enums.SecuritySchemeType;
+import io.swagger.v3.oas.annotations.info.Contact;
+import io.swagger.v3.oas.annotations.info.Info;
+import io.swagger.v3.oas.annotations.info.License;
+import io.swagger.v3.oas.annotations.security.SecurityScheme;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+@OpenAPIDefinition(
+ info = @Info(
+ title = "API-REST TRANSACTION",
+ version = "1.0",
+
+ description = "Api Rest para transacciones",
+
+ license = @License(
+ name = "Apache 2.0",
+ url = "http://www.apache.org/licenses/LICENSE-2.0.html"
+ ),
+
+ contact = @Contact(
+ name = "evercarlos.com",
+ url = "evercarlos.com",
+ email = "evercarlosrojas@gmail.com")
+ )
+)
+@SecurityScheme(name = "bearerAuth", type = SecuritySchemeType.HTTP, bearerFormat = "JWT", description = "Autenticación" +
+ "tipo Bearer API-TC", scheme = "bearer")
+public class OpenApicConfig {
+
+
+}
diff --git a/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/infrastructure/config/dto/ErrorDto.java b/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/infrastructure/config/dto/ErrorDto.java
new file mode 100644
index 0000000000..7943aa2499
--- /dev/null
+++ b/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/infrastructure/config/dto/ErrorDto.java
@@ -0,0 +1,13 @@
+package com.tec.yape.transaction.infrastructure.config.dto;
+
+import lombok.Builder;
+import lombok.Data;
+import org.springframework.http.HttpStatus;
+
+@Data
+@Builder
+public class ErrorDto {
+ private String code;
+ private String message;
+ private HttpStatus status;
+}
diff --git a/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/infrastructure/config/kafka/consumer/KafkaConsumerConfig.java b/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/infrastructure/config/kafka/consumer/KafkaConsumerConfig.java
new file mode 100644
index 0000000000..c4f1a0045c
--- /dev/null
+++ b/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/infrastructure/config/kafka/consumer/KafkaConsumerConfig.java
@@ -0,0 +1,44 @@
+package com.tec.yape.transaction.infrastructure.config.kafka.consumer;
+
+import org.apache.kafka.clients.consumer.ConsumerConfig;
+import org.apache.kafka.common.serialization.StringSerializer;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory;
+import org.springframework.kafka.config.KafkaListenerContainerFactory;
+import org.springframework.kafka.core.ConsumerFactory;
+import org.springframework.kafka.core.DefaultKafkaConsumerFactory;
+import org.springframework.kafka.listener.ConcurrentMessageListenerContainer;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@Configuration
+public class KafkaConsumerConfig {
+
+ @Value("${spring.kafka.bootstrap-servers}")
+ private String boostrapServers;
+
+
+ public Map consumerConfig() {
+ Map properties = new HashMap<>();
+ properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, boostrapServers);
+ properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringSerializer.class);// deserializa: convirte de string a bytes
+ properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringSerializer.class);// deserializa
+ return properties;
+ }
+
+ @Bean
+ public ConsumerFactory consumerFactory() {
+ return new DefaultKafkaConsumerFactory<>(consumerConfig());
+ }
+
+ @Bean // Para poder inyectar en otros lugares
+ public KafkaListenerContainerFactory> consumer() {
+ ConcurrentKafkaListenerContainerFactory factory = new ConcurrentKafkaListenerContainerFactory<>();
+ factory.setConsumerFactory(consumerFactory());
+ return factory;
+ }
+
+}
diff --git a/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/infrastructure/config/kafka/producer/KafkaProducerConfig.java b/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/infrastructure/config/kafka/producer/KafkaProducerConfig.java
new file mode 100644
index 0000000000..c244942525
--- /dev/null
+++ b/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/infrastructure/config/kafka/producer/KafkaProducerConfig.java
@@ -0,0 +1,41 @@
+package com.tec.yape.transaction.infrastructure.config.kafka.producer;
+
+import org.apache.kafka.clients.producer.ProducerConfig;
+import org.apache.kafka.common.serialization.StringSerializer;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.kafka.core.DefaultKafkaProducerFactory;
+import org.springframework.kafka.core.KafkaTemplate;
+import org.springframework.kafka.core.ProducerFactory;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@Configuration
+public class KafkaProducerConfig {
+
+ @Value("${spring.kafka.bootstrap-servers}")
+ private String boostrapServers;
+
+
+ public Map producerConfig() {
+ Map properties = new HashMap<>();
+ properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, boostrapServers);
+ properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);// serializa: convirte de string a bytes
+ properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class);// serializa
+ return properties;
+ }
+
+ @Bean
+ public ProducerFactory providerFactory() {
+ return new DefaultKafkaProducerFactory<>(producerConfig());
+ }
+
+ // Envian mensaje
+ @Bean
+ public KafkaTemplate kafkaTemplate(ProducerFactory providerFactory) {// inyectando
+ return new KafkaTemplate<>(providerFactory);
+ }
+
+}
diff --git a/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/infrastructure/helper/TransactionHelper.java b/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/infrastructure/helper/TransactionHelper.java
new file mode 100644
index 0000000000..64637172a3
--- /dev/null
+++ b/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/infrastructure/helper/TransactionHelper.java
@@ -0,0 +1,19 @@
+package com.tec.yape.transaction.infrastructure.helper;
+
+import org.springframework.data.domain.Sort;
+
+public class TransactionHelper {
+
+ private TransactionHelper() {
+
+ }
+
+ public static boolean validateSorName(Sort sort) {
+ if (sort.isSorted()) {
+ return !sort.iterator().next().getProperty().equals("string");
+ } else {
+ System.out.println("No sort criteria applied");
+ return true;
+ }
+ }
+}
diff --git a/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/infrastructure/messaging/consumer/TransactionValidatedConsumer.java b/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/infrastructure/messaging/consumer/TransactionValidatedConsumer.java
new file mode 100644
index 0000000000..a2a59e6392
--- /dev/null
+++ b/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/infrastructure/messaging/consumer/TransactionValidatedConsumer.java
@@ -0,0 +1,32 @@
+package com.tec.yape.transaction.infrastructure.messaging.consumer;
+
+import com.tec.yape.transaction.domain.model.TransactionValidatedEvent;
+import com.tec.yape.transaction.domain.port.input.TransactionUpdateInPort;
+import com.tec.yape.transaction.domain.util.JsonUtil;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.kafka.annotation.KafkaListener;
+import org.springframework.messaging.handler.annotation.Headers;
+import org.springframework.stereotype.Component;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Map;
+
+@Component
+@Slf4j
+@RequiredArgsConstructor
+public class TransactionValidatedConsumer {
+
+ private final TransactionUpdateInPort transactionUpdateInPort;
+
+ @KafkaListener(topics = "topic-transaction-validated", groupId = "transaction-service-group")
+ public void listener(String message, @Headers Map headers) {
+ log.info("[TransactionValidatedConsumer] Event received: {}", message);
+
+ String jsonMessage = new String(message.getBytes(), StandardCharsets.UTF_8);
+
+ TransactionValidatedEvent event = JsonUtil.fromJson(jsonMessage, TransactionValidatedEvent.class);
+
+ transactionUpdateInPort.updateTransactionStatus(event);
+ }
+}
diff --git a/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/infrastructure/messaging/producer/TransactionEventPublisher.java b/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/infrastructure/messaging/producer/TransactionEventPublisher.java
new file mode 100644
index 0000000000..c85adc3660
--- /dev/null
+++ b/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/infrastructure/messaging/producer/TransactionEventPublisher.java
@@ -0,0 +1,8 @@
+package com.tec.yape.transaction.infrastructure.messaging.producer;
+
+import com.tec.yape.transaction.domain.model.TransactionCreatedEvent;
+
+public interface TransactionEventPublisher {
+
+ void publish(TransactionCreatedEvent message);
+}
diff --git a/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/infrastructure/messaging/producer/TransactionEventPublisherImpl.java b/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/infrastructure/messaging/producer/TransactionEventPublisherImpl.java
new file mode 100644
index 0000000000..0258974f91
--- /dev/null
+++ b/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/infrastructure/messaging/producer/TransactionEventPublisherImpl.java
@@ -0,0 +1,52 @@
+package com.tec.yape.transaction.infrastructure.messaging.producer;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.tec.yape.transaction.domain.model.TransactionCreatedEvent;
+import com.tec.yape.transaction.domain.util.JsonUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.kafka.core.KafkaTemplate;
+import org.springframework.kafka.support.KafkaHeaders;
+import org.springframework.messaging.Message;
+import org.springframework.messaging.support.MessageBuilder;
+import org.springframework.stereotype.Component;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@Component
+@Slf4j
+public class TransactionEventPublisherImpl implements TransactionEventPublisher {
+
+ private final KafkaTemplate kafkaTemplate;
+
+ public TransactionEventPublisherImpl(KafkaTemplate kafkaTemplate) {
+ this.kafkaTemplate = kafkaTemplate;
+ }
+
+ @Override
+ public void publish(TransactionCreatedEvent event) {
+ log.info("[TransactionEventPublisherImpl] {}", "Start async process");
+
+ try {
+ String message = JsonUtil.ToJSON(event);
+
+ Message kafkaMessage = MessageBuilder
+ .withPayload(message)
+ .setHeader(KafkaHeaders.TOPIC, "topic-transaction")
+ .copyHeaders(getHeaders())
+ .build();
+
+ kafkaTemplate.send(kafkaMessage);
+
+ } catch (JsonProcessingException e) {
+ log.error("[TransactionEventPublisherImpl] message:{}", e.getMessage());
+ }
+ }
+
+ private Map getHeaders() {
+ Map headers = new HashMap<>();
+ headers.put("client-id", "EVER CARLOS ROJAS");
+ return headers;
+ }
+
+}
diff --git a/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/infrastructure/repository/TransactionRepository.java b/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/infrastructure/repository/TransactionRepository.java
new file mode 100644
index 0000000000..16358f2754
--- /dev/null
+++ b/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/infrastructure/repository/TransactionRepository.java
@@ -0,0 +1,14 @@
+package com.tec.yape.transaction.infrastructure.repository;
+
+import com.tec.yape.transaction.infrastructure.repository.model.entity.TransactionEntity;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Query;
+
+import java.util.UUID;
+
+public interface TransactionRepository extends JpaRepository {
+
+ @Query("select t from TransactionEntity t where t.transactionExternalId=:transactionExternalId")
+ TransactionEntity findByTransactionExternalId(UUID transactionExternalId);
+}
+
diff --git a/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/infrastructure/repository/model/entity/TransactionEntity.java b/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/infrastructure/repository/model/entity/TransactionEntity.java
new file mode 100644
index 0000000000..a0757268c7
--- /dev/null
+++ b/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/infrastructure/repository/model/entity/TransactionEntity.java
@@ -0,0 +1,36 @@
+package com.tec.yape.transaction.infrastructure.repository.model.entity;
+
+
+import com.tec.yape.transaction.domain.enums.TransactionStatus;
+import jakarta.persistence.*;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.util.UUID;
+
+
+@Entity
+@Data
+@Table(name = "transacciones")
+public class TransactionEntity {
+
+ @Id
+ @GeneratedValue
+ private UUID id;
+
+ private UUID transactionExternalId;
+
+ private UUID accountExternalIdDebit;
+
+ private UUID accountExternalIdCredit;
+
+ private Integer transferTypeId;
+
+ private BigDecimal value;
+
+ @Enumerated(EnumType.STRING)
+ private TransactionStatus transactionStatus;
+
+ private LocalDateTime createdAt;
+}
diff --git a/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/infrastructure/repository/model/mapper/TransactionMapper.java b/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/infrastructure/repository/model/mapper/TransactionMapper.java
new file mode 100644
index 0000000000..bde705dc31
--- /dev/null
+++ b/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/infrastructure/repository/model/mapper/TransactionMapper.java
@@ -0,0 +1,22 @@
+package com.tec.yape.transaction.infrastructure.repository.model.mapper;
+
+import com.tec.yape.transaction.domain.model.TransactionRequest;
+import com.tec.yape.transaction.domain.model.TransactionResponse;
+import com.tec.yape.transaction.domain.model.TransactionStatus;
+import com.tec.yape.transaction.domain.model.TransactionType;
+import com.tec.yape.transaction.infrastructure.repository.model.entity.TransactionEntity;
+import org.mapstruct.Mapper;
+import org.mapstruct.ReportingPolicy;
+import org.mapstruct.factory.Mappers;
+
+@Mapper(componentModel = "spring", unmappedSourcePolicy = ReportingPolicy.IGNORE,
+ imports = {TransactionStatus.class, TransactionType.class})
+public interface TransactionMapper {
+
+ TransactionMapper MAPPER = Mappers.getMapper(TransactionMapper.class);
+
+ TransactionEntity toTransaction(TransactionRequest transactionRequest);
+
+ TransactionResponse toTransactionResponse(TransactionEntity transactionEntity);
+
+}
diff --git a/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/infrastructure/rest/controller/IndexController.java b/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/infrastructure/rest/controller/IndexController.java
new file mode 100644
index 0000000000..5c0c70a26d
--- /dev/null
+++ b/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/infrastructure/rest/controller/IndexController.java
@@ -0,0 +1,13 @@
+package com.tec.yape.transaction.infrastructure.rest.controller;
+
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.RequestMapping;
+
+@Controller
+public class IndexController {
+
+ @RequestMapping("/")
+ public String getIndex(){
+ return "redirect:swagger-ui.html";
+ }
+}
\ No newline at end of file
diff --git a/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/infrastructure/rest/controller/v1/TransactionController.java b/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/infrastructure/rest/controller/v1/TransactionController.java
new file mode 100644
index 0000000000..5519b2dd4e
--- /dev/null
+++ b/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/infrastructure/rest/controller/v1/TransactionController.java
@@ -0,0 +1,56 @@
+package com.tec.yape.transaction.infrastructure.rest.controller.v1;
+
+import com.tec.yape.transaction.application.usecase.TransactionUseCase;
+import com.tec.yape.transaction.infrastructure.rest.dto.request.TransactionRequestDto;
+import com.tec.yape.transaction.infrastructure.rest.dto.response.TransactionResponseDto;
+import io.swagger.v3.oas.annotations.Operation;
+import jakarta.validation.Valid;
+import lombok.RequiredArgsConstructor;
+import org.springdoc.api.annotations.ParameterObject;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+import java.util.UUID;
+
+
+@RestController
+@RequestMapping(value = "/api/v1/transaction", produces = "application/json")
+@CrossOrigin("*")
+@RequiredArgsConstructor
+public class TransactionController {
+
+ private final TransactionUseCase transactionUseCase;
+
+
+ @Operation(
+ summary = "Register a new transaction",
+ description = "Creates a transaction and sends it to antifraud validation"
+ )
+ @PostMapping
+ public TransactionResponseDto registerTransaction( @Valid @RequestBody TransactionRequestDto transactionRequestDto) {
+ return transactionUseCase.registerTransaction(transactionRequestDto);
+ }
+
+ @Operation(summary = "List transactions", description = "Method order: \"id,asc\"")
+ @GetMapping("withPagination")
+ public Page findAllPageable(
+ @ParameterObject Pageable pageable) {
+ return transactionUseCase.findAllPageable(pageable);
+ }
+
+ @Operation(summary = "List transactions")
+ @GetMapping()
+ public List findAll() {
+ return transactionUseCase.findAll();
+ }
+
+ @Operation(summary = "Find transaction by ExternalId")
+ @GetMapping("/{transactionExternalId}")
+ public TransactionResponseDto findByExternalId(
+ @PathVariable UUID transactionExternalId) {
+ return transactionUseCase.findByExternalId(transactionExternalId);
+ }
+
+}
diff --git a/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/infrastructure/rest/dto/request/TransactionRequestDto.java b/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/infrastructure/rest/dto/request/TransactionRequestDto.java
new file mode 100644
index 0000000000..961e818a0d
--- /dev/null
+++ b/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/infrastructure/rest/dto/request/TransactionRequestDto.java
@@ -0,0 +1,32 @@
+package com.tec.yape.transaction.infrastructure.rest.dto.request;
+
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+
+@Getter
+@Setter
+public class TransactionRequestDto {
+
+ @NotNull(message = "accountExternalIdDebit is required")
+ private UUID accountExternalIdDebit;
+
+ @NotNull(message = "accountExternalIdCredit is required")
+ private UUID accountExternalIdCredit;
+
+ @NotNull(message = "transferTypeId is required")
+ @Schema(description = "Transfer type identifier", example = "1", defaultValue = "1")
+ @JsonProperty("tranferTypeId")
+ private Integer transferTypeId;
+
+ @NotNull(message = "value is required")
+ private BigDecimal value;
+
+
+}
diff --git a/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/infrastructure/rest/dto/response/TransactionResponseDto.java b/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/infrastructure/rest/dto/response/TransactionResponseDto.java
new file mode 100644
index 0000000000..364e5a60e2
--- /dev/null
+++ b/yape-financial/yape-transaction/src/main/java/com/tec/yape/transaction/infrastructure/rest/dto/response/TransactionResponseDto.java
@@ -0,0 +1,26 @@
+package com.tec.yape.transaction.infrastructure.rest.dto.response;
+
+import com.tec.yape.transaction.domain.model.TransactionStatus;
+import com.tec.yape.transaction.domain.model.TransactionType;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.util.UUID;
+
+@Setter
+@Getter
+public class TransactionResponseDto {
+
+ private UUID transactionExternalId;
+
+ private TransactionType transactionType;
+
+ private TransactionStatus transactionStatus;
+
+ private BigDecimal value;
+
+ private LocalDateTime createdAt;
+
+}
diff --git a/yape-financial/yape-transaction/src/main/resources/application.properties b/yape-financial/yape-transaction/src/main/resources/application.properties
new file mode 100644
index 0000000000..eed9ce1ef7
--- /dev/null
+++ b/yape-financial/yape-transaction/src/main/resources/application.properties
@@ -0,0 +1,22 @@
+server.tomcat.uri-encoding=UTF-8
+server.port=8081
+
+#Datasource
+spring.datasource.url=jdbc:postgresql://192.168.18.179:5432/dbtransactionv1
+#spring.datasource.username=postgres
+spring.datasource.username=ever
+spring.datasource.password=123
+
+spring.datasource.driverClassName=org.postgresql.Driver
+spring.jpa.show-sql=false
+spring.jpa.hibernate.ddl-auto=none
+spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
+spring.jpa.properties.hibernate.current_session_context_class=thread
+spring.jpa.properties.hibernate.implicit_naming_strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyJpaImpl
+spring.jpa.properties.hibernate.temp.use_jdbc_metadata_defaults= false
+spring.jpa.properties.hibernate.temp.use_nationalized_character_data= true
+spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect
+
+# settings KAFKA
+#spring.kafka.bootstrap-servers = localhost:9092
+spring.kafka.bootstrap-servers=kafka:9092
diff --git a/yape-financial/yape-transaction/src/test/java/com/tec/transaction/AbstractContextTest.java b/yape-financial/yape-transaction/src/test/java/com/tec/transaction/AbstractContextTest.java
new file mode 100644
index 0000000000..ea50b1382c
--- /dev/null
+++ b/yape-financial/yape-transaction/src/test/java/com/tec/transaction/AbstractContextTest.java
@@ -0,0 +1,51 @@
+package com.tec.transaction;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializationFeature;
+import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.test.context.TestPropertySource;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+
+import java.nio.file.Files;
+import java.nio.file.Paths;
+
+@ExtendWith(SpringExtension.class)
+@TestPropertySource(locations = "classpath:application-test.properties")
+public class AbstractContextTest {
+
+ protected static final String DEFAULT_TOKEN = "";
+ protected static ObjectMapper objectMapper;
+
+ static {
+ objectMapper = getObjectMapper();
+ }
+
+
+ protected static String getJsonFromPath(String pathJson) throws Exception {
+ return new String(Files.readAllBytes(Paths.get(AbstractContextTest.class.getResource(pathJson).toURI())));
+ }
+
+ protected static T convertTo(String path, Class aClass) throws Exception {
+ String jsonRequest = getJsonFromPath(path);
+ return objectMapper.readValue(jsonRequest, aClass);
+ }
+
+ public static String convertToJSONString(Object object) throws JsonProcessingException {
+ ObjectMapper mapper = getObjectMapper();
+ return mapper.writeValueAsString(object);
+ }
+
+ public static ObjectMapper getObjectMapper() {
+ ObjectMapper mapper = new ObjectMapper();
+ mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+ mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
+ mapper.registerModule(new Jdk8Module());
+ mapper.registerModule(new JavaTimeModule());
+ return mapper;
+ }
+
+}
diff --git a/yape-financial/yape-transaction/src/test/java/com/tec/transaction/application/TransactionUpdateUseCaseTest.java b/yape-financial/yape-transaction/src/test/java/com/tec/transaction/application/TransactionUpdateUseCaseTest.java
new file mode 100644
index 0000000000..8645fd3ab9
--- /dev/null
+++ b/yape-financial/yape-transaction/src/test/java/com/tec/transaction/application/TransactionUpdateUseCaseTest.java
@@ -0,0 +1,62 @@
+package com.tec.transaction.application;
+
+import com.tec.yape.transaction.application.usecase.TransactionUpdateUseCase;
+import com.tec.yape.transaction.domain.enums.TransactionStatus;
+import com.tec.yape.transaction.domain.model.TransactionResponse;
+import com.tec.yape.transaction.domain.model.TransactionValidatedEvent;
+import com.tec.yape.transaction.domain.port.output.TransactionOutPort;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+
+import static org.mockito.Mockito.*;
+
+@ExtendWith(MockitoExtension.class)
+class TransactionUpdateUseCaseTest {
+
+ @Mock
+ private TransactionOutPort transactionOutPort;
+
+ @InjectMocks
+ private TransactionUpdateUseCase useCase;
+
+ @Test
+ void shouldUpdateTransactionStatus() {
+
+ TransactionValidatedEvent event = new TransactionValidatedEvent(
+ UUID.randomUUID(),
+ TransactionStatus.APROBADO
+ );
+ when(transactionOutPort.findByExternalId(any()))
+ .thenReturn(transactionResponse());
+
+ useCase.updateTransactionStatus(event);
+
+ verify(transactionOutPort).update(argThat(request ->
+ request.getTransactionStatus() == TransactionStatus.APROBADO
+ ));
+ }
+
+
+ private TransactionResponse transactionResponse() {
+ UUID transactionId = UUID.fromString("231111ef-5c32-4294-bf0a-18ff2193fa90");
+ UUID debitId = UUID.fromString("550e8400-e29b-41d4-a716-446655440000");
+ UUID creditId = UUID.fromString("660e8400-e29b-41d4-a716-446655440111");
+
+ TransactionResponse transactionResponse = new TransactionResponse();
+ transactionResponse.setId(UUID.randomUUID());
+ transactionResponse.setTransactionExternalId(transactionId);
+ transactionResponse.setAccountExternalIdCredit(debitId);
+ transactionResponse.setAccountExternalIdDebit(creditId);
+ transactionResponse.setValue(new BigDecimal("500"));
+ transactionResponse.setTransactionStatus(TransactionStatus.PENDIENTE);
+ return transactionResponse;
+ }
+
+
+}
diff --git a/yape-financial/yape-transaction/src/test/java/com/tec/transaction/application/TransactionUseCaseTest.java b/yape-financial/yape-transaction/src/test/java/com/tec/transaction/application/TransactionUseCaseTest.java
new file mode 100644
index 0000000000..83553159e6
--- /dev/null
+++ b/yape-financial/yape-transaction/src/test/java/com/tec/transaction/application/TransactionUseCaseTest.java
@@ -0,0 +1,69 @@
+package com.tec.transaction.application;
+
+import com.tec.yape.transaction.application.mapper.TransactionUseCaseMapper;
+import com.tec.yape.transaction.application.usecase.TransactionUseCase;
+import com.tec.yape.transaction.domain.enums.TransactionStatus;
+import com.tec.yape.transaction.domain.model.TransactionRequest;
+import com.tec.yape.transaction.domain.model.TransactionResponse;
+import com.tec.yape.transaction.domain.port.output.TransactionEventPublisherOutPort;
+import com.tec.yape.transaction.domain.port.output.TransactionOutPort;
+import com.tec.yape.transaction.infrastructure.rest.dto.request.TransactionRequestDto;
+import com.tec.yape.transaction.infrastructure.rest.dto.response.TransactionResponseDto;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+@ExtendWith(MockitoExtension.class)
+class TransactionUseCaseTest {
+
+ @Mock
+ private TransactionOutPort transactionOutPort;
+
+ @Mock
+ private TransactionEventPublisherOutPort eventPublisher;
+
+ @InjectMocks
+ private TransactionUseCase useCase;
+
+ @Test
+ void shouldCreateTransactionWithPendingStatus() {
+
+ TransactionRequestDto request = new TransactionRequestDto();
+ request.setValue(new BigDecimal("500"));
+
+
+ when(transactionOutPort.create(any(TransactionRequest.class)))
+ .thenReturn(transactionResponse());
+
+ TransactionResponseDto response = useCase.registerTransaction(request);
+
+ assertEquals(TransactionStatus.PENDIENTE.name(), response.getTransactionStatus().name());
+ verify(eventPublisher).publish(any());
+ }
+
+
+ private TransactionResponse transactionResponse() {
+ UUID transactionId = UUID.fromString("231111ef-5c32-4294-bf0a-18ff2193fa90");
+ UUID debitId = UUID.fromString("550e8400-e29b-41d4-a716-446655440000");
+ UUID creditId = UUID.fromString("660e8400-e29b-41d4-a716-446655440111");
+
+ TransactionResponse transactionResponse = new TransactionResponse();
+ transactionResponse.setId(UUID.randomUUID());
+ transactionResponse.setTransactionExternalId(transactionId);
+ transactionResponse.setAccountExternalIdCredit(debitId);
+ transactionResponse.setAccountExternalIdDebit(creditId);
+ transactionResponse.setValue(new BigDecimal("500"));
+ transactionResponse.setTransactionStatus(TransactionStatus.PENDIENTE);
+ return transactionResponse;
+ }
+
+
+}
diff --git a/yape-financial/yape-transaction/src/test/java/com/tec/transaction/infrastructure/TransactionControllerTest.java b/yape-financial/yape-transaction/src/test/java/com/tec/transaction/infrastructure/TransactionControllerTest.java
new file mode 100644
index 0000000000..94c9f7191e
--- /dev/null
+++ b/yape-financial/yape-transaction/src/test/java/com/tec/transaction/infrastructure/TransactionControllerTest.java
@@ -0,0 +1,50 @@
+package com.tec.transaction.infrastructure;
+
+import com.tec.yape.transaction.YapeTransactionService;
+import com.tec.yape.transaction.application.usecase.TransactionUseCase;
+import com.tec.yape.transaction.infrastructure.rest.controller.v1.TransactionController;
+import com.tec.yape.transaction.infrastructure.rest.dto.response.TransactionResponseDto;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.http.MediaType;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.web.servlet.MockMvc;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+@WebMvcTest(TransactionController.class)
+@ContextConfiguration(classes = YapeTransactionService.class)
+class TransactionControllerTest {
+
+ @Autowired
+ private MockMvc mockMvc;
+
+ @MockBean
+ private TransactionUseCase transactionUseCase;
+
+ @Test
+ void shouldCreateTransactionSuccessfully() throws Exception {
+
+ String requestJson = """
+ {
+ "accountExternalIdDebit": "550e8400-e29b-41d4-a716-446655440000",
+ "accountExternalIdCredit": "660e8400-e29b-41d4-a716-446655440111",
+ "tranferTypeId": 1,
+ "value": 500
+ }
+ """;
+
+ Mockito.when(transactionUseCase.registerTransaction(any()))
+ .thenReturn(new TransactionResponseDto());
+
+ mockMvc.perform(post("/api/v1/transaction")
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(requestJson))
+ .andExpect(status().isOk());
+ }
+}
diff --git a/yape-financial/yape-transaction/src/test/resources/application-test.properties b/yape-financial/yape-transaction/src/test/resources/application-test.properties
new file mode 100644
index 0000000000..e69de29bb2