Estas acciones gestionan el flujo de atención psicológica después de que un tamizaje identifica riesgo: desde agendar una cita inicial, registrar notas de sesión, canalizar al estudiante a servicios externos, y registrar intentos de contacto con los padres.
Todas las acciones en este módulo requieren que la sesión activa tenga uno de estos roles: PSICOLOGO, ORIENTADOR o ADMIN. Solicitudes de roles DIRECTOR o ESTUDIANTE son rechazadas con "Acceso denegado".
Acciones de citas
agendarCita
export async function agendarCita(
_prev: CitaResult,
formData: FormData
): Promise<CitaResult>
Agenda una nueva cita para un estudiante. Si el estudiante no tiene un expediente clínico, crea uno automáticamente. Tras agendar con éxito, redirige a /citas.
| Campo | Tipo | Descripción |
|---|
estudianteId | string | ID del estudiante |
fecha | string | Fecha y hora en formato ISO 8601 |
notas | string? | Notas previas (opcional) |
Validaciones
estudianteId y fecha son obligatorios.
- La fecha no puede ser un sábado (día 6) ni un domingo (día 0).
Retorno
export type CitaResult = { error: string } | undefined
Ejemplo
import { agendarCita } from "@/lib/actions/cita"
import { useActionState } from "react"
function FormularioCita() {
const [state, formAction] = useActionState(agendarCita, undefined)
return (
<form action={formAction}>
<input type="hidden" name="estudianteId" value="cm1abc123" />
<input type="datetime-local" name="fecha" />
<textarea name="notas" />
{state?.error && <p>{state.error}</p>}
<button type="submit">Agendar cita</button>
</form>
)
}
actualizarEstadoCita
export async function actualizarEstadoCita(
citaId: string,
estado: "PENDIENTE" | "CONFIRMADA" | "COMPLETADA" | "CANCELADA"
): Promise<void>
Cambia el estado de una cita existente e invalida la caché de /citas. No retorna error — si la sesión no tiene rol permitido, la función retorna silenciosamente sin hacer cambios.
Ejemplo
import { actualizarEstadoCita } from "@/lib/actions/cita"
await actualizarEstadoCita("cm1cita456", "CONFIRMADA")
completarCitaConSesion
export async function completarCitaConSesion(
_prev: CompletarCitaResult,
formData: FormData
): Promise<CompletarCitaResult>
Marca la cita como COMPLETADA y crea simultáneamente un registro de sesión clínica. Ambas operaciones ocurren en la misma transacción. Tras completar, redirige a /citas.
| Campo | Tipo | Descripción |
|---|
citaId | string | ID de la cita |
estudianteId | string | ID del estudiante |
tipo | string | Tipo de sesión — ver enum TipoSesion. Por defecto: SEGUIMIENTO |
motivo | string? | Motivo de la sesión (opcional) |
notas | string | Notas clínicas — obligatorio |
acuerdos | string? | Acuerdos alcanzados (opcional) |
planActualizado | string? | Actualizaciones al plan de intervención (opcional) |
Retorno
export type CompletarCitaResult = { error: string } | undefined
Ejemplo
import { completarCitaConSesion } from "@/lib/actions/cita"
import { useActionState } from "react"
function FormularioCompletarCita({ citaId, estudianteId }: Props) {
const [state, formAction] = useActionState(completarCitaConSesion, undefined)
return (
<form action={formAction}>
<input type="hidden" name="citaId" value={citaId} />
<input type="hidden" name="estudianteId" value={estudianteId} />
<select name="tipo">
<option value="EVALUACION_INICIAL">Evaluación inicial</option>
<option value="SEGUIMIENTO">Seguimiento</option>
<option value="INTERVENCION">Intervención</option>
<option value="CRISIS">Crisis</option>
<option value="CIERRE">Cierre</option>
<option value="DEVOLUCION">Devolución</option>
</select>
<textarea name="notas" required />
<textarea name="acuerdos" />
{state?.error && <p>{state.error}</p>}
<button type="submit">Completar cita</button>
</form>
)
}
Modelo de datos: Cita
| Campo | Tipo | Descripción |
|---|
id | String | CUID autogenerado |
fecha | DateTime | Fecha y hora de la cita |
estado | EstadoCita | Estado actual de la cita |
notas | String? | Notas previas |
estudianteId | String | FK al estudiante |
EstadoCita
| Valor | Descripción |
|---|
PENDIENTE | Cita agendada, sin confirmar (estado inicial) |
CONFIRMADA | Confirmada por el psicólogo u orientador |
COMPLETADA | Se realizó — siempre tiene un registro de sesión asociado |
CANCELADA | Cancelada antes de realizarse |
Acciones de sesiones
crearSesion
export async function crearSesion(
_prev: SesionResult,
formData: FormData
): Promise<SesionResult>
Crea una sesión clínica independiente (sin cita previa). También hace upsert del ExpedienteClinico del estudiante, actualizando actualizadaEn. Invalida la caché de /expediente/{estudianteId}.
| Campo | Tipo | Descripción |
|---|
estudianteId | string | ID del estudiante — obligatorio |
tipo | string | Tipo de sesión (TipoSesion). Por defecto: SEGUIMIENTO |
motivo | string? | Motivo de la sesión |
notas | string | Notas clínicas — obligatorio |
acuerdos | string? | Acuerdos alcanzados |
planActualizado | string? | Actualizaciones al plan |
Retorno
export type SesionResult = { error?: string; ok?: boolean }
Ejemplo
import { crearSesion } from "@/lib/actions/sesion"
import { useActionState } from "react"
function FormularioSesion({ estudianteId }: { estudianteId: string }) {
const [state, formAction] = useActionState(crearSesion, {})
return (
<form action={formAction}>
<input type="hidden" name="estudianteId" value={estudianteId} />
<select name="tipo">
<option value="SEGUIMIENTO">Seguimiento</option>
<option value="CRISIS">Crisis</option>
</select>
<textarea name="notas" required />
{state.error && <p>{state.error}</p>}
{state.ok && <p>Sesión guardada.</p>}
<button type="submit">Guardar sesión</button>
</form>
)
}
actualizarExpediente
export async function actualizarExpediente(
_prev: SesionResult,
formData: FormData
): Promise<SesionResult>
Actualiza (o crea) el expediente clínico de un estudiante con información narrativa. Usa upsert sobre estudianteId.
| Campo | Tipo | Descripción |
|---|
estudianteId | string | ID del estudiante |
motivoConsulta | string? | Motivo de consulta |
antecedentes | string? | Antecedentes relevantes |
diagnosticoPreliminar | string? | Diagnóstico preliminar |
planIntervencion | string? | Plan de intervención |
estado | string | ACTIVO, CERRADO, DERIVADO o EN_ESPERA. Por defecto: ACTIVO |
Modelo de datos: Sesion
| Campo | Tipo | Descripción |
|---|
id | String | CUID autogenerado |
fecha | DateTime | Fecha de la sesión (automática) |
tipo | TipoSesion | Tipo de sesión |
motivo | String? | Motivo de la sesión |
notas | String | Notas clínicas — obligatorio |
acuerdos | String? | Acuerdos alcanzados |
planActualizado | String? | Actualizaciones al plan |
citaId | String? | FK a Cita si la sesión surge de una cita |
estudianteId | String | FK al estudiante |
TipoSesion
import type { TipoSesion } from "@/lib/enums"
| Valor | Descripción |
|---|
EVALUACION_INICIAL | Primera sesión de evaluación |
SEGUIMIENTO | Seguimiento regular |
INTERVENCION | Sesión de intervención terapéutica |
CRISIS | Atención en situación de crisis |
CIERRE | Cierre del proceso |
DEVOLUCION | Devolución de resultados al estudiante o familia |
Acciones de canalizaciones
crearCanalizacion
export async function crearCanalizacion(
_prev: CanalizacionResult,
formData: FormData
): Promise<CanalizacionResult>
Registra la canalización de un estudiante a una institución externa. Si urgente es true, actualiza automáticamente el estado del expediente clínico a DERIVADO.
| Campo | Tipo | Descripción |
|---|
estudianteId | string | ID del estudiante |
institucion | string | Nombre de la institución — obligatorio |
tipoInstitucion | string | PUBLICA o PRIVADA. Por defecto: PUBLICA |
tipoAtencion | string? | Tipo de atención requerida |
motivo | string | Motivo de la canalización — obligatorio |
nivelRiesgo | string? | Número de 1 a 10 (valoración clínica) |
urgente | "on" | Presente si la canalización es urgente (checkbox HTML) |
notas | string? | Notas adicionales |
Retorno
export type CanalizacionResult = { error?: string; ok?: boolean }
Ejemplo
import { crearCanalizacion } from "@/lib/actions/canalizacion"
import { useActionState } from "react"
function FormularioCanalizacion({ estudianteId }: { estudianteId: string }) {
const [state, formAction] = useActionState(crearCanalizacion, {})
return (
<form action={formAction}>
<input type="hidden" name="estudianteId" value={estudianteId} />
<input name="institucion" placeholder="IMSS - UMF 5" />
<select name="tipoInstitucion">
<option value="PUBLICA">Pública</option>
<option value="PRIVADA">Privada</option>
</select>
<textarea name="motivo" required />
<input name="nivelRiesgo" type="number" min="1" max="10" />
<input type="checkbox" name="urgente" /> Urgente
{state.error && <p>{state.error}</p>}
<button type="submit">Canalizar</button>
</form>
)
}
actualizarSeguimientoCanalizacion
export async function actualizarSeguimientoCanalizacion(
_prev: CanalizacionResult,
formData: FormData
): Promise<CanalizacionResult>
Actualiza el estado y documentación de seguimiento de una canalización existente. Si documentoRecibido está marcado, registra fechaDocumento con la fecha actual.
| Campo | Tipo | Descripción |
|---|
id | string | ID de la canalización |
estudianteId | string | ID del estudiante (para invalidar caché) |
estado | string | Estado actual de la canalización. Por defecto: PENDIENTE |
firmaPadres | "on"? | Checkbox — indica si los padres firmaron |
documentoRecibido | "on"? | Checkbox — indica si se recibió documentación de la institución |
tipoDocumento | string? | Tipo de documento recibido |
notas | string? | Notas de seguimiento |
actualizarNivelRiesgo
export async function actualizarNivelRiesgo(
estudianteId: string,
nivelRiesgo: number | null
): Promise<void>
Actualiza el nivel de riesgo clínico manual (escala 1–10) en el expediente del estudiante. Usa upsert — crea el expediente si no existe.
Ejemplo
import { actualizarNivelRiesgo } from "@/lib/actions/canalizacion"
await actualizarNivelRiesgo("cm1abc123", 8)
// Para limpiar el nivel de riesgo:
await actualizarNivelRiesgo("cm1abc123", null)
Modelo de datos: Canalizacion
| Campo | Tipo | Descripción |
|---|
id | String | CUID autogenerado |
estudianteId | String | FK al estudiante |
institucion | String | Nombre de la institución |
tipoInstitucion | TipoInstitucion | PUBLICA o PRIVADA |
tipoAtencion | String? | Tipo de atención requerida |
motivo | String | Motivo de la canalización |
nivelRiesgo | Int? | Nivel de riesgo 1–10 |
urgente | Boolean | Si es urgente (por defecto false) |
fecha | DateTime | Fecha de registro (automática) |
estado | EstadoCanalizacion | Estado del proceso |
notas | String? | Notas adicionales |
firmaPadres | Boolean | Si los padres firmaron el consentimiento |
documentoRecibido | Boolean | Si se recibió documentación |
tipoDocumento | String? | Tipo de documento recibido |
fechaDocumento | DateTime? | Fecha en que se recibió el documento |
EstadoCanalizacion
| Valor | Descripción |
|---|
PENDIENTE | Canalización registrada, sin iniciar proceso |
EN_PROCESO | En proceso de atención externa |
COMPLETADA | Atención externa completada |
SIN_SEGUIMIENTO | Sin respuesta o seguimiento posible |
export async function registrarContacto(
_prev: ContactoResult,
formData: FormData
): Promise<ContactoResult>
Registra un intento de contacto con los padres o tutores del estudiante.
| Campo | Tipo | Descripción |
|---|
estudianteId | string | ID del estudiante |
tipo | string | Tipo de contacto — ver TipoContacto |
resultado | string | Resultado del contacto — ver ResultadoContacto |
notas | string? | Notas adicionales |
Retorno
export type ContactoResult = { error?: string; ok?: boolean }
Ejemplo
import { registrarContacto } from "@/lib/actions/contacto"
import { useActionState } from "react"
function FormularioContacto({ estudianteId }: { estudianteId: string }) {
const [state, formAction] = useActionState(registrarContacto, {})
return (
<form action={formAction}>
<input type="hidden" name="estudianteId" value={estudianteId} />
<select name="tipo">
<option value="LLAMADA">Llamada</option>
<option value="MENSAJE_TEXTO">Mensaje de texto</option>
<option value="CONTACTO_POR_ALUMNO">Contacto por el alumno</option>
<option value="CITA_PADRES">Cita de padres</option>
</select>
<select name="resultado">
<option value="CONTESTO">Contestó</option>
<option value="NO_CONTESTO">No contestó</option>
<option value="MENSAJE_ENVIADO">Mensaje enviado</option>
<option value="SIN_RESPUESTA">Sin respuesta</option>
<option value="ACUDIO">Acudió</option>
<option value="NO_ACUDIO">No acudió</option>
</select>
<textarea name="notas" />
{state.error && <p>{state.error}</p>}
<button type="submit">Registrar contacto</button>
</form>
)
}
export async function eliminarContacto(
id: string,
estudianteId: string
): Promise<void>
Elimina un registro de contacto. Si el ID no existe, la operación falla silenciosamente (usa .catch(() => {})). Invalida la caché del expediente.
Ejemplo
import { eliminarContacto } from "@/lib/actions/contacto"
await eliminarContacto("cm1contacto999", "cm1abc123")
| Campo | Tipo | Descripción |
|---|
id | String | CUID autogenerado |
estudianteId | String | FK al estudiante |
fecha | DateTime | Fecha del contacto (automática) |
tipo | TipoContacto | Medio de contacto utilizado |
resultado | ResultadoContacto | Resultado del intento de contacto |
notas | String? | Notas adicionales |
| Valor | Descripción |
|---|
LLAMADA | Llamada telefónica |
MENSAJE_TEXTO | Mensaje de texto (SMS o WhatsApp) |
CONTACTO_POR_ALUMNO | Mensaje entregado a través del alumno |
CITA_PADRES | Cita presencial de padres |
| Valor | Descripción |
|---|
CONTESTO | El padre o tutor contestó |
NO_CONTESTO | No contestó la llamada |
MENSAJE_ENVIADO | Mensaje enviado sin confirmación de lectura |
SIN_RESPUESTA | Sin respuesta al mensaje |
ACUDIO | Acudió a la cita de padres |
NO_ACUDIO | No acudió a la cita de padres |