diff --git a/supabase/functions/_shared/estructuras.ts b/supabase/functions/_shared/estructuras.ts new file mode 100644 index 0000000..5713e34 --- /dev/null +++ b/supabase/functions/_shared/estructuras.ts @@ -0,0 +1,94 @@ +export const definicionesDeEstructurasDeColumnas = { + contenido_tematico: { + "type": "object", + "properties": { + "x-column": { + "type": "string", + "enum": [ + "contenido_tematico", + ], + }, + "x-definicion": { + "type": "array", + "items": { + "type": "object", + "properties": { + "unidad": { + "type": "integer", + }, + "titulo": { + "type": "string", + }, + "temas": { + "type": "array", + "items": { + "type": "object", + "properties": { + "nombre": { + "type": "string", + }, + "horasEstimadas": { + "type": "integer", + "description": + "Horas del tema. La suma de todas las horasEstimadas debe ser igual a las horas académicas del prompt.", + }, + }, + "required": [ + "nombre", + "horasEstimadas", + ], + "additionalProperties": false, + }, + }, + }, + "required": [ + "unidad", + "titulo", + "temas", + ], + "additionalProperties": false, + }, + }, + }, + "required": [ + "x-column", + "x-definicion", + ], + "additionalProperties": false, + }, + criterios_de_evaluacion: { + "type": "object", + "properties": { + "x-column": { + "type": "string", + "enum": [ + "criterios_de_evaluacion", + ], + }, + "x-definicion": { + "type": "array", + "items": { + "type": "object", + "properties": { + "criterio": { + "type": "string", + }, + "porcentaje": { + "type": "integer", + }, + }, + "required": [ + "criterio", + "porcentaje", + ], + "additionalProperties": false, + }, + }, + }, + "required": [ + "x-column", + "x-definicion", + ], + "additionalProperties": false, + }, +} as const; \ No newline at end of file diff --git a/supabase/functions/create-chat-conversation/lib/plan.ts b/supabase/functions/create-chat-conversation/lib/plan.ts index c5b446f..1e93e94 100644 --- a/supabase/functions/create-chat-conversation/lib/plan.ts +++ b/supabase/functions/create-chat-conversation/lib/plan.ts @@ -1,5 +1,5 @@ +import { definicionesDeEstructurasDeColumnas } from "../../_shared/estructuras.ts"; import { HttpError } from "./errors.ts"; - export function pickSchemaFields( definicion: any, campos: string[], @@ -45,17 +45,15 @@ export function pickSchemaFields( return out; } + export function pickSchemaAsignaturaFields( definicion: any, campos: string[], ) { - console.log(campos); - console.log(definicion); if (!definicion || definicion.type !== "object" || !definicion.properties) { return definicion; } - // Campos extra de control const extra = { properties: { "ai-message": { @@ -70,33 +68,36 @@ export function pickSchemaAsignaturaFields( }; const out = structuredClone(definicion); + const finalProperties: Record = {}; + const finalRequired: string[] = []; - // FILTRADO: Si el usuario pidió campos específicos, filtramos. - // Si campos está vacío, la lógica de Planes devolvería todo, - // pero aquí asumimos que siempre vienen campos si isStructured es true. - const entries = Object.entries(out.properties).filter(([k]) => - campos.includes(k) - ); - - out.properties = Object.fromEntries(entries); - - if (Array.isArray(out.required)) { - out.required = out.required.filter((k: string) => campos.includes(k)); - } + campos.forEach((key) => { + + // REVISIÓN DE ESTRUCTURA ESPECIAL + if (key in definicionesDeEstructurasDeColumnas) { + // Extraemos solo el esquema del array que vive en 'x-definicion' + // Esto hace que la IA reciba el esquema del array directamente + const especial = (definicionesDeEstructurasDeColumnas as any)[key]; + + finalProperties[key] = especial.properties["x-definicion"]; + finalRequired.push(key); + } + // CAMPOS NORMALES (los que están en 'datos') + else if (out.properties[key]) { + finalProperties[key] = out.properties[key]; + if (out.required?.includes(key)) { + finalRequired.push(key); + } + } + }); - // SIEMPRE agregamos los campos de control (ai-message, is_refusal) - out.properties = { ...out.properties, ...extra.properties }; - - // Reconstruimos los requeridos para que OpenAI sepa que DEBE llenar los campos solicitados - out.required = Array.isArray(out.required) - ? [...new Set([...out.required, ...Object.keys(extra.properties)])] - : Object.keys(extra.properties); - - // Forzamos que no invente campos + out.properties = { ...finalProperties, ...extra.properties }; + out.required = [...new Set([...finalRequired, ...Object.keys(extra.properties)])]; out.additionalProperties = false; return out; } + export function safePlanForPrompt(plan: any) { const copy = structuredClone(plan); if (copy?.estructuras_plan) delete copy.estructuras_plan;