diff --git a/src/components/planes/wizard/PasoDetallesPanel/ReferenciasParaIA.tsx b/src/components/planes/wizard/PasoDetallesPanel/ReferenciasParaIA.tsx
index c0a7a63..af04fc0 100644
--- a/src/components/planes/wizard/PasoDetallesPanel/ReferenciasParaIA.tsx
+++ b/src/components/planes/wizard/PasoDetallesPanel/ReferenciasParaIA.tsx
@@ -1,4 +1,7 @@
import { FileText, FolderOpen, Upload } from 'lucide-react'
+import { useMemo, useState } from 'react'
+
+import BarraBusqueda from '../../BarraBusqueda'
import { FileDropzone } from './FileDropZone'
@@ -13,93 +16,142 @@ import {
} from '@/components/ui/motion-tabs'
import { ARCHIVOS, REPOSITORIOS } from '@/features/planes/nuevo/catalogs'
-const tabs = [
- {
- name: 'Archivos existentes',
+const ReferenciasParaIA = ({
+ onFilesChange,
+}: {
+ onFilesChange?: (
+ files: Array<{ id: string; name: string; size: string; type: string }>,
+ ) => void
+}) => {
+ const [busquedaArchivos, setBusquedaArchivos] = useState('')
+ const [busquedaRepositorios, setBusquedaRepositorios] = useState('')
- value: 'archivos-existentes',
+ const cleanText = (text: string) => {
+ return text
+ .normalize('NFD') // Descompone "á" en "a" + "´"
+ .replace(/[\u0300-\u036f]/g, '') // Elimina los símbolos diacríticos
+ .toLowerCase() // Convierte a minúsculas
+ }
- icon: FileText,
+ // Filtrado de archivos y de repositorios
+ const archivosFiltrados = useMemo(() => {
+ // Función helper para limpiar texto (quita acentos y hace minúsculas)
- content: (
-
- {ARCHIVOS.map((archivo) => (
-
- ))}
-
- ),
- },
+ value: 'archivos-existentes',
- {
- name: 'Repositorios',
+ icon: FileText,
- value: 'repositorios',
+ content: (
+
+
+
+ {archivosFiltrados.map((archivo) => (
+
-
- ))}
-
- ),
- },
+ {
+ name: 'Repositorios',
- {
- name: 'Subir archivos',
+ value: 'repositorios',
- value: 'subir-archivos',
+ icon: FolderOpen,
- icon: Upload,
+ content: (
+
+
+
+ {repositoriosFiltrados.map((repositorio) => (
+
+
- content: (
-
-
- // handleChange("archivosAdhocIds", files.map((f) => f.id))
- // }
- title="Sube archivos de referencia"
- description="Documentos que serán usados como contexto para la generación"
- />
-
- ),
- },
-]
+
+
+
+ {repositorio.nombre}
+
+
+
+ {repositorio.descripcion} · {repositorio.cantidadArchivos}{' '}
+ archivos
+
+
+
+ ))}
+
+
+ ),
+ },
+
+ {
+ name: 'Subir archivos',
+
+ value: 'subir-archivos',
+
+ icon: Upload,
+
+ content: (
+
+
+
+ ),
+ },
+ ]
-const ReferenciasParaIA = () => {
return (
Referencias para la IA
diff --git a/src/components/planes/wizard/PasoResumenCard.tsx b/src/components/planes/wizard/PasoResumenCard.tsx
index 35e255d..31a4d45 100644
--- a/src/components/planes/wizard/PasoResumenCard.tsx
+++ b/src/components/planes/wizard/PasoResumenCard.tsx
@@ -7,6 +7,10 @@ import {
CardHeader,
CardTitle,
} from '@/components/ui/card'
+import {
+ PLANTILLAS_ANEXO_1,
+ PLANTILLAS_ANEXO_2,
+} from '@/features/planes/nuevo/catalogs'
export function PasoResumenCard({ wizard }: { wizard: NewPlanWizardState }) {
const modo = wizard.modoCreacion
@@ -46,6 +50,35 @@ export function PasoResumenCard({ wizard }: { wizard: NewPlanWizardState }) {
{wizard.datosBasicos.numCiclos} ({wizard.datosBasicos.tipoCiclo})
+ {/* Plantillas seleccionadas */}
+
+ Plantilla plan:
+
+ {(() => {
+ const t = PLANTILLAS_ANEXO_1.find(
+ (x) => x.id === wizard.datosBasicos.plantillaPlanId,
+ )
+ const name =
+ t?.name || wizard.datosBasicos.plantillaPlanId || '—'
+ const ver = wizard.datosBasicos.plantillaPlanVersion || '—'
+ return `${name} · ${ver}`
+ })()}
+
+
+
+ Mapa curricular:
+
+ {(() => {
+ const t = PLANTILLAS_ANEXO_2.find(
+ (x) => x.id === wizard.datosBasicos.plantillaMapaId,
+ )
+ const name =
+ t?.name || wizard.datosBasicos.plantillaMapaId || '—'
+ const ver = wizard.datosBasicos.plantillaMapaVersion || '—'
+ return `${name} · ${ver}`
+ })()}
+
+
Modo:
@@ -59,6 +92,57 @@ export function PasoResumenCard({ wizard }: { wizard: NewPlanWizardState }) {
'Importado desde documentos tradicionales'}
+ {modo === 'IA' && (
+
+
+ Enfoque:
+
+ {wizard.iaConfig?.descripcionEnfoque || '—'}
+
+
+
+ Notas:
+
+ {wizard.iaConfig?.notasAdicionales || '—'}
+
+
+ {!!(wizard.iaConfig?.archivosReferencia?.length || 0) && (
+
+ Archivos existentes:{' '}
+ {wizard.iaConfig?.archivosReferencia?.length}
+
+ )}
+ {!!(wizard.iaConfig?.repositoriosReferencia?.length || 0) && (
+
+ Repositorios:{' '}
+ {wizard.iaConfig?.repositoriosReferencia?.length}
+
+ )}
+ {!!(wizard.iaConfig?.archivosAdjuntos?.length || 0) && (
+
+
Adjuntos
+
+ {wizard.iaConfig?.archivosAdjuntos?.map((f) => (
+ -
+ {f.name}{' '}
+ · {f.size}
+
+ ))}
+
+
+ )}
+
+ )}
+ {modo === 'CLONADO' && sub === 'TRADICIONAL' && (
+
+
+ Archivo Word del plan:{' '}
+
+
+ {wizard.clonTradicional?.archivoWordPlanId?.name || '—'}
+
+
+ )}
{wizard.resumen.previewPlan && (
Preview IA
diff --git a/src/features/planes/nuevo/catalogs.ts b/src/features/planes/nuevo/catalogs.ts
index b9bffa8..805193e 100644
--- a/src/features/planes/nuevo/catalogs.ts
+++ b/src/features/planes/nuevo/catalogs.ts
@@ -115,20 +115,6 @@ export const REPOSITORIOS = [
},
];
-export const ESTRUCTURAS_PLAN_ESTUDIO = [
- {
- id: "estruc-1",
- nombre: "Estructura RVOE 2017.docx",
- versiones: ["v1.0", "v1.1", "v2.0"],
- },
- {
- id: "estruc-2",
- nombre: "Estructura RVOE 2026.docx",
- versiones: ["v1.0", "v1.1"],
- },
- { id: "estruc-3", nombre: "Estructura ULSA 2022.docx", versiones: ["v1.0"] },
-];
-
export const PLANTILLAS_ANEXO_1 = [
{
id: "sep-2025",
diff --git a/src/features/planes/nuevo/hooks/useNuevoPlanWizard.ts b/src/features/planes/nuevo/hooks/useNuevoPlanWizard.ts
index 5bc8ece..8809d74 100644
--- a/src/features/planes/nuevo/hooks/useNuevoPlanWizard.ts
+++ b/src/features/planes/nuevo/hooks/useNuevoPlanWizard.ts
@@ -8,6 +8,18 @@ export function useNuevoPlanWizard() {
const [wizard, setWizard] = useState
({
step: 1,
modoCreacion: null,
+ // datosBasicos: {
+ // nombrePlan: "",
+ // carreraId: "",
+ // facultadId: "",
+ // nivel: "",
+ // tipoCiclo: "",
+ // numCiclos: undefined,
+ // plantillaPlanId: "",
+ // plantillaPlanVersion: "",
+ // plantillaMapaId: "",
+ // plantillaMapaVersion: "",
+ // },
datosBasicos: {
nombrePlan: "Medicina",
carreraId: "medico",
@@ -15,6 +27,10 @@ export function useNuevoPlanWizard() {
nivel: "Licenciatura",
tipoCiclo: "SEMESTRE",
numCiclos: 8,
+ plantillaPlanId: "sep-2025",
+ plantillaPlanVersion: "v2025.2 (Vigente)",
+ plantillaMapaId: "sep-2017-xlsx",
+ plantillaMapaVersion: "v2017.0",
},
clonInterno: { planOrigenId: null },
clonTradicional: {
@@ -27,6 +43,8 @@ export function useNuevoPlanWizard() {
poblacionObjetivo: "",
notasAdicionales: "",
archivosReferencia: [],
+ repositoriosReferencia: [],
+ archivosAdjuntos: [],
},
resumen: {},
isLoading: false,
@@ -47,12 +65,19 @@ export function useNuevoPlanWizard() {
!!wizard.datosBasicos.facultadId &&
!!wizard.datosBasicos.nivel &&
(wizard.datosBasicos.numCiclos !== undefined &&
- wizard.datosBasicos.numCiclos > 0);
+ wizard.datosBasicos.numCiclos > 0) &&
+ // Requerir ambas plantillas (plan y mapa) con versión
+ !!wizard.datosBasicos.plantillaPlanId &&
+ !!wizard.datosBasicos.plantillaPlanVersion &&
+ !!wizard.datosBasicos.plantillaMapaId &&
+ !!wizard.datosBasicos.plantillaMapaVersion;
const canContinueDesdeDetalles = (() => {
if (wizard.modoCreacion === "MANUAL") return true;
if (wizard.modoCreacion === "IA") {
- return !!wizard.iaConfig?.descripcionEnfoque;
+ // Requerimos descripción del enfoque y notas adicionales
+ return !!wizard.iaConfig?.descripcionEnfoque &&
+ !!wizard.iaConfig?.notasAdicionales;
}
if (wizard.modoCreacion === "CLONADO") {
if (wizard.subModoClonado === "INTERNO") {
diff --git a/src/features/planes/nuevo/types.ts b/src/features/planes/nuevo/types.ts
index 49c08b1..fae8153 100644
--- a/src/features/planes/nuevo/types.ts
+++ b/src/features/planes/nuevo/types.ts
@@ -22,11 +22,20 @@ export type NewPlanWizardState = {
nivel: string;
tipoCiclo: TipoCiclo | "";
numCiclos: number | undefined;
+ // Selección de plantillas (obligatorias)
+ plantillaPlanId?: string;
+ plantillaPlanVersion?: string;
+ plantillaMapaId?: string;
+ plantillaMapaVersion?: string;
};
clonInterno?: { planOrigenId: string | null };
clonTradicional?: {
- archivoWordPlanId: string | null;
- archivoMapaExcelId: string | null;
+ archivoWordPlanId:
+ | { id: string; name: string; size: string; type: string }
+ | null;
+ archivoMapaExcelId:
+ | string
+ | null;
archivoAsignaturasExcelId: string | null;
};
iaConfig?: {
@@ -34,6 +43,10 @@ export type NewPlanWizardState = {
poblacionObjetivo: string;
notasAdicionales: string;
archivosReferencia: Array;
+ repositoriosReferencia?: Array;
+ archivosAdjuntos?: Array<
+ { id: string; name: string; size: string; type: string }
+ >;
};
resumen: { previewPlan?: PlanPreview };
isLoading: boolean;
diff --git a/src/routes/planes/_lista/route.tsx b/src/routes/planes/_lista/route.tsx
index 3a21a99..eddb20a 100644
--- a/src/routes/planes/_lista/route.tsx
+++ b/src/routes/planes/_lista/route.tsx
@@ -183,10 +183,19 @@ function RouteComponent() {
// Filtrado de planes
const filteredPlans = useMemo(() => {
- const term = search.trim().toLowerCase()
+ // Función helper para limpiar texto (quita acentos y hace minúsculas)
+ const cleanText = (text: string) => {
+ return text
+ .normalize('NFD') // Descompone "á" en "a" + "´"
+ .replace(/[\u0300-\u036f]/g, '') // Elimina los símbolos diacríticos
+ .toLowerCase() // Convierte a minúsculas
+ }
+ // Limpiamos el término de búsqueda una sola vez antes de filtrar
+ const term = cleanText(search.trim())
return planes.filter((p) => {
const matchName = term
- ? p.nombrePrograma.toLowerCase().includes(term)
+ ? // Limpiamos también el nombre del programa antes de comparar
+ cleanText(p.nombrePrograma).includes(term)
: true
const matchFac =
facultadSel === 'todas' ? true : p.facultadId === facultadSel