diff --git a/diplomas/src/components/app-sidebar.jsx b/diplomas/src/components/app-sidebar.jsx index 628aa4a..62d21dc 100644 --- a/diplomas/src/components/app-sidebar.jsx +++ b/diplomas/src/components/app-sidebar.jsx @@ -53,15 +53,6 @@ const data = { }, ], }, - { - title: "Competencias", - items: [ - { - title: "Vista de competencias", - url: "/competenciasVista", - }, - ], - }, { title: "Diplomas", items: [ diff --git a/diplomas/src/pages/alumnosArchivo.jsx b/diplomas/src/pages/alumnosArchivo.jsx index 91026ee..da5df59 100644 --- a/diplomas/src/pages/alumnosArchivo.jsx +++ b/diplomas/src/pages/alumnosArchivo.jsx @@ -3,13 +3,17 @@ import Papa from "papaparse"; import * as XLSX from "xlsx"; import Layout from "@/components/layout/Layout"; import { Button } from "@/components/ui/button"; -import { Dialog, DialogTrigger, DialogContent, DialogHeader, DialogTitle, DialogDescription } from "@/components/ui/dialog"; +import { Dialog, DialogTrigger, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from "@/components/ui/dialog"; +import { CursosManualForm } from "./cursosManual"; // Importa el formulario sin Layout +import { supabaseClient } from "@/utils/supabase"; export default function AlumnosArchivo() { const [archivo, setArchivo] = useState(null); const [datos, setDatos] = useState([]); const [dialogoAbierto, setDialogoAbierto] = useState(false); const [mensajeDialogo, setMensajeDialogo] = useState(""); + const [mostrarDialogCurso, setMostrarDialogCurso] = useState(false); + const [cursoFaltante, setCursoFaltante] = useState(""); useEffect(() => { if (archivo) extraerContenido(); @@ -21,13 +25,36 @@ export default function AlumnosArchivo() { const errores = []; for (const alumno of datos) { + // 1. Verifica si el curso existe + const { data: cursosEncontrados, error: errorCurso } = await supabaseClient + .from("curso") + .select("id") + .eq("nombre", alumno.nombreCurso) + .maybeSingle(); + + if (errorCurso) { + errores.push({ alumno, error: "Error al buscar el curso" }); + continue; + } + + if (!cursosEncontrados) { + // Si no existe el curso, muestra el dialog para registrar el curso + setCursoFaltante(alumno.nombreCurso); + setMostrarDialogCurso(true); + setMensajeDialogo(`El curso "${alumno.nombreCurso}" no existe. Por favor, regístralo primero.`); + setDialogoAbierto(true); + return; // Detiene el registro de alumnos + } + + // 2. Si existe, registra el alumno con el curso_id correcto const res = await fetch("/api/alumno", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ nombre: alumno.nombre, correo: alumno.correo, - nombreCurso: alumno.nombreCurso, + telefono: alumno.telefono, + curso_id: cursosEncontrados.id, // Usar el id del curso }), }); @@ -161,7 +188,25 @@ export default function AlumnosArchivo() { - {/* Dialog Component */} + {/* Dialog para curso faltante */} + + + + + Registrar curso faltante + + + El curso {cursoFaltante} no existe. Por favor, regístralo antes de continuar. + + + + + setMostrarDialogCurso(false)}>Cerrar + + + + + {/* Dialog de información */} diff --git a/diplomas/src/pages/alumnosManual.jsx b/diplomas/src/pages/alumnosManual.jsx index 51539df..2a6a72f 100644 --- a/diplomas/src/pages/alumnosManual.jsx +++ b/diplomas/src/pages/alumnosManual.jsx @@ -66,7 +66,7 @@ export default function AlumnosManual() { nombre: data.nombre, correo: data.correo, telefono: data.telefono, - nombreCurso: data.cursoSeleccionado, // Guardar el nombre del curso + curso_id: Number(data.cursoSeleccionado), // Guardar el ID del curso }, ]); @@ -138,7 +138,7 @@ export default function AlumnosManual() { {cursos.map((curso) => ( - + {curso.nombre} ))} diff --git a/diplomas/src/pages/cursosManual.jsx b/diplomas/src/pages/cursosManual.jsx index 6b9b2d4..59b67e9 100644 --- a/diplomas/src/pages/cursosManual.jsx +++ b/diplomas/src/pages/cursosManual.jsx @@ -96,116 +96,244 @@ export default function CursosManual() { Nuevo curso - - - {errors.nombre && ( - {errors.nombre.message} - )} - - - {errors.descripcion && ( - - {errors.descripcion.message} - - )} - - - {errors.horas && ( - {errors.horas.message} - )} - - - Competencias - - - {competenciasGuardadas.length > 0 && ( - - {competenciasGuardadas.map((competencia, index) => ( - - {competencia} - handleDeleteCompetencia(index)} - className="bg-red-400 hover:bg-red-500 text-white font-bold py-1 px-3 rounded-md" - > - X - - - ))} - - )} - - {addCompetencia && ( - - - - {errors.nuevaCompetencia && ( - - {errors.nuevaCompetencia.message} - - )} - - - - Guardar - - - Cancelar - - - - )} - - - Agregar competencia - - - - - {loading ? "Guardando..." : "Guardar curso"} - - - + ); } + +export function CursosManualForm({ nombreSugerido = "" }) { + const [addCompetencia, setAddCompetencia] = useState(false); + const [competenciasGuardadas, setCompetenciasGuardadas] = useState([]); // [{id, descripcion}] + const [loading, setLoading] = useState(false); + + const form = useForm({ + resolver: zodResolver(cursosSchema), + defaultValues: { + nombre: nombreSugerido, + descripcion: "", + horas: 0, + nuevaCompetencia: "", + }, + }); + + const { + register, + handleSubmit, + setValue, + getValues, + formState: { errors }, + } = form; + + // Cambia para insertar la competencia en la tabla competencia + const handleSaveCompetencia = async (e) => { + e.preventDefault(); + const nuevaCompetencia = getValues("nuevaCompetencia").trim(); + if (!nuevaCompetencia) return; + + // Verifica si ya existe en el estado + if (competenciasGuardadas.some(c => c.descripcion === nuevaCompetencia)) { + alert("La competencia ya fue agregada."); + return; + } + + // Verifica si ya existe en la base de datos + let competenciaId = null; + try { + // Busca si ya existe + const { data: existente } = await supabaseClient + .from("competencia") + .select("id") + .eq("descripcion", nuevaCompetencia) + .maybeSingle(); + + if (existente && existente.id) { + competenciaId = existente.id; + } else { + // Si no existe, la crea + const { data: insertada, error } = await supabaseClient + .from("competencia") + .insert([{ descripcion: nuevaCompetencia }]) + .select("id") + .single(); + if (error) throw error; + competenciaId = insertada.id; + } + + setCompetenciasGuardadas([ + ...competenciasGuardadas, + { id: competenciaId, descripcion: nuevaCompetencia }, + ]); + setAddCompetencia(false); + setValue("nuevaCompetencia", ""); + } catch (err) { + alert("Error al guardar la competencia: " + (err.message || err)); + } + }; + + const handleDeleteCompetencia = (index) => { + setCompetenciasGuardadas( + competenciasGuardadas.filter((_, i) => i !== index) + ); + }; + + // Guardar curso y asociar competencias + const onSubmit = async (data) => { + const { nombre, descripcion } = data; + const horas = parseInt(data.horas, 10); + + setLoading(true); + + try { + // 1. Inserta el curso + const { data: cursoInsertado, error: errorCurso } = await supabaseClient + .from("curso") + .insert([{ nombre, descripcion, horas }]) + .select("id") + .single(); + + if (errorCurso) { + alert("Error al guardar el curso: " + errorCurso.message); + setLoading(false); + return; + } + + // 2. Inserta en la tabla pivote curso_competencia + const cursoId = cursoInsertado.id; + const relaciones = competenciasGuardadas.map((c) => ({ + curso_id: cursoId, + competencia_id: c.id, + })); + + if (relaciones.length > 0) { + const { error: errorPivote } = await supabaseClient + .from("curso_competencia") + .insert(relaciones); + if (errorPivote) { + alert("Error al asociar competencias: " + errorPivote.message); + setLoading(false); + return; + } + } + + alert("Curso guardado exitosamente"); + form.reset(); + setCompetenciasGuardadas([]); + } catch (err) { + alert("Ocurrió un error inesperado"); + } finally { + setLoading(false); + } + }; + + return ( + + + {errors.nombre && ( + {errors.nombre.message} + )} + + + {errors.descripcion && ( + {errors.descripcion.message} + )} + + + {errors.horas && ( + {errors.horas.message} + )} + + Competencias + + {competenciasGuardadas.length > 0 && ( + + {competenciasGuardadas.map((competencia, index) => ( + + {competencia.descripcion} + handleDeleteCompetencia(index)} + className="bg-red-400 hover:bg-red-500 text-white font-bold py-1 px-3 rounded-md" + > + X + + + ))} + + )} + + {addCompetencia && ( + + + + {errors.nuevaCompetencia && ( + + {errors.nuevaCompetencia.message} + + )} + + + + Guardar + + { + setAddCompetencia(false); + setValue("nuevaCompetencia", ""); + }} + className="bg-gray-400 hover:bg-gray-500 text-white font-bold py-2 px-4 rounded-md" + > + Cancelar + + + + )} + + setAddCompetencia(true)} + className="w-full bg-blue-400 hover:bg-blue-500 text-white font-bold py-2 px-4 rounded-md mt-5" + > + Agregar competencia + + + + + {loading ? "Guardando..." : "Guardar curso"} + + + + ); +} diff --git a/diplomas/src/schemas/AlumnosSchema.js b/diplomas/src/schemas/AlumnosSchema.js index 01a389a..5047914 100644 --- a/diplomas/src/schemas/AlumnosSchema.js +++ b/diplomas/src/schemas/AlumnosSchema.js @@ -6,5 +6,9 @@ export const alumnoSchema = z.object({ .nonempty("Escribe el nombre") .regex(/^[\p{L}\s]+$/u, "Solo se permiten letras en el nombre"), correo: z.string().email("Escribe un correo válido"), + telefono: z + .string() + .nonempty("Escribe el número de teléfono") + .regex(/^\d+$/, "Solo se permiten números en el teléfono"), cursoSeleccionado: z.string().nonempty("Selecciona un curso"), });
{errors.nombre.message}
- {errors.descripcion.message} -
{errors.horas.message}
- {errors.nuevaCompetencia.message} -
{errors.descripcion.message}
+ {errors.nuevaCompetencia.message} +