diff --git a/package-lock.json b/package-lock.json index b684fe3..a6809cd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,9 +14,11 @@ "@azure/openai": "^1.0.0-beta.13", "@nestjs/common": "^10.0.0", "@nestjs/core": "^10.0.0", + "@nestjs/jwt": "^11.0.2", "@nestjs/platform-express": "^10.0.0", "@nestjs/swagger": "^7.4.2", "axios": "^1.13.6", + "bcryptjs": "^3.0.3", "class-transformer": "^0.5.1", "class-validator": "^0.14.1", "dotenv": "^16.4.5", @@ -30,6 +32,8 @@ "swagger-ui-express": "^5.0.1" }, "devDependencies": { + "@types/bcryptjs": "^2.4.6", + "@types/multer": "^2.1.0", "@types/node": "^18.0.0", "ts-node-dev": "^2.0.0", "typescript": "^5.0.0" @@ -360,6 +364,19 @@ } } }, + "node_modules/@nestjs/jwt": { + "version": "11.0.2", + "resolved": "https://registry.npmjs.org/@nestjs/jwt/-/jwt-11.0.2.tgz", + "integrity": "sha512-rK8aE/3/Ma45gAWfCksAXUNbOoSOUudU0Kn3rT39htPF7wsYXtKfjALKeKKJbFrIWbLjsbqfXX5bIJNvgBugGA==", + "license": "MIT", + "dependencies": { + "@types/jsonwebtoken": "9.0.10", + "jsonwebtoken": "9.0.3" + }, + "peerDependencies": { + "@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0" + } + }, "node_modules/@nestjs/mapped-types": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/@nestjs/mapped-types/-/mapped-types-2.0.5.tgz", @@ -567,6 +584,92 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/bcryptjs": { + "version": "2.4.6", + "resolved": "https://registry.npmjs.org/@types/bcryptjs/-/bcryptjs-2.4.6.tgz", + "integrity": "sha512-9xlo6R2qDs5uixm0bcIqCeMCE6HiQsIyel9KQySStiyqNl2tnj2mP3DX1Nf56MD6KMenNNlBBsy3LJ7gUEQPXQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/body-parser": { + "version": "1.19.6", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", + "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/express": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.6.tgz", + "integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^5.0.0", + "@types/serve-static": "^2" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.1.tgz", + "integrity": "sha512-v4zIMr/cX7/d2BpAEX3KNKL/JrT1s43s96lLvvdTmza1oEvDudCqK9aF/djc/SWgy8Yh0h30TZx5VpzqFCxk5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/jsonwebtoken": { + "version": "9.0.10", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.10.tgz", + "integrity": "sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==", + "license": "MIT", + "dependencies": { + "@types/ms": "*", + "@types/node": "*" + } + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "license": "MIT" + }, + "node_modules/@types/multer": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/multer/-/multer-2.1.0.tgz", + "integrity": "sha512-zYZb0+nJhOHtPpGDb3vqPjwpdeGlGC157VpkqNQL+UU2qwoacoQ7MpsAmUptI/0Oa127X32JzWDqQVEXp2RcIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/express": "*" + } + }, "node_modules/@types/node": { "version": "18.19.130", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.130.tgz", @@ -576,6 +679,41 @@ "undici-types": "~5.26.4" } }, + "node_modules/@types/qs": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.15.0.tgz", + "integrity": "sha512-JawvT8iBVWpzTrz3EGw9BTQFg3BQNmwERdKE22vlTxawwtbyUSlMppvZYKLZzB5zgACXdXxbD3m1bXaMqP/9ow==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", + "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*" + } + }, "node_modules/@types/strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@types/strip-bom/-/strip-bom-3.0.0.tgz", @@ -803,6 +941,15 @@ ], "license": "MIT" }, + "node_modules/bcryptjs": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-3.0.3.tgz", + "integrity": "sha512-GlF5wPWnSa/X5LKM1o0wz0suXIINz1iHRLvTS+sLyi7XPbe5ycmYI3DlZqVGZZtDgl4DmasFg7gOB3JYbphV5g==", + "license": "BSD-3-Clause", + "bin": { + "bcrypt": "bin/bcrypt" + } + }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -908,6 +1055,12 @@ "ieee754": "^1.1.13" } }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -1348,6 +1501,15 @@ "xtend": "^4.0.0" } }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -2118,6 +2280,55 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsonwebtoken": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz", + "integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==", + "license": "MIT", + "dependencies": { + "jws": "^4.0.1", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jsonwebtoken/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", + "license": "MIT", + "dependencies": { + "jwa": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, "node_modules/libphonenumber-js": { "version": "1.12.40", "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.12.40.tgz", @@ -2130,6 +2341,48 @@ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "license": "MIT" }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "license": "MIT" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "license": "MIT" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "license": "MIT" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "license": "MIT" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "license": "MIT" + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "license": "MIT" + }, "node_modules/long": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", diff --git a/package.json b/package.json index 30ea720..4ff4c99 100644 --- a/package.json +++ b/package.json @@ -17,9 +17,11 @@ "@azure/openai": "^1.0.0-beta.13", "@nestjs/common": "^10.0.0", "@nestjs/core": "^10.0.0", + "@nestjs/jwt": "^11.0.2", "@nestjs/platform-express": "^10.0.0", "@nestjs/swagger": "^7.4.2", "axios": "^1.13.6", + "bcryptjs": "^3.0.3", "class-transformer": "^0.5.1", "class-validator": "^0.14.1", "dotenv": "^16.4.5", @@ -33,6 +35,8 @@ "swagger-ui-express": "^5.0.1" }, "devDependencies": { + "@types/bcryptjs": "^2.4.6", + "@types/multer": "^2.1.0", "@types/node": "^18.0.0", "ts-node-dev": "^2.0.0", "typescript": "^5.0.0" diff --git a/src/ai/ai.controller.ts b/src/ai/ai.controller.ts index 48c3e2e..456dc0e 100644 --- a/src/ai/ai.controller.ts +++ b/src/ai/ai.controller.ts @@ -1,4 +1,5 @@ -import { Body, Controller, Get, Post } from '@nestjs/common'; +import { Body, Controller, Get, Post, UploadedFiles, UseInterceptors } from '@nestjs/common'; +import { FilesInterceptor } from '@nestjs/platform-express'; import { AiService } from './ai.service'; import { AgentRunDto, @@ -38,6 +39,15 @@ export class AiController { return this.aiService.providerStatus(); } + @Post('documents/upload') + @UseInterceptors(FilesInterceptor('files')) + async uploadProjectDocuments( + @UploadedFiles() files: Array, + @Body('projectId') projectId: string, + ) { + return this.aiService.processUploadedDocuments(files, projectId); + } + @Post('protocol/interpret') interpretProtocol(@Body() dto: ProtocolInterpretationDto) { return this.aiService.interpretProtocol(dto); diff --git a/src/ai/ai.service.ts b/src/ai/ai.service.ts index 407b63b..88aea4b 100644 --- a/src/ai/ai.service.ts +++ b/src/ai/ai.service.ts @@ -39,6 +39,22 @@ export class AiService { }; } + async processUploadedDocuments(files: Array, projectId: string) { + if (!files || files.length === 0) { + throw new BadRequestException('No se han proporcionado archivos válidos'); + } + + // Aquí en el futuro puedes parsear PDFs o crear Embeddings de IA reales + // Por el momento devolveremos la lista de nombres como prueba de guardado exitoso + const fileNames = files.map(file => file.originalname); + return { + success: true, + message: `¡Se subieron ${fileNames.length} archivos para procesarlos!`, + project: projectId, + processedFiles: fileNames + }; + } + async interpretProtocol(dto: ProtocolInterpretationDto) { const system = 'You are a Responsible AI lab co-pilot. Always return valid JSON with keys: summary, checklist (array with title, action, cautions, riskLevel), instrumentation, hazards, compliance. Flag any disallowed content.'; diff --git a/src/app.module.ts b/src/app.module.ts index 2529a61..c824733 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -4,9 +4,10 @@ import { AppService } from './app.service'; import { AiModule } from './ai/ai.module'; import { DatabaseModule } from './database/database.module'; import { PlatformModule } from './platform/platform.module'; +import { AuthModule } from './auth/auth.module'; @Module({ - imports: [DatabaseModule, AiModule, PlatformModule], + imports: [DatabaseModule, AiModule, PlatformModule, AuthModule], controllers: [AppController], providers: [AppService], }) diff --git a/src/auth/auth.controller.ts b/src/auth/auth.controller.ts new file mode 100644 index 0000000..7b0f6f8 --- /dev/null +++ b/src/auth/auth.controller.ts @@ -0,0 +1,25 @@ +import { Controller, Post, Body, HttpCode, HttpStatus } from '@nestjs/common'; +import { AuthService } from './auth.service'; + +@Controller('auth') +export class AuthController { + constructor(private authService: AuthService) {} + + @HttpCode(HttpStatus.OK) + @Post('login') + login(@Body() signInDto: Record) { + return this.authService.login(signInDto.email, signInDto.password); + } + + @HttpCode(HttpStatus.CREATED) + @Post('register') + register(@Body() signUpDto: Record) { + return this.authService.register(signUpDto.name, signUpDto.email, signUpDto.password); + } + + @HttpCode(HttpStatus.OK) + @Post('forgot-password') + forgotPassword(@Body() resetDto: Record) { + return this.authService.forgotPassword(resetDto.email); + } +} diff --git a/src/auth/auth.module.ts b/src/auth/auth.module.ts new file mode 100644 index 0000000..e258a1c --- /dev/null +++ b/src/auth/auth.module.ts @@ -0,0 +1,18 @@ +import { Module } from '@nestjs/common'; +import { JwtModule } from '@nestjs/jwt'; +import { AuthController } from './auth.controller'; +import { AuthService } from './auth.service'; + +@Module({ + imports: [ + JwtModule.register({ + global: true, + secret: 'EL_SECRETO_SUPER_SEGURO_DE_SAPIENTLAB_123', // En producción debería ir en un archivo .env + signOptions: { expiresIn: '1d' }, + }), + ], + controllers: [AuthController], + providers: [AuthService], + exports: [AuthService], +}) +export class AuthModule {} \ No newline at end of file diff --git a/src/auth/auth.service.ts b/src/auth/auth.service.ts new file mode 100644 index 0000000..e91a1cf --- /dev/null +++ b/src/auth/auth.service.ts @@ -0,0 +1,91 @@ +import { Injectable, UnauthorizedException, BadRequestException, InternalServerErrorException } from '@nestjs/common'; +import { JwtService } from '@nestjs/jwt'; +import * as bcrypt from 'bcryptjs'; +import { DatabaseService } from '../database/database.service'; + +@Injectable() +export class AuthService { + constructor( + private jwtService: JwtService, + private databaseService: DatabaseService + ) {} + + async register(name: string, email: string, passwordString: string) { + // Validar si el usuario existe + const existingUser = await this.databaseService.queryOne( + 'SELECT id FROM users WHERE email = ?', + [email] + ); + + if (existingUser) { + throw new BadRequestException({ message: 'El correo ya está registrado' }); + } + + const hashedPassword = await bcrypt.hash(passwordString, 10); + const now = this.databaseService.getDialect() === 'mysql' + ? new Date().toISOString().slice(0, 19).replace('T', ' ') + : new Date().toISOString(); + + // Insertar nuevo usuario + let insertId; + try { + const result = await this.databaseService.execute( + 'INSERT INTO users (name, email, password, created_at) VALUES (?, ?, ?, ?)', + [name, email, hashedPassword, now] + ); + insertId = result.insertId; + } catch (error) { + throw new InternalServerErrorException({ message: 'Error al crear el usuario en la base de datos' }); + } + + const payload = { sub: insertId, email, name }; + const token = await this.jwtService.signAsync(payload); + + return { + message: 'Registro exitoso', + token, + user: { id: insertId, name, email } + }; + } + + async login(email: string, passwordString: string) { + // Buscar usuario en base de datos + const user = await this.databaseService.queryOne( + 'SELECT * FROM users WHERE email = ?', + [email] + ); + + if (!user) { + throw new UnauthorizedException({ message: 'Credenciales inválidas' }); + } + + const isMatch = await bcrypt.compare(passwordString, user.password); + if (!isMatch) { + throw new UnauthorizedException({ message: 'Credenciales inválidas' }); + } + + const payload = { sub: user.id, email: user.email, name: user.name }; + const token = await this.jwtService.signAsync(payload); + + return { + message: 'Login exitoso', + token, + user: { id: user.id, name: user.name, email: user.email } + }; + } + + async forgotPassword(email: string) { + const user = await this.databaseService.queryOne( + 'SELECT id FROM users WHERE email = ?', + [email] + ); + + if (!user) { + // Por seguridad, siempre devolvemos "éxito" aunque no exista el correo. + return { success: true, message: 'Si el correo existe, te hemos enviado un enlace.' }; + } + + // Aquí iría la lógica de NodeMailer o Resend para enviar el correo real + return { success: true, message: 'Si el correo existe, te hemos enviado un enlace.' }; + } +} diff --git a/temp_db.ts b/temp_db.ts new file mode 100644 index 0000000..bc5bec3 Binary files /dev/null and b/temp_db.ts differ diff --git a/ts_errors.txt b/ts_errors.txt new file mode 100644 index 0000000..e69de29