Compare commits
11 Commits
issue/182-
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 658b2e245c | |||
| 30562fead0 | |||
| 2b91004129 | |||
| 96a045dc67 | |||
| a8229f12d5 | |||
| dd4ac5374a | |||
| 670e0b1d14 | |||
| 93fe247a19 | |||
| 32ebfde9ed | |||
| 32f0c4c4d4 | |||
| 6a520ef6b1 |
37
.gitea/workflows/deploy.yaml
Normal file
37
.gitea/workflows/deploy.yaml
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
name: Deploy to Azure Static Web Apps
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-and-deploy:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup Bun
|
||||||
|
uses: oven-sh/setup-bun@v2
|
||||||
|
with:
|
||||||
|
bun-version: latest
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: bun install
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
env:
|
||||||
|
VITE_SUPABASE_URL: ${{ vars.VITE_SUPABASE_URL }}
|
||||||
|
VITE_SUPABASE_ANON_KEY: ${{ vars.VITE_SUPABASE_ANON_KEY }}
|
||||||
|
run: bunx --bun vite build
|
||||||
|
|
||||||
|
# No hace falta instalar el CLI globalmente, usamos bunx
|
||||||
|
- name: Deploy to Azure Static Web Apps
|
||||||
|
env:
|
||||||
|
AZURE_SWA_DEPLOYMENT_TOKEN: ${{ secrets.AZURE_SWA_DEPLOYMENT_TOKEN }}
|
||||||
|
run: |
|
||||||
|
bunx @azure/static-web-apps-cli deploy ./dist \
|
||||||
|
--env production \
|
||||||
|
--deployment-token "$AZURE_SWA_DEPLOYMENT_TOKEN"
|
||||||
@@ -688,6 +688,12 @@ export function ContenidoTematico() {
|
|||||||
>
|
>
|
||||||
{({ handleRef }) => (
|
{({ handleRef }) => (
|
||||||
<>
|
<>
|
||||||
|
{index === 0 && (
|
||||||
|
<InsertUnidadOverlay
|
||||||
|
position="top"
|
||||||
|
onInsert={() => insertUnidadAt(index)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<InsertUnidadOverlay
|
<InsertUnidadOverlay
|
||||||
position="bottom"
|
position="bottom"
|
||||||
onInsert={() => insertUnidadAt(index + 1)}
|
onInsert={() => insertUnidadAt(index + 1)}
|
||||||
|
|||||||
@@ -207,7 +207,7 @@ export async function plan_asignaturas_list(
|
|||||||
const { data, error } = await supabase
|
const { data, error } = await supabase
|
||||||
.from('asignaturas')
|
.from('asignaturas')
|
||||||
.select(
|
.select(
|
||||||
'id,plan_estudio_id,horas_academicas,horas_independientes,estructura_id,codigo,nombre,tipo,creditos,numero_ciclo,linea_plan_id,orden_celda,estado,datos,contenido_tematico,asignatura_hash,tipo_origen,meta_origen,creado_por,actualizado_por,creado_en,actualizado_en',
|
'id,plan_estudio_id,horas_academicas,horas_independientes,estructura_id,codigo,nombre,tipo,creditos,numero_ciclo,linea_plan_id,orden_celda,estado,datos,contenido_tematico,asignatura_hash,tipo_origen,meta_origen,creado_por,actualizado_por,creado_en,actualizado_en,prerrequisito_asignatura_id',
|
||||||
)
|
)
|
||||||
.eq('plan_estudio_id', planId)
|
.eq('plan_estudio_id', planId)
|
||||||
.order('numero_ciclo', { ascending: true, nullsFirst: false })
|
.order('numero_ciclo', { ascending: true, nullsFirst: false })
|
||||||
|
|||||||
@@ -2036,6 +2036,12 @@ function DatosBasicosManualStep({
|
|||||||
publisher: e.target.value.slice(0, 300),
|
publisher: e.target.value.slice(0, 300),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
onBlur={() => {
|
||||||
|
const trimmed = draft.publisher.trim()
|
||||||
|
if (trimmed !== draft.publisher) {
|
||||||
|
onChangeDraft({ ...draft, publisher: trimmed })
|
||||||
|
}
|
||||||
|
}}
|
||||||
maxLength={300}
|
maxLength={300}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -2434,9 +2440,17 @@ const FormatoYCitasStep = forwardRef<
|
|||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const raw = e.currentTarget.value.slice(0, 300)
|
const raw = e.currentTarget.value.slice(0, 300)
|
||||||
onChangeRef(r.id, {
|
onChangeRef(r.id, {
|
||||||
publisher: raw.trim() || undefined,
|
publisher: raw.length > 0 ? raw : undefined,
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
|
onBlur={() => {
|
||||||
|
const trimmed = publisherText.trim()
|
||||||
|
if (trimmed !== publisherText) {
|
||||||
|
onChangeRef(r.id, {
|
||||||
|
publisher: trimmed || undefined,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import {
|
|||||||
Archive,
|
Archive,
|
||||||
Loader2,
|
Loader2,
|
||||||
Sparkles,
|
Sparkles,
|
||||||
|
RotateCcw,
|
||||||
} from 'lucide-react'
|
} from 'lucide-react'
|
||||||
import { useState, useEffect, useRef, useMemo } from 'react'
|
import { useState, useEffect, useRef, useMemo } from 'react'
|
||||||
|
|
||||||
@@ -128,6 +129,7 @@ function RouteComponent() {
|
|||||||
const [pendingSuggestion, setPendingSuggestion] = useState<any>(null)
|
const [pendingSuggestion, setPendingSuggestion] = useState<any>(null)
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
const scrollRef = useRef<HTMLDivElement>(null)
|
const scrollRef = useRef<HTMLDivElement>(null)
|
||||||
|
const isInitialLoad = useRef(true)
|
||||||
const [showArchived, setShowArchived] = useState(false)
|
const [showArchived, setShowArchived] = useState(false)
|
||||||
const [editingChatId, setEditingChatId] = useState<string | null>(null)
|
const [editingChatId, setEditingChatId] = useState<string | null>(null)
|
||||||
const editableRef = useRef<HTMLSpanElement>(null)
|
const editableRef = useRef<HTMLSpanElement>(null)
|
||||||
@@ -204,20 +206,20 @@ function RouteComponent() {
|
|||||||
return messages
|
return messages
|
||||||
})
|
})
|
||||||
}, [mensajesDelChat, activeChatId, availableFields])
|
}, [mensajesDelChat, activeChatId, availableFields])
|
||||||
const scrollToBottom = () => {
|
const scrollToBottom = (behavior = 'smooth') => {
|
||||||
if (scrollRef.current) {
|
if (scrollRef.current) {
|
||||||
// Buscamos el viewport interno del ScrollArea de Radix
|
|
||||||
const scrollContainer = scrollRef.current.querySelector(
|
const scrollContainer = scrollRef.current.querySelector(
|
||||||
'[data-radix-scroll-area-viewport]',
|
'[data-radix-scroll-area-viewport]',
|
||||||
)
|
)
|
||||||
if (scrollContainer) {
|
if (scrollContainer) {
|
||||||
scrollContainer.scrollTo({
|
scrollContainer.scrollTo({
|
||||||
top: scrollContainer.scrollHeight,
|
top: scrollContainer.scrollHeight,
|
||||||
behavior: 'smooth',
|
behavior: behavior, // 'instant' para carga inicial, 'smooth' para mensajes nuevos
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const { activeChats, archivedChats } = useMemo(() => {
|
const { activeChats, archivedChats } = useMemo(() => {
|
||||||
const allChats = lastConversation || []
|
const allChats = lastConversation || []
|
||||||
return {
|
return {
|
||||||
@@ -229,22 +231,22 @@ function RouteComponent() {
|
|||||||
}, [lastConversation])
|
}, [lastConversation])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log(mensajesDelChat)
|
if (chatMessages.length > 0) {
|
||||||
|
if (isInitialLoad.current) {
|
||||||
scrollToBottom()
|
// Si es el primer render con mensajes, vamos al final al instante
|
||||||
}, [chatMessages, isLoading])
|
scrollToBottom('instant')
|
||||||
|
isInitialLoad.current = false
|
||||||
/* useEffect(() => {
|
} else {
|
||||||
// Verificamos cuáles campos de la lista "selectedFields" ya no están presentes en el texto del input
|
// Si ya estaba cargado y llegan nuevos, hacemos el smooth
|
||||||
const camposActualizados = selectedFields.filter((field) =>
|
scrollToBottom('smooth')
|
||||||
input.includes(field.label),
|
}
|
||||||
)
|
|
||||||
|
|
||||||
// Solo actualizamos el estado si hubo un cambio real (para evitar bucles infinitos)
|
|
||||||
if (camposActualizados.length !== selectedFields.length) {
|
|
||||||
setSelectedFields(camposActualizados)
|
|
||||||
}
|
}
|
||||||
}, [input, selectedFields]) */
|
}, [chatMessages])
|
||||||
|
|
||||||
|
// 2. Resetear el flag cuando cambies de chat activo
|
||||||
|
useEffect(() => {
|
||||||
|
isInitialLoad.current = true
|
||||||
|
}, [activeChatId])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isLoadingConv || isSending) return
|
if (isLoadingConv || isSending) return
|
||||||
@@ -508,27 +510,38 @@ function RouteComponent() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ScrollArea className="flex-1">
|
<ScrollArea className="flex-1">
|
||||||
<div className="space-y-1">
|
<div className="space-y-1 pr-2">
|
||||||
|
{' '}
|
||||||
|
{/* Agregamos un pr-2 para que el scrollbar no tape botones */}
|
||||||
{!showArchived ? (
|
{!showArchived ? (
|
||||||
activeChats.map((chat) => (
|
activeChats.map((chat) => (
|
||||||
<div
|
<div
|
||||||
key={chat.id}
|
key={chat.id}
|
||||||
onClick={() => setActiveChatId(chat.id)}
|
onClick={() => setActiveChatId(chat.id)}
|
||||||
className={`group relative flex w-full items-center justify-between overflow-hidden rounded-lg px-3 py-3 text-sm transition-colors ${
|
className={`group relative flex w-full items-center overflow-hidden rounded-lg px-3 py-3 text-sm transition-colors ${
|
||||||
activeChatId === chat.id
|
activeChatId === chat.id
|
||||||
? 'bg-slate-100 font-medium text-slate-900'
|
? 'bg-slate-100 font-medium text-slate-900'
|
||||||
: 'text-slate-600 hover:bg-slate-50'
|
: 'text-slate-600 hover:bg-slate-50'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{/* LADO IZQUIERDO: Icono + Texto con Tooltip */}
|
{/* LADO IZQUIERDO: Icono + Texto */}
|
||||||
<div className="flex min-w-0 flex-1 items-center gap-3">
|
<div
|
||||||
|
className="flex min-w-0 flex-1 items-center gap-3 transition-all duration-200"
|
||||||
|
style={{
|
||||||
|
// Aplicamos la máscara solo cuando el mouse está encima para que se note el desvanecimiento
|
||||||
|
// donde aparecen los botones
|
||||||
|
maskImage:
|
||||||
|
'linear-gradient(to right, black 70%, transparent 95%)',
|
||||||
|
WebkitMaskImage:
|
||||||
|
'linear-gradient(to right, black 70%, transparent 95%)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* pr-12 reserva espacio para los botones absolutos */}
|
||||||
<FileText size={16} className="shrink-0 opacity-40" />
|
<FileText size={16} className="shrink-0 opacity-40" />
|
||||||
|
|
||||||
<TooltipProvider delayDuration={400}>
|
<TooltipProvider delayDuration={400}>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild className="min-w-0 flex-1">
|
||||||
{/* Este contenedor es el que obliga al span a truncarse */}
|
<div className="min-w-0 flex-1">
|
||||||
<div className="max-w-[calc(100%-48px)] min-w-0 flex-1">
|
|
||||||
<span
|
<span
|
||||||
ref={
|
ref={
|
||||||
editingChatId === chat.id ? editableRef : null
|
editingChatId === chat.id ? editableRef : null
|
||||||
@@ -574,8 +587,6 @@ function RouteComponent() {
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
|
|
||||||
{/* Tooltip: Solo aparece si no estás editando y el texto es largo */}
|
|
||||||
{editingChatId !== chat.id && (
|
{editingChatId !== chat.id && (
|
||||||
<TooltipContent
|
<TooltipContent
|
||||||
side="right"
|
side="right"
|
||||||
@@ -588,9 +599,9 @@ function RouteComponent() {
|
|||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* LADO DERECHO: Acciones con shrink-0 para que no se muevan */}
|
{/* LADO DERECHO: Acciones ABSOLUTAS */}
|
||||||
<div
|
<div
|
||||||
className={`flex shrink-0 items-center gap-1 pl-2 opacity-0 transition-opacity group-hover:opacity-100 ${
|
className={`absolute top-1/2 right-2 z-20 flex -translate-y-1/2 items-center gap-1 rounded-md px-1 opacity-0 transition-opacity group-hover:opacity-100 ${
|
||||||
activeChatId === chat.id ? 'bg-slate-100' : 'bg-slate-50'
|
activeChatId === chat.id ? 'bg-slate-100' : 'bg-slate-50'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
@@ -614,7 +625,7 @@ function RouteComponent() {
|
|||||||
</div>
|
</div>
|
||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
/* Sección de archivados */
|
/* Sección de archivados (Simplificada para mantener consistencia) */
|
||||||
<div className="animate-in fade-in slide-in-from-left-2 px-1">
|
<div className="animate-in fade-in slide-in-from-left-2 px-1">
|
||||||
<p className="mb-2 px-2 text-[10px] font-bold text-slate-400 uppercase">
|
<p className="mb-2 px-2 text-[10px] font-bold text-slate-400 uppercase">
|
||||||
Archivados
|
Archivados
|
||||||
@@ -622,18 +633,18 @@ function RouteComponent() {
|
|||||||
{archivedChats.map((chat) => (
|
{archivedChats.map((chat) => (
|
||||||
<div
|
<div
|
||||||
key={chat.id}
|
key={chat.id}
|
||||||
className="group relative mb-1 flex w-full items-center justify-between overflow-hidden rounded-lg bg-slate-50/50 px-3 py-2 text-sm text-slate-400"
|
className="group relative mb-1 flex w-full items-center overflow-hidden rounded-lg bg-slate-50/50 px-3 py-2 text-sm text-slate-400"
|
||||||
>
|
>
|
||||||
<div className="flex min-w-0 flex-1 items-center gap-3">
|
<div className="flex min-w-0 flex-1 items-center gap-3 pr-10">
|
||||||
<Archive size={14} className="shrink-0 opacity-30" />
|
<Archive size={14} className="shrink-0 opacity-30" />
|
||||||
<span className="block min-w-0 flex-1 truncate">
|
<span className="block truncate">
|
||||||
{chat.nombre ||
|
{chat.nombre ||
|
||||||
`Archivado ${chat.creado_en.split('T')[0]}`}
|
`Archivado ${chat.creado_en.split('T')[0]}`}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
onClick={(e) => unarchiveChat(e, chat.id)}
|
onClick={(e) => unarchiveChat(e, chat.id)}
|
||||||
className="ml-2 shrink-0 rounded bg-slate-50/80 p-1 opacity-0 transition-opacity group-hover:opacity-100 hover:text-teal-600"
|
className="absolute top-1/2 right-2 shrink-0 -translate-y-1/2 rounded bg-slate-100 p-1 opacity-0 transition-opacity group-hover:opacity-100 hover:text-teal-600"
|
||||||
>
|
>
|
||||||
<RotateCcw size={14} />
|
<RotateCcw size={14} />
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ const mapAsignaturasToAsignaturas = (
|
|||||||
// Mapeo directo de los nuevos campos de la API
|
// Mapeo directo de los nuevos campos de la API
|
||||||
hd: asig.horas_academicas ?? 0,
|
hd: asig.horas_academicas ?? 0,
|
||||||
hi: asig.horas_independientes ?? 0,
|
hi: asig.horas_independientes ?? 0,
|
||||||
prerrequisitos: [],
|
prerrequisito_asignatura_id: asig.prerrequisito_asignatura_id ?? null,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -336,6 +336,7 @@ function MapaCurricularPage() {
|
|||||||
horas_independientes?: TablesUpdate<'asignaturas'>['horas_independientes']
|
horas_independientes?: TablesUpdate<'asignaturas'>['horas_independientes']
|
||||||
numero_ciclo?: TablesUpdate<'asignaturas'>['numero_ciclo']
|
numero_ciclo?: TablesUpdate<'asignaturas'>['numero_ciclo']
|
||||||
linea_plan_id?: TablesUpdate<'asignaturas'>['linea_plan_id']
|
linea_plan_id?: TablesUpdate<'asignaturas'>['linea_plan_id']
|
||||||
|
prerrequisito_asignatura_id?: string | null
|
||||||
}
|
}
|
||||||
const patch: Partial<AsignaturaPatch> = {
|
const patch: Partial<AsignaturaPatch> = {
|
||||||
nombre: editingData.nombre,
|
nombre: editingData.nombre,
|
||||||
@@ -345,6 +346,7 @@ function MapaCurricularPage() {
|
|||||||
horas_independientes: editingData.hi,
|
horas_independientes: editingData.hi,
|
||||||
numero_ciclo: editingData.ciclo,
|
numero_ciclo: editingData.ciclo,
|
||||||
linea_plan_id: editingData.lineaCurricularId,
|
linea_plan_id: editingData.lineaCurricularId,
|
||||||
|
prerrequisito_asignatura_id: editingData.prerrequisito_asignatura_id,
|
||||||
tipo: editingData.tipo.toUpperCase() as TipoAsignatura, // Asegurar que coincida con el ENUM (OBLIGATORIA/OPTATIVA)
|
tipo: editingData.tipo.toUpperCase() as TipoAsignatura, // Asegurar que coincida con el ENUM (OBLIGATORIA/OPTATIVA)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -490,7 +492,7 @@ function MapaCurricularPage() {
|
|||||||
e: React.FocusEvent<HTMLSpanElement>,
|
e: React.FocusEvent<HTMLSpanElement>,
|
||||||
id: string,
|
id: string,
|
||||||
) => {
|
) => {
|
||||||
const nuevoNombre = e.currentTarget.textContent?.trim() || ''
|
const nuevoNombre = e.currentTarget.textContent.trim() || ''
|
||||||
|
|
||||||
// Buscamos la línea original para comparar
|
// Buscamos la línea original para comparar
|
||||||
const lineaOriginal = lineas.find((l) => l.id === id)
|
const lineaOriginal = lineas.find((l) => l.id === id)
|
||||||
@@ -935,65 +937,55 @@ function MapaCurricularPage() {
|
|||||||
{/* Fila 4: Seriación (Prerrequisitos) */}
|
{/* Fila 4: Seriación (Prerrequisitos) */}
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<label className="text-xs font-bold text-slate-500 uppercase">
|
<label className="text-xs font-bold text-slate-500 uppercase">
|
||||||
Seriación (Prerrequisitos)
|
Seriación (Prerrequisito)
|
||||||
</label>
|
</label>
|
||||||
<Select
|
<Select
|
||||||
value={seriacionValue}
|
// Cambiamos a manejo de valor único basado en el ID de la columna
|
||||||
|
value={editingData.prerrequisito_asignatura_id || undefined}
|
||||||
onValueChange={(val) => {
|
onValueChange={(val) => {
|
||||||
if (val === 'none') {
|
console.log(editingData)
|
||||||
setSeriacionValue('')
|
|
||||||
return
|
setEditingData({
|
||||||
}
|
...editingData,
|
||||||
if (!editingData.prerrequisitos.includes(val)) {
|
prerrequisito_asignatura_id: val === 'none' ? null : val,
|
||||||
setEditingData({
|
})
|
||||||
...editingData,
|
|
||||||
prerrequisitos: [...editingData.prerrequisitos, val],
|
|
||||||
})
|
|
||||||
}
|
|
||||||
setSeriacionValue('')
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<SelectTrigger>
|
<SelectTrigger className="w-full bg-white">
|
||||||
<SelectValue placeholder="Seleccionar asignatura..." />
|
<SelectValue placeholder="Seleccionar asignatura..." />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectItem value="none">-- Sin Seriación --</SelectItem>
|
<SelectItem value="none">-- Sin Seriación --</SelectItem>
|
||||||
|
|
||||||
{asignaturas
|
{asignaturas
|
||||||
.filter((m) => m.id !== editingData.id)
|
.filter((asig) => {
|
||||||
.map((m) => (
|
// 1. No es la misma materia
|
||||||
<SelectItem key={m.id} value={m.id}>
|
const noEsMisma = asig.id !== editingData.id
|
||||||
{m.nombre} ({m.clave})
|
// 2. El ciclo debe ser estrictamente MENOR
|
||||||
|
const esCicloMenor =
|
||||||
|
asig.ciclo !== null &&
|
||||||
|
editingData.ciclo !== null &&
|
||||||
|
asig.ciclo < editingData.ciclo
|
||||||
|
|
||||||
|
return noEsMisma && esCicloMenor
|
||||||
|
})
|
||||||
|
.sort(
|
||||||
|
(a, b) =>
|
||||||
|
(a.ciclo || 0) - (b.ciclo || 0) ||
|
||||||
|
a.nombre.localeCompare(b.nombre),
|
||||||
|
)
|
||||||
|
.map((asig) => (
|
||||||
|
<SelectItem key={asig.id} value={asig.id}>
|
||||||
|
<span className="font-bold text-teal-600">
|
||||||
|
[C{asig.ciclo}]
|
||||||
|
</span>{' '}
|
||||||
|
{asig.nombre}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
|
|
||||||
{/* Visualización de los prerrequisitos seleccionados */}
|
{/* Visualización del Prerrequisito con el Nombre */}
|
||||||
<div className="mt-2 flex flex-wrap gap-2">
|
|
||||||
{editingData.prerrequisitos.map((pre) => (
|
|
||||||
<Badge
|
|
||||||
key={pre}
|
|
||||||
variant="secondary"
|
|
||||||
className="bg-slate-100 text-slate-600"
|
|
||||||
>
|
|
||||||
{pre}
|
|
||||||
<button
|
|
||||||
className="ml-1 hover:text-red-500"
|
|
||||||
onClick={() => {
|
|
||||||
setEditingData({
|
|
||||||
...editingData,
|
|
||||||
prerrequisitos: editingData.prerrequisitos.filter(
|
|
||||||
(p) => p !== pre,
|
|
||||||
),
|
|
||||||
})
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
×
|
|
||||||
</button>
|
|
||||||
</Badge>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Fila 5: Tipo */}
|
{/* Fila 5: Tipo */}
|
||||||
|
|||||||
@@ -166,30 +166,20 @@ function AsignaturaLayout() {
|
|||||||
onSave={(val) => handleUpdateHeader('nombre', val)}
|
onSave={(val) => handleUpdateHeader('nombre', val)}
|
||||||
/>
|
/>
|
||||||
</h1>
|
</h1>
|
||||||
|
{
|
||||||
|
// console.log(headerData),
|
||||||
|
|
||||||
|
console.log(asignaturaApi.planes_estudio?.nombre)
|
||||||
|
}
|
||||||
<div className="flex flex-wrap gap-4 text-sm text-blue-200">
|
<div className="flex flex-wrap gap-4 text-sm text-blue-200">
|
||||||
<span className="flex items-center gap-1">
|
<span className="flex items-center gap-1">
|
||||||
<GraduationCap className="h-4 w-4 shrink-0" />
|
<GraduationCap className="h-4 w-4 shrink-0" />
|
||||||
|
Pertenece al plan:{' '}
|
||||||
<span className="text-blue-100">
|
<span className="text-blue-100">
|
||||||
{(asignaturaApi.planes_estudio?.datos as DatosPlan)
|
{(asignaturaApi.planes_estudio as DatosPlan).nombre || ''}
|
||||||
.nombre || ''}
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span className="flex items-center gap-1">
|
|
||||||
<span className="text-blue-100">
|
|
||||||
{(asignaturaApi.planes_estudio?.datos as DatosPlan)
|
|
||||||
.nombre ?? ''}
|
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p className="text-sm text-blue-300">
|
|
||||||
Pertenece al plan:{' '}
|
|
||||||
<span className="cursor-pointer underline">
|
|
||||||
{asignaturaApi.planes_estudio?.nombre}
|
|
||||||
</span>
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-col items-end gap-2 text-right">
|
<div className="flex flex-col items-end gap-2 text-right">
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ export interface Asignatura {
|
|||||||
orden?: number
|
orden?: number
|
||||||
hd: number // <--- Añadir
|
hd: number // <--- Añadir
|
||||||
hi: number // <--- Añadir
|
hi: number // <--- Añadir
|
||||||
prerrequisitos: Array<string>
|
prerrequisito_asignatura_id: string | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Plan {
|
export interface Plan {
|
||||||
|
|||||||
@@ -156,6 +156,7 @@ export type Database = {
|
|||||||
plan_estudio_id: string
|
plan_estudio_id: string
|
||||||
tipo: Database['public']['Enums']['tipo_asignatura']
|
tipo: Database['public']['Enums']['tipo_asignatura']
|
||||||
tipo_origen: Database['public']['Enums']['tipo_origen'] | null
|
tipo_origen: Database['public']['Enums']['tipo_origen'] | null
|
||||||
|
prerrequisito_asignatura_id?: string
|
||||||
}
|
}
|
||||||
Insert: {
|
Insert: {
|
||||||
actualizado_en?: string
|
actualizado_en?: string
|
||||||
|
|||||||
14
staticwebapp.config.json
Normal file
14
staticwebapp.config.json
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"navigationFallback": {
|
||||||
|
"rewrite": "/index.html",
|
||||||
|
"exclude": [
|
||||||
|
"/assets/*",
|
||||||
|
"/*.css",
|
||||||
|
"/*.js",
|
||||||
|
"/*.ico",
|
||||||
|
"/*.png",
|
||||||
|
"/*.jpg",
|
||||||
|
"/*.svg"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user