From 675c76db74fec463b27f033e1c8e733231294e12 Mon Sep 17 00:00:00 2001 From: Guillermo Arrieta Medina Date: Tue, 10 Feb 2026 13:45:21 -0600 Subject: [PATCH] wip --- .../PasoBasicosForm/PasoSugerenciasForm.tsx | 130 +++++------------- src/data/api/subjects.api.ts | 10 ++ src/data/query/keys.ts | 1 + .../nueva/hooks/useNuevaAsignaturaWizard.ts | 5 +- src/features/asignaturas/nueva/types.ts | 24 +++- 5 files changed, 70 insertions(+), 100 deletions(-) diff --git a/src/components/asignaturas/wizard/PasoBasicosForm/PasoSugerenciasForm.tsx b/src/components/asignaturas/wizard/PasoBasicosForm/PasoSugerenciasForm.tsx index fcbc79f..c857844 100644 --- a/src/components/asignaturas/wizard/PasoBasicosForm/PasoSugerenciasForm.tsx +++ b/src/components/asignaturas/wizard/PasoBasicosForm/PasoSugerenciasForm.tsx @@ -1,88 +1,19 @@ import { RefreshCw, Sparkles } from 'lucide-react' +import { useState } from 'react' -import type { NewSubjectWizardState } from '@/features/asignaturas/nueva/types' +import type { + AsignaturaSugerida, + NewSubjectWizardState, +} from '@/features/asignaturas/nueva/types' import type { Dispatch, SetStateAction } from 'react' import { Button } from '@/components/ui/button' import { Checkbox } from '@/components/ui/checkbox' import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' +import { usePlan } from '@/data' import { cn } from '@/lib/utils' -// Interfaces -interface Suggestion { - id: string - nombre: string - tipo: 'Obligatoria' | 'Optativa' - creditos: number - horasAcademicas: number - horasIndependientes: number - descripcion: string -} - -// Datos Mock basados en tu imagen -const MOCK_SUGGESTIONS: Array = [ - { - id: '1', - nombre: 'Propiedad Intelectual en Entornos Digitales', - tipo: 'Optativa', - creditos: 4, - horasAcademicas: 32, - horasIndependientes: 16, - descripcion: - 'Derechos de autor, patentes de software y marcas en el ecosistema digital.', - }, - { - id: '2', - nombre: 'Derecho Constitucional Digital', - tipo: 'Obligatoria', - creditos: 8, - horasAcademicas: 64, - horasIndependientes: 32, - descripcion: - 'Marco constitucional aplicado al entorno digital y derechos fundamentales en línea.', - }, - { - id: '3', - nombre: 'Gobernanza de Internet', - tipo: 'Optativa', - creditos: 4, - horasAcademicas: 32, - horasIndependientes: 16, - descripcion: - 'Políticas públicas, regulación internacional y gobernanza del ecosistema digital.', - }, - { - id: '4', - nombre: 'Protección de Datos Personales', - tipo: 'Obligatoria', - creditos: 6, - horasAcademicas: 48, - horasIndependientes: 24, - descripcion: - 'Regulación y cumplimiento de leyes de protección de datos (GDPR, LFPDPPP).', - }, - { - id: '5', - nombre: 'Inteligencia Artificial y Ética Jurídica', - tipo: 'Optativa', - creditos: 4, - horasAcademicas: 32, - horasIndependientes: 16, - descripcion: - 'Implicaciones legales y éticas del uso de IA en la práctica jurídica.', - }, - { - id: '6', - nombre: 'Ciberseguridad y Derecho Penal', - tipo: 'Obligatoria', - creditos: 6, - horasAcademicas: 48, - horasIndependientes: 24, - descripcion: - 'Delitos informáticos, evidencia digital y marco penal en el ciberespacio.', - }, -] export default function PasoSugerenciasForm({ wizard, onChange, @@ -90,22 +21,28 @@ export default function PasoSugerenciasForm({ wizard: NewSubjectWizardState onChange: Dispatch> }) { - const selectedIds = wizard.iaMultiple?.selectedIds ?? [] + const selectedIds = wizard.sugerencias + .filter((s) => s.selected) + .map((s) => s.id) const ciclo = wizard.iaMultiple?.ciclo ?? '' const enfoque = wizard.iaMultiple?.enfoque ?? '' + const [suggestions, setSuggestions] = useState>([]) const setIaMultiple = ( patch: Partial>, ) => - onChange((w) => ({ - ...w, - iaMultiple: { - ciclo: w.iaMultiple?.ciclo ?? '', - enfoque: w.iaMultiple?.enfoque ?? '', - selectedIds: w.iaMultiple?.selectedIds ?? [], - ...patch, - }, - })) + onChange( + (w): NewSubjectWizardState => ({ + ...w, + iaMultiple: { + ciclo: w.iaMultiple?.ciclo ?? null, + enfoque: w.iaMultiple?.enfoque ?? '', + ...patch, + }, + }), + ) + + const { data: plan } = usePlan(wizard.plan_estudio_id) const toggleAsignatura = (id: string, checked: boolean) => { const prev = selectedIds @@ -133,7 +70,8 @@ export default function PasoSugerenciasForm({ setIaMultiple({ ciclo: e.target.value })} + type="number" + onChange={(e) => setIaMultiple({ ciclo: Number(e.target.value) })} /> @@ -152,7 +90,7 @@ export default function PasoSugerenciasForm({ {/* Botón Refrescar */} @@ -164,7 +102,8 @@ export default function PasoSugerenciasForm({ Asignaturas sugeridas

