diff --git a/deno.json b/deno.json index aedfed2..c46e55a 100644 --- a/deno.json +++ b/deno.json @@ -3,5 +3,6 @@ "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" } } diff --git a/deno.lock b/deno.lock index 8af7c40..47882cb 100644 --- a/deno.lock +++ b/deno.lock @@ -1,11 +1,22 @@ { "version": "5", "specifiers": { + "jsr:@david/console-static-text@0.3": "0.3.0", + "jsr:@david/dax@~0.44.2": "0.44.2", + "jsr:@david/path@0.2": "0.2.0", + "jsr:@david/which@~0.4.1": "0.4.1", "jsr:@openai/openai@*": "6.9.0", "jsr:@openai/openai@^6.16.0": "6.16.0", "jsr:@std/assert@1": "1.0.16", + "jsr:@std/bytes@^1.0.5": "1.0.6", "jsr:@std/dotenv@*": "0.225.6", + "jsr:@std/fmt@1": "1.0.9", + "jsr:@std/fs@1": "1.0.22", + "jsr:@std/fs@^1.0.20": "1.0.22", "jsr:@std/internal@^1.0.12": "1.0.12", + "jsr:@std/io@0.225": "0.225.2", + "jsr:@std/path@1": "1.1.4", + "jsr:@std/path@^1.1.4": "1.1.4", "jsr:@supabase/functions-js@*": "2.90.1", "jsr:@zod/zod@^4.3.5": "4.3.5", "npm:@supabase/supabase-js@^2.90.1": "2.90.1", @@ -17,6 +28,31 @@ "npm:zod@3": "3.25.76" }, "jsr": { + "@david/console-static-text@0.3.0": { + "integrity": "2dfb46ecee525755f7989f94ece30bba85bd8ffe3e8666abc1bf926e1ee0698d" + }, + "@david/dax@0.44.2": { + "integrity": "26f5985f66a4340d55fb05ca90a0063bb5f0d670a326e14cb33a974aafcbb8d9", + "dependencies": [ + "jsr:@david/console-static-text", + "jsr:@david/path", + "jsr:@david/which", + "jsr:@std/fmt", + "jsr:@std/fs@^1.0.20", + "jsr:@std/io", + "jsr:@std/path@1" + ] + }, + "@david/path@0.2.0": { + "integrity": "f2d7aa7f02ce5a55e27c09f9f1381794acb09d328f8d3c8a2e3ab3ffc294dccd", + "dependencies": [ + "jsr:@std/fs@1", + "jsr:@std/path@1" + ] + }, + "@david/which@0.4.1": { + "integrity": "896a682b111f92ab866cc70c5b4afab2f5899d2f9bde31ed00203b9c250f225e" + }, "@openai/openai@6.9.0": { "integrity": "d1828101f5a782a75c25805c4e06c1316d9c19f422d84755fb908046184c3ef6" }, @@ -32,12 +68,37 @@ "jsr:@std/internal" ] }, + "@std/bytes@1.0.6": { + "integrity": "f6ac6adbd8ccd99314045f5703e23af0a68d7f7e58364b47d2c7f408aeb5820a" + }, "@std/dotenv@0.225.6": { "integrity": "1d6f9db72f565bd26790fa034c26e45ecb260b5245417be76c2279e5734c421b" }, + "@std/fmt@1.0.9": { + "integrity": "2487343e8899fb2be5d0e3d35013e54477ada198854e52dd05ed0422eddcabe0" + }, + "@std/fs@1.0.22": { + "integrity": "de0f277a58a867147a8a01bc1b181d0dfa80bfddba8c9cf2bacd6747bcec9308", + "dependencies": [ + "jsr:@std/internal", + "jsr:@std/path@^1.1.4" + ] + }, "@std/internal@1.0.12": { "integrity": "972a634fd5bc34b242024402972cd5143eac68d8dffaca5eaa4dba30ce17b027" }, + "@std/io@0.225.2": { + "integrity": "3c740cd4ee4c082e6cfc86458f47e2ab7cb353dc6234d5e9b1f91a2de5f4d6c7", + "dependencies": [ + "jsr:@std/bytes" + ] + }, + "@std/path@1.1.4": { + "integrity": "1d2d43f39efb1b42f0b1882a25486647cb851481862dc7313390b2bb044314b5", + "dependencies": [ + "jsr:@std/internal" + ] + }, "@supabase/functions-js@2.90.1": { "integrity": "9a077ecf42aa84594ef3aac621d643e15ffaa9506a4c051e35c93f127ffdb035" }, @@ -260,10 +321,6 @@ "signal-exit@4.1.0": { "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==" }, - "supabase-js@1.0.4": { - "integrity": "sha512-b9Pxxl3ACEUhY/qTyF7WG9yUQTQQ/5Qm/0KnqbBOQdMaKi13glxs0SzqB1LSJnk2mZvufm6FF8Pyv5ZMc5YVqw==", - "scripts": true - }, "supabase@2.72.6": { "integrity": "sha512-axO1MkZgwMIxj78vKiGosnuXwzOaQq0Kz0I8jheNMwJH9dKGf3q/arivYT44z8u3t5FjGUxQWOuQoJetzcmm/A==", "dependencies": [ @@ -283,7 +340,8 @@ "minipass", "minizlib", "yallist" - ] + ], + "deprecated": true }, "tslib@2.8.1": { "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" @@ -315,6 +373,7 @@ "dependencies": [ "jsr:@openai/openai@^6.16.0", "jsr:@zod/zod@^4.3.5" + "jsr:@david/dax@~0.44.2" ], "packageJson": { "dependencies": [ @@ -322,7 +381,6 @@ "npm:@types/bun@^1.3.5", "npm:deno@^2.6.4", "npm:openai@^6.16.0", - "npm:supabase-js@^1.0.4", "npm:supabase@^2.72.6" ] } diff --git a/local_como_remoto.sql b/local_como_remoto.sql new file mode 100644 index 0000000..b4fd5dd Binary files /dev/null and b/local_como_remoto.sql differ diff --git a/package.json b/package.json index 0f774ef..a0f5f90 100644 --- a/package.json +++ b/package.json @@ -3,8 +3,7 @@ "@supabase/supabase-js": "^2.90.1", "deno": "^2.6.4", "openai": "^6.16.0", - "supabase": "^2.72.6", - "supabase-js": "^1.0.4" + "supabase": "^2.72.6" }, "name": "genesis-2", "module": "index.ts", diff --git a/remoto_como_local.sql b/remoto_como_local.sql new file mode 100644 index 0000000..beff8b3 --- /dev/null +++ b/remoto_como_local.sql @@ -0,0 +1,126 @@ + + create table "public"."tabla_prueba" ( + "id" bigint generated by default as identity not null, + "created_at" timestamp with time zone not null default now(), + "columna_prueba" text + ); + + +alter table "public"."tabla_prueba" enable row level security; + +CREATE UNIQUE INDEX tabla_prueba_pkey ON public.tabla_prueba USING btree (id); + +alter table "public"."tabla_prueba" add constraint "tabla_prueba_pkey" PRIMARY KEY using index "tabla_prueba_pkey"; + +set check_function_bodies = off; + +CREATE OR REPLACE FUNCTION public.set_actualizado_en() + RETURNS trigger + LANGUAGE plpgsql +AS $function$ +begin + new.actualizado_en = now(); + return new; +end; +$function$ +; + +CREATE OR REPLACE FUNCTION public.unaccent_immutable(text) + RETURNS text + LANGUAGE sql + IMMUTABLE PARALLEL SAFE STRICT +AS $function$ + SELECT public.unaccent('public.unaccent', $1); +$function$ +; + +CREATE OR REPLACE FUNCTION public.validar_numero_ciclo_asignatura() + RETURNS trigger + LANGUAGE plpgsql +AS $function$ +declare + v_numero_ciclos int; +begin + if new.numero_ciclo is null then + return new; + end if; + + select pe.numero_ciclos into v_numero_ciclos + from planes_estudio pe + where pe.id = new.plan_estudio_id; + + if v_numero_ciclos is null then + raise exception 'plan_estudio_id inválido %, plan no encontrado', new.plan_estudio_id; + end if; + + if new.numero_ciclo < 1 then + raise exception 'numero_ciclo debe ser >= 1 (recibido %)', new.numero_ciclo; + end if; + + if new.numero_ciclo > v_numero_ciclos then + raise exception 'numero_ciclo % excede planes_estudio.numero_ciclos % para plan_estudio_id %', + new.numero_ciclo, v_numero_ciclos, new.plan_estudio_id; + end if; + + return new; +end; +$function$ +; + +grant delete on table "public"."tabla_prueba" to "anon"; + +grant insert on table "public"."tabla_prueba" to "anon"; + +grant references on table "public"."tabla_prueba" to "anon"; + +grant select on table "public"."tabla_prueba" to "anon"; + +grant trigger on table "public"."tabla_prueba" to "anon"; + +grant truncate on table "public"."tabla_prueba" to "anon"; + +grant update on table "public"."tabla_prueba" to "anon"; + +grant delete on table "public"."tabla_prueba" to "authenticated"; + +grant insert on table "public"."tabla_prueba" to "authenticated"; + +grant references on table "public"."tabla_prueba" to "authenticated"; + +grant select on table "public"."tabla_prueba" to "authenticated"; + +grant trigger on table "public"."tabla_prueba" to "authenticated"; + +grant truncate on table "public"."tabla_prueba" to "authenticated"; + +grant update on table "public"."tabla_prueba" to "authenticated"; + +grant delete on table "public"."tabla_prueba" to "postgres"; + +grant insert on table "public"."tabla_prueba" to "postgres"; + +grant references on table "public"."tabla_prueba" to "postgres"; + +grant select on table "public"."tabla_prueba" to "postgres"; + +grant trigger on table "public"."tabla_prueba" to "postgres"; + +grant truncate on table "public"."tabla_prueba" to "postgres"; + +grant update on table "public"."tabla_prueba" to "postgres"; + +grant delete on table "public"."tabla_prueba" to "service_role"; + +grant insert on table "public"."tabla_prueba" to "service_role"; + +grant references on table "public"."tabla_prueba" to "service_role"; + +grant select on table "public"."tabla_prueba" to "service_role"; + +grant trigger on table "public"."tabla_prueba" to "service_role"; + +grant truncate on table "public"."tabla_prueba" to "service_role"; + +grant update on table "public"."tabla_prueba" to "service_role"; + + diff --git a/scripts/sql_para_local_como_remoto.ts b/scripts/sql_para_local_como_remoto.ts new file mode 100644 index 0000000..f01cdaf --- /dev/null +++ b/scripts/sql_para_local_como_remoto.ts @@ -0,0 +1,30 @@ +// scripts/db-diff.ts +/* Uso: + deno run -A scripts/sql_para_local_como_remoto.ts +*/ +import { $ } from "@david/dax"; + +const OUT_FILE = "supabase/diffs/sql_para_remoto_como_local.sql"; + +console.log("🔍 Comparando base de datos local vs. remota..."); + +try { + // 1. Ejecutamos el comando y capturamos la salida como texto + // Esto evita que la terminal de Windows/Mac corrompa los acentos + const output = await $`dx supabase db diff --linked`.text(); + + if (!output.trim()) { + console.log( + "✅ No hay diferencias. Tu base de datos local está sincronizada.", + ); + } else { + // 2. Escribimos el archivo usando Deno (UTF-8 garantizado) + await Deno.writeTextFile(OUT_FILE, output); + + console.log(`⚠️ Diferencias encontradas.`); + console.log(`📄 Guardadas en: ${OUT_FILE}`); + console.log(`👉 Abre ese archivo en VS Code para revisar los cambios.`); + } +} catch (error) { + console.error("❌ Error al ejecutar db diff:", error); +} diff --git a/scripts/sql_para_remoto_como_local.ts b/scripts/sql_para_remoto_como_local.ts new file mode 100644 index 0000000..4e5ed24 --- /dev/null +++ b/scripts/sql_para_remoto_como_local.ts @@ -0,0 +1,30 @@ +// scripts/db-diff.ts +/* Uso: + deno run -A scripts/sql_para_remoto_como_local.ts +*/ +import { $ } from "@david/dax"; + +const OUT_FILE = "supabase/diffs/sql_para_remoto_como_local.sql"; + +console.log("🔍 Comparando base de datos local vs. remota..."); + +try { + // 1. Ejecutamos el comando y capturamos la salida como texto + // Esto evita que la terminal de Windows/Mac corrompa los acentos + const output = await $`dx supabase db diff`.text(); + + if (!output.trim()) { + console.log( + "✅ No hay diferencias. Tu base de datos local está sincronizada.", + ); + } else { + // 2. Escribimos el archivo usando Deno (UTF-8 garantizado) + await Deno.writeTextFile(OUT_FILE, output); + + console.log(`⚠️ Diferencias encontradas.`); + console.log(`📄 Guardadas en: ${OUT_FILE}`); + console.log(`👉 Abre ese archivo en VS Code para revisar los cambios.`); + } +} catch (error) { + console.error("❌ Error al ejecutar db diff:", error); +} diff --git a/supabase/diffs/sql_para_remoto_como_local.sql b/supabase/diffs/sql_para_remoto_como_local.sql new file mode 100644 index 0000000..f17c71e --- /dev/null +++ b/supabase/diffs/sql_para_remoto_como_local.sql @@ -0,0 +1,56 @@ +set check_function_bodies = off; + +CREATE OR REPLACE FUNCTION public.set_actualizado_en() + RETURNS trigger + LANGUAGE plpgsql +AS $function$ +begin + new.actualizado_en = now(); + return new; +end; +$function$ +; + +CREATE OR REPLACE FUNCTION public.unaccent_immutable(text) + RETURNS text + LANGUAGE sql + IMMUTABLE PARALLEL SAFE STRICT +AS $function$ + SELECT public.unaccent('public.unaccent', $1); +$function$ +; + +CREATE OR REPLACE FUNCTION public.validar_numero_ciclo_asignatura() + RETURNS trigger + LANGUAGE plpgsql +AS $function$ +declare + v_numero_ciclos int; +begin + if new.numero_ciclo is null then + return new; + end if; + + select pe.numero_ciclos into v_numero_ciclos + from planes_estudio pe + where pe.id = new.plan_estudio_id; + + if v_numero_ciclos is null then + raise exception 'plan_estudio_id inválido %, plan no encontrado', new.plan_estudio_id; + end if; + + if new.numero_ciclo < 1 then + raise exception 'numero_ciclo debe ser >= 1 (recibido %)', new.numero_ciclo; + end if; + + if new.numero_ciclo > v_numero_ciclos then + raise exception 'numero_ciclo % excede planes_estudio.numero_ciclos % para plan_estudio_id %', + new.numero_ciclo, v_numero_ciclos, new.plan_estudio_id; + end if; + + return new; +end; +$function$ +; + + diff --git a/supabase/functions/ai-generate-plan/index.ts b/supabase/functions/ai-generate-plan/index.ts index fd58eda..e3ff851 100644 --- a/supabase/functions/ai-generate-plan/index.ts +++ b/supabase/functions/ai-generate-plan/index.ts @@ -19,12 +19,16 @@ type TipoCicloType = Database["public"]["Tables"]["planes_estudio"]["Insert"]["tipo_ciclo"]; Deno.serve(async (req) => { + const url = new URL(req.url); + const functionName = url.pathname.split("/").pop(); + console.log( + `[${new Date().toISOString()}][${functionName}]: Request received`, + ); + if (req.method === "OPTIONS") { return new Response(null, { status: 204, headers: corsHeaders }); } - const url = new URL(req.url); - const functionName = url.pathname.split("/").pop(); try { const method = req.method; if (method !== "POST") { @@ -143,7 +147,7 @@ Deno.serve(async (req) => { : {}; const aiStructuredPayload: StructuredResponseOptions = { - model: "gpt-5-mini", + model: "gpt-5-nano", input: [ { role: "system", content: systemPrompt }, { role: "user", content: userPrompt }, @@ -241,6 +245,8 @@ Deno.serve(async (req) => { ) .single(); + // TODO: interaccion con IA y cambio de estado + if (planError) { throw new Error("Error inserting plan: " + planError.message); } @@ -251,6 +257,11 @@ Deno.serve(async (req) => { plan, }; + console.log( + `[${ + new Date().toISOString() + }][${functionName}]: Request processed successfully`, + ); return new Response( JSON.stringify(jsonResponse), { diff --git a/supabase/migrations/20260122192101_cambios_a_remoto.sql b/supabase/migrations/20260122192101_cambios_a_remoto.sql new file mode 100644 index 0000000..dd89fe9 --- /dev/null +++ b/supabase/migrations/20260122192101_cambios_a_remoto.sql @@ -0,0 +1,71 @@ +alter type "public"."tipo_cambio" rename to "tipo_cambio__old_version_to_be_dropped"; + +create type "public"."tipo_cambio" as enum ('ACTUALIZACION_CAMPO', 'ACTUALIZACION_MAPA', 'TRANSICION_ESTADO', 'OTRO', 'CREACION'); + +alter table "public"."cambios_asignatura" alter column tipo type "public"."tipo_cambio" using tipo::text::"public"."tipo_cambio"; + +alter table "public"."cambios_plan" alter column tipo type "public"."tipo_cambio" using tipo::text::"public"."tipo_cambio"; + +drop type "public"."tipo_cambio__old_version_to_be_dropped"; + +set check_function_bodies = off; + +create or replace view "public"."plantilla_plan" as SELECT plan.id AS plan_estudio_id, + struct.id AS estructura_id, + struct.template_id + FROM (public.planes_estudio plan + JOIN public.estructuras_plan struct ON ((plan.estructura_id = struct.id))); + +CREATE OR REPLACE FUNCTION public.set_actualizado_en() + RETURNS trigger + LANGUAGE plpgsql +AS $function$ +begin + new.actualizado_en = now(); + return new; +end; +$function$ +; + +CREATE OR REPLACE FUNCTION public.unaccent_immutable(text) + RETURNS text + LANGUAGE sql + IMMUTABLE PARALLEL SAFE STRICT +AS $function$ + SELECT public.unaccent('public.unaccent', $1); +$function$ +; + +CREATE OR REPLACE FUNCTION public.validar_numero_ciclo_asignatura() + RETURNS trigger + LANGUAGE plpgsql +AS $function$ +declare + v_numero_ciclos int; +begin + if new.numero_ciclo is null then + return new; + end if; + + select pe.numero_ciclos into v_numero_ciclos + from planes_estudio pe + where pe.id = new.plan_estudio_id; + + if v_numero_ciclos is null then + raise exception 'plan_estudio_id inválido %, plan no encontrado', new.plan_estudio_id; + end if; + + if new.numero_ciclo < 1 then + raise exception 'numero_ciclo debe ser >= 1 (recibido %)', new.numero_ciclo; + end if; + + if new.numero_ciclo > v_numero_ciclos then + raise exception 'numero_ciclo % excede planes_estudio.numero_ciclos % para plan_estudio_id %', + new.numero_ciclo, v_numero_ciclos, new.plan_estudio_id; + end if; + + return new; +end; +$function$ +; +