bugs arreglados de FileDropZone
This commit is contained in:
9
bun.lock
9
bun.lock
@@ -46,6 +46,7 @@
|
|||||||
"eslint-import-resolver-typescript": "^4.4.4",
|
"eslint-import-resolver-typescript": "^4.4.4",
|
||||||
"eslint-plugin-import": "^2.32.0",
|
"eslint-plugin-import": "^2.32.0",
|
||||||
"eslint-plugin-jsx-a11y": "^6.10.2",
|
"eslint-plugin-jsx-a11y": "^6.10.2",
|
||||||
|
"eslint-plugin-react-hooks": "^7.0.1",
|
||||||
"eslint-plugin-unused-imports": "^4.3.0",
|
"eslint-plugin-unused-imports": "^4.3.0",
|
||||||
"jsdom": "^27.0.0",
|
"jsdom": "^27.0.0",
|
||||||
"prettier": "^3.5.3",
|
"prettier": "^3.5.3",
|
||||||
@@ -770,6 +771,8 @@
|
|||||||
|
|
||||||
"eslint-plugin-n": ["eslint-plugin-n@17.23.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.5.0", "enhanced-resolve": "^5.17.1", "eslint-plugin-es-x": "^7.8.0", "get-tsconfig": "^4.8.1", "globals": "^15.11.0", "globrex": "^0.1.2", "ignore": "^5.3.2", "semver": "^7.6.3", "ts-declaration-location": "^1.0.6" }, "peerDependencies": { "eslint": ">=8.23.0" } }, "sha512-68PealUpYoHOBh332JLLD9Sj7OQUDkFpmcfqt8R9sySfFSeuGJjMTJQvCRRB96zO3A/PELRLkPrzsHmzEFQQ5A=="],
|
"eslint-plugin-n": ["eslint-plugin-n@17.23.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.5.0", "enhanced-resolve": "^5.17.1", "eslint-plugin-es-x": "^7.8.0", "get-tsconfig": "^4.8.1", "globals": "^15.11.0", "globrex": "^0.1.2", "ignore": "^5.3.2", "semver": "^7.6.3", "ts-declaration-location": "^1.0.6" }, "peerDependencies": { "eslint": ">=8.23.0" } }, "sha512-68PealUpYoHOBh332JLLD9Sj7OQUDkFpmcfqt8R9sySfFSeuGJjMTJQvCRRB96zO3A/PELRLkPrzsHmzEFQQ5A=="],
|
||||||
|
|
||||||
|
"eslint-plugin-react-hooks": ["eslint-plugin-react-hooks@7.0.1", "", { "dependencies": { "@babel/core": "^7.24.4", "@babel/parser": "^7.24.4", "hermes-parser": "^0.25.1", "zod": "^3.25.0 || ^4.0.0", "zod-validation-error": "^3.5.0 || ^4.0.0" }, "peerDependencies": { "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" } }, "sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA=="],
|
||||||
|
|
||||||
"eslint-plugin-unused-imports": ["eslint-plugin-unused-imports@4.3.0", "", { "peerDependencies": { "@typescript-eslint/eslint-plugin": "^8.0.0-0 || ^7.0.0 || ^6.0.0 || ^5.0.0", "eslint": "^9.0.0 || ^8.0.0" }, "optionalPeers": ["@typescript-eslint/eslint-plugin"] }, "sha512-ZFBmXMGBYfHttdRtOG9nFFpmUvMtbHSjsKrS20vdWdbfiVYsO3yA2SGYy9i9XmZJDfMGBflZGBCm70SEnFQtOA=="],
|
"eslint-plugin-unused-imports": ["eslint-plugin-unused-imports@4.3.0", "", { "peerDependencies": { "@typescript-eslint/eslint-plugin": "^8.0.0-0 || ^7.0.0 || ^6.0.0 || ^5.0.0", "eslint": "^9.0.0 || ^8.0.0" }, "optionalPeers": ["@typescript-eslint/eslint-plugin"] }, "sha512-ZFBmXMGBYfHttdRtOG9nFFpmUvMtbHSjsKrS20vdWdbfiVYsO3yA2SGYy9i9XmZJDfMGBflZGBCm70SEnFQtOA=="],
|
||||||
|
|
||||||
"eslint-scope": ["eslint-scope@8.4.0", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg=="],
|
"eslint-scope": ["eslint-scope@8.4.0", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg=="],
|
||||||
@@ -864,6 +867,10 @@
|
|||||||
|
|
||||||
"hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="],
|
"hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="],
|
||||||
|
|
||||||
|
"hermes-estree": ["hermes-estree@0.25.1", "", {}, "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw=="],
|
||||||
|
|
||||||
|
"hermes-parser": ["hermes-parser@0.25.1", "", { "dependencies": { "hermes-estree": "0.25.1" } }, "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA=="],
|
||||||
|
|
||||||
"html-encoding-sniffer": ["html-encoding-sniffer@4.0.0", "", { "dependencies": { "whatwg-encoding": "^3.1.1" } }, "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ=="],
|
"html-encoding-sniffer": ["html-encoding-sniffer@4.0.0", "", { "dependencies": { "whatwg-encoding": "^3.1.1" } }, "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ=="],
|
||||||
|
|
||||||
"http-proxy-agent": ["http-proxy-agent@7.0.2", "", { "dependencies": { "agent-base": "^7.1.0", "debug": "^4.3.4" } }, "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig=="],
|
"http-proxy-agent": ["http-proxy-agent@7.0.2", "", { "dependencies": { "agent-base": "^7.1.0", "debug": "^4.3.4" } }, "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig=="],
|
||||||
@@ -1318,6 +1325,8 @@
|
|||||||
|
|
||||||
"zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
|
"zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
|
||||||
|
|
||||||
|
"zod-validation-error": ["zod-validation-error@4.0.2", "", { "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" } }, "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ=="],
|
||||||
|
|
||||||
"@babel/helper-compilation-targets/lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="],
|
"@babel/helper-compilation-targets/lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="],
|
||||||
|
|
||||||
"@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
|
"@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
import { tanstackConfig } from '@tanstack/eslint-config'
|
import { tanstackConfig } from '@tanstack/eslint-config'
|
||||||
import eslintConfigPrettier from 'eslint-config-prettier'
|
import eslintConfigPrettier from 'eslint-config-prettier'
|
||||||
import jsxA11y from 'eslint-plugin-jsx-a11y'
|
import jsxA11y from 'eslint-plugin-jsx-a11y'
|
||||||
|
import reactHooks from 'eslint-plugin-react-hooks'
|
||||||
import unusedImports from 'eslint-plugin-unused-imports'
|
import unusedImports from 'eslint-plugin-unused-imports'
|
||||||
|
|
||||||
export default [
|
export default [
|
||||||
@@ -24,9 +25,12 @@ export default [
|
|||||||
|
|
||||||
// 3. TUS REGLAS Y CONFIGURACIÓN "PRO"
|
// 3. TUS REGLAS Y CONFIGURACIÓN "PRO"
|
||||||
{
|
{
|
||||||
|
// Opcional: Puedes ser explícito sobre dónde aplicar esto
|
||||||
|
files: ['**/*.{ts,tsx,js,jsx}'],
|
||||||
plugins: {
|
plugins: {
|
||||||
'jsx-a11y': jsxA11y,
|
'jsx-a11y': jsxA11y,
|
||||||
'unused-imports': unusedImports,
|
'unused-imports': unusedImports,
|
||||||
|
'react-hooks': reactHooks,
|
||||||
},
|
},
|
||||||
// Configuración robusta del Resolver (La versión de Copilot)
|
// Configuración robusta del Resolver (La versión de Copilot)
|
||||||
settings: {
|
settings: {
|
||||||
@@ -44,7 +48,8 @@ export default [
|
|||||||
// --- REGLAS DE ACCESIBILIDAD (A11Y) ---
|
// --- REGLAS DE ACCESIBILIDAD (A11Y) ---
|
||||||
// Activamos las recomendadas manualmente
|
// Activamos las recomendadas manualmente
|
||||||
...jsxA11y.configs.recommended.rules,
|
...jsxA11y.configs.recommended.rules,
|
||||||
|
'react-hooks/rules-of-hooks': 'error',
|
||||||
|
'react-hooks/exhaustive-deps': 'warn',
|
||||||
// --- ORDEN DE IMPORTS ---
|
// --- ORDEN DE IMPORTS ---
|
||||||
'sort-imports': 'off', // Apagamos el nativo
|
'sort-imports': 'off', // Apagamos el nativo
|
||||||
'import/order': [
|
'import/order': [
|
||||||
|
|||||||
@@ -59,6 +59,7 @@
|
|||||||
"eslint-import-resolver-typescript": "^4.4.4",
|
"eslint-import-resolver-typescript": "^4.4.4",
|
||||||
"eslint-plugin-import": "^2.32.0",
|
"eslint-plugin-import": "^2.32.0",
|
||||||
"eslint-plugin-jsx-a11y": "^6.10.2",
|
"eslint-plugin-jsx-a11y": "^6.10.2",
|
||||||
|
"eslint-plugin-react-hooks": "^7.0.1",
|
||||||
"eslint-plugin-unused-imports": "^4.3.0",
|
"eslint-plugin-unused-imports": "^4.3.0",
|
||||||
"jsdom": "^27.0.0",
|
"jsdom": "^27.0.0",
|
||||||
"prettier": "^3.5.3",
|
"prettier": "^3.5.3",
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Upload, File, X, FileText } from 'lucide-react'
|
import { Upload, File, X, FileText } from 'lucide-react'
|
||||||
import { useState, useCallback } from 'react'
|
import { useState, useCallback, useEffect, useRef } from 'react'
|
||||||
|
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
@@ -28,33 +28,7 @@ export function FileDropzone({
|
|||||||
}: FileDropzoneProps) {
|
}: FileDropzoneProps) {
|
||||||
const [isDragging, setIsDragging] = useState(false)
|
const [isDragging, setIsDragging] = useState(false)
|
||||||
const [files, setFiles] = useState<Array<UploadedFile>>([])
|
const [files, setFiles] = useState<Array<UploadedFile>>([])
|
||||||
|
const onFilesChangeRef = useRef<typeof onFilesChange>(onFilesChange)
|
||||||
const handleDragOver = useCallback((e: React.DragEvent) => {
|
|
||||||
e.preventDefault()
|
|
||||||
setIsDragging(true)
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const handleDragLeave = useCallback((e: React.DragEvent) => {
|
|
||||||
e.preventDefault()
|
|
||||||
setIsDragging(false)
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const handleDrop = useCallback((e: React.DragEvent) => {
|
|
||||||
e.preventDefault()
|
|
||||||
setIsDragging(false)
|
|
||||||
const droppedFiles = Array.from(e.dataTransfer.files)
|
|
||||||
addFiles(droppedFiles)
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const handleFileInput = useCallback(
|
|
||||||
(e: React.ChangeEvent<HTMLInputElement>) => {
|
|
||||||
if (e.target.files) {
|
|
||||||
const selectedFiles = Array.from(e.target.files)
|
|
||||||
addFiles(selectedFiles)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[],
|
|
||||||
)
|
|
||||||
|
|
||||||
const addFiles = useCallback(
|
const addFiles = useCallback(
|
||||||
(newFiles: Array<File>) => {
|
(newFiles: Array<File>) => {
|
||||||
@@ -70,24 +44,59 @@ export function FileDropzone({
|
|||||||
setFiles((prev) => {
|
setFiles((prev) => {
|
||||||
const room = Math.max(0, maxFiles - prev.length)
|
const room = Math.max(0, maxFiles - prev.length)
|
||||||
const next = [...prev, ...toUpload.slice(0, room)].slice(0, maxFiles)
|
const next = [...prev, ...toUpload.slice(0, room)].slice(0, maxFiles)
|
||||||
if (onFilesChange) onFilesChange(next)
|
|
||||||
return next
|
return next
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
[maxFiles, onFilesChange],
|
[maxFiles],
|
||||||
)
|
)
|
||||||
|
|
||||||
const removeFile = useCallback(
|
const handleDragOver = useCallback((e: React.DragEvent) => {
|
||||||
(fileId: string) => {
|
e.preventDefault()
|
||||||
setFiles((prev) => {
|
setIsDragging(true)
|
||||||
const next = prev.filter((f) => f.id !== fileId)
|
}, [])
|
||||||
if (onFilesChange) onFilesChange(next)
|
|
||||||
return next
|
const handleDragLeave = useCallback((e: React.DragEvent) => {
|
||||||
})
|
e.preventDefault()
|
||||||
|
setIsDragging(false)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const handleDrop = useCallback(
|
||||||
|
(e: React.DragEvent) => {
|
||||||
|
e.preventDefault()
|
||||||
|
setIsDragging(false)
|
||||||
|
const droppedFiles = Array.from(e.dataTransfer.files)
|
||||||
|
addFiles(droppedFiles)
|
||||||
},
|
},
|
||||||
[onFilesChange],
|
[addFiles],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const handleFileInput = useCallback(
|
||||||
|
(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
if (e.target.files) {
|
||||||
|
const selectedFiles = Array.from(e.target.files)
|
||||||
|
addFiles(selectedFiles)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[addFiles],
|
||||||
|
)
|
||||||
|
|
||||||
|
const removeFile = useCallback((fileId: string) => {
|
||||||
|
setFiles((prev) => {
|
||||||
|
const next = prev.filter((f) => f.id !== fileId)
|
||||||
|
return next
|
||||||
|
})
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
// Keep latest callback in a ref to avoid retriggering effect on identity change
|
||||||
|
useEffect(() => {
|
||||||
|
onFilesChangeRef.current = onFilesChange
|
||||||
|
}, [onFilesChange])
|
||||||
|
|
||||||
|
// Only emit when files actually change to avoid parent update loops
|
||||||
|
useEffect(() => {
|
||||||
|
if (onFilesChangeRef.current) onFilesChangeRef.current(files)
|
||||||
|
}, [files])
|
||||||
|
|
||||||
const formatFileSize = (bytes: number): string => {
|
const formatFileSize = (bytes: number): string => {
|
||||||
if (bytes < 1024) return bytes + ' B'
|
if (bytes < 1024) return bytes + ' B'
|
||||||
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB'
|
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB'
|
||||||
@@ -116,7 +125,6 @@ export function FileDropzone({
|
|||||||
'border-border hover:border-primary/50 cursor-pointer rounded-xl border-2 border-dashed p-8 text-center transition-all duration-300',
|
'border-border hover:border-primary/50 cursor-pointer rounded-xl border-2 border-dashed p-8 text-center transition-all duration-300',
|
||||||
isDragging && 'active',
|
isDragging && 'active',
|
||||||
)}
|
)}
|
||||||
style={{ background: 'var(--gradient-subtle)' }}
|
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
type="file"
|
type="file"
|
||||||
@@ -126,7 +134,11 @@ export function FileDropzone({
|
|||||||
className="hidden"
|
className="hidden"
|
||||||
id="file-upload"
|
id="file-upload"
|
||||||
/>
|
/>
|
||||||
<label htmlFor="file-upload" className="cursor-pointer">
|
<label
|
||||||
|
htmlFor="file-upload"
|
||||||
|
className="cursor-pointer"
|
||||||
|
aria-label="Seleccionar archivos"
|
||||||
|
>
|
||||||
<div className="flex flex-col items-center gap-3">
|
<div className="flex flex-col items-center gap-3">
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
|
|||||||
@@ -84,6 +84,38 @@ export function PasoDetallesPanel({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<ReferenciasParaIA
|
<ReferenciasParaIA
|
||||||
|
selectedArchivoIds={wizard.iaConfig?.archivosReferencia || []}
|
||||||
|
selectedRepositorioIds={wizard.iaConfig?.repositoriosReferencia || []}
|
||||||
|
onToggleArchivo={(id, checked) =>
|
||||||
|
onChange((w) => {
|
||||||
|
const prev = w.iaConfig?.archivosReferencia || []
|
||||||
|
const next = checked
|
||||||
|
? [...prev, id]
|
||||||
|
: prev.filter((x) => x !== id)
|
||||||
|
return {
|
||||||
|
...w,
|
||||||
|
iaConfig: {
|
||||||
|
...(w.iaConfig || ({} as any)),
|
||||||
|
archivosReferencia: next,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
onToggleRepositorio={(id, checked) =>
|
||||||
|
onChange((w) => {
|
||||||
|
const prev = w.iaConfig?.repositoriosReferencia || []
|
||||||
|
const next = checked
|
||||||
|
? [...prev, id]
|
||||||
|
: prev.filter((x) => x !== id)
|
||||||
|
return {
|
||||||
|
...w,
|
||||||
|
iaConfig: {
|
||||||
|
...(w.iaConfig || ({} as any)),
|
||||||
|
repositoriosReferencia: next,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
onFilesChange={(files) =>
|
onFilesChange={(files) =>
|
||||||
onChange((w) => ({
|
onChange((w) => ({
|
||||||
...w,
|
...w,
|
||||||
@@ -142,6 +174,7 @@ export function PasoDetallesPanel({
|
|||||||
<select
|
<select
|
||||||
id="clonFacultad"
|
id="clonFacultad"
|
||||||
className="bg-background text-foreground ring-offset-background focus-visible:ring-ring h-10 w-full rounded-md border px-3 text-sm shadow-sm focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none"
|
className="bg-background text-foreground ring-offset-background focus-visible:ring-ring h-10 w-full rounded-md border px-3 text-sm shadow-sm focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none"
|
||||||
|
aria-label="Facultad"
|
||||||
value={wizard.datosBasicos.facultadId}
|
value={wizard.datosBasicos.facultadId}
|
||||||
onChange={(e: React.ChangeEvent<HTMLSelectElement>) =>
|
onChange={(e: React.ChangeEvent<HTMLSelectElement>) =>
|
||||||
onChange((w) => ({
|
onChange((w) => ({
|
||||||
@@ -166,6 +199,7 @@ export function PasoDetallesPanel({
|
|||||||
<select
|
<select
|
||||||
id="clonCarrera"
|
id="clonCarrera"
|
||||||
className="bg-background text-foreground ring-offset-background focus-visible:ring-ring h-10 w-full rounded-md border px-3 text-sm shadow-sm focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none"
|
className="bg-background text-foreground ring-offset-background focus-visible:ring-ring h-10 w-full rounded-md border px-3 text-sm shadow-sm focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none"
|
||||||
|
aria-label="Carrera"
|
||||||
value={wizard.datosBasicos.carreraId}
|
value={wizard.datosBasicos.carreraId}
|
||||||
onChange={(e: React.ChangeEvent<HTMLSelectElement>) =>
|
onChange={(e: React.ChangeEvent<HTMLSelectElement>) =>
|
||||||
onChange((w) => ({
|
onChange((w) => ({
|
||||||
@@ -264,12 +298,13 @@ export function PasoDetallesPanel({
|
|||||||
<FileDropzone
|
<FileDropzone
|
||||||
acceptedTypes=".doc,.docx"
|
acceptedTypes=".doc,.docx"
|
||||||
maxFiles={1}
|
maxFiles={1}
|
||||||
onFilesChange={(file) => {
|
onFilesChange={(files) => {
|
||||||
|
const f = files[0] || null
|
||||||
onChange((w) => ({
|
onChange((w) => ({
|
||||||
...w,
|
...w,
|
||||||
clonTradicional: {
|
clonTradicional: {
|
||||||
...(w.clonTradicional || ({} as any)),
|
...(w.clonTradicional || ({} as any)),
|
||||||
archivoWordPlanId: file,
|
archivoWordPlanId: f,
|
||||||
},
|
},
|
||||||
}))
|
}))
|
||||||
}}
|
}}
|
||||||
@@ -281,17 +316,32 @@ export function PasoDetallesPanel({
|
|||||||
id="mapa"
|
id="mapa"
|
||||||
type="file"
|
type="file"
|
||||||
accept=".xls,.xlsx"
|
accept=".xls,.xlsx"
|
||||||
|
title="Subir mapa curricular"
|
||||||
className="bg-background text-foreground ring-offset-background focus-visible:ring-ring file:bg-secondary block w-full rounded-md border px-3 py-2 text-sm shadow-sm file:mr-4 file:rounded-md file:border-0 file:px-3 file:py-1.5 file:text-sm focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none"
|
className="bg-background text-foreground ring-offset-background focus-visible:ring-ring file:bg-secondary block w-full rounded-md border px-3 py-2 text-sm shadow-sm file:mr-4 file:rounded-md file:border-0 file:px-3 file:py-1.5 file:text-sm focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none"
|
||||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||||
onChange((w) => ({
|
onChange((w) => {
|
||||||
...w,
|
const file = e.target.files?.[0] || null
|
||||||
clonTradicional: {
|
const next = file
|
||||||
...(w.clonTradicional || ({} as any)),
|
? {
|
||||||
archivoMapaExcelId: e.target.files?.[0]
|
id:
|
||||||
? `file_${e.target.files[0].name}`
|
typeof crypto !== 'undefined' && 'randomUUID' in crypto
|
||||||
: null,
|
? (crypto as any).randomUUID()
|
||||||
},
|
: `file-${Date.now()}-${Math.random()
|
||||||
}))
|
.toString(36)
|
||||||
|
.substr(2, 9)}`,
|
||||||
|
name: file.name,
|
||||||
|
size: formatFileSize(file.size),
|
||||||
|
type: file.name.split('.').pop() || 'file',
|
||||||
|
}
|
||||||
|
: null
|
||||||
|
return {
|
||||||
|
...w,
|
||||||
|
clonTradicional: {
|
||||||
|
...(w.clonTradicional || ({} as any)),
|
||||||
|
archivoMapaExcelId: next,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -301,17 +351,32 @@ export function PasoDetallesPanel({
|
|||||||
id="asignaturas"
|
id="asignaturas"
|
||||||
type="file"
|
type="file"
|
||||||
accept=".xls,.xlsx,.csv"
|
accept=".xls,.xlsx,.csv"
|
||||||
|
title="Subir listado de asignaturas"
|
||||||
className="bg-background text-foreground ring-offset-background focus-visible:ring-ring file:bg-secondary block w-full rounded-md border px-3 py-2 text-sm shadow-sm file:mr-4 file:rounded-md file:border-0 file:px-3 file:py-1.5 file:text-sm focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none"
|
className="bg-background text-foreground ring-offset-background focus-visible:ring-ring file:bg-secondary block w-full rounded-md border px-3 py-2 text-sm shadow-sm file:mr-4 file:rounded-md file:border-0 file:px-3 file:py-1.5 file:text-sm focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none"
|
||||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||||
onChange((w) => ({
|
onChange((w) => {
|
||||||
...w,
|
const file = e.target.files?.[0] || null
|
||||||
clonTradicional: {
|
const next = file
|
||||||
...(w.clonTradicional || ({} as any)),
|
? {
|
||||||
archivoAsignaturasExcelId: e.target.files?.[0]
|
id:
|
||||||
? `file_${e.target.files[0].name}`
|
typeof crypto !== 'undefined' && 'randomUUID' in crypto
|
||||||
: null,
|
? (crypto as any).randomUUID()
|
||||||
},
|
: `file-${Date.now()}-${Math.random()
|
||||||
}))
|
.toString(36)
|
||||||
|
.substr(2, 9)}`,
|
||||||
|
name: file.name,
|
||||||
|
size: formatFileSize(file.size),
|
||||||
|
type: file.name.split('.').pop() || 'file',
|
||||||
|
}
|
||||||
|
: null
|
||||||
|
return {
|
||||||
|
...w,
|
||||||
|
clonTradicional: {
|
||||||
|
...(w.clonTradicional || ({} as any)),
|
||||||
|
archivoAsignaturasExcelId: next,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -333,3 +398,9 @@ export function PasoDetallesPanel({
|
|||||||
</Card>
|
</Card>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function formatFileSize(bytes: number): string {
|
||||||
|
if (bytes < 1024) return bytes + ' B'
|
||||||
|
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB'
|
||||||
|
return (bytes / (1024 * 1024)).toFixed(1) + ' MB'
|
||||||
|
}
|
||||||
|
|||||||
@@ -17,8 +17,16 @@ import {
|
|||||||
import { ARCHIVOS, REPOSITORIOS } from '@/features/planes/nuevo/catalogs'
|
import { ARCHIVOS, REPOSITORIOS } from '@/features/planes/nuevo/catalogs'
|
||||||
|
|
||||||
const ReferenciasParaIA = ({
|
const ReferenciasParaIA = ({
|
||||||
|
selectedArchivoIds = [],
|
||||||
|
selectedRepositorioIds = [],
|
||||||
|
onToggleArchivo,
|
||||||
|
onToggleRepositorio,
|
||||||
onFilesChange,
|
onFilesChange,
|
||||||
}: {
|
}: {
|
||||||
|
selectedArchivoIds?: Array<string>
|
||||||
|
selectedRepositorioIds?: Array<string>
|
||||||
|
onToggleArchivo?: (id: string, checked: boolean) => void
|
||||||
|
onToggleRepositorio?: (id: string, checked: boolean) => void
|
||||||
onFilesChange?: (
|
onFilesChange?: (
|
||||||
files: Array<{ id: string; name: string; size: string; type: string }>,
|
files: Array<{ id: string; name: string; size: string; type: string }>,
|
||||||
) => void
|
) => void
|
||||||
@@ -72,7 +80,13 @@ const ReferenciasParaIA = ({
|
|||||||
key={archivo.id}
|
key={archivo.id}
|
||||||
className="border-border hover:border-primary/30 hover:bg-accent/50 m-0.5 flex cursor-pointer items-center gap-3 rounded-lg border p-3 transition-colors has-aria-checked:border-blue-600 has-aria-checked:bg-blue-50 dark:has-aria-checked:border-blue-900 dark:has-aria-checked:bg-blue-950"
|
className="border-border hover:border-primary/30 hover:bg-accent/50 m-0.5 flex cursor-pointer items-center gap-3 rounded-lg border p-3 transition-colors has-aria-checked:border-blue-600 has-aria-checked:bg-blue-50 dark:has-aria-checked:border-blue-900 dark:has-aria-checked:bg-blue-950"
|
||||||
>
|
>
|
||||||
<Checkbox className="peer border-primary ring-offset-background data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground focus-visible:ring-ring h-5 w-5 shrink-0 rounded-sm border focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50" />
|
<Checkbox
|
||||||
|
checked={selectedArchivoIds.includes(archivo.id)}
|
||||||
|
onCheckedChange={(checked) =>
|
||||||
|
onToggleArchivo?.(archivo.id, !!checked)
|
||||||
|
}
|
||||||
|
className="peer border-primary ring-offset-background data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground focus-visible:ring-ring h-5 w-5 shrink-0 rounded-sm border focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50"
|
||||||
|
/>
|
||||||
|
|
||||||
<FileText className="text-muted-foreground h-4 w-4" />
|
<FileText className="text-muted-foreground h-4 w-4" />
|
||||||
|
|
||||||
@@ -113,7 +127,13 @@ const ReferenciasParaIA = ({
|
|||||||
key={repositorio.id}
|
key={repositorio.id}
|
||||||
className="border-border hover:border-primary/30 hover:bg-accent/50 m-0.5 flex cursor-pointer items-center gap-3 rounded-lg border p-3 transition-colors has-aria-checked:border-blue-600 has-aria-checked:bg-blue-50 dark:has-aria-checked:border-blue-900 dark:has-aria-checked:bg-blue-950"
|
className="border-border hover:border-primary/30 hover:bg-accent/50 m-0.5 flex cursor-pointer items-center gap-3 rounded-lg border p-3 transition-colors has-aria-checked:border-blue-600 has-aria-checked:bg-blue-50 dark:has-aria-checked:border-blue-900 dark:has-aria-checked:bg-blue-950"
|
||||||
>
|
>
|
||||||
<Checkbox className="peer border-primary ring-offset-background data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground focus-visible:ring-ring h-5 w-5 shrink-0 rounded-sm border focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50" />
|
<Checkbox
|
||||||
|
checked={selectedRepositorioIds.includes(repositorio.id)}
|
||||||
|
onCheckedChange={(checked) =>
|
||||||
|
onToggleRepositorio?.(repositorio.id, !!checked)
|
||||||
|
}
|
||||||
|
className="peer border-primary ring-offset-background data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground focus-visible:ring-ring h-5 w-5 shrink-0 rounded-sm border focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50"
|
||||||
|
/>
|
||||||
|
|
||||||
<FolderOpen className="text-muted-foreground h-4 w-4" />
|
<FolderOpen className="text-muted-foreground h-4 w-4" />
|
||||||
<div className="min-w-0 flex-1">
|
<div className="min-w-0 flex-1">
|
||||||
|
|||||||
@@ -10,11 +10,12 @@ import {
|
|||||||
import {
|
import {
|
||||||
PLANTILLAS_ANEXO_1,
|
PLANTILLAS_ANEXO_1,
|
||||||
PLANTILLAS_ANEXO_2,
|
PLANTILLAS_ANEXO_2,
|
||||||
|
PLANES_EXISTENTES,
|
||||||
|
ARCHIVOS,
|
||||||
|
REPOSITORIOS,
|
||||||
} from '@/features/planes/nuevo/catalogs'
|
} from '@/features/planes/nuevo/catalogs'
|
||||||
|
|
||||||
export function PasoResumenCard({ wizard }: { wizard: NewPlanWizardState }) {
|
export function PasoResumenCard({ wizard }: { wizard: NewPlanWizardState }) {
|
||||||
const modo = wizard.modoCreacion
|
|
||||||
const sub = wizard.subModoClonado
|
|
||||||
return (
|
return (
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
@@ -25,133 +26,210 @@ export function PasoResumenCard({ wizard }: { wizard: NewPlanWizardState }) {
|
|||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<div className="grid gap-2 text-sm">
|
<div className="grid gap-2 text-sm">
|
||||||
<div>
|
{(() => {
|
||||||
<span className="text-muted-foreground">Nombre: </span>
|
// Precompute common derived values to avoid unnecessary optional chaining warnings
|
||||||
<span className="font-medium">
|
const archivosRef = wizard.iaConfig?.archivosReferencia ?? []
|
||||||
{wizard.datosBasicos.nombrePlan || '—'}
|
const repositoriosRef =
|
||||||
</span>
|
wizard.iaConfig?.repositoriosReferencia ?? []
|
||||||
</div>
|
const adjuntos = wizard.iaConfig?.archivosAdjuntos ?? []
|
||||||
<div>
|
const plantillaPlan = PLANTILLAS_ANEXO_1.find(
|
||||||
<span className="text-muted-foreground">Facultad/Carrera: </span>
|
(x) => x.id === wizard.datosBasicos.plantillaPlanId,
|
||||||
<span className="font-medium">
|
)
|
||||||
{wizard.datosBasicos.facultadId || '—'} /{' '}
|
const plantillaMapa = PLANTILLAS_ANEXO_2.find(
|
||||||
{wizard.datosBasicos.carreraId || '—'}
|
(x) => x.id === wizard.datosBasicos.plantillaMapaId,
|
||||||
</span>
|
)
|
||||||
</div>
|
const contenido = (
|
||||||
<div>
|
<>
|
||||||
<span className="text-muted-foreground">Nivel: </span>
|
<div>
|
||||||
<span className="font-medium">
|
<span className="text-muted-foreground">Nombre: </span>
|
||||||
{wizard.datosBasicos.nivel || '—'}
|
<span className="font-medium">
|
||||||
</span>
|
{wizard.datosBasicos.nombrePlan || '—'}
|
||||||
</div>
|
</span>
|
||||||
<div>
|
|
||||||
<span className="text-muted-foreground">Ciclos: </span>
|
|
||||||
<span className="font-medium">
|
|
||||||
{wizard.datosBasicos.numCiclos} ({wizard.datosBasicos.tipoCiclo})
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
{/* Plantillas seleccionadas */}
|
|
||||||
<div className="mt-2">
|
|
||||||
<span className="text-muted-foreground">Plantilla plan: </span>
|
|
||||||
<span className="font-medium">
|
|
||||||
{(() => {
|
|
||||||
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}`
|
|
||||||
})()}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<span className="text-muted-foreground">Mapa curricular: </span>
|
|
||||||
<span className="font-medium">
|
|
||||||
{(() => {
|
|
||||||
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}`
|
|
||||||
})()}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div className="mt-2">
|
|
||||||
<span className="text-muted-foreground">Modo: </span>
|
|
||||||
<span className="font-medium">
|
|
||||||
{modo === 'MANUAL' && 'Manual'}
|
|
||||||
{modo === 'IA' && 'Generado con IA'}
|
|
||||||
{modo === 'CLONADO' &&
|
|
||||||
sub === 'INTERNO' &&
|
|
||||||
'Clonado desde plan del sistema'}
|
|
||||||
{modo === 'CLONADO' &&
|
|
||||||
sub === 'TRADICIONAL' &&
|
|
||||||
'Importado desde documentos tradicionales'}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
{modo === 'IA' && (
|
|
||||||
<div className="bg-muted/50 mt-2 rounded-md p-3">
|
|
||||||
<div>
|
|
||||||
<span className="text-muted-foreground">Enfoque: </span>
|
|
||||||
<span className="font-medium">
|
|
||||||
{wizard.iaConfig?.descripcionEnfoque || '—'}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<span className="text-muted-foreground">Notas: </span>
|
|
||||||
<span className="font-medium">
|
|
||||||
{wizard.iaConfig?.notasAdicionales || '—'}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
{!!(wizard.iaConfig?.archivosReferencia?.length || 0) && (
|
|
||||||
<div className="text-muted-foreground text-xs">
|
|
||||||
Archivos existentes:{' '}
|
|
||||||
{wizard.iaConfig?.archivosReferencia?.length}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
<div>
|
||||||
{!!(wizard.iaConfig?.repositoriosReferencia?.length || 0) && (
|
<span className="text-muted-foreground">
|
||||||
<div className="text-muted-foreground text-xs">
|
Facultad/Carrera:{' '}
|
||||||
Repositorios:{' '}
|
</span>
|
||||||
{wizard.iaConfig?.repositoriosReferencia?.length}
|
<span className="font-medium">
|
||||||
|
{wizard.datosBasicos.facultadId || '—'} /{' '}
|
||||||
|
{wizard.datosBasicos.carreraId || '—'}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span className="text-muted-foreground">Nivel: </span>
|
||||||
|
<span className="font-medium">
|
||||||
|
{wizard.datosBasicos.nivel || '—'}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span className="text-muted-foreground">Ciclos: </span>
|
||||||
|
<span className="font-medium">
|
||||||
|
{wizard.datosBasicos.numCiclos} (
|
||||||
|
{wizard.datosBasicos.tipoCiclo})
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
{!!(wizard.iaConfig?.archivosAdjuntos?.length || 0) && (
|
|
||||||
<div className="mt-2">
|
<div className="mt-2">
|
||||||
<div className="font-medium">Adjuntos</div>
|
<span className="text-muted-foreground">
|
||||||
<ul className="text-muted-foreground list-disc pl-5 text-xs">
|
Plantilla plan:{' '}
|
||||||
{wizard.iaConfig?.archivosAdjuntos?.map((f) => (
|
</span>
|
||||||
<li key={f.id}>
|
<span className="font-medium">
|
||||||
<span className="text-foreground">{f.name}</span>{' '}
|
{(plantillaPlan?.name ||
|
||||||
<span>· {f.size}</span>
|
wizard.datosBasicos.plantillaPlanId ||
|
||||||
</li>
|
'—') +
|
||||||
))}
|
' · ' +
|
||||||
</ul>
|
(wizard.datosBasicos.plantillaPlanVersion || '—')}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
<div>
|
||||||
</div>
|
<span className="text-muted-foreground">
|
||||||
)}
|
Mapa curricular:{' '}
|
||||||
{modo === 'CLONADO' && sub === 'TRADICIONAL' && (
|
</span>
|
||||||
<div className="mt-2">
|
<span className="font-medium">
|
||||||
<span className="text-muted-foreground">
|
{(plantillaMapa?.name ||
|
||||||
Archivo Word del plan:{' '}
|
wizard.datosBasicos.plantillaMapaId ||
|
||||||
</span>
|
'—') +
|
||||||
<span className="font-medium">
|
' · ' +
|
||||||
{wizard.clonTradicional?.archivoWordPlanId?.name || '—'}
|
(wizard.datosBasicos.plantillaMapaVersion || '—')}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
<div className="mt-2">
|
||||||
{wizard.resumen.previewPlan && (
|
<span className="text-muted-foreground">Modo: </span>
|
||||||
<div className="bg-muted mt-2 rounded-md p-3">
|
<span className="font-medium">
|
||||||
<div className="font-medium">Preview IA</div>
|
{wizard.modoCreacion === 'MANUAL' && 'Manual'}
|
||||||
<div className="text-muted-foreground">
|
{wizard.modoCreacion === 'IA' && 'Generado con IA'}
|
||||||
Asignaturas aprox.:{' '}
|
{wizard.modoCreacion === 'CLONADO' &&
|
||||||
{wizard.resumen.previewPlan.numAsignaturasAprox}
|
wizard.subModoClonado === 'INTERNO' &&
|
||||||
</div>
|
'Clonado desde plan del sistema'}
|
||||||
</div>
|
{wizard.modoCreacion === 'CLONADO' &&
|
||||||
)}
|
wizard.subModoClonado === 'TRADICIONAL' &&
|
||||||
|
'Importado desde documentos tradicionales'}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{wizard.modoCreacion === 'CLONADO' &&
|
||||||
|
wizard.subModoClonado === 'INTERNO' && (
|
||||||
|
<div className="mt-2">
|
||||||
|
<span className="text-muted-foreground">
|
||||||
|
Plan origen:{' '}
|
||||||
|
</span>
|
||||||
|
<span className="font-medium">
|
||||||
|
{(() => {
|
||||||
|
const p = PLANES_EXISTENTES.find(
|
||||||
|
(x) => x.id === wizard.clonInterno?.planOrigenId,
|
||||||
|
)
|
||||||
|
return (
|
||||||
|
p?.nombre || wizard.clonInterno?.planOrigenId || '—'
|
||||||
|
)
|
||||||
|
})()}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{wizard.modoCreacion === 'CLONADO' &&
|
||||||
|
wizard.subModoClonado === 'TRADICIONAL' && (
|
||||||
|
<div className="mt-2">
|
||||||
|
<div className="font-medium">Documentos adjuntos</div>
|
||||||
|
<ul className="text-muted-foreground list-disc pl-5 text-xs">
|
||||||
|
<li>
|
||||||
|
<span className="text-foreground">
|
||||||
|
Word del plan:
|
||||||
|
</span>{' '}
|
||||||
|
{wizard.clonTradicional?.archivoWordPlanId?.name ||
|
||||||
|
'—'}
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<span className="text-foreground">
|
||||||
|
Mapa curricular:
|
||||||
|
</span>{' '}
|
||||||
|
{wizard.clonTradicional?.archivoMapaExcelId?.name ||
|
||||||
|
'—'}
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<span className="text-foreground">Asignaturas:</span>{' '}
|
||||||
|
{wizard.clonTradicional?.archivoAsignaturasExcelId
|
||||||
|
?.name || '—'}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{wizard.modoCreacion === 'IA' && (
|
||||||
|
<div className="bg-muted/50 mt-2 rounded-md p-3">
|
||||||
|
<div>
|
||||||
|
<span className="text-muted-foreground">Enfoque: </span>
|
||||||
|
<span className="font-medium">
|
||||||
|
{wizard.iaConfig?.descripcionEnfoque || '—'}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span className="text-muted-foreground">Notas: </span>
|
||||||
|
<span className="font-medium">
|
||||||
|
{wizard.iaConfig?.notasAdicionales || '—'}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{archivosRef.length > 0 && (
|
||||||
|
<div className="mt-2">
|
||||||
|
<div className="font-medium">Archivos existentes</div>
|
||||||
|
<ul className="text-muted-foreground list-disc pl-5 text-xs">
|
||||||
|
{archivosRef.map((id) => {
|
||||||
|
const a = ARCHIVOS.find((x) => x.id === id)
|
||||||
|
return (
|
||||||
|
<li key={id}>
|
||||||
|
<span className="text-foreground">
|
||||||
|
{a?.nombre || id}
|
||||||
|
</span>{' '}
|
||||||
|
{a?.tamaño ? <span>· {a.tamaño}</span> : null}
|
||||||
|
</li>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{repositoriosRef.length > 0 && (
|
||||||
|
<div className="mt-2">
|
||||||
|
<div className="font-medium">Repositorios</div>
|
||||||
|
<ul className="text-muted-foreground list-disc pl-5 text-xs">
|
||||||
|
{repositoriosRef.map((id) => {
|
||||||
|
const r = REPOSITORIOS.find((x) => x.id === id)
|
||||||
|
return (
|
||||||
|
<li key={id}>
|
||||||
|
<span className="text-foreground">
|
||||||
|
{r?.nombre || id}
|
||||||
|
</span>{' '}
|
||||||
|
{r?.cantidadArchivos ? (
|
||||||
|
<span>· {r.cantidadArchivos} archivos</span>
|
||||||
|
) : null}
|
||||||
|
</li>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{adjuntos.length > 0 && (
|
||||||
|
<div className="mt-2">
|
||||||
|
<div className="font-medium">Adjuntos</div>
|
||||||
|
<ul className="text-muted-foreground list-disc pl-5 text-xs">
|
||||||
|
{adjuntos.map((f) => (
|
||||||
|
<li key={f.id}>
|
||||||
|
<span className="text-foreground">{f.name}</span>{' '}
|
||||||
|
<span>· {f.size}</span>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{wizard.resumen.previewPlan && (
|
||||||
|
<div className="bg-muted mt-2 rounded-md p-3">
|
||||||
|
<div className="font-medium">Preview IA</div>
|
||||||
|
<div className="text-muted-foreground">
|
||||||
|
Asignaturas aprox.:{' '}
|
||||||
|
{wizard.resumen.previewPlan.numAsignaturasAprox}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
return contenido
|
||||||
|
})()}
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -31,12 +31,25 @@ export type NewPlanWizardState = {
|
|||||||
clonInterno?: { planOrigenId: string | null };
|
clonInterno?: { planOrigenId: string | null };
|
||||||
clonTradicional?: {
|
clonTradicional?: {
|
||||||
archivoWordPlanId:
|
archivoWordPlanId:
|
||||||
| { id: string; name: string; size: string; type: string }
|
| {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
size: string;
|
||||||
|
type: string;
|
||||||
|
}
|
||||||
| null;
|
| null;
|
||||||
archivoMapaExcelId:
|
archivoMapaExcelId: {
|
||||||
| string
|
id: string;
|
||||||
| null;
|
name: string;
|
||||||
archivoAsignaturasExcelId: string | null;
|
size: string;
|
||||||
|
type: string;
|
||||||
|
} | null;
|
||||||
|
archivoAsignaturasExcelId: {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
size: string;
|
||||||
|
type: string;
|
||||||
|
} | null;
|
||||||
};
|
};
|
||||||
iaConfig?: {
|
iaConfig?: {
|
||||||
descripcionEnfoque: string;
|
descripcionEnfoque: string;
|
||||||
|
|||||||
Reference in New Issue
Block a user