feat: implement formulario de registro with community and account validation, including dynamic response handling and submission logic

This commit is contained in:
jorge miguel
2025-09-09 19:24:35 -06:00
parent dc161cf269
commit dcb66ce2c1
8 changed files with 990 additions and 468 deletions
+3 -1
View File
@@ -1,6 +1,8 @@
'use client'
import dynamic from 'next/dynamic';
import Footer from '@/components/layout/footer';
import Header from '@/components/layout/header';
import Navbar from '@/components/navbar';
const Navbar = dynamic(() => import('@/components/navbar'), { ssr: false });
import React from 'react';
export default function Layout({ children }: { children: React.ReactNode }) {
+38 -43
View File
@@ -10,34 +10,30 @@ import toast from 'react-hot-toast';
const Modal = dynamic(() => import('@/components/modal'), { ssr: false });
export default function Page() {
const [scannedData, setScannedData] = useState<{
id_participante: number;
id_cuestionario: number;
} | null>(null);
const [showModal, setShowModal] = useState(false);
const [enableScan, setEnableScan] = useState(true);
const [statusMessage, setStatusMessage] = useState('');
const [participante, setParticipante] = useState('');
const [message, setMessage] = useState('');
const [token, setToken] = useState('');
const [tokenValid, setTokenValid] = useState(false);
const handleScan = async (rawValue: string) => {
console.log('QR Escaneado:', rawValue);
if (!enableScan) return;
try {
const data = JSON.parse(rawValue);
if (data.id_participante && data.id_cuestionario) {
if (rawValue) {
try {
const response = await axiosInstance.get(
`/participante-evento/${data.id_participante}/${data.id_cuestionario}`
await axiosInstance.post(
`/participante-evento/decode-token`, {
token: rawValue
}
);
setParticipante(response.data.participante.correo);
setScannedData({
id_participante: data.id_participante,
id_cuestionario: data.id_cuestionario,
});
setToken(rawValue);
setTokenValid(true);
setShowModal(true);
setEnableScan(false);
} catch (err) {
@@ -56,13 +52,17 @@ export default function Page() {
};
const handleConfirm = async () => {
if (!scannedData) return;
if (!tokenValid) return;
try {
const response = await axiosInstance.post(
`/participante-evento/asistencia/${scannedData.id_participante}/${scannedData.id_cuestionario}`
`/participante-evento/asistencia`, {
token: token
}
);
setParticipante(response.data.data.participante);
setMessage(response.data.message);
console.log('Asistencia registrada:', response.data);
toast.success('✅ Asistencia registrada correctamente');
} catch (err) {
@@ -72,7 +72,6 @@ export default function Page() {
setShowModal(false);
setEnableScan(true);
setScannedData(null);
};
return (
@@ -124,6 +123,24 @@ export default function Page() {
</div>
</div>
{participante && (
<div className="mb-4">
<div className="alert alert-success border-0 rounded-3 d-flex align-items-center">
<i className="bi bi-person-check-fill me-2"></i>
<span>Ultimo participante registrado: {participante}</span>
</div>
</div>
)}
{message && (
<div className="mb-4">
<div className="alert alert-info border-0 rounded-3 d-flex align-items-center">
<i className="bi bi-info-circle-fill me-2"></i>
<span>{message}</span>
</div>
</div>
)}
<Button
onClick={() => setEnableScan(false)}
variant="outline-danger"
@@ -195,7 +212,7 @@ export default function Page() {
onClose={() => {
setShowModal(false);
setEnableScan(true);
setScannedData(null);
setTokenValid(false);
}}
closeButton
className={{
@@ -211,31 +228,8 @@ export default function Page() {
</div>
<div className="modal-body p-4">
{scannedData ? (
{tokenValid ? (
<>
<div className="mb-4">
<div className="mb-3">
<i
className="bi bi-person-check-fill text-success"
style={{ fontSize: '3rem' }}
></i>
</div>
<h5 className="mb-3 fw-semibold">
Información del participante
</h5>
<div className="card bg-light border-0 rounded-3">
<div className="card-body py-3">
<div className="d-flex align-items-center justify-content-center">
<i className="bi bi-envelope-fill text-primary me-2"></i>
<strong className="me-2">Correo:</strong>
<span className="text-primary fw-medium">
{participante}
</span>
</div>
</div>
</div>
</div>
<div className="d-grid gap-2 d-md-flex justify-content-center">
<Button
variant="success"
@@ -250,7 +244,8 @@ export default function Page() {
onClick={() => {
setShowModal(false);
setEnableScan(true);
setScannedData(null);
setTokenValid(false);
setToken('');
}}
className="px-4 py-2 rounded-pill"
>
+4 -2
View File
@@ -34,13 +34,15 @@ export default function LoginForm() {
password: loginData.password,
});
console.log(response)
// Guardar el token en sessionStorage
if (response.data.token) {
sessionStorage.setItem('token', response.data.token);
// Opcional: guardar información adicional del usuario si viene en la respuesta
if (response.data.user) {
sessionStorage.setItem('user', JSON.stringify(response.data.user));
if (response.data.tipo_usuario) {
sessionStorage.setItem('axxxd', JSON.stringify(response.data.tipo_usuario));
}
// Redireccionar al dashboard
+11 -2
View File
@@ -5,16 +5,25 @@ import { usePathname } from 'next/navigation';
const Navbar: React.FC = () => {
const pathname = usePathname();
const tipo_usuario = sessionStorage.getItem('axxxd')?.replace(/"/g, '') || '';
const navItems = [
const navItemsAdmin = [
{ href: '/', label: 'Inicio' },
{ href: '/user/eventos', label: 'Eventos' },
{ href: '/user/qr', label: 'Lector de QRs' },
{ href: '/user/eventos/crear', label: 'Crear Evento' },
{ href: '/user/usuarios', label: 'Usuarios' },
{ href: '/signout', label: 'Cerrar sesión' },
{ href: '/login', label: 'Cerrar sesión' },
];
const navItemsUser = [
{ 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;
return (
<nav className="navbar navbar-expand-lg navbar-dark bg-azul">
<div className="container">
+19 -9
View File
@@ -31,6 +31,8 @@ export default function EditEvento({
handleChange,
handleOnChange,
}: EditEventoProps) {
const tipo_usuario = sessionStorage.getItem('axxxd')?.replace(/"/g, '') || '';
const [loading, setLoading] = useState(false);
const [nuevoBanner, setNuevoBanner] = useState<File | null>(null);
@@ -102,17 +104,19 @@ export default function EditEvento({
<div className="p-4">
{/* Banner */}
<div className="mb-4">
<ImageUploader
label="Banner del evento"
defaultBanner={
evento.banner
? `${process.env.NEXT_PUBLIC_API_URL}/banners/${evento.banner}`
: undefined
{tipo_usuario === 'administrador' && (
<>
<ImageUploader
label="Banner del evento"
defaultBanner={
evento.banner
? `${process.env.NEXT_PUBLIC_API_URL}/banners/${evento.banner}`
: undefined
}
onChange={(file) => {
setNuevoBanner(file);
}}
/>
/>
<p className="text-muted mt-2 small">
<strong>
<i className="bi bi-info-circle-fill me-2"></i>
@@ -129,6 +133,8 @@ export default function EditEvento({
Formatos soportados: PNG, JPG, JPEG.
</strong>
</p>
</>
)}
</div>
{/* Detalles del evento */}
@@ -142,6 +148,8 @@ export default function EditEvento({
onChange={(e) =>
handleChange('nombre_evento', e.target.value)
}
disabled={tipo_usuario !== 'administrador'}
/>
<div className="mb-4">
@@ -150,6 +158,7 @@ export default function EditEvento({
<MDEditor
value={evento.descripcion_evento ?? ''}
onChange={(value) => {
if (tipo_usuario !== 'administrador') return;
const texto = value || '';
if (texto.length <= MAX_LENGTH) {
handleChange('descripcion_evento', texto);
@@ -167,6 +176,7 @@ export default function EditEvento({
placeholder="Selecciona un tipo de evento"
onChange={(e) => handleChange('tipo_evento', e.target.value)}
options={tipoEventos}
disabled={tipo_usuario !== 'administrador'}
/>
<div className="row">
@@ -197,11 +207,11 @@ export default function EditEvento({
</div>
{/* Botón de guardar */}
<div className="d-flex justify-content-end mt-4">
{tipo_usuario === "administrador" && <div className="d-flex justify-content-end mt-4">
<Button icon="save" disabled={loading} onClick={handleOnSave}>
{loading ? 'Guardando...' : 'Guardar Cambios'}
</Button>
</div>
</div>}
</div>
</div>
</div>
+8 -5
View File
@@ -23,6 +23,7 @@ export default function EditFormulario({
evento,
handleChange,
}: EditFormularioProps) {
const tipo_usuario = sessionStorage.getItem('axxxd')?.replace(/"/g, '') || '';
const [loading, setLoading] = useState(false);
const [nuevoBanner, setNuevoBanner] = useState<File | null>(null);
@@ -213,11 +214,13 @@ export default function EditFormulario({
</div>
{/* Botón de guardar */}
<div className="d-flex justify-content-end mt-4">
<Button icon="save" disabled={loading} onClick={handleOnSave}>
{loading ? 'Guardando...' : 'Guardar Cambios'}
</Button>
</div>
{tipo_usuario === "administrador" && (
<div className="d-flex justify-content-end mt-4">
<Button icon="save" disabled={loading} onClick={handleOnSave}>
{loading ? 'Guardando...' : 'Guardar Cambios'}
</Button>
</div>
)}
</div>
</div>
</div>
@@ -0,0 +1,591 @@
import React, { useState, useEffect } from 'react';
import { useGetApi } from '@/hooks/use-get-api';
import { GetCuestionario } from '@/types/evento';
import SimpleInput from '@/components/input';
import RadioOptionGroup, { RadioOption } from '@/components/radio-option-group';
import axiosInstance from '@/utils/api-config';
import PrefetchAbiertaCorta from './prefetch-abierta-corta';
import Button from '@/components/button';
import toast from 'react-hot-toast';
import { useRouter } from 'next/navigation';
import { getAxiosError } from '@/utils/errors-utils';
import { SubmitResponse } from '@/types/submit';
export type UsuarioDataResponse = {
cuenta: string | null;
nombre: string | null;
apellidos: string | null;
carrera: string | null;
genero: string | null;
rfc?: string | null; // Solo para trabajadores
};
type UsuarioData = {
nombre: string; // alumno o trabajador
apellidos: string; // alumno o trabajador
correo: string; // alumno o trabajador (en los alumnos se usa el id_ncuenta en trabajadores ellos deben escribirlo)
genero: 'M' | 'F'; // alumno o trabajador
carrera: string; // solo para alumnos
rfc?: string; // solo para trabajadores se usa para buscar información de cuenta
};
const confirmativeOption = ['Sí', 'Si', 'Si, claro', 'Claro que sí'];
const negativeOption = ['No', 'No, gracias', 'No, de momento'];
type RespuestaFormulario = Record<string, string | number>; // id_pregunta: respuesta
export default function FormularioRegistro({
id_cuestionario,
handleSubmitFormulario,
}: {
evento: string;
cuestionario: string;
id_evento: number;
id_cuestionario: number;
handleSubmitFormulario: (data: SubmitResponse) => void;
}) {
// ------------------------
// Estados
// ------------------------
const [isComunidad, setIsComunidad] = useState<RadioOption<number>>();
const [respuestas, setRespuestas] = useState<RespuestaFormulario>({});
const [cuentaInfo, setCuentaInfo] = useState<UsuarioData | null>(null);
const [isSending, setIsSending] = useState(false);
const router = useRouter();
// ------------------------
// Carga del cuestionario
// ------------------------
const { data, error } = useGetApi<GetCuestionario>(
`/cuestionario/${id_cuestionario}/formulario`
);
// ------------------------
// Extracción de preguntas clave
// ------------------------
const preguntas = data?.cuestionario.secciones.flatMap((seccion) =>
seccion.preguntas.map((pregunta) => pregunta.pregunta)
);
const preguntaComunidad = preguntas?.find(
(pregunta) =>
pregunta.validacion === 'comunidad_alumno' ||
pregunta.validacion === 'comunidad_trabajador'
);
const preguntaCuenta = preguntas?.find(
(pregunta) =>
pregunta.validacion === 'cuenta_alumno' ||
pregunta.validacion === 'cuenta_trabajador' ||
pregunta.validacion === 'rfc'
);
// Solución: Agregar estado para controlar si ya se hizo la búsqueda
const [cuentaBuscada, setCuentaBuscada] = useState<string>(''); // Nuevo estado
// ------------------------
// Efecto: buscar información de cuenta
// ------------------------
useEffect(() => {
const cuentaId = preguntaCuenta?.id_pregunta ?? '';
const cuenta = respuestas[cuentaId];
const esCuentaAlumno = preguntaCuenta?.validacion === 'cuenta_alumno';
const esCuentaTrabajador =
preguntaCuenta?.validacion === 'cuenta_trabajador' ||
preguntaCuenta?.validacion === 'rfc';
const puedeBuscar =
cuenta &&
typeof cuenta === 'string' &&
((esCuentaAlumno && cuenta.length === 9) ||
(esCuentaTrabajador && cuenta.length === 10));
const fetchCuentaInfo = async () => {
try {
const endpoint = esCuentaAlumno
? `/alumnos/${cuenta}`
: `/trabajadores/${cuenta}`;
const res = await axiosInstance.get<UsuarioDataResponse>(endpoint);
console.log('Datos de cuenta obtenidos:', res.data);
// Verificar si realmente hay datos válidos
const hayDatosValidos =
res.data.nombre ||
res.data.apellidos ||
res.data.carrera ||
res.data.genero;
if (hayDatosValidos) {
setCuentaInfo({
nombre: res.data.nombre ? res.data.nombre : '',
apellidos: res.data.apellidos ? res.data.apellidos : '',
correo: res.data.cuenta
? `${res.data.cuenta}@pcpuma.acatlan.unam.mx`
: '',
genero:
res.data.genero === 'M' || res.data.genero === 'F'
? res.data.genero
: 'M',
carrera: res.data.carrera ? res.data.carrera : '',
});
} else {
// Si no hay datos válidos, establecer como null
setCuentaInfo(null);
}
// Marcar que ya se buscó esta cuenta
setCuentaBuscada(cuenta as string);
} catch (error) {
console.error('Error al obtener datos de cuenta:', error);
setCuentaInfo(null);
setCuentaBuscada(cuenta as string); // También marcar en caso de error
}
};
// Solo buscar si puede buscar Y no se ha buscado ya esta cuenta
if (puedeBuscar && cuentaBuscada !== cuenta) {
fetchCuentaInfo();
} else if (!puedeBuscar) {
setCuentaInfo(null);
setCuentaBuscada(''); // Reset cuando no se puede buscar
}
}, [
respuestas,
preguntaCuenta?.id_pregunta,
preguntaCuenta?.validacion,
cuentaBuscada,
]);
// También resetear cuentaBuscada cuando cambie el número de cuenta
useEffect(() => {
const cuentaId = preguntaCuenta?.id_pregunta ?? '';
const cuenta = respuestas[cuentaId];
// Si la cuenta cambió, resetear el estado de búsqueda
if (cuentaBuscada && cuenta !== cuentaBuscada) {
setCuentaBuscada('');
}
}, [respuestas, preguntaCuenta?.id_pregunta, cuentaBuscada]);
// ------------------------
// Efecto: precargar respuestas si hay cuentaInfo
// ------------------------
useEffect(() => {
if (!cuentaInfo) {
if (data?.cuestionario.secciones) {
const preguntas = data.cuestionario.secciones.flatMap((seccion) =>
seccion.preguntas.map((pregunta) => pregunta.pregunta)
);
const idsPrefetch = preguntas
.filter((pregunta) =>
[
'nombre',
'apellidos',
'correo',
'institucion',
'carrera',
'genero',
].includes(pregunta.validacion)
)
.map((pregunta) => pregunta.id_pregunta);
setRespuestas((prev) => {
const nuevas = { ...prev };
idsPrefetch.forEach((id) => {
delete nuevas[id];
});
return nuevas;
});
}
return;
}
const nuevasRespuestas: RespuestaFormulario = {};
if (data?.cuestionario.secciones) {
const preguntas = data.cuestionario.secciones.flatMap((seccion) =>
seccion.preguntas.map((pregunta) => pregunta.pregunta)
);
for (const pregunta of preguntas) {
const id = pregunta.id_pregunta;
switch (pregunta.validacion) {
case 'nombre':
nuevasRespuestas[id] = cuentaInfo.nombre;
break;
case 'apellidos':
nuevasRespuestas[id] = cuentaInfo.apellidos;
break;
case 'correo':
nuevasRespuestas[id] = cuentaInfo.correo;
break;
case 'institucion':
nuevasRespuestas[id] = 'FES Acatlán';
break;
case 'carrera':
nuevasRespuestas[id] = cuentaInfo.carrera;
break;
case 'genero':
const generoTexto =
cuentaInfo.genero === 'M' ? 'Masculino' : 'Femenino';
const opcion = pregunta.opciones?.find(
(op) =>
op.opcion.opcion.toLowerCase() === generoTexto.toLowerCase()
);
if (opcion) {
nuevasRespuestas[id] = String(opcion.id_opcion);
}
break;
}
}
setRespuestas((prev) => ({
...prev,
...nuevasRespuestas,
}));
}
}, [cuentaInfo, data?.cuestionario.secciones]);
// ------------------------
// Funciones auxiliares
// ------------------------
const actualizarRespuesta = (idPregunta: string, valor: string) => {
setRespuestas((prev) => ({ ...prev, [idPregunta]: valor }));
};
const handleOnSubmit = async () => {
setIsSending(true);
if (!data?.cuestionario.id_cuestionario) {
alert('No se ha cargado correctamente el formulario');
return;
}
const preguntasObligatorias =
data?.cuestionario.secciones.flatMap((seccion) =>
seccion.preguntas
.filter((pregunta) => pregunta.pregunta.obligatoria)
.map((pregunta) => pregunta.pregunta)
) || [];
const faltantes = preguntasObligatorias.filter(
(pregunta) =>
respuestas[pregunta.id_pregunta] === undefined ||
respuestas[pregunta.id_pregunta] === '' ||
respuestas[pregunta.id_pregunta] === null
);
const preguntaCorreo = preguntasObligatorias.find(
(pregunta) =>
pregunta.validacion === 'correo' ||
pregunta.validacion === 'correo_institucional'
);
if (faltantes.length > 0) {
toast.error('Por favor responde todas las preguntas obligatorias.');
setIsSending(false);
return;
}
// Buscar un posible correo en las respuestas si no hay cuentaInfo
let correo =
cuentaInfo?.correo === respuestas[preguntaCorreo?.id_pregunta ?? '']
? cuentaInfo?.correo
: respuestas[preguntaCorreo?.id_pregunta ?? ''];
if (!correo) {
// Buscar en las respuestas una que parezca correo
const correoRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
for (const valor of Object.values(respuestas)) {
if (typeof valor === 'string' && correoRegex.test(valor)) {
correo = valor;
break;
}
}
}
const payload = {
id_cuestionario: id_cuestionario,
correo: correo || '',
fecha_envio: new Date().toISOString(),
respuestas: Object.entries(respuestas).map(([id_pregunta, valor]) => ({
id_pregunta: Number(id_pregunta),
valor: isNaN(Number(valor)) ? valor : Number(valor),
})),
};
try {
const res = await axiosInstance.post<SubmitResponse>(
'/cuestionario-respondido/submit',
payload
);
toast(
() => (
<span
className="fs-4 pc-3 py-2"
style={{
maxWidth: '300px',
wordWrap: 'break-word',
whiteSpace: 'normal',
}}
>
{res.data.message.includes('ya ha respondido') ? (
<>
<i className="bi bi-exclamation-circle-fill text-warning me-2"></i>
Respuestas Enviadas Con Éxito
</>
) : (
<>
<i className="bi bi-check-circle-fill text-success me-2"></i>
{res.data.message || '¡Formulario enviado!'}
</>
)}
</span>
),
{
duration: 6000,
position: 'bottom-center',
style: {
maxWidth: '425px',
},
}
);
handleSubmitFormulario(res.data);
} catch (error) {
console.log(getAxiosError(error));
toast.error('Hubo un error al enviar el formulario', {
duration: 5000,
});
router.push('/');
}
};
// ------------------------
// Condición para mostrar el formulario
// ------------------------
const mostrarFormularioRestante =
!preguntaComunidad || // Si no hay pregunta de comunidad, mostrar el formulario
(isComunidad &&
confirmativeOption.some((opt) =>
isComunidad.label?.toLowerCase().includes(opt.toLowerCase())
) &&
!!cuentaInfo) ||
(isComunidad &&
negativeOption.some((opt) =>
isComunidad.label?.toLowerCase().includes(opt.toLowerCase())
));
if (error) {
console.error('Error al cargar el cuestionario:', error);
return <div>Error al cargar el cuestionario</div>;
}
return (
<div>
{preguntaComunidad && (
<div className="my-4">
<label
className={`form-label ${
preguntaComunidad.obligatoria ? 'required' : ''
}`}
>
{preguntaComunidad.pregunta}
</label>
<RadioOptionGroup
name="comunidad"
options={preguntaComunidad.opciones.map((op) => ({
label: op.opcion.opcion,
value: op.id_opcion,
}))}
selectedValue={isComunidad?.value}
onChange={(opt) => {
console.log('Opción comunidad seleccionada:', opt);
setIsComunidad(opt);
actualizarRespuesta(
preguntaComunidad.id_pregunta.toString(),
String(opt.value)
);
}}
/>
</div>
)}
{isComunidad &&
confirmativeOption.some((opt) =>
isComunidad.label?.toLowerCase().includes(opt.toLowerCase())
) &&
preguntaCuenta && (
<div className="my-4">
<label
className={`form-label ${
preguntaCuenta.obligatoria ? 'required' : ''
}`}
>
{preguntaCuenta.pregunta}
</label>
<SimpleInput
type="text"
name="cuenta"
placeholder={
preguntaCuenta.validacion === 'cuenta_alumno'
? 'Ingrese su cuenta'
: preguntaCuenta?.validacion === 'rfc'
? 'Ingrese su RFC sin homoclave'
: preguntaCuenta?.validacion === 'cuenta_trabajador'
? 'Ingrese su numero de trabajador'
: 'Ingrese su ' + preguntaCuenta.pregunta
}
maxLength={10}
value={respuestas[preguntaCuenta.id_pregunta] || ''}
onChange={(e) =>
setRespuestas((prev) => ({
...prev,
[preguntaCuenta.id_pregunta]: e.target.value,
}))
}
/>
</div>
)}
{mostrarFormularioRestante &&
data?.cuestionario.secciones.map((seccion, i) => (
<div key={i}>
{seccion.preguntas.map((pregunta) => {
// Evitar renderizar las preguntas ya manejadas
if (
pregunta.pregunta.id_pregunta ===
preguntaComunidad?.id_pregunta ||
pregunta.pregunta.id_pregunta === preguntaCuenta?.id_pregunta
)
return null;
if (
pregunta.pregunta.tipo_pregunta.tipo_pregunta ===
'Abierta (Respuesta corta)'
) {
return (
<PrefetchAbiertaCorta
key={pregunta.pregunta.id_pregunta}
obligatorio={pregunta.pregunta.obligatoria}
idPregunta={pregunta.pregunta.id_pregunta}
enunciado={pregunta.pregunta.pregunta}
validacion={pregunta.pregunta.validacion}
respuestas={respuestas}
cuentaInfo={cuentaInfo}
isComunidad={isComunidad?.label}
onChange={actualizarRespuesta}
preguntaComunidad={preguntaComunidad?.validacion ?? ''}
/>
);
}
if (
pregunta.pregunta.tipo_pregunta.tipo_pregunta ===
'Abierta (Parrafo)'
) {
return (
<div key={pregunta.pregunta.id_pregunta} className="my-4">
<label
className={`form-label ${
pregunta.pregunta.obligatoria ? 'required' : ''
}`}
>
{pregunta.pregunta.pregunta}
</label>
<SimpleInput
type="textarea"
name={`respuesta_${pregunta.pregunta.id_pregunta}`}
placeholder="Ingrese una respuesta"
value={respuestas[pregunta.pregunta.id_pregunta] || ''}
onChange={(e) =>
setRespuestas((prev) => ({
...prev,
[pregunta.pregunta.id_pregunta]: e.target.value,
}))
}
/>
</div>
);
}
if (pregunta.pregunta.tipo_pregunta.tipo_pregunta === 'Cerrada') {
const opciones: RadioOption<number>[] =
pregunta.pregunta.opciones.map((op) => ({
label: op.opcion.opcion,
value: op.id_opcion,
}));
let respuestaActual =
respuestas[`${pregunta.pregunta.id_pregunta}`];
if (
(!preguntaComunidad ||
(confirmativeOption.includes(isComunidad?.label || '') &&
cuentaInfo)) &&
respuestaActual === undefined // solo si no ha respondido
) {
if (pregunta.pregunta.validacion === 'genero' && cuentaInfo) {
const generoTexto =
cuentaInfo.genero === 'M' ? 'Masculino' : 'Femenino';
const opcionGenero = pregunta.pregunta.opciones.find(
(op) =>
op.opcion.opcion.toLowerCase() ===
generoTexto.toLowerCase()
);
if (opcionGenero) {
respuestaActual = String(opcionGenero.id_opcion);
}
}
}
return (
<div key={pregunta.pregunta.id_pregunta}>
<label
className={`form-label ${
pregunta.pregunta.obligatoria ? 'required' : ''
}`}
>
{pregunta.pregunta.pregunta}
</label>
<RadioOptionGroup
name={`pregunta_${pregunta.pregunta.id_pregunta}`}
options={opciones}
selectedValue={
respuestaActual ? Number(respuestaActual) : undefined
}
onChange={(opt) => {
actualizarRespuesta(
`${pregunta.pregunta.id_pregunta}`,
String(opt.value)
);
}}
/>
</div>
);
}
return (
<div key={pregunta.pregunta.id_pregunta} className="my-4">
<label
className={`form-label ${
pregunta.pregunta.obligatoria ? 'required' : ''
}`}
>
{pregunta.pregunta.pregunta}
</label>
</div>
);
})}
</div>
))}
<Button
className="btn btn-primary"
onClick={handleOnSubmit}
disabled={!mostrarFormularioRestante || isSending}
>
{isSending ? 'Enviando...' : 'Enviar Formulario'}
</Button>
</div>
);
}
+316 -406
View File
@@ -1,15 +1,13 @@
import React, { useState, useEffect } from 'react';
import { useGetApi } from '@/hooks/use-get-api';
import { GetCuestionario } from '@/types/evento';
import { SubmitResponse } from '@/types/submit';
import SimpleInput from '@/components/input';
import RadioOptionGroup, { RadioOption } from '@/components/radio-option-group';
import axiosInstance from '@/utils/api-config';
import PrefetchAbiertaCorta from './prefetch-abierta-corta';
import Button from '@/components/button';
import axiosInstance from '@/utils/api-config';
import toast from 'react-hot-toast';
import { useRouter } from 'next/navigation';
import { getAxiosError } from '@/utils/errors-utils';
import { SubmitResponse } from '@/types/submit';
export type UsuarioDataResponse = {
cuenta: string | null;
@@ -17,22 +15,19 @@ export type UsuarioDataResponse = {
apellidos: string | null;
carrera: string | null;
genero: string | null;
rfc?: string | null; // Solo para trabajadores
rfc?: string | null;
};
type UsuarioData = {
nombre: string; // alumno o trabajador
apellidos: string; // alumno o trabajador
correo: string; // alumno o trabajador (en los alumnos se usa el id_ncuenta en trabajadores ellos deben escribirlo)
genero: 'M' | 'F'; // alumno o trabajador
carrera: string; // solo para alumnos
rfc?: string; // solo para trabajadores se usa para buscar información de cuenta
nombre: string;
apellidos: string;
correo: string;
genero: 'M' | 'F';
carrera: string;
rfc?: string;
};
const confirmativeOption = ['Sí', 'Si', 'Si, claro', 'Claro que sí'];
const negativeOption = ['No', 'No, gracias', 'No, de momento'];
type RespuestaFormulario = Record<string, string | number>; // id_pregunta: respuesta
type RespuestaFormulario = Record<string, string | number>;
export default function FormularioRegistro({
id_cuestionario,
@@ -44,28 +39,24 @@ export default function FormularioRegistro({
id_cuestionario: number;
handleSubmitFormulario: (data: SubmitResponse) => void;
}) {
// ------------------------
// Estados
// ------------------------
const [isComunidad, setIsComunidad] = useState<RadioOption<number>>();
// Estados principales
const [respuestas, setRespuestas] = useState<RespuestaFormulario>({});
const [cuentaInfo, setCuentaInfo] = useState<UsuarioData | null>(null);
const [busquedaCompletada, setBusquedaCompletada] = useState<boolean>(false);
const [cuentaBuscada, setCuentaBuscada] = useState<string>('');
const [isSending, setIsSending] = useState(false);
const router = useRouter();
// ------------------------
// Carga del cuestionario
// ------------------------
const { data, error } = useGetApi<GetCuestionario>(
`/cuestionario/${id_cuestionario}/formulario`
);
// ------------------------
// Extracción de preguntas clave
// ------------------------
// Extraer todas las preguntas del cuestionario
const preguntas = data?.cuestionario.secciones.flatMap((seccion) =>
seccion.preguntas.map((pregunta) => pregunta.pregunta)
);
// Identificar preguntas especiales
const preguntaComunidad = preguntas?.find(
(pregunta) =>
pregunta.validacion === 'comunidad_alumno' ||
@@ -79,166 +70,168 @@ export default function FormularioRegistro({
pregunta.validacion === 'rfc'
);
// ------------------------
// Efecto: buscar información de cuenta
// ------------------------
// Determinar tipo de comunidad seleccionada
const comunidadSeleccionada = preguntaComunidad
? respuestas[preguntaComunidad.id_pregunta]
: null;
const esComunidadSi = comunidadSeleccionada &&
preguntaComunidad?.opciones.find(op =>
op.id_opcion === Number(comunidadSeleccionada) &&
op.opcion.opcion.toLowerCase().includes('si')
);
// Efecto para buscar información de cuenta
useEffect(() => {
const cuentaId = preguntaCuenta?.id_pregunta ?? '';
const cuenta = respuestas[cuentaId];
const esCuentaAlumno = preguntaCuenta?.validacion === 'cuenta_alumno';
const esCuentaTrabajador =
preguntaCuenta?.validacion === 'cuenta_trabajador' ||
preguntaCuenta?.validacion === 'rfc';
const puedeBuscar =
cuenta &&
typeof cuenta === 'string' &&
((esCuentaAlumno && cuenta.length === 9) ||
(esCuentaTrabajador && cuenta.length === 10));
const fetchCuentaInfo = async () => {
try {
const endpoint = esCuentaAlumno
? `/alumnos/${cuenta}`
: `/trabajadores/${cuenta}`;
const res = await axiosInstance.get<UsuarioDataResponse>(endpoint);
console.log('Datos de cuenta obtenidos:', res.data);
setCuentaInfo({
nombre: res.data.nombre ? res.data.nombre : '',
apellidos: res.data.apellidos ? res.data.apellidos : '',
correo: res.data.cuenta
? `${res.data.cuenta}@pcpuma.acatlan.unam.mx`
: '',
genero:
res.data.genero === 'M' || res.data.genero === 'F'
? res.data.genero
: 'M',
carrera: res.data.carrera ? res.data.carrera : '',
});
// Usar datos fake para pruebas
/* setCuentaInfo({
nombre: 'Juan',
apellidos: 'Pérez',
correo: 'juan.perez@pcpuma.acatlan.unam.mx',
genero: 'M',
carrera: 'Ingeniería en Computación',
}); */
} catch (error) {
console.error('Error al obtener datos de cuenta:', error);
setCuentaInfo(null);
}
};
if (puedeBuscar) {
fetchCuentaInfo();
} else {
if (!preguntaCuenta || !esComunidadSi) {
setCuentaInfo(null);
}
}, [respuestas, preguntaCuenta?.id_pregunta, preguntaCuenta?.validacion]);
// ------------------------
// Efecto: precargar respuestas si hay cuentaInfo
// ------------------------
useEffect(() => {
if (!cuentaInfo) {
if (data?.cuestionario.secciones) {
const preguntas = data.cuestionario.secciones.flatMap((seccion) =>
seccion.preguntas.map((pregunta) => pregunta.pregunta)
);
const idsPrefetch = preguntas
.filter((pregunta) =>
[
'nombre',
'apellidos',
'correo',
'institucion',
'carrera',
'genero',
].includes(pregunta.validacion)
)
.map((pregunta) => pregunta.id_pregunta);
setRespuestas((prev) => {
const nuevas = { ...prev };
idsPrefetch.forEach((id) => {
delete nuevas[id];
});
return nuevas;
});
}
setBusquedaCompletada(false);
setCuentaBuscada('');
return;
}
const cuenta = respuestas[preguntaCuenta.id_pregunta];
if (!cuenta || typeof cuenta !== 'string') {
setCuentaInfo(null);
setBusquedaCompletada(false);
setCuentaBuscada('');
return;
}
// Determinar longitudes válidas según el tipo
const longitudesValidas = {
'cuenta_alumno': 9,
'cuenta_trabajador': 6,
'rfc': 10
};
const longitudRequerida = longitudesValidas[preguntaCuenta.validacion as keyof typeof longitudesValidas];
const puedeBuscar = cuenta.length === longitudRequerida;
// Solo buscar si no se ha buscado ya esta cuenta
if (puedeBuscar && cuentaBuscada !== cuenta) {
setBusquedaCompletada(false);
const fetchCuentaInfo = async () => {
try {
let endpoint: string;
switch (preguntaCuenta.validacion) {
case 'cuenta_alumno':
endpoint = `/alumnos/${cuenta}`;
break;
case 'cuenta_trabajador':
endpoint = `/trabajadores/${cuenta}`;
break;
case 'rfc':
endpoint = `/trabajadores/${cuenta}`;
break;
default:
throw new Error('Tipo de validación no reconocido');
}
const res = await axiosInstance.get<UsuarioDataResponse>(endpoint);
console.log('Datos de cuenta obtenidos:', res.data);
// Verificar si hay datos válidos
const hayDatosValidos = res.data.nombre || res.data.apellidos ||
res.data.carrera || res.data.genero;
if (hayDatosValidos) {
setCuentaInfo({
nombre: res.data.nombre || '',
apellidos: res.data.apellidos || '',
correo: res.data.cuenta
? `${res.data.cuenta}@pcpuma.acatlan.unam.mx`
: '',
genero: (res.data.genero === 'M' || res.data.genero === 'F')
? res.data.genero
: 'M',
carrera: res.data.carrera || '',
rfc: res.data.rfc || undefined,
});
} else {
setCuentaInfo(null);
}
setCuentaBuscada(cuenta);
setBusquedaCompletada(true);
} catch (error) {
console.error('Error al obtener datos de cuenta:', error);
setCuentaInfo(null);
setCuentaBuscada(cuenta);
setBusquedaCompletada(true);
}
};
fetchCuentaInfo();
} else if (!puedeBuscar) {
setCuentaInfo(null);
setBusquedaCompletada(false);
setCuentaBuscada('');
}
}, [respuestas, preguntaCuenta, esComunidadSi, cuentaBuscada]);
// Efecto para prellenar respuestas cuando se obtiene información de cuenta
useEffect(() => {
if (!cuentaInfo || !data?.cuestionario.secciones) return;
const nuevasRespuestas: RespuestaFormulario = {};
if (data?.cuestionario.secciones) {
const preguntas = data.cuestionario.secciones.flatMap((seccion) =>
seccion.preguntas.map((pregunta) => pregunta.pregunta)
);
for (const pregunta of preguntas) {
const id = pregunta.id_pregunta;
switch (pregunta.validacion) {
case 'nombre':
nuevasRespuestas[id] = cuentaInfo.nombre;
break;
case 'apellidos':
nuevasRespuestas[id] = cuentaInfo.apellidos;
break;
case 'correo':
nuevasRespuestas[id] = cuentaInfo.correo;
break;
case 'institucion':
nuevasRespuestas[id] = 'FES Acatlán';
break;
case 'carrera':
nuevasRespuestas[id] = cuentaInfo.carrera;
break;
case 'genero':
const generoTexto =
cuentaInfo.genero === 'M' ? 'Masculino' : 'Femenino';
const opcion = pregunta.opciones?.find(
(op) =>
op.opcion.opcion.toLowerCase() === generoTexto.toLowerCase()
);
if (opcion) {
nuevasRespuestas[id] = String(opcion.id_opcion);
}
break;
}
preguntas?.forEach((pregunta) => {
const id = pregunta.id_pregunta;
switch (pregunta.validacion) {
case 'nombre':
nuevasRespuestas[id] = cuentaInfo.nombre;
break;
case 'apellidos':
nuevasRespuestas[id] = cuentaInfo.apellidos;
break;
case 'correo':
nuevasRespuestas[id] = cuentaInfo.correo;
break;
case 'carrera':
nuevasRespuestas[id] = cuentaInfo.carrera;
break;
case 'genero':
const generoTexto = cuentaInfo.genero === 'M' ? 'Masculino' : 'Femenino';
const opcionGenero = pregunta.opciones?.find(
(op) => op.opcion.opcion.toLowerCase() === generoTexto.toLowerCase()
);
if (opcionGenero) {
nuevasRespuestas[id] = String(opcionGenero.id_opcion);
}
break;
}
});
setRespuestas((prev) => ({
...prev,
...nuevasRespuestas,
}));
}
}, [cuentaInfo, data?.cuestionario.secciones]);
setRespuestas(prev => ({ ...prev, ...nuevasRespuestas }));
}, [cuentaInfo, data?.cuestionario.secciones, preguntas]);
// ------------------------
// Funciones auxiliares
// ------------------------
// Función para actualizar respuestas
const actualizarRespuesta = (idPregunta: string, valor: string) => {
setRespuestas((prev) => ({ ...prev, [idPregunta]: valor }));
setRespuestas(prev => ({ ...prev, [idPregunta]: valor }));
};
// Función para enviar formulario
const handleOnSubmit = async () => {
setIsSending(true);
if (!data?.cuestionario.id_cuestionario) {
alert('No se ha cargado correctamente el formulario');
toast.error('No se ha cargado correctamente el formulario');
setIsSending(false);
return;
}
const preguntasObligatorias =
data?.cuestionario.secciones.flatMap((seccion) =>
seccion.preguntas
.filter((pregunta) => pregunta.pregunta.obligatoria)
.map((pregunta) => pregunta.pregunta)
) || [];
// Validar preguntas obligatorias
const preguntasObligatorias = data.cuestionario.secciones.flatMap((seccion) =>
seccion.preguntas
.filter((pregunta) => pregunta.pregunta.obligatoria)
.map((pregunta) => pregunta.pregunta)
);
const faltantes = preguntasObligatorias.filter(
(pregunta) =>
@@ -247,26 +240,22 @@ export default function FormularioRegistro({
respuestas[pregunta.id_pregunta] === null
);
const preguntaCorreo = preguntasObligatorias.find(
(pregunta) =>
pregunta.validacion === 'correo' ||
pregunta.validacion === 'correo_institucional'
);
if (faltantes.length > 0) {
toast.error('Por favor responde todas las preguntas obligatorias.');
setIsSending(false);
return;
}
// Buscar un posible correo en las respuestas si no hay cuentaInfo
let correo =
cuentaInfo?.correo === respuestas[preguntaCorreo?.id_pregunta ?? '']
? cuentaInfo?.correo
: respuestas[preguntaCorreo?.id_pregunta ?? ''];
// Buscar correo
const preguntaCorreo = preguntas?.find(p =>
p.validacion === 'correo' || p.validacion === 'correo_institucional'
);
let correo = preguntaCorreo
? String(respuestas[preguntaCorreo.id_pregunta] || '')
: '';
if (!correo) {
// Buscar en las respuestas una que parezca correo
const correoRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
for (const valor of Object.values(respuestas)) {
if (typeof valor === 'string' && correoRegex.test(valor)) {
@@ -278,7 +267,7 @@ export default function FormularioRegistro({
const payload = {
id_cuestionario: id_cuestionario,
correo: correo || '',
correo: correo,
fecha_envio: new Date().toISOString(),
respuestas: Object.entries(respuestas).map(([id_pregunta, valor]) => ({
id_pregunta: Number(id_pregunta),
@@ -291,77 +280,38 @@ export default function FormularioRegistro({
'/cuestionario-respondido/submit',
payload
);
toast(
() => (
<span
className="fs-4 pc-3 py-2"
style={{
maxWidth: '300px',
wordWrap: 'break-word',
whiteSpace: 'normal',
}}
>
{res.data.message.includes('ya ha respondido') ? (
<>
<i className="bi bi-exclamation-circle-fill text-warning me-2"></i>
Respuestas Enviadas Con Éxito
</>
) : (
<>
<i className="bi bi-check-circle-fill text-success me-2"></i>
{res.data.message || '¡Formulario enviado!'}
</>
)}
</span>
),
{
duration: 6000,
position: 'bottom-center',
style: {
maxWidth: '425px',
},
}
);
toast.success(res.data.message || '¡Formulario enviado exitosamente!');
handleSubmitFormulario(res.data);
} catch (error) {
console.log(getAxiosError(error));
toast.error('Hubo un error al enviar el formulario', {
duration: 5000,
});
router.push('/');
console.error('Error al enviar formulario:', getAxiosError(error));
toast.error('Hubo un error al enviar el formulario');
} finally {
setIsSending(false);
}
};
// ------------------------
// Condición para mostrar el formulario
// ------------------------
const mostrarFormularioRestante =
!preguntaComunidad || // Si no hay pregunta de comunidad, mostrar el formulario
(isComunidad &&
confirmativeOption.some((opt) =>
isComunidad.label?.toLowerCase().includes(opt.toLowerCase())
) &&
!!cuentaInfo) ||
(isComunidad &&
negativeOption.some((opt) =>
isComunidad.label?.toLowerCase().includes(opt.toLowerCase())
));
// Condición para mostrar el resto del formulario
const mostrarFormularioCompleto =
!preguntaComunidad || // Sin pregunta de comunidad
(esComunidadSi && busquedaCompletada) || // Con comunidad "Sí" y búsqueda completada
(!esComunidadSi && comunidadSeleccionada); // Con comunidad "No"
if (error) {
console.error('Error al cargar el cuestionario:', error);
return <div>Error al cargar el cuestionario</div>;
}
if (!data) {
return <div>Cargando...</div>;
}
return (
<div>
{/* Pregunta de comunidad */}
{preguntaComunidad && (
<div className="my-4">
<label
className={`form-label ${
preguntaComunidad.obligatoria ? 'required' : ''
}`}
>
<label className={`form-label ${preguntaComunidad.obligatoria ? 'required' : ''}`}>
{preguntaComunidad.pregunta}
</label>
<RadioOptionGroup
@@ -370,192 +320,152 @@ export default function FormularioRegistro({
label: op.opcion.opcion,
value: op.id_opcion,
}))}
selectedValue={isComunidad?.value}
selectedValue={comunidadSeleccionada ? Number(comunidadSeleccionada) : undefined}
onChange={(opt) => {
console.log('Opción comunidad seleccionada:', opt);
setIsComunidad(opt);
actualizarRespuesta(
preguntaComunidad.id_pregunta.toString(),
String(opt.value)
);
actualizarRespuesta(preguntaComunidad.id_pregunta.toString(), String(opt.value));
}}
/>
</div>
)}
{isComunidad &&
confirmativeOption.some((opt) =>
isComunidad.label?.toLowerCase().includes(opt.toLowerCase())
) &&
preguntaCuenta && (
<div className="my-4">
<label
className={`form-label ${
preguntaCuenta.obligatoria ? 'required' : ''
}`}
>
{preguntaCuenta.pregunta}
</label>
<SimpleInput
type="text"
name="cuenta"
placeholder={
preguntaCuenta.validacion === 'cuenta_alumno'
? 'Ingrese su cuenta'
: preguntaCuenta?.validacion === 'rfc'
? 'Ingrese su RFC sin homoclave'
: preguntaCuenta?.validacion === 'cuenta_trabajador'
? 'Ingrese su numero de trabajador'
: 'Ingrese su ' + preguntaCuenta.pregunta
}
maxLength={10}
value={respuestas[preguntaCuenta.id_pregunta] || ''}
onChange={(e) =>
setRespuestas((prev) => ({
...prev,
[preguntaCuenta.id_pregunta]: e.target.value,
}))
}
/>
</div>
)}
{mostrarFormularioRestante &&
data?.cuestionario.secciones.map((seccion, i) => (
<div key={i}>
{seccion.preguntas.map((pregunta) => {
// Evitar renderizar las preguntas ya manejadas
if (
pregunta.pregunta.id_pregunta ===
preguntaComunidad?.id_pregunta ||
pregunta.pregunta.id_pregunta === preguntaCuenta?.id_pregunta
)
return null;
if (
pregunta.pregunta.tipo_pregunta.tipo_pregunta ===
'Abierta (Respuesta corta)'
) {
return (
<PrefetchAbiertaCorta
key={pregunta.pregunta.id_pregunta}
obligatorio={pregunta.pregunta.obligatoria}
idPregunta={pregunta.pregunta.id_pregunta}
enunciado={pregunta.pregunta.pregunta}
validacion={pregunta.pregunta.validacion}
respuestas={respuestas}
cuentaInfo={cuentaInfo}
isComunidad={isComunidad?.label}
onChange={actualizarRespuesta}
preguntaComunidad={preguntaComunidad?.validacion ?? ''}
{/* Pregunta de cuenta (si respondió "Sí" a comunidad) */}
{esComunidadSi && preguntaCuenta && (
<div className="my-4">
<label className={`form-label ${preguntaCuenta.obligatoria ? 'required' : ''}`}>
{preguntaCuenta.pregunta}
</label>
<SimpleInput
type="text"
name="cuenta"
placeholder={
preguntaCuenta.validacion === 'cuenta_alumno'
? 'Ingrese su número de cuenta (9 dígitos)'
: preguntaCuenta.validacion === 'cuenta_trabajador'
? 'Ingrese su número de trabajador (6 dígitos)'
: preguntaCuenta.validacion === 'rfc'
? 'Ingrese su RFC sin homoclave (10 caracteres)'
: `Ingrese su ${preguntaCuenta.pregunta}`
}
maxLength={preguntaCuenta.validacion === 'rfc' ? 10 :
preguntaCuenta.validacion === 'cuenta_alumno' ? 9 : 6}
value={respuestas[preguntaCuenta.id_pregunta] || ''}
onChange={(e) => actualizarRespuesta(
preguntaCuenta.id_pregunta.toString(),
e.target.value
)}
/>
</div>
)}
{/* Resto del formulario */}
{mostrarFormularioCompleto && data.cuestionario.secciones.map((seccion, i) => (
<div key={i}>
{seccion.preguntas.map((pregunta) => {
// No renderizar preguntas ya manejadas
if (
pregunta.pregunta.id_pregunta === preguntaComunidad?.id_pregunta ||
pregunta.pregunta.id_pregunta === preguntaCuenta?.id_pregunta
) {
return null;
}
const valor = respuestas[pregunta.pregunta.id_pregunta] || '';
// Pregunta abierta corta
if (pregunta.pregunta.tipo_pregunta.tipo_pregunta === 'Abierta (Respuesta corta)') {
return (
<div key={pregunta.pregunta.id_pregunta} className="my-4">
<label className={`form-label ${pregunta.pregunta.obligatoria ? 'required' : ''}`}>
{pregunta.pregunta.pregunta}
</label>
<SimpleInput
type="text"
name={`respuesta_${pregunta.pregunta.id_pregunta}`}
placeholder="Ingrese una respuesta"
value={valor}
onChange={(e) => actualizarRespuesta(
pregunta.pregunta.id_pregunta.toString(),
e.target.value
)}
/>
);
}
</div>
);
}
if (
pregunta.pregunta.tipo_pregunta.tipo_pregunta ===
'Abierta (Parrafo)'
) {
return (
<div key={pregunta.pregunta.id_pregunta} className="my-4">
<label
className={`form-label ${
pregunta.pregunta.obligatoria ? 'required' : ''
}`}
>
{pregunta.pregunta.pregunta}
</label>
<SimpleInput
type="textarea"
name={`respuesta_${pregunta.pregunta.id_pregunta}`}
placeholder="Ingrese una respuesta"
value={respuestas[pregunta.pregunta.id_pregunta] || ''}
onChange={(e) =>
setRespuestas((prev) => ({
...prev,
[pregunta.pregunta.id_pregunta]: e.target.value,
}))
}
/>
</div>
);
}
// Pregunta abierta párrafo
if (pregunta.pregunta.tipo_pregunta.tipo_pregunta === 'Abierta (Parrafo)') {
return (
<div key={pregunta.pregunta.id_pregunta} className="my-4">
<label className={`form-label ${pregunta.pregunta.obligatoria ? 'required' : ''}`}>
{pregunta.pregunta.pregunta}
</label>
<SimpleInput
type="textarea"
name={`respuesta_${pregunta.pregunta.id_pregunta}`}
placeholder="Ingrese una respuesta"
value={valor}
onChange={(e) => actualizarRespuesta(
pregunta.pregunta.id_pregunta.toString(),
e.target.value
)}
/>
</div>
);
}
if (pregunta.pregunta.tipo_pregunta.tipo_pregunta === 'Cerrada') {
const opciones: RadioOption<number>[] =
pregunta.pregunta.opciones.map((op) => ({
label: op.opcion.opcion,
value: op.id_opcion,
}));
let respuestaActual =
respuestas[`${pregunta.pregunta.id_pregunta}`];
if (
(!preguntaComunidad ||
(confirmativeOption.includes(isComunidad?.label || '') &&
cuentaInfo)) &&
respuestaActual === undefined // solo si no ha respondido
) {
if (pregunta.pregunta.validacion === 'genero' && cuentaInfo) {
const generoTexto =
cuentaInfo.genero === 'M' ? 'Masculino' : 'Femenino';
const opcionGenero = pregunta.pregunta.opciones.find(
(op) =>
op.opcion.opcion.toLowerCase() ===
generoTexto.toLowerCase()
);
if (opcionGenero) {
respuestaActual = String(opcionGenero.id_opcion);
}
}
}
return (
<div key={pregunta.pregunta.id_pregunta}>
<label
className={`form-label ${
pregunta.pregunta.obligatoria ? 'required' : ''
}`}
>
{pregunta.pregunta.pregunta}
</label>
<RadioOptionGroup
name={`pregunta_${pregunta.pregunta.id_pregunta}`}
options={opciones}
selectedValue={
respuestaActual ? Number(respuestaActual) : undefined
}
onChange={(opt) => {
actualizarRespuesta(
`${pregunta.pregunta.id_pregunta}`,
String(opt.value)
);
}}
/>
</div>
);
}
// Pregunta cerrada
if (pregunta.pregunta.tipo_pregunta.tipo_pregunta === 'Cerrada') {
const opciones: RadioOption<number>[] = pregunta.pregunta.opciones.map((op) => ({
label: op.opcion.opcion,
value: op.id_opcion,
}));
return (
<div key={pregunta.pregunta.id_pregunta} className="my-4">
<label
className={`form-label ${
pregunta.pregunta.obligatoria ? 'required' : ''
}`}
>
<label className={`form-label ${pregunta.pregunta.obligatoria ? 'required' : ''}`}>
{pregunta.pregunta.pregunta}
</label>
<RadioOptionGroup
name={`pregunta_${pregunta.pregunta.id_pregunta}`}
options={opciones}
selectedValue={valor ? Number(valor) : undefined}
onChange={(opt) => actualizarRespuesta(
pregunta.pregunta.id_pregunta.toString(),
String(opt.value)
)}
/>
</div>
);
})}
</div>
))}
<Button
className="btn btn-primary"
onClick={handleOnSubmit}
disabled={!mostrarFormularioRestante || isSending}
>
{isSending ? 'Enviando...' : 'Enviar Formulario'}
</Button>
}
return null;
})}
</div>
))}
{/* Botón de envío */}
{mostrarFormularioCompleto && (
<Button
className="btn btn-primary"
onClick={handleOnSubmit}
disabled={isSending}
>
{isSending ? 'Enviando...' : 'Enviar Formulario'}
</Button>
)}
</div>
);
}
/*
curl -X 'POST' \
'https://venus.acatlan.unam.mx/registro_test/administrador' \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-d '{
"nombre_usuario": "staffmike",
"correo": "staffmike@ejemplo.com",
"password": "password",
"id_tipo_user": 2
}'
*/