diff --git a/deno.json b/deno.json
index 60f816d..2128fe4 100644
--- a/deno.json
+++ b/deno.json
@@ -7,6 +7,10 @@
   "imports": {
     "@openai/openai": "jsr:@openai/openai@^6.16.0",
     "@zod/zod": "jsr:@zod/zod@^4.3.5",
-    "@david/dax": "jsr:@david/dax@^0.44.2"
+    "@david/dax": "jsr:@david/dax@^0.44.2",
+    "zod": "https://deno.land/x/zod@v3.22.4/mod.ts",
+    "openai": "npm:openai@6.16.0",
+    "@supabase/supabase-js": "npm:@supabase/supabase-js@2",
+    "@supabase/functions-js/edge-runtime.d.ts": "jsr:@supabase/functions-js@^2/edge-runtime.d.ts"
   }
 }
diff --git a/diff.txt b/diff.txt
index b2704fc..e69de29 100644
Binary files a/diff.txt and b/diff.txt differ
diff --git a/supabase/functions/_shared/database.types.ts b/supabase/functions/_shared/database.types.ts
index fa3ef8e..bf4b960 100644
--- a/supabase/functions/_shared/database.types.ts
+++ b/supabase/functions/_shared/database.types.ts
@@ -4,7 +4,7 @@
     | boolean
     | null
     | { [key: string]: Json | undefined }
