From d3e1441f87aa892653c24088f27ae15ca204b195 Mon Sep 17 00:00:00 2001 From: miguel Date: Sun, 8 Mar 2026 19:18:23 -0600 Subject: [PATCH 1/2] feat: add bulk user upload functionality and form success page - Implemented bulk upload for students and workers with file validation and error handling. - Added Excel template download feature for user uploads. - Created success page for form submissions with QR code display. - Established context and reducer for form state management, including user search and response handling. - Added mermaid diagrams for event registration documentation. --- next.config.ts | 6 +- package-lock.json | 90 ++++ public/plantillas/carga-alumnos.xlsx | Bin 0 -> 5724 bytes public/plantillas/carga-trabajadores.xlsx | Bin 0 -> 6052 bytes .../user/carga_masiva_usuarios/page.tsx | 224 ++++++++ .../[id_evento]/formularios/crear/page.tsx | 18 +- .../(admins)/user/evento/[id_evento]/page.tsx | 93 +++- src/components/navbar.tsx | 4 + src/containers/create-formulario.tsx | 7 + .../formulario/formulario-exito.tsx | 132 +++++ .../formulario/formulario-registro.tsx | 500 +++++------------- src/containers/pages/formulario-page.tsx | 143 +---- src/context/formulario/formulario-context.tsx | 330 ++++++++++++ src/context/formulario/formulario-reducer.ts | 152 ++++++ src/context/formulario/index.ts | 2 + src/data/plantillas.ts | 126 ++++- src/types/create-formulario.d.ts | 1 + 17 files changed, 1320 insertions(+), 508 deletions(-) create mode 100644 public/plantillas/carga-alumnos.xlsx create mode 100644 public/plantillas/carga-trabajadores.xlsx create mode 100644 src/app/(admins)/user/carga_masiva_usuarios/page.tsx create mode 100644 src/containers/formulario/formulario-exito.tsx create mode 100644 src/context/formulario/formulario-context.tsx create mode 100644 src/context/formulario/formulario-reducer.ts create mode 100644 src/context/formulario/index.ts diff --git a/next.config.ts b/next.config.ts index db91768..b8119aa 100644 --- a/next.config.ts +++ b/next.config.ts @@ -6,12 +6,12 @@ const nextConfig: NextConfig = { { protocol: 'http', hostname: 'localhost', - port: '3342', //4200 + port: '4200', //4200 }, { protocol: 'https', - hostname: 'venus.acatlan.unam.mx' - } + hostname: 'venus.acatlan.unam.mx', + }, ], }, }; diff --git a/package-lock.json b/package-lock.json index b168c61..82e1a46 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7403,6 +7403,96 @@ "peerDependencies": { "@types/emscripten": ">=1.39.6" } + }, + "node_modules/@next/swc-darwin-arm64": { + "version": "15.5.12", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.5.12.tgz", + "integrity": "sha512-RnRjBtH8S8eXCpUNkQ+543DUc7ys8y15VxmFU9HRqlo9BG3CcBUiwNtF8SNoi2xvGCVJq1vl2yYq+3oISBS0Zg==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-darwin-x64": { + "version": "15.5.12", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.5.12.tgz", + "integrity": "sha512-nqa9/7iQlboF1EFtNhWxQA0rQstmYRSBGxSM6g3GxvxHxcoeqVXfGNr9stJOme674m2V7r4E3+jEhhGvSQhJRA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-gnu": { + "version": "15.5.12", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.5.12.tgz", + "integrity": "sha512-dCzAjqhDHwmoB2M4eYfVKqXs99QdQxNQVpftvP1eGVppamXh/OkDAwV737Zr0KPXEqRUMN4uCjh6mjO+XtF3Mw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-musl": { + "version": "15.5.12", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.5.12.tgz", + "integrity": "sha512-+fpGWvQiITgf7PUtbWY1H7qUSnBZsPPLyyq03QuAKpVoTy/QUx1JptEDTQMVvQhvizCEuNLEeghrQUyXQOekuw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-arm64-msvc": { + "version": "15.5.12", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.5.12.tgz", + "integrity": "sha512-xhsL1OvQSfGmlL5RbOmU+FV120urrgFpYLq+6U8C6KIym32gZT6XF/SDE92jKzzlPWskkbjOKCpqk5m4i8PEfg==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-x64-msvc": { + "version": "15.5.12", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.5.12.tgz", + "integrity": "sha512-Z1Dh6lhFkxvBDH1FoW6OU/L6prYwPSlwjLiZkExIAh8fbP6iI/M7iGTQAJPYJ9YFlWobCZ1PHbchFhFYb2ADkw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } } } } diff --git a/public/plantillas/carga-alumnos.xlsx b/public/plantillas/carga-alumnos.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..828408173de32d1e7db8ddf646d43753571db7b8 GIT binary patch literal 5724 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 OZlI4KbQA9Q>Gdy;daQu} literal 0 HcmV?d00001 diff --git a/public/plantillas/carga-trabajadores.xlsx b/public/plantillas/carga-trabajadores.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..33f6470e7f324be1ba09ddca8c4c3b46d2b5ea31 GIT binary patch literal 6052 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 EU*&2W!vFvP literal 0 HcmV?d00001 diff --git a/src/app/(admins)/user/carga_masiva_usuarios/page.tsx b/src/app/(admins)/user/carga_masiva_usuarios/page.tsx new file mode 100644 index 0000000..bc80b3b --- /dev/null +++ b/src/app/(admins)/user/carga_masiva_usuarios/page.tsx @@ -0,0 +1,224 @@ +'use client'; + +import React, { useRef, useState } from 'react'; +import axiosInstance from '@/utils/api-config'; +import { Button, Form, Alert } from 'react-bootstrap'; +import { FaUpload, FaDownload } from 'react-icons/fa'; + +type TipoCarga = 'alumnos' | 'trabajadores'; + +interface ResultadoCarga { + insertados: number; + omitidos: number; +} + +interface SeccionState { + archivo: File | null; + loading: boolean; + error: string | null; + success: string | null; + resultado: ResultadoCarga | null; +} + +const initialState: SeccionState = { + archivo: null, + loading: false, + error: null, + success: null, + resultado: null, +}; + +export default function Page() { + const [alumnos, setAlumnos] = useState(initialState); + const [trabajadores, setTrabajadores] = useState(initialState); + + const alumnosRef = useRef(null); + const trabajadoresRef = useRef(null); + + const getState = (tipo: TipoCarga) => + tipo === 'alumnos' ? alumnos : trabajadores; + const setState = (tipo: TipoCarga) => + tipo === 'alumnos' ? setAlumnos : setTrabajadores; + + const validarExt = (file: File | null): boolean => { + if (!file) return false; + return /\.xlsx$/i.test(file.name); + }; + + const handleArchivoChange = (tipo: TipoCarga, file: File | null) => { + if (file && !validarExt(file)) { + setState(tipo)((prev) => ({ + ...prev, + archivo: null, + error: 'Selecciona un archivo .xlsx válido', + success: null, + })); + return; + } + setState(tipo)((prev) => ({ + ...prev, + archivo: file, + error: null, + success: null, + })); + }; + + const handleCargaMasiva = async (tipo: TipoCarga) => { + const estado = getState(tipo); + if (!estado.archivo) { + setState(tipo)((prev) => ({ + ...prev, + error: 'Selecciona un archivo xlsx', + })); + return; + } + const formData = new FormData(); + formData.append('file', estado.archivo); + setState(tipo)((prev) => ({ + ...prev, + loading: true, + error: null, + success: null, + })); + try { + const res = await axiosInstance.post(`/${tipo}/cargar`, formData, { + headers: { 'Content-Type': 'multipart/form-data' }, + }); + setState(tipo)((prev) => ({ + ...prev, + archivo: null, + success: `${tipo === 'alumnos' ? 'Alumnos' : 'Trabajadores'} cargados exitosamente`, + resultado: res.data ?? null, + })); + const ref = tipo === 'alumnos' ? alumnosRef : trabajadoresRef; + if (ref.current) ref.current.value = ''; + } catch (error) { + const err = error as { response?: { data?: { message?: string } } }; + setState(tipo)((prev) => ({ + ...prev, + error: err?.response?.data?.message || 'Error al cargar el archivo', + })); + } finally { + setState(tipo)((prev) => ({ ...prev, loading: false })); + } + }; + + const handleDescargarPlantilla = (tipo: TipoCarga) => { + const nombre = tipo === 'alumnos' ? 'carga-alumnos' : 'carga-trabajadores'; + const link = document.createElement('a'); + link.href = `/plantillas/${nombre}.xlsx`; + link.setAttribute('download', `${nombre}.xlsx`); + document.body.appendChild(link); + link.click(); + link.remove(); + }; + + const SeccionCarga = ({ + tipo, + label, + }: { + tipo: TipoCarga; + label: string; + }) => { + const estado = getState(tipo); + const inputRef = tipo === 'alumnos' ? alumnosRef : trabajadoresRef; + + return ( +
+

{label}

+ + {estado.error && ( + setState(tipo)((p) => ({ ...p, error: null }))} + dismissible + > + {estado.error} + + )} + {estado.success && ( + + setState(tipo)((p) => ({ ...p, success: null, resultado: null })) + } + dismissible + > +

{estado.success}

+ {estado.resultado && ( +
    +
  • + Insertados: {estado.resultado.insertados} +
  • +
  • + Omitidos: {estado.resultado.omitidos} +
  • +
+ )} +
+ )} + + + Archivo Excel (.xlsx) +
inputRef.current?.click()} + > + +

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

