El sistema de evaluación es editable y se puede ligar con un dato general. En esta caso está ligado a criterios_de_evaluación #157

Merged
Guillermo.Arrieta merged 3 commits from issue/148-homologar-campo-de-criterios-de-evaluacin-y-sistem into main 2026-03-05 17:35:50 +00:00
3 changed files with 720 additions and 186 deletions

View File

@@ -1,11 +1,12 @@
import { createFileRoute, useNavigate, useParams } from '@tanstack/react-router'
import { Pencil, Sparkles } from 'lucide-react'
import { useState, useEffect } from 'react'
import { Minus, Pencil, Plus, Sparkles } from 'lucide-react'
import { useEffect, useMemo, useRef, useState } from 'react'
import type { AsignaturaDetail } from '@/data'
import { Button } from '@/components/ui/button'
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
import { Input } from '@/components/ui/input'
import { Textarea } from '@/components/ui/textarea'
import {
Tooltip,
@@ -37,54 +38,15 @@ export interface AsignaturaResponse {
datos: AsignaturaDatos
}
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === 'object' && value !== null && !Array.isArray(value)
type CriterioEvaluacionRow = {
criterio: string
porcentaje: number
}
function parseContenidoTematicoToPlainText(value: unknown): string {
if (!Array.isArray(value)) return ''
const blocks: Array<string> = []
for (const item of value) {
if (!isRecord(item)) continue
const unidad =
typeof item.unidad === 'number' && Number.isFinite(item.unidad)
? item.unidad
: undefined
const titulo = typeof item.titulo === 'string' ? item.titulo : ''
const header = `${unidad ?? ''}${unidad ? '.' : ''} ${titulo}`.trim()
if (!header) continue
const lines: Array<string> = [header]
const temas = Array.isArray(item.temas) ? item.temas : []
temas.forEach((tema, idx) => {
const temaNombre =
typeof tema === 'string'
? tema
: isRecord(tema) && typeof tema.nombre === 'string'
? tema.nombre
: ''
if (!temaNombre) return
if (unidad != null) {
lines.push(`${unidad}.${idx + 1} ${temaNombre}`.trim())
} else {
lines.push(`${idx + 1}. ${temaNombre}`)
}
})
blocks.push(lines.join('\n'))
}
return blocks.join('\n\n').trimEnd()
}
const columnParsers: Partial<Record<string, (value: unknown) => string>> = {
contenido_tematico: parseContenidoTematicoToPlainText,
type CriterioEvaluacionRowDraft = {
id: string
criterio: string
porcentaje: string // allow empty while editing
}
export const Route = createFileRoute(
@@ -132,11 +94,19 @@ function DatosGenerales({
}: {
onPersistDato: (clave: string, value: string) => void
}) {
const { asignaturaId } = useParams({
const { asignaturaId, planId } = useParams({
from: '/planes/$planId/asignaturas/$asignaturaId',
})
const navigate = useNavigate()
const { data: data, isLoading: isLoading } = useSubject(asignaturaId)
const updateAsignatura = useUpdateAsignatura()
const evaluationCardRef = useRef<HTMLDivElement | null>(null)
const [evaluationForceEditToken, setEvaluationForceEditToken] =
useState<number>(0)
const [evaluationHighlightToken, setEvaluationHighlightToken] =
useState<number>(0)
// 1. Extraemos la definición de la estructura (los metadatos)
const definicionRaw = data?.estructuras_asignatura?.definicion
@@ -154,6 +124,56 @@ function DatosGenerales({
const valoresActuales = isRecord(datosRaw)
? (datosRaw as Record<string, any>)
: {}
const criteriosEvaluacion: Array<CriterioEvaluacionRow> = useMemo(() => {
const raw = (data as any)?.criterios_de_evaluacion
console.log(raw)
if (!Array.isArray(raw)) return []
const rows: Array<CriterioEvaluacionRow> = []
for (const item of raw) {
if (!isRecord(item)) continue
const criterio = typeof item.criterio === 'string' ? item.criterio : ''
const porcentajeNum =
typeof item.porcentaje === 'number'
? item.porcentaje
: typeof item.porcentaje === 'string'
? Number(item.porcentaje)
: NaN
if (!criterio.trim()) continue
if (!Number.isFinite(porcentajeNum)) continue
const porcentaje = Math.trunc(porcentajeNum)
if (porcentaje < 1 || porcentaje > 100) continue
rows.push({ criterio: criterio.trim(), porcentaje: porcentaje })
}
return rows
}, [data])
const openEvaluationEditor = () => {
evaluationCardRef.current?.scrollIntoView({
behavior: 'smooth',
block: 'start',
})
const now = Date.now()
setEvaluationForceEditToken(now)
setEvaluationHighlightToken(now)
}
const persistCriteriosEvaluacion = async (
rows: Array<CriterioEvaluacionRow>,
) => {
await updateAsignatura.mutateAsync({
asignaturaId: asignaturaId as any,
patch: {
criterios_de_evaluacion: rows,
} as any,
})
}
if (isLoading) return <p>Cargando información...</p>
return (
@@ -209,10 +229,29 @@ function DatosGenerales({
clave={key}
title={cardTitle}
initialContent={currentContent}
xColumn={xColumn}
placeholder={placeholder}
description={description}
onPersist={(clave, value) => onPersistDato(clave, value)}
onPersist={({ clave, value }) =>
onPersistDato(String(clave ?? key), String(value ?? ''))
}
onClickEditButton={({ startEditing }) => {
switch (xColumn) {
case 'contenido_tematico': {
navigate({
to: '/planes/$planId/asignaturas/$asignaturaId/contenido',
params: { planId, asignaturaId },
})
return
}
case 'criterios_de_evaluacion': {
openEvaluationEditor()
return
}
default: {
startEditing()
}
}
}}
/>
)
},
@@ -244,12 +283,11 @@ function DatosGenerales({
<InfoCard
title="Sistema de Evaluación"
type="evaluation"
initialContent={[
{ label: 'Exámenes parciales', value: '30%' },
{ label: 'Proyecto integrador', value: '35%' },
{ label: 'Prácticas de laboratorio', value: '20%' },
{ label: 'Participación', value: '15%' },
]}
initialContent={criteriosEvaluacion}
containerRef={evaluationCardRef}
forceEditToken={evaluationForceEditToken}
highlightToken={evaluationHighlightToken}
onPersist={({ value }) => persistCriteriosEvaluacion(value)}
/>
</div>
</div>
@@ -265,11 +303,19 @@ interface InfoCardProps {
initialContent: any
placeholder?: string
description?: string
xColumn?: string
required?: boolean // Nueva prop para el asterisco
type?: 'text' | 'requirements' | 'evaluation'
onEnhanceAI?: (content: any) => void
onPersist?: (clave: string, value: string) => void
onPersist?: (payload: {
type: NonNullable<InfoCardProps['type']>
clave?: string
value: any
}) => void | Promise<void>
onClickEditButton?: (helpers: { startEditing: () => void }) => void
containerRef?: React.RefObject<HTMLDivElement | null>
forceEditToken?: number
highlightToken?: number
}
function InfoCard({
@@ -279,14 +325,22 @@ function InfoCard({
initialContent,
placeholder,
description,
xColumn,
required,
type = 'text',
onPersist,
onClickEditButton,
containerRef,
forceEditToken,
highlightToken,
}: InfoCardProps) {
const [isEditing, setIsEditing] = useState(false)
const [isHighlighted, setIsHighlighted] = useState(false)
const [data, setData] = useState(initialContent)
const [tempText, setTempText] = useState(initialContent)
const [evalRows, setEvalRows] = useState<Array<CriterioEvaluacionRowDraft>>(
[],
)
const navigate = useNavigate()
const { planId } = useParams({
from: '/planes/$planId/asignaturas/$asignaturaId',
@@ -295,16 +349,85 @@ function InfoCard({
useEffect(() => {
setData(initialContent)
setTempText(initialContent)
}, [initialContent])
if (type === 'evaluation') {
const raw = Array.isArray(initialContent) ? initialContent : []
const rows: Array<CriterioEvaluacionRowDraft> = raw
.map((r: any): CriterioEvaluacionRowDraft | null => {
const criterio = typeof r?.criterio === 'string' ? r.criterio : ''
const porcentajeNum =
typeof r?.porcentaje === 'number'
? r.porcentaje
: typeof r?.porcentaje === 'string'
? Number(r.porcentaje)
: NaN
const porcentaje = Number.isFinite(porcentajeNum)
? String(Math.trunc(porcentajeNum))
: ''
return {
id: crypto.randomUUID(),
criterio,
porcentaje,
}
})
.filter(Boolean) as Array<CriterioEvaluacionRowDraft>
setEvalRows(rows)
}
}, [initialContent, type])
useEffect(() => {
if (!forceEditToken) return
setIsEditing(true)
}, [forceEditToken])
useEffect(() => {
if (!highlightToken) return
setIsHighlighted(true)
const t = window.setTimeout(() => setIsHighlighted(false), 900)
return () => window.clearTimeout(t)
}, [highlightToken])
const handleSave = () => {
console.log('clave, valor:', clave, String(tempText ?? ''))
if (type === 'evaluation') {
const cleaned: Array<CriterioEvaluacionRow> = []
for (const r of evalRows) {
const criterio = String(r.criterio).trim()
const porcentajeStr = String(r.porcentaje).trim()
if (!criterio) continue
if (!porcentajeStr) continue
const n = Number(porcentajeStr)
if (!Number.isFinite(n)) continue
const porcentaje = Math.trunc(n)
if (porcentaje < 1 || porcentaje > 100) continue
cleaned.push({ criterio, porcentaje })
}
setData(cleaned)
setEvalRows(
cleaned.map((x) => ({
id: crypto.randomUUID(),
criterio: x.criterio,
porcentaje: String(x.porcentaje),
})),
)
setIsEditing(false)
void onPersist?.({ type, clave, value: cleaned })
return
}
setData(tempText)
setIsEditing(false)
if (type === 'text' && clave && onPersist) {
onPersist(clave, String(tempText ?? ''))
if (type === 'text') {
void onPersist?.({ type, clave, value: String(tempText ?? '') })
}
}
@@ -325,122 +448,300 @@ function InfoCard({
})
}
return (
<Card className="overflow-hidden transition-all hover:border-slate-300">
<TooltipProvider>
<CardHeader className="border-b bg-slate-50/50 px-5 py-3">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<Tooltip>
<TooltipTrigger asChild>
<CardTitle className="cursor-help text-sm font-bold text-slate-700">
{title}
</CardTitle>
</TooltipTrigger>
<TooltipContent side="top" className="max-w-xs text-xs">
{description || 'Información del campo'}
</TooltipContent>
</Tooltip>
const evaluationTotal = useMemo(() => {
if (type !== 'evaluation') return 0
return evalRows.reduce((acc, r) => {
const v = String(r.porcentaje).trim()
if (!v) return acc
const n = Number(v)
if (!Number.isFinite(n)) return acc
const porcentaje = Math.trunc(n)
if (porcentaje < 1 || porcentaje > 100) return acc
return acc + porcentaje
}, 0)
}, [type, evalRows])
{required && (
<span
className="text-sm font-bold text-red-500"
title="Requerido"
>
*
</span>
return (
<div ref={containerRef as any}>
<Card
className={
'overflow-hidden transition-all hover:border-slate-300 ' +
(isHighlighted ? 'ring-primary/40 ring-2' : '')
}
>
<TooltipProvider>
<CardHeader className="border-b bg-slate-50/50 px-5 py-3">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<Tooltip>
<TooltipTrigger asChild>
<CardTitle className="cursor-help text-sm font-bold text-slate-700">
{title}
</CardTitle>
</TooltipTrigger>
<TooltipContent side="top" className="max-w-xs text-xs">
{description || 'Información del campo'}
</TooltipContent>
</Tooltip>
{required && (
<span
className="text-sm font-bold text-red-500"
title="Requerido"
>
*
</span>
)}
</div>
{!isEditing && (
<div className="flex gap-1">
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="ghost"
size="icon"
className="h-8 w-8 text-blue-500 hover:bg-blue-100"
onClick={() => clave && handleIARequest(clave)}
>
<Sparkles className="h-4 w-4" />
</Button>
</TooltipTrigger>
<TooltipContent>Mejorar con IA</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="ghost"
size="icon"
className="h-8 w-8 text-slate-400"
onClick={() => {
const startEditing = () => setIsEditing(true)
if (onClickEditButton) {
onClickEditButton({ startEditing })
return
}
startEditing()
}}
>
<Pencil className="h-3 w-3" />
</Button>
</TooltipTrigger>
<TooltipContent>Editar campo</TooltipContent>
</Tooltip>
</div>
)}
</div>
</CardHeader>
</TooltipProvider>
{!isEditing && (
<div className="flex gap-1">
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="ghost"
size="icon"
className="h-8 w-8 text-blue-500 hover:bg-blue-100"
onClick={() => clave && handleIARequest(clave)}
<CardContent className="pt-4">
{isEditing ? (
<div className="space-y-3">
{type === 'evaluation' ? (
<div className="space-y-3">
<div className="space-y-2">
{evalRows.map((row) => (
<div
key={row.id}
className="grid grid-cols-[2fr_1fr_1ch_32px] items-center gap-2"
>
<Input
value={row.criterio}
placeholder="Criterio"
onChange={(e) => {
const nextCriterio = e.target.value
setEvalRows((prev) =>
prev.map((r) =>
r.id === row.id
? { ...r, criterio: nextCriterio }
: r,
),
)
}}
/>
<Input
value={row.porcentaje}
placeholder="%"
type="number"
min={1}
max={100}
step={1}
inputMode="numeric"
onChange={(e) => {
const raw = e.target.value
// Solo permitir '' o dígitos
if (raw !== '' && !/^\d+$/.test(raw)) return
if (raw === '') {
setEvalRows((prev) =>
prev.map((r) =>
r.id === row.id
? {
id: r.id,
criterio: r.criterio,
porcentaje: '',
}
: r,
),
)
return
}
const n = Number(raw)
if (!Number.isFinite(n)) return
const porcentaje = Math.trunc(n)
if (porcentaje < 1 || porcentaje > 100) return
// No permitir suma > 100
setEvalRows((prev) => {
const next = prev.map((r) =>
r.id === row.id
? {
id: r.id,
criterio: r.criterio,
porcentaje: raw,
}
: r,
)
const total = next.reduce((acc, r) => {
const v = String(r.porcentaje).trim()
if (!v) return acc
const nn = Number(v)
if (!Number.isFinite(nn)) return acc
const vv = Math.trunc(nn)
if (vv < 1 || vv > 100) return acc
return acc + vv
}, 0)
return total > 100 ? prev : next
})
}}
/>
<div
className="flex w-[1ch] items-center justify-center text-sm text-slate-600"
aria-hidden
>
%
</div>
<Button
variant="ghost"
size="icon"
className="h-8 w-8 text-red-600 hover:bg-red-50"
onClick={() => {
setEvalRows((prev) =>
prev.filter((r) => r.id !== row.id),
)
}}
aria-label="Quitar renglón"
title="Quitar"
>
<Minus className="h-4 w-4" />
</Button>
</div>
))}
</div>
<div className="flex items-center justify-between">
<span
className={
'text-sm ' +
(evaluationTotal === 100
? 'text-muted-foreground'
: 'text-destructive font-semibold')
}
>
<Sparkles className="h-4 w-4" />
</Button>
</TooltipTrigger>
<TooltipContent>Mejorar con IA</TooltipContent>
</Tooltip>
Total: {evaluationTotal}/100
</span>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="ghost"
size="icon"
className="h-8 w-8 text-slate-400"
size="sm"
className="text-emerald-700 hover:bg-emerald-50"
onClick={() => {
// Si esta InfoCard proviene de una columna externa (ej: contenido_tematico),
// redirigimos a la pestaña de Contenido en vez de editar inline.
if (xColumn === 'contenido_tematico') {
// Agregamos un timestamp para forzar la actualización
// de la location.state aunque la ruta sea la misma.
navigate({
to: '/planes/$planId/asignaturas/$asignaturaId/contenido',
params: { planId, asignaturaId: asignaturaId! },
})
return
}
setIsEditing(true)
// Agregar una fila vacía (siempre permitido)
setEvalRows((prev) => [
...prev,
{
id: crypto.randomUUID(),
criterio: '',
porcentaje: '',
},
])
}}
>
<Pencil className="h-3 w-3" />
<Plus className="mr-2 h-4 w-4" /> Agregar renglón
</Button>
</TooltipTrigger>
<TooltipContent>Editar campo</TooltipContent>
</Tooltip>
</div>
)}
</div>
</CardHeader>
</TooltipProvider>
<CardContent className="pt-4">
{isEditing ? (
<div className="space-y-3">
<Textarea
value={tempText}
placeholder={placeholder}
onChange={(e) => setTempText(e.target.value)}
className="min-h-30 text-sm leading-relaxed"
/>
<div className="flex justify-end gap-2">
<Button
size="sm"
variant="ghost"
onClick={() => setIsEditing(false)}
>
Cancelar
</Button>
<Button
size="sm"
className="bg-[#00a878] hover:bg-[#008f66]"
onClick={handleSave}
>
Guardar
</Button>
</div>
</div>
) : (
<div className="text-sm leading-relaxed text-slate-600">
{type === 'text' &&
(data ? (
<p className="whitespace-pre-wrap">{data}</p>
</div>
</div>
) : (
<p className="text-slate-400 italic">Sin información.</p>
))}
{type === 'requirements' && <RequirementsView items={data} />}
{type === 'evaluation' && <EvaluationView items={data} />}
</div>
)}
</CardContent>
</Card>
<Textarea
value={tempText}
placeholder={placeholder}
onChange={(e) => setTempText(e.target.value)}
className="min-h-30 text-sm leading-relaxed"
/>
)}
<div className="flex justify-end gap-2">
<Button
size="sm"
variant="ghost"
onClick={() => {
setIsEditing(false)
if (type === 'evaluation') {
const raw = Array.isArray(data) ? data : []
setEvalRows(
raw.map((r: CriterioEvaluacionRow) => ({
id: crypto.randomUUID(),
criterio:
typeof r.criterio === 'string' ? r.criterio : '',
porcentaje:
typeof r.porcentaje === 'number'
? String(Math.trunc(r.porcentaje))
: typeof r.porcentaje === 'string'
? String(Math.trunc(Number(r.porcentaje)))
: '',
})),
)
}
}}
>
Cancelar
</Button>
<Button
size="sm"
className="bg-[#00a878] hover:bg-[#008f66]"
onClick={handleSave}
disabled={type === 'evaluation' && evaluationTotal > 100}
>
Guardar
</Button>
</div>
</div>
) : (
<div className="text-sm leading-relaxed text-slate-600">
{type === 'text' &&
(data ? (
<p className="whitespace-pre-wrap">{data}</p>
) : (
<p className="text-slate-400 italic">Sin información.</p>
))}
{type === 'requirements' && <RequirementsView items={data} />}
{type === 'evaluation' && (
<EvaluationView items={data as Array<CriterioEvaluacionRow>} />
)}
</div>
)}
</CardContent>
</Card>
</div>
)
}
@@ -466,7 +767,11 @@ function RequirementsView({ items }: { items: Array<any> }) {
}
// Vista de Evaluación
function EvaluationView({ items }: { items: Array<any> }) {
function EvaluationView({ items }: { items: Array<CriterioEvaluacionRow> }) {
const porcentajeTotal = items.reduce(
(total, item) => total + Number(item.porcentaje),
0,
)
return (
<div className="space-y-2">
{items.map((item, i) => (
@@ -474,10 +779,92 @@ function EvaluationView({ items }: { items: Array<any> }) {
key={i}
className="flex justify-between border-b border-slate-50 pb-1.5 text-sm italic"
>
<span className="text-slate-500">{item.label}</span>
<span className="font-bold text-blue-600">{item.value}</span>
<span className="text-slate-500">{item.criterio}</span>
<span className="font-bold text-blue-600">{item.porcentaje}%</span>
</div>
))}
{porcentajeTotal < 100 && (
<p className="text-destructive text-sm font-medium">
El porcentaje total es menor a 100%.
</p>
)}
</div>
)
}
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === 'object' && value !== null && !Array.isArray(value)
}
function parseContenidoTematicoToPlainText(value: unknown): string {
if (!Array.isArray(value)) return ''
const blocks: Array<string> = []
for (const item of value) {
if (!isRecord(item)) continue
const unidad =
typeof item.unidad === 'number' && Number.isFinite(item.unidad)
? item.unidad
: undefined
const titulo = typeof item.titulo === 'string' ? item.titulo : ''
const header = `${unidad ?? ''}${unidad ? '.' : ''} ${titulo}`.trim()
if (!header) continue
const lines: Array<string> = [header]
const temas = Array.isArray(item.temas) ? item.temas : []
temas.forEach((tema, idx) => {
const temaNombre =
typeof tema === 'string'
? tema
: isRecord(tema) && typeof tema.nombre === 'string'
? tema.nombre
: ''
if (!temaNombre) return
if (unidad != null) {
lines.push(`${unidad}.${idx + 1} ${temaNombre}`.trim())
} else {
lines.push(`${idx + 1}. ${temaNombre}`)
}
})
blocks.push(lines.join('\n'))
}
return blocks.join('\n\n').trimEnd()
}
function parseCriteriosEvaluacionToPlainText(value: unknown): string {
if (!Array.isArray(value)) return ''
const lines: Array<string> = []
for (const item of value) {
if (!isRecord(item)) continue
const label = typeof item.criterio === 'string' ? item.criterio.trim() : ''
const valueNum =
typeof item.porcentaje === 'number'
? item.porcentaje
: typeof item.porcentaje === 'string'
? Number(item.porcentaje)
: NaN
if (!label) continue
if (!Number.isFinite(valueNum)) continue
const v = Math.trunc(valueNum)
if (v < 1 || v > 100) continue
lines.push(`${label}: ${v}%`)
}
return lines.join('\n')
}
const columnParsers: Partial<Record<string, (value: unknown) => string>> = {
contenido_tematico: parseContenidoTematicoToPlainText,
criterios_de_evaluacion: parseCriteriosEvaluacionToPlainText,
}

View File

@@ -112,7 +112,7 @@ export async function subjects_get(subjectId: UUID): Promise<AsignaturaDetail> {
.from('asignaturas')
.select(
`
id,plan_estudio_id,estructura_id,codigo,nombre,tipo,creditos,numero_ciclo,linea_plan_id,orden_celda,estado,datos,contenido_tematico,horas_academicas,horas_independientes,asignatura_hash,tipo_origen,meta_origen,creado_por,actualizado_por,creado_en,actualizado_en,
id,plan_estudio_id,estructura_id,codigo,nombre,tipo,creditos,numero_ciclo,linea_plan_id,orden_celda,estado,datos,contenido_tematico,horas_academicas,horas_independientes,asignatura_hash,tipo_origen,meta_origen,creado_por,actualizado_por,creado_en,actualizado_en,criterios_de_evaluacion,
planes_estudio(
id,carrera_id,estructura_id,nombre,nivel,tipo_ciclo,numero_ciclos,datos,estado_actual_id,activo,tipo_origen,meta_origen,creado_por,actualizado_por,creado_en,actualizado_en,
carreras(id,facultad_id,nombre,nombre_corto,clave_sep,activa, facultades(id,nombre,nombre_corto,color,icono))

View File

@@ -81,6 +81,56 @@ export type Database = {
},
]
}
asignatura_mensajes_ia: {
Row: {
campos: Array<string>
conversacion_asignatura_id: string
enviado_por: string
estado: Database['public']['Enums']['estado_mensaje_ia']
fecha_actualizacion: string
fecha_creacion: string
id: string
is_refusal: boolean
mensaje: string
propuesta: Json | null
respuesta: string | null
}
Insert: {
campos?: Array<string>
conversacion_asignatura_id: string
enviado_por?: string
estado?: Database['public']['Enums']['estado_mensaje_ia']
fecha_actualizacion?: string
fecha_creacion?: string
id?: string
is_refusal?: boolean
mensaje: string
propuesta?: Json | null
respuesta?: string | null
}
Update: {
campos?: Array<string>
conversacion_asignatura_id?: string
enviado_por?: string
estado?: Database['public']['Enums']['estado_mensaje_ia']
fecha_actualizacion?: string
fecha_creacion?: string
id?: string
is_refusal?: boolean
mensaje?: string
propuesta?: Json | null
respuesta?: string | null
}
Relationships: [
{
foreignKeyName: 'asignatura_mensajes_ia_conversacion_asignatura_id_fkey'
columns: ['conversacion_asignatura_id']
isOneToOne: false
referencedRelation: 'conversaciones_asignatura'
referencedColumns: ['id']
},
]
}
asignaturas: {
Row: {
actualizado_en: string
@@ -91,6 +141,7 @@ export type Database = {
creado_en: string
creado_por: string | null
creditos: number
criterios_de_evaluacion: Json
datos: Json
estado: Database['public']['Enums']['estado_asignatura']
estructura_id: string | null
@@ -115,6 +166,7 @@ export type Database = {
creado_en?: string
creado_por?: string | null
creditos: number
criterios_de_evaluacion?: Json
datos?: Json
estado?: Database['public']['Enums']['estado_asignatura']
estructura_id?: string | null
@@ -139,6 +191,7 @@ export type Database = {
creado_en?: string
creado_por?: string | null
creditos?: number
criterios_de_evaluacion?: Json
datos?: Json
estado?: Database['public']['Enums']['estado_asignatura']
estructura_id?: string | null
@@ -176,6 +229,13 @@ export type Database = {
referencedRelation: 'estructuras_asignatura'
referencedColumns: ['id']
},
{
foreignKeyName: 'asignaturas_estructura_id_fkey'
columns: ['estructura_id']
isOneToOne: false
referencedRelation: 'plantilla_asignatura'
referencedColumns: ['estructura_id']
},
{
foreignKeyName: 'asignaturas_linea_plan_fk_compuesta'
columns: ['linea_plan_id', 'plan_estudio_id']
@@ -241,6 +301,13 @@ export type Database = {
referencedRelation: 'asignaturas'
referencedColumns: ['id']
},
{
foreignKeyName: 'bibliografia_asignatura_asignatura_id_fkey'
columns: ['asignatura_id']
isOneToOne: false
referencedRelation: 'plantilla_asignatura'
referencedColumns: ['asignatura_id']
},
{
foreignKeyName: 'bibliografia_asignatura_creado_por_fkey'
columns: ['creado_por']
@@ -295,6 +362,13 @@ export type Database = {
referencedRelation: 'asignaturas'
referencedColumns: ['id']
},
{
foreignKeyName: 'cambios_asignatura_asignatura_id_fkey'
columns: ['asignatura_id']
isOneToOne: false
referencedRelation: 'plantilla_asignatura'
referencedColumns: ['asignatura_id']
},
{
foreignKeyName: 'cambios_asignatura_cambiado_por_fkey'
columns: ['cambiado_por']
@@ -441,6 +515,13 @@ export type Database = {
referencedRelation: 'asignaturas'
referencedColumns: ['id']
},
{
foreignKeyName: 'conversaciones_asignatura_asignatura_id_fkey'
columns: ['asignatura_id']
isOneToOne: false
referencedRelation: 'plantilla_asignatura'
referencedColumns: ['asignatura_id']
},
{
foreignKeyName: 'conversaciones_asignatura_creado_por_fkey'
columns: ['creado_por']
@@ -552,7 +633,8 @@ export type Database = {
definicion: Json
id: string
nombre: string
version: string | null
template_id: string | null
tipo: Database['public']['Enums']['tipo_estructura_plan'] | null
}
Insert: {
actualizado_en?: string
@@ -560,7 +642,8 @@ export type Database = {
definicion?: Json
id?: string
nombre: string
version?: string | null
template_id?: string | null
tipo?: Database['public']['Enums']['tipo_estructura_plan'] | null
}
Update: {
actualizado_en?: string
@@ -568,7 +651,8 @@ export type Database = {
definicion?: Json
id?: string
nombre?: string
version?: string | null
template_id?: string | null
tipo?: Database['public']['Enums']['tipo_estructura_plan'] | null
}
Relationships: []
}
@@ -692,6 +776,13 @@ export type Database = {
referencedRelation: 'asignaturas'
referencedColumns: ['id']
},
{
foreignKeyName: 'interacciones_ia_asignatura_id_fkey'
columns: ['asignatura_id']
isOneToOne: false
referencedRelation: 'plantilla_asignatura'
referencedColumns: ['asignatura_id']
},
{
foreignKeyName: 'interacciones_ia_plan_estudio_id_fkey'
columns: ['plan_estudio_id']
@@ -798,6 +889,56 @@ export type Database = {
},
]
}
plan_mensajes_ia: {
Row: {
campos: Array<string>
conversacion_plan_id: string
enviado_por: string
estado: Database['public']['Enums']['estado_mensaje_ia']
fecha_actualizacion: string
fecha_creacion: string
id: string
is_refusal: boolean
mensaje: string
propuesta: Json | null
respuesta: string | null
}
Insert: {
campos?: Array<string>
conversacion_plan_id: string
enviado_por?: string
estado?: Database['public']['Enums']['estado_mensaje_ia']
fecha_actualizacion?: string
fecha_creacion?: string
id?: string
is_refusal?: boolean
mensaje: string
propuesta?: Json | null
respuesta?: string | null
}
Update: {
campos?: Array<string>
conversacion_plan_id?: string
enviado_por?: string
estado?: Database['public']['Enums']['estado_mensaje_ia']
fecha_actualizacion?: string
fecha_creacion?: string
id?: string
is_refusal?: boolean
mensaje?: string
propuesta?: Json | null
respuesta?: string | null
}
Relationships: [
{
foreignKeyName: 'plan_mensajes_ia_conversacion_plan_id_fkey'
columns: ['conversacion_plan_id']
isOneToOne: false
referencedRelation: 'conversaciones_plan'
referencedColumns: ['id']
},
]
}
planes_estudio: {
Row: {
activo: boolean
@@ -934,6 +1075,13 @@ export type Database = {
referencedRelation: 'asignaturas'
referencedColumns: ['id']
},
{
foreignKeyName: 'responsables_asignatura_asignatura_id_fkey'
columns: ['asignatura_id']
isOneToOne: false
referencedRelation: 'plantilla_asignatura'
referencedColumns: ['asignatura_id']
},
{
foreignKeyName: 'responsables_asignatura_usuario_id_fkey'
columns: ['usuario_id']
@@ -1199,6 +1347,14 @@ export type Database = {
}
}
Views: {
plantilla_asignatura: {
Row: {
asignatura_id: string | null
estructura_id: string | null
template_id: string | null
}
Relationships: []
}
plantilla_plan: {
Row: {
estructura_id: string | null
@@ -1221,13 +1377,9 @@ export type Database = {
unaccent_immutable: { Args: { '': string }; Returns: string }
}
Enums: {
estado_asignatura:
| 'borrador'
| 'revisada'
| 'aprobada'
| 'generando'
| 'fallida'
estado_asignatura: 'borrador' | 'revisada' | 'aprobada' | 'generando'
estado_conversacion: 'ACTIVA' | 'ARCHIVANDO' | 'ARCHIVADA' | 'ERROR'
estado_mensaje_ia: 'PROCESANDO' | 'COMPLETADO' | 'ERROR'
estado_tarea_revision: 'PENDIENTE' | 'COMPLETADA' | 'OMITIDA'
fuente_cambio: 'HUMANO' | 'IA'
nivel_plan_estudio:
@@ -1400,14 +1552,9 @@ export const Constants = {
},
public: {
Enums: {
estado_asignatura: [
'borrador',
'revisada',
'aprobada',
'generando',
'fallida',
],
estado_asignatura: ['borrador', 'revisada', 'aprobada', 'generando'],
estado_conversacion: ['ACTIVA', 'ARCHIVANDO', 'ARCHIVADA', 'ERROR'],
estado_mensaje_ia: ['PROCESANDO', 'COMPLETADO', 'ERROR'],
estado_tarea_revision: ['PENDIENTE', 'COMPLETADA', 'OMITIDA'],
fuente_cambio: ['HUMANO', 'IA'],
nivel_plan_estudio: [