diff --git a/src/components/asignaturas/wizard/PasoBasicosForm/PasoSugerenciasForm.tsx b/src/components/asignaturas/wizard/PasoBasicosForm/PasoSugerenciasForm.tsx index e512e4a..e8f8de2 100644 --- a/src/components/asignaturas/wizard/PasoBasicosForm/PasoSugerenciasForm.tsx +++ b/src/components/asignaturas/wizard/PasoBasicosForm/PasoSugerenciasForm.tsx @@ -26,7 +26,7 @@ export default function PasoSugerenciasForm({ onChange: Dispatch> }) { const enfoque = wizard.iaMultiple?.enfoque ?? '' - const cantidadDeSugerencias = wizard.iaMultiple?.cantidadDeSugerencias ?? 10 + const cantidadDeSugerencias = wizard.iaMultiple?.cantidadDeSugerencias ?? 5 const isLoading = wizard.iaMultiple?.isLoading ?? false const [showConservacionTooltip, setShowConservacionTooltip] = useState(false) @@ -163,7 +163,7 @@ export default function PasoSugerenciasForm({ Cantidad de sugerencias (null) + const watchSubjectIdRef = useRef(null) + const watchTimeoutRef = useRef(null) + + useEffect(() => { + cancelledRef.current = false + return () => { + cancelledRef.current = true + } + }, []) + + const stopSubjectWatch = useCallback(() => { + if (watchTimeoutRef.current) { + window.clearTimeout(watchTimeoutRef.current) + watchTimeoutRef.current = null + } + + watchSubjectIdRef.current = null + + const ch = realtimeChannelRef.current + if (ch) { + realtimeChannelRef.current = null + try { + supabaseBrowser().removeChannel(ch) + } catch { + // noop + } + } + }, []) + + useEffect(() => { + return () => { + stopSubjectWatch() + } + }, [stopSubjectWatch]) + + const handleSubjectReady = (args: { + id: string + plan_estudio_id: string + estado?: unknown + }) => { + if (cancelledRef.current) return + + const estado = String(args.estado ?? '').toLowerCase() + if (estado === 'generando') return + + stopSubjectWatch() + setIsSpinningIA(false) + setWizard((w) => ({ ...w, isLoading: false })) + + navigate({ + to: `/planes/${args.plan_estudio_id}/asignaturas/${args.id}`, + state: { showConfetti: true }, + }) + } + + const beginSubjectWatch = (args: { subjectId: string; planId: string }) => { + stopSubjectWatch() + + watchSubjectIdRef.current = args.subjectId + + // Timeout de seguridad (mismo límite que teníamos con polling) + watchTimeoutRef.current = window.setTimeout( + () => { + if (cancelledRef.current) return + if (watchSubjectIdRef.current !== args.subjectId) return + + stopSubjectWatch() + setIsSpinningIA(false) + setWizard((w) => ({ + ...w, + isLoading: false, + errorMessage: + 'La generación está tardando demasiado. Intenta de nuevo en unos minutos.', + })) + }, + 6 * 60 * 1000, + ) + + const supabase = supabaseBrowser() + const channel = supabase.channel(`asignaturas-status-${args.subjectId}`) + realtimeChannelRef.current = channel + + channel.on( + 'postgres_changes', + { + event: 'UPDATE', + schema: 'public', + table: 'asignaturas', + filter: `id=eq.${args.subjectId}`, + }, + (payload) => { + if (cancelledRef.current) return + + const next: any = (payload as any)?.new + if (!next?.id || !next?.plan_estudio_id) return + handleSubjectReady({ + id: String(next.id), + plan_estudio_id: String(next.plan_estudio_id), + estado: next.estado, + }) + }, + ) + + channel.subscribe((status) => { + if (cancelledRef.current) return + if (status === 'CHANNEL_ERROR' || status === 'TIMED_OUT') { + stopSubjectWatch() + setIsSpinningIA(false) + setWizard((w) => ({ + ...w, + isLoading: false, + errorMessage: + 'No se pudo suscribir al estado de la asignatura. Intenta de nuevo.', + })) + } + }) + } + + const uploadAiAttachments = async (args: { + planId: string + files: Array<{ file: File }> + }): Promise> => { + const supabase = supabaseBrowser() + if (!args.files.length) return [] + + const runId = crypto.randomUUID() + const basePath = `planes/${args.planId}/asignaturas/ai/${runId}` + + const keys: Array = [] + for (const f of args.files) { + const safeName = (f.file.name || 'archivo').replace(/[\\/]+/g, '_') + const key = `${basePath}/${crypto.randomUUID()}-${safeName}` + + const { error } = await supabase.storage + .from('ai-storage') + .upload(key, f.file, { + contentType: f.file.type || undefined, + }) + + if (error) throw new Error(error.message) + keys.push(key) + } + + return keys + } + const handleCreate = async () => { setWizard((w) => ({ ...w, @@ -48,48 +198,99 @@ export function WizardControls({ errorMessage: null, })) + let startedWaiting = false + try { if (wizard.tipoOrigen === 'IA_SIMPLE') { - const aiInput: AIGenerateSubjectInput = { + if (!wizard.plan_estudio_id) { + throw new Error('Plan de estudio inválido.') + } + if (!wizard.datosBasicos.estructuraId) { + throw new Error('Estructura inválida.') + } + if (!wizard.datosBasicos.nombre.trim()) { + throw new Error('Nombre inválido.') + } + if (wizard.datosBasicos.creditos == null) { + throw new Error('Créditos inválidos.') + } + + console.log(`${new Date().toISOString()} - Insertando asignatura IA`) + + const supabase = supabaseBrowser() + const placeholder: TablesInsert<'asignaturas'> = { plan_estudio_id: wizard.plan_estudio_id, - datosBasicos: { + estructura_id: wizard.datosBasicos.estructuraId, + nombre: wizard.datosBasicos.nombre, + codigo: wizard.datosBasicos.codigo ?? null, + tipo: wizard.datosBasicos.tipo ?? undefined, + creditos: wizard.datosBasicos.creditos, + horas_academicas: wizard.datosBasicos.horasAcademicas ?? null, + horas_independientes: wizard.datosBasicos.horasIndependientes ?? null, + estado: 'generando', + tipo_origen: 'IA', + } + + const { data: inserted, error: insertError } = await supabase + .from('asignaturas') + .insert(placeholder) + .select('id,plan_estudio_id') + .single() + + if (insertError) throw new Error(insertError.message) + const subjectId = inserted.id + + setIsSpinningIA(true) + + // Inicia watch realtime antes de disparar la Edge para no perder updates. + startedWaiting = true + beginSubjectWatch({ subjectId, planId: wizard.plan_estudio_id }) + + const archivosAdjuntos = await uploadAiAttachments({ + planId: wizard.plan_estudio_id, + files: (wizard.iaConfig?.archivosAdjuntos ?? []).map((x) => ({ + file: x.file, + })), + }) + + const payload: AISubjectUnifiedInput = { + datosUpdate: { + id: subjectId, + plan_estudio_id: wizard.plan_estudio_id, + estructura_id: wizard.datosBasicos.estructuraId, nombre: wizard.datosBasicos.nombre, - codigo: wizard.datosBasicos.codigo, - tipo: wizard.datosBasicos.tipo!, - creditos: wizard.datosBasicos.creditos!, - horasIndependientes: wizard.datosBasicos.horasIndependientes, - horasAcademicas: wizard.datosBasicos.horasAcademicas, - estructuraId: wizard.datosBasicos.estructuraId!, + codigo: wizard.datosBasicos.codigo ?? null, + tipo: wizard.datosBasicos.tipo ?? null, + creditos: wizard.datosBasicos.creditos, + horas_academicas: wizard.datosBasicos.horasAcademicas ?? null, + horas_independientes: + wizard.datosBasicos.horasIndependientes ?? null, }, iaConfig: { descripcionEnfoqueAcademico: - wizard.iaConfig!.descripcionEnfoqueAcademico, + wizard.iaConfig?.descripcionEnfoqueAcademico ?? undefined, instruccionesAdicionalesIA: - wizard.iaConfig!.instruccionesAdicionalesIA, - archivosReferencia: wizard.iaConfig!.archivosReferencia, - repositoriosReferencia: - wizard.iaConfig!.repositoriosReferencia || [], - archivosAdjuntos: wizard.iaConfig!.archivosAdjuntos || [], + wizard.iaConfig?.instruccionesAdicionalesIA ?? undefined, + archivosAdjuntos, }, } console.log( - `${new Date().toISOString()} - Enviando a generar asignatura con IA`, + `${new Date().toISOString()} - Disparando Edge IA asignatura (unified)`, ) - setIsSpinningIA(true) - const asignatura = await generateSubjectAI.mutateAsync(aiInput) - // await new Promise((resolve) => setTimeout(resolve, 20000)) // debug - setIsSpinningIA(false) - // console.log( - // `${new Date().toISOString()} - Asignatura IA generada`, - // asignatura, - // ) + await generateSubjectAI.mutateAsync(payload as any) + + // Fallback: una lectura puntual por si el UPDATE llegó antes de suscribir. + const latest = await subjects_get_maybe(subjectId) + if (latest) { + handleSubjectReady({ + id: latest.id as any, + plan_estudio_id: latest.plan_estudio_id as any, + estado: (latest as any).estado, + }) + } - navigate({ - to: `/planes/${wizard.plan_estudio_id}/asignaturas/${asignatura.id}`, - state: { showConfetti: true }, - }) return } @@ -108,6 +309,15 @@ export function WizardControls({ const supabase = supabaseBrowser() + setIsSpinningIA(true) + + const archivosAdjuntos = await uploadAiAttachments({ + planId: wizard.plan_estudio_id, + files: (wizard.iaConfig?.archivosAdjuntos ?? []).map((x) => ({ + file: x.file, + })), + }) + const placeholders: Array> = selected.map( (s): TablesInsert<'asignaturas'> => ({ plan_estudio_id: wizard.plan_estudio_id, @@ -141,16 +351,33 @@ export function WizardControls({ // Disparar generación en paralelo (no bloquear navegación) insertedIds.forEach((id, idx) => { const s = selected[idx] - const payload: AIGenerateSubjectJsonInput = { - id, - descripcionEnfoqueAcademico: s.descripcion, - // (opcionales) parches directos si el edge los usa - estructura_id: wizard.estructuraId, - linea_plan_id: s.linea_plan_id, - numero_ciclo: s.numero_ciclo, + const creditosForEdge = + typeof s.creditos === 'number' && s.creditos > 0 + ? s.creditos + : undefined + const payload: AISubjectUnifiedInput = { + datosUpdate: { + id, + plan_estudio_id: wizard.plan_estudio_id, + estructura_id: wizard.estructuraId ?? undefined, + nombre: s.nombre, + codigo: s.codigo ?? null, + tipo: s.tipo ?? null, + creditos: creditosForEdge, + horas_academicas: s.horasAcademicas ?? null, + horas_independientes: s.horasIndependientes ?? null, + numero_ciclo: s.numero_ciclo ?? null, + linea_plan_id: s.linea_plan_id ?? null, + }, + iaConfig: { + descripcionEnfoqueAcademico: s.descripcion, + instruccionesAdicionalesIA: + wizard.iaConfig?.instruccionesAdicionalesIA ?? undefined, + archivosAdjuntos, + }, } - void generateSubjectAI.mutateAsync(payload).catch((e) => { + void generateSubjectAI.mutateAsync(payload as any).catch((e) => { console.error('Error generando asignatura IA (multiple):', e) }) }) @@ -166,6 +393,8 @@ export function WizardControls({ resetScroll: false, }) + setIsSpinningIA(false) + return } @@ -195,14 +424,17 @@ export function WizardControls({ } } catch (err: any) { setIsSpinningIA(false) + stopSubjectWatch() setWizard((w) => ({ ...w, isLoading: false, errorMessage: err?.message ?? 'Error creando la asignatura', })) } finally { - setIsSpinningIA(false) - setWizard((w) => ({ ...w, isLoading: false })) + if (!startedWaiting) { + setIsSpinningIA(false) + setWizard((w) => ({ ...w, isLoading: false })) + } } } diff --git a/src/components/planes/wizard/WizardControls.tsx b/src/components/planes/wizard/WizardControls.tsx index 29e74bc..77bae39 100644 --- a/src/components/planes/wizard/WizardControls.tsx +++ b/src/components/planes/wizard/WizardControls.tsx @@ -1,15 +1,21 @@ import { useNavigate } from '@tanstack/react-router' import { Loader2 } from 'lucide-react' -import { useState } from 'react' +import { useCallback, useEffect, useRef, useState } from 'react' import type { AIGeneratePlanInput } from '@/data' import type { NivelPlanEstudio, TipoCiclo } from '@/data/types/domain' import type { NewPlanWizardState } from '@/features/planes/nuevo/types' // import type { Database } from '@/types/supabase' +import type { RealtimeChannel } from '@supabase/supabase-js' import { Button } from '@/components/ui/button' -// import { supabaseBrowser } from '@/data' -import { useCreatePlanManual, useGeneratePlanAI } from '@/data/hooks/usePlans' +import { plans_get_maybe } from '@/data/api/plans.api' +import { + useCreatePlanManual, + useDeletePlanEstudio, + useGeneratePlanAI, +} from '@/data/hooks/usePlans' +import { supabaseBrowser } from '@/data/supabase/client' export function WizardControls({ errorMessage, @@ -35,9 +41,152 @@ export function WizardControls({ const navigate = useNavigate() const generatePlanAI = useGeneratePlanAI() const createPlanManual = useCreatePlanManual() + const deletePlan = useDeletePlanEstudio() const [isSpinningIA, setIsSpinningIA] = useState(false) - // const supabaseClient = supabaseBrowser() - // const persistPlanFromAI = usePersistPlanFromAI() + const cancelledRef = useRef(false) + const realtimeChannelRef = useRef(null) + const watchPlanIdRef = useRef(null) + const watchTimeoutRef = useRef(null) + + useEffect(() => { + cancelledRef.current = false + return () => { + cancelledRef.current = true + } + }, []) + + const stopPlanWatch = useCallback(() => { + if (watchTimeoutRef.current) { + window.clearTimeout(watchTimeoutRef.current) + watchTimeoutRef.current = null + } + + watchPlanIdRef.current = null + + const ch = realtimeChannelRef.current + if (ch) { + realtimeChannelRef.current = null + try { + supabaseBrowser().removeChannel(ch) + } catch { + // noop + } + } + }, []) + + useEffect(() => { + return () => { + stopPlanWatch() + } + }, [stopPlanWatch]) + + const checkPlanStateAndAct = useCallback( + async (planId: string) => { + if (cancelledRef.current) return + if (watchPlanIdRef.current !== planId) return + + const plan = await plans_get_maybe(planId as any) + if (!plan) return + + const clave = String(plan.estados_plan?.clave ?? '').toUpperCase() + + if (clave.startsWith('GENERANDO')) return + + if (clave.startsWith('BORRADOR')) { + stopPlanWatch() + setIsSpinningIA(false) + setWizard((w) => ({ ...w, isLoading: false })) + navigate({ + to: `/planes/${plan.id}`, + state: { showConfetti: true }, + }) + return + } + + if (clave.startsWith('FALLID')) { + stopPlanWatch() + setIsSpinningIA(false) + + deletePlan + .mutateAsync(plan.id) + .catch(() => { + // Si falla el borrado, igual mostramos el error. + }) + .finally(() => { + setWizard((w) => ({ + ...w, + isLoading: false, + errorMessage: 'La generación del plan falló', + })) + }) + } + }, + [deletePlan, navigate, setWizard, stopPlanWatch], + ) + + const beginPlanWatch = useCallback( + (planId: string) => { + stopPlanWatch() + watchPlanIdRef.current = planId + + watchTimeoutRef.current = window.setTimeout( + () => { + if (cancelledRef.current) return + if (watchPlanIdRef.current !== planId) return + + stopPlanWatch() + setIsSpinningIA(false) + setWizard((w) => ({ + ...w, + isLoading: false, + errorMessage: + 'La generación está tardando demasiado. Intenta de nuevo en unos minutos.', + })) + }, + 6 * 60 * 1000, + ) + + const supabase = supabaseBrowser() + const channel = supabase.channel(`planes-status-${planId}`) + realtimeChannelRef.current = channel + + channel.on( + 'postgres_changes', + { + event: '*', + schema: 'public', + table: 'planes_estudio', + filter: `id=eq.${planId}`, + }, + () => { + void checkPlanStateAndAct(planId) + }, + ) + + channel.subscribe((status) => { + const st = status as + | 'SUBSCRIBED' + | 'TIMED_OUT' + | 'CLOSED' + | 'CHANNEL_ERROR' + if (cancelledRef.current) return + if (st === 'CHANNEL_ERROR' || st === 'TIMED_OUT') { + stopPlanWatch() + setIsSpinningIA(false) + setWizard((w) => ({ + ...w, + isLoading: false, + errorMessage: + 'No se pudo suscribir al estado del plan. Intenta de nuevo.', + })) + } + }) + + // Fallback inmediato por si el plan ya cambió antes de suscribir. + void checkPlanStateAndAct(planId) + }, + [checkPlanStateAndAct, setWizard, stopPlanWatch], + ) const handleCreate = async () => { // Start loading @@ -82,14 +231,16 @@ export function WizardControls({ console.log(`${new Date().toISOString()} - Enviando a generar plan IA`) setIsSpinningIA(true) - const plan = await generatePlanAI.mutateAsync(aiInput as any) - setIsSpinningIA(false) - console.log(`${new Date().toISOString()} - Plan IA generado`, plan) + const resp: any = await generatePlanAI.mutateAsync(aiInput as any) + const planId = resp?.plan?.id ?? resp?.id + console.log(`${new Date().toISOString()} - Plan IA generado`, resp) - navigate({ - to: `/planes/${plan.id}`, - state: { showConfetti: true }, - }) + if (!planId) { + throw new Error('No se pudo obtener el id del plan generado por IA') + } + + // Inicia realtime; los efectos navegan o marcan error. + beginPlanWatch(String(planId)) return } @@ -114,14 +265,14 @@ export function WizardControls({ } } catch (err: any) { setIsSpinningIA(false) + stopPlanWatch() setWizard((w) => ({ ...w, isLoading: false, errorMessage: err?.message ?? 'Error generando el plan', })) } finally { - setIsSpinningIA(false) - setWizard((w) => ({ ...w, isLoading: false })) + // Si entramos en watch realtime, el loading se corta desde checkPlanStateAndAct. } } diff --git a/src/data/api/plans.api.ts b/src/data/api/plans.api.ts index a6e4ba3..ee59d2b 100644 --- a/src/data/api/plans.api.ts +++ b/src/data/api/plans.api.ts @@ -144,6 +144,48 @@ export async function plans_get(planId: UUID): Promise { return requireData(data, 'Plan no encontrado.') } +/** + * Variante de `plans_get` que NO lanza si no existe (devuelve null). + * Útil para flujos de polling donde el plan puede tardar en aparecer. + */ +export async function plans_get_maybe( + planId: UUID, +): Promise { + const supabase = supabaseBrowser() + + const { data, error } = await supabase + .from('planes_estudio') + .select( + ` + *, + carreras (*, facultades(*)), + estructuras_plan (*), + estados_plan (*) + `, + ) + .eq('id', planId) + .maybeSingle() + + throwIfError(error) + return (data ?? null) as unknown as PlanEstudio | null +} + +export async function plans_delete(planId: UUID): Promise<{ id: UUID }> { + const supabase = supabaseBrowser() + + const { data, error } = await supabase + .from('planes_estudio') + .delete() + .eq('id', planId) + .select('id') + .maybeSingle() + + throwIfError(error) + + // Si por alguna razón no retorna fila (RLS / triggers), devolvemos el id solicitado. + return { id: ((data as any)?.id ?? planId) as UUID } +} + export async function plan_lineas_list( planId: UUID, ): Promise> { diff --git a/src/data/api/subjects.api.ts b/src/data/api/subjects.api.ts index dd6bcb0..9cbc0cc 100644 --- a/src/data/api/subjects.api.ts +++ b/src/data/api/subjects.api.ts @@ -15,7 +15,6 @@ import type { TipoAsignatura, UUID, } from '../types/domain' -import type { UploadedFile } from '@/components/planes/wizard/PasoDetallesPanel/FileDropZone' import type { AsignaturaSugerida, DataAsignaturaSugerida, @@ -178,54 +177,49 @@ export async function subjects_create_manual( return requireData(data, 'No se pudo crear la asignatura.') } -export type AIGenerateSubjectInput = { - plan_estudio_id: Asignatura['plan_estudio_id'] - datosBasicos: { - nombre: Asignatura['nombre'] - codigo?: Asignatura['codigo'] - tipo: Asignatura['tipo'] | null - creditos: Asignatura['creditos'] | null - horasAcademicas?: Asignatura['horas_academicas'] | null - horasIndependientes?: Asignatura['horas_independientes'] | null - estructuraId: Asignatura['estructura_id'] | null +/** + * Nuevo payload unificado (JSON) para la Edge `ai_generate_subject`. + * - Siempre incluye `datosUpdate.plan_estudio_id`. + * - `datosUpdate.id` es opcional (si no existe, la Edge puede crear). + * En el frontend, insertamos primero y usamos `id` para actualizar. + */ +export type AISubjectUnifiedInput = { + datosUpdate: Partial<{ + id: string + plan_estudio_id: string + estructura_id: string + nombre: string + codigo: string | null + tipo: string | null + creditos: number + horas_academicas: number | null + horas_independientes: number | null + numero_ciclo: number | null + linea_plan_id: string | null + orden_celda: number | null + }> & { + plan_estudio_id: string } - // clonInterno?: { - // facultadId?: string - // carreraId?: string - // planOrigenId?: string - // asignaturaOrigenId?: string | null - // } - // clonTradicional?: { - // archivoWordAsignaturaId: string | null - // archivosAdicionalesIds: Array - // } iaConfig?: { - descripcionEnfoqueAcademico: string - instruccionesAdicionalesIA: string - archivosReferencia: Array - repositoriosReferencia?: Array - archivosAdjuntos?: Array + descripcionEnfoqueAcademico?: string + instruccionesAdicionalesIA?: string + archivosAdjuntos?: Array } } -/** - * Edge (JSON): actualizar/llenar una asignatura existente por id. - * Nota: este flujo NO acepta `instruccionesAdicionalesIA` (solo FormData lo usa). - */ -export type AIGenerateSubjectJsonInput = Partial<{ - plan_estudio_id: Asignatura['plan_estudio_id'] - nombre: Asignatura['nombre'] - codigo: Asignatura['codigo'] - tipo: Asignatura['tipo'] | null - creditos: Asignatura['creditos'] - horas_academicas: Asignatura['horas_academicas'] | null - horas_independientes: Asignatura['horas_independientes'] | null - estructura_id: Asignatura['estructura_id'] | null - linea_plan_id: Asignatura['linea_plan_id'] | null - numero_ciclo: Asignatura['numero_ciclo'] | null - descripcionEnfoqueAcademico: string -}> & { - id: Asignatura['id'] +export async function subjects_get_maybe( + subjectId: UUID, +): Promise { + const supabase = supabaseBrowser() + + const { data, error } = await supabase + .from('asignaturas') + .select('id,plan_estudio_id,estado') + .eq('id', subjectId) + .maybeSingle() + + throwIfError(error) + return (data ?? null) as unknown as Asignatura | null } export type GenerateSubjectSuggestionsInput = { @@ -263,30 +257,8 @@ export async function generate_subject_suggestions( } export async function ai_generate_subject( - input: AIGenerateSubjectInput | AIGenerateSubjectJsonInput, + input: AISubjectUnifiedInput, ): Promise { - if ('datosBasicos' in input) { - const edgeFunctionBody = new FormData() - edgeFunctionBody.append('plan_estudio_id', input.plan_estudio_id) - edgeFunctionBody.append('datosBasicos', JSON.stringify(input.datosBasicos)) - edgeFunctionBody.append( - 'iaConfig', - JSON.stringify({ - ...input.iaConfig, - archivosAdjuntos: undefined, // los manejamos aparte - }), - ) - input.iaConfig?.archivosAdjuntos?.forEach((file) => { - edgeFunctionBody.append(`archivosAdjuntos`, file.file) - }) - return invokeEdge( - EDGE.ai_generate_subject, - edgeFunctionBody, - undefined, - supabaseBrowser(), - ) - } - return invokeEdge(EDGE.ai_generate_subject, input, { headers: { 'Content-Type': 'application/json' }, }) diff --git a/src/data/hooks/usePlans.ts b/src/data/hooks/usePlans.ts index 42f221a..d5341ee 100644 --- a/src/data/hooks/usePlans.ts +++ b/src/data/hooks/usePlans.ts @@ -4,6 +4,7 @@ import { useQuery, useQueryClient, } from '@tanstack/react-query' +import { useEffect } from 'react' import { ai_generate_plan, @@ -12,6 +13,7 @@ import { plan_lineas_list, plans_clone_from_existing, plans_create_manual, + plans_delete, plans_generate_document, plans_get, plans_get_document, @@ -25,6 +27,7 @@ import { } from '../api/plans.api' import { lineas_delete } from '../api/subjects.api' import { qk } from '../query/keys' +import { supabaseBrowser } from '../supabase/client' import type { PlanListFilters, @@ -71,23 +74,79 @@ export function usePlanLineas(planId: UUID | null | undefined) { } export function usePlanAsignaturas(planId: UUID | null | undefined) { - return useQuery({ + const qc = useQueryClient() + + const query = useQuery({ queryKey: planId ? qk.planAsignaturas(planId) : ['planes', 'asignaturas', null], queryFn: () => plan_asignaturas_list(planId as UUID), enabled: Boolean(planId), - - refetchInterval: (query) => { - const data = query.state.data - if (!Array.isArray(data)) return false - const hayGenerando = data.some( - (a: any) => (a as { estado?: unknown }).estado === 'generando', - ) - return hayGenerando ? 500 : false - }, - refetchIntervalInBackground: true, }) + + useEffect(() => { + if (!planId) return + + const supabase = supabaseBrowser() + const channel = supabase.channel(`plan-asignaturas-${planId}`) + + channel.on( + 'postgres_changes', + { + event: '*', + schema: 'public', + table: 'asignaturas', + filter: `plan_estudio_id=eq.${planId}`, + }, + (payload: { + eventType?: 'INSERT' | 'UPDATE' | 'DELETE' + new?: any + old?: any + }) => { + const eventType = payload.eventType + + if (eventType === 'DELETE') { + const oldRow: any = payload.old + const deletedId = oldRow?.id + if (!deletedId) return + + qc.setQueryData(qk.planAsignaturas(planId), (prev) => { + if (!Array.isArray(prev)) return prev + return prev.filter((a: any) => String(a?.id) !== String(deletedId)) + }) + return + } + + const newRow: any = payload.new + if (!newRow?.id) return + + qc.setQueryData(qk.planAsignaturas(planId), (prev) => { + if (!Array.isArray(prev)) return prev + + const idx = prev.findIndex( + (a: any) => String(a?.id) === String(newRow.id), + ) + if (idx === -1) return [...prev, newRow] + + const next = [...prev] + next[idx] = { ...prev[idx], ...newRow } + return next + }) + }, + ) + + channel.subscribe() + + return () => { + try { + supabase.removeChannel(channel) + } catch { + // noop + } + } + }, [planId, qc]) + + return query } export function usePlanHistorial( @@ -263,6 +322,23 @@ export function useTransitionPlanEstado() { }) } +export function useDeletePlanEstudio() { + const qc = useQueryClient() + + return useMutation({ + mutationFn: (planId: UUID) => plans_delete(planId), + onSuccess: (_ok, planId) => { + qc.invalidateQueries({ queryKey: ['planes', 'list'] }) + qc.removeQueries({ queryKey: qk.plan(planId) }) + qc.removeQueries({ queryKey: qk.planMaybe(planId) }) + qc.removeQueries({ queryKey: qk.planAsignaturas(planId) }) + qc.removeQueries({ queryKey: qk.planLineas(planId) }) + qc.removeQueries({ queryKey: qk.planHistorial(planId) }) + qc.removeQueries({ queryKey: qk.planDocumento(planId) }) + }, + }) +} + export function useGeneratePlanDocumento() { const qc = useQueryClient() diff --git a/src/data/query/keys.ts b/src/data/query/keys.ts index 1ac5890..9f50063 100644 --- a/src/data/query/keys.ts +++ b/src/data/query/keys.ts @@ -13,6 +13,7 @@ export const qk = { planesList: (filters: unknown) => ['planes', 'list', filters] as const, plan: (planId: string) => ['planes', 'detail', planId] as const, + planMaybe: (planId: string) => ['planes', 'detail-maybe', planId] as const, planLineas: (planId: string) => ['planes', planId, 'lineas'] as const, planAsignaturas: (planId: string) => ['planes', planId, 'asignaturas'] as const, @@ -22,6 +23,8 @@ export const qk = { sugerenciasAsignaturas: () => ['asignaturas', 'sugerencias'] as const, asignatura: (asignaturaId: string) => ['asignaturas', 'detail', asignaturaId] as const, + asignaturaMaybe: (asignaturaId: string) => + ['asignaturas', 'detail-maybe', asignaturaId] as const, asignaturaBibliografia: (asignaturaId: string) => ['asignaturas', asignaturaId, 'bibliografia'] as const, asignaturaHistorial: (asignaturaId: string) => diff --git a/src/routes/planes/$planId/asignaturas/$asignaturaId/route.tsx b/src/routes/planes/$planId/asignaturas/$asignaturaId/route.tsx index 1088a3b..9f0fd10 100644 --- a/src/routes/planes/$planId/asignaturas/$asignaturaId/route.tsx +++ b/src/routes/planes/$planId/asignaturas/$asignaturaId/route.tsx @@ -2,6 +2,7 @@ import { createFileRoute, Outlet, Link, + useLocation, useParams, useRouterState, } from '@tanstack/react-router' @@ -9,6 +10,7 @@ import { ArrowLeft, GraduationCap } from 'lucide-react' import { useEffect, useState } from 'react' import { Badge } from '@/components/ui/badge' +import { lateralConfetti } from '@/components/ui/lateral-confetti' import { useSubject, useUpdateAsignatura } from '@/data' export const Route = createFileRoute( @@ -62,8 +64,7 @@ interface DatosPlan { } function AsignaturaLayout() { - const routerState = useRouterState() - const state = routerState.location.state as any + const location = useLocation() const { asignaturaId } = useParams({ from: '/planes/$planId/asignaturas/$asignaturaId', }) @@ -117,6 +118,14 @@ function AsignaturaLayout() { select: (state) => state.location.pathname, }) + // Confetti al llegar desde creación IA + useEffect(() => { + if ((location.state as any)?.showConfetti) { + lateralConfetti() + window.history.replaceState({}, document.title) + } + }, [location.state]) + if (loadingAsig) { return (
@@ -130,7 +139,7 @@ function AsignaturaLayout() { return (
-
+
export type Database = { graphql_public: { @@ -73,11 +73,11 @@ export type Database = { } Relationships: [ { - foreignKeyName: "archivos_subido_por_fkey" - columns: ["subido_por"] + foreignKeyName: 'archivos_subido_por_fkey' + columns: ['subido_por'] isOneToOne: false - referencedRelation: "usuarios_app" - referencedColumns: ["id"] + referencedRelation: 'usuarios_app' + referencedColumns: ['id'] }, ] } @@ -92,7 +92,7 @@ export type Database = { creado_por: string | null creditos: number datos: Json - estado: Database["public"]["Enums"]["estado_asignatura"] + estado: Database['public']['Enums']['estado_asignatura'] estructura_id: string | null horas_academicas: number | null horas_independientes: number | null @@ -103,8 +103,8 @@ export type Database = { numero_ciclo: number | null orden_celda: number | null plan_estudio_id: string - tipo: Database["public"]["Enums"]["tipo_asignatura"] - tipo_origen: Database["public"]["Enums"]["tipo_origen"] | null + tipo: Database['public']['Enums']['tipo_asignatura'] + tipo_origen: Database['public']['Enums']['tipo_origen'] | null } Insert: { actualizado_en?: string @@ -116,7 +116,7 @@ export type Database = { creado_por?: string | null creditos: number datos?: Json - estado?: Database["public"]["Enums"]["estado_asignatura"] + estado?: Database['public']['Enums']['estado_asignatura'] estructura_id?: string | null horas_academicas?: number | null horas_independientes?: number | null @@ -127,8 +127,8 @@ export type Database = { numero_ciclo?: number | null orden_celda?: number | null plan_estudio_id: string - tipo?: Database["public"]["Enums"]["tipo_asignatura"] - tipo_origen?: Database["public"]["Enums"]["tipo_origen"] | null + tipo?: Database['public']['Enums']['tipo_asignatura'] + tipo_origen?: Database['public']['Enums']['tipo_origen'] | null } Update: { actualizado_en?: string @@ -140,7 +140,7 @@ export type Database = { creado_por?: string | null creditos?: number datos?: Json - estado?: Database["public"]["Enums"]["estado_asignatura"] + estado?: Database['public']['Enums']['estado_asignatura'] estructura_id?: string | null horas_academicas?: number | null horas_independientes?: number | null @@ -151,51 +151,51 @@ export type Database = { numero_ciclo?: number | null orden_celda?: number | null plan_estudio_id?: string - tipo?: Database["public"]["Enums"]["tipo_asignatura"] - tipo_origen?: Database["public"]["Enums"]["tipo_origen"] | null + tipo?: Database['public']['Enums']['tipo_asignatura'] + tipo_origen?: Database['public']['Enums']['tipo_origen'] | null } Relationships: [ { - foreignKeyName: "asignaturas_actualizado_por_fkey" - columns: ["actualizado_por"] + foreignKeyName: 'asignaturas_actualizado_por_fkey' + columns: ['actualizado_por'] isOneToOne: false - referencedRelation: "usuarios_app" - referencedColumns: ["id"] + referencedRelation: 'usuarios_app' + referencedColumns: ['id'] }, { - foreignKeyName: "asignaturas_creado_por_fkey" - columns: ["creado_por"] + foreignKeyName: 'asignaturas_creado_por_fkey' + columns: ['creado_por'] isOneToOne: false - referencedRelation: "usuarios_app" - referencedColumns: ["id"] + referencedRelation: 'usuarios_app' + referencedColumns: ['id'] }, { - foreignKeyName: "asignaturas_estructura_id_fkey" - columns: ["estructura_id"] + foreignKeyName: 'asignaturas_estructura_id_fkey' + columns: ['estructura_id'] isOneToOne: false - referencedRelation: "estructuras_asignatura" - referencedColumns: ["id"] + referencedRelation: 'estructuras_asignatura' + referencedColumns: ['id'] }, { - foreignKeyName: "asignaturas_linea_plan_fk_compuesta" - columns: ["linea_plan_id", "plan_estudio_id"] + foreignKeyName: 'asignaturas_linea_plan_fk_compuesta' + columns: ['linea_plan_id', 'plan_estudio_id'] isOneToOne: false - referencedRelation: "lineas_plan" - referencedColumns: ["id", "plan_estudio_id"] + referencedRelation: 'lineas_plan' + referencedColumns: ['id', 'plan_estudio_id'] }, { - foreignKeyName: "asignaturas_plan_estudio_id_fkey" - columns: ["plan_estudio_id"] + foreignKeyName: 'asignaturas_plan_estudio_id_fkey' + columns: ['plan_estudio_id'] isOneToOne: false - referencedRelation: "planes_estudio" - referencedColumns: ["id"] + referencedRelation: 'planes_estudio' + referencedColumns: ['id'] }, { - foreignKeyName: "asignaturas_plan_estudio_id_fkey" - columns: ["plan_estudio_id"] + foreignKeyName: 'asignaturas_plan_estudio_id_fkey' + columns: ['plan_estudio_id'] isOneToOne: false - referencedRelation: "plantilla_plan" - referencedColumns: ["plan_estudio_id"] + referencedRelation: 'plantilla_plan' + referencedColumns: ['plan_estudio_id'] }, ] } @@ -208,8 +208,8 @@ export type Database = { creado_en: string creado_por: string | null id: string - tipo: Database["public"]["Enums"]["tipo_bibliografia"] - tipo_fuente: Database["public"]["Enums"]["tipo_fuente_bibliografia"] + tipo: Database['public']['Enums']['tipo_bibliografia'] + tipo_fuente: Database['public']['Enums']['tipo_fuente_bibliografia'] } Insert: { actualizado_en?: string @@ -219,8 +219,8 @@ export type Database = { creado_en?: string creado_por?: string | null id?: string - tipo: Database["public"]["Enums"]["tipo_bibliografia"] - tipo_fuente?: Database["public"]["Enums"]["tipo_fuente_bibliografia"] + tipo: Database['public']['Enums']['tipo_bibliografia'] + tipo_fuente?: Database['public']['Enums']['tipo_fuente_bibliografia'] } Update: { actualizado_en?: string @@ -230,23 +230,23 @@ export type Database = { creado_en?: string creado_por?: string | null id?: string - tipo?: Database["public"]["Enums"]["tipo_bibliografia"] - tipo_fuente?: Database["public"]["Enums"]["tipo_fuente_bibliografia"] + tipo?: Database['public']['Enums']['tipo_bibliografia'] + tipo_fuente?: Database['public']['Enums']['tipo_fuente_bibliografia'] } Relationships: [ { - foreignKeyName: "bibliografia_asignatura_asignatura_id_fkey" - columns: ["asignatura_id"] + foreignKeyName: 'bibliografia_asignatura_asignatura_id_fkey' + columns: ['asignatura_id'] isOneToOne: false - referencedRelation: "asignaturas" - referencedColumns: ["id"] + referencedRelation: 'asignaturas' + referencedColumns: ['id'] }, { - foreignKeyName: "bibliografia_asignatura_creado_por_fkey" - columns: ["creado_por"] + foreignKeyName: 'bibliografia_asignatura_creado_por_fkey' + columns: ['creado_por'] isOneToOne: false - referencedRelation: "usuarios_app" - referencedColumns: ["id"] + referencedRelation: 'usuarios_app' + referencedColumns: ['id'] }, ] } @@ -256,10 +256,10 @@ export type Database = { cambiado_en: string cambiado_por: string | null campo: string | null - fuente: Database["public"]["Enums"]["fuente_cambio"] | null + fuente: Database['public']['Enums']['fuente_cambio'] | null id: string interaccion_ia_id: string | null - tipo: Database["public"]["Enums"]["tipo_cambio"] + tipo: Database['public']['Enums']['tipo_cambio'] valor_anterior: Json | null valor_nuevo: Json | null } @@ -268,10 +268,10 @@ export type Database = { cambiado_en?: string cambiado_por?: string | null campo?: string | null - fuente?: Database["public"]["Enums"]["fuente_cambio"] | null + fuente?: Database['public']['Enums']['fuente_cambio'] | null id?: string interaccion_ia_id?: string | null - tipo: Database["public"]["Enums"]["tipo_cambio"] + tipo: Database['public']['Enums']['tipo_cambio'] valor_anterior?: Json | null valor_nuevo?: Json | null } @@ -280,27 +280,27 @@ export type Database = { cambiado_en?: string cambiado_por?: string | null campo?: string | null - fuente?: Database["public"]["Enums"]["fuente_cambio"] | null + fuente?: Database['public']['Enums']['fuente_cambio'] | null id?: string interaccion_ia_id?: string | null - tipo?: Database["public"]["Enums"]["tipo_cambio"] + tipo?: Database['public']['Enums']['tipo_cambio'] valor_anterior?: Json | null valor_nuevo?: Json | null } Relationships: [ { - foreignKeyName: "cambios_asignatura_asignatura_id_fkey" - columns: ["asignatura_id"] + foreignKeyName: 'cambios_asignatura_asignatura_id_fkey' + columns: ['asignatura_id'] isOneToOne: false - referencedRelation: "asignaturas" - referencedColumns: ["id"] + referencedRelation: 'asignaturas' + referencedColumns: ['id'] }, { - foreignKeyName: "cambios_asignatura_cambiado_por_fkey" - columns: ["cambiado_por"] + foreignKeyName: 'cambios_asignatura_cambiado_por_fkey' + columns: ['cambiado_por'] isOneToOne: false - referencedRelation: "usuarios_app" - referencedColumns: ["id"] + referencedRelation: 'usuarios_app' + referencedColumns: ['id'] }, ] } @@ -312,7 +312,7 @@ export type Database = { id: string plan_estudio_id: string response_id: string | null - tipo: Database["public"]["Enums"]["tipo_cambio"] + tipo: Database['public']['Enums']['tipo_cambio'] valor_anterior: Json | null valor_nuevo: Json | null } @@ -323,7 +323,7 @@ export type Database = { id?: string plan_estudio_id: string response_id?: string | null - tipo: Database["public"]["Enums"]["tipo_cambio"] + tipo: Database['public']['Enums']['tipo_cambio'] valor_anterior?: Json | null valor_nuevo?: Json | null } @@ -334,17 +334,17 @@ export type Database = { id?: string plan_estudio_id?: string response_id?: string | null - tipo?: Database["public"]["Enums"]["tipo_cambio"] + tipo?: Database['public']['Enums']['tipo_cambio'] valor_anterior?: Json | null valor_nuevo?: Json | null } Relationships: [ { - foreignKeyName: "cambios_plan_cambiado_por_fkey" - columns: ["cambiado_por"] + foreignKeyName: 'cambios_plan_cambiado_por_fkey' + columns: ['cambiado_por'] isOneToOne: false - referencedRelation: "usuarios_app" - referencedColumns: ["id"] + referencedRelation: 'usuarios_app' + referencedColumns: ['id'] }, ] } @@ -381,11 +381,11 @@ export type Database = { } Relationships: [ { - foreignKeyName: "carreras_facultad_id_fkey" - columns: ["facultad_id"] + foreignKeyName: 'carreras_facultad_id_fkey' + columns: ['facultad_id'] isOneToOne: false - referencedRelation: "facultades" - referencedColumns: ["id"] + referencedRelation: 'facultades' + referencedColumns: ['id'] }, ] } @@ -397,7 +397,7 @@ export type Database = { conversacion_json: Json creado_en: string creado_por: string | null - estado: Database["public"]["Enums"]["estado_conversacion"] + estado: Database['public']['Enums']['estado_conversacion'] id: string intento_archivado: number openai_conversation_id: string @@ -409,7 +409,7 @@ export type Database = { conversacion_json?: Json creado_en?: string creado_por?: string | null - estado?: Database["public"]["Enums"]["estado_conversacion"] + estado?: Database['public']['Enums']['estado_conversacion'] id?: string intento_archivado?: number openai_conversation_id: string @@ -421,32 +421,32 @@ export type Database = { conversacion_json?: Json creado_en?: string creado_por?: string | null - estado?: Database["public"]["Enums"]["estado_conversacion"] + estado?: Database['public']['Enums']['estado_conversacion'] id?: string intento_archivado?: number openai_conversation_id?: string } Relationships: [ { - foreignKeyName: "conversaciones_asignatura_archivado_por_fkey" - columns: ["archivado_por"] + foreignKeyName: 'conversaciones_asignatura_archivado_por_fkey' + columns: ['archivado_por'] isOneToOne: false - referencedRelation: "usuarios_app" - referencedColumns: ["id"] + referencedRelation: 'usuarios_app' + referencedColumns: ['id'] }, { - foreignKeyName: "conversaciones_asignatura_asignatura_id_fkey" - columns: ["asignatura_id"] + foreignKeyName: 'conversaciones_asignatura_asignatura_id_fkey' + columns: ['asignatura_id'] isOneToOne: false - referencedRelation: "asignaturas" - referencedColumns: ["id"] + referencedRelation: 'asignaturas' + referencedColumns: ['id'] }, { - foreignKeyName: "conversaciones_asignatura_creado_por_fkey" - columns: ["creado_por"] + foreignKeyName: 'conversaciones_asignatura_creado_por_fkey' + columns: ['creado_por'] isOneToOne: false - referencedRelation: "usuarios_app" - referencedColumns: ["id"] + referencedRelation: 'usuarios_app' + referencedColumns: ['id'] }, ] } @@ -457,7 +457,7 @@ export type Database = { conversacion_json: Json creado_en: string creado_por: string | null - estado: Database["public"]["Enums"]["estado_conversacion"] + estado: Database['public']['Enums']['estado_conversacion'] id: string intento_archivado: number nombre: string | null @@ -470,7 +470,7 @@ export type Database = { conversacion_json?: Json creado_en?: string creado_por?: string | null - estado?: Database["public"]["Enums"]["estado_conversacion"] + estado?: Database['public']['Enums']['estado_conversacion'] id?: string intento_archivado?: number nombre?: string | null @@ -483,7 +483,7 @@ export type Database = { conversacion_json?: Json creado_en?: string creado_por?: string | null - estado?: Database["public"]["Enums"]["estado_conversacion"] + estado?: Database['public']['Enums']['estado_conversacion'] id?: string intento_archivado?: number nombre?: string | null @@ -492,32 +492,32 @@ export type Database = { } Relationships: [ { - foreignKeyName: "conversaciones_plan_archivado_por_fkey" - columns: ["archivado_por"] + foreignKeyName: 'conversaciones_plan_archivado_por_fkey' + columns: ['archivado_por'] isOneToOne: false - referencedRelation: "usuarios_app" - referencedColumns: ["id"] + referencedRelation: 'usuarios_app' + referencedColumns: ['id'] }, { - foreignKeyName: "conversaciones_plan_creado_por_fkey" - columns: ["creado_por"] + foreignKeyName: 'conversaciones_plan_creado_por_fkey' + columns: ['creado_por'] isOneToOne: false - referencedRelation: "usuarios_app" - referencedColumns: ["id"] + referencedRelation: 'usuarios_app' + referencedColumns: ['id'] }, { - foreignKeyName: "conversaciones_plan_plan_estudio_id_fkey" - columns: ["plan_estudio_id"] + foreignKeyName: 'conversaciones_plan_plan_estudio_id_fkey' + columns: ['plan_estudio_id'] isOneToOne: false - referencedRelation: "planes_estudio" - referencedColumns: ["id"] + referencedRelation: 'planes_estudio' + referencedColumns: ['id'] }, { - foreignKeyName: "conversaciones_plan_plan_estudio_id_fkey" - columns: ["plan_estudio_id"] + foreignKeyName: 'conversaciones_plan_plan_estudio_id_fkey' + columns: ['plan_estudio_id'] isOneToOne: false - referencedRelation: "plantilla_plan" - referencedColumns: ["plan_estudio_id"] + referencedRelation: 'plantilla_plan' + referencedColumns: ['plan_estudio_id'] }, ] } @@ -580,7 +580,7 @@ export type Database = { id: string nombre: string template_id: string | null - tipo: Database["public"]["Enums"]["tipo_estructura_plan"] + tipo: Database['public']['Enums']['tipo_estructura_plan'] } Insert: { actualizado_en?: string @@ -589,7 +589,7 @@ export type Database = { id?: string nombre: string template_id?: string | null - tipo: Database["public"]["Enums"]["tipo_estructura_plan"] + tipo: Database['public']['Enums']['tipo_estructura_plan'] } Update: { actualizado_en?: string @@ -598,7 +598,7 @@ export type Database = { id?: string nombre?: string template_id?: string | null - tipo?: Database["public"]["Enums"]["tipo_estructura_plan"] + tipo?: Database['public']['Enums']['tipo_estructura_plan'] } Relationships: [] } @@ -647,7 +647,7 @@ export type Database = { respuesta: Json rutas_storage: Json temperatura: number | null - tipo: Database["public"]["Enums"]["tipo_interaccion_ia"] + tipo: Database['public']['Enums']['tipo_interaccion_ia'] usuario_id: string | null } Insert: { @@ -664,7 +664,7 @@ export type Database = { respuesta?: Json rutas_storage?: Json temperatura?: number | null - tipo: Database["public"]["Enums"]["tipo_interaccion_ia"] + tipo: Database['public']['Enums']['tipo_interaccion_ia'] usuario_id?: string | null } Update: { @@ -681,37 +681,37 @@ export type Database = { respuesta?: Json rutas_storage?: Json temperatura?: number | null - tipo?: Database["public"]["Enums"]["tipo_interaccion_ia"] + tipo?: Database['public']['Enums']['tipo_interaccion_ia'] usuario_id?: string | null } Relationships: [ { - foreignKeyName: "interacciones_ia_asignatura_id_fkey" - columns: ["asignatura_id"] + foreignKeyName: 'interacciones_ia_asignatura_id_fkey' + columns: ['asignatura_id'] isOneToOne: false - referencedRelation: "asignaturas" - referencedColumns: ["id"] + referencedRelation: 'asignaturas' + referencedColumns: ['id'] }, { - foreignKeyName: "interacciones_ia_plan_estudio_id_fkey" - columns: ["plan_estudio_id"] + foreignKeyName: 'interacciones_ia_plan_estudio_id_fkey' + columns: ['plan_estudio_id'] isOneToOne: false - referencedRelation: "planes_estudio" - referencedColumns: ["id"] + referencedRelation: 'planes_estudio' + referencedColumns: ['id'] }, { - foreignKeyName: "interacciones_ia_plan_estudio_id_fkey" - columns: ["plan_estudio_id"] + foreignKeyName: 'interacciones_ia_plan_estudio_id_fkey' + columns: ['plan_estudio_id'] isOneToOne: false - referencedRelation: "plantilla_plan" - referencedColumns: ["plan_estudio_id"] + referencedRelation: 'plantilla_plan' + referencedColumns: ['plan_estudio_id'] }, { - foreignKeyName: "interacciones_ia_usuario_id_fkey" - columns: ["usuario_id"] + foreignKeyName: 'interacciones_ia_usuario_id_fkey' + columns: ['usuario_id'] isOneToOne: false - referencedRelation: "usuarios_app" - referencedColumns: ["id"] + referencedRelation: 'usuarios_app' + referencedColumns: ['id'] }, ] } @@ -745,18 +745,18 @@ export type Database = { } Relationships: [ { - foreignKeyName: "lineas_plan_plan_estudio_id_fkey" - columns: ["plan_estudio_id"] + foreignKeyName: 'lineas_plan_plan_estudio_id_fkey' + columns: ['plan_estudio_id'] isOneToOne: false - referencedRelation: "planes_estudio" - referencedColumns: ["id"] + referencedRelation: 'planes_estudio' + referencedColumns: ['id'] }, { - foreignKeyName: "lineas_plan_plan_estudio_id_fkey" - columns: ["plan_estudio_id"] + foreignKeyName: 'lineas_plan_plan_estudio_id_fkey' + columns: ['plan_estudio_id'] isOneToOne: false - referencedRelation: "plantilla_plan" - referencedColumns: ["plan_estudio_id"] + referencedRelation: 'plantilla_plan' + referencedColumns: ['plan_estudio_id'] }, ] } @@ -767,7 +767,7 @@ export type Database = { leida: boolean leida_en: string | null payload: Json - tipo: Database["public"]["Enums"]["tipo_notificacion"] + tipo: Database['public']['Enums']['tipo_notificacion'] usuario_id: string } Insert: { @@ -776,7 +776,7 @@ export type Database = { leida?: boolean leida_en?: string | null payload?: Json - tipo: Database["public"]["Enums"]["tipo_notificacion"] + tipo: Database['public']['Enums']['tipo_notificacion'] usuario_id: string } Update: { @@ -785,16 +785,16 @@ export type Database = { leida?: boolean leida_en?: string | null payload?: Json - tipo?: Database["public"]["Enums"]["tipo_notificacion"] + tipo?: Database['public']['Enums']['tipo_notificacion'] usuario_id?: string } Relationships: [ { - foreignKeyName: "notificaciones_usuario_id_fkey" - columns: ["usuario_id"] + foreignKeyName: 'notificaciones_usuario_id_fkey' + columns: ['usuario_id'] isOneToOne: false - referencedRelation: "usuarios_app" - referencedColumns: ["id"] + referencedRelation: 'usuarios_app' + referencedColumns: ['id'] }, ] } @@ -811,13 +811,13 @@ export type Database = { estructura_id: string id: string meta_origen: Json - nivel: Database["public"]["Enums"]["nivel_plan_estudio"] + nivel: Database['public']['Enums']['nivel_plan_estudio'] nombre: string nombre_search: string | null numero_ciclos: number plan_hash: string | null - tipo_ciclo: Database["public"]["Enums"]["tipo_ciclo"] - tipo_origen: Database["public"]["Enums"]["tipo_origen"] | null + tipo_ciclo: Database['public']['Enums']['tipo_ciclo'] + tipo_origen: Database['public']['Enums']['tipo_origen'] | null } Insert: { activo?: boolean @@ -831,13 +831,13 @@ export type Database = { estructura_id: string id?: string meta_origen?: Json - nivel: Database["public"]["Enums"]["nivel_plan_estudio"] + nivel: Database['public']['Enums']['nivel_plan_estudio'] nombre: string nombre_search?: string | null numero_ciclos: number plan_hash?: string | null - tipo_ciclo: Database["public"]["Enums"]["tipo_ciclo"] - tipo_origen?: Database["public"]["Enums"]["tipo_origen"] | null + tipo_ciclo: Database['public']['Enums']['tipo_ciclo'] + tipo_origen?: Database['public']['Enums']['tipo_origen'] | null } Update: { activo?: boolean @@ -851,56 +851,56 @@ export type Database = { estructura_id?: string id?: string meta_origen?: Json - nivel?: Database["public"]["Enums"]["nivel_plan_estudio"] + nivel?: Database['public']['Enums']['nivel_plan_estudio'] nombre?: string nombre_search?: string | null numero_ciclos?: number plan_hash?: string | null - tipo_ciclo?: Database["public"]["Enums"]["tipo_ciclo"] - tipo_origen?: Database["public"]["Enums"]["tipo_origen"] | null + tipo_ciclo?: Database['public']['Enums']['tipo_ciclo'] + tipo_origen?: Database['public']['Enums']['tipo_origen'] | null } Relationships: [ { - foreignKeyName: "planes_estudio_actualizado_por_fkey" - columns: ["actualizado_por"] + foreignKeyName: 'planes_estudio_actualizado_por_fkey' + columns: ['actualizado_por'] isOneToOne: false - referencedRelation: "usuarios_app" - referencedColumns: ["id"] + referencedRelation: 'usuarios_app' + referencedColumns: ['id'] }, { - foreignKeyName: "planes_estudio_carrera_id_fkey" - columns: ["carrera_id"] + foreignKeyName: 'planes_estudio_carrera_id_fkey' + columns: ['carrera_id'] isOneToOne: false - referencedRelation: "carreras" - referencedColumns: ["id"] + referencedRelation: 'carreras' + referencedColumns: ['id'] }, { - foreignKeyName: "planes_estudio_creado_por_fkey" - columns: ["creado_por"] + foreignKeyName: 'planes_estudio_creado_por_fkey' + columns: ['creado_por'] isOneToOne: false - referencedRelation: "usuarios_app" - referencedColumns: ["id"] + referencedRelation: 'usuarios_app' + referencedColumns: ['id'] }, { - foreignKeyName: "planes_estudio_estado_actual_id_fkey" - columns: ["estado_actual_id"] + foreignKeyName: 'planes_estudio_estado_actual_id_fkey' + columns: ['estado_actual_id'] isOneToOne: false - referencedRelation: "estados_plan" - referencedColumns: ["id"] + referencedRelation: 'estados_plan' + referencedColumns: ['id'] }, { - foreignKeyName: "planes_estudio_estructura_id_fkey" - columns: ["estructura_id"] + foreignKeyName: 'planes_estudio_estructura_id_fkey' + columns: ['estructura_id'] isOneToOne: false - referencedRelation: "estructuras_plan" - referencedColumns: ["id"] + referencedRelation: 'estructuras_plan' + referencedColumns: ['id'] }, { - foreignKeyName: "planes_estudio_estructura_id_fkey" - columns: ["estructura_id"] + foreignKeyName: 'planes_estudio_estructura_id_fkey' + columns: ['estructura_id'] isOneToOne: false - referencedRelation: "plantilla_plan" - referencedColumns: ["estructura_id"] + referencedRelation: 'plantilla_plan' + referencedColumns: ['estructura_id'] }, ] } @@ -909,37 +909,37 @@ export type Database = { asignatura_id: string creado_en: string id: string - rol: Database["public"]["Enums"]["rol_responsable_asignatura"] + rol: Database['public']['Enums']['rol_responsable_asignatura'] usuario_id: string } Insert: { asignatura_id: string creado_en?: string id?: string - rol?: Database["public"]["Enums"]["rol_responsable_asignatura"] + rol?: Database['public']['Enums']['rol_responsable_asignatura'] usuario_id: string } Update: { asignatura_id?: string creado_en?: string id?: string - rol?: Database["public"]["Enums"]["rol_responsable_asignatura"] + rol?: Database['public']['Enums']['rol_responsable_asignatura'] usuario_id?: string } Relationships: [ { - foreignKeyName: "responsables_asignatura_asignatura_id_fkey" - columns: ["asignatura_id"] + foreignKeyName: 'responsables_asignatura_asignatura_id_fkey' + columns: ['asignatura_id'] isOneToOne: false - referencedRelation: "asignaturas" - referencedColumns: ["id"] + referencedRelation: 'asignaturas' + referencedColumns: ['id'] }, { - foreignKeyName: "responsables_asignatura_usuario_id_fkey" - columns: ["usuario_id"] + foreignKeyName: 'responsables_asignatura_usuario_id_fkey' + columns: ['usuario_id'] isOneToOne: false - referencedRelation: "usuarios_app" - referencedColumns: ["id"] + referencedRelation: 'usuarios_app' + referencedColumns: ['id'] }, ] } @@ -970,7 +970,7 @@ export type Database = { completado_en: string | null creado_en: string estado_id: string | null - estatus: Database["public"]["Enums"]["estado_tarea_revision"] + estatus: Database['public']['Enums']['estado_tarea_revision'] fecha_limite: string | null id: string plan_estudio_id: string @@ -981,7 +981,7 @@ export type Database = { completado_en?: string | null creado_en?: string estado_id?: string | null - estatus?: Database["public"]["Enums"]["estado_tarea_revision"] + estatus?: Database['public']['Enums']['estado_tarea_revision'] fecha_limite?: string | null id?: string plan_estudio_id: string @@ -992,7 +992,7 @@ export type Database = { completado_en?: string | null creado_en?: string estado_id?: string | null - estatus?: Database["public"]["Enums"]["estado_tarea_revision"] + estatus?: Database['public']['Enums']['estado_tarea_revision'] fecha_limite?: string | null id?: string plan_estudio_id?: string @@ -1000,39 +1000,39 @@ export type Database = { } Relationships: [ { - foreignKeyName: "tareas_revision_asignado_a_fkey" - columns: ["asignado_a"] + foreignKeyName: 'tareas_revision_asignado_a_fkey' + columns: ['asignado_a'] isOneToOne: false - referencedRelation: "usuarios_app" - referencedColumns: ["id"] + referencedRelation: 'usuarios_app' + referencedColumns: ['id'] }, { - foreignKeyName: "tareas_revision_estado_id_fkey" - columns: ["estado_id"] + foreignKeyName: 'tareas_revision_estado_id_fkey' + columns: ['estado_id'] isOneToOne: false - referencedRelation: "estados_plan" - referencedColumns: ["id"] + referencedRelation: 'estados_plan' + referencedColumns: ['id'] }, { - foreignKeyName: "tareas_revision_plan_estudio_id_fkey" - columns: ["plan_estudio_id"] + foreignKeyName: 'tareas_revision_plan_estudio_id_fkey' + columns: ['plan_estudio_id'] isOneToOne: false - referencedRelation: "planes_estudio" - referencedColumns: ["id"] + referencedRelation: 'planes_estudio' + referencedColumns: ['id'] }, { - foreignKeyName: "tareas_revision_plan_estudio_id_fkey" - columns: ["plan_estudio_id"] + foreignKeyName: 'tareas_revision_plan_estudio_id_fkey' + columns: ['plan_estudio_id'] isOneToOne: false - referencedRelation: "plantilla_plan" - referencedColumns: ["plan_estudio_id"] + referencedRelation: 'plantilla_plan' + referencedColumns: ['plan_estudio_id'] }, { - foreignKeyName: "tareas_revision_rol_id_fkey" - columns: ["rol_id"] + foreignKeyName: 'tareas_revision_rol_id_fkey' + columns: ['rol_id'] isOneToOne: false - referencedRelation: "roles" - referencedColumns: ["id"] + referencedRelation: 'roles' + referencedColumns: ['id'] }, ] } @@ -1060,25 +1060,25 @@ export type Database = { } Relationships: [ { - foreignKeyName: "transiciones_estado_plan_desde_estado_id_fkey" - columns: ["desde_estado_id"] + foreignKeyName: 'transiciones_estado_plan_desde_estado_id_fkey' + columns: ['desde_estado_id'] isOneToOne: false - referencedRelation: "estados_plan" - referencedColumns: ["id"] + referencedRelation: 'estados_plan' + referencedColumns: ['id'] }, { - foreignKeyName: "transiciones_estado_plan_hacia_estado_id_fkey" - columns: ["hacia_estado_id"] + foreignKeyName: 'transiciones_estado_plan_hacia_estado_id_fkey' + columns: ['hacia_estado_id'] isOneToOne: false - referencedRelation: "estados_plan" - referencedColumns: ["id"] + referencedRelation: 'estados_plan' + referencedColumns: ['id'] }, { - foreignKeyName: "transiciones_estado_plan_rol_permitido_id_fkey" - columns: ["rol_permitido_id"] + foreignKeyName: 'transiciones_estado_plan_rol_permitido_id_fkey' + columns: ['rol_permitido_id'] isOneToOne: false - referencedRelation: "roles" - referencedColumns: ["id"] + referencedRelation: 'roles' + referencedColumns: ['id'] }, ] } @@ -1136,32 +1136,32 @@ export type Database = { } Relationships: [ { - foreignKeyName: "usuarios_roles_carrera_id_fkey" - columns: ["carrera_id"] + foreignKeyName: 'usuarios_roles_carrera_id_fkey' + columns: ['carrera_id'] isOneToOne: false - referencedRelation: "carreras" - referencedColumns: ["id"] + referencedRelation: 'carreras' + referencedColumns: ['id'] }, { - foreignKeyName: "usuarios_roles_facultad_id_fkey" - columns: ["facultad_id"] + foreignKeyName: 'usuarios_roles_facultad_id_fkey' + columns: ['facultad_id'] isOneToOne: false - referencedRelation: "facultades" - referencedColumns: ["id"] + referencedRelation: 'facultades' + referencedColumns: ['id'] }, { - foreignKeyName: "usuarios_roles_rol_id_fkey" - columns: ["rol_id"] + foreignKeyName: 'usuarios_roles_rol_id_fkey' + columns: ['rol_id'] isOneToOne: false - referencedRelation: "roles" - referencedColumns: ["id"] + referencedRelation: 'roles' + referencedColumns: ['id'] }, { - foreignKeyName: "usuarios_roles_usuario_id_fkey" - columns: ["usuario_id"] + foreignKeyName: 'usuarios_roles_usuario_id_fkey' + columns: ['usuario_id'] isOneToOne: false - referencedRelation: "usuarios_app" - referencedColumns: ["id"] + referencedRelation: 'usuarios_app' + referencedColumns: ['id'] }, ] } @@ -1189,11 +1189,11 @@ export type Database = { } Relationships: [ { - foreignKeyName: "vector_stores_creado_por_fkey" - columns: ["creado_por"] + foreignKeyName: 'vector_stores_creado_por_fkey' + columns: ['creado_por'] isOneToOne: false - referencedRelation: "usuarios_app" - referencedColumns: ["id"] + referencedRelation: 'usuarios_app' + referencedColumns: ['id'] }, ] } @@ -1209,54 +1209,67 @@ export type Database = { } } Functions: { - unaccent: { Args: { "": string }; Returns: string } - unaccent_immutable: { Args: { "": string }; Returns: string } + append_conversacion_asignatura: { + Args: { p_append: Json; p_id: string } + Returns: undefined + } + append_conversacion_plan: { + Args: { p_append: Json; p_id: string } + Returns: undefined + } + unaccent: { Args: { '': string }; Returns: string } + unaccent_immutable: { Args: { '': string }; Returns: string } } Enums: { - estado_asignatura: "borrador" | "revisada" | "aprobada" | "generando" - estado_conversacion: "ACTIVA" | "ARCHIVANDO" | "ARCHIVADA" | "ERROR" - estado_tarea_revision: "PENDIENTE" | "COMPLETADA" | "OMITIDA" - fuente_cambio: "HUMANO" | "IA" + estado_asignatura: + | 'borrador' + | 'revisada' + | 'aprobada' + | 'generando' + | 'fallida' + estado_conversacion: 'ACTIVA' | 'ARCHIVANDO' | 'ARCHIVADA' | 'ERROR' + estado_tarea_revision: 'PENDIENTE' | 'COMPLETADA' | 'OMITIDA' + fuente_cambio: 'HUMANO' | 'IA' nivel_plan_estudio: - | "Licenciatura" - | "Maestría" - | "Doctorado" - | "Especialidad" - | "Diplomado" - | "Otro" + | 'Licenciatura' + | 'Maestría' + | 'Doctorado' + | 'Especialidad' + | 'Diplomado' + | 'Otro' puesto_tipo: - | "vicerrector" - | "director_facultad" - | "secretario_academico" - | "jefe_carrera" - | "profesor" - | "lci" - rol_responsable_asignatura: "PROFESOR_RESPONSABLE" | "COAUTOR" | "REVISOR" - tipo_asignatura: "OBLIGATORIA" | "OPTATIVA" | "TRONCAL" | "OTRA" - tipo_bibliografia: "BASICA" | "COMPLEMENTARIA" + | 'vicerrector' + | 'director_facultad' + | 'secretario_academico' + | 'jefe_carrera' + | 'profesor' + | 'lci' + rol_responsable_asignatura: 'PROFESOR_RESPONSABLE' | 'COAUTOR' | 'REVISOR' + tipo_asignatura: 'OBLIGATORIA' | 'OPTATIVA' | 'TRONCAL' | 'OTRA' + tipo_bibliografia: 'BASICA' | 'COMPLEMENTARIA' tipo_cambio: - | "ACTUALIZACION_CAMPO" - | "ACTUALIZACION_MAPA" - | "TRANSICION_ESTADO" - | "OTRO" - | "CREACION" - | "ACTUALIZACION" - tipo_ciclo: "Semestre" | "Cuatrimestre" | "Trimestre" | "Otro" - tipo_estructura_plan: "CURRICULAR" | "NO_CURRICULAR" - tipo_fuente_bibliografia: "MANUAL" | "BIBLIOTECA" - tipo_interaccion_ia: "GENERAR" | "MEJORAR_SECCION" | "CHAT" | "OTRA" + | 'ACTUALIZACION_CAMPO' + | 'ACTUALIZACION_MAPA' + | 'TRANSICION_ESTADO' + | 'OTRO' + | 'CREACION' + | 'ACTUALIZACION' + tipo_ciclo: 'Semestre' | 'Cuatrimestre' | 'Trimestre' | 'Otro' + tipo_estructura_plan: 'CURRICULAR' | 'NO_CURRICULAR' + tipo_fuente_bibliografia: 'MANUAL' | 'BIBLIOTECA' + tipo_interaccion_ia: 'GENERAR' | 'MEJORAR_SECCION' | 'CHAT' | 'OTRA' tipo_notificacion: - | "PLAN_ASIGNADO" - | "ESTADO_CAMBIADO" - | "TAREA_ASIGNADA" - | "COMENTARIO" - | "OTRA" + | 'PLAN_ASIGNADO' + | 'ESTADO_CAMBIADO' + | 'TAREA_ASIGNADA' + | 'COMENTARIO' + | 'OTRA' tipo_origen: - | "MANUAL" - | "IA" - | "CLONADO_INTERNO" - | "CLONADO_TRADICIONAL" - | "OTRO" + | 'MANUAL' + | 'IA' + | 'CLONADO_INTERNO' + | 'CLONADO_TRADICIONAL' + | 'OTRO' } CompositeTypes: { [_ in never]: never @@ -1264,33 +1277,33 @@ export type Database = { } } -type DatabaseWithoutInternals = Omit +type DatabaseWithoutInternals = Omit -type DefaultSchema = DatabaseWithoutInternals[Extract] +type DefaultSchema = DatabaseWithoutInternals[Extract] export type Tables< DefaultSchemaTableNameOrOptions extends - | keyof (DefaultSchema["Tables"] & DefaultSchema["Views"]) + | keyof (DefaultSchema['Tables'] & DefaultSchema['Views']) | { schema: keyof DatabaseWithoutInternals }, TableName extends DefaultSchemaTableNameOrOptions extends { schema: keyof DatabaseWithoutInternals } - ? keyof (DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"] & - DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Views"]) + ? keyof (DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions['schema']]['Tables'] & + DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions['schema']]['Views']) : never = never, > = DefaultSchemaTableNameOrOptions extends { schema: keyof DatabaseWithoutInternals } - ? (DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"] & - DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Views"])[TableName] extends { + ? (DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions['schema']]['Tables'] & + DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions['schema']]['Views'])[TableName] extends { Row: infer R } ? R : never - : DefaultSchemaTableNameOrOptions extends keyof (DefaultSchema["Tables"] & - DefaultSchema["Views"]) - ? (DefaultSchema["Tables"] & - DefaultSchema["Views"])[DefaultSchemaTableNameOrOptions] extends { + : DefaultSchemaTableNameOrOptions extends keyof (DefaultSchema['Tables'] & + DefaultSchema['Views']) + ? (DefaultSchema['Tables'] & + DefaultSchema['Views'])[DefaultSchemaTableNameOrOptions] extends { Row: infer R } ? R @@ -1299,23 +1312,23 @@ export type Tables< export type TablesInsert< DefaultSchemaTableNameOrOptions extends - | keyof DefaultSchema["Tables"] + | keyof DefaultSchema['Tables'] | { schema: keyof DatabaseWithoutInternals }, TableName extends DefaultSchemaTableNameOrOptions extends { schema: keyof DatabaseWithoutInternals } - ? keyof DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"] + ? keyof DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions['schema']]['Tables'] : never = never, > = DefaultSchemaTableNameOrOptions extends { schema: keyof DatabaseWithoutInternals } - ? DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"][TableName] extends { + ? DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions['schema']]['Tables'][TableName] extends { Insert: infer I } ? I : never - : DefaultSchemaTableNameOrOptions extends keyof DefaultSchema["Tables"] - ? DefaultSchema["Tables"][DefaultSchemaTableNameOrOptions] extends { + : DefaultSchemaTableNameOrOptions extends keyof DefaultSchema['Tables'] + ? DefaultSchema['Tables'][DefaultSchemaTableNameOrOptions] extends { Insert: infer I } ? I @@ -1324,23 +1337,23 @@ export type TablesInsert< export type TablesUpdate< DefaultSchemaTableNameOrOptions extends - | keyof DefaultSchema["Tables"] + | keyof DefaultSchema['Tables'] | { schema: keyof DatabaseWithoutInternals }, TableName extends DefaultSchemaTableNameOrOptions extends { schema: keyof DatabaseWithoutInternals } - ? keyof DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"] + ? keyof DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions['schema']]['Tables'] : never = never, > = DefaultSchemaTableNameOrOptions extends { schema: keyof DatabaseWithoutInternals } - ? DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"][TableName] extends { + ? DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions['schema']]['Tables'][TableName] extends { Update: infer U } ? U : never - : DefaultSchemaTableNameOrOptions extends keyof DefaultSchema["Tables"] - ? DefaultSchema["Tables"][DefaultSchemaTableNameOrOptions] extends { + : DefaultSchemaTableNameOrOptions extends keyof DefaultSchema['Tables'] + ? DefaultSchema['Tables'][DefaultSchemaTableNameOrOptions] extends { Update: infer U } ? U @@ -1349,36 +1362,36 @@ export type TablesUpdate< export type Enums< DefaultSchemaEnumNameOrOptions extends - | keyof DefaultSchema["Enums"] + | keyof DefaultSchema['Enums'] | { schema: keyof DatabaseWithoutInternals }, EnumName extends DefaultSchemaEnumNameOrOptions extends { schema: keyof DatabaseWithoutInternals } - ? keyof DatabaseWithoutInternals[DefaultSchemaEnumNameOrOptions["schema"]]["Enums"] + ? keyof DatabaseWithoutInternals[DefaultSchemaEnumNameOrOptions['schema']]['Enums'] : never = never, > = DefaultSchemaEnumNameOrOptions extends { schema: keyof DatabaseWithoutInternals } - ? DatabaseWithoutInternals[DefaultSchemaEnumNameOrOptions["schema"]]["Enums"][EnumName] - : DefaultSchemaEnumNameOrOptions extends keyof DefaultSchema["Enums"] - ? DefaultSchema["Enums"][DefaultSchemaEnumNameOrOptions] + ? DatabaseWithoutInternals[DefaultSchemaEnumNameOrOptions['schema']]['Enums'][EnumName] + : DefaultSchemaEnumNameOrOptions extends keyof DefaultSchema['Enums'] + ? DefaultSchema['Enums'][DefaultSchemaEnumNameOrOptions] : never export type CompositeTypes< PublicCompositeTypeNameOrOptions extends - | keyof DefaultSchema["CompositeTypes"] + | keyof DefaultSchema['CompositeTypes'] | { schema: keyof DatabaseWithoutInternals }, CompositeTypeName extends PublicCompositeTypeNameOrOptions extends { schema: keyof DatabaseWithoutInternals } - ? keyof DatabaseWithoutInternals[PublicCompositeTypeNameOrOptions["schema"]]["CompositeTypes"] + ? keyof DatabaseWithoutInternals[PublicCompositeTypeNameOrOptions['schema']]['CompositeTypes'] : never = never, > = PublicCompositeTypeNameOrOptions extends { schema: keyof DatabaseWithoutInternals } - ? DatabaseWithoutInternals[PublicCompositeTypeNameOrOptions["schema"]]["CompositeTypes"][CompositeTypeName] - : PublicCompositeTypeNameOrOptions extends keyof DefaultSchema["CompositeTypes"] - ? DefaultSchema["CompositeTypes"][PublicCompositeTypeNameOrOptions] + ? DatabaseWithoutInternals[PublicCompositeTypeNameOrOptions['schema']]['CompositeTypes'][CompositeTypeName] + : PublicCompositeTypeNameOrOptions extends keyof DefaultSchema['CompositeTypes'] + ? DefaultSchema['CompositeTypes'][PublicCompositeTypeNameOrOptions] : never export const Constants = { @@ -1387,60 +1400,65 @@ export const Constants = { }, public: { Enums: { - estado_asignatura: ["borrador", "revisada", "aprobada", "generando"], - estado_conversacion: ["ACTIVA", "ARCHIVANDO", "ARCHIVADA", "ERROR"], - estado_tarea_revision: ["PENDIENTE", "COMPLETADA", "OMITIDA"], - fuente_cambio: ["HUMANO", "IA"], + estado_asignatura: [ + 'borrador', + 'revisada', + 'aprobada', + 'generando', + 'fallida', + ], + estado_conversacion: ['ACTIVA', 'ARCHIVANDO', 'ARCHIVADA', 'ERROR'], + estado_tarea_revision: ['PENDIENTE', 'COMPLETADA', 'OMITIDA'], + fuente_cambio: ['HUMANO', 'IA'], nivel_plan_estudio: [ - "Licenciatura", - "Maestría", - "Doctorado", - "Especialidad", - "Diplomado", - "Otro", + 'Licenciatura', + 'Maestría', + 'Doctorado', + 'Especialidad', + 'Diplomado', + 'Otro', ], puesto_tipo: [ - "vicerrector", - "director_facultad", - "secretario_academico", - "jefe_carrera", - "profesor", - "lci", + 'vicerrector', + 'director_facultad', + 'secretario_academico', + 'jefe_carrera', + 'profesor', + 'lci', ], rol_responsable_asignatura: [ - "PROFESOR_RESPONSABLE", - "COAUTOR", - "REVISOR", + 'PROFESOR_RESPONSABLE', + 'COAUTOR', + 'REVISOR', ], - tipo_asignatura: ["OBLIGATORIA", "OPTATIVA", "TRONCAL", "OTRA"], - tipo_bibliografia: ["BASICA", "COMPLEMENTARIA"], + tipo_asignatura: ['OBLIGATORIA', 'OPTATIVA', 'TRONCAL', 'OTRA'], + tipo_bibliografia: ['BASICA', 'COMPLEMENTARIA'], tipo_cambio: [ - "ACTUALIZACION_CAMPO", - "ACTUALIZACION_MAPA", - "TRANSICION_ESTADO", - "OTRO", - "CREACION", - "ACTUALIZACION", + 'ACTUALIZACION_CAMPO', + 'ACTUALIZACION_MAPA', + 'TRANSICION_ESTADO', + 'OTRO', + 'CREACION', + 'ACTUALIZACION', ], - tipo_ciclo: ["Semestre", "Cuatrimestre", "Trimestre", "Otro"], - tipo_estructura_plan: ["CURRICULAR", "NO_CURRICULAR"], - tipo_fuente_bibliografia: ["MANUAL", "BIBLIOTECA"], - tipo_interaccion_ia: ["GENERAR", "MEJORAR_SECCION", "CHAT", "OTRA"], + tipo_ciclo: ['Semestre', 'Cuatrimestre', 'Trimestre', 'Otro'], + tipo_estructura_plan: ['CURRICULAR', 'NO_CURRICULAR'], + tipo_fuente_bibliografia: ['MANUAL', 'BIBLIOTECA'], + tipo_interaccion_ia: ['GENERAR', 'MEJORAR_SECCION', 'CHAT', 'OTRA'], tipo_notificacion: [ - "PLAN_ASIGNADO", - "ESTADO_CAMBIADO", - "TAREA_ASIGNADA", - "COMENTARIO", - "OTRA", + 'PLAN_ASIGNADO', + 'ESTADO_CAMBIADO', + 'TAREA_ASIGNADA', + 'COMENTARIO', + 'OTRA', ], tipo_origen: [ - "MANUAL", - "IA", - "CLONADO_INTERNO", - "CLONADO_TRADICIONAL", - "OTRO", + 'MANUAL', + 'IA', + 'CLONADO_INTERNO', + 'CLONADO_TRADICIONAL', + 'OTRO', ], }, }, } as const -