diff --git a/README.md b/README.md index 23e54b3..034c72a 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ ### Modulos Requeridos para el funcionamiento ejs -mysql +mysql : se cambiara a mysql2 express-myconnection express express-session: necesario para controlar las ssiones @@ -17,6 +17,8 @@ express-fileupload cors jsonwebtoken +npm install mysql2 + ### recomendaciones previas diff --git a/node_modules/semver/README.md b/node_modules/semver/README.md index 673e9c3..e952215 100644 --- a/node_modules/semver/README.md +++ b/node_modules/semver/README.md @@ -100,7 +100,7 @@ Options: -i --increment [] Increment a version by the specified level. Level can be one of: major, minor, patch, premajor, preminor, - prepatch, or prerelease. Default level is 'patch'. + prepatch, prerelease, or release. Default level is 'patch'. Only one version may be specified. --preid @@ -141,6 +141,8 @@ A "version" is described by the `v2.0.0` specification found at . A leading `"="` or `"v"` character is stripped off and ignored. +Support for stripping a leading "v" is kept for compatibility with `v1.0.0` of the SemVer +specification but should not be used anymore. ## Ranges @@ -237,6 +239,13 @@ $ semver 1.2.4-beta.0 -i prerelease 1.2.4-beta.1 ``` +To get out of the prerelease phase, use the `release` option: + +```bash +$ semver 1.2.4-beta.1 -i release +1.2.4 +``` + #### Prerelease Identifier Base The method `.inc` takes an optional parameter 'identifierBase' string @@ -415,10 +424,10 @@ Strict-mode Comparators and Ranges will be strict about the SemVer strings that they parse. * `valid(v)`: Return the parsed version, or null if it's not valid. -* `inc(v, release, options, identifier, identifierBase)`: +* `inc(v, releaseType, options, identifier, identifierBase)`: Return the version incremented by the release type (`major`, `premajor`, `minor`, `preminor`, `patch`, - `prepatch`, or `prerelease`), or null if it's not valid + `prepatch`, `prerelease`, or `release`), or null if it's not valid * `premajor` in one call will bump the version up to the next major version and down to a prerelease of that major version. `preminor`, and `prepatch` work the same way. @@ -426,6 +435,7 @@ strings that they parse. same as `prepatch`. It increments the patch version and then makes a prerelease. If the input version is already a prerelease it simply increments it. + * `release` will remove any prerelease part of the version. * `identifier` can be used to prefix `premajor`, `preminor`, `prepatch`, or `prerelease` version increments. `identifierBase` is the base to be used for the `prerelease` identifier. @@ -459,7 +469,7 @@ strings that they parse. in descending order when passed to `Array.sort()`. * `compareBuild(v1, v2)`: The same as `compare` but considers `build` when two versions are equal. Sorts in ascending order if passed to `Array.sort()`. -* `compareLoose(v1, v2)`: Short for ``compare(v1, v2, { loose: true })`. +* `compareLoose(v1, v2)`: Short for `compare(v1, v2, { loose: true })`. * `diff(v1, v2)`: Returns the difference between two versions by the release type (`major`, `premajor`, `minor`, `preminor`, `patch`, `prepatch`, or `prerelease`), or null if the versions are the same. @@ -477,7 +487,7 @@ strings that they parse. ### Ranges -* `validRange(range)`: Return the valid range or null if it's not valid +* `validRange(range)`: Return the valid range or null if it's not valid. * `satisfies(version, range)`: Return true if the version satisfies the range. * `maxSatisfying(versions, range)`: Return the highest version in the list diff --git a/node_modules/semver/package.json b/node_modules/semver/package.json index cb8def4..1fbef5a 100644 --- a/node_modules/semver/package.json +++ b/node_modules/semver/package.json @@ -1,20 +1,21 @@ { "name": "semver", - "version": "7.6.2", + "version": "7.7.2", "description": "The semantic version parser used by npm.", "main": "index.js", "scripts": { "test": "tap", "snap": "tap", - "lint": "eslint \"**/*.{js,cjs,ts,mjs,jsx,tsx}\"", + "lint": "npm run eslint", "postlint": "template-oss-check", - "lintfix": "npm run lint -- --fix", + "lintfix": "npm run eslint -- --fix", "posttest": "npm run lint", - "template-oss-apply": "template-oss-apply --force" + "template-oss-apply": "template-oss-apply --force", + "eslint": "eslint \"**/*.{js,cjs,ts,mjs,jsx,tsx}\"" }, "devDependencies": { - "@npmcli/eslint-config": "^4.0.0", - "@npmcli/template-oss": "4.22.0", + "@npmcli/eslint-config": "^5.0.0", + "@npmcli/template-oss": "4.24.3", "benchmark": "^2.1.4", "tap": "^16.0.0" }, @@ -51,7 +52,7 @@ "author": "GitHub Inc.", "templateOSS": { "//@npmcli/template-oss": "This file is partially managed by @npmcli/template-oss. Edits may be overwritten.", - "version": "4.22.0", + "version": "4.24.3", "engines": ">=10", "distPaths": [ "classes/", diff --git a/package-lock.json b/package-lock.json index c74b5e4..09e562c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,8 +18,10 @@ "jsonwebtoken": "^9.0.2", "morgan": "^1.10.0", "mysql": "^2.18.1", + "mysql2": "^3.14.1", "node-fetch": "^2.7.0", "nodejs-base64": "^2.0.0", + "puppeteer": "^24.10.0", "swagger-jsdoc": "^6.2.8", "swagger-ui-express": "^5.0.1", "telegraf": "^3.37.0", @@ -73,6 +75,19 @@ "openapi-types": ">=7" } }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/generator": { "version": "7.18.2", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.18.2.tgz", @@ -100,7 +115,6 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", - "dev": true, "engines": { "node": ">=6.9.0" } @@ -219,11 +233,136 @@ "node": ">= 8" } }, + "node_modules/@puppeteer/browsers": { + "version": "2.10.5", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.10.5.tgz", + "integrity": "sha512-eifa0o+i8dERnngJwKrfp3dEq7ia5XFyoqB17S4gK8GhsQE4/P8nxOfQSE0zQHxzzLo/cmF+7+ywEQ7wK7Fb+w==", + "dependencies": { + "debug": "^4.4.1", + "extract-zip": "^2.0.1", + "progress": "^2.0.3", + "proxy-agent": "^6.5.0", + "semver": "^7.7.2", + "tar-fs": "^3.0.8", + "yargs": "^17.7.2" + }, + "bin": { + "browsers": "lib/cjs/main-cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@puppeteer/browsers/node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@puppeteer/browsers/node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@puppeteer/browsers/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==" + }, + "node_modules/@puppeteer/browsers/node_modules/tar-fs": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.9.tgz", + "integrity": "sha512-XF4w9Xp+ZQgifKakjZYmFdkLoSWd34VGKcsTCwlNWM7QG3ZbaxnTsaBwnjFZqHRf/rROxaR8rXnbtwdvaDI+lA==", + "dependencies": { + "pump": "^3.0.0", + "tar-stream": "^3.1.5" + }, + "optionalDependencies": { + "bare-fs": "^4.0.1", + "bare-path": "^3.0.0" + } + }, + "node_modules/@puppeteer/browsers/node_modules/tar-stream": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", + "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", + "dependencies": { + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } + }, + "node_modules/@puppeteer/browsers/node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@puppeteer/browsers/node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "engines": { + "node": ">=12" + } + }, + "node_modules/@tootallnate/quickjs-emscripten": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", + "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==" + }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==" }, + "node_modules/@types/node": { + "version": "22.15.29", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.29.tgz", + "integrity": "sha512-LNdjOkUDlU1RZb8e1kOIUpN1qQUlzGkEtbVNo53vbrwDg5om6oduhm4SiUaPW5ASTXhAiP0jInWG8Qx9fVlOeQ==", + "optional": true, + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/yauzl": { + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", + "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", + "optional": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -275,7 +414,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, "engines": { "node": ">=8" } @@ -326,6 +464,17 @@ "node": ">=8" } }, + "node_modules/ast-types": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", + "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/async": { "version": "3.2.5", "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", @@ -340,11 +489,91 @@ "node": ">= 4.0.0" } }, + "node_modules/aws-ssl-profiles": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz", + "integrity": "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==", + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/b4a": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.7.tgz", + "integrity": "sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg==" + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, + "node_modules/bare-events": { + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.5.4.tgz", + "integrity": "sha512-+gFfDkR8pj4/TrWCGUGWmJIkBwuxPS5F+a5yWjOHQt2hHvNZd5YLzadjmDUtFmMM4y429bnKLa8bYBMHcYdnQA==", + "optional": true + }, + "node_modules/bare-fs": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.1.5.tgz", + "integrity": "sha512-1zccWBMypln0jEE05LzZt+V/8y8AQsQQqxtklqaIyg5nu6OAYFhZxPXinJTSG+kU5qyNmeLgcn9AW7eHiCHVLA==", + "optional": true, + "dependencies": { + "bare-events": "^2.5.4", + "bare-path": "^3.0.0", + "bare-stream": "^2.6.4" + }, + "engines": { + "bare": ">=1.16.0" + }, + "peerDependencies": { + "bare-buffer": "*" + }, + "peerDependenciesMeta": { + "bare-buffer": { + "optional": true + } + } + }, + "node_modules/bare-os": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.6.1.tgz", + "integrity": "sha512-uaIjxokhFidJP+bmmvKSgiMzj2sV5GPHaZVAIktcxcpCyBFFWO+YlikVAdhmUo2vYFvFhOXIAlldqV29L8126g==", + "optional": true, + "engines": { + "bare": ">=1.14.0" + } + }, + "node_modules/bare-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.0.tgz", + "integrity": "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==", + "optional": true, + "dependencies": { + "bare-os": "^3.0.1" + } + }, + "node_modules/bare-stream": { + "version": "2.6.5", + "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.6.5.tgz", + "integrity": "sha512-jSmxKJNJmHySi6hC42zlZnq00rga4jjxcgNZjY9N5WlOe/iOoGRtdwGsHzQv2RlH2KOYMwGUXhf2zXd32BA9RA==", + "optional": true, + "dependencies": { + "streamx": "^2.21.0" + }, + "peerDependencies": { + "bare-buffer": "*", + "bare-events": "*" + }, + "peerDependenciesMeta": { + "bare-buffer": { + "optional": true + }, + "bare-events": { + "optional": true + } + } + }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -381,6 +610,14 @@ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, + "node_modules/basic-ftp": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.0.5.tgz", + "integrity": "sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==", + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/bignumber.js": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.0.tgz", @@ -494,6 +731,14 @@ "ieee754": "^1.1.13" } }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "engines": { + "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", @@ -550,6 +795,14 @@ "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz", "integrity": "sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==" }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "engines": { + "node": ">=6" + } + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -595,6 +848,18 @@ "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", "dev": true }, + "node_modules/chromium-bidi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-5.1.0.tgz", + "integrity": "sha512-9MSRhWRVoRPDG0TgzkHrshFSJJNZzfY5UFqUMuksg7zL1yoZIZ3jLB0YAgHclbiAxPI86pBnwDX1tbzoiV8aFw==", + "dependencies": { + "mitt": "^3.0.1", + "zod": "^3.24.1" + }, + "peerDependencies": { + "devtools-protocol": "*" + } + }, "node_modules/cliui": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", @@ -684,6 +949,39 @@ "node": ">= 0.10" } }, + "node_modules/cosmiconfig": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", + "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", + "dependencies": { + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/data-uri-to-buffer": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", + "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==", + "engines": { + "node": ">= 14" + } + }, "node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -716,6 +1014,27 @@ "node": ">=4.0.0" } }, + "node_modules/degenerator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", + "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", + "dependencies": { + "ast-types": "^0.13.4", + "escodegen": "^2.1.0", + "esprima": "^4.0.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/denque": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", + "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==", + "engines": { + "node": ">=0.10" + } + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -742,6 +1061,11 @@ "node": ">=8" } }, + "node_modules/devtools-protocol": { + "version": "0.0.1452169", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1452169.tgz", + "integrity": "sha512-FOFDVMGrAUNp0dDKsAU1TorWJUx2JOU1k9xdgBKKJF3IBh/Uhl2yswG5r3TEAOrCiGY2QRp1e6LVDQrCsTKO4g==" + }, "node_modules/dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -808,8 +1132,7 @@ "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, "node_modules/encodeurl": { "version": "2.0.0", @@ -823,11 +1146,26 @@ "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dev": true, "dependencies": { "once": "^1.4.0" } }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "engines": { + "node": ">=6" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, "node_modules/es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", @@ -859,7 +1197,6 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, "engines": { "node": ">=6" } @@ -869,6 +1206,46 @@ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" }, + "node_modules/escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "engines": { + "node": ">=4.0" + } + }, "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", @@ -986,6 +1363,51 @@ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==" }, + "node_modules/extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "dependencies": { + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + }, + "engines": { + "node": ">= 10.17.0" + }, + "optionalDependencies": { + "@types/yauzl": "^2.9.1" + } + }, + "node_modules/extract-zip/node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/extract-zip/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==" + }, + "node_modules/fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==" + }, "node_modules/fast-glob": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", @@ -1011,6 +1433,14 @@ "reusify": "^1.0.4" } }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "dependencies": { + "pend": "~1.2.0" + } + }, "node_modules/filelist": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", @@ -1141,11 +1571,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/generate-function": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", + "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", + "dependencies": { + "is-property": "^1.0.2" + } + }, "node_modules/get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, "engines": { "node": "6.* || 8.* || >= 10.*" } @@ -1185,6 +1622,54 @@ "node": ">= 0.4" } }, + "node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-uri": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.4.tgz", + "integrity": "sha512-E1b1lFFLvLgak2whF2xDBcOy6NLVGZBqqjJjsIhvopKfWWEi64pLVTWWehV8KlLerZkfNTA95sTe2OdJKm1OzQ==", + "dependencies": { + "basic-ftp": "^5.0.2", + "data-uri-to-buffer": "^6.0.2", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/get-uri/node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/get-uri/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==" + }, "node_modules/github-from-package": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", @@ -1314,6 +1799,47 @@ "node": ">= 0.8" } }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/http-proxy-agent/node_modules/agent-base": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", + "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", + "engines": { + "node": ">= 14" + } + }, + "node_modules/http-proxy-agent/node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/http-proxy-agent/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==" + }, "node_modules/https-proxy-agent": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", @@ -1396,6 +1922,21 @@ "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", "dev": true }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -1433,6 +1974,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/ip-address": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", + "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", + "dependencies": { + "jsbn": "1.1.0", + "sprintf-js": "^1.1.3" + }, + "engines": { + "node": ">= 12" + } + }, "node_modules/ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -1441,6 +1994,11 @@ "node": ">= 0.10" } }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" + }, "node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -1478,7 +2036,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, "engines": { "node": ">=8" } @@ -1504,6 +2061,11 @@ "node": ">=0.12.0" } }, + "node_modules/is-property": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", + "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==" + }, "node_modules/isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", @@ -1526,6 +2088,11 @@ "node": ">=10" } }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, "node_modules/js-yaml": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", @@ -1537,6 +2104,11 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsbn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", + "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==" + }, "node_modules/jsesc": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", @@ -1549,6 +2121,11 @@ "node": ">=4" } }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" + }, "node_modules/jsonfile": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", @@ -1606,6 +2183,11 @@ "safe-buffer": "^5.0.1" } }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" + }, "node_modules/lodash.get": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", @@ -1656,6 +2238,33 @@ "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" }, + "node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==" + }, + "node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/lru.min": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.2.tgz", + "integrity": "sha512-Nv9KddBcQSlQopmBHXSsZVY5xsdlZkdH/Iey0BlcBYggMd4two7cZnKOK9vmy3nY0O5RGH99z1PCeTpPqszUYg==", + "engines": { + "bun": ">=1.0.0", + "deno": ">=1.30.0", + "node": ">=8.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wellwelwel" + } + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -1771,6 +2380,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==" + }, "node_modules/mkdirp-classic": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", @@ -1870,6 +2484,55 @@ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, + "node_modules/mysql2": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.14.1.tgz", + "integrity": "sha512-7ytuPQJjQB8TNAYX/H2yhL+iQOnIBjAMam361R7UAL0lOVXWjtdrmoL9HYKqKoLp/8UUTRcvo1QPvK9KL7wA8w==", + "dependencies": { + "aws-ssl-profiles": "^1.1.1", + "denque": "^2.1.0", + "generate-function": "^2.3.1", + "iconv-lite": "^0.6.3", + "long": "^5.2.1", + "lru.min": "^1.0.0", + "named-placeholders": "^1.1.3", + "seq-queue": "^0.0.5", + "sqlstring": "^2.3.2" + }, + "engines": { + "node": ">= 8.0" + } + }, + "node_modules/mysql2/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/mysql2/node_modules/sqlstring": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz", + "integrity": "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/named-placeholders": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.3.tgz", + "integrity": "sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w==", + "dependencies": { + "lru-cache": "^7.14.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/napi-build-utils": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", @@ -1884,6 +2547,14 @@ "node": ">= 0.6" } }, + "node_modules/netmask": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz", + "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==", + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/node-abi": { "version": "3.75.0", "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.75.0.tgz", @@ -2062,6 +2733,105 @@ "node": ">=8" } }, + "node_modules/pac-proxy-agent": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.2.0.tgz", + "integrity": "sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==", + "dependencies": { + "@tootallnate/quickjs-emscripten": "^0.23.0", + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "get-uri": "^6.0.1", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.6", + "pac-resolver": "^7.0.1", + "socks-proxy-agent": "^8.0.5" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pac-proxy-agent/node_modules/agent-base": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", + "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", + "engines": { + "node": ">= 14" + } + }, + "node_modules/pac-proxy-agent/node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/pac-proxy-agent/node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pac-proxy-agent/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==" + }, + "node_modules/pac-resolver": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz", + "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==", + "dependencies": { + "degenerator": "^5.0.0", + "netmask": "^2.0.2" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -2098,6 +2868,16 @@ "node": ">=8" } }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" + }, "node_modules/picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", @@ -2197,7 +2977,6 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "dev": true, "engines": { "node": ">=0.4.0" } @@ -2214,6 +2993,70 @@ "node": ">= 0.10" } }, + "node_modules/proxy-agent": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.5.0.tgz", + "integrity": "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "http-proxy-agent": "^7.0.1", + "https-proxy-agent": "^7.0.6", + "lru-cache": "^7.14.1", + "pac-proxy-agent": "^7.1.0", + "proxy-from-env": "^1.1.0", + "socks-proxy-agent": "^8.0.5" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/proxy-agent/node_modules/agent-base": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", + "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", + "engines": { + "node": ">= 14" + } + }, + "node_modules/proxy-agent/node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/proxy-agent/node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/proxy-agent/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==" + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, "node_modules/pstree.remy": { "version": "1.1.8", "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", @@ -2224,12 +3067,68 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz", "integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==", - "dev": true, "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, + "node_modules/puppeteer": { + "version": "24.10.0", + "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-24.10.0.tgz", + "integrity": "sha512-Oua9VkGpj0S2psYu5e6mCer6W9AU9POEQh22wRgSXnLXASGH+MwLUVWgLCLeP9QPHHcJ7tySUlg4Sa9OJmaLpw==", + "hasInstallScript": true, + "dependencies": { + "@puppeteer/browsers": "2.10.5", + "chromium-bidi": "5.1.0", + "cosmiconfig": "^9.0.0", + "devtools-protocol": "0.0.1452169", + "puppeteer-core": "24.10.0", + "typed-query-selector": "^2.12.0" + }, + "bin": { + "puppeteer": "lib/cjs/puppeteer/node/cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/puppeteer-core": { + "version": "24.10.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-24.10.0.tgz", + "integrity": "sha512-xX0QJRc8t19iAwRDsAOR38Q/Zx/W6WVzJCEhKCAwp2XMsaWqfNtQ+rBfQW9PlF+Op24d7c8Zlgq9YNmbnA7hdQ==", + "dependencies": { + "@puppeteer/browsers": "2.10.5", + "chromium-bidi": "5.1.0", + "debug": "^4.4.1", + "devtools-protocol": "0.0.1452169", + "typed-query-selector": "^2.12.0", + "ws": "^8.18.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/puppeteer-core/node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/puppeteer-core/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==" + }, "node_modules/qs": { "version": "6.13.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", @@ -2344,7 +3243,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -2369,6 +3267,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "engines": { + "node": ">=4" + } + }, "node_modules/resolve/node_modules/is-core-module": { "version": "2.16.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", @@ -2450,9 +3356,9 @@ } }, "node_modules/semver": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", - "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", "bin": { "semver": "bin/semver.js" }, @@ -2496,6 +3402,11 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, + "node_modules/seq-queue": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz", + "integrity": "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q==" + }, "node_modules/serve-static": { "version": "1.16.2", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", @@ -2649,6 +3560,84 @@ "node": ">=8" } }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.8.4", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.4.tgz", + "integrity": "sha512-D3YaD0aRxR3mEcqnidIs7ReYJFVzWdd6fXJYUM8ixcQcJRGTka/b3saV0KflYhyVJXKhb947GndU35SxYNResQ==", + "dependencies": { + "ip-address": "^9.0.5", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", + "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "socks": "^2.8.3" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/socks-proxy-agent/node_modules/agent-base": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", + "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", + "engines": { + "node": ">= 14" + } + }, + "node_modules/socks-proxy-agent/node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socks-proxy-agent/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==" + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==" + }, "node_modules/sqlstring": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.1.tgz", @@ -2682,6 +3671,18 @@ "node": ">=10.0.0" } }, + "node_modules/streamx": { + "version": "2.22.0", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.22.0.tgz", + "integrity": "sha512-sLh1evHOzBy/iWRiR6d1zRcLao4gGZr3C1kzNz4fopCOKJb6xD9ub8Mpi9Mr1R6id5o43S+d93fI48UC5uM9aw==", + "dependencies": { + "fast-fifo": "^1.3.2", + "text-decoder": "^1.1.0" + }, + "optionalDependencies": { + "bare-events": "^2.2.0" + } + }, "node_modules/string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", @@ -2699,7 +3700,6 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -2713,7 +3713,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "dependencies": { "ansi-regex": "^5.0.1" }, @@ -2884,6 +3883,14 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, + "node_modules/text-decoder": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.3.tgz", + "integrity": "sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==", + "dependencies": { + "b4a": "^1.6.4" + } + }, "node_modules/to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", @@ -2927,6 +3934,11 @@ "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, "node_modules/tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", @@ -2951,6 +3963,11 @@ "node": ">= 0.6" } }, + "node_modules/typed-query-selector": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/typed-query-selector/-/typed-query-selector-2.12.0.tgz", + "integrity": "sha512-SbklCd1F0EiZOyPiW192rrHZzZ5sBijB6xM+cpmrwDqObvdtunOHHIk9fCGsoK5JVIYXoyEp4iEdE3upFH3PAg==" + }, "node_modules/typegram": { "version": "3.12.0", "resolved": "https://registry.npmjs.org/typegram/-/typegram-3.12.0.tgz", @@ -2973,6 +3990,12 @@ "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", "dev": true }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "optional": true + }, "node_modules/universalify": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", @@ -3049,7 +4072,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -3067,11 +4089,30 @@ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, + "node_modules/ws": { + "version": "8.18.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz", + "integrity": "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, "engines": { "node": ">=10" } @@ -3111,6 +4152,15 @@ "node": ">=10" } }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, "node_modules/z-schema": { "version": "5.0.5", "resolved": "https://registry.npmjs.org/z-schema/-/z-schema-5.0.5.tgz", @@ -3138,6 +4188,14 @@ "engines": { "node": "^12.20.0 || >=14" } + }, + "node_modules/zod": { + "version": "3.25.47", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.47.tgz", + "integrity": "sha512-+f8+agSYoT9niC0VUL60IuXnr81FJeJ27Lf5YPrmcxTWmygcpGBeEuAAovDDEjkyQ36KyqNswwbhISZ1Z7yY+A==", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } } } } diff --git a/package.json b/package.json index 4d05222..ad0ecc6 100644 --- a/package.json +++ b/package.json @@ -27,8 +27,10 @@ "jsonwebtoken": "^9.0.2", "morgan": "^1.10.0", "mysql": "^2.18.1", + "mysql2": "^3.14.1", "node-fetch": "^2.7.0", "nodejs-base64": "^2.0.0", + "puppeteer": "^24.10.0", "swagger-jsdoc": "^6.2.8", "swagger-ui-express": "^5.0.1", "telegraf": "^3.37.0", diff --git a/src/app.js b/src/app.js index bfcb69e..9655d35 100644 --- a/src/app.js +++ b/src/app.js @@ -1,7 +1,8 @@ const express = require('express'); const path = require('path'); const morgan = require('morgan'); -const mysql = require('mysql'); +//const mysql = require('mysql'); +const mysql2 = require('mysql2'); // Importa mysql2 const cloud_file = require('express-fileupload'); const myConecction = require('express-myconnection'); const cors_origins = require('cors'); @@ -23,6 +24,7 @@ const generalesRutas = require('./rutas/rt_Generales'); const cloud_rutas = require('./rutas/rt_cloud'); const app_restaurant = require('./rutas/rt_apps'); const app_arduino = require('./rutas/rt_arduino'); +const proyectos = require('./rutas/rt_proyectos'); //configuraciones app.set('port',process.env.PORT||puerto); @@ -46,7 +48,7 @@ app.use(session({ //middlewares app.use(express.static(__dirname+'/public'));//para usar la carpeta public *js*css*img app.use(morgan('dev')); -app.use(myConecction(mysql,{//conexion bd +app.use(myConecction(mysql2,{ // <-- Usa 'mysql2' aquí host:global.config.db.host, user: global.config.db.user, password:global.config.db.pswd, @@ -73,6 +75,7 @@ app.use('/', generalesRutas); app.use('/', cloud_rutas); app.use('/', app_restaurant); app.use('/', app_arduino); +app.use('/', proyectos); //prueba de json directa app.get('/pruebaJson',function(req,res){ diff --git a/src/controladores/controlador_Apps.js b/src/controladores/controlador_Apps.js index fde7516..d15b822 100644 --- a/src/controladores/controlador_Apps.js +++ b/src/controladores/controlador_Apps.js @@ -40,4 +40,10 @@ controlador.upload = (req, res) => { res.render('video_upload') }; +/** aqui iniciamo la vista de ejs **/ +controlador.config = (req, res) => { + //res.render('speedtest'); + res.render('configuracion'); +}; + module.exports = controlador; \ No newline at end of file diff --git a/src/controladores/controlador_Clientes.js b/src/controladores/controlador_Clientes.js index 7667f1e..28df921 100644 --- a/src/controladores/controlador_Clientes.js +++ b/src/controladores/controlador_Clientes.js @@ -135,6 +135,100 @@ controlador.eliminarCliente = (req, res) => { }); } +/**** Listar miembros *****/ +controlador.listarMiembros = (req, res) => { + req.getConnection((err, conn) => { + if (err) { + return res.status(500).json({ error: 'Error de conexión' }); + } + + conn.query('SELECT * FROM miembros ORDER BY nombre ASC', (err, miembros) => { + if (err) { + return res.status(500).json({ error: 'Error al consultar miembros' }); + } + res.json(miembros); + }); + }); +}; + +/**** Obtener miembro para edición (ahora vía /api/miembros/:id) *****/ +controlador.editarMiembro = (req, res) => { + const { id } = req.params; // *** MODIFICADO: id ahora viene de req.params *** + req.getConnection((err, conn) => { + if (err) { + return res.status(500).json({ error: 'Error de conexión' }); + } + + conn.query('SELECT * FROM miembros WHERE id = ?', [id], (err, miembro) => { + if (err) { + return res.status(500).json({ error: 'Error al consultar miembro' }); + } + if (miembro.length === 0) { + return res.status(404).json({ error: 'Miembro no encontrado' }); + } + res.json(miembro[0]); + }); + }); +}; + +/**** Guardar/Crear nuevo miembro (vía POST /api/miembros) *****/ +controlador.guardarMiembro = (req, res) => { + const miembro = req.body; // Obtener los datos del miembro desde el cuerpo de la solicitud + req.getConnection((err, conn) => { + if (err) { + return res.status(500).json({ error: 'Error de conexión' }); + } + + conn.query('INSERT INTO miembros SET ?', miembro, (err, result) => { + if (err) { + return res.status(500).json({ error: 'Error al guardar miembro' }); + } + res.status(201).json({ id: result.insertId, ...miembro, message: 'Miembro creado exitosamente' }); // 201 Created + }); + }); +}; + +/**** Actualizar miembro existente (vía PUT /api/miembros/:id) *****/ +controlador.actualizarMiembro = (req, res) => { + const { id } = req.params; // *** MODIFICADO: id ahora viene de req.params *** + const miembroActualizado = req.body; // Obtener los datos actualizados del miembro desde el cuerpo de la solicitud + req.getConnection((err, conn) => { + if (err) { + return res.status(500).json({ error: 'Error de conexión' }); + } + + conn.query('UPDATE miembros SET ? WHERE id = ?', [miembroActualizado, id], (err, result) => { + if (err) { + return res.status(500).json({ error: 'Error al actualizar miembro' }); + } + if (result.affectedRows === 0) { + return res.status(404).json({ error: 'Miembro no encontrado' }); + } + res.json({ message: 'Miembro actualizado exitosamente' }); + }); + }); +}; + +/**** Eliminar miembro (vía DELETE /api/miembros/:id) *****/ +controlador.eliminarMiembro = (req, res) => { + const { id } = req.params; // *** MODIFICADO: id ahora viene de req.params *** + req.getConnection((err, conn) => { + if (err) { + return res.status(500).json({ error: 'Error de conexión' }); + } + + conn.query('DELETE FROM miembros WHERE id = ?', [id], (err, result) => { + if (err) { + return res.status(500).json({ error: 'Error al eliminar miembro' }); + } + if (result.affectedRows === 0) { + return res.status(404).json({ error: 'Miembro no encontrado' }); + } + res.json({ message: 'Miembro eliminado exitosamente' }); + }); + }); +}; + /** * controlador para obtener las ciudades de la base de datos * @param {*} req diff --git a/src/controladores/controlador_proyecto.js b/src/controladores/controlador_proyecto.js new file mode 100644 index 0000000..edc1ea0 --- /dev/null +++ b/src/controladores/controlador_proyecto.js @@ -0,0 +1,2711 @@ +// src/controladores/controlador_proyecto.js + +const controlador = {}; +const path = require('path'); +const fs = require('fs').promises; // Usamos la versión de promesas de fs para async/await +const puppeteer = require('puppeteer'); // Importar Puppeteer + +// Función para renderizar el panel de proyectos +/** + * @swagger + * /panel_proyectos: + * get: + * summary: Renderiza el panel principal de proyectos. + * tags: [Proyectos] + * responses: + * 200: + * description: Panel de proyectos renderizado exitosamente. + * 500: + * description: Error del servidor al obtener los proyectos. + */ +controlador.panel_proyectos = async (req, res) => { + try { + req.getConnection(async (err, conn) => { // Usar async/await con conn.promise() + if (err) { + console.error("Error al conectar a la BD:", err); + return res.status(500).send("Error de servidor al conectar a la base de datos."); + } + + try { + // Consulta para obtener todos los proyectos, incluyendo el nombre del cliente + // CORRECCIÓN: Cambiado 'p.nombre' a 'p.nombre_proyecto' + const query = ` + SELECT + p.id, + p.nombre_proyecto AS nombre_proyecto, + p.objetivo, + p.estado, + c.client_nombre AS nombre_cliente + FROM proyectos p + LEFT JOIN clientes c ON p.id_cliente = c.client_id + ORDER BY p.id DESC; + `; + const [proyectos] = await conn.promise().query(query); + + // Renderiza la vista EJS y le pasa los datos de los proyectos + res.render('panel_proyectos', { proyectos: proyectos }); + + } catch (queryErr) { + console.error("Error al ejecutar la consulta de proyectos:", queryErr); + res.status(500).send("Error al obtener proyectos."); + } + }); + } catch (error) { + console.error("Error inesperado en panel_proyectos:", error); + res.status(500).send("Error interno del servidor."); + } +}; + +// Función para renderizar el formulario de Memoria Técnica +/** + * @swagger + * /proyecto_memoria_tecnica: + * get: + * summary: Renderiza el formulario para crear o editar una Memoria Técnica. + * tags: [Proyectos] + * parameters: + * - in: query + * name: proyectoId + * schema: + * type: integer + * description: ID del proyecto para pre-seleccionar en el formulario. + * - in: query + * name: memoriaId + * schema: + * type: integer + * description: ID de la Memoria Técnica a editar. + * responses: + * 200: + * description: Formulario de Memoria Técnica renderizado exitosamente. + * 500: + * description: Error del servidor al cargar el formulario. + */ +controlador.memoriaTecnica = async (req, res) => { + const proyectoId = req.query.proyectoId; // Obtener el ID del proyecto si viene en la URL + const memoriaId = req.query.memoriaId; // En el futuro, si se edita una memoria existente + + try { + req.getConnection(async (err, conn) => { + if (err) { + console.error("Error al conectar a la BD:", err); + return res.status(500).send("Error de servidor al conectar a la base de datos."); + } + + let proyectos = []; + let empresasAutoras = []; + let memoria = null; // Para almacenar los datos de la memoria si se está editando + + // 1. Obtener todos los proyectos para el ComboBox (cmbProyecto) + try { + // Asumiendo que `id_cliente` en `proyectos` es una FK a `clientes.client_id` + // CORRECCIÓN: Cambiado 'p.nombre' a 'p.nombre_proyecto' + const [rowsProyectos] = await conn.promise().query(` + SELECT + p.id, + p.nombre_proyecto AS nombre_proyecto, + c.client_nombre AS nombre_cliente + FROM proyectos p + LEFT JOIN clientes c ON p.id_cliente = c.client_id + ORDER BY p.nombre_proyecto ASC; + `); + proyectos = rowsProyectos; + } catch (queryErr) { + console.error("Error al obtener proyectos:", queryErr); + // No es un error crítico si no se cargan los proyectos, la página puede seguir funcionando parcialmente. + } + + // 2. Obtener todas las empresas autoras para el ComboBox (cmbEmpresaAutora) + try { + const [rowsEmpresas] = await conn.promise().query(` + SELECT idEmpresa, Nombre, RazonSocial FROM empresa_datos; + `); + empresasAutoras = rowsEmpresas; + } catch (queryErr) { + console.error("Error al obtener empresas autoras:", queryErr); + // No es un error crítico si no se cargan las empresas, la página puede seguir funcionando parcialmente. + } + + // 3. Si existe memoriaId, cargar los datos de la memoria existente para edición + if (memoriaId) { + try { + const [rowsMemoria] = await conn.promise().query(` + SELECT * FROM proyecto_memorias_tecnicas WHERE id = ?; + `, [memoriaId]); + memoria = rowsMemoria[0]; // Si no encuentra, será undefined + } catch (queryErr) { + console.error("Error al obtener memoria técnica por ID:", queryErr); + // Aquí podrías decidir si mostrar un error o simplemente no precargar la memoria. + } + } + + // Renderizar la vista EJS + res.render('proyecto_memoria_tecnica', { + proyectos: proyectos, + empresasAutoras: empresasAutoras, + proyectoId: proyectoId, // Pasa el ID del proyecto para pre-seleccionar + memoria: memoria // Pasa los datos de la memoria si existe para pre-llenar el formulario + }); + + // No es necesario liberar la conexión aquí, express-myconnection lo maneja en modo 'pool'. + }); + } catch (error) { + console.error("Error inesperado en controlador.memoriaTecnica:", error); + res.status(500).send("Error interno del servidor al procesar la solicitud."); + } +}; + +// ============================================================================ +// NUEVA FUNCIÓN: Listar todas las Memorias Técnicas para una tabla +// ============================================================================ +/** + * @swagger + * /lista_memorias_tecnicas: + * get: + * summary: Renderiza la lista de todas las memorias técnicas para su selección. + * tags: [Memoria Técnica API] + * responses: + * 200: + * description: Lista de memorias técnicas renderizada exitosamente. + * 500: + * description: Error del servidor al obtener las memorias técnicas. + */ +controlador.listarMemoriasTecnicas = async (req, res) => { + try { + req.getConnection(async (err, conn) => { + if (err) { + console.error("Error al conectar a la BD:", err); + return res.status(500).send("Error de servidor al conectar a la base de datos."); + } + + try { + // Consulta para obtener todas las memorias técnicas con información relevante + // CORRECCIÓN: Cambiado 'p.nombre' a 'p.nombre_proyecto' + const query = ` + SELECT + pmt.id, + pmt.objetivo_memoria_especifico, + pmt.fecha_elaboracion, + pmt.ultima_modificacion, + p.nombre_proyecto AS nombre_proyecto, + c.client_nombre AS nombre_cliente, + ed.Nombre AS nombre_empresa_autora + FROM proyecto_memorias_tecnicas pmt + LEFT JOIN proyectos p ON pmt.id_proyecto = p.id + LEFT JOIN empresa_datos ed ON pmt.id_empresa_autora = ed.idEmpresa + LEFT JOIN clientes c ON p.id_cliente = c.client_id + ORDER BY pmt.fecha_elaboracion DESC; + `; + const [memorias] = await conn.promise().query(query); + + res.render('lista_memorias_tecnicas', { memorias: memorias }); + + } catch (queryErr) { + console.error("Error al obtener la lista de memorias técnicas:", queryErr); + res.status(500).send("Error al obtener la lista de memorias técnicas."); + } + }); + } catch (error) { + console.error("Error inesperado en listarMemoriasTecnicas:", error); + res.status(500).send("Error interno del servidor."); + } +}; + + +// ============================================================================ +// API CRUD PARA MEMORIAS TÉCNICAS +// ============================================================================ + +/** + * @swagger + * /api/proyectos/memorias-tecnicas: + * post: + * summary: Crea una nueva Memoria Técnica. + * tags: [Memoria Técnica API] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * required: + * - id_proyecto + * - id_empresa_autora + * - objetivo_memoria_especifico + * properties: + * id_proyecto: + * type: integer + * description: ID del proyecto asociado. + * id_empresa_autora: + * type: integer + * description: ID de la empresa autora de la memoria. + * objetivo_memoria_especifico: + * type: string + * description: Objetivo específico de esta memoria técnica. + * descripcion_introduccion: + * type: string + * description: Descripción introductoria de la memoria. + * funcionamiento_operacion: + * type: string + * description: Detalles de funcionamiento y operación. + * conectividad_red: + * type: string + * description: Información sobre conectividad de red. + * acceso_remoto_aplicaciones: + * type: string + * description: Detalles de acceso remoto y aplicaciones. + * seguridad_fisica_logica: + * type: string + * description: Aspectos de seguridad física y lógica. + * responses: + * 201: + * description: Memoria Técnica creada exitosamente. + * content: + * application/json: + * schema: + * type: object + * properties: + * message: + * type: string + * example: Memoria técnica creada exitosamente + * id: + * type: integer + * example: 1 + * 400: + * description: Datos de entrada inválidos. + * 500: + * description: Error del servidor. + */ +controlador.createMemoriaTecnica = async (req, res) => { + try { + const { + id_proyecto, + id_empresa_autora, + objetivo_memoria_especifico, + descripcion_introduccion, + funcionamiento_operacion, + conectividad_red, + acceso_remoto_aplicaciones, + seguridad_fisica_logica + } = req.body; + + // Validaciones básicas + if (!id_proyecto || !id_empresa_autora || !objetivo_memoria_especifico) { + return res.status(400).json({ + error: 'Los campos id_proyecto, id_empresa_autora y objetivo_memoria_especifico son obligatorios' + }); + } + + req.getConnection(async (err, conn) => { + if (err) { + console.error("Error al conectar a la BD:", err); + return res.status(500).json({ error: "Error de conexión a la base de datos" }); + } + + try { + const query = ` + INSERT INTO proyecto_memorias_tecnicas + (id_proyecto, id_empresa_autora, objetivo_memoria_especifico, + descripcion_introduccion, funcionamiento_operacion, conectividad_red, + acceso_remoto_aplicaciones, seguridad_fisica_logica, reg_user, reg_host) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + `; + + const [result] = await conn.promise().query(query, [ + id_proyecto, + id_empresa_autora, + objetivo_memoria_especifico, + descripcion_introduccion || null, + funcionamiento_operacion || null, + conectividad_red || null, + acceso_remoto_aplicaciones || null, + seguridad_fisica_logica || null, + req.user?.username || 'sistema', // Asumiendo que req.user está disponible por un middleware de autenticación + req.ip || 'localhost' + ]); + + res.status(201).json({ + message: 'Memoria técnica creada exitosamente', + id: result.insertId + }); + + } catch (queryErr) { + console.error("Error al crear memoria técnica:", queryErr); + res.status(500).json({ error: "Error al crear la memoria técnica" }); + } + }); + } catch (error) { + console.error("Error inesperado en createMemoriaTecnica:", error); + res.status(500).json({ error: "Error interno del servidor" }); + } +}; + +// Obtener memoria técnica por ID +/** + * @swagger + * /api/proyectos/memorias-tecnicas/{id}: + * get: + * summary: Obtiene una Memoria Técnica por su ID. + * tags: [Memoria Técnica API] + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: integer + * description: ID de la Memoria Técnica a obtener. + * responses: + * 200: + * description: Detalles de la Memoria Técnica. + * content: + * application/json: + * schema: + * type: object + * properties: + * id: { type: integer, example: 1 } + * id_proyecto: { type: integer, example: 101 } + * objetivo_memoria_especifico: { type: string, example: "Documentar instalación de sistema CCTV" } + * descripcion_introduccion: { type: string, example: "Esta memoria detalla la configuración inicial del sistema." } + * 404: + * description: Memoria Técnica no encontrada. + * 500: + * description: Error del servidor. + */ +controlador.getMemoriaTecnicaById = async (req, res) => { + try { + const { id } = req.params; + + req.getConnection(async (err, conn) => { + if (err) { + console.error("Error al conectar a la BD:", err); + return res.status(500).json({ error: "Error de conexión a la base de datos" }); + } + + try { + const [rows] = await conn.promise().query( + 'SELECT * FROM proyecto_memorias_tecnicas WHERE id = ?', + [id] + ); + + if (rows.length === 0) { + return res.status(404).json({ error: "Memoria técnica no encontrada" }); + } + + res.json(rows[0]); + + } catch (queryErr) { + console.error("Error al obtener memoria técnica:", queryErr); + res.status(500).json({ error: "Error al obtener la memoria técnica" }); + } + }); + } catch (error) { + console.error("Error inesperado en getMemoriaTecnicaById:", error); + res.status(500).json({ error: "Error interno del servidor" }); + } +}; + +// Actualizar memoria técnica +/** + * @swagger + * /api/proyectos/memorias-tecnicas/{id}: + * put: + * summary: Actualiza una Memoria Técnica existente. + * tags: [Memoria Técnica API] + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: integer + * description: ID de la Memoria Técnica a actualizar. + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * id_proyecto: + * type: integer + * id_empresa_autora: + * type: integer + * objetivo_memoria_especifico: + * type: string + * descripcion_introduccion: + * type: string + * funcionamiento_operacion: + * type: string + * conectividad_red: + * type: string + * acceso_remoto_aplicaciones: + * type: string + * seguridad_fisica_logica: + * type: string + * responses: + * 200: + * description: Memoria Técnica actualizada exitosamente. + * content: + * application/json: + * schema: + * type: object + * properties: + * message: + * type: string + * example: Memoria técnica actualizada exitosamente + * 400: + * description: Datos de entrada inválidos. + * 404: + * description: Memoria Técnica no encontrada. + * 500: + * description: Error del servidor. + */ +controlador.updateMemoriaTecnica = async (req, res) => { + try { + const { id } = req.params; + const { + id_proyecto, + id_empresa_autora, + objetivo_memoria_especifico, + descripcion_introduccion, + funcionamiento_operacion, + conectividad_red, + acceso_remoto_aplicaciones, + seguridad_fisica_logica + } = req.body; + + req.getConnection(async (err, conn) => { + if (err) { + console.error("Error al conectar a la BD:", err); + return res.status(500).json({ error: "Error de conexión a la base de datos" }); + } + + try { + // Verificar que la memoria existe + const [existing] = await conn.promise().query( + 'SELECT id FROM proyecto_memorias_tecnicas WHERE id = ?', + [id] + ); + + if (existing.length === 0) { + return res.status(404).json({ error: "Memoria técnica no encontrada" }); + } + + const query = ` + UPDATE proyecto_memorias_tecnicas SET + id_proyecto = COALESCE(?, id_proyecto), + id_empresa_autora = COALESCE(?, id_empresa_autora), + objetivo_memoria_especifico = COALESCE(?, objetivo_memoria_especifico), + descripcion_introduccion = ?, + funcionamiento_operacion = ?, + conectividad_red = ?, + acceso_remoto_aplicaciones = ?, + seguridad_fisica_logica = ? + WHERE id = ? + `; + + await conn.promise().query(query, [ + id_proyecto, + id_empresa_autora, + objetivo_memoria_especifico, + descripcion_introduccion, + funcionamiento_operacion, + conectividad_red, + acceso_remoto_aplicaciones, + seguridad_fisica_logica, + id + ]); + + res.json({ message: 'Memoria técnica actualizada exitosamente' }); + + } catch (queryErr) { + console.error("Error al actualizar memoria técnica:", queryErr); + res.status(500).json({ error: "Error al actualizar la memoria técnica" }); + } + }); + } catch (error) { + console.error("Error inesperado en updateMemoriaTecnica:", error); + res.status(500).json({ error: "Error interno del servidor" }); + } +}; + +// Eliminar memoria técnica +/** + * @swagger + * /api/proyectos/memorias-tecnicas/{id}: + * delete: + * summary: Elimina una Memoria Técnica. + * tags: [Memoria Técnica API] + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: integer + * description: ID de la Memoria Técnica a eliminar. + * responses: + * 204: + * description: Memoria Técnica eliminada exitosamente. + * 404: + * description: Memoria Técnica no encontrada. + * 500: + * description: Error del servidor. + */ +controlador.deleteMemoriaTecnica = async (req, res) => { + try { + const { id } = req.params; + + req.getConnection(async (err, conn) => { + if (err) { + console.error("Error al conectar a la BD:", err); + return res.status(500).json({ error: "Error de conexión a la base de datos" }); + } + + try { + const [result] = await conn.promise().query( + 'DELETE FROM proyecto_memorias_tecnicas WHERE id = ?', + [id] + ); + + if (result.affectedRows === 0) { + return res.status(404).json({ error: "Memoria técnica no encontrada" }); + } + + res.status(204).send(); // 204 No Content para eliminación exitosa + + } catch (queryErr) { + console.error("Error al eliminar memoria técnica:", queryErr); + res.status(500).json({ error: "Error al eliminar la memoria técnica" }); + } + }); + } catch (error) { + console.error("Error inesperado en deleteMemoriaTecnica:", error); + res.status(500).json({ error: "Error interno del servidor" }); + } +}; + +// ============================================================================ +// API CRUD PARA COMPONENTES DE MEMORIA TÉCNICA +// ============================================================================ + +// Obtener componentes por memoria ID +/** + * @swagger + * /api/proyectos/memorias-tecnicas/{memoriaId}/componentes: + * get: + * summary: Obtiene todos los componentes asociados a una Memoria Técnica. + * tags: [Memoria Técnica API] + * parameters: + * - in: path + * name: memoriaId + * required: true + * schema: + * type: integer + * description: ID de la Memoria Técnica. + * responses: + * 200: + * description: Lista de componentes. + * content: + * application/json: + * schema: + * type: array + * items: + * type: object + * properties: + * id: { type: integer, example: 1 } + * id_memoria: { type: integer, example: 10 } + * nombre_componente: { type: string, example: "Cámara IP Hikvision" } + * tipo: { type: string, example: "Cámara" } + * modelo_marca: { type: string, example: "DS-2CD2143G0-I" } + * numero_serie: { type: string, example: "SN123456789" } + * ubicacion: { type: string, example: "Entrada principal" } + * descripcion_breve: { type: string, example: "Cámara domo de 4MP con IR" } + * 500: + * description: Error del servidor. + */ +controlador.getComponentesByMemoriaId = async (req, res) => { + try { + const { memoriaId } = req.params; + + req.getConnection(async (err, conn) => { + if (err) { + console.error("Error al conectar a la BD:", err); + return res.status(500).json({ error: "Error de conexión a la base de datos" }); + } + + try { + const [rows] = await conn.promise().query( + 'SELECT * FROM proyecto_componentes_memoria WHERE id_memoria = ? ORDER BY id', + [memoriaId] + ); + + res.json(rows); + + } catch (queryErr) { + console.error("Error al obtener componentes:", queryErr); + res.status(500).json({ error: "Error al obtener los componentes" }); + } + }); + } catch (error) { + console.error("Error inesperado en getComponentesByMemoriaId:", error); + res.status(500).json({ error: "Error interno del servidor" }); + } +}; + +// Añadir componente a memoria técnica +/** + * @swagger + * /api/proyectos/memorias-tecnicas/{memoriaId}/componentes: + * post: + * summary: Agrega un nuevo componente a una Memoria Técnica. + * tags: [Memoria Técnica API] + * parameters: + * - in: path + * name: memoriaId + * required: true + * schema: + * type: integer + * description: ID de la Memoria Técnica. + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * required: + * - nombre_componente + * properties: + * nombre_componente: + * type: string + * tipo: + * type: string + * modelo_marca: + * type: string + * numero_serie: + * type: string + * ubicacion: + * type: string + * descripcion_breve: + * type: string + * responses: + * 201: + * description: Componente añadido exitosamente. + * content: + * application/json: + * schema: + * type: object + * properties: + * message: + * type: string + * example: Componente añadido exitosamente + * id: + * type: integer + * example: 1 + * 400: + * description: Datos de entrada inválidos. + * 500: + * description: Error del servidor. + */ +controlador.addComponente = async (req, res) => { + try { + const { memoriaId } = req.params; + const { + nombre_componente, + tipo, + modelo_marca, + numero_serie, + ubicacion, + descripcion_breve + } = req.body; + + if (!nombre_componente) { + return res.status(400).json({ error: 'El nombre del componente es obligatorio' }); + } + + req.getConnection(async (err, conn) => { + if (err) { + console.error("Error al conectar a la BD:", err); + return res.status(500).json({ error: "Error de conexión a la base de datos" }); + } + + try { + const query = ` + INSERT INTO proyecto_componentes_memoria + (id_memoria, nombre_componente, tipo, modelo_marca, numero_serie, ubicacion, descripcion_breve) + VALUES (?, ?, ?, ?, ?, ?, ?) + `; + + const [result] = await conn.promise().query(query, [ + memoriaId, + nombre_componente, + tipo || null, + modelo_marca || null, + numero_serie || null, + ubicacion || null, + descripcion_breve || null + ]); + + res.status(201).json({ + message: 'Componente añadido exitosamente', + id: result.insertId + }); + + } catch (queryErr) { + console.error("Error al añadir componente:", queryErr); + res.status(500).json({ error: "Error al añadir el componente" }); + } + }); + } catch (error) { + console.error("Error inesperado en addComponente:", error); + res.status(500).json({ error: "Error interno del servidor" }); + } +}; + +// Actualizar componente +/** + * @swagger + * /api/proyectos/componentes/{id}: + * put: + * summary: Actualiza un componente de la Memoria Técnica. + * tags: [Memoria Técnica API] + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: integer + * description: ID del componente a actualizar. + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * nombre_componente: + * type: string + * tipo: + * type: string + * modelo_marca: + * type: string + * numero_serie: + * type: string + * ubicacion: + * type: string + * descripcion_breve: + * type: string + * responses: + * 200: + * description: Componente actualizado exitosamente. + * 404: + * description: Componente no encontrado. + * 500: + * description: Error del servidor. + */ +controlador.updateComponente = async (req, res) => { + try { + const { id } = req.params; + const { + nombre_componente, + tipo, + modelo_marca, + numero_serie, + ubicacion, + descripcion_breve + } = req.body; + + req.getConnection(async (err, conn) => { + if (err) { + console.error("Error al conectar a la BD:", err); + return res.status(500).json({ error: "Error de conexión a la base de datos" }); + } + + try { + const query = ` + UPDATE proyecto_componentes_memoria SET + nombre_componente = COALESCE(?, nombre_componente), + tipo = ?, + modelo_marca = ?, + numero_serie = ?, + ubicacion = ?, + descripcion_breve = ? + WHERE id = ? + `; + + const [result] = await conn.promise().query(query, [ + nombre_componente, + tipo, + modelo_marca, + numero_serie, + ubicacion, + descripcion_breve, + id + ]); + + if (result.affectedRows === 0) { + return res.status(404).json({ error: "Componente no encontrado" }); + } + + res.json({ message: 'Componente actualizado exitosamente' }); + + } catch (queryErr) { + console.error("Error al actualizar componente:", queryErr); + res.status(500).json({ error: "Error al actualizar el componente" }); + } + }); + } catch (error) { + console.error("Error inesperado en updateComponente:", error); + res.status(500).json({ error: "Error interno del servidor" }); + } +}; + +// Eliminar componente +/** + * @swagger + * /api/proyectos/componentes/{id}: + * delete: + * summary: Elimina un componente de la Memoria Técnica. + * tags: [Memoria Técnica API] + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: integer + * description: ID del componente a eliminar. + * responses: + * 204: + * description: Componente eliminado exitosamente. + * 404: + * description: Componente no encontrado. + * 500: + * description: Error del servidor. + */ +controlador.deleteComponente = async (req, res) => { + try { + const { id } = req.params; + + req.getConnection(async (err, conn) => { + if (err) { + console.error("Error al conectar a la BD:", err); + return res.status(500).json({ error: "Error de conexión a la base de datos" }); + } + + try { + const [result] = await conn.promise().query( + 'DELETE FROM proyecto_componentes_memoria WHERE id = ?', + [id] + ); + + if (result.affectedRows === 0) { + return res.status(404).json({ error: "Componente no encontrado" }); + } + + res.status(204).send(); + + } catch (queryErr) { + console.error("Error al eliminar componente:", queryErr); + res.status(500).json({ error: "Error al eliminar el componente" }); + } + }); + } catch (error) { + console.error("Error inesperado en deleteComponente:", error); + res.status(500).json({ error: "Error interno del servidor" }); + } +}; + +// ============================================================================ +// API CRUD PARA PLAN DE MANTENIMIENTO +// ============================================================================ + +// Obtener planes de mantenimiento por memoria ID +/** + * @swagger + * /api/proyectos/memorias-tecnicas/{memoriaId}/mantenimiento: + * get: + * summary: Obtiene todos los planes de mantenimiento asociados a una Memoria Técnica. + * tags: [Memoria Técnica API] + * parameters: + * - in: path + * name: memoriaId + * required: true + * schema: + * type: integer + * description: ID de la Memoria Técnica. + * responses: + * 200: + * description: Lista de planes de mantenimiento. + * content: + * application/json: + * schema: + * type: array + * items: + * type: object + * properties: + * id: { type: integer, example: 1 } + * id_memoria: { type: integer, example: 10 } + * tipo_mantenimiento: { type: string, example: "Preventivo" } + * frecuencia: { type: string, example: "Mensual" } + * tareas_procedimientos: { type: string, example: "Limpieza de filtros y revisión de conexiones." } + * responsable: { type: string, example: "Equipo de Mantenimiento A" } + * 500: + * description: Error del servidor. + */ +controlador.getMantenimientoByMemoriaId = async (req, res) => { + try { + const { memoriaId } = req.params; + + req.getConnection(async (err, conn) => { + if (err) { + console.error("Error al conectar a la BD:", err); + return res.status(500).json({ error: "Error de conexión a la base de datos" }); + } + + try { + const [rows] = await conn.promise().query( + 'SELECT * FROM proyecto_plan_mantenimiento_memoria WHERE id_memoria = ? ORDER BY id', + [memoriaId] + ); + + res.json(rows); + + } catch (queryErr) { + console.error("Error al obtener planes de mantenimiento:", queryErr); + res.status(500).json({ error: "Error al obtener los planes de mantenimiento" }); + } + }); + } catch (error) { + console.error("Error inesperado en getMantenimientoByMemoriaId:", error); + res.status(500).json({ error: "Error interno del servidor" }); + } +}; + +// Añadir plan de mantenimiento +/** + * @swagger + * /api/proyectos/memorias-tecnicas/{memoriaId}/mantenimiento: + * post: + * summary: Agrega un nuevo plan de mantenimiento a una Memoria Técnica. + * tags: [Memoria Técnica API] + * parameters: + * - in: path + * name: memoriaId + * required: true + * schema: + * type: integer + * description: ID de la Memoria Técnica. + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * required: + * - tareas_procedimientos + * properties: + * tipo_mantenimiento: + * type: string + * frecuencia: + * type: string + * tareas_procedimientos: + * type: string + * responsable: + * type: string + * responses: + * 201: + * description: Plan de mantenimiento añadido exitosamente. + * content: + * application/json: + * schema: + * type: object + * properties: + * message: + * type: string + * example: Plan de mantenimiento añadido exitosamente + * id: + * type: integer + * example: 1 + * 400: + * description: Datos de entrada inválidos. + * 500: + * description: Error del servidor. + */ +controlador.addMantenimiento = async (req, res) => { + try { + const { memoriaId } = req.params; + const { + tipo_mantenimiento, + frecuencia, + tareas_procedimientos, + responsable + } = req.body; + + if (!tareas_procedimientos) { + return res.status(400).json({ error: 'Las tareas y procedimientos son obligatorios' }); + } + + req.getConnection(async (err, conn) => { + if (err) { + console.error("Error al conectar a la BD:", err); + return res.status(500).json({ error: "Error de conexión a la base de datos" }); + } + + try { + const query = ` + INSERT INTO proyecto_plan_mantenimiento_memoria + (id_memoria, tipo_mantenimiento, frecuencia, tareas_procedimientos, responsable) + VALUES (?, ?, ?, ?, ?) + `; + + const [result] = await conn.promise().query(query, [ + memoriaId, + tipo_mantenimiento || null, + frecuencia || null, + tareas_procedimientos, + responsable || null + ]); + + res.status(201).json({ + message: 'Plan de mantenimiento añadido exitosamente', + id: result.insertId + }); + + } catch (queryErr) { + console.error("Error al añadir plan de mantenimiento:", queryErr); + res.status(500).json({ error: "Error al añadir el plan de mantenimiento" }); + } + }); + } catch (error) { + console.error("Error inesperado en addMantenimiento:", error); + res.status(500).json({ error: "Error interno del servidor" }); + } +}; + +// Actualizar plan de mantenimiento +/** + * @swagger + * /api/proyectos/mantenimiento/{id}: + * put: + * summary: Actualiza un plan de mantenimiento. + * tags: [Memoria Técnica API] + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: integer + * description: ID del plan de mantenimiento a actualizar. + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * tipo_mantenimiento: + * type: string + * frecuencia: + * type: string + * tareas_procedimientos: + * type: string + * responsable: + * type: string + * responses: + * 200: + * description: Plan de mantenimiento actualizado exitosamente. + * 404: + * description: Plan de mantenimiento no encontrado. + * 500: + * description: Error del servidor. + */ +controlador.updateMantenimiento = async (req, res) => { + try { + const { id } = req.params; + const { + tipo_mantenimiento, + frecuencia, + tareas_procedimientos, + responsable + } = req.body; + + req.getConnection(async (err, conn) => { + if (err) { + console.error("Error al conectar a la BD:", err); + return res.status(500).json({ error: "Error de conexión a la base de datos" }); + } + + try { + const query = ` + UPDATE proyecto_plan_mantenimiento_memoria SET + tipo_mantenimiento = ?, + frecuencia = ?, + tareas_procedimientos = COALESCE(?, tareas_procedimientos), + responsable = ? + WHERE id = ? + `; + + const [result] = await conn.promise().query(query, [ + tipo_mantenimiento, + frecuencia, + tareas_procedimientos, + responsable, + id + ]); + + if (result.affectedRows === 0) { + return res.status(404).json({ error: "Plan de mantenimiento no encontrado" }); + } + + res.json({ message: 'Plan de mantenimiento actualizado exitosamente' }); + + } catch (queryErr) { + console.error("Error al actualizar plan de mantenimiento:", queryErr); + res.status(500).json({ error: "Error al actualizar el plan de mantenimiento" }); + } + }); + } catch (error) { + console.error("Error inesperado en updateMantenimiento:", error); + res.status(500).json({ error: "Error interno del servidor" }); + } +}; + +// Eliminar plan de mantenimiento +/** + * @swagger + * /api/proyectos/mantenimiento/{id}: + * delete: + * summary: Elimina un plan de mantenimiento. + * tags: [Memoria Técnica API] + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: integer + * description: ID del plan de mantenimiento a eliminar. + * responses: + * 204: + * description: Plan de mantenimiento eliminado exitosamente. + * 404: + * description: Plan de mantenimiento no encontrado. + * 500: + * description: Error del servidor. + */ +controlador.deleteMantenimiento = async (req, res) => { + try { + const { id } = req.params; + + req.getConnection(async (err, conn) => { + if (err) { + console.error("Error al conectar a la BD:", err); + return res.status(500).json({ error: "Error de conexión a la base de datos" }); + } + + try { + const [result] = await conn.promise().query( + 'DELETE FROM proyecto_plan_mantenimiento_memoria WHERE id = ?', + [id] + ); + + if (result.affectedRows === 0) { + return res.status(404).json({ error: "Plan de mantenimiento no encontrado" }); + } + + res.status(204).send(); + + } catch (queryErr) { + console.error("Error al eliminar plan de mantenimiento:", queryErr); + res.status(500).json({ error: "Error al eliminar el plan de mantenimiento" }); + } + }); + } catch (error) { + console.error("Error inesperado en deleteMantenimiento:", error); + res.status(500).json({ error: "Error interno del servidor" }); + } +}; + +// ============================================================================ +// API CRUD PARA CREDENCIALES DE DISPOSITIVOS +// ============================================================================ + +// Obtener credenciales por memoria ID +/** + * @swagger + * /api/proyectos/memorias-tecnicas/{memoriaId}/credenciales: + * get: + * summary: Obtiene todas las credenciales asociadas a una Memoria Técnica. + * tags: [Memoria Técnica API] + * parameters: + * - in: path + * name: memoriaId + * required: true + * schema: + * type: integer + * description: ID de la Memoria Técnica. + * responses: + * 200: + * description: Lista de credenciales. + * content: + * application/json: + * schema: + * type: array + * items: + * type: object + * properties: + * id: { type: integer, example: 1 } + * id_memoria: { type: integer, example: 10 } + * tipo_credencial: { type: string, example: "Usuario" } + * valor_credencial: { type: string, example: "admin" } + * descripcion: { type: string, example: "Credencial de acceso al NVR" } + * 500: + * description: Error del servidor. + */ +controlador.getCredencialesByMemoriaId = async (req, res) => { + try { + const { memoriaId } = req.params; + + req.getConnection(async (err, conn) => { + if (err) { + console.error("Error al conectar a la BD:", err); + return res.status(500).json({ error: "Error de conexión a la base de datos" }); + } + + try { + const [rows] = await conn.promise().query( + 'SELECT * FROM proyecto_credenciales_dispositivos WHERE id_memoria = ? ORDER BY id', + [memoriaId] + ); + + res.json(rows); + + } catch (queryErr) { + console.error("Error al obtener credenciales:", queryErr); + res.status(500).json({ error: "Error al obtener las credenciales" }); + } + }); + } catch (error) { + console.error("Error inesperado en getCredencialesByMemoriaId:", error); + res.status(500).json({ error: "Error interno del servidor" }); + } +}; + +// Añadir credencial +/** + * @swagger + * /api/proyectos/memorias-tecnicas/{memoriaId}/credenciales: + * post: + * summary: Agrega una nueva credencial a una Memoria Técnica. + * tags: [Memoria Técnica API] + * parameters: + * - in: path + * name: memoriaId + * required: true + * schema: + * type: integer + * description: ID de la Memoria Técnica. + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * required: + * - tipo_credencial + * - valor_credencial + * properties: + * tipo_credencial: + * type: string + * valor_credencial: + * type: string + * descripcion: + * type: string + * responses: + * 201: + * description: Credencial agregada exitosamente. + * content: + * application/json: + * schema: + * type: object + * properties: + * message: + * type: string + * example: Credencial añadida exitosamente + * id: + * type: integer + * example: 1 + * 400: + * description: Datos de entrada inválidos. + * 500: + * description: Error del servidor. + */ +controlador.addCredencial = async (req, res) => { + try { + const { memoriaId } = req.params; + const { + tipo_credencial, + valor_credencial, + descripcion + } = req.body; + + if (!tipo_credencial || !valor_credencial) { + return res.status(400).json({ error: 'El tipo y valor de credencial son obligatorios' }); + } + + req.getConnection(async (err, conn) => { + if (err) { + console.error("Error al conectar a la BD:", err); + return res.status(500).json({ error: "Error de conexión a la base de datos" }); + } + + try { + const query = ` + INSERT INTO proyecto_credenciales_dispositivos + (id_memoria, tipo_credencial, valor_credencial, descripcion) + VALUES (?, ?, ?, ?) + `; + + const [result] = await conn.promise().query(query, [ + memoriaId, + tipo_credencial, + valor_credencial, + descripcion || null + ]); + + res.status(201).json({ + message: 'Credencial añadida exitosamente', + id: result.insertId + }); + + } catch (queryErr) { + console.error("Error al añadir credencial:", queryErr); + res.status(500).json({ error: "Error al añadir la credencial" }); + } + }); + } catch (error) { + console.error("Error inesperado en addCredencial:", error); + res.status(500).json({ error: "Error interno del servidor" }); + } +}; + +// Actualizar credencial +/** + * @swagger + * /api/proyectos/credenciales/{id}: + * put: + * summary: Actualiza una credencial de dispositivo. + * tags: [Memoria Técnica API] + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: integer + * description: ID de la credencial a actualizar. + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * tipo_credencial: + * type: string + * valor_credencial: + * type: string + * descripcion: + * type: string + * responses: + * 200: + * description: Credencial actualizada exitosamente. + * 404: + * description: Credencial no encontrada. + * 500: + * description: Error del servidor. + */ +controlador.updateCredencial = async (req, res) => { + try { + const { id } = req.params; + const { + tipo_credencial, + valor_credencial, + descripcion + } = req.body; + + req.getConnection(async (err, conn) => { + if (err) { + console.error("Error al conectar a la BD:", err); + return res.status(500).json({ error: "Error de conexión a la base de datos" }); + } + + try { + const query = ` + UPDATE proyecto_credenciales_dispositivos SET + tipo_credencial = COALESCE(?, tipo_credencial), + valor_credencial = COALESCE(?, valor_credencial), + descripcion = ? + WHERE id = ? + `; + + const [result] = await conn.promise().query(query, [ + tipo_credencial, + valor_credencial, + descripcion, + id + ]); + + if (result.affectedRows === 0) { + return res.status(404).json({ error: "Credencial no encontrada" }); + } + + res.json({ message: 'Credencial actualizada exitosamente' }); + + } catch (queryErr) { + console.error("Error al actualizar credencial:", queryErr); + res.status(500).json({ error: "Error al actualizar la credencial" }); + } + }); + } catch (error) { + console.error("Error inesperado en updateCredencial:", error); + res.status(500).json({ error: "Error interno del servidor" }); + } +}; + +// Eliminar credencial +/** + * @swagger + * /api/proyectos/credenciales/{id}: + * delete: + * summary: Elimina una credencial de dispositivo. + * tags: [Memoria Técnica API] + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: integer + * description: ID de la credencial a eliminar. + * responses: + * 204: + * description: Credencial eliminada exitosamente. + * 404: + * description: Credencial no encontrada. + * 500: + * description: Error del servidor. + */ +controlador.deleteCredencial = async (req, res) => { + try { + const { id } = req.params; + + req.getConnection(async (err, conn) => { + if (err) { + console.error("Error al conectar a la BD:", err); + return res.status(500).json({ error: "Error de conexión a la base de datos" }); + } + + try { + const [result] = await conn.promise().query( + 'DELETE FROM proyecto_credenciales_dispositivos WHERE id = ?', + [id] + ); + + if (result.affectedRows === 0) { + return res.status(404).json({ error: "Credencial no encontrada" }); + } + + res.status(204).send(); + + } catch (queryErr) { + console.error("Error al eliminar credencial:", queryErr); + res.status(500).json({ error: "Error al eliminar la credencial" }); + } + }); + } catch (error) { + console.error("Error inesperado en deleteCredencial:", error); + res.status(500).json({ error: "Error interno del servidor" }); + } +}; + +// ============================================================================ +// API CRUD PARA DOCUMENTOS GENÉRICOS (sys_documentos) +// ============================================================================ + +// Subir documento genérico +/** + * @swagger + * /api/documentos: + * post: + * summary: Sube un nuevo documento. + * tags: [Documentos y Archivos API] + * requestBody: + * required: true + * content: + * multipart/form-data: + * schema: + * type: object + * required: + * - documento + * - id_entidad_asociada + * - tipo_entidad_asociada + * properties: + * documento: + * type: string + * format: binary + * description: El archivo del documento a subir. + * id_entidad_asociada: + * type: integer + * description: ID de la entidad a la que se asocia el documento (ej. ID de una memoria técnica). + * tipo_entidad_asociada: + * type: string + * description: Tipo de entidad asociada (ej. 'MEMORIA_TECNICA', 'PROYECTO', 'CLIENTE'). + * nombre_documento: + * type: string + * description: Nombre del documento (opcional, se usa el nombre del archivo si no se provee). + * descripcion: + * type: string + * description: Descripción del documento. + * responses: + * 201: + * description: Documento subido y registrado exitosamente. + * content: + * application/json: + * schema: + * type: object + * properties: + * message: + * type: string + * example: Documento subido y registrado exitosamente + * id: + * type: integer + * example: 1 + * nombre_archivo: + * type: string + * example: doc_1678901234567_abcde1234.pdf + * 400: + * description: Datos de entrada inválidos o archivo no proporcionado. + * 500: + * description: Error del servidor. + */ +controlador.uploadDocumento = async (req, res) => { + try { + // Verificar si se proporcionó un archivo + if (!req.files || !req.files.documento) { + return res.status(400).json({ error: 'No se proporcionó ningún archivo de documento' }); + } + + const { + id_entidad_asociada, + tipo_entidad_asociada, + nombre_documento, + descripcion + } = req.body; + + // Validaciones básicas + if (!id_entidad_asociada || !tipo_entidad_asociada) { + return res.status(400).json({ + error: 'Los campos id_entidad_asociada y tipo_entidad_asociada son obligatorios' + }); + } + + const archivo = req.files.documento; + + // Crear directorio de destino si no existe + const directorioDestino = path.join(__dirname, '../uploads/documentos'); + try { + await fs.mkdir(directorioDestino, { recursive: true }); + } catch (mkdirError) { + console.error("Error al crear directorio:", mkdirError); + } + + // Generar nombre único para el archivo + const timestamp = Date.now(); + const extension = path.extname(archivo.name); + const nombreArchivo = `doc_${timestamp}_${Math.random().toString(36).substr(2, 9)}${extension}`; + const rutaCompleta = path.join(directorioDestino, nombreArchivo); + const rutaRelativa = `uploads/documentos/${nombreArchivo}`; + + req.getConnection(async (err, conn) => { + if (err) { + console.error("Error al conectar a la BD:", err); + return res.status(500).json({ error: "Error de conexión a la base de datos" }); + } + + try { + // Mover archivo al directorio de destino + await archivo.mv(rutaCompleta); + + // Insertar registro en la base de datos + const query = ` + INSERT INTO sys_documentos + (nombre_documento, descripcion, ruta_archivo, tipo_contenido, tamano_bytes, + id_entidad_asociada, tipo_entidad_asociada, reg_user, reg_host) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) + `; + + const [result] = await conn.promise().query(query, [ + nombre_documento || archivo.name, + descripcion || null, + rutaRelativa, + archivo.mimetype, + archivo.size, + id_entidad_asociada, + tipo_entidad_asociada, + req.user?.username || 'sistema', // Asumiendo que req.user está disponible + req.ip || 'localhost' + ]); + + res.status(201).json({ + message: 'Documento subido y registrado exitosamente', + id: result.insertId, + nombre_archivo: nombreArchivo + }); + + } catch (queryErr) { + console.error("Error al registrar documento:", queryErr); + // Intentar eliminar el archivo si falló la inserción en BD + try { + await fs.unlink(rutaCompleta); + } catch (unlinkErr) { + console.error("Error al eliminar archivo tras fallo de BD:", unlinkErr); + } + res.status(500).json({ error: "Error al registrar el documento en la base de datos" }); + } + }); + } catch (error) { + console.error("Error inesperado en uploadDocumento:", error); + res.status(500).json({ error: "Error interno del servidor" }); + } +}; + +// Obtener metadatos de documento por ID +/** + * @swagger + * /api/documentos/{id}: + * get: + * summary: Obtiene los metadatos de un documento por su ID. + * tags: [Documentos y Archivos API] + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: integer + * description: ID del documento a obtener. + * responses: + * 200: + * description: Metadatos del documento. + * content: + * application/json: + * schema: + * type: object + * properties: + * id: { type: integer, example: 1 } + * nombre_documento: { type: string, example: "Informe_Final_Proyecto_X.pdf" } + * ruta_archivo: { type: string, example: "uploads/documentos/doc_12345.pdf" } + * tipo_contenido: { type: string, example: "application/pdf" } + * tamano_bytes: { type: integer, example: 102400 } + * id_entidad_asociada: { type: integer, example: 10 } + * tipo_entidad_asociada: { type: string, example: "MEMORIA_TECNICA" } + * 404: + * description: Documento no encontrado. + * 500: + * description: Error del servidor. + */ +controlador.getDocumentoById = async (req, res) => { + try { + const { id } = req.params; + + req.getConnection(async (err, conn) => { + if (err) { + console.error("Error al conectar a la BD:", err); + return res.status(500).json({ error: "Error de conexión a la base de datos" }); + } + + try { + const [rows] = await conn.promise().query( + 'SELECT * FROM sys_documentos WHERE id = ?', + [id] + ); + + if (rows.length === 0) { + return res.status(404).json({ error: "Documento no encontrado" }); + } + + res.json(rows[0]); + + } catch (queryErr) { + console.error("Error al obtener documento:", queryErr); + res.status(500).json({ error: "Error al obtener el documento" }); + } + }); + } catch (error) { + console.error("Error inesperado en getDocumentoById:", error); + res.status(500).json({ error: "Error interno del servidor" }); + } +}; + +// Eliminar documento +/** + * @swagger + * /api/documentos/{id}: + * delete: + * summary: Elimina un documento por su ID (incluyendo el archivo físico). + * tags: [Documentos y Archivos API] + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: integer + * description: ID del documento a eliminar. + * responses: + * 204: + * description: Documento eliminado exitosamente. + * 404: + * description: Documento no encontrado. + * 500: + * description: Error del servidor. + */ +controlador.deleteDocumento = async (req, res) => { + try { + const { id } = req.params; + + req.getConnection(async (err, conn) => { + if (err) { + console.error("Error al conectar a la BD:", err); + return res.status(500).json({ error: "Error de conexión a la base de datos" }); + } + + try { + // Primero obtener la información del documento para eliminar el archivo + const [documentoRows] = await conn.promise().query( + 'SELECT ruta_archivo FROM sys_documentos WHERE id = ?', + [id] + ); + + if (documentoRows.length === 0) { + return res.status(404).json({ error: "Documento no encontrado" }); + } + + const rutaArchivo = path.join(__dirname, '..', documentoRows[0].ruta_archivo); + + // Eliminar registro de la base de datos + const [result] = await conn.promise().query( + 'DELETE FROM sys_documentos WHERE id = ?', + [id] + ); + + if (result.affectedRows === 0) { + return res.status(404).json({ error: "Documento no encontrado" }); + } + + // Intentar eliminar el archivo físico + try { + await fs.unlink(rutaArchivo); + } catch (unlinkErr) { + console.error("Error al eliminar archivo tras fallo de BD:", unlinkErr); + // No devolver error al cliente, el registro ya fue eliminado de BD + } + + res.status(204).send(); // 204 No Content para eliminación exitosa + + } catch (queryErr) { + console.error("Error al eliminar documento:", queryErr); + res.status(500).json({ error: "Error al eliminar el documento" }); + } + }); + } catch (error) { + console.error("Error inesperado en deleteDocumento:", error); + res.status(500).json({ error: "Error interno del servidor" }); + } +}; + +// Obtener documentos por memoria ID (función específica ya mencionada en las rutas) +/** + * @swagger + * /api/proyectos/memorias-tecnicas/{memoriaId}/documentos: + * get: + * summary: Obtiene todos los documentos asociados a una Memoria Técnica. + * tags: [Documentos y Archivos API] + * parameters: + * - in: path + * name: memoriaId + * required: true + * schema: + * type: integer + * description: ID de la Memoria Técnica. + * responses: + * 200: + * description: Lista de documentos. + * content: + * application/json: + * schema: + * type: array + * items: + * type: object + * properties: + * id: { type: integer, example: 1 } + * nombre_documento: { type: string, example: "Manual_CCTV.pdf" } + * ruta_archivo: { type: string, example: "uploads/documentos/manual_cctv.pdf" } + * tipo_entidad_asociada: { type: string, example: "MEMORIA_TECNICA" } + * 500: + * description: Error del servidor. + */ +controlador.getDocumentosByMemoriaId = async (req, res) => { + try { + const { memoriaId } = req.params; + + req.getConnection(async (err, conn) => { + if (err) { + console.error("Error al conectar a la BD:", err); + return res.status(500).json({ error: "Error de conexión a la base de datos" }); + } + + try { + const [rows] = await conn.promise().query(` + SELECT * FROM sys_documentos + WHERE id_entidad_asociada = ? AND tipo_entidad_asociada = 'MEMORIA_TECNICA' + ORDER BY fecha_subida DESC + `, [memoriaId]); + + res.json(rows); + + } catch (queryErr) { + console.error("Error al obtener documentos por memoria ID:", queryErr); + res.status(500).json({ error: "Error al obtener los documentos" }); + } + }); + } catch (error) { + console.error("Error inesperado en getDocumentosByMemoriaId:", error); + res.status(500).json({ error: "Error interno del servidor" }); + } +}; + +// ============================================================================ +// API CRUD PARA IMÁGENES GENÉRICAS (sys_imagenes) +// ============================================================================ + +// Subir imagen genérica +/** + * @swagger + * /api/imagenes: + * post: + * summary: Sube una nueva imagen. + * tags: [Documentos y Archivos API] + * requestBody: + * required: true + * content: + * multipart/form-data: + * schema: + * type: object + * required: + * - imagen + * - id_entidad_asociada + * - tipo_entidad_asociada + * properties: + * imagen: + * type: string + * format: binary + * description: El archivo de imagen a subir. + * id_entidad_asociada: + * type: integer + * description: ID de la entidad a la que se asocia la imagen (ej. ID de una memoria técnica). + * tipo_entidad_asociada: + * type: string + * description: Tipo de entidad asociada (ej. 'MEMORIA_TECNICA', 'PROYECTO', 'CLIENTE'). + * nombre_imagen: + * type: string + * description: Nombre de la imagen (opcional, se usa el nombre del archivo si no se provee). + * descripcion: + * type: string + * description: Descripción de la imagen. + * responses: + * 201: + * description: Imagen subida y registrada exitosamente. + * content: + * application/json: + * schema: + * type: object + * properties: + * message: + * type: string + * example: Imagen subida y registrada exitosamente + * id: + * type: integer + * example: 1 + * nombre_archivo: + * type: string + * example: img_1678901234567_abcde1234.jpg + * 400: + * description: Datos de entrada inválidos o archivo no proporcionado. + * 500: + * description: Error del servidor. + */ +controlador.uploadImagen = async (req, res) => { + try { + // Verificar si se proporcionó un archivo + if (!req.files || !req.files.imagen) { + return res.status(400).json({ error: 'No se proporcionó ningún archivo de imagen' }); + } + + const { + id_entidad_asociada, + tipo_entidad_asociada, + nombre_imagen, + descripcion + } = req.body; + + // Validaciones básicas + if (!id_entidad_asociada || !tipo_entidad_asociada) { + return res.status(400).json({ + error: 'Los campos id_entidad_asociada y tipo_entidad_asociada son obligatorios' + }); + } + + const archivo = req.files.imagen; + + // Validar que sea una imagen + if (!archivo.mimetype.startsWith('image/')) { + return res.status(400).json({ error: 'El archivo debe ser una imagen válida' }); + } + + // Crear directorio de destino si no existe + const directorioDestino = path.join(__dirname, '../uploads/imagenes'); + try { + await fs.mkdir(directorioDestino, { recursive: true }); + } catch (mkdirError) { + console.error("Error al crear directorio:", mkdirError); + } + + // Generar nombre único para el archivo + const timestamp = Date.now(); + const extension = path.extname(archivo.name); + const nombreArchivo = `img_${timestamp}_${Math.random().toString(36).substr(2, 9)}${extension}`; + const rutaCompleta = path.join(directorioDestino, nombreArchivo); + const rutaRelativa = `uploads/imagenes/${nombreArchivo}`; + + req.getConnection(async (err, conn) => { + if (err) { + console.error("Error al conectar a la BD:", err); + return res.status(500).json({ error: "Error de conexión a la base de datos" }); + } + + try { + // Mover archivo al directorio de destino + await archivo.mv(rutaCompleta); + + // Obtener dimensiones de la imagen (opcional - requiere una librería como 'sharp' o 'image-size') + // Por ahora dejamos estos campos como null + const anchoPx = null; + const altoPx = null; + + // Insertar registro en la base de datos + const query = ` + INSERT INTO sys_imagenes + (nombre_imagen, descripcion, ruta_imagen, tipo_contenido, tamano_bytes, + ancho_px, alto_px, id_entidad_asociada, tipo_entidad_asociada, reg_user, reg_host) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + `; + + const [result] = await conn.promise().query(query, [ + nombre_imagen || archivo.name, + descripcion || null, + rutaRelativa, + archivo.mimetype, + archivo.size, + anchoPx, + altoPx, + id_entidad_asociada, + tipo_entidad_asociada, + req.user?.username || 'sistema', + req.ip || 'localhost' + ]); + + res.status(201).json({ + message: 'Imagen subida y registrada exitosamente', + id: result.insertId, + nombre_archivo: nombreArchivo + }); + + } catch (queryErr) { + console.error("Error al registrar imagen:", queryErr); + // Intentar eliminar el archivo si falló la inserción en BD + try { + await fs.unlink(rutaCompleta); + } catch (unlinkErr) { + console.error("Error al eliminar archivo tras fallo de BD:", unlinkErr); + } + res.status(500).json({ error: "Error al registrar la imagen en la base de datos" }); + } + }); + } catch (error) { + console.error("Error inesperado en uploadImagen:", error); + res.status(500).json({ error: "Error interno del servidor" }); + } +}; + +// Obtener metadatos de imagen por ID +/** + * @swagger + * /api/imagenes/{id}: + * get: + * summary: Obtiene los metadatos de una imagen por su ID. + * tags: [Documentos y Archivos API] + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: integer + * description: ID de la imagen a obtener. + * responses: + * 200: + * description: Metadatos de la imagen. + * content: + * application/json: + * schema: + * type: object + * properties: + * id: { type: integer, example: 1 } + * nombre_imagen: { type: string, example: "Esquema_Red.png" } + * ruta_imagen: { type: string, example: "uploads/imagenes/esquema_red.png" } + * tipo_contenido: { type: string, example: "image/png" } + * tamano_bytes: { type: integer, example: 51200 } + * ancho_px: { type: integer, example: 800 } + * alto_px: { type: integer, example: 600 } + * id_entidad_asociada: { type: integer, example: 10 } + * tipo_entidad_asociada: { type: string, example: "MEMORIA_TECNICA" } + * 404: + * description: Imagen no encontrada. + * 500: + * description: Error del servidor. + */ +controlador.getImagenById = async (req, res) => { + try { + const { id } = req.params; + + req.getConnection(async (err, conn) => { + if (err) { + console.error("Error al conectar a la BD:", err); + return res.status(500).json({ error: "Error de conexión a la base de datos" }); + } + + try { + const [rows] = await conn.promise().query( + 'SELECT * FROM sys_imagenes WHERE id = ?', + [id] + ); + + if (rows.length === 0) { + return res.status(404).json({ error: "Imagen no encontrada" }); + } + + res.json(rows[0]); + + } catch (queryErr) { + console.error("Error al obtener imagen:", queryErr); + res.status(500).json({ error: "Error al obtener la imagen" }); + } + }); + } catch (error) { + console.error("Error inesperado en getImagenById:", error); + res.status(500).json({ error: "Error interno del servidor" }); + } +}; + +// Eliminar imagen +/** + * @swagger + * /api/imagenes/{id}: + * delete: + * summary: Elimina una imagen por su ID (incluyendo el archivo físico). + * tags: [Documentos y Archivos API] + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: integer + * description: ID de la imagen a eliminar. + * responses: + * 204: + * description: Imagen eliminada exitosamente. + * 404: + * description: Imagen no encontrada. + * 500: + * description: Error del servidor. + */ +controlador.deleteImagen = async (req, res) => { + try { + const { id } = req.params; + + req.getConnection(async (err, conn) => { + if (err) { + console.error("Error al conectar a la BD:", err); + return res.status(500).json({ error: "Error de conexión a la base de datos" }); + } + + try { + // Primero obtener la información de la imagen para eliminar el archivo + const [imagenRows] = await conn.promise().query( + 'SELECT ruta_imagen FROM sys_imagenes WHERE id = ?', + [id] + ); + + if (imagenRows.length === 0) { + return res.status(404).json({ error: "Imagen no encontrada" }); + } + + const rutaArchivo = path.join(__dirname, '..', imagenRows[0].ruta_imagen); + + // Eliminar registro de la base de datos + const [result] = await conn.promise().query( + 'DELETE FROM sys_imagenes WHERE id = ?', + [id] + ); + + if (result.affectedRows === 0) { + return res.status(404).json({ error: "Imagen no encontrada" }); + } + + // Intentar eliminar el archivo físico + try { + await fs.unlink(rutaArchivo); + } catch (unlinkErr) { + console.error("Error al eliminar archivo tras fallo de BD:", unlinkErr); + // No devolver error al cliente, el registro ya fue eliminado de BD + } + + res.status(204).send(); + + } catch (queryErr) { + console.error("Error al eliminar imagen:", queryErr); + res.status(500).json({ error: "Error al eliminar la imagen" }); + } + }); + } catch (error) { + console.error("Error inesperado en deleteImagen:", error); + res.status(500).json({ error: "Error interno del servidor" }); + } +}; + +// Obtener imágenes por memoria ID +/** + * @swagger + * /api/proyectos/memorias-tecnicas/{memoriaId}/imagenes: + * get: + * summary: Obtiene todas las imágenes asociadas a una Memoria Técnica. + * tags: [Documentos y Archivos API] + * parameters: + * - in: path + * name: memoriaId + * required: true + * schema: + * type: integer + * description: ID de la Memoria Técnica. + * responses: + * 200: + * description: Lista de imágenes. + * content: + * application/json: + * schema: + * type: array + * items: + * type: object + * properties: + * id: { type: integer, example: 1 } + * nombre_imagen: { type: string, example: "Foto_Instalacion.jpg" } + * ruta_imagen: { type: string, example: "uploads/imagenes/foto_instalacion.jpg" } + * tipo_entidad_asociada: { type: string, example: "MEMORIA_TECNICA" } + * 500: + * description: Error del servidor. + */ +controlador.getImagenesByMemoriaId = async (req, res) => { + try { + const { memoriaId } = req.params; + + req.getConnection(async (err, conn) => { + if (err) { + console.error("Error al conectar a la BD:", err); + return res.status(500).json({ error: "Error de conexión a la base de datos" }); + } + + try { + const [rows] = await conn.promise().query(` + SELECT * FROM sys_imagenes + WHERE id_entidad_asociada = ? AND tipo_entidad_asociada = 'MEMORIA_TECNICA' + ORDER BY fecha_subida DESC + `, [memoriaId]); + + res.json(rows); + + } catch (queryErr) { + console.error("Error al obtener imágenes por memoria ID:", queryErr); + res.status(500).json({ error: "Error al obtener las imágenes" }); + } + }); + } catch (error) { + console.error("Error inesperado en getImagenesByMemoriaId:", error); + res.status(500).json({ error: "Error interno del servidor" }); + } +}; + +// ============================================================================ +// FUNCIONES ADICIONALES PARA GESTIÓN DE ARCHIVOS (Descarga/Visualización) +// ============================================================================ + +// Descargar documento +/** + * @swagger + * /api/documentos/{id}/download: + * get: + * summary: Descarga un documento por su ID. + * tags: [Documentos y Archivos API] + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: integer + * description: ID del documento a descargar. + * responses: + * 200: + * description: Archivo descargado exitosamente. + * content: + * application/octet-stream: + * schema: + * type: string + * format: binary + * 404: + * description: Documento o archivo no encontrado. + * 500: + * description: Error del servidor. + */ +controlador.downloadDocumento = async (req, res) => { + try { + const { id } = req.params; + + req.getConnection(async (err, conn) => { + if (err) { + console.error("Error al conectar a la BD:", err); + return res.status(500).json({ error: "Error de conexión a la base de datos" }); + } + + try { + const [rows] = await conn.promise().query( + 'SELECT nombre_documento, ruta_archivo, tipo_contenido FROM sys_documentos WHERE id = ?', + [id] + ); + + if (rows.length === 0) { + return res.status(404).json({ error: "Documento no encontrado" }); + } + + const documento = rows[0]; + const rutaCompleta = path.join(__dirname, '..', documento.ruta_archivo); + + // Verificar si el archivo existe + try { + await fs.access(rutaCompleta); + } catch (accessErr) { + return res.status(404).json({ error: "Archivo no encontrado en el servidor" }); + } + + // Configurar headers para descarga + res.setHeader('Content-Disposition', `attachment; filename="${documento.nombre_documento}"`); + res.setHeader('Content-Type', documento.tipo_contenido || 'application/octet-stream'); + + // Enviar archivo + res.sendFile(rutaCompleta); + + } catch (queryErr) { + console.error("Error al descargar documento:", queryErr); + res.status(500).json({ error: "Error al descargar el documento" }); + } + }); + } catch (error) { + console.error("Error inesperado en downloadDocumento:", error); + res.status(500).json({ error: "Error interno del servidor" }); + } +}; + +// Visualizar imagen +/** + * @swagger + * /api/imagenes/{id}/view: + * get: + * summary: Visualiza una imagen por su ID. + * tags: [Documentos y Archivos API] + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: integer + * description: ID de la imagen a visualizar. + * responses: + * 200: + * description: Imagen mostrada exitosamente. + * content: + * image/*: + * schema: + * type: string + * format: binary + * 404: + * description: Imagen o archivo no encontrado. + * 500: + * description: Error del servidor. + */ +controlador.viewImagen = async (req, res) => { + try { + const { id } = req.params; + + req.getConnection(async (err, conn) => { + if (err) { + console.error("Error al conectar a la BD:", err); + return res.status(500).json({ error: "Error de conexión a la base de datos" }); + } + + try { + const [rows] = await conn.promise().query( + 'SELECT ruta_imagen, tipo_contenido FROM sys_imagenes WHERE id = ?', + [id] + ); + + if (rows.length === 0) { + return res.status(404).json({ error: "Imagen no encontrada" }); + } + + const imagen = rows[0]; + const rutaCompleta = path.join(__dirname, '..', imagen.ruta_imagen); + + // Verificar si el archivo existe + try { + await fs.access(rutaCompleta); + } catch (accessErr) { + return res.status(404).json({ error: "Archivo de imagen no encontrado en el servidor" }); + } + + // Configurar headers para visualización + res.setHeader('Content-Type', imagen.tipo_contenido || 'image/jpeg'); + + // Enviar archivo + res.sendFile(rutaCompleta); + + } catch (queryErr) { + console.error("Error al visualizar imagen:", queryErr); + res.status(500).json({ error: "Error al visualizar la imagen" }); + } + }); + } catch (error) { + console.error("Error inesperado en viewImagen:", error); + res.status(500).json({ error: "Error interno del servidor" }); + } +}; + +// Obtener documentos por entidad (función genérica) +/** + * @swagger + * /api/entidades/{tipoEntidad}/{entidadId}/documentos: + * get: + * summary: Obtiene todos los documentos asociados a una entidad específica. + * tags: [Documentos y Archivos API] + * parameters: + * - in: path + * name: tipoEntidad + * required: true + * schema: + * type: string + * description: Tipo de la entidad (ej. 'PROYECTO', 'CLIENTE', 'MEMORIA_TECNICA'). + * - in: path + * name: entidadId + * required: true + * schema: + * type: integer + * description: ID de la entidad. + * responses: + * 200: + * description: Lista de documentos. + * content: + * application/json: + * schema: + * type: array + * items: + * type: object + * properties: + * id: { type: integer, example: 1 } + * nombre_documento: { type: string, example: "Contrato_Cliente.pdf" } + * tipo_entidad_asociada: { type: string, example: "CLIENTE" } + * id_entidad_asociada: { type: integer, example: 5 } + * 500: + * description: Error del servidor. + */ +controlador.getDocumentosByEntidad = async (req, res) => { + try { + const { entidadId, tipoEntidad } = req.params; + + req.getConnection(async (err, conn) => { + if (err) { + console.error("Error al conectar a la BD:", err); + return res.status(500).json({ error: "Error de conexión a la base de datos" }); + } + + try { + const [rows] = await conn.promise().query(` + SELECT * FROM sys_documentos + WHERE id_entidad_asociada = ? AND tipo_entidad_asociada = ? + ORDER BY fecha_subida DESC + `, [entidadId, tipoEntidad]); + + res.json(rows); + + } catch (queryErr) { + console.error("Error al obtener documentos por entidad:", queryErr); + res.status(500).json({ error: "Error al obtener los documentos" }); + } + }); + } catch (error) { + console.error("Error inesperado en getDocumentosByEntidad:", error); + res.status(500).json({ error: "Error interno del servidor" }); + } +}; + +// Obtener imágenes por entidad (función genérica) +/** + * @swagger + * /api/entidades/{tipoEntidad}/{entidadId}/imagenes: + * get: + * summary: Obtiene todas las imágenes asociadas a una entidad específica. + * tags: [Documentos y Archivos API] + * parameters: + * - in: path + * name: tipoEntidad + * required: true + * schema: + * type: string + * description: Tipo de la entidad (ej. 'PROYECTO', 'CLIENTE', 'MEMORIA_TECNICA'). + * - in: path + * name: entidadId + * required: true + * schema: + * type: integer + * description: ID de la entidad. + * responses: + * 200: + * description: Lista de imágenes. + * content: + * application/json: + * schema: + * type: array + * items: + * type: object + * properties: + * id: { type: integer, example: 1 } + * nombre_imagen: { type: string, example: "Fachada_Edificio.jpg" } + * tipo_entidad_asociada: { type: string, example: "PROYECTO" } + * id_entidad_asociada: { type: integer, example: 3 } + * 500: + * description: Error del servidor. + */ +controlador.getImagenesByEntidad = async (req, res) => { + try { + const { entidadId, tipoEntidad } = req.params; + + req.getConnection(async (err, conn) => { + if (err) { + console.error("Error al conectar a la BD:", err); + return res.status(500).json({ error: "Error de conexión a la base de datos" }); + } + + try { + const [rows] = await conn.promise().query(` + SELECT * FROM sys_imagenes + WHERE id_entidad_asociada = ? AND tipo_entidad_asociada = ? + ORDER BY fecha_subida DESC + `, [entidadId, tipoEntidad]); + + res.json(rows); + + } catch (queryErr) { + console.error("Error al obtener imágenes por entidad:", queryErr); + res.status(500).json({ error: "Error al obtener las imágenes" }); + } + }); + } catch (error) { + console.error("Error inesperado en getImagenesByEntidad:", error); + res.status(500).json({ error: "Error interno del servidor" }); + } +}; + +/** + * @swagger + * /api/proyectos/memorias-tecnicas/{id}/generar-pdf: + * get: + * summary: Genera un PDF de una memoria técnica específica. + * tags: [Memoria Técnica API] + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: integer + * description: ID de la Memoria Técnica para generar el PDF. + * responses: + * 200: + * description: PDF generado y enviado exitosamente. + * content: + * application/pdf: + * schema: + * type: string + * format: binary + * 404: + * description: Memoria Técnica no encontrada. + * 500: + * description: Error del servidor al generar el PDF. + */ +controlador.generarPdfMemoriaTecnica = async (req, res) => { + const { id } = req.params; + let browser = null; + + try { + req.getConnection(async (err, conn) => { + if (err) { + console.error("Error al conectar a la BD:", err); + return res.status(500).json({ error: "Error de conexión a la base de datos" }); + } + + // 1. Obtener todos los datos de la memoria técnica + let memoria = null; + let componentes = []; + let mantenimiento = []; + let credenciales = []; + let documentos = []; + let imagenes = []; + let proyecto = null; + let empresaAutora = null; + let cliente = null; + + try { + const [rowsMemoria] = await conn.promise().query( + 'SELECT * FROM proyecto_memorias_tecnicas WHERE id = ?;', + [id] + ); + memoria = rowsMemoria[0]; + + if (!memoria) { + return res.status(404).json({ error: "Memoria técnica no encontrada." }); + } + + // Obtener datos del proyecto + const [rowsProyecto] = await conn.promise().query( + 'SELECT p.nombre_proyecto AS nombre_proyecto, p.objetivo, c.client_nombre AS nombre_cliente FROM proyectos p LEFT JOIN clientes c ON p.id_cliente = c.client_id WHERE p.id = ?;', + [memoria.id_proyecto] + ); + proyecto = rowsProyecto[0]; + + // Obtener datos de la empresa autora + const [rowsEmpresaAutora] = await conn.promise().query( + 'SELECT Nombre, RazonSocial FROM empresa_datos WHERE idEmpresa = ?;', + [memoria.id_empresa_autora] + ); + empresaAutora = rowsEmpresaAutora[0]; + + // Obtener componentes + const [rowsComponentes] = await conn.promise().query( + 'SELECT * FROM proyecto_componentes_memoria WHERE id_memoria = ? ORDER BY id;', + [id] + ); + componentes = rowsComponentes; + + // Obtener plan de mantenimiento + const [rowsMantenimiento] = await conn.promise().query( + 'SELECT * FROM proyecto_plan_mantenimiento_memoria WHERE id_memoria = ? ORDER BY id;', + [id] + ); + mantenimiento = rowsMantenimiento; + + // Obtener credenciales + const [rowsCredenciales] = await conn.promise().query( + 'SELECT * FROM proyecto_credenciales_dispositivos WHERE id_memoria = ? ORDER BY id;', + [id] + ); + credenciales = rowsCredenciales; + + // Obtener documentos (metadatos) + const [rowsDocumentos] = await conn.promise().query( + "SELECT * FROM sys_documentos WHERE id_entidad_asociada = ? AND tipo_entidad_asociada = 'MEMORIA_TECNICA' ORDER BY fecha_subida DESC;", + [id] + ); + documentos = rowsDocumentos; + + // Obtener imágenes (metadatos) + const [rowsImagenes] = await conn.promise().query( + "SELECT * FROM sys_imagenes WHERE id_entidad_asociada = ? AND tipo_entidad_asociada = 'MEMORIA_TECNICA' ORDER BY fecha_subida DESC;", + [id] + ); + imagenes = rowsImagenes; + + } catch (queryErr) { + console.error("Error al recopilar datos para PDF:", queryErr); + return res.status(500).json({ error: "Error al recopilar los datos de la memoria técnica para el PDF." }); + } + + // 2. Renderizar la plantilla EJS a HTML + // Usamos res.render para obtener el HTML como string, no para enviarlo directamente + const htmlContent = await res.render('pdf_memoria_tecnica', { + memoria, + proyecto, + cliente, // Aunque lo obtuvimos en proyecto, lo pasamos explícitamente si se necesita por separado + empresaAutora, + componentes, + mantenimiento, + credenciales, + documentos, + imagenes, + // Función auxiliar para rutas de archivos para el PDF (si se incrustan imágenes, etc.) + getFileUrl: (relativePath) => { + // Asegúrate de que esta URL sea accesible desde Puppeteer + // Si Puppeteer se ejecuta en el mismo servidor, 'http://localhost:PUERTO/uploads/...' + // Si los archivos están en un CDN, la URL completa + return `${req.protocol}://${req.get('host')}/${relativePath}`; + } + }); + + // 3. Generar el PDF con Puppeteer + browser = await puppeteer.launch({ + headless: true, // Ejecutar en modo headless (sin interfaz gráfica) + args: ['--no-sandbox', '--disable-setuid-sandbox'] // Necesario para entornos de servidor + }); + const page = await browser.newPage(); + + // Establecer el contenido HTML de la página + await page.setContent(htmlContent, { + waitUntil: 'networkidle0' // Esperar hasta que la red esté inactiva (cargado todo) + }); + + // Generar el PDF + const pdfBuffer = await page.pdf({ + format: 'A4', + printBackground: true, // Incluir colores y fondos + margin: { + top: '20mm', + right: '20mm', + bottom: '20mm', + left: '20mm' + } + }); + + // 4. Enviar el PDF como respuesta + res.setHeader('Content-Type', 'application/pdf'); + res.setHeader('Content-Disposition', `attachment; filename="Memoria_Tecnica_${memoria.id}_${proyecto ? proyecto.nombre_proyecto : 'SinProyecto'}.pdf"`); + res.send(pdfBuffer); + + }); + } catch (error) { + console.error("Error inesperado en generarPdfMemoriaTecnica:", error); + res.status(500).json({ error: "Error interno del servidor al generar el PDF." }); + } finally { + if (browser) { + await browser.close(); // Asegurarse de cerrar el navegador de Puppeteer + } + } +}; + +module.exports = controlador; diff --git a/src/rutas/rt_apps.js b/src/rutas/rt_apps.js index 17fac92..27fa645 100644 --- a/src/rutas/rt_apps.js +++ b/src/rutas/rt_apps.js @@ -10,4 +10,7 @@ rutas.get('/users', controlador_init.user);//devuelve usuaios con el el ROL mese rutas.get('/speedtest', controlador_init.speedtest);//testing velocimetro server rutas.get('/videos', controlador_init.videos);//videos sigma server rutas.get('/video_upload', controlador_init.upload);//videos sigma server + +/*** CONFIGURACION DE UN PAGINA QUE CONFIGURE EL ARCHIVO config.js ***/ +rutas.get('/configuracion', controlador_init.config);//llama a un view de configuracion del archivo config.js module.exports = rutas; \ No newline at end of file diff --git a/src/rutas/rt_clientes.js b/src/rutas/rt_clientes.js index 8e2f576..706fa66 100644 --- a/src/rutas/rt_clientes.js +++ b/src/rutas/rt_clientes.js @@ -18,9 +18,22 @@ rutas.post('/actCliente/:client_id', controladorClientes.modificaCliente);//actu //eliminar clientes rutas.get('/eliminarCliente/:client_id', controladorClientes.eliminarCliente); //almacena cliente -rutas.post('/addCliente', controladorClientes.guardaCliente);//almacena en bd el nuevo cliente +rutas.post('/addCliente', controladorClientes.guardaCliente);//almacena en bd el nuevo cliente: https://app.factura-e.net/addCliente rutas.get('/addClienteForm', controladorClientes.verFormNclientes);//muesta form para crear cliente +// --- Rutas RESTful para Miembros --- https://app.factura-e.net/api/miembros +// Listar todos los miembros (GET /api/miembros) +rutas.get('/api/miembros', controladorClientes.listarMiembros); +// Obtener un miembro específico por ID (GET /api/miembros/:id) +// Usado para "editar" (obtener datos para un formulario de edición) +rutas.get('/api/miembros/:id', controladorClientes.editarMiembro); +// Crear un nuevo miembro (POST /api/miembros) +rutas.post('/api/miembros', controladorClientes.guardarMiembro); +// Actualizar un miembro existente por ID (PUT /api/miembros/:id) +rutas.put('/api/miembros/:id', controladorClientes.actualizarMiembro); +// Eliminar un miembro por ID (DELETE /api/miembros/:id) +rutas.delete('/api/miembros/:id', controladorClientes.eliminarMiembro); + // Ruta para obtener ciudades rutas.get('/api/ciudades', controladorClientes.obtenerCiudades); diff --git a/src/rutas/rt_proyectos.js b/src/rutas/rt_proyectos.js new file mode 100644 index 0000000..78073b9 --- /dev/null +++ b/src/rutas/rt_proyectos.js @@ -0,0 +1,947 @@ +// src/rutas/rt_proyectos.js + +// Importar el módulo express para crear el router +const express = require('express'); +const router = express.Router(); + +// Importar el controlador de proyectos +// Asumo que el controlador estará en 'src/controladores/controlador_proyecto.js' +const controlador_proyecto = require('../controladores/controlador_proyecto'); + +// --- Rutas para las VISTAS EJS (Renderizan archivos EJS) --- + +/** + * @swagger + * /panel_proyectos: + * get: + * summary: Muestra el dashboard principal de gestión de proyectos. + * tags: [Proyectos] + * responses: + * 200: + * description: Dashboard de proyectos renderizado exitosamente. + * content: + * text/html: + * schema: + * type: string + * example: | + * ... (HTML del dashboard de proyectos) + * 500: + * description: Error del servidor al cargar los proyectos. + */ +router.get('/panel_proyectos', controlador_proyecto.panel_proyectos); + +/** + * @swagger + * /proyecto_memoria_tecnica: + * get: + * summary: Muestra el formulario para crear o editar una Memoria Técnica. + * tags: [Proyectos] + * parameters: + * - in: query + * name: proyectoId + * schema: + * type: integer + * description: ID del proyecto para pre-seleccionar en el formulario. + * - in: query + * name: memoriaId + * schema: + * type: integer + * description: ID de la Memoria Técnica a editar. + * responses: + * 200: + * description: Formulario de Memoria Técnica renderizado exitosamente. + * content: + * text/html: + * schema: + * type: string + * example: | + * ... (HTML del formulario de memoria técnica) + * 500: + * description: Error del servidor al cargar el formulario. + */ +router.get('/proyecto_memoria_tecnica', controlador_proyecto.memoriaTecnica); + +/** + * @swagger + * /lista_memorias_tecnicas: + * get: + * summary: Muestra una tabla con todas las memorias técnicas. + * tags: [Proyectos] + * responses: + * 200: + * description: Lista de memorias técnicas renderizada exitosamente. + * content: + * text/html: + * schema: + * type: string + * example: | + * ... (HTML de la lista de memorias) + * 500: + * description: Error del servidor al cargar la lista. + */ +router.get('/lista_memorias_tecnicas', controlador_proyecto.listarMemoriasTecnicas); + + +// --- Rutas de la API RESTful (Para operaciones CRUD de datos) --- + +// --- Rutas para la Memoria Técnica Principal --- + +/** + * @swagger + * /api/proyectos/memorias-tecnicas: + * post: + * summary: Crea una nueva Memoria Técnica. + * tags: [Memoria Técnica API] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * required: + * - id_proyecto + * - id_empresa_autora + * - objetivo_memoria_especifico + * properties: + * id_proyecto: + * type: integer + * description: ID del proyecto asociado. + * id_empresa_autora: + * type: integer + * description: ID de la empresa autora de la memoria. + * objetivo_memoria_especifico: + * type: string + * description: Objetivo específico de esta memoria técnica. + * descripcion_introduccion: + * type: string + * description: Descripción introductoria de la memoria. + * funcionamiento_operacion: + * type: string + * description: Detalles de funcionamiento y operación. + * conectividad_red: + * type: string + * description: Información sobre conectividad de red. + * acceso_remoto_aplicaciones: + * type: string + * description: Detalles de acceso remoto y aplicaciones. + * seguridad_fisica_logica: + * type: string + * responses: + * 201: + * description: Memoria Técnica creada exitosamente. + * content: + * application/json: + * schema: + * type: object + * properties: + * message: + * type: string + * example: Memoria técnica creada exitosamente + * id: + * type: integer + * example: 1 + * 400: + * description: Datos de entrada inválidos. + * 500: + * description: Error del servidor. + */ +router.post('/api/proyectos/memorias-tecnicas', controlador_proyecto.createMemoriaTecnica); + +/** + * @swagger + * /api/proyectos/memorias-tecnicas/{id}: + * get: + * summary: Obtiene una Memoria Técnica por su ID. + * tags: [Memoria Técnica API] + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: integer + * description: ID de la Memoria Técnica a obtener. + * responses: + * 200: + * description: Detalles de la Memoria Técnica. + * content: + * application/json: + * schema: + * type: object + * properties: + * id: { type: integer, example: 1 } + * id_proyecto: { type: integer, example: 101 } + * objetivo_memoria_especifico: { type: string, example: "Documentar instalación de sistema CCTV" } + * descripcion_introduccion: { type: string, example: "Esta memoria detalla la configuración inicial del sistema." } + * 404: + * description: Memoria Técnica no encontrada. + * 500: + * description: Error del servidor. + */ +router.get('/api/proyectos/memorias-tecnicas/:id', controlador_proyecto.getMemoriaTecnicaById); + +/** + * @swagger + * /api/proyectos/memorias-tecnicas/{id}: + * put: + * summary: Actualiza una Memoria Técnica existente. + * tags: [Memoria Técnica API] + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: integer + * description: ID de la Memoria Técnica a actualizar. + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * id_proyecto: + * type: integer + * id_empresa_autora: + * type: integer + * objetivo_memoria_especifico: + * type: string + * descripcion_introduccion: + * type: string + * funcionamiento_operacion: + * type: string + * conectividad_red: + * type: string + * acceso_remoto_aplicaciones: + * type: string + * seguridad_fisica_logica: + * type: string + * responses: + * 200: + * description: Memoria Técnica actualizada exitosamente. + * content: + * application/json: + * schema: + * type: object + * properties: + * message: + * type: string + * example: Memoria técnica actualizada exitosamente + * 400: + * description: Datos de entrada inválidos. + * 404: + * description: Memoria Técnica no encontrada. + * 500: + * description: Error del servidor. + */ +router.put('/api/proyectos/memorias-tecnicas/:id', controlador_proyecto.updateMemoriaTecnica); + +/** + * @swagger + * /api/proyectos/memorias-tecnicas/{id}: + * delete: + * summary: Elimina una Memoria Técnica. + * tags: [Memoria Técnica API] + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: integer + * description: ID de la Memoria Técnica a eliminar. + * responses: + * 204: + * description: Memoria Técnica eliminada exitosamente. + * 404: + * description: Memoria Técnica no encontrada. + * 500: + * description: Error del servidor. + */ +router.delete('/api/proyectos/memorias-tecnicas/:id', controlador_proyecto.deleteMemoriaTecnica); + +// --- Rutas para Componentes de la Memoria Técnica --- + +/** + * @swagger + * /api/proyectos/memorias-tecnicas/{memoriaId}/componentes: + * get: + * summary: Obtiene todos los componentes asociados a una Memoria Técnica. + * tags: [Memoria Técnica API] + * parameters: + * - in: path + * name: memoriaId + * required: true + * schema: + * type: integer + * description: ID de la Memoria Técnica. + * responses: + * 200: + * description: Lista de componentes. + * content: + * application/json: + * schema: + * type: array + * items: + * type: object + * properties: + * id: { type: integer, example: 1 } + * id_memoria: { type: integer, example: 10 } + * nombre_componente: { type: string, example: "Cámara IP Hikvision" } + * tipo: { type: string, example: "Cámara" } + * modelo_marca: { type: string, example: "DS-2CD2143G0-I" } + * numero_serie: { type: string, example: "SN123456789" } + * ubicacion: { type: string, example: "Entrada principal" } + * descripcion_breve: { type: string, example: "Cámara domo de 4MP con IR" } + * 500: + * description: Error del servidor. + * post: + * summary: Agrega un nuevo componente a una Memoria Técnica. + * tags: [Memoria Técnica API] + * parameters: + * - in: path + * name: memoriaId + * required: true + * schema: + * type: integer + * description: ID de la Memoria Técnica. + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * required: + * - nombre_componente + * properties: + * nombre_componente: + * type: string + * tipo: + * type: string + * modelo_marca: + * type: string + * numero_serie: + * type: string + * ubicacion: + * type: string + * descripcion_breve: + * type: string + * responses: + * 201: + * description: Componente añadido exitosamente. + * 400: + * description: Datos de entrada inválidos. + * 500: + * description: Error del servidor. + */ +router.get('/api/proyectos/memorias-tecnicas/:memoriaId/componentes', controlador_proyecto.getComponentesByMemoriaId); +router.post('/api/proyectos/memorias-tecnicas/:memoriaId/componentes', controlador_proyecto.addComponente); + +/** + * @swagger + * /api/proyectos/componentes/{id}: + * put: + * summary: Actualiza un componente de la Memoria Técnica. + * tags: [Memoria Técnica API] + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: integer + * description: ID del componente a actualizar. + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * nombre_componente: + * type: string + * tipo: + * type: string + * modelo_marca: + * type: string + * numero_serie: + * type: string + * ubicacion: + * type: string + * descripcion_breve: + * type: string + * responses: + * 200: + * description: Componente actualizado exitosamente. + * 404: + * description: Componente no encontrado. + * 500: + * description: Error del servidor. + * delete: + * summary: Elimina un componente de la Memoria Técnica. + * tags: [Memoria Técnica API] + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: integer + * description: ID del componente a eliminar. + * responses: + * 204: + * description: Componente eliminado exitosamente. + * 404: + * description: Componente no encontrado. + * 500: + * description: Error del servidor. + */ +router.put('/api/proyectos/componentes/:id', controlador_proyecto.updateComponente); +router.delete('/api/proyectos/componentes/:id', controlador_proyecto.deleteComponente); + +// --- Rutas para Plan de Mantenimiento --- + +/** + * @swagger + * /api/proyectos/memorias-tecnicas/{memoriaId}/mantenimiento: + * get: + * summary: Obtiene todos los planes de mantenimiento asociados a una Memoria Técnica. + * tags: [Memoria Técnica API] + * parameters: + * - in: path + * name: memoriaId + * required: true + * schema: + * type: integer + * description: ID de la Memoria Técnica. + * responses: + * 200: + * description: Lista de planes de mantenimiento. + * content: + * application/json: + * schema: + * type: array + * items: + * type: object + * properties: + * id: { type: integer, example: 1 } + * id_memoria: { type: integer, example: 10 } + * tipo_mantenimiento: { type: string, example: "Preventivo" } + * frecuencia: { type: string, example: "Mensual" } + * tareas_procedimientos: { type: string, example: "Limpieza de filtros y revisión de conexiones." } + * responsable: { type: string, example: "Equipo de Mantenimiento A" } + * 500: + * description: Error del servidor. + * post: + * summary: Agrega un nuevo plan de mantenimiento a una Memoria Técnica. + * tags: [Memoria Técnica API] + * parameters: + * - in: path + * name: memoriaId + * required: true + * schema: + * type: integer + * description: ID de la Memoria Técnica. + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * required: + * - tareas_procedimientos + * properties: + * tipo_mantenimiento: + * type: string + * frecuencia: + * type: string + * tareas_procedimientos: + * type: string + * responsable: + * type: string + * responses: + * 201: + * description: Plan de mantenimiento añadido exitosamente. + * 400: + * description: Datos de entrada inválidos. + * 500: + * description: Error del servidor. + */ +router.get('/api/proyectos/memorias-tecnicas/:memoriaId/mantenimiento', controlador_proyecto.getMantenimientoByMemoriaId); +router.post('/api/proyectos/memorias-tecnicas/:memoriaId/mantenimiento', controlador_proyecto.addMantenimiento); + +/** + * @swagger + * /api/proyectos/mantenimiento/{id}: + * put: + * summary: Actualiza un plan de mantenimiento. + * tags: [Memoria Técnica API] + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: integer + * description: ID del plan de mantenimiento a actualizar. + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * tipo_mantenimiento: + * type: string + * frecuencia: + * type: string + * tareas_procedimientos: + * type: string + * responsable: + * type: string + * responses: + * 200: + * description: Plan de mantenimiento actualizado exitosamente. + * 404: + * description: Plan de mantenimiento no encontrado. + * 500: + * description: Error del servidor. + * delete: + * summary: Elimina un plan de mantenimiento. + * tags: [Memoria Técnica API] + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: integer + * description: ID del plan de mantenimiento a eliminar. + * responses: + * 200: + * description: Plan de mantenimiento eliminado exitosamente. + * 404: + * description: Plan de mantenimiento no encontrado. + * 500: + * description: Error del servidor. + */ +router.put('/api/proyectos/mantenimiento/:id', controlador_proyecto.updateMantenimiento); +router.delete('/api/proyectos/mantenimiento/:id', controlador_proyecto.deleteMantenimiento); + +// --- Rutas para Credenciales de Dispositivos --- + +/** + * @swagger + * /api/proyectos/memorias-tecnicas/{memoriaId}/credenciales: + * get: + * summary: Obtiene todas las credenciales asociadas a una Memoria Técnica. + * tags: [Memoria Técnica API] + * parameters: + * - in: path + * name: memoriaId + * required: true + * schema: + * type: integer + * description: ID de la Memoria Técnica. + * responses: + * 200: + * description: Lista de credenciales. + * content: + * application/json: + * schema: + * type: array + * items: + * type: object + * properties: + * id: { type: integer, example: 1 } + * id_memoria: { type: integer, example: 10 } + * tipo_credencial: { type: string, example: "Usuario" } + * valor_credencial: { type: string, example: "admin" } + * descripcion: { type: string, example: "Credencial de acceso al NVR" } + * 500: + * description: Error del servidor. + * post: + * summary: Agrega una nueva credencial a una Memoria Técnica. + * tags: [Memoria Técnica API] + * parameters: + * - in: path + * name: memoriaId + * required: true + * schema: + * type: integer + * description: ID de la Memoria Técnica. + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * required: + * - tipo_credencial + * - valor_credencial + * properties: + * tipo_credencial: + * type: string + * valor_credencial: + * type: string + * descripcion: + * type: string + * responses: + * 201: + * description: Credencial agregada exitosamente. + * 400: + * description: Datos de entrada inválidos. + * 500: + * description: Error del servidor. + */ +router.get('/api/proyectos/memorias-tecnicas/:memoriaId/credenciales', controlador_proyecto.getCredencialesByMemoriaId); +router.post('/api/proyectos/memorias-tecnicas/:memoriaId/credenciales', controlador_proyecto.addCredencial); + +/** + * @swagger + * /api/proyectos/credenciales/{id}: + * put: + * summary: Actualiza una credencial de dispositivo. + * tags: [Memoria Técnica API] + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: integer + * description: ID de la credencial a actualizar. + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * tipo_credencial: + * type: string + * valor_credencial: + * type: string + * descripcion: + * type: string + * responses: + * 200: + * description: Credencial actualizada exitosamente. + * 404: + * description: Credencial no encontrada. + * 500: + * description: Error del servidor. + * delete: + * summary: Elimina una credencial de dispositivo. + * tags: [Memoria Técnica API] + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: integer + * description: ID de la credencial a eliminar. + * responses: + * 200: + * description: Credencial eliminada exitosamente. + * 404: + * description: Credencial no encontrada. + * 500: + * description: Error del servidor. + */ +router.put('/api/proyectos/credenciales/:id', controlador_proyecto.updateCredencial); +router.delete('/api/proyectos/credenciales/:id', controlador_proyecto.deleteCredencial); + +// --- Rutas para Documentos Genéricos (relacionados con Memoria Técnica) --- + +/** + * @swagger + * /api/documentos: + * post: + * summary: Sube un nuevo documento. + * tags: [Documentos y Archivos API] + * requestBody: + * required: true + * content: + * multipart/form-data: + * schema: + * type: object + * required: + * - documento + * - id_entidad_asociada + * - tipo_entidad_asociada + * properties: + * documento: + * type: string + * format: binary + * memoriaId: + * type: integer + * description: ID de la Memoria Técnica a la que se asocia el documento (opcional). + * descripcion: + * type: string + * description: Descripción del documento. + * responses: + * 201: + * description: Documento subido exitosamente. + * 400: + * description: Datos de entrada inválidos o archivo no proporcionado. + * 500: + * description: Error del servidor. + */ +router.post('/api/documentos', controlador_proyecto.uploadDocumento); + +/** + * @swagger + * /api/documentos/{id}: + * get: + * summary: Obtiene un documento por su ID. + * tags: [Documentos y Archivos API] + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: integer + * description: ID del documento a obtener. + * responses: + * 200: + * description: Documento encontrado (puede devolver el archivo o metadatos). + * 404: + * description: Documento no encontrado. + * 500: + * description: Error del servidor. + * delete: + * summary: Elimina un documento por su ID. + * tags: [Documentos y Archivos API] + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: integer + * description: ID del documento a eliminar. + * responses: + * 200: + * description: Documento eliminado exitosamente. + * 404: + * description: Documento no encontrado. + * 500: + * description: Error del servidor. + */ +router.get('/api/documentos/:id', controlador_proyecto.getDocumentoById); +router.delete('/api/documentos/:id', controlador_proyecto.deleteDocumento); + +/** + * @swagger + * /api/proyectos/memorias-tecnicas/{memoriaId}/documentos: + * get: + * summary: Obtiene todos los documentos asociados a una Memoria Técnica. + * tags: [Documentos y Archivos API] + * parameters: + * - in: path + * name: memoriaId + * required: true + * schema: + * type: integer + * description: ID de la Memoria Técnica. + * responses: + * 200: + * description: Lista de documentos. + * content: + * application/json: + * schema: + * type: array + * items: + * type: object + * properties: + * id: { type: integer, example: 1 } + * nombre_documento: { type: string, example: "Manual_CCTV.pdf" } + * ruta_archivo: { type: string, example: "uploads/documentos/manual_cctv.pdf" } + * tipo_entidad_asociada: { type: string, example: "MEMORIA_TECNICA" } + * 500: + * description: Error del servidor. + */ +router.get('/api/proyectos/memorias-tecnicas/:memoriaId/documentos', controlador_proyecto.getDocumentosByMemoriaId); +router.get('/api/documentos/:id/download', controlador_proyecto.downloadDocumento); // Nueva ruta para descargar + +// --- Rutas para Imágenes Genéricas (relacionadas con Memoria Técnica) --- + +/** + * @swagger + * /api/imagenes: + * post: + * summary: Sube una nueva imagen. + * tags: [Documentos y Archivos API] + * requestBody: + * required: true + * content: + * multipart/form-data: + * schema: + * type: object + * required: + * - imagen + * - id_entidad_asociada + * - tipo_entidad_asociada + * properties: + * imagen: + * type: string + * format: binary + * memoriaId: + * type: integer + * description: ID de la Memoria Técnica a la que se asocia la imagen (opcional). + * descripcion: + * type: string + * description: Descripción de la imagen. + * responses: + * 201: + * description: Imagen subida exitosamente. + * 400: + * description: Datos de entrada inválidos o archivo no proporcionado. + * 500: + * description: Error del servidor. + */ +router.post('/api/imagenes', controlador_proyecto.uploadImagen); + +/** + * @swagger + * /api/imagenes/{id}: + * get: + * summary: Obtiene una imagen por su ID. + * tags: [Documentos y Archivos API] + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: integer + * description: ID de la imagen a obtener. + * responses: + * 200: + * description: Imagen encontrada (puede devolver el archivo o metadatos). + * 404: + * description: Imagen no encontrada. + * 500: + * description: Error del servidor. + * delete: + * summary: Elimina una imagen por su ID. + * tags: [Documentos y Archivos API] + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: integer + * description: ID de la imagen a eliminar. + * responses: + * 200: + * description: Imagen eliminada exitosamente. + * 404: + * description: Imagen no encontrada. + * 500: + * description: Error del servidor. + */ +router.get('/api/imagenes/:id', controlador_proyecto.getImagenById); +router.delete('/api/imagenes/:id', controlador_proyecto.deleteImagen); +router.get('/api/imagenes/:id/view', controlador_proyecto.viewImagen); // Nueva ruta para visualizar + +/** + * @swagger + * /api/entidades/{tipoEntidad}/{entidadId}/documentos: + * get: + * summary: Obtiene todos los documentos asociados a una entidad específica. + * tags: [Documentos y Archivos API] + * parameters: + * - in: path + * name: tipoEntidad + * required: true + * schema: + * type: string + * description: Tipo de la entidad (ej. 'PROYECTO', 'CLIENTE', 'MEMORIA_TECNICA'). + * - in: path + * name: entidadId + * required: true + * schema: + * type: integer + * description: ID de la entidad. + * responses: + * 200: + * description: Lista de documentos. + * content: + * application/json: + * schema: + * type: array + * items: + * type: object + * properties: + * id: { type: integer, example: 1 } + * nombre_documento: { type: string, example: "Contrato_Cliente.pdf" } + * tipo_entidad_asociada: { type: string, example: "CLIENTE" } + * id_entidad_asociada: { type: integer, example: 5 } + * 500: + * description: Error del servidor. + */ +router.get('/api/entidades/:tipoEntidad/:entidadId/documentos', controlador_proyecto.getDocumentosByEntidad); + +/** + * @swagger + * /api/entidades/{tipoEntidad}/{entidadId}/imagenes: + * get: + * summary: Obtiene todas las imágenes asociadas a una entidad específica. + * tags: [Documentos y Archivos API] + * parameters: + * - in: path + * name: tipoEntidad + * required: true + * schema: + * type: string + * description: Tipo de la entidad (ej. 'PROYECTO', 'CLIENTE', 'MEMORIA_TECNICA'). + * - in: path + * name: entidadId + * required: true + * schema: + * type: integer + * description: ID de la entidad. + * responses: + * 200: + * description: Lista de imágenes. + * content: + * application/json: + * schema: + * type: array + * items: + * type: object + * properties: + * id: { type: integer, example: 1 } + * nombre_imagen: { type: string, example: "Fachada_Edificio.jpg" } + * tipo_entidad_asociada: { type: string, example: "PROYECTO" } + * id_entidad_asociada: { type: integer, example: 3 } + * 500: + * description: Error del servidor. + */ +router.get('/api/entidades/:tipoEntidad/:entidadId/imagenes', controlador_proyecto.getImagenesByEntidad); + +// Nueva ruta para generar PDF +/** + * @swagger + * /api/proyectos/memorias-tecnicas/{id}/generar-pdf: + * get: + * summary: Genera un PDF de una memoria técnica específica. + * tags: [Memoria Técnica API] + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: integer + * description: ID de la Memoria Técnica para generar el PDF. + * responses: + * 200: + * description: PDF generado y enviado exitosamente. + * content: + * application/pdf: + * schema: + * type: string + * format: binary + * 404: + * description: Memoria Técnica no encontrada. + * 500: + * description: Error del servidor al generar el PDF. + */ +router.get('/api/proyectos/memorias-tecnicas/:id/generar-pdf', controlador_proyecto.generarPdfMemoriaTecnica); + + +// Exportar el router +module.exports = router; diff --git a/src/swager_config.js b/src/swager_config.js index 887c34d..fbf7525 100644 --- a/src/swager_config.js +++ b/src/swager_config.js @@ -41,7 +41,8 @@ const swaggerOptions = { './rutas/rt_Generales.js', './rutas/rt_cloud.js', './rutas/rt_apps.js', - './rutas/rt_arduino.js' + './rutas/rt_arduino.js', + './rutas/rt_proyectos.js', ] }; diff --git a/src/uploads/documentos/doc_1748852222471_mkdl5f19o.xlsx b/src/uploads/documentos/doc_1748852222471_mkdl5f19o.xlsx new file mode 100644 index 0000000..3213d9a Binary files /dev/null and b/src/uploads/documentos/doc_1748852222471_mkdl5f19o.xlsx differ diff --git a/src/uploads/imagenes/img_1748852241294_vk7eefxl3.jpg b/src/uploads/imagenes/img_1748852241294_vk7eefxl3.jpg new file mode 100644 index 0000000..69d3ac6 Binary files /dev/null and b/src/uploads/imagenes/img_1748852241294_vk7eefxl3.jpg differ diff --git a/src/views/configuracion.ejs b/src/views/configuracion.ejs new file mode 100644 index 0000000..e69de29 diff --git a/src/views/lista_memorias_tecnicas.ejs b/src/views/lista_memorias_tecnicas.ejs new file mode 100644 index 0000000..c4545f9 --- /dev/null +++ b/src/views/lista_memorias_tecnicas.ejs @@ -0,0 +1,158 @@ + + + + + + Lista de Memorias Técnicas - SIGMA + + + + +
+