+
+ + handleArchivoChange(tipo, e.target.files?.[0] ?? null) + } + /> +
+ +
+ + + +
+
+ ); + }; + + return ( +
+
+
+
+

Carga masiva de usuarios

+

+ Sube un archivo Excel para registrar alumnos o trabajadores en el + sistema. +

+
+
+
+ + + +
+ ); +} diff --git a/src/app/(admins)/user/evento/[id_evento]/formularios/crear/page.tsx b/src/app/(admins)/user/evento/[id_evento]/formularios/crear/page.tsx index f8fafba..3861fd1 100644 --- a/src/app/(admins)/user/evento/[id_evento]/formularios/crear/page.tsx +++ b/src/app/(admins)/user/evento/[id_evento]/formularios/crear/page.tsx @@ -1,7 +1,6 @@ 'use client'; import { plantillasDisponibles } from '@/data/plantillas'; import { FormularioCreacion } from '@/types/create-formulario'; -import Image from 'next/image'; import React, { useState } from 'react'; import CreateFormulario from '@/containers/create-formulario'; import FormularioCardPreview from '@/components/formulario/formulario-card-preview'; @@ -193,21 +192,18 @@ export default function Page() {

-
+
{plantillasDisponibles.map((plantilla) => (
handleSeleccionarPlantilla(plantilla.id)} > -
- {`Plantilla +
+ {plantilla.nombre}
))} diff --git a/src/app/(admins)/user/evento/[id_evento]/page.tsx b/src/app/(admins)/user/evento/[id_evento]/page.tsx index 129796c..249e487 100644 --- a/src/app/(admins)/user/evento/[id_evento]/page.tsx +++ b/src/app/(admins)/user/evento/[id_evento]/page.tsx @@ -27,6 +27,40 @@ export default function Page() { {} ); + type TipoCarga = 'alumnos' | 'trabajadores'; + const [tipoCarga, setTipoCarga] = useState('alumnos'); + const [archivoCarga, setArchivoCarga] = useState(null); + const [loadingCarga, setLoadingCarga] = useState(false); + + const handleCargaMasiva = async () => { + if (!archivoCarga) { + toast.error('Selecciona un archivo xlsx'); + return; + } + const formData = new FormData(); + formData.append('file', archivoCarga); + formData.append('id_evento', params.id_evento); + setLoadingCarga(true); + try { + await axiosInstance.post( + `/evento/${params.id_evento}/usuarios`, + formData, + { + headers: { 'Content-Type': 'multipart/form-data' }, + } + ); + toast.success( + `${tipoCarga === 'alumnos' ? 'Alumnos' : 'Trabajadores'} cargados exitosamente` + ); + setArchivoCarga(null); + } catch (error) { + const err = error as { response?: { data?: { message?: string } } }; + toast.error(err?.response?.data?.message || 'Error al cargar el archivo'); + } finally { + setLoadingCarga(false); + } + }; + const handleChange = ( field: keyof CreateEventoType, value: string | Date @@ -88,8 +122,61 @@ export default function Page() { handleOnChange={handleEventoActualizado} /> + {/* Carga masiva de alumnos o trabajadores */} + {tipo_usuario !== 'staff' && ( +
+

Carga masiva de participantes

+

+ Sube un archivo .xlsx para registrar los + participantes que podrán inscribirse a este evento. Solo los + usuarios incluidos en la lista tendrán acceso al registro. +

+ +
+
+ +
+ + +
+
+ +
+ + setArchivoCarga(e.target.files?.[0] ?? null)} + /> +
+ + +
+
+ )} + {/* Validacion del tipo de usuario */} - { tipo_usuario !== "staff" && ( + {tipo_usuario !== 'staff' && (
@@ -103,7 +190,9 @@ export default function Page() { icon="plus" variant="primary" onClick={() => - router.push(`/user/evento/${params.id_evento}/formularios/crear`) + router.push( + `/user/evento/${params.id_evento}/formularios/crear` + ) } > Crear nuevo formulario diff --git a/src/components/navbar.tsx b/src/components/navbar.tsx index 8f8b3fc..84d51ee 100644 --- a/src/components/navbar.tsx +++ b/src/components/navbar.tsx @@ -29,6 +29,10 @@ const Navbar: React.FC = () => { href: '/user/eventos/carga_masiva_banner', label: 'Carga Masiva Imagenes', }, + { + href: '/user/carga_masiva_usuarios', + label: 'Carga Masiva Usuarios', + }, ], }, { href: '/user/usuarios', label: 'Usuarios' }, diff --git a/src/containers/create-formulario.tsx b/src/containers/create-formulario.tsx index eb8c981..9d92dc1 100644 --- a/src/containers/create-formulario.tsx +++ b/src/containers/create-formulario.tsx @@ -214,6 +214,13 @@ export default function FormularioEditor({
)} + actualizarCampo('mensaje_correo', e.target.value)} + placeholder="Mensaje que se enviará al correo del participante al registrarse" + /> +