-    | Json[];
+    | Array<Json>;
 
 export type Database = {
     // Allows to automatically instantiate createClient with right options
@@ -90,6 +90,7 @@ export type Database = {
                 Row: {
                     actualizado_en: string;
                     actualizado_por: string | null;
+                    asignatura_hash: string | null;
                     codigo: string | null;
                     contenido_tematico: Json;
                     creado_en: string;
@@ -97,8 +98,8 @@ export type Database = {
                     creditos: number;
                     datos: Json;
                     estructura_id: string | null;
-                    facultad_propietaria_id: string | null;
-                    horas_semana: number | null;
+                    horas_academicas: number | null;
+                    horas_independientes: number | null;
                     id: string;
                     linea_plan_id: string | null;
                     meta_origen: Json;
@@ -114,6 +115,7 @@ export type Database = {
                 Insert: {
                     actualizado_en?: string;
                     actualizado_por?: string | null;
+                    asignatura_hash?: string | null;
                     codigo?: string | null;
                     contenido_tematico?: Json;
                     creado_en?: string;
@@ -121,8 +123,8 @@ export type Database = {
                     creditos: number;
                     datos?: Json;
                     estructura_id?: string | null;
-                    facultad_propietaria_id?: string | null;
-                    horas_semana?: number | null;
+                    horas_academicas?: number | null;
+                    horas_independientes?: number | null;
                     id?: string;
                     linea_plan_id?: string | null;
                     meta_origen?: Json;
@@ -138,6 +140,7 @@ export type Database = {
                 Update: {
                     actualizado_en?: string;
                     actualizado_por?: string | null;
+                    asignatura_hash?: string | null;
                     codigo?: string | null;
                     contenido_tematico?: Json;
                     creado_en?: string;
@@ -145,8 +148,8 @@ export type Database = {
                     creditos?: number;
                     datos?: Json;
                     estructura_id?: string | null;
-                    facultad_propietaria_id?: string | null;
-                    horas_semana?: number | null;
+                    horas_academicas?: number | null;
+                    horas_independientes?: number | null;
                     id?: string;
                     linea_plan_id?: string | null;
                     meta_origen?: Json;
@@ -181,14 +184,6 @@ export type Database = {
                         referencedRelation: "estructuras_asignatura";
                         referencedColumns: ["id"];
                     },
-                    {
-                        foreignKeyName:
-                            "asignaturas_facultad_propietaria_id_fkey";
-                        columns: ["facultad_propietaria_id"];
-                        isOneToOne: false;
-                        referencedRelation: "facultades";
-                        referencedColumns: ["id"];
-                    },
                     {
                         foreignKeyName: "asignaturas_linea_plan_fk_compuesta";
                         columns: ["linea_plan_id", "plan_estudio_id"];
@@ -332,8 +327,8 @@ export type Database = {
                     cambiado_por: string | null;
                     campo: string | null;
                     id: string;
-                    interaccion_ia_id: string | null;
                     plan_estudio_id: string;
+                    response_id: string | null;
                     tipo: Database["public"]["Enums"]["tipo_cambio"];
                     valor_anterior: Json | null;
                     valor_nuevo: Json | null;
@@ -343,8 +338,8 @@ export type Database = {
                     cambiado_por?: string | null;
                     campo?: string | null;
                     id?: string;
-                    interaccion_ia_id?: string | null;
                     plan_estudio_id: string;
+                    response_id?: string | null;
                     tipo: Database["public"]["Enums"]["tipo_cambio"];
                     valor_anterior?: Json | null;
                     valor_nuevo?: Json | null;
@@ -354,8 +349,8 @@ export type Database = {
                     cambiado_por?: string | null;
                     campo?: string | null;
                     id?: string;
-                    interaccion_ia_id?: string | null;
                     plan_estudio_id?: string;
+                    response_id?: string | null;
                     tipo?: Database["public"]["Enums"]["tipo_cambio"];
                     valor_anterior?: Json | null;
                     valor_nuevo?: Json | null;
@@ -368,20 +363,6 @@ export type Database = {
                         referencedRelation: "usuarios_app";
                         referencedColumns: ["id"];
                     },
-                    {
-                        foreignKeyName: "cambios_plan_plan_estudio_id_fkey";
-                        columns: ["plan_estudio_id"];
-                        isOneToOne: false;
-                        referencedRelation: "planes_estudio";
-                        referencedColumns: ["id"];
-                    },
-                    {
-                        foreignKeyName: "cambios_plan_plan_estudio_id_fkey";
-                        columns: ["plan_estudio_id"];
-                        isOneToOne: false;
-                        referencedRelation: "plantilla_plan";
-                        referencedColumns: ["plan_estudio_id"];
-                    },
                 ];
             };
             carreras: {
@@ -708,6 +689,7 @@ export type Database = {
                     actualizado_en: string;
                     actualizado_por: string | null;
                     carrera_id: string;
+                    conversation_id: string | null;
                     creado_en: string;
                     creado_por: string | null;
                     datos: Json;
@@ -719,6 +701,7 @@ export type Database = {
                     nombre: string;
                     nombre_search: string | null;
                     numero_ciclos: number;
+                    plan_hash: string | null;
                     tipo_ciclo: Database["public"]["Enums"]["tipo_ciclo"];
                     tipo_origen:
                         | Database["public"]["Enums"]["tipo_origen"]
@@ -729,6 +712,7 @@ export type Database = {
                     actualizado_en?: string;
                     actualizado_por?: string | null;
                     carrera_id: string;
+                    conversation_id?: string | null;
                     creado_en?: string;
                     creado_por?: string | null;
                     datos?: Json;
@@ -740,6 +724,7 @@ export type Database = {
                     nombre: string;
                     nombre_search?: string | null;
                     numero_ciclos: number;
+                    plan_hash?: string | null;
                     tipo_ciclo: Database["public"]["Enums"]["tipo_ciclo"];
                     tipo_origen?:
                         | Database["public"]["Enums"]["tipo_origen"]
@@ -750,6 +735,7 @@ export type Database = {
                     actualizado_en?: string;
                     actualizado_por?: string | null;
                     carrera_id?: string;
+                    conversation_id?: string | null;
                     creado_en?: string;
                     creado_por?: string | null;
                     datos?: Json;
@@ -761,6 +747,7 @@ export type Database = {
                     nombre?: string;
                     nombre_search?: string | null;
                     numero_ciclos?: number;
+                    plan_hash?: string | null;
                     tipo_ciclo?: Database["public"]["Enums"]["tipo_ciclo"];
                     tipo_origen?:
                         | Database["public"]["Enums"]["tipo_origen"]
@@ -1140,7 +1127,7 @@ export type Database = {
             fuente_cambio: "HUMANO" | "IA";
             nivel_plan_estudio:
                 | "Licenciatura"
-                | "MaestrÔö£┬ía"
+                | "Maestr├¡a"
                 | "Doctorado"
                 | "Especialidad"
                 | "Diplomado"
@@ -1163,7 +1150,8 @@ export type Database = {
                 | "ACTUALIZACION_MAPA"
                 | "TRANSICION_ESTADO"
                 | "OTRO"
-                | "CREACION";
+                | "CREACION"
+                | "ACTUALIZACION";
             tipo_ciclo: "Semestre" | "Cuatrimestre" | "Trimestre" | "Otro";
             tipo_estructura_plan: "CURRICULAR" | "NO_CURRICULAR";
             tipo_fuente_bibliografia: "MANUAL" | "BIBLIOTECA";
@@ -1343,7 +1331,7 @@ export const Constants = {
             fuente_cambio: ["HUMANO", "IA"],
             nivel_plan_estudio: [
                 "Licenciatura",
-                "MaestrÔö£┬ía",
+                "Maestr├¡a",
                 "Doctorado",
                 "Especialidad",
                 "Diplomado",
@@ -1370,6 +1358,7 @@ export const Constants = {
                 "TRANSICION_ESTADO",
                 "OTRO",
                 "CREACION",
+                "ACTUALIZACION",
             ],
             tipo_ciclo: ["Semestre", "Cuatrimestre", "Trimestre", "Otro"],
             tipo_estructura_plan: ["CURRICULAR", "NO_CURRICULAR"],
diff --git a/supabase/functions/ai-generate-plan/index.ts b/supabase/functions/ai-generate-plan/index.ts
index 0f3185b..1db2eef 100644
--- a/supabase/functions/ai-generate-plan/index.ts
+++ b/supabase/functions/ai-generate-plan/index.ts
@@ -3,10 +3,10 @@
 // This enables autocomplete, go to definition, etc.
 
 // Setup type definitions for built-in Supabase Runtime APIs
-import "jsr:@supabase/functions-js/edge-runtime.d.ts";
+import "@supabase/functions-js/edge-runtime.d.ts";
 import { corsHeaders } from "../_shared/cors.ts";
 import { HttpError, sendError, sendSuccess } from "../_shared/utils.ts";
-import { createClient } from "npm:@supabase/supabase-js@2";
+import { createClient } from "@supabase/supabase-js";
 import type { Database, Json } from "../_shared/database.types.ts";
 import type { AIGeneratePlanInput } from "./types.ts";
 import { z } from "zod";
diff --git a/supabase/functions/ai-generate-subject/deno.json b/supabase/functions/ai-generate-subject/deno.json
index f6ca845..8b204ca 100644
--- a/supabase/functions/ai-generate-subject/deno.json
+++ b/supabase/functions/ai-generate-subject/deno.json
@@ -1,3 +1,8 @@
 {
-  "imports": {}
+  "imports": {
+    "zod": "https://deno.land/x/zod@v3.22.4/mod.ts",
+    "openai": "npm:openai@6.16.0",
+    "@supabase/supabase-js": "npm:@supabase/supabase-js@2",
+    "@supabase/functions-js/edge-runtime.d.ts": "jsr:@supabase/functions-js@^2/edge-runtime.d.ts"
+  }
 }
diff --git a/supabase/functions/ai-generate-subject/index.ts b/supabase/functions/ai-generate-subject/index.ts
index e974ea3..9045854 100644
--- a/supabase/functions/ai-generate-subject/index.ts
+++ b/supabase/functions/ai-generate-subject/index.ts
@@ -1,12 +1,15 @@
-import "jsr:@supabase/functions-js/edge-runtime.d.ts";
+import "@supabase/functions-js/edge-runtime.d.ts";
 import { corsHeaders } from "../_shared/cors.ts";
 import { HttpError, sendError, sendSuccess } from "../_shared/utils.ts";
-import { createClient } from "npm:@supabase/supabase-js@2";
+import { createClient } from "@supabase/supabase-js";
 import type { Database, Json } from "../_shared/database.types.ts";
+import { z } from "zod";
+import {
+  OpenAIService,
+  type StructuredResponseOptions,
+} from "../_shared/openai-service.ts";
 
-console.log("Hello from Functions!");
-
-Deno.serve(async (req) => {
+Deno.serve(async (req: Request): Promise<Response> => {
   const url = new URL(req.url);
   const functionName = url.pathname.split("/").pop();
   console.log(
@@ -18,6 +21,52 @@ Deno.serve(async (req) => {
   }
 
   try {
+    const method = req.method;
+    if (method !== "POST") {
+      console.error(
+        `[${
+          new Date().toISOString()
+        }][${functionName}]: Invalid method: ${method}`,
+      );
+      throw new HttpError(
+        405,
+        "M├®todo no permitido.",
+        "METHOD_NOT_ALLOWED",
+        { method },
+      );
+    }
+
+    const authHeaderRaw = req.headers.get("Authorization") ??
+      req.headers.get("authorization");
+    if (!authHeaderRaw) {
+      console.error(
+        `[${
+          new Date().toISOString()
+        }][${functionName}]: Missing Authorization header`,
+      );
+      throw new HttpError(
+        401,
+        "No autorizado.",
+        "UNAUTHORIZED",
+        { reason: "missing_authorization_header" },
+      );
+    }
+
+    const contentType = (req.headers.get("content-type") || "").toLowerCase();
+    if (!contentType.startsWith("multipart/form-data")) {
+      console.error(
+        `[${
+          new Date().toISOString()
+        }][${functionName}]: Unsupported content type: ${contentType}`,
+      );
+      throw new HttpError(
+        415,
+        "Content-Type no soportado.",
+        "UNSUPPORTED_MEDIA_TYPE",
+        { contentType },
+      );
+    }
+
     const SUPABASE_URL = Deno.env.get("SUPABASE_URL");
     const SUPABASE_ANON_KEY = Deno.env.get("SUPABASE_ANON_KEY");
     if (!SUPABASE_URL || !SUPABASE_ANON_KEY) {
@@ -59,20 +108,207 @@ Deno.serve(async (req) => {
       SERVICE_ROLE_KEY,
     );
 
-    // INICIO DE CODIGO PARA DEBBUGGING
+    const formData = await req.formData();
+    const validation = parseAndValidate(formData);
+    if (!validation.success) {
+      console.error(
+        `[${new Date().toISOString()}][${functionName}]: Validation errors:`,
+        validation.errors,
+      );
+      const message = validation.errors
+        .map((e, i) => `${i + 1}. ${e}`)
+        .join("\n");
+      throw new HttpError(
+        422,
+        message,
+        "VALIDATION_ERROR",
+        { errors: validation.errors },
+      );
+    }
+
+    const payload = validation.data;
+
+    const { data: estructura, error: estructuraError } = await supabaseService
+      .from("estructuras_asignatura")
+      .select("id,nombre,definicion,version")
+      .eq("id", payload.datosBasicos.estructuraId)
+      .single();
+    if (estructuraError) {
+      const maybeCode = (estructuraError as { code?: string }).code;
+      if (maybeCode === "PGRST116") {
+        throw new HttpError(
+          404,
+          "No se encontr├│ la estructura de la asignatura.",
+          "NOT_FOUND",
+          {
+            table: "estructuras_asignatura",
+            id: payload.datosBasicos.estructuraId,
+          },
+        );
+      }
+      throw new HttpError(
+        500,
+        "No se pudo obtener la estructura de la asignatura.",
+        "SUPABASE_QUERY_FAILED",
+        estructuraError,
+      );
+    }
+
+    const systemPrompt =
+      "Eres un asistente experto en dise├▒o curricular. Responde ├║nicamente con JSON v├ílido que cumpla estrictamente el JSON Schema proporcionado.";
+
+    const userPrompt =
+      `Genera un borrador completo completo de una ASIGNATURA con base en lo siguiente:\n` +
+      `- Nombre de la asignatura: ${payload.datosBasicos.nombre}\n` +
+      `- C├│digo (clave de la asignatura): ${
+        payload.datosBasicos.codigo ?? "(no especificado)"
+      }\n` +
+      `- Tipo: ${payload.datosBasicos.tipo ?? "(no especificado)"}\n` +
+      `- N├║mero de cr├®ditos: ${payload.datosBasicos.creditos}\n` +
+      `- Horas acad├®micas: ${
+        payload.datosBasicos.horasAcademicas ?? "(no especificado)"
+      }\n` +
+      `- Horas independientes: ${
+        payload.datosBasicos.horasIndependientes ?? "(no especificado)"
+      }\n` +
+      `- Descripci├│n del enfoque acad├®mico (sobre el contenido de la respuesta generada): ${
+        payload.iaConfig?.descripcionEnfoqueAcademico ?? "(ninguna)"
+      }\n` +
+      `- Instrucciones adicionales (sobre el formato de la respuesta generada): ${
+        payload.iaConfig?.instruccionesAdicionalesIA ?? "(ninguna)"
+      }`;
+
+    const schemaDef: Record<string, unknown> =
+      typeof estructura?.definicion === "object" &&
+        estructura?.definicion !== null
+        ? (estructura.definicion as Record<string, unknown>)
+        : {};
+
+    const aiStructuredPayload: StructuredResponseOptions = {
+      model: "gpt-5-nano",
+      input: [
+        { role: "system", content: systemPrompt },
+        { role: "user", content: userPrompt },
+      ],
+      text: {
+        format: {
+          type: "json_schema",
+          name: "asignatura_contenido_tematico",
+          schema: schemaDef,
+          strict: true,
+        },
+      },
+    };
+
+    const svc = OpenAIService.fromEnv();
+    if (!(svc instanceof OpenAIService)) {
+      throw new HttpError(
+        500,
+        "Configuraci├│n del servidor incompleta.",
+        "OPENAI_MISCONFIGURED",
+        svc,
+      );
+    }
+
+    const aiResult = await svc.createStructuredResponse(
+      aiStructuredPayload,
+      payload.archivosAdjuntos,
+    );
+    if (!aiResult.ok) {
+      const status = aiResult.code === "MissingEnv" ? 500 : 502;
+      throw new HttpError(
+        status,
+        "No se pudo generar la asignatura con IA.",
+        "OPENAI_REQUEST_FAILED",
+        aiResult,
+      );
+    }
+
+    let aiOutput = aiResult.output ?? null;
+    if (aiOutput == null && aiResult.outputText) {
+      try {
+        aiOutput = JSON.parse(aiResult.outputText);
+      } catch {
+        throw new HttpError(
+          502,
+          "La respuesta de la IA no es JSON v├ílido.",
+          "OPENAI_INVALID_JSON",
+          { outputText: aiResult.outputText },
+        );
+      }
+    }
+    if (!aiOutput) {
+      throw new HttpError(
+        502,
+        "La respuesta de la IA no contiene salida estructurada.",
+        "OPENAI_MISSING_STRUCTURED_OUTPUT",
+        { outputText: aiResult.outputText ?? null },
+      );
+    }
+
+    const aiOutputJson: Json = aiOutput as unknown as Json;
+
+    const asignaturaInsert:
+      Database["public"]["Tables"]["asignaturas"]["Insert"] = {
+        plan_estudio_id: payload.plan_estudio_id,
+        estructura_id: payload.datosBasicos.estructuraId,
+        nombre: payload.datosBasicos.nombre,
+        codigo: payload.datosBasicos.codigo ?? null,
+        creditos: payload.datosBasicos.creditos,
+        horas_academicas: payload.datosBasicos.horasAcademicas ?? null,
+        horas_independientes: payload.datosBasicos.horasIndependientes ?? null,
+        tipo: (payload.datosBasicos.tipo ?? undefined) as Database["public"][
+          "Tables"
+        ]["asignaturas"]["Insert"]["tipo"],
+        tipo_origen: "IA",
+        datos: aiOutputJson,
+        meta_origen: {
+          generado_por: "ai_generate_subject",
+          ai: {
+            responseId: aiResult.responseId ?? null,
+            conversationId: aiResult.conversationId ?? null,
+            model: aiResult.model,
+            usage: aiResult.usage ?? null,
+          },
+          referencias: {
+            archivosReferenciaIds: payload.iaConfig?.archivosReferencia ?? null,
+            repositoriosReferencia: payload.iaConfig?.repositoriosReferencia ??
+              null,
+          },
+          iaConfig: {
+            descripcionEnfoqueAcademico:
+              payload.iaConfig?.descripcionEnfoqueAcademico ?? null,
+            instruccionesAdicionalesIA:
+              payload.iaConfig?.instruccionesAdicionalesIA ?? null,
+          },
+        } as unknown as Json,
+      };
+
+    const { data: asignatura, error: asignaturaError } = await supabaseService
+      .from("asignaturas")
+      .insert(asignaturaInsert)
+      .select(
+        "id,nombre,codigo,tipo,creditos,horas_academicas,horas_independientes,estructura_id,plan_estudio_id,contenido_tematico,meta_origen,creado_en,actualizado_en",
+      )
+      .single();
+
+    if (asignaturaError) {
+      const maybeCode = (asignaturaError as { code?: string }).code;
+      const status = maybeCode ? 409 : 500;
+      throw new HttpError(
+        status,
+        "No se pudo guardar la asignatura.",
+        "SUPABASE_INSERT_FAILED",
+        { ...asignaturaError, code: maybeCode },
+      );
+    }
+
     console.log(
       `[${
         new Date().toISOString()
       }][${functionName}]: Request processed successfully`,
     );
-    const { data: asignatura_debug, error: asignatura_error } =
-      await supabaseService
-        .from("asignaturas")
-        .select("*")
-        .eq("id", "9d4dda6a-488f-428a-8a07-38081592a641")
-        .single();
-    return sendSuccess(asignatura_debug);
-    // FIN DE CODIGO PARA DEBBUGGING
+    return sendSuccess(asignatura);
   } catch (error) {
     if (error instanceof HttpError) {
       console.error(
@@ -105,3 +341,83 @@ Deno.serve(async (req) => {
     );
   }
 });
+
+const jsonFromString = <T extends z.ZodTypeAny>(schema: T) =>
+  z.string().transform((str, ctx) => {
+    try {
+      return JSON.parse(str);
+    } catch {
+      ctx.addIssue({
+        code: z.ZodIssueCode.custom,
+        message: "El formato no es un JSON v├ílido",
+      });
+      return z.NEVER;
+    }
+  }).pipe(schema);
+
+const jsonFromStringOptional = <T extends z.ZodTypeAny>(schema: T) =>
+  z.string().optional().default("{}").transform((str, ctx) => {
+    try {
+      return JSON.parse(str);
+    } catch {
+      ctx.addIssue({
+        code: z.ZodIssueCode.custom,
+        message: "El formato no es un JSON v├ílido",
+      });
+      return z.NEVER;
+    }
+  }).pipe(schema);
+
+const DatosBasicosSchema = z.object({
+  nombre: z.string().min(1, "El nombre es requerido"),
+  codigo: z.string().min(1).optional().nullable(),
+  tipo: z.union([z.string().min(1), z.null()]).optional().transform((v) =>
+    v ?? null
+  ),
+  creditos: z.number({ invalid_type_error: "Cr├®ditos debe ser num├®rico" })
+    .positive("Cr├®ditos debe ser >= 0"),
+  horasAcademicas: z.number().int().nonnegative().optional().nullable(),
+  horasIndependientes: z.number().int().nonnegative().optional().nullable(),
+  estructuraId: z.string().uuid("estructuraId debe ser un UUID"),
+});
+
+const IAConfigSchema = z.object({
+  descripcionEnfoqueAcademico: z.string().optional(),
+  instruccionesAdicionalesIA: z.string().optional(),
+  archivosReferencia: z.array(z.string().uuid()).optional(),
+  repositoriosReferencia: z.array(z.string().uuid()).optional(),
+}).partial();
+
+const SolicitudSchema = z.object({
+  plan_estudio_id: z.string().uuid("plan_estudio_id debe ser un UUID"),
+  datosBasicos: jsonFromString(DatosBasicosSchema),
+  iaConfig: jsonFromStringOptional(IAConfigSchema),
+  archivosAdjuntos: z.array(z.instanceof(File)).optional().default([]),
+});
+
+type EdgeAIGenerateSubjectInput = z.infer<typeof SolicitudSchema>;
+
+function parseAndValidate(
+  formData: FormData,
+): { success: true; data: EdgeAIGenerateSubjectInput } | {
+  success: false;
+  errors: string[];
+} {
+  const rawInput = {
+    plan_estudio_id: formData.get("plan_estudio_id"),
+    datosBasicos: formData.get("datosBasicos"),
+    iaConfig: formData.get("iaConfig"),
+    archivosAdjuntos: formData.getAll("archivosAdjuntos"),
+  };
+
+  const result = SolicitudSchema.safeParse(rawInput);
+  if (!result.success) {
+    const errors = result.error.errors.map((issue) => {
+      const path = issue.path.length ? `${issue.path.join(".")}: ` : "";
+      return `${path}${issue.message}`;
+    });
+    return { success: false, errors };
+  }
+
+  return { success: true, data: result.data };
+}