Lista de Memorias Técnicas

+ +
+ +
+ + + + + + + + + + + + + + + + <% if (memorias && memorias.length > 0) { %> + <% memorias.forEach(function(memoria) { %> + + + + + + + + + + + <% }); %> + <% } else { %> + + + + <% } %> + +
IDObjetivo de la MemoriaProyectoClienteEmpresa AutoraFecha ElaboraciónÚltima ModificaciónAcciones
<%= memoria.id %><%= memoria.objetivo_memoria_especifico %><%= memoria.nombre_proyecto || 'N/A' %><%= memoria.nombre_cliente || 'N/A' %><%= memoria.nombre_empresa_autora || 'N/A' %><%= new Date(memoria.fecha_elaboracion).toLocaleDateString() %><%= new Date(memoria.ultima_modificacion).toLocaleDateString() %> + + +
No hay memorias técnicas disponibles.
+
+ + + + \ No newline at end of file diff --git a/src/views/panel_proyectos.ejs b/src/views/panel_proyectos.ejs new file mode 100644 index 0000000..4b59a77 --- /dev/null +++ b/src/views/panel_proyectos.ejs @@ -0,0 +1,130 @@ + + + + + + Panel de Proyectos - SIGMA + + + + +
+

Panel de Gestión de Proyectos