- Basadas en el plan "Licenciatura en Derecho Digital" + Basadas en el plan{' '} + {plan ? `${plan.nivel} en ${plan.nombre}` : '...'}

@@ -174,7 +113,7 @@ export default function PasoSugerenciasForm({ {/* --- LISTA DE ASIGNATURAS (CON EL ESTILO PEDIDO) --- */}
- {MOCK_SUGGESTIONS.map((asignatura) => { + {suggestions.map((asignatura) => { const isSelected = selectedIds.includes(asignatura.id) return ( @@ -203,29 +142,30 @@ export default function PasoSugerenciasForm({
- {asignatura.nombre} + {asignatura.data.nombre} {/* Badges de Tipo */} - {asignatura.tipo} + {asignatura.data.tipo} - {asignatura.creditos} cred. · {asignatura.horasAcademicas}h - acad. · {asignatura.horasIndependientes}h indep. + {asignatura.data.creditos} cred. ·{' '} + {asignatura.data.horasAcademicas}h acad. ·{' '} + {asignatura.data.horasIndependientes}h indep.

- {asignatura.descripcion} + {asignatura.data.descripcion}

diff --git a/src/data/api/subjects.api.ts b/src/data/api/subjects.api.ts index acfe531..7e99716 100644 --- a/src/data/api/subjects.api.ts +++ b/src/data/api/subjects.api.ts @@ -15,6 +15,7 @@ import type { UploadedFile } from '@/components/planes/wizard/PasoDetallesPanel/ 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', @@ -133,6 +134,15 @@ export type AIGenerateSubjectInput = { } } +export async function generate_subject_suggestions(): Promise< + Array<{ [key: string]: any }> +> { + return invokeEdge>( + EDGE.generate_subject_suggestions, + {}, + ) +} + export async function ai_generate_subject( input: AIGenerateSubjectInput, ): Promise { diff --git a/src/data/query/keys.ts b/src/data/query/keys.ts index ec61bbd..1ac5890 100644 --- a/src/data/query/keys.ts +++ b/src/data/query/keys.ts @@ -19,6 +19,7 @@ export const qk = { planHistorial: (planId: string) => ['planes', planId, 'historial'] as const, planDocumento: (planId: string) => ['planes', planId, 'documento'] as const, + sugerenciasAsignaturas: () => ['asignaturas', 'sugerencias'] as const, asignatura: (asignaturaId: string) => ['asignaturas', 'detail', asignaturaId] as const, asignaturaBibliografia: (asignaturaId: string) => diff --git a/src/features/asignaturas/nueva/hooks/useNuevaAsignaturaWizard.ts b/src/features/asignaturas/nueva/hooks/useNuevaAsignaturaWizard.ts index 4e281a4..6085d4d 100644 --- a/src/features/asignaturas/nueva/hooks/useNuevaAsignaturaWizard.ts +++ b/src/features/asignaturas/nueva/hooks/useNuevaAsignaturaWizard.ts @@ -16,6 +16,7 @@ export function useNuevaAsignaturaWizard(planId: string) { horasIndependientes: null, estructuraId: '', }, + sugerencias: [], clonInterno: {}, clonTradicional: { archivoWordAsignaturaId: null, @@ -29,9 +30,9 @@ export function useNuevaAsignaturaWizard(planId: string) { archivosAdjuntos: [], }, iaMultiple: { - ciclo: '', + ciclo: null, enfoque: '', - selectedIds: ['1', '3', '6'], + selectedIds: [], }, resumen: {}, isLoading: false, diff --git a/src/features/asignaturas/nueva/types.ts b/src/features/asignaturas/nueva/types.ts index 064b308..48b4be9 100644 --- a/src/features/asignaturas/nueva/types.ts +++ b/src/features/asignaturas/nueva/types.ts @@ -2,7 +2,6 @@ import type { UploadedFile } from '@/components/planes/wizard/PasoDetallesPanel/ import type { Asignatura } from '@/data' export type ModoCreacion = 'MANUAL' | 'IA' | 'CLONADO' -export type SubModoClonado = 'INTERNO' | 'TRADICIONAL' export type TipoAsignatura = 'OBLIGATORIA' | 'OPTATIVA' | 'TRONCAL' | 'OTRO' export type AsignaturaPreview = { @@ -12,6 +11,24 @@ export type AsignaturaPreview = { bibliografiaCount: number } +export type DataAsignaturaSugerida = { + nombre: Asignatura['nombre'] + codigo?: Asignatura['codigo'] + tipo: Asignatura['tipo'] | null + creditos: Asignatura['creditos'] | null + horasAcademicas?: number | null + horasIndependientes?: number | null + estructuraId: Asignatura['estructura_id'] | null + descripcion?: string +} + +export type AsignaturaSugerida = { + id: string + selected: boolean + source: 'IA' | 'MANUAL' | 'CLON' + data: DataAsignaturaSugerida +} + export type NewSubjectWizardState = { step: 1 | 2 | 3 | 4 plan_estudio_id: Asignatura['plan_estudio_id'] @@ -30,6 +47,7 @@ export type NewSubjectWizardState = { horasIndependientes?: Asignatura['horas_independientes'] | null estructuraId: Asignatura['estructura_id'] | null } + sugerencias: Array clonInterno?: { facultadId?: string carreraId?: string @@ -48,9 +66,9 @@ export type NewSubjectWizardState = { archivosAdjuntos?: Array } iaMultiple?: { - ciclo: string + ciclo: number | null enfoque: string - selectedIds: Array + selectedIds?: Array } resumen: { previewAsignatura?: AsignaturaPreview