Se generalizó envio de correos a diferentes sistemas

This commit is contained in:
evenegas
2025-05-02 16:06:58 -06:00
parent 66c81120aa
commit 336bf545b7
19 changed files with 742 additions and 307 deletions
+312 -41
View File
@@ -12,9 +12,12 @@
"@nestjs/common": "^11.0.1",
"@nestjs/config": "^4.0.1",
"@nestjs/core": "^11.0.1",
"@nestjs/jwt": "^11.0.0",
"@nestjs/passport": "^11.0.5",
"@nestjs/platform-express": "^11.0.1",
"@nestjs/swagger": "^11.0.5",
"@nestjs/typeorm": "^11.0.0",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.1",
"csv-parser": "^3.2.0",
"dotenv": "^16.4.7",
@@ -23,6 +26,7 @@
"multer": "^1.4.5-lts.1",
"mysql2": "^3.13.0",
"nodemailer": "^6.10.0",
"passport": "^0.7.0",
"reflect-metadata": "^0.2.2",
"rxjs": "^7.8.1",
"typeorm": "^0.3.21"
@@ -371,25 +375,27 @@
}
},
"node_modules/@babel/helpers": {
"version": "7.26.9",
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.9.tgz",
"integrity": "sha512-Mz/4+y8udxBKdmzt/UjPACs4G3j5SshJJEFFKxlCGPydG4JAHXxjWjAwjd09tf6oINvl1VfMJo+nB7H2YKQ0dA==",
"version": "7.27.0",
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.0.tgz",
"integrity": "sha512-U5eyP/CTFPuNE3qk+WZMxFkp/4zUzdceQlfzf7DdGdhp+Fezd7HD+i8Y24ZuTMKX3wQBld449jijbGq6OdGNQg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/template": "^7.26.9",
"@babel/types": "^7.26.9"
"@babel/template": "^7.27.0",
"@babel/types": "^7.27.0"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/parser": {
"version": "7.26.9",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.9.tgz",
"integrity": "sha512-81NWa1njQblgZbQHxWHpxxCzNsa3ZwvFqpUg7P+NNUU6f3UU2jBEg4OlF/J6rl8+PQGh1q6/zWScd001YwcA5A==",
"version": "7.27.0",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.0.tgz",
"integrity": "sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/types": "^7.26.9"
"@babel/types": "^7.27.0"
},
"bin": {
"parser": "bin/babel-parser.js"
@@ -621,14 +627,15 @@
}
},
"node_modules/@babel/template": {
"version": "7.26.9",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.26.9.tgz",
"integrity": "sha512-qyRplbeIpNZhmzOysF/wFMuP9sctmh2cFzRAZOn1YapxBsE1i9bJIY586R/WBLfLcmcBlM8ROBiQURnnNy+zfA==",
"version": "7.27.0",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.0.tgz",
"integrity": "sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/code-frame": "^7.26.2",
"@babel/parser": "^7.26.9",
"@babel/types": "^7.26.9"
"@babel/parser": "^7.27.0",
"@babel/types": "^7.27.0"
},
"engines": {
"node": ">=6.9.0"
@@ -662,10 +669,11 @@
}
},
"node_modules/@babel/types": {
"version": "7.26.9",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.9.tgz",
"integrity": "sha512-Y3IR1cRnOxOCDvMmNiym7XpXQ93iGDDPHx+Zj+NM+rg0fBaShfQLkg+hKPaZCEvg5N/LeCo4+Rj/i3FuJsIQaw==",
"version": "7.27.0",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.0.tgz",
"integrity": "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-string-parser": "^7.25.9",
"@babel/helper-validator-identifier": "^7.25.9"
@@ -2163,11 +2171,14 @@
}
},
"node_modules/@nestjs/common": {
"version": "11.0.10",
"resolved": "https://registry.npmjs.org/@nestjs/common/-/common-11.0.10.tgz",
"integrity": "sha512-pzGXp14KF2Q4CDZGQgPK4l8zEg7i6cNkb+10yc8ZA5K41cLe3ZbWW1YxtY2e/glHauOJwTLSVjH4tiRVtOTizg==",
"version": "11.1.0",
"resolved": "https://registry.npmjs.org/@nestjs/common/-/common-11.1.0.tgz",
"integrity": "sha512-8MrajltjtIN6eW9cTpv+1IZogqz2Zsrc8YDt0LwQPUq8cSq0j50DETdQpPsNMeib+p9avkV41+NrzGk1z2o5Wg==",
"license": "MIT",
"dependencies": {
"file-type": "20.4.1",
"iterare": "1.2.1",
"load-esm": "1.0.2",
"tslib": "2.8.1",
"uid": "2.0.2"
},
@@ -2190,6 +2201,54 @@
}
}
},
"node_modules/@nestjs/common/node_modules/file-type": {
"version": "20.4.1",
"resolved": "https://registry.npmjs.org/file-type/-/file-type-20.4.1.tgz",
"integrity": "sha512-hw9gNZXUfZ02Jo0uafWLaFVPter5/k2rfcrjFJJHX/77xtSDOfJuEFb6oKlFV86FLP1SuyHMW1PSk0U9M5tKkQ==",
"license": "MIT",
"dependencies": {
"@tokenizer/inflate": "^0.2.6",
"strtok3": "^10.2.0",
"token-types": "^6.0.0",
"uint8array-extras": "^1.4.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sindresorhus/file-type?sponsor=1"
}
},
"node_modules/@nestjs/common/node_modules/peek-readable": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-7.0.0.tgz",
"integrity": "sha512-nri2TO5JE3/mRryik9LlHFT53cgHfRK0Lt0BAZQXku/AW3E6XLt2GaY8siWi7dvW/m1z0ecn+J+bpDa9ZN3IsQ==",
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/Borewit"
}
},
"node_modules/@nestjs/common/node_modules/strtok3": {
"version": "10.2.2",
"resolved": "https://registry.npmjs.org/strtok3/-/strtok3-10.2.2.tgz",
"integrity": "sha512-Xt18+h4s7Z8xyZ0tmBoRmzxcop97R4BAh+dXouUDCYn+Em+1P3qpkUfI5ueWLT8ynC5hZ+q4iPEmGG1urvQGBg==",
"license": "MIT",
"dependencies": {
"@tokenizer/token": "^0.3.0",
"peek-readable": "^7.0.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/Borewit"
}
},
"node_modules/@nestjs/config": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/@nestjs/config/-/config-4.0.1.tgz",
@@ -2244,6 +2303,19 @@
}
}
},
"node_modules/@nestjs/jwt": {
"version": "11.0.0",
"resolved": "https://registry.npmjs.org/@nestjs/jwt/-/jwt-11.0.0.tgz",
"integrity": "sha512-v7YRsW3Xi8HNTsO+jeHSEEqelX37TVWgwt+BcxtkG/OfXJEOs6GZdbdza200d6KqId1pJQZ6UPj1F0M6E+mxaA==",
"license": "MIT",
"dependencies": {
"@types/jsonwebtoken": "9.0.7",
"jsonwebtoken": "9.0.2"
},
"peerDependencies": {
"@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0"
}
},
"node_modules/@nestjs/mapped-types": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@nestjs/mapped-types/-/mapped-types-2.1.0.tgz",
@@ -2263,6 +2335,16 @@
}
}
},
"node_modules/@nestjs/passport": {
"version": "11.0.5",
"resolved": "https://registry.npmjs.org/@nestjs/passport/-/passport-11.0.5.tgz",
"integrity": "sha512-ulQX6mbjlws92PIM15Naes4F4p2JoxGnIJuUsdXQPT+Oo2sqQmENEZXM7eYuimocfHnKlcfZOuyzbA33LwUlOQ==",
"license": "MIT",
"peerDependencies": {
"@nestjs/common": "^10.0.0 || ^11.0.0",
"passport": "^0.5.0 || ^0.6.0 || ^0.7.0"
}
},
"node_modules/@nestjs/platform-express": {
"version": "11.0.10",
"resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-11.0.10.tgz",
@@ -2446,6 +2528,19 @@
"typeorm": "^0.3.0"
}
},
"node_modules/@noble/hashes": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz",
"integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^14.21.3 || >=16"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -2496,6 +2591,16 @@
"npm": ">=5.10.0"
}
},
"node_modules/@paralleldrive/cuid2": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/@paralleldrive/cuid2/-/cuid2-2.2.2.tgz",
"integrity": "sha512-ZOBkgDwEdoYVlSeRbYYXs0S9MejQofiVYoTbKzy/6GQa39/q5tQU2IX46+shYnUkpEl3wc+J6wRlar7r2EK2xA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@noble/hashes": "^1.1.5"
}
},
"node_modules/@pkgjs/parseargs": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
@@ -2862,11 +2967,28 @@
"node": ">=14.16"
}
},
"node_modules/@tokenizer/inflate": {
"version": "0.2.7",
"resolved": "https://registry.npmjs.org/@tokenizer/inflate/-/inflate-0.2.7.tgz",
"integrity": "sha512-MADQgmZT1eKjp06jpI2yozxaU9uVs4GzzgSL+uEq7bVcJ9V1ZXQkeGNql1fsSI0gMy1vhvNTNbUqrx+pZfJVmg==",
"license": "MIT",
"dependencies": {
"debug": "^4.4.0",
"fflate": "^0.8.2",
"token-types": "^6.0.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/Borewit"
}
},
"node_modules/@tokenizer/token": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz",
"integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==",
"dev": true
"integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A=="
},
"node_modules/@tsconfig/node10": {
"version": "1.0.11",
@@ -3069,6 +3191,15 @@
"integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
"dev": true
},
"node_modules/@types/jsonwebtoken": {
"version": "9.0.7",
"resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.7.tgz",
"integrity": "sha512-ugo316mmTYBl2g81zDFnZ7cfxlut3o+/EQdaP7J8QN2kY6lJ22hmQYCK5EHcJHbrW+dkCGSCPgbG8JtYj6qSrg==",
"license": "MIT",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/methods": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/@types/methods/-/methods-1.1.4.tgz",
@@ -3094,7 +3225,6 @@
"version": "22.13.5",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.5.tgz",
"integrity": "sha512-+lTU0PxZXn0Dr1NBtC7Y8cR21AJr87dLLU953CWA6pMxxv/UDc7jYAY90upcrie1nRcD6XNG5HOYEDtgW5TxAg==",
"devOptional": true,
"dependencies": {
"undici-types": "~6.20.0"
}
@@ -4431,6 +4561,12 @@
"node": "*"
}
},
"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",
@@ -4650,6 +4786,12 @@
"integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==",
"dev": true
},
"node_modules/class-transformer": {
"version": "0.5.1",
"resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz",
"integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==",
"license": "MIT"
},
"node_modules/class-validator": {
"version": "0.14.1",
"resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.14.1.tgz",
@@ -5296,6 +5438,15 @@
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="
},
"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",
@@ -6004,6 +6155,12 @@
"bser": "2.1.1"
}
},
"node_modules/fflate": {
"version": "0.8.2",
"resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz",
"integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==",
"license": "MIT"
},
"node_modules/file-entry-cache": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
@@ -6368,15 +6525,19 @@
}
},
"node_modules/formidable": {
"version": "3.5.2",
"resolved": "https://registry.npmjs.org/formidable/-/formidable-3.5.2.tgz",
"integrity": "sha512-Jqc1btCy3QzRbJaICGwKcBfGWuLADRerLzDqi2NwSt/UkXLsHJw2TVResiaoBufHVHy9aSgClOHCeJsSsFLTbg==",
"version": "3.5.4",
"resolved": "https://registry.npmjs.org/formidable/-/formidable-3.5.4.tgz",
"integrity": "sha512-YikH+7CUTOtP44ZTnUhR7Ic2UASBPOqmaRkRKxRbywPTe5VxF7RRCck4af9wutiZ/QKM5nME9Bie2fFaPz5Gug==",
"dev": true,
"license": "MIT",
"dependencies": {
"@paralleldrive/cuid2": "^2.2.2",
"dezalgo": "^1.0.4",
"hexoid": "^2.0.0",
"once": "^1.4.0"
},
"engines": {
"node": ">=14.0.0"
},
"funding": {
"url": "https://ko-fi.com/tunnckoCore/commissions"
}
@@ -6729,15 +6890,6 @@
"node": ">= 0.4"
}
},
"node_modules/hexoid": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/hexoid/-/hexoid-2.0.0.tgz",
"integrity": "sha512-qlspKUK7IlSQv2o+5I7yhUd7TxlOG2Vr5LTa3ve2XSNVKAL/n/u/7KLvKmFNimomDIKvZFXWHv0T12mv7rT8Aw==",
"dev": true,
"engines": {
"node": ">=8"
}
},
"node_modules/html-escaper": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
@@ -7876,6 +8028,28 @@
"graceful-fs": "^4.1.6"
}
},
"node_modules/jsonwebtoken": {
"version": "9.0.2",
"resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz",
"integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==",
"license": "MIT",
"dependencies": {
"jws": "^3.2.2",
"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/jszip": {
"version": "3.10.1",
"resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz",
@@ -7887,6 +8061,27 @@
"setimmediate": "^1.0.5"
}
},
"node_modules/jwa": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz",
"integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==",
"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": "3.2.2",
"resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz",
"integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==",
"license": "MIT",
"dependencies": {
"jwa": "^1.4.1",
"safe-buffer": "^5.0.1"
}
},
"node_modules/keyv": {
"version": "4.5.4",
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
@@ -7972,6 +8167,25 @@
"resolved": "https://registry.npmjs.org/listenercount/-/listenercount-1.0.1.tgz",
"integrity": "sha512-3mk/Zag0+IJxeDrxSgaDPy4zZ3w05PRZeJNnlWhzFz5OkX49J4krc+A8X2d2M69vGMBEX0uyl8M+W+8gH+kBqQ=="
},
"node_modules/load-esm": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/load-esm/-/load-esm-1.0.2.tgz",
"integrity": "sha512-nVAvWk/jeyrWyXEAs84mpQCYccxRqgKY4OznLuJhJCa0XsPSfdOIr2zvBZEj3IHEHbX97jjscKRRV539bW0Gpw==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/Borewit"
},
{
"type": "buymeacoffee",
"url": "https://buymeacoffee.com/borewit"
}
],
"license": "MIT",
"engines": {
"node": ">=13.2.0"
}
},
"node_modules/loader-runner": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz",
@@ -8026,6 +8240,12 @@
"resolved": "https://registry.npmjs.org/lodash.groupby/-/lodash.groupby-4.6.0.tgz",
"integrity": "sha512-5dcWxm23+VAoz+awKmBaiBvzox8+RqMgFhi7UvX9DHZr2HdxHXM/Wrf8cfKpsW37RNrvtPn6hSwNqurSILbmJw=="
},
"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",
@@ -8042,16 +8262,34 @@
"resolved": "https://registry.npmjs.org/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz",
"integrity": "sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw=="
},
"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.isnil": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/lodash.isnil/-/lodash.isnil-4.0.0.tgz",
"integrity": "sha512-up2Mzq3545mwVnMhTDMdfoG1OurpA/s5t88JmQX809eH3C8491iu2sfKhTfhQtKY78oPNhiaHJUpT/dUDAAtng=="
},
"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=="
},
"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.isundefined": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/lodash.isundefined/-/lodash.isundefined-3.0.1.tgz",
@@ -8069,6 +8307,12 @@
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
"dev": true
},
"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/lodash.union": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz",
@@ -8776,6 +9020,32 @@
"node": ">= 0.8"
}
},
"node_modules/passport": {
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/passport/-/passport-0.7.0.tgz",
"integrity": "sha512-cPLl+qZpSc+ireUvt+IzqbED1cHHkDoVYMo30jbJIdOOjQ1MQYZBPiNvmi8UM6lJuOpTPXJGZQk0DtC4y61MYQ==",
"license": "MIT",
"dependencies": {
"passport-strategy": "1.x.x",
"pause": "0.0.1",
"utils-merge": "^1.0.1"
},
"engines": {
"node": ">= 0.4.0"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/jaredhanson"
}
},
"node_modules/passport-strategy": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz",
"integrity": "sha512-CB97UUvDKJde2V0KDWWB3lyf6PC3FaZP7YxZ2G8OAtn9p4HI9j9JLP9qjOGZFvyl8uwNT8qM+hGnz/n16NI7oA==",
"engines": {
"node": ">= 0.4.0"
}
},
"node_modules/path-exists": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
@@ -8849,6 +9119,11 @@
"node": ">=8"
}
},
"node_modules/pause": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz",
"integrity": "sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg=="
},
"node_modules/peek-readable": {
"version": "5.4.2",
"resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-5.4.2.tgz",
@@ -9542,7 +9817,6 @@
"version": "7.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz",
"integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==",
"dev": true,
"bin": {
"semver": "bin/semver.js"
},
@@ -10482,7 +10756,6 @@
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/token-types/-/token-types-6.0.0.tgz",
"integrity": "sha512-lbDrTLVsHhOMljPscd0yitpozq7Ga2M5Cvez5AjGg8GASBjtt6iERCAJ93yommPmz62fb45oFIXHEZ3u9bfJEA==",
"dev": true,
"dependencies": {
"@tokenizer/token": "^0.3.0",
"ieee754": "^1.2.1"
@@ -10981,7 +11254,6 @@
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-1.4.0.tgz",
"integrity": "sha512-ZPtzy0hu4cZjv3z5NW9gfKnNLjoz4y6uv4HlelAjDK7sY/xOkKZv9xK/WQpcsBB3jEybChz9DPC2U/+cusjJVQ==",
"dev": true,
"engines": {
"node": ">=18"
},
@@ -11002,8 +11274,7 @@
"node_modules/undici-types": {
"version": "6.20.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz",
"integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==",
"devOptional": true
"integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="
},
"node_modules/universalify": {
"version": "2.0.1",
+4
View File
@@ -23,9 +23,12 @@
"@nestjs/common": "^11.0.1",
"@nestjs/config": "^4.0.1",
"@nestjs/core": "^11.0.1",
"@nestjs/jwt": "^11.0.0",
"@nestjs/passport": "^11.0.5",
"@nestjs/platform-express": "^11.0.1",
"@nestjs/swagger": "^11.0.5",
"@nestjs/typeorm": "^11.0.0",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.1",
"csv-parser": "^3.2.0",
"dotenv": "^16.4.7",
@@ -34,6 +37,7 @@
"multer": "^1.4.5-lts.1",
"mysql2": "^3.13.0",
"nodemailer": "^6.10.0",
"passport": "^0.7.0",
"reflect-metadata": "^0.2.2",
"rxjs": "^7.8.1",
"typeorm": "^0.3.21"
+5 -2
View File
@@ -12,7 +12,10 @@ import { SistemaModule } from './sistema/sistema.module';
@Module({
imports: [
ConfigModule.forRoot(),
ConfigModule.forRoot(
{isGlobal: true}
),
TypeOrmModule.forRoot({
type: 'mysql',
host: process.env.db_host,
@@ -21,7 +24,7 @@ import { SistemaModule } from './sistema/sistema.module';
password: process.env.db_password,
port: Number(process.env.db_port),
synchronize: true,
dropSchema: true, // elimina la base de datos
dropSchema: false, // elimina la base de datos
// logging: true, // Habilita los logs para depuración
autoLoadEntities: true, // Carga automáticamente las entidades
+22
View File
@@ -0,0 +1,22 @@
// auth.module.ts
import { Module } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { AuthService } from './auth.service';
@Module({
imports: [
ConfigModule, // Ya es global, pero lo puedes dejar
JwtModule.registerAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: (config: ConfigService) => ({
secret: config.get('JWT_SECRET'),
signOptions: { expiresIn: '1d' },
}),
}),
],
providers: [AuthService],
exports: [AuthService, JwtModule],
})
export class AuthModule {}
+24
View File
@@ -0,0 +1,24 @@
import { Injectable } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
@Injectable()
export class AuthService {
constructor(private jwtService: JwtService) {}
generarToken(payload: any) {
return this.jwtService.sign(payload, {
secret: process.env.JWT_SECRET, // <-- Aquí lo defines explícitamente
expiresIn: '1d',
});
}
verificarToken(token: string) {
try {
return this.jwtService.verify(token, {
secret: process.env.JWT_SECRET, // <-- También aquí
});
} catch {
return null;
}
}
}
+35
View File
@@ -0,0 +1,35 @@
import * as crypto from 'crypto';
export function encrypt(text: string) {
const secret = process.env.KEY_SECRET;
if (!secret) throw new Error('Falta la variable de entorno KEY_SECRET');
const key = crypto.scryptSync(secret, 'salt', 32);
const iv = crypto.randomBytes(16);
const cipher = crypto.createCipheriv('aes-256-cbc', key, iv);
let encrypted = cipher.update(text, 'utf8', 'hex');
encrypted += cipher.final('hex');
return {
iv: iv.toString('hex'),
encryptedData: encrypted,
};
}
export function decrypt(encryptedData: string, ivHex: string) {
const secret = process.env.KEY_SECRET;
if (!process.env.KEY_SECRET) {
throw new Error('Falta la variable de entorno KEY_SECRET');
}
if (!secret) throw new Error('Falta la variable de entorno KEY_SECRET');
const key = crypto.scryptSync(secret, 'salt', 32);
const iv = Buffer.from(ivHex, 'hex');
const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv);
let decrypted = decipher.update(encryptedData, 'hex', 'utf8');
decrypted += decipher.final('utf8');
return decrypted;
}
+29
View File
@@ -0,0 +1,29 @@
import { IsString, IsEmail, IsDate, IsOptional } from 'class-validator';
import { Type } from 'class-transformer';
export class SendCorreoDto {
@IsEmail()
to: string;
@IsString()
@IsOptional()
subject: string;
@IsString()
@IsOptional()
text: string;
@IsDate()
@Type(() => Date)
fecha_recibido: Date;
@IsString()
@IsOptional()
html: string;
@IsOptional()
adjuntos: any;
}
+57 -35
View File
@@ -6,24 +6,30 @@ import {
Headers,
UnauthorizedException,
Get,
UseInterceptors,
} from '@nestjs/common';
import { MailService } from './mail.service';
import { ExcelService } from 'src/excel/excel.service';
import { ApiTags } from '@nestjs/swagger';
import { ApiFindAllMails, ApiSendMail, ApiSendMailExcel} from './mail.decorators';
import { ApiSendMail} from './mail.decorators';
import { Repository } from 'typeorm';
import { Sistema } from 'src/typeorm/votacionesPrueba.entity';
import { Sistema } from 'src/typeorm/entidades';
import { InjectRepository } from '@nestjs/typeorm';
import { SistemaService } from 'src/sistema/sistema.service';
import { JwtService } from '@nestjs/jwt';
import { SendCorreoDto } from 'src/mail/dto/send-email.dto';
import { FileInterceptor } from '@nestjs/platform-express';
import { AuthService } from 'src/auth/auth.service';
@ApiTags('Mail') // Grupo de endpoints
@Controller('mail')
export class MailController {
constructor(
private readonly sistemaService: SistemaService,
private readonly mailService: MailService,
private readonly excelService: ExcelService,
@InjectRepository(Sistema) private readonly sistemaRepository: Repository<Sistema>,
// private readonly excelService: ExcelService,
private readonly authService: AuthService
) {}
@@ -35,22 +41,32 @@ export class MailController {
@ApiSendMail()
async sendMail(
@Headers() headers:{token:string},
@Body() body: { to: string; subject: string; text: string; fecha_recibido: Date, html:string, adjuntos:any }
@Body() body: SendCorreoDto
) {
if (!headers.token) {
throw new UnauthorizedException('Header "token" es requerido');
}
const sistem=await this.sistemaRepository.findOne({where:{token:headers.token}})
if(!sistem){
throw new UnauthorizedException('Password incorrecto');
}
let payload
const { to, subject, text, html, fecha_recibido, adjuntos } = body;
payload = await this.authService.verificarToken(headers.token);
if (!payload || !payload.nombre) {
throw new UnauthorizedException('Token inválido o incompleto');
}
const sistem= await this.sistemaService.findByNombre(payload.nombre)
if(!sistem){
throw new UnauthorizedException('sistema No encontrado');
}
const result = await this.mailService.sendMail(to, subject, text,html, fecha_recibido,sistem,adjuntos); // sistem
const result = await this.mailService.sendMail(body,sistem); // sistem
return result;
@@ -58,42 +74,48 @@ export class MailController {
}
/*
@Post('send-excel')
@UseInterceptors(FileInterceptor('file'))
@ApiSendMailExcel()
async sendMailExcel(@Headers() headers:{password:string},@Body() file: Express.Multer.File) {
if (!headers.password) {
throw new UnauthorizedException('Header "Password" es requerido');
async sendMailExcel(
@Headers() headers: { token: string },
@UploadedFile() file: Express.Multer.File
) {
if (!headers.token) {
throw new UnauthorizedException('Header "token" es requerido');
}
const sistem = await this.sistemaRepository.findOne({ where: { token: headers.password } });
let payload;
try {
payload = await this.jwtService.verify(headers.token);
} catch {
throw new UnauthorizedException('Token inválido');
}
const sistem = await this.sistemaService.findByNombre(payload.nombre);
if (!sistem) {
throw new UnauthorizedException('Password incorrecto');
throw new UnauthorizedException('Sistema no encontrado');
}
if (!file) {
throw new Error('Archivo Excel no encontrado');
}
const emails=this.excelService.readEmailsFromExcel(file);
let fechaActual: Date = new Date();
for (const email of await emails) {
}
const emails = await this.excelService.readEmailsFromExcel(file);
const fechaActual: Date = new Date();
for (const email of emails) {
try {
await this.mailService.sendMail(email, "subject", "text","html", fechaActual,sistem);
console.log(`📧 Enviado a: ${email}`);
await this.mailService.sendMail(email, 'subject', 'text', 'html', fechaActual, sistem);
console.log(`Enviado a: ${email}`);
} catch (error) {
console.error(`Error enviando a ${email}:`, error.message);
console.error(`Error enviando a ${email}:`, error.message);
}
}
}
@Get()
@ApiFindAllMails()
async findAll(@Headers() headers:{token:string}){
return await this.mailService.findAll(headers.token)
return { message: 'Correos enviados (si no hubo errores)' };
}
*/
}
File diff suppressed because one or more lines are too long
+4 -2
View File
@@ -7,11 +7,13 @@ import {
Status,
Correo,
Sistema,
}from 'src/typeorm/votacionesPrueba.entity'
}from 'src/typeorm/entidades'
import { SistemaModule } from 'src/sistema/sistema.module';
import { JwtService } from '@nestjs/jwt';
import { AuthService } from 'src/auth/auth.service';
@Module({
providers: [MailService],
providers: [MailService,JwtService,AuthService],
imports: [
ExcelModule,
SistemaModule,
+1 -1
View File
@@ -2,7 +2,7 @@ import { Test, TestingModule } from '@nestjs/testing';
import { MailService } from './mail.service';
import { getRepositoryToken } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Correo, Sistema, Status } from '../typeorm/votacionesPrueba.entity'; // Ajusta si es necesario
import { Correo, Sistema, Status } from '../typeorm/entidades'; // Ajusta si es necesario
describe('MailService', () => {
let service: MailService;
+69 -25
View File
@@ -3,18 +3,25 @@ import * as nodemailer from 'nodemailer';
import * as dotenv from 'dotenv';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Correo, Sistema, Status } from '../typeorm/votacionesPrueba.entity';
import { Correo, Sistema, Status } from '../typeorm/entidades';
import { SistemaService } from 'src/sistema/sistema.service';
import { SendCorreoDto } from 'src/mail/dto/send-email.dto';
import { AuthService } from 'src/auth/auth.service';
import { decrypt } from '../crypto/crypto.util';
dotenv.config();
@Injectable()
export class MailService {
private transporter;
constructor(
private readonly sistemaService: SistemaService,
private readonly authService: AuthService,
@InjectRepository(Correo) private readonly correoRepository: Repository<Correo>,
@InjectRepository(Status) private readonly statusRepository: Repository<Status>,
@InjectRepository(Sistema) private readonly sistemaRepository: Repository<Sistema>
@InjectRepository(Sistema) private readonly sistemaRepositosistemaRepositoryry: Repository<Sistema>
) {
@@ -31,6 +38,8 @@ export class MailService {
},
}); */
/*
this.transporter = nodemailer.createTransport({
host: 'smtp.gmail.com', // o smtprelay.gmail.com (ver punto 2)
port: 587,
@@ -45,7 +54,7 @@ export class MailService {
});
*/
/*
this.transporter = nodemailer.createTransport({
@@ -57,39 +66,74 @@ export class MailService {
}
async findAll(TOKEN:string){
async findAllBySistema(TOKEN:string,nombreSistema:string){
if (!TOKEN) {
throw new UnauthorizedException('Header "Password" es requerido');
}
const sistem = await this.sistemaRepository.findOne({ where: { token:TOKEN } });
if (!sistem) {
const sistem=await this.sistemaService.acceso(TOKEN)
if(!sistem){
throw new UnauthorizedException('Password incorrecto');
}
const result= await this.correoRepository.find()
const result= await this.correoRepository.find({where:{
id_sistema:{nombre_sistema:nombreSistema}
},
relations: {
id_sistema: true,
id_status: true,
},}
)
return result
}
async sendMail(to: string, subject: string, text: string, html: string, fecha_recibido: Date, sistema, adjuntos?: any[] ) {
async sendMail(sendEmail:SendCorreoDto,sistem:Sistema,) {
const decryptedPassword = decrypt(
sistem.email_password_encrypted,
sistem.email_password_iv
);
const transporter = nodemailer.createTransport({
host: 'smtp.gmail.com', // o smtprelay.gmail.com (ver punto 2)
port: 587,
secure: false,
requireTLS: true, // TLS STARTTLS
auth: { user:sistem.email, pass:decryptedPassword},
pool: true, // <‑‑ activa reuse
maxConnections: 1, // una sola conexión viva
maxMessages: 100, // reabrir después de 100 envíos
rateDelta: 2000, // ventana 1 s
rateLimit: 1 // máx. 5 mensajes/seg
});
console.log("estos son los archivos adjuntos en el service",adjuntos);
console.log("estos son los archivos adjuntos en el service",sendEmail.adjuntos);
const mailOptions = {
from: process.env.USER_GMAIL,
to,
subject,
text,
html,
from: sistem.email,
to:sendEmail.to,
subject:sendEmail.subject,
text: sendEmail.text,
html:sendEmail.html,
// OJO: nodemailer usa 'attachments', no 'adjuntos'
attachments: (adjuntos || []).map((adj) => {
attachments: (sendEmail.adjuntos || []).map((adj) => {
// Asegúrate de no incluir 'data:image/png;base64,' en 'content'
let base64Clean = adj.content;
// Si viene con prefijo "data:image...", lo quitamos
@@ -114,7 +158,7 @@ export class MailService {
let resMail = await this.transporter.sendMail(mailOptions);
let resMail = await transporter.sendMail(mailOptions);
const statusTexto = resMail.accepted.length > 0 ? "Enviado" : "Fallido";
@@ -128,10 +172,10 @@ export class MailService {
const info= this.correoRepository.create({
id_status: status,
id_sistema: sistema,
fecha_recibido: fecha_recibido,
id_sistema: sistem,
fecha_recibido: sendEmail.fecha_recibido,
fecha_enviado: new Date(),
destinatario: to,
destinatario: sendEmail.to,
remitente: process.env.USER_GMAIL,
});
+4 -7
View File
@@ -1,8 +1,5 @@
import { IsString, IsOptional, IsBoolean, IsNotEmpty } from 'class-validator';
// comprobar-sistema.dto.ts
export class ComprobarSistemaDto {
@IsNotEmpty()
@IsString()
token: string;
}
password: string;
}
+10
View File
@@ -13,4 +13,14 @@ export class CreateSistemaDto {
@IsString()
password: string;
@IsNotEmpty()
@IsString()
email: string;
@IsNotEmpty()
@IsString()
email_password:string
}
+14 -5
View File
@@ -1,8 +1,8 @@
import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/common';
import { Controller, Get, Post, Body, Patch, Param, Query } from '@nestjs/common';
import { SistemaService } from './sistema.service';
import { CreateSistemaDto } from './dto/create-sistema.dto';
import { ApiCreateSistema, ApiGenerarToken } from './sistema.decorators';
import { ComprobarSistemaDto } from './dto/comprobar-sistema';
import { ApiAccesoSistema, ApiCreateSistema, ApiGenerarToken } from './sistema.decorators';
@Controller('sistema')
export class SistemaController {
@@ -16,7 +16,16 @@ export class SistemaController {
@Patch(':nombre_sistema')
@ApiGenerarToken()
update(@Param('nombre_sistema') nombre_sistema: string, @Body() password: string) {
return this.sistemaService.Generar_Token(nombre_sistema, password);
update(
@Param('nombre_sistema') nombre_sistema: string,
@Body() comprobarSistemaDto: ComprobarSistemaDto,
) {
return this.sistemaService.Generar_Token(nombre_sistema, comprobarSistemaDto.password);
}
@ApiAccesoSistema()
@Get('acceso')
async acceso(@Query('token') token: string) {
return this.sistemaService.acceso(token);
}
}
+59 -16
View File
@@ -1,26 +1,47 @@
import { applyDecorators } from '@nestjs/common';
import { ApiBody, ApiOperation, ApiParam } from '@nestjs/swagger';
import { ApiBody, ApiOperation, ApiParam, ApiQuery } from '@nestjs/swagger';
export const ApiCreateSistema = () => {
return applyDecorators(
ApiOperation({
summary: 'Registrar un nuevo sistema',
description: `Este endpoint permite dar de alta un sistema para poder utilizar funcionalidades autenticadas mediante contraseña.
Se debe proporcionar:
- **Nombre**: Nombre único del sistema.
- **ip**: Dirección IP desde la cual se harán peticiones.
- **password**: Contraseña segura que el sistema usará para autenticarse.`,
description: `Este endpoint permite registrar un sistema que podrá autenticarse y utilizar servicios protegidos.
Debe incluirse la contraseña maestra del administrador para autorizar la creación.
Además, se deben proporcionar las credenciales del correo que el sistema usará para enviar emails.`,
}),
ApiBody({
description: 'Datos del sistema a registrar',
schema: {
type: 'object',
properties: {
Nombre: { type: 'string', example: 'Sistema de Reportes' },
ip: { type: 'string', example: '192.168.1.100' },
password: { type: 'string', example: 'clave-segura-123' },
Nombre: {
type: 'string',
example: 'Sistema de Notificaciones',
description: 'Nombre único del sistema a registrar',
},
ip: {
type: 'string',
example: '192.168.0.10',
description: 'Dirección IP desde donde se harán las peticiones',
},
email: {
type: 'string',
format: 'email',
example: 'sistema@gmail.com',
description: 'Correo electrónico del sistema para enviar emails',
},
email_password: {
type: 'string',
example: 'contraseña-de-aplicacion',
description: 'Contraseña de aplicación del correo proporcionado',
},
password: {
type: 'string',
example: 'clave-admin-secreta',
description: 'Contraseña maestra para autorizar el registro',
},
},
required: ['Nombre', 'ip', 'password'],
required: ['Nombre', 'ip', 'email', 'email_password', 'password'],
},
})
);
@@ -30,21 +51,43 @@ export const ApiGenerarToken = () => {
return applyDecorators(
ApiOperation({
summary: 'Generar token JWT para un sistema',
description: `Este endpoint permite generar un token de acceso válido para el sistema registrado.
Se debe enviar el nombre del sistema como parámetro de ruta y la contraseña como cuerpo.`,
description: `Este endpoint permite generar un token de autenticación JWT para un sistema previamente registrado.
Se debe enviar el nombre del sistema como parámetro de ruta y su contraseña de registro en el cuerpo.`,
}),
ApiParam({
name: 'nombre_sistema',
required: true,
description: 'Nombre exacto del sistema registrado',
example: 'Sistema de Reportes',
example: 'Sistema de Notificaciones',
}),
ApiBody({
description: 'Contraseña del sistema',
description: 'Contraseña del sistema para verificar identidad',
schema: {
type: 'string',
example: 'clave-segura-123',
type: 'object',
properties: {
password: {
type: 'string',
example: 'clave-admin-secreta',
description: 'Contraseña de autenticación del sistema',
},
},
required: ['password'],
},
})
);
};
export const ApiAccesoSistema = () => {
return applyDecorators(
ApiOperation({
summary: 'Verificar acceso del sistema mediante token',
description: `Este endpoint permite verificar si un token JWT enviado por un sistema es válido y tiene acceso.`,
}),
ApiQuery({
name: 'token',
required: true,
description: 'Token JWT del sistema',
example: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...',
})
);
};
+4 -2
View File
@@ -2,14 +2,16 @@ import { Module } from '@nestjs/common';
import { SistemaService } from './sistema.service';
import { SistemaController } from './sistema.controller';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Sistema } from 'src/typeorm/votacionesPrueba.entity';
import { Sistema } from 'src/typeorm/entidades';
import { AuthModule } from 'src/auth/auth.module';
@Module({
controllers: [SistemaController],
providers: [SistemaService],
imports:[
TypeOrmModule.forFeature([
Sistema])
Sistema]),
AuthModule
],
exports:[SistemaModule,SistemaService]
})
+47 -39
View File
@@ -1,63 +1,71 @@
import { Injectable, NotFoundException, UnauthorizedException } from '@nestjs/common';
import { CreateSistemaDto } from './dto/create-sistema.dto';
import { InjectRepository } from '@nestjs/typeorm';
import { Sistema } from 'src/typeorm/votacionesPrueba.entity';
import { Sistema } from 'src/typeorm/entidades';
import { Repository } from 'typeorm';
import { randomBytes } from 'crypto';
import { ComprobarSistemaDto } from './dto/comprobar-sistema';
import { AuthService } from 'src/auth/auth.service';
import { encrypt } from 'src/crypto/crypto.util';
@Injectable()
export class SistemaService {
constructor(
@InjectRepository(Sistema) private readonly sistemaRepository: Repository<Sistema>
@InjectRepository(Sistema) private readonly sistemaRepository: Repository<Sistema>,
private readonly authService: AuthService
) {}
){}
generarPassword(longitud = 12): string {
return randomBytes(longitud)
.toString('base64')
.slice(0, longitud)
.replace(/[+/=]/g, () => String.fromCharCode(33 + Math.floor(Math.random() * 94))); // reemplaza caracteres raros
}
async Generar_Token(nombreSistema:string,adminPasword:string) {
if(adminPasword!==process.env.ADMIN_PASSWORD){
throw new UnauthorizedException('Password erroneo');
async Generar_Token(nombreSistema: string, adminPassword: string) {
if (adminPassword !== process.env.ADMIN_PASSWORD) {
throw new UnauthorizedException('Password erróneo');
}
const sistema = await this.sistemaRepository.findOneBy({nombre_sistema:nombreSistema})
if(!sistema){
throw new NotFoundException('Sistema no encontrado')
}
sistema.token=this.generarPassword()
this.sistemaRepository.save(sistema)
return sistema.token;
const sistema = await this.sistemaRepository.findOneBy({ nombre_sistema: nombreSistema });
if (!sistema) {
throw new NotFoundException('Sistema no encontrado');
}
const token = this.authService.generarToken({ sistemaId: sistema.id_sistema, nombre: sistema.nombre_sistema, ip:sistema.ip });
return token;
}
async acceso(TOKEN:string){
const result = await this.sistemaRepository.findOne({where:{token:TOKEN}})
if(!result){
throw new UnauthorizedException('token erroneo')
async acceso(token: string) {
const decoded = this.authService.verificarToken(token);
if (!decoded) {
throw new UnauthorizedException('Token inválido');
}
return true
const sistema = await this.sistemaRepository.findOneBy({ id_sistema: decoded.sistemaId });
if (!sistema) {
throw new UnauthorizedException('Sistema no encontrado');
}
return true;
}
async create(createSistemaDto: CreateSistemaDto) {
if(createSistemaDto.password!==process.env.ADMIN_PASSWORD){
throw new UnauthorizedException('Password erroneo');
if (createSistemaDto.password !== process.env.ADMIN_PASSWORD) {
throw new UnauthorizedException('Password erróneo');
}
const sistem = this.sistemaRepository.create({
nombre_sistema:createSistemaDto.Nombre,
ip:createSistemaDto.ip,
});
await this.sistemaRepository.save(sistem)
const { encryptedData, iv } = encrypt(createSistemaDto.email_password);
return this.Generar_Token(createSistemaDto.Nombre,createSistemaDto.password);
const sistema = this.sistemaRepository.create({
nombre_sistema: createSistemaDto.Nombre,
ip: createSistemaDto.ip,
email:createSistemaDto.email,
email_password_encrypted: encryptedData,
email_password_iv: iv
});
await this.sistemaRepository.save(sistema);
return this.Generar_Token(createSistemaDto.Nombre, createSistemaDto.password);
}
async findByNombre(sistema:string){
const sistem= this.sistemaRepository.findOne({where:{nombre_sistema:sistema}})
if(!sistem){
throw new UnauthorizedException('Password erróneo');
}
return sistem;
}
}
@@ -6,15 +6,21 @@ export class Sistema {
@PrimaryGeneratedColumn()
id_sistema: number;
@Column({ type: 'varchar', length: 255, nullable: true })
token: string;
@Column({ type: 'varchar', length: 50 })
nombre_sistema: string;
@Column({ type: 'varchar', length: 50 })
ip:string;
@Column()
email: string;
@Column()
email_password_encrypted: string;
@Column()
email_password_iv: string;
@OneToMany(() => Correo, (correo) => correo.id_sistema)
correos: Correo[];
}