diff --git a/bun.lock b/bun.lock index 52c256f..7118678 100644 --- a/bun.lock +++ b/bun.lock @@ -8,6 +8,7 @@ "@radix-ui/react-avatar": "^1.1.11", "@radix-ui/react-checkbox": "^1.3.3", "@radix-ui/react-collapsible": "^1.1.12", + "@radix-ui/react-context-menu": "^2.2.16", "@radix-ui/react-dialog": "^1.1.15", "@radix-ui/react-dropdown-menu": "^2.1.16", "@radix-ui/react-label": "^2.1.8", @@ -266,6 +267,8 @@ "@radix-ui/react-context": ["@radix-ui/react-context@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA=="], + "@radix-ui/react-context-menu": ["@radix-ui/react-context-menu@2.2.16", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-menu": "2.1.16", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-O8morBEW+HsVG28gYDZPTrT9UUovQUlJue5YO836tiTJhuIWBm/zQHc7j388sHWtdH/xUZurK9olD2+pcqx5ww=="], + "@radix-ui/react-dialog": ["@radix-ui/react-dialog@1.1.15", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-focus-guards": "1.1.3", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-controllable-state": "1.2.2", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw=="], "@radix-ui/react-direction": ["@radix-ui/react-direction@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw=="], diff --git a/src/components/asignaturas/detalle/MateriaDetailPage.tsx b/src/components/asignaturas/detalle/AsignaturaDetailPage.tsx similarity index 98% rename from src/components/asignaturas/detalle/MateriaDetailPage.tsx rename to src/components/asignaturas/detalle/AsignaturaDetailPage.tsx index cf9cc10..ceb7179 100644 --- a/src/components/asignaturas/detalle/MateriaDetailPage.tsx +++ b/src/components/asignaturas/detalle/AsignaturaDetailPage.tsx @@ -12,9 +12,13 @@ import { BibliographyItem } from './BibliographyItem' import { ContenidoTematico } from './ContenidoTematico' import { DocumentoSEPTab } from './DocumentoSEPTab' import { HistorialTab } from './HistorialTab' -import { IAMateriaTab } from './IAMateriaTab' +import { IAAsignaturaTab } from './IAAsignaturaTab' -import type { CampoEstructura, IAMessage, IASugerencia } from '@/types/materia' +import type { + CampoEstructura, + IAMessage, + IASugerencia, +} from '@/types/asignatura' import { Badge } from '@/components/ui/badge' import { Button } from '@/components/ui/button' @@ -30,10 +34,10 @@ import { } from '@/components/ui/tooltip' import { useSubject } from '@/data/hooks/useSubjects' import { - mockMateria, + mockAsignatura, mockEstructura, mockDocumentoSep, -} from '@/data/mockMateriaData' +} from '@/data/mockAsignaturaData' export interface BibliografiaEntry { id: string @@ -101,10 +105,10 @@ function EditableHeaderField({ export const Route = createFileRoute( '/planes/$planId/asignaturas/$asignaturaId', )({ - component: MateriaDetailPage, + component: AsignaturaDetailPage, }) -export default function MateriaDetailPage() { +export default function AsignaturaDetailPage() { const routerState = useRouterState() const state = routerState.location.state as any const { asignaturaId } = useParams({ @@ -121,7 +125,7 @@ export default function MateriaDetailPage() { const [campos, setCampos] = useState>([]) const [activeTab, setActiveTab] = useState('datos') - // Dentro de MateriaDetailPage + // Dentro de AsignaturaDetailPage const [headerData, setHeaderData] = useState({ codigo: '', nombre: '', @@ -315,7 +319,7 @@ export default function MateriaDetailPage() { Datos generales Contenido temático Bibliografía - IA de la materia + IA de la asignatura Documento SEP Historial @@ -348,7 +352,7 @@ export default function MateriaDetailPage() { - >(bibliografia) const [isAddDialogOpen, setIsAddDialogOpen] = useState(false) diff --git a/src/components/asignaturas/detalle/DocumentoSEPTab.tsx b/src/components/asignaturas/detalle/DocumentoSEPTab.tsx index e3d2771..0baad29 100644 --- a/src/components/asignaturas/detalle/DocumentoSEPTab.tsx +++ b/src/components/asignaturas/detalle/DocumentoSEPTab.tsx @@ -23,10 +23,10 @@ import { AlertDialogTrigger, } from '@/components/ui/alert-dialog' import type { - DocumentoMateria, - Materia, - MateriaStructure, -} from '@/types/materia' + DocumentoAsignatura, + Asignatura, + AsignaturaStructure, +} from '@/types/asignatura' import { cn } from '@/lib/utils' import { useSubjectBibliografia } from '@/data/hooks/useSubjects' //import { toast } from 'sonner'; @@ -34,9 +34,9 @@ import { useSubjectBibliografia } from '@/data/hooks/useSubjects' //import { es } from 'date-fns/locale'; interface DocumentoSEPTabProps { - documento: DocumentoMateria | null - materia: Materia - estructura: MateriaStructure + documento: DocumentoAsignatura | null + asignatura: Asignatura + estructura: AsignaturaStructure datosGenerales: Record onRegenerate: () => void isRegenerating: boolean @@ -44,7 +44,7 @@ interface DocumentoSEPTabProps { export function DocumentoSEPTab({ documento, - materia, + asignatura, estructura, datosGenerales, onRegenerate, @@ -112,7 +112,7 @@ export function DocumentoSEPTab({ ¿Regenerar documento SEP? Se creará una nueva versión del documento con los datos - actuales de la materia. La versión anterior quedará en el + actuales de la asignatura. La versión anterior quedará en el historial. @@ -139,7 +139,7 @@ export function DocumentoSEPTab({
- Programa de Estudios - {materia.clave} + Programa de Estudios - {asignatura.clave}
Versión {documento.version} @@ -155,28 +155,29 @@ export function DocumentoSEPTab({ Secretaría de Educación Pública

- {materia.nombre} + {asignatura.nombre}

- Clave: {materia.clave} | Créditos:{' '} - {materia.creditos || 'N/A'} + Clave: {asignatura.clave} | Créditos:{' '} + {asignatura.creditos || 'N/A'}

{/* Datos de la institución */}

- Carrera: {materia.carrera} + Carrera: {asignatura.carrera}

- Facultad: {materia.facultad} + Facultad: {asignatura.facultad}

- Plan de estudios: {materia.planNombre} + Plan de estudios:{' '} + {asignatura.planNombre}

- {materia.ciclo && ( + {asignatura.ciclo && (

- Ciclo: {materia.ciclo} + Ciclo: {asignatura.ciclo}

)}
diff --git a/src/components/asignaturas/detalle/IAMateriaTab.tsx b/src/components/asignaturas/detalle/IAAsignaturaTab.tsx similarity index 96% rename from src/components/asignaturas/detalle/IAMateriaTab.tsx rename to src/components/asignaturas/detalle/IAAsignaturaTab.tsx index 40ce816..fc97699 100644 --- a/src/components/asignaturas/detalle/IAMateriaTab.tsx +++ b/src/components/asignaturas/detalle/IAAsignaturaTab.tsx @@ -13,7 +13,11 @@ import { } from 'lucide-react' import { useState, useEffect, useRef, useMemo } from 'react' -import type { IAMessage, IASugerencia, CampoEstructura } from '@/types/materia' +import type { + IAMessage, + IASugerencia, + CampoEstructura, +} from '@/types/asignatura' import { Avatar, AvatarFallback } from '@/components/ui/avatar' import { Badge } from '@/components/ui/badge' @@ -22,7 +26,7 @@ import { ScrollArea } from '@/components/ui/scroll-area' import { Textarea } from '@/components/ui/textarea' import { cn } from '@/lib/utils' -// Tipos importados de tu archivo de materia +// Tipos importados de tu archivo de asignatura const PRESETS = [ { @@ -35,7 +39,7 @@ const PRESETS = [ id: 'contenido-tematico', label: 'Sugerir contenido', icon: BookOpen, - prompt: 'Genera un desglose de temas para esta materia...', + prompt: 'Genera un desglose de temas para esta asignatura...', }, { id: 'actividades', @@ -57,7 +61,7 @@ interface SelectedField { value: string } -interface IAMateriaTabProps { +interface IAAsignaturaTabProps { campos: Array datosGenerales: Record messages: Array @@ -66,14 +70,14 @@ interface IAMateriaTabProps { onRejectSuggestion: (messageId: string) => void } -export function IAMateriaTab({ +export function IAAsignaturaTab({ campos, datosGenerales, messages, onSendMessage, onAcceptSuggestion, onRejectSuggestion, -}: IAMateriaTabProps) { +}: IAAsignaturaTabProps) { const routerState = useRouterState() // ESTADOS PRINCIPALES (Igual que en Planes) @@ -83,7 +87,7 @@ export function IAMateriaTab({ const [isLoading, setIsLoading] = useState(false) const scrollRef = useRef(null) - // 1. Transformar datos de la materia para el menú + // 1. Transformar datos de la asignatura para el menú const availableFields = useMemo(() => { // Extraemos las claves directamente del objeto datosGenerales // ["nombre", "descripcion", "perfil_de_egreso", "fines_de_aprendizaje_o_formacion"] @@ -105,7 +109,7 @@ export function IAMateriaTab({ }) }, [campos, datosGenerales]) - // 2. Manejar el estado inicial si viene de "Datos de Materia" (Prefill) + // 2. Manejar el estado inicial si viene de "Datos de Asignatura" (Prefill) useEffect(() => { const state = routerState.location.state as any @@ -244,7 +248,7 @@ export function IAMateriaTab({ {msg.content} - {/* Renderizado de Sugerencias (Homologado con lógica de Materia) */} + {/* Renderizado de Sugerencias (Homologado con lógica de Asignatura) */} {msg.sugerencia && !msg.sugerencia.aceptada && (
@@ -302,7 +306,7 @@ export function IAMateriaTab({ {showSuggestions && (
- Seleccionar campo de materia + Seleccionar campo de asignatura
{availableFields.map((field) => ( diff --git a/src/data/api/_helpers.ts b/src/data/api/_helpers.ts index 3d2176f..d5f15f7 100644 --- a/src/data/api/_helpers.ts +++ b/src/data/api/_helpers.ts @@ -1,45 +1,56 @@ -import type { PostgrestError, AuthError, SupabaseClient } from "@supabase/supabase-js"; -import type { Database } from "../types/database"; +import type { Database } from '../types/database' +import type { + PostgrestError, + AuthError, + SupabaseClient, +} from '@supabase/supabase-js' export class ApiError extends Error { constructor( message: string, public readonly code?: string, public readonly details?: unknown, - public readonly hint?: string + public readonly hint?: string, ) { - super(message); - this.name = "ApiError"; + super(message) + this.name = 'ApiError' } } export function throwIfError(error: PostgrestError | AuthError | null): void { - if (!error) return; - - const anyErr = error as any; + if (!error) return + const anyErr = error as any throw new ApiError( - anyErr.message ?? "Error inesperado", + anyErr.message ?? 'Error inesperado', anyErr.code, anyErr.details, - anyErr.hint - ); + anyErr.hint, + ) } -export function requireData(data: T | null | undefined, message = "Respuesta vacía"): T { - if (data === null || data === undefined) throw new ApiError(message); - return data; +export function requireData( + data: T | null | undefined, + message = 'Respuesta vacía', +): T { + if (data === null || data === undefined) throw new ApiError(message) + return data } -export async function getUserIdOrThrow(supabase: SupabaseClient): Promise { - const { data, error } = await supabase.auth.getUser(); - throwIfError(error); - if (!data?.user?.id) throw new ApiError("No hay sesión activa (auth)."); - return data.user.id; +export async function getUserIdOrThrow( + supabase: SupabaseClient, +): Promise { + const { data, error } = await supabase.auth.getUser() + throwIfError(error) + if (!data?.user?.id) throw new ApiError('No hay sesión activa (auth).') + return data.user.id } -export function buildRange(limit?: number, offset?: number): { from?: number; to?: number } { - if (!limit) return {}; - const from = Math.max(0, offset ?? 0); - const to = from + Math.max(1, limit) - 1; - return { from, to }; +export function buildRange( + limit?: number, + offset?: number, +): { from?: number; to?: number } { + if (!limit) return {} + const from = Math.max(0, offset ?? 0) + const to = from + Math.max(1, limit) - 1 + return { from, to } } diff --git a/src/data/api/openaiFiles.api.ts b/src/data/api/openaiFiles.api.ts index ad7b9e8..385914e 100644 --- a/src/data/api/openaiFiles.api.ts +++ b/src/data/api/openaiFiles.api.ts @@ -1,32 +1,32 @@ -import { invokeEdge } from "../supabase/invokeEdge"; -import type { UUID } from "../types/domain"; +import { invokeEdge } from '../supabase/invokeEdge' +import type { UUID } from '../types/domain' /** * Metadata “canónica” para UI (archivo OpenAI + espejo en Supabase) * Se apoya en tu tabla `archivos`. */ export type AppFile = { - id: UUID; // id interno (tabla archivos) - openai_file_id: string; // id OpenAI - nombre: string; - mime_type: string | null; - bytes: number | null; + id: UUID // id interno (tabla archivos) + openai_file_id: string // id OpenAI + nombre: string + mime_type: string | null + bytes: number | null // espejo Supabase para preview/descarga - ruta_storage: string | null; // "bucket/path" - signed_url?: string | null; + ruta_storage: string | null // "bucket/path" + signed_url?: string | null // auditoría/evidencia - temporal: boolean; - notas?: string | null; + temporal: boolean + notas?: string | null - subido_en: string; -}; + subido_en: string +} const EDGE = { - upload: "openai_files_upload", - remove: "openai_files_delete", -} as const; + upload: 'openai_files_upload', + remove: 'openai_files_delete', +} as const /** * Sube archivo a OpenAI y (opcional) crea espejo en Storage @@ -37,28 +37,28 @@ export async function openai_files_upload(payload: { * Si tu Edge soporta multipart: manda File/Blob directo. * Si no, manda base64/bytes (según tu implementación). */ - file: File; + file: File - /** “temporal” = evidencia usada para generar plan/materia */ - temporal?: boolean; + /** “temporal” = evidencia usada para generar plan/asignatura */ + temporal?: boolean /** contexto para auditoría */ contexto?: { - planId?: UUID; - asignaturaId?: UUID; - motivo?: "WIZARD_PLAN" | "WIZARD_MATERIA" | "ADHOC"; - }; + planId?: UUID + asignaturaId?: UUID + motivo?: 'WIZARD_PLAN' | 'WIZARD_MATERIA' | 'ADHOC' + } /** si quieres forzar espejo para preview siempre */ - mirrorToSupabase?: boolean; + mirrorToSupabase?: boolean }): Promise { - return invokeEdge(EDGE.upload, payload); + return invokeEdge(EDGE.upload, payload) } export async function openai_files_delete(payload: { - openaiFileId: string; + openaiFileId: string /** si quieres borrar también espejo y registro */ - hardDelete?: boolean; + hardDelete?: boolean }): Promise<{ ok: true }> { - return invokeEdge<{ ok: true }>(EDGE.remove, payload); + return invokeEdge<{ ok: true }>(EDGE.remove, payload) } diff --git a/src/data/api/plans.api.ts b/src/data/api/plans.api.ts index 8a47440..ff40312 100644 --- a/src/data/api/plans.api.ts +++ b/src/data/api/plans.api.ts @@ -333,7 +333,7 @@ export async function plans_import_from_files(payload: { } archivoWordPlanId: UUID archivoMapaExcelId?: UUID | null - archivoMateriasExcelId?: UUID | null + archivoAsignaturasExcelId?: UUID | null }): Promise { return invokeEdge(EDGE.plans_import_from_files, payload) } diff --git a/src/data/api/subjects.api.ts b/src/data/api/subjects.api.ts index 61d0fdc..cfd6a67 100644 --- a/src/data/api/subjects.api.ts +++ b/src/data/api/subjects.api.ts @@ -1,35 +1,35 @@ -import { supabaseBrowser } from "../supabase/client"; -import { invokeEdge } from "../supabase/invokeEdge"; -import { throwIfError, requireData } from "./_helpers"; +import { supabaseBrowser } from '../supabase/client' +import { invokeEdge } from '../supabase/invokeEdge' +import { throwIfError, requireData } from './_helpers' import type { Asignatura, BibliografiaAsignatura, CambioAsignatura, TipoAsignatura, UUID, -} from "../types/domain"; -import type { DocumentoResult } from "./plans.api"; +} from '../types/domain' +import type { DocumentoResult } from './plans.api' const EDGE = { - 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_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_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; + 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 supabase = supabaseBrowser() const { data, error } = await supabase - .from("asignaturas") + .from('asignaturas') .select( ` id,plan_estudio_id,estructura_id,facultad_propietaria_id,codigo,nombre,tipo,creditos,horas_semana,numero_ciclo,linea_plan_id,orden_celda,datos,contenido_tematico,tipo_origen,meta_origen,creado_por,actualizado_por,creado_en,actualizado_en, @@ -38,144 +38,170 @@ export async function subjects_get(subjectId: UUID): Promise { 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(); + .eq('id', subjectId) + .single() - throwIfError(error); - return requireData(data, "Materia no encontrada."); + throwIfError(error) + return requireData(data, 'Asignatura no encontrada.') } -export async function subjects_history(subjectId: UUID): Promise { - const supabase = supabaseBrowser(); +export async function subjects_history( + subjectId: UUID, +): Promise { + const supabase = supabaseBrowser() const { data, error } = await supabase - .from("cambios_asignatura") + .from('cambios_asignatura') .select( - "id,asignatura_id,cambiado_por,cambiado_en,tipo,campo,valor_anterior,valor_nuevo,fuente,interaccion_ia_id" + '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 }); + .eq('asignatura_id', subjectId) + .order('cambiado_en', { ascending: false }) - throwIfError(error); - return data ?? []; + throwIfError(error) + return data ?? [] } -export async function subjects_bibliografia_list(subjectId: UUID): Promise { - const supabase = supabaseBrowser(); +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 }); + .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 ?? []; + throwIfError(error) + return data ?? [] } -/** Wizard: crear materia manual (Edge Function) */ +/** Wizard: crear asignatura manual (Edge Function) */ export type SubjectsCreateManualInput = { - planId: UUID; + planId: UUID datosBasicos: { - nombre: string; - clave?: string; - tipo: TipoAsignatura; - creditos: number; - horasSemana?: number; - estructuraId: UUID; - }; -}; + 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 async function subjects_create_manual( + payload: SubjectsCreateManualInput, +): Promise { + return invokeEdge(EDGE.subjects_create_manual, payload) } export async function ai_generate_subject(payload: { - planId: UUID; + planId: UUID datosBasicos: { - nombre: string; - clave?: string; - tipo: TipoAsignatura; - creditos: number; - horasSemana?: number; - estructuraId: UUID; - }; + nombre: string + clave?: string + tipo: TipoAsignatura + creditos: number + horasSemana?: number + estructuraId: UUID + } iaConfig: { - descripcionEnfoque: string; - notasAdicionales?: string; - archivosExistentesIds?: UUID[]; - repositoriosIds?: UUID[]; - archivosAdhocIds?: UUID[]; - usarMCP?: boolean; - }; + descripcionEnfoque: string + notasAdicionales?: string + archivosExistentesIds?: UUID[] + repositoriosIds?: UUID[] + archivosAdhocIds?: UUID[] + usarMCP?: boolean + } }): Promise { - return invokeEdge(EDGE.ai_generate_subject, payload); + return invokeEdge(EDGE.ai_generate_subject, payload) } -export async function subjects_persist_from_ai(payload: { planId: UUID; jsonMateria: any }): Promise { - return invokeEdge(EDGE.subjects_persist_from_ai, payload); +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: { - materiaOrigenId: UUID; - planDestinoId: UUID; + asignaturaOrigenId: UUID + planDestinoId: UUID overrides?: Partial<{ - nombre: string; - codigo: string; - tipo: TipoAsignatura; - creditos: number; - horas_semana: number; - }>; + nombre: string + codigo: string + tipo: TipoAsignatura + creditos: number + horas_semana: number + }> }): Promise { - return invokeEdge(EDGE.subjects_clone_from_existing, payload); + return invokeEdge(EDGE.subjects_clone_from_existing, payload) } export async function subjects_import_from_file(payload: { - planId: UUID; - archivoWordMateriaId: UUID; - archivosAdicionalesIds?: UUID[]; + planId: UUID + archivoWordAsignaturaId: UUID + archivosAdicionalesIds?: UUID[] }): Promise { - return invokeEdge(EDGE.subjects_import_from_file, payload); + 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; + codigo: string | null + nombre: string + tipo: TipoAsignatura + creditos: number + horas_semana: number | null + numero_ciclo: number | null + linea_plan_id: UUID | null - datos: Record; -}>; + 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_fields( + subjectId: UUID, + patch: SubjectsUpdateFieldsPatch, +): Promise { + return invokeEdge(EDGE.subjects_update_fields, { + subjectId, + patch, + }) } -export async function subjects_update_contenido(subjectId: UUID, unidades: any[]): Promise { - return invokeEdge(EDGE.subjects_update_contenido, { subjectId, unidades }); +export async function subjects_update_contenido( + subjectId: UUID, + unidades: any[], +): 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; -}>; + 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 + entries: BibliografiaUpsertInput, ): Promise<{ ok: true }> { - return invokeEdge<{ ok: true }>(EDGE.subjects_update_bibliografia, { subjectId, entries }); + return invokeEdge<{ ok: true }>(EDGE.subjects_update_bibliografia, { + subjectId, + entries, + }) } -/** Documento SEP materia */ +/** Documento SEP asignatura */ /* export type DocumentoResult = { archivoId: UUID; signedUrl: string; @@ -183,10 +209,18 @@ export async function subjects_update_bibliografia( nombre?: string; }; */ -export async function subjects_generate_document(subjectId: UUID): Promise { - return invokeEdge(EDGE.subjects_generate_document, { subjectId }); +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_document( + subjectId: UUID, +): Promise { + return invokeEdge(EDGE.subjects_get_document, { + subjectId, + }) } diff --git a/src/data/hooks/useSubjects.ts b/src/data/hooks/useSubjects.ts index ebcf4ea..afeedf0 100644 --- a/src/data/hooks/useSubjects.ts +++ b/src/data/hooks/useSubjects.ts @@ -91,7 +91,7 @@ export function usePersistSubjectFromAI() { const qc = useQueryClient() return useMutation({ - mutationFn: (payload: { planId: UUID; jsonMateria: any }) => + mutationFn: (payload: { planId: UUID; jsonAsignatura: any }) => subjects_persist_from_ai(payload), onSuccess: (subject) => { qc.setQueryData(qk.asignatura(subject.id), subject) diff --git a/src/data/mockMateriaData.ts b/src/data/mockAsignaturaData.ts similarity index 54% rename from src/data/mockMateriaData.ts rename to src/data/mockAsignaturaData.ts index aa8e3fb..208d066 100644 --- a/src/data/mockMateriaData.ts +++ b/src/data/mockAsignaturaData.ts @@ -1,14 +1,14 @@ -import type { - Materia, - MateriaStructure, - UnidadTematica, - BibliografiaEntry, - CambioMateria, - DocumentoMateria, - LibraryResource -} from '@/types/materia'; +import type { + Asignatura, + AsignaturaStructure, + UnidadTematica, + BibliografiaEntry, + CambioAsignatura, + DocumentoAsignatura, + LibraryResource, +} from '@/types/asignatura' -export const mockMateria: Materia = { +export const mockAsignatura: Asignatura = { id: '1', nombre: 'Inteligencia Artificial Aplicada', clave: 'IAA-401', @@ -20,9 +20,9 @@ export const mockMateria: Materia = { carrera: 'Ingeniería en Sistemas Computacionales', facultad: 'Facultad de Ingeniería', estructuraId: 'estructura-1', -}; +} -export const mockEstructura: MateriaStructure = { +export const mockEstructura: AsignaturaStructure = { id: 'estructura-1', nombre: 'Plantilla SEP Licenciatura', campos: [ @@ -31,7 +31,7 @@ export const mockEstructura: MateriaStructure = { nombre: 'Objetivo General', tipo: 'texto_largo', obligatorio: true, - descripcion: 'Describe el propósito principal de la materia', + descripcion: 'Describe el propósito principal de la asignatura', placeholder: 'Al finalizar el curso, el estudiante será capaz de...', }, { @@ -46,14 +46,14 @@ export const mockEstructura: MateriaStructure = { nombre: 'Justificación', tipo: 'texto_largo', obligatorio: true, - descripcion: 'Relevancia de la materia en el plan de estudios', + descripcion: 'Relevancia de la asignatura en el plan de estudios', }, { id: 'requisitos', nombre: 'Requisitos / Seriación', tipo: 'texto', obligatorio: false, - descripcion: 'Materias previas requeridas', + descripcion: 'Asignaturas previas requeridas', }, { id: 'estrategias_didacticas', @@ -77,27 +77,49 @@ export const mockEstructura: MateriaStructure = { descripcion: 'Características requeridas del profesor', }, ], -}; +} export const mockDatosGenerales: Record = { - objetivo_general: 'Formar profesionales capaces de diseñar, implementar y evaluar sistemas de inteligencia artificial que resuelvan problemas complejos del mundo real, aplicando principios éticos y metodologías actuales en el campo.', - competencias: '• Diseñar algoritmos de machine learning para clasificación y predicción\n• Implementar redes neuronales profundas para procesamiento de imágenes y texto\n• Evaluar y optimizar modelos de IA considerando métricas de rendimiento\n• Aplicar principios éticos en el desarrollo de sistemas inteligentes', - justificacion: 'La inteligencia artificial es una de las tecnologías más disruptivas del siglo XXI. Su integración en diversos sectores demanda profesionales con sólidas bases teóricas y prácticas. Esta materia proporciona las competencias necesarias para que el egresado pueda innovar y contribuir al desarrollo tecnológico del país.', - requisitos: 'Programación Avanzada (PAV-301), Matemáticas Discretas (MAT-201)', - estrategias_didacticas: '• Aprendizaje basado en proyectos\n• Talleres prácticos con datasets reales\n• Exposiciones y discusiones grupales\n• Análisis de casos de estudio\n• Desarrollo de prototipo integrador', - evaluacion: '• Exámenes parciales: 30%\n• Proyecto integrador: 35%\n• Prácticas de laboratorio: 20%\n• Participación y tareas: 15%', - perfil_docente: 'Profesional con maestría o doctorado en áreas afines a la inteligencia artificial, con experiencia mínima de 3 años en docencia y desarrollo de proyectos de IA.', -}; + objetivo_general: + 'Formar profesionales capaces de diseñar, implementar y evaluar sistemas de inteligencia artificial que resuelvan problemas complejos del mundo real, aplicando principios éticos y metodologías actuales en el campo.', + competencias: + '• Diseñar algoritmos de machine learning para clasificación y predicción\n• Implementar redes neuronales profundas para procesamiento de imágenes y texto\n• Evaluar y optimizar modelos de IA considerando métricas de rendimiento\n• Aplicar principios éticos en el desarrollo de sistemas inteligentes', + justificacion: + 'La inteligencia artificial es una de las tecnologías más disruptivas del siglo XXI. Su integración en diversos sectores demanda profesionales con sólidas bases teóricas y prácticas. Esta asignatura proporciona las competencias necesarias para que el egresado pueda innovar y contribuir al desarrollo tecnológico del país.', + requisitos: + 'Programación Avanzada (PAV-301), Matemáticas Discretas (MAT-201)', + estrategias_didacticas: + '• Aprendizaje basado en proyectos\n• Talleres prácticos con datasets reales\n• Exposiciones y discusiones grupales\n• Análisis de casos de estudio\n• Desarrollo de prototipo integrador', + evaluacion: + '• Exámenes parciales: 30%\n• Proyecto integrador: 35%\n• Prácticas de laboratorio: 20%\n• Participación y tareas: 15%', + perfil_docente: + 'Profesional con maestría o doctorado en áreas afines a la inteligencia artificial, con experiencia mínima de 3 años en docencia y desarrollo de proyectos de IA.', +} -export const mockContenidoTematico: UnidadTematica[] = [ +export const mockContenidoTematico: Array = [ { id: 'unidad-1', nombre: 'Fundamentos de Inteligencia Artificial', numero: 1, temas: [ - { id: 'tema-1-1', nombre: 'Historia y evolución de la IA', descripcion: 'Desde los orígenes hasta la actualidad', horasEstimadas: 2 }, - { id: 'tema-1-2', nombre: 'Tipos de IA y aplicaciones', descripcion: 'IA débil, fuerte y superinteligencia', horasEstimadas: 3 }, - { id: 'tema-1-3', nombre: 'Ética en IA', descripcion: 'Consideraciones éticas y responsabilidad', horasEstimadas: 2 }, + { + id: 'tema-1-1', + nombre: 'Historia y evolución de la IA', + descripcion: 'Desde los orígenes hasta la actualidad', + horasEstimadas: 2, + }, + { + id: 'tema-1-2', + nombre: 'Tipos de IA y aplicaciones', + descripcion: 'IA débil, fuerte y superinteligencia', + horasEstimadas: 3, + }, + { + id: 'tema-1-3', + nombre: 'Ética en IA', + descripcion: 'Consideraciones éticas y responsabilidad', + horasEstimadas: 2, + }, ], }, { @@ -105,9 +127,24 @@ export const mockContenidoTematico: UnidadTematica[] = [ nombre: 'Machine Learning', numero: 2, temas: [ - { id: 'tema-2-1', nombre: 'Aprendizaje supervisado', descripcion: 'Regresión y clasificación', horasEstimadas: 6 }, - { id: 'tema-2-2', nombre: 'Aprendizaje no supervisado', descripcion: 'Clustering y reducción de dimensionalidad', horasEstimadas: 5 }, - { id: 'tema-2-3', nombre: 'Evaluación de modelos', descripcion: 'Métricas y validación cruzada', horasEstimadas: 4 }, + { + id: 'tema-2-1', + nombre: 'Aprendizaje supervisado', + descripcion: 'Regresión y clasificación', + horasEstimadas: 6, + }, + { + id: 'tema-2-2', + nombre: 'Aprendizaje no supervisado', + descripcion: 'Clustering y reducción de dimensionalidad', + horasEstimadas: 5, + }, + { + id: 'tema-2-3', + nombre: 'Evaluación de modelos', + descripcion: 'Métricas y validación cruzada', + horasEstimadas: 4, + }, ], }, { @@ -115,10 +152,30 @@ export const mockContenidoTematico: UnidadTematica[] = [ nombre: 'Deep Learning', numero: 3, temas: [ - { id: 'tema-3-1', nombre: 'Redes neuronales artificiales', descripcion: 'Perceptrón y backpropagation', horasEstimadas: 5 }, - { id: 'tema-3-2', nombre: 'Redes convolucionales (CNN)', descripcion: 'Procesamiento de imágenes', horasEstimadas: 6 }, - { id: 'tema-3-3', nombre: 'Redes recurrentes (RNN)', descripcion: 'Procesamiento de secuencias', horasEstimadas: 5 }, - { id: 'tema-3-4', nombre: 'Transformers y atención', descripcion: 'Arquitecturas modernas', horasEstimadas: 6 }, + { + id: 'tema-3-1', + nombre: 'Redes neuronales artificiales', + descripcion: 'Perceptrón y backpropagation', + horasEstimadas: 5, + }, + { + id: 'tema-3-2', + nombre: 'Redes convolucionales (CNN)', + descripcion: 'Procesamiento de imágenes', + horasEstimadas: 6, + }, + { + id: 'tema-3-3', + nombre: 'Redes recurrentes (RNN)', + descripcion: 'Procesamiento de secuencias', + horasEstimadas: 5, + }, + { + id: 'tema-3-4', + nombre: 'Transformers y atención', + descripcion: 'Arquitecturas modernas', + horasEstimadas: 6, + }, ], }, { @@ -126,14 +183,29 @@ export const mockContenidoTematico: UnidadTematica[] = [ nombre: 'Aplicaciones Prácticas', numero: 4, temas: [ - { id: 'tema-4-1', nombre: 'Procesamiento de lenguaje natural', descripcion: 'NLP y chatbots', horasEstimadas: 6 }, - { id: 'tema-4-2', nombre: 'Visión por computadora', descripcion: 'Detección y reconocimiento', horasEstimadas: 5 }, - { id: 'tema-4-3', nombre: 'Sistemas de recomendación', descripcion: 'Filtrado colaborativo y contenido', horasEstimadas: 4 }, + { + id: 'tema-4-1', + nombre: 'Procesamiento de lenguaje natural', + descripcion: 'NLP y chatbots', + horasEstimadas: 6, + }, + { + id: 'tema-4-2', + nombre: 'Visión por computadora', + descripcion: 'Detección y reconocimiento', + horasEstimadas: 5, + }, + { + id: 'tema-4-3', + nombre: 'Sistemas de recomendación', + descripcion: 'Filtrado colaborativo y contenido', + horasEstimadas: 4, + }, ], }, -]; +] -export const mockBibliografia: BibliografiaEntry[] = [ +export const mockBibliografia: Array = [ { id: 'bib-1', tipo: 'BASICA', @@ -153,13 +225,14 @@ export const mockBibliografia: BibliografiaEntry[] = [ { id: 'bib-2', tipo: 'BASICA', - cita: 'Géron, A. (2022). Hands-On Machine Learning with Scikit-Learn, Keras, and TensorFlow (3rd ed.). O\'Reilly Media.', + cita: "Géron, A. (2022). Hands-On Machine Learning with Scikit-Learn, Keras, and TensorFlow (3rd ed.). O'Reilly Media.", fuenteBibliotecaId: 'lib-2', fuenteBiblioteca: { id: 'lib-2', - titulo: 'Hands-On Machine Learning with Scikit-Learn, Keras, and TensorFlow', + titulo: + 'Hands-On Machine Learning with Scikit-Learn, Keras, and TensorFlow', autor: 'Aurélien Géron', - editorial: 'O\'Reilly Media', + editorial: "O'Reilly Media", anio: 2022, isbn: '978-1098125974', tipo: 'libro', @@ -187,9 +260,9 @@ export const mockBibliografia: BibliografiaEntry[] = [ disponible: false, }, }, -]; +] -export const mockHistorial: CambioMateria[] = [ +export const mockHistorial: Array = [ { id: 'cambio-1', tipo: 'datos', @@ -228,17 +301,17 @@ export const mockHistorial: CambioMateria[] = [ usuario: 'Sistema', fecha: new Date('2024-12-06T11:30:00'), }, -]; +] -export const mockDocumentoSep: DocumentoMateria = { +export const mockDocumentoSep: DocumentoAsignatura = { id: 'doc-1', - materiaId: '1', + asignaturaId: '1', version: 3, fechaGeneracion: new Date('2024-12-06T11:30:00'), estado: 'listo', -}; +} -export const mockLibraryResources: LibraryResource[] = [ +export const mockLibraryResources: Array = [ { id: 'lib-1', titulo: 'Artificial Intelligence: A Modern Approach', @@ -251,9 +324,10 @@ export const mockLibraryResources: LibraryResource[] = [ }, { id: 'lib-2', - titulo: 'Hands-On Machine Learning with Scikit-Learn, Keras, and TensorFlow', + titulo: + 'Hands-On Machine Learning with Scikit-Learn, Keras, and TensorFlow', autor: 'Aurélien Géron', - editorial: 'O\'Reilly Media', + editorial: "O'Reilly Media", anio: 2022, isbn: '978-1098125974', tipo: 'libro', @@ -299,4 +373,4 @@ export const mockLibraryResources: LibraryResource[] = [ tipo: 'libro', disponible: true, }, -]; +] diff --git a/src/features/planes/nuevo/catalogs.ts b/src/features/planes/nuevo/catalogs.ts index afe98be..4741794 100644 --- a/src/features/planes/nuevo/catalogs.ts +++ b/src/features/planes/nuevo/catalogs.ts @@ -1,156 +1,156 @@ -import type { NivelPlanEstudio, TipoCiclo } from "@/data/types/domain"; +import type { NivelPlanEstudio, TipoCiclo } from '@/data/types/domain' export const FACULTADES = [ - { id: "ing", nombre: "Facultad de Ingeniería" }, + { id: 'ing', nombre: 'Facultad de Ingeniería' }, { - id: "med", - nombre: "Facultad de Medicina en medicina en medicina en medicina", + id: 'med', + nombre: 'Facultad de Medicina en medicina en medicina en medicina', }, - { id: "neg", nombre: "Facultad de Negocios" }, -]; + { id: 'neg', nombre: 'Facultad de Negocios' }, +] export const CARRERAS = [ - { id: "sis", nombre: "Ing. en Sistemas", facultadId: "ing" }, - { id: "ind", nombre: "Ing. Industrial", facultadId: "ing" }, - { id: "medico", nombre: "Médico Cirujano", facultadId: "med" }, - { id: "act", nombre: "Actuaría", facultadId: "neg" }, -]; + { id: 'sis', nombre: 'Ing. en Sistemas', facultadId: 'ing' }, + { id: 'ind', nombre: 'Ing. Industrial', facultadId: 'ing' }, + { id: 'medico', nombre: 'Médico Cirujano', facultadId: 'med' }, + { id: 'act', nombre: 'Actuaría', facultadId: 'neg' }, +] export const NIVELES: Array = [ - "Licenciatura", - "Maestría", - "Doctorado", - "Especialidad", - "Diplomado", - "Otro", -]; + 'Licenciatura', + 'Maestría', + 'Doctorado', + 'Especialidad', + 'Diplomado', + 'Otro', +] export const TIPOS_CICLO: Array = [ - "Semestre", - "Cuatrimestre", - "Trimestre", - "Otro", -]; + 'Semestre', + 'Cuatrimestre', + 'Trimestre', + 'Otro', +] export const PLANES_EXISTENTES = [ { - id: "plan-2021-sis", - nombre: "ISC 2021", - estado: "Aprobado", + id: 'plan-2021-sis', + nombre: 'ISC 2021', + estado: 'Aprobado', anio: 2021, - facultadId: "ing", - carreraId: "sis", + facultadId: 'ing', + carreraId: 'sis', }, { - id: "plan-2020-ind", - nombre: "I. Industrial 2020", - estado: "Aprobado", + id: 'plan-2020-ind', + nombre: 'I. Industrial 2020', + estado: 'Aprobado', anio: 2020, - facultadId: "ing", - carreraId: "ind", + facultadId: 'ing', + carreraId: 'ind', }, { - id: "plan-2019-med", - nombre: "Medicina 2019", - estado: "Vigente", + id: 'plan-2019-med', + nombre: 'Medicina 2019', + estado: 'Vigente', anio: 2019, - facultadId: "med", - carreraId: "medico", + facultadId: 'med', + carreraId: 'medico', }, -]; +] export const ARCHIVOS = [ { - id: "file-1", - nombre: "Sílabo POO 2023.docx", - tipo: "docx", - tamaño: "245 KB", + id: 'file-1', + nombre: 'Sílabo POO 2023.docx', + tipo: 'docx', + tamaño: '245 KB', }, { - id: "file-2", - nombre: "Guía de prácticas BD.pdf", - tipo: "pdf", - tamaño: "1.2 MB", + id: 'file-2', + nombre: 'Guía de prácticas BD.pdf', + tipo: 'pdf', + tamaño: '1.2 MB', }, { - id: "file-3", - nombre: "Rúbrica evaluación proyectos.xlsx", - tipo: "xlsx", - tamaño: "89 KB", + id: 'file-3', + nombre: 'Rúbrica evaluación proyectos.xlsx', + tipo: 'xlsx', + tamaño: '89 KB', }, { - id: "file-4", - nombre: "Banco de reactivos IA.docx", - tipo: "docx", - tamaño: "567 KB", + id: 'file-4', + nombre: 'Banco de reactivos IA.docx', + tipo: 'docx', + tamaño: '567 KB', }, { - id: "file-5", - nombre: "Material didáctico Web.pdf", - tipo: "pdf", - tamaño: "3.4 MB", + id: 'file-5', + nombre: 'Asignatural didáctico Web.pdf', + tipo: 'pdf', + tamaño: '3.4 MB', }, -]; +] export const REPOSITORIOS = [ { - id: "repo-1", - nombre: "Materiales ISC 2024", - descripcion: "Documentos de referencia para Ingeniería en Sistemas", + id: 'repo-1', + nombre: 'Asignaturales ISC 2024', + descripcion: 'Documentos de referencia para Ingeniería en Sistemas', cantidadArchivos: 45, }, { - id: "repo-2", - nombre: "Lineamientos SEP", - descripcion: "Documentos oficiales y normativas SEP actualizadas", + id: 'repo-2', + nombre: 'Lineamientos SEP', + descripcion: 'Documentos oficiales y normativas SEP actualizadas', cantidadArchivos: 12, }, { - id: "repo-3", - nombre: "Bibliografía Digital", - descripcion: "Recursos bibliográficos digitalizados", + id: 'repo-3', + nombre: 'Bibliografía Digital', + descripcion: 'Recursos bibliográficos digitalizados', cantidadArchivos: 128, }, { - id: "repo-4", - nombre: "Plantillas Institucionales", - descripcion: "Formatos y plantillas oficiales ULSA", + id: 'repo-4', + nombre: 'Plantillas Institucionales', + descripcion: 'Formatos y plantillas oficiales ULSA', cantidadArchivos: 23, }, -]; +] export const PLANTILLAS_ANEXO_1 = [ { - id: "sep-2025", - name: "Licenciatura RVOE SEP.docx", - versions: ["v2025.2 (Vigente)", "v2025.1", "v2024.Final"], + id: 'sep-2025', + name: 'Licenciatura RVOE SEP.docx', + versions: ['v2025.2 (Vigente)', 'v2025.1', 'v2024.Final'], }, { - id: "interno-mix", - name: "Estándar Institucional Mixto.docx", - versions: ["v2.0", "v1.5", "v1.0-beta"], + id: 'interno-mix', + name: 'Estándar Institucional Mixto.docx', + versions: ['v2.0', 'v1.5', 'v1.0-beta'], }, { - id: "conacyt", - name: "Formato Posgrado CONAHCYT.docx", - versions: ["v3.0 (2025)", "v2.8"], + id: 'conacyt', + name: 'Formato Posgrado CONAHCYT.docx', + versions: ['v3.0 (2025)', 'v2.8'], }, -]; +] export const PLANTILLAS_ANEXO_2 = [ { - id: "sep-2017-xlsx", - name: "Licenciatura RVOE 2017.xlsx", - versions: ["v2017.0", "v2018.1", "v2019.2", "v2020.Final"], + id: 'sep-2017-xlsx', + name: 'Licenciatura RVOE 2017.xlsx', + versions: ['v2017.0', 'v2018.1', 'v2019.2', 'v2020.Final'], }, { - id: "interno-mix-xlsx", - name: "Estándar Institucional Mixto.xlsx", - versions: ["v1.0", "v1.5"], + id: 'interno-mix-xlsx', + name: 'Estándar Institucional Mixto.xlsx', + versions: ['v1.0', 'v1.5'], }, { - id: "conacyt-xlsx", - name: "Formato Posgrado CONAHCYT.xlsx", - versions: ["v1.0", "v2.0"], + id: 'conacyt-xlsx', + name: 'Formato Posgrado CONAHCYT.xlsx', + versions: ['v1.0', 'v2.0'], }, -]; +] diff --git a/src/routes/planes/$planId/_detalle/MateriaCard.tsx b/src/routes/planes/$planId/_detalle/MateriaCard.tsx deleted file mode 100644 index 403c7c9..0000000 --- a/src/routes/planes/$planId/_detalle/MateriaCard.tsx +++ /dev/null @@ -1,137 +0,0 @@ -import * as Dialog from '@radix-ui/react-dialog'; -import { Pencil, X } from 'lucide-react'; -export type Materia = { - id: string; - clave: string; - nombre: string; - creditos: number; - hd: number; // Horas Docente - hi: number; // Horas Independientes - tipo: 'Obligatoria' | 'Optativa' | 'Especialidad'; - ciclo: number; - linea: string; - estado: string; -}; - -interface MateriaCardProps { - materia: Materia; -} - -export function MateriaCard({ materia }: MateriaCardProps) { - return ( - - {/* Trigger: La tarjeta en sí misma */} - -
- {/* Header de la tarjeta */} -
- {materia.clave} -
- - {materia.tipo === 'Obligatoria' ? 'OB' : 'OP'} - -
-
- - {/* Nombre */} -

- {materia.nombre} -

- - {/* Footer de la tarjeta (Créditos y Horas) */} -
- {materia.creditos} cr -
- HD:{materia.hd} - HI:{materia.hi} -
-
- - {/* Overlay de Hover (Opcional: un iconito de editar) */} -
- -
-
-
- - {/* Modal / Portal */} - - - - -
- Editar Materia - - - -
- -
- {/* Clave y Nombre */} -
-
- - -
-
- - -
-
- - {/* Créditos y Horas */} -
-
- - -
-
- - -
-
- - -
-
- - {/* Ciclo y Línea */} -
-
- - -
-
- - -
-
- - {/* Botones de acción */} -
- - Cancelar - - -
-
-
-
-
- ); -} \ No newline at end of file diff --git a/src/routes/planes/$planId/_detalle/asignaturas.tsx b/src/routes/planes/$planId/_detalle/asignaturas.tsx index 735bc5e..0128351 100644 --- a/src/routes/planes/$planId/_detalle/asignaturas.tsx +++ b/src/routes/planes/$planId/_detalle/asignaturas.tsx @@ -10,7 +10,7 @@ import { } from 'lucide-react' import { useState, useMemo } from 'react' -import type { Materia } from '@/types/plan' +import type { Asignatura } from '@/types/plan' import { Badge } from '@/components/ui/badge' import { Button } from '@/components/ui/button' @@ -46,7 +46,7 @@ const tipoConfig: Record = { } // --- Mapeadores de API --- -const mapAsignaturas = (asigApi: Array = []): Array => { +const mapAsignaturas = (asigApi: Array = []): Array => { return asigApi.map((asig) => ({ id: asig.id, clave: asig.codigo, @@ -63,10 +63,10 @@ const mapAsignaturas = (asigApi: Array = []): Array => { } export const Route = createFileRoute('/planes/$planId/_detalle/asignaturas')({ - component: MateriasPage, + component: AsignaturasPage, }) -function MateriasPage() { +function AsignaturasPage() { const { planId } = Route.useParams() const navigate = useNavigate() @@ -82,13 +82,13 @@ function MateriasPage() { const [filterLinea, setFilterLinea] = useState('all') // 3. Procesamiento de datos - const materias = useMemo( + const asignaturas = useMemo( () => mapAsignaturas(asignaturasApi), [asignaturasApi], ) const lineas = useMemo(() => lineasApi || [], [lineasApi]) - const filteredMaterias = materias.filter((m) => { + const filteredAsignaturas = asignaturas.filter((m) => { const matchesSearch = m.nombre.toLowerCase().includes(searchTerm.toLowerCase()) || m.clave.toLowerCase().includes(searchTerm.toLowerCase()) @@ -119,11 +119,11 @@ function MateriasPage() {

- Materias del Plan + Asignaturas del Plan

- {materias.length} materias en total • {filteredMaterias.length}{' '} - filtradas + {asignaturas.length} asignaturas en total •{' '} + {filteredAsignaturas.length} filtradas

@@ -132,7 +132,7 @@ function MateriasPage() { Clonar
@@ -207,12 +207,12 @@ function MateriasPage() { - {filteredMaterias.length === 0 ? ( + {filteredAsignaturas.length === 0 ? (
-

No se encontraron materias

+

No se encontraron asignaturas

Intenta cambiar los filtros de búsqueda

@@ -220,59 +220,59 @@ function MateriasPage() { ) : ( - filteredMaterias.map((materia) => ( + filteredAsignaturas.map((asignatura) => ( navigate({ to: '/planes/$planId/asignaturas/$asignaturaId', params: { planId, - asignaturaId: materia.id, // 👈 puede ser índice, consecutivo o slug + asignaturaId: asignatura.id, // 👈 puede ser índice, consecutivo o slug }, state: { - realId: materia.id, // 👈 ID largo oculto - asignaturaId: materia.id, + realId: asignatura.id, // 👈 ID largo oculto + asignaturaId: asignatura.id, } as any, }) } > - {materia.clave} + {asignatura.clave} - {materia.nombre} + {asignatura.nombre} - {materia.creditos} + {asignatura.creditos} - {materia.ciclo ? ( + {asignatura.ciclo ? ( - Ciclo {materia.ciclo} + Ciclo {asignatura.ciclo} ) : ( )} - {getLineaNombre(materia.lineaCurricularId)} + {getLineaNombre(asignatura.lineaCurricularId)} - {tipoConfig[materia.tipo]?.label} + {tipoConfig[asignatura.tipo]?.label} - {statusConfig[materia.estado]?.label} + {statusConfig[asignatura.estado]?.label} diff --git a/src/routes/planes/$planId/_detalle/mapa.tsx b/src/routes/planes/$planId/_detalle/mapa.tsx index 218b4ec..47adae2 100644 --- a/src/routes/planes/$planId/_detalle/mapa.tsx +++ b/src/routes/planes/$planId/_detalle/mapa.tsx @@ -9,7 +9,7 @@ import { } from 'lucide-react' import { useMemo, useState, useEffect } from 'react' -import type { Materia, LineaCurricular } from '@/types/plan' +import type { Asignatura, LineaCurricular } from '@/types/plan' import { Badge } from '@/components/ui/badge' import { Button } from '@/components/ui/button' @@ -47,7 +47,9 @@ const mapLineasToLineaCurricular = ( })) } -const mapAsignaturasToMaterias = (asigApi: Array = []): Array => { +const mapAsignaturasToAsignaturas = ( + asigApi: Array = [], +): Array => { return asigApi.map((asig) => ({ id: asig.id, clave: asig.codigo, @@ -104,13 +106,13 @@ function StatItem({ ) } -function MateriaCardItem({ - materia, +function AsignaturaCardItem({ + asignatura, onDragStart, isDragging, onClick, }: { - materia: Materia + asignatura: Asignatura onDragStart: (e: React.DragEvent, id: string) => void isDragging: boolean onClick: () => void @@ -118,7 +120,7 @@ function MateriaCardItem({ return (