Refactor: unifica wizards con WizardLayout/WizardResponsiveHeader y convierte asignaturas en layout con Outlet
- Se introdujo un layout genérico de wizard (WizardLayout) con headerSlot/footerSlot y se migraron los modales de Nuevo Plan y Nueva Asignatura a esta estructura usando defineStepper. - Se creó y reutilizó WizardResponsiveHeader para un encabezado responsivo consistente (progreso en móvil y navegación en escritorio) en ambos wizards. - Se homologó WizardControls del wizard de asignaturas para alinearlo al patrón del wizard de planes (props onPrev/onNext, flags de disable, manejo de error/loading y creación). - Se mejoró la captura de datos en el wizard de asignatura: créditos como flotante con 2 decimales, placeholders/estilos en inputs/selects y uso de catálogo real de estructuras vía useSubjectEstructuras con qk.estructurasAsignatura. - Se reorganizó la sección de asignaturas del detalle del plan: el contenido del antiguo index se movió a asignaturas.tsx como layout y se agregó <Outlet />; navegación a “nueva asignatura” ajustada al path correcto.
This commit is contained in:
@@ -9,9 +9,38 @@ import {
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from '@/components/ui/card'
|
||||
import { ESTRUCTURAS_SEP } from '@/features/asignaturas/nueva/catalogs'
|
||||
import { usePlan, useSubjectEstructuras } from '@/data'
|
||||
import { formatFileSize } from '@/features/planes/utils/format-file-size'
|
||||
|
||||
export function PasoResumenCard({ wizard }: { wizard: NewSubjectWizardState }) {
|
||||
const { data: plan } = usePlan(wizard.plan_estudio_id)
|
||||
const { data: estructuras } = useSubjectEstructuras()
|
||||
|
||||
const estructuraNombre = (() => {
|
||||
const estructuraId = wizard.datosBasicos.estructuraId
|
||||
if (!estructuraId) return '—'
|
||||
const hit = estructuras?.find((e) => e.id === estructuraId)
|
||||
return hit?.nombre ?? estructuraId
|
||||
})()
|
||||
|
||||
const modoLabel = (() => {
|
||||
if (wizard.tipoOrigen === 'MANUAL') return 'Manual (Vacía)'
|
||||
if (wizard.tipoOrigen === 'IA') return 'Generada con IA'
|
||||
if (wizard.tipoOrigen === 'CLONADO_INTERNO') return 'Clonada (Sistema)'
|
||||
if (wizard.tipoOrigen === 'CLONADO_TRADICIONAL') return 'Clonada (Archivo)'
|
||||
return '—'
|
||||
})()
|
||||
|
||||
const creditosText =
|
||||
typeof wizard.datosBasicos.creditos === 'number' &&
|
||||
Number.isFinite(wizard.datosBasicos.creditos)
|
||||
? wizard.datosBasicos.creditos.toFixed(2)
|
||||
: '—'
|
||||
|
||||
const archivosRef = wizard.iaConfig?.archivosReferencia ?? []
|
||||
const repositoriosRef = wizard.iaConfig?.repositoriosReferencia ?? []
|
||||
const adjuntos = wizard.iaConfig?.archivosAdjuntos ?? []
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
@@ -20,53 +49,145 @@ export function PasoResumenCard({ wizard }: { wizard: NewSubjectWizardState }) {
|
||||
Verifica los datos antes de crear la asignatura.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="grid gap-4 text-sm">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<span className="text-muted-foreground">Nombre:</span>
|
||||
<div className="font-medium">{wizard.datosBasicos.nombre}</div>
|
||||
<CardContent>
|
||||
<div className="grid gap-4 text-sm">
|
||||
<div className="grid gap-2">
|
||||
<div>
|
||||
<span className="text-muted-foreground">Plan de estudios: </span>
|
||||
<span className="font-medium">
|
||||
{plan?.nombre || wizard.plan_estudio_id || '—'}
|
||||
</span>
|
||||
</div>
|
||||
{plan?.carreras?.nombre ? (
|
||||
<div>
|
||||
<span className="text-muted-foreground">Carrera: </span>
|
||||
<span className="font-medium">{plan.carreras.nombre}</span>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-muted-foreground">Tipo:</span>
|
||||
<div className="font-medium">{wizard.datosBasicos.tipo}</div>
|
||||
|
||||
<div className="bg-muted rounded-md p-3">
|
||||
<span className="text-muted-foreground">Tipo de origen: </span>
|
||||
<span className="inline-flex items-center gap-2 font-medium">
|
||||
{wizard.tipoOrigen === 'MANUAL' && (
|
||||
<Icons.Pencil className="h-4 w-4" />
|
||||
)}
|
||||
{wizard.tipoOrigen === 'IA' && (
|
||||
<Icons.Sparkles className="h-4 w-4" />
|
||||
)}
|
||||
{(wizard.tipoOrigen === 'CLONADO_INTERNO' ||
|
||||
wizard.tipoOrigen === 'CLONADO_TRADICIONAL') && (
|
||||
<Icons.Copy className="h-4 w-4" />
|
||||
)}
|
||||
{modoLabel}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-muted-foreground">Créditos:</span>
|
||||
<div className="font-medium">{wizard.datosBasicos.creditos}</div>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-muted-foreground">Estructura:</span>
|
||||
<div className="font-medium">
|
||||
{
|
||||
ESTRUCTURAS_SEP.find(
|
||||
(e) => e.id === wizard.datosBasicos.estructuraId,
|
||||
)?.label
|
||||
}
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="col-span-2">
|
||||
<span className="text-muted-foreground">Nombre: </span>
|
||||
<span className="font-medium">
|
||||
{wizard.datosBasicos.nombre || '—'}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-muted-foreground">Código: </span>
|
||||
<span className="font-medium">
|
||||
{wizard.datosBasicos.codigo || '—'}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-muted-foreground">Tipo: </span>
|
||||
<span className="font-medium">
|
||||
{wizard.datosBasicos.tipo || '—'}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-muted-foreground">Créditos: </span>
|
||||
<span className="font-medium">{creditosText}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-muted-foreground">Estructura: </span>
|
||||
<span className="font-medium">{estructuraNombre}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-muted-foreground">Horas académicas: </span>
|
||||
<span className="font-medium">
|
||||
{wizard.datosBasicos.horasAcademicas ?? '—'}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-muted-foreground">
|
||||
Horas independientes:{' '}
|
||||
</span>
|
||||
<span className="font-medium">
|
||||
{wizard.datosBasicos.horasIndependientes ?? '—'}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-muted rounded-md p-3">
|
||||
<span className="text-muted-foreground">Modo de creación:</span>
|
||||
<div className="flex items-center gap-2 font-medium">
|
||||
{wizard.modoCreacion === 'MANUAL' && (
|
||||
<>
|
||||
<Icons.Pencil className="h-4 w-4" /> Manual (Vacía)
|
||||
</>
|
||||
)}
|
||||
{wizard.modoCreacion === 'IA' && (
|
||||
<>
|
||||
<Icons.Sparkles className="h-4 w-4" /> Generada con IA
|
||||
</>
|
||||
)}
|
||||
{wizard.modoCreacion === 'CLONADO' && (
|
||||
<>
|
||||
<Icons.Copy className="h-4 w-4" /> Clonada
|
||||
{wizard.subModoClonado === 'INTERNO'
|
||||
? ' (Sistema)'
|
||||
: ' (Archivo)'}
|
||||
</>
|
||||
)}
|
||||
<div className="bg-muted/50 rounded-md p-3">
|
||||
<div className="font-medium">Configuración IA</div>
|
||||
<div className="mt-2 grid gap-2">
|
||||
<div>
|
||||
<span className="text-muted-foreground">
|
||||
Enfoque académico:{' '}
|
||||
</span>
|
||||
<span className="font-medium">
|
||||
{wizard.iaConfig?.descripcionEnfoqueAcademico || '—'}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-muted-foreground">
|
||||
Instrucciones adicionales:{' '}
|
||||
</span>
|
||||
<span className="font-medium">
|
||||
{wizard.iaConfig?.instruccionesAdicionalesIA || '—'}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="mt-2">
|
||||
<div className="font-medium">Archivos de referencia</div>
|
||||
{archivosRef.length ? (
|
||||
<ul className="text-muted-foreground list-disc pl-5 text-xs">
|
||||
{archivosRef.map((id) => (
|
||||
<li key={id}>{id}</li>
|
||||
))}
|
||||
</ul>
|
||||
) : (
|
||||
<div className="text-muted-foreground text-xs">—</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div className="font-medium">Repositorios de referencia</div>
|
||||
{repositoriosRef.length ? (
|
||||
<ul className="text-muted-foreground list-disc pl-5 text-xs">
|
||||
{repositoriosRef.map((id) => (
|
||||
<li key={id}>{id}</li>
|
||||
))}
|
||||
</ul>
|
||||
) : (
|
||||
<div className="text-muted-foreground text-xs">—</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div className="font-medium">Archivos adjuntos</div>
|
||||
{adjuntos.length ? (
|
||||
<ul className="text-muted-foreground list-disc pl-5 text-xs">
|
||||
{adjuntos.map((f) => (
|
||||
<li key={f.id}>
|
||||
<span className="text-foreground">{f.file.name}</span>{' '}
|
||||
<span>· {formatFileSize(f.file.size)}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
) : (
|
||||
<div className="text-muted-foreground text-xs">—</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
|
||||
Reference in New Issue
Block a user