Estas acciones gestionan los dos tipos de entidades principales del sistema: los estudiantes que completan el cuestionario SENA, y los usuarios del staff (psicólogos, orientadores, directores y administradores) que operan la plataforma.
Acciones de estudiantes
registrarEstudiante
export async function registrarEstudiante(
_prev: RegistrarEstudianteResult,
formData: FormData
): Promise<RegistrarEstudianteResult>
Registra un nuevo estudiante en la base de datos. Diseñada para usarse con useActionState de React en el formulario de registro. Tras un registro exitoso, redirige a /estudiantes/{id}?nuevo=1 e invalida la caché de /estudiantes.
| Campo | Tipo | Validación |
|---|
nombre | string | Mínimo 3 caracteres |
curp | string | Formato CURP mexicano (18 caracteres, regex estricto) |
fechaNac | string | Fecha ISO — usada para calcular la edad automáticamente |
sexo | string | MASCULINO, FEMENINO o OTRO |
grado | string | Grado escolar |
grupo | string | Grupo (se convierte a mayúsculas, ej. A, B) |
escuela | string | Nombre del plantel |
La edad se calcula automáticamente a partir de fechaNac. No se recibe como campo independiente.
Retorno
export type RegistrarEstudianteResult = {
error?: string
fieldErrors?: Record<string, string>
} | undefined
undefined — éxito (la función llama a redirect() antes de retornar)
{ fieldErrors } — errores de validación por campo
{ error } — error genérico de base de datos
Errores comunes
| Campo | Mensaje |
|---|
nombre | "Ingresa el nombre completo" |
curp | "CURP inválida — verifica el formato (18 caracteres)" |
curp | "Ya existe un estudiante con esa CURP" (código Prisma P2002) |
fechaNac | "Selecciona la fecha de nacimiento" |
sexo | "Selecciona el sexo" |
grado | "Selecciona el grado" |
grupo | "Ingresa el grupo (ej. A, B, C)" |
escuela | "Ingresa el nombre del plantel" |
Ejemplo
import { registrarEstudiante } from "@/lib/actions/estudiante"
import { useActionState } from "react"
function FormularioEstudiante() {
const [state, formAction] = useActionState(registrarEstudiante, undefined)
return (
<form action={formAction}>
<input name="nombre" />
<input name="curp" />
<input name="fechaNac" type="date" />
<select name="sexo">
<option value="MASCULINO">Masculino</option>
<option value="FEMENINO">Femenino</option>
<option value="OTRO">Otro</option>
</select>
<input name="grado" />
<input name="grupo" />
<input name="escuela" />
{state?.fieldErrors?.nombre && <p>{state.fieldErrors.nombre}</p>}
<button type="submit">Registrar</button>
</form>
)
}
Modelo de datos: Estudiante
| Campo | Tipo | Descripción |
|---|
id | String | CUID autogenerado, clave primaria |
curp | String | CURP mexicana — única por estudiante |
nombre | String | Nombre completo |
edad | Int | Edad calculada desde fecha de nacimiento |
sexo | Sexo | Enum: MASCULINO, FEMENINO, OTRO |
grado | String | Grado escolar |
grupo | String | Grupo (letra, ej. A) |
escuela | String | Nombre del plantel |
tokenEncuesta | String | Token único para acceso al cuestionario (CUID autogenerado) |
Acciones de usuarios
Todas las acciones de usuarios requieren que la sesión activa tenga el rol ADMIN. Cualquier intento de un rol diferente lanza Error("No autorizado").
crearUsuario
export async function crearUsuario(data: {
nombre: string
email: string
password: string
rol: string
}): Promise<void>
Crea un nuevo usuario del staff. La contraseña se hashea con bcrypt (12 rondas) antes de persistir. Invalida la caché de /usuarios.
Parámetros
| Campo | Tipo | Descripción |
|---|
nombre | string | Nombre completo (se recorta con .trim()) |
email | string | Correo electrónico — único en la tabla Usuario |
password | string | Mínimo 6 caracteres |
rol | string | Debe ser uno de: ADMIN, PSICOLOGO, DIRECTOR, ORIENTADOR |
Errores lanzados (throw)
| Mensaje | Causa |
|---|
"No autorizado" | Sesión sin rol ADMIN |
"Rol no válido" | Rol fuera de los valores permitidos del staff |
"La contraseña debe tener al menos 6 caracteres" | Contraseña muy corta |
"El usuario no puede estar vacío" | Email vacío tras trim |
"Ya existe un usuario con ese nombre" | Email duplicado |
Ejemplo
import { crearUsuario } from "@/lib/actions/usuario"
await crearUsuario({
nombre: "Dr. Carlos Pérez",
email: "cperez@cecyten.edu.mx",
password: "segura123",
rol: "PSICOLOGO",
})
actualizarUsuario
export async function actualizarUsuario(
id: string,
data: {
nombre: string
email: string
rol: string
password?: string
}
): Promise<void>
Actualiza los datos de un usuario existente. Si password se proporciona y no está vacío, se rehashea. El administrador no puede cambiar su propio rol.
Parámetros
| Parámetro | Tipo | Descripción |
|---|
id | string | ID del usuario a actualizar |
data.nombre | string | Nuevo nombre |
data.email | string | Nuevo email |
data.rol | string | Nuevo rol (ADMIN, PSICOLOGO, DIRECTOR, ORIENTADOR) |
data.password | string? | Nueva contraseña — opcional. Si se omite o es vacío, no se modifica |
Ejemplo
import { actualizarUsuario } from "@/lib/actions/usuario"
await actualizarUsuario("cm1xyz789", {
nombre: "Dra. Ana Martínez",
email: "amartinez@cecyten.edu.mx",
rol: "ORIENTADOR",
// sin password → no se modifica
})
eliminarUsuario
export async function eliminarUsuario(id: string): Promise<void>
Elimina un usuario del sistema. El administrador no puede eliminarse a sí mismo.
Parámetros
| Parámetro | Tipo | Descripción |
|---|
id | string | ID del usuario a eliminar |
Errores lanzados (throw)
| Mensaje | Causa |
|---|
"No autorizado" | Sesión sin rol ADMIN |
"Usuario no encontrado" | El ID no existe |
"No puedes eliminar tu propio usuario" | El ID corresponde al admin en sesión |
Ejemplo
import { eliminarUsuario } from "@/lib/actions/usuario"
await eliminarUsuario("cm1xyz789")
Modelo de datos: Usuario
| Campo | Tipo | Descripción |
|---|
id | String | CUID autogenerado |
email | String | Correo electrónico — único |
password | String | Hash bcrypt (no se expone en consultas) |
nombre | String | Nombre completo |
rol | Rol | Enum de rol |
creadoEn | DateTime | Fecha de creación (automática) |
estudianteId | String? | FK a Estudiante si el usuario es un estudiante |
Enums
Rol
import type { Rol } from "@/lib/enums"
| Valor | Descripción |
|---|
ESTUDIANTE | Solo puede contestar su propio cuestionario |
PSICOLOGO | Gestiona citas, sesiones, canalizaciones y expedientes |
ORIENTADOR | Gestiona citas, sesiones, canalizaciones y expedientes |
DIRECTOR | Acceso de lectura al dashboard |
ADMIN | Acceso total — crea y administra usuarios |
El rol ESTUDIANTE es para cuentas vinculadas a un registro de estudiante. Las acciones de usuario (crearUsuario, actualizarUsuario) solo aceptan los roles de staff: ADMIN, PSICOLOGO, DIRECTOR y ORIENTADOR.
Sexo
import type { Sexo } from "@/lib/enums"
| Valor | Descripción |
|---|
MASCULINO | Sexo masculino |
FEMENINO | Sexo femenino |
OTRO | Otra opción |