Refactor curso and diploma management pages for improved readability and functionality

- Updated `cursosArchivo.jsx` to enhance dialog imports and improve code formatting.
- Refactored `cursosManual.jsx` to streamline competency management and improve user feedback through dialogs.
- Enhanced `cursosVista.jsx` for better table layout and added functionality for managing course competencies.
- Improved `diplomasVista.jsx` layout for better responsiveness and removed unused dialog component.
This commit is contained in:
SirRobert-1 2025-05-23 15:33:53 -06:00
parent 65893bf052
commit 985af4f2fd
9 changed files with 617 additions and 706 deletions

View File

@ -1,7 +1,26 @@
"use client";
import { AppSidebar } from "@/components/app-sidebar";
import { SidebarInset, SidebarProvider } from "@/components/ui/sidebar";
import {
SidebarInset,
SidebarProvider,
SidebarTrigger,
useSidebar,
} from "@/components/ui/sidebar";
function MainContent({ children }) {
const { open } = useSidebar();
return (
<div
className={`p-4 min-h-screen flex justify-center transition-all duration-200 ${
open ? "w-screen md:w-[80vw]" : "w-screen"
}`}
>
{children}
</div>
);
}
export default function Layout({ children }) {
return (
@ -9,7 +28,8 @@ export default function Layout({ children }) {
<div className="flex">
<AppSidebar />
<SidebarInset>
<div className="p-4 w-full">{children}</div>
<SidebarTrigger className="-ml-1 text-black" />
<MainContent>{children}</MainContent>
</SidebarInset>
</div>
</SidebarProvider>

View File

@ -238,7 +238,7 @@ function SidebarTrigger({ className, onClick, ...props }) {
data-slot="sidebar-trigger"
variant="ghost"
size="icon"
className={cn("size-7", className)}
className={cn("size-10", className)}
onClick={(event) => {
onClick?.(event);
toggleSidebar();

View File

@ -3,7 +3,15 @@ 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, DialogFooter } 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";
@ -26,11 +34,12 @@ export default function AlumnosArchivo() {
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();
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" });
@ -41,7 +50,9 @@ export default function AlumnosArchivo() {
// 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.`);
setMensajeDialogo(
`El curso "${alumno.nombreCurso}" no existe. Por favor, regístralo primero.`
);
setDialogoAbierto(true);
return; // Detiene el registro de alumnos
}
@ -65,7 +76,9 @@ export default function AlumnosArchivo() {
}
if (errores.length > 0) {
setMensajeDialogo(`Se registraron algunos errores:\n${JSON.stringify(errores, null, 2)}`);
setMensajeDialogo(
`Se registraron algunos errores:\n${JSON.stringify(errores, null, 2)}`
);
} else {
setMensajeDialogo("Todos los alumnos fueron registrados correctamente.");
setArchivo(null);
@ -130,8 +143,8 @@ export default function AlumnosArchivo() {
return (
<Layout>
<div className="w-[60vw] pt-10 flex flex-col items-end justify-center text-black">
<div className="bg-white p-8 font-sans text-center w-[70%] flex flex-col items-center">
<div className="w-full pt-10 flex flex-col items-start md:items-center md:justify-center text-black">
<div className="bg-white font-sans text-center w-full flex flex-col items-center">
<h1 className="text-xl font-semibold mb-4 text-black">
Nuevo alumno
</h1>
@ -144,7 +157,9 @@ export default function AlumnosArchivo() {
{archivo ? (
<span className="text-black font-medium">{archivo.name}</span>
) : (
<span>Arrastra y suelta un archivo o haz clic para seleccionarlo</span>
<span>
Arrastra y suelta un archivo o haz clic para seleccionarlo
</span>
)}
<input
type="file"
@ -169,7 +184,9 @@ export default function AlumnosArchivo() {
<thead className="bg-gray-100 text-gray-700">
<tr>
{Object.keys(datos[0]).map((columna, index) => (
<th key={index} className="border px-4 py-2">{columna}</th>
<th key={index} className="border px-4 py-2">
{columna}
</th>
))}
</tr>
</thead>
@ -177,7 +194,9 @@ export default function AlumnosArchivo() {
{datos.map((fila, index) => (
<tr key={index}>
{Object.values(fila).map((valor, i) => (
<td key={i} className="border px-4 py-1">{valor}</td>
<td key={i} className="border px-4 py-1">
{valor}
</td>
))}
</tr>
))}
@ -196,7 +215,8 @@ export default function AlumnosArchivo() {
Registrar curso faltante
</DialogTitle>
<DialogDescription>
El curso <b>{cursoFaltante}</b> no existe. Por favor, regístralo antes de continuar.
El curso <b>{cursoFaltante}</b> no existe. Por favor, regístralo
antes de continuar.
</DialogDescription>
</DialogHeader>
<CursosManualForm nombreSugerido={cursoFaltante} />
@ -217,4 +237,4 @@ export default function AlumnosArchivo() {
</Dialog>
</Layout>
);
}
}

View File

@ -84,80 +84,66 @@ export default function AlumnosManual() {
return (
<Layout>
<div className="w-[60vw] pt-10 flex flex-col items-end justify-center text-black">
<div className="bg-white p-8 font-sans text-center w-[70%]">
<h1 className="text-xl font-semibold mb-4 text-black">
Nuevo alumno
</h1>
<form onSubmit={handleSubmit(manejarGuardar)}>
<div className="mb-3">
<Input
type="text"
placeholder="Nombre"
{...register("nombre")}
className="w-full px-3 py-2 border border-gray-300 rounded-md"
/>
{errors.nombre && (
<p className="text-red-500 text-sm mt-1">
{errors.nombre.message}
</p>
)}
</div>
<div className="mb-3">
<Input
type="text"
placeholder="Email"
{...register("correo")}
className="w-full px-3 py-2 border border-gray-300 rounded-md"
/>
{errors.correo && (
<p className="text-red-500 text-sm mt-1">
{errors.correo.message}
</p>
)}
</div>
<div className="mb-3">
<Input
type="text"
placeholder="Teléfono"
{...register("telefono")}
className="w-full px-3 py-2 border border-gray-300 rounded-md"
/>
{errors.telefono && (
<p className="text-red-500 text-sm mt-1">
{errors.telefono.message}
</p>
)}
</div>
<div className="mb-4">
<Select
onValueChange={(value) => setValue("cursoSeleccionado", value)}
>
<SelectTrigger className="w-full px-3 py-2 border border-gray-300 rounded-md">
<SelectValue placeholder="Selecciona un curso" />
</SelectTrigger>
<SelectContent>
{cursos.map((curso) => (
<SelectItem key={curso.id} value={curso.id.toString()}>
{curso.nombre}
</SelectItem>
))}
</SelectContent>
</Select>
{errors.cursoSeleccionado && (
<p className="text-red-500 text-sm mt-1">
{errors.cursoSeleccionado.message}
</p>
)}
</div>
<Button
type="submit"
className="bg-green-400 hover:bg-green-500 font-bold py-2 px-4 rounded-md text-black"
>
Registrar
</Button>
</form>
</div>
<div className="w-full bg-white font-sans text-center md:w-[80%] pt-10 flex flex-col items-center justify-start text-black">
<h1 className="text-xl font-semibold mb-10 text-black my-10">
Nuevo alumno
</h1>
<form onSubmit={handleSubmit(manejarGuardar)} className="w-full">
<Input
type="text"
placeholder="Nombre"
{...register("nombre")}
className="w-full px-3 py-2 border border-gray-300 rounded-md mb-3"
/>
{errors.nombre && (
<p className="text-red-500 text-sm mt-1">{errors.nombre.message}</p>
)}
<Input
type="text"
placeholder="Email"
{...register("correo")}
className="w-full px-3 py-2 border border-gray-300 rounded-md mb-3"
/>
{errors.correo && (
<p className="text-red-500 text-sm mt-1">{errors.correo.message}</p>
)}
<Input
type="text"
placeholder="Teléfono"
{...register("telefono")}
className="w-full px-3 py-2 border border-gray-300 rounded-md mb-3"
/>
{errors.telefono && (
<p className="text-red-500 text-sm mt-1">
{errors.telefono.message}
</p>
)}
<Select
onValueChange={(value) => setValue("cursoSeleccionado", value)}
>
<SelectTrigger className="w-full px-3 py-2 border border-gray-300 rounded-md mb-3">
<SelectValue placeholder="Selecciona un curso" />
</SelectTrigger>
<SelectContent>
{cursos.map((curso) => (
<SelectItem key={curso.id} value={curso.id.toString()}>
{curso.nombre}
</SelectItem>
))}
</SelectContent>
</Select>
{errors.cursoSeleccionado && (
<p className="text-red-500 text-sm mt-1">
{errors.cursoSeleccionado.message}
</p>
)}
<Button
type="submit"
className="bg-green-400 hover:bg-green-500 font-bold py-2 px-4 rounded-md text-white"
>
Registrar
</Button>
</form>
</div>
{/* Diálogo de confirmación */}

View File

@ -142,124 +142,126 @@ export default function AlumnosVista() {
return (
<Layout>
<div className="w-[80vw] pt-10 flex flex-col items-center text-black">
<h1 className="text-2xl font-semibold mb-6 text-black">
<div className="w-full pt-5 flex flex-col items-center text-black">
<h1 className="text-2xl font-semibold mt-5 mb-10 text-black">
Lista de Alumnos
</h1>
<table className="min-w-full bg-white border">
<thead>
<tr className="bg-gray-100">
<th className="py-2 border-b">ID</th>
<th className="py-2 border-b">Nombre</th>
<th className="py-2 border-b">Correo</th>
<th className="py-2 border-b">Teléfono</th>
<th className="py-2 border-b">Curso</th>
<th className="py-2 border-b">Acciones</th>
</tr>
</thead>
<tbody>
{alumnos.map((alumno) =>
alumnoEditando === alumno.id ? (
<tr key={alumno.id}>
<td className="py-2 px-4 border-b text-center">
{alumno.id}
</td>
<td className="py-2 px-4 border-b">
<Input type="text" {...register("nombre")} />
{errors.nombre && (
<span className="text-red-500 text-xs">
{errors.nombre.message}
</span>
)}
</td>
<td className="py-2 px-4 border-b">
<Input type="email" {...register("correo")} />
{errors.correo && (
<span className="text-red-500 text-xs">
{errors.correo.message}
</span>
)}
</td>
<td className="py-2 px-4 border-b">
<Input type="text" {...register("telefono")} />
{errors.telefono && (
<span className="text-red-500 text-xs">
{errors.telefono.message}
</span>
)}
</td>
<td className="py-2 px-4 border-b">
<Select
value={undefined}
onValueChange={(value) =>
setValue("cursoSeleccionado", value)
}
{...register("cursoSeleccionado")}
>
<SelectTrigger>
<SelectValue placeholder="Selecciona un curso" />
</SelectTrigger>
<SelectContent>
{cursos.map((curso) => (
<SelectItem
key={curso.id}
value={curso.id.toString()}
>
{curso.nombre}
</SelectItem>
))}
</SelectContent>
</Select>
{errors.cursoSeleccionado && (
<span className="text-red-500 text-xs">
{errors.cursoSeleccionado.message}
</span>
)}
</td>
<td className="py-2 px-4 border-b flex justify-center">
<Button
className="bg-green-500 hover:bg-green-700 text-white font-bold py-1 px-3 m-2 rounded"
onClick={handleSubmit(guardarEdicion)}
>
Guardar
</Button>
<Button
className="bg-gray-400 hover:bg-gray-600 text-white font-bold py-1 px-3 m-2 rounded"
onClick={cancelarEdicion}
>
Cancelar
</Button>
</td>
</tr>
) : (
<tr key={alumno.id}>
<td className="py-2 px-4 border-b">{alumno.id}</td>
<td className="py-2 px-4 border-b">{alumno.nombre}</td>
<td className="py-2 px-4 border-b">{alumno.correo}</td>
<td className="py-2 px-4 border-b">{alumno.telefono}</td>
<td className="py-2 px-4 border-b">
{alumno.curso?.nombre || "Sin curso"}
</td>
<td className="py-2 px-4 border-b space-x-2">
<Button
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-1 px-3 rounded"
onClick={() => iniciarEdicion(alumno)}
>
Editar
</Button>
<div className="overflow-x-auto w-full">
<table className="min-w-full bg-white border">
<thead>
<tr className="bg-gray-100">
<th className="py-2 border-b">ID</th>
<th className="py-2 border-b">Nombre</th>
<th className="py-2 border-b">Correo</th>
<th className="py-2 border-b">Teléfono</th>
<th className="py-2 border-b">Curso</th>
<th className="py-2 border-b">Acciones</th>
</tr>
</thead>
<tbody>
{alumnos.map((alumno) =>
alumnoEditando === alumno.id ? (
<tr key={alumno.id}>
<td className="py-2 px-4 border-b text-center">
{alumno.id}
</td>
<td className="py-2 px-4 border-b">
<Input type="text" {...register("nombre")} />
{errors.nombre && (
<span className="text-red-500 text-xs">
{errors.nombre.message}
</span>
)}
</td>
<td className="py-2 px-4 border-b">
<Input type="email" {...register("correo")} />
{errors.correo && (
<span className="text-red-500 text-xs">
{errors.correo.message}
</span>
)}
</td>
<td className="py-2 px-4 border-b">
<Input type="text" {...register("telefono")} />
{errors.telefono && (
<span className="text-red-500 text-xs">
{errors.telefono.message}
</span>
)}
</td>
<td className="py-2 px-4 border-b">
<Select
value={(alumno.curso_id || "").toString()}
onValueChange={(value) =>
setValue("cursoSeleccionado", value)
}
{...register("cursoSeleccionado")}
>
<SelectTrigger>
<SelectValue placeholder="Selecciona un curso" />
</SelectTrigger>
<SelectContent>
{cursos.map((curso) => (
<SelectItem
key={curso.id}
value={curso.id.toString()}
>
{curso.nombre}
</SelectItem>
))}
</SelectContent>
</Select>
{errors.cursoSeleccionado && (
<span className="text-red-500 text-xs">
{errors.cursoSeleccionado.message}
</span>
)}
</td>
<td className="py-2 px-4 border-b flex justify-center">
<Button
className="bg-green-500 hover:bg-green-700 text-white font-bold py-1 px-3 m-1 rounded"
onClick={handleSubmit(guardarEdicion)}
>
Guardar
</Button>
<Button
className="bg-gray-400 hover:bg-gray-600 text-white font-bold py-1 px-3 m-1 rounded"
onClick={cancelarEdicion}
>
Cancelar
</Button>
</td>
</tr>
) : (
<tr key={alumno.id}>
<td className="py-2 px-4 border-b">{alumno.id}</td>
<td className="py-2 px-4 border-b">{alumno.nombre}</td>
<td className="py-2 px-4 border-b">{alumno.correo}</td>
<td className="py-2 px-4 border-b">{alumno.telefono}</td>
<td className="py-2 px-4 border-b">
{alumno.curso?.nombre || "Sin curso"}
</td>
<td className="py-2 px-4 border-b flex justify-center">
<Button
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-1 px-3 m-1 rounded"
onClick={() => iniciarEdicion(alumno)}
>
Editar
</Button>
<Button
className="bg-red-500 hover:bg-red-700 text-white font-bold py-1 px-3 rounded"
onClick={() => confirmarEliminacion(alumno.id)}
>
Eliminar
</Button>
</td>
</tr>
)
)}
</tbody>
</table>
<Button
className="bg-red-500 hover:bg-red-700 text-white font-bold py-1 px-3 m-1 rounded"
onClick={() => confirmarEliminacion(alumno.id)}
>
Eliminar
</Button>
</td>
</tr>
)
)}
</tbody>
</table>
</div>
</div>
{/* Modal de confirmación */}

View File

@ -3,7 +3,15 @@ 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, DialogFooter } from "@/components/ui/dialog";
import {
Dialog,
DialogTrigger,
DialogContent,
DialogHeader,
DialogTitle,
DialogDescription,
DialogFooter,
} from "@/components/ui/dialog";
import { useRouter } from "next/router";
export default function cursosArchivo() {
@ -53,7 +61,7 @@ export default function cursosArchivo() {
horas: curso.horas,
descripcion: curso.descripcion,
competencias: curso.competencias
? curso.competencias.split(",").map(c => c.trim())
? curso.competencias.split(",").map((c) => c.trim())
: [],
}),
});
@ -67,7 +75,9 @@ export default function cursosArchivo() {
setDialogoCargando(false); // Ocultar dialogo de carga
if (errores.length > 0) {
setMensajeDialogo(`Se registraron algunos errores:\n${JSON.stringify(errores, null, 2)}`);
setMensajeDialogo(
`Se registraron algunos errores:\n${JSON.stringify(errores, null, 2)}`
);
} else {
setMensajeDialogo("Todos los cursos fueron registrados correctamente.");
setArchivo(null);
@ -132,11 +142,9 @@ export default function cursosArchivo() {
return (
<Layout>
<div className="w-[60vw] pt-10 flex flex-col items-end justify-center text-black">
<div className="bg-white p-8 font-sans text-center w-[70%] flex flex-col items-center">
<h1 className="text-xl font-semibold mb-4 text-black">
Nuevo curso
</h1>
<div className="w-full pt-10 flex flex-col items-start md:items-center md:justify-center text-black">
<div className="bg-white font-sans text-center w-full flex flex-col items-center">
<h1 className="text-xl font-semibold mb-4 text-black">Nuevo curso</h1>
<label
htmlFor="archivo"
onDrop={manejarSoltar}
@ -146,7 +154,9 @@ export default function cursosArchivo() {
{archivo ? (
<span className="text-black font-medium">{archivo.name}</span>
) : (
<span>Arrastra y suelta un archivo o haz clic para seleccionarlo</span>
<span>
Arrastra y suelta un archivo o haz clic para seleccionarlo
</span>
)}
<input
type="file"
@ -171,7 +181,9 @@ export default function cursosArchivo() {
<thead className="bg-gray-100 text-gray-700">
<tr>
{Object.keys(datos[0]).map((columna, index) => (
<th key={index} className="border px-4 py-2">{columna}</th>
<th key={index} className="border px-4 py-2">
{columna}
</th>
))}
</tr>
</thead>
@ -179,7 +191,9 @@ export default function cursosArchivo() {
{datos.map((fila, index) => (
<tr key={index}>
{Object.values(fila).map((valor, i) => (
<td key={i} className="border px-4 py-1">{valor}</td>
<td key={i} className="border px-4 py-1">
{valor}
</td>
))}
</tr>
))}
@ -216,7 +230,8 @@ export default function cursosArchivo() {
<DialogHeader>
<DialogTitle className="text-black">Advertencia</DialogTitle>
<DialogDescription>
Si cambias de ventana perderás la subida del archivo. ¿Deseas continuar?
Si cambias de ventana perderás la subida del archivo. ¿Deseas
continuar?
</DialogDescription>
</DialogHeader>
<DialogFooter>
@ -242,4 +257,4 @@ export default function cursosArchivo() {
</Dialog>
</Layout>
);
}
}

View File

@ -6,7 +6,7 @@ import Layout from "@/components/layout/Layout";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea";
import { supabaseClient } from "@/utils/supabase"; // Importar el cliente de Supabase
import { supabaseClient } from "@/utils/supabase";
import {
Dialog,
DialogContent,
@ -17,11 +17,11 @@ import {
} from "@/components/ui/dialog";
export default function CursosManual() {
const [addCompetencia, setAddCompetencia] = useState(false);
const [competenciasGuardadas, setCompetenciasGuardadas] = useState([]);
const [competencias, setCompetencias] = useState([]); // [{id, descripcion}]
const [showDialog, setShowDialog] = useState(false);
const [dialogMsg, setDialogMsg] = useState("");
const [loading, setLoading] = useState(false);
const [mostrarDialog, setMostrarDialog] = useState(false);
const [mensajeDialog, setMensajeDialog] = useState("");
const [addCompetencia, setAddCompetencia] = useState(false);
const form = useForm({
resolver: zodResolver(cursosSchema),
@ -32,7 +32,6 @@ export default function CursosManual() {
nuevaCompetencia: "",
},
});
const {
register,
handleSubmit,
@ -41,348 +40,211 @@ export default function CursosManual() {
formState: { errors },
} = form;
const handleAddCompetencia = () => {
setAddCompetencia(true);
};
const handleCancel = () => {
setAddCompetencia(false);
setValue("nuevaCompetencia", "");
};
const handleSaveCompetencia = (e) => {
e.preventDefault();
const nuevaCompetencia = getValues("nuevaCompetencia");
if (nuevaCompetencia.trim() !== "") {
setCompetenciasGuardadas([
...competenciasGuardadas,
nuevaCompetencia.trim(),
]);
handleCancel();
}
};
const handleDeleteCompetencia = (index) => {
setCompetenciasGuardadas(
competenciasGuardadas.filter((_, i) => i !== index)
);
};
const onSubmit = async (data) => {
const { nombre, descripcion } = data;
const horas = parseInt(data.horas, 10); // Convertir horas a número
const competencias = competenciasGuardadas;
setLoading(true); // Mostrar estado de carga
try {
const { error } = await supabaseClient.from("curso").insert([
{
nombre,
descripcion,
horas,
competencias, // Guardar competencias como array
},
]);
if (error) {
console.error("Error al guardar en Supabase:", error.message);
alert("Error al guardar el curso: " + error.message);
} else {
setMensajeDialog("Curso guardado exitosamente");
setMostrarDialog(true);
form.reset(); // Reiniciar el formulario
setCompetenciasGuardadas([]); // Limpiar competencias guardadas
}
} catch (err) {
console.error("Error inesperado:", err);
alert("Ocurrió un error inesperado");
} finally {
setLoading(false); // Ocultar estado de carga
}
};
return (
<Layout>
<div className="w-[60vw] pt-10 flex flex-col items-end justify-center">
<div className="bg-white p-8 font-sans text-center w-[70%] flex flex-col items-center">
<h1 className="text-xl font-semibold mb-4 text-black">Nuevo curso</h1>
<CursosManualForm nombreSugerido="" />
</div>
</div>
</Layout>
);
}
export function CursosManualForm({ nombreSugerido = "" }) {
const [addCompetencia, setAddCompetencia] = useState(false);
const [competenciasGuardadas, setCompetenciasGuardadas] = useState([]); // [{id, descripcion}]
const [loading, setLoading] = useState(false);
const [mostrarDialog, setMostrarDialog] = useState(false);
const [mensajeDialog, setMensajeDialog] = useState("");
// Estado para dialog de competencia agregada
const [mostrarDialogCompetencia, setMostrarDialogCompetencia] = useState(false);
const form = useForm({
resolver: zodResolver(cursosSchema),
defaultValues: {
nombre: nombreSugerido,
descripcion: "",
horas: 0,
nuevaCompetencia: "",
},
});
const {
register,
handleSubmit,
setValue,
getValues,
formState: { errors },
} = form;
// Cambia handleSaveCompetencia para mostrar el dialog
// Añadir competencia (busca o crea en BD)
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.");
const desc = getValues("nuevaCompetencia").trim();
if (!desc) return;
if (competencias.some((c) => c.descripcion === desc)) {
setDialogMsg("La competencia ya fue agregada.");
setShowDialog(true);
return;
}
// Verifica si ya existe en la base de datos
let competenciaId = null;
try {
// Busca si ya existe
const { data: existente } = await supabaseClient
let { data: existente } = await supabaseClient
.from("competencia")
.select("id")
.eq("descripcion", nuevaCompetencia)
.eq("descripcion", desc)
.maybeSingle();
if (existente && existente.id) {
competenciaId = existente.id;
} else {
// Si no existe, la crea
let id = existente?.id;
if (!id) {
const { data: insertada, error } = await supabaseClient
.from("competencia")
.insert([{ descripcion: nuevaCompetencia }])
.insert([{ descripcion: desc }])
.select("id")
.single();
if (error) throw error;
competenciaId = insertada.id;
id = insertada.id;
}
setCompetenciasGuardadas([
...competenciasGuardadas,
{ id: competenciaId, descripcion: nuevaCompetencia },
]);
setCompetencias([...competencias, { id, descripcion: desc }]);
setAddCompetencia(false);
setValue("nuevaCompetencia", "");
setMostrarDialogCompetencia(true); // Mostrar dialog de éxito
setDialogMsg("¡La competencia fue agregada exitosamente!");
setShowDialog(true);
} catch (err) {
alert("Error al guardar la competencia: " + (err.message || err));
setDialogMsg("Error al guardar la competencia: " + (err.message || err));
setShowDialog(true);
}
};
// Eliminar competencia
const handleDeleteCompetencia = (index) => {
setCompetenciasGuardadas(
competenciasGuardadas.filter((_, i) => i !== index)
);
setCompetencias(competencias.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
const { nombre, descripcion, horas } = data;
const { data: curso, error: errorCurso } = await supabaseClient
.from("curso")
.insert([{ nombre, descripcion, horas }])
.insert([{ nombre, descripcion, horas: parseInt(horas, 10) }])
.select("id")
.single();
if (errorCurso) {
setMensajeDialog("Error al guardar el curso: " + errorCurso.message);
setMostrarDialog(true);
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) {
if (errorCurso) throw errorCurso;
if (competencias.length) {
const relaciones = competencias.map((c) => ({
curso_id: curso.id,
competencia_id: c.id,
}));
const { error: errorPivote } = await supabaseClient
.from("curso_competencia")
.insert(relaciones);
if (errorPivote) {
setMensajeDialog("Error al asociar competencias: " + errorPivote.message);
setMostrarDialog(true);
setLoading(false);
return;
}
if (errorPivote) throw errorPivote;
}
setMensajeDialog("Curso guardado exitosamente");
setMostrarDialog(true);
setDialogMsg("Curso guardado exitosamente");
setCompetencias([]);
form.reset();
setCompetenciasGuardadas([]);
} catch (err) {
alert("Ocurrió un error inesperado");
setDialogMsg("Error: " + (err.message || err));
} finally {
setShowDialog(true);
setLoading(false);
}
};
return (
<form onSubmit={handleSubmit(onSubmit)} className="w-full">
<Input
type="text"
placeholder="Nombre del curso"
{...register("nombre")}
className="w-full px-3 py-2 border border-gray-300 rounded-md mb-3 text-black"
/>
{errors.nombre && (
<p className="text-red-500 text-sm">{errors.nombre.message}</p>
)}
<Layout>
<div className="w-full bg-white pt-5 font-sans text-center md:w-[80%] flex flex-col items-center justify-start text-black">
<h1 className="text-xl font-semibold mb-10 text-black">Nuevo curso</h1>
<form onSubmit={handleSubmit(onSubmit)} className="w-full">
<Input
type="text"
placeholder="Nombre del curso"
{...register("nombre")}
className="w-full px-3 py-2 border border-gray-300 rounded-md mb-3 text-black"
/>
{errors.nombre && (
<p className="text-red-500 text-sm">{errors.nombre.message}</p>
)}
<Textarea
placeholder="Descripción"
{...register("descripcion")}
className="w-full px-3 py-2 border border-gray-300 rounded-md mb-3 h-24 text-black"
/>
{errors.descripcion && (
<p className="text-red-500 text-sm">{errors.descripcion.message}</p>
)}
<Textarea
placeholder="Descripción"
{...register("descripcion")}
className="w-full px-3 py-2 border border-gray-300 rounded-md mb-3 h-24 text-black"
/>
{errors.descripcion && (
<p className="text-red-500 text-sm">{errors.descripcion.message}</p>
)}
<Input
type="number"
placeholder="Horas del curso"
{...register("horas", { valueAsNumber: true })}
className="w-full px-3 py-2 border border-gray-300 rounded-md mb-3 text-black"
/>
{errors.horas && (
<p className="text-red-500 text-sm">{errors.horas.message}</p>
)}
<Input
type="number"
placeholder="Horas del curso"
{...register("horas", { valueAsNumber: true })}
className="w-full px-3 py-2 border border-gray-300 rounded-md mb-3 text-black"
/>
{errors.horas && (
<p className="text-red-500 text-sm">{errors.horas.message}</p>
)}
<h2 className="text-lg font-semibold mb-3 text-black">Competencias</h2>
<p className="text-xs text-gray-500 mb-2">
Puedes agregar competencias nuevas sin necesidad de crear un nuevo curso. Las competencias se guardarán y podrás asociarlas a otros cursos después.
</p>
<h2 className="text-lg font-semibold mb-3 text-black">
Competencias
</h2>
<p className="text-xs text-gray-500 mb-2">
Puedes agregar competencias nuevas sin necesidad de crear un nuevo
curso. Las competencias se guardarán y podrás asociarlas a otros
cursos después.
</p>
{competenciasGuardadas.length > 0 && (
<div className="mt-5 w-full flex-wrap">
{competenciasGuardadas.map((competencia, index) => (
<div
key={index}
className="w-full flex justify-between items-center px-2 mb-2"
>
<span className="text-black">{competencia.descripcion}</span>
<Button
type="button"
onClick={() => handleDeleteCompetencia(index)}
className="bg-red-400 hover:bg-red-500 text-white font-bold py-1 px-3 rounded-md"
>
X
</Button>
{competencias.length > 0 && (
<div className="mt-5 w-full flex-wrap">
{competencias.map((c, i) => (
<div
key={i}
className="w-full flex justify-between items-center px-2 mb-2"
>
<span className="text-black">{c.descripcion}</span>
<Button
type="button"
onClick={() => handleDeleteCompetencia(i)}
className="bg-red-400 hover:bg-red-500 text-white font-bold py-1 px-3 rounded-md"
>
X
</Button>
</div>
))}
</div>
))}
</div>
)}
)}
{addCompetencia && (
<div className="w-full flex flex-col md:flex-row mt-5">
<div className="flex flex-col">
<Input
type="text"
placeholder="Nueva competencia"
{...register("nuevaCompetencia")}
className="w-80 px-3 py-2 border border-gray-300 rounded-md mb-3 text-black"
/>
{errors.nuevaCompetencia && (
<p className="text-red-500 text-sm">
{errors.nuevaCompetencia.message}
</p>
)}
</div>
<div className="flex flex-row">
{addCompetencia && (
<div className="w-full flex flex-col md:flex-row mt-5">
<div className="flex flex-col">
<Input
type="text"
placeholder="Nueva competencia"
{...register("nuevaCompetencia")}
className="w-80 px-3 py-2 border border-gray-300 rounded-md mb-3 text-black"
/>
{errors.nuevaCompetencia && (
<p className="text-red-500 text-sm">
{errors.nuevaCompetencia.message}
</p>
)}
</div>
<div className="flex flex-row">
<Button
type="button"
onClick={handleSaveCompetencia}
className="bg-green-400 hover:bg-green-500 text-white font-bold py-2 px-4 rounded-md mr-2"
>
Guardar
</Button>
<Button
type="button"
onClick={() => {
setAddCompetencia(false);
setValue("nuevaCompetencia", "");
}}
className="bg-gray-400 hover:bg-gray-500 text-white font-bold py-2 px-4 rounded-md"
>
Cancelar
</Button>
</div>
</div>
)}
<Button
type="button"
onClick={() => 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
</Button>
<div className="flex justify-center w-full mt-5">
<Button
type="button"
onClick={handleSaveCompetencia}
className="bg-green-400 hover:bg-green-500 text-white font-bold py-2 px-4 rounded-md mr-2"
type="submit"
className="bg-green-400 hover:bg-green-500 text-white font-bold py-2 px-4 rounded-md"
disabled={loading}
>
Guardar
</Button>
<Button
type="button"
onClick={() => {
setAddCompetencia(false);
setValue("nuevaCompetencia", "");
}}
className="bg-gray-400 hover:bg-gray-500 text-white font-bold py-2 px-4 rounded-md"
>
Cancelar
{loading ? "Guardando..." : "Guardar curso"}
</Button>
</div>
</div>
)}
<Button
type="button"
onClick={() => 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
</Button>
<div className="flex justify-center w-full mt-5">
<Button
type="submit"
className="bg-green-400 hover:bg-green-500 text-white font-bold py-2 px-4 rounded-md"
disabled={loading}
>
{loading ? "Guardando..." : "Guardar curso"}
</Button>
<Dialog open={showDialog} onOpenChange={setShowDialog}>
<DialogContent>
<DialogHeader>
<DialogTitle className="text-black">Resultado</DialogTitle>
<DialogDescription>{dialogMsg}</DialogDescription>
</DialogHeader>
<DialogFooter>
<Button onClick={() => setShowDialog(false)}>Cerrar</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</form>
</div>
<Dialog open={mostrarDialog} onOpenChange={setMostrarDialog}>
<DialogContent>
<DialogHeader>
<DialogTitle className="text-black">Resultado</DialogTitle>
<DialogDescription>{mensajeDialog}</DialogDescription>
</DialogHeader>
<DialogFooter>
<Button onClick={() => setMostrarDialog(false)}>Cerrar</Button>
</DialogFooter>
</DialogContent>
</Dialog>
<Dialog open={mostrarDialogCompetencia} onOpenChange={setMostrarDialogCompetencia}>
<DialogContent>
<DialogHeader>
<DialogTitle className="text-black">Competencia agregada</DialogTitle>
<DialogDescription>
¡La competencia fue agregada exitosamente!
</DialogDescription>
</DialogHeader>
<DialogFooter>
<Button onClick={() => setMostrarDialogCompetencia(false)}>Cerrar</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</form>
</Layout>
);
}

View File

@ -39,7 +39,8 @@ export default function CursosVista() {
const cargarCursos = async () => {
const { data, error } = await supabaseClient
.from("curso")
.select(`
.select(
`
id,
nombre,
descripcion,
@ -50,7 +51,8 @@ export default function CursosVista() {
descripcion
)
)
`)
`
)
.order("id", { ascending: true });
if (error) {
console.error("Error al cargar cursos:", error.message);
@ -90,7 +92,7 @@ export default function CursosVista() {
// Guardar cambios en curso y competencias
const guardarEdicion = async (id) => {
// Validar que no haya competencias repetidas
const ids = competenciasGuardadas.map(c => c?.id).filter(Boolean);
const ids = competenciasGuardadas.map((c) => c?.id).filter(Boolean);
const setIds = new Set(ids);
if (ids.length !== setIds.size) {
setModalMensaje("No puedes repetir competencias en un curso.");
@ -109,15 +111,12 @@ export default function CursosVista() {
// Actualiza competencias (tabla pivote)
// 1. Elimina todas las competencias actuales del curso
await supabaseClient
.from("curso_competencia")
.delete()
.eq("curso_id", id);
await supabaseClient.from("curso_competencia").delete().eq("curso_id", id);
// 2. Inserta las nuevas competencias seleccionadas
const competenciasAInsertar = competenciasGuardadas
.filter(c => c && c.id)
.map(c => ({
.filter((c) => c && c.id)
.map((c) => ({
curso_id: id,
competencia_id: c.id,
}));
@ -157,7 +156,9 @@ export default function CursosVista() {
}
if (alumnosInscritos && alumnosInscritos.length > 0) {
setModalMensaje("No se puede eliminar el curso porque hay alumnos inscritos a este curso.");
setModalMensaje(
"No se puede eliminar el curso porque hay alumnos inscritos a este curso."
);
setConfirmarEliminar(false);
setMostrarModal(true);
return;
@ -192,150 +193,167 @@ export default function CursosVista() {
};
const quitarCompetencia = () => {
setCompetenciasGuardadas(competenciasGuardadas.filter((_, i) => i !== compAEliminar));
setCompetenciasGuardadas(
competenciasGuardadas.filter((_, i) => i !== compAEliminar)
);
setDialogQuitarComp(false);
setCompAEliminar(null);
};
return (
<Layout>
<div className="w-[80vw] pt-10 flex flex-col items-center text-black">
<div className="w-full pt-10 flex flex-col items-center text-black">
<h1 className="text-2xl font-semibold mb-6">Lista de Cursos</h1>
<table className="min-w-full bg-white border">
<thead>
<tr className="bg-gray-100">
<th className="py-2 border-b">ID</th>
<th className="py-2 border-b">Nombre</th>
<th className="py-2 border-b">Descripción</th>
<th className="py-2 border-b">Horas</th>
<th className="py-2 border-b">Competencias</th>
<th className="py-2 border-b">Acciones</th>
</tr>
</thead>
<tbody>
{cursos.map((curso) =>
cursoEditando === curso.id ? (
<tr key={curso.id}>
<td className="py-2 px-4 border-b text-center">{curso.id}</td>
<td className="py-2 px-4 border-b">
<Input
value={nuevoNombre}
onChange={(e) => setNuevoNombre(e.target.value)}
/>
</td>
<td className="py-2 px-4 border-b">
<Input
value={nuevaDescripcion}
onChange={(e) => setNuevaDescripcion(e.target.value)}
/>
</td>
<td className="py-2 px-4 border-b">
<Input
type="number"
value={nuevaHoras}
onChange={(e) => setNuevaHoras(e.target.value)}
/>
</td>
<td className="py-2 px-4 border-b">
<div className="flex flex-col gap-2">
{competenciasGuardadas.map((comp, idx) => (
<div key={idx} className="flex items-center gap-2">
<select
className="border rounded px-2 py-1"
value={comp?.id || ""}
onChange={e => {
const nuevaLista = [...competenciasGuardadas];
const nuevaComp = todasCompetencias.find(c => c.id === Number(e.target.value));
nuevaLista[idx] = nuevaComp;
setCompetenciasGuardadas(nuevaLista);
}}
>
<option value="">Selecciona competencia</option>
{todasCompetencias.map(tc => (
<option
key={tc.id}
value={tc.id}
disabled={
// Deshabilita si ya está seleccionada en otro select
competenciasGuardadas.some(
(c, i) => c && c.id === tc.id && i !== idx
)
}
>
{tc.descripcion}
</option>
))}
</select>
<Button
type="button"
className="bg-red-500 hover:bg-red-700 text-white px-2 py-1 rounded"
onClick={() => pedirConfirmacionQuitarComp(idx)}
>
Quitar
</Button>
</div>
))}
<div className="overflow-x-auto w-full">
<table className="min-w-full bg-white border">
<thead>
<tr className="bg-gray-100">
<th className="py-2 border-b">ID</th>
<th className="py-2 border-b">Nombre</th>
<th className="py-2 border-b">Descripción</th>
<th className="py-2 border-b">Horas</th>
<th className="py-2 border-b">Competencias</th>
<th className="py-2 border-b">Acciones</th>
</tr>
</thead>
<tbody>
{cursos.map((curso) =>
cursoEditando === curso.id ? (
<tr key={curso.id}>
<td className="py-2 px-4 border-b text-center">
{curso.id}
</td>
<td className="py-2 px-4 border-b">
<Input
value={nuevoNombre}
onChange={(e) => setNuevoNombre(e.target.value)}
/>
</td>
<td className="py-2 px-4 border-b">
<Input
value={nuevaDescripcion}
onChange={(e) => setNuevaDescripcion(e.target.value)}
/>
</td>
<td className="py-2 px-4 border-b">
<Input
type="number"
value={nuevaHoras}
onChange={(e) => setNuevaHoras(e.target.value)}
/>
</td>
<td className="py-2 px-4 border-b">
<div className="flex flex-col gap-2">
{competenciasGuardadas.map((comp, idx) => (
<div key={idx} className="flex items-center gap-2">
<select
className="border rounded px-2 py-1"
value={comp?.id || ""}
onChange={(e) => {
const nuevaLista = [...competenciasGuardadas];
const nuevaComp = todasCompetencias.find(
(c) => c.id === Number(e.target.value)
);
nuevaLista[idx] = nuevaComp;
setCompetenciasGuardadas(nuevaLista);
}}
>
<option value="">Selecciona competencia</option>
{todasCompetencias.map((tc) => (
<option
key={tc.id}
value={tc.id}
disabled={
// Deshabilita si ya está seleccionada en otro select
competenciasGuardadas.some(
(c, i) => c && c.id === tc.id && i !== idx
)
}
>
{tc.descripcion}
</option>
))}
</select>
<Button
type="button"
className="bg-red-500 hover:bg-red-700 text-white px-2 py-1 rounded"
onClick={() => pedirConfirmacionQuitarComp(idx)}
>
Quitar
</Button>
</div>
))}
<Button
type="button"
className="bg-blue-500 hover:bg-blue-700 text-white px-2 py-1 rounded mt-2"
onClick={() =>
setCompetenciasGuardadas([
...competenciasGuardadas,
null,
])
}
disabled={
competenciasGuardadas.length >=
todasCompetencias.length
}
>
Agregar competencia
</Button>
</div>
</td>
<td className="py-2 px-4 border-b flex justify-center">
<Button
type="button"
className="bg-blue-500 hover:bg-blue-700 text-white px-2 py-1 rounded mt-2"
onClick={() => setCompetenciasGuardadas([...competenciasGuardadas, null])}
disabled={competenciasGuardadas.length >= todasCompetencias.length}
className="bg-green-500 hover:bg-green-700 text-white font-bold py-1 px-3 m-2 rounded"
onClick={() => guardarEdicion(curso.id)}
>
Agregar competencia
Guardar
</Button>
</div>
</td>
<td className="py-2 px-4 border-b flex justify-center">
<Button
className="bg-green-500 hover:bg-green-700 text-white font-bold py-1 px-3 m-2 rounded"
onClick={() => guardarEdicion(curso.id)}
>
Guardar
</Button>
<Button
className="bg-gray-400 hover:bg-gray-600 text-white font-bold py-1 px-3 m-2 rounded"
onClick={cancelarEdicion}
>
Cancelar
</Button>
</td>
</tr>
) : (
<tr key={curso.id}>
<td className="py-2 px-4 border-b">{curso.id}</td>
<td className="py-2 px-4 border-b">{curso.nombre}</td>
<td className="py-2 px-4 border-b">{curso.descripcion}</td>
<td className="py-2 px-4 border-b">{curso.horas}</td>
<td className="py-2 px-4 border-b">
{Array.isArray(curso.competencias) && curso.competencias.length > 0
? (
<Button
className="bg-gray-400 hover:bg-gray-600 text-white font-bold py-1 px-3 m-2 rounded"
onClick={cancelarEdicion}
>
Cancelar
</Button>
</td>
</tr>
) : (
<tr key={curso.id}>
<td className="py-2 px-4 border-b">{curso.id}</td>
<td className="py-2 px-4 border-b">{curso.nombre}</td>
<td className="py-2 px-4 border-b">{curso.descripcion}</td>
<td className="py-2 px-4 border-b">{curso.horas}</td>
<td className="py-2 px-4 border-b">
{Array.isArray(curso.competencias) &&
curso.competencias.length > 0 ? (
<ul className="list-disc pl-4">
{curso.competencias.map((comp) => (
<li key={comp.id}>{comp.descripcion}</li>
))}
</ul>
)
: "Sin competencias"}
</td>
<td className="py-2 px-4 border-b space-x-2">
<Button
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-1 px-3 rounded"
onClick={() => iniciarEdicion(curso)}
>
Editar
</Button>
<Button
className="bg-red-500 hover:bg-red-700 text-white font-bold py-1 px-3 rounded"
onClick={() => confirmarEliminacion(curso.id)}
>
Eliminar
</Button>
</td>
</tr>
)
)}
</tbody>
</table>
) : (
"Sin competencias"
)}
</td>
<td className="py-2 px-4 border-b flex justify-center">
<Button
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-1 px-3 m-1 rounded"
onClick={() => iniciarEdicion(curso)}
>
Editar
</Button>
<Button
className="bg-red-500 hover:bg-red-700 text-white font-bold py-1 px-3 m-1 rounded"
onClick={() => confirmarEliminacion(curso.id)}
>
Eliminar
</Button>
</td>
</tr>
)
)}
</tbody>
</table>
</div>
</div>
{/* Dialog para eliminar curso */}
@ -371,9 +389,7 @@ export default function CursosVista() {
<Dialog open={dialogQuitarComp} onOpenChange={setDialogQuitarComp}>
<DialogContent>
<DialogHeader>
<DialogTitle className="text-black">
Quitar competencia
</DialogTitle>
<DialogTitle className="text-black">Quitar competencia</DialogTitle>
<DialogDescription>
¿Estás seguro de que deseas quitar esta competencia del curso?
</DialogDescription>
@ -411,4 +427,4 @@ export default function CursosVista() {
</Dialog>
</Layout>
);
}
}

View File

@ -2,7 +2,6 @@ import React, { useEffect, useState } from "react";
import Layout from "@/components/layout/Layout";
import { supabaseClient } from "@/utils/supabase";
import { Button } from "@/components/ui/button";
import CrearDiplomaDialog from "@/components/dialogs/crearDiplomaDialog";
import VistaPreviaDiplomaDialog from "@/components/dialogs/vistaPreviaDiplomaDialog";
export default function DiplomasVista() {
@ -53,60 +52,51 @@ export default function DiplomasVista() {
return (
<Layout>
<div className="w-[80vw] pt-10 flex flex-col items-center text-black">
<div className="w-full pt-10 flex flex-col items-center text-black">
<h1 className="text-2xl font-semibold mb-6">Vista de Diplomas</h1>
<table className="min-w-full bg-white border">
<thead>
<tr className="bg-gray-100">
<th className="py-2 border-b">ID</th>
<th className="py-2 border-b">Nombre</th>
<th className="py-2 border-b">Correo</th>
<th className="py-2 border-b">Teléfono</th>
<th className="py-2 border-b">Curso</th>
<th className="py-2 border-b">Acciones</th>
</tr>
</thead>
<tbody>
{alumnos.map((alumno) => (
<tr key={alumno.id}>
<td className="py-2 px-4 border-b">{alumno.id}</td>
<td className="py-2 px-4 border-b">{alumno.nombre}</td>
<td className="py-2 px-4 border-b">{alumno.correo}</td>
<td className="py-2 px-4 border-b">{alumno.telefono}</td>
<td className="py-2 px-4 border-b">
{alumno.curso?.nombre || "Sin curso"}
</td>
<td className="py-2 px-4 border-b">
<Button
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-1 px-3 rounded"
onClick={() => {
setAlumnoSeleccionado(alumno);
setMostrarDialog(true);
}}
>
Crear Diploma
</Button>
</td>
<div className="overflow-x-auto w-full">
<table className="w-full bg-white border">
<thead>
<tr className="bg-gray-100">
<th className="py-2 border-b">ID</th>
<th className="py-2 border-b">Nombre</th>
<th className="py-2 border-b">Correo</th>
<th className="py-2 border-b">Teléfono</th>
<th className="py-2 border-b">Curso</th>
<th className="py-2 border-b">Acciones</th>
</tr>
))}
</tbody>
</table>
</thead>
<tbody>
{alumnos.map((alumno) => (
<tr key={alumno.id}>
<td className="py-2 px-4 border-b">{alumno.id}</td>
<td className="py-2 px-4 border-b">{alumno.nombre}</td>
<td className="py-2 px-4 border-b">{alumno.correo}</td>
<td className="py-2 px-4 border-b">{alumno.telefono}</td>
<td className="py-2 px-4 border-b">
{alumno.curso?.nombre || "Sin curso"}
</td>
<td className="py-2 px-4 border-b">
<Button
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-1 px-3 rounded"
onClick={() => {
setAlumnoSeleccionado(alumno);
setMostrarDialog(true);
}}
>
Crear Diploma
</Button>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
{/* Dialog para crear diploma y vista previa juntos */}
{mostrarDialog && (
<div className="fixed inset-0 z-50 flex items-center justify-center">
<div className="flex gap-8 bg-black bg-opacity-30 p-8 rounded">
{/*<CrearDiplomaDialog
open={mostrarDialog}
onOpenChange={handleCloseDialog}
alumno={alumnoSeleccionado}
competencias={competencias}
setCompetencias={setCompetencias}
competenciasAcreditadas={competenciasAcreditadas}
setCompetenciasAcreditadas={setCompetenciasAcreditadas}
fecha={fecha}
setFecha={setFecha}
/>*/}
<VistaPreviaDiplomaDialog
open={mostrarDialog}
onOpenChange={handleCloseDialog}