diff --git a/src/app/(admins)/administrador/formulario/[id_formulario]/layout.tsx b/src/app/(admins)/administrador/formulario/[id_formulario]/layout.tsx
index 37562b8..4b2545e 100644
--- a/src/app/(admins)/administrador/formulario/[id_formulario]/layout.tsx
+++ b/src/app/(admins)/administrador/formulario/[id_formulario]/layout.tsx
@@ -1,19 +1,23 @@
+'use client';
import Link from 'next/link';
+import { useParams } from 'next/navigation';
import React from 'react';
export default function Layout({ children }: { children: React.ReactNode }) {
+ const params = useParams<{ id_formulario: string }>();
+
return (
{children}
diff --git a/src/app/(public)/page.tsx b/src/app/(public)/page.tsx
index 6157155..16d4128 100644
--- a/src/app/(public)/page.tsx
+++ b/src/app/(public)/page.tsx
@@ -36,6 +36,17 @@ export default async function Page() {
return Error al cargar los eventos
;
}
+ function slugify(text: string): string {
+ return text
+ .toString()
+ .normalize('NFD') // Quita tildes
+ .replace(/[\u0300-\u036f]/g, '')
+ .toLowerCase()
+ .trim()
+ .replace(/[^a-z0-9]+/g, '-') // Reemplaza cualquier cosa que no sea alfanumérica por guiones
+ .replace(/^-+|-+$/g, ''); // Quita guiones al inicio o final
+ }
+
return (
<>
@@ -44,19 +55,19 @@ export default async function Page() {
- {eventos.map((cuestionario) => (
-
- ))}
- {eventos.length === 0 && (
-
- No hay formularios disponibles.
-
- )}
+ {eventos.map((cuestionario) => {
+ const slug = slugify(cuestionario.nombre_form); // Asumiendo que el nombre del evento está en `nombre`
+ const url = `/registro/${slug}-${cuestionario.id_cuestionario}`;
+
+ return (
+
+ );
+ })}
diff --git a/src/app/(public)/registro/[id_formulario]/page.tsx b/src/app/(public)/registro/[id_formulario]/page.tsx
deleted file mode 100644
index 2592fea..0000000
--- a/src/app/(public)/registro/[id_formulario]/page.tsx
+++ /dev/null
@@ -1,306 +0,0 @@
-'use client';
-import Button from '@/components/button';
-import CheckboxOptionGroup from '@/components/checkbox-option-group';
-import Input from '@/components/input';
-import RadioOptionGroup from '@/components/radio-option-group';
-import { CuestionarioResponse } from '@/types/responder-formulario';
-import axiosInstance from '@/utils/api-config';
-import { validarRespuesta } from '@/utils/validador';
-import Image from 'next/image';
-import { useParams, useRouter } from 'next/navigation';
-import React, { useEffect, useState } from 'react';
-import toast from 'react-hot-toast';
-
-export default function Page() {
- const router = useRouter();
- const params = useParams<{ id_formulario: string }>();
- const id_formulario = params?.id_formulario;
- const [cuestionario, setCuestionario] = useState
(
- null
- );
- const [loading, setLoading] = useState(true);
- const [error, setError] = useState(null);
- const [respuestas, setRespuestas] = useState<
- Record>
- >({});
- const [isSubmitting, setIsSubmitting] = useState(false); // New state for submission
-
- useEffect(() => {
- const fetchData = async () => {
- try {
- setLoading(true);
- const { data } = await axiosInstance.get(
- `/cuestionario/${id_formulario}/formulario`
- );
- if (data) {
- console.log(data);
- setCuestionario(data);
- } else {
- setError('Error fetching data');
- }
- } catch (error) {
- console.error('Error fetching data:', error);
- setError('Error de conexión');
- } finally {
- setLoading(false);
- }
- };
-
- if (id_formulario) fetchData();
- }, [id_formulario]);
-
- const handleInputChange = (id: number, value: string) => {
- setRespuestas((prev) => ({ ...prev, [id]: value }));
- };
-
- const handleRadioChange = (id: number, opcion: { value: number }) => {
- setRespuestas((prev) => ({ ...prev, [id]: opcion.value }));
- };
-
- const handleCheckboxChange = (id: number, opcion: { value: number }) => {
- setRespuestas((prev) => {
- const actuales: number[] = Array.isArray(prev[id])
- ? (prev[id] as number[])
- : [];
- const existe = actuales.includes(opcion.value);
- const nuevos = existe
- ? actuales.filter((v: number) => v !== opcion.value)
- : [...actuales, opcion.value];
- return { ...prev, [id]: nuevos };
- });
- };
-
- const formatearRespuestas = async () => {
- if (!validarRespuestasObligatorias()) return;
-
- const respuestasArray = Object.entries(respuestas).flatMap(
- ([id, valor]) => {
- if (Array.isArray(valor)) {
- return valor.map((v) => ({
- id_pregunta: Number(id),
- valor: v,
- }));
- }
- return [{ id_pregunta: Number(id), 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(),
- };
-
- setIsSubmitting(true); // Set submitting state to 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('/');
- })
- .catch((error) => {
- console.error('Error al enviar el formulario:', error);
- })
- .finally(() => {
- setIsSubmitting(false); // Reset submitting state
- });
- };
-
- const validarRespuestasObligatorias = (): boolean => {
- const faltantes: number[] = [];
- const errores: { id: number; mensaje: string }[] = [];
-
- cuestionario?.cuestionario.secciones.forEach((seccion) => {
- seccion.preguntas.forEach(({ pregunta }) => {
- const id = pregunta.id_pregunta;
- const valor = respuestas[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) {
- if (pregunta.obligatoria) {
- faltantes.push(id);
- }
- return; // no hay nada más que validar si está vacía
- }
-
- if (tipo === 'Abierta' && validacion) {
- const resultado = validarRespuesta(valor as string, validacion);
- if (!resultado.valido) {
- errores.push({ id, mensaje: resultado.mensaje });
- }
- }
- });
- });
-
- if (faltantes.length > 0) {
- 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;
- };
-
- if (loading) return Cargando...
;
- if (error) return {error}
;
- if (!cuestionario) return No hay datos
;
-
- return (
-
-
-
-
-
-
-
{cuestionario.cuestionario.nombre_form}
-
{cuestionario.cuestionario.descripcion}
-
- {cuestionario.cuestionario.secciones.map((seccion, index) => (
-
- {seccion.seccion.titulo}
- {seccion.seccion.descripcion}
-
- {seccion.preguntas.map((p) => {
- const pregunta = p.pregunta;
- const id = pregunta.id_pregunta;
- const tipo = pregunta.tipo_pregunta.tipo_pregunta;
-
- if (tipo === 'Abierta') {
- return (
- handleInputChange(id, e.target.value)}
- required={pregunta.obligatoria}
- placeholder={
- pregunta.validacion === 'correo'
- ? 'ejemplo@correo.com'
- : pregunta.validacion === 'cuenta_alumno'
- ? '123456789'
- : undefined
- }
- type={
- pregunta.validacion === 'correo'
- ? 'email'
- : pregunta.validacion === 'cuenta_alumno'
- ? 'number'
- : 'text'
- }
- />
- );
- }
-
- if (tipo === 'Cerrada' || tipo === 'Multiple') {
- const opciones = (pregunta.opciones ?? []).map((o) => ({
- label: o.opcion.opcion,
- value: o.opcion.id_opcion,
- }));
-
- if (tipo === 'Multiple') {
- return (
-
-
{pregunta.pregunta}
-
{
- if (typeof opcion.value === 'number') {
- handleCheckboxChange(id, { value: opcion.value });
- } else {
- console.error('Invalid value type:', opcion.value);
- }
- }}
- />
-
- );
- }
-
- return (
-
-
{pregunta.pregunta}
-
{
- if (typeof opcion.value === 'number') {
- handleRadioChange(id, { value: opcion.value });
- } else {
- console.error('Invalid value type:', opcion.value);
- }
- }}
- disabled={false}
- />
-
- );
- }
-
- return null;
- })}
-
- ))}
-
-
-
-
- );
-}
diff --git a/src/app/(public)/registro/[slug]/page.tsx b/src/app/(public)/registro/[slug]/page.tsx
new file mode 100644
index 0000000..7d7d660
--- /dev/null
+++ b/src/app/(public)/registro/[slug]/page.tsx
@@ -0,0 +1,80 @@
+'use client';
+import Button from '@/components/button';
+import Formulario from '@/containers/formulario';
+import { CuestionarioResponse } from '@/types/responder-formulario';
+import axiosInstance from '@/utils/api-config';
+import Image from 'next/image';
+import { useParams, useRouter } from 'next/navigation';
+import React, { useEffect, useState } from 'react';
+
+export default function Page() {
+ const router = useRouter();
+ const params = useParams<{ slug: string }>();
+ const slug = params?.slug;
+
+ const id_formulario = slug?.split('-').pop(); // Toma el último fragmento como ID
+ const [cuestionario, setCuestionario] = useState(
+ null
+ );
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+
+ useEffect(() => {
+ const fetchData = async () => {
+ try {
+ setLoading(true);
+ const { data } = await axiosInstance.get(
+ `/cuestionario/${id_formulario}/formulario`
+ );
+ if (data) {
+ console.log(data);
+ setCuestionario(data);
+ } else {
+ setError('Error fetching data');
+ }
+ } catch (error) {
+ console.error('Error fetching data:', error);
+ setError('Error de conexión');
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ if (id_formulario) fetchData();
+ }, [id_formulario]);
+
+ if (loading) return Cargando...
;
+ if (error) return {error}
;
+ if (!cuestionario) return No hay datos
;
+
+ return (
+
+
+
+
+
+
+
{cuestionario.cuestionario.nombre_form}
+
{cuestionario.cuestionario.descripcion}
+
+
+
+
+ );
+}
diff --git a/src/containers/formulario.tsx b/src/containers/formulario.tsx
new file mode 100644
index 0000000..00fdc75
--- /dev/null
+++ b/src/containers/formulario.tsx
@@ -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(null);
+ const [cuenta, setCuenta] = useState('');
+ const [datosAuto, setDatosAuto] = useState(null);
+ const [respuestas, setRespuestas] = useState>({});
+ 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 (
+
+ );
+}
diff --git a/src/data/plantillas.ts b/src/data/plantillas.ts
index 1a0d771..dcbb65f 100644
--- a/src/data/plantillas.ts
+++ b/src/data/plantillas.ts
@@ -42,7 +42,7 @@ export const plantillasDisponibles: Plantilla[] = [
{
titulo: 'Numero de cuenta',
tipo: 'Abierta',
- obligatoria: true,
+ obligatoria: false,
limite: 250,
validacion: 'cuenta_alumno',
},
diff --git a/src/utils/helpers.ts b/src/utils/helpers.ts
new file mode 100644
index 0000000..b541f63
--- /dev/null
+++ b/src/utils/helpers.ts
@@ -0,0 +1,57 @@
+import { CuestionarioResponse } from "@/types/responder-formulario";
+import toast from "react-hot-toast";
+import { validarRespuesta } from "./validador";
+
+export const validarRespuestasObligatorias = (
+ cuestionario: CuestionarioResponse | null,
+ respuestas: Record>
+ ): boolean => {
+ const faltantes: number[] = [];
+ const errores: { id: number; mensaje: string }[] = [];
+
+ cuestionario?.cuestionario.secciones.forEach((seccion) => {
+ seccion.preguntas.forEach(({ pregunta }) => {
+ const id = pregunta.id_pregunta;
+ const valor = respuestas[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) {
+ if (pregunta.obligatoria) {
+ faltantes.push(id);
+ }
+ return; // no hay nada más que validar si está vacía
+ }
+
+ if (tipo === 'Abierta' && validacion) {
+ const resultado = validarRespuesta(valor as string, validacion);
+ if (!resultado.valido) {
+ errores.push({ id, mensaje: resultado.mensaje });
+ }
+ }
+ });
+ });
+
+ if (faltantes.length > 0) {
+ 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;
+ };
\ No newline at end of file