diff --git a/README.md b/README.md
index b067a71026..c0b85f48e9 100644
--- a/README.md
+++ b/README.md
@@ -1,82 +1,90 @@
-# Yape Code Challenge :rocket:
+# Yape Code Challenge
-Our code challenge will let you marvel us with your Jedi coding skills :smile:.
+Solución para el reto técnico de Yape.
-Don't forget that the proper way to submit your work is to fork the repo and create a PR :wink: ... have fun !!
+## Descripción
-- [Problem](#problem)
-- [Tech Stack](#tech_stack)
-- [Send us your challenge](#send_us_your_challenge)
+El sistema consta de dos microservicios desacoplados que se comunican de forma asíncrona. El objetivo es procesar transacciones de alto volumen y validar reglas de negocio antifraude sin bloquear la respuesta al cliente.
-# Problem
+### Flujo de la Aplicación
+1. **Transaction Service** recibe una solicitud HTTP para crear una transacción.
+2. La transacción se guarda en **MySQL** con estado `PENDING`.
+3. Se emite un evento `transaction_created` a **Kafka**.
+4. **Anti-Fraud Service** consume el evento y valida el monto.
+5. Si el valor es **> 1000**, se rechaza (`REJECTED`); de lo contrario, se aprueba (`APPROVED`).
+6. Se emite un evento `transaction_status_update` de vuelta.
+7. **Transaction Service** actualiza el estado final en la base de datos.
-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:
+## Tecnologías
-
- - pending
- - approved
- - rejected
-
+* **Node.js & NestJS**: Framework principal (Modo Híbrido: HTTP + Microservice).
+* **Apache Kafka**: Message Broker para comunicación asíncrona.
+* **MySQL**: Base de datos relacional.
+* **Prisma ORM**: Gestión de esquemas y migraciones automáticas.
+* **Docker & Docker Compose**: Orquestación de contenedores y entorno de desarrollo.
-Every transaction with a value greater than 1000 should be rejected.
+## Instalación y Ejecución
-```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)]
-```
+### Prerrequisitos
+* Docker
+* Git
-# Tech Stack
+### Pasos
+1. Clona el repositorio:
+ ```bash
+ git clone
+ cd
+ ```
-
- - Node. You can use any framework you want (i.e. Nestjs with an ORM like TypeOrm or Prisma)
- - Any database
- - Kafka
-
+2. Ejecuta el entorno con Docker Compose:
+ ```bash
+ docker-compose up --build
+ ```
-We do provide a `Dockerfile` to help you get started with a dev environment.
+3. Verifica que los servicios estén corriendo:
+ * **API Transaction:** `http://localhost:3000`
+ * **Kafka UI (Opcional):** `http://localhost:8080`
-You must have two resources:
+## Uso de la API
-1. Resource to create a transaction that must containt:
+Puedes probar los endpoints usando `curl` o Postman.
-```json
-{
- "accountExternalIdDebit": "Guid",
- "accountExternalIdCredit": "Guid",
- "tranferTypeId": 1,
- "value": 120
-}
+### 1. Crear una Transacción
+
+Envia una transacción. Si el `value` es mayor a 1000, será rechazada.
+
+**Request:**
+```bash
+curl -X POST http://localhost:3000/transactions \
+ -H "Content-Type: application/json" \
+ -d '{
+ "accountExternalIdDebit": "28608054-47b6-4522-9214-722e0322b270",
+ "accountExternalIdCredit": "28608054-47b6-4522-9214-722e0322b271",
+ "transferTypeId": 1,
+ "value": 120
+ }'
```
-2. Resource to retrieve a transaction
+### 2. Consultar una Transacción
+Usa el transactionExternalId (UUID) que recibiste en la respuesta anterior para ver su estado final.
+
+**Request:**
+
+```bash
+curl http://localhost:3000/transactions/{TU-UUID-AQUI}
+```
+Respuesta Exitosa (Aprobada):
-```json
+```JSON
{
- "transactionExternalId": "Guid",
+ "transactionExternalId": "1f0bfb31-4577-4755-8253-60ea07d0273e",
"transactionType": {
- "name": ""
+ "name": "1"
},
"transactionStatus": {
- "name": ""
+ "name": "APPROVED"
},
"value": 120,
- "createdAt": "Date"
+ "createdAt": "2025-12-13T20:31:23.691Z"
}
-```
-
-## 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.
+```
\ No newline at end of file
diff --git a/apps/anti-fraud/.gitignore b/apps/anti-fraud/.gitignore
new file mode 100644
index 0000000000..4b56acfbeb
--- /dev/null
+++ b/apps/anti-fraud/.gitignore
@@ -0,0 +1,56 @@
+# compiled output
+/dist
+/node_modules
+/build
+
+# Logs
+logs
+*.log
+npm-debug.log*
+pnpm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+lerna-debug.log*
+
+# OS
+.DS_Store
+
+# Tests
+/coverage
+/.nyc_output
+
+# IDEs and editors
+/.idea
+.project
+.classpath
+.c9/
+*.launch
+.settings/
+*.sublime-workspace
+
+# IDE - VSCode
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
+
+# dotenv environment variable files
+.env
+.env.development.local
+.env.test.local
+.env.production.local
+.env.local
+
+# temp directory
+.temp
+.tmp
+
+# Runtime data
+pids
+*.pid
+*.seed
+*.pid.lock
+
+# Diagnostic reports (https://nodejs.org/api/report.html)
+report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
diff --git a/apps/anti-fraud/.prettierrc b/apps/anti-fraud/.prettierrc
new file mode 100644
index 0000000000..a20502b7f0
--- /dev/null
+++ b/apps/anti-fraud/.prettierrc
@@ -0,0 +1,4 @@
+{
+ "singleQuote": true,
+ "trailingComma": "all"
+}
diff --git a/apps/anti-fraud/Dockerfile b/apps/anti-fraud/Dockerfile
new file mode 100644
index 0000000000..6c53074b56
--- /dev/null
+++ b/apps/anti-fraud/Dockerfile
@@ -0,0 +1,23 @@
+# Usa una imagen ligera de Node
+FROM node:18-alpine
+
+# Directorio de trabajo dentro del contenedor
+WORKDIR /app
+
+# Copiamos archivos de dependencias primero (para aprovechar el caché de Docker)
+COPY package*.json ./
+
+# Instalamos dependencias
+RUN npm install
+
+# Copiamos el resto del código
+COPY . .
+
+# Generamos el cliente de Prisma (Si usas Prisma, descomenta esta línea)
+# RUN npx prisma generate
+
+# Construimos la aplicación NestJS
+RUN npm run build
+
+# Comando para iniciar la app (dist/main es el estándar de NestJS)
+CMD ["node", "dist/main"]
\ No newline at end of file
diff --git a/apps/anti-fraud/README.md b/apps/anti-fraud/README.md
new file mode 100644
index 0000000000..8f0f65f7e7
--- /dev/null
+++ b/apps/anti-fraud/README.md
@@ -0,0 +1,98 @@
+
+
+
+
+[circleci-image]: https://img.shields.io/circleci/build/github/nestjs/nest/master?token=abc123def456
+[circleci-url]: https://circleci.com/gh/nestjs/nest
+
+ A progressive Node.js framework for building efficient and scalable server-side applications.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+## Description
+
+[Nest](https://github.com/nestjs/nest) framework TypeScript starter repository.
+
+## Project setup
+
+```bash
+$ npm install
+```
+
+## Compile and run the project
+
+```bash
+# development
+$ npm run start
+
+# watch mode
+$ npm run start:dev
+
+# production mode
+$ npm run start:prod
+```
+
+## Run tests
+
+```bash
+# unit tests
+$ npm run test
+
+# e2e tests
+$ npm run test:e2e
+
+# test coverage
+$ npm run test:cov
+```
+
+## Deployment
+
+When you're ready to deploy your NestJS application to production, there are some key steps you can take to ensure it runs as efficiently as possible. Check out the [deployment documentation](https://docs.nestjs.com/deployment) for more information.
+
+If you are looking for a cloud-based platform to deploy your NestJS application, check out [Mau](https://mau.nestjs.com), our official platform for deploying NestJS applications on AWS. Mau makes deployment straightforward and fast, requiring just a few simple steps:
+
+```bash
+$ npm install -g @nestjs/mau
+$ mau deploy
+```
+
+With Mau, you can deploy your application in just a few clicks, allowing you to focus on building features rather than managing infrastructure.
+
+## Resources
+
+Check out a few resources that may come in handy when working with NestJS:
+
+- Visit the [NestJS Documentation](https://docs.nestjs.com) to learn more about the framework.
+- For questions and support, please visit our [Discord channel](https://discord.gg/G7Qnnhy).
+- To dive deeper and get more hands-on experience, check out our official video [courses](https://courses.nestjs.com/).
+- Deploy your application to AWS with the help of [NestJS Mau](https://mau.nestjs.com) in just a few clicks.
+- Visualize your application graph and interact with the NestJS application in real-time using [NestJS Devtools](https://devtools.nestjs.com).
+- Need help with your project (part-time to full-time)? Check out our official [enterprise support](https://enterprise.nestjs.com).
+- To stay in the loop and get updates, follow us on [X](https://x.com/nestframework) and [LinkedIn](https://linkedin.com/company/nestjs).
+- Looking for a job, or have a job to offer? Check out our official [Jobs board](https://jobs.nestjs.com).
+
+## Support
+
+Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support).
+
+## Stay in touch
+
+- Author - [Kamil Myśliwiec](https://twitter.com/kammysliwiec)
+- Website - [https://nestjs.com](https://nestjs.com/)
+- Twitter - [@nestframework](https://twitter.com/nestframework)
+
+## License
+
+Nest is [MIT licensed](https://github.com/nestjs/nest/blob/master/LICENSE).
diff --git a/apps/anti-fraud/eslint.config.mjs b/apps/anti-fraud/eslint.config.mjs
new file mode 100644
index 0000000000..4e9f8271c9
--- /dev/null
+++ b/apps/anti-fraud/eslint.config.mjs
@@ -0,0 +1,35 @@
+// @ts-check
+import eslint from '@eslint/js';
+import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended';
+import globals from 'globals';
+import tseslint from 'typescript-eslint';
+
+export default tseslint.config(
+ {
+ ignores: ['eslint.config.mjs'],
+ },
+ eslint.configs.recommended,
+ ...tseslint.configs.recommendedTypeChecked,
+ eslintPluginPrettierRecommended,
+ {
+ languageOptions: {
+ globals: {
+ ...globals.node,
+ ...globals.jest,
+ },
+ sourceType: 'commonjs',
+ parserOptions: {
+ projectService: true,
+ tsconfigRootDir: import.meta.dirname,
+ },
+ },
+ },
+ {
+ rules: {
+ '@typescript-eslint/no-explicit-any': 'off',
+ '@typescript-eslint/no-floating-promises': 'warn',
+ '@typescript-eslint/no-unsafe-argument': 'warn',
+ "prettier/prettier": ["error", { endOfLine: "auto" }],
+ },
+ },
+);
diff --git a/apps/anti-fraud/nest-cli.json b/apps/anti-fraud/nest-cli.json
new file mode 100644
index 0000000000..f9aa683b1a
--- /dev/null
+++ b/apps/anti-fraud/nest-cli.json
@@ -0,0 +1,8 @@
+{
+ "$schema": "https://json.schemastore.org/nest-cli",
+ "collection": "@nestjs/schematics",
+ "sourceRoot": "src",
+ "compilerOptions": {
+ "deleteOutDir": true
+ }
+}
diff --git a/apps/anti-fraud/package.json b/apps/anti-fraud/package.json
new file mode 100644
index 0000000000..842c8d8039
--- /dev/null
+++ b/apps/anti-fraud/package.json
@@ -0,0 +1,74 @@
+{
+ "name": "anti-fraud",
+ "version": "0.0.1",
+ "description": "",
+ "author": "",
+ "private": true,
+ "license": "UNLICENSED",
+ "scripts": {
+ "build": "nest build",
+ "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
+ "start": "nest start",
+ "start:dev": "nest start --watch",
+ "start:debug": "nest start --debug --watch",
+ "start:prod": "node dist/main",
+ "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
+ "test": "jest",
+ "test:watch": "jest --watch",
+ "test:cov": "jest --coverage",
+ "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
+ "test:e2e": "jest --config ./test/jest-e2e.json"
+ },
+ "dependencies": {
+ "@nestjs/common": "^11.0.1",
+ "@nestjs/config": "^4.0.2",
+ "@nestjs/core": "^11.0.1",
+ "@nestjs/microservices": "^11.1.9",
+ "@nestjs/platform-express": "^11.0.1",
+ "kafkajs": "^2.2.4",
+ "reflect-metadata": "^0.2.2",
+ "rxjs": "^7.8.1"
+ },
+ "devDependencies": {
+ "@eslint/eslintrc": "^3.2.0",
+ "@eslint/js": "^9.18.0",
+ "@nestjs/cli": "^11.0.0",
+ "@nestjs/schematics": "^11.0.0",
+ "@nestjs/testing": "^11.0.1",
+ "@types/express": "^5.0.0",
+ "@types/jest": "^30.0.0",
+ "@types/node": "^22.10.7",
+ "@types/supertest": "^6.0.2",
+ "eslint": "^9.18.0",
+ "eslint-config-prettier": "^10.0.1",
+ "eslint-plugin-prettier": "^5.2.2",
+ "globals": "^16.0.0",
+ "jest": "^30.0.0",
+ "prettier": "^3.4.2",
+ "source-map-support": "^0.5.21",
+ "supertest": "^7.0.0",
+ "ts-jest": "^29.2.5",
+ "ts-loader": "^9.5.2",
+ "ts-node": "^10.9.2",
+ "tsconfig-paths": "^4.2.0",
+ "typescript": "^5.7.3",
+ "typescript-eslint": "^8.20.0"
+ },
+ "jest": {
+ "moduleFileExtensions": [
+ "js",
+ "json",
+ "ts"
+ ],
+ "rootDir": "src",
+ "testRegex": ".*\\.spec\\.ts$",
+ "transform": {
+ "^.+\\.(t|j)s$": "ts-jest"
+ },
+ "collectCoverageFrom": [
+ "**/*.(t|j)s"
+ ],
+ "coverageDirectory": "../coverage",
+ "testEnvironment": "node"
+ }
+}
diff --git a/apps/anti-fraud/src/app.controller.spec.ts b/apps/anti-fraud/src/app.controller.spec.ts
new file mode 100644
index 0000000000..d22f3890a3
--- /dev/null
+++ b/apps/anti-fraud/src/app.controller.spec.ts
@@ -0,0 +1,22 @@
+import { Test, TestingModule } from '@nestjs/testing';
+import { AppController } from './app.controller';
+import { AppService } from './app.service';
+
+describe('AppController', () => {
+ let appController: AppController;
+
+ beforeEach(async () => {
+ const app: TestingModule = await Test.createTestingModule({
+ controllers: [AppController],
+ providers: [AppService],
+ }).compile();
+
+ appController = app.get(AppController);
+ });
+
+ describe('root', () => {
+ it('should return "Hello World!"', () => {
+ expect(appController.getHello()).toBe('Hello World!');
+ });
+ });
+});
diff --git a/apps/anti-fraud/src/app.controller.ts b/apps/anti-fraud/src/app.controller.ts
new file mode 100644
index 0000000000..f2738ba5b5
--- /dev/null
+++ b/apps/anti-fraud/src/app.controller.ts
@@ -0,0 +1,13 @@
+import { Controller } from '@nestjs/common';
+import { EventPattern, Payload } from '@nestjs/microservices';
+import { AppService } from './app.service';
+
+@Controller()
+export class AppController {
+ constructor(private readonly appService: AppService) {}
+
+ @EventPattern('transaction_created')
+ handleTransactionCreated(@Payload() data: any) {
+ this.appService.handleTransactionCreated(data);
+ }
+}
\ No newline at end of file
diff --git a/apps/anti-fraud/src/app.module.ts b/apps/anti-fraud/src/app.module.ts
new file mode 100644
index 0000000000..5842630166
--- /dev/null
+++ b/apps/anti-fraud/src/app.module.ts
@@ -0,0 +1,28 @@
+import { Module } from '@nestjs/common';
+import { ClientsModule, Transport } from '@nestjs/microservices';
+import { AppController } from './app.controller';
+import { AppService } from './app.service';
+import { ConfigModule } from '@nestjs/config';
+
+@Module({
+ imports: [
+ ConfigModule.forRoot(),
+ ClientsModule.register([
+ {
+ name: 'KAFKA_SERVICE',
+ transport: Transport.KAFKA,
+ options: {
+ client: {
+ brokers: [process.env.KAFKA_BROKERS || 'localhost:29092'],
+ },
+ consumer: {
+ groupId: 'transaction-consumer-client',
+ },
+ },
+ },
+ ]),
+ ],
+ controllers: [AppController],
+ providers: [AppService],
+})
+export class AppModule {}
diff --git a/apps/anti-fraud/src/app.service.ts b/apps/anti-fraud/src/app.service.ts
new file mode 100644
index 0000000000..c4b0d4f81d
--- /dev/null
+++ b/apps/anti-fraud/src/app.service.ts
@@ -0,0 +1,25 @@
+import { Inject, Injectable } from '@nestjs/common';
+import { ClientKafka } from '@nestjs/microservices';
+
+@Injectable()
+export class AppService {
+ constructor(
+ @Inject('KAFKA_SERVICE') private readonly kafkaClient: ClientKafka,
+ ) {}
+
+ handleTransactionCreated(data: any) {
+ const transaction = typeof data === 'string' ? JSON.parse(data) : data;
+
+ console.log(`[Anti-Fraud] Analizando transacción: ${transaction.transactionExternalId} con valor ${transaction.value}`);
+
+ const isFraud = transaction.value > 1000;
+ const newStatus = isFraud ? 'REJECTED' : 'APPROVED';
+
+ console.log(`[Anti-Fraud] Resultado: ${newStatus}. Enviando evento...`);
+
+ this.kafkaClient.emit('transaction_status_update', JSON.stringify({
+ transactionExternalId: transaction.transactionExternalId,
+ status: newStatus,
+ }));
+ }
+}
\ No newline at end of file
diff --git a/apps/anti-fraud/src/main.ts b/apps/anti-fraud/src/main.ts
new file mode 100644
index 0000000000..3d96928375
--- /dev/null
+++ b/apps/anti-fraud/src/main.ts
@@ -0,0 +1,25 @@
+import { NestFactory } from '@nestjs/core';
+import { MicroserviceOptions, Transport } from '@nestjs/microservices';
+import { AppModule } from './app.module';
+
+async function bootstrap() {
+ const app = await NestFactory.createMicroservice(
+ AppModule,
+ {
+ transport: Transport.KAFKA,
+ options: {
+ client: {
+ brokers: [process.env.KAFKA_BROKERS || 'localhost:29092'],
+ },
+ consumer: {
+ groupId: 'anti-fraud-consumer',
+ },
+ },
+ },
+ );
+
+
+ await app.listen();
+ console.log('Anti-Fraud Microservice is listening via Kafka...');
+}
+bootstrap();
\ No newline at end of file
diff --git a/apps/anti-fraud/test/app.e2e-spec.ts b/apps/anti-fraud/test/app.e2e-spec.ts
new file mode 100644
index 0000000000..36852c54f0
--- /dev/null
+++ b/apps/anti-fraud/test/app.e2e-spec.ts
@@ -0,0 +1,25 @@
+import { Test, TestingModule } from '@nestjs/testing';
+import { INestApplication } from '@nestjs/common';
+import request from 'supertest';
+import { App } from 'supertest/types';
+import { AppModule } from './../src/app.module';
+
+describe('AppController (e2e)', () => {
+ let app: INestApplication;
+
+ beforeEach(async () => {
+ const moduleFixture: TestingModule = await Test.createTestingModule({
+ imports: [AppModule],
+ }).compile();
+
+ app = moduleFixture.createNestApplication();
+ await app.init();
+ });
+
+ it('/ (GET)', () => {
+ return request(app.getHttpServer())
+ .get('/')
+ .expect(200)
+ .expect('Hello World!');
+ });
+});
diff --git a/apps/anti-fraud/test/jest-e2e.json b/apps/anti-fraud/test/jest-e2e.json
new file mode 100644
index 0000000000..e9d912f3e3
--- /dev/null
+++ b/apps/anti-fraud/test/jest-e2e.json
@@ -0,0 +1,9 @@
+{
+ "moduleFileExtensions": ["js", "json", "ts"],
+ "rootDir": ".",
+ "testEnvironment": "node",
+ "testRegex": ".e2e-spec.ts$",
+ "transform": {
+ "^.+\\.(t|j)s$": "ts-jest"
+ }
+}
diff --git a/apps/anti-fraud/tsconfig.build.json b/apps/anti-fraud/tsconfig.build.json
new file mode 100644
index 0000000000..64f86c6bd2
--- /dev/null
+++ b/apps/anti-fraud/tsconfig.build.json
@@ -0,0 +1,4 @@
+{
+ "extends": "./tsconfig.json",
+ "exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
+}
diff --git a/apps/anti-fraud/tsconfig.json b/apps/anti-fraud/tsconfig.json
new file mode 100644
index 0000000000..aba29b0e7f
--- /dev/null
+++ b/apps/anti-fraud/tsconfig.json
@@ -0,0 +1,25 @@
+{
+ "compilerOptions": {
+ "module": "nodenext",
+ "moduleResolution": "nodenext",
+ "resolvePackageJsonExports": true,
+ "esModuleInterop": true,
+ "isolatedModules": true,
+ "declaration": true,
+ "removeComments": true,
+ "emitDecoratorMetadata": true,
+ "experimentalDecorators": true,
+ "allowSyntheticDefaultImports": true,
+ "target": "ES2023",
+ "sourceMap": true,
+ "outDir": "./dist",
+ "baseUrl": "./",
+ "incremental": true,
+ "skipLibCheck": true,
+ "strictNullChecks": true,
+ "forceConsistentCasingInFileNames": true,
+ "noImplicitAny": false,
+ "strictBindCallApply": false,
+ "noFallthroughCasesInSwitch": false
+ }
+}
diff --git a/apps/transaction/.gitignore b/apps/transaction/.gitignore
new file mode 100644
index 0000000000..78cff2123d
--- /dev/null
+++ b/apps/transaction/.gitignore
@@ -0,0 +1,58 @@
+# compiled output
+/dist
+/node_modules
+/build
+
+# Logs
+logs
+*.log
+npm-debug.log*
+pnpm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+lerna-debug.log*
+
+# OS
+.DS_Store
+
+# Tests
+/coverage
+/.nyc_output
+
+# IDEs and editors
+/.idea
+.project
+.classpath
+.c9/
+*.launch
+.settings/
+*.sublime-workspace
+
+# IDE - VSCode
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
+
+# dotenv environment variable files
+.env
+.env.development.local
+.env.test.local
+.env.production.local
+.env.local
+
+# temp directory
+.temp
+.tmp
+
+# Runtime data
+pids
+*.pid
+*.seed
+*.pid.lock
+
+# Diagnostic reports (https://nodejs.org/api/report.html)
+report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
+
+/generated/prisma
diff --git a/apps/transaction/.prettierrc b/apps/transaction/.prettierrc
new file mode 100644
index 0000000000..a20502b7f0
--- /dev/null
+++ b/apps/transaction/.prettierrc
@@ -0,0 +1,4 @@
+{
+ "singleQuote": true,
+ "trailingComma": "all"
+}
diff --git a/apps/transaction/Dockerfile b/apps/transaction/Dockerfile
new file mode 100644
index 0000000000..e4f454d01b
--- /dev/null
+++ b/apps/transaction/Dockerfile
@@ -0,0 +1,20 @@
+# apps/transaction/Dockerfile
+FROM node:18-alpine
+
+WORKDIR /app
+
+# Esta línea es VITAL para Alpine
+RUN apk add --no-cache openssl libc6-compat
+
+COPY package*.json ./
+
+RUN npm install
+
+COPY . .
+
+# Usamos la ruta explícita del esquema por seguridad
+RUN npx prisma generate --schema=./prisma/schema.prisma
+
+RUN npm run build
+
+CMD ["npm", "run", "start:prod"]
\ No newline at end of file
diff --git a/apps/transaction/README.md b/apps/transaction/README.md
new file mode 100644
index 0000000000..8f0f65f7e7
--- /dev/null
+++ b/apps/transaction/README.md
@@ -0,0 +1,98 @@
+
+
+
+
+[circleci-image]: https://img.shields.io/circleci/build/github/nestjs/nest/master?token=abc123def456
+[circleci-url]: https://circleci.com/gh/nestjs/nest
+
+ A progressive Node.js framework for building efficient and scalable server-side applications.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+## Description
+
+[Nest](https://github.com/nestjs/nest) framework TypeScript starter repository.
+
+## Project setup
+
+```bash
+$ npm install
+```
+
+## Compile and run the project
+
+```bash
+# development
+$ npm run start
+
+# watch mode
+$ npm run start:dev
+
+# production mode
+$ npm run start:prod
+```
+
+## Run tests
+
+```bash
+# unit tests
+$ npm run test
+
+# e2e tests
+$ npm run test:e2e
+
+# test coverage
+$ npm run test:cov
+```
+
+## Deployment
+
+When you're ready to deploy your NestJS application to production, there are some key steps you can take to ensure it runs as efficiently as possible. Check out the [deployment documentation](https://docs.nestjs.com/deployment) for more information.
+
+If you are looking for a cloud-based platform to deploy your NestJS application, check out [Mau](https://mau.nestjs.com), our official platform for deploying NestJS applications on AWS. Mau makes deployment straightforward and fast, requiring just a few simple steps:
+
+```bash
+$ npm install -g @nestjs/mau
+$ mau deploy
+```
+
+With Mau, you can deploy your application in just a few clicks, allowing you to focus on building features rather than managing infrastructure.
+
+## Resources
+
+Check out a few resources that may come in handy when working with NestJS:
+
+- Visit the [NestJS Documentation](https://docs.nestjs.com) to learn more about the framework.
+- For questions and support, please visit our [Discord channel](https://discord.gg/G7Qnnhy).
+- To dive deeper and get more hands-on experience, check out our official video [courses](https://courses.nestjs.com/).
+- Deploy your application to AWS with the help of [NestJS Mau](https://mau.nestjs.com) in just a few clicks.
+- Visualize your application graph and interact with the NestJS application in real-time using [NestJS Devtools](https://devtools.nestjs.com).
+- Need help with your project (part-time to full-time)? Check out our official [enterprise support](https://enterprise.nestjs.com).
+- To stay in the loop and get updates, follow us on [X](https://x.com/nestframework) and [LinkedIn](https://linkedin.com/company/nestjs).
+- Looking for a job, or have a job to offer? Check out our official [Jobs board](https://jobs.nestjs.com).
+
+## Support
+
+Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support).
+
+## Stay in touch
+
+- Author - [Kamil Myśliwiec](https://twitter.com/kammysliwiec)
+- Website - [https://nestjs.com](https://nestjs.com/)
+- Twitter - [@nestframework](https://twitter.com/nestframework)
+
+## License
+
+Nest is [MIT licensed](https://github.com/nestjs/nest/blob/master/LICENSE).
diff --git a/apps/transaction/eslint.config.mjs b/apps/transaction/eslint.config.mjs
new file mode 100644
index 0000000000..4e9f8271c9
--- /dev/null
+++ b/apps/transaction/eslint.config.mjs
@@ -0,0 +1,35 @@
+// @ts-check
+import eslint from '@eslint/js';
+import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended';
+import globals from 'globals';
+import tseslint from 'typescript-eslint';
+
+export default tseslint.config(
+ {
+ ignores: ['eslint.config.mjs'],
+ },
+ eslint.configs.recommended,
+ ...tseslint.configs.recommendedTypeChecked,
+ eslintPluginPrettierRecommended,
+ {
+ languageOptions: {
+ globals: {
+ ...globals.node,
+ ...globals.jest,
+ },
+ sourceType: 'commonjs',
+ parserOptions: {
+ projectService: true,
+ tsconfigRootDir: import.meta.dirname,
+ },
+ },
+ },
+ {
+ rules: {
+ '@typescript-eslint/no-explicit-any': 'off',
+ '@typescript-eslint/no-floating-promises': 'warn',
+ '@typescript-eslint/no-unsafe-argument': 'warn',
+ "prettier/prettier": ["error", { endOfLine: "auto" }],
+ },
+ },
+);
diff --git a/apps/transaction/nest-cli.json b/apps/transaction/nest-cli.json
new file mode 100644
index 0000000000..f9aa683b1a
--- /dev/null
+++ b/apps/transaction/nest-cli.json
@@ -0,0 +1,8 @@
+{
+ "$schema": "https://json.schemastore.org/nest-cli",
+ "collection": "@nestjs/schematics",
+ "sourceRoot": "src",
+ "compilerOptions": {
+ "deleteOutDir": true
+ }
+}
diff --git a/apps/transaction/package.json b/apps/transaction/package.json
new file mode 100644
index 0000000000..689ef1c9f1
--- /dev/null
+++ b/apps/transaction/package.json
@@ -0,0 +1,76 @@
+{
+ "name": "transaction",
+ "version": "0.0.1",
+ "description": "",
+ "author": "",
+ "private": true,
+ "license": "UNLICENSED",
+ "scripts": {
+ "build": "nest build",
+ "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
+ "start": "nest start",
+ "start:dev": "nest start --watch",
+ "start:debug": "nest start --debug --watch",
+ "start:prod": "npx prisma migrate deploy && node dist/main",
+ "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
+ "test": "jest",
+ "test:watch": "jest --watch",
+ "test:cov": "jest --coverage",
+ "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
+ "test:e2e": "jest --config ./test/jest-e2e.json"
+ },
+ "dependencies": {
+ "@nestjs/common": "^11.0.1",
+ "@nestjs/config": "^4.0.2",
+ "@nestjs/core": "^11.0.1",
+ "@nestjs/microservices": "^11.1.9",
+ "@nestjs/platform-express": "^11.0.1",
+ "@prisma/client": "^5.22.0",
+ "kafkajs": "^2.2.4",
+ "reflect-metadata": "^0.2.2",
+ "rxjs": "^7.8.1"
+ },
+ "devDependencies": {
+ "@eslint/eslintrc": "^3.2.0",
+ "@eslint/js": "^9.18.0",
+ "@nestjs/cli": "^11.0.0",
+ "@nestjs/schematics": "^11.0.0",
+ "@nestjs/testing": "^11.0.1",
+ "@types/express": "^5.0.0",
+ "@types/jest": "^30.0.0",
+ "@types/node": "^22.10.7",
+ "@types/supertest": "^6.0.2",
+ "eslint": "^9.18.0",
+ "eslint-config-prettier": "^10.0.1",
+ "eslint-plugin-prettier": "^5.2.2",
+ "globals": "^16.0.0",
+ "jest": "^30.0.0",
+ "prettier": "^3.4.2",
+ "prisma": "5.22.0",
+ "source-map-support": "^0.5.21",
+ "supertest": "^7.0.0",
+ "ts-jest": "^29.2.5",
+ "ts-loader": "^9.5.2",
+ "ts-node": "^10.9.2",
+ "tsconfig-paths": "^4.2.0",
+ "typescript": "^5.7.3",
+ "typescript-eslint": "^8.20.0"
+ },
+ "jest": {
+ "moduleFileExtensions": [
+ "js",
+ "json",
+ "ts"
+ ],
+ "rootDir": "src",
+ "testRegex": ".*\\.spec\\.ts$",
+ "transform": {
+ "^.+\\.(t|j)s$": "ts-jest"
+ },
+ "collectCoverageFrom": [
+ "**/*.(t|j)s"
+ ],
+ "coverageDirectory": "../coverage",
+ "testEnvironment": "node"
+ }
+}
\ No newline at end of file
diff --git a/apps/transaction/prisma/schema.prisma b/apps/transaction/prisma/schema.prisma
new file mode 100644
index 0000000000..cfd1b59051
--- /dev/null
+++ b/apps/transaction/prisma/schema.prisma
@@ -0,0 +1,26 @@
+generator client {
+ provider = "prisma-client-js"
+ binaryTargets = ["native", "linux-musl", "linux-musl-openssl-3.0.x"]
+}
+
+datasource db {
+ provider = "mysql"
+ url = env("DATABASE_URL")
+}
+
+model Transaction {
+ id String @id @default(uuid())
+ accountExternalIdDebit String?
+ accountExternalIdCredit String?
+ transferTypeId Int
+ value Float
+ status TransactionStatus @default(PENDING)
+ createdAt DateTime @default(now())
+ updatedAt DateTime @updatedAt
+}
+
+enum TransactionStatus {
+ PENDING
+ APPROVED
+ REJECTED
+}
\ No newline at end of file
diff --git a/apps/transaction/src/app.controller.spec.ts b/apps/transaction/src/app.controller.spec.ts
new file mode 100644
index 0000000000..d22f3890a3
--- /dev/null
+++ b/apps/transaction/src/app.controller.spec.ts
@@ -0,0 +1,22 @@
+import { Test, TestingModule } from '@nestjs/testing';
+import { AppController } from './app.controller';
+import { AppService } from './app.service';
+
+describe('AppController', () => {
+ let appController: AppController;
+
+ beforeEach(async () => {
+ const app: TestingModule = await Test.createTestingModule({
+ controllers: [AppController],
+ providers: [AppService],
+ }).compile();
+
+ appController = app.get(AppController);
+ });
+
+ describe('root', () => {
+ it('should return "Hello World!"', () => {
+ expect(appController.getHello()).toBe('Hello World!');
+ });
+ });
+});
diff --git a/apps/transaction/src/app.controller.ts b/apps/transaction/src/app.controller.ts
new file mode 100644
index 0000000000..af856ba03b
--- /dev/null
+++ b/apps/transaction/src/app.controller.ts
@@ -0,0 +1,40 @@
+import { Body, Controller, Get, Param, Post } from '@nestjs/common';
+import { EventPattern, Payload } from '@nestjs/microservices';
+import { AppService } from './app.service';
+import { CreateTransactionDto } from './dtos/create-transaction.dto';
+
+@Controller('transactions')
+export class AppController {
+ constructor(private readonly appService: AppService) {}
+
+ @Post()
+ create(@Body() createTransactionDto: CreateTransactionDto) {
+ return this.appService.createTransaction(createTransactionDto);
+ }
+
+ @EventPattern('transaction_status_update')
+ handleTransactionStatusUpdate(@Payload() data: any) {
+ this.appService.handleTransactionStatusUpdate(data);
+ }
+
+ @Get(':id')
+ async getTransaction(@Param('id') id: string) {
+ const transaction = await this.appService.prisma.transaction.findUnique({
+ where: { id },
+ });
+
+ if (!transaction) return null;
+
+ return {
+ transactionExternalId: transaction.id,
+ transactionType: {
+ name: transaction.transferTypeId,
+ },
+ transactionStatus: {
+ name: transaction.status,
+ },
+ value: transaction.value,
+ createdAt: transaction.createdAt,
+ };
+ }
+}
diff --git a/apps/transaction/src/app.module.ts b/apps/transaction/src/app.module.ts
new file mode 100644
index 0000000000..f7e0d010b7
--- /dev/null
+++ b/apps/transaction/src/app.module.ts
@@ -0,0 +1,33 @@
+import { Module } from '@nestjs/common';
+import { ConfigModule } from '@nestjs/config';
+import { ClientsModule, Transport } from '@nestjs/microservices';
+import { AppController } from './app.controller';
+import { AppService } from './app.service';
+import { PrismaService } from './prisma.service';
+
+@Module({
+ imports: [
+ ConfigModule.forRoot(),
+ ClientsModule.register([
+ {
+ name: 'KAFKA_SERVICE',
+ transport: Transport.KAFKA,
+ options: {
+ client: {
+ // <--- 2. USAR LA VARIABLE DE ENTORNO
+ brokers: [process.env.KAFKA_BROKERS || 'localhost:29092'],
+ },
+ consumer: {
+ groupId: 'transaction-consumer-client',
+ },
+ },
+ },
+ ]),
+ ],
+ controllers: [AppController],
+ providers: [
+ AppService,
+ PrismaService,
+ ],
+})
+export class AppModule {}
diff --git a/apps/transaction/src/app.service.ts b/apps/transaction/src/app.service.ts
new file mode 100644
index 0000000000..44a9622442
--- /dev/null
+++ b/apps/transaction/src/app.service.ts
@@ -0,0 +1,62 @@
+import { Inject, Injectable } from '@nestjs/common';
+import { ClientKafka } from '@nestjs/microservices';
+import { PrismaService } from './prisma.service';
+import { CreateTransactionDto } from './dtos/create-transaction.dto';
+import { TransactionStatus } from '@prisma/client';
+
+@Injectable()
+export class AppService {
+ constructor(
+ public readonly prisma: PrismaService,
+ @Inject('KAFKA_SERVICE') private readonly kafkaClient: ClientKafka,
+ ) {}
+
+ async createTransaction(data: CreateTransactionDto) {
+ const transaction = await this.prisma.transaction.create({
+ data: {
+ accountExternalIdDebit: data.accountExternalIdDebit,
+ accountExternalIdCredit: data.accountExternalIdCredit,
+ transferTypeId: data.transferTypeId,
+ value: data.value,
+ status: 'PENDING',
+ },
+ });
+
+ console.log(
+ `[Transaction] Creada en DB: ${transaction.id}. Enviando a Kafka...`,
+ );
+
+ this.kafkaClient.emit(
+ 'transaction_created',
+ JSON.stringify({
+ transactionExternalId: transaction.id,
+ value: transaction.value,
+ }),
+ );
+
+ return transaction;
+ }
+
+ async handleTransactionStatusUpdate(data: any) {
+ let transactionData = data;
+
+ if (typeof data === 'string') {
+ transactionData = JSON.parse(data);
+ }
+
+ console.log(
+ `[Transaction] Recibido update para ${transactionData.transactionExternalId}. Nuevo estado: ${transactionData.status}`,
+ );
+
+ await this.prisma.transaction.update({
+ where: {
+ id: transactionData.transactionExternalId,
+ },
+ data: {
+ status: transactionData.status as TransactionStatus,
+ },
+ });
+
+ console.log(`[Transaction] DB actualizada correctamente.`);
+ }
+}
diff --git a/apps/transaction/src/dtos/create-transaction.dto.ts b/apps/transaction/src/dtos/create-transaction.dto.ts
new file mode 100644
index 0000000000..1a035ef976
--- /dev/null
+++ b/apps/transaction/src/dtos/create-transaction.dto.ts
@@ -0,0 +1,6 @@
+export class CreateTransactionDto {
+ accountExternalIdDebit: string;
+ accountExternalIdCredit: string;
+ transferTypeId: number;
+ value: number;
+}
\ No newline at end of file
diff --git a/apps/transaction/src/main.ts b/apps/transaction/src/main.ts
new file mode 100644
index 0000000000..136d702566
--- /dev/null
+++ b/apps/transaction/src/main.ts
@@ -0,0 +1,24 @@
+import { NestFactory } from '@nestjs/core';
+import { AppModule } from './app.module';
+import { MicroserviceOptions, Transport } from '@nestjs/microservices';
+
+async function bootstrap() {
+ const app = await NestFactory.create(AppModule);
+
+ app.connectMicroservice({
+ transport: Transport.KAFKA,
+ options: {
+ client: {
+ brokers: [process.env.KAFKA_BROKERS || 'localhost:29092'],
+ },
+ consumer: {
+ groupId: 'transaction-consumer-server',
+ },
+ },
+ });
+
+ await app.startAllMicroservices();
+ await app.listen(3000);
+ console.log('Transaction Service is running on port 3000 and listening to Kafka');
+}
+bootstrap();
\ No newline at end of file
diff --git a/apps/transaction/src/prisma.service.ts b/apps/transaction/src/prisma.service.ts
new file mode 100644
index 0000000000..e6f150324d
--- /dev/null
+++ b/apps/transaction/src/prisma.service.ts
@@ -0,0 +1,13 @@
+import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common';
+import { PrismaClient } from '@prisma/client';
+
+@Injectable()
+export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy {
+ async onModuleInit() {
+ await this.$connect();
+ }
+
+ async onModuleDestroy() {
+ await this.$disconnect();
+ }
+}
\ No newline at end of file
diff --git a/apps/transaction/test/app.e2e-spec.ts b/apps/transaction/test/app.e2e-spec.ts
new file mode 100644
index 0000000000..36852c54f0
--- /dev/null
+++ b/apps/transaction/test/app.e2e-spec.ts
@@ -0,0 +1,25 @@
+import { Test, TestingModule } from '@nestjs/testing';
+import { INestApplication } from '@nestjs/common';
+import request from 'supertest';
+import { App } from 'supertest/types';
+import { AppModule } from './../src/app.module';
+
+describe('AppController (e2e)', () => {
+ let app: INestApplication;
+
+ beforeEach(async () => {
+ const moduleFixture: TestingModule = await Test.createTestingModule({
+ imports: [AppModule],
+ }).compile();
+
+ app = moduleFixture.createNestApplication();
+ await app.init();
+ });
+
+ it('/ (GET)', () => {
+ return request(app.getHttpServer())
+ .get('/')
+ .expect(200)
+ .expect('Hello World!');
+ });
+});
diff --git a/apps/transaction/test/jest-e2e.json b/apps/transaction/test/jest-e2e.json
new file mode 100644
index 0000000000..e9d912f3e3
--- /dev/null
+++ b/apps/transaction/test/jest-e2e.json
@@ -0,0 +1,9 @@
+{
+ "moduleFileExtensions": ["js", "json", "ts"],
+ "rootDir": ".",
+ "testEnvironment": "node",
+ "testRegex": ".e2e-spec.ts$",
+ "transform": {
+ "^.+\\.(t|j)s$": "ts-jest"
+ }
+}
diff --git a/apps/transaction/tsconfig.build.json b/apps/transaction/tsconfig.build.json
new file mode 100644
index 0000000000..64f86c6bd2
--- /dev/null
+++ b/apps/transaction/tsconfig.build.json
@@ -0,0 +1,4 @@
+{
+ "extends": "./tsconfig.json",
+ "exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
+}
diff --git a/apps/transaction/tsconfig.json b/apps/transaction/tsconfig.json
new file mode 100644
index 0000000000..aba29b0e7f
--- /dev/null
+++ b/apps/transaction/tsconfig.json
@@ -0,0 +1,25 @@
+{
+ "compilerOptions": {
+ "module": "nodenext",
+ "moduleResolution": "nodenext",
+ "resolvePackageJsonExports": true,
+ "esModuleInterop": true,
+ "isolatedModules": true,
+ "declaration": true,
+ "removeComments": true,
+ "emitDecoratorMetadata": true,
+ "experimentalDecorators": true,
+ "allowSyntheticDefaultImports": true,
+ "target": "ES2023",
+ "sourceMap": true,
+ "outDir": "./dist",
+ "baseUrl": "./",
+ "incremental": true,
+ "skipLibCheck": true,
+ "strictNullChecks": true,
+ "forceConsistentCasingInFileNames": true,
+ "noImplicitAny": false,
+ "strictBindCallApply": false,
+ "noFallthroughCasesInSwitch": false
+ }
+}
diff --git a/docker-compose.yml b/docker-compose.yml
index 0e8807f21c..268ad18853 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -1,25 +1,77 @@
-version: "3.7"
services:
- postgres:
- image: postgres:14
- ports:
- - "5432:5432"
+ mysql:
+ image: mysql:8.0
+ container_name: challenge-mysql
+ restart: always
environment:
- - POSTGRES_USER=postgres
- - POSTGRES_PASSWORD=postgres
+ MYSQL_ROOT_PASSWORD: "SuperSecureRootPassword!2025"
+ MYSQL_DATABASE: challenge_db
+ MYSQL_USER: admin_user
+ MYSQL_PASSWORD: "AppPassword_Strong#99"
+ ports:
+ - "3310:3306"
+ volumes:
+ - ./mysql-data:/var/lib/mysql
+
zookeeper:
- image: confluentinc/cp-zookeeper:5.5.3
+ image: confluentinc/cp-zookeeper:7.4.0
+ container_name: challenge-zookeeper
environment:
ZOOKEEPER_CLIENT_PORT: 2181
+ ZOOKEEPER_TICK_TIME: 2000
+
kafka:
- image: confluentinc/cp-enterprise-kafka:5.5.3
- depends_on: [zookeeper]
+ image: confluentinc/cp-kafka:7.4.0
+ container_name: challenge-kafka
+ depends_on:
+ - zookeeper
+ ports:
+ - "9092:9092"
+ - "29092:29092"
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:9092,PLAINTEXT_HOST://localhost:29092
+ KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT
+ KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
- KAFKA_JMX_PORT: 9991
+
+ transaction-service:
+ build:
+ context: ./apps/transaction
+ dockerfile: Dockerfile
+ container_name: app-transaction
+ restart: always
ports:
- - 9092:9092
+ - "3000:3000"
+ depends_on:
+ - mysql
+ - kafka
+ environment:
+ DATABASE_URL: "mysql://root:SuperSecureRootPassword!2025@mysql:3306/challenge_db"
+
+ KAFKA_BROKERS: "kafka:9092"
+ KAFKA_GROUP_ID: "transaction-consumer-server"
+
+ anti-fraud-service:
+ build:
+ context: ./apps/anti-fraud
+ dockerfile: Dockerfile
+ container_name: app-anti-fraud
+ restart: always
+ depends_on:
+ - kafka
+ environment:
+ KAFKA_BROKERS: "kafka:9092"
+ KAFKA_GROUP_ID: "anti-fraud-group"
+
+ kafka-ui:
+ image: provectuslabs/kafka-ui:latest
+ container_name: challenge-kafka-ui
+ depends_on:
+ - kafka
+ ports:
+ - "8080:8080"
+ environment:
+ KAFKA_CLUSTERS_0_NAME: local
+ KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS: kafka:9092
\ No newline at end of file
diff --git a/package.json b/package.json
new file mode 100644
index 0000000000..a212ba0377
--- /dev/null
+++ b/package.json
@@ -0,0 +1,10 @@
+{
+ "devDependencies": {
+ "prisma": "^7.1.0"
+ },
+ "dependencies": {
+ "@nestjs/config": "^4.0.2",
+ "@nestjs/microservices": "^11.1.9",
+ "kafkajs": "^2.2.4"
+ }
+}