wip
This commit is contained in:
5
bun.lock
5
bun.lock
@@ -45,6 +45,7 @@
|
|||||||
"@tanstack/eslint-config": "^0.3.0",
|
"@tanstack/eslint-config": "^0.3.0",
|
||||||
"@testing-library/dom": "^10.4.0",
|
"@testing-library/dom": "^10.4.0",
|
||||||
"@testing-library/react": "^16.2.0",
|
"@testing-library/react": "^16.2.0",
|
||||||
|
"@types/bun": "^1.3.6",
|
||||||
"@types/node": "^22.10.2",
|
"@types/node": "^22.10.2",
|
||||||
"@types/react": "^19.2.0",
|
"@types/react": "^19.2.0",
|
||||||
"@types/react-dom": "^19.2.0",
|
"@types/react-dom": "^19.2.0",
|
||||||
@@ -503,6 +504,8 @@
|
|||||||
|
|
||||||
"@types/babel__traverse": ["@types/babel__traverse@7.28.0", "", { "dependencies": { "@babel/types": "^7.28.2" } }, "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q=="],
|
"@types/babel__traverse": ["@types/babel__traverse@7.28.0", "", { "dependencies": { "@babel/types": "^7.28.2" } }, "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q=="],
|
||||||
|
|
||||||
|
"@types/bun": ["@types/bun@1.3.6", "", { "dependencies": { "bun-types": "1.3.6" } }, "sha512-uWCv6FO/8LcpREhenN1d1b6fcspAB+cefwD7uti8C8VffIv0Um08TKMn98FynpTiU38+y2dUO55T11NgDt8VAA=="],
|
||||||
|
|
||||||
"@types/chai": ["@types/chai@5.2.3", "", { "dependencies": { "@types/deep-eql": "*", "assertion-error": "^2.0.1" } }, "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA=="],
|
"@types/chai": ["@types/chai@5.2.3", "", { "dependencies": { "@types/deep-eql": "*", "assertion-error": "^2.0.1" } }, "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA=="],
|
||||||
|
|
||||||
"@types/deep-eql": ["@types/deep-eql@4.0.2", "", {}, "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw=="],
|
"@types/deep-eql": ["@types/deep-eql@4.0.2", "", {}, "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw=="],
|
||||||
@@ -663,6 +666,8 @@
|
|||||||
|
|
||||||
"browserslist": ["browserslist@4.28.1", "", { "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", "electron-to-chromium": "^1.5.263", "node-releases": "^2.0.27", "update-browserslist-db": "^1.2.0" }, "bin": { "browserslist": "cli.js" } }, "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA=="],
|
"browserslist": ["browserslist@4.28.1", "", { "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", "electron-to-chromium": "^1.5.263", "node-releases": "^2.0.27", "update-browserslist-db": "^1.2.0" }, "bin": { "browserslist": "cli.js" } }, "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA=="],
|
||||||
|
|
||||||
|
"bun-types": ["bun-types@1.3.6", "", { "dependencies": { "@types/node": "*" } }, "sha512-OlFwHcnNV99r//9v5IIOgQ9Uk37gZqrNMCcqEaExdkVq3Avwqok1bJFmvGMCkCE0FqzdY8VMOZpfpR3lwI+CsQ=="],
|
||||||
|
|
||||||
"cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="],
|
"cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="],
|
||||||
|
|
||||||
"call-bind": ["call-bind@1.0.8", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.0", "es-define-property": "^1.0.0", "get-intrinsic": "^1.2.4", "set-function-length": "^1.2.2" } }, "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww=="],
|
"call-bind": ["call-bind@1.0.8", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.0", "es-define-property": "^1.0.0", "get-intrinsic": "^1.2.4", "set-function-length": "^1.2.2" } }, "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww=="],
|
||||||
|
|||||||
@@ -58,6 +58,7 @@
|
|||||||
"@tanstack/eslint-config": "^0.3.0",
|
"@tanstack/eslint-config": "^0.3.0",
|
||||||
"@testing-library/dom": "^10.4.0",
|
"@testing-library/dom": "^10.4.0",
|
||||||
"@testing-library/react": "^16.2.0",
|
"@testing-library/react": "^16.2.0",
|
||||||
|
"@types/bun": "^1.3.6",
|
||||||
"@types/node": "^22.10.2",
|
"@types/node": "^22.10.2",
|
||||||
"@types/react": "^19.2.0",
|
"@types/react": "^19.2.0",
|
||||||
"@types/react-dom": "^19.2.0",
|
"@types/react-dom": "^19.2.0",
|
||||||
|
|||||||
18
scripts/update-types.ts
Normal file
18
scripts/update-types.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
// scripts/update-types.ts
|
||||||
|
// Uso:
|
||||||
|
// bun run scripts/update-types.ts
|
||||||
|
import { $ } from "bun";
|
||||||
|
|
||||||
|
console.log("🔄 Generando tipos de Supabase...");
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Ejecutamos el comando y capturamos la salida como texto
|
||||||
|
const output = await $`supabase gen types typescript --linked`.text();
|
||||||
|
|
||||||
|
// Escribimos el archivo directamente con Bun (garantiza UTF-8)
|
||||||
|
await Bun.write("src/types/supabase.ts", output);
|
||||||
|
|
||||||
|
console.log("✅ Tipos actualizados correctamente con acentos.");
|
||||||
|
} catch (error) {
|
||||||
|
console.error("❌ Error generando tipos:", error);
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import { TemplateSelectorCard } from './TemplateSelectorCard'
|
import { TemplateSelectorCard } from './TemplateSelectorCard'
|
||||||
|
|
||||||
|
import type { NivelPlanEstudio, TipoCiclo } from '@/data/types/domain'
|
||||||
import type { CARRERAS } from '@/features/planes/nuevo/catalogs'
|
import type { CARRERAS } from '@/features/planes/nuevo/catalogs'
|
||||||
import type { NewPlanWizardState } from '@/features/planes/nuevo/types'
|
import type { NewPlanWizardState } from '@/features/planes/nuevo/types'
|
||||||
|
|
||||||
@@ -13,6 +14,7 @@ import {
|
|||||||
SelectValue,
|
SelectValue,
|
||||||
} from '@/components/ui/select'
|
} from '@/components/ui/select'
|
||||||
import { Separator } from '@/components/ui/separator'
|
import { Separator } from '@/components/ui/separator'
|
||||||
|
import { useCatalogosPlanes } from '@/data/hooks/usePlans'
|
||||||
import {
|
import {
|
||||||
FACULTADES,
|
FACULTADES,
|
||||||
NIVELES,
|
NIVELES,
|
||||||
@@ -31,6 +33,18 @@ export function PasoBasicosForm({
|
|||||||
onChange: React.Dispatch<React.SetStateAction<NewPlanWizardState>>
|
onChange: React.Dispatch<React.SetStateAction<NewPlanWizardState>>
|
||||||
carrerasFiltradas: typeof CARRERAS
|
carrerasFiltradas: typeof CARRERAS
|
||||||
}) {
|
}) {
|
||||||
|
const { data: catalogos } = useCatalogosPlanes()
|
||||||
|
|
||||||
|
// Preferir los catálogos remotos si están disponibles; si no, usar los locales
|
||||||
|
const facultadesList = catalogos?.facultades ?? FACULTADES
|
||||||
|
const rawCarreras = catalogos?.carreras ?? carrerasFiltradas
|
||||||
|
|
||||||
|
const filteredCarreras = rawCarreras.filter((c: any) => {
|
||||||
|
const facId = wizard.datosBasicos.facultadId
|
||||||
|
if (!facId) return true
|
||||||
|
// soportar ambos shapes: `facultad_id` (BD) o `facultadId` (local)
|
||||||
|
return c.facultad_id ? c.facultad_id === facId : c.facultadId === facId
|
||||||
|
})
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
<div className="grid gap-4 sm:grid-cols-2">
|
<div className="grid gap-4 sm:grid-cols-2">
|
||||||
@@ -40,7 +54,7 @@ export function PasoBasicosForm({
|
|||||||
</Label>
|
</Label>
|
||||||
<Input
|
<Input
|
||||||
id="nombrePlan"
|
id="nombrePlan"
|
||||||
placeholder="Ej. Ingeniería en Sistemas 2026"
|
placeholder="Ej. Ingeniería en Sistemas (2026)"
|
||||||
value={wizard.datosBasicos.nombrePlan}
|
value={wizard.datosBasicos.nombrePlan}
|
||||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||||
onChange((w) => ({
|
onChange((w) => ({
|
||||||
@@ -79,7 +93,7 @@ export function PasoBasicosForm({
|
|||||||
<SelectValue placeholder="Ej. Facultad de Ingeniería" />
|
<SelectValue placeholder="Ej. Facultad de Ingeniería" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
{FACULTADES.map((f) => (
|
{facultadesList.map((f: any) => (
|
||||||
<SelectItem key={f.id} value={f.id}>
|
<SelectItem key={f.id} value={f.id}>
|
||||||
{f.nombre}
|
{f.nombre}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
@@ -112,7 +126,7 @@ export function PasoBasicosForm({
|
|||||||
<SelectValue placeholder="Ej. Ingeniería en Cibernética y Sistemas Computacionales" />
|
<SelectValue placeholder="Ej. Ingeniería en Cibernética y Sistemas Computacionales" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
{carrerasFiltradas.map((c) => (
|
{filteredCarreras.map((c: any) => (
|
||||||
<SelectItem key={c.id} value={c.id}>
|
<SelectItem key={c.id} value={c.id}>
|
||||||
{c.nombre}
|
{c.nombre}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
@@ -125,11 +139,13 @@ export function PasoBasicosForm({
|
|||||||
<Label htmlFor="nivel">Nivel</Label>
|
<Label htmlFor="nivel">Nivel</Label>
|
||||||
<Select
|
<Select
|
||||||
value={wizard.datosBasicos.nivel}
|
value={wizard.datosBasicos.nivel}
|
||||||
onValueChange={(value) =>
|
onValueChange={(value: NivelPlanEstudio) =>
|
||||||
onChange((w) => ({
|
onChange(
|
||||||
|
(w): NewPlanWizardState => ({
|
||||||
...w,
|
...w,
|
||||||
datosBasicos: { ...w.datosBasicos, nivel: value },
|
datosBasicos: { ...w.datosBasicos, nivel: value },
|
||||||
}))
|
}),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<SelectTrigger
|
<SelectTrigger
|
||||||
@@ -157,7 +173,7 @@ export function PasoBasicosForm({
|
|||||||
<Label htmlFor="tipoCiclo">Tipo de ciclo</Label>
|
<Label htmlFor="tipoCiclo">Tipo de ciclo</Label>
|
||||||
<Select
|
<Select
|
||||||
value={wizard.datosBasicos.tipoCiclo}
|
value={wizard.datosBasicos.tipoCiclo}
|
||||||
onValueChange={(value) =>
|
onValueChange={(value: TipoCiclo) =>
|
||||||
onChange((w) => ({
|
onChange((w) => ({
|
||||||
...w,
|
...w,
|
||||||
datosBasicos: {
|
datosBasicos: {
|
||||||
@@ -180,8 +196,8 @@ export function PasoBasicosForm({
|
|||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
{TIPOS_CICLO.map((t) => (
|
{TIPOS_CICLO.map((t) => (
|
||||||
<SelectItem key={t.value} value={t.value}>
|
<SelectItem key={t} value={t}>
|
||||||
{t.label}
|
{t}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ export function PasoDetallesPanel({
|
|||||||
onGenerarIA: () => void
|
onGenerarIA: () => void
|
||||||
isLoading: boolean
|
isLoading: boolean
|
||||||
}) {
|
}) {
|
||||||
if (wizard.modoCreacion === 'MANUAL') {
|
if (wizard.tipoOrigen === 'MANUAL') {
|
||||||
return (
|
return (
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
@@ -43,7 +43,7 @@ export function PasoDetallesPanel({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (wizard.modoCreacion === 'IA') {
|
if (wizard.tipoOrigen === 'IA') {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
<div className="flex flex-col gap-1">
|
<div className="flex flex-col gap-1">
|
||||||
@@ -162,10 +162,7 @@ export function PasoDetallesPanel({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (wizard.tipoOrigen === 'CLONADO_INTERNO') {
|
||||||
wizard.modoCreacion === 'CLONADO' &&
|
|
||||||
wizard.subModoClonado === 'INTERNO'
|
|
||||||
) {
|
|
||||||
return (
|
return (
|
||||||
<div className="grid gap-4">
|
<div className="grid gap-4">
|
||||||
<div className="grid gap-3 sm:grid-cols-3">
|
<div className="grid gap-3 sm:grid-cols-3">
|
||||||
@@ -269,10 +266,7 @@ export function PasoDetallesPanel({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (wizard.tipoOrigen === 'CLONADO_TRADICIONAL') {
|
||||||
wizard.modoCreacion === 'CLONADO' &&
|
|
||||||
wizard.subModoClonado === 'TRADICIONAL'
|
|
||||||
) {
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
<div className="flex flex-col gap-1">
|
<div className="flex flex-col gap-1">
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
import * as Icons from 'lucide-react'
|
import * as Icons from 'lucide-react'
|
||||||
|
|
||||||
import type {
|
import type { TipoOrigen } from '@/data/types/domain'
|
||||||
NewPlanWizardState,
|
import type { NewPlanWizardState } from '@/features/planes/nuevo/types'
|
||||||
ModoCreacion,
|
|
||||||
SubModoClonado,
|
|
||||||
} from '@/features/planes/nuevo/types'
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
@@ -21,8 +18,7 @@ export function PasoModoCardGroup({
|
|||||||
wizard: NewPlanWizardState
|
wizard: NewPlanWizardState
|
||||||
onChange: React.Dispatch<React.SetStateAction<NewPlanWizardState>>
|
onChange: React.Dispatch<React.SetStateAction<NewPlanWizardState>>
|
||||||
}) {
|
}) {
|
||||||
const isSelected = (m: ModoCreacion) => wizard.modoCreacion === m
|
const isSelected = (m: TipoOrigen) => wizard.tipoOrigen === m
|
||||||
const isSubSelected = (s: SubModoClonado) => wizard.subModoClonado === s
|
|
||||||
const handleKeyActivate = (e: React.KeyboardEvent, cb: () => void) => {
|
const handleKeyActivate = (e: React.KeyboardEvent, cb: () => void) => {
|
||||||
const key = e.key
|
const key = e.key
|
||||||
if (
|
if (
|
||||||
@@ -41,19 +37,21 @@ export function PasoModoCardGroup({
|
|||||||
<Card
|
<Card
|
||||||
className={isSelected('MANUAL') ? 'ring-ring ring-2' : ''}
|
className={isSelected('MANUAL') ? 'ring-ring ring-2' : ''}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
onChange((w) => ({
|
onChange(
|
||||||
|
(w): NewPlanWizardState => ({
|
||||||
...w,
|
...w,
|
||||||
modoCreacion: 'MANUAL',
|
tipoOrigen: 'MANUAL',
|
||||||
subModoClonado: undefined,
|
}),
|
||||||
}))
|
)
|
||||||
}
|
}
|
||||||
onKeyDown={(e: React.KeyboardEvent) =>
|
onKeyDown={(e: React.KeyboardEvent) =>
|
||||||
handleKeyActivate(e, () =>
|
handleKeyActivate(e, () =>
|
||||||
onChange((w) => ({
|
onChange(
|
||||||
|
(w): NewPlanWizardState => ({
|
||||||
...w,
|
...w,
|
||||||
modoCreacion: 'MANUAL',
|
tipoOrigen: 'MANUAL',
|
||||||
subModoClonado: undefined,
|
}),
|
||||||
})),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
role="button"
|
role="button"
|
||||||
@@ -70,19 +68,21 @@ export function PasoModoCardGroup({
|
|||||||
<Card
|
<Card
|
||||||
className={isSelected('IA') ? 'ring-ring ring-2' : ''}
|
className={isSelected('IA') ? 'ring-ring ring-2' : ''}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
onChange((w) => ({
|
onChange(
|
||||||
|
(w): NewPlanWizardState => ({
|
||||||
...w,
|
...w,
|
||||||
modoCreacion: 'IA',
|
tipoOrigen: 'IA',
|
||||||
subModoClonado: undefined,
|
}),
|
||||||
}))
|
)
|
||||||
}
|
}
|
||||||
onKeyDown={(e: React.KeyboardEvent) =>
|
onKeyDown={(e: React.KeyboardEvent) =>
|
||||||
handleKeyActivate(e, () =>
|
handleKeyActivate(e, () =>
|
||||||
onChange((w) => ({
|
onChange(
|
||||||
|
(w): NewPlanWizardState => ({
|
||||||
...w,
|
...w,
|
||||||
modoCreacion: 'IA',
|
tipoOrigen: 'IA',
|
||||||
subModoClonado: undefined,
|
}),
|
||||||
})),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
role="button"
|
role="button"
|
||||||
@@ -99,11 +99,13 @@ export function PasoModoCardGroup({
|
|||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<Card
|
<Card
|
||||||
className={isSelected('CLONADO') ? 'ring-ring ring-2' : ''}
|
className={isSelected('OTRO') ? 'ring-ring ring-2' : ''}
|
||||||
onClick={() => onChange((w) => ({ ...w, modoCreacion: 'CLONADO' }))}
|
onClick={() =>
|
||||||
|
onChange((w): NewPlanWizardState => ({ ...w, tipoOrigen: 'OTRO' }))
|
||||||
|
}
|
||||||
onKeyDown={(e: React.KeyboardEvent) =>
|
onKeyDown={(e: React.KeyboardEvent) =>
|
||||||
handleKeyActivate(e, () =>
|
handleKeyActivate(e, () =>
|
||||||
onChange((w) => ({ ...w, modoCreacion: 'CLONADO' })),
|
onChange((w): NewPlanWizardState => ({ ...w, tipoOrigen: 'OTRO' })),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
role="button"
|
role="button"
|
||||||
@@ -115,22 +117,34 @@ export function PasoModoCardGroup({
|
|||||||
</CardTitle>
|
</CardTitle>
|
||||||
<CardDescription>Desde un plan existente o archivos.</CardDescription>
|
<CardDescription>Desde un plan existente o archivos.</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
{wizard.modoCreacion === 'CLONADO' && (
|
{(wizard.tipoOrigen === 'OTRO' ||
|
||||||
|
wizard.tipoOrigen === 'CLONADO_INTERNO' ||
|
||||||
|
wizard.tipoOrigen === 'CLONADO_TRADICIONAL') && (
|
||||||
<CardContent className="flex flex-col gap-3">
|
<CardContent className="flex flex-col gap-3">
|
||||||
<div
|
<div
|
||||||
role="button"
|
role="button"
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
onChange((w) => ({ ...w, subModoClonado: 'INTERNO' }))
|
onChange(
|
||||||
|
(w): NewPlanWizardState => ({
|
||||||
|
...w,
|
||||||
|
tipoOrigen: 'CLONADO_INTERNO',
|
||||||
|
}),
|
||||||
|
)
|
||||||
}}
|
}}
|
||||||
onKeyDown={(e: React.KeyboardEvent) =>
|
onKeyDown={(e: React.KeyboardEvent) =>
|
||||||
handleKeyActivate(e, () =>
|
handleKeyActivate(e, () =>
|
||||||
onChange((w) => ({ ...w, subModoClonado: 'INTERNO' })),
|
onChange(
|
||||||
|
(w): NewPlanWizardState => ({
|
||||||
|
...w,
|
||||||
|
tipoOrigen: 'CLONADO_INTERNO',
|
||||||
|
}),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
className={`hover:border-primary/50 hover:bg-accent flex cursor-pointer flex-row items-center justify-center gap-2 rounded-lg border p-4 text-center transition-all sm:flex-col ${
|
className={`hover:border-primary/50 hover:bg-accent flex cursor-pointer flex-row items-center justify-center gap-2 rounded-lg border p-4 text-center transition-all sm:flex-col ${
|
||||||
isSubSelected('INTERNO')
|
isSelected('CLONADO_INTERNO')
|
||||||
? 'border-primary bg-primary/5 ring-primary text-primary ring-1'
|
? 'border-primary bg-primary/5 ring-primary text-primary ring-1'
|
||||||
: 'border-border text-muted-foreground'
|
: 'border-border text-muted-foreground'
|
||||||
} `}
|
} `}
|
||||||
@@ -144,15 +158,25 @@ export function PasoModoCardGroup({
|
|||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
onChange((w) => ({ ...w, subModoClonado: 'TRADICIONAL' }))
|
onChange(
|
||||||
|
(w): NewPlanWizardState => ({
|
||||||
|
...w,
|
||||||
|
tipoOrigen: 'CLONADO_TRADICIONAL',
|
||||||
|
}),
|
||||||
|
)
|
||||||
}}
|
}}
|
||||||
onKeyDown={(e: React.KeyboardEvent) =>
|
onKeyDown={(e: React.KeyboardEvent) =>
|
||||||
handleKeyActivate(e, () =>
|
handleKeyActivate(e, () =>
|
||||||
onChange((w) => ({ ...w, subModoClonado: 'TRADICIONAL' })),
|
onChange(
|
||||||
|
(w): NewPlanWizardState => ({
|
||||||
|
...w,
|
||||||
|
tipoOrigen: 'CLONADO_TRADICIONAL',
|
||||||
|
}),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
className={`hover:border-primary/50 hover:bg-accent flex cursor-pointer flex-row items-center justify-center gap-2 rounded-lg border p-4 text-center transition-all sm:flex-col ${
|
className={`hover:border-primary/50 hover:bg-accent flex cursor-pointer flex-row items-center justify-center gap-2 rounded-lg border p-4 text-center transition-all sm:flex-col ${
|
||||||
isSubSelected('TRADICIONAL')
|
isSelected('CLONADO_TRADICIONAL')
|
||||||
? 'border-primary bg-primary/5 ring-primary text-primary ring-1'
|
? 'border-primary bg-primary/5 ring-primary text-primary ring-1'
|
||||||
: 'border-border text-muted-foreground'
|
: 'border-border text-muted-foreground'
|
||||||
} `}
|
} `}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { Database, Enums, Tables } from "../../types/supabase";
|
import type { Enums, Tables } from "../../types/supabase";
|
||||||
|
|
||||||
export type UUID = string;
|
export type UUID = string;
|
||||||
|
|
||||||
@@ -52,14 +52,14 @@ export type PlanDatosSep = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type PlanEstudioWithRel =
|
export type PlanEstudioWithRel =
|
||||||
& Database["public"]["Tables"]["planes_estudio"]["Row"]
|
& Tables<"planes_estudio">
|
||||||
& {
|
& {
|
||||||
carreras:
|
carreras:
|
||||||
| Database["public"]["Tables"]["carreras"]["Row"] & {
|
| Tables<"carreras"> & {
|
||||||
facultades: Database["public"]["Tables"]["facultades"]["Row"] | null;
|
facultades: Tables<"facultades"> | null;
|
||||||
}
|
}
|
||||||
| null;
|
| null;
|
||||||
estados_plan: Database["public"]["Tables"]["estados_plan"]["Row"] | null;
|
estados_plan: Tables<"estados_plan"> | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Paged<T> = { data: Array<T>; count: number | null };
|
export type Paged<T> = { data: Array<T>; count: number | null };
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import * as Icons from 'lucide-react'
|
|||||||
|
|
||||||
import { useNuevoPlanWizard } from './hooks/useNuevoPlanWizard'
|
import { useNuevoPlanWizard } from './hooks/useNuevoPlanWizard'
|
||||||
|
|
||||||
|
import type { NewPlanWizardState } from './types'
|
||||||
|
|
||||||
import { PasoBasicosForm } from '@/components/planes/wizard/PasoBasicosForm/PasoBasicosForm'
|
import { PasoBasicosForm } from '@/components/planes/wizard/PasoBasicosForm/PasoBasicosForm'
|
||||||
import { PasoDetallesPanel } from '@/components/planes/wizard/PasoDetallesPanel/PasoDetallesPanel'
|
import { PasoDetallesPanel } from '@/components/planes/wizard/PasoDetallesPanel/PasoDetallesPanel'
|
||||||
import { PasoModoCardGroup } from '@/components/planes/wizard/PasoModoCardGroup'
|
import { PasoModoCardGroup } from '@/components/planes/wizard/PasoModoCardGroup'
|
||||||
@@ -61,12 +63,20 @@ export default function NuevoPlanModalContainer() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const crearPlan = async () => {
|
const crearPlan = async () => {
|
||||||
setWizard((w) => ({ ...w, isLoading: true, errorMessage: null }))
|
setWizard((w: NewPlanWizardState) => ({
|
||||||
|
...w,
|
||||||
|
isLoading: true,
|
||||||
|
errorMessage: null,
|
||||||
|
}))
|
||||||
await new Promise((r) => setTimeout(r, 900))
|
await new Promise((r) => setTimeout(r, 900))
|
||||||
const nuevoId = (() => {
|
const nuevoId = (() => {
|
||||||
if (wizard.modoCreacion === 'MANUAL') return 'plan_new_manual_001'
|
if (wizard.tipoOrigen === 'MANUAL') return 'plan_new_manual_001'
|
||||||
if (wizard.modoCreacion === 'IA') return 'plan_new_ai_001'
|
if (wizard.tipoOrigen === 'IA') return 'plan_new_ai_001'
|
||||||
if (wizard.subModoClonado === 'INTERNO') return 'plan_new_clone_001'
|
if (
|
||||||
|
wizard.tipoOrigen === 'CLONADO_INTERNO' ||
|
||||||
|
wizard.tipoOrigen === 'CLONADO_TRADICIONAL'
|
||||||
|
)
|
||||||
|
return 'plan_new_clone_001'
|
||||||
return 'plan_new_import_001'
|
return 'plan_new_import_001'
|
||||||
})()
|
})()
|
||||||
navigate({ to: `/planes/${nuevoId}` })
|
navigate({ to: `/planes/${nuevoId}` })
|
||||||
@@ -124,7 +134,7 @@ export default function NuevoPlanModalContainer() {
|
|||||||
totalSteps={totalSteps}
|
totalSteps={totalSteps}
|
||||||
currentTitle={methods.current.title}
|
currentTitle={methods.current.title}
|
||||||
currentDescription={methods.current.description}
|
currentDescription={methods.current.description}
|
||||||
nextTitle={nextStep?.title}
|
nextTitle={nextStep.title}
|
||||||
onClose={handleClose}
|
onClose={handleClose}
|
||||||
Wizard={Wizard}
|
Wizard={Wizard}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { TipoCiclo } from "./types";
|
import type { NivelPlanEstudio, TipoCiclo } from "@/data/types/domain";
|
||||||
|
|
||||||
export const FACULTADES = [
|
export const FACULTADES = [
|
||||||
{ id: "ing", nombre: "Facultad de Ingeniería" },
|
{ id: "ing", nombre: "Facultad de Ingeniería" },
|
||||||
@@ -16,16 +16,20 @@ export const CARRERAS = [
|
|||||||
{ id: "act", nombre: "Actuaría", facultadId: "neg" },
|
{ id: "act", nombre: "Actuaría", facultadId: "neg" },
|
||||||
];
|
];
|
||||||
|
|
||||||
export const NIVELES = [
|
export const NIVELES: Array<NivelPlanEstudio> = [
|
||||||
"Licenciatura",
|
"Licenciatura",
|
||||||
"Especialidad",
|
|
||||||
"Maestría",
|
"Maestría",
|
||||||
"Doctorado",
|
"Doctorado",
|
||||||
|
"Especialidad",
|
||||||
|
"Diplomado",
|
||||||
|
"Otro",
|
||||||
];
|
];
|
||||||
export const TIPOS_CICLO: Array<{ value: TipoCiclo; label: string }> = [
|
|
||||||
{ value: "SEMESTRE", label: "Semestre" },
|
export const TIPOS_CICLO: Array<TipoCiclo> = [
|
||||||
{ value: "CUATRIMESTRE", label: "Cuatrimestre" },
|
"Semestre",
|
||||||
{ value: "TRIMESTRE", label: "Trimestre" },
|
"Cuatrimestre",
|
||||||
|
"Trimestre",
|
||||||
|
"Otro",
|
||||||
];
|
];
|
||||||
|
|
||||||
export const PLANES_EXISTENTES = [
|
export const PLANES_EXISTENTES = [
|
||||||
|
|||||||
@@ -2,12 +2,13 @@ import { useMemo, useState } from "react";
|
|||||||
|
|
||||||
import { CARRERAS } from "../catalogs";
|
import { CARRERAS } from "../catalogs";
|
||||||
|
|
||||||
import type { NewPlanWizardState, PlanPreview, TipoCiclo } from "../types";
|
import type { NewPlanWizardState, PlanPreview } from "../types";
|
||||||
|
import type { NivelPlanEstudio, TipoCiclo } from "@/data/types/domain";
|
||||||
|
|
||||||
export function useNuevoPlanWizard() {
|
export function useNuevoPlanWizard() {
|
||||||
const [wizard, setWizard] = useState<NewPlanWizardState>({
|
const [wizard, setWizard] = useState<NewPlanWizardState>({
|
||||||
step: 1,
|
step: 1,
|
||||||
modoCreacion: null,
|
tipoOrigen: null,
|
||||||
datosBasicos: {
|
datosBasicos: {
|
||||||
nombrePlan: "",
|
nombrePlan: "",
|
||||||
carreraId: "",
|
carreraId: "",
|
||||||
@@ -40,7 +41,6 @@ export function useNuevoPlanWizard() {
|
|||||||
},
|
},
|
||||||
iaConfig: {
|
iaConfig: {
|
||||||
descripcionEnfoque: "",
|
descripcionEnfoque: "",
|
||||||
poblacionObjetivo: "",
|
|
||||||
notasAdicionales: "",
|
notasAdicionales: "",
|
||||||
archivosReferencia: [],
|
archivosReferencia: [],
|
||||||
repositoriosReferencia: [],
|
repositoriosReferencia: [],
|
||||||
@@ -56,9 +56,10 @@ export function useNuevoPlanWizard() {
|
|||||||
return fac ? CARRERAS.filter((c) => c.facultadId === fac) : CARRERAS;
|
return fac ? CARRERAS.filter((c) => c.facultadId === fac) : CARRERAS;
|
||||||
}, [wizard.datosBasicos.facultadId]);
|
}, [wizard.datosBasicos.facultadId]);
|
||||||
|
|
||||||
const canContinueDesdeModo = wizard.modoCreacion === "MANUAL" ||
|
const canContinueDesdeModo = wizard.tipoOrigen === "MANUAL" ||
|
||||||
wizard.modoCreacion === "IA" ||
|
wizard.tipoOrigen === "IA" ||
|
||||||
(wizard.modoCreacion === "CLONADO" && !!wizard.subModoClonado);
|
(wizard.tipoOrigen === "CLONADO_INTERNO" ||
|
||||||
|
wizard.tipoOrigen === "CLONADO_TRADICIONAL");
|
||||||
|
|
||||||
const canContinueDesdeBasicos = !!wizard.datosBasicos.nombrePlan &&
|
const canContinueDesdeBasicos = !!wizard.datosBasicos.nombrePlan &&
|
||||||
!!wizard.datosBasicos.carreraId &&
|
!!wizard.datosBasicos.carreraId &&
|
||||||
@@ -73,17 +74,16 @@ export function useNuevoPlanWizard() {
|
|||||||
!!wizard.datosBasicos.plantillaMapaVersion;
|
!!wizard.datosBasicos.plantillaMapaVersion;
|
||||||
|
|
||||||
const canContinueDesdeDetalles = (() => {
|
const canContinueDesdeDetalles = (() => {
|
||||||
if (wizard.modoCreacion === "MANUAL") return true;
|
if (wizard.tipoOrigen === "MANUAL") return true;
|
||||||
if (wizard.modoCreacion === "IA") {
|
if (wizard.tipoOrigen === "IA") {
|
||||||
// Requerimos descripción del enfoque y notas adicionales
|
// Requerimos descripción del enfoque y notas adicionales
|
||||||
return !!wizard.iaConfig?.descripcionEnfoque &&
|
return !!wizard.iaConfig?.descripcionEnfoque &&
|
||||||
!!wizard.iaConfig?.notasAdicionales;
|
!!wizard.iaConfig.notasAdicionales;
|
||||||
}
|
}
|
||||||
if (wizard.modoCreacion === "CLONADO") {
|
if (wizard.tipoOrigen === "CLONADO_INTERNO") {
|
||||||
if (wizard.subModoClonado === "INTERNO") {
|
|
||||||
return !!wizard.clonInterno?.planOrigenId;
|
return !!wizard.clonInterno?.planOrigenId;
|
||||||
}
|
}
|
||||||
if (wizard.subModoClonado === "TRADICIONAL") {
|
if (wizard.tipoOrigen === "CLONADO_TRADICIONAL") {
|
||||||
const t = wizard.clonTradicional;
|
const t = wizard.clonTradicional;
|
||||||
if (!t) return false;
|
if (!t) return false;
|
||||||
const tieneWord = !!t.archivoWordPlanId;
|
const tieneWord = !!t.archivoWordPlanId;
|
||||||
@@ -91,7 +91,6 @@ export function useNuevoPlanWizard() {
|
|||||||
!!t.archivoAsignaturasExcelId;
|
!!t.archivoAsignaturasExcelId;
|
||||||
return tieneWord && tieneAlMenosUnExcel;
|
return tieneWord && tieneAlMenosUnExcel;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return false;
|
return false;
|
||||||
})();
|
})();
|
||||||
|
|
||||||
@@ -101,7 +100,7 @@ export function useNuevoPlanWizard() {
|
|||||||
// Ensure preview has the stricter types required by `PlanPreview`.
|
// Ensure preview has the stricter types required by `PlanPreview`.
|
||||||
let tipoCicloSafe: TipoCiclo;
|
let tipoCicloSafe: TipoCiclo;
|
||||||
if (wizard.datosBasicos.tipoCiclo === "") {
|
if (wizard.datosBasicos.tipoCiclo === "") {
|
||||||
tipoCicloSafe = "SEMESTRE";
|
tipoCicloSafe = "Semestre";
|
||||||
} else {
|
} else {
|
||||||
tipoCicloSafe = wizard.datosBasicos.tipoCiclo;
|
tipoCicloSafe = wizard.datosBasicos.tipoCiclo;
|
||||||
}
|
}
|
||||||
@@ -112,7 +111,7 @@ export function useNuevoPlanWizard() {
|
|||||||
|
|
||||||
const preview: PlanPreview = {
|
const preview: PlanPreview = {
|
||||||
nombrePlan: wizard.datosBasicos.nombrePlan || "Plan sin nombre",
|
nombrePlan: wizard.datosBasicos.nombrePlan || "Plan sin nombre",
|
||||||
nivel: wizard.datosBasicos.nivel || "Licenciatura",
|
nivel: wizard.datosBasicos.nivel as NivelPlanEstudio,
|
||||||
tipoCiclo: tipoCicloSafe,
|
tipoCiclo: tipoCicloSafe,
|
||||||
numCiclos: numCiclosSafe,
|
numCiclos: numCiclosSafe,
|
||||||
numAsignaturasAprox: numCiclosSafe * 6,
|
numAsignaturasAprox: numCiclosSafe * 6,
|
||||||
@@ -121,7 +120,7 @@ export function useNuevoPlanWizard() {
|
|||||||
{ id: "perfil", titulo: "Perfil de egreso", resumen: "Borrador…" },
|
{ id: "perfil", titulo: "Perfil de egreso", resumen: "Borrador…" },
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
setWizard((w) => ({
|
setWizard((w: NewPlanWizardState) => ({
|
||||||
...w,
|
...w,
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
resumen: { previewPlan: preview },
|
resumen: { previewPlan: preview },
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
export type TipoCiclo = "SEMESTRE" | "CUATRIMESTRE" | "TRIMESTRE";
|
import type {
|
||||||
export type ModoCreacion = "MANUAL" | "IA" | "CLONADO";
|
NivelPlanEstudio,
|
||||||
export type SubModoClonado = "INTERNO" | "TRADICIONAL";
|
TipoCiclo,
|
||||||
|
TipoOrigen,
|
||||||
|
} from "@/data/types/domain";
|
||||||
|
|
||||||
export type PlanPreview = {
|
export type PlanPreview = {
|
||||||
nombrePlan: string;
|
nombrePlan: string;
|
||||||
nivel: string;
|
nivel: NivelPlanEstudio;
|
||||||
tipoCiclo: TipoCiclo;
|
tipoCiclo: TipoCiclo;
|
||||||
numCiclos: number;
|
numCiclos: number;
|
||||||
numAsignaturasAprox?: number;
|
numAsignaturasAprox?: number;
|
||||||
@@ -13,13 +15,12 @@ export type PlanPreview = {
|
|||||||
|
|
||||||
export type NewPlanWizardState = {
|
export type NewPlanWizardState = {
|
||||||
step: 1 | 2 | 3 | 4;
|
step: 1 | 2 | 3 | 4;
|
||||||
modoCreacion: ModoCreacion | null;
|
tipoOrigen: TipoOrigen | null;
|
||||||
subModoClonado?: SubModoClonado;
|
|
||||||
datosBasicos: {
|
datosBasicos: {
|
||||||
nombrePlan: string;
|
nombrePlan: string;
|
||||||
carreraId: string;
|
carreraId: string;
|
||||||
facultadId: string;
|
facultadId: string;
|
||||||
nivel: string;
|
nivel: NivelPlanEstudio | "";
|
||||||
tipoCiclo: TipoCiclo | "";
|
tipoCiclo: TipoCiclo | "";
|
||||||
numCiclos: number | undefined;
|
numCiclos: number | undefined;
|
||||||
// Selección de plantillas (obligatorias)
|
// Selección de plantillas (obligatorias)
|
||||||
@@ -53,7 +54,6 @@ export type NewPlanWizardState = {
|
|||||||
};
|
};
|
||||||
iaConfig?: {
|
iaConfig?: {
|
||||||
descripcionEnfoque: string;
|
descripcionEnfoque: string;
|
||||||
poblacionObjetivo: string;
|
|
||||||
notasAdicionales: string;
|
notasAdicionales: string;
|
||||||
archivosReferencia: Array<string>;
|
archivosReferencia: Array<string>;
|
||||||
repositoriosReferencia?: Array<string>;
|
repositoriosReferencia?: Array<string>;
|
||||||
|
|||||||
Binary file not shown.
Reference in New Issue
Block a user