From 4c300a8e2000b0fa4f4b19c90ddf24d2940d6324 Mon Sep 17 00:00:00 2001 From: santiago Date: Mon, 29 Sep 2025 12:13:38 -0600 Subject: [PATCH 1/8] Validacion en la asistencia del usuario --- next.config.ts | 2 +- src/app/(admins)/user/qr/page.tsx | 16 ++++++++++++---- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/next.config.ts b/next.config.ts index 7442e11..db91768 100644 --- a/next.config.ts +++ b/next.config.ts @@ -6,7 +6,7 @@ const nextConfig: NextConfig = { { protocol: 'http', hostname: 'localhost', - port: '4200', + port: '3342', //4200 }, { protocol: 'https', diff --git a/src/app/(admins)/user/qr/page.tsx b/src/app/(admins)/user/qr/page.tsx index 974a3f8..adafd84 100644 --- a/src/app/(admins)/user/qr/page.tsx +++ b/src/app/(admins)/user/qr/page.tsx @@ -61,10 +61,18 @@ export default function Page() { } ); - setParticipante(response.data.data.participante); - setMessage(response.data.message); - console.log('Asistencia registrada:', response.data); - toast.success('✅ Asistencia registrada correctamente'); + //Validacion + if(response.data.success === false) { + console.error("El usuario ya se registro"); + setMessage(response.data.message); + toast.error("❌ Error el usaurio ya se registro previamente.") + } else { + setParticipante(response.data.data.participante); + setMessage(response.data.message); + console.log('Asistencia registrada:', response.data); + toast.success('✅ Asistencia registrada correctamente'); + } + } catch (err) { console.error('Error en la petición:', err); setStatusMessage('❌ Error de red al registrar asistencia'); From 509ff4255a65f56833eb28c038e0d759ac80eb38 Mon Sep 17 00:00:00 2001 From: santiago Date: Wed, 1 Oct 2025 14:14:36 -0600 Subject: [PATCH 2/8] Modificaciones en error al escanear codigo qr --- package-lock.json | 229 +++++++++++++++++- package.json | 4 +- .../user/eventos/carga_masiva/page.tsx | 93 +++++++ .../user/eventos/carga_masiva_banner/page.tsx | 7 + src/app/(admins)/user/qr/page.tsx | 105 ++++---- src/components/navbar.tsx | 72 ++++-- 6 files changed, 435 insertions(+), 75 deletions(-) create mode 100644 src/app/(admins)/user/eventos/carga_masiva/page.tsx create mode 100644 src/app/(admins)/user/eventos/carga_masiva_banner/page.tsx diff --git a/package-lock.json b/package-lock.json index d03e6fb..6b647cb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,13 +11,15 @@ "@uiw/react-md-editor": "^4.0.7", "@yudiel/react-qr-scanner": "^2.2.1", "axios": "^1.8.4", - "bootstrap": "^5.3.3", + "bootstrap": "^5.3.8", "bootstrap-icons": "^1.11.3", "js-cookie": "^3.0.5", "next": "15.3.3", "react": "^19.0.0", + "react-bootstrap": "^2.10.10", "react-dom": "^19.0.0", "react-hot-toast": "^2.5.2", + "react-icons": "^5.5.0", "react-markdown": "^10.1.0", "xlsx": "^0.18.5" }, @@ -891,6 +893,75 @@ "url": "https://opencollective.com/popperjs" } }, + "node_modules/@react-aria/ssr": { + "version": "3.9.10", + "resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.9.10.tgz", + "integrity": "sha512-hvTm77Pf+pMBhuBm760Li0BVIO38jv1IBws1xFm1NoL26PU+fe+FMW5+VZWyANR6nYL65joaJKZqOdTQMkO9IQ==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + }, + "engines": { + "node": ">= 12" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@restart/hooks": { + "version": "0.4.16", + "resolved": "https://registry.npmjs.org/@restart/hooks/-/hooks-0.4.16.tgz", + "integrity": "sha512-f7aCv7c+nU/3mF7NWLtVVr0Ra80RqsO89hO72r+Y/nvQr5+q0UFGkocElTH6MJApvReVh6JHUFYn2cw1WdHF3w==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.3" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@restart/ui": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@restart/ui/-/ui-1.9.4.tgz", + "integrity": "sha512-N4C7haUc3vn4LTwVUPlkJN8Ach/+yIMvRuTVIhjilNHqegY60SGLrzud6errOMNJwSnmYFnt1J0H/k8FE3A4KA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.26.0", + "@popperjs/core": "^2.11.8", + "@react-aria/ssr": "^3.5.0", + "@restart/hooks": "^0.5.0", + "@types/warning": "^3.0.3", + "dequal": "^2.0.3", + "dom-helpers": "^5.2.0", + "uncontrollable": "^8.0.4", + "warning": "^4.0.3" + }, + "peerDependencies": { + "react": ">=16.14.0", + "react-dom": ">=16.14.0" + } + }, + "node_modules/@restart/ui/node_modules/@restart/hooks": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@restart/hooks/-/hooks-0.5.1.tgz", + "integrity": "sha512-EMoH04NHS1pbn07iLTjIjgttuqb7qu4+/EyhAx27MHpoENcB2ZdSsLTNxmKD+WEPnZigo62Qc8zjGnNxoSE/5Q==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.3" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@restart/ui/node_modules/uncontrollable": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-8.0.4.tgz", + "integrity": "sha512-ulRWYWHvscPFc0QQXvyJjY6LIXU56f0h8pQFvhxiKk5V1fcI8gp9Ht9leVAhrVjzqMw0BgjspBINx9r6oyJUvQ==", + "license": "MIT", + "peerDependencies": { + "react": ">=16.14.0" + } + }, "node_modules/@rtsao/scc": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", @@ -1032,6 +1103,12 @@ "integrity": "sha512-AUZTa7hQ2KY5L7AmtSiqxlhWxb4ina0yd8hNbl4TWuqnv/pFP0nDMb3YrfSBf4hJVGLh2YEIBfKaBW/9UEl6IQ==", "license": "MIT" }, + "node_modules/@types/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "license": "MIT" + }, "node_modules/@types/react": { "version": "19.0.12", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.0.12.tgz", @@ -1051,12 +1128,27 @@ "@types/react": "^19.0.0" } }, + "node_modules/@types/react-transition-group": { + "version": "4.4.12", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz", + "integrity": "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*" + } + }, "node_modules/@types/unist": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", "license": "MIT" }, + "node_modules/@types/warning": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/warning/-/warning-3.0.3.tgz", + "integrity": "sha512-D1XC7WK8K+zZEveUPY+cf4+kgauk8N4eHr/XIHXGlGYkHLud6hK9lYfZk1ry1TNh798cZUCgb6MqGEG8DkJt6Q==", + "license": "MIT" + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.29.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.29.0.tgz", @@ -2002,9 +2094,9 @@ "license": "ISC" }, "node_modules/bootstrap": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.3.tgz", - "integrity": "sha512-8HLCdWgyoMguSO9o+aH+iuZ+aht+mzW0u3HIMzVu7Srrpv7EBBxTnrFlSCskwdY1+EOFQSm7uMJhNQHkdPcmjg==", + "version": "5.3.8", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.8.tgz", + "integrity": "sha512-HP1SZDqaLDPwsNiqRqi5NcP0SSXciX2s9E+RyqJIIqGo+vJeN5AJVM98CXmW/Wux0nQ5L7jeWUdplCEf0Ee+tg==", "funding": [ { "type": "github", @@ -2268,6 +2360,12 @@ "node": ">= 6" } }, + "node_modules/classnames": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==", + "license": "MIT" + }, "node_modules/client-only": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", @@ -2607,6 +2705,16 @@ "node": ">=0.10.0" } }, + "node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -4083,6 +4191,15 @@ "node": ">= 0.4" } }, + "node_modules/invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, "node_modules/is-alphabetical": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", @@ -4592,7 +4709,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true, "license": "MIT" }, "node_modules/js-yaml": { @@ -4739,7 +4855,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "dev": true, "license": "MIT", "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" @@ -5791,7 +5906,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -6132,7 +6246,6 @@ "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", - "dev": true, "license": "MIT", "dependencies": { "loose-envify": "^1.4.0", @@ -6140,6 +6253,19 @@ "react-is": "^16.13.1" } }, + "node_modules/prop-types-extra": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/prop-types-extra/-/prop-types-extra-1.1.1.tgz", + "integrity": "sha512-59+AHNnHYCdiC+vMwY52WmvP5dM3QLeoumYuEyceQDi9aEhtwN9zIQ2ZNo25sMyXnbh32h+P1ezDsUpUH3JAew==", + "license": "MIT", + "dependencies": { + "react-is": "^16.3.2", + "warning": "^4.0.0" + }, + "peerDependencies": { + "react": ">=0.14.0" + } + }, "node_modules/property-information": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", @@ -6196,6 +6322,37 @@ "node": ">=0.10.0" } }, + "node_modules/react-bootstrap": { + "version": "2.10.10", + "resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-2.10.10.tgz", + "integrity": "sha512-gMckKUqn8aK/vCnfwoBpBVFUGT9SVQxwsYrp9yDHt0arXMamxALerliKBxr1TPbntirK/HGrUAHYbAeQTa9GHQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.24.7", + "@restart/hooks": "^0.4.9", + "@restart/ui": "^1.9.4", + "@types/prop-types": "^15.7.12", + "@types/react-transition-group": "^4.4.6", + "classnames": "^2.3.2", + "dom-helpers": "^5.2.1", + "invariant": "^2.2.4", + "prop-types": "^15.8.1", + "prop-types-extra": "^1.1.0", + "react-transition-group": "^4.4.5", + "uncontrollable": "^7.2.1", + "warning": "^4.0.3" + }, + "peerDependencies": { + "@types/react": ">=16.14.8", + "react": ">=16.14.0", + "react-dom": ">=16.14.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/react-dom": { "version": "19.1.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz", @@ -6225,11 +6382,25 @@ "react-dom": ">=16" } }, + "node_modules/react-icons": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz", + "integrity": "sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==", + "license": "MIT", + "peerDependencies": { + "react": "*" + } + }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "dev": true, + "license": "MIT" + }, + "node_modules/react-lifecycles-compat": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", + "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==", "license": "MIT" }, "node_modules/react-markdown": { @@ -6259,6 +6430,22 @@ "react": ">=18" } }, + "node_modules/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "license": "BSD-3-Clause", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } + }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -7509,6 +7696,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/uncontrollable": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-7.2.1.tgz", + "integrity": "sha512-svtcfoTADIB0nT9nltgjujTi7BzVmwjZClOmskKu/E8FW9BXzg9os8OLr4f8Dlnk0rYWJIWr4wv9eKUXiQvQwQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.6.3", + "@types/react": ">=16.9.11", + "invariant": "^2.2.4", + "react-lifecycles-compat": "^3.0.4" + }, + "peerDependencies": { + "react": ">=15.0.0" + } + }, "node_modules/undici-types": { "version": "6.19.8", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", @@ -7693,6 +7895,15 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/warning": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", + "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, "node_modules/web-namespaces": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz", diff --git a/package.json b/package.json index 883d394..01e72b8 100644 --- a/package.json +++ b/package.json @@ -14,13 +14,15 @@ "@uiw/react-md-editor": "^4.0.7", "@yudiel/react-qr-scanner": "^2.2.1", "axios": "^1.8.4", - "bootstrap": "^5.3.3", + "bootstrap": "^5.3.8", "bootstrap-icons": "^1.11.3", "js-cookie": "^3.0.5", "next": "15.3.3", "react": "^19.0.0", + "react-bootstrap": "^2.10.10", "react-dom": "^19.0.0", "react-hot-toast": "^2.5.2", + "react-icons": "^5.5.0", "react-markdown": "^10.1.0", "xlsx": "^0.18.5" }, diff --git a/src/app/(admins)/user/eventos/carga_masiva/page.tsx b/src/app/(admins)/user/eventos/carga_masiva/page.tsx new file mode 100644 index 0000000..e8c1b2d --- /dev/null +++ b/src/app/(admins)/user/eventos/carga_masiva/page.tsx @@ -0,0 +1,93 @@ +'use client' +import axiosInstance from "@/utils/api-config"; +import React, { useState } from "react"; +import { Button, Form } from "react-bootstrap"; +import { FaUpload } from "react-icons/fa"; + +export default function CargaMasiva() { + const [xlsx, setXlsx] = useState(null) + + const validarExt = (file: File | null) => { + if (!file) return + const extPermitidas = /(.xlsx)$/i + if (!extPermitidas.exec(file.name)) { + console.assert({ message: "Asegurate de ingresar un archivo .xlsx"}) + setXlsx(null) + } else { + setXlsx(file) + } + } + + const handleFileChange = (e: React.ChangeEvent) => { + const file = e.target.files?.[0] || null; + validarExt(file); + } + + const enviarCargaMasiva = async () => { + if (!xlsx) return; + + const formData = new FormData(); + formData.append("file", xlsx); + + try { + const res = await axiosInstance.post( + "/cuestionario/carga-masiva", + formData, + { + headers: { "Content-Type": "multipart/form-data" } + } + ); + console.log("Carga masiva exitosa", res.data); + } catch (error) { + console.error("Carga masiva fallida", error); + } + }; + + + return ( +
+
+
+
+

Carga Masiva de Eventos

+

+ Sube tu archivo excel para poder crear los ventos. +

+
+
+
+ + + Archivo Excel +
document.getElementById("xlsxInput")?.click()} + > + +

+ {xlsx?.name || 'Arrastra aquí tu archivo o da click aquí para buscar'} +

+

Tamaño máximo 20MB

+

Si al momento de elegir uin archivo este no se selecciona, haga click en cancelar en la ventana emergente e intente de nuevo.

+
+ +
+ +
+ +
+
+ ); +} diff --git a/src/app/(admins)/user/eventos/carga_masiva_banner/page.tsx b/src/app/(admins)/user/eventos/carga_masiva_banner/page.tsx new file mode 100644 index 0000000..efca5a4 --- /dev/null +++ b/src/app/(admins)/user/eventos/carga_masiva_banner/page.tsx @@ -0,0 +1,7 @@ +export default function CargaMasivaBanner() { + return ( +
+ Hola mundo +
+ ); +} \ No newline at end of file diff --git a/src/app/(admins)/user/qr/page.tsx b/src/app/(admins)/user/qr/page.tsx index adafd84..903ad65 100644 --- a/src/app/(admins)/user/qr/page.tsx +++ b/src/app/(admins)/user/qr/page.tsx @@ -23,34 +23,37 @@ export default function Page() { console.log('QR Escaneado:', rawValue); if (!enableScan) return; - try { - if (rawValue) { - try { - await axiosInstance.post( - `/participante-evento/decode-token`, { - token: rawValue - } - ); + if (!rawValue) { + setStatusMessage('⚠️ El QR no contiene los campos requeridos'); + return; + } - setToken(rawValue); - setTokenValid(true); - setShowModal(true); - setEnableScan(false); - } catch (err) { - console.warn('Participante no registrado en el cuestionario:', err); - setStatusMessage( - '❌ El participante no está registrado en este evento.' - ); - } + try { + const response = await axiosInstance.post('/participante-evento/decode-token', { token: rawValue }); + + if (response.data.success) { + // Token válido + setToken(rawValue); + setTokenValid(true); + setShowModal(true); + setEnableScan(false); } else { - setStatusMessage('⚠️ El QR no contiene los campos requeridos'); + // Token caducado o cualquier otro error + setStatusMessage(`❌ Error el codigo qr ya caduco`); + setTokenValid(false); + setShowModal(true); // mostramos modal con el mensaje de error + setEnableScan(false); } } catch (err) { - console.error('QR malformado:', err); - setStatusMessage('❌ Error al leer el QR: formato inválido'); + console.error('Error al decodificar el QR:', err); + setStatusMessage('❌ Error al leer el QR'); + setTokenValid(false); + setShowModal(true); + setEnableScan(false); } }; + const handleConfirm = async () => { if (!tokenValid) return; @@ -237,40 +240,50 @@ export default function Page() {
{tokenValid ? ( - <> -
+ // Modal de confirmación de asistencia +
+ + +
+ ) : ( + // Modal de error +
+
+ +
+
{statusMessage}
+
-
- - ) : ( -
-
- -
-
QR inválido o malformado
)}
diff --git a/src/components/navbar.tsx b/src/components/navbar.tsx index 8a1c606..a7a4bfd 100644 --- a/src/components/navbar.tsx +++ b/src/components/navbar.tsx @@ -3,26 +3,43 @@ import React from 'react'; import Link from 'next/link'; import { usePathname } from 'next/navigation'; +type NavChild = { + href: string; // siempre string + label: string; +}; + +type NavItem = + | { href: string; label: string } // items normales + | { label: string; children: NavChild[] } // items con submenu + + const Navbar: React.FC = () => { const pathname = usePathname(); const tipo_usuario = sessionStorage.getItem('axxxd')?.replace(/"/g, '') || ''; - const navItemsAdmin = [ + const navItemsAdmin: NavItem[] = [ { href: '/', label: 'Inicio' }, { href: '/user/eventos', label: 'Eventos' }, { href: '/user/qr', label: 'Lector de QRs' }, { href: '/user/eventos/crear', label: 'Crear Evento' }, + { + label: 'Carga Masiva', + children: [ + { href: '/user/eventos/carga_masiva', label: 'Carga Masiva Eventos' }, + { href: '/user/eventos/carga_masiva_banner', label: 'Carga Masiva Imagenes' }, + ], + }, { href: '/user/usuarios', label: 'Usuarios' }, { href: '/login', label: 'Cerrar sesión' }, ]; - const navItemsUser = [ + const navItemsUser: NavItem[] = [ { href: '/user/eventos', label: 'Eventos' }, { href: '/user/qr', label: 'Lector de QRs' }, { href: '/login', label: 'Cerrar sesión' }, ]; - const navItems = tipo_usuario === "administrador" ? navItemsAdmin : navItemsUser; + const navItems = tipo_usuario === 'administrador' ? navItemsAdmin : navItemsUser; return (
-

- Sistema de Registro de Eventos -

+

Sistema de Registro de Eventos

    {navItems.map((item, index) => ( -
  • - - {item.label} - +
  • + {'href' in item ? ( + + {item.label} + + ) : ( +
    + +
      + {item.children.map((child: NavChild, idx: number) => ( +
    • + + {child.label} + +
    • + ))} +
    +
    + )}
  • ))}
From 8f2ee2771166747f95c65304265dff21783201e1 Mon Sep 17 00:00:00 2001 From: santiago Date: Wed, 1 Oct 2025 15:21:02 -0600 Subject: [PATCH 3/8] Se agregaron las funcionalidades de subir eventos e imagenes en carga masiva --- .../user/eventos/carga_masiva_banner/page.tsx | 145 +++++++++++++++++- 1 file changed, 141 insertions(+), 4 deletions(-) diff --git a/src/app/(admins)/user/eventos/carga_masiva_banner/page.tsx b/src/app/(admins)/user/eventos/carga_masiva_banner/page.tsx index efca5a4..a0d0892 100644 --- a/src/app/(admins)/user/eventos/carga_masiva_banner/page.tsx +++ b/src/app/(admins)/user/eventos/carga_masiva_banner/page.tsx @@ -1,7 +1,144 @@ -export default function CargaMasivaBanner() { +"use client"; + +import React, { useState } from "react"; +import axios from "axios"; +import { Image } from "react-bootstrap"; + +//Para poder subir las imagenes +interface UploadedImage { + originalName: string; + filename: string; + path: string; +} + +export default function BannerUpload() { + const [files, setFiles] = useState([]); + const [loading, setLoading] = useState(false); + const [uploaded, setUploaded] = useState([]); + const [progress, setProgress] = useState(0); + + const handleFileChange = (e: React.ChangeEvent) => { + if (e.target.files) { + setFiles(Array.from(e.target.files)); + } + }; + + const handleUpload = async () => { + if (files.length === 0) { + alert("Selecciona al menos un archivo"); + return; + } + + const formData = new FormData(); + files.forEach((file) => { + formData.append("banners", file); + }); + + try { + setLoading(true); + setProgress(0); + + const res = await axios.post("/cuestionario/banners/bulk", formData, { + headers: { "Content-Type": "multipart/form-data" }, + onUploadProgress: (progressEvent) => { + if (progressEvent.total) { + setProgress( + Math.round((progressEvent.loaded * 100) / progressEvent.total) + ); + } + }, + }); + + setUploaded(res.data); + alert("Banners subidos correctamente ✅"); + setFiles([]); + } catch (error) { + console.error(error); + alert(error || "Error al subir archivos"); + } finally { + setLoading(false); + } + }; + return ( -
- Hola mundo +
+

Subir Banners

+ + {/* Área Drag & Drop */} + + + {/* Falta corregir el tamaño de la imagen */} + {/* Previsualización de imágenes */} + {files.length > 0 && ( +
+ {files.map((file, i) => ( +
+ {file.name} +

{file.name}

+
+ ))} +
+ )} + + {/* Botón Subir */} + + + {/* Barra de progreso */} + {loading && ( +
+
+
+ )} + + {/* Archivos subidos */} + {uploaded.length > 0 && ( +
+

Archivos subidos:

+
    + {uploaded.map((file, i) => ( +
  • + {file.originalName} + + Ver + +
  • + ))} +
+
+ )}
); -} \ No newline at end of file +} From fee9d92ed74efe3acf05414b4471c14b6640725f Mon Sep 17 00:00:00 2001 From: santiago Date: Thu, 2 Oct 2025 16:03:28 -0600 Subject: [PATCH 4/8] Modificaciones en las vistas de las cargas masivas --- .../user/eventos/carga_masiva/page.tsx | 90 +++++--- .../user/eventos/carga_masiva_banner/page.tsx | 198 ++++++++++-------- 2 files changed, 173 insertions(+), 115 deletions(-) diff --git a/src/app/(admins)/user/eventos/carga_masiva/page.tsx b/src/app/(admins)/user/eventos/carga_masiva/page.tsx index e8c1b2d..2f93bcf 100644 --- a/src/app/(admins)/user/eventos/carga_masiva/page.tsx +++ b/src/app/(admins)/user/eventos/carga_masiva/page.tsx @@ -1,49 +1,70 @@ 'use client' import axiosInstance from "@/utils/api-config"; import React, { useState } from "react"; -import { Button, Form } from "react-bootstrap"; +import { Button, Form, ProgressBar } from "react-bootstrap"; import { FaUpload } from "react-icons/fa"; export default function CargaMasiva() { - const [xlsx, setXlsx] = useState(null) + const [xlsx, setXlsx] = useState(null); + const [progress, setProgress] = useState(0); + const [loading, setLoading] = useState(false); const validarExt = (file: File | null) => { - if (!file) return - const extPermitidas = /(.xlsx)$/i + if (!file) return; + const extPermitidas = /(.xlsx)$/i; if (!extPermitidas.exec(file.name)) { - console.assert({ message: "Asegurate de ingresar un archivo .xlsx"}) - setXlsx(null) + console.error("Asegúrate de ingresar un archivo .xlsx"); + setXlsx(null); } else { - setXlsx(file) + setXlsx(file); } - } + }; const handleFileChange = (e: React.ChangeEvent) => { const file = e.target.files?.[0] || null; validarExt(file); - } + }; const enviarCargaMasiva = async () => { if (!xlsx) return; const formData = new FormData(); - formData.append("file", xlsx); + formData.append("file", xlsx); try { + setLoading(true); + setProgress(0); + const res = await axiosInstance.post( "/cuestionario/carga-masiva", formData, { - headers: { "Content-Type": "multipart/form-data" } + headers: { "Content-Type": "multipart/form-data" }, + onUploadProgress: (event) => { + if (event.total) { + const porcentaje = Math.round( + (event.loaded * 100) / event.total + ); + setProgress(porcentaje); + } + }, } ); + + //Falta hacer validacion + console.log("Carga masiva exitosa", res.data); + setTimeout(() => { + setProgress(100); + setLoading(false); + }, 500); } catch (error) { console.error("Carga masiva fallida", error); + setLoading(false); + setProgress(0); } }; - return (
@@ -51,7 +72,7 @@ export default function CargaMasiva() {

Carga Masiva de Eventos

- Sube tu archivo excel para poder crear los ventos. + Sube tu archivo excel para poder crear los eventos.

@@ -66,28 +87,47 @@ export default function CargaMasiva() { >

- {xlsx?.name || 'Arrastra aquí tu archivo o da click aquí para buscar'} + {xlsx?.name || + "Arrastra aquí tu archivo o da click aquí para buscar"}

Tamaño máximo 20MB

-

Si al momento de elegir uin archivo este no se selecciona, haga click en cancelar en la ventana emergente e intente de nuevo.

+

+ Si al momento de elegir un archivo este no se selecciona, + haga click en cancelar en la ventana emergente e intente de + nuevo. +

- + /> -
+
+ variant="outline-primary" + disabled={!xlsx || loading} + onClick={enviarCargaMasiva} + > + {loading ? "Subiendo..." : "Enviar archivo"} +
+ + {/* Barra de progreso */} + {loading && ( +
+ +
+ )}
); } diff --git a/src/app/(admins)/user/eventos/carga_masiva_banner/page.tsx b/src/app/(admins)/user/eventos/carga_masiva_banner/page.tsx index a0d0892..9edad3d 100644 --- a/src/app/(admins)/user/eventos/carga_masiva_banner/page.tsx +++ b/src/app/(admins)/user/eventos/carga_masiva_banner/page.tsx @@ -2,7 +2,9 @@ import React, { useState } from "react"; import axios from "axios"; -import { Image } from "react-bootstrap"; +import { Button, Image } from "react-bootstrap"; +import { FaUpload } from "react-icons/fa"; +import axiosInstance from "@/utils/api-config"; //Para poder subir las imagenes interface UploadedImage { @@ -35,110 +37,126 @@ export default function BannerUpload() { }); try { - setLoading(true); - setProgress(0); + setLoading(true); + setProgress(0); - const res = await axios.post("/cuestionario/banners/bulk", formData, { - headers: { "Content-Type": "multipart/form-data" }, - onUploadProgress: (progressEvent) => { - if (progressEvent.total) { - setProgress( - Math.round((progressEvent.loaded * 100) / progressEvent.total) - ); - } - }, - }); + const res = await axiosInstance.post("/cuestionario/banners/bulk", formData, { + headers: { "Content-Type": "multipart/form-data" }, + onUploadProgress: (progressEvent) => { + if (progressEvent.total) { + setProgress( + Math.round((progressEvent.loaded * 100) / progressEvent.total) + ); + } + }, + }); - setUploaded(res.data); - alert("Banners subidos correctamente ✅"); - setFiles([]); + //Falta hacer validacion + + setUploaded(res.data); + alert("Banners subidos correctamente ✅"); + setFiles([]); } catch (error) { - console.error(error); - alert(error || "Error al subir archivos"); + console.error(error); + alert(error || "Error al subir archivos"); } finally { - setLoading(false); + setLoading(false); } }; return ( -
-

Subir Banners

+
+
+
+
+

Subir Banners

+

+ Arrastra o selecciona imágenes para subir tus banners (jpg, png, webp). +

+
+
+
- {/* Área Drag & Drop */} -
+ + + + {/* Previsualización */} + {files.length > 0 && ( +
+ {files.map((file, i) => ( +
+ {file.name} - - - {/* Falta corregir el tamaño de la imagen */} - {/* Previsualización de imágenes */} - {files.length > 0 && ( -
- {files.map((file, i) => ( -
- {file.name} -

{file.name}

-
- ))} +

{file.name}

- )} + ))} +
+ )} - {/* Botón Subir */} - + {loading ? "Subiendo..." : "Subir Banners"} + +
- {/* Barra de progreso */} - {loading && ( -
-
-
- )} + {/* Barra de progreso */} + {loading && ( +
+
+
+ )} - {/* Archivos subidos */} - {uploaded.length > 0 && ( -
-

Archivos subidos:

-
    - {uploaded.map((file, i) => ( -
  • - {file.originalName} - - Ver - -
  • - ))} -
-
- )} + {/* Archivos subidos */} + {uploaded.length > 0 && ( +
+
Archivos subidos:
+
    + {uploaded.map((file, i) => ( +
  • + {file.originalName} + + Ver + +
  • + ))} +
+
+ )}
); } From 3123c9911a17c02bc5571689f6432ffa20cefe6f Mon Sep 17 00:00:00 2001 From: santiago Date: Thu, 2 Oct 2025 16:08:57 -0600 Subject: [PATCH 5/8] Modificaciones y correcion de errores --- src/app/(admins)/user/eventos/carga_masiva_banner/page.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/app/(admins)/user/eventos/carga_masiva_banner/page.tsx b/src/app/(admins)/user/eventos/carga_masiva_banner/page.tsx index 9edad3d..e644bc6 100644 --- a/src/app/(admins)/user/eventos/carga_masiva_banner/page.tsx +++ b/src/app/(admins)/user/eventos/carga_masiva_banner/page.tsx @@ -1,8 +1,7 @@ "use client"; import React, { useState } from "react"; -import axios from "axios"; -import { Button, Image } from "react-bootstrap"; +import { Button } from "react-bootstrap"; import { FaUpload } from "react-icons/fa"; import axiosInstance from "@/utils/api-config"; From 4ff66e81c7e5bb3701364621d4b1d32ce5553315 Mon Sep 17 00:00:00 2001 From: santiago Date: Thu, 2 Oct 2025 16:11:31 -0600 Subject: [PATCH 6/8] Modificaciones --- src/app/(admins)/user/eventos/carga_masiva_banner/page.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/(admins)/user/eventos/carga_masiva_banner/page.tsx b/src/app/(admins)/user/eventos/carga_masiva_banner/page.tsx index e644bc6..27d8d87 100644 --- a/src/app/(admins)/user/eventos/carga_masiva_banner/page.tsx +++ b/src/app/(admins)/user/eventos/carga_masiva_banner/page.tsx @@ -1,7 +1,7 @@ "use client"; import React, { useState } from "react"; -import { Button } from "react-bootstrap"; +import { Button, Image } from "react-bootstrap"; import { FaUpload } from "react-icons/fa"; import axiosInstance from "@/utils/api-config"; @@ -107,7 +107,7 @@ export default function BannerUpload() { className="border rounded overflow-hidden shadow-sm" style={{ width: "350px", height: "350px" }} > - {file.name} Date: Fri, 3 Oct 2025 14:45:35 -0600 Subject: [PATCH 7/8] Modificaciones --- src/app/(admins)/user/qr/page.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/app/(admins)/user/qr/page.tsx b/src/app/(admins)/user/qr/page.tsx index 903ad65..f5d785f 100644 --- a/src/app/(admins)/user/qr/page.tsx +++ b/src/app/(admins)/user/qr/page.tsx @@ -31,7 +31,7 @@ export default function Page() { try { const response = await axiosInstance.post('/participante-evento/decode-token', { token: rawValue }); - if (response.data.success) { + if (response.data) { // Token válido setToken(rawValue); setTokenValid(true); @@ -75,7 +75,6 @@ export default function Page() { console.log('Asistencia registrada:', response.data); toast.success('✅ Asistencia registrada correctamente'); } - } catch (err) { console.error('Error en la petición:', err); setStatusMessage('❌ Error de red al registrar asistencia'); From 9068cef62d1cafc0eed4d50831278a639fa2564f Mon Sep 17 00:00:00 2001 From: miguel Date: Sun, 5 Oct 2025 13:15:55 -0600 Subject: [PATCH 8/8] =?UTF-8?q?Implementar=20gesti=C3=B3n=20de=20usuarios?= =?UTF-8?q?=20con=20modales=20para=20agregar,=20editar=20y=20eliminar=20ad?= =?UTF-8?q?ministradores?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/(admins)/user/usuarios/page.tsx | 319 +++++++++++++++++++++++- 1 file changed, 313 insertions(+), 6 deletions(-) diff --git a/src/app/(admins)/user/usuarios/page.tsx b/src/app/(admins)/user/usuarios/page.tsx index 540c060..a4b791d 100644 --- a/src/app/(admins)/user/usuarios/page.tsx +++ b/src/app/(admins)/user/usuarios/page.tsx @@ -1,12 +1,158 @@ 'use client'; import Button from '@/components/button'; import Table, { Header } from '@/components/table'; +const Modal = dynamic(() => import('@/components/modal'), { ssr: false }); import { useGetApi } from '@/hooks/use-get-api'; import { Administrador } from '@/types/administradores'; -import React from 'react'; +import React, { useState } from 'react'; +import axiosInstance from '@/utils/api-config'; +import dynamic from 'next/dynamic'; export default function Page() { - const { data } = useGetApi('/administrador'); + const { data, setData } = useGetApi('/administrador'); + + // Estado del modal + const [isModalVisible, setIsModalVisible] = useState(false); + const [modalMode, setModalMode] = useState<'add' | 'edit'>('add'); + const [selectedUser, setSelectedUser] = useState(null); + + // Estado del modal de eliminación + const [isDeleteModalVisible, setIsDeleteModalVisible] = useState(false); + const [userToDelete, setUserToDelete] = useState(null); + + // Estado del formulario + const [formData, setFormData] = useState({ + nombre_usuario: '', + correo: '', + password: '', + id_tipo_user: '', + }); + + // Handlers del modal + const handleOpenAddModal = () => { + setModalMode('add'); + setSelectedUser(null); + setFormData({ + nombre_usuario: '', + correo: '', + password: '', + id_tipo_user: '', + }); + setIsModalVisible(true); + }; + + const handleOpenEditModal = (user: Administrador) => { + setModalMode('edit'); + setSelectedUser(user); + setFormData({ + nombre_usuario: user.nombre_usuario, + correo: user.correo, + password: '', + id_tipo_user: user.tipoUser.id.toString(), + }); + setIsModalVisible(true); + }; + + const handleCloseModal = () => { + setIsModalVisible(false); + setSelectedUser(null); + }; + + // Handlers del modal de eliminación + const handleOpenDeleteModal = (user: Administrador) => { + setUserToDelete(user); + setIsDeleteModalVisible(true); + }; + + const handleCloseDeleteModal = () => { + setIsDeleteModalVisible(false); + setUserToDelete(null); + }; + + const handleConfirmDelete = async () => { + // Aquí iría la lógica para eliminar el usuario + try { + if (userToDelete) { + // Lógica para eliminar el usuario + const response = await axiosInstance.delete( + `/administrador/${userToDelete.id_administrador}` + ); + + console.log('Usuario eliminado:', response.data); + // Actualizar la lista de usuarios en el estado + if (data) { + const updatedData = data.filter( + (user) => user.id_administrador !== userToDelete.id_administrador + ); + setData(updatedData); + } + } + } catch (error) { + console.error('Error al eliminar el usuario:', error); + } + handleCloseDeleteModal(); + }; + + // Handler para envío del formulario + const handleSubmitForm = async () => { + try { + if (modalMode === 'add') { + // Crear nuevo usuario + const payload = { + nombre_usuario: formData.nombre_usuario, + correo: formData.correo, + password: formData.password, + id_tipo_user: parseInt(formData.id_tipo_user), + }; + + const response = await axiosInstance.post('/administrador', payload); + console.log('Usuario creado:', response.data); + + // Actualizar la lista de usuarios + if (data) { + setData([ + ...data, + { + correo: formData.correo, + id_administrador: response.data.id_administrador, + nombre_usuario: formData.nombre_usuario, + tipoUser: { + id: parseInt(formData.id_tipo_user), + tipo: formData.id_tipo_user === '1' ? 'Administrador' : 'Staff', + }, + }, + ]); + } + } else if (modalMode === 'edit' && selectedUser) { + // Editar usuario existente + const payload = { + nombre_usuario: formData.nombre_usuario, + correo: formData.correo, + id_tipo_user: parseInt(formData.id_tipo_user), + }; + + const response = await axiosInstance.put( + `/administrador/${selectedUser.id_administrador}`, + payload + ); + console.log('Usuario actualizado:', response.data); + + // Actualizar la lista de usuarios + if (data) { + const updatedData = data.map((user) => + user.id_administrador === selectedUser.id_administrador + ? { ...user, ...response.data } + : user + ); + setData(updatedData); + } + } + + handleCloseModal(); + } catch (error) { + console.error('Error al guardar el usuario:', error); + } + }; const headers: Header[] = [ { key: 'id_administrador', label: 'ID' }, @@ -19,12 +165,16 @@ export default function Page() { }, { key: 'tipoUser', - render: () => ( + render: (_, row) => (
- -
@@ -37,9 +187,166 @@ export default function Page() {

Administradores

- +
{data && } + + +
+

+ {modalMode === 'add' + ? 'Agregar Nuevo Administrador' + : 'Editar Administrador'} +

+ + {modalMode === 'edit' && selectedUser && ( +
+

+ ID: {selectedUser.id_administrador} +

+

+ Usuario actual: {selectedUser.nombre_usuario} +

+

+ Correo actual: {selectedUser.correo} +

+

+ Tipo: {selectedUser.tipoUser.tipo} +

+
+ )} + +
+ + + setFormData((prev) => ({ + ...prev, + nombre_usuario: e.target.value, + })) + } + placeholder="Ingrese el nombre de usuario" + /> +
+ +
+ + + setFormData((prev) => ({ + ...prev, + correo: e.target.value, + })) + } + placeholder="Ingrese el correo electrónico" + /> +
+ + {modalMode === 'add' && ( +
+ + + setFormData((prev) => ({ + ...prev, + password: e.target.value, + })) + } + placeholder="Ingrese la contraseña" + /> +
+ )} + +
+ + +
+ +
+ + +
+
+
+ + {/* Modal de confirmación de eliminación */} + +
+
+ +
+ +

¿Está seguro de eliminar al usuario?

+ + {userToDelete && ( +
+

+ {userToDelete.nombre_usuario} +

+

Esta acción no se puede deshacer

+
+ )} + +
+ + +
+
+
); }