auto complete alumnos

This commit is contained in:
jorgemike
2025-04-06 16:04:56 -06:00
parent 6c51e326dd
commit a0e86b3873
7 changed files with 468 additions and 323 deletions
+299
View File
@@ -0,0 +1,299 @@
'use client';
import React, { useEffect, useState } from 'react';
import { CuestionarioSeccion } from '@/types/responder-formulario';
import RadioOptionGroup, { RadioOption } from '@/components/radio-option-group';
import Input from '@/components/input';
import Button from '@/components/button';
import { validarRespuesta } from '@/utils/validador';
import toast from 'react-hot-toast';
import axiosInstance from '@/utils/api-config';
import { useRouter } from 'next/navigation';
export default function Formulario({
id_formulario,
secciones,
}: {
id_formulario: number;
secciones: CuestionarioSeccion[];
}) {
const router = useRouter();
const [esDeFES, setEsDeFES] = useState<boolean | null>(null);
const [cuenta, setCuenta] = useState('');
const [datosAuto, setDatosAuto] = useState<null | {
nombre: string;
apellidos: string;
correo: string;
}>(null);
const [respuestas, setRespuestas] = useState<Record<string, string>>({});
const [isSubmitting, setIsSubmitting] = useState(false); // New state for submission
const actualizarRespuesta = (idPregunta: string, valor: string) => {
setRespuestas((prev) => ({ ...prev, [idPregunta]: valor }));
};
useEffect(() => {
if (esDeFES && cuenta.length === 9) {
fetch(`/datos/alumnos/${cuenta}`)
.then((res) => res.json())
.then((data) => setDatosAuto(data))
.catch((err) => {
console.error('Error al obtener datos del alumno:', err);
setDatosAuto(null);
});
}
}, [cuenta, esDeFES]);
// Encuentra preguntas por texto o validación
const todasPreguntas = secciones.flatMap((s) =>
s.preguntas.map((p) => p.pregunta)
);
const preguntaFES = todasPreguntas.find(
(p) => p.pregunta === '¿Eres parte de la comunidad de la FES Acatlán?'
);
const preguntaCuenta = todasPreguntas.find(
(p) => p.validacion === 'cuenta_alumno'
);
const preguntasRestantes = todasPreguntas.filter(
(p) =>
![preguntaFES?.id_pregunta, preguntaCuenta?.id_pregunta].includes(
p.id_pregunta
)
);
const respuestaFES = respuestas[`pregunta_${preguntaFES?.id_pregunta}`];
const formatearRespuestas = async () => {
if (!validarRespuestasObligatorias()) return;
const respuestasArray = Object.entries(respuestas).flatMap(
([key, valor]) => {
const idNumerico = Number(key.replace('pregunta_', ''));
const pregunta = secciones
.flatMap((s) => s.preguntas)
.find((p) => p.pregunta.id_pregunta === idNumerico);
const tipo = pregunta?.pregunta.tipo_pregunta.tipo_pregunta;
// Si es múltiple, convertir cada valor individual si corresponde
if (Array.isArray(valor)) {
return valor.map((v) => ({
id_pregunta: idNumerico,
valor: tipo === 'Cerrada' || tipo === 'Multiple' ? Number(v) : v,
}));
}
return [
{
id_pregunta: idNumerico,
valor: tipo === 'Cerrada' ? Number(valor) : valor,
},
];
}
);
const correoEntry = respuestasArray.find(
(r) =>
typeof r.valor === 'string' &&
/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(r.valor)
);
const resultado = {
id_formulario: Number(id_formulario),
correo: correoEntry?.valor || 'correo@no-encontrado.com',
respuestas: respuestasArray,
fecha_envio: new Date().toISOString(),
};
console.log('Resultado a enviar:', resultado);
setIsSubmitting(true);
toast
.promise(
axiosInstance.post('/cuestionario-respondido/submit', resultado),
{
loading: 'Enviando formulario...',
success: 'Formulario enviado con éxito!',
error: 'Error al enviar el formulario.',
}
)
.then(() => router.push('/'))
.finally(() => setIsSubmitting(false));
};
const validarRespuestasObligatorias = (): boolean => {
const faltantes: number[] = [];
const errores: { id: number; mensaje: string }[] = [];
console.log('Validando respuestas:', respuestas);
secciones.forEach((seccion) => {
seccion.preguntas.forEach(({ pregunta }) => {
const id = pregunta.id_pregunta;
const valor = respuestas[`pregunta_${id}`];
const tipo = pregunta.tipo_pregunta.tipo_pregunta;
const validacion = pregunta.validacion;
const respondida =
(tipo === 'Abierta' &&
typeof valor === 'string' &&
valor.trim() !== '') ||
(tipo === 'Cerrada' && valor !== undefined) ||
(tipo === 'Multiple' && Array.isArray(valor) && valor.length > 0);
if (!respondida && pregunta.obligatoria) {
faltantes.push(id);
}
if (tipo === 'Abierta' && validacion && typeof valor === 'string') {
const resultado = validarRespuesta(valor, validacion);
if (!resultado.valido) {
errores.push({ id, mensaje: resultado.mensaje });
}
}
});
});
if (faltantes.length > 0) {
console.log('Faltan preguntas obligatorias:', faltantes);
toast.error('Responde todas las preguntas obligatorias antes de enviar.');
return false;
}
if (errores.length > 0) {
toast.error(
`Corrige las respuestas con error de formato:\n${errores
.map((e) => `${e.mensaje}`)
.join('\n')}`
);
return false;
}
return true;
};
return (
<form className="container py-4">
{/* Pregunta inicial */}
{preguntaFES && (
<div className="mb-4">
<label className="form-label">{preguntaFES.pregunta}</label>
<RadioOptionGroup
name="comunidad_fes"
options={preguntaFES.opciones.map((op) => ({
label: op.opcion.opcion,
value: op.id_opcion,
}))}
selectedValue={respuestaFES ? Number(respuestaFES) : undefined}
onChange={(opt) => {
const idSeleccionado = opt.value;
const opcionSeleccionada = preguntaFES.opciones.find(
(o) => o.id_opcion === idSeleccionado
);
const valorTexto = opcionSeleccionada?.opcion.opcion ?? '';
setEsDeFES(valorTexto === 'Si'); // Se actualiza esDeFES con el texto
setCuenta('');
setDatosAuto(null);
actualizarRespuesta(
`pregunta_${preguntaFES.id_pregunta}`,
String(idSeleccionado)
);
}}
/>
</div>
)}
{/* Si dijo que sí, pedir número de cuenta */}
{esDeFES && preguntaCuenta && (
<Input
key={preguntaCuenta.id_pregunta}
label={preguntaCuenta.pregunta}
name={`pregunta_${preguntaCuenta.id_pregunta}`}
value={cuenta}
onChange={(e) => {
const value = e.target.value;
setCuenta(value);
actualizarRespuesta(
`pregunta_${preguntaCuenta.id_pregunta}`,
value
); // ✅ Guarda la respuesta
}}
/>
)}
{/* Si ya tiene datos o dijo que no es de la FES, mostrar el resto */}
{(esDeFES === false || (esDeFES && datosAuto)) &&
preguntasRestantes.map((p) => {
if (p.tipo_pregunta.tipo_pregunta === 'Abierta') {
let defaultValue = '';
let disabled = false;
if (esDeFES && datosAuto) {
if (p.validacion === 'nombre') {
defaultValue = p.pregunta.toLowerCase().includes('apellido')
? datosAuto.apellidos
: datosAuto.nombre;
disabled = true;
}
if (p.validacion === 'correo') {
defaultValue = datosAuto.correo;
disabled = true;
}
}
return (
<Input
key={p.id_pregunta}
label={p.pregunta}
name={`pregunta_${p.id_pregunta}`}
defaultValue={defaultValue}
disabled={disabled}
onChange={(e) =>
actualizarRespuesta(
`pregunta_${p.id_pregunta}`,
e.target.value
)
}
/>
);
}
if (p.tipo_pregunta.tipo_pregunta === 'Cerrada') {
const opciones: RadioOption<number>[] = p.opciones.map(
(op) => ({
label: op.opcion.opcion,
value: op.id_opcion,
})
);
const respuestaActual = respuestas[`pregunta_${p.id_pregunta}`];
return (
<div key={p.id_pregunta}>
<label className="form-label">{p.pregunta}</label>
<RadioOptionGroup
name={`pregunta_${p.id_pregunta}`}
options={opciones}
selectedValue={
respuestaActual ? Number(respuestaActual) : undefined
} // ✅ Aquí se fija la opción
onChange={(opt) =>
actualizarRespuesta(
`pregunta_${p.id_pregunta}`,
String(opt.value)
)
}
/>
</div>
);
}
return null;
})}
{/* Botón de enviar */}
<Button onClick={formatearRespuestas} disabled={isSubmitting}>
{isSubmitting ? 'Enviando...' : 'Enviar'}
</Button>
</form>
);
}