spinners, creación manual de asignatura, actualización de asignaturas generadas por sugerencias

fix #29:
-  Se agregaron spinners en la creación con IA de un plan o una asignatura
- Se añadió la creación manual de asignaturas
- Al generar asignaturas a partir de sugerencias, el badge de estado de la asignatura dice 'Generando' y muestra una animación tipo respiro para indicar que está siendo generada. Adicionalmente, se actualiza automáticamente la UI una vez que acabó de ser generada
This commit is contained in:
2026-02-13 12:44:05 -06:00
parent 04909513bb
commit 2624b0694d
6 changed files with 105 additions and 55 deletions

View File

@@ -16,7 +16,7 @@ import type {
AsignaturaSugerida,
DataAsignaturaSugerida,
} from '@/features/asignaturas/nueva/types'
import type { Database } from '@/types/supabase'
import type { Database, TablesInsert } from '@/types/supabase'
const EDGE = {
generate_subject_suggestions: 'generate-subject-suggestions',
@@ -89,23 +89,18 @@ export async function subjects_bibliografia_list(
return data ?? []
}
/** Wizard: crear asignatura manual (Edge Function) */
export type SubjectsCreateManualInput = {
planId: UUID
datosBasicos: {
nombre: string
clave?: string
tipo: TipoAsignatura
creditos: number
horasSemana?: number
estructuraId: UUID
}
}
export async function subjects_create_manual(
payload: SubjectsCreateManualInput,
payload: TablesInsert<'asignaturas'>,
): Promise<Asignatura> {
return invokeEdge<Asignatura>(EDGE.subjects_create_manual, payload)
const supabase = supabaseBrowser()
const { data, error } = await supabase
.from('asignaturas')
.insert(payload)
.select()
.single()
throwIfError(error)
return requireData(data, 'No se pudo crear la asignatura.')
}
export type AIGenerateSubjectInput = {

View File

@@ -77,6 +77,16 @@ export function usePlanAsignaturas(planId: UUID | null | undefined) {
: ['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,
})
}
@@ -269,7 +279,7 @@ export function useDeleteLinea() {
const qc = useQueryClient()
return useMutation({
mutationFn: lineas_delete,
onSuccess: (idEliminado) => {
onSuccess: (_idEliminado) => {
// Invalidamos para que las materias y líneas se refresquen
qc.invalidateQueries({ queryKey: ['plan_lineas'] })
qc.invalidateQueries({ queryKey: ['plan_asignaturas'] })

View File

@@ -23,10 +23,10 @@ import { qk } from '../query/keys'
import type {
BibliografiaUpsertInput,
SubjectsCreateManualInput,
SubjectsUpdateFieldsPatch,
} from '../api/subjects.api'
import type { UUID } from '../types/domain'
import type { TablesInsert } from '@/types/supabase'
export function useSubject(subjectId: UUID | null | undefined) {
return useQuery({
@@ -82,7 +82,7 @@ export function useCreateSubjectManual() {
const qc = useQueryClient()
return useMutation({
mutationFn: (payload: SubjectsCreateManualInput) =>
mutationFn: (payload: TablesInsert<'asignaturas'>) =>
subjects_create_manual(payload),
onSuccess: (subject) => {
qc.setQueryData(qk.asignatura(subject.id), subject)