+ +
+ +
+ +

Lista de Proyectos

+ + + + + + + + + + + + + <% if (proyectos && proyectos.length > 0) { %> + <% proyectos.forEach(function(proyecto) { %> + + + + + + + + + <% }); %> + <% } else { %> + + + + <% } %> + +
ID ProyectoNombre del ProyectoClienteObjetivoEstadoAcciones
<%= proyecto.id %><%= proyecto.nombre_proyecto %><%= proyecto.nombre_cliente || 'N/A' %><%= proyecto.objetivo %><%= proyecto.estado %> + + +
No hay proyectos disponibles.
+
+ + + \ No newline at end of file diff --git a/src/views/pdf_memoria_tecnica.ejs b/src/views/pdf_memoria_tecnica.ejs new file mode 100644 index 0000000..0d93b50 --- /dev/null +++ b/src/views/pdf_memoria_tecnica.ejs @@ -0,0 +1,284 @@ + + + + + Memoria Técnica - <%= memoria.id %> + + + +
+ Memoria Técnica SIGMA - ID: <%= memoria.id %> +
+ + + +
+

MEMORIA TÉCNICA DEL PROYECTO

+ +
+

Datos Generales de la Memoria

+
ID Memoria: <%= memoria.id %>
+
Proyecto: <%= proyecto ? proyecto.nombre_proyecto : 'N/A' %>
+
Cliente: <%= proyecto ? proyecto.nombre_cliente : 'N/A' %>
+
Empresa Autora: <%= empresaAutora ? (empresaAutora.RazonSocial || empresaAutora.Nombre) : 'N/A' %>
+
Fecha Elaboración: <%= new Date(memoria.fecha_elaboracion).toLocaleDateString('es-ES', { year: 'numeric', month: 'long', day: 'numeric' }) %>
+
Última Modificación: <%= new Date(memoria.ultima_modificacion).toLocaleDateString('es-ES', { year: 'numeric', month: 'long', day: 'numeric' }) %>
+
Objetivo de la Memoria:
+
<%= memoria.objetivo_memoria_especifico || 'No especificado.' %>
+
Descripción/Introducción:
+
<%= memoria.descripcion_introduccion || 'No especificado.' %>
+
+ + <% if (memoria.funcionamiento_operacion || memoria.conectividad_red || memoria.seguridad_fisica_logica || credenciales.length > 0) { %> +
+
+

Información de Conectividad y Seguridad

+ <% if (memoria.funcionamiento_operacion) { %> +

Funcionamiento y Operación:

+
<%= memoria.funcionamiento_operacion %>
+ <% } %> + <% if (memoria.conectividad_red) { %> +

Conectividad de Red:

+
<%= memoria.conectividad_red %>
+ <% } %> + <% if (memoria.acceso_remoto_aplicaciones) { %> +

Acceso Remoto y Aplicaciones:

+
<%= memoria.acceso_remoto_aplicaciones %>
+ <% } %> + <% if (memoria.seguridad_fisica_logica) { %> +

Seguridad Física y Lógica:

+
<%= memoria.seguridad_fisica_logica %>
+ <% } %> + + <% if (credenciales.length > 0) { %> +

Credenciales de Dispositivos:

+ + + + + + + + + + <% credenciales.forEach(function(cred) { %> + + + + + + <% }); %> + +
TipoValorDescripción
<%= cred.tipo_credencial %><%= cred.valor_credencial %><%= cred.descripcion || 'N/A' %>
+ <% } %> +
+ <% } %> + + <% if (componentes.length > 0) { %> +
+
+

Componentes del Proyecto

+ + + + + + + + + + + + + <% componentes.forEach(function(comp) { %> + + + + + + + + + <% }); %> + +
NombreTipoModelo/MarcaNº SerieUbicaciónDescripción
<%= comp.nombre_componente %><%= comp.tipo || 'N/A' %><%= comp.modelo_marca || 'N/A' %><%= comp.numero_serie || 'N/A' %><%= comp.ubicacion || 'N/A' %><%= comp.descripcion_breve || 'N/A' %>
+
+ <% } %> + + <% if (mantenimiento.length > 0) { %> +
+
+

Plan de Mantenimiento

+ + + + + + + + + + + <% mantenimiento.forEach(function(mtto) { %> + + + + + + + <% }); %> + +
Tipo MantenimientoFrecuenciaTareas/ProcedimientosResponsable
<%= mtto.tipo_mantenimiento || 'N/A' %><%= mtto.frecuencia || 'N/A' %><%= mtto.tareas_procedimientos %><%= mtto.responsable || 'N/A' %>
+
+ <% } %> + + <% if (documentos.length > 0 || imagenes.length > 0) { %> +
+
+

Documentos y Archivos Adjuntos

+ <% if (documentos.length > 0) { %> +

Documentos:

+ + + + + + + + + + + <% documentos.forEach(function(doc) { %> + + + + + + + <% }); %> + +
NombreDescripciónTipoTamaño
<%= doc.nombre_documento %><%= doc.descripcion || 'N/A' %><%= doc.tipo_contenido || 'N/A' %><%= (doc.tamano_bytes / 1024).toFixed(2) %> KB
+ <% } %> + + <% if (imagenes.length > 0) { %> +

Imágenes / Planos:

+ + <% } %> +
+ <% } %> + +
+ + \ No newline at end of file diff --git a/src/views/proyecto_memoria_tecnica.ejs b/src/views/proyecto_memoria_tecnica.ejs new file mode 100644 index 0000000..d86f598 --- /dev/null +++ b/src/views/proyecto_memoria_tecnica.ejs @@ -0,0 +1,1124 @@ + + + + + + Formulario de Memoria Técnica - SIGMA + + + + +
+

Formulario de Memoria Técnica

+ + + +
+
+ + + + + +
+ +
+

Datos Generales de la Memoria

+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+
+ + + + + + + + +
+ +
+ + + + +
+
+ + + +