7 Commits

Author SHA1 Message Date
jalvarado 2af1b23f4d qr genereta 2025-04-02 13:54:50 -06:00
eithan f9549529b9 Merge pull request 'eithan' (#7) from eithan into develop
Reviewed-on: #7
2025-04-02 12:44:32 -06:00
Your Name a16e82005e formato correcto de cuestionario por verificar 2025-04-02 12:43:37 -06:00
Your Name a1f52daa8a swagger 2025-04-02 11:50:08 -06:00
Your Name bf7c0e93a8 variables de entorno, correcciones de codigo actual, docker file 2025-04-02 11:14:45 -06:00
eithan 60c75f437f Merge pull request 'santiago' (#6) from santiago into develop
Reviewed-on: #6
2025-04-02 10:31:24 -06:00
santiago 7b72ee4ec6 Cambios en las relaciones de evento, participante_evento y participante. 2025-04-02 09:15:14 -06:00
67 changed files with 2972 additions and 200 deletions
+18
View File
@@ -0,0 +1,18 @@
# Etapa de desarrollo para ejecutar en watch mode
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
# Asegurarse de compilar el proyecto
RUN npm run build
# Puerto expuesto
EXPOSE 4200
# Comando para iniciar en modo desarrollo
CMD ["npm", "run", "start:dev"]
+33
View File
@@ -0,0 +1,33 @@
version: '3.8'
services:
mariadb:
image: mariadb:latest
container_name: formularios_db
ports:
- "4201:3306"
environment:
MARIADB_ROOT_PASSWORD: mypass
MARIADB_DATABASE: formularios_test
volumes:
- mariadb_data:/var/lib/mysql
restart: unless-stopped
api:
build:
context: .
dockerfile: Dockerfile
container_name: formularios_api
env_file:
- .env
volumes:
- .:/app
- /app/node_modules
ports:
- "4200:4200"
depends_on:
- mariadb
restart: unless-stopped
volumes:
mariadb_data:
+422 -18
View File
@@ -9,13 +9,19 @@
"version": "0.0.1", "version": "0.0.1",
"license": "UNLICENSED", "license": "UNLICENSED",
"dependencies": { "dependencies": {
"@nestjs/common": "^11.0.1", "@nestjs/common": "^11.0.12",
"@nestjs/config": "^4.0.2", "@nestjs/config": "^4.0.2",
"@nestjs/core": "^11.0.1", "@nestjs/core": "^11.0.1",
"@nestjs/mapped-types": "*", "@nestjs/mapped-types": "*",
"@nestjs/platform-express": "^11.0.1", "@nestjs/platform-express": "^11.0.1",
"@nestjs/swagger": "^11.1.0",
"@nestjs/typeorm": "^11.0.0", "@nestjs/typeorm": "^11.0.0",
"axios": "^1.8.4",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.1", "class-validator": "^0.14.1",
"dotenv": "^16.4.7",
"mysql2": "^3.14.0",
"qrcode": "^1.5.4",
"reflect-metadata": "^0.2.2", "reflect-metadata": "^0.2.2",
"rxjs": "^7.8.1", "rxjs": "^7.8.1",
"typeorm": "^0.3.21" "typeorm": "^0.3.21"
@@ -31,6 +37,7 @@
"@types/express": "^5.0.0", "@types/express": "^5.0.0",
"@types/jest": "^29.5.14", "@types/jest": "^29.5.14",
"@types/node": "^22.10.7", "@types/node": "^22.10.7",
"@types/qrcode": "^1.5.5",
"@types/supertest": "^6.0.2", "@types/supertest": "^6.0.2",
"eslint": "^9.18.0", "eslint": "^9.18.0",
"eslint-config-prettier": "^10.0.1", "eslint-config-prettier": "^10.0.1",
@@ -44,7 +51,7 @@
"ts-loader": "^9.5.2", "ts-loader": "^9.5.2",
"ts-node": "^10.9.2", "ts-node": "^10.9.2",
"tsconfig-paths": "^4.2.0", "tsconfig-paths": "^4.2.0",
"typescript": "^5.7.3", "typescript": "^5.8.2",
"typescript-eslint": "^8.20.0" "typescript-eslint": "^8.20.0"
} }
}, },
@@ -1906,6 +1913,12 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/@microsoft/tsdoc": {
"version": "0.15.1",
"resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.15.1.tgz",
"integrity": "sha512-4aErSrCR/On/e5G2hDP0wjooqDdauzEbIq8hIkIe5pXV0rtWJZvdCEKL0ykZxex+IxIwBp0eGeV48hQN07dXtw==",
"license": "MIT"
},
"node_modules/@napi-rs/nice": { "node_modules/@napi-rs/nice": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/@napi-rs/nice/-/nice-1.0.1.tgz", "resolved": "https://registry.npmjs.org/@napi-rs/nice/-/nice-1.0.1.tgz",
@@ -2495,6 +2508,39 @@
"tslib": "^2.1.0" "tslib": "^2.1.0"
} }
}, },
"node_modules/@nestjs/swagger": {
"version": "11.1.0",
"resolved": "https://registry.npmjs.org/@nestjs/swagger/-/swagger-11.1.0.tgz",
"integrity": "sha512-+GQ+q1ASTBvGi0DYHukWi8NVVVLszedwLLqHdLRnJh8rjokt8YTDb7roImvT/YMmYgPvaWBv/4JYdZH4FueLPQ==",
"license": "MIT",
"dependencies": {
"@microsoft/tsdoc": "0.15.1",
"@nestjs/mapped-types": "2.1.0",
"js-yaml": "4.1.0",
"lodash": "4.17.21",
"path-to-regexp": "8.2.0",
"swagger-ui-dist": "5.20.1"
},
"peerDependencies": {
"@fastify/static": "^8.0.0",
"@nestjs/common": "^11.0.1",
"@nestjs/core": "^11.0.1",
"class-transformer": "*",
"class-validator": "*",
"reflect-metadata": "^0.1.12 || ^0.2.0"
},
"peerDependenciesMeta": {
"@fastify/static": {
"optional": true
},
"class-transformer": {
"optional": true
},
"class-validator": {
"optional": true
}
}
},
"node_modules/@nestjs/testing": { "node_modules/@nestjs/testing": {
"version": "11.0.12", "version": "11.0.12",
"resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-11.0.12.tgz", "resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-11.0.12.tgz",
@@ -2613,6 +2659,13 @@
"url": "https://opencollective.com/unts" "url": "https://opencollective.com/unts"
} }
}, },
"node_modules/@scarf/scarf": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.4.0.tgz",
"integrity": "sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==",
"hasInstallScript": true,
"license": "Apache-2.0"
},
"node_modules/@sec-ant/readable-stream": { "node_modules/@sec-ant/readable-stream": {
"version": "0.4.1", "version": "0.4.1",
"resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz",
@@ -3231,6 +3284,16 @@
"undici-types": "~6.20.0" "undici-types": "~6.20.0"
} }
}, },
"node_modules/@types/qrcode": {
"version": "1.5.5",
"resolved": "https://registry.npmjs.org/@types/qrcode/-/qrcode-1.5.5.tgz",
"integrity": "sha512-CdfBi/e3Qk+3Z/fXYShipBT13OJ2fDO2Q2w5CIP5anLTLIndQG9z6P1cnm+8zCWSpm5dnxMFd/uREtb0EXuQzg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/qs": { "node_modules/@types/qs": {
"version": "6.9.18", "version": "6.9.18",
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.18.tgz", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.18.tgz",
@@ -4115,7 +4178,6 @@
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
"dev": true,
"license": "Python-2.0" "license": "Python-2.0"
}, },
"node_modules/array-timsort": { "node_modules/array-timsort": {
@@ -4143,9 +4205,28 @@
"version": "0.4.0", "version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"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==",
"license": "MIT",
"engines": {
"node": ">= 6.0.0"
}
},
"node_modules/axios": {
"version": "1.8.4",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.8.4.tgz",
"integrity": "sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==",
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.15.6",
"form-data": "^4.0.0",
"proxy-from-env": "^1.1.0"
}
},
"node_modules/b4a": { "node_modules/b4a": {
"version": "1.6.7", "version": "1.6.7",
"resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.7.tgz", "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.7.tgz",
@@ -4636,7 +4717,6 @@
"version": "5.3.1", "version": "5.3.1",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
"integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=6" "node": ">=6"
@@ -4746,6 +4826,12 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/class-transformer": {
"version": "0.5.1",
"resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz",
"integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==",
"license": "MIT"
},
"node_modules/class-validator": { "node_modules/class-validator": {
"version": "0.14.1", "version": "0.14.1",
"resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.14.1.tgz", "resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.14.1.tgz",
@@ -4911,7 +4997,6 @@
"version": "1.0.8", "version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"delayed-stream": "~1.0.0" "delayed-stream": "~1.0.0"
@@ -5154,6 +5239,15 @@
} }
} }
}, },
"node_modules/decamelize": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
"integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/decompress-response": { "node_modules/decompress-response": {
"version": "6.0.0", "version": "6.0.0",
"resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz",
@@ -5242,12 +5336,20 @@
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=0.4.0" "node": ">=0.4.0"
} }
}, },
"node_modules/denque": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz",
"integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==",
"license": "Apache-2.0",
"engines": {
"node": ">=0.10"
}
},
"node_modules/depd": { "node_modules/depd": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
@@ -5308,6 +5410,12 @@
"node": "^14.15.0 || ^16.10.0 || >=18.0.0" "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
} }
}, },
"node_modules/dijkstrajs": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.3.tgz",
"integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==",
"license": "MIT"
},
"node_modules/dotenv": { "node_modules/dotenv": {
"version": "16.4.7", "version": "16.4.7",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz",
@@ -5477,7 +5585,6 @@
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"es-errors": "^1.3.0", "es-errors": "^1.3.0",
@@ -6255,6 +6362,26 @@
"dev": true, "dev": true,
"license": "ISC" "license": "ISC"
}, },
"node_modules/follow-redirects": {
"version": "1.15.9",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
"integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/RubenVerborgh"
}
],
"license": "MIT",
"engines": {
"node": ">=4.0"
},
"peerDependenciesMeta": {
"debug": {
"optional": true
}
}
},
"node_modules/foreground-child": { "node_modules/foreground-child": {
"version": "3.3.1", "version": "3.3.1",
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz",
@@ -6368,7 +6495,6 @@
"version": "4.0.2", "version": "4.0.2",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz",
"integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"asynckit": "^0.4.0", "asynckit": "^0.4.0",
@@ -6394,7 +6520,6 @@
"version": "1.52.0", "version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">= 0.6" "node": ">= 0.6"
@@ -6404,7 +6529,6 @@
"version": "2.1.35", "version": "2.1.35",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"mime-db": "1.52.0" "mime-db": "1.52.0"
@@ -6499,6 +6623,15 @@
"url": "https://github.com/sponsors/ljharb" "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==",
"license": "MIT",
"dependencies": {
"is-property": "^1.0.2"
}
},
"node_modules/gensync": { "node_modules/gensync": {
"version": "1.0.0-beta.2", "version": "1.0.0-beta.2",
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
@@ -6749,7 +6882,6 @@
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"has-symbols": "^1.0.3" "has-symbols": "^1.0.3"
@@ -7077,6 +7209,12 @@
"integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==",
"license": "MIT" "license": "MIT"
}, },
"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==",
"license": "MIT"
},
"node_modules/is-stream": { "node_modules/is-stream": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
@@ -7912,7 +8050,6 @@
"version": "4.1.0", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"argparse": "^2.0.1" "argparse": "^2.0.1"
@@ -8125,6 +8262,12 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/long": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/long/-/long-5.3.1.tgz",
"integrity": "sha512-ka87Jz3gcx/I7Hal94xaN2tZEOPoUOEVftkQqZx2EeQRN7LGdfLlI3FvZ+7WDplm+vK2Urx9ULrvSowtdCieng==",
"license": "Apache-2.0"
},
"node_modules/lowercase-keys": { "node_modules/lowercase-keys": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz",
@@ -8148,6 +8291,21 @@
"yallist": "^3.0.2" "yallist": "^3.0.2"
} }
}, },
"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==",
"license": "MIT",
"engines": {
"bun": ">=1.0.0",
"deno": ">=1.30.0",
"node": ">=8.0.0"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wellwelwel"
}
},
"node_modules/magic-string": { "node_modules/magic-string": {
"version": "0.30.17", "version": "0.30.17",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz",
@@ -8464,6 +8622,59 @@
"node": "^18.17.0 || >=20.5.0" "node": "^18.17.0 || >=20.5.0"
} }
}, },
"node_modules/mysql2": {
"version": "3.14.0",
"resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.14.0.tgz",
"integrity": "sha512-8eMhmG6gt/hRkU1G+8KlGOdQi2w+CgtNoD1ksXZq9gQfkfDsX4LHaBwTe1SY0Imx//t2iZA03DFnyYKPinxSRw==",
"license": "MIT",
"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==",
"license": "MIT",
"dependencies": {
"safer-buffer": ">= 2.1.2 < 3.0.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"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==",
"license": "MIT",
"dependencies": {
"lru-cache": "^7.14.1"
},
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/named-placeholders/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==",
"license": "ISC",
"engines": {
"node": ">=12"
}
},
"node_modules/natural-compare": { "node_modules/natural-compare": {
"version": "1.4.0", "version": "1.4.0",
"resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
@@ -8733,7 +8944,6 @@
"version": "2.2.0", "version": "2.2.0",
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=6" "node": ">=6"
@@ -8790,7 +9000,6 @@
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
"integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=8" "node": ">=8"
@@ -9008,6 +9217,15 @@
"node": ">=4" "node": ">=4"
} }
}, },
"node_modules/pngjs": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz",
"integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==",
"license": "MIT",
"engines": {
"node": ">=10.13.0"
}
},
"node_modules/prelude-ls": { "node_modules/prelude-ls": {
"version": "1.2.1", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
@@ -9108,6 +9326,12 @@
"node": ">= 0.10" "node": ">= 0.10"
} }
}, },
"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==",
"license": "MIT"
},
"node_modules/punycode": { "node_modules/punycode": {
"version": "2.3.1", "version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
@@ -9135,6 +9359,148 @@
], ],
"license": "MIT" "license": "MIT"
}, },
"node_modules/qrcode": {
"version": "1.5.4",
"resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.4.tgz",
"integrity": "sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==",
"license": "MIT",
"dependencies": {
"dijkstrajs": "^1.0.1",
"pngjs": "^5.0.0",
"yargs": "^15.3.1"
},
"bin": {
"qrcode": "bin/qrcode"
},
"engines": {
"node": ">=10.13.0"
}
},
"node_modules/qrcode/node_modules/ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/qrcode/node_modules/cliui": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz",
"integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==",
"license": "ISC",
"dependencies": {
"string-width": "^4.2.0",
"strip-ansi": "^6.0.0",
"wrap-ansi": "^6.2.0"
}
},
"node_modules/qrcode/node_modules/find-up": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
"integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
"license": "MIT",
"dependencies": {
"locate-path": "^5.0.0",
"path-exists": "^4.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/qrcode/node_modules/locate-path": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
"integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
"license": "MIT",
"dependencies": {
"p-locate": "^4.1.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/qrcode/node_modules/p-limit": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
"integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
"license": "MIT",
"dependencies": {
"p-try": "^2.0.0"
},
"engines": {
"node": ">=6"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/qrcode/node_modules/p-locate": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
"integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
"license": "MIT",
"dependencies": {
"p-limit": "^2.2.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/qrcode/node_modules/strip-ansi": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"license": "MIT",
"dependencies": {
"ansi-regex": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/qrcode/node_modules/y18n": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz",
"integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==",
"license": "ISC"
},
"node_modules/qrcode/node_modules/yargs": {
"version": "15.4.1",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz",
"integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==",
"license": "MIT",
"dependencies": {
"cliui": "^6.0.0",
"decamelize": "^1.2.0",
"find-up": "^4.1.0",
"get-caller-file": "^2.0.1",
"require-directory": "^2.1.1",
"require-main-filename": "^2.0.0",
"set-blocking": "^2.0.0",
"string-width": "^4.2.0",
"which-module": "^2.0.0",
"y18n": "^4.0.0",
"yargs-parser": "^18.1.2"
},
"engines": {
"node": ">=8"
}
},
"node_modules/qrcode/node_modules/yargs-parser": {
"version": "18.1.3",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz",
"integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==",
"license": "ISC",
"dependencies": {
"camelcase": "^5.0.0",
"decamelize": "^1.2.0"
},
"engines": {
"node": ">=6"
}
},
"node_modules/qs": { "node_modules/qs": {
"version": "6.13.0", "version": "6.13.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
@@ -9307,6 +9673,12 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/require-main-filename": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
"integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==",
"license": "ISC"
},
"node_modules/resolve": { "node_modules/resolve": {
"version": "1.22.10", "version": "1.22.10",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
@@ -9637,6 +10009,11 @@
"node": ">= 0.6" "node": ">= 0.6"
} }
}, },
"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/serialize-javascript": { "node_modules/serialize-javascript": {
"version": "6.0.2", "version": "6.0.2",
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz",
@@ -9662,6 +10039,12 @@
"node": ">= 18" "node": ">= 18"
} }
}, },
"node_modules/set-blocking": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
"integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==",
"license": "ISC"
},
"node_modules/setprototypeof": { "node_modules/setprototypeof": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
@@ -9883,6 +10266,15 @@
"node": ">=14" "node": ">=14"
} }
}, },
"node_modules/sqlstring": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz",
"integrity": "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/stack-utils": { "node_modules/stack-utils": {
"version": "2.0.6", "version": "2.0.6",
"resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz",
@@ -10220,6 +10612,15 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/swagger-ui-dist": {
"version": "5.20.1",
"resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.20.1.tgz",
"integrity": "sha512-qBPCis2w8nP4US7SvUxdJD3OwKcqiWeZmjN2VWhq2v+ESZEXOP/7n4DeiOiiZcGYTKMHAHUUrroHaTsjUWTEGw==",
"license": "Apache-2.0",
"dependencies": {
"@scarf/scarf": "=1.4.0"
}
},
"node_modules/symbol-observable": { "node_modules/symbol-observable": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-4.0.0.tgz", "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-4.0.0.tgz",
@@ -11459,6 +11860,12 @@
"node": ">= 8" "node": ">= 8"
} }
}, },
"node_modules/which-module": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz",
"integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==",
"license": "ISC"
},
"node_modules/word-wrap": { "node_modules/word-wrap": {
"version": "1.2.5", "version": "1.2.5",
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
@@ -11473,7 +11880,6 @@
"version": "6.2.0", "version": "6.2.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
"integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"ansi-styles": "^4.0.0", "ansi-styles": "^4.0.0",
@@ -11527,7 +11933,6 @@
"version": "5.0.1", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=8" "node": ">=8"
@@ -11537,7 +11942,6 @@
"version": "6.0.1", "version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"ansi-regex": "^5.0.1" "ansi-regex": "^5.0.1"
+9 -2
View File
@@ -20,13 +20,19 @@
"test:e2e": "jest --config ./test/jest-e2e.json" "test:e2e": "jest --config ./test/jest-e2e.json"
}, },
"dependencies": { "dependencies": {
"@nestjs/common": "^11.0.1", "@nestjs/common": "^11.0.12",
"@nestjs/config": "^4.0.2", "@nestjs/config": "^4.0.2",
"@nestjs/core": "^11.0.1", "@nestjs/core": "^11.0.1",
"@nestjs/mapped-types": "*", "@nestjs/mapped-types": "*",
"@nestjs/platform-express": "^11.0.1", "@nestjs/platform-express": "^11.0.1",
"@nestjs/swagger": "^11.1.0",
"@nestjs/typeorm": "^11.0.0", "@nestjs/typeorm": "^11.0.0",
"axios": "^1.8.4",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.1", "class-validator": "^0.14.1",
"dotenv": "^16.4.7",
"mysql2": "^3.14.0",
"qrcode": "^1.5.4",
"reflect-metadata": "^0.2.2", "reflect-metadata": "^0.2.2",
"rxjs": "^7.8.1", "rxjs": "^7.8.1",
"typeorm": "^0.3.21" "typeorm": "^0.3.21"
@@ -42,6 +48,7 @@
"@types/express": "^5.0.0", "@types/express": "^5.0.0",
"@types/jest": "^29.5.14", "@types/jest": "^29.5.14",
"@types/node": "^22.10.7", "@types/node": "^22.10.7",
"@types/qrcode": "^1.5.5",
"@types/supertest": "^6.0.2", "@types/supertest": "^6.0.2",
"eslint": "^9.18.0", "eslint": "^9.18.0",
"eslint-config-prettier": "^10.0.1", "eslint-config-prettier": "^10.0.1",
@@ -55,7 +62,7 @@
"ts-loader": "^9.5.2", "ts-loader": "^9.5.2",
"ts-node": "^10.9.2", "ts-node": "^10.9.2",
"tsconfig-paths": "^4.2.0", "tsconfig-paths": "^4.2.0",
"typescript": "^5.7.3", "typescript": "^5.8.2",
"typescript-eslint": "^8.20.0" "typescript-eslint": "^8.20.0"
}, },
"jest": { "jest": {
+36 -2
View File
@@ -1,3 +1,4 @@
//import { Module } from '@nestjs/common;
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
import { AppController } from './app.controller'; import { AppController } from './app.controller';
import { AppService } from './app.service'; import { AppService } from './app.service';
@@ -15,14 +16,22 @@ import { CuestionarioRespondidoModule } from './cuestionario_respondido/cuestion
import { RespuestaParticipanteAbiertaModule } from './respuesta_participante_abierta/respuesta_participante_abierta.module'; import { RespuestaParticipanteAbiertaModule } from './respuesta_participante_abierta/respuesta_participante_abierta.module';
import { TypeOrmModule } from '@nestjs/typeorm'; import { TypeOrmModule } from '@nestjs/typeorm';
import { ConfigModule } from '@nestjs/config'; import { ConfigModule } from '@nestjs/config';
import { EventoModule } from './evento/evento.module';
import { TipoUserModule } from './tipo_user/tipo_user.module';
import { ParticipanteModule } from './participante/participante.module';
import { QrModule } from './qr/qr.module';
import { AdministradorModule } from './administrador/administrador.module';
import { AsistenciaModule } from './asistencia/asistencia.module';
import { ParticipanteEventoModule } from './participante_evento/participante_evento.module';
@Module({ @Module({
imports: [ imports: [
ConfigModule.forRoot(), ConfigModule.forRoot(),
TypeOrmModule.forRoot({ TypeOrmModule.forRoot({
type: 'mysql', type: 'mysql',
host: process.env.db_host, host: process.env.db_host, //process.env.db_host
username: process.env.db_username, username: process.env.db_username,
database: process.env.db_database, database: process.env.db_database,
password: process.env.db_password, password: process.env.db_password,
@@ -32,6 +41,23 @@ import { ConfigModule } from '@nestjs/config';
// logging: true, // Habilita los logs para depuración // logging: true, // Habilita los logs para depuración
autoLoadEntities: true, // Carga automáticamente las entidades autoLoadEntities: true, // Carga automáticamente las entidades
//esta es mi base de datos local
/*
type: 'mysql',
host: 'localhost',
port: 3306, //3306
username: 'root',
password: 'admin', //admin
database: 'cidwa', //nestdb
*/
// entities: [__dirname + '/**/*.entity{.ts,.js}'],
// synchronize: true,
//extra
retryAttempts: 10, // Intentos para reconectar
retryDelay: 3000, // Tiempo entre reintentos
}), }),
CuestionarioModule, CuestionarioModule,
TipoCuestionarioModule, TipoCuestionarioModule,
@@ -43,7 +69,15 @@ import { ConfigModule } from '@nestjs/config';
OpcionModule, PreguntaOpcionModule, OpcionModule, PreguntaOpcionModule,
RespuestaParticipanteCerradaModule, RespuestaParticipanteCerradaModule,
CuestionarioRespondidoModule, CuestionarioRespondidoModule,
RespuestaParticipanteAbiertaModule], RespuestaParticipanteAbiertaModule,
EventoModule,
TipoUserModule,
ParticipanteModule,
QrModule,
AdministradorModule,
AsistenciaModule,
ParticipanteEventoModule,
],
controllers: [AppController], controllers: [AppController],
providers: [AppService], providers: [AppService],
@@ -2,32 +2,46 @@ import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/commo
import { CuestionarioService } from './cuestionario.service'; import { CuestionarioService } from './cuestionario.service';
import { CreateCuestionarioDto } from './dto/create-cuestionario.dto'; import { CreateCuestionarioDto } from './dto/create-cuestionario.dto';
import { UpdateCuestionarioDto } from './dto/update-cuestionario.dto'; import { UpdateCuestionarioDto } from './dto/update-cuestionario.dto';
import { CuestionarioApiDocumentation } from './cuestionario.documentation';
import { ApiOperation, ApiParam, ApiResponse } from '@nestjs/swagger';
@Controller('cuestionario') @Controller('cuestionario')
@CuestionarioApiDocumentation.ApiController
export class CuestionarioController { export class CuestionarioController {
constructor(private readonly cuestionarioService: CuestionarioService) {} constructor(private readonly cuestionarioService: CuestionarioService) {}
@Post() @Post()
@CuestionarioApiDocumentation.ApiCreate
create(@Body() createCuestionarioDto: CreateCuestionarioDto) { create(@Body() createCuestionarioDto: CreateCuestionarioDto) {
return this.cuestionarioService.create(createCuestionarioDto); return this.cuestionarioService.create(createCuestionarioDto);
} }
@Get() @Get()
@CuestionarioApiDocumentation.ApiGetAll
findAll() { findAll() {
return this.cuestionarioService.findAll(); return this.cuestionarioService.findAll();
} }
@Get(':id') @Get(':id')
@CuestionarioApiDocumentation.ApiGetOne
findOne(@Param('id') id: string) { findOne(@Param('id') id: string) {
return this.cuestionarioService.findOne(+id); return this.cuestionarioService.findOne(+id);
} }
@Get(':id/formulario')
@CuestionarioApiDocumentation.ApiGetFormulario
findFormulario(@Param('id') id: string) {
return this.cuestionarioService.findCompleto(+id);
}
@Patch(':id') @Patch(':id')
@CuestionarioApiDocumentation.ApiUpdate
update(@Param('id') id: string, @Body() updateCuestionarioDto: UpdateCuestionarioDto) { update(@Param('id') id: string, @Body() updateCuestionarioDto: UpdateCuestionarioDto) {
return this.cuestionarioService.update(+id, updateCuestionarioDto); return this.cuestionarioService.update(+id, updateCuestionarioDto);
} }
@Delete(':id') @Delete(':id')
@CuestionarioApiDocumentation.ApiRemove
remove(@Param('id') id: string) { remove(@Param('id') id: string) {
return this.cuestionarioService.remove(+id); return this.cuestionarioService.remove(+id);
} }
@@ -0,0 +1,210 @@
import { ApiBody, ApiOperation, ApiParam, ApiResponse, ApiTags, getSchemaPath } from '@nestjs/swagger';
import { feriaSexualidad } from '../../utils/crear_formulario_feria';
import { applyDecorators } from '@nestjs/common';
import { FormularioDto } from './dto/formulario.schema';
export class CuestionarioApiDocumentation {
// Decoradores para toda la clase del controlador
static ApiController = ApiTags('Cuestionarios');
// Documentación para obtener el formulario completo
static ApiGetFormulario = applyDecorators(
ApiOperation({
summary: 'Obtener formulario completo',
description: 'Devuelve el cuestionario completo con la misma estructura que se usa para crearlo'
}),
ApiParam({
name: 'id',
description: 'ID del cuestionario',
required: true,
type: 'number',
example: 1
}),
ApiResponse({
status: 200,
description: 'Formulario completo con todas sus secciones, preguntas y opciones',
type: FormularioDto,
content: {
'application/json': {
schema: { $ref: getSchemaPath(FormularioDto) },
example: feriaSexualidad
}
}
}),
ApiResponse({ status: 404, description: 'Cuestionario no encontrado' })
);
// Documentación para crear un cuestionario
static ApiCreate = applyDecorators(
ApiOperation({
summary: 'Crear un nuevo cuestionario',
description: 'Crea un nuevo cuestionario con secciones, preguntas y opciones'
}),
ApiBody({
description: 'Datos del cuestionario a crear',
type: FormularioDto,
examples: {
feriaSexualidad: {
summary: 'Ejemplo de cuestionario para feria de sexualidad',
description: 'Cuestionario con secciones, preguntas y opciones para feria de sexualidad',
value: feriaSexualidad
}
}
}),
ApiResponse({
status: 201,
description: 'Cuestionario creado correctamente',
schema: {
properties: {
success: { type: 'boolean', example: true },
cuestionario: {
type: 'object',
properties: {
id_cuestionario: { type: 'number', example: 1 },
nombre_form: { type: 'string', example: 'Registro para la Feria de la Sexualidad - FES Acatlán' },
descripcion: { type: 'string' },
contador_secciones: { type: 'number', example: 1 },
editable: { type: 'boolean', example: true },
fecha_fin: { type: 'string', format: 'date-time' },
fecha_inicio: { type: 'string', format: 'date-time' },
id_tipo_cuestionario: { type: 'number', example: 1 }
}
}
}
}
}),
ApiResponse({ status: 400, description: 'Datos del cuestionario inválidos' }),
ApiResponse({ status: 500, description: 'Error interno del servidor' })
);
// Documentación para obtener todos los cuestionarios
static ApiGetAll = applyDecorators(
ApiOperation({
summary: 'Obtener todos los cuestionarios',
description: 'Retorna una lista de todos los cuestionarios registrados'
}),
ApiResponse({
status: 200,
description: 'Lista de cuestionarios obtenida correctamente',
schema: {
type: 'array',
items: {
type: 'object',
properties: {
id_cuestionario: { type: 'number', example: 1 },
nombre_form: { type: 'string', example: 'Registro para la Feria de la Sexualidad - FES Acatlán' },
descripcion: { type: 'string' },
contador_secciones: { type: 'number', example: 1 },
editable: { type: 'boolean', example: true },
fecha_fin: { type: 'string', format: 'date-time' },
fecha_inicio: { type: 'string', format: 'date-time' },
id_tipo_cuestionario: { type: 'number', example: 1 }
}
}
}
}),
ApiResponse({ status: 500, description: 'Error interno del servidor' })
);
// Documentación para obtener un cuestionario por ID
static ApiGetOne = applyDecorators(
ApiOperation({
summary: 'Obtener un cuestionario por ID',
description: 'Retorna un cuestionario específico por su ID'
}),
ApiParam({
name: 'id',
description: 'ID del cuestionario',
required: true,
type: 'number',
example: 1
}),
ApiResponse({
status: 200,
description: 'Cuestionario obtenido correctamente',
schema: {
type: 'object',
properties: {
id_cuestionario: { type: 'number', example: 1 },
nombre_form: { type: 'string', example: 'Registro para la Feria de la Sexualidad - FES Acatlán' },
descripcion: { type: 'string' },
contador_secciones: { type: 'number', example: 1 },
editable: { type: 'boolean', example: true },
fecha_fin: { type: 'string', format: 'date-time' },
fecha_inicio: { type: 'string', format: 'date-time' },
id_tipo_cuestionario: { type: 'number', example: 1 }
}
}
}),
ApiResponse({ status: 404, description: 'Cuestionario no encontrado' }),
ApiResponse({ status: 500, description: 'Error interno del servidor' })
);
// Documentación para actualizar un cuestionario
static ApiUpdate = applyDecorators(
ApiOperation({
summary: 'Actualizar un cuestionario',
description: 'Actualiza los datos de un cuestionario existente'
}),
ApiParam({
name: 'id',
description: 'ID del cuestionario a actualizar',
required: true,
type: 'number',
example: 1
}),
ApiBody({
description: 'Datos a actualizar del cuestionario',
schema: {
type: 'object',
properties: {
nombre_form: { type: 'string', example: 'Nombre actualizado del formulario' },
descripcion: { type: 'string', example: 'Descripción actualizada' },
fecha_inicio: { type: 'string', format: 'date-time' },
fecha_fin: { type: 'string', format: 'date-time' },
editable: { type: 'boolean', example: true }
}
}
}),
ApiResponse({
status: 200,
description: 'Cuestionario actualizado correctamente',
schema: {
type: 'object',
properties: {
affected: { type: 'number', example: 1 }
}
}
}),
ApiResponse({ status: 400, description: 'Datos de actualización inválidos' }),
ApiResponse({ status: 404, description: 'Cuestionario no encontrado' }),
ApiResponse({ status: 500, description: 'Error interno del servidor' })
);
// Documentación para eliminar un cuestionario
static ApiRemove = applyDecorators(
ApiOperation({
summary: 'Eliminar un cuestionario',
description: 'Elimina permanentemente un cuestionario por su ID'
}),
ApiParam({
name: 'id',
description: 'ID del cuestionario a eliminar',
required: true,
type: 'number',
example: 1
}),
ApiResponse({
status: 200,
description: 'Cuestionario eliminado correctamente',
schema: {
type: 'object',
properties: {
affected: { type: 'number', example: 1 }
}
}
}),
ApiResponse({ status: 404, description: 'Cuestionario no encontrado' }),
ApiResponse({ status: 500, description: 'Error interno del servidor' })
);
}
+24
View File
@@ -1,9 +1,33 @@
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
import { CuestionarioService } from './cuestionario.service'; import { CuestionarioService } from './cuestionario.service';
import { CuestionarioController } from './cuestionario.controller'; import { CuestionarioController } from './cuestionario.controller';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Cuestionario } from './entities/cuestionario.entity';
import { Seccion } from '../seccion/entities/seccion.entity';
import { CuestionarioSeccion } from '../cuestionario_seccion/entities/cuestionario_seccion.entity';
import { Pregunta } from '../pregunta/entities/pregunta.entity';
import { SeccionPregunta } from '../seccion_pregunta/entities/seccion_pregunta.entity';
import { Opcion } from '../opcion/entities/opcion.entity';
import { PreguntaOpcion } from '../pregunta_opcion/entities/pregunta_opcion.entity';
import { TipoPregunta } from '../tipo_pregunta/entities/tipo_pregunta.entity';
import { TipoCuestionario } from '../tipo_cuestionario/entities/tipo_cuestionario.entity';
@Module({ @Module({
imports: [
TypeOrmModule.forFeature([
Cuestionario,
Seccion,
CuestionarioSeccion,
Pregunta,
SeccionPregunta,
Opcion,
PreguntaOpcion,
TipoPregunta,
TipoCuestionario
])
],
controllers: [CuestionarioController], controllers: [CuestionarioController],
providers: [CuestionarioService], providers: [CuestionarioService],
exports: [TypeOrmModule, CuestionarioService]
}) })
export class CuestionarioModule {} export class CuestionarioModule {}
+293 -8
View File
@@ -1,26 +1,311 @@
import { Injectable } from '@nestjs/common'; import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository, DataSource, In } from 'typeorm';
import { CreateCuestionarioDto } from './dto/create-cuestionario.dto'; import { CreateCuestionarioDto } from './dto/create-cuestionario.dto';
import { UpdateCuestionarioDto } from './dto/update-cuestionario.dto'; import { UpdateCuestionarioDto } from './dto/update-cuestionario.dto';
import { Cuestionario } from './entities/cuestionario.entity';
import { Seccion } from '../seccion/entities/seccion.entity';
import { CuestionarioSeccion } from '../cuestionario_seccion/entities/cuestionario_seccion.entity';
import { Pregunta } from '../pregunta/entities/pregunta.entity';
import { SeccionPregunta } from '../seccion_pregunta/entities/seccion_pregunta.entity';
import { Opcion } from '../opcion/entities/opcion.entity';
import { PreguntaOpcion } from '../pregunta_opcion/entities/pregunta_opcion.entity';
import { TipoPregunta } from '../tipo_pregunta/entities/tipo_pregunta.entity';
import { TipoCuestionario } from '../tipo_cuestionario/entities/tipo_cuestionario.entity';
@Injectable() @Injectable()
export class CuestionarioService { export class CuestionarioService {
create(createCuestionarioDto: CreateCuestionarioDto) { constructor(
return 'This action adds a new cuestionario'; @InjectRepository(Cuestionario)
private cuestionarioRepository: Repository<Cuestionario>,
@InjectRepository(Seccion)
private seccionRepository: Repository<Seccion>,
@InjectRepository(CuestionarioSeccion)
private cuestionarioSeccionRepository: Repository<CuestionarioSeccion>,
@InjectRepository(Pregunta)
private preguntaRepository: Repository<Pregunta>,
@InjectRepository(SeccionPregunta)
private seccionPreguntaRepository: Repository<SeccionPregunta>,
@InjectRepository(Opcion)
private opcionRepository: Repository<Opcion>,
@InjectRepository(PreguntaOpcion)
private preguntaOpcionRepository: Repository<PreguntaOpcion>,
@InjectRepository(TipoPregunta)
private tipoPreguntaRepository: Repository<TipoPregunta>,
private dataSource: DataSource,
) {}
async create(createCuestionarioDto: CreateCuestionarioDto) {
const queryRunner = this.dataSource.createQueryRunner();
await queryRunner.connect();
await queryRunner.startTransaction();
try {
// 1. Crear cuestionario
const cuestionario = new Cuestionario();
cuestionario.nombre_form = createCuestionarioDto.nombre_form;
cuestionario.descripcion = createCuestionarioDto.descripcion;
cuestionario.fecha_inicio = createCuestionarioDto.fecha_inicio ? new Date(createCuestionarioDto.fecha_inicio) : undefined;
cuestionario.fecha_fin = createCuestionarioDto.fecha_fin ? new Date(createCuestionarioDto.fecha_fin) : undefined;
cuestionario.id_tipo_cuestionario = createCuestionarioDto.id_tipo_cuestionario;
cuestionario.contador_secciones = createCuestionarioDto.secciones?.length || 0;
cuestionario.editable = true;
const savedCuestionario = await queryRunner.manager.save(cuestionario);
// 2. Crear secciones y vincularlas al cuestionario
if (createCuestionarioDto.secciones && createCuestionarioDto.secciones.length > 0) {
for (let i = 0; i < createCuestionarioDto.secciones.length; i++) {
const seccionDto = createCuestionarioDto.secciones[i];
// Crear sección
const seccion = new Seccion();
seccion.titulo = seccionDto.titulo;
seccion.descripcion = seccionDto.descripcion;
seccion.contador_pregunta = seccionDto.preguntas?.length || 0;
const savedSeccion = await queryRunner.manager.save(seccion);
// Vincular sección con cuestionario
const cuestionarioSeccion = new CuestionarioSeccion();
cuestionarioSeccion.id_cuestionario = savedCuestionario.id_cuestionario;
cuestionarioSeccion.id_seccion = savedSeccion.id_seccion;
cuestionarioSeccion.posicion = i + 1;
await queryRunner.manager.save(cuestionarioSeccion);
// 3. Crear preguntas y vincularlas a la sección
if (seccionDto.preguntas && seccionDto.preguntas.length > 0) {
for (let j = 0; j < seccionDto.preguntas.length; j++) {
const preguntaDto = seccionDto.preguntas[j];
// Obtener el tipo de pregunta por su nombre
const tipoPregunta = await queryRunner.manager.findOne(TipoPregunta, {
where: { tipo_pregunta: preguntaDto.tipo },
});
if (!tipoPregunta) {
throw new Error(`Tipo de pregunta '${preguntaDto.tipo}' no encontrado`);
}
// Crear pregunta
const pregunta = new Pregunta();
pregunta.pregunta = preguntaDto.titulo;
pregunta.obligatoria = preguntaDto.obligatoria || false;
pregunta.id_tipo_pregunta = tipoPregunta.id_tipo;
pregunta.id_opcion_dependiente = preguntaDto.id_opcion_dependiente || undefined;
pregunta.contador_opcion = preguntaDto.opciones?.length || 0;
const savedPregunta = await queryRunner.manager.save(pregunta);
// Vincular pregunta con sección
const seccionPregunta = new SeccionPregunta();
seccionPregunta.id_seccion = savedSeccion.id_seccion;
seccionPregunta.id_pregunta = savedPregunta.id_pregunta;
seccionPregunta.posicion = j + 1;
await queryRunner.manager.save(seccionPregunta);
// 4. Crear opciones para preguntas de tipo multiple/radio
if (preguntaDto.opciones && preguntaDto.opciones.length > 0) {
for (let k = 0; k < preguntaDto.opciones.length; k++) {
const opcionDto = preguntaDto.opciones[k];
// Crear opción
const opcion = new Opcion();
opcion.opcion = opcionDto.valor;
const savedOpcion = await queryRunner.manager.save(opcion);
// Vincular opción con pregunta
const preguntaOpcion = new PreguntaOpcion();
preguntaOpcion.id_pregunta = savedPregunta.id_pregunta;
// Utilizamos la opción como objeto, no como ID
preguntaOpcion.opcion = savedOpcion;
preguntaOpcion.posicion = k + 1;
await queryRunner.manager.save(preguntaOpcion);
}
}
}
}
}
}
await queryRunner.commitTransaction();
return {
success: true,
cuestionario: savedCuestionario,
};
} catch (error) {
await queryRunner.rollbackTransaction();
throw error;
} finally {
await queryRunner.release();
}
} }
findAll() { findAll() {
return `This action returns all cuestionario`; return this.cuestionarioRepository.find();
} }
findOne(id: number) { async findOne(id: number) {
return `This action returns a #${id} cuestionario`; const cuestionario = await this.cuestionarioRepository.findOne({
where: { id_cuestionario: id }
});
if (!cuestionario) {
throw new NotFoundException(`Cuestionario con ID ${id} no encontrado`);
}
return cuestionario;
}
async findCompleto(id: number) {
// Obtener el cuestionario básico
const cuestionario = await this.cuestionarioRepository.findOne({
where: { id_cuestionario: id }
});
if (!cuestionario) {
throw new NotFoundException(`Cuestionario con ID ${id} no encontrado`);
}
// Para fines de demostración, usar valores hardcodeados
// para el tipo de cuestionario según get_formulario_feria.ts
const tipoCuestionario = {
id_tipo_cuestionario: 1,
tipo_cuestionario: 'Encuesta'
};
// Obtener las relaciones cuestionario-seccion
const cuestionarioSecciones = await this.cuestionarioSeccionRepository.find({
where: { id_cuestionario: id },
order: { posicion: 'ASC' }
});
// Obtener todas las secciones
const seccionIds = cuestionarioSecciones.map(cs => cs.id_seccion);
const secciones = await this.seccionRepository.find({
where: { id_seccion: In(seccionIds) }
});
const seccionesFormateadas = await Promise.all(
cuestionarioSecciones.map(async (cs) => {
const seccion = secciones.find(s => s.id_seccion === cs.id_seccion);
if (!seccion) return null;
// Obtener relaciones sección-pregunta
const seccionPreguntas = await this.seccionPreguntaRepository.find({
where: { id_seccion: seccion.id_seccion },
order: { posicion: 'ASC' }
});
// Obtener preguntas
const preguntaIds = seccionPreguntas.map(sp => sp.id_pregunta);
const preguntas = await this.preguntaRepository.find({
where: { id_pregunta: In(preguntaIds) }
});
// Formatear cada pregunta según el formato requerido
const preguntasFormateadas = await Promise.all(
seccionPreguntas.map(async (sp) => {
const pregunta = preguntas.find(p => p.id_pregunta === sp.id_pregunta);
if (!pregunta) return null;
// Usar un mapeo hardcodeado para tipos de pregunta
const tiposPregunta = {
1: { id_tipo: 1, tipo_pregunta: 'Cerrada' },
2: { id_tipo: 2, tipo_pregunta: 'Abierta' },
3: { id_tipo: 3, tipo_pregunta: 'Multiple' }
};
const tipoPregunta = tiposPregunta[pregunta.id_tipo_pregunta] ||
{ id_tipo: pregunta.id_tipo_pregunta, tipo_pregunta: 'Desconocido' };
// Obtener opciones para preguntas de tipo multiple/radio
const preguntaOpciones = await this.preguntaOpcionRepository.find({
where: { id_pregunta: pregunta.id_pregunta },
relations: ['opcion'],
order: { posicion: 'ASC' }
});
// Formatear las opciones según el formato requerido
const opcionesFormateadas = preguntaOpciones.map(po => ({
id_pregunta_opcion: po.id_pregunta_opcion,
posicion: po.posicion,
id_opcion: po.opcion.id_opcion,
opcion: {
id_opcion: po.opcion.id_opcion,
opcion: po.opcion.opcion
}
}));
// Construir objeto de pregunta según el formato requerido
return {
id_seccion_pregunta: sp.id_seccion_pregunta,
posicion: sp.posicion,
pregunta: {
id_pregunta: pregunta.id_pregunta,
pregunta: pregunta.pregunta,
contador_opcion: pregunta.contador_opcion,
obligatoria: pregunta.obligatoria,
id_tipo_pregunta: pregunta.id_tipo_pregunta,
id_opcion_dependiente: pregunta.id_opcion_dependiente,
tipo_pregunta: {
id_tipo: tipoPregunta.id_tipo,
tipo_pregunta: tipoPregunta.tipo_pregunta
},
opciones: opcionesFormateadas
}
};
})
).then(results => results.filter(p => p !== null));
// Construir objeto de sección según el formato requerido
return {
id_cuestionario_seccion: cs.id_cuestionario_seccion,
posicion: cs.posicion,
seccion: {
id_seccion: seccion.id_seccion,
contador_pregunta: seccion.contador_pregunta,
descripcion: seccion.descripcion,
titulo: seccion.titulo
},
preguntas: preguntasFormateadas
};
})
).then(results => results.filter(s => s !== null));
// Construir objeto final respetando el formato de get_formulario_feria.ts
return {
tipo_cuestionario: {
id_tipo_cuestionario: tipoCuestionario.id_tipo_cuestionario,
tipo_cuestionario: tipoCuestionario.tipo_cuestionario
},
cuestionario: {
id_cuestionario: cuestionario.id_cuestionario,
nombre_form: cuestionario.nombre_form,
contador_secciones: cuestionario.contador_secciones,
descripcion: cuestionario.descripcion,
editable: cuestionario.editable,
fecha_inicio: cuestionario.fecha_inicio ? cuestionario.fecha_inicio.toISOString() : null,
fecha_fin: cuestionario.fecha_fin ? cuestionario.fecha_fin.toISOString() : null,
id_cuestionario_original: cuestionario.id_cuestionario_original,
id_tipo_cuestionario: cuestionario.id_tipo_cuestionario,
secciones: seccionesFormateadas
}
};
} }
update(id: number, updateCuestionarioDto: UpdateCuestionarioDto) { update(id: number, updateCuestionarioDto: UpdateCuestionarioDto) {
return `This action updates a #${id} cuestionario`; return this.cuestionarioRepository.update(id, updateCuestionarioDto);
} }
remove(id: number) { remove(id: number) {
return `This action removes a #${id} cuestionario`; return this.cuestionarioRepository.delete(id);
} }
} }
@@ -1 +1,31 @@
export class CreateCuestionarioDto {} import { CreateSeccionDto } from '../../seccion/dto/create-seccion.dto';
import { IsArray, IsDateString, IsNotEmpty, IsNumber, IsOptional, IsString, ValidateNested } from 'class-validator';
import { Type } from 'class-transformer';
export class CreateCuestionarioDto {
@IsNotEmpty()
@IsString()
nombre_form: string;
@IsOptional()
@IsString()
descripcion?: string;
@IsOptional()
@IsDateString()
fecha_inicio?: Date;
@IsOptional()
@IsDateString()
fecha_fin?: Date;
@IsNotEmpty()
@IsNumber()
id_tipo_cuestionario: number;
@IsOptional()
@IsArray()
@ValidateNested({ each: true })
@Type(() => CreateSeccionDto)
secciones?: CreateSeccionDto[];
}
+94
View File
@@ -0,0 +1,94 @@
import { ApiProperty } from '@nestjs/swagger';
export class OpcionDto {
@ApiProperty({
description: 'Valor de la opción',
example: 'Si'
})
valor: string;
}
export class PreguntaDto {
@ApiProperty({
description: 'Título de la pregunta',
example: '¿Eres parte de la comunidad de la FES Acatlán?'
})
titulo: string;
@ApiProperty({
description: 'Indica si la pregunta es obligatoria',
example: true
})
obligatoria: boolean;
@ApiProperty({
description: 'Tipo de pregunta (Texto, Numero, Multiple, Radio, Abierto, Fecha)',
example: 'Multiple'
})
tipo: string;
@ApiProperty({
description: 'Lista de opciones para preguntas de tipo Multiple o Radio',
type: [OpcionDto],
required: false
})
opciones?: OpcionDto[];
}
export class SeccionDto {
@ApiProperty({
description: 'Título de la sección',
example: 'Información Personal'
})
titulo: string;
@ApiProperty({
description: 'Descripción de la sección',
example: 'Proporciona tus datos personales para poder contactarte.'
})
descripcion?: string;
@ApiProperty({
description: 'Lista de preguntas que conforman la sección',
type: [PreguntaDto]
})
preguntas: PreguntaDto[];
}
export class FormularioDto {
@ApiProperty({
description: 'Nombre del formulario',
example: 'Registro para la Feria de la Sexualidad - FES Acatlán'
})
nombre_form: string;
@ApiProperty({
description: 'Descripción del formulario',
example: 'La Feria de la Sexualidad es un espacio seguro e informativo...'
})
descripcion?: string;
@ApiProperty({
description: 'Fecha de inicio del formulario',
example: '2025-03-07T00:00:01'
})
fecha_inicio?: string;
@ApiProperty({
description: 'Fecha de fin del formulario',
example: '2025-03-18T23:59:59'
})
fecha_fin?: string;
@ApiProperty({
description: 'ID del tipo de cuestionario',
example: 1
})
id_tipo_cuestionario: number;
@ApiProperty({
description: 'Secciones que conforman el formulario',
type: [SeccionDto]
})
secciones: SeccionDto[];
}
@@ -16,29 +16,27 @@ export class Cuestionario {
contador_secciones: number; contador_secciones: number;
@Column({ type: 'text', nullable: true }) @Column({ type: 'text', nullable: true })
descripcion: string; descripcion?: string;
@Column({ type: 'boolean', default: true }) @Column({ type: 'boolean', default: true })
editable: boolean; editable: boolean;
@Column({ type: 'datetime', nullable: true }) @Column({ type: 'datetime', nullable: true })
fecha_fin: Date; fecha_fin?: Date;
@Column({ type: 'datetime', nullable: true }) @Column({ type: 'datetime', nullable: true })
fecha_inicio: Date; fecha_inicio?: Date;
@OneToMany(() => CuestionarioSeccion,(cuestionario_seccion) => cuestionario_seccion.id_cuestionario) @OneToMany(() => CuestionarioSeccion,(cuestionario_seccion) => cuestionario_seccion.id_cuestionario)
cuestionarioSeccion:CuestionarioSeccion[] cuestionarioSeccion:CuestionarioSeccion[]
@OneToMany(()=> Cuestionario, (cuestionario) => cuestionario.id_cuestionario) @Column({ type: 'int', nullable: true })
@Column({ type: 'int' })
id_cuestionario_original: number; id_cuestionario_original: number;
@OneToMany(()=> TipoCuestionario, (tipo_cuestionario) => tipo_cuestionario.id_tipo_cuestionario )
@Column({ type: 'int' }) @Column({ type: 'int' })
id_tipo_cuestionario: number; id_tipo_cuestionario: number;
@ManyToOne(()=> Cuestionario, (cuestionario) => cuestionario.id_cuestionario) @ManyToOne(()=> Cuestionario)
Cuestionario: Cuestionario[]; cuestionarioOriginal: Cuestionario;
} }
@@ -1,9 +1,19 @@
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
import { CuestionarioRespondidoService } from './cuestionario_respondido.service'; import { CuestionarioRespondidoService } from './cuestionario_respondido.service';
import { CuestionarioRespondidoController } from './cuestionario_respondido.controller'; import { CuestionarioRespondidoController } from './cuestionario_respondido.controller';
import { TypeOrmModule } from '@nestjs/typeorm';
import { CuestionarioRespondido } from './entities/cuestionario_respondido.entity';
import { CuestionarioModule } from '../cuestionario/cuestionario.module';
import { ParticipanteModule } from '../participante/participante.module';
@Module({ @Module({
imports: [
TypeOrmModule.forFeature([CuestionarioRespondido]),
CuestionarioModule,
ParticipanteModule
],
controllers: [CuestionarioRespondidoController], controllers: [CuestionarioRespondidoController],
providers: [CuestionarioRespondidoService], providers: [CuestionarioRespondidoService],
exports: [TypeOrmModule]
}) })
export class CuestionarioRespondidoModule {} export class CuestionarioRespondidoModule {}
@@ -3,6 +3,7 @@ import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, OneToMany, JoinColum
import { Cuestionario } from 'src/cuestionario/entities/cuestionario.entity'; import { Cuestionario } from 'src/cuestionario/entities/cuestionario.entity';
import { RespuestaParticipanteCerrada } from 'src/respuesta_participante_cerrada/entities/respuesta_participante_cerrada.entity'; import { RespuestaParticipanteCerrada } from 'src/respuesta_participante_cerrada/entities/respuesta_participante_cerrada.entity';
import { RespuestaParticipanteAbierta } from 'src/respuesta_participante_abierta/entities/respuesta_participante_abierta.entity'; import { RespuestaParticipanteAbierta } from 'src/respuesta_participante_abierta/entities/respuesta_participante_abierta.entity';
import { Participante } from 'src/participante/participante.entity';
@Entity('cuestionario_respondido') @Entity('cuestionario_respondido')
export class CuestionarioRespondido { export class CuestionarioRespondido {
@@ -15,18 +16,18 @@ export class CuestionarioRespondido {
// Relación con Participante // Relación con Participante
@ManyToOne(() => Participante, participante => participante.cuestionariosRespondidos) @ManyToOne(() => Participante, participante => participante.cuestionariosRespondidos)
@JoinColumn({ name: 'id_participante' }) @JoinColumn({ name: 'id_participante' })
id_participante: Participante; participante: Participante;
// Relación con Cuestionario // Relación con Cuestionario
@ManyToOne(() => Cuestionario, cuestionario => cuestionario.id_cuestionario) @ManyToOne(() => Cuestionario)
@JoinColumn({ name: 'id_cuestionario' }) @JoinColumn({ name: 'id_cuestionario' })
id_cuestionario: Cuestionario; cuestionario: Cuestionario;
// Relación con RespuestasCerradas // Relación con RespuestasCerradas
@OneToMany(() => RespuestaParticipanteCerrada, respuestaCerrada => respuestaCerrada.id_cuestionario_respondido) @OneToMany(() => RespuestaParticipanteCerrada, respuestaCerrada => respuestaCerrada.cuestionarioRespondido)
respuestasCerradas: RespuestaParticipanteCerrada[]; respuestasCerradas: RespuestaParticipanteCerrada[];
// Relación con RespuestasAbiertas // Relación con RespuestasAbiertas
@OneToMany(() => RespuestaParticipanteAbierta, respuestaAbierta => respuestaAbierta.id_cuestionario_respondido) @OneToMany(() => RespuestaParticipanteAbierta, respuestaAbierta => respuestaAbierta.cuestionarioRespondido)
respuestasAbiertas: RespuestaParticipanteAbierta[]; respuestasAbiertas: RespuestaParticipanteAbierta[];
} }
@@ -1,9 +1,19 @@
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
import { CuestionarioSeccionService } from './cuestionario_seccion.service'; import { CuestionarioSeccionService } from './cuestionario_seccion.service';
import { CuestionarioSeccionController } from './cuestionario_seccion.controller'; import { CuestionarioSeccionController } from './cuestionario_seccion.controller';
import { TypeOrmModule } from '@nestjs/typeorm';
import { CuestionarioSeccion } from './entities/cuestionario_seccion.entity';
import { CuestionarioModule } from '../cuestionario/cuestionario.module';
import { SeccionModule } from '../seccion/seccion.module';
@Module({ @Module({
imports: [
TypeOrmModule.forFeature([CuestionarioSeccion]),
CuestionarioModule,
SeccionModule
],
controllers: [CuestionarioSeccionController], controllers: [CuestionarioSeccionController],
providers: [CuestionarioSeccionService], providers: [CuestionarioSeccionService],
exports: [TypeOrmModule]
}) })
export class CuestionarioSeccionModule {} export class CuestionarioSeccionModule {}
@@ -11,11 +11,15 @@ export class CuestionarioSeccion {
posicion: number; posicion: number;
@ManyToOne(() => Cuestionario, (cuestionario) => cuestionario.id_cuestionario) @ManyToOne(() => Cuestionario, (cuestionario) => cuestionario.cuestionarioSeccion)
cuestionario: Cuestionario;
@Column({ type: 'int' }) @Column({ type: 'int' })
id_cuestionario: number; id_cuestionario: number;
@ManyToOne(() => Seccion, (seccion) => seccion.id_seccion) @ManyToOne(() => Seccion, (seccion) => seccion.seccion)
seccion: Seccion;
@Column({type:'int'}) @Column({type:'int'})
id_seccion:number; id_seccion:number;
} }
+2 -2
View File
@@ -6,7 +6,7 @@ import { INestApplication } from '@nestjs/common';
@Module({}) @Module({})
export class DocsModule { export class DocsModule {
static setupSwagger(app: INestApplication) { static setupSwagger(app: INestApplication) {
const document = SwaggerModule.createDocument(app, swaggerConfig); const document = SwaggerModule.createDocument(app, swaggerConfig);
SwaggerModule.setup('api-docs', app, document); SwaggerModule.setup('api-docs', app, document);
} }
} }
+23 -3
View File
@@ -1,11 +1,31 @@
import { DocumentBuilder } from '@nestjs/swagger'; import { DocumentBuilder } from '@nestjs/swagger';
export const swaggerConfig = new DocumentBuilder() export const swaggerConfig = new DocumentBuilder()
.setTitle('Sistema de Registro de Usuarios y Eventos') .setTitle('Sistema de Formularios y Eventos CIDWA')
.setDescription('API para gestionar usuarios, eventos, asistencia y códigos QR') .setDescription(`
API para la gestión de formularios, cuestionarios, eventos y asistencias.
## Funcionalidades principales
- **Cuestionarios**: Creación y gestión de formularios con secciones y preguntas
- **Eventos**: Registro y control de eventos
- **Participantes**: Gestión de usuarios y participantes
- **Asistencia**: Control de asistencia a eventos
- **QR**: Generación y validación de códigos QR
## Ejemplos de uso
La API permite crear cuestionarios complejos con secciones y diferentes tipos de preguntas.
Consulte la documentación de cada endpoint para ver ejemplos específicos.
`)
.setVersion('1.0') .setVersion('1.0')
.addTag('Cuestionarios')
.addTag('Secciones')
.addTag('Preguntas')
.addTag('Opciones')
.addTag('Usuarios') .addTag('Usuarios')
.addTag('Eventos') .addTag('Eventos')
.addTag('Asistencia') .addTag('Asistencia')
.addTag('QR') .addTag('QR')
.addBearerAuth()
.build(); .build();
+8 -5
View File
@@ -1,5 +1,6 @@
import { Participante } from "src/participante/participante.entity";
import { ParticipanteEvento } from "src/participante_evento/participante_evento.entity"; import { ParticipanteEvento } from "src/participante_evento/participante_evento.entity";
import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from "typeorm"; import { Column, Entity, ManyToMany, OneToMany, PrimaryGeneratedColumn } from "typeorm";
@Entity() @Entity()
export class Evento { export class Evento {
@@ -26,13 +27,15 @@ export class Evento {
@JoinColumn({ name: "id_administrador" }) @JoinColumn({ name: "id_administrador" })
administrador: Administrador; administrador: Administrador;
@OneToMany(() => ParticipanteEvento, (pe) => pe.evento)
participantes: ParticipanteEvento[];
@OneToMany(() => Asistencia, (asistencia) => asistencia.evento) @OneToMany(() => Asistencia, (asistencia) => asistencia.evento)
asistencias: Asistencia[]; asistencias: Asistencia[];
*/ */
@OneToMany(() => ParticipanteEvento, (participanteEvento) => participanteEvento.evento)
participantes: ParticipanteEvento[];
@OneToMany(() => ParticipanteEvento, participanteEvento => participanteEvento.evento)
participanteEventos: ParticipanteEvento[];
} }
+2 -1
View File
@@ -7,6 +7,7 @@ import { Evento } from './evento.entity';
@Module({ @Module({
imports: [TypeOrmModule.forFeature([Evento])], imports: [TypeOrmModule.forFeature([Evento])],
controllers: [EventoController], controllers: [EventoController],
providers: [EventoService] providers: [EventoService],
exports: [EventoService]
}) })
export class EventoModule {} export class EventoModule {}
+51 -1
View File
@@ -1,8 +1,58 @@
import { NestFactory } from '@nestjs/core'; import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module'; import { AppModule } from './app.module';
import 'dotenv/config';
import { DocsModule } from './docs/docs.module';
import { DataSource } from 'typeorm';
import { TipoPregunta } from './tipo_pregunta/entities/tipo_pregunta.entity';
import { ValidationPipe } from '@nestjs/common';
async function bootstrap() { async function bootstrap() {
const app = await NestFactory.create(AppModule); const app = await NestFactory.create(AppModule);
await app.listen(process.env.PORT ?? 3000);
// Configuración de validación global
app.useGlobalPipes(
new ValidationPipe({
whitelist: true, // Elimina propiedades no decoradas
forbidNonWhitelisted: true, // Arroja error si hay propiedades no decoradas
transform: true, // Transforma los datos recibidos al tipo definido en el DTO
})
);
// CORS configuration
app.enableCors({
origin: '*',
methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'],
allowedHeaders: ['Content-Type', 'Authorization'],
});
// Swagger setup
DocsModule.setupSwagger(app);
// Seed TipoPregunta data
const dataSource = app.get(DataSource);
const tiposPreguntaRepository = dataSource.getRepository(TipoPregunta);
// Check if data exists
const count = await tiposPreguntaRepository.count();
if (count === 0) {
// Create default tipos de pregunta
const tiposPregunta = [
{ tipo_pregunta: 'Texto' },
{ tipo_pregunta: 'Numero' },
{ tipo_pregunta: 'Radio' },
{ tipo_pregunta: 'Multiple' },
{ tipo_pregunta: 'Abierto' },
{ tipo_pregunta: 'Fecha' },
];
await tiposPreguntaRepository.save(tiposPregunta);
console.log('Tipos de pregunta creados');
}
// Start the server
await app.listen(process.env.PORT ?? 4200);
console.log('API en ejecución en http://localhost:4200');
console.log('Swagger disponible en http://localhost:4200/api-docs');
} }
bootstrap(); bootstrap();
-2
View File
@@ -1,8 +1,6 @@
import { IsString, MaxLength } from "class-validator"; import { IsString, MaxLength } from "class-validator";
export class CreateOpcionDto { export class CreateOpcionDto {
@IsString() @IsString()
@MaxLength(50) @MaxLength(50)
opcion: string; opcion: string;
+2 -2
View File
@@ -9,8 +9,8 @@ export class Opcion {
@Column({ type: String, nullable: false, length: 200, default: '' }) @Column({ type: String, nullable: false, length: 200, default: '' })
opcion: string; opcion: string;
@OneToMany(() => PreguntaOpcion, (preguntaOpcion) => preguntaOpcion.id_opcion) @OneToMany(() => PreguntaOpcion, (preguntaOpcion) => preguntaOpcion.opcion)
Preguntas: PreguntaOpcion[]; preguntasOpciones: PreguntaOpcion[];
} }
+7
View File
@@ -2,32 +2,39 @@ import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/commo
import { OpcionService } from './opcion.service'; import { OpcionService } from './opcion.service';
import { CreateOpcionDto } from './dto/create-opcion.dto'; import { CreateOpcionDto } from './dto/create-opcion.dto';
import { UpdateOpcionDto } from './dto/update-opcion.dto'; import { UpdateOpcionDto } from './dto/update-opcion.dto';
import { OpcionApiDocumentation } from './opcion.documentation';
@Controller('opcion') @Controller('opcion')
@OpcionApiDocumentation.ApiController
export class OpcionController { export class OpcionController {
constructor(private readonly opcionService: OpcionService) {} constructor(private readonly opcionService: OpcionService) {}
@Post() @Post()
@OpcionApiDocumentation.ApiCreate
create(@Body() createOpcionDto: CreateOpcionDto) { create(@Body() createOpcionDto: CreateOpcionDto) {
return this.opcionService.create(createOpcionDto); return this.opcionService.create(createOpcionDto);
} }
@Get() @Get()
@OpcionApiDocumentation.ApiGetAll
findAll() { findAll() {
return this.opcionService.findAll(); return this.opcionService.findAll();
} }
@Get(':id') @Get(':id')
@OpcionApiDocumentation.ApiGetOne
findOne(@Param('id') id: string) { findOne(@Param('id') id: string) {
return this.opcionService.findOne(+id); return this.opcionService.findOne(+id);
} }
@Patch(':id') @Patch(':id')
@OpcionApiDocumentation.ApiUpdate
update(@Param('id') id: string, @Body() updateOpcionDto: UpdateOpcionDto) { update(@Param('id') id: string, @Body() updateOpcionDto: UpdateOpcionDto) {
return this.opcionService.update(+id, updateOpcionDto); return this.opcionService.update(+id, updateOpcionDto);
} }
@Delete(':id') @Delete(':id')
@OpcionApiDocumentation.ApiRemove
remove(@Param('id') id: string) { remove(@Param('id') id: string) {
return this.opcionService.remove(+id); return this.opcionService.remove(+id);
} }
+153
View File
@@ -0,0 +1,153 @@
import { ApiBody, ApiOperation, ApiParam, ApiResponse, ApiTags } from '@nestjs/swagger';
import { applyDecorators } from '@nestjs/common';
export class OpcionApiDocumentation {
// Decoradores para toda la clase del controlador
static ApiController = ApiTags('Opciones');
// Documentación para crear una opción
static ApiCreate = applyDecorators(
ApiOperation({
summary: 'Crear una nueva opción',
description: 'Crea una nueva opción para una pregunta'
}),
ApiBody({
description: 'Datos de la opción a crear',
schema: {
type: 'object',
required: ['opcion'],
properties: {
opcion: { type: 'string', example: 'Si' }
}
}
}),
ApiResponse({
status: 201,
description: 'Opción creada correctamente',
schema: {
type: 'object',
properties: {
id_opcion: { type: 'number', example: 1 },
opcion: { type: 'string', example: 'Si' }
}
}
}),
ApiResponse({ status: 400, description: 'Datos de la opción inválidos' }),
ApiResponse({ status: 500, description: 'Error interno del servidor' })
);
// Documentación para obtener todas las opciones
static ApiGetAll = applyDecorators(
ApiOperation({
summary: 'Obtener todas las opciones',
description: 'Retorna una lista de todas las opciones registradas'
}),
ApiResponse({
status: 200,
description: 'Lista de opciones obtenida correctamente',
schema: {
type: 'array',
items: {
type: 'object',
properties: {
id_opcion: { type: 'number', example: 1 },
opcion: { type: 'string', example: 'Si' }
}
}
}
}),
ApiResponse({ status: 500, description: 'Error interno del servidor' })
);
// Documentación para obtener una opción por ID
static ApiGetOne = applyDecorators(
ApiOperation({
summary: 'Obtener una opción por ID',
description: 'Retorna una opción específica por su ID'
}),
ApiParam({
name: 'id',
description: 'ID de la opción',
required: true,
type: 'number',
example: 1
}),
ApiResponse({
status: 200,
description: 'Opción obtenida correctamente',
schema: {
type: 'object',
properties: {
id_opcion: { type: 'number', example: 1 },
opcion: { type: 'string', example: 'Si' }
}
}
}),
ApiResponse({ status: 404, description: 'Opción no encontrada' }),
ApiResponse({ status: 500, description: 'Error interno del servidor' })
);
// Documentación para actualizar una opción
static ApiUpdate = applyDecorators(
ApiOperation({
summary: 'Actualizar una opción',
description: 'Actualiza los datos de una opción existente'
}),
ApiParam({
name: 'id',
description: 'ID de la opción a actualizar',
required: true,
type: 'number',
example: 1
}),
ApiBody({
description: 'Datos a actualizar de la opción',
schema: {
type: 'object',
properties: {
opcion: { type: 'string', example: 'Opción actualizada' }
}
}
}),
ApiResponse({
status: 200,
description: 'Opción actualizada correctamente',
schema: {
type: 'object',
properties: {
affected: { type: 'number', example: 1 }
}
}
}),
ApiResponse({ status: 400, description: 'Datos de actualización inválidos' }),
ApiResponse({ status: 404, description: 'Opción no encontrada' }),
ApiResponse({ status: 500, description: 'Error interno del servidor' })
);
// Documentación para eliminar una opción
static ApiRemove = applyDecorators(
ApiOperation({
summary: 'Eliminar una opción',
description: 'Elimina permanentemente una opción por su ID'
}),
ApiParam({
name: 'id',
description: 'ID de la opción a eliminar',
required: true,
type: 'number',
example: 1
}),
ApiResponse({
status: 200,
description: 'Opción eliminada correctamente',
schema: {
type: 'object',
properties: {
affected: { type: 'number', example: 1 }
}
}
}),
ApiResponse({ status: 404, description: 'Opción no encontrada' }),
ApiResponse({ status: 500, description: 'Error interno del servidor' })
);
}
+4
View File
@@ -1,9 +1,13 @@
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
import { OpcionService } from './opcion.service'; import { OpcionService } from './opcion.service';
import { OpcionController } from './opcion.controller'; import { OpcionController } from './opcion.controller';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Opcion } from './entities/opcion.entity';
@Module({ @Module({
imports: [TypeOrmModule.forFeature([Opcion])], // Importa el repositorio de Opcion
controllers: [OpcionController], controllers: [OpcionController],
providers: [OpcionService], providers: [OpcionService],
exports: [OpcionService, TypeOrmModule]
}) })
export class OpcionModule {} export class OpcionModule {}
+6 -2
View File
@@ -12,12 +12,16 @@ export class OpcionService {
private repository: Repository<Opcion>, private repository: Repository<Opcion>,
){} ){}
create(createOpcionDto: CreateOpcionDto) {
return this.repository.save(createOpcionDto);
}
/*
create():Promise<Opcion> { create():Promise<Opcion> {
return this.repository.save(this.repository.create()); return this.repository.save(this.repository.create());
} }
*/
findAll() { findAll() {
return `This action returns all opcion`; return `This action returns all opcion`;
} }
+19 -4
View File
@@ -1,6 +1,8 @@
import { CuestionarioRespondido } from "src/cuestionario_respondido/entities/cuestionario_respondido.entity";
import { Evento } from "src/evento/evento.entity";
import { ParticipanteEvento } from "src/participante_evento/participante_evento.entity"; import { ParticipanteEvento } from "src/participante_evento/participante_evento.entity";
import { TipoUser } from "src/tipo_user/tipo_user.entity"; import { TipoUser } from "src/tipo_user/tipo_user.entity";
import { Column, Entity, JoinColumn, ManyToOne, OneToMany, PrimaryGeneratedColumn } from "typeorm"; import { Column, Entity, JoinColumn, JoinTable, ManyToMany, ManyToOne, OneToMany, PrimaryGeneratedColumn } from "typeorm";
@Entity() @Entity()
export class Participante { export class Participante {
@@ -16,19 +18,32 @@ export class Participante {
//Relacion con tipo usuario //Relacion con tipo usuario
@ManyToOne(() => TipoUser, tipoUser => tipoUser.participante) @ManyToOne(() => TipoUser, tipoUser => tipoUser.participante)
@JoinColumn({ name: 'participante_id' }) //nombre de la relacion @JoinColumn({ name: 'participante_id' }) //nombre de la relacion
tipo_user: TipoUser[] tipo_user: TipoUser
/* /*
@ManyToOne(() => Administrador, (admin) => admin.eventos) @ManyToOne(() => Administrador, (admin) => admin.eventos)
@JoinColumn({ name: "id_administrador" }) @JoinColumn({ name: "id_administrador" })
administrador: Administrador; administrador: Administrador;
@OneToMany(() => ParticipanteEvento, (pe) => pe.evento) @ManyToMany(() => Evento, evento => evento.participantes)
participantes: ParticipanteEvento[]; @JoinTable({
name: "participante_evento",
joinColumn: { name: "id_participante", referencedColumnName: "id_participante" },
inverseJoinColumn: { name: "id_evento", referencedColumnName: "id_evento" }
})
eventos: Evento[];
@OneToMany(() => Asistencia, (asistencia) => asistencia.evento) @OneToMany(() => Asistencia, (asistencia) => asistencia.evento)
asistencias: Asistencia[]; asistencias: Asistencia[];
*/ */
@OneToMany(() => CuestionarioRespondido, cuestionario => cuestionario.participante)
cuestionariosRespondidos: CuestionarioRespondido[];
@OneToMany(() => ParticipanteEvento, participanteEvento => participanteEvento.participante)
participanteEventos: ParticipanteEvento[];
} }
+1 -1
View File
@@ -8,6 +8,6 @@ import { Participante } from './participante.entity';
imports: [TypeOrmModule.forFeature([Participante])], imports: [TypeOrmModule.forFeature([Participante])],
controllers: [ParticipanteController], controllers: [ParticipanteController],
providers: [ParticipanteService], providers: [ParticipanteService],
exports: [ParticipanteService], exports: [ParticipanteService, TypeOrmModule],
}) })
export class ParticipanteModule {} export class ParticipanteModule {}
+3 -3
View File
@@ -3,7 +3,7 @@ import { InjectRepository } from '@nestjs/typeorm';
import { Participante } from './participante.entity'; import { Participante } from './participante.entity';
import { Repository } from 'typeorm'; import { Repository } from 'typeorm';
import { CreateParticipanteDto } from './dto/create-participante.dto'; import { CreateParticipanteDto } from './dto/create-participante.dto';
import { UpdateAdminDto } from 'src/admin/dto/update.admin.dto'; //import { UpdateAdminDto } from 'src/admin/dto/update.admin.dto';
import { UpdateParticipanteDto } from './dto/update.participante.dto'; import { UpdateParticipanteDto } from './dto/update.participante.dto';
@Injectable() @Injectable()
@@ -28,7 +28,7 @@ export class ParticipanteService {
getParticipantes() { getParticipantes() {
return this.participanteRepository.find({ return this.participanteRepository.find({
relations: ['tipo_user'] relations: ['tipo_user', 'participanteEventos']
}) })
} }
@@ -37,7 +37,7 @@ export class ParticipanteService {
where: { where: {
id_participante id_participante
}, },
relations: ['tipo_user'] relations: ['tipo_user', 'participanteEventos']
}) })
if (!participanteFound) if (!participanteFound)
@@ -1,4 +1,5 @@
import { Evento } from "src/evento/evento.entity"; import { Evento } from "src/evento/evento.entity";
import { Participante } from "src/participante/participante.entity";
import { Column, Entity, JoinColumn, ManyToOne, PrimaryColumn, PrimaryGeneratedColumn } from "typeorm"; import { Column, Entity, JoinColumn, ManyToOne, PrimaryColumn, PrimaryGeneratedColumn } from "typeorm";
@Entity() @Entity()
@@ -17,17 +18,21 @@ export class ParticipanteEvento {
/* /*
@OneToOne(() => Qr, (qr) => qr.participanteEvento) @OneToOne(() => Qr, (qr) => qr.participanteEvento)
qr: Qr; qr: Qr;
@ManyToOne(() => Participante, (participante) => participante.eventos) @OneToMany(() => ParticipanteEvento, (pe) => pe.evento)
@JoinColumn({ name: "id_participante" }) participantes: ParticipanteEvento[];
participante: Participante;
@ManyToOne(() => Evento, (evento) => evento.participantes) @ManyToOne(() => Evento, (evento) => evento.participantes)
@JoinColumn({ name: "id_evento" }) @JoinColumn({ name: "id_evento" })
evento: Evento; evento: Evento;
*/ */
@ManyToOne(() => Evento, (evento) => evento.participantes) @ManyToOne(() => Participante, participante => participante.participanteEventos)
@JoinColumn({ name: "id_participante" })
participante: Participante;
@ManyToOne(() => Evento, evento => evento.participanteEventos)
@JoinColumn({ name: "id_evento" }) @JoinColumn({ name: "id_evento" })
evento: Evento; evento: Evento;
} }
@@ -4,10 +4,12 @@ import { ParticipanteEventoController } from './participante_evento.controller';
import { TypeOrmModule } from '@nestjs/typeorm'; import { TypeOrmModule } from '@nestjs/typeorm';
import { ParticipanteEvento } from './participante_evento.entity'; import { ParticipanteEvento } from './participante_evento.entity';
import { Evento } from 'src/evento/evento.entity'; import { Evento } from 'src/evento/evento.entity';
import { Participante } from 'src/participante/participante.entity';
@Module({ @Module({
imports: [TypeOrmModule.forFeature([ParticipanteEvento]), Evento], imports: [TypeOrmModule.forFeature([ParticipanteEvento, Evento, Participante])],
controllers: [ParticipanteEventoController], controllers: [ParticipanteEventoController],
providers: [ParticipanteEventoService] providers: [ParticipanteEventoService],
exports: [ParticipanteEventoService],
}) })
export class ParticipanteEventoModule {} export class ParticipanteEventoModule {}
@@ -4,12 +4,16 @@ import { ParticipanteEvento } from './participante_evento.entity';
import { Repository } from 'typeorm'; import { Repository } from 'typeorm';
import { CreateParticipanteEventoDto } from './dto/create-participante_evento.dto'; import { CreateParticipanteEventoDto } from './dto/create-participante_evento.dto';
import { UpdateParticipanteEventoDto } from './dto/update.participante_evento.dto'; import { UpdateParticipanteEventoDto } from './dto/update.participante_evento.dto';
import { Evento } from 'src/evento/evento.entity';
import { Participante } from 'src/participante/participante.entity';
@Injectable() @Injectable()
export class ParticipanteEventoService { export class ParticipanteEventoService {
constructor( constructor(
@InjectRepository(ParticipanteEvento) private participanteEventoRepository: Repository<ParticipanteEvento> @InjectRepository(ParticipanteEvento) private participanteEventoRepository: Repository<ParticipanteEvento>,
@InjectRepository(Evento) private eventoRepository: Repository<Evento>,
@InjectRepository(Participante) private participanteRepository: Repository<Participante>
) {} ) {}
async createParticipanteEvento(participanteEvento: CreateParticipanteEventoDto) { async createParticipanteEvento(participanteEvento: CreateParticipanteEventoDto) {
@@ -30,7 +34,7 @@ export class ParticipanteEventoService {
getParticipantesEvento() { getParticipantesEvento() {
return this.participanteEventoRepository.find({ return this.participanteEventoRepository.find({
relations: ['evento'] relations: ['evento', 'participante']
}) })
} }
@@ -39,7 +43,7 @@ export class ParticipanteEventoService {
where: { where: {
id_participante id_participante
}, },
relations: ['evento'] relations: ['evento', 'participante']
}) })
if (!participante_eventoFound) { if (!participante_eventoFound) {
+36 -1
View File
@@ -1 +1,36 @@
export class CreatePreguntaDto {} import { Type } from 'class-transformer';
import { IsArray, IsBoolean, IsNotEmpty, IsNumber, IsOptional, IsString, ValidateNested } from 'class-validator';
export class OpcionDto {
@IsNotEmpty()
@IsString()
valor: string;
}
export class CreatePreguntaDto {
@IsNotEmpty()
@IsString()
titulo: string;
@IsOptional()
@IsBoolean()
obligatoria?: boolean;
@IsNotEmpty()
@IsString()
tipo: string;
@IsOptional()
@IsNumber()
contador_opcion?: number;
@IsOptional()
@IsNumber()
id_opcion_dependiente?: number;
@IsOptional()
@IsArray()
@ValidateNested({ each: true })
@Type(() => OpcionDto)
opciones?: OpcionDto[];
}
+8 -5
View File
@@ -20,14 +20,17 @@ export class Pregunta {
@ManyToOne(() => TipoPregunta, tipo => tipo.preguntas) @ManyToOne(() => TipoPregunta, tipo => tipo.preguntas)
@JoinColumn({ name: 'id_tipo_pregunta' }) @JoinColumn({ name: 'id_tipo_pregunta' })
id_tipo_pregunta: TipoPregunta; tipoPregunta: TipoPregunta;
@Column()
id_tipo_pregunta: number;
@Column({ nullable: true }) @Column({ type: 'int', nullable: true })
id_opcion_dependiente: number; id_opcion_dependiente?: number;
@OneToMany(() => SeccionPregunta, seccionPregunta => seccionPregunta.id_pregunta) @OneToMany(() => SeccionPregunta, seccionPregunta => seccionPregunta.pregunta)
seccionPreguntas: SeccionPregunta[]; seccionPreguntas: SeccionPregunta[];
@OneToMany(() => PreguntaOpcion, preguntaOpcion => preguntaOpcion.id_pregunta) @OneToMany(() => PreguntaOpcion, preguntaOpcion => preguntaOpcion.pregunta)
opciones: PreguntaOpcion[]; opciones: PreguntaOpcion[];
} }
+7
View File
@@ -2,32 +2,39 @@ import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/commo
import { PreguntaService } from './pregunta.service'; import { PreguntaService } from './pregunta.service';
import { CreatePreguntaDto } from './dto/create-pregunta.dto'; import { CreatePreguntaDto } from './dto/create-pregunta.dto';
import { UpdatePreguntaDto } from './dto/update-pregunta.dto'; import { UpdatePreguntaDto } from './dto/update-pregunta.dto';
import { PreguntaApiDocumentation } from './pregunta.documentation';
@Controller('pregunta') @Controller('pregunta')
@PreguntaApiDocumentation.ApiController
export class PreguntaController { export class PreguntaController {
constructor(private readonly preguntaService: PreguntaService) {} constructor(private readonly preguntaService: PreguntaService) {}
@Post() @Post()
@PreguntaApiDocumentation.ApiCreate
create(@Body() createPreguntaDto: CreatePreguntaDto) { create(@Body() createPreguntaDto: CreatePreguntaDto) {
return this.preguntaService.create(createPreguntaDto); return this.preguntaService.create(createPreguntaDto);
} }
@Get() @Get()
@PreguntaApiDocumentation.ApiGetAll
findAll() { findAll() {
return this.preguntaService.findAll(); return this.preguntaService.findAll();
} }
@Get(':id') @Get(':id')
@PreguntaApiDocumentation.ApiGetOne
findOne(@Param('id') id: string) { findOne(@Param('id') id: string) {
return this.preguntaService.findOne(+id); return this.preguntaService.findOne(+id);
} }
@Patch(':id') @Patch(':id')
@PreguntaApiDocumentation.ApiUpdate
update(@Param('id') id: string, @Body() updatePreguntaDto: UpdatePreguntaDto) { update(@Param('id') id: string, @Body() updatePreguntaDto: UpdatePreguntaDto) {
return this.preguntaService.update(+id, updatePreguntaDto); return this.preguntaService.update(+id, updatePreguntaDto);
} }
@Delete(':id') @Delete(':id')
@PreguntaApiDocumentation.ApiRemove
remove(@Param('id') id: string) { remove(@Param('id') id: string) {
return this.preguntaService.remove(+id); return this.preguntaService.remove(+id);
} }
+189
View File
@@ -0,0 +1,189 @@
import { ApiBody, ApiOperation, ApiParam, ApiResponse, ApiTags } from '@nestjs/swagger';
import { applyDecorators } from '@nestjs/common';
export class PreguntaApiDocumentation {
// Decoradores para toda la clase del controlador
static ApiController = ApiTags('Preguntas');
// Documentación para crear una pregunta
static ApiCreate = applyDecorators(
ApiOperation({
summary: 'Crear una nueva pregunta',
description: 'Crea una nueva pregunta para una sección'
}),
ApiBody({
description: 'Datos de la pregunta a crear',
schema: {
type: 'object',
required: ['titulo', 'tipo'],
properties: {
titulo: { type: 'string', example: '¿Eres parte de la comunidad?' },
obligatoria: { type: 'boolean', example: true },
tipo: { type: 'string', example: 'Multiple' },
contador_opcion: { type: 'number', example: 0 },
id_opcion_dependiente: { type: 'number', example: null },
opciones: {
type: 'array',
items: {
type: 'object',
properties: {
valor: { type: 'string', example: 'Si' }
}
}
}
}
}
}),
ApiResponse({
status: 201,
description: 'Pregunta creada correctamente',
schema: {
type: 'object',
properties: {
id_pregunta: { type: 'number', example: 1 },
pregunta: { type: 'string', example: '¿Eres parte de la comunidad?' },
obligatoria: { type: 'boolean', example: true },
contador_opcion: { type: 'number', example: 0 },
id_tipo_pregunta: { type: 'number', example: 1 },
id_opcion_dependiente: { type: 'number', example: null }
}
}
}),
ApiResponse({ status: 400, description: 'Datos de la pregunta inválidos' }),
ApiResponse({ status: 500, description: 'Error interno del servidor' })
);
// Documentación para obtener todas las preguntas
static ApiGetAll = applyDecorators(
ApiOperation({
summary: 'Obtener todas las preguntas',
description: 'Retorna una lista de todas las preguntas registradas'
}),
ApiResponse({
status: 200,
description: 'Lista de preguntas obtenida correctamente',
schema: {
type: 'array',
items: {
type: 'object',
properties: {
id_pregunta: { type: 'number', example: 1 },
pregunta: { type: 'string', example: '¿Eres parte de la comunidad?' },
obligatoria: { type: 'boolean', example: true },
contador_opcion: { type: 'number', example: 0 },
id_tipo_pregunta: { type: 'number', example: 1 },
id_opcion_dependiente: { type: 'number', example: null }
}
}
}
}),
ApiResponse({ status: 500, description: 'Error interno del servidor' })
);
// Documentación para obtener una pregunta por ID
static ApiGetOne = applyDecorators(
ApiOperation({
summary: 'Obtener una pregunta por ID',
description: 'Retorna una pregunta específica por su ID'
}),
ApiParam({
name: 'id',
description: 'ID de la pregunta',
required: true,
type: 'number',
example: 1
}),
ApiResponse({
status: 200,
description: 'Pregunta obtenida correctamente',
schema: {
type: 'object',
properties: {
id_pregunta: { type: 'number', example: 1 },
pregunta: { type: 'string', example: '¿Eres parte de la comunidad?' },
obligatoria: { type: 'boolean', example: true },
contador_opcion: { type: 'number', example: 0 },
id_tipo_pregunta: { type: 'number', example: 1 },
id_opcion_dependiente: { type: 'number', example: null },
opciones: {
type: 'array',
items: {
type: 'object',
properties: {
id_opcion: { type: 'number', example: 1 },
opcion: { type: 'string', example: 'Si' }
}
}
}
}
}
}),
ApiResponse({ status: 404, description: 'Pregunta no encontrada' }),
ApiResponse({ status: 500, description: 'Error interno del servidor' })
);
// Documentación para actualizar una pregunta
static ApiUpdate = applyDecorators(
ApiOperation({
summary: 'Actualizar una pregunta',
description: 'Actualiza los datos de una pregunta existente'
}),
ApiParam({
name: 'id',
description: 'ID de la pregunta a actualizar',
required: true,
type: 'number',
example: 1
}),
ApiBody({
description: 'Datos a actualizar de la pregunta',
schema: {
type: 'object',
properties: {
pregunta: { type: 'string', example: 'Pregunta actualizada' },
obligatoria: { type: 'boolean', example: false }
}
}
}),
ApiResponse({
status: 200,
description: 'Pregunta actualizada correctamente',
schema: {
type: 'object',
properties: {
affected: { type: 'number', example: 1 }
}
}
}),
ApiResponse({ status: 400, description: 'Datos de actualización inválidos' }),
ApiResponse({ status: 404, description: 'Pregunta no encontrada' }),
ApiResponse({ status: 500, description: 'Error interno del servidor' })
);
// Documentación para eliminar una pregunta
static ApiRemove = applyDecorators(
ApiOperation({
summary: 'Eliminar una pregunta',
description: 'Elimina permanentemente una pregunta por su ID'
}),
ApiParam({
name: 'id',
description: 'ID de la pregunta a eliminar',
required: true,
type: 'number',
example: 1
}),
ApiResponse({
status: 200,
description: 'Pregunta eliminada correctamente',
schema: {
type: 'object',
properties: {
affected: { type: 'number', example: 1 }
}
}
}),
ApiResponse({ status: 404, description: 'Pregunta no encontrada' }),
ApiResponse({ status: 500, description: 'Error interno del servidor' })
);
}
+16
View File
@@ -1,9 +1,25 @@
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
import { PreguntaService } from './pregunta.service'; import { PreguntaService } from './pregunta.service';
import { PreguntaController } from './pregunta.controller'; import { PreguntaController } from './pregunta.controller';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Pregunta } from './entities/pregunta.entity';
import { TipoPreguntaModule } from '../tipo_pregunta/tipo_pregunta.module';
import { TipoPregunta } from '../tipo_pregunta/entities/tipo_pregunta.entity';
import { Opcion } from '../opcion/entities/opcion.entity';
import { PreguntaOpcion } from '../pregunta_opcion/entities/pregunta_opcion.entity';
@Module({ @Module({
imports: [
TypeOrmModule.forFeature([
Pregunta,
TipoPregunta,
Opcion,
PreguntaOpcion
]),
TipoPreguntaModule
],
controllers: [PreguntaController], controllers: [PreguntaController],
providers: [PreguntaService], providers: [PreguntaService],
exports: [TypeOrmModule, PreguntaService]
}) })
export class PreguntaModule {} export class PreguntaModule {}
+240 -12
View File
@@ -1,26 +1,254 @@
import { Injectable } from '@nestjs/common'; import { Injectable, NotFoundException } from '@nestjs/common';
import { CreatePreguntaDto } from './dto/create-pregunta.dto'; import { InjectRepository } from '@nestjs/typeorm';
import { Repository, DataSource } from 'typeorm';
import { CreatePreguntaDto, OpcionDto } from './dto/create-pregunta.dto';
import { UpdatePreguntaDto } from './dto/update-pregunta.dto'; import { UpdatePreguntaDto } from './dto/update-pregunta.dto';
import { Pregunta } from './entities/pregunta.entity';
import { TipoPregunta } from '../tipo_pregunta/entities/tipo_pregunta.entity';
import { Opcion } from '../opcion/entities/opcion.entity';
import { PreguntaOpcion } from '../pregunta_opcion/entities/pregunta_opcion.entity';
@Injectable() @Injectable()
export class PreguntaService { export class PreguntaService {
create(createPreguntaDto: CreatePreguntaDto) { constructor(
return 'This action adds a new pregunta'; @InjectRepository(Pregunta)
private preguntaRepository: Repository<Pregunta>,
@InjectRepository(TipoPregunta)
private tipoPreguntaRepository: Repository<TipoPregunta>,
@InjectRepository(Opcion)
private opcionRepository: Repository<Opcion>,
@InjectRepository(PreguntaOpcion)
private preguntaOpcionRepository: Repository<PreguntaOpcion>,
private dataSource: DataSource
) {}
async create(createPreguntaDto: CreatePreguntaDto) {
const queryRunner = this.dataSource.createQueryRunner();
await queryRunner.connect();
await queryRunner.startTransaction();
try {
// Obtener el tipo de pregunta por su nombre
const tipoPregunta = await this.tipoPreguntaRepository.findOne({
where: { tipo_pregunta: createPreguntaDto.tipo }
});
if (!tipoPregunta) {
throw new Error(`Tipo de pregunta '${createPreguntaDto.tipo}' no encontrado`);
}
// Crear pregunta
const pregunta = new Pregunta();
pregunta.pregunta = createPreguntaDto.titulo;
pregunta.obligatoria = createPreguntaDto.obligatoria || false;
pregunta.id_tipo_pregunta = tipoPregunta.id_tipo;
pregunta.id_opcion_dependiente = createPreguntaDto.id_opcion_dependiente || undefined;
pregunta.contador_opcion = createPreguntaDto.opciones?.length || 0;
const savedPregunta = await queryRunner.manager.save(pregunta);
// Crear opciones para preguntas de tipo multiple/radio
if (createPreguntaDto.opciones && createPreguntaDto.opciones.length > 0) {
await this.createOpciones(
queryRunner,
savedPregunta.id_pregunta,
createPreguntaDto.opciones
);
}
await queryRunner.commitTransaction();
return {
success: true,
pregunta: savedPregunta
};
} catch (error) {
await queryRunner.rollbackTransaction();
throw error;
} finally {
await queryRunner.release();
}
} }
findAll() { private async createOpciones(
return `This action returns all pregunta`; queryRunner: any,
preguntaId: number,
opciones: OpcionDto[]
) {
for (let i = 0; i < opciones.length; i++) {
const opcionDto = opciones[i];
// Crear opción
const opcion = new Opcion();
opcion.opcion = opcionDto.valor;
const savedOpcion = await queryRunner.manager.save(opcion);
// Vincular opción con pregunta
const preguntaOpcion = new PreguntaOpcion();
preguntaOpcion.id_pregunta = preguntaId;
preguntaOpcion.opcion = savedOpcion;
preguntaOpcion.posicion = i + 1;
await queryRunner.manager.save(preguntaOpcion);
}
} }
findOne(id: number) { async findAll() {
return `This action returns a #${id} pregunta`; return this.preguntaRepository.find();
} }
update(id: number, updatePreguntaDto: UpdatePreguntaDto) { async findOne(id: number) {
return `This action updates a #${id} pregunta`; const pregunta = await this.preguntaRepository.findOne({
where: { id_pregunta: id }
});
if (!pregunta) {
throw new NotFoundException(`Pregunta con ID ${id} no encontrada`);
}
return pregunta;
} }
remove(id: number) { async findWithOptions(id: number) {
return `This action removes a #${id} pregunta`; const pregunta = await this.preguntaRepository.findOne({
where: { id_pregunta: id },
relations: ['tipoPregunta']
});
if (!pregunta) {
throw new NotFoundException(`Pregunta con ID ${id} no encontrada`);
}
// Obtener las opciones para esta pregunta
const preguntaOpciones = await this.preguntaOpcionRepository.find({
where: { id_pregunta: id },
relations: ['opcion'],
order: { posicion: 'ASC' }
});
const opciones = preguntaOpciones.map(po => ({
id: po.opcion.id_opcion,
valor: po.opcion.opcion,
posicion: po.posicion
}));
return {
...pregunta,
tipo: pregunta.tipoPregunta?.tipo_pregunta,
opciones
};
}
async update(id: number, updatePreguntaDto: UpdatePreguntaDto) {
const queryRunner = this.dataSource.createQueryRunner();
await queryRunner.connect();
await queryRunner.startTransaction();
try {
const pregunta = await this.preguntaRepository.findOne({
where: { id_pregunta: id }
});
if (!pregunta) {
throw new NotFoundException(`Pregunta con ID ${id} no encontrada`);
}
// Actualizar los campos básicos de la pregunta
if (updatePreguntaDto.titulo) pregunta.pregunta = updatePreguntaDto.titulo;
if (updatePreguntaDto.obligatoria !== undefined) pregunta.obligatoria = updatePreguntaDto.obligatoria;
if (updatePreguntaDto.id_opcion_dependiente !== undefined) pregunta.id_opcion_dependiente = updatePreguntaDto.id_opcion_dependiente;
// Si se proporciona un nuevo tipo de pregunta, actualizarlo
if (updatePreguntaDto.tipo) {
const tipoPregunta = await this.tipoPreguntaRepository.findOne({
where: { tipo_pregunta: updatePreguntaDto.tipo }
});
if (!tipoPregunta) {
throw new Error(`Tipo de pregunta '${updatePreguntaDto.tipo}' no encontrado`);
}
pregunta.id_tipo_pregunta = tipoPregunta.id_tipo;
}
// Actualizar la pregunta
const updatedPregunta = await queryRunner.manager.save(pregunta);
// Si se proporcionan nuevas opciones, actualizarlas
if (updatePreguntaDto.opciones && updatePreguntaDto.opciones.length > 0) {
// Eliminar las opciones existentes
const existingOptions = await this.preguntaOpcionRepository.find({
where: { id_pregunta: id }
});
for (const option of existingOptions) {
await queryRunner.manager.remove(option);
}
// Crear las nuevas opciones
await this.createOpciones(
queryRunner,
id,
updatePreguntaDto.opciones
);
// Actualizar el contador de opciones
updatedPregunta.contador_opcion = updatePreguntaDto.opciones.length;
await queryRunner.manager.save(updatedPregunta);
}
await queryRunner.commitTransaction();
return {
success: true,
pregunta: updatedPregunta
};
} catch (error) {
await queryRunner.rollbackTransaction();
throw error;
} finally {
await queryRunner.release();
}
}
async remove(id: number) {
const queryRunner = this.dataSource.createQueryRunner();
await queryRunner.connect();
await queryRunner.startTransaction();
try {
const pregunta = await this.preguntaRepository.findOne({
where: { id_pregunta: id }
});
if (!pregunta) {
throw new NotFoundException(`Pregunta con ID ${id} no encontrada`);
}
// Eliminar las relaciones con opciones
const preguntaOpciones = await this.preguntaOpcionRepository.find({
where: { id_pregunta: id }
});
for (const po of preguntaOpciones) {
await queryRunner.manager.remove(po);
}
// Eliminar la pregunta
await queryRunner.manager.remove(pregunta);
await queryRunner.commitTransaction();
return {
success: true,
message: `Pregunta con ID ${id} eliminada correctamente`
};
} catch (error) {
await queryRunner.rollbackTransaction();
throw error;
} finally {
await queryRunner.release();
}
} }
} }
@@ -12,14 +12,15 @@ export class PreguntaOpcion {
@Column() @Column()
posicion: number; posicion: number;
@ManyToOne(() => Pregunta, pregunta => pregunta.id_pregunta) @ManyToOne(() => Pregunta)
pregunta: Pregunta;
@Column() @Column()
id_pregunta: Pregunta; id_pregunta: number;
@ManyToOne(() => Opcion, opcion => opcion.id_opcion) @ManyToOne(() => Opcion, opcion => opcion.preguntasOpciones)
@Column() opcion: Opcion;
id_opcion: Opcion;
@OneToMany(()=> RespuestaParticipanteCerrada, respuestaParticipanteCerrada=>respuestaParticipanteCerrada.id_pregunta_opcion) @OneToMany(()=> RespuestaParticipanteCerrada, respuestaParticipanteCerrada=>respuestaParticipanteCerrada.preguntaOpcion)
respuestaParticipanteCerrada:RespuestaParticipanteCerrada[]; respuestaParticipanteCerrada:RespuestaParticipanteCerrada[];
} }
@@ -1,9 +1,19 @@
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
import { PreguntaOpcionService } from './pregunta_opcion.service'; import { PreguntaOpcionService } from './pregunta_opcion.service';
import { PreguntaOpcionController } from './pregunta_opcion.controller'; import { PreguntaOpcionController } from './pregunta_opcion.controller';
import { TypeOrmModule } from '@nestjs/typeorm';
import { PreguntaOpcion } from './entities/pregunta_opcion.entity';
import { OpcionModule } from '../opcion/opcion.module';
import { PreguntaModule } from '../pregunta/pregunta.module';
@Module({ @Module({
imports: [
TypeOrmModule.forFeature([PreguntaOpcion]),
OpcionModule,
PreguntaModule
],
controllers: [PreguntaOpcionController], controllers: [PreguntaOpcionController],
providers: [PreguntaOpcionService], providers: [PreguntaOpcionService],
exports: [TypeOrmModule]
}) })
export class PreguntaOpcionModule {} export class PreguntaOpcionModule {}
+43 -22
View File
@@ -1,36 +1,57 @@
import { Body, Controller, Delete, Get, Param, ParseIntPipe, Patch, Post } from '@nestjs/common'; import {
Body,
Controller,
Delete,
Get,
Param,
ParseIntPipe,
Patch,
Post,
Query,
} from '@nestjs/common';
import { QrService } from './qr.service'; import { QrService } from './qr.service';
import { Qr } from './qr.entity'; import { Qr } from './qr.entity';
import { CreateQrDto } from './dto/create-qr.dto'; import { CreateQrDto } from './dto/create-qr.dto';
import { UpdateQrDto } from './dto/update.qr.dto'; import { UpdateQrDto } from './dto/update.qr.dto';
import { ApiOperation, ApiQuery } from '@nestjs/swagger';
@Controller('qr') @Controller('qr')
export class QrController { export class QrController {
constructor(private qrService: QrService) {}
constructor(private qrService: QrService) {} @Get('generate')
@ApiOperation({ summary: 'Genera un código QR a partir de un texto' })
@ApiQuery({
name: 'text',
required: true,
description: 'Texto a codificar en el QR',
})
async generateQRCode(@Query('text') text: string): Promise<string> {
return this.qrService.generateQRCode(text);
}
@Get() @Get()
getQrs(): Promise<Qr[]> { getQrs(): Promise<Qr[]> {
return this.qrService.getQrs(); return this.qrService.getQrs();
} }
@Get(':id') @Get(':id')
getQr(@Param('id', ParseIntPipe) id: number) { getQr(@Param('id', ParseIntPipe) id: number) {
return this.qrService.getQr(id); return this.qrService.getQr(id);
} }
@Post() //en el body ValidationPipe @Post() //en el body ValidationPipe
createQr(@Body() newQr: CreateQrDto) { createQr(@Body() newQr: CreateQrDto) {
return this.qrService.createQr(newQr) return this.qrService.createQr(newQr);
} }
@Delete(':id') @Delete(':id')
deleteQr(@Param('id', ParseIntPipe) id:number) { deleteQr(@Param('id', ParseIntPipe) id: number) {
return this.qrService.deleteQr(id); return this.qrService.deleteQr(id);
} }
@Patch(':id') @Patch(':id')
updateQr(@Param('id', ParseIntPipe) id: number, @Body() qr: UpdateQrDto) { updateQr(@Param('id', ParseIntPipe) id: number, @Body() qr: UpdateQrDto) {
return this.qrService.updateQr(id, qr) return this.qrService.updateQr(id, qr);
} }
} }
+61 -44
View File
@@ -4,70 +4,87 @@ import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm'; import { Repository } from 'typeorm';
import { CreateQrDto } from './dto/create-qr.dto'; import { CreateQrDto } from './dto/create-qr.dto';
import { UpdateQrDto } from './dto/update.qr.dto'; import { UpdateQrDto } from './dto/update.qr.dto';
import * as QRCode from 'qrcode';
@Injectable() @Injectable()
export class QrService { export class QrService {
constructor(@InjectRepository(Qr) private qrRepository: Repository<Qr>) {}
constructor( async generateQRCode(text: string): Promise<string> {
@InjectRepository(Qr) private qrRepository: Repository<Qr> return await QRCode.toDataURL(text);
) {} }
async createQr(qr: CreateQrDto) { async generateBuffer(text: string): Promise<Buffer> {
const qrFound = await this.qrRepository.findOne({ return await QRCode.toBuffer(text); // Devuelve un buffer para adjuntar en email o stream
where: { }
id_qr: qr.id_participante_evento
}
})
if (qrFound) { // Para enviar el QR por email
return new HttpException('Qr already exists', HttpStatus.CONFLICT) /* const qrBuffer = await this.qrService.generateBuffer('Texto a codificar');
} await transporter.sendMail({
to: 'destinatario@example.com',
subject: 'Tu código QR',
html: '<p>Escanea el siguiente código:</p><img src="cid:qrcode"/>',
attachments: [{
filename: 'qrcode.png',
content: qrBuffer,
cid: 'qrcode',
}],
}); */
return this.qrRepository.save(qr) async createQr(qr: CreateQrDto) {
const qrFound = await this.qrRepository.findOne({
where: {
id_qr: qr.id_participante_evento,
},
});
if (qrFound) {
return new HttpException('Qr already exists', HttpStatus.CONFLICT);
} }
getQrs() { return this.qrRepository.save(qr);
return this.qrRepository.find({}) }
}
async getQr(id_qr) { getQrs() {
const qrFound = await this.qrRepository.findOne({ return this.qrRepository.find({});
where: { }
id_qr
}
})
if (!qrFound) { async getQr(id_qr) {
return new HttpException('User not found', HttpStatus.NOT_FOUND); const qrFound = await this.qrRepository.findOne({
} where: {
id_qr,
},
});
return qrFound if (!qrFound) {
return new HttpException('User not found', HttpStatus.NOT_FOUND);
} }
async deleteQr(id_qr: number) { return qrFound;
const result = await this.qrRepository.delete({ id_qr }) }
if (result.affected === 0) { async deleteQr(id_qr: number) {
return new HttpException('User not found', HttpStatus.NOT_FOUND); const result = await this.qrRepository.delete({ id_qr });
}
return result if (result.affected === 0) {
return new HttpException('User not found', HttpStatus.NOT_FOUND);
} }
async updateQr(id_qr: number, qr: UpdateQrDto) { return result;
const qrFound = await this.qrRepository.findOne({ }
where: {
id_qr
}
})
if (!qrFound) { async updateQr(id_qr: number, qr: UpdateQrDto) {
return new HttpException('User not found', HttpStatus.NOT_FOUND) const qrFound = await this.qrRepository.findOne({
} where: {
id_qr,
},
});
const updateQr = Object.assign(qrFound, qr) if (!qrFound) {
return this.qrRepository.save(updateQr) return new HttpException('User not found', HttpStatus.NOT_FOUND);
} }
const updateQr = Object.assign(qrFound, qr);
return this.qrRepository.save(updateQr);
}
} }
@@ -14,17 +14,14 @@ export class RespuestaParticipanteAbierta {
// Relación con CuestionarioRespondido // Relación con CuestionarioRespondido
@ManyToOne( @ManyToOne(
() => CuestionarioRespondido, () => CuestionarioRespondido,
cuestionarioRespondido => cuestionarioRespondido.idCuestionarioRespondido cuestionarioRespondido => cuestionarioRespondido.respuestasAbiertas
) )
@JoinColumn({ name: 'id_cuestionario_respondido' }) @JoinColumn({ name: 'id_cuestionario_respondido' })
id_cuestionario_respondido: CuestionarioRespondido; cuestionarioRespondido: CuestionarioRespondido;
@ManyToOne( @ManyToOne(() => Pregunta)
() => Pregunta,
pregunta => pregunta.id_pregunta
)
@JoinColumn({ name: 'id_pregunta' }) @JoinColumn({ name: 'id_pregunta' })
id_pregunta: Pregunta; pregunta: Pregunta;
} }
@@ -1,9 +1,19 @@
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
import { RespuestaParticipanteAbiertaService } from './respuesta_participante_abierta.service'; import { RespuestaParticipanteAbiertaService } from './respuesta_participante_abierta.service';
import { RespuestaParticipanteAbiertaController } from './respuesta_participante_abierta.controller'; import { RespuestaParticipanteAbiertaController } from './respuesta_participante_abierta.controller';
import { TypeOrmModule } from '@nestjs/typeorm';
import { RespuestaParticipanteAbierta } from './entities/respuesta_participante_abierta.entity';
import { CuestionarioRespondidoModule } from '../cuestionario_respondido/cuestionario_respondido.module';
import { PreguntaModule } from '../pregunta/pregunta.module';
@Module({ @Module({
imports: [
TypeOrmModule.forFeature([RespuestaParticipanteAbierta]),
CuestionarioRespondidoModule,
PreguntaModule
],
controllers: [RespuestaParticipanteAbiertaController], controllers: [RespuestaParticipanteAbiertaController],
providers: [RespuestaParticipanteAbiertaService], providers: [RespuestaParticipanteAbiertaService],
exports: [TypeOrmModule]
}) })
export class RespuestaParticipanteAbiertaModule {} export class RespuestaParticipanteAbiertaModule {}
@@ -10,11 +10,11 @@ export class RespuestaParticipanteCerrada {
@ManyToOne(() => CuestionarioRespondido, cuestionarioRespondido => cuestionarioRespondido.respuestasCerradas) @ManyToOne(() => CuestionarioRespondido, cuestionarioRespondido => cuestionarioRespondido.respuestasCerradas)
@JoinColumn({ name: 'id_cuestionario_respondido' }) @JoinColumn({ name: 'id_cuestionario_respondido' })
id_cuestionario_respondido: CuestionarioRespondido; cuestionarioRespondido: CuestionarioRespondido;
@ManyToOne(() => PreguntaOpcion, preguntaOpcion => preguntaOpcion.id_pregunta_opcion) @ManyToOne(() => PreguntaOpcion, preguntaOpcion => preguntaOpcion.respuestaParticipanteCerrada)
@JoinColumn({ name: 'id_pregunta_opcion' }) @JoinColumn({ name: 'id_pregunta_opcion' })
id_pregunta_opcion: PreguntaOpcion; preguntaOpcion: PreguntaOpcion;
} }
@@ -1,9 +1,19 @@
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
import { RespuestaParticipanteCerradaService } from './respuesta_participante_cerrada.service'; import { RespuestaParticipanteCerradaService } from './respuesta_participante_cerrada.service';
import { RespuestaParticipanteCerradaController } from './respuesta_participante_cerrada.controller'; import { RespuestaParticipanteCerradaController } from './respuesta_participante_cerrada.controller';
import { TypeOrmModule } from '@nestjs/typeorm';
import { RespuestaParticipanteCerrada } from './entities/respuesta_participante_cerrada.entity';
import { PreguntaOpcionModule } from '../pregunta_opcion/pregunta_opcion.module';
import { CuestionarioRespondidoModule } from '../cuestionario_respondido/cuestionario_respondido.module';
@Module({ @Module({
imports: [
TypeOrmModule.forFeature([RespuestaParticipanteCerrada]),
PreguntaOpcionModule,
CuestionarioRespondidoModule
],
controllers: [RespuestaParticipanteCerradaController], controllers: [RespuestaParticipanteCerradaController],
providers: [RespuestaParticipanteCerradaService], providers: [RespuestaParticipanteCerradaService],
exports: [TypeOrmModule]
}) })
export class RespuestaParticipanteCerradaModule {} export class RespuestaParticipanteCerradaModule {}
+23 -1
View File
@@ -1 +1,23 @@
export class CreateSeccionDto {} import { CreatePreguntaDto } from '../../pregunta/dto/create-pregunta.dto';
import { IsArray, IsNotEmpty, IsNumber, IsOptional, IsString, ValidateNested } from 'class-validator';
import { Type } from 'class-transformer';
export class CreateSeccionDto {
@IsNotEmpty()
@IsString()
titulo: string;
@IsOptional()
@IsString()
descripcion?: string;
@IsOptional()
@IsNumber()
contador_pregunta?: number;
@IsOptional()
@IsArray()
@ValidateNested({ each: true })
@Type(() => CreatePreguntaDto)
preguntas?: CreatePreguntaDto[];
}
+3 -3
View File
@@ -11,7 +11,7 @@ export class Seccion {
contador_pregunta: number; contador_pregunta: number;
@Column({ type: 'text', nullable: true }) @Column({ type: 'text', nullable: true })
descripcion: string; descripcion?: string;
@Column({ type: 'text', nullable: true }) @Column({ type: 'text', nullable: true })
titulo: string; titulo: string;
@@ -19,6 +19,6 @@ export class Seccion {
@OneToMany(() => CuestionarioSeccion,(cuestionario_seccion) => cuestionario_seccion.id_seccion ) @OneToMany(() => CuestionarioSeccion,(cuestionario_seccion) => cuestionario_seccion.id_seccion )
seccion:CuestionarioSeccion[]; seccion:CuestionarioSeccion[];
@OneToMany(()=> SeccionPregunta, (seccion_pregunta)=> seccion_pregunta.id_seccion) @OneToMany(()=> SeccionPregunta, (seccion_pregunta)=> seccion_pregunta.seccion)
seccion_pregunta:SeccionPregunta[]; seccionPreguntas:SeccionPregunta[];
} }
+7
View File
@@ -2,32 +2,39 @@ import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/commo
import { SeccionService } from './seccion.service'; import { SeccionService } from './seccion.service';
import { CreateSeccionDto } from './dto/create-seccion.dto'; import { CreateSeccionDto } from './dto/create-seccion.dto';
import { UpdateSeccionDto } from './dto/update-seccion.dto'; import { UpdateSeccionDto } from './dto/update-seccion.dto';
import { SeccionApiDocumentation } from './seccion.documentation';
@Controller('seccion') @Controller('seccion')
@SeccionApiDocumentation.ApiController
export class SeccionController { export class SeccionController {
constructor(private readonly seccionService: SeccionService) {} constructor(private readonly seccionService: SeccionService) {}
@Post() @Post()
@SeccionApiDocumentation.ApiCreate
create(@Body() createSeccionDto: CreateSeccionDto) { create(@Body() createSeccionDto: CreateSeccionDto) {
return this.seccionService.create(createSeccionDto); return this.seccionService.create(createSeccionDto);
} }
@Get() @Get()
@SeccionApiDocumentation.ApiGetAll
findAll() { findAll() {
return this.seccionService.findAll(); return this.seccionService.findAll();
} }
@Get(':id') @Get(':id')
@SeccionApiDocumentation.ApiGetOne
findOne(@Param('id') id: string) { findOne(@Param('id') id: string) {
return this.seccionService.findOne(+id); return this.seccionService.findOne(+id);
} }
@Patch(':id') @Patch(':id')
@SeccionApiDocumentation.ApiUpdate
update(@Param('id') id: string, @Body() updateSeccionDto: UpdateSeccionDto) { update(@Param('id') id: string, @Body() updateSeccionDto: UpdateSeccionDto) {
return this.seccionService.update(+id, updateSeccionDto); return this.seccionService.update(+id, updateSeccionDto);
} }
@Delete(':id') @Delete(':id')
@SeccionApiDocumentation.ApiRemove
remove(@Param('id') id: string) { remove(@Param('id') id: string) {
return this.seccionService.remove(+id); return this.seccionService.remove(+id);
} }
+185
View File
@@ -0,0 +1,185 @@
import { ApiBody, ApiOperation, ApiParam, ApiResponse, ApiTags } from '@nestjs/swagger';
import { applyDecorators } from '@nestjs/common';
export class SeccionApiDocumentation {
// Decoradores para toda la clase del controlador
static ApiController = ApiTags('Secciones');
// Documentación para crear una sección
static ApiCreate = applyDecorators(
ApiOperation({
summary: 'Crear una nueva sección',
description: 'Crea una nueva sección para un cuestionario'
}),
ApiBody({
description: 'Datos de la sección a crear',
schema: {
type: 'object',
required: ['titulo'],
properties: {
titulo: { type: 'string', example: 'Información Personal' },
descripcion: {
type: 'string',
example: 'Proporciona tus datos personales para poder contactarte'
},
contador_pregunta: { type: 'number', example: 0 },
preguntas: {
type: 'array',
items: {
type: 'object',
properties: {
titulo: { type: 'string', example: '¿Eres parte de la comunidad?' },
obligatoria: { type: 'boolean', example: true },
tipo: { type: 'string', example: 'Multiple' },
opciones: {
type: 'array',
items: {
type: 'object',
properties: {
valor: { type: 'string', example: 'Si' }
}
}
}
}
}
}
}
}
}),
ApiResponse({
status: 201,
description: 'Sección creada correctamente',
schema: {
type: 'object',
properties: {
id_seccion: { type: 'number', example: 1 },
titulo: { type: 'string', example: 'Información Personal' },
descripcion: { type: 'string' },
contador_pregunta: { type: 'number', example: 0 }
}
}
}),
ApiResponse({ status: 400, description: 'Datos de la sección inválidos' }),
ApiResponse({ status: 500, description: 'Error interno del servidor' })
);
// Documentación para obtener todas las secciones
static ApiGetAll = applyDecorators(
ApiOperation({
summary: 'Obtener todas las secciones',
description: 'Retorna una lista de todas las secciones registradas'
}),
ApiResponse({
status: 200,
description: 'Lista de secciones obtenida correctamente',
schema: {
type: 'array',
items: {
type: 'object',
properties: {
id_seccion: { type: 'number', example: 1 },
titulo: { type: 'string', example: 'Información Personal' },
descripcion: { type: 'string' },
contador_pregunta: { type: 'number', example: 0 }
}
}
}
}),
ApiResponse({ status: 500, description: 'Error interno del servidor' })
);
// Documentación para obtener una sección por ID
static ApiGetOne = applyDecorators(
ApiOperation({
summary: 'Obtener una sección por ID',
description: 'Retorna una sección específica por su ID'
}),
ApiParam({
name: 'id',
description: 'ID de la sección',
required: true,
type: 'number',
example: 1
}),
ApiResponse({
status: 200,
description: 'Sección obtenida correctamente',
schema: {
type: 'object',
properties: {
id_seccion: { type: 'number', example: 1 },
titulo: { type: 'string', example: 'Información Personal' },
descripcion: { type: 'string' },
contador_pregunta: { type: 'number', example: 0 }
}
}
}),
ApiResponse({ status: 404, description: 'Sección no encontrada' }),
ApiResponse({ status: 500, description: 'Error interno del servidor' })
);
// Documentación para actualizar una sección
static ApiUpdate = applyDecorators(
ApiOperation({
summary: 'Actualizar una sección',
description: 'Actualiza los datos de una sección existente'
}),
ApiParam({
name: 'id',
description: 'ID de la sección a actualizar',
required: true,
type: 'number',
example: 1
}),
ApiBody({
description: 'Datos a actualizar de la sección',
schema: {
type: 'object',
properties: {
titulo: { type: 'string', example: 'Título actualizado' },
descripcion: { type: 'string', example: 'Descripción actualizada' }
}
}
}),
ApiResponse({
status: 200,
description: 'Sección actualizada correctamente',
schema: {
type: 'object',
properties: {
affected: { type: 'number', example: 1 }
}
}
}),
ApiResponse({ status: 400, description: 'Datos de actualización inválidos' }),
ApiResponse({ status: 404, description: 'Sección no encontrada' }),
ApiResponse({ status: 500, description: 'Error interno del servidor' })
);
// Documentación para eliminar una sección
static ApiRemove = applyDecorators(
ApiOperation({
summary: 'Eliminar una sección',
description: 'Elimina permanentemente una sección por su ID'
}),
ApiParam({
name: 'id',
description: 'ID de la sección a eliminar',
required: true,
type: 'number',
example: 1
}),
ApiResponse({
status: 200,
description: 'Sección eliminada correctamente',
schema: {
type: 'object',
properties: {
affected: { type: 'number', example: 1 }
}
}
}),
ApiResponse({ status: 404, description: 'Sección no encontrada' }),
ApiResponse({ status: 500, description: 'Error interno del servidor' })
);
}
+18
View File
@@ -1,9 +1,27 @@
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
import { SeccionService } from './seccion.service'; import { SeccionService } from './seccion.service';
import { SeccionController } from './seccion.controller'; import { SeccionController } from './seccion.controller';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Seccion } from './entities/seccion.entity';
import { Pregunta } from '../pregunta/entities/pregunta.entity';
import { SeccionPregunta } from '../seccion_pregunta/entities/seccion_pregunta.entity';
import { TipoPregunta } from '../tipo_pregunta/entities/tipo_pregunta.entity';
import { Opcion } from '../opcion/entities/opcion.entity';
import { PreguntaOpcion } from '../pregunta_opcion/entities/pregunta_opcion.entity';
@Module({ @Module({
imports: [
TypeOrmModule.forFeature([
Seccion,
Pregunta,
SeccionPregunta,
TipoPregunta,
Opcion,
PreguntaOpcion
])
],
controllers: [SeccionController], controllers: [SeccionController],
providers: [SeccionService], providers: [SeccionService],
exports: [TypeOrmModule, SeccionService]
}) })
export class SeccionModule {} export class SeccionModule {}
+190 -11
View File
@@ -1,26 +1,205 @@
import { Injectable } from '@nestjs/common'; import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository, DataSource } from 'typeorm';
import { CreateSeccionDto } from './dto/create-seccion.dto'; import { CreateSeccionDto } from './dto/create-seccion.dto';
import { UpdateSeccionDto } from './dto/update-seccion.dto'; import { UpdateSeccionDto } from './dto/update-seccion.dto';
import { Seccion } from './entities/seccion.entity';
import { Pregunta } from '../pregunta/entities/pregunta.entity';
import { SeccionPregunta } from '../seccion_pregunta/entities/seccion_pregunta.entity';
import { TipoPregunta } from '../tipo_pregunta/entities/tipo_pregunta.entity';
import { Opcion } from '../opcion/entities/opcion.entity';
import { PreguntaOpcion } from '../pregunta_opcion/entities/pregunta_opcion.entity';
@Injectable() @Injectable()
export class SeccionService { export class SeccionService {
create(createSeccionDto: CreateSeccionDto) { constructor(
return 'This action adds a new seccion'; @InjectRepository(Seccion)
private seccionRepository: Repository<Seccion>,
@InjectRepository(Pregunta)
private preguntaRepository: Repository<Pregunta>,
@InjectRepository(SeccionPregunta)
private seccionPreguntaRepository: Repository<SeccionPregunta>,
@InjectRepository(TipoPregunta)
private tipoPreguntaRepository: Repository<TipoPregunta>,
@InjectRepository(Opcion)
private opcionRepository: Repository<Opcion>,
@InjectRepository(PreguntaOpcion)
private preguntaOpcionRepository: Repository<PreguntaOpcion>,
private dataSource: DataSource
) {}
async create(createSeccionDto: CreateSeccionDto) {
const queryRunner = this.dataSource.createQueryRunner();
await queryRunner.connect();
await queryRunner.startTransaction();
try {
// 1. Crear sección
const seccion = new Seccion();
seccion.titulo = createSeccionDto.titulo;
seccion.descripcion = createSeccionDto.descripcion;
seccion.contador_pregunta = createSeccionDto.preguntas?.length || 0;
const savedSeccion = await queryRunner.manager.save(seccion);
// 2. Crear preguntas y vincularlas a la sección
if (createSeccionDto.preguntas && createSeccionDto.preguntas.length > 0) {
for (let i = 0; i < createSeccionDto.preguntas.length; i++) {
const preguntaDto = createSeccionDto.preguntas[i];
// Obtener el tipo de pregunta por su nombre
const tipoPregunta = await queryRunner.manager.findOne(TipoPregunta, {
where: { tipo_pregunta: preguntaDto.tipo },
});
if (!tipoPregunta) {
throw new Error(`Tipo de pregunta '${preguntaDto.tipo}' no encontrado`);
}
// Crear pregunta
const pregunta = new Pregunta();
pregunta.pregunta = preguntaDto.titulo;
pregunta.obligatoria = preguntaDto.obligatoria || false;
pregunta.id_tipo_pregunta = tipoPregunta.id_tipo;
pregunta.id_opcion_dependiente = preguntaDto.id_opcion_dependiente || undefined;
pregunta.contador_opcion = preguntaDto.opciones?.length || 0;
const savedPregunta = await queryRunner.manager.save(pregunta);
// Vincular pregunta con sección
const seccionPregunta = new SeccionPregunta();
seccionPregunta.id_seccion = savedSeccion.id_seccion;
seccionPregunta.id_pregunta = savedPregunta.id_pregunta;
seccionPregunta.posicion = i + 1;
await queryRunner.manager.save(seccionPregunta);
// Crear opciones para preguntas de tipo multiple/radio
if (preguntaDto.opciones && preguntaDto.opciones.length > 0) {
for (let j = 0; j < preguntaDto.opciones.length; j++) {
const opcionDto = preguntaDto.opciones[j];
// Crear opción
const opcion = new Opcion();
opcion.opcion = opcionDto.valor;
const savedOpcion = await queryRunner.manager.save(opcion);
// Vincular opción con pregunta
const preguntaOpcion = new PreguntaOpcion();
preguntaOpcion.id_pregunta = savedPregunta.id_pregunta;
preguntaOpcion.opcion = savedOpcion;
preguntaOpcion.posicion = j + 1;
await queryRunner.manager.save(preguntaOpcion);
}
}
}
}
await queryRunner.commitTransaction();
return {
success: true,
seccion: savedSeccion,
};
} catch (error) {
await queryRunner.rollbackTransaction();
throw error;
} finally {
await queryRunner.release();
}
} }
findAll() { async findAll() {
return `This action returns all seccion`; return this.seccionRepository.find();
} }
findOne(id: number) { async findOne(id: number) {
return `This action returns a #${id} seccion`; const seccion = await this.seccionRepository.findOne({
where: { id_seccion: id }
});
if (!seccion) {
throw new NotFoundException(`Sección con ID ${id} no encontrada`);
}
return seccion;
} }
update(id: number, updateSeccionDto: UpdateSeccionDto) { async findWithPreguntas(id: number) {
return `This action updates a #${id} seccion`; const seccion = await this.seccionRepository.findOne({
where: { id_seccion: id }
});
if (!seccion) {
throw new NotFoundException(`Sección con ID ${id} no encontrada`);
}
// Obtener las relaciones seccion-pregunta para esta sección
const seccionPreguntas = await this.seccionPreguntaRepository.find({
where: { id_seccion: id },
relations: ['pregunta']
});
// Extraer y organizar las preguntas
const preguntas = await Promise.all(
seccionPreguntas.map(async (sp) => {
const pregunta = await this.preguntaRepository.findOne({
where: { id_pregunta: sp.id_pregunta }
});
// Obtener las opciones para preguntas de tipo multiple/radio
const preguntaOpciones = await this.preguntaOpcionRepository.find({
where: { id_pregunta: sp.id_pregunta },
relations: ['opcion'],
order: { posicion: 'ASC' }
});
const opciones = preguntaOpciones.map(po => po.opcion);
return {
...pregunta,
posicion: sp.posicion,
opciones
};
})
);
// Ordenar las preguntas por posición
preguntas.sort((a, b) => a.posicion - b.posicion);
return {
...seccion,
preguntas
};
} }
remove(id: number) { async update(id: number, updateSeccionDto: UpdateSeccionDto) {
return `This action removes a #${id} seccion`; const seccion = await this.seccionRepository.findOne({
where: { id_seccion: id }
});
if (!seccion) {
throw new NotFoundException(`Sección con ID ${id} no encontrada`);
}
// Actualizar solo los campos proporcionados
if (updateSeccionDto.titulo) seccion.titulo = updateSeccionDto.titulo;
if (updateSeccionDto.descripcion !== undefined) seccion.descripcion = updateSeccionDto.descripcion;
return this.seccionRepository.save(seccion);
}
async remove(id: number) {
const seccion = await this.seccionRepository.findOne({
where: { id_seccion: id }
});
if (!seccion) {
throw new NotFoundException(`Sección con ID ${id} no encontrada`);
}
return this.seccionRepository.remove(seccion);
} }
} }
@@ -11,11 +11,15 @@ export class SeccionPregunta {
@Column({ type: 'tinyint' }) @Column({ type: 'tinyint' })
posicion: number; posicion: number;
@ManyToOne(() => Pregunta, (pregunta) => pregunta.id_pregunta) @ManyToOne(() => Pregunta, (pregunta) => pregunta.seccionPreguntas)
pregunta: Pregunta;
@Column({ type: 'int' }) @Column({ type: 'int' })
id_pregunta: number; id_pregunta: number;
@ManyToOne(() => Seccion, (seccion) => seccion.id_seccion) @ManyToOne(() => Seccion, (seccion) => seccion.seccionPreguntas)
seccion: Seccion;
@Column({ type: 'int' }) @Column({ type: 'int' })
id_seccion: number; id_seccion: number;
} }
@@ -1,9 +1,19 @@
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
import { SeccionPreguntaService } from './seccion_pregunta.service'; import { SeccionPreguntaService } from './seccion_pregunta.service';
import { SeccionPreguntaController } from './seccion_pregunta.controller'; import { SeccionPreguntaController } from './seccion_pregunta.controller';
import { TypeOrmModule } from '@nestjs/typeorm';
import { SeccionPregunta } from './entities/seccion_pregunta.entity';
import { PreguntaModule } from '../pregunta/pregunta.module';
import { SeccionModule } from '../seccion/seccion.module';
@Module({ @Module({
imports: [
TypeOrmModule.forFeature([SeccionPregunta]),
PreguntaModule,
SeccionModule
],
controllers: [SeccionPreguntaController], controllers: [SeccionPreguntaController],
providers: [SeccionPreguntaService], providers: [SeccionPreguntaService],
exports: [TypeOrmModule]
}) })
export class SeccionPreguntaModule {} export class SeccionPreguntaModule {}
@@ -9,6 +9,6 @@ export class TipoPregunta {
@Column() @Column()
tipo_pregunta: string; tipo_pregunta: string;
@OneToMany(() => Pregunta, pregunta => pregunta.id_tipo_pregunta) @OneToMany(() => Pregunta, pregunta => pregunta.tipoPregunta)
preguntas: Pregunta[]; preguntas: Pregunta[];
} }
@@ -0,0 +1,6 @@
import { Controller } from '@nestjs/common';
@Controller('tipo_pregunta')
export class TipoPreguntaController {
// Métodos del controlador
}
@@ -1,9 +1,13 @@
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
import { TipoPreguntaService } from './tipo_pregunta.service'; import { TipoPreguntaService } from './tipo_pregunta.service';
import { TipoPreguntaController } from './tipo_pregunta.controller'; import { TipoPreguntaController } from './tipo_pregunta.controller';
import { TypeOrmModule } from '@nestjs/typeorm';
import { TipoPregunta } from './entities/tipo_pregunta.entity';
@Module({ @Module({
imports: [TypeOrmModule.forFeature([TipoPregunta])],
controllers: [TipoPreguntaController], controllers: [TipoPreguntaController],
providers: [TipoPreguntaService], providers: [TipoPreguntaService],
exports: [TypeOrmModule]
}) })
export class TipoPreguntaModule {} export class TipoPreguntaModule {}
@@ -0,0 +1,6 @@
import { Injectable } from '@nestjs/common';
@Injectable()
export class TipoPreguntaService {
// Métodos del servicio
}
+53
View File
@@ -0,0 +1,53 @@
export const feriaSexualidad = {
nombre_form: 'Registro para la Feria de la Sexualidad - FES Acatlán',
descripcion:
'La Feria de la Sexualidad es un espacio seguro e informativo dirigido a toda la comunidad universitaria. Este registro nos ayudará a organizar mejor las actividades, talleres y charlas, así como conocer tus intereses. Por favor, completa el siguiente formulario para confirmar tu participación.',
fecha_inicio: '2025-03-07T00:00:01',
fecha_fin: '2025-03-18T23:59:59',
id_tipo_cuestionario: 1,
secciones: [
{
titulo: 'Información Personal',
descripcion:
'Proporciona tus datos personales para poder contactarte y enviarte información relevante sobre la Feria de la Sexualidad.',
preguntas: [
{
titulo: '¿Eres parte de la comunidad de la FES Acatlán?',
tipo: 'Multiple',
opciones: [{ valor: 'Si' }, { valor: 'No' }],
obligatoria: true,
},
{
titulo: 'Numero de cuenta',
obligatoria: true,
tipo: 'Numero',
},
{
titulo: 'Nombre(s)',
obligatoria: true,
tipo: 'Texto',
},
{
titulo: 'Apellidos',
obligatoria: true,
tipo: 'Texto',
},
{
titulo: 'genero',
obligatoria: true,
tipo: 'Radio',
opciones: [
{ valor: 'Masculino' },
{ valor: 'Femenino' },
{ valor: 'Prefiero no decirlo' },
],
},
{
titulo: 'Institución de procedencia',
tipo: 'Abierto',
obligatoria: true,
},
],
},
],
};
+197
View File
@@ -0,0 +1,197 @@
export const cuestionario_feria = {
tipo_cuestionario: {
id_tipo_cuestionario: 1,
tipo_cuestionario: 'Encuesta',
},
cuestionario: {
id_cuestionario: 10,
nombre_form:
'Registro para la Feria de la Sexualidad - Comunidad FES Acatlán',
contador_secciones: 2,
descripcion:
'La Feria de la Sexualidad es un espacio seguro e informativo dirigido a toda la comunidad universitaria. Este registro nos ayudará a organizar mejor las actividades, talleres y charlas, así como conocer tus intereses. Por favor, completa el siguiente formulario para confirmar tu participación.',
editable: true,
fecha_inicio: '2025-03-26T00:00:00',
fecha_fin: '2025-03-31T23:59:59',
id_cuestionario_original: null,
id_tipo_cuestionario: 1,
secciones: [
{
id_cuestionario_seccion: 1,
posicion: 1,
seccion: {
id_seccion: 100,
contador_pregunta: 2,
descripcion: 'Preguntas generales',
titulo: 'General',
},
preguntas: [
{
id_seccion_pregunta: 1000,
posicion: 1,
pregunta: {
id_pregunta: 10000,
pregunta: '¿Cómo calificaría nuestro servicio?',
contador_opcion: 4,
obligatoria: true,
id_tipo_pregunta: 1,
id_opcion_dependiente: null,
tipo_pregunta: {
id_tipo: 1,
tipo_pregunta: 'Cerrada',
},
opciones: [
{
id_pregunta_opcion: 50000,
posicion: 1,
id_opcion: 90000,
opcion: {
id_opcion: 90000,
opcion: 'Excelente',
},
},
{
id_pregunta_opcion: 50001,
posicion: 2,
id_opcion: 90001,
opcion: {
id_opcion: 90001,
opcion: 'Bueno',
},
},
{
id_pregunta_opcion: 50002,
posicion: 3,
id_opcion: 90002,
opcion: {
id_opcion: 90002,
opcion: 'Regular',
},
},
{
id_pregunta_opcion: 50003,
posicion: 4,
id_opcion: 90003,
opcion: {
id_opcion: 90003,
opcion: 'Malo',
},
},
],
},
},
{
id_seccion_pregunta: 1001,
posicion: 2,
pregunta: {
id_pregunta: 10001,
pregunta: '¿Recomendaría nuestro servicio?',
contador_opcion: 2,
obligatoria: true,
id_tipo_pregunta: 1,
id_opcion_dependiente: null,
tipo_pregunta: {
id_tipo: 1,
tipo_pregunta: 'Cerrada',
},
opciones: [
{
id_pregunta_opcion: 50004,
posicion: 1,
id_opcion: 90004,
opcion: {
id_opcion: 90004,
opcion: 'Sí',
},
},
{
id_pregunta_opcion: 50005,
posicion: 2,
id_opcion: 90005,
opcion: {
id_opcion: 90005,
opcion: 'No',
},
},
],
},
},
],
},
{
id_cuestionario_seccion: 2,
posicion: 2,
seccion: {
id_seccion: 101,
contador_pregunta: 1,
descripcion: 'Preguntas adicionales para conocer más detalles',
titulo: 'Adicionales',
},
preguntas: [
{
id_seccion_pregunta: 1002,
posicion: 1,
pregunta: {
id_pregunta: 10002,
pregunta: '¿Qué mejorarías en nuestro servicio?',
contador_opcion: 0,
obligatoria: false,
id_tipo_pregunta: 2,
id_opcion_dependiente: null,
tipo_pregunta: {
id_tipo: 2,
tipo_pregunta: 'Abierta',
},
opciones: [],
},
},
{
id_seccion_pregunta: 1003,
posicion: 3,
pregunta: {
id_pregunta: 10003,
pregunta: '¿Qué aspectos del servicio fueron de tu agrado?',
contador_opcion: 3,
obligatoria: false,
id_tipo_pregunta: 3, // Puedes usar este ID para distinguir múltiples
id_opcion_dependiente: null,
tipo_pregunta: {
id_tipo: 3,
tipo_pregunta: 'Multiple',
},
opciones: [
{
id_pregunta_opcion: 50006,
posicion: 1,
id_opcion: 90006,
opcion: {
id_opcion: 90006,
opcion: 'Rapidez',
},
},
{
id_pregunta_opcion: 50007,
posicion: 2,
id_opcion: 90007,
opcion: {
id_opcion: 90007,
opcion: 'Atención al cliente',
},
},
{
id_pregunta_opcion: 50008,
posicion: 3,
id_opcion: 90008,
opcion: {
id_opcion: 90008,
opcion: 'Facilidad de uso',
},
},
],
},
},
],
},
],
},
};
+46
View File
@@ -0,0 +1,46 @@
// Script para cargar un formulario desde un archivo JSON
const axios = require('axios');
const fs = require('fs');
const path = require('path');
const API_URL = 'http://localhost:4200';
async function loadFormFromFile(filePath) {
try {
// Leer el archivo JSON
const formData = require(filePath);
console.log('Enviando datos del formulario al servidor...');
console.log('Datos:', JSON.stringify(formData, null, 2));
// Enviar los datos al endpoint
const response = await axios.post(`${API_URL}/cuestionario`, formData);
console.log('Formulario creado exitosamente:');
console.log(JSON.stringify(response.data, null, 2));
return response.data;
} catch (error) {
console.error('Error al crear el formulario:');
if (error.response) {
console.error('Status:', error.response.status);
console.error('Data:', JSON.stringify(error.response.data, null, 2));
} else {
console.error(error.message);
}
throw error;
}
}
// Verificar si se proporcionó un argumento de archivo
const args = process.argv.slice(2);
const filePath = args[0] || './crear_formulario_feria';
// Ejecutar la función
loadFormFromFile(filePath)
.then(() => console.log('Proceso completado'))
.catch(() => {
console.log('Proceso fallido');
process.exit(1);
});
View File
View File
View File
+21
View File
@@ -0,0 +1,21 @@
const comunidad = {
id_formulario: 1,
respuestas: [
{ id_pregunta: 101, valor: 'Si' }, // ¿Eres parte de la comunidad de la FES Acatlán?
{ id_pregunta: 102, valor: '123456789' }, // Numero de cuenta
{ id_pregunta: 106, valor: 'Prefiero no decirlo' },
],
fecha_envio: '2025-04-02T13:45:00',
};
const externos = {
id_formulario: 1,
respuestas: [
{ id_pregunta: 101, valor: 'No' }, // ¿Eres parte de la comunidad de la FES Acatlán?
{ id_pregunta: 103, valor: 'Juan Carlos' }, // Nombre(s)
{ id_pregunta: 104, valor: 'Ramírez López' }, // Apellidos
{ id_pregunta: 106, valor: 'Prefiero no decirlo' }, // genero
{ id_pregunta: 107, valor: 'UAM Azcapotzalco' }, // Institución de procedencia
],
fecha_envio: '2025-04-02T13:45:00',
};
+28
View File
@@ -0,0 +1,28 @@
import { feriaSexualidad } from './crear_formulario_feria';
import axios from 'axios';
// Asumiendo que el servidor está corriendo en localhost:4200
const API_URL = 'http://localhost:4200';
async function crearFormulario() {
try {
const response = await axios.post(`${API_URL}/cuestionario`, feriaSexualidad);
console.log('Formulario creado exitosamente:');
console.log(JSON.stringify(response.data, null, 2));
return response.data;
} catch (error) {
console.error('Error al crear el formulario:');
if (error.response) {
console.error('Status:', error.response.status);
console.error('Data:', error.response.data);
} else {
console.error(error.message);
}
throw error;
}
}
// Ejecutar la función
crearFormulario()
.then(() => console.log('Proceso completado'))
.catch(() => console.log('Proceso fallido'));