import { supabaseBrowser } from '../supabase/client' import { invokeEdge } from '../supabase/invokeEdge' import { throwIfError, requireData } from './_helpers' import type { DocumentoResult } from './plans.api' import type { Asignatura, BibliografiaAsignatura, CambioAsignatura, TipoAsignatura, UUID, } from '../types/domain' import type { UploadedFile } from '@/components/planes/wizard/PasoDetallesPanel/FileDropZone' import type { AsignaturaSugerida, DataAsignaturaSugerida, } from '@/features/asignaturas/nueva/types' import type { Database } from '@/types/supabase' const EDGE = { generate_subject_suggestions: 'generate-subject-suggestions', subjects_create_manual: 'subjects_create_manual', ai_generate_subject: 'ai-generate-subject', subjects_persist_from_ai: 'subjects_persist_from_ai', subjects_clone_from_existing: 'subjects_clone_from_existing', subjects_import_from_file: 'subjects_import_from_file', subjects_update_fields: 'subjects_update_fields', subjects_update_contenido: 'subjects_update_contenido', subjects_update_bibliografia: 'subjects_update_bibliografia', subjects_generate_document: 'subjects_generate_document', subjects_get_document: 'subjects_get_document', } as const export async function subjects_get(subjectId: UUID): Promise { const supabase = supabaseBrowser() const { data, error } = await supabase .from('asignaturas') .select( ` id,plan_estudio_id,estructura_id,codigo,nombre,tipo,creditos,numero_ciclo,linea_plan_id,orden_celda,datos,contenido_tematico,horas_academicas,horas_independientes,asignatura_hash,conversation_id,tipo_origen,meta_origen,creado_por,actualizado_por,creado_en,actualizado_en, 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)) ), estructuras_asignatura(id,nombre,version,definicion) `, ) .eq('id', subjectId) .single() throwIfError(error) return requireData(data, 'Asignatura no encontrada.') } export async function subjects_history( subjectId: UUID, ): Promise> { const supabase = supabaseBrowser() const { data, error } = await supabase .from('cambios_asignatura') .select( 'id,asignatura_id,cambiado_por,cambiado_en,tipo,campo,valor_anterior,valor_nuevo,fuente,interaccion_ia_id', ) .eq('asignatura_id', subjectId) .order('cambiado_en', { ascending: false }) throwIfError(error) return data ?? [] } export async function subjects_bibliografia_list( subjectId: UUID, ): Promise> { const supabase = supabaseBrowser() const { data, error } = await supabase .from('bibliografia_asignatura') .select( 'id,asignatura_id,tipo,cita,tipo_fuente,biblioteca_item_id,creado_por,creado_en,actualizado_en', ) .eq('asignatura_id', subjectId) .order('tipo', { ascending: true }) .order('creado_en', { ascending: true }) throwIfError(error) 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, ): Promise { return invokeEdge(EDGE.subjects_create_manual, payload) } 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 } // 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 } } export type GenerateSubjectSuggestionsInput = { plan_estudio_id: UUID enfoque?: string cantidad_de_sugerencias: number sugerencias_conservadas: Array<{ nombre: string; descripcion: string }> } export async function generate_subject_suggestions( input: GenerateSubjectSuggestionsInput, ): Promise> { const raw = await invokeEdge>( EDGE.generate_subject_suggestions, input, { headers: { 'Content-Type': 'application/json' } }, ) return raw.map( (s): AsignaturaSugerida => ({ id: crypto.randomUUID(), selected: false, source: 'IA', estructuraId: null, nombre: s.nombre, codigo: s.codigo, tipo: s.tipo ?? null, creditos: s.creditos ?? null, horasAcademicas: s.horasAcademicas ?? null, horasIndependientes: s.horasIndependientes ?? null, descripcion: s.descripcion, }), ) } export async function ai_generate_subject( input: AIGenerateSubjectInput, ): Promise { 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(), ) } export async function subjects_persist_from_ai(payload: { planId: UUID jsonAsignatura: any }): Promise { return invokeEdge(EDGE.subjects_persist_from_ai, payload) } export async function subjects_clone_from_existing(payload: { asignaturaOrigenId: UUID planDestinoId: UUID overrides?: Partial<{ nombre: string codigo: string tipo: TipoAsignatura creditos: number horas_semana: number }> }): Promise { return invokeEdge(EDGE.subjects_clone_from_existing, payload) } export async function subjects_import_from_file(payload: { planId: UUID archivoWordAsignaturaId: UUID archivosAdicionalesIds?: Array }): Promise { return invokeEdge(EDGE.subjects_import_from_file, payload) } /** Guardado de tarjetas/fields (Edge: merge server-side en asignaturas.datos y columnas) */ export type SubjectsUpdateFieldsPatch = Partial<{ codigo: string | null nombre: string tipo: TipoAsignatura creditos: number horas_semana: number | null numero_ciclo: number | null linea_plan_id: UUID | null datos: Record }> export async function subjects_update_fields( subjectId: UUID, patch: SubjectsUpdateFieldsPatch, ): Promise { return invokeEdge(EDGE.subjects_update_fields, { subjectId, patch, }) } export async function subjects_update_contenido( subjectId: UUID, unidades: Array, ): Promise { return invokeEdge(EDGE.subjects_update_contenido, { subjectId, unidades, }) } export type BibliografiaUpsertInput = Array<{ id?: UUID tipo: 'BASICA' | 'COMPLEMENTARIA' cita: string tipo_fuente?: 'MANUAL' | 'BIBLIOTECA' biblioteca_item_id?: string | null }> export async function subjects_update_bibliografia( subjectId: UUID, entries: BibliografiaUpsertInput, ): Promise<{ ok: true }> { return invokeEdge<{ ok: true }>(EDGE.subjects_update_bibliografia, { subjectId, entries, }) } /** Documento SEP asignatura */ /* export type DocumentoResult = { archivoId: UUID; signedUrl: string; mimeType?: string; nombre?: string; }; */ export async function subjects_generate_document( subjectId: UUID, ): Promise { return invokeEdge(EDGE.subjects_generate_document, { subjectId, }) } export async function subjects_get_document( subjectId: UUID, ): Promise { return invokeEdge(EDGE.subjects_get_document, { subjectId, }) } export async function subjects_get_structure_catalog(): Promise< Array > { const supabase = supabaseBrowser() const { data, error } = await supabase .from('estructuras_asignatura') .select('*') .order('nombre', { ascending: true }) if (error) { throw error } return data } export async function asignaturas_update( asignaturaId: UUID, patch: Partial, // O tu tipo específico para el Patch de materias ): Promise { const supabase = supabaseBrowser() const { data, error } = await supabase .from('asignaturas') .update(patch) .eq('id', asignaturaId) .select() // Trae la materia actualizada .single() throwIfError(error) return requireData(data, 'No se pudo actualizar la asignatura.') } // Insertar una nueva línea export async function lineas_insert(linea: { nombre: string plan_estudio_id: string orden: number area?: string }) { const supabase = supabaseBrowser() const { data, error } = await supabase .from('lineas_plan') // Asegúrate que el nombre de la tabla sea correcto .insert([linea]) .select() .single() if (error) throw error return data } // Actualizar una línea existente export async function lineas_update( lineaId: string, patch: { nombre?: string; orden?: number; area?: string }, ) { const supabase = supabaseBrowser() const { data, error } = await supabase .from('lineas_plan') .update(patch) .eq('id', lineaId) .select() .single() if (error) throw error return data } export async function lineas_delete(lineaId: string) { const supabase = supabaseBrowser() // Nota: Si configuraste "ON DELETE SET NULL" en tu base de datos, // las asignaturas se desvincularán solas. Si no, Supabase podría dar error. const { error } = await supabase .from('lineas_plan') .delete() .eq('id', lineaId) if (error) throw error return lineaId }