feat: update AI subject generation and handling responses

- Refactor AIGenerateSubjectInput type to include optional fields for updates.
- Add handling for "asignaturas" responses in OpenAI webhook.
- Implement crear.ts for creating/updating subjects based on AI responses.
- Update tests to validate OpenAI file uploads instead of storage uploads.
This commit is contained in:
2026-02-27 11:34:08 -06:00
parent 527f3aa56f
commit 288278ea6c
10 changed files with 742 additions and 670 deletions
+176 -14
View File
@@ -2,7 +2,7 @@
"cells": [
{
"cell_type": "code",
"execution_count": null,
"execution_count": 1,
"id": "bb61ec88",
"metadata": {},
"outputs": [],
@@ -14,12 +14,12 @@
" export: true,\n",
"});\n",
"\n",
"const openai = new OpenAI();"
"const openai = new OpenAI();\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"execution_count": 2,
"id": "8baca5f2",
"metadata": {},
"outputs": [],
@@ -27,7 +27,170 @@
"import { createClient } from \"@supabase/supabase-js\";\n",
"const supabaseUrl = Deno.env.get(\"SUPABASE_URL\") || env.SUPABASE_URL;\n",
"const supabaseKey = Deno.env.get(\"SUPABASE_ANON_KEY\") || env.SUPABASE_ANON_KEY;\n",
"const supabase = createClient(supabaseUrl, supabaseKey);"
"const supabase = createClient(supabaseUrl, supabaseKey);\n"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "923b8e08",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Esperando a que la IA termine con la asignatura fe5eb09c-501b-4db6-b6d3-f02bdcfdc4b4...\n",
"Conexión WebSocket establecida. La celda está esperando cambios...\n"
]
},
{
"ename": "",
"evalue": "",
"output_type": "error",
"traceback": [
"\u001b[1;31mThe Kernel crashed while executing code in the current cell or a previous cell. \n",
"\u001b[1;31mPlease review the code in the cell(s) to identify a possible cause of the failure. \n",
"\u001b[1;31mClick <a href='https://aka.ms/vscodeJupyterKernelCrash'>here</a> for more info. \n",
"\u001b[1;31mView Jupyter <a href='command:jupyter.viewOutput'>log</a> for further details."
]
}
],
"source": [
"const ID_ASIGNATURA = \"fe5eb09c-501b-4db6-b6d3-f02bdcfdc4b4\";\n",
"console.log(\n",
" `Esperando a que la IA termine con la asignatura ${ID_ASIGNATURA}...`,\n",
");\n",
"\n",
"if (!supabase) {\n",
" console.log(\"No hay supabase\");\n",
"} else {\n",
" // 2. Armas la \"trampa\" (el listener)\n",
" await new Promise((resolve) => {\n",
" const canal = supabase\n",
" .channel(\"test-asignatura\")\n",
" .on(\n",
" \"postgres_changes\",\n",
" {\n",
" event: \"UPDATE\",\n",
" schema: \"public\",\n",
" table: \"asignaturas\",\n",
" filter: `id=eq.${ID_ASIGNATURA}`,\n",
" },\n",
" (payload) => {\n",
" const nuevoEstado = payload.new.estado;\n",
"\n",
" if (nuevoEstado !== \"generando\") {\n",
" console.log(\n",
" `¡Proceso completado! El nuevo estado es: ${nuevoEstado}`,\n",
" );\n",
"\n",
" supabase.removeChannel(canal);\n",
" resolve(true); // ¡Aquí destrabamos la celda de Jupyter!\n",
" } else {\n",
" console.log(\"Hubo un update, pero sigue generando...\");\n",
" }\n",
" },\n",
" )\n",
" .subscribe((status) => {\n",
" if (status === \"SUBSCRIBED\") {\n",
" console.log(\n",
" \"Conexión WebSocket establecida. La celda está esperando cambios...\",\n",
" );\n",
" }\n",
" });\n",
" });\n",
"\n",
" console.log(\"Script finalizado correctamente.\");\n",
"}\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "8a1509d8",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Iniciando conexión abierta y sin filtros...\n",
"Conectado. Esperando cualquier cambio en la tabla asignaturas...\n"
]
},
{
"ename": "",
"evalue": "",
"output_type": "error",
"traceback": [
"\u001b[1;31mThe Kernel crashed while executing code in the current cell or a previous cell. \n",
"\u001b[1;31mPlease review the code in the cell(s) to identify a possible cause of the failure. \n",
"\u001b[1;31mClick <a href='https://aka.ms/vscodeJupyterKernelCrash'>here</a> for more info. \n",
"\u001b[1;31mView Jupyter <a href='command:jupyter.viewOutput'>log</a> for further details."
]
}
],
"source": [
"console.log(`Iniciando conexión abierta y sin filtros...`);\n",
"\n",
"await new Promise((resolve) => {\n",
" const canal = supabase\n",
" .channel(\"test-global-asignaturas\")\n",
" .on(\n",
" \"postgres_changes\",\n",
" {\n",
" event: \"*\", // Escuchamos absolutamente TODO\n",
" schema: \"public\",\n",
" table: \"asignaturas\",\n",
" },\n",
" (payload) => {\n",
" // Imprimimos todo el objeto tal como llega del servidor\n",
" console.log(\"🚨 ¡EVENTO RECIBIDO EN CRUDO!\", payload);\n",
"\n",
" // Cerramos la conexión para liberar la celda\n",
" supabase.removeChannel(canal);\n",
" resolve(true);\n",
" },\n",
" )\n",
" .subscribe((status, err) => {\n",
" if (status === \"SUBSCRIBED\") {\n",
" console.log(\n",
" \"Conectado. Esperando cualquier cambio en la tabla asignaturas...\",\n",
" );\n",
" }\n",
" if (err) {\n",
" console.error(\"Error en el socket:\", err);\n",
" }\n",
" });\n",
"});\n",
"\n",
"console.log(\"Script finalizado.\");\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3602b930",
"metadata": {},
"outputs": [],
"source": [
"import(\"https://esm.sh/@supabase/supabase-js\").then(({ createClient }) => {\n",
" const supabase = createClient(\n",
" \"https://bxkskdxwppdlplrkidcz.supabase.co\",\n",
" \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImJ4a3NrZHh3cHBkbHBscmtpZGN6Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzIwMzU2NDMsImV4cCI6MjA4NzYxMTY0M30.RIyvmB8Ij_qrsiMn0f65awiG5VaCUA5dvb117wuU3jo\",\n",
" );\n",
"\n",
" supabase.channel(\"prueba-consola\")\n",
" .on(\"postgres_changes\", {\n",
" event: \"*\",\n",
" schema: \"public\",\n",
" table: \"asignaturas\",\n",
" }, (payload) => {\n",
" console.log(\"🔥 ¡LLEGÓ EL EVENTO DESDE SUPABASE!\", payload);\n",
" })\n",
" .subscribe((status) => console.log(\"Estado de la conexión:\", status));\n",
"});\n"
]
},
{
@@ -55,7 +218,7 @@
},
{
"cell_type": "code",
"execution_count": 21,
"execution_count": null,
"id": "8abb080e",
"metadata": {},
"outputs": [
@@ -71,12 +234,12 @@
}
],
"source": [
"plan.id"
"plan.id;\n"
]
},
{
"cell_type": "code",
"execution_count": 22,
"execution_count": null,
"id": "a382bdc6",
"metadata": {},
"outputs": [
@@ -144,7 +307,6 @@
}
],
"source": [
"\n",
"let response = await openai.responses.create({\n",
" model: \"gpt-5-mini\",\n",
" input: \"Hola mundo\",\n",
@@ -183,7 +345,7 @@
},
{
"cell_type": "code",
"execution_count": 23,
"execution_count": null,
"id": "6b2470ed",
"metadata": {},
"outputs": [
@@ -293,12 +455,12 @@
" response = await openai.responses.retrieve(response.id);\n",
"}\n",
"\n",
"response;"
"response;\n"
]
},
{
"cell_type": "code",
"execution_count": 24,
"execution_count": null,
"id": "ebb67bd8",
"metadata": {},
"outputs": [
@@ -314,12 +476,12 @@
}
],
"source": [
"response.metadata"
"response.metadata;\n"
]
},
{
"cell_type": "code",
"execution_count": 27,
"execution_count": null,
"id": "cd18fc2d",
"metadata": {},
"outputs": [
@@ -365,7 +527,7 @@
"source": [
"await supabase.from(response.metadata.tabla).update({\n",
" datos: JSON.parse(response.output_text),\n",
"}).eq(\"id\", response.metadata.id).select(\"*\").single();"
"}).eq(\"id\", response.metadata.id).select(\"*\").single();\n"
]
},
{