This repository has been archived on 2026-01-21. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
Acad-IA/src/components/planes/AddAsignaturaButton.tsx

148 lines
7.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useState } from "react"
import { Button } from "@/components/ui/button"
import * as Icons from "lucide-react"
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from "@/components/ui/dialog"
import { Input } from "@/components/ui/input"
import { Textarea } from "@/components/ui/textarea"
import { AuroraButton } from "@/components/effect/aurora-button"
import confetti from "canvas-confetti"
import { supabase, useSupabaseAuth } from "@/auth/supabase"
import { Field } from "./Field"
import { Tabs, TabsContent, TabsList, TabsTrigger } from "../ui/tabs"
import { useRouter } from "@tanstack/react-router"
export function AddAsignaturaButton({ planId, onAdded }: { planId: string; onAdded?: () => void }) {
const router = useRouter()
const supabaseAuth = useSupabaseAuth()
const [open, setOpen] = useState(false)
const [saving, setSaving] = useState(false)
const [mode, setMode] = useState<"manual" | "ia">("manual")
const [f, setF] = useState({ nombre: "", clave: "", tipo: "", semestre: "", creditos: "", horas_teoricas: "", horas_practicas: "", objetivos: "" })
const [iaPrompt, setIaPrompt] = useState("")
const [iaSemestre, setIaSemestre] = useState("")
const toNull = (s: string) => s.trim() ? s : null
const toNum = (s: string) => s.trim() ? Number(s) || null : null
const canManual = f.nombre.trim().length > 0
const canIA = iaPrompt.trim().length > 0
const canSubmit = mode === "manual" ? canManual : canIA
async function createManual() {
if (!canManual) return
setSaving(true)
const payload = {
plan_id: planId,
nombre: f.nombre.trim(),
clave: toNull(f.clave),
tipo: toNull(f.tipo),
semestre: toNum(f.semestre),
creditos: toNum(f.creditos),
horas_teoricas: toNum(f.horas_teoricas),
horas_practicas: toNum(f.horas_practicas),
objetivos: toNull(f.objetivos),
contenidos: [], bibliografia: [], criterios_evaluacion: null,
}
const { error,data } = await supabase.from("asignaturas").insert([payload]).select().single()
console.log(data);
router.invalidate()
router.navigate({
to: "/asignatura/$asignaturaId",
params: { asignaturaId: data.id },
})
setSaving(false)
if (error) { alert(error.message); return }
setOpen(false)
onAdded?.()
}
async function createWithAI() {
if (!canIA) return
setSaving(true)
// inserte la asignatura generada directamente
// obtengas el uuid que se insertó
try {
const res = await fetch(`${import.meta.env.VITE_BACK_ORIGIN}/api/generar/asignatura`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ planEstudiosId: planId, prompt: iaPrompt, semestre: iaSemestre.trim() ? Number(iaSemestre) : undefined, insert: true, uuid: supabaseAuth.user?.id }),
})
if (!res.ok) throw new Error(await res.text())
const data = await res.json()
console.log("Asignatura generada:", data)
const asignaturaId = data.asignaturaId || data.insertResult?.id
if (!asignaturaId) throw new Error("No se recibió el ID de la asignatura generada")
confetti({ particleCount: 120, spread: 80, origin: { y: 0.6 } })
setOpen(false)
router.invalidate()
router.navigate({
to: "/asignatura/$asignaturaId",
params: { asignaturaId },
})
onAdded?.()
// qc.invalidateQueries({ queryKey: asignaturaKeys.preview(planId) })
// qc.invalidateQueries({ queryKey: asignaturaKeys.count(planId) })
} catch (e: any) {
alert(e?.message ?? "Error al generar la asignatura")
} finally { setSaving(false) }
}
const submit = () => (mode === "manual" ? createManual() : createWithAI())
return (
<>
<Button onClick={() => setOpen(true)}>
<Icons.Plus className="w-4 h-4 mr-2" /> Nueva asignatura
</Button>
<Dialog open={open} onOpenChange={setOpen}>
<DialogContent className="w-[min(92vw,760px)]">
<DialogHeader>
<DialogTitle className="font-mono" >Nueva asignatura</DialogTitle>
<DialogDescription>Elige cómo crearla: manual o generada por IA.</DialogDescription>
</DialogHeader>
<Tabs value={mode} onValueChange={v => setMode(v as "manual" | "ia")} className="w-full">
<TabsList className="grid w-full grid-cols-2 rounded-xl border bg-neutral-50 p-1" aria-label="Modo de creación">
<TabsTrigger value="manual" className="rounded-lg data-[state=active]:bg-white data-[state=active]:shadow-sm">
<Icons.PencilLine className="h-4 w-4 mr-2" /> Manual
</TabsTrigger>
<TabsTrigger value="ia" className="rounded-lg data-[state=active]:bg-white data-[state=active]:shadow-sm">
<Icons.Sparkles className="h-4 w-4 mr-2" /> Generado por IA
</TabsTrigger>
</TabsList>
<TabsContent value="manual" className="mt-4">
<div className="grid gap-3 sm:grid-cols-2">
<Field label="Nombre"><Input value={f.nombre} onChange={e => setF(s => ({ ...s, nombre: e.target.value }))} /></Field>
<Field label="Clave"><Input value={f.clave} onChange={e => setF(s => ({ ...s, clave: e.target.value }))} /></Field>
<Field label="Tipo"><Input value={f.tipo} onChange={e => setF(s => ({ ...s, tipo: e.target.value }))} placeholder="Obligatoria / Optativa / Taller…" /></Field>
<Field label="Semestre"><Input value={f.semestre} onChange={e => setF(s => ({ ...s, semestre: e.target.value }))} placeholder="110" /></Field>
<Field label="Créditos"><Input value={f.creditos} onChange={e => setF(s => ({ ...s, creditos: e.target.value }))} /></Field>
<Field label="Horas teóricas"><Input value={f.horas_teoricas} onChange={e => setF(s => ({ ...s, horas_teoricas: e.target.value }))} /></Field>
<Field label="Horas prácticas"><Input value={f.horas_practicas} onChange={e => setF(s => ({ ...s, horas_practicas: e.target.value }))} /></Field>
<div className="sm:col-span-2"><Field label="Objetivo (opcional)"><Textarea value={f.objetivos} onChange={e => setF(s => ({ ...s, objetivos: e.target.value }))} className="min-h-[90px]" /></Field></div>
</div>
</TabsContent>
<TabsContent value="ia" className="mt-4">
<div className="grid gap-3 sm:grid-cols-2">
<div className="sm:col-span-2"><Field label="Indica el enfoque / requisitos"><Textarea value={iaPrompt} onChange={e => setIaPrompt(e.target.value)} className="min-h-[120px]" placeholder="Ej.: Diseña una materia de Programación Web con proyectos, evaluación por rúbricas y bibliografía actual…" /></Field></div>
<Field label="Periodo (opcional)"><Input value={iaSemestre} onChange={e => setIaSemestre(e.target.value)} placeholder="110" /></Field>
</div>
</TabsContent>
</Tabs>
<DialogFooter>
<Button variant="outline" onClick={() => setOpen(false)}>Cancelar</Button>
<AuroraButton onClick={submit} disabled={saving || !canSubmit}>
{saving ? (mode === "manual" ? "Guardando…" : "Generando…") : (mode === "manual" ? "Crear" : "Generar e insertar")}
</AuroraButton>
</DialogFooter>
</DialogContent>
</Dialog>
</>
)